Binary Events and Event-Driven Operations#
This tutorial develops the event side of BrainEvent: create binary event arrays, inspect them, multiply them by data, and process a complete time series without a Python time-step loop.
Creating Binary Events#
Creating Events from Array-Like Inputs#
import brainevent
import brainstate
import jax
import jax.numpy as jnp
import numpy as np
from_list = brainevent.BinaryArray([1, 0, 1, 0])
from_numpy = brainevent.BinaryArray(np.array([True, False, True]))
from_jax = brainevent.BinaryArray(jnp.array([False, True, True]))
print(from_list)
print(from_numpy)
print(from_jax)
BinaryArray(value=[1 0 1 0], dtype=int32)
BinaryArray(value=[ True False True], dtype=bool)
BinaryArray(value=[False True True], dtype=bool)
Representing Simulated Spikes#
brainstate.random.seed(11)
spike_values = brainstate.random.bernoulli(0.25, size=(12,))
spikes = brainevent.BinaryArray(spike_values)
print(spikes)
print("active events:", int(spike_values.sum()))
BinaryArray(value=[False True False False False True False False False False False False], dtype=bool)
active events: 2
Inspecting and Transforming Binary Events#
Indexing#
events_2d = brainevent.BinaryArray([[1, 0, 1], [0, 1, 0]])
print("first row:", events_2d[0])
print("last two columns:", events_2d[:, 1:])
first row: [1 0 1]
last two columns: [[0 1]
[1 0]]
Reductions and Logical Operations#
event_a = brainevent.BinaryArray([1, 0, 1, 0])
event_b = brainevent.BinaryArray([1, 1, 0, 0])
print("events per row:", jnp.sum(events_2d.value, axis=1))
print("A AND B:", jnp.logical_and(event_a.value, event_b.value))
print("A OR B:", jnp.logical_or(event_a.value, event_b.value))
events per row: [2 1]
A AND B: [ True False False False]
A OR B: [ True True True False]
Event-Driven Matrix Multiplication#
Binary Events with Dense Data#
pre_spikes = brainevent.BinaryArray([1, 0, 1, 0, 1])
weights = jnp.array([
[0.5, 0.2, 0.1],
[0.3, 0.4, 0.2],
[0.1, 0.5, 0.3],
[0.2, 0.1, 0.4],
[0.4, 0.3, 0.5],
])
post_input = jax.block_until_ready(pre_spikes @ weights)
print(post_input)
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.14.7/x64/lib/python3.14/site-packages/brainevent/_op/main.py:569, in XLACustomKernel._register_fallback_lowering.<locals>.fallback_kernel_fn(*args, **kwargs)
568 try:
--> 569 kernel = entry.kernel_generator(**kwargs)
570 except Exception as exc:
File /opt/hostedtoolcache/Python/3.14.7/x64/lib/python3.14/site-packages/brainevent/_dense/binary.py:173, in _binary_densemv_numba_kernel(spk_info, transpose, **kwargs)
168 def _binary_densemv_numba_kernel(
169 spk_info: jax.ShapeDtypeStruct,
170 transpose: bool,
171 **kwargs
172 ):
--> 173 import numba
175 if transpose:
176 # weights[k,n], spikes[k] -> out[n]
ModuleNotFoundError: No module named 'numba'
The above exception was the direct cause of the following exception:
JaxStackTraceBeforeTransformation Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.14.7/x64/lib/python3.14/site-packages/brainevent/_dense/binary.py:164, in binary_densemv()
163 spk_val = spk_val.astype(jnp.bool_)
--> 164 r = binary_densemv_p_call(weight_val, spk_val, transpose=transpose, backend=backend)
165 return u.maybe_decimal(r[0] * wunit * spkunit)
File /opt/hostedtoolcache/Python/3.14.7/x64/lib/python3.14/site-packages/brainevent/_dense/binary.py:424, in binary_densemv_p_call()
423 out = jax.ShapeDtypeStruct([weights.shape[0]], weights.dtype)
--> 424 return binary_densemv_p(
425 weights,
426 spikes,
427 outs=[out],
428 transpose=transpose,
429 spk_info=jax.ShapeDtypeStruct(spikes.shape, spikes.dtype),
430 weight_info=jax.ShapeDtypeStruct(weights.shape, weights.dtype),
431 backend=backend,
432 )
File /opt/hostedtoolcache/Python/3.14.7/x64/lib/python3.14/site-packages/brainevent/_op/main.py:338, in __call__()
337 flat_outs, tree_def = abstract_arguments(outs)
--> 338 r = self.primitive.bind(*ins, **kwargs, outs=tuple(flat_outs))
339 if len(r) != len(flat_outs):
JaxStackTraceBeforeTransformation: brainevent.KernelCompilationError: Backend 'numba' failed to construct a kernel for primitive 'binary_densemv' on platform 'cpu': ModuleNotFoundError: No module named 'numba'. Available backend(s) for platform 'cpu': ['numba', 'jax_raw']. Switch with backend='<name>' on this call, or binary_densemv.set_default('cpu', '<name>') / brainevent.set_backend('cpu', '<name>') to change the default.
The preceding stack trace is the source of the JAX operation that, once transformed by JAX, triggered the following exception.
--------------------
The above exception was the direct cause of the following exception:
KernelCompilationError Traceback (most recent call last)
Cell In[5], line 9
5 [0.1, 0.5, 0.3],
6 [0.2, 0.1, 0.4],
7 [0.4, 0.3, 0.5],
8 ])
----> 9 post_input = jax.block_until_ready(pre_spikes @ weights)
10 print(post_input)
File /opt/hostedtoolcache/Python/3.14.7/x64/lib/python3.14/site-packages/brainevent/_event/binary.py:208, in BinaryArray.__matmul__(self, oc)
206 # Perform the appropriate multiplication based on dimensions
207 if self.ndim == 1:
--> 208 return binary_densemv(oc, self.value, transpose=True)
209 else: # self.ndim == 2
210 # self[m,k] @ oc[k,n]: use weights=oc[k,n], spikes=self.value.T[k,m]
211 # gives oc.T @ self.value.T = [n,m], then .T = [m,n]
212 return binary_densemm(oc, self.value.T, transpose=True).T
File /opt/hostedtoolcache/Python/3.14.7/x64/lib/python3.14/site-packages/brainevent/_misc.py:1770, in NameScope.__call__(self, *args, **kwargs)
1768 backend = kwargs.pop('backend', None)
1769 jit_fn = self._get_jit_fn(backend)
-> 1770 return jit_fn(*args, **kwargs)
[... skipping hidden 18 frame]
File /opt/hostedtoolcache/Python/3.14.7/x64/lib/python3.14/site-packages/brainevent/_op/main.py:571, in XLACustomKernel._register_fallback_lowering.<locals>.fallback_kernel_fn(*args, **kwargs)
569 kernel = entry.kernel_generator(**kwargs)
570 except Exception as exc:
--> 571 raise KernelCompilationError(
572 f"Backend '{backend_to_use}' failed to construct a kernel "
573 f"for primitive '{self.name}' on platform '{platform}': "
574 f"{type(exc).__name__}: {exc}." + _construction_error_suffix()
575 ) from exc
577 try:
578 return kernel(*args)
KernelCompilationError: Backend 'numba' failed to construct a kernel for primitive 'binary_densemv' on platform 'cpu': ModuleNotFoundError: No module named 'numba'. Available backend(s) for platform 'cpu': ['numba', 'jax_raw']. Switch with backend='<name>' on this call, or binary_densemv.set_default('cpu', '<name>') / brainevent.set_backend('cpu', '<name>') to change the default.
Correctness and Performance Comparison#
The event-driven result must first match ordinary dense multiplication. Timing is a separate question: warm up compiled work, synchronize every measured result, and report the backend, shapes, event density, and repetition count before interpreting a speed difference.
dense_spikes = jnp.array([1, 0, 1, 0, 1], dtype=weights.dtype)
dense_result = jax.block_until_ready(dense_spikes @ weights)
event_result = jax.block_until_ready(pre_spikes @ weights)
print("results match:", bool(jnp.allclose(event_result, dense_result)))
A Small Event-Driven Feedforward Network#
brainstate.random.seed(19)
w1 = brainstate.random.normal(size=(5, 4)) * 0.2
w2 = brainstate.random.normal(size=(4, 2)) * 0.2
hidden_drive = pre_spikes @ w1
hidden_events = brainevent.BinaryArray(hidden_drive > 0.15)
network_output = jax.block_until_ready(hidden_events @ w2)
print("hidden events:", hidden_events)
print("network output:", network_output)
Processing Time-Series Events#
BinaryArray accepts a two-dimensional event matrix, so the time axis can be processed in one compiled matrix operation rather than a Python loop.
brainstate.random.seed(23)
spike_trains = brainstate.random.bernoulli(0.1, size=(40, 12))
brainstate.random.seed(29)
readout_weights = brainstate.random.normal(size=(12, 3)) * 0.1
time_series_output = jax.block_until_ready(
brainevent.BinaryArray(spike_trains) @ readout_weights
)
print("input shape:", spike_trains.shape)
print("output shape:", time_series_output.shape)
Summary and Next Steps#
BinaryArray represents vector or batched binary events and composes with dense data and JAX synchronization. Continue with Event-Driven Synaptic Plasticity for event-triggered weight updates, or move to Data to choose a connectivity representation.