tinydiffeq
Tiny differentiable ODE/SDE/DAE/SDAE solvers for JAX: fixed-step Euler/RK4,
adaptive Tsit5, linearly implicit Rodas5P for stiff ODEs and index-1 DAEs, and
fixed-step Euler–Maruyama, Milstein, and SRA1 for Itô SDEs and semi-explicit
index-1 SDAEs. Solves run in bounded lax.scan loops with static shapes and
compose with jit, vmap, forward mode, reverse mode, and
reverse-over-forward. Finite-state Markov simulation, probability forecasts,
and general fixed homogeneous linear solves (dense or matrix-free Krylov
exponential actions, after SciML's
ExponentialUtilities.expv)
round out the package.
This is a deliberately small, jvp/vjp-friendly package. Rodas5P is a JAX
adaptation of Steinebach's method following SciML's
OrdinaryDiffEqRosenbrock
implementation, and DAE algebraic roots delegate both the primal solve and
the implicit derivative to
nlls-gram. Use
diffrax or
SciML if you need general mass
matrices, fully implicit or higher-index DAEs, adaptive SDE stepping, events,
continuous solution objects, sparse/Krylov ODE/DAE stages, or specialized
adjoints.
Install
uv add tinydiffeq
For GPU use, install the JAX accelerator build that matches your hardware, for example:
uv add tinydiffeq "jax[cuda13]"
Minimal example
The vector field may take (x), (x, t), (x, t, args), or
(x, t, args, p) — always in that order. args is pass-through data (not an
AD target by convention); p holds differentiable parameters, and the state
may be any pytree of same-dtype real floating arrays.
import jax
import jax.numpy as jnp
from tinydiffeq import solve_ode, Tsit5, IController, SaveAt
jax.config.update("jax_enable_x64", True) # your call — the library never sets it
def f(x, t, args, p):
return -p * x
sol = solve_ode(
f, Tsit5(), 0.0, 2.0, jnp.asarray(1.0),
p=jnp.asarray(1.3),
dt_0=0.1,
controller=IController(rtol=1e-8, atol=1e-10),
max_steps=512,
save_at=SaveAt(ts=jnp.linspace(0.0, 2.0, 21)), # fixed output shape,
) # however many steps adapt
print(sol.xs) # states on the grid
print(sol.ok) # reached t_1 with every requested output valid?
max_steps is the internal attempt budget (accepted plus rejected steps),
not the number of returned times: SaveAt picks the endpoint, a fixed
interpolation grid, or the padded accepted-step prefix, so output shapes
never depend on how many steps the controller took. Omitted controller
tolerances follow the state dtype (1e-4/1e-6 in float32,
1e-7/1e-9 in float64).
SDEs with first-class noise
solve_sde integrates diagonal-noise Itô SDEs with EulerMaruyama (strong
order 0.5), Milstein (1.0, commutative diagonal noise), or SRA1 (1.5,
additive noise). An Ornstein–Uhlenbeck process under SRA1:
from tinydiffeq import solve_sde, SRA1
theta, sigma, n = 1.0, 0.5, 256
def ou_drift(x):
return -theta * x
def ou_diffusion(x):
return sigma * jnp.ones_like(x)
sol = solve_sde(
ou_drift, ou_diffusion, SRA1(), 0.0, 1.0, jnp.asarray(1.0),
key=jax.random.key(0), n_steps=n,
)
The noise realization can also be passed explicitly — the same pytree
sample_noise would draw, now inspectable, storable data that is
differentiable like any other input:
x_0 = jnp.asarray(1.0)
noise = SRA1().sample_noise(x_0, jax.random.key(0), n, jnp.asarray(1.0 / n), x_0.dtype)
same_sol = solve_sde(
ou_drift, ou_diffusion, SRA1(), 0.0, 1.0, x_0, noise=noise, n_steps=n
) # bit-identical to the key= call
d_endpoint_d_noise = jax.grad(
lambda noise: solve_sde(
ou_drift, ou_diffusion, SRA1(), 0.0, 1.0, x_0, noise=noise, n_steps=n
).xs
)(noise)
A fixed key (or fixed noise) pins the whole path, so gradients with respect
to x_0, p, and noise are pathwise derivatives under common random
numbers — the setup simulation-based estimators want. vmap over
trajectories with per-trajectory x_0 and noise composes with jit and
grad.
Semi-explicit DAEs
For a square index-1 system dy/dt = f(y, z, t, args, p) and
0 = g(y, z, t, args, p):
from tinydiffeq import solve_semi_explicit_dae
def dae_f(y, z, t, args, p):
dy = p * z
return dy, {"flow": dy}
def dae_g(y, z, t, args, p):
return z - y
dae_sol = solve_semi_explicit_dae(
dae_f, dae_g, Tsit5(), 0.0, 1.0,
jnp.asarray(1.0), jnp.asarray(0.5),
p=jnp.asarray(2.0), dt_0=0.1,
controller=IController(), max_steps=128,
)
print(dae_sol.ys, dae_sol.zs, dae_sol.aux["flow"])
z_0 is a guess and is made consistent automatically. RK4 and Tsit5 restore
the algebraic root at every stage through nlls-gram, which also supplies
the root's implicit derivative; Rodas5P() instead performs one initial
consistency solve and then advances the block mass-matrix system with one
reused LU factorization per attempt — the stiff path. Stochastic
semi-explicit systems use solve_semi_explicit_sdae with EulerMaruyama or
SRA1. See the
DAE and
SDAE docs.
Gradients through the solve
def endpoint(p):
return solve_ode(
f, Tsit5(), 0.0, 2.0, jnp.asarray(1.0), p=p,
dt_0=0.1, controller=IController(rtol=1e-10, atol=1e-12),
max_steps=512,
).xs
jax.grad(endpoint)(jnp.asarray(1.3)) # reverse mode
jax.jvp(endpoint, (jnp.asarray(1.3),), (jnp.asarray(1.0),)) # forward mode
The step-size controller is wrapped in stop_gradient (accept/reject is
non-differentiable either way); states differentiate through the solver
stages on the realized, frozen mesh. See the
docs for the design
contracts: static shapes and SaveAt, AD through adaptive stepping, SDE
noise semantics, and the package API.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tinydiffeq-2.5.0.tar.gz.
File metadata
- Download URL: tinydiffeq-2.5.0.tar.gz
- Upload date:
- Size: 683.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d17eed157c42474d8e66e7d5a265316ce9f371f0ad39668559a6fdcbdd93c56
|
|
| MD5 |
ce23da2979bbb9bb6e6b0b2864bc92a8
|
|
| BLAKE2b-256 |
1447abd21c7a1a6246f52a6de4e788d9f208d907ec76b593cd0e048cb12c8eb5
|
Provenance
The following attestation bundles were made for tinydiffeq-2.5.0.tar.gz:
Publisher:
publish.yml on HighDimensionalEconLab/tinydiffeq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tinydiffeq-2.5.0.tar.gz -
Subject digest:
0d17eed157c42474d8e66e7d5a265316ce9f371f0ad39668559a6fdcbdd93c56 - Sigstore transparency entry: 2341907139
- Sigstore integration time:
-
Permalink:
HighDimensionalEconLab/tinydiffeq@841b3e9dbe5e4e1219e06d39183e1f0021931bc2 -
Branch / Tag:
refs/tags/v2.5.0 - Owner: https://github.com/HighDimensionalEconLab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@841b3e9dbe5e4e1219e06d39183e1f0021931bc2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file tinydiffeq-2.5.0-py3-none-any.whl.
File metadata
- Download URL: tinydiffeq-2.5.0-py3-none-any.whl
- Upload date:
- Size: 65.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d6d177f55b98ddce2d41cb31f7ab8a80758ee84219b6c3c8e4198b2364f4a8e4
|
|
| MD5 |
f0d62ee7a334f38edf943f0861ffcd46
|
|
| BLAKE2b-256 |
2cf52d393cb22afae26a78163d2658e2f99dffead42fcaa20bef4ab9b7fddb9f
|
Provenance
The following attestation bundles were made for tinydiffeq-2.5.0-py3-none-any.whl:
Publisher:
publish.yml on HighDimensionalEconLab/tinydiffeq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tinydiffeq-2.5.0-py3-none-any.whl -
Subject digest:
d6d177f55b98ddce2d41cb31f7ab8a80758ee84219b6c3c8e4198b2364f4a8e4 - Sigstore transparency entry: 2341907143
- Sigstore integration time:
-
Permalink:
HighDimensionalEconLab/tinydiffeq@841b3e9dbe5e4e1219e06d39183e1f0021931bc2 -
Branch / Tag:
refs/tags/v2.5.0 - Owner: https://github.com/HighDimensionalEconLab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@841b3e9dbe5e4e1219e06d39183e1f0021931bc2 -
Trigger Event:
release
-
Statement type: