sindy-exp 0.2.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.
- sindy_exp/__init__.py +28 -0
- sindy_exp/_data.py +202 -0
- sindy_exp/_diffrax_solver.py +104 -0
- sindy_exp/_dysts_to_sympy.py +452 -0
- sindy_exp/_odes.py +287 -0
- sindy_exp/_plotting.py +544 -0
- sindy_exp/_typing.py +158 -0
- sindy_exp/_utils.py +381 -0
- sindy_exp/addl_attractors.json +91 -0
- sindy_exp-0.2.0.dist-info/METADATA +111 -0
- sindy_exp-0.2.0.dist-info/RECORD +14 -0
- sindy_exp-0.2.0.dist-info/WHEEL +5 -0
- sindy_exp-0.2.0.dist-info/licenses/LICENSE +21 -0
- sindy_exp-0.2.0.dist-info/top_level.txt +1 -0
sindy_exp/_utils.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from itertools import chain
|
|
3
|
+
from typing import cast
|
|
4
|
+
from warnings import warn
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pysindy as ps
|
|
8
|
+
import sklearn
|
|
9
|
+
import sklearn.metrics
|
|
10
|
+
import sympy as sp
|
|
11
|
+
from sympy.parsing.sympy_parser import (
|
|
12
|
+
convert_xor,
|
|
13
|
+
implicit_multiplication_application,
|
|
14
|
+
parse_expr,
|
|
15
|
+
standard_transformations,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from sindy_exp._typing import SINDyTrialUpdate
|
|
19
|
+
|
|
20
|
+
from ._typing import Float1D, Float2D, _BaseSINDy
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _sympy_expr_to_feat_coeff(sp_expr: list[sp.Expr]) -> list[dict[sp.Expr, float]]:
|
|
26
|
+
"""Convert symbolic rhs expressions into feature/coeff dictionaries.
|
|
27
|
+
|
|
28
|
+
Each expression is assumed to be a sum of terms; each term is either a
|
|
29
|
+
simple SymPy expression or a product of a numeric coefficient and a
|
|
30
|
+
feature expression. The output is a list of dictionaries, one per
|
|
31
|
+
expression, mapping feature expressions to their numeric coefficients.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
expressions: list[dict[sp.Expr, float]] = []
|
|
35
|
+
|
|
36
|
+
def kv_term(term: sp.Expr) -> tuple[sp.Expr, float]:
|
|
37
|
+
if not isinstance(term, sp.Mul):
|
|
38
|
+
# Term is either a constant or a feature without a coefficient
|
|
39
|
+
if isinstance(term, sp.Number):
|
|
40
|
+
coeff = float(term)
|
|
41
|
+
feat = sp.Integer(1)
|
|
42
|
+
elif isinstance(term, (sp.Symbol, sp.Pow, sp.Function)):
|
|
43
|
+
coeff = 1.0
|
|
44
|
+
feat = term
|
|
45
|
+
else:
|
|
46
|
+
raise ValueError(f"Unrecognized term format: {term}")
|
|
47
|
+
else:
|
|
48
|
+
try:
|
|
49
|
+
# Assume multiplication is coefficient * feature(s)
|
|
50
|
+
coeff = float(term.args[0])
|
|
51
|
+
args = term.args[1:]
|
|
52
|
+
except TypeError:
|
|
53
|
+
# e.g. x**2 or x * y has no numeric coefficient
|
|
54
|
+
coeff = 1.0
|
|
55
|
+
args = term.args
|
|
56
|
+
if len(args) == 1:
|
|
57
|
+
feat = args[0]
|
|
58
|
+
else:
|
|
59
|
+
feat = sp.Mul(*args)
|
|
60
|
+
return feat, coeff
|
|
61
|
+
|
|
62
|
+
for exp in sp_expr:
|
|
63
|
+
expr_dict: dict[sp.Expr, float] = {}
|
|
64
|
+
if not isinstance(exp, sp.Add):
|
|
65
|
+
feat, coeff = kv_term(exp)
|
|
66
|
+
expr_dict[feat] = coeff
|
|
67
|
+
else:
|
|
68
|
+
for term in exp.args:
|
|
69
|
+
feat, coeff = kv_term(term)
|
|
70
|
+
expr_dict[feat] = coeff
|
|
71
|
+
|
|
72
|
+
expressions.append(expr_dict)
|
|
73
|
+
return expressions
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _sindy_equations_to_sympy(model: _BaseSINDy) -> tuple[list[sp.Expr], list[sp.Expr]]:
|
|
77
|
+
"""Convert a SINDy model's string equations to SymPy expressions.
|
|
78
|
+
|
|
79
|
+
Uses sympy's parser with ``convert_xor`` so that terms like ``x^2`` are
|
|
80
|
+
interpreted as powers rather than bitwise XOR.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
# Use a fixed precision for reproducible string equations.
|
|
84
|
+
input_features = {feat: sp.sympify(feat) for feat in model.feature_names}
|
|
85
|
+
eq_strings = model.equations(10) # type: ignore[call-arg]
|
|
86
|
+
feat_strs = model.feature_library.get_feature_names(
|
|
87
|
+
input_features=model.feature_names
|
|
88
|
+
)
|
|
89
|
+
xforms = standard_transformations + (
|
|
90
|
+
implicit_multiplication_application,
|
|
91
|
+
convert_xor,
|
|
92
|
+
)
|
|
93
|
+
feat_symb = [
|
|
94
|
+
parse_expr(fstr, transformations=xforms, evaluate=False) for fstr in feat_strs
|
|
95
|
+
]
|
|
96
|
+
eq_symb = [
|
|
97
|
+
parse_expr(
|
|
98
|
+
eq, local_dict=input_features, transformations=xforms, evaluate=False
|
|
99
|
+
)
|
|
100
|
+
for eq in eq_strings
|
|
101
|
+
]
|
|
102
|
+
return feat_symb, eq_symb
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def diff_lookup(kind):
|
|
106
|
+
normalized_kind = kind.lower().replace(" ", "")
|
|
107
|
+
if normalized_kind == "finitedifference":
|
|
108
|
+
return ps.FiniteDifference
|
|
109
|
+
if normalized_kind == "smoothedfinitedifference":
|
|
110
|
+
return ps.SmoothedFiniteDifference
|
|
111
|
+
elif normalized_kind == "sindy":
|
|
112
|
+
return ps.SINDyDerivative
|
|
113
|
+
else:
|
|
114
|
+
raise ValueError
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def feature_lookup(kind):
|
|
118
|
+
normalized_kind = kind.lower().replace(" ", "")
|
|
119
|
+
if normalized_kind is None:
|
|
120
|
+
return ps.PolynomialLibrary
|
|
121
|
+
elif normalized_kind == "polynomial":
|
|
122
|
+
return ps.PolynomialLibrary
|
|
123
|
+
elif normalized_kind == "fourier":
|
|
124
|
+
return ps.FourierLibrary
|
|
125
|
+
elif normalized_kind == "weak":
|
|
126
|
+
return ps.WeakPDELibrary
|
|
127
|
+
elif normalized_kind == "pde":
|
|
128
|
+
return ps.PDELibrary
|
|
129
|
+
else:
|
|
130
|
+
raise ValueError
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def opt_lookup(kind):
|
|
134
|
+
normalized_kind = kind.lower().replace(" ", "")
|
|
135
|
+
if normalized_kind == "stlsq":
|
|
136
|
+
return ps.STLSQ
|
|
137
|
+
elif normalized_kind == "sr3":
|
|
138
|
+
return ps.SR3
|
|
139
|
+
elif normalized_kind == "miosr":
|
|
140
|
+
return ps.MIOSR
|
|
141
|
+
elif normalized_kind == "trap":
|
|
142
|
+
return ps.TrappingSR3
|
|
143
|
+
elif normalized_kind == "ensemble":
|
|
144
|
+
return ps.EnsembleOptimizer
|
|
145
|
+
else:
|
|
146
|
+
raise ValueError
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def coeff_metrics(
|
|
150
|
+
coeff_est_dicts: list[dict[sp.Expr, float]],
|
|
151
|
+
coeff_true_dicts: list[dict[sp.Expr, float]],
|
|
152
|
+
) -> dict[str, float | np.floating]:
|
|
153
|
+
"""Compute coefficient metrics from aligned coefficient dictionaries.
|
|
154
|
+
|
|
155
|
+
Both arguments are expected to be lists of coefficient dictionaries sharing
|
|
156
|
+
the same SymPy-expression keys, such as the output of ``unionize_coeff_dicts``.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
if not coeff_true_dicts or not coeff_est_dicts:
|
|
160
|
+
raise ValueError("Coefficient dictionaries must be non-empty")
|
|
161
|
+
if len(coeff_true_dicts) != len(coeff_est_dicts):
|
|
162
|
+
raise ValueError("True and estimated coefficients must have same length")
|
|
163
|
+
|
|
164
|
+
features = list(coeff_true_dicts[0].keys())
|
|
165
|
+
n_coord = len(coeff_true_dicts)
|
|
166
|
+
n_feat = len(features)
|
|
167
|
+
|
|
168
|
+
coeff_true = np.zeros((n_coord, n_feat), dtype=float)
|
|
169
|
+
coefficients = np.zeros_like(coeff_true)
|
|
170
|
+
|
|
171
|
+
for row_ind, (true_row, est_row) in enumerate(
|
|
172
|
+
zip(coeff_true_dicts, coeff_est_dicts)
|
|
173
|
+
):
|
|
174
|
+
if set(true_row.keys()) != set(features) or set(est_row.keys()) != set(
|
|
175
|
+
features
|
|
176
|
+
):
|
|
177
|
+
raise ValueError(
|
|
178
|
+
"Coefficient dictionaries are not aligned across coordinates"
|
|
179
|
+
)
|
|
180
|
+
for col_ind, feat in enumerate(features):
|
|
181
|
+
coeff_true[row_ind, col_ind] = true_row[feat]
|
|
182
|
+
coefficients[row_ind, col_ind] = est_row[feat]
|
|
183
|
+
|
|
184
|
+
metrics: dict[str, float | np.floating] = {}
|
|
185
|
+
metrics["coeff_precision"] = sklearn.metrics.precision_score(
|
|
186
|
+
coeff_true.flatten() != 0, coefficients.flatten() != 0
|
|
187
|
+
)
|
|
188
|
+
metrics["coeff_recall"] = sklearn.metrics.recall_score(
|
|
189
|
+
coeff_true.flatten() != 0, coefficients.flatten() != 0
|
|
190
|
+
)
|
|
191
|
+
metrics["coeff_f1"] = sklearn.metrics.f1_score(
|
|
192
|
+
coeff_true.flatten() != 0, coefficients.flatten() != 0
|
|
193
|
+
)
|
|
194
|
+
metrics["coeff_mse"] = sklearn.metrics.mean_squared_error(
|
|
195
|
+
coeff_true.flatten(), coefficients.flatten()
|
|
196
|
+
)
|
|
197
|
+
metrics["coeff_mae"] = sklearn.metrics.mean_absolute_error(
|
|
198
|
+
coeff_true.flatten(), coefficients.flatten()
|
|
199
|
+
)
|
|
200
|
+
metrics["main"] = metrics["coeff_f1"]
|
|
201
|
+
return metrics
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def pred_metrics(
|
|
205
|
+
model: _BaseSINDy, x_test: np.ndarray, x_dot_test: np.ndarray
|
|
206
|
+
) -> dict[str, np.ndarray | float | np.floating]:
|
|
207
|
+
preds = model.predict(x_test)
|
|
208
|
+
err = preds - x_dot_test
|
|
209
|
+
return {
|
|
210
|
+
"pred_l2_fro": (np.linalg.norm(err) / np.linalg.norm(x_dot_test)),
|
|
211
|
+
"pred_l2_each": (np.linalg.norm(err) / np.linalg.norm(x_dot_test)),
|
|
212
|
+
"pred_r2": sklearn.metrics.r2_score(x_dot_test, preds),
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def integration_metrics(model: _BaseSINDy, x_test, t_train, x_dot_test):
|
|
217
|
+
metrics = {}
|
|
218
|
+
metrics["mse-plot"] = model.score(
|
|
219
|
+
x_test,
|
|
220
|
+
t_train,
|
|
221
|
+
x_dot_test,
|
|
222
|
+
metric=sklearn.metrics.mean_squared_error,
|
|
223
|
+
)
|
|
224
|
+
metrics["mae-plot"] = model.score(
|
|
225
|
+
x_test,
|
|
226
|
+
t_train,
|
|
227
|
+
x_dot_test,
|
|
228
|
+
metric=sklearn.metrics.mean_absolute_error,
|
|
229
|
+
)
|
|
230
|
+
return metrics
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def unionize_coeff_dicts(
|
|
234
|
+
model: _BaseSINDy,
|
|
235
|
+
true_equations: list[dict[sp.Expr, float]],
|
|
236
|
+
) -> tuple[list[dict[sp.Expr, float]], list[dict[sp.Expr, float]]]:
|
|
237
|
+
"""Align true and estimated coefficient dictionaries using SymPy expressions.
|
|
238
|
+
|
|
239
|
+
This function compares the symbolic features present in the true
|
|
240
|
+
equations and in the fitted SINDy model's equations and returns two
|
|
241
|
+
lists of coefficient dictionaries with identical feature keys. Missing
|
|
242
|
+
features in either source are filled with a coefficient of 0.0.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
model: Fitted SINDy-like model.
|
|
246
|
+
true_equations: List of coefficient dictionaries mapping SymPy
|
|
247
|
+
expressions to true coefficients, one dict per state coordinate.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
A pair ``(true_aligned, est_aligned)`` where each element is a list
|
|
251
|
+
of coefficient dictionaries ``dict[sp.Expr, float]`` with identical
|
|
252
|
+
keys across all coordinates.
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
feat_exprs, est_eq_exprs = _sindy_equations_to_sympy(model)
|
|
256
|
+
# est_equations should be unnecessary; all terms are provided by features
|
|
257
|
+
est_equations = _sympy_expr_to_feat_coeff(est_eq_exprs)
|
|
258
|
+
|
|
259
|
+
if len(est_equations) != len(true_equations):
|
|
260
|
+
raise ValueError(
|
|
261
|
+
"True equations and estimated equations must have"
|
|
262
|
+
" the same number of coordinates"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
true_aligned: list[dict[sp.Expr, float]] = []
|
|
266
|
+
est_aligned: list[dict[sp.Expr, float]] = []
|
|
267
|
+
empty_feats: list[dict[sp.Expr, float]] = [dict.fromkeys(feat_exprs, 0.0)]
|
|
268
|
+
|
|
269
|
+
all_features = [
|
|
270
|
+
expr
|
|
271
|
+
for eq in chain(empty_feats, true_equations, est_equations)
|
|
272
|
+
for expr in eq.keys()
|
|
273
|
+
]
|
|
274
|
+
all_features = list(dict.fromkeys(all_features)) # deduplicate, preserve order
|
|
275
|
+
for true_eq, est_eq in zip(true_equations, est_equations):
|
|
276
|
+
true_aligned.append({feat: true_eq.get(feat, 0.0) for feat in all_features})
|
|
277
|
+
est_aligned.append({feat: est_eq.get(feat, 0.0) for feat in all_features})
|
|
278
|
+
|
|
279
|
+
return true_aligned, est_aligned
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def make_model(
|
|
283
|
+
input_features: list[str],
|
|
284
|
+
dt: float,
|
|
285
|
+
diff_params: dict | ps.BaseDifferentiation,
|
|
286
|
+
feat_params: dict | ps.feature_library.base.BaseFeatureLibrary,
|
|
287
|
+
opt_params: dict | ps.BaseOptimizer,
|
|
288
|
+
) -> ps.SINDy:
|
|
289
|
+
"""Build a model with object parameters dictionaries
|
|
290
|
+
|
|
291
|
+
e.g. {"kind": "finitedifference"} instead of FiniteDifference()
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
def finalize_param(lookup_func, pdict, lookup_key):
|
|
295
|
+
try:
|
|
296
|
+
cls_name = pdict.pop(lookup_key)
|
|
297
|
+
except AttributeError:
|
|
298
|
+
cls_name = pdict.vals.pop(lookup_key)
|
|
299
|
+
pdict = pdict.vals
|
|
300
|
+
|
|
301
|
+
param_cls = lookup_func(cls_name)
|
|
302
|
+
param_final = param_cls(**pdict)
|
|
303
|
+
pdict[lookup_key] = cls_name
|
|
304
|
+
return param_final
|
|
305
|
+
|
|
306
|
+
if isinstance(diff_params, ps.BaseDifferentiation):
|
|
307
|
+
diff = diff_params
|
|
308
|
+
else:
|
|
309
|
+
diff = finalize_param(diff_lookup, diff_params, "diffcls")
|
|
310
|
+
if isinstance(feat_params, ps.feature_library.base.BaseFeatureLibrary):
|
|
311
|
+
features = feat_params
|
|
312
|
+
else:
|
|
313
|
+
features = finalize_param(feature_lookup, feat_params, "featcls")
|
|
314
|
+
if isinstance(opt_params, ps.BaseOptimizer):
|
|
315
|
+
opt = opt_params
|
|
316
|
+
else:
|
|
317
|
+
opt = finalize_param(opt_lookup, opt_params, "optcls")
|
|
318
|
+
return ps.SINDy(
|
|
319
|
+
differentiation_method=diff,
|
|
320
|
+
optimizer=opt,
|
|
321
|
+
t_default=dt, # type: ignore
|
|
322
|
+
feature_library=features,
|
|
323
|
+
feature_names=input_features,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _simulate_test_data(
|
|
328
|
+
model: _BaseSINDy, dt: float, x_test: Float2D
|
|
329
|
+
) -> SINDyTrialUpdate:
|
|
330
|
+
"""Add simulation data to grid_data
|
|
331
|
+
|
|
332
|
+
This includes the t_sim and x_sim keys. Does not mutate argument.
|
|
333
|
+
Returns:
|
|
334
|
+
Complete GridPointData
|
|
335
|
+
"""
|
|
336
|
+
t_test = cast(Float1D, np.arange(0, len(x_test) * dt, step=dt))
|
|
337
|
+
t_sim = t_test
|
|
338
|
+
try:
|
|
339
|
+
|
|
340
|
+
def quit(t, x):
|
|
341
|
+
return np.abs(x).max() - 1000
|
|
342
|
+
|
|
343
|
+
quit.terminal = True # type: ignore
|
|
344
|
+
x_sim = cast(
|
|
345
|
+
Float2D,
|
|
346
|
+
model.simulate(
|
|
347
|
+
x_test[0],
|
|
348
|
+
t_test,
|
|
349
|
+
integrator_kws={
|
|
350
|
+
"method": "LSODA",
|
|
351
|
+
"rtol": 1e-12,
|
|
352
|
+
"atol": 1e-12,
|
|
353
|
+
"events": [quit],
|
|
354
|
+
},
|
|
355
|
+
),
|
|
356
|
+
)
|
|
357
|
+
except ValueError:
|
|
358
|
+
warn(message="Simulation blew up; returning zeros")
|
|
359
|
+
x_sim = np.zeros_like(x_test)
|
|
360
|
+
# truncate if integration returns wrong number of points
|
|
361
|
+
t_sim = cast(Float1D, t_test[: len(x_sim)])
|
|
362
|
+
return SINDyTrialUpdate(t_sim=t_sim, t_test=t_test, x_sim=x_sim)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _drop_and_warn(arrs):
|
|
366
|
+
"""Drop trajectories that blew up during simulation"""
|
|
367
|
+
maxlen = max(arr.shape[0] for arr in arrs)
|
|
368
|
+
|
|
369
|
+
def _alert_short(arr):
|
|
370
|
+
if arr.shape[0] < maxlen:
|
|
371
|
+
warn(message="Dropping simulation due to blow-up")
|
|
372
|
+
return False
|
|
373
|
+
return True
|
|
374
|
+
|
|
375
|
+
arrs = list(filter(_alert_short, arrs))
|
|
376
|
+
if len(arrs) == 0:
|
|
377
|
+
raise ValueError(
|
|
378
|
+
"Simulations failed due to blow-up. System is too stiff for solver's"
|
|
379
|
+
" numerical tolerance"
|
|
380
|
+
)
|
|
381
|
+
return arrs
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"LotkaVolterra": {
|
|
3
|
+
"bifurcation_parameter": null,
|
|
4
|
+
"citation": "Lotka, A. J. (1925). Elements of Physical Biology. Williams & Wilkins.; Volterra, V. (1926). Fluctuations in the abundance of a species considered mathematically. Nature, 118, 558-560.",
|
|
5
|
+
"delay": false,
|
|
6
|
+
"description": "Predator-Prey Model.",
|
|
7
|
+
"dt": 0.1,
|
|
8
|
+
"embedding_dimension": 2,
|
|
9
|
+
"initial_conditions": [10.0, 5.0],
|
|
10
|
+
"nonautonomous": false,
|
|
11
|
+
"parameters": {
|
|
12
|
+
"alpha": 1.0,
|
|
13
|
+
"beta": 0.1,
|
|
14
|
+
"gamma": 1.5,
|
|
15
|
+
"delta": 0.075
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"Hopf": {
|
|
19
|
+
"bifurcation_parameter": null,
|
|
20
|
+
"citation": "Standard normal form of a Hopf bifurcation.",
|
|
21
|
+
"delay": false,
|
|
22
|
+
"description": "Planar Hopf normal form with cubic nonlinearity.",
|
|
23
|
+
"dt": 0.1,
|
|
24
|
+
"embedding_dimension": 2,
|
|
25
|
+
"initial_conditions": [1.0, 0.0],
|
|
26
|
+
"nonautonomous": false,
|
|
27
|
+
"parameters": {
|
|
28
|
+
"mu": 0.05,
|
|
29
|
+
"omega": 1.0,
|
|
30
|
+
"A": 1.0
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"SHO": {
|
|
34
|
+
"bifurcation_parameter": null,
|
|
35
|
+
"citation": "Linear damped harmonic oscillator.",
|
|
36
|
+
"delay": false,
|
|
37
|
+
"description": "Two-dimensional linear damped oscillator.",
|
|
38
|
+
"dt": 0.1,
|
|
39
|
+
"embedding_dimension": 2,
|
|
40
|
+
"initial_conditions": [1.0, 0.0],
|
|
41
|
+
"nonautonomous": false,
|
|
42
|
+
"parameters": {
|
|
43
|
+
"a": -0.1,
|
|
44
|
+
"b": 2.0,
|
|
45
|
+
"c": -2.0,
|
|
46
|
+
"d": -0.1
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"CubicHO": {
|
|
50
|
+
"bifurcation_parameter": null,
|
|
51
|
+
"citation": "Cubic damped harmonic oscillator.",
|
|
52
|
+
"delay": false,
|
|
53
|
+
"description": "Two-dimensional cubic damped oscillator.",
|
|
54
|
+
"dt": 0.1,
|
|
55
|
+
"embedding_dimension": 2,
|
|
56
|
+
"initial_conditions": [1.0, 0.0],
|
|
57
|
+
"nonautonomous": false,
|
|
58
|
+
"parameters": {
|
|
59
|
+
"a": -0.1,
|
|
60
|
+
"b": 2.0,
|
|
61
|
+
"c": -2.0,
|
|
62
|
+
"d": -0.1
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"VanDerPol": {
|
|
66
|
+
"bifurcation_parameter": null,
|
|
67
|
+
"citation": "Van der Pol, B. (1926). On relaxation-oscillations. The London, Edinburgh, and Dublin Philosophical Magazine and Journal of Science, 2(11), 978-992.",
|
|
68
|
+
"delay": false,
|
|
69
|
+
"description": "Classic Van der Pol relaxation oscillator.",
|
|
70
|
+
"dt": 0.1,
|
|
71
|
+
"embedding_dimension": 2,
|
|
72
|
+
"initial_conditions": [1.0, 0.0],
|
|
73
|
+
"nonautonomous": false,
|
|
74
|
+
"parameters": {
|
|
75
|
+
"mu": 0.5
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"Kinematics": {
|
|
79
|
+
"bifurcation_parameter": null,
|
|
80
|
+
"citation": "Uniformly accelerated one-dimensional motion.",
|
|
81
|
+
"delay": false,
|
|
82
|
+
"description": "Position and velocity with constant acceleration.",
|
|
83
|
+
"dt": 0.1,
|
|
84
|
+
"embedding_dimension": 2,
|
|
85
|
+
"initial_conditions": [0.0, 0.0],
|
|
86
|
+
"nonautonomous": false,
|
|
87
|
+
"parameters": {
|
|
88
|
+
"a": -1.0
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sindy-exp
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A basic library for constructing dynamics experiments
|
|
5
|
+
Author-email: Jake Stevens-Haas <jacob.stevens.haas@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2022 Jacob Stevens-Haas
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: homepage, https://github.com/Jake-Stevens-Haas/gen-experiments
|
|
29
|
+
Keywords: Machine Learning,Science,Mathematics,Experiments
|
|
30
|
+
Classifier: Development Status :: 4 - Beta
|
|
31
|
+
Classifier: Programming Language :: Python
|
|
32
|
+
Classifier: Framework :: Jupyter
|
|
33
|
+
Classifier: Intended Audience :: Science/Research
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Natural Language :: English
|
|
36
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
37
|
+
Requires-Python: >=3.10
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
License-File: LICENSE
|
|
40
|
+
Requires-Dist: matplotlib
|
|
41
|
+
Requires-Dist: numpy>=2.0.0
|
|
42
|
+
Requires-Dist: seaborn
|
|
43
|
+
Requires-Dist: scipy
|
|
44
|
+
Requires-Dist: sympy
|
|
45
|
+
Requires-Dist: dysts
|
|
46
|
+
Provides-Extra: jax
|
|
47
|
+
Requires-Dist: jax[cuda12]; extra == "jax"
|
|
48
|
+
Requires-Dist: diffrax; extra == "jax"
|
|
49
|
+
Requires-Dist: sympy2jax; extra == "jax"
|
|
50
|
+
Requires-Dist: typing_extensions; extra == "jax"
|
|
51
|
+
Provides-Extra: dev
|
|
52
|
+
Requires-Dist: ipykernel; extra == "dev"
|
|
53
|
+
Requires-Dist: mypy; extra == "dev"
|
|
54
|
+
Requires-Dist: pytest>=6.0.0; extra == "dev"
|
|
55
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
56
|
+
Requires-Dist: flake8; extra == "dev"
|
|
57
|
+
Requires-Dist: flake8-comprehensions>=3.1.0; extra == "dev"
|
|
58
|
+
Requires-Dist: black; extra == "dev"
|
|
59
|
+
Requires-Dist: coverage; extra == "dev"
|
|
60
|
+
Requires-Dist: isort; extra == "dev"
|
|
61
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
62
|
+
Requires-Dist: codecov; extra == "dev"
|
|
63
|
+
Requires-Dist: tomli; extra == "dev"
|
|
64
|
+
Requires-Dist: pysindy>=2.1.0; extra == "dev"
|
|
65
|
+
Dynamic: license-file
|
|
66
|
+
|
|
67
|
+
# Dynamics Experiments
|
|
68
|
+
|
|
69
|
+
A library for constructing dynamics experiments.
|
|
70
|
+
This includes data generation and plotting/evaluation.
|
|
71
|
+
|
|
72
|
+
## Getting started
|
|
73
|
+
|
|
74
|
+
It's not yet on PyPI, so install it with `pip install sindy_exp @ git+https://github.com/Jacob-Stevens-Haas/sindy-experiments`
|
|
75
|
+
|
|
76
|
+
Generate data
|
|
77
|
+
|
|
78
|
+
data = sindy_exp.data.gen_data("lorenz", num_trajectories=5, t_end=10.0, dt=0.01)["data]
|
|
79
|
+
|
|
80
|
+
Evaluate your SINDy-like model with:
|
|
81
|
+
|
|
82
|
+
sindy_exp.odes.fit_eval(model, data)
|
|
83
|
+
|
|
84
|
+

|
|
85
|
+
|
|
86
|
+
A list of available ODE systems can be found in `ODE_CLASSES`, which includes most
|
|
87
|
+
of the systems from the [dysts package](https://pypi.org/project/dysts/) as well as some non-chaotic systems.
|
|
88
|
+
|
|
89
|
+
## ODE representation
|
|
90
|
+
|
|
91
|
+
We deal primarily with autonomous ODE systems of the form:
|
|
92
|
+
|
|
93
|
+
dx/dt = sum_i f_i(x)
|
|
94
|
+
|
|
95
|
+
Thus, we represent ODE systems as a list of right-hand side expressions.
|
|
96
|
+
Each element is a dictionary mapping a term (Sympy expression) to its coefficient.
|
|
97
|
+
|
|
98
|
+
## Other useful imports, compatibility, and extensions
|
|
99
|
+
|
|
100
|
+
This is built to be compatible with dynamics learning models that follow the
|
|
101
|
+
pysindy _BaseSINDy interface.
|
|
102
|
+
The experiments are also built to be compatible with the `mitosis` tool,
|
|
103
|
+
an experiment runner.
|
|
104
|
+
To integrate your own experiments or data generation in a way that is compatible,
|
|
105
|
+
see the `ProbData` and `DynamicsTrialData` classes.
|
|
106
|
+
For plotting tools, see `plot_coefficients`, `compare_coefficient_plots_from_dicts`,
|
|
107
|
+
`plot_test_trajectory`, `plot_training_data`, and `COLOR`.
|
|
108
|
+
For metrics, see `coeff_metrics`, `pred_metrics`, and `integration_metrics`.
|
|
109
|
+
|
|
110
|
+

|
|
111
|
+

|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
sindy_exp/__init__.py,sha256=F1bJz9Gzk2nPB6DGjEa0qZcaQPTb-Yhh0ZnO9TcQci0,689
|
|
2
|
+
sindy_exp/_data.py,sha256=xEQByYeL2ejPdCpFVP9oH48Faoiz1E3OjRhK06lmkFo,6512
|
|
3
|
+
sindy_exp/_diffrax_solver.py,sha256=YmKF-U1fkxW4z4AbXW_qm1sX6eEy1BGzKbqL63dxo2M,2705
|
|
4
|
+
sindy_exp/_dysts_to_sympy.py,sha256=d_rvnfayOmFcGn4bZRJCfNGFO6yS1mw2QmBbOdWZwxg,15654
|
|
5
|
+
sindy_exp/_odes.py,sha256=RN97nh1Y_6DQCLPfRTyCCJq3exxBq9pfVHVPTxr0na4,8830
|
|
6
|
+
sindy_exp/_plotting.py,sha256=dpcqAXKzb0mSVl0p2WyMadVoGhQi43oL6ZqsbuheEuk,17470
|
|
7
|
+
sindy_exp/_typing.py,sha256=hIOj270YkoITi_qJ8xTO1EaBeoVzcDidIGMXuC0QHzM,4046
|
|
8
|
+
sindy_exp/_utils.py,sha256=RnMoNV_N7ubWVMbgdiJaRGo437DvWUcOIn4fP2rhwJI,12717
|
|
9
|
+
sindy_exp/addl_attractors.json,sha256=KXoHWekFoa4KctjLCqcj_BpLBhXV0zlYrpgxV-uObwE,2928
|
|
10
|
+
sindy_exp-0.2.0.dist-info/licenses/LICENSE,sha256=ubi77tIG3RVrqo0Z8cK91D4KZePQs-W1J-vJ-LkVOmE,1075
|
|
11
|
+
sindy_exp-0.2.0.dist-info/METADATA,sha256=_BRUa1zXAqRJtMWeIcyUDyx74eo_VeAwu7V1NVhbhZM,4514
|
|
12
|
+
sindy_exp-0.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
13
|
+
sindy_exp-0.2.0.dist-info/top_level.txt,sha256=0-tKKdmxHG3IRccz463rOb6xTsVJD-v9c8zSDpTRr5E,10
|
|
14
|
+
sindy_exp-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Jacob Stevens-Haas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sindy_exp
|