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_generator re-implements NEST’s stimulation device with the same name. For each output train, it emits the per-step multiplicity of a superposition of n_proc independent 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 bin a.

  • 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_spikes is 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): sample Poisson(h_t * occ_active) and clip to occ_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 StimulationDevice semantics for generators:

\[t_{\min} < t \le t_{\max}, \qquad t_{\min} = origin + start,\quad t_{\max} = origin + stop.\]

Therefore start is exclusive and stop is inclusive. Internally, finite times are projected to steps with round(time_ms / dt_ms) and checked as t_min_step < curr_step <= t_max_step.

3. Assumptions, constraints, and computational implications

All physical parameters are scalarized to host-side float64 or int before simulation. Enforced constraints are:

  • dead_time >= 0.

  • n_proc >= 1.

  • relative_amplitude in [0, 1].

  • stop >= start.

  • 1000 / rate > dead_time (or rate == 0).

If dt is available, finite origin, start, and stop must be exact grid multiples (absolute tolerance 1e-12 in time/dt ratio). Runtime of update() is \(O(\prod \mathrm{varshape})\) per step; memory is \(O(\prod \mathrm{varshape} \cdot \lfloor\tau_d/\Delta t\rfloor)\). Random draws are produced by numpy.random.Generator (seeded by rng_seed), so stochasticity is NumPy-host based rather than JAX-key based.

Parameters:
  • in_size (Size, optional) – Output size specification consumed by brainstate.nn.Dynamics. self.varshape derived from this value is the exact shape returned by update(), and each element corresponds to one independent output train. Default is 1.

  • rate (ArrayLike, optional) – Scalar component-process rate in spikes/s (Hz), shape () after conversion. Accepts a single-element numeric ArrayLike or a saiunit.Quantity convertible to u.Hz. Must satisfy 1000 / rate > dead_time when rate > 0. Default is 0.0 * u.Hz.

  • dead_time (ArrayLike, optional) – Scalar absolute refractory time in ms, shape () after conversion. Accepts a single-element numeric ArrayLike or a saiunit.Quantity convertible to u.ms. Must satisfy dead_time >= 0. Default is 0.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 tolerance 1e-12. Must satisfy n_proc >= 1. Default is 1.

  • frequency (ArrayLike, optional) – Scalar sinusoidal modulation frequency in Hz, shape () after conversion. frequency == 0 disables sinusoidal variation even when relative_amplitude > 0. Default is 0.0 * u.Hz.

  • relative_amplitude (ArrayLike, optional) – Scalar dimensionless modulation amplitude \(A\), shape () after conversion. Must satisfy 0 <= relative_amplitude <= 1. Default is 0.0.

  • start (ArrayLike, optional) – Scalar relative activation time in ms, shape () after conversion. Effective lower activity bound is origin + start and is exclusive. Must be grid-representable when dt is available. Default is 0.0 * u.ms.

  • stop (ArrayLike or None, optional) – Scalar relative deactivation time in ms, shape () after conversion. Effective upper activity bound is origin + stop and is inclusive. None maps to +inf. Must satisfy stop >= start and be grid-representable when finite and dt is available. Default is None.

  • origin (ArrayLike, optional) – Scalar time-origin offset in ms, shape () after conversion. Added to start and stop to compute absolute active bounds. Must be grid-representable when finite and dt is available. Default is 0.0 * u.ms.

  • rng_seed (int, optional) – Seed used to initialize numpy.random.default_rng in init_state(). Default is 0.

  • name (str or None, optional) – Optional node name passed to brainstate.nn.Dynamics.

Parameter Mapping

Table 38 Parameter mapping to model symbols#

Parameter

Default

Math symbol

Semantics

rate

0.0 * u.Hz

\(r\)

Component-process rate in spikes/s.

dead_time

0.0 * u.ms

\(\tau_d\)

Absolute refractory duration in ms.

n_proc

1

\(n_{\mathrm{proc}}\)

Number of component processes per output train.

frequency

0.0 * u.Hz

\(f\)

Modulation frequency in Hz.

relative_amplitude

0.0

\(A\)

Relative sinusoidal modulation amplitude.

start

0.0 * u.ms

\(t_{\mathrm{start,rel}}\)

Relative exclusive lower activity bound.

stop

None

\(t_{\mathrm{stop,rel}}\)

Relative inclusive upper bound; None maps to +\infty.

origin

0.0 * u.ms

\(t_0\)

Global offset added to start and stop.

in_size

1

Defines self.varshape for independent output trains.

rng_seed

0

Seed for NumPy RNG used by stochastic transition draws.

Raises:
  • ValueError – If scalar conversion fails due to non-scalar inputs; if dead_time is negative; if n_proc < 1; if relative_amplitude is outside [0, 1]; if stop < start; if 1000 / rate <= dead_time for nonzero rate; if integer-valued inputs are non-integral beyond tolerance; or if finite origin/start/stop are not multiples of simulation resolution when dt is available.

  • TypeError – If conversion to u.Hz/u.ms or numeric casting fails for provided parameter types.

  • KeyError – At runtime, if required simulation-context fields (for example dt used by brainstate.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 in occ_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_generator

Superposition of gamma-process component trains.

sinusoidal_gamma_generator

Inhomogeneous gamma generator with sinusoidal rate modulation.

poisson_generator

Independent Poisson trains without dead time.

References

get()[source]#

Return current public parameters as plain Python scalars.

Returns:

outdict with 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.inf when the generator was constructed or set with stop=None.

  • 'origin'float, time-origin offset in ms.

Return type:

dict

init_state(batch_size=None, **kwargs)[source]#

Initialize occupancy arrays and NumPy RNG for all output trains.

Allocates three brainstate.ShortTermState arrays representing the age-discretized occupation model and seeds the NumPy random generator. The initial occupancy follows NEST’s pre_run_hook() logic: floor(rate / 1000 * n_proc * dt) processes are placed in each refractory age bin, and the remainder fills occ_active.

Parameters:
  • batch_size (int or None, optional) – Unused API placeholder for compatibility with the brainstate.nn.Dynamics interface. Ignored.

  • **kwargs – Additional unused keyword arguments accepted for API compatibility. Ignored.

Notes

If dt is not available in the simulation environment at call time, dt_ms defaults to 0.0 so that ini_occ_ref == 0 and all n_proc processes start in occ_active. The refractory array is still allocated with the correct number of age bins computed from any previously cached _num_age_bins value, which may also be zero if no dt context 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 _UNSET retains 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. If dt is currently available in brainstate.environ, the cached hazard step, angular frequency, number of age bins, and timing step bounds are recomputed immediately after mutation.

Parameters:
  • rate (ArrayLike or object, optional) – New scalar component-process rate in Hz. If omitted, keep the current value. Must satisfy 1000 / rate > dead_time for rate > 0 after scalar conversion.

  • dead_time (ArrayLike or object, optional) – New scalar absolute refractory duration in ms. If omitted, keep current value. Must be >= 0 and satisfy 1000 / rate > dead_time for nonzero rate.

  • n_proc (ArrayLike or object, optional) – New scalar integer number of component processes >= 1. If omitted, keep current value.

  • frequency (ArrayLike or object, optional) – New scalar sinusoidal modulation frequency in Hz. 0 disables modulation even when relative_amplitude > 0. If omitted, keep current value.

  • relative_amplitude (ArrayLike or object, optional) – New scalar dimensionless modulation amplitude in [0, 1]. If omitted, keep current value.

  • start (ArrayLike or object, optional) – New scalar relative start time in ms (exclusive lower bound). If omitted, keep current value.

  • stop (ArrayLike, None, or object, optional) – New scalar relative stop time in ms (inclusive upper bound). None maps to +inf (unbounded). If omitted, keep current value.

  • origin (ArrayLike or object, 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; if n_proc < 1; if relative_amplitude is outside [0, 1]; if stop < start; if 1000 / rate <= dead_time for nonzero rate; if integer inputs are non-integral beyond tolerance; or if finite timing values are off the simulation grid when dt is available.

  • TypeError – If unit conversion to u.Hz or u.ms fails 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 dt changes, 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::update procedure:

  1. Ensure internal state is initialized; refresh cache if dt changed since the last call.

  2. Return zeros immediately when rate <= 0 or no output trains exist.

  3. Evaluate the active-window guard: \(t_{\min} < t \le t_{\max}\).

  4. 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 == 0 or frequency == 0.

  5. For each output train, call _update_age_distribution_single() which draws n_spikes from the active pool and rotates the refractory ring buffer.

Returns:

out – JAX array of dtype int64 and shape self.varshape. Each element is the number of spikes emitted by the corresponding output train during the current step. Returns all-zeros when inactive, when rate <= 0, or when no targets are defined.

Return type:

jax.Array

Raises:
  • KeyError – If required simulation-context fields (for example dt via brainstate.environ.get_dt()) are unavailable.

  • ValueError – If finite timing parameters are not on the simulation grid after a dt change triggers cache refresh.

  • TypeError – If simulation-time values in the environment cannot be converted to scalar milliseconds.