OpenReservoirComputing 0.1.0__py3-none-any.whl
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.
- openreservoircomputing-0.1.0.dist-info/METADATA +168 -0
- openreservoircomputing-0.1.0.dist-info/RECORD +28 -0
- openreservoircomputing-0.1.0.dist-info/WHEEL +5 -0
- openreservoircomputing-0.1.0.dist-info/licenses/LICENSE +201 -0
- openreservoircomputing-0.1.0.dist-info/top_level.txt +1 -0
- orc/__init__.py +29 -0
- orc/classifier/__init__.py +6 -0
- orc/classifier/base.py +6 -0
- orc/classifier/models.py +6 -0
- orc/classifier/train.py +6 -0
- orc/control/__init__.py +15 -0
- orc/control/base.py +362 -0
- orc/control/models.py +139 -0
- orc/control/train.py +90 -0
- orc/data/__init__.py +27 -0
- orc/data/integrators.py +753 -0
- orc/drivers.py +851 -0
- orc/embeddings.py +459 -0
- orc/forecaster/__init__.py +20 -0
- orc/forecaster/base.py +487 -0
- orc/forecaster/models.py +416 -0
- orc/forecaster/train.py +301 -0
- orc/readouts.py +742 -0
- orc/tuning/__init__.py +6 -0
- orc/utils/__init__.py +6 -0
- orc/utils/numerics.py +115 -0
- orc/utils/regressions.py +73 -0
- orc/utils/visualization.py +193 -0
orc/data/integrators.py
ADDED
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
"""Integrators for solving ODEs and PDEs."""
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
|
|
5
|
+
import diffrax
|
|
6
|
+
import jax
|
|
7
|
+
import jax.numpy as jnp
|
|
8
|
+
from jaxtyping import Array, Float
|
|
9
|
+
|
|
10
|
+
jax.config.update("jax_enable_x64", True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# TODO: typing
|
|
14
|
+
######################## Basic Chaotic ODEs ########################
|
|
15
|
+
@jax.jit
|
|
16
|
+
def _lorenz63_f(t, u, args):
|
|
17
|
+
"""Define Lorenz 63 ODE."""
|
|
18
|
+
rho, sigma, beta = args
|
|
19
|
+
u1, u2, u3 = u
|
|
20
|
+
du1dt = sigma * (u2 - u1)
|
|
21
|
+
du2dt = u1 * (rho - u3) - u2
|
|
22
|
+
du3dt = u1 * u2 - beta * u3
|
|
23
|
+
dudt = du1dt, du2dt, du3dt
|
|
24
|
+
return jnp.array(dudt)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def lorenz63(
|
|
28
|
+
tN: Float,
|
|
29
|
+
dt: Float,
|
|
30
|
+
u0: Float[Array, "3"] = (-10.0, 1.0, 10.0),
|
|
31
|
+
rho: Float = 28.0,
|
|
32
|
+
sigma: Float = 10.0,
|
|
33
|
+
beta: Float = 8.0 / 3.0,
|
|
34
|
+
**diffeqsolve_kwargs,
|
|
35
|
+
) -> tuple[Float, Float]:
|
|
36
|
+
"""Solve the Lorenz 63 system of ODEs.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
tN : float
|
|
41
|
+
The final time to solve the ODEs to.
|
|
42
|
+
dt : float
|
|
43
|
+
The time step size for the interpolated solution. Will be overridden if
|
|
44
|
+
`diffeqsolve_kwargs` contains a saveat argument with a different time grid.
|
|
45
|
+
u0 : jnp.ndarray, optional
|
|
46
|
+
The initial conditions for the ODEs. Default is (-10, 1, 10).
|
|
47
|
+
rho : float, optional
|
|
48
|
+
The rho parameter for the Lorenz system. Default is 28.0.
|
|
49
|
+
sigma : float, optional
|
|
50
|
+
The sigma parameter for the Lorenz system. Default is 10.0.
|
|
51
|
+
beta : float, optional
|
|
52
|
+
The beta parameter for the Lorenz system. Default is 8.0/3.0.
|
|
53
|
+
diffeqsolve_kwargs : dict, optional
|
|
54
|
+
Additional keyword arguments to pass to the `diffrax.diffeqsolve` function.
|
|
55
|
+
Default solver is `diffrax.Tsit5()`, saveat is set to a grid of times from 0 to
|
|
56
|
+
tN with step size dt, and stepsize_controller is set to
|
|
57
|
+
`diffrax.PIDController(rtol=1e-7, atol=1e-9)`. dt0 is set to dt. max_steps is
|
|
58
|
+
set to None.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
us : jnp.ndarray
|
|
63
|
+
The solution array with shape (Nt, 3), where Nt is the number of time steps.
|
|
64
|
+
ts : jnp.ndarray
|
|
65
|
+
The time vector corresponding to the solution steps.
|
|
66
|
+
"""
|
|
67
|
+
# set kwarg defaults
|
|
68
|
+
diffeqsolve_kwargs.setdefault("solver", diffrax.Tsit5())
|
|
69
|
+
diffeqsolve_kwargs.setdefault("saveat", diffrax.SaveAt(ts=jnp.arange(0, tN, dt)))
|
|
70
|
+
diffeqsolve_kwargs.setdefault(
|
|
71
|
+
"stepsize_controller", diffrax.PIDController(rtol=1e-7, atol=1e-9)
|
|
72
|
+
)
|
|
73
|
+
diffeqsolve_kwargs.setdefault("dt0", dt)
|
|
74
|
+
diffeqsolve_kwargs.setdefault("max_steps", None)
|
|
75
|
+
|
|
76
|
+
# solve
|
|
77
|
+
u0 = jnp.array(u0)
|
|
78
|
+
term = diffrax.ODETerm(_lorenz63_f)
|
|
79
|
+
args = (rho, sigma, beta)
|
|
80
|
+
sol = diffrax.diffeqsolve(term, t0=0, t1=tN, y0=u0, args=args, **diffeqsolve_kwargs)
|
|
81
|
+
us = sol.ys
|
|
82
|
+
return us, sol.ts
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@jax.jit
|
|
86
|
+
def _rossler_f(t, u, args):
|
|
87
|
+
"""Define Rössler ODE."""
|
|
88
|
+
a, b, c = args
|
|
89
|
+
u1, u2, u3 = u
|
|
90
|
+
du1dt = -u2 - u3
|
|
91
|
+
du2dt = u1 + a * u2
|
|
92
|
+
du3dt = b + u3 * (u1 - c)
|
|
93
|
+
dudt = du1dt, du2dt, du3dt
|
|
94
|
+
return jnp.array(dudt)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def rossler(
|
|
98
|
+
tN: Float,
|
|
99
|
+
dt: Float,
|
|
100
|
+
u0: Float[Array, "3"] = (1.0, 1.0, 1.0),
|
|
101
|
+
a: Float = 0.1,
|
|
102
|
+
b: Float = 0.1,
|
|
103
|
+
c: Float = 14.0,
|
|
104
|
+
**diffeqsolve_kwargs,
|
|
105
|
+
) -> tuple[Float, Float]:
|
|
106
|
+
"""Solve the Rossler system of ODEs.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
tN : float
|
|
111
|
+
The final time to solve the ODEs to.
|
|
112
|
+
dt : float
|
|
113
|
+
The time step size for the interpolated solution. Will be overridden if
|
|
114
|
+
`diffeqsolve_kwargs` contains a saveat argument with a different time grid.
|
|
115
|
+
u0 : jnp.ndarray, optional
|
|
116
|
+
The initial conditions for the ODEs. Default is (1.0, 1.0, 1.0).
|
|
117
|
+
a : float, optional
|
|
118
|
+
The a parameter for the Rössler system. Default is 0.1.
|
|
119
|
+
b : float, optional
|
|
120
|
+
The b parameter for the Rössler system. Default is 0.1.
|
|
121
|
+
c : float, optional
|
|
122
|
+
The c parameter for the Rössler system. Default is 14.0.
|
|
123
|
+
diffeqsolve_kwargs : dict, optional
|
|
124
|
+
Additional keyword arguments to pass to the `diffrax.diffeqsolve` function.
|
|
125
|
+
Default solver is `diffrax.Tsit5()`, saveat is set to a grid of times from 0 to
|
|
126
|
+
tN with step size dt, and stepsize_controller is set to
|
|
127
|
+
`diffrax.PIDController(rtol=1e-7, atol=1e-9)`. dt0 is set to dt. max_steps is
|
|
128
|
+
set to None.
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
us : jnp.ndarray
|
|
133
|
+
The solution array with shape (Nt, 3), where Nt is the number of time steps.
|
|
134
|
+
ts : jnp.ndarray
|
|
135
|
+
The time vector corresponding to the solution steps.
|
|
136
|
+
"""
|
|
137
|
+
# set kwarg defaults
|
|
138
|
+
diffeqsolve_kwargs.setdefault("solver", diffrax.Tsit5())
|
|
139
|
+
diffeqsolve_kwargs.setdefault("saveat", diffrax.SaveAt(ts=jnp.arange(0, tN, dt)))
|
|
140
|
+
diffeqsolve_kwargs.setdefault(
|
|
141
|
+
"stepsize_controller", diffrax.PIDController(rtol=1e-7, atol=1e-9)
|
|
142
|
+
)
|
|
143
|
+
diffeqsolve_kwargs.setdefault("dt0", dt)
|
|
144
|
+
diffeqsolve_kwargs.setdefault("max_steps", None)
|
|
145
|
+
|
|
146
|
+
# solve
|
|
147
|
+
u0 = jnp.array(u0)
|
|
148
|
+
term = diffrax.ODETerm(_rossler_f)
|
|
149
|
+
args = (a, b, c)
|
|
150
|
+
sol = diffrax.diffeqsolve(term, t0=0, t1=tN, y0=u0, args=args, **diffeqsolve_kwargs)
|
|
151
|
+
us = sol.ys
|
|
152
|
+
return us, sol.ts
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@jax.jit
|
|
156
|
+
def _sakaraya_f(t, u, args):
|
|
157
|
+
"""Define Sakaraya ODE."""
|
|
158
|
+
a, b, m = args
|
|
159
|
+
u1, u2, u3 = u
|
|
160
|
+
du1dt = a * u1 + u2 + u2 * u3
|
|
161
|
+
du2dt = -u1 * u3 + u2 * u3
|
|
162
|
+
du3dt = -u3 - m * u1 * u2 + b
|
|
163
|
+
|
|
164
|
+
return jnp.array([du1dt, du2dt, du3dt])
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def sakaraya(
|
|
168
|
+
tN: Float,
|
|
169
|
+
dt: Float,
|
|
170
|
+
u0: Float[Array, "3"] = (-2.8976045, 3.8877978, 3.07465),
|
|
171
|
+
a: Float = 1.0,
|
|
172
|
+
b: Float = 1.0,
|
|
173
|
+
m: Float = 1.0,
|
|
174
|
+
**diffeqsolve_kwargs,
|
|
175
|
+
) -> tuple[Float, Float]:
|
|
176
|
+
"""Solve the Sakaraya system of ODEs.
|
|
177
|
+
|
|
178
|
+
Parameters
|
|
179
|
+
----------
|
|
180
|
+
tN : float
|
|
181
|
+
The final time to solve the ODEs to.
|
|
182
|
+
dt : float
|
|
183
|
+
The time step size for the interpolated solution. Will be overridden if
|
|
184
|
+
`diffeqsolve_kwargs` contains a saveat argument with a different time grid.
|
|
185
|
+
u0 : jnp.ndarray, optional
|
|
186
|
+
The initial conditions for the ODEs. Default is
|
|
187
|
+
(-2.8976045, 3.8877978, 3.07465).
|
|
188
|
+
a : float, optional
|
|
189
|
+
The a parameter for the Sakaraya system. Default is 1.0.
|
|
190
|
+
b : float, optional
|
|
191
|
+
The b parameter for the Sakaraya system. Default is 1.0.
|
|
192
|
+
m : float, optional
|
|
193
|
+
The m parameter for the Sakaraya system. Default is 1.0.
|
|
194
|
+
diffeqsolve_kwargs : dict, optional
|
|
195
|
+
Additional keyword arguments to pass to the `diffrax.diffeqsolve` function.
|
|
196
|
+
Default solver is `diffrax.Tsit5()`, saveat is set to a grid of times from 0 to
|
|
197
|
+
tN with step size dt, and stepsize_controller is set to
|
|
198
|
+
`diffrax.PIDController(rtol=1e-7, atol=1e-9)`. dt0 is set to dt. max_steps is
|
|
199
|
+
set to None.
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
-------
|
|
203
|
+
us : jnp.ndarray
|
|
204
|
+
The solution array with shape (Nt, 3), where Nt is the number of time steps.
|
|
205
|
+
ts : jnp.ndarray
|
|
206
|
+
The time vector corresponding to the solution steps.
|
|
207
|
+
"""
|
|
208
|
+
# set kwarg defaults
|
|
209
|
+
diffeqsolve_kwargs.setdefault("solver", diffrax.Tsit5())
|
|
210
|
+
diffeqsolve_kwargs.setdefault("saveat", diffrax.SaveAt(ts=jnp.arange(0, tN, dt)))
|
|
211
|
+
diffeqsolve_kwargs.setdefault(
|
|
212
|
+
"stepsize_controller", diffrax.PIDController(rtol=1e-7, atol=1e-9)
|
|
213
|
+
)
|
|
214
|
+
diffeqsolve_kwargs.setdefault("dt0", dt)
|
|
215
|
+
diffeqsolve_kwargs.setdefault("max_steps", None)
|
|
216
|
+
|
|
217
|
+
# solve
|
|
218
|
+
u0 = jnp.array(u0)
|
|
219
|
+
term = diffrax.ODETerm(_sakaraya_f)
|
|
220
|
+
args = (a, b, m)
|
|
221
|
+
sol = diffrax.diffeqsolve(term, t0=0, t1=tN, y0=u0, args=args, **diffeqsolve_kwargs)
|
|
222
|
+
us = sol.ys
|
|
223
|
+
return us, sol.ts
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@jax.jit
|
|
227
|
+
def _colpitts_f(t, u, args):
|
|
228
|
+
"""Define Colpitts oscillator ODE."""
|
|
229
|
+
u1, u2, u3 = u
|
|
230
|
+
alpha, gamma, q, eta = args
|
|
231
|
+
du1dt = alpha * u2
|
|
232
|
+
du2dt = -gamma * (u1 + u3) - q * u2
|
|
233
|
+
du3dt = eta * (u2 + 1 - jnp.exp(-u1))
|
|
234
|
+
return jnp.array([du1dt, du2dt, du3dt])
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def colpitts(
|
|
238
|
+
tN: Float,
|
|
239
|
+
dt: Float,
|
|
240
|
+
u0: Float[Array, "3"] = (1.0, -1.0, 1.0),
|
|
241
|
+
alpha: Float = 5.0,
|
|
242
|
+
gamma: Float = 0.0797,
|
|
243
|
+
q: Float = 0.6898,
|
|
244
|
+
eta: Float = 6.2723,
|
|
245
|
+
**diffeqsolve_kwargs,
|
|
246
|
+
) -> tuple[Float, Float]:
|
|
247
|
+
"""Solve the Colpitts oscillator system of ODEs.
|
|
248
|
+
|
|
249
|
+
Parameters
|
|
250
|
+
----------
|
|
251
|
+
tN : float
|
|
252
|
+
The final time to solve the ODEs to.
|
|
253
|
+
dt : float
|
|
254
|
+
The time step size for the interpolated solution. Will be overridden if
|
|
255
|
+
`diffeqsolve_kwargs` contains a saveat argument with a different time grid.
|
|
256
|
+
u0 : jnp.ndarray, optional
|
|
257
|
+
The initial conditions for the ODEs. Default is (1.0, -1.0, 1.0).
|
|
258
|
+
alpha : float, optional
|
|
259
|
+
The alpha parameter for the Colpitts oscillator system. Default is
|
|
260
|
+
5.0 (Platt, 2020).
|
|
261
|
+
gamma : float, optional
|
|
262
|
+
The gamma parameter for the Colpitts oscillator system. Default is
|
|
263
|
+
0.0797 (Platt, 2020).
|
|
264
|
+
q : float, optional
|
|
265
|
+
The q parameter for the Colpitts oscillator system. Default is
|
|
266
|
+
0.6898 (Platt, 2020).
|
|
267
|
+
eta : float, optional
|
|
268
|
+
The eta parameter for the Colpitts oscillator system. Default is
|
|
269
|
+
6.2723 (Platt, 2020).
|
|
270
|
+
diffeqsolve_kwargs : dict, optional
|
|
271
|
+
Additional keyword arguments to pass to the `diffrax.diffeqsolve` function.
|
|
272
|
+
Default solver is `diffrax.Tsit5()`, saveat is set to a grid of times from 0 to
|
|
273
|
+
tN with step size dt, and stepsize_controller is set to
|
|
274
|
+
`diffrax.PIDController(rtol=1e-7, atol=1e-9)`. dt0 is set to dt. max_steps is
|
|
275
|
+
set to None.
|
|
276
|
+
|
|
277
|
+
Returns
|
|
278
|
+
-------
|
|
279
|
+
us : jnp.ndarray
|
|
280
|
+
The solution array with shape (Nt, 3), where Nt is the number of time steps.
|
|
281
|
+
ts : jnp.ndarray
|
|
282
|
+
The time vector corresponding to the solution steps.
|
|
283
|
+
"""
|
|
284
|
+
# set kwarg defaults
|
|
285
|
+
diffeqsolve_kwargs.setdefault("solver", diffrax.Tsit5())
|
|
286
|
+
diffeqsolve_kwargs.setdefault("saveat", diffrax.SaveAt(ts=jnp.arange(0, tN, dt)))
|
|
287
|
+
diffeqsolve_kwargs.setdefault(
|
|
288
|
+
"stepsize_controller", diffrax.PIDController(rtol=1e-7, atol=1e-9)
|
|
289
|
+
)
|
|
290
|
+
diffeqsolve_kwargs.setdefault("dt0", dt)
|
|
291
|
+
diffeqsolve_kwargs.setdefault("max_steps", None)
|
|
292
|
+
|
|
293
|
+
# solve
|
|
294
|
+
u0 = jnp.array(u0)
|
|
295
|
+
term = diffrax.ODETerm(_colpitts_f)
|
|
296
|
+
args = (alpha, gamma, q, eta)
|
|
297
|
+
sol = diffrax.diffeqsolve(term, t0=0, t1=tN, y0=u0, args=args, **diffeqsolve_kwargs)
|
|
298
|
+
us = sol.ys
|
|
299
|
+
return us, sol.ts
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
######################## Hyper-chaotic ODEs ########################
|
|
303
|
+
@jax.jit
|
|
304
|
+
def _hyper_lorenz63_f(t, u, args):
|
|
305
|
+
"""Define Hyper-Lorenz 63 ODE."""
|
|
306
|
+
a, b, c, d = args
|
|
307
|
+
u1, u2, u3, u4 = u
|
|
308
|
+
du1dt = a * (u2 - u1) + u4
|
|
309
|
+
du2dt = u1 * (b - u3) - u2
|
|
310
|
+
du3dt = u1 * u2 - c * u3
|
|
311
|
+
du4dt = d * u4 - u2 * u3
|
|
312
|
+
return jnp.array([du1dt, du2dt, du3dt, du4dt])
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def hyper_lorenz63(
|
|
316
|
+
tN: Float,
|
|
317
|
+
dt: Float,
|
|
318
|
+
u0: Float[Array, "4"] = (-10.0, 6.0, 0.0, 10.0),
|
|
319
|
+
a: Float = 10.0,
|
|
320
|
+
b: Float = 28.0,
|
|
321
|
+
c: Float = 8.0 / 3.0,
|
|
322
|
+
d: Float = -1.0,
|
|
323
|
+
**diffeqsolve_kwargs,
|
|
324
|
+
) -> tuple[Float, Float]:
|
|
325
|
+
"""Solve the Hyper-Lorenz 63 system of ODEs.
|
|
326
|
+
|
|
327
|
+
Parameters
|
|
328
|
+
----------
|
|
329
|
+
tN : float
|
|
330
|
+
The final time to solve the ODEs to.
|
|
331
|
+
dt : float
|
|
332
|
+
The time step size for the interpolated solution. Will be overridden if
|
|
333
|
+
`diffeqsolve_kwargs` contains a saveat argument with a different time grid.
|
|
334
|
+
u0 : jnp.ndarray, optional
|
|
335
|
+
The initial conditions for the ODEs. Default is (-10.0, 6.0, 0.0, 10.0).
|
|
336
|
+
a : float, optional
|
|
337
|
+
The a parameter for the Hyper-Lorenz system. Default is 10.0.
|
|
338
|
+
b : float, optional
|
|
339
|
+
The b parameter for the Hyper-Lorenz system. Default is 28.0.
|
|
340
|
+
c : float, optional
|
|
341
|
+
The c parameter for the Hyper-Lorenz system. Default is 8.0/3.0.
|
|
342
|
+
d : float, optional
|
|
343
|
+
The d parameter for the Hyper-Lorenz system. Default is -1.0.
|
|
344
|
+
diffeqsolve_kwargs : dict, optional
|
|
345
|
+
Additional keyword arguments to pass to the `diffrax.diffeqsolve` function.
|
|
346
|
+
Default solver is `diffrax.Tsit5()`, saveat is set to a grid of times from 0 to
|
|
347
|
+
tN with step size dt, and stepsize_controller is set to
|
|
348
|
+
`diffrax.PIDController(rtol=1e-7, atol=1e-9)`. dt0 is set to dt. max_steps is
|
|
349
|
+
set to None.
|
|
350
|
+
|
|
351
|
+
Returns
|
|
352
|
+
-------
|
|
353
|
+
us : jnp.ndarray
|
|
354
|
+
The solution array with shape (Nt, 4), where Nt is the number of time steps.
|
|
355
|
+
ts : jnp.ndarray
|
|
356
|
+
The time vector corresponding to the solution steps.
|
|
357
|
+
"""
|
|
358
|
+
# set kwarg defaults
|
|
359
|
+
diffeqsolve_kwargs.setdefault("solver", diffrax.Tsit5())
|
|
360
|
+
diffeqsolve_kwargs.setdefault("saveat", diffrax.SaveAt(ts=jnp.arange(0, tN, dt)))
|
|
361
|
+
diffeqsolve_kwargs.setdefault(
|
|
362
|
+
"stepsize_controller", diffrax.PIDController(rtol=1e-7, atol=1e-9)
|
|
363
|
+
)
|
|
364
|
+
diffeqsolve_kwargs.setdefault("dt0", dt)
|
|
365
|
+
diffeqsolve_kwargs.setdefault("max_steps", None)
|
|
366
|
+
|
|
367
|
+
# solve
|
|
368
|
+
u0 = jnp.array(u0)
|
|
369
|
+
term = diffrax.ODETerm(_hyper_lorenz63_f)
|
|
370
|
+
args = (a, b, c, d)
|
|
371
|
+
sol = diffrax.diffeqsolve(term, t0=0, t1=tN, y0=u0, args=args, **diffeqsolve_kwargs)
|
|
372
|
+
us = sol.ys
|
|
373
|
+
return us, sol.ts
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@jax.jit
|
|
377
|
+
def _hyper_xu_f(t, u, args):
|
|
378
|
+
"""Define Hyper-Xu ODE."""
|
|
379
|
+
a, b, c, d, e = args
|
|
380
|
+
u1, u2, u3, u4 = u
|
|
381
|
+
du1dt = a * (u2 - u1) + u4
|
|
382
|
+
du2dt = b * u1 + e * u1 * u3
|
|
383
|
+
du3dt = -c * u3 - u1 * u2
|
|
384
|
+
du4dt = u1 * u3 - d * u2
|
|
385
|
+
return jnp.array([du1dt, du2dt, du3dt, du4dt])
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def hyper_xu(
|
|
389
|
+
tN: Float,
|
|
390
|
+
dt: Float,
|
|
391
|
+
u0: Float[Array, "4"] = (-2.0, -1.0, -2.0, -10.0),
|
|
392
|
+
a: Float = 10.0,
|
|
393
|
+
b: Float = 40.0,
|
|
394
|
+
c: Float = 2.5,
|
|
395
|
+
d: Float = 2.0,
|
|
396
|
+
e: Float = 16.0,
|
|
397
|
+
**diffeqsolve_kwargs,
|
|
398
|
+
) -> tuple[Float, Float]:
|
|
399
|
+
"""Solve the Hyper-Xu system of ODEs.
|
|
400
|
+
|
|
401
|
+
Parameters
|
|
402
|
+
----------
|
|
403
|
+
tN : float
|
|
404
|
+
The final time to solve the ODEs to.
|
|
405
|
+
dt : float
|
|
406
|
+
The time step size for the interpolated solution. Will be overridden if
|
|
407
|
+
`diffeqsolve_kwargs` contains a saveat argument with a different time grid.
|
|
408
|
+
u0 : jnp.ndarray, optional
|
|
409
|
+
The initial conditions for the ODEs. Default is (-2.0, -1.0, -2.0, -10.0).
|
|
410
|
+
a : float, optional
|
|
411
|
+
The a parameter for the Hyper-Xu system. Default is 10.0.
|
|
412
|
+
b : float, optional
|
|
413
|
+
The b parameter for the Hyper-Xu system. Default is 40.0.
|
|
414
|
+
c : float, optional
|
|
415
|
+
The c parameter for the Hyper-Xu system. Default is 2.5.
|
|
416
|
+
d : float, optional
|
|
417
|
+
The d parameter for the Hyper-Xu system. Default is 2.0.
|
|
418
|
+
e : float, optional
|
|
419
|
+
The e parameter for the Hyper-Xu system. Default is 16.0.
|
|
420
|
+
diffeqsolve_kwargs : dict, optional
|
|
421
|
+
Additional keyword arguments to pass to the `diffrax.diffeqsolve` function.
|
|
422
|
+
Default solver is `diffrax.Tsit5()`, saveat is set to a grid of times from 0 to
|
|
423
|
+
tN with step size dt, and stepsize_controller is set to
|
|
424
|
+
`diffrax.PIDController(rtol=1e-7, atol=1e-9)`. dt0 is set to dt. max_steps is
|
|
425
|
+
set to None.
|
|
426
|
+
|
|
427
|
+
Returns
|
|
428
|
+
-------
|
|
429
|
+
us : jnp.ndarray
|
|
430
|
+
The solution array with shape (Nt, 4), where Nt is the number of time steps.
|
|
431
|
+
ts : jnp.ndarray
|
|
432
|
+
The time vector corresponding to the solution steps.
|
|
433
|
+
"""
|
|
434
|
+
# set kwarg defaults
|
|
435
|
+
diffeqsolve_kwargs.setdefault("solver", diffrax.Tsit5())
|
|
436
|
+
diffeqsolve_kwargs.setdefault("saveat", diffrax.SaveAt(ts=jnp.arange(0, tN, dt)))
|
|
437
|
+
diffeqsolve_kwargs.setdefault(
|
|
438
|
+
"stepsize_controller", diffrax.PIDController(rtol=1e-7, atol=1e-9)
|
|
439
|
+
)
|
|
440
|
+
diffeqsolve_kwargs.setdefault("dt0", dt)
|
|
441
|
+
diffeqsolve_kwargs.setdefault("max_steps", None)
|
|
442
|
+
|
|
443
|
+
# solve
|
|
444
|
+
u0 = jnp.array(u0)
|
|
445
|
+
term = diffrax.ODETerm(_hyper_xu_f)
|
|
446
|
+
args = (a, b, c, d, e)
|
|
447
|
+
sol = diffrax.diffeqsolve(term, t0=0, t1=tN, y0=u0, args=args, **diffeqsolve_kwargs)
|
|
448
|
+
us = sol.ys
|
|
449
|
+
return us, sol.ts
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
####################### Hamiltonian Systems #######################
|
|
453
|
+
@jax.jit
|
|
454
|
+
def _double_pendulum_f(t, u, args):
|
|
455
|
+
"""Define the equations of motion for double pendulum."""
|
|
456
|
+
m1, m2, L1, L2, g, damping = args
|
|
457
|
+
theta1, omega1, theta2, omega2 = u
|
|
458
|
+
|
|
459
|
+
# define some vars to shorten the expressions
|
|
460
|
+
delta_theta = theta1 - theta2
|
|
461
|
+
M_tot = m1 + m2
|
|
462
|
+
alpha = m1 + m2 * jnp.sin(delta_theta) ** 2
|
|
463
|
+
|
|
464
|
+
# compute derivs
|
|
465
|
+
dtheta1_dt = omega1
|
|
466
|
+
dtheta2_dt = omega2
|
|
467
|
+
domega1_dt_num = -jnp.sin(delta_theta) * (
|
|
468
|
+
m2 * L1 * omega1**2 * jnp.cos(delta_theta) + m2 * L2 * omega2**2
|
|
469
|
+
) - g * (M_tot * jnp.sin(theta1) - m2 * jnp.sin(theta2) * jnp.cos(delta_theta))
|
|
470
|
+
domega1_dt_denom = L1 * alpha
|
|
471
|
+
domega1_dt_damp = damping * (omega1 - omega2) + damping * omega1
|
|
472
|
+
domega1_dt = domega1_dt_num / domega1_dt_denom - domega1_dt_damp
|
|
473
|
+
domega2_dt_num = jnp.sin(delta_theta) * (
|
|
474
|
+
M_tot * L1 * omega1**2 + m2 * L2 * omega2**2 * jnp.cos(delta_theta)
|
|
475
|
+
) + g * (M_tot * jnp.sin(theta1) * jnp.cos(delta_theta) - M_tot * jnp.sin(theta2))
|
|
476
|
+
domega2_dt_denom = L2 * alpha
|
|
477
|
+
domega2_dt_damp = damping * (omega2 - omega1)
|
|
478
|
+
domega2_dt = domega2_dt_num / domega2_dt_denom - domega2_dt_damp
|
|
479
|
+
|
|
480
|
+
return jnp.array([dtheta1_dt, domega1_dt, dtheta2_dt, domega2_dt])
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def double_pendulum(
|
|
484
|
+
tN: Float,
|
|
485
|
+
dt: Float,
|
|
486
|
+
u0: Float[Array, "4"] = (jnp.pi / 4, -1.0, jnp.pi / 2, 1.0),
|
|
487
|
+
m1: Float = 1.0,
|
|
488
|
+
m2: Float = 1.0,
|
|
489
|
+
L1: Float = 1.0,
|
|
490
|
+
L2: Float = 1.0,
|
|
491
|
+
g: Float = 9.81,
|
|
492
|
+
damping: Float = 0.0,
|
|
493
|
+
**diffeqsolve_kwargs,
|
|
494
|
+
) -> tuple[Float, Float]:
|
|
495
|
+
"""Solve the equations of motion for a damped double pendulum.
|
|
496
|
+
|
|
497
|
+
The state u is represented as a 4-tuple (theta1, omega1, theta2, omega2) where:
|
|
498
|
+
- theta1 is the angle of the first pendulum from vertical (in radians).
|
|
499
|
+
- omega1 is the angular velocity of the first pendulum (in radians/s).
|
|
500
|
+
- theta2 is the angle of the second pendulum from vertical (in radians).
|
|
501
|
+
- omega2 is the angular velocity of the second pendulum (in radians/s).
|
|
502
|
+
|
|
503
|
+
Parameters
|
|
504
|
+
----------
|
|
505
|
+
tN : float
|
|
506
|
+
The final time to solve the ODEs to.
|
|
507
|
+
dt : float
|
|
508
|
+
The time step size for the interpolated solution. Will be overridden if
|
|
509
|
+
`diffeqsolve_kwargs` contains a saveat argument with a different time grid.
|
|
510
|
+
u0 : jnp.ndarray, optional
|
|
511
|
+
The initial conditions for the ODEs. Default is (jnp.pi/4, -1.0, jnp.pi/2, 1.0).
|
|
512
|
+
m1 : float, optional
|
|
513
|
+
The mass of the first pendulum bob. Default is 1.0.
|
|
514
|
+
m2 : float, optional
|
|
515
|
+
The mass of the second pendulum bob. Default is 1.0.
|
|
516
|
+
L1 : float, optional
|
|
517
|
+
The length of the first pendulum rod. Default is 1.0.
|
|
518
|
+
L2 : float, optional
|
|
519
|
+
The length of the second pendulum rod. Default is 1.0.
|
|
520
|
+
g : float, optional
|
|
521
|
+
The acceleration due to gravity. Default is 9.81.
|
|
522
|
+
damping : float, optional
|
|
523
|
+
The damping coefficient for the pendulum system. Default is 0.0 (no damping).
|
|
524
|
+
diffeqsolve_kwargs : dict, optional #TODO test which sovler best conserves energy
|
|
525
|
+
Additional keyword arguments to pass to the `diffrax.diffeqsolve` function.
|
|
526
|
+
Default solver is `diffrax.Tsit5()`, saveat is set to a grid of times from 0 to
|
|
527
|
+
tN with step size dt, and stepsize_controller is set to
|
|
528
|
+
`diffrax.PIDController(rtol=1e-7, atol=1e-9)`. dt0 is set to dt. max_steps is
|
|
529
|
+
set to None.
|
|
530
|
+
|
|
531
|
+
Returns
|
|
532
|
+
-------
|
|
533
|
+
us : jnp.ndarray
|
|
534
|
+
The solution array with shape (Nt, 4), where Nt is the number of time steps.
|
|
535
|
+
ts : jnp.ndarray
|
|
536
|
+
The time vector corresponding to the solution steps.
|
|
537
|
+
"""
|
|
538
|
+
# set kwarg defaults
|
|
539
|
+
diffeqsolve_kwargs.setdefault("solver", diffrax.Tsit5())
|
|
540
|
+
diffeqsolve_kwargs.setdefault("saveat", diffrax.SaveAt(ts=jnp.arange(0, tN, dt)))
|
|
541
|
+
diffeqsolve_kwargs.setdefault(
|
|
542
|
+
"stepsize_controller", diffrax.PIDController(rtol=1e-7, atol=1e-9)
|
|
543
|
+
)
|
|
544
|
+
diffeqsolve_kwargs.setdefault("dt0", dt)
|
|
545
|
+
diffeqsolve_kwargs.setdefault("max_steps", None)
|
|
546
|
+
|
|
547
|
+
# solve
|
|
548
|
+
u0 = jnp.array(u0)
|
|
549
|
+
term = diffrax.ODETerm(_double_pendulum_f)
|
|
550
|
+
args = (m1, m2, L1, L2, g, damping)
|
|
551
|
+
sol = diffrax.diffeqsolve(term, t0=0, t1=tN, y0=u0, args=args, **diffeqsolve_kwargs)
|
|
552
|
+
us = sol.ys
|
|
553
|
+
return us, sol.ts
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
###################### High Dimensional ODEs ######################
|
|
557
|
+
@jax.jit
|
|
558
|
+
def _lorenz96_interior(i, u, F):
|
|
559
|
+
"""Define the interior points of the Lorenz 96 ODE."""
|
|
560
|
+
return (u[i + 1] - u[i - 2]) * u[i - 1] - u[i] + F
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
@functools.partial(jax.jit, static_argnames=["args"])
|
|
564
|
+
def _lorenz96_f(t, u, args):
|
|
565
|
+
"""Define the Lorenz 96 ODE."""
|
|
566
|
+
N, F = args
|
|
567
|
+
|
|
568
|
+
# boundary at N
|
|
569
|
+
dudt_N = (u[0] - u[N - 3]) * u[N - 2] - u[N - 1] + F
|
|
570
|
+
|
|
571
|
+
# calculate all other points (interior plus boundary at 0)
|
|
572
|
+
dudt_func = jax.vmap(_lorenz96_interior, in_axes=(0, None, None))
|
|
573
|
+
interior_idxs = jnp.arange(N - 1)
|
|
574
|
+
dudt_interior = dudt_func(interior_idxs, u, F)
|
|
575
|
+
|
|
576
|
+
return jnp.append(dudt_interior, dudt_N)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def lorenz96(
|
|
580
|
+
tN: Float,
|
|
581
|
+
dt: Float,
|
|
582
|
+
u0: Array = None,
|
|
583
|
+
N: Float = 10,
|
|
584
|
+
F: Float = 8.0,
|
|
585
|
+
**diffeqsolve_kwargs,
|
|
586
|
+
) -> tuple[Float, Float]:
|
|
587
|
+
"""Solve the Lorenz 96 system of ODEs.
|
|
588
|
+
|
|
589
|
+
Parameters
|
|
590
|
+
----------
|
|
591
|
+
tN : float
|
|
592
|
+
The final time to solve the ODEs to.
|
|
593
|
+
dt : float
|
|
594
|
+
The time step size for the interpolated solution. Will be overridden if
|
|
595
|
+
`diffeqsolve_kwargs` contains a saveat argument with a different time grid.
|
|
596
|
+
u0 : jnp.ndarray, optional
|
|
597
|
+
The initial conditions for the ODEs. Default is None, which initializes y0
|
|
598
|
+
to `jnp.sin(jnp.arange(N))`.
|
|
599
|
+
N : int, optional
|
|
600
|
+
The number of variables in the Lorenz 96 system. Default is 10.
|
|
601
|
+
F : float, optional
|
|
602
|
+
The forcing parameter for the Lorenz 96 system. Default is 8.0.
|
|
603
|
+
diffeqsolve_kwargs : dict, optional
|
|
604
|
+
Additional keyword arguments to pass to the `diffrax.diffeqsolve` function.
|
|
605
|
+
Default solver is `diffrax.Tsit5()`, saveat is set to a grid of times from 0 to
|
|
606
|
+
tN with step size dt, and stepsize_controller is set to
|
|
607
|
+
`diffrax.PIDController(rtol=1e-7, atol=1e-9)`. dt0 is set to dt. max_steps is
|
|
608
|
+
set to None.
|
|
609
|
+
|
|
610
|
+
Returns
|
|
611
|
+
-------
|
|
612
|
+
us : jnp.ndarray
|
|
613
|
+
The solution array with shape (Nt, N), where Nt is the number of time steps.
|
|
614
|
+
ts : jnp.ndarray
|
|
615
|
+
The time vector corresponding to the solution steps.
|
|
616
|
+
"""
|
|
617
|
+
# set kwarg defaults
|
|
618
|
+
diffeqsolve_kwargs.setdefault("solver", diffrax.Tsit5())
|
|
619
|
+
diffeqsolve_kwargs.setdefault("saveat", diffrax.SaveAt(ts=jnp.arange(0, tN, dt)))
|
|
620
|
+
diffeqsolve_kwargs.setdefault(
|
|
621
|
+
"stepsize_controller", diffrax.PIDController(rtol=1e-7, atol=1e-9)
|
|
622
|
+
)
|
|
623
|
+
diffeqsolve_kwargs.setdefault("dt0", dt)
|
|
624
|
+
diffeqsolve_kwargs.setdefault("max_steps", None)
|
|
625
|
+
|
|
626
|
+
# solve
|
|
627
|
+
if u0 is None:
|
|
628
|
+
u0 = jnp.sin(jnp.arange(N))
|
|
629
|
+
u0 = jnp.array(u0)
|
|
630
|
+
term = diffrax.ODETerm(_lorenz96_f)
|
|
631
|
+
args = (N, F)
|
|
632
|
+
sol = diffrax.diffeqsolve(term, t0=0, t1=tN, y0=u0, args=args, **diffeqsolve_kwargs)
|
|
633
|
+
us = sol.ys
|
|
634
|
+
return us, sol.ts
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
########################### Chaotic PDEs ###########################
|
|
638
|
+
@functools.partial(jax.jit, static_argnames=["tN", "dt", "Nx"])
|
|
639
|
+
def KS_1D(
|
|
640
|
+
tN: Float,
|
|
641
|
+
u0: Array = None,
|
|
642
|
+
dt: Float = 0.25,
|
|
643
|
+
domain: tuple[Float, Float] = (0, 22),
|
|
644
|
+
Nx: int = 64,
|
|
645
|
+
) -> tuple[Float, Float]:
|
|
646
|
+
"""Solve the Kuramoto-Sivashinsky equation in 1D with periodic boundary conditions.
|
|
647
|
+
|
|
648
|
+
The KS PDE solved is:
|
|
649
|
+
u_t + u*u_x + u_xx + u_xxxx = 0
|
|
650
|
+
|
|
651
|
+
The solver uses a fixed time-step ETDRK4 (Kassam & Trefethen 2005) method
|
|
652
|
+
for handling the stiffness of the PDE. Dealiasing (2/3 rule) is applied.
|
|
653
|
+
|
|
654
|
+
Parameters
|
|
655
|
+
----------
|
|
656
|
+
tN : float
|
|
657
|
+
The final time to solve the PDE to.
|
|
658
|
+
u0 : jnp.ndarray, optional
|
|
659
|
+
The initial condition for the PDE (shape (Nx,)). Default is None, which
|
|
660
|
+
initializes u0 to `sin((32/domain[1])*pi*x)`.
|
|
661
|
+
dt : float, optional
|
|
662
|
+
The time step size for the solution. Default is 0.25.
|
|
663
|
+
domain : tuple[float, float], optional
|
|
664
|
+
The spatial domain (x_min, x_max). Default is (0, 22).
|
|
665
|
+
Nx : int, optional
|
|
666
|
+
The number of spatial grid points. Default is 64.
|
|
667
|
+
|
|
668
|
+
Returns
|
|
669
|
+
-------
|
|
670
|
+
U : jnp.ndarray
|
|
671
|
+
The solution array with shape (Nt, Nx+1), where Nt is the number of time steps.
|
|
672
|
+
Includes the periodic boundary point.
|
|
673
|
+
t : jnp.ndarray
|
|
674
|
+
The time vector corresponding to the solution steps.
|
|
675
|
+
"""
|
|
676
|
+
# Setup spatial grid
|
|
677
|
+
if u0 is None:
|
|
678
|
+
x0 = jnp.linspace(domain[0], domain[1], Nx, endpoint=True)
|
|
679
|
+
u0 = jnp.sin((32 / domain[1]) * jnp.pi * x0) # TODO find a better default
|
|
680
|
+
u0 = u0[:-1]
|
|
681
|
+
Nx = Nx - 1 # remove duplicate periodic point
|
|
682
|
+
x = jnp.linspace(domain[0], domain[1], Nx, endpoint=False)
|
|
683
|
+
dx = x[1] - x[0]
|
|
684
|
+
|
|
685
|
+
Nt = int(tN / dt)
|
|
686
|
+
U = jnp.zeros((Nx, Nt))
|
|
687
|
+
U = U.at[:, 0].set(u0)
|
|
688
|
+
|
|
689
|
+
# Wavenumbers
|
|
690
|
+
k = jnp.fft.fftfreq(Nx, d=dx) * 2 * jnp.pi
|
|
691
|
+
k2 = k**2
|
|
692
|
+
k4 = k**4
|
|
693
|
+
L_op = k2 - k4
|
|
694
|
+
|
|
695
|
+
# Dealiasing (2/3 rule)
|
|
696
|
+
def dealias(f_hat):
|
|
697
|
+
cutoff = Nx // 3
|
|
698
|
+
f_hat = f_hat.at[cutoff:-cutoff].set(0)
|
|
699
|
+
return f_hat
|
|
700
|
+
|
|
701
|
+
# nonlinear operators on u and u_hat
|
|
702
|
+
def N_op_u(u):
|
|
703
|
+
return dealias(1j * k * jnp.fft.fft(-0.5 * u**2))
|
|
704
|
+
|
|
705
|
+
def N_op_uhat(u_hat):
|
|
706
|
+
return dealias(1j * k * jnp.fft.fft(-0.5 * jnp.real(jnp.fft.ifft(u_hat)) ** 2))
|
|
707
|
+
|
|
708
|
+
# ETDRK4 coefficients (Kassam & Trefethen 2005)
|
|
709
|
+
E1 = jnp.exp(L_op * dt)
|
|
710
|
+
E2 = jnp.exp(L_op * dt / 2)
|
|
711
|
+
M = 16
|
|
712
|
+
r = jnp.exp(1j * jnp.pi * (jnp.arange(1, M + 1) - 0.5) / M)
|
|
713
|
+
LR = dt * jnp.column_stack([L_op] * M) + jnp.vstack([r] * Nx)
|
|
714
|
+
Q = dt * jnp.mean((jnp.exp(LR / 2) - 1) / LR, axis=1)
|
|
715
|
+
f1 = dt * jnp.mean((-4 - LR + jnp.exp(LR) * (4 - 3 * LR + LR**2)) / LR**3, axis=1)
|
|
716
|
+
f2 = dt * jnp.mean((2 + LR + jnp.exp(LR) * (-2 + LR)) / LR**3, axis=1)
|
|
717
|
+
f3 = dt * jnp.mean((-4 - 3 * LR - LR**2 + jnp.exp(LR) * (4 - LR)) / LR**3, axis=1)
|
|
718
|
+
|
|
719
|
+
def _KS_ETDRK4_step(carry, _):
|
|
720
|
+
u, E1, E2, Q, f1, f2, f3 = carry
|
|
721
|
+
|
|
722
|
+
u_hat = jnp.fft.fft(u)
|
|
723
|
+
|
|
724
|
+
a = E2 * u_hat + Q * N_op_u(u)
|
|
725
|
+
b = E2 * u_hat + Q * N_op_uhat(a)
|
|
726
|
+
c = E2 * a + Q * (2 * N_op_uhat(b) - N_op_u(u))
|
|
727
|
+
|
|
728
|
+
u_hat = (
|
|
729
|
+
E1 * u_hat
|
|
730
|
+
+ f1 * N_op_u(u)
|
|
731
|
+
+ f2 * (N_op_uhat(a) + N_op_uhat(b))
|
|
732
|
+
+ f3 * N_op_uhat(c)
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
# Enforce conservation by zeroing the mean mode
|
|
736
|
+
u_hat = u_hat.at[0].set(0.0)
|
|
737
|
+
|
|
738
|
+
u_next = jnp.real(jnp.fft.ifft(u_hat, n=Nx))
|
|
739
|
+
carry_next = (u_next, E1, E2, Q, f1, f2, f3)
|
|
740
|
+
return carry_next, u_next
|
|
741
|
+
|
|
742
|
+
_, u_vals = jax.lax.scan(
|
|
743
|
+
_KS_ETDRK4_step, (u0, E1, E2, Q, f1, f2, f3), length=Nt - 1
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
# add back in the initial point and boundary points
|
|
747
|
+
U = jnp.concatenate([u0[None, :], u_vals], axis=0)
|
|
748
|
+
U = jnp.concatenate((U, U[:, 0:1]), axis=1)
|
|
749
|
+
|
|
750
|
+
# create time vector for output
|
|
751
|
+
t = jnp.arange(0, tN, dt)
|
|
752
|
+
|
|
753
|
+
return U, t
|