// PoissonLayer — port of SNNModels.jl/src/stimuli/poisson_layer.jl
//
// A PoissonLayer is a population of `n_sources` independent Poisson
// sources (each firing at `rate` Hz) projecting sparsely onto a target
// IF population. Each source i ∈ [0, n_sources) connects to each
// post-neuron j with probability `p`, with weight drawn from Normal(μ, σ)
// if `dist == "Normal"` or Fixed(μ) if `dist == "Fixed"`.
//
// At each step:
// for i in [0, n_sources):
// if active[i]:
// draw k ~ Poisson(rate * dt)
// if k > 0:
// fire[i] = true
// for j in [0, post.N) where connectivity[j, i]:
// post.glu[j] += weights[j, i] (or .gaba for :gi)
//
// Mirrors Julia's `PoissonLayer` parameter + `Stimulus` wrapper.
//
// Bit-exact note: per-step Poisson sample sequences differ from Julia's
// Distributions.jl (we use Knuth + Xoshiro Float64 internal; Julia uses
// Float32-specific RNGs). Expected per-step conductance (μ * rate * dt)
// and qualitative dynamics match.
///|
/// PoissonLayer parameter — a population of `n_sources` independent
/// Poisson sources with shared rate, drawn from optional Normal/Fixed
/// connection weights. Mirrors Julia's `PoissonLayer` struct.
pub(all) struct PoissonLayer {
rate : Float
n_sources : Int
mut active : Array[Bool]
mu : Float
sigma : Float
p : Float
dist : String
rule : String
}
///|
/// Default PoissonLayer: rate in Hz, n_sources=1, fully active,
/// μ=1.0, σ=0.0, p=1.0, Fixed weight, Fixed (no-plasticity) rule.
pub fn PoissonLayer::new(rate : Float) -> PoissonLayer {
{ rate, n_sources: 1, active: [true], mu: 1.0F, sigma: 0.0F, p: 1.0F,
dist: "Fixed", rule: "Fixed" }
}
///|
/// PoissonLayer with `n_sources` sources, all initially active.
pub fn PoissonLayer::with_n(rate : Float, n_sources : Int) -> PoissonLayer {
let active : Array[Bool] = []
for _ in 0.. PoissonLayer {
{ rate, n_sources, active, mu: 1.0F, sigma: 0.0F, p: 1.0F,
dist: "Fixed", rule: "Fixed" }
}
///|
/// PoissonLayer with full connection parameters (Julia: conn = (p, μ, σ,
/// dist, rule)).
pub fn PoissonLayer::with_conn(
rate : Float,
n_sources : Int,
active : Array[Bool],
mu : Float,
sigma : Float,
p : Float,
dist : String,
rule : String,
) -> PoissonLayer {
{ rate, n_sources, active, mu, sigma, p, dist, rule }
}
///|
/// PoissonLayerStimulus — wraps a PoissonLayer with a target IF
/// population and the per-(pre, post) connection weights drawn at
/// construction time. Sym is "ge" or "gi" (which receptor to write).
pub struct PoissonLayerStimulus {
param : PoissonLayer
post : IF
sym : String
fire : Array[Bool]
weights : Array[Float]
connectivity : Array[Bool]
rng : Xoshiro
}
///|
/// Construct a PoissonLayerStimulus. Draws each connection weight
/// independently: with probability `p` the connection exists, and
/// if it does, weight is drawn Normal(μ, σ) or Fixed(μ) per `dist`.
/// `weights` and `connectivity` are stored row-major with shape
/// `[post.N, param.n_sources]` (weights[j, i] = weight from source i
/// to neuron j).
pub fn PoissonLayerStimulus::new(
param : PoissonLayer,
post : IF,
sym : String,
rng : Xoshiro,
) -> PoissonLayerStimulus {
let n_pre = param.n_sources
let n_post = post.n
let fire : Array[Bool] = Array::make(n_pre, false)
let weights : Array[Float] = Array::make(n_post * n_pre, 0.0F)
let connectivity : Array[Bool] = Array::make(n_post * n_pre, false)
let p_conn = param.p
let mu = param.mu
let sigma = param.sigma
let use_normal = param.dist == "Normal"
for i in 0.. Unit {
let _ = time // unused for fixed-rate; suppress warning
let n_pre = s.param.n_sources
let n_post = s.post.n
let lambda = s.param.rate * dt
// Reset fire buffer (single-step firing record).
for i in 0.. 0 {
s.fire[i] = true
for j in 0..