tinydiffeq 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/PKG-INFO +41 -8
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/README.md +38 -6
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/docs/api.md +6 -0
- tinydiffeq-0.3.0/docs/dae.md +120 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/docs/index.md +8 -5
- tinydiffeq-0.3.0/docs/llms.txt +12 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/mkdocs.yml +1 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/pyproject.toml +3 -2
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/src/tinydiffeq/__init__.py +11 -5
- tinydiffeq-0.3.0/src/tinydiffeq/dae.py +453 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/src/tinydiffeq/solution.py +21 -0
- tinydiffeq-0.3.0/tests/test_dae.py +406 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/tests/test_float64_subprocess.py +85 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/tests/test_gpu.py +37 -1
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/uv.lock +18 -2
- tinydiffeq-0.2.0/docs/llms.txt +0 -11
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/.githooks/pre-commit +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/.github/workflows/ci.yml +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/.github/workflows/docs.yml +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/.github/workflows/publish.yml +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/.gitignore +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/.python-version +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/LICENSE +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/docs/adaptive_ad.md +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/docs/sde.md +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/docs/static_shapes.md +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/src/tinydiffeq/controllers.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/src/tinydiffeq/interpolation.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/src/tinydiffeq/ode.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/src/tinydiffeq/quadrature.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/src/tinydiffeq/save_at.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/src/tinydiffeq/sde.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/src/tinydiffeq/solvers.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/tests/conftest.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/tests/kernels_reference.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/tests/test_ad.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/tests/test_adaptive.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/tests/test_quadrature.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/tests/test_recompile.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/tests/test_save_at.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/tests/test_sde.py +0 -0
- {tinydiffeq-0.2.0 → tinydiffeq-0.3.0}/tests/test_solvers_fixed.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tinydiffeq
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Tiny differentiable ODE/SDE solvers for JAX
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Tiny differentiable ODE/SDE/DAE solvers for JAX with static shapes and composable AD
|
|
5
5
|
Project-URL: Homepage, https://github.com/HighDimensionalEconLab/tinydiffeq
|
|
6
6
|
Project-URL: Repository, https://github.com/HighDimensionalEconLab/tinydiffeq
|
|
7
7
|
Project-URL: Documentation, https://highdimensionaleconlab.github.io/tinydiffeq/
|
|
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
19
19
|
Requires-Python: >=3.11
|
|
20
20
|
Requires-Dist: jax>=0.7.0
|
|
21
|
+
Requires-Dist: nlls-gram>=1.6.0
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
22
23
|
|
|
23
24
|
# tinydiffeq
|
|
@@ -29,9 +30,9 @@ Description-Content-Type: text/markdown
|
|
|
29
30
|
[](https://github.com/HighDimensionalEconLab/tinydiffeq/blob/main/LICENSE)
|
|
30
31
|
[](https://github.com/astral-sh/ruff)
|
|
31
32
|
|
|
32
|
-
Tiny differentiable ODE/SDE solvers for JAX: fixed-step Euler/RK4,
|
|
33
|
-
Tsit5 with integral or proportional-integral step-size control,
|
|
34
|
-
Euler–Maruyama for Itô SDEs.
|
|
33
|
+
Tiny differentiable ODE/SDE/DAE solvers for JAX: fixed-step Euler/RK4,
|
|
34
|
+
adaptive Tsit5 with integral or proportional-integral step-size control,
|
|
35
|
+
Euler–Maruyama for Itô SDEs, and nonstiff semi-explicit index-1 DAEs.
|
|
35
36
|
One bounded `lax.scan` of exactly `max_steps` iterations serves fixed and
|
|
36
37
|
adaptive stepping, so shapes are static, nothing recompiles as tolerances or
|
|
37
38
|
curvature change, and every solve is differentiable in **both** forward and
|
|
@@ -42,9 +43,9 @@ differentiates through a rollout. After a solve reaches its horizon, a
|
|
|
42
43
|
|
|
43
44
|
This is a deliberately small, jvp/vjp-friendly subset of
|
|
44
45
|
[diffrax](https://docs.kidger.site/diffrax/). Use diffrax if you need pytree
|
|
45
|
-
states, stiff
|
|
46
|
-
dense output, or checkpointed/backsolve
|
|
47
|
-
|
|
46
|
+
states, stiff or fully implicit solvers, higher-index DAEs, full
|
|
47
|
+
derivative-term PID control, events, dense output, or checkpointed/backsolve
|
|
48
|
+
adjoints. The DAE algebraic solve uses `nlls-gram`.
|
|
48
49
|
|
|
49
50
|
## Install
|
|
50
51
|
|
|
@@ -107,6 +108,38 @@ padding. Rejected attempts never appear in the returned trajectory.
|
|
|
107
108
|
the adaptive controller still chooses its own internal mesh, and cubic
|
|
108
109
|
Hermite interpolation evaluates the solution at every requested point.
|
|
109
110
|
|
|
111
|
+
## Semi-explicit DAEs
|
|
112
|
+
|
|
113
|
+
For a square index-1 system `dy/dt = f(y, z, t, args, p)` and
|
|
114
|
+
`0 = g(y, z, t, args, p)`:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from tinydiffeq import IController, Tsit5, solve_semi_explicit_dae
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def dae_f(y, z, t, args, p):
|
|
121
|
+
return p * z
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def dae_g(y, z):
|
|
125
|
+
return z - y
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
dae_sol = solve_semi_explicit_dae(
|
|
129
|
+
dae_f, dae_g, Tsit5(), 0.0, 1.0,
|
|
130
|
+
jnp.asarray(1.0), jnp.asarray(0.5),
|
|
131
|
+
p=jnp.asarray(2.0), dt_0=0.1,
|
|
132
|
+
controller=IController(), max_steps=128,
|
|
133
|
+
)
|
|
134
|
+
print(dae_sol.ys, dae_sol.zs)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`z_0` is a guess and is made consistent automatically. Algebraic roots use
|
|
138
|
+
an implicitly differentiated square LM solve, so JVP, VJP, and
|
|
139
|
+
reverse-over-forward propagate with respect to `y`, `t`, and `p`. See the
|
|
140
|
+
[DAE documentation](https://highdimensionaleconlab.github.io/tinydiffeq/dae/)
|
|
141
|
+
for root controls, `SaveAt`, and scope limits.
|
|
142
|
+
|
|
110
143
|
## Gradients through the solve
|
|
111
144
|
|
|
112
145
|
```python
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
[](https://github.com/HighDimensionalEconLab/tinydiffeq/blob/main/LICENSE)
|
|
8
8
|
[](https://github.com/astral-sh/ruff)
|
|
9
9
|
|
|
10
|
-
Tiny differentiable ODE/SDE solvers for JAX: fixed-step Euler/RK4,
|
|
11
|
-
Tsit5 with integral or proportional-integral step-size control,
|
|
12
|
-
Euler–Maruyama for Itô SDEs.
|
|
10
|
+
Tiny differentiable ODE/SDE/DAE solvers for JAX: fixed-step Euler/RK4,
|
|
11
|
+
adaptive Tsit5 with integral or proportional-integral step-size control,
|
|
12
|
+
Euler–Maruyama for Itô SDEs, and nonstiff semi-explicit index-1 DAEs.
|
|
13
13
|
One bounded `lax.scan` of exactly `max_steps` iterations serves fixed and
|
|
14
14
|
adaptive stepping, so shapes are static, nothing recompiles as tolerances or
|
|
15
15
|
curvature change, and every solve is differentiable in **both** forward and
|
|
@@ -20,9 +20,9 @@ differentiates through a rollout. After a solve reaches its horizon, a
|
|
|
20
20
|
|
|
21
21
|
This is a deliberately small, jvp/vjp-friendly subset of
|
|
22
22
|
[diffrax](https://docs.kidger.site/diffrax/). Use diffrax if you need pytree
|
|
23
|
-
states, stiff
|
|
24
|
-
dense output, or checkpointed/backsolve
|
|
25
|
-
|
|
23
|
+
states, stiff or fully implicit solvers, higher-index DAEs, full
|
|
24
|
+
derivative-term PID control, events, dense output, or checkpointed/backsolve
|
|
25
|
+
adjoints. The DAE algebraic solve uses `nlls-gram`.
|
|
26
26
|
|
|
27
27
|
## Install
|
|
28
28
|
|
|
@@ -85,6 +85,38 @@ padding. Rejected attempts never appear in the returned trajectory.
|
|
|
85
85
|
the adaptive controller still chooses its own internal mesh, and cubic
|
|
86
86
|
Hermite interpolation evaluates the solution at every requested point.
|
|
87
87
|
|
|
88
|
+
## Semi-explicit DAEs
|
|
89
|
+
|
|
90
|
+
For a square index-1 system `dy/dt = f(y, z, t, args, p)` and
|
|
91
|
+
`0 = g(y, z, t, args, p)`:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from tinydiffeq import IController, Tsit5, solve_semi_explicit_dae
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def dae_f(y, z, t, args, p):
|
|
98
|
+
return p * z
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def dae_g(y, z):
|
|
102
|
+
return z - y
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
dae_sol = solve_semi_explicit_dae(
|
|
106
|
+
dae_f, dae_g, Tsit5(), 0.0, 1.0,
|
|
107
|
+
jnp.asarray(1.0), jnp.asarray(0.5),
|
|
108
|
+
p=jnp.asarray(2.0), dt_0=0.1,
|
|
109
|
+
controller=IController(), max_steps=128,
|
|
110
|
+
)
|
|
111
|
+
print(dae_sol.ys, dae_sol.zs)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`z_0` is a guess and is made consistent automatically. Algebraic roots use
|
|
115
|
+
an implicitly differentiated square LM solve, so JVP, VJP, and
|
|
116
|
+
reverse-over-forward propagate with respect to `y`, `t`, and `p`. See the
|
|
117
|
+
[DAE documentation](https://highdimensionaleconlab.github.io/tinydiffeq/dae/)
|
|
118
|
+
for root controls, `SaveAt`, and scope limits.
|
|
119
|
+
|
|
88
120
|
## Gradients through the solve
|
|
89
121
|
|
|
90
122
|
```python
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
::: tinydiffeq.solve_ode
|
|
6
6
|
|
|
7
|
+
::: tinydiffeq.solve_semi_explicit_dae
|
|
8
|
+
|
|
7
9
|
::: tinydiffeq.solve_sde
|
|
8
10
|
|
|
9
11
|
## Solvers
|
|
@@ -28,6 +30,10 @@
|
|
|
28
30
|
|
|
29
31
|
::: tinydiffeq.SaveAt
|
|
30
32
|
|
|
33
|
+
::: tinydiffeq.LMRootSolver
|
|
34
|
+
|
|
35
|
+
::: tinydiffeq.DAESolution
|
|
36
|
+
|
|
31
37
|
::: tinydiffeq.Solution
|
|
32
38
|
|
|
33
39
|
## Utilities
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Semi-Explicit Index-1 DAEs
|
|
2
|
+
|
|
3
|
+
`solve_semi_explicit_dae` integrates nonstiff systems of the form
|
|
4
|
+
|
|
5
|
+
$$
|
|
6
|
+
\dot y = f(y, z, t, \mathrm{args}, p), \qquad
|
|
7
|
+
0 = g(y, z, t, \mathrm{args}, p),
|
|
8
|
+
$$
|
|
9
|
+
|
|
10
|
+
where the algebraic equation is square and $g_z$ is nonsingular along the
|
|
11
|
+
solution. `y` and `z` are array states. The implementation supports fixed-step
|
|
12
|
+
RK4 and Tsit5 with fixed or adaptive control.
|
|
13
|
+
|
|
14
|
+
The algebraic solve uses
|
|
15
|
+
[`nlls-gram`](https://highdimensionaleconlab.github.io/nlls_gram/square_systems/)'s
|
|
16
|
+
solve-only `SquareLevenbergMarquardt`: an augmented-QR damped step for the
|
|
17
|
+
primal root and a direct implicit Jacobian solve for derivatives.
|
|
18
|
+
|
|
19
|
+
## Minimal examples
|
|
20
|
+
|
|
21
|
+
Consider
|
|
22
|
+
|
|
23
|
+
$$
|
|
24
|
+
\dot y = pz, \qquad 0=z-y,
|
|
25
|
+
$$
|
|
26
|
+
|
|
27
|
+
whose reduced solution is $y(t)=z(t)=y_0e^{pt}$.
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import jax.numpy as jnp
|
|
31
|
+
|
|
32
|
+
from tinydiffeq import (
|
|
33
|
+
IController,
|
|
34
|
+
RK4,
|
|
35
|
+
Tsit5,
|
|
36
|
+
solve_semi_explicit_dae,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def f(y, z, t, args, p):
|
|
41
|
+
return p * z
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def g(y, z):
|
|
45
|
+
return z - y
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
fixed = solve_semi_explicit_dae(
|
|
49
|
+
f, g, RK4(), 0.0, 1.0,
|
|
50
|
+
jnp.asarray(1.0), jnp.asarray(0.5),
|
|
51
|
+
p=jnp.asarray(2.0), dt_0=0.01, max_steps=100,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
adaptive = solve_semi_explicit_dae(
|
|
55
|
+
f, g, Tsit5(), 0.0, 1.0,
|
|
56
|
+
jnp.asarray(1.0), jnp.asarray(0.5),
|
|
57
|
+
p=jnp.asarray(2.0), dt_0=0.1,
|
|
58
|
+
controller=IController(), max_steps=128,
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`z_0` is a root-finding guess, not an assumed-consistent initial value. Both
|
|
63
|
+
calls first solve `g(y_0, z, t_0, args, p) = 0`, so the `0.5` guess becomes
|
|
64
|
+
the consistent value `1.0`.
|
|
65
|
+
|
|
66
|
+
## Nonlinear-solve and AD contract
|
|
67
|
+
|
|
68
|
+
The default root configuration is:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from tinydiffeq import LMRootSolver
|
|
72
|
+
|
|
73
|
+
root_solver = LMRootSolver(
|
|
74
|
+
max_steps=8,
|
|
75
|
+
atol=None, # 1e-6 float32, 1e-10 float64
|
|
76
|
+
init_damping=1e-3,
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The outer `max_steps` counts attempted time steps, including adaptive
|
|
81
|
+
rejections. `root_solver.max_steps` separately bounds one algebraic root.
|
|
82
|
+
|
|
83
|
+
Every root passes `(y, t, p)` through nlls-gram's differentiated parameter
|
|
84
|
+
pytree. Thus it differentiates the defining constraint,
|
|
85
|
+
|
|
86
|
+
$$
|
|
87
|
+
\dot z = -g_z^{-1}
|
|
88
|
+
(g_y\dot y + g_t\dot t + g_p\dot p),
|
|
89
|
+
$$
|
|
90
|
+
|
|
91
|
+
rather than differentiating the LM iterations. The warm-start guess has zero
|
|
92
|
+
derivative by design. `args` is fixed data; put every differentiated model
|
|
93
|
+
quantity in `p`. JVP, VJP, `vmap`, and reverse-over-forward compose through
|
|
94
|
+
the complete DAE solve.
|
|
95
|
+
|
|
96
|
+
An adaptive stage-root failure rejects the time-step attempt and asks the
|
|
97
|
+
controller for a smaller step. A fixed-step root failure terminates the solve.
|
|
98
|
+
In either case `sol.ok` is false if the endpoint is not reached with valid
|
|
99
|
+
algebraic states.
|
|
100
|
+
|
|
101
|
+
## Saving output
|
|
102
|
+
|
|
103
|
+
All `SaveAt` modes are supported:
|
|
104
|
+
|
|
105
|
+
- `SaveAt(t_1=True)` returns the consistent endpoint.
|
|
106
|
+
- `SaveAt(steps=True)` returns the initial point and accepted internal steps
|
|
107
|
+
as a padded `max_steps + 1` buffer with the usual `accepted` mask.
|
|
108
|
+
- `SaveAt(ts=grid)` Hermite-interpolates `y` onto the requested grid and then
|
|
109
|
+
solves `g=0` at each requested time. It never interpolates `z` independently,
|
|
110
|
+
so every returned algebraic state satisfies the constraint.
|
|
111
|
+
|
|
112
|
+
The result is `DAESolution(ts, ys, zs, ok, num_accepted, accepted)`.
|
|
113
|
+
|
|
114
|
+
## Deliberate limits
|
|
115
|
+
|
|
116
|
+
This is an explicit, nonstiff, semi-explicit index-1 integrator. It does not
|
|
117
|
+
support mass-matrix or fully implicit DAEs, stiff implicit time stepping,
|
|
118
|
+
higher-index constraints, or automatic index reduction. It is an initial-value
|
|
119
|
+
solver: it does not determine unknown initial costates or solve boundary-value
|
|
120
|
+
or saddle-path conditions.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# tinydiffeq
|
|
2
2
|
|
|
3
|
-
`tinydiffeq` is a deliberately tiny set of differentiable ODE/SDE
|
|
4
|
-
for JAX: fixed-step Euler and RK4, adaptive Tsit5 with integral or
|
|
5
|
-
proportional-integral step-size control,
|
|
6
|
-
|
|
3
|
+
`tinydiffeq` is a deliberately tiny set of differentiable ODE/SDE/DAE
|
|
4
|
+
integrators for JAX: fixed-step Euler and RK4, adaptive Tsit5 with integral or
|
|
5
|
+
proportional-integral step-size control, fixed-step Euler–Maruyama for Itô
|
|
6
|
+
SDEs, and nonstiff semi-explicit index-1 DAEs. Time integration runs
|
|
7
7
|
inside one bounded `lax.scan` with static shapes, and every solve is
|
|
8
8
|
differentiable in **both** forward and reverse mode — including
|
|
9
9
|
reverse-over-forward, the pattern a Levenberg–Marquardt optimizer with
|
|
@@ -14,7 +14,7 @@ It is a jvp/vjp-friendly subset of
|
|
|
14
14
|
need any of:**
|
|
15
15
|
|
|
16
16
|
- pytree states (tinydiffeq states are single arrays, scalar or vector)
|
|
17
|
-
- stiff or implicit solvers
|
|
17
|
+
- stiff or fully implicit solvers, or higher-index DAEs
|
|
18
18
|
- full derivative-term PID step-size control
|
|
19
19
|
- events, root-finding, or backward-time integration
|
|
20
20
|
- dense output / continuous interpolation objects
|
|
@@ -61,6 +61,9 @@ The arity is inspected once and the function is wrapped into the canonical
|
|
|
61
61
|
four-argument form, so the compiled code is identical for all four. There is
|
|
62
62
|
no special autonomous code path: an unused `t` is dead-code-eliminated.
|
|
63
63
|
`drift` and `diffusion` in [`solve_sde`](sde.md) follow the same convention.
|
|
64
|
+
Semi-explicit DAE fields use `(y, z)`, `(y, z, t)`,
|
|
65
|
+
`(y, z, t, args)`, or `(y, z, t, args, p)`; see
|
|
66
|
+
[Semi-Explicit DAEs](dae.md).
|
|
64
67
|
|
|
65
68
|
## Minimal example
|
|
66
69
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# tinydiffeq
|
|
2
|
+
|
|
3
|
+
> Tiny differentiable ODE/SDE/DAE solvers for JAX. Fixed-step (Euler, RK4) and adaptive (Tsit5 + integral or proportional-integral controller) explicit Runge-Kutta inside one bounded lax.scan of exactly max_steps attempted steps: static shapes, one compilation across explicit controller values/curvature/initial conditions, and solves differentiable in both forward and reverse mode (including reverse-over-forward) with O(max_steps) memory. Nonstiff semi-explicit index-1 DAEs use RK4 or Tsit5 plus nlls-gram's square LM algebraic solve, whose custom JVP implicitly differentiates each root with respect to the differential state, time, and parameters. Adaptive-controller defaults are precision-aware: rtol=1e-4, atol=1e-6 for float32 and rtol=1e-7, atol=1e-9 for float64; dt_min is 10*eps*max(1, abs(t_1)) in the time dtype. Expensive solver work is skipped after the horizon via lax.cond while the scan emits its padded tail. Array states only (scalar or vector). SaveAt selects endpoint, cubic-Hermite interpolation onto a requested grid, or a compact accepted-step prefix padded to max_steps + 1. For DAE observation grids, only the differential state is interpolated and the algebraic constraint is re-solved. solve_sde is fixed-step Euler-Maruyama with presampled diagonal noise keyed on a PRNG key. A deliberately small subset of diffrax: use diffrax for pytree states, stiff or fully implicit solvers, higher-index DAEs, full derivative-term PID control, events, dense output, and adjoint methods.
|
|
4
|
+
|
|
5
|
+
## Docs
|
|
6
|
+
|
|
7
|
+
- [Home — positioning, vector-field signature convention f(x, t, args, p), minimal examples](https://highdimensionaleconlab.github.io/tinydiffeq/)
|
|
8
|
+
- [Static shapes — the bounded-scan design, SaveAt observation grids, compact accepted steps and padding, why nothing recompiles](https://highdimensionaleconlab.github.io/tinydiffeq/static_shapes/)
|
|
9
|
+
- [Adaptive stepping and AD — stop-gradiented controller rationale, horizon-clip growth guard, non-differentiable interpolation knots, double-where NaN safety](https://highdimensionaleconlab.github.io/tinydiffeq/adaptive_ad/)
|
|
10
|
+
- [Semi-explicit DAEs — index-1 contract, algebraic LM roots, implicit AD, failure behavior, and SaveAt](https://highdimensionaleconlab.github.io/tinydiffeq/dae/)
|
|
11
|
+
- [SDEs — Euler-Maruyama orders, fixed-noise key semantics, shared-path strong-convergence testing, why SaveAt(ts) raises](https://highdimensionaleconlab.github.io/tinydiffeq/sde/)
|
|
12
|
+
- [API reference — ODE/SDE/DAE solves, solvers, controllers, root configuration, SaveAt, solution types, interpolation, and quadrature](https://highdimensionaleconlab.github.io/tinydiffeq/api/)
|
|
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tinydiffeq"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Tiny differentiable ODE/SDE solvers for JAX
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Tiny differentiable ODE/SDE/DAE solvers for JAX with static shapes and composable AD"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
license-files = ["LICENSE"]
|
|
@@ -29,6 +29,7 @@ classifiers = [
|
|
|
29
29
|
]
|
|
30
30
|
dependencies = [
|
|
31
31
|
"jax>=0.7.0",
|
|
32
|
+
"nlls-gram>=1.6.0",
|
|
32
33
|
]
|
|
33
34
|
|
|
34
35
|
[project.urls]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Tiny differentiable ODE/SDE solvers for JAX.
|
|
1
|
+
"""Tiny differentiable ODE/SDE/DAE solvers for JAX.
|
|
2
2
|
|
|
3
3
|
solve_ode integrates dx/dt = f(x, t, args, p) with fixed-step (Euler, RK4)
|
|
4
4
|
or adaptive (Tsit5 + IController/PIController) explicit Runge-Kutta methods
|
|
@@ -8,22 +8,26 @@ static and solves are differentiable in BOTH forward and reverse mode
|
|
|
8
8
|
output: the endpoint, cubic-Hermite interpolation onto a fixed grid, or
|
|
9
9
|
accepted internal steps with fixed-shape padding. solve_sde is fixed-step
|
|
10
10
|
Euler-Maruyama with presampled
|
|
11
|
-
diagonal noise.
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
diagonal noise. solve_semi_explicit_dae handles nonstiff index-1 systems with
|
|
12
|
+
an implicitly differentiated algebraic root. States are arrays (scalar or
|
|
13
|
+
vector); pytree states, stiff or fully implicit solvers, full derivative-term
|
|
14
|
+
PID control, events, dense output, and adjoint methods are non-goals — use
|
|
15
|
+
diffrax for those.
|
|
14
16
|
"""
|
|
15
17
|
|
|
16
18
|
from tinydiffeq.controllers import ConstantStepSize, IController, PIController
|
|
19
|
+
from tinydiffeq.dae import LMRootSolver, solve_semi_explicit_dae
|
|
17
20
|
from tinydiffeq.interpolation import hermite_interpolate
|
|
18
21
|
from tinydiffeq.ode import solve_ode
|
|
19
22
|
from tinydiffeq.quadrature import cumulative_trapezoid
|
|
20
23
|
from tinydiffeq.save_at import SaveAt
|
|
21
24
|
from tinydiffeq.sde import solve_sde
|
|
22
|
-
from tinydiffeq.solution import Solution
|
|
25
|
+
from tinydiffeq.solution import DAESolution, Solution
|
|
23
26
|
from tinydiffeq.solvers import RK4, Euler, EulerMaruyama, Tsit5
|
|
24
27
|
|
|
25
28
|
__all__ = [
|
|
26
29
|
"solve_ode",
|
|
30
|
+
"solve_semi_explicit_dae",
|
|
27
31
|
"solve_sde",
|
|
28
32
|
"Euler",
|
|
29
33
|
"RK4",
|
|
@@ -34,6 +38,8 @@ __all__ = [
|
|
|
34
38
|
"PIController",
|
|
35
39
|
"SaveAt",
|
|
36
40
|
"Solution",
|
|
41
|
+
"DAESolution",
|
|
42
|
+
"LMRootSolver",
|
|
37
43
|
"hermite_interpolate",
|
|
38
44
|
"cumulative_trapezoid",
|
|
39
45
|
]
|