odeint#
- class braintools.quad.odeint(f, y0, t0, t1=None, dt=None, *args, method='rk4', save_every=1, checkpoint=False, n_steps=None, **kwargs)#
Integrate an ODE with a fixed step and return the trajectory.
Runs
nsteps of a one-step integrator (seebraintools.quad.get_method()) as one compiledscaninsidebrainstate.environ.context(dt=dt), saving everysave_every-th state. Use it instead of a Pythonforloop overode_*_stepcalls: the step function is traced once and the whole loop compiles into a single XLA program.- Parameters:
f (
Callable) – Right-hand sidef(y, t, *args, **kwargs) -> dy/dtwith the same PyTree structure (and units[y]/[t]) asy. Note the(y, t)argument order of thebraintools.quadsteppers.y0 (PyTree) – Initial state at
t0. Leaves may be arrays or brainunit Quantities.t0 (float or Quantity) – Initial time.
t1 (float or Quantity, optional) – Final time. The number of steps is
n = round((t1 - t0) / dt), computed on the host, sot0,t1anddtmust be concrete (not traced). Mutually exclusive withn_steps.dt (float or Quantity) – Step size (required). May be negative to integrate backwards (then
t1 < t0).*args – Extra positional arguments forwarded to
f.method (
str|Callable) – Name of a registered'ode'stepper ('euler','rk2','rk4','dopri5', …; seebraintools.quad.list_methods()) or a stepper callable with thestep(f, y, t, *args, **kwargs)convention. Default'rk4'.save_every (
int) – Save one state everysave_everysteps. Default1.checkpoint (
bool) – IfTrue, usebrainstate.transform.checkpointed_scanso that reverse-mode gradients through long rollouts rematerialise the intermediate states instead of storing them all. The values and gradients are identical tocheckpoint=False. DefaultFalse.n_steps (
int|None) – Number of steps, given instead oft1. Use this underjax.jitwhent0ordtare traced. Must be a Pythonint >= 0.**kwargs – Extra keyword arguments forwarded to
f.
- Returns:
ts (array or Quantity) – Saved times, shape
(n // save_every + 1,), starting att0:ts[k] = t0 + k * save_every * dt.ys (PyTree) – Saved states with the structure of
y0; every leaf gains a leading axis of lengthn // save_every + 1andys[0] == y0.
- Raises:
ValueError – If
dtis missing or zero, if neither or both oft1andn_stepsare given, if the step count is negative (t1lies behindt0in the direction ofdt), ifn_stepsis not a non-negativeint, ifsave_every < 1, or ift1is given butt0,t1ordtis traced (usen_stepsunder jit).brainunit.UnitMismatchError – If
(t1 - t0) / dtis not dimensionless.KeyError – If
methodis an unknown name.
See also
solve_ivpAdaptive-step solver with error control.
get_methodResolve a stepper by name.
Notes
The
i-th step is taken at timet0 + i * dt(computed from the step index, so round-off does not accumulate int).If
save_everydoes not dividen, the trajectory stops at the last saved state: only(n // save_every) * save_everysteps are run, and the last saved time ist0 + (n // save_every) * save_every * dt, which is earlier thant1. Choosesave_everydividingnto reacht1.t1 == t0(orn < save_every) returnsts == [t0]andysholding onlyy0.Since
nis rounded, the last timet0 + n * dtcan differ fromt1by up todt / 2.Transformations:
odeintisjax.jit-compatible (withn_stepsinstead oft1when times are traced;method,save_every,checkpointandn_stepsare static),jax.vmap-compatible overy0,argsandkwargs, and differentiable in forward and reverse mode with respect toy0,args,kwargs,t0anddt.Examples
>>> import jax.numpy as jnp >>> from braintools.quad import odeint >>> ts, ys = odeint(lambda y, t: -y, jnp.array(1.0), 0.0, 1.0, 0.1) >>> ts.shape, ys.shape ((11,), (11,)) >>> print(f'{float(ys[-1]):.5f} exact {float(jnp.exp(-1.0)):.5f}') 0.36788 exact 0.36788 >>> >>> ts, ys = odeint(lambda y, t: -y, jnp.array(1.0), 0.0, 1.0, 0.1, ... method='euler', save_every=5) >>> ts.shape (3,)
Pytree and unit-aware states:
>>> import brainunit as u >>> def neuron(s, t, I): ... return {'V': (-65. * u.mV - s['V'] + 10. * u.Mohm * I) / (10. * u.ms)} >>> ts, ys = odeint(neuron, {'V': -65. * u.mV}, 0. * u.ms, 50. * u.ms, ... 0.1 * u.ms, 0.5 * u.nA) >>> ys['V'].shape (501,) >>> print(u.math.round(ys['V'][-1], 2)) -60.03 mV