mip_generator#

class brainpy.state.mip_generator(in_size=1, rate=Quantity(0., 'Hz'), p_copy=1.0, start=Quantity(0., 'ms'), stop=None, origin=Quantity(0., 'ms'), rng_seed=0, name=None)#

Correlated spike trains from a Multiple Interaction Process (MIP).

mip_generator reproduces NEST’s mip_generator device by combining one shared parent Poisson process with independent copy operations for each child output train.

1. Parent-child process model and derivation

Let \(r = \mathrm{rate}\) in spikes/s and simulation step \(\Delta t\) in ms. For each step \(n\):

\[N_n \sim \mathrm{Poisson}(\lambda), \qquad \lambda = r \, \Delta t / 1000.\]

For each child train \(i \in \{1,\dots,M\}\) and each parent spike \(m \in \{1,\dots,N_n\}\), draw \(B_{i,m} \sim \mathrm{Bernoulli}(p_{\mathrm{copy}})\) independently across \(i\) and \(m\). The emitted multiplicity is

\[K_{i,n} = \sum_{m=1}^{N_n} B_{i,m}.\]

Marginally, \(K_{i,n}\) is Poisson with parameter \(p_{\mathrm{copy}} \lambda\) (Poisson thinning), so each child has mean rate \(p_{\mathrm{copy}} r\). Shared parent fluctuations induce cross-child covariance:

\[\mathrm{Cov}(K_{i,n}, K_{j,n}) = p_{\mathrm{copy}}^2 \lambda,\quad \mathrm{Var}(K_{i,n}) = p_{\mathrm{copy}} \lambda,\quad \rho_{ij} = p_{\mathrm{copy}} \quad (i \neq j).\]

2. Source-equivalent sampling order and computational implications

The update path mirrors models/mip_generator.cpp:

  1. Check whether the stimulation device is active at current step.

  2. Draw parent multiplicity from the parent Poisson process.

  3. For each output train, run explicit Bernoulli trials for each parent spike and count copied spikes.

This implementation intentionally preserves NEST’s explicit Bernoulli loop (instead of vectorised Binomial sampling). Runtime per active step is \(O(M N_n)\) random comparisons in the general case, with fast paths for p_copy <= 0 and p_copy >= 1. RNG sampling uses numpy.random.Generator (seeded by rng_seed), so draws are CPU NumPy-based rather than JAX-key-based.

3. Timing semantics and grid constraints

Activity follows NEST stimulation-device semantics:

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

Therefore start is exclusive and stop is inclusive. Internally, finite times are projected to integer steps with \(\mathrm{round}(t / \Delta t)\) and checked as t_min_step < curr_step <= t_max_step. Finite origin, start, and stop must be on the simulation grid (absolute tolerance 1e-12 in time/dt ratio), otherwise ValueError is raised.

Parameters:
  • in_size (Size, optional) – Output size specification consumed by brainstate.nn.Dynamics. self.varshape is derived from in_size and determines the exact shape of arrays emitted by update(). Each element of self.varshape corresponds to one child process. Default is 1.

  • rate (ArrayLike, optional) – Scalar parent Poisson rate \(r\) in spikes/s (Hz), shape () after conversion. Accepts a single-element numeric ArrayLike or a saiunit.Quantity convertible to u.Hz. Must satisfy rate >= 0. Default is 0.0 * u.Hz.

  • p_copy (ArrayLike, optional) – Scalar copy probability \(p_{\mathrm{copy}}\) for each parent spike and each child process, shape () after conversion. Must be scalar-convertible to float64 and satisfy 0 <= p_copy <= 1. Default is 1.0.

  • start (ArrayLike, optional) – Scalar relative start time in ms (exclusive lower bound after adding origin), shape () after conversion. Must be scalar-convertible to float64 and, when dt is available, representable on the simulation grid. Default is 0.0 * u.ms.

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

  • origin (ArrayLike, optional) – Scalar time offset in ms added to both start and stop, shape () after conversion. Must be scalar-convertible and grid-representable when dt is available. Default is 0.0 * u.ms.

  • rng_seed (int, optional) – Seed passed to numpy.random.SeedSequence and split into two independent RNG streams (parent Poisson and child-copy Bernoulli). Default is 0.

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

Parameter Mapping

Table 37 Parameter mapping to model symbols#

Parameter

Default

Math symbol

Semantics

rate

0.0 * u.Hz

\(r\)

Parent Poisson intensity in spikes/s.

p_copy

1.0

\(p_{\mathrm{copy}}\)

Copy probability per parent spike and per child train.

start

0.0 * u.ms

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

Relative exclusive lower activity bound.

stop

None

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

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

origin

0.0 * u.ms

\(t_0\)

Time offset added to start and stop.

in_size

1

\(M\)

Number/shape of child processes (M = prod(varshape)).

rng_seed

0

Entropy source for parent/child RNG stream initialization.

Raises:
  • ValueError – If rate < 0; if p_copy is outside [0, 1]; if stop < start; if scalar conversion fails due to non-scalar inputs; or if finite origin/start/stop are not multiples of dt when simulation resolution is available.

  • TypeError – If conversion of unitful inputs to u.Hz or u.ms is invalid.

  • KeyError – At update time, if the simulation environment does not provide required entries such as dt via brainstate.environ.get_dt().

Notes

  • Outputs are multiplicities 0, 1, 2, ... per discrete step, matching NEST SpikeEvent multiplicity semantics rather than binary spike flags.

  • init_state() creates two independent RNG instances to mirror NEST’s separation of parent and child stochastic paths.

  • set() updates cached timing boundaries immediately when dt is already available in brainstate.environ.

Examples

>>> import brainpy
>>> import brainstate
>>> import saiunit as u
>>> with brainstate.environ.context(dt=0.1 * u.ms):
...     gen = brainpy.state.mip_generator(
...         in_size=(2, 3),
...         rate=800.0 * u.Hz,
...         p_copy=0.25,
...         start=5.0 * u.ms,
...         stop=40.0 * u.ms,
...         rng_seed=7,
...     )
...     with brainstate.environ.context(t=10.0 * u.ms):
...         counts = gen.update()
...     _ = counts.shape, counts.dtype
>>> import brainpy
>>> import saiunit as u
>>> gen = brainpy.state.mip_generator(rate=1200.0 * u.Hz, p_copy=0.1)
>>> gen.set(start=2.0 * u.ms, stop=None, origin=1.0 * u.ms)
>>> params = gen.get()
>>> _ = params['rate'], params['p_copy'], params['stop']

See also

poisson_generator

Independent Poisson trains without shared parent process.

poisson_generator_ps

Precise-time Poisson generator with dead time.

inhomogeneous_poisson_generator

Time-varying Poisson rate generator.

References

get()[source]#

Return current public parameters as plain Python scalars.

Returns:

outdict with keys 'rate', 'p_copy', 'start', 'stop', and 'origin'. Values are Python float in public units: Hz for rate and ms for all time fields. 'stop' is math.inf if unbounded (i.e., stop=None was supplied at construction or via set()).

Return type:

dict

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

Initialize RNG state for parent and child stochastic paths.

Spawns two independent numpy.random.Generator instances from rng_seed via numpy.random.SeedSequence, mirroring NEST’s separation of parent Poisson draws and per-child Bernoulli draws.

Parameters:
  • batch_size (int or None, optional) – Unused placeholder for brainstate.nn.Dynamics API compatibility. Ignored by this implementation.

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

Raises:
  • ValueError – If rng_seed cannot be consumed by numpy.random.SeedSequence.

  • TypeError – If rng_seed has an invalid type for NumPy RNG initialization.

set(*, rate=<object object>, p_copy=<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 provided values are validated and converted before any attribute is mutated, so the generator state remains consistent on failure. If dt is currently available in brainstate.environ, the cached step bounds are recomputed immediately after mutation.

Parameters:
  • rate (ArrayLike or object, optional) – New scalar parent Poisson rate in Hz. If omitted, keep current value. Must satisfy rate >= 0 after scalar conversion.

  • p_copy (ArrayLike or object, optional) – New scalar copy probability in [0, 1]. If omitted, keep current value.

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

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

  • origin (ArrayLike or object, optional) – New scalar time origin in ms. If omitted, keep current value.

Raises:
  • ValueError – If any provided parameter is non-scalar, violates parameter constraints (for example p_copy outside [0, 1] or stop < start), or finite times are off the simulation grid when dt is available.

  • TypeError – If unit conversion or scalar coercion fails for provided values.

simulate(n_steps)[source]#

Run n_steps simulation steps in one vectorised NumPy call.

Equivalent to calling update() in a loop with brainstate.environ.context(t=k*dt) for k = 0, 1, ..., n_steps-1, but avoids per-step Python overhead by batching all random draws.

Parameters:

n_steps (int) – Number of simulation steps to run. Assumes step index k corresponds to time t = k * dt.

Returns:

out – Integer array of shape (n_steps, *self.varshape) with spike multiplicities per step and per child train.

Return type:

numpy.ndarray

Notes

Binomial(n, p) is used in place of n independent Bernoulli(p) trials — statistically equivalent but faster.

update()[source]#

Advance one simulation step and emit child spike multiplicities.

Executes the source-equivalent MIP sampling pipeline: lazily initialises state if needed, refreshes the timing/rate cache when dt changes, gates activity with \(t_{\min} < t \le t_{\max}\), draws parent spike multiplicity from \(\mathrm{Poisson}(r \Delta t / 1000)\), then independently copies each parent spike into each child train with probability p_copy.

Returns:

out – NumPy int64 array of shape self.varshape. Entries are per-step spike multiplicities for each child train. Returns all zeros when the generator is inactive, when rate <= 0, or when the parent draw yields zero spikes.

Return type:

jax.Array

Raises:
  • KeyError – If the simulation context does not provide dt required by brainstate.environ.get_dt().

  • ValueError – If finite timing parameters are not aligned to the simulation grid after a dt change.

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