Skip to main content

tinydiffeq

CI Docs PyPI Python versions License: MIT Ruff

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

tinydiffeq-2.5.0.tar.gz (683.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tinydiffeq-2.5.0-py3-none-any.whl (65.3 kB view details)

Uploaded Python 3

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

Hashes for tinydiffeq-2.5.0.tar.gz
Algorithm Hash digest
SHA256 0d17eed157c42474d8e66e7d5a265316ce9f371f0ad39668559a6fdcbdd93c56
MD5 ce23da2979bbb9bb6e6b0b2864bc92a8
BLAKE2b-256 1447abd21c7a1a6246f52a6de4e788d9f208d907ec76b593cd0e048cb12c8eb5

See more details on using hashes here.

Provenance

The following attestation bundles were made for tinydiffeq-2.5.0.tar.gz:

Publisher: publish.yml on HighDimensionalEconLab/tinydiffeq

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for tinydiffeq-2.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d6d177f55b98ddce2d41cb31f7ab8a80758ee84219b6c3c8e4198b2364f4a8e4
MD5 f0d62ee7a334f38edf943f0861ffcd46
BLAKE2b-256 2cf52d393cb22afae26a78163d2658e2f99dffead42fcaa20bef4ab9b7fddb9f

See more details on using hashes here.

Provenance

The following attestation bundles were made for tinydiffeq-2.5.0-py3-none-any.whl:

Publisher: publish.yml on HighDimensionalEconLab/tinydiffeq

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

2.6.1

2 files

2.6.0

2 files

This release

2.5.0 This release

2 files

2.4.0

2 files

2.3.0

2 files

2.2.0

2 files

2.1.0

2 files

2.0.0

2 files

1.1.0

2 files

1.0.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page