spike_recorder#

class brainpy.state.spike_recorder(in_size=1, start=Quantity(0., 'ms'), stop=None, origin=Quantity(0., 'ms'), time_in_steps=False, frozen=False, name=None)#

NEST-compatible spike recording device.

spike_recorder accumulates spike events into an in-memory events dictionary, with timestamping and activity-window semantics matching NEST spike_recorder. The NEST recording-device timing model is reproduced while exposing a Python batch API:

  • Incoming spike arrays are timestamped at step \(n + 1\) where \(n = \mathrm{round}(t / dt)\) is the current simulation step.

  • Recording is gated by a window \((\mathrm{origin} + \mathrm{start},\;\mathrm{origin} + \mathrm{stop}]\) (start exclusive, stop inclusive) evaluated in simulation steps.

  • Event writes are immediate — there is no one-step delivery lag, unlike the request/reply mechanism of multimeter.

1. Step-Stamp and Physical-Time Model

Let \(dt > 0\) be the simulation resolution (ms), and let \(n = \mathrm{round}(t / dt)\) be the current step index when update() is called at simulation time \(t\). Incoming events are stamped at

\[s = n + 1,\]

i.e., spikes are interpreted as generated during \((t,\, t + dt]\). If per-event offsets \(\delta_j\) (ms) are provided, the stored physical event time for item \(j\) is

\[t_j = s \cdot dt - \delta_j.\]

With time_in_steps=True, storage is split into integer stamps events['times'] (step index \(s\)) and continuous offsets events['offsets'] (\(\delta_j\), ms), preserving sub-step timing.

2. Activity-Window Gate on the Step Lattice

Define step bounds

\[s_{\min} = \frac{\mathrm{origin} + \mathrm{start}}{dt}, \qquad s_{\max} = \frac{\mathrm{origin} + \mathrm{stop}}{dt} \quad (\text{or } +\infty \text{ if stop is None}).\]

The recorder is active for stamp step \(s\) iff

\[s > s_{\min} \;\land\; s \le s_{\max}.\]

Therefore, start is exclusive and stop is inclusive, exactly as in NEST recording devices.

3. Multiplicity Inference and Payload Normalization

Incoming arrays are flattened to one-dimensional vectors of length \(N\). Scalars are broadcast to \((N,)\) for senders and offsets. Let \(x_j\) denote spikes[j]:

  • If multiplicities is None and all spikes are integer-like (within 1e-12 tolerance), event counts are \(c_j = \max(\mathrm{round}(x_j),\, 0)\).

  • If multiplicities is None and spikes contains non-integer values, \(c_j = \mathbf{1}[x_j > 0]\).

  • If multiplicities is provided with non-negative integers \(m_j\), then \(c_j = m_j \,\mathbf{1}[x_j > 0]\).

Each item contributes exactly \(c_j\) stored events by repetition.

4. Constraints and Computational Implications

start, stop (when not None), origin, current t, and dt must be scalar-convertible and aligned to the simulation grid. Alignment is enforced by round-trip integer checks with 1e-12 tolerance. Per update() call, normalization is \(O(N)\) and event expansion is \(O(E_{\mathrm{new}})\) where \(E_{\mathrm{new}} = \sum_j c_j\). Persistent memory usage is linear in the total number of stored events.

Parameters:
  • in_size (Size, optional) – Shape/size argument consumed by brainstate.nn.Dynamics. The recorder returns event dictionaries rather than dense tensors; in_size is retained for API compatibility only. Default is 1.

  • start (saiunit.Quantity or float, optional) – Scalar relative exclusive lower bound of the recording window, convertible to ms. Must be finite and an integer multiple of dt. The effective gate is stamp_step > (origin + start) / dt. Default is 0.0 * u.ms.

  • stop (saiunit.Quantity, float, or None, optional) – Scalar relative inclusive upper bound of the recording window, convertible to ms. Must be None or finite and aligned to dt. Must satisfy stop >= start when not None. The effective gate is stamp_step <= (origin + stop) / dt. None means no upper bound (\(s_{\max} = +\infty\)). Default is None.

  • origin (saiunit.Quantity or float, optional) – Scalar global time-origin shift added to both start and stop when constructing the active window, convertible to ms. Shifting the origin displaces the entire recording window without changing its duration. Must be finite and aligned to dt. Default is 0.0 * u.ms.

  • time_in_steps (bool, optional) – Controls the time representation in events. If False, events['times'] stores float64 milliseconds computed as \(s \cdot dt - \delta_j\). If True, events['times'] stores integer step stamps (int64) and events['offsets'] stores the corresponding float64 offsets in ms. Becomes immutable after the first update() call. Default is False.

  • frozen (bool, optional) – NEST-compatibility flag. True is unconditionally rejected because this recorder cannot be frozen. Default is False.

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

Parameter Mapping

Table 41 Mapping of constructor parameters to model symbols#

Parameter

Default

Math symbol

Semantics

start

0.0 * u.ms

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

Relative exclusive lower bound of the active window.

stop

None

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

Relative inclusive upper bound of the active window.

origin

0.0 * u.ms

\(t_0\)

Global origin shift applied before window gating.

time_in_steps

False

\(\mathrm{repr}_t\)

Time storage mode: physical ms or integer (step, offset) pair.

Raises:
  • ValueError – If frozen=True; if any time parameter (start, stop, origin, dt, or current t) is non-scalar, non-finite when required, not aligned to dt, or violates stop >= start; if time_in_steps is modified after update() has been called; if n_events is assigned a value other than 0; if payload array sizes are incompatible with spikes length; or if explicit multiplicities contain negative entries.

  • TypeError – If unit conversion or numeric casting of any payload or time parameter fails.

  • KeyError – If get() is called with an unsupported key, or if required simulation context entries ('t' or dt) are not available via brainstate.environ.

Notes

  • Event writes are immediate (no one-step delivery lag), unlike the request/reply mechanism of multimeter.

  • time_in_steps becomes immutable after the first update() call that accesses simulation context, matching NEST backend constraints.

  • spikes=None is treated as a no-op update that returns the current events without writing any new events.

  • init_state() clears all accumulated events; it can be used to reset the recorder between simulation segments without reconstructing the object.

References

Examples

Record spikes from a three-neuron population over a 1 ms window at 0.1 ms resolution, using integer-like spike counts:

>>> import brainpy
>>> import brainstate
>>> import saiunit as u
>>> import numpy as np
>>> with brainstate.environ.context(dt=0.1 * u.ms):
...     sr = brainpy.state.spike_recorder(start=0.0 * u.ms, stop=1.0 * u.ms)
...     with brainstate.environ.context(t=0.0 * u.ms):
dftype = brainstate.environ.dftype()
ditype = brainstate.environ.ditype()
...         _ = sr.update(
...             spikes=np.array([1.0, 0.0, 2.0], dtype=dftype),
...             senders=np.array([3, 4, 5], dtype=ditype),
...         )
...     ev = sr.flush()
...     _ = ev['times'].shape

Record a single spike with a sub-step offset using time_in_steps=True, which splits the timestamp into an integer step index and a float offset:

>>> import brainpy
>>> import brainstate
>>> import saiunit as u
>>> import numpy as np
>>> with brainstate.environ.context(dt=0.1 * u.ms):
...     sr = brainpy.state.spike_recorder(time_in_steps=True)
...     with brainstate.environ.context(t=0.0 * u.ms):
...         _ = sr.update(
...             spikes=np.array([1.0], dtype=dftype),
...             senders=np.array([9], dtype=ditype),
...             offsets=np.array([0.03], dtype=dftype) * u.ms,
...         )
...     ev = sr.events
...     _ = (ev['times'][0], ev['offsets'][0])
init_state(batch_size=None, **kwargs)[source]#

State initialization function.

update(spikes=None, senders=None, offsets=None, multiplicities=None)[source]#

Record spike events for the current simulation step.

Reads the current simulation time t and resolution dt from brainstate.environ, computes the stamp step \(s = n + 1\) where \(n = \mathrm{round}(t / dt)\), applies the activity-window gate, expands the spike payload into individual events, and appends them to the internal buffers.

Parameters:
  • spikes (ArrayLike or None, optional) –

    Input spike payload, flattened to shape (N,). Accepted dtypes include boolean, integer, and floating-point values.

    • None: no new events are written; current events dict is returned immediately.

    • Integer-like values (all within 1e-12 of an integer) with multiplicities is None: each element \(j\) contributes \(c_j = \max(\mathrm{round}(x_j),\, 0)\) events.

    • Non-integer floating values with multiplicities is None: each element contributes \(c_j = \mathbf{1}[x_j > 0]\) events (binary threshold).

  • senders (ArrayLike or None, optional) – Sender node IDs cast to int64, shape (N,) or scalar broadcastable to (N,). Default sender ID is 1 for all entries when None.

  • offsets (ArrayLike or None, optional) – Per-event sub-step timing offsets \(\delta_j\) in ms, shape (N,) or scalar broadcastable to (N,). Values may carry a saiunit time unit and are converted to ms. Must contain only finite values. Default is 0.0 * u.ms for all entries.

  • multiplicities (ArrayLike or None, optional) – Explicit non-negative integer event multiplicities cast to int64, shape (N,) or scalar broadcastable to (N,). When provided, the integer-like inference path from spikes is disabled; the count rule becomes \(c_j = m_j \,\mathbf{1}[x_j > 0]\). Negative values raise ValueError. Default is None.

Returns:

events – Current accumulated events dictionary after processing this step. All arrays are one-dimensional with length \(E\) equal to the total number of stored events:

  • 'senders'int64, shape (E,).

  • 'times'float64 ms when time_in_steps=False; int64 step stamps when time_in_steps=True.

  • 'offsets'float64 ms, shape (E,) (only present when time_in_steps=True).

Return type:

dict[str, np.ndarray]

Raises:
  • ValueError – If t is not grid-aligned to dt; if start, stop, or origin are invalid with respect to dt; if dt <= 0; if provided payload array sizes are incompatible with the N inferred from spikes; if offsets contain non-finite values; or if explicit multiplicities contain negative entries.

  • TypeError – If numeric or unit conversion of any payload or time parameter fails.

  • KeyError – If required simulation context entries ('t' or dt) are not available via brainstate.environ.

Notes

Events are written at stamp step \(s = \mathrm{round}(t / dt) + 1\) and then gated by the active window \((s_{\min},\, s_{\max}]\) in step space. If the current stamp step falls outside the window, the method returns the unchanged events dict without writing any new data.