sindy-exp 0.2.2__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.
Files changed (31) hide show
  1. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/.github/workflows/main.yaml +3 -3
  2. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/.github/workflows/release.yml +1 -1
  3. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/.gitignore +0 -1
  4. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/PKG-INFO +24 -17
  5. sindy_exp-0.3.0/README.md +51 -0
  6. sindy_exp-0.3.0/images/1d.png +0 -0
  7. sindy_exp-0.3.0/images/coeff.png +0 -0
  8. sindy_exp-0.3.0/images/composite.png +0 -0
  9. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/pyproject.toml +9 -13
  10. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp/__init__.py +2 -1
  11. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp/_data.py +30 -25
  12. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp/_diffrax_solver.py +12 -4
  13. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp/_odes.py +11 -33
  14. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp/_plotting.py +11 -5
  15. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp/_typing.py +24 -9
  16. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp/_utils.py +3 -4
  17. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp.egg-info/PKG-INFO +24 -17
  18. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp.egg-info/SOURCES.txt +3 -0
  19. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp.egg-info/requires.txt +1 -0
  20. sindy_exp-0.2.2/README.md +0 -45
  21. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/.pre-commit-config.yaml +0 -0
  22. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/CITATION.cff +0 -0
  23. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/LICENSE +0 -0
  24. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/setup.cfg +0 -0
  25. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp/_dysts_to_sympy.py +0 -0
  26. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp/addl_attractors.json +0 -0
  27. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp/py.typed +0 -0
  28. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp.egg-info/dependency_links.txt +0 -0
  29. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/src/sindy_exp.egg-info/top_level.txt +0 -0
  30. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/tests/test_all.py +0 -0
  31. {sindy_exp-0.2.2 → sindy_exp-0.3.0}/tests/test_inspect_to_sympy.py +0 -0
@@ -17,7 +17,7 @@ jobs:
17
17
  - name: "Set up Python"
18
18
  uses: actions/setup-python@v3
19
19
  with:
20
- python-version: "3.10"
20
+ python-version: "3.12"
21
21
  - name: run pre-commit
22
22
  run: |
23
23
  pip install pre-commit
@@ -30,7 +30,7 @@ jobs:
30
30
  - name: "Set up Python"
31
31
  uses: actions/setup-python@v3
32
32
  with:
33
- python-version: "3.10"
33
+ python-version: "3.12"
34
34
  - name: install dependencies
35
35
  run: |
36
36
  pip install -e .[dev,jax]
@@ -45,7 +45,7 @@ jobs:
45
45
  fail-fast: false
46
46
  max-parallel: 4
47
47
  matrix:
48
- python-version: ["3.10", "3.11", "3.12"]
48
+ python-version: ["3.12", "3.13"]
49
49
  steps:
50
50
  - uses: actions/checkout@v3
51
51
  - name: Set up Python ${{ matrix.python-version }}
@@ -27,7 +27,7 @@ jobs:
27
27
  - name: Set up Python
28
28
  uses: actions/setup-python@v6
29
29
  with:
30
- python-version: "3.10"
30
+ python-version: "3.12"
31
31
  - name: Install Build
32
32
  run: |
33
33
  python -m pip install --upgrade pip
@@ -1,7 +1,6 @@
1
1
  commit-msg
2
2
  todo
3
3
  scratch/
4
- *.png
5
4
  debugme*.py
6
5
  trials/*
7
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sindy-exp
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: A basic library for constructing dynamics experiments
5
5
  Author-email: Jake Stevens-Haas <jacob.stevens.haas@gmail.com>
6
6
  License: MIT License
@@ -34,7 +34,7 @@ Classifier: Intended Audience :: Science/Research
34
34
  Classifier: License :: OSI Approved :: MIT License
35
35
  Classifier: Natural Language :: English
36
36
  Classifier: Operating System :: POSIX :: Linux
37
- Requires-Python: >=3.10
37
+ Requires-Python: >=3.12
38
38
  Description-Content-Type: text/markdown
39
39
  License-File: LICENSE
40
40
  Requires-Dist: matplotlib
@@ -43,6 +43,7 @@ Requires-Dist: seaborn
43
43
  Requires-Dist: scipy
44
44
  Requires-Dist: sympy
45
45
  Requires-Dist: dysts
46
+ Requires-Dist: scikit-learn
46
47
  Provides-Extra: jax
47
48
  Requires-Dist: jax[cuda12]; extra == "jax"
48
49
  Requires-Dist: diffrax; extra == "jax"
@@ -64,14 +65,20 @@ Requires-Dist: tomli; extra == "dev"
64
65
  Requires-Dist: pysindy>=2.1.0; extra == "dev"
65
66
  Dynamic: license-file
66
67
 
67
- # Dynamics Experiments
68
+ # Overview
68
69
 
69
- A library for constructing dynamics experiments.
70
- This includes data generation and plotting/evaluation.
70
+ A library for constructing dynamics experiments from the dynamics models in the `dysts` package.
71
+ This includes data generation and model evaluation.
72
+ The first contribution is the static typing of trajectory data (`ProbData`) that, I believe, provides the necessary information to be useful in evaluating a wide variety of dynamics/time-series learning methods.
73
+ The second contribution is the collection of utility functions for designing dynamics learning experiments.
74
+ The third contribution is the collection of such experiments for evaluating dynamics/time-series learning models that meet the `BaseSINDy` API.
75
+
76
+ It aims to (a) be amenable to both `numpy` and `jax` arrays, (b) be usable by any dynamics/time-series learning models that meet the `BaseSINDy` or scikit-time API.
77
+ Internally, this package is used/will be used in benchmarking pysindy runtime/memory usage and choosing default hyperparameters.
71
78
 
72
79
  ## Getting started
73
80
 
74
- It's not yet on PyPI, so install it with `pip install sindy_exp @ git+https://github.com/Jacob-Stevens-Haas/sindy-experiments`
81
+ Install with `pip install sindy-exp` or `pip install sindy-exp[jax]`.
75
82
 
76
83
  Generate data
77
84
 
@@ -86,26 +93,26 @@ Evaluate your SINDy-like model with:
86
93
  A list of available ODE systems can be found in `ODE_CLASSES`, which includes most
87
94
  of the systems from the [dysts package](https://pypi.org/project/dysts/) as well as some non-chaotic systems.
88
95
 
89
- ## ODE representation
96
+ ## ODE & Data Model
97
+
98
+ Generated or measured data has the dataclass type `ProbData` or `SimProbData`, respectively,
99
+ to indicate whether it includes ground truth information and a noise level.
100
+ If the data is generated in jax, it will have an integrator that can later be used to evaluate the true data on collocation points.
90
101
 
91
102
  We deal primarily with autonomous ODE systems of the form:
92
103
 
93
104
  dx/dt = sum_i f_i(x)
94
105
 
95
- Thus, we represent ODE systems as a list of right-hand side expressions.
106
+ We represent ODE systems as a list of right-hand side expressions.
96
107
  Each element is a dictionary mapping a term (Sympy expression) to its coefficient.
108
+ Thus, the rhs of an ODE is of type: `list[dict[sympy.Expr, float]]`
97
109
 
98
110
  ## Other useful imports, compatibility, and extensions
99
111
 
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`.
112
+ * The experiments are built to be compatible with the `mitosis` tool, an experiment runner. Mitosis is not a dependency, however, to allow using other experiment runners.
113
+ * To integrate your own experiments or data generation in a way that is compatible, see the `ProbData`, `SimProbData`, `DynamicsTrialData`, and `FullDynamicsTrialData` classes.
114
+ * For plotting tools, see `plot_coefficients`, `compare_coefficient_plots_from_dicts`, `plot_test_trajectory`, `plot_training_data`, and `COLOR`.
115
+ * For evaluation of models, see `coeff_metrics`, `pred_metrics`, and `integration_metrics`.
109
116
 
110
117
  ![3d plot](images/composite.png)
111
118
  ![1d plot](images/1d.png)
@@ -0,0 +1,51 @@
1
+ # Overview
2
+
3
+ A library for constructing dynamics experiments from the dynamics models in the `dysts` package.
4
+ This includes data generation and model evaluation.
5
+ The first contribution is the static typing of trajectory data (`ProbData`) that, I believe, provides the necessary information to be useful in evaluating a wide variety of dynamics/time-series learning methods.
6
+ The second contribution is the collection of utility functions for designing dynamics learning experiments.
7
+ The third contribution is the collection of such experiments for evaluating dynamics/time-series learning models that meet the `BaseSINDy` API.
8
+
9
+ It aims to (a) be amenable to both `numpy` and `jax` arrays, (b) be usable by any dynamics/time-series learning models that meet the `BaseSINDy` or scikit-time API.
10
+ Internally, this package is used/will be used in benchmarking pysindy runtime/memory usage and choosing default hyperparameters.
11
+
12
+ ## Getting started
13
+
14
+ Install with `pip install sindy-exp` or `pip install sindy-exp[jax]`.
15
+
16
+ Generate data
17
+
18
+ data = sindy_exp.data.gen_data("lorenz", num_trajectories=5, t_end=10.0, dt=0.01)["data]
19
+
20
+ Evaluate your SINDy-like model with:
21
+
22
+ sindy_exp.odes.fit_eval(model, data)
23
+
24
+ ![Coefficient plots](images/coeff.png)
25
+
26
+ A list of available ODE systems can be found in `ODE_CLASSES`, which includes most
27
+ of the systems from the [dysts package](https://pypi.org/project/dysts/) as well as some non-chaotic systems.
28
+
29
+ ## ODE & Data Model
30
+
31
+ Generated or measured data has the dataclass type `ProbData` or `SimProbData`, respectively,
32
+ to indicate whether it includes ground truth information and a noise level.
33
+ If the data is generated in jax, it will have an integrator that can later be used to evaluate the true data on collocation points.
34
+
35
+ We deal primarily with autonomous ODE systems of the form:
36
+
37
+ dx/dt = sum_i f_i(x)
38
+
39
+ We represent ODE systems as a list of right-hand side expressions.
40
+ Each element is a dictionary mapping a term (Sympy expression) to its coefficient.
41
+ Thus, the rhs of an ODE is of type: `list[dict[sympy.Expr, float]]`
42
+
43
+ ## Other useful imports, compatibility, and extensions
44
+
45
+ * The experiments are built to be compatible with the `mitosis` tool, an experiment runner. Mitosis is not a dependency, however, to allow using other experiment runners.
46
+ * To integrate your own experiments or data generation in a way that is compatible, see the `ProbData`, `SimProbData`, `DynamicsTrialData`, and `FullDynamicsTrialData` classes.
47
+ * For plotting tools, see `plot_coefficients`, `compare_coefficient_plots_from_dicts`, `plot_test_trajectory`, `plot_training_data`, and `COLOR`.
48
+ * For evaluation of models, see `coeff_metrics`, `pred_metrics`, and `integration_metrics`.
49
+
50
+ ![3d plot](images/composite.png)
51
+ ![1d plot](images/1d.png)
Binary file
Binary file
Binary file
@@ -7,7 +7,7 @@ name = "sindy-exp"
7
7
  dynamic = ["version"]
8
8
  description = "A basic library for constructing dynamics experiments"
9
9
  readme = "README.md"
10
- requires-python = ">=3.10"
10
+ requires-python = ">=3.12"
11
11
  license = {file = "LICENSE"}
12
12
  keywords = ["Machine Learning", "Science", "Mathematics", "Experiments"]
13
13
  authors = [
@@ -29,6 +29,7 @@ dependencies = [
29
29
  "scipy",
30
30
  "sympy",
31
31
  "dysts",
32
+ "scikit-learn",
32
33
  ]
33
34
 
34
35
  [project.optional-dependencies]
@@ -94,25 +95,20 @@ markers = ["slow"]
94
95
  [tool.mypy]
95
96
  files = [
96
97
  "src/sindy_exp/__init__.py",
98
+ "src/sindy_exp/_data.py",
97
99
  "src/sindy_exp/_utils.py",
98
100
  "src/sindy_exp/_diffrax_solver.py",
99
101
  "src/sindy_exp/_odes.py",
102
+ "src/sindy_exp/_plotting.py",
100
103
  "src/sindy_exp/_typing.py",
101
104
  "tests/test_all.py",
102
105
  ]
106
+ warn_unused_configs = true
103
107
 
104
108
  [[tool.mypy.overrides]]
105
- module="sklearn.*"
106
- ignore_missing_imports=true
109
+ module = ["sindy_exp.*"]
110
+ disable_error_code = ["import-untyped"]
107
111
 
108
112
  [[tool.mypy.overrides]]
109
- module="pysindy.*"
110
- ignore_missing_imports=true
111
-
112
- [[tool.mypy.overrides]]
113
- module="sympy.*"
114
- ignore_missing_imports=true
115
-
116
- [[tool.mypy.overrides]]
117
- module="scipy.*"
118
- ignore_missing_imports=true
113
+ module = ["pysindy.*", "sympy.*"]
114
+ ignore_missing_imports = true
@@ -7,13 +7,14 @@ from ._plotting import (
7
7
  plot_test_trajectory,
8
8
  plot_training_data,
9
9
  )
10
- from ._typing import DynamicsTrialData, FullDynamicsTrialData, ProbData
10
+ from ._typing import DynamicsTrialData, FullDynamicsTrialData, ProbData, SimProbData
11
11
  from ._utils import coeff_metrics, integration_metrics, pred_metrics
12
12
 
13
13
  __all__ = [
14
14
  "gen_data",
15
15
  "fit_eval",
16
16
  "ProbData",
17
+ "SimProbData",
17
18
  "DynamicsTrialData",
18
19
  "FullDynamicsTrialData",
19
20
  "coeff_metrics",
@@ -11,7 +11,7 @@ from dysts.base import DynSys
11
11
 
12
12
  from ._dysts_to_sympy import dynsys_to_sympy
13
13
  from ._plotting import plot_training_data
14
- from ._typing import ExperimentResult, Float1D, ProbData
14
+ from ._typing import ExperimentResult, Float1D, SimProbData
15
15
  from ._utils import _sympy_expr_to_feat_coeff
16
16
 
17
17
  try:
@@ -42,7 +42,7 @@ def gen_data(
42
42
  t_end: float = 10,
43
43
  display: bool = False,
44
44
  array_namespace: str = "numpy",
45
- ) -> ExperimentResult[tuple[list[ProbData], list[dict[sp.Expr, float]]]]:
45
+ ) -> ExperimentResult[tuple[list[SimProbData], list[dict[sp.Expr, float]]]]:
46
46
  """Generate random training and test data
47
47
 
48
48
  An Experiment step according to the mitosis experiment runner.
@@ -75,7 +75,7 @@ def gen_data(
75
75
  coeff_true = _sympy_expr_to_feat_coeff(sp_expr)
76
76
  rhsfunc = lambda t, X: dyst_sys.rhs(X, t) # noqa: E731
77
77
  try:
78
- x0_center = dyst_sys.ic
78
+ x0_center = cast(Float1D, dyst_sys.ic)
79
79
  except KeyError:
80
80
  x0_center = np.zeros((len(input_features)), dtype=np.float64)
81
81
  try:
@@ -88,7 +88,7 @@ def gen_data(
88
88
  noise_abs = 0.1
89
89
 
90
90
  MOD_LOG.info(f"Generating {n_trajectories} trajectories of f{system}")
91
- prob_data_list: list[ProbData] = []
91
+ prob_data_list: list[SimProbData] = []
92
92
  if array_namespace == "numpy":
93
93
  feature_names = [feat.name for feat in input_features]
94
94
  for _ in range(n_trajectories):
@@ -108,20 +108,20 @@ def gen_data(
108
108
  prob_data_list.append(prob)
109
109
  elif array_namespace == "jax":
110
110
  try:
111
- globals()["_gen_data_jax"]
112
- except KeyError:
111
+ jax # type: ignore
112
+ except NameError:
113
113
  raise ImportError(
114
114
  "jax data generation requested but diffrax or sympy2jax not"
115
115
  " installed"
116
116
  )
117
- this_seed = jax.random.PRNGKey(seed)
117
+ this_seed = jax.random.PRNGKey(seed) # type: ignore
118
118
  for _ in range(n_trajectories):
119
- this_seed, _ = jax.random.split(this_seed)
120
- prob = _gen_data_jax(
119
+ this_seed, _ = jax.random.split(this_seed) # type: ignore
120
+ prob = _gen_data_jax( # type: ignore
121
121
  sp_expr,
122
122
  input_features,
123
123
  this_seed,
124
- x0_center=x0_center,
124
+ x0_center=x0_center, # type: ignore # numpy->jax
125
125
  nonnegative=nonnegative,
126
126
  ic_stdev=ic_stdev,
127
127
  noise_abs=noise_abs,
@@ -136,8 +136,10 @@ def gen_data(
136
136
  )
137
137
  if display and prob_data_list:
138
138
  sample = prob_data_list[0]
139
+ assert sample.x_train_true is not None # typing
139
140
  figs = plot_training_data(sample.t_train, sample.x_train, sample.x_train_true)
140
141
  figs[0].suptitle("Sample Trajectory")
142
+
141
143
  return {
142
144
  "data": (prob_data_list, coeff_true),
143
145
  "main": f"{n_trajectories} trajectories of {rhsfunc}",
@@ -156,7 +158,7 @@ def _gen_data(
156
158
  nonnegative: bool,
157
159
  dt: float,
158
160
  t_end: float,
159
- ) -> ProbData:
161
+ ) -> SimProbData:
160
162
  rng = np.random.default_rng(seed)
161
163
  t_train = np.arange(0, t_end, dt)
162
164
  t_train_span = (t_train[0], t_train[-1])
@@ -180,8 +182,9 @@ def _gen_data(
180
182
  noise_abs = np.sqrt(_signal_avg_power(x_train) * noise_rel)
181
183
  x_train = x_train + cast(float, noise_abs) * rng.standard_normal(x_train.shape)
182
184
 
183
- return ProbData(
184
- dt, t_train, x_train, x_train_true, x_train_true_dot, input_features
185
+ assert noise_abs is not None # typing
186
+ return SimProbData(
187
+ t_train, x_train, input_features, x_train_true, x_train_true_dot, noise_abs
185
188
  )
186
189
 
187
190
 
@@ -208,10 +211,12 @@ class LotkaVolterra(DynSys):
208
211
  nonnegative = True
209
212
 
210
213
  def __init__(self):
211
- super().__init__(metadata_path=LOCAL_DYNAMICS_PATH)
214
+ super().__init__(metadata_path=LOCAL_DYNAMICS_PATH) # type: ignore # dysts
212
215
 
213
216
  @staticmethod
214
- def _rhs(x, y, t: float, alpha, beta, gamma, delta) -> np.ndarray:
217
+ def _rhs( # type: ignore # dysts
218
+ x, y, t: float, alpha, beta, gamma, delta
219
+ ) -> np.ndarray:
215
220
  """LV dynamics
216
221
 
217
222
  Args:
@@ -233,10 +238,10 @@ class Hopf(DynSys):
233
238
  """Hopf normal form dynamical system."""
234
239
 
235
240
  def __init__(self):
236
- super().__init__(metadata_path=LOCAL_DYNAMICS_PATH)
241
+ super().__init__(metadata_path=LOCAL_DYNAMICS_PATH) # type: ignore # dysts
237
242
 
238
243
  @staticmethod
239
- def _rhs(x, y, t: float, mu, omega, A) -> np.ndarray:
244
+ def _rhs(x, y, t: float, mu, omega, A) -> np.ndarray: # type: ignore # dysts
240
245
  dxdt = mu * x - omega * y - A * (x**3 + x * y**2)
241
246
  dydt = omega * x + mu * y - A * (x**2 * y + y**3)
242
247
  return np.array([dxdt, dydt])
@@ -247,10 +252,10 @@ class SHO(DynSys):
247
252
  """Linear damped simple harmonic oscillator"""
248
253
 
249
254
  def __init__(self):
250
- super().__init__(metadata_path=LOCAL_DYNAMICS_PATH)
255
+ super().__init__(metadata_path=LOCAL_DYNAMICS_PATH) # type: ignore # dysts
251
256
 
252
257
  @staticmethod
253
- def _rhs(x, y, t: float, a, b, c, d) -> np.ndarray:
258
+ def _rhs(x, y, t: float, a, b, c, d) -> np.ndarray: # type: ignore # dysts
254
259
  dxdt = a * x + b * y
255
260
  dydt = c * x + d * y
256
261
  return np.array([dxdt, dydt])
@@ -261,10 +266,10 @@ class CubicHO(DynSys):
261
266
  """Cubic damped harmonic oscillator."""
262
267
 
263
268
  def __init__(self):
264
- super().__init__(metadata_path=LOCAL_DYNAMICS_PATH)
269
+ super().__init__(metadata_path=LOCAL_DYNAMICS_PATH) # type: ignore # dysts
265
270
 
266
271
  @staticmethod
267
- def _rhs(x, y, t: float, a, b, c, d) -> np.ndarray:
272
+ def _rhs(x, y, t: float, a, b, c, d) -> np.ndarray: # type: ignore # dysts
268
273
  dxdt = a * x**3 + b * y**3
269
274
  dydt = c * x**3 + d * y**3
270
275
  return np.array([dxdt, dydt])
@@ -279,10 +284,10 @@ class VanDerPol(DynSys):
279
284
  """
280
285
 
281
286
  def __init__(self):
282
- super().__init__(metadata_path=LOCAL_DYNAMICS_PATH)
287
+ super().__init__(metadata_path=LOCAL_DYNAMICS_PATH) # type: ignore # dysts
283
288
 
284
289
  @staticmethod
285
- def _rhs(x, x_dot, t: float, mu) -> np.ndarray:
290
+ def _rhs(x, x_dot, t: float, mu) -> np.ndarray: # type: ignore # dysts
286
291
  dxdt = x_dot
287
292
  dx2dt2 = mu * (1 - x**2) * x_dot - x
288
293
  return np.array([dxdt, dx2dt2])
@@ -297,10 +302,10 @@ class Kinematics(DynSys):
297
302
  """
298
303
 
299
304
  def __init__(self):
300
- super().__init__(metadata_path=LOCAL_DYNAMICS_PATH)
305
+ super().__init__(metadata_path=LOCAL_DYNAMICS_PATH) # type: ignore # dysts
301
306
 
302
307
  @staticmethod
303
- def _rhs(x, v, t: float, a) -> np.ndarray:
308
+ def _rhs(x, v, t: float, a) -> np.ndarray: # type: ignore # dysts
304
309
  dxdt = v
305
310
  dvdt = a
306
311
  return np.array([dxdt, dvdt])
@@ -6,7 +6,7 @@ import jax.numpy as jnp
6
6
  import sympy2jax
7
7
  from sympy import Expr, Symbol
8
8
 
9
- from ._typing import ProbData
9
+ from ._typing import SimProbData
10
10
 
11
11
  jax.config.update("jax_enable_x64", True)
12
12
 
@@ -22,7 +22,7 @@ def _gen_data_jax(
22
22
  nonnegative: bool,
23
23
  dt: float,
24
24
  t_end: float,
25
- ) -> ProbData:
25
+ ) -> SimProbData:
26
26
  rhstree = sympy2jax.SymbolicModule(exprs)
27
27
 
28
28
  def ode_sys(t, state, args):
@@ -71,6 +71,8 @@ def _gen_data_jax(
71
71
  if noise_abs is None:
72
72
  assert noise_rel is not None # force type narrowing
73
73
  noise_abs = float(jnp.sqrt(_signal_avg_power(x_train_true)) * noise_rel)
74
+ else:
75
+ noise_rel = noise_abs / float(jnp.sqrt(_signal_avg_power(x_train_true)))
74
76
 
75
77
  x_train = x_train_true + jax.random.normal(key, x_train_true.shape) * noise_abs
76
78
 
@@ -78,8 +80,14 @@ def _gen_data_jax(
78
80
  x_train_true_dot = jnp.array([ode_sys(0, xi, None) for xi in x_train_true])
79
81
 
80
82
  stringy_features = [sym.name for sym in input_features]
81
- return ProbData(
82
- dt, t_train, x_train, x_train_true, x_train_true_dot, stringy_features, sol
83
+ return SimProbData(
84
+ t_train, # type: ignore # jax->numpy
85
+ x_train, # type: ignore # jax->numpy
86
+ stringy_features,
87
+ x_train_true, # type: ignore # jax->numpy
88
+ x_train_true_dot, # type: ignore # jax->numpy
89
+ noise_abs,
90
+ sol,
83
91
  )
84
92
 
85
93
 
@@ -1,5 +1,5 @@
1
1
  from logging import getLogger
2
- from typing import Any, Callable, Literal, TypeVar, cast, overload
2
+ from typing import Any, Literal, TypeVar, cast, overload
3
3
 
4
4
  import matplotlib.pyplot as plt
5
5
  import numpy as np
@@ -14,7 +14,7 @@ from ._typing import (
14
14
  DynamicsTrialData,
15
15
  ExperimentResult,
16
16
  FullDynamicsTrialData,
17
- ProbData,
17
+ SimProbData,
18
18
  SINDyTrialUpdate,
19
19
  _BaseSINDy,
20
20
  )
@@ -41,33 +41,9 @@ DType = TypeVar("DType", bound=np.dtype)
41
41
  MOD_LOG = getLogger(__name__)
42
42
 
43
43
 
44
- def _add_forcing(
45
- forcing_func: Callable[[float], np.ndarray[tuple[T], DType]],
46
- auto_func: Callable[
47
- [float, np.ndarray[tuple[T], DType]], np.ndarray[tuple[T], DType]
48
- ],
49
- ) -> Callable[[float, np.ndarray], np.ndarray]:
50
- """Add a time-dependent forcing term to a rhs func
51
-
52
- Args:
53
- forcing_func: The forcing function to add
54
- auto_func: An existing rhs func for solve_ivp
55
-
56
- Returns:
57
- A rhs function for integration
58
- """
59
-
60
- def sum_of_terms(
61
- t: float, state: np.ndarray[tuple[T], DType]
62
- ) -> np.ndarray[tuple[T], DType]:
63
- return np.array(forcing_func(t)) + np.array(auto_func(t, state))
64
-
65
- return sum_of_terms
66
-
67
-
68
44
  @overload
69
45
  def fit_eval(
70
- data: tuple[list[ProbData], list[dict[sp.Expr, float]]],
46
+ data: tuple[list[SimProbData], list[dict[sp.Expr, float]]],
71
47
  model: _BaseSINDy,
72
48
  simulations: Literal[False],
73
49
  display: bool,
@@ -76,7 +52,7 @@ def fit_eval(
76
52
 
77
53
  @overload
78
54
  def fit_eval(
79
- data: tuple[list[ProbData], list[dict[sp.Expr, float]]],
55
+ data: tuple[list[SimProbData], list[dict[sp.Expr, float]]],
80
56
  model: _BaseSINDy,
81
57
  simulations: Literal[True],
82
58
  display: bool,
@@ -84,7 +60,7 @@ def fit_eval(
84
60
 
85
61
 
86
62
  def fit_eval(
87
- data: tuple[list[ProbData], list[dict[sp.Expr, float]]],
63
+ data: tuple[list[SimProbData], list[dict[sp.Expr, float]]],
88
64
  model: Any,
89
65
  simulations: bool = True,
90
66
  display: bool = True,
@@ -93,7 +69,7 @@ def fit_eval(
93
69
 
94
70
  Args:
95
71
  data: Tuple of (trajectories, true_equations), where ``trajectories`` is
96
- a list of ProbData objects and ``true_equations`` is a list of
72
+ a list of SimProbData objects and ``true_equations`` is a list of
97
73
  dictionaries mapping SymPy symbols to their true coefficients for
98
74
  each state coordinate.
99
75
  model: A SINDy-like model implementing the _BaseSINDy protocol.
@@ -101,6 +77,8 @@ def fit_eval(
101
77
  display: Whether to generate plots as part of evaluation.
102
78
  """
103
79
  model = cast(_BaseSINDy, model)
80
+ for trajectory in data[0]:
81
+ assert trajectory.x_train_true is not None
104
82
  trajectories, true_equations = data
105
83
  input_features = trajectories[0].input_features
106
84
 
@@ -143,7 +121,7 @@ def fit_eval(
143
121
  sims: list[SINDyTrialUpdate] = []
144
122
  integration_metric_list: list[dict[str, float | np.floating]] = []
145
123
  for traj in trajectories:
146
- sim = _simulate_test_data(model, traj.dt, traj.x_train_true)
124
+ sim = _simulate_test_data(model, traj.t_train, traj.x_train_true)
147
125
  sims.append(sim)
148
126
  integration_metric_list.append(
149
127
  integration_metrics(
@@ -154,9 +132,9 @@ def fit_eval(
154
132
  )
155
133
  )
156
134
 
157
- agg_integration_metrics: dict[str, float | np.floating] = {}
135
+ agg_integration_metrics: dict[str, float] = {}
158
136
  for key in integration_metric_list[0].keys():
159
- values = [m[key] for m in integration_metric_list]
137
+ values = cast(list[float], [m[key] for m in integration_metric_list])
160
138
  agg_integration_metrics[key] = float(np.mean(values))
161
139
  metrics.update(agg_integration_metrics)
162
140
 
@@ -57,7 +57,7 @@ def plot_coefficients(
57
57
  feature_names: Sequence[str],
58
58
  ax: Axes,
59
59
  **heatmap_kws,
60
- ) -> None:
60
+ ) -> Axes:
61
61
  """Plot a set of dynamical system coefficients in a heatmap.
62
62
 
63
63
  Args:
@@ -162,6 +162,7 @@ def _compare_coefficient_plots_impl(
162
162
  1, 2, figsize=(1.9 * n_cols, 8), sharey=True, sharex=True
163
163
  )
164
164
  fig.tight_layout()
165
+ assert axs is not None # type narrowing
165
166
 
166
167
  vmax = signed_root(max_val)
167
168
 
@@ -275,7 +276,12 @@ def _plot_training_trajectory(
275
276
  """
276
277
  if x_train.shape[1] == 2:
277
278
  ax.plot(
278
- x_true[:, 0], x_true[:, 1], ".", label="True", color=COLOR.TRUE, **PLOT_KWS
279
+ x_true[:, 0],
280
+ x_true[:, 1],
281
+ ".",
282
+ label="True",
283
+ color=COLOR.TRUE,
284
+ **PLOT_KWS, # type: ignore[arg-type]
279
285
  )
280
286
  ax.plot(
281
287
  x_train[:, 0],
@@ -283,7 +289,7 @@ def _plot_training_trajectory(
283
289
  ".",
284
290
  label="Measured",
285
291
  color=COLOR.MEAS,
286
- **PLOT_KWS,
292
+ **PLOT_KWS, # type: ignore[arg-type]
287
293
  )
288
294
  if (
289
295
  x_smooth is not None
@@ -295,7 +301,7 @@ def _plot_training_trajectory(
295
301
  ".",
296
302
  label="Smoothed",
297
303
  color=COLOR.EST,
298
- **PLOT_KWS,
304
+ **PLOT_KWS, # type: ignore[arg-type]
299
305
  )
300
306
  if labels:
301
307
  ax.set(xlabel="$x_0$", ylabel="$x_1$")
@@ -308,7 +314,7 @@ def _plot_training_trajectory(
308
314
  x_true[:, 2],
309
315
  color=COLOR.TRUE,
310
316
  label="True values",
311
- **PLOT_KWS,
317
+ **PLOT_KWS, # type: ignore[arg-type]
312
318
  )
313
319
 
314
320
  ax.plot(
@@ -1,10 +1,10 @@
1
1
  from collections import defaultdict
2
+ from collections.abc import Mapping
2
3
  from dataclasses import dataclass
3
4
  from typing import (
4
5
  Any,
5
6
  Callable,
6
7
  Literal,
7
- NamedTuple,
8
8
  Optional,
9
9
  Protocol,
10
10
  TypedDict,
@@ -31,9 +31,9 @@ TrajectoryType = TypeVar("TrajectoryType", list[np.ndarray], np.ndarray)
31
31
  class ExperimentResult[T](TypedDict):
32
32
  """Results from a SINDy ODE experiment."""
33
33
 
34
- metrics: float
34
+ metrics: Mapping[str, float | None]
35
35
  data: T
36
- main: float
36
+ main: object
37
37
 
38
38
 
39
39
  class _BaseSINDy(Protocol):
@@ -71,23 +71,38 @@ class _BaseSINDy(Protocol):
71
71
  self, precision: int, fmt: Literal["sympy"]
72
72
  ) -> list[dict[Expr, float]]: ...
73
73
 
74
+ @overload
75
+ def print(self, **kwargs) -> None: ...
76
+
77
+ @overload
74
78
  def print(self, precision: int, **kwargs) -> None: ...
75
79
 
76
80
  def get_feature_names(self) -> list[str]: ...
77
81
 
78
82
 
79
- class ProbData(NamedTuple):
80
- """Data bundle for a single trajectory.
83
+ @dataclass
84
+ class ProbData:
85
+ """Represents a single trajectory's data.
81
86
 
82
- Represents a trajectory's training data and associated metadata.
87
+ For measured data, only t_train, x_train, and input_features are required.
83
88
  """
84
89
 
85
- dt: float
86
90
  t_train: Float1D
87
91
  x_train: Float2D
92
+ input_features: list[str]
93
+
94
+
95
+ @dataclass
96
+ class SimProbData(ProbData):
97
+ """For simulated data, the noiseless trajectory is known.
98
+
99
+ Optionally includes the integrator solution object for evaluating
100
+ at other points.
101
+ """
102
+
88
103
  x_train_true: Float2D
89
104
  x_train_true_dot: Float2D
90
- input_features: list[str]
105
+ noise_abs: float
91
106
  integrator: Optional[Any] = None # diffrax.Solution
92
107
 
93
108
 
@@ -147,7 +162,7 @@ class NestedDict(defaultdict):
147
162
 
148
163
  @dataclass
149
164
  class DynamicsTrialData:
150
- trajectories: list[ProbData]
165
+ trajectories: list[SimProbData]
151
166
  true_equations: list[dict[sp.Expr, float]]
152
167
  sindy_equations: list[dict[sp.Expr, float]]
153
168
  model: _BaseSINDy
@@ -149,7 +149,7 @@ def opt_lookup(kind):
149
149
  def coeff_metrics(
150
150
  coeff_est_dicts: list[dict[sp.Expr, float]],
151
151
  coeff_true_dicts: list[dict[sp.Expr, float]],
152
- ) -> dict[str, float | np.floating]:
152
+ ) -> dict[str, float]:
153
153
  """Compute coefficient metrics from aligned coefficient dictionaries.
154
154
 
155
155
  Both arguments are expected to be lists of coefficient dictionaries sharing
@@ -202,7 +202,7 @@ def coeff_metrics(
202
202
  coeff_true.flatten(), coefficients.flatten()
203
203
  )
204
204
  metrics["main"] = metrics["coeff_f1"]
205
- return metrics
205
+ return {k: float(v) for k, v in metrics.items()}
206
206
 
207
207
 
208
208
  def pred_metrics(
@@ -284,7 +284,7 @@ def unionize_coeff_dicts(
284
284
 
285
285
 
286
286
  def _simulate_test_data(
287
- model: _BaseSINDy, dt: float, x_test: Float2D
287
+ model: _BaseSINDy, t_test: Float1D, x_test: Float2D
288
288
  ) -> SINDyTrialUpdate:
289
289
  """Add simulation data to grid_data
290
290
 
@@ -292,7 +292,6 @@ def _simulate_test_data(
292
292
  Returns:
293
293
  Complete GridPointData
294
294
  """
295
- t_test = cast(Float1D, np.arange(0, len(x_test) * dt, step=dt))
296
295
  t_sim = t_test
297
296
  try:
298
297
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sindy-exp
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: A basic library for constructing dynamics experiments
5
5
  Author-email: Jake Stevens-Haas <jacob.stevens.haas@gmail.com>
6
6
  License: MIT License
@@ -34,7 +34,7 @@ Classifier: Intended Audience :: Science/Research
34
34
  Classifier: License :: OSI Approved :: MIT License
35
35
  Classifier: Natural Language :: English
36
36
  Classifier: Operating System :: POSIX :: Linux
37
- Requires-Python: >=3.10
37
+ Requires-Python: >=3.12
38
38
  Description-Content-Type: text/markdown
39
39
  License-File: LICENSE
40
40
  Requires-Dist: matplotlib
@@ -43,6 +43,7 @@ Requires-Dist: seaborn
43
43
  Requires-Dist: scipy
44
44
  Requires-Dist: sympy
45
45
  Requires-Dist: dysts
46
+ Requires-Dist: scikit-learn
46
47
  Provides-Extra: jax
47
48
  Requires-Dist: jax[cuda12]; extra == "jax"
48
49
  Requires-Dist: diffrax; extra == "jax"
@@ -64,14 +65,20 @@ Requires-Dist: tomli; extra == "dev"
64
65
  Requires-Dist: pysindy>=2.1.0; extra == "dev"
65
66
  Dynamic: license-file
66
67
 
67
- # Dynamics Experiments
68
+ # Overview
68
69
 
69
- A library for constructing dynamics experiments.
70
- This includes data generation and plotting/evaluation.
70
+ A library for constructing dynamics experiments from the dynamics models in the `dysts` package.
71
+ This includes data generation and model evaluation.
72
+ The first contribution is the static typing of trajectory data (`ProbData`) that, I believe, provides the necessary information to be useful in evaluating a wide variety of dynamics/time-series learning methods.
73
+ The second contribution is the collection of utility functions for designing dynamics learning experiments.
74
+ The third contribution is the collection of such experiments for evaluating dynamics/time-series learning models that meet the `BaseSINDy` API.
75
+
76
+ It aims to (a) be amenable to both `numpy` and `jax` arrays, (b) be usable by any dynamics/time-series learning models that meet the `BaseSINDy` or scikit-time API.
77
+ Internally, this package is used/will be used in benchmarking pysindy runtime/memory usage and choosing default hyperparameters.
71
78
 
72
79
  ## Getting started
73
80
 
74
- It's not yet on PyPI, so install it with `pip install sindy_exp @ git+https://github.com/Jacob-Stevens-Haas/sindy-experiments`
81
+ Install with `pip install sindy-exp` or `pip install sindy-exp[jax]`.
75
82
 
76
83
  Generate data
77
84
 
@@ -86,26 +93,26 @@ Evaluate your SINDy-like model with:
86
93
  A list of available ODE systems can be found in `ODE_CLASSES`, which includes most
87
94
  of the systems from the [dysts package](https://pypi.org/project/dysts/) as well as some non-chaotic systems.
88
95
 
89
- ## ODE representation
96
+ ## ODE & Data Model
97
+
98
+ Generated or measured data has the dataclass type `ProbData` or `SimProbData`, respectively,
99
+ to indicate whether it includes ground truth information and a noise level.
100
+ If the data is generated in jax, it will have an integrator that can later be used to evaluate the true data on collocation points.
90
101
 
91
102
  We deal primarily with autonomous ODE systems of the form:
92
103
 
93
104
  dx/dt = sum_i f_i(x)
94
105
 
95
- Thus, we represent ODE systems as a list of right-hand side expressions.
106
+ We represent ODE systems as a list of right-hand side expressions.
96
107
  Each element is a dictionary mapping a term (Sympy expression) to its coefficient.
108
+ Thus, the rhs of an ODE is of type: `list[dict[sympy.Expr, float]]`
97
109
 
98
110
  ## Other useful imports, compatibility, and extensions
99
111
 
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`.
112
+ * The experiments are built to be compatible with the `mitosis` tool, an experiment runner. Mitosis is not a dependency, however, to allow using other experiment runners.
113
+ * To integrate your own experiments or data generation in a way that is compatible, see the `ProbData`, `SimProbData`, `DynamicsTrialData`, and `FullDynamicsTrialData` classes.
114
+ * For plotting tools, see `plot_coefficients`, `compare_coefficient_plots_from_dicts`, `plot_test_trajectory`, `plot_training_data`, and `COLOR`.
115
+ * For evaluation of models, see `coeff_metrics`, `pred_metrics`, and `integration_metrics`.
109
116
 
110
117
  ![3d plot](images/composite.png)
111
118
  ![1d plot](images/1d.png)
@@ -7,6 +7,9 @@ pyproject.toml
7
7
  setup.cfg
8
8
  .github/workflows/main.yaml
9
9
  .github/workflows/release.yml
10
+ images/1d.png
11
+ images/coeff.png
12
+ images/composite.png
10
13
  src/sindy_exp/__init__.py
11
14
  src/sindy_exp/_data.py
12
15
  src/sindy_exp/_diffrax_solver.py
@@ -4,6 +4,7 @@ seaborn
4
4
  scipy
5
5
  sympy
6
6
  dysts
7
+ scikit-learn
7
8
 
8
9
  [dev]
9
10
  ipykernel
sindy_exp-0.2.2/README.md DELETED
@@ -1,45 +0,0 @@
1
- # Dynamics Experiments
2
-
3
- A library for constructing dynamics experiments.
4
- This includes data generation and plotting/evaluation.
5
-
6
- ## Getting started
7
-
8
- It's not yet on PyPI, so install it with `pip install sindy_exp @ git+https://github.com/Jacob-Stevens-Haas/sindy-experiments`
9
-
10
- Generate data
11
-
12
- data = sindy_exp.data.gen_data("lorenz", num_trajectories=5, t_end=10.0, dt=0.01)["data]
13
-
14
- Evaluate your SINDy-like model with:
15
-
16
- sindy_exp.odes.fit_eval(model, data)
17
-
18
- ![Coefficient plots](images/coeff.png)
19
-
20
- A list of available ODE systems can be found in `ODE_CLASSES`, which includes most
21
- of the systems from the [dysts package](https://pypi.org/project/dysts/) as well as some non-chaotic systems.
22
-
23
- ## ODE representation
24
-
25
- We deal primarily with autonomous ODE systems of the form:
26
-
27
- dx/dt = sum_i f_i(x)
28
-
29
- Thus, we represent ODE systems as a list of right-hand side expressions.
30
- Each element is a dictionary mapping a term (Sympy expression) to its coefficient.
31
-
32
- ## Other useful imports, compatibility, and extensions
33
-
34
- This is built to be compatible with dynamics learning models that follow the
35
- pysindy _BaseSINDy interface.
36
- The experiments are also built to be compatible with the `mitosis` tool,
37
- an experiment runner.
38
- To integrate your own experiments or data generation in a way that is compatible,
39
- see the `ProbData` and `DynamicsTrialData` classes.
40
- For plotting tools, see `plot_coefficients`, `compare_coefficient_plots_from_dicts`,
41
- `plot_test_trajectory`, `plot_training_data`, and `COLOR`.
42
- For metrics, see `coeff_metrics`, `pred_metrics`, and `integration_metrics`.
43
-
44
- ![3d plot](images/composite.png)
45
- ![1d plot](images/1d.png)
File without changes
File without changes
File without changes
File without changes