Overview: How Integration Works#
Every dynamical model in braincell — a single ion channel, a point neuron,
a branching dendrite — is ultimately a system of differential equations
where \(y\) stacks together every time-varying quantity (membrane potential,
gating variables, ion concentrations). Turning that system into a simulation
means repeatedly answering one question: given the state now, what is it one
small step dt later?
braincell separates that question into two responsibilities:
Responsibility |
Who owns it |
Where it lives |
|---|---|---|
What to integrate — the variables and their right-hand side \(f\) |
your model |
|
How to advance one step |
a numerical solver |
|
This separation is the whole design. You describe the equations once; you can then swap solvers freely without rewriting the model.
The two protocol pieces#
DiffEqStateis a state variable that participates in integration. It is an ordinarybrainstatestate with two extra slots the solver fills in:derivative(the right-hand side \(f(t, y)\)) and, for stochastic systems,diffusion. You meet it again in Defining Differential Equations.DiffEqModuleis a mixin that marks a class as integrable. It exposes a three-method lifecycle that every solver calls in the same order on each step.
The per-step lifecycle#
A solver never reaches inside your model. It only calls three hooks, always in this order, once per step:
┌─────────────────────────────────────────────┐
│ one integration step (advances t by dt) │
└─────────────────────────────────────────────┘
pre_integral(...) # refresh rate constants, gather inputs
│
▼
compute_derivative(...) # REQUIRED: write state.derivative for each DiffEqState
│
▼
<< solver combines values + derivatives to produce y(t + dt) >>
│
▼
post_integral(...) # clamp / project / fire post-step events
Only compute_derivative is mandatory; pre_integral and post_integral
default to no-ops. The current time t and step size dt are not passed as
arguments — solvers read them from the active
brainstate.environ context, which is why
you will see every simulation wrapped in with brainstate.environ.context(dt=...).
The solver registry#
Solvers are looked up by name through a registry. You rarely import a step
function directly; instead you name it — either by passing solver="..." to a
cell, or by asking the registry for it. Let’s see what is available.
import braincell.quad as quad
integrators = sorted(quad.all_integrators)
print(f"{len(integrators)} integrators registered\n")
print(integrators)
25 integrators registered
['backward_euler', 'cn_exp_euler', 'cn_rk4', 'dhs_voltage', 'euler', 'exp_euler', 'exp_exp_euler', 'explicit', 'heun2', 'heun3', 'implicit_euler', 'implicit_exp_euler', 'implicit_rk4', 'ind_exp_euler', 'midpoint', 'ralston2', 'ralston3', 'ralston4', 'rk2', 'rk3', 'rk4', 'splitting', 'ssprk3', 'stagger', 'staggered']
get_integrator resolves a name (or alias) to its step function:
rk4 = quad.get_integrator("rk4")
rk4
<function braincell.quad._runge_kutta.rk4_step(target: braincell.DiffEqModule, *args)>
That is the entire mental model:
Describe equations with
DiffEqState/DiffEqModule.Pick a solver by name from
braincell.quad.Step inside a
brainstate.environcontext that suppliesdt(andt).
The next page makes this concrete by building a model from scratch.