Event-Driven Operators#
In a spiking neural network only a small fraction of neurons fire on any given step. A dense matrix multiply ignores this: it pays for every connection whether or not the presynaptic neuron spiked. Event-driven operators exploit the sparsity of spike trains — their cost scales with the number of active inputs, not the total number of neurons — while producing exactly the same result as the dense computation.
BrainState provides several, covering the connectivity patterns used in large-scale SNNs:
EventLinear— event-driven dense connectivity;EventFixedProb— sparse random connectivity with a fixed connection probability;FixedNumConn/EventFixedNumConn— a fixed number of connections per neuron.
import jax.numpy as jnp
import brainstate
import brainstate.nn as nn
import braintools
brainstate.random.seed(0)
brainstate.__version__
An NVIDIA GPU may be present on this machine, but a CUDA-enabled jaxlib is not installed. Falling back to cpu.
'0.4.0'
Sparse spike vectors#
A layer’s input is a spike vector: mostly zeros, with a one at each neuron that fired. We build a population of 200 neurons with roughly 10% activity.
n_pre, n_post = 200, 100
spikes = (brainstate.random.rand(n_pre) < 0.1).astype(float)
print('active inputs:', int(spikes.sum()), 'of', n_pre)
active inputs: 25 of 200
EventLinear: event-driven dense connectivity#
EventLinear holds a full weight matrix, exactly like nn.Linear, but performs the matrix-vector
product event-driven: it accumulates only the columns selected by spiking inputs. The result is
identical to the dense product — only the cost differs.
weight = brainstate.random.randn(n_pre, n_post) * 0.1
event_linear = nn.EventLinear(n_pre, n_post, weight=weight)
event_out = event_linear(spikes)
dense_out = spikes @ weight
print('output shape:', event_out.shape)
print('matches dense matmul:', bool(jnp.allclose(event_out, dense_out, atol=1e-5)))
output shape: (100,)
matches dense matmul: True
Spikes are naturally boolean, and EventLinear accepts boolean input directly — the most
efficient form, since a spike is simply present or absent.
bool_out = event_linear(spikes.astype(bool))
print('boolean spikes give the same result:', bool(jnp.allclose(bool_out, dense_out, atol=1e-5)))
boolean spikes give the same result: True
EventFixedProb: sparse random connectivity#
Cortical-scale models are not densely connected — each neuron contacts a small random subset of
targets. EventFixedProb represents this directly: conn_num is the connection probability, and
the operator never materialises the full dense matrix, so memory scales with the number of actual
synapses.
sparse_syn = nn.EventFixedProb(
n_pre, n_post,
conn_num=0.2, # each post neuron receives ~20% of pre neurons
conn_weight=0.5,
)
sparse_out = sparse_syn(spikes)
print('sparse output shape:', sparse_out.shape)
sparse output shape: (100,)
FixedNumConn: a fixed number of connections#
When biological fan-in must be controlled exactly, FixedNumConn wires each neuron to a fixed
number of partners rather than a probability. EventFixedNumConn is its event-driven form for
spiking input.
fixed_syn = nn.FixedNumConn(
n_pre, n_post,
conn_num=10, # exactly 10 connections per neuron
conn_weight=0.5,
)
fixed_out = fixed_syn(spikes)
print('fixed-fan-in output shape:', fixed_out.shape)
fixed-fan-in output shape: (100,)
When to use event-driven operators#
Reach for these operators whenever a connection’s input is a spike train:
Sparse activity — if only a few percent of neurons fire per step, event-driven evaluation avoids the wasted work of a dense multiply.
Sparse connectivity —
EventFixedProbandEventFixedNumConnstore only real synapses, so large networks fit in memory.Drop-in correctness — the numerical result equals the dense equivalent, so you can prototype with
nn.Linearand switch toEventLinearfor scale without changing the model’s behaviour.
See also#
Building a spiking neural network — these operators as synapses in a full SNN.
Dynamics and integration — the neuron models that emit the spikes.