API Reference
Solve functions
tinydiffeq.solve_ode(f, solver, t0, t1, x0, *, p=None, args=None, dt0=None, saveat=None, controller=None, max_steps=4096, project=None)
Integrate dx/dt = f(x, t, args, p) from t0 to t1 > t0.
The vector field may be declared f(x), f(x, t), f(x, t, args),
or f(x, t, args, p) — always in that order. x is an array state
(scalar or vector), args is pass-through data that is by convention
not an AD target, and p holds differentiable parameters (any pytree);
jvp/vjp with respect to p and x0 are first-class.
Fixed and adaptive stepping share one bounded lax.scan of exactly
max_steps iterations, so shapes are static and curvature-dependent
step counts never retrace. dt0 is required (no auto-initial-step
heuristic). Each attempt is clipped to the remaining horizon; the clipped
step also feeds the controller's next-step proposal, which doubles as the
growth guard — near-flat fields otherwise grow steps into quarter-horizon
leaps. project (e.g. a positivity clamp, assumed idempotent) is
applied at every point where f is evaluated and to every accepted
state. Returns a :class:Solution; sol.ok is False if the budget ran
out before t1 (outputs are then the reached prefix, never poisoned).
The time dtype follows jnp.result_type(x0, float); the library never
sets jax_enable_x64 — do that in your application.
Source code in src/tinydiffeq/ode.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | |
tinydiffeq.solve_sde(drift, diffusion, solver, t0, t1, x0, *, key, n_steps, p=None, args=None, saveat=None, project=None)
Integrate the Ito SDE dx = drift dt + diffusion dW (diagonal noise)
on the fixed grid of n_steps uniform steps from t0 to t1 > t0.
drift and diffusion follow the same signature convention as
solve_ode — (x), (x, t), (x, t, args), or
(x, t, args, p). n_steps must be a static Python int (the honest
static-shape contract; there is no adaptive SDE stepping in v1). The
Brownian increments are presampled from key as
sqrt(dt) * normal(key, (n_steps,) + x0.shape), so a fixed key gives a
fixed noise process: reproducible across calls and differentiable with
respect to x0 and p (not key).
SaveAt(ts=...) raises — cubic Hermite interpolation is wrong for
rough paths; use t1 (default) or steps (here n_steps + 1
rows, all accepted).
Source code in src/tinydiffeq/sde.py
Solvers
tinydiffeq.Euler
dataclass
Explicit Euler. Fixed-step only: no embedded error estimate.
Source code in src/tinydiffeq/solvers.py
tinydiffeq.RK4
dataclass
Classic fourth-order Runge-Kutta. Fixed-step only: no error estimate.
Source code in src/tinydiffeq/solvers.py
tinydiffeq.Tsit5
dataclass
Tsitouras 5(4) explicit Runge-Kutta with embedded error estimate.
FSAL: the last stage k7 = g(x1, t + dt) is the next step's first stage,
so an accepted adaptive step costs six fresh evaluations. Note k7 is
evaluated at the projected accepted state, so the FSAL cache stays
consistent with the state actually carried forward when project binds.
Source code in src/tinydiffeq/solvers.py
tinydiffeq.EulerMaruyama
dataclass
Euler-Maruyama for Ito SDEs with diagonal noise. Fixed-step only.
Source code in src/tinydiffeq/solvers.py
Step-size controllers
tinydiffeq.ConstantStepSize
dataclass
Accept every step and keep the carried step size unchanged.
Source code in src/tinydiffeq/controllers.py
tinydiffeq.IController
dataclass
Integral step-size controller with max-norm error.
Accept iff E = max(|err| / (atol + rtol * max(|x0|, |x1|))) <= 1
(forced accept once the step reaches dtmin), and propose
dt_next = dt_used * clip(safety * E**(-1/order), factormin, factormax)
clipped to [dtmin, dtmax]. This is the classic integral controller —
equal to diffrax's PIDController at its default coefficients
(pcoeff=0, dcoeff=0); there is no proportional term, hence the name.
Source code in src/tinydiffeq/controllers.py
Output selection and results
tinydiffeq.SaveAt
dataclass
What solve_ode/solve_sde return. Exactly one mode must be set.
t1=True: the endpoint only (the default in the solve functions).ts=grid: cubic-Hermite interpolation of the internal steps onto a fixed, sorted query grid in[t0, t1]. Output shape is(len(ts), ...)regardless of how many internal steps the controller takes, so changing curvature never changes shapes or recompiles.tsis a data leaf; a different grid of the same length retraces nothing. ODE only.steps=True: the raw padded attempt rows,max_steps + 1of them including the initial state. Rejected attempts and post-horizon frozen iterations duplicate the previous row.fill="last"(default) keeps those duplicates;fill="inf"overwrites every non-accepted row (including mid-trajectory rejection rows) withinf, diffrax-style.
fill only applies to steps=True.
Source code in src/tinydiffeq/saveat.py
tinydiffeq.Solution
dataclass
Result of solve_ode/solve_sde.
ts/xs: times and states in the shape dictated bySaveAt— scalar/endpoint fort1,(len(ts), ...)forts,(max_steps + 1, ...)forsteps.ok: scalar bool, True iff the integration reachedt1within the attempt budget. The package never poisons outputs; callers that want diverging residuals dojnp.where(sol.ok, sol.xs, jnp.inf).num_accepted: number of accepted steps (excluding the initial state).accepted:stepsmode only (otherwise None): per-row bool mask, True for rows holding a newly computed state. Row 0 (the initial state) is always True, soaccepted.sum() == num_accepted + 1.
Source code in src/tinydiffeq/solution.py
Utilities
tinydiffeq.hermite_interpolate(ts_query, knot_ts, knot_xs, knot_fs)
Cubic Hermite interpolation of (knot_ts, knot_xs, knot_fs) at
ts_query, where knot_fs holds the time derivatives at the knots.
knot_ts must be nondecreasing; duplicate knots are allowed and
queries falling on a zero-width bracket return the left knot value.
Queries outside the knot span clamp to the boundary knot values (flat
extrapolation) rather than evaluating the cubic outside its bracket.
Source code in src/tinydiffeq/interpolation.py
tinydiffeq.cumulative_trapezoid(g, ts, *, substeps=1)
Cumulative composite-trapezoid integral of a time-only g(t) on the
(possibly nonuniform) sorted grid ts.
Each grid interval is subdivided into substeps uniform panels, so the
quadrature error shrinks with substeps without changing the output
grid. Returns (integral, values) where integral[k] approximates
the integral of g from ts[0] to ts[k] (integral[0] = 0)
and values = g(ts); g may return any array shape, which is
appended to the leading grid axis.