ppd_sup_generator#
- class brainpy.state.ppd_sup_generator(in_size=1, rate=Quantity(0., 'Hz'), dead_time=Quantity(0., 'ms'), n_proc=1, frequency=Quantity(0., 'Hz'), relative_amplitude=0.0, start=Quantity(0., 'ms'), stop=None, origin=Quantity(0., 'ms'), rng_seed=0, name=None)#
Superposition of Poisson processes with dead time (NEST-compatible).
Description
ppd_sup_generatorre-implements NEST’s stimulation device with the same name. For each output train, it emits the per-step multiplicity of a superposition ofn_procindependent Poisson-like component processes with absolute dead time.1. State model, derivation, and update equations
Let \(r=\mathrm{rate}\) (Hz), \(\tau_d=\mathrm{dead\_time}\) (ms), and \(\Delta t\) be the simulation resolution in ms. For each output train, the internal state is an age-discretized occupancy model:
occ_active: number of currently active component processes.occ_refractory[a]for \(a=0,\dots,\lfloor\tau_d/\Delta t\rfloor-1\): number of processes in refractory age bina.activate: rotating pointer indicating the bin whose occupants become active at the current step.
NEST’s per-step hazard for one active process is
\[h_{\mathrm{step}} = \frac{\Delta t}{1000/r-\tau_d}.\]This discretization is valid under NEST’s model constraint \(1000/r>\tau_d\) (or
rate == 0). If sinusoidal modulation is enabled, the instantaneous hazard becomes\[h_t = h_{\mathrm{step}} \left(1 + A \sin\left(2\pi f t / 1000\right)\right),\]where \(A=\mathrm{relative\_amplitude}\in[0,1]\) and \(f=\mathrm{frequency}\) in Hz.
For each train and step, emitted multiplicity
n_spikesis sampled from the active pool using NEST’s branch logic:Binomial branch:
Binomial(occ_active, h_t).Poisson approximation branch when
(occ_active >= 100 and h_t <= 0.01)or(occ_active >= 500 and h_t * occ_active <= 0.1): samplePoisson(h_t * occ_active)and clip toocc_active.
State transition for nonzero refractory bins is
\[occ\_active' = occ\_active + occ\_refractory[p] - n\_spikes, \quad occ\_refractory[p]' = n\_spikes,\]with pointer update \(p'=(p+1)\bmod B\), \(B=\lfloor\tau_d/\Delta t\rfloor\). If
B == 0(zero dead time), the active pool is not decremented by refractory bookkeeping and each component can contribute at most one spike per step through the binomial/Poisson draw.2. Timing semantics and activity window
Activity follows NEST
StimulationDevicesemantics for generators:\[t_{\min} < t \le t_{\max}, \qquad t_{\min} = origin + start,\quad t_{\max} = origin + stop.\]Therefore
startis exclusive andstopis inclusive. Internally, finite times are projected to steps withround(time_ms / dt_ms)and checked ast_min_step < curr_step <= t_max_step.3. Assumptions, constraints, and computational implications
All physical parameters are scalarized to host-side
float64orintbefore simulation. Enforced constraints are:dead_time >= 0.n_proc >= 1.relative_amplitude in [0, 1].stop >= start.1000 / rate > dead_time(orrate == 0).
If
dtis available, finiteorigin,start, andstopmust be exact grid multiples (absolute tolerance1e-12intime/dtratio). Runtime ofupdate()is \(O(\prod \mathrm{varshape})\) per step; memory is \(O(\prod \mathrm{varshape} \cdot \lfloor\tau_d/\Delta t\rfloor)\). Random draws are produced bynumpy.random.Generator(seeded byrng_seed), so stochasticity is NumPy-host based rather than JAX-key based.- Parameters:
in_size (
Size, optional) – Output size specification consumed bybrainstate.nn.Dynamics.self.varshapederived from this value is the exact shape returned byupdate(), and each element corresponds to one independent output train. Default is1.rate (
ArrayLike, optional) – Scalar component-process rate in spikes/s (Hz), shape()after conversion. Accepts a single-element numericArrayLikeor asaiunit.Quantityconvertible tou.Hz. Must satisfy1000 / rate > dead_timewhenrate > 0. Default is0.0 * u.Hz.dead_time (
ArrayLike, optional) – Scalar absolute refractory time in ms, shape()after conversion. Accepts a single-element numericArrayLikeor asaiunit.Quantityconvertible tou.ms. Must satisfydead_time >= 0. Default is0.0 * u.ms.n_proc (
ArrayLike, optional) – Scalar integer number of independent component processes per output train, shape()after conversion. Parsed by nearest-integer check with absolute tolerance1e-12. Must satisfyn_proc >= 1. Default is1.frequency (
ArrayLike, optional) – Scalar sinusoidal modulation frequency in Hz, shape()after conversion.frequency == 0disables sinusoidal variation even whenrelative_amplitude > 0. Default is0.0 * u.Hz.relative_amplitude (
ArrayLike, optional) – Scalar dimensionless modulation amplitude \(A\), shape()after conversion. Must satisfy0 <= relative_amplitude <= 1. Default is0.0.start (
ArrayLike, optional) – Scalar relative activation time in ms, shape()after conversion. Effective lower activity bound isorigin + startand is exclusive. Must be grid-representable whendtis available. Default is0.0 * u.ms.stop (
ArrayLikeorNone, optional) – Scalar relative deactivation time in ms, shape()after conversion. Effective upper activity bound isorigin + stopand is inclusive.Nonemaps to+inf. Must satisfystop >= startand be grid-representable when finite anddtis available. Default isNone.origin (
ArrayLike, optional) – Scalar time-origin offset in ms, shape()after conversion. Added tostartandstopto compute absolute active bounds. Must be grid-representable when finite anddtis available. Default is0.0 * u.ms.rng_seed (
int, optional) – Seed used to initializenumpy.random.default_rngininit_state(). Default is0.name (
strorNone, optional) – Optional node name passed tobrainstate.nn.Dynamics.
Parameter Mapping
Table 38 Parameter mapping to model symbols# Parameter
Default
Math symbol
Semantics
rate0.0 * u.Hz\(r\)
Component-process rate in spikes/s.
dead_time0.0 * u.ms\(\tau_d\)
Absolute refractory duration in ms.
n_proc1\(n_{\mathrm{proc}}\)
Number of component processes per output train.
frequency0.0 * u.Hz\(f\)
Modulation frequency in Hz.
relative_amplitude0.0\(A\)
Relative sinusoidal modulation amplitude.
start0.0 * u.ms\(t_{\mathrm{start,rel}}\)
Relative exclusive lower activity bound.
stopNone\(t_{\mathrm{stop,rel}}\)
Relative inclusive upper bound;
Nonemaps to+\infty.origin0.0 * u.ms\(t_0\)
Global offset added to
startandstop.in_size1Defines
self.varshapefor independent output trains.rng_seed0Seed for NumPy RNG used by stochastic transition draws.
- Raises:
ValueError – If scalar conversion fails due to non-scalar inputs; if
dead_timeis negative; ifn_proc < 1; ifrelative_amplitudeis outside[0, 1]; ifstop < start; if1000 / rate <= dead_timefor nonzerorate; if integer-valued inputs are non-integral beyond tolerance; or if finiteorigin/start/stopare not multiples of simulation resolution whendtis available.TypeError – If conversion to
u.Hz/u.msor numeric casting fails for provided parameter types.KeyError – At runtime, if required simulation-context fields (for example
dtused bybrainstate.environ.get_dt()) are unavailable.
Notes
Initial occupancy matches NEST
pre_run_hook():floor(rate / 1000 * n_proc * dt)in each refractory bin and the remainder inocc_active.NEST does not initialize to sinusoidal equilibrium, so modulation can show startup transients.
Stimulation-backend parameter order in NEST is
[dead_time, rate, n_proc, frequency, relative_amplitude].
Examples
>>> import brainpy >>> import brainstate >>> import saiunit as u >>> with brainstate.environ.context(dt=0.1 * u.ms): ... gen = brainpy.state.ppd_sup_generator( ... in_size=(2, 2), ... rate=20.0 * u.Hz, ... dead_time=2.0 * u.ms, ... n_proc=80, ... frequency=8.0 * u.Hz, ... relative_amplitude=0.25, ... start=5.0 * u.ms, ... stop=50.0 * u.ms, ... rng_seed=3, ... ) ... with brainstate.environ.context(t=12.0 * u.ms): ... counts = gen.update() ... _ = counts.shape
>>> import brainpy >>> import saiunit as u >>> gen = brainpy.state.ppd_sup_generator(rate=15.0 * u.Hz, n_proc=30) >>> gen.set(dead_time=1.5 * u.ms, stop=None, origin=2.0 * u.ms) >>> params = gen.get() >>> _ = params['dead_time'], params['stop']
See also
gamma_sup_generatorSuperposition of gamma-process component trains.
sinusoidal_gamma_generatorInhomogeneous gamma generator with sinusoidal rate modulation.
poisson_generatorIndependent Poisson trains without dead time.
References
- get()[source]#
Return current public parameters as plain Python scalars.
- Returns:
out –
dictwith the following keys and value types:'rate'—float, component-process rate in Hz.'dead_time'—float, absolute refractory duration in ms.'n_proc'—int, number of component processes.'frequency'—float, sinusoidal modulation frequency in Hz.'relative_amplitude'—float, modulation depth in[0, 1].'start'—float, relative exclusive lower activity bound in ms.'stop'—float, relative inclusive upper activity bound in ms;math.infwhen the generator was constructed or set withstop=None.'origin'—float, time-origin offset in ms.
- Return type:
- init_state(batch_size=None, **kwargs)[source]#
Initialize occupancy arrays and NumPy RNG for all output trains.
Allocates three
brainstate.ShortTermStatearrays representing the age-discretized occupation model and seeds the NumPy random generator. The initial occupancy follows NEST’spre_run_hook()logic:floor(rate / 1000 * n_proc * dt)processes are placed in each refractory age bin, and the remainder fillsocc_active.- Parameters:
Notes
If
dtis not available in the simulation environment at call time,dt_msdefaults to0.0so thatini_occ_ref == 0and alln_procprocesses start inocc_active. The refractory array is still allocated with the correct number of age bins computed from any previously cached_num_age_binsvalue, which may also be zero if nodtcontext was ever set.
- set(*, rate=<object object>, dead_time=<object object>, n_proc=<object object>, frequency=<object object>, relative_amplitude=<object object>, start=<object object>, stop=<object object>, origin=<object object>)[source]#
Update public generator parameters with NEST-compatible semantics.
Any parameter left at the internal sentinel
_UNSETretains its current value. All supplied values are converted and cross-validated before any attribute is mutated, so the generator state remains self-consistent on failure. Ifdtis currently available inbrainstate.environ, the cached hazard step, angular frequency, number of age bins, and timing step bounds are recomputed immediately after mutation.- Parameters:
rate (
ArrayLikeorobject, optional) – New scalar component-process rate in Hz. If omitted, keep the current value. Must satisfy1000 / rate > dead_timeforrate > 0after scalar conversion.dead_time (
ArrayLikeorobject, optional) – New scalar absolute refractory duration in ms. If omitted, keep current value. Must be>= 0and satisfy1000 / rate > dead_timefor nonzerorate.n_proc (
ArrayLikeorobject, optional) – New scalar integer number of component processes>= 1. If omitted, keep current value.frequency (
ArrayLikeorobject, optional) – New scalar sinusoidal modulation frequency in Hz.0disables modulation even whenrelative_amplitude > 0. If omitted, keep current value.relative_amplitude (
ArrayLikeorobject, optional) – New scalar dimensionless modulation amplitude in[0, 1]. If omitted, keep current value.start (
ArrayLikeorobject, optional) – New scalar relative start time in ms (exclusive lower bound). If omitted, keep current value.stop (
ArrayLike,None, orobject, optional) – New scalar relative stop time in ms (inclusive upper bound).Nonemaps to+inf(unbounded). If omitted, keep current value.origin (
ArrayLikeorobject, optional) – New scalar time-origin offset in ms. If omitted, keep current value.
- Raises:
ValueError – If any provided value is non-scalar; if
dead_time < 0; ifn_proc < 1; ifrelative_amplitudeis outside[0, 1]; ifstop < start; if1000 / rate <= dead_timefor nonzerorate; if integer inputs are non-integral beyond tolerance; or if finite timing values are off the simulation grid whendtis available.TypeError – If unit conversion to
u.Hzoru.msfails for supplied inputs.
- update()[source]#
Advance one simulation step and return per-train spike multiplicity.
Lazily initializes state on the first call, refreshes the runtime cache when
dtchanges, applies the active-window test, computes an instantaneous hazard (with optional sinusoidal modulation), and updates each output train’s age-discretized occupation model using NEST’s branch logic (binomial or Poisson approximation).The method mirrors NEST’s
ppd_sup_generator::updateprocedure:Ensure internal state is initialized; refresh cache if
dtchanged since the last call.Return zeros immediately when
rate <= 0or no output trains exist.Evaluate the active-window guard: \(t_{\min} < t \le t_{\max}\).
Compute the per-step hazard:
\[h_t = h_{\mathrm{step}} \left(1 + A \sin(2\pi f\, t / 1000)\right),\]skipping the sinusoidal term when
relative_amplitude == 0orfrequency == 0.For each output train, call
_update_age_distribution_single()which drawsn_spikesfrom the active pool and rotates the refractory ring buffer.
- Returns:
out – JAX array of dtype
int64and shapeself.varshape. Each element is the number of spikes emitted by the corresponding output train during the current step. Returns all-zeros when inactive, whenrate <= 0, or when no targets are defined.- Return type:
jax.Array- Raises:
KeyError – If required simulation-context fields (for example
dtviabrainstate.environ.get_dt()) are unavailable.ValueError – If finite timing parameters are not on the simulation grid after a
dtchange triggers cache refresh.TypeError – If simulation-time values in the environment cannot be converted to scalar milliseconds.