spike_generator

spike_generator#

class brainpy.state.spike_generator(in_size=1, spike_times=(), spike_weights=(), start=Quantity(0., 'ms'), stop=None, origin=Quantity(0., 'ms'), name=None)#

Spike generator – NEST-compatible stimulation device.

Emit deterministic spike-like outputs at prescribed times with optional per-event amplitudes, while respecting a half-open activity window.

1. Model equations

Let \(\{t_i\}_{i=1}^{K}\) be configured spike times in ms (non-descending after conversion), and \(\{w_i\}_{i=1}^{K}\) optional spike weights. At simulation time \(t\) with step \(\Delta t\) (both in ms), define the matching indicator

\[m_i(t) = \mathbf{1}\!\left[|t - t_i| < \frac{\Delta t}{2}\right].\]

The active-window gate is

\[g(t) = \mathbf{1}\!\left[t \ge t_0 + t_{\mathrm{start,rel}}\right] \cdot \mathbf{1}\!\left[t < t_0 + t_{\mathrm{stop,rel}}\right],\]

where the second indicator is omitted when stop is None.

This implementation computes a scalar amplitude \(a(t)\) as follows:

  • no spike_weights: \(a(t)=1\) if any \(m_i(t)=1\), else \(a(t)=0\);

  • with spike_weights: \(a(t)\) equals the weight associated with the last matching index (iteration order through spike_times).

The returned output is broadcast to self.varshape:

\[y(t) = g(t)\,a(t)\,\mathbf{1}_{\mathrm{varshape}}.\]

2. Timing semantics, assumptions, and constraints

A configured spike at \(t_s\) is intended for the step satisfying \(t_s-\Delta t < t \le t_s\) under grid-aligned simulation. The implementation uses \(|t-t_s| < \Delta t/2\) for robust floating-point matching, which is equivalent to \(t - \Delta t/2 < t_s < t + \Delta t/2\).

Enforced constraints:

  • spike_times must be sorted in non-descending order after conversion.

  • spike_weights must be empty or have exactly len(spike_times) elements.

Accepted but not additionally constrained:

  • Unitless spike_times are interpreted as ms.

  • Duplicate spike times are allowed. Without weights, duplicates remain binary output. With weights, the last duplicate’s weight is used.

3. Computational implications

Each update() call uses u.math.searchsorted() to locate the spike-time range matching the current step, then selects the last matching weight with u.math.clip() and u.math.where(). Per-call complexity is \(O(\log K + \prod\mathrm{varshape})\), where \(K\) is the number of configured spike times.

Parameters:
  • in_size (Size, optional) – Output size/shape specification consumed by brainstate.nn.Dynamics. The emitted array has shape self.varshape derived from in_size. Default is 1.

  • spike_times (Sequence, optional) – Sequence of spike times with length K. Entries may be unitful times (typically ms) or unitless numerics interpreted as ms. Passed directly to u.math.asarray(), which validates unit consistency across all entries. Must be non-descending. Default is ().

  • spike_weights (Sequence, optional) – Optional sequence of per-spike amplitudes with length K matching spike_times exactly, or empty to use binary spikes. Entries are passed to u.math.asarray() (dimensionless). Default is ().

  • start (ArrayLike, optional) – Relative activation time \(t_{\mathrm{start,rel}}\) (typically ms), initialized through braintools.init.param(). Effective lower bound is origin + start (inclusive). Default is 0. * u.ms.

  • stop (ArrayLike or None, optional) – Relative deactivation time \(t_{\mathrm{stop,rel}}\) (typically ms), initialized through braintools.init.param() when provided. Effective upper bound is origin + stop (exclusive). None means no upper bound. Default is None.

  • origin (ArrayLike, optional) – Global time origin \(t_0\) (typically ms) added to start and stop, broadcast to self.varshape. Default is 0. * u.ms.

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

Parameter Mapping

Table 28 Parameter mapping to model symbols#

Parameter

Default

Math symbol

Semantics

spike_times

()

\(t_i\)

Scheduled spike times in ms, checked by |t - t_i| < dt/2.

spike_weights

()

\(w_i\)

Per-spike amplitude; when multiple indices match, the last wins.

start

0. * u.ms

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

Relative inclusive lower bound of active window.

stop

None

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

Relative exclusive upper bound of active window.

origin

0. * u.ms

\(t_0\)

Global offset applied to start and stop.

Raises:
  • ValueError – If spike_times is not non-descending, or if len(spike_weights) is non-zero and differs from len(spike_times).

  • TypeError – If u.math.asarray() detects unit inconsistency across entries, or if unitful/unitless arithmetic is invalid during time-window comparisons.

  • KeyError – At update time, if simulation context lacks 't' or dt in brainstate.environ.

Notes

Unlike current generators (dc_generator, step_current_generator), spike_generator emits dimensionless impulses (or weighted real values) rather than physical current quantities. The output is intended to be consumed directly as pre-synaptic spike events or injected into a synapse model that scales by connection weight.

NEST’s spike_generator uses multiplicity to allow multiple spikes per time step; this implementation preserves that semantics — the last matching weight wins when duplicates exist. The update() method is fully compatible with jax.jit: both the spike-time lookup and the activity-window check use purely functional operations with no Python control flow over traced values.

Spike times should ideally be aligned to the simulation grid (multiples of dt) to avoid off-by-one steps due to floating-point comparison. The half-open tolerance dt/2 generally covers one-ULP rounding errors for grid-aligned times.

See also

dc_generator

Constant-current stimulation device.

ac_generator

Sinusoidal current stimulation device.

step_current_generator

Piecewise-constant current stimulation device.

spike_train_injector

Inject pre-recorded spike trains into the network.

References

Examples

>>> import brainpy
>>> import brainstate
>>> import saiunit as u
>>> with brainstate.environ.context(dt=0.1 * u.ms):
...     sg = brainpy.state.spike_generator(
...         spike_times=[5.0 * u.ms, 10.0 * u.ms, 15.0 * u.ms],
...     )
...     with brainstate.environ.context(t=10.0 * u.ms):
...         spk = sg.update()
...     _ = spk.shape
>>> import brainpy
>>> import brainstate
>>> import saiunit as u
>>> with brainstate.environ.context(dt=0.1 * u.ms):
...     sg = brainpy.state.spike_generator(
...         spike_times=[5.0 * u.ms, 5.0 * u.ms, 10.0 * u.ms],
...         spike_weights=[0.25, 0.5, 2.0],
...     )
...     with brainstate.environ.context(t=5.0 * u.ms):
...         spk = sg.update()
...     _ = spk.shape
update()[source]#

Compute spike output for the current simulation step.

The implementation is fully compatible with jax.jit: spike-time matching uses u.math.searchsorted() on the static spike_times array while t and dt remain traced values throughout. The activity-window check uses u.math.logical_and() and u.math.where() with no Python branching over traced values.

Returns:

out – Float-valued JAX array with shape self.varshape. Output semantics:

  • 0 when outside [origin + start, origin + stop) (or [origin + start, +inf) if stop is None),

  • 0 when active but no configured spike matches |t - t_i| < dt/2,

  • 1 at a matching spike time without weights,

  • last matching weight when spike_weights is configured.

Return type:

jax.Array

Raises:

KeyError – If required simulation context values are missing from brainstate.environ (e.g. 't' or dt).

Notes

Both spike_times and t are divided by u.ms to obtain dimensionless arrays before calling u.math.searchsorted(). The matching condition |t - t_s| < dt/2 is rewritten as the open interval (t - dt/2, t + dt/2) and located with two searchsorted calls:

  • idx_lo = searchsorted(times, t - dt/2, side='right') — first index strictly greater than the lower bound.

  • idx_hi = searchsorted(times, t + dt/2, side='left') — first index at or above the upper bound.

Any spike exists when idx_hi > idx_lo; the last matching spike index is idx_hi - 1, clamped to a valid range for the gather. Start is inclusive and stop is exclusive, matching NEST semantics.

See also

spike_generator

Class-level parameter definitions and model equations.

dc_generator.update

Windowed constant-current update rule.

step_current_generator.update

Windowed piecewise-constant update rule.