tsdynamics 2.1.1__tar.gz → 2.2.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 (72) hide show
  1. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/PKG-INFO +1 -1
  2. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/__init__.py +1 -1
  3. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/base/ode_base.py +18 -6
  4. tsdynamics-2.2.0/tests/test_diffsol_backend.py +169 -0
  5. tsdynamics-2.2.0/tests/test_diffsol_translation.py +42 -0
  6. tsdynamics-2.1.1/tests/test_diffsol_backend.py +0 -76
  7. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/.gitignore +0 -0
  8. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/LICENSE +0 -0
  9. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/README.md +0 -0
  10. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/pyproject.toml +0 -0
  11. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/analysis/__init__.py +0 -0
  12. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/analysis/fixed_points.py +0 -0
  13. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/analysis/lyapunov.py +0 -0
  14. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/analysis/orbit_diagram.py +0 -0
  15. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/analysis/poincare.py +0 -0
  16. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/backends/__init__.py +0 -0
  17. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/backends/diffsol.py +0 -0
  18. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/base/__init__.py +0 -0
  19. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/base/base.py +0 -0
  20. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/base/dde_base.py +0 -0
  21. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/base/map_base.py +0 -0
  22. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/base/protocol.py +0 -0
  23. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/derived/__init__.py +0 -0
  24. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/derived/_base.py +0 -0
  25. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/derived/ensemble.py +0 -0
  26. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/derived/poincare.py +0 -0
  27. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/derived/projected.py +0 -0
  28. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/derived/stroboscopic.py +0 -0
  29. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/derived/tangent.py +0 -0
  30. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/derived/wrapped.py +0 -0
  31. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/py.typed +0 -0
  32. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/registry.py +0 -0
  33. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/sampling.py +0 -0
  34. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/__init__.py +0 -0
  35. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/continuous/__init__.py +0 -0
  36. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/continuous/chaotic_attractors.py +0 -0
  37. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/continuous/chem_bio_systems.py +0 -0
  38. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/continuous/climate_geophysics.py +0 -0
  39. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/continuous/coupled_systems.py +0 -0
  40. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/continuous/delayed_systems.py +0 -0
  41. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/continuous/exotic_systems.py +0 -0
  42. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/continuous/oscillatory_systems.py +0 -0
  43. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/continuous/physical_systems.py +0 -0
  44. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/continuous/population_dynamics.py +0 -0
  45. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/discrete/__init__.py +0 -0
  46. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/discrete/chaotic_maps.py +0 -0
  47. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/discrete/exotic_maps.py +0 -0
  48. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/discrete/geometric_maps.py +0 -0
  49. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/discrete/polynomial_maps.py +0 -0
  50. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/systems/discrete/population_maps.py +0 -0
  51. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/utils/__init__.py +0 -0
  52. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/utils/general.py +0 -0
  53. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/src/tsdynamics/utils/sagitta_dt.py +0 -0
  54. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/_sampling.py +0 -0
  55. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/conftest.py +0 -0
  56. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_analysis.py +0 -0
  57. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_base.py +0 -0
  58. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_cache_isolation.py +0 -0
  59. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_dde_systems.py +0 -0
  60. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_derived.py +0 -0
  61. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_jacobians.py +0 -0
  62. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_known_values.py +0 -0
  63. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_map_systems.py +0 -0
  64. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_ode_rhs_symbolic.py +0 -0
  65. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_ode_systems.py +0 -0
  66. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_protocol.py +0 -0
  67. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_registry.py +0 -0
  68. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_sampling.py +0 -0
  69. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_signatures.py +0 -0
  70. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_smoke.py +0 -0
  71. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_utils.py +0 -0
  72. {tsdynamics-2.1.1 → tsdynamics-2.2.0}/tests/test_wrapped.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tsdynamics
3
- Version: 2.1.1
3
+ Version: 2.2.0
4
4
  Summary: Compiled ODE/DDE integration, discrete maps, and chaos analysis for dynamical systems.
5
5
  Project-URL: Source, https://github.com/El3ssar/TSDynamics
6
6
  Project-URL: Documentation, https://el3ssar.github.io/TSDynamics/
@@ -48,7 +48,7 @@ from .systems import continuous as _continuous
48
48
  from .systems import discrete as _discrete
49
49
 
50
50
  # Single source of truth for the package version; rewritten by python-semantic-release.
51
- __version__ = "2.1.1"
51
+ __version__ = "2.2.0"
52
52
 
53
53
  # Re-export every system class at the top level so ``from tsdynamics import Lorenz``
54
54
  # works without users having to remember which submodule a system lives in.
@@ -718,11 +718,14 @@ class ContinuousSystem(SystemBase, ABC):
718
718
  ``tsit45`` / ``bdf`` / ``tr_bdf2`` / ``esdirk34``.
719
719
  rtol, atol : float
720
720
  Solver tolerances (default 1e-6 / 1e-9).
721
- backend : {"jitcode", "diffsol"}
722
- ``"jitcode"`` (default) compiles via C; ``"diffsol"`` is the
723
- experimental Rust solver suite
724
- (``pip install tsdynamics[diffsol]``,
725
- see :mod:`tsdynamics.backends.diffsol`).
721
+ backend : {"jitcode", "diffsol", "auto"}
722
+ ``"jitcode"`` (default) compiles the RHS to C; ``"diffsol"`` uses
723
+ the Rust solver suite via LLVM JIT — no C compiler, prebuilt
724
+ wheels (``pip install tsdynamics[diffsol]``), ~10× faster on small
725
+ chaotic systems, and validated against JiTCODE across the whole
726
+ ODE catalogue (see :mod:`tsdynamics.backends.diffsol`). ``"auto"``
727
+ picks ``"diffsol"`` when it is installed, else ``"jitcode"`` —
728
+ the recommended zero-compiler fast path.
726
729
  **integrator_kwargs
727
730
  Forwarded to ``jitcode.set_integrator`` (e.g. ``max_step``).
728
731
 
@@ -731,6 +734,15 @@ class ContinuousSystem(SystemBase, ABC):
731
734
  Trajectory
732
735
  Supports tuple-unpacking: ``t, y = sys.integrate(...)``.
733
736
  """
737
+ if backend == "auto":
738
+ # Prefer the zero-compiler Rust path when its optional dependency
739
+ # is installed; otherwise fall back to the always-available
740
+ # JiTCODE path. Lets `tsdynamics[diffsol]` users get the fast
741
+ # backend without naming it, with no surprise for everyone else.
742
+ from tsdynamics.backends import diffsol as _diffsol
743
+
744
+ backend = "diffsol" if _diffsol.available() else "jitcode"
745
+
734
746
  if backend == "diffsol":
735
747
  from tsdynamics.backends import diffsol as _diffsol
736
748
 
@@ -754,7 +766,7 @@ class ContinuousSystem(SystemBase, ABC):
754
766
  ),
755
767
  )
756
768
  if backend != "jitcode":
757
- raise ValueError(f"Unknown backend {backend!r}; use 'jitcode' or 'diffsol'.")
769
+ raise ValueError(f"Unknown backend {backend!r}; use 'jitcode', 'diffsol', or 'auto'.")
758
770
 
759
771
  method = method or self._default_method
760
772
  integ_name = _INTEGRATOR_MAP.get(method, method)
@@ -0,0 +1,169 @@
1
+ """
2
+ Cross-validation of the experimental diffsol backend against JiTCODE.
3
+
4
+ Skipped entirely when the ``tsdynamics[diffsol]`` extra is not installed.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import numpy as np
10
+ import pytest
11
+
12
+ pytest.importorskip("pydiffsol")
13
+
14
+ import tsdynamics as ts
15
+ from tsdynamics.backends import diffsol as dsl
16
+
17
+ # ---------------------------------------------------------------------------
18
+ # Translator (fast)
19
+ # ---------------------------------------------------------------------------
20
+
21
+
22
+ def test_to_diffsl_lorenz_structure() -> None:
23
+ code, control = dsl.to_diffsl(ts.Lorenz())
24
+ assert control == ["sigma", "rho", "beta"]
25
+ assert "in_i {" in code and "u_i {" in code and "F_i {" in code
26
+ assert "ic0" in code and "ic2" in code
27
+
28
+
29
+ def test_unknown_backend_rejected() -> None:
30
+ with pytest.raises(ValueError, match="Unknown backend"):
31
+ ts.Lorenz().integrate(final_time=1.0, dt=0.1, backend="quantum")
32
+
33
+
34
+ def test_auto_backend_uses_diffsol_when_available() -> None:
35
+ """With pydiffsol installed, backend='auto' routes to diffsol."""
36
+ traj = ts.Lorenz().integrate(final_time=1.0, dt=0.1, ic=[1.0, 1.0, 1.0], backend="auto")
37
+ assert traj.meta["backend"] == "diffsol"
38
+
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Solving + cross-validation (no C compiler needed — pure LLVM JIT)
42
+ # ---------------------------------------------------------------------------
43
+
44
+
45
+ def test_lorenz_diffsol_short_integration() -> None:
46
+ traj = ts.Lorenz().integrate(
47
+ final_time=2.0, dt=0.01, ic=[1.0, 1.0, 1.0], backend="diffsol", rtol=1e-9, atol=1e-11
48
+ )
49
+ assert traj.y.shape == (201, 3) # grid includes the endpoint
50
+ assert np.all(np.isfinite(traj.y))
51
+ assert traj.meta["backend"] == "diffsol"
52
+
53
+
54
+ def test_param_change_reuses_compiled_module() -> None:
55
+ a = ts.Lorenz().integrate(final_time=1.0, dt=0.1, ic=[1.0, 1.0, 1.0], backend="diffsol")
56
+ before = dict(dsl._ODE_CACHE)
57
+ b = ts.Lorenz(params={"rho": 35.0}).integrate(
58
+ final_time=1.0, dt=0.1, ic=[1.0, 1.0, 1.0], backend="diffsol"
59
+ )
60
+ assert dict(dsl._ODE_CACHE) == before # no recompilation
61
+ assert not np.allclose(a.y[-1], b.y[-1]) # but different dynamics
62
+
63
+
64
+ @pytest.mark.slow
65
+ @pytest.mark.parametrize("name,ic", [("Lorenz", [1.0, 1.0, 1.0]), ("Rossler", [1.0, 0.0, 0.0])])
66
+ def test_cross_validation_against_jitcode(name: str, ic: list) -> None:
67
+ """Both backends must agree to tight tolerance over a short window."""
68
+ sys_a = getattr(ts, name)()
69
+ sys_b = getattr(ts, name)()
70
+ kw = dict(final_time=5.0, dt=0.01, ic=ic, rtol=1e-10, atol=1e-12)
71
+ ref = sys_a.integrate(method="dop853", **kw)
72
+ alt = sys_b.integrate(backend="diffsol", **kw)
73
+ np.testing.assert_allclose(alt.y, ref.y, rtol=1e-5, atol=1e-6)
74
+
75
+
76
+ @pytest.mark.slow
77
+ def test_cross_validation_over_sample() -> None:
78
+ """
79
+ diffsol (BDF) reproduces JiTCODE (dop853) across the curated ODE sample.
80
+
81
+ Short horizon + tight tolerances keep chaotic sensitivity from masking a
82
+ real translator/solver discrepancy; max abs deviation must stay < 1e-3.
83
+ """
84
+ import sys
85
+ from pathlib import Path
86
+
87
+ sys.path.insert(0, str(Path(__file__).parent))
88
+ from _sampling import INTEGRATION_SAMPLE
89
+
90
+ from tsdynamics import registry
91
+
92
+ bad = []
93
+ for name in INTEGRATION_SAMPLE:
94
+ cls = registry.get(name).cls
95
+ ic = cls().resolve_ic(None)
96
+ yj = (
97
+ cls()
98
+ .integrate(ic=ic, final_time=1.5, dt=0.03, method="dop853", rtol=1e-10, atol=1e-12)
99
+ .y
100
+ )
101
+ yd = (
102
+ cls()
103
+ .integrate(
104
+ ic=ic,
105
+ final_time=1.5,
106
+ dt=0.03,
107
+ backend="diffsol",
108
+ method="LSODA",
109
+ rtol=1e-10,
110
+ atol=1e-12,
111
+ )
112
+ .y
113
+ )
114
+ n = min(len(yj), len(yd))
115
+ dev = float(np.max(np.abs(yj[:n] - yd[:n])))
116
+ if dev >= 1e-3:
117
+ bad.append((name, dev))
118
+ assert not bad, f"diffsol disagrees with jitcode on: {bad}"
119
+
120
+
121
+ @pytest.mark.slow
122
+ def test_stiff_solver_path() -> None:
123
+ """The BDF mapping handles a stiff-ish problem."""
124
+ traj = ts.Lorenz().integrate(
125
+ final_time=2.0, dt=0.01, ic=[1.0, 1.0, 1.0], backend="diffsol", method="LSODA"
126
+ )
127
+ assert np.all(np.isfinite(traj.y))
128
+
129
+
130
+ @pytest.mark.full
131
+ def test_cross_validation_full_catalogue() -> None:
132
+ """
133
+ The gate for flipping the default backend to diffsol: *every* built-in ODE
134
+ must integrate on diffsol (BDF) and agree with JiTCODE (dop853) to <1e-3
135
+ over a short window. Runs nightly (``-m full``) with the diffsol extra.
136
+ """
137
+ from tsdynamics import registry
138
+
139
+ bad = []
140
+ for e in registry.all_systems(family="ode"):
141
+ cls = e.cls
142
+ try:
143
+ ic = cls().resolve_ic(None)
144
+ yj = (
145
+ cls()
146
+ .integrate(ic=ic, final_time=1.0, dt=0.02, method="dop853", rtol=1e-10, atol=1e-12)
147
+ .y
148
+ )
149
+ yd = (
150
+ cls()
151
+ .integrate(
152
+ ic=ic,
153
+ final_time=1.0,
154
+ dt=0.02,
155
+ backend="diffsol",
156
+ method="LSODA",
157
+ rtol=1e-10,
158
+ atol=1e-12,
159
+ )
160
+ .y
161
+ )
162
+ except Exception as exc: # noqa: BLE001 — record which system & why
163
+ bad.append((e.name, f"error: {str(exc).splitlines()[-1][:50]}"))
164
+ continue
165
+ n = min(len(yj), len(yd))
166
+ dev = float(np.max(np.abs(yj[:n] - yd[:n])))
167
+ if dev >= 1e-3:
168
+ bad.append((e.name, round(dev, 5)))
169
+ assert not bad, f"{len(bad)}/118 ODEs disagree or error on diffsol: {bad}"
@@ -0,0 +1,42 @@
1
+ """
2
+ Diffsol *translator* coverage — runs without the pydiffsol extra.
3
+
4
+ ``to_diffsl`` only needs SymEngine + the jitcode symbols, so this guards the
5
+ SymEngine→DiffSL translation for the whole ODE catalogue on every CI run,
6
+ independently of whether the Rust solver itself is installed. The numeric
7
+ cross-validation against JiTCODE lives in ``test_diffsol_backend.py`` (which
8
+ needs pydiffsol and is therefore skipped when the extra is absent).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from tsdynamics import registry
14
+ from tsdynamics.backends.diffsol import DiffSLTranslationError, to_diffsl
15
+
16
+
17
+ def test_every_ode_translates_to_diffsl(ode_entry) -> None:
18
+ """Every built-in ODE must translate to a well-formed DiffSL module."""
19
+ try:
20
+ code, control_names = to_diffsl(ode_entry.cls())
21
+ except DiffSLTranslationError as exc:
22
+ # A genuine "DiffSL can't express this" is allowed but must be visible:
23
+ # fail loudly so the coverage number can't silently regress unnoticed.
24
+ raise AssertionError(f"{ode_entry.name}: no longer translates to DiffSL — {exc}") from exc
25
+ assert "in_i {" in code and "u_i {" in code and "F_i {" in code
26
+ # control inputs = the non-structural params (structural ones, e.g.
27
+ # Lorenz96's N, are baked into the generated module, not solve-time inputs).
28
+ cls = ode_entry.cls
29
+ structural = getattr(cls, "_structural_params", frozenset())
30
+ expected = [k for k in cls().params if k not in structural]
31
+ assert list(control_names) == expected
32
+
33
+
34
+ def test_translation_coverage_is_total() -> None:
35
+ """The whole ODE catalogue translates — the headline Phase-2 coverage fact."""
36
+ failed = []
37
+ for e in registry.all_systems(family="ode"):
38
+ try:
39
+ to_diffsl(e.cls())
40
+ except Exception as exc: # noqa: BLE001 — record, don't abort
41
+ failed.append((e.name, str(exc).splitlines()[0][:60]))
42
+ assert not failed, f"{len(failed)} ODE systems no longer translate: {failed}"
@@ -1,76 +0,0 @@
1
- """
2
- Cross-validation of the experimental diffsol backend against JiTCODE.
3
-
4
- Skipped entirely when the ``tsdynamics[diffsol]`` extra is not installed.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import numpy as np
10
- import pytest
11
-
12
- pytest.importorskip("pydiffsol")
13
-
14
- import tsdynamics as ts
15
- from tsdynamics.backends import diffsol as dsl
16
-
17
- # ---------------------------------------------------------------------------
18
- # Translator (fast)
19
- # ---------------------------------------------------------------------------
20
-
21
-
22
- def test_to_diffsl_lorenz_structure() -> None:
23
- code, control = dsl.to_diffsl(ts.Lorenz())
24
- assert control == ["sigma", "rho", "beta"]
25
- assert "in_i {" in code and "u_i {" in code and "F_i {" in code
26
- assert "ic0" in code and "ic2" in code
27
-
28
-
29
- def test_unknown_backend_rejected() -> None:
30
- with pytest.raises(ValueError, match="Unknown backend"):
31
- ts.Lorenz().integrate(final_time=1.0, dt=0.1, backend="quantum")
32
-
33
-
34
- # ---------------------------------------------------------------------------
35
- # Solving + cross-validation (no C compiler needed — pure LLVM JIT)
36
- # ---------------------------------------------------------------------------
37
-
38
-
39
- def test_lorenz_diffsol_short_integration() -> None:
40
- traj = ts.Lorenz().integrate(
41
- final_time=2.0, dt=0.01, ic=[1.0, 1.0, 1.0], backend="diffsol", rtol=1e-9, atol=1e-11
42
- )
43
- assert traj.y.shape == (201, 3) # grid includes the endpoint
44
- assert np.all(np.isfinite(traj.y))
45
- assert traj.meta["backend"] == "diffsol"
46
-
47
-
48
- def test_param_change_reuses_compiled_module() -> None:
49
- a = ts.Lorenz().integrate(final_time=1.0, dt=0.1, ic=[1.0, 1.0, 1.0], backend="diffsol")
50
- before = dict(dsl._ODE_CACHE)
51
- b = ts.Lorenz(params={"rho": 35.0}).integrate(
52
- final_time=1.0, dt=0.1, ic=[1.0, 1.0, 1.0], backend="diffsol"
53
- )
54
- assert dict(dsl._ODE_CACHE) == before # no recompilation
55
- assert not np.allclose(a.y[-1], b.y[-1]) # but different dynamics
56
-
57
-
58
- @pytest.mark.slow
59
- @pytest.mark.parametrize("name,ic", [("Lorenz", [1.0, 1.0, 1.0]), ("Rossler", [1.0, 0.0, 0.0])])
60
- def test_cross_validation_against_jitcode(name: str, ic: list) -> None:
61
- """Both backends must agree to tight tolerance over a short window."""
62
- sys_a = getattr(ts, name)()
63
- sys_b = getattr(ts, name)()
64
- kw = dict(final_time=5.0, dt=0.01, ic=ic, rtol=1e-10, atol=1e-12)
65
- ref = sys_a.integrate(method="dop853", **kw)
66
- alt = sys_b.integrate(backend="diffsol", **kw)
67
- np.testing.assert_allclose(alt.y, ref.y, rtol=1e-5, atol=1e-6)
68
-
69
-
70
- @pytest.mark.slow
71
- def test_stiff_solver_path() -> None:
72
- """The BDF mapping handles a stiff-ish problem."""
73
- traj = ts.Lorenz().integrate(
74
- final_time=2.0, dt=0.01, ic=[1.0, 1.0, 1.0], backend="diffsol", method="LSODA"
75
- )
76
- assert np.all(np.isfinite(traj.y))
File without changes
File without changes
File without changes
File without changes
File without changes