tsdynamics 5.2.4__tar.gz → 5.2.6__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.
- tsdynamics-5.2.6/PKG-INFO +221 -0
- tsdynamics-5.2.6/README.md +185 -0
- tsdynamics-5.2.6/crates/tsdyn-core/src/bridge/lyapunov.rs +162 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/map.rs +89 -1
- tsdynamics-5.2.6/crates/tsdyn-core/src/bridge/map_lyapunov.rs +142 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/marshal.rs +5 -2
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/mod.rs +5 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/lib.rs +208 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/basin.rs +13 -5
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/ensemble.rs +8 -4
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/lib.rs +15 -0
- tsdynamics-5.2.6/crates/tsdyn-engine/src/lyapunov.rs +476 -0
- tsdynamics-5.2.6/crates/tsdyn-engine/src/map_lyapunov.rs +436 -0
- tsdynamics-5.2.6/crates/tsdyn-engine/src/param_sweep.rs +381 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/src/op.rs +6 -2
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/dop853.rs +8 -3
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/registry.rs +58 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/solver.rs +9 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/pyproject.toml +4 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/__init__.py +1 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_base.py +1 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_scaling.py +30 -9
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/basins/metrics.py +17 -3
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/chaos/expansion.py +1 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/chaos/zero_one.py +7 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/dimensions/_common.py +1 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/dimensions/correlation.py +12 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/embedding/delay.py +23 -8
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/embedding/dimension.py +13 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/sample.py +11 -0
- tsdynamics-5.2.6/src/tsdynamics/analysis/fixedpoints/_interval.py +763 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/fixedpoints/fixed.py +107 -6
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/fixedpoints/periodic.py +9 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/lyapunov/__init__.py +121 -3
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/lyapunov/from_data.py +10 -2
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/orbits/orbit_diagram.py +172 -54
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/sampling/sagitta.py +24 -6
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/surrogate/_common.py +15 -7
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/projected.py +34 -16
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/tangent.py +246 -25
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/compile.py +168 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/events.py +10 -3
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/run.py +278 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/continuous.py +80 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/discrete.py +50 -3
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/wrapped.py +4 -3
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/plugins.py +4 -3
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/chaotic_attractors.py +6 -4
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/climate_geophysics.py +11 -2
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/exotic_systems.py +6 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/oscillatory_systems.py +18 -4
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/discrete/chaotic_maps.py +4 -6
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/discrete/exotic_maps.py +14 -8
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/discrete/geometric_maps.py +10 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/transforms/spectral.py +3 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/compose.py +23 -6
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/mpl/_core.py +78 -8
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/plotly/_anim.py +80 -9
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/threejs/_lower.py +7 -5
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/spec.py +8 -2
- tsdynamics-5.2.4/PKG-INFO +0 -160
- tsdynamics-5.2.4/README.md +0 -124
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/LICENSE +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/Cargo.lock +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/basin.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/dde.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/events.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/ode.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/sde.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/stepper.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/dde.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/event.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/integrate.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/map.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/problem.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/rng.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/sde.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/testkit.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/src/builder.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/src/eval.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/src/lib.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/src/reference.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/src/tape.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/coverage_tape.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/fixtures/CircadianRhythm.txt +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/fixtures/Lorenz.txt +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/fixtures/SanUmSrisuchinwong.txt +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/fixtures/SprottMore.txt +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/fixtures/Torus.txt +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/fixtures/YuWang2.txt +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/gen_fixtures.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/golden_fixtures.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/roundtrip.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/src/codegen.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/src/error.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/src/evaluator.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/src/lib.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/src/shims.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/tests/compile_latency.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/tests/golden_fixtures.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/tests/interpreter_equivalence.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/caps.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/adams.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/bs3.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/cashkarp.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/control.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/euler.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/heun.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/heun_euler.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/midpoint.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/mod.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/ralston.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/rk4.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/rk45.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/rk4_38.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/rkf45.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/ssprk3.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/testkit.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/tsit5.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/backward_euler.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/bdf.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/control.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/implicit_midpoint.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/linalg.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/mod.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/newton.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/rosenbrock.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/sdirk2.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/trapezoid.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/trbdf2.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/lib.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/sde/euler_maruyama.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/sde/milstein.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/sde/mod.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/sde/testkit.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/tests/explicit_accuracy.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/tests/implicit_stiff.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/tests/registry.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-vm/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-vm/src/interp.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-vm/src/lib.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-vm/tests/golden_fixtures.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-vm/tests/oracle_equivalence.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/_result_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_plotbuilder.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_array.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_collection.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_json.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_scalar.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_viz.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_tangent.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/basins/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/basins/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/basins/attractors.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/basins/basins.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/basins/continuation.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/chaos/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/chaos/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/chaos/gali.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/dimensions/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/dimensions/_scaling.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/dimensions/fixedmass.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/dimensions/generalized.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/embedding/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/embedding/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/embedding/embed.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/core.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/dispersion.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/lz.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/multiscale.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/permutation.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/fixedpoints/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/fixedpoints/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/orbits/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/orbits/poincare.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/orbits/return_map.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/recurrence/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/recurrence/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/recurrence/matrix.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/recurrence/rqa.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/recurrence/windowed.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/sampling/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/surrogate/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/surrogate/generators.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/surrogate/hypothesis.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/surrogate/statistics.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/data/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/data/sampling.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/data/trajectory.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/_base.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/_crossings.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/_variational.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/ensemble.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/poincare.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/stroboscopic.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/wrapped.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/_families.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/problem.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/reference.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/run_methods.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/sde_run.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/stepper.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/symbols.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/errors.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/_accessors.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/_dde_lyapunov.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/_plottable.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/base.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/delay.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/protocol.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/stochastic.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/py.typed +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/registry.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/solvers/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/solvers/explicit.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/solvers/implicit.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/solvers/select.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/solvers/stochastic.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/chem_bio_systems.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/coupled_systems.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/delayed_systems.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/physical_systems.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/population_dynamics.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/spatial_fields.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/discrete/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/discrete/polynomial_maps.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/discrete/population_maps.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/transforms/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/transforms/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/transforms/_result.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/transforms/features.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/transforms/preprocessing.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/utils/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/utils/grids.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/export.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/producers.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/caps.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/json.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/mpl/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/mpl/_anim.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/mpl/_threed.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/plotly/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/plotly/_composite.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/plotly/_core.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/plotly/_html.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/plotly/_threed.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/threejs/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/style.py +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tsdynamics
|
|
3
|
+
Version: 5.2.6
|
|
4
|
+
Classifier: Development Status :: 4 - Beta
|
|
5
|
+
Classifier: Intended Audience :: Science/Research
|
|
6
|
+
Classifier: Operating System :: OS Independent
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
9
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
11
|
+
Classifier: Typing :: Typed
|
|
12
|
+
Requires-Dist: numpy>=2.0,<3.0
|
|
13
|
+
Requires-Dist: scipy>=1.14,<2.0
|
|
14
|
+
Requires-Dist: symengine>=0.13,<1.0
|
|
15
|
+
Requires-Dist: sympy>=1.13,<2.0
|
|
16
|
+
Requires-Dist: plotly>=6,<7 ; extra == 'interactive'
|
|
17
|
+
Requires-Dist: lzcomplexity>=0.9 ; extra == 'lz'
|
|
18
|
+
Requires-Dist: matplotlib>=3.9,<5 ; extra == 'plot'
|
|
19
|
+
Requires-Dist: matplotlib>=3.9,<5 ; extra == 'viz'
|
|
20
|
+
Provides-Extra: interactive
|
|
21
|
+
Provides-Extra: lz
|
|
22
|
+
Provides-Extra: plot
|
|
23
|
+
Provides-Extra: viz
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Summary: Zero-warmup Rust-engine ODE/DDE/SDE integration, discrete maps, and chaos analysis for dynamical systems.
|
|
26
|
+
Keywords: dynamical systems,ODE,DDE,chaos,Lyapunov exponents,nonlinear dynamics,bifurcation,attractors,reservoir computing
|
|
27
|
+
Author-email: Daniel Estevez <kemossabee@gmail.com>
|
|
28
|
+
License-Expression: MIT
|
|
29
|
+
Requires-Python: >=3.12, <4.0
|
|
30
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
31
|
+
Project-URL: Changelog, https://github.com/El3ssar/TSDynamics/releases
|
|
32
|
+
Project-URL: Documentation, https://el3ssar.github.io/TSDynamics/
|
|
33
|
+
Project-URL: Issues, https://github.com/El3ssar/TSDynamics/issues
|
|
34
|
+
Project-URL: Source, https://github.com/El3ssar/TSDynamics
|
|
35
|
+
|
|
36
|
+
# TSDynamics
|
|
37
|
+
|
|
38
|
+
[](https://pypi.org/project/tsdynamics/)
|
|
39
|
+
[](https://github.com/El3ssar/TSDynamics/actions/workflows/ci.yml)
|
|
40
|
+
[](https://github.com/El3ssar/TSDynamics/actions/workflows/release.yml)
|
|
41
|
+
[](https://el3ssar.github.io/TSDynamics/)
|
|
42
|
+
[](https://pypi.org/project/tsdynamics/)
|
|
43
|
+
[](https://codecov.io/gh/El3ssar/TSDynamics)
|
|
44
|
+
|
|
45
|
+
**Dynamical systems in Python: 151 built-in systems, a native Rust integration
|
|
46
|
+
engine, and a chaos-analysis toolkit — with the simplest system-definition
|
|
47
|
+
contract anywhere.**
|
|
48
|
+
|
|
49
|
+
You write the math (one symbolic method); TSDynamics lowers it to a native Rust
|
|
50
|
+
engine and handles integration, Lyapunov spectra, bifurcation diagrams, Poincaré
|
|
51
|
+
sections, attractors & basins — and even the documentation page for your system.
|
|
52
|
+
|
|
53
|
+
<p align="center">
|
|
54
|
+
<img src="docs/assets/readme/lorenz_spin.gif" width="440" alt="A spinning Lorenz attractor"><br>
|
|
55
|
+
<em>A built-in Lorenz attractor — integrated, spun, and saved to a GIF (code below).</em>
|
|
56
|
+
</p>
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import tsdynamics as ts
|
|
60
|
+
|
|
61
|
+
lor = ts.Lorenz()
|
|
62
|
+
traj = lor.integrate(final_time=100.0, dt=0.01)
|
|
63
|
+
traj["x"] # named component access
|
|
64
|
+
lor.lyapunov_spectrum() # → [0.91, ~0, -14.57]
|
|
65
|
+
ts.kaplan_yorke_dimension(_) # → ~2.06
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
📖 **Documentation: <https://el3ssar.github.io/TSDynamics/>**
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Define your own system
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import tsdynamics as ts
|
|
76
|
+
|
|
77
|
+
class Rossler(ts.ContinuousSystem):
|
|
78
|
+
params = {"a": 0.2, "b": 0.2, "c": 5.7}
|
|
79
|
+
dim = 3
|
|
80
|
+
variables = ("x", "y", "z") # optional niceties
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def _equations(y, t, *, a, b, c):
|
|
84
|
+
x, yv, z = y(0), y(1), y(2)
|
|
85
|
+
return (-yv - z, x + a * yv, b + z * (x - c))
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
That's the whole contract. The class auto-registers: every analysis tool works
|
|
89
|
+
on it, the test-suite sweeps it, and the docs build renders its equations
|
|
90
|
+
(LaTeX, straight from the symbolics) and its attractor — zero extra steps. Delay
|
|
91
|
+
systems use `y(0, t - tau)`; maps implement `_step`/`_jacobian` (signature order
|
|
92
|
+
validated at import); SDEs add a `_diffusion` term.
|
|
93
|
+
|
|
94
|
+
## From equations to figures
|
|
95
|
+
|
|
96
|
+
Every system produces a `Trajectory`, and every `Trajectory` knows how to plot
|
|
97
|
+
itself (`to_plot_spec` auto-detects the right kind from the data). The plot is a
|
|
98
|
+
backend-neutral *spec* you tweak fluently, then `render` or `save` to matplotlib,
|
|
99
|
+
plotly (interactive), or a three.js / JSON export.
|
|
100
|
+
|
|
101
|
+
**Bifurcation diagram of the logistic map**, with the period-doubling onsets
|
|
102
|
+
marked — `orbit_diagram` is one call, and `.bifurcation_points()` finds the
|
|
103
|
+
cascade ($r_1 = 3$, $r_2 = 1 + \sqrt6 \approx 3.449$, …):
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import numpy as np, tsdynamics as ts
|
|
107
|
+
from tsdynamics.viz import Annotation
|
|
108
|
+
|
|
109
|
+
orbit = ts.orbit_diagram(ts.Logistic(), "r", np.linspace(2.8, 4.0, 2000))
|
|
110
|
+
pts = orbit.bifurcation_points()
|
|
111
|
+
|
|
112
|
+
spec = orbit.to_plot_spec().relabel(x="r", y="x*", title="Logistic bifurcation")
|
|
113
|
+
spec.style(color="k", s=0.2, alpha=0.5) # tiny semi-transparent dots
|
|
114
|
+
spec.annotations = [
|
|
115
|
+
Annotation("vline", x=pts[i], text=lbl, style={"color": "red", "linestyle": "--"})
|
|
116
|
+
for i, lbl in [(0, " r₁"), (1, " r₂"), (3, " r₄")]
|
|
117
|
+
]
|
|
118
|
+
spec.save("bifurcation.png", size=(1600, 700))
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+

|
|
122
|
+
|
|
123
|
+
**A PDE, too** — the Kuramoto–Sivashinsky equation is a built-in spatially
|
|
124
|
+
extended system; its space–time field is auto-detected and drawn as a heatmap:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
ks = ts.KuramotoSivashinsky(N=128, L=22.0)
|
|
128
|
+
traj = ks.integrate(final_time=200.0, dt=0.25)
|
|
129
|
+
traj.to_plot_spec().save("ks.png") # 128-mode space–time field
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
<p align="center">
|
|
135
|
+
<img src="docs/assets/readme/ks_spacetime.png" alt="Kuramoto–Sivashinsky space–time field">
|
|
136
|
+
</p>
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
The spinning attractor at the top is the same `to_plot_spec`, animated:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
traj = ts.Lorenz().integrate(final_time=100.0, dt=0.01)
|
|
143
|
+
spec = traj.to_plot_spec()
|
|
144
|
+
spec.style(axes=False).trail(None).camera(spin=0.4) # full curve, no axes, rotate
|
|
145
|
+
spec.animate(fps=30, duration=10, loop=True)
|
|
146
|
+
spec.save("lorenz.gif")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## A taste of the analysis layer
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
import numpy as np, tsdynamics as ts
|
|
153
|
+
|
|
154
|
+
# Poincaré section of the Rössler attractor (root-refined crossings)
|
|
155
|
+
section = ts.poincare_section(ts.Rossler(), plane=("y", 0.0, "up"), n=500)
|
|
156
|
+
|
|
157
|
+
# Fixed points of the Hénon map, with stability
|
|
158
|
+
ts.fixed_points(ts.Henon())
|
|
159
|
+
# [FixedPoint([-1.1314 -0.3394], unstable), FixedPoint([0.6314 0.1894], unstable)]
|
|
160
|
+
|
|
161
|
+
# Maximal Lyapunov exponent — no Jacobian needed
|
|
162
|
+
ts.max_lyapunov(ts.Lorenz(ic=[1, 1, 1]), dt=0.05) # ≈ 0.9
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Plus: **attractors & basins** of any flow or map, correlation/Rényi **fractal
|
|
166
|
+
dimensions**, **permutation/sample/dispersion entropy**, **RQA** (recurrence
|
|
167
|
+
quantification), **surrogate** hypothesis tests, **delay embedding** (Takens,
|
|
168
|
+
optimal τ, Cao/FNN), GALI & the 0–1 chaos test, and Lyapunov exponents **from a
|
|
169
|
+
bare time series** (Kantz/Rosenstein).
|
|
170
|
+
|
|
171
|
+
## Highlights
|
|
172
|
+
|
|
173
|
+
- **Three families, one interface**
|
|
174
|
+
|
|
175
|
+
- **ODEs**
|
|
176
|
+
|
|
177
|
+
- **DDEs**
|
|
178
|
+
- **SDEs**
|
|
179
|
+
- **Discrete Maps**
|
|
180
|
+
|
|
181
|
+
- **151 built-in systems** with literature parameters.
|
|
182
|
+
|
|
183
|
+
- **Native engine**: equations lower to a Rust engine (an SSA-tape
|
|
184
|
+
interpreter, with a Cranelift JIT alongside) in-process; parameters are runtime
|
|
185
|
+
values, so changing them is free and there is no compile step or cache.
|
|
186
|
+
|
|
187
|
+
- **Composition** — a `PoincareMap` of a flow *is* a discrete map, so
|
|
188
|
+
`orbit_diagram(PoincareMap(Rossler(), ("y", 0.0)), "c", values)` draws the
|
|
189
|
+
bifurcation diagram of a *flow* in one line.
|
|
190
|
+
|
|
191
|
+
- **Backend-neutral plotting** — one `PlotSpec` IR renders to matplotlib, plotly
|
|
192
|
+
(interactive + animated HTML), three.js, or JSON, with a fluent styling/theming
|
|
193
|
+
vocabulary.
|
|
194
|
+
|
|
195
|
+
## Install
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
pip install tsdynamics # or: uv add tsdynamics
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
A prebuilt `abi3` wheel (manylinux / musllinux / macOS / Windows) bundles the
|
|
202
|
+
native Rust engine. No Rust toolchain and no C compiler needed to install or
|
|
203
|
+
run. Optional plotting extra: `tsdynamics[plot,interactive]` (matplotlib, plotly).
|
|
204
|
+
|
|
205
|
+
## Development
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
git clone https://github.com/El3ssar/TSDynamics && cd TSDynamics
|
|
209
|
+
uv sync --group dev --group docs
|
|
210
|
+
uv run pytest -m "not slow" --no-cov # fast tier
|
|
211
|
+
uv run pytest --no-cov # full local suite
|
|
212
|
+
TSD_DOCS_FIGURES=0 uv run mkdocs serve # docs preview
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Releases are automated: conventional-commit PR titles drive
|
|
216
|
+
[semantic-release](https://python-semantic-release.readthedocs.io/) on merge see [CONTRIBUTING](https://el3ssar.github.io/TSDynamics/project/contributing/).
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
MIT © Daniel Estevez
|
|
221
|
+
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# TSDynamics
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/tsdynamics/)
|
|
4
|
+
[](https://github.com/El3ssar/TSDynamics/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/El3ssar/TSDynamics/actions/workflows/release.yml)
|
|
6
|
+
[](https://el3ssar.github.io/TSDynamics/)
|
|
7
|
+
[](https://pypi.org/project/tsdynamics/)
|
|
8
|
+
[](https://codecov.io/gh/El3ssar/TSDynamics)
|
|
9
|
+
|
|
10
|
+
**Dynamical systems in Python: 151 built-in systems, a native Rust integration
|
|
11
|
+
engine, and a chaos-analysis toolkit — with the simplest system-definition
|
|
12
|
+
contract anywhere.**
|
|
13
|
+
|
|
14
|
+
You write the math (one symbolic method); TSDynamics lowers it to a native Rust
|
|
15
|
+
engine and handles integration, Lyapunov spectra, bifurcation diagrams, Poincaré
|
|
16
|
+
sections, attractors & basins — and even the documentation page for your system.
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img src="docs/assets/readme/lorenz_spin.gif" width="440" alt="A spinning Lorenz attractor"><br>
|
|
20
|
+
<em>A built-in Lorenz attractor — integrated, spun, and saved to a GIF (code below).</em>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import tsdynamics as ts
|
|
25
|
+
|
|
26
|
+
lor = ts.Lorenz()
|
|
27
|
+
traj = lor.integrate(final_time=100.0, dt=0.01)
|
|
28
|
+
traj["x"] # named component access
|
|
29
|
+
lor.lyapunov_spectrum() # → [0.91, ~0, -14.57]
|
|
30
|
+
ts.kaplan_yorke_dimension(_) # → ~2.06
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
📖 **Documentation: <https://el3ssar.github.io/TSDynamics/>**
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Define your own system
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import tsdynamics as ts
|
|
41
|
+
|
|
42
|
+
class Rossler(ts.ContinuousSystem):
|
|
43
|
+
params = {"a": 0.2, "b": 0.2, "c": 5.7}
|
|
44
|
+
dim = 3
|
|
45
|
+
variables = ("x", "y", "z") # optional niceties
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def _equations(y, t, *, a, b, c):
|
|
49
|
+
x, yv, z = y(0), y(1), y(2)
|
|
50
|
+
return (-yv - z, x + a * yv, b + z * (x - c))
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
That's the whole contract. The class auto-registers: every analysis tool works
|
|
54
|
+
on it, the test-suite sweeps it, and the docs build renders its equations
|
|
55
|
+
(LaTeX, straight from the symbolics) and its attractor — zero extra steps. Delay
|
|
56
|
+
systems use `y(0, t - tau)`; maps implement `_step`/`_jacobian` (signature order
|
|
57
|
+
validated at import); SDEs add a `_diffusion` term.
|
|
58
|
+
|
|
59
|
+
## From equations to figures
|
|
60
|
+
|
|
61
|
+
Every system produces a `Trajectory`, and every `Trajectory` knows how to plot
|
|
62
|
+
itself (`to_plot_spec` auto-detects the right kind from the data). The plot is a
|
|
63
|
+
backend-neutral *spec* you tweak fluently, then `render` or `save` to matplotlib,
|
|
64
|
+
plotly (interactive), or a three.js / JSON export.
|
|
65
|
+
|
|
66
|
+
**Bifurcation diagram of the logistic map**, with the period-doubling onsets
|
|
67
|
+
marked — `orbit_diagram` is one call, and `.bifurcation_points()` finds the
|
|
68
|
+
cascade ($r_1 = 3$, $r_2 = 1 + \sqrt6 \approx 3.449$, …):
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import numpy as np, tsdynamics as ts
|
|
72
|
+
from tsdynamics.viz import Annotation
|
|
73
|
+
|
|
74
|
+
orbit = ts.orbit_diagram(ts.Logistic(), "r", np.linspace(2.8, 4.0, 2000))
|
|
75
|
+
pts = orbit.bifurcation_points()
|
|
76
|
+
|
|
77
|
+
spec = orbit.to_plot_spec().relabel(x="r", y="x*", title="Logistic bifurcation")
|
|
78
|
+
spec.style(color="k", s=0.2, alpha=0.5) # tiny semi-transparent dots
|
|
79
|
+
spec.annotations = [
|
|
80
|
+
Annotation("vline", x=pts[i], text=lbl, style={"color": "red", "linestyle": "--"})
|
|
81
|
+
for i, lbl in [(0, " r₁"), (1, " r₂"), (3, " r₄")]
|
|
82
|
+
]
|
|
83
|
+
spec.save("bifurcation.png", size=(1600, 700))
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+

|
|
87
|
+
|
|
88
|
+
**A PDE, too** — the Kuramoto–Sivashinsky equation is a built-in spatially
|
|
89
|
+
extended system; its space–time field is auto-detected and drawn as a heatmap:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
ks = ts.KuramotoSivashinsky(N=128, L=22.0)
|
|
93
|
+
traj = ks.integrate(final_time=200.0, dt=0.25)
|
|
94
|
+
traj.to_plot_spec().save("ks.png") # 128-mode space–time field
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
<p align="center">
|
|
100
|
+
<img src="docs/assets/readme/ks_spacetime.png" alt="Kuramoto–Sivashinsky space–time field">
|
|
101
|
+
</p>
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
The spinning attractor at the top is the same `to_plot_spec`, animated:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
traj = ts.Lorenz().integrate(final_time=100.0, dt=0.01)
|
|
108
|
+
spec = traj.to_plot_spec()
|
|
109
|
+
spec.style(axes=False).trail(None).camera(spin=0.4) # full curve, no axes, rotate
|
|
110
|
+
spec.animate(fps=30, duration=10, loop=True)
|
|
111
|
+
spec.save("lorenz.gif")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## A taste of the analysis layer
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import numpy as np, tsdynamics as ts
|
|
118
|
+
|
|
119
|
+
# Poincaré section of the Rössler attractor (root-refined crossings)
|
|
120
|
+
section = ts.poincare_section(ts.Rossler(), plane=("y", 0.0, "up"), n=500)
|
|
121
|
+
|
|
122
|
+
# Fixed points of the Hénon map, with stability
|
|
123
|
+
ts.fixed_points(ts.Henon())
|
|
124
|
+
# [FixedPoint([-1.1314 -0.3394], unstable), FixedPoint([0.6314 0.1894], unstable)]
|
|
125
|
+
|
|
126
|
+
# Maximal Lyapunov exponent — no Jacobian needed
|
|
127
|
+
ts.max_lyapunov(ts.Lorenz(ic=[1, 1, 1]), dt=0.05) # ≈ 0.9
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Plus: **attractors & basins** of any flow or map, correlation/Rényi **fractal
|
|
131
|
+
dimensions**, **permutation/sample/dispersion entropy**, **RQA** (recurrence
|
|
132
|
+
quantification), **surrogate** hypothesis tests, **delay embedding** (Takens,
|
|
133
|
+
optimal τ, Cao/FNN), GALI & the 0–1 chaos test, and Lyapunov exponents **from a
|
|
134
|
+
bare time series** (Kantz/Rosenstein).
|
|
135
|
+
|
|
136
|
+
## Highlights
|
|
137
|
+
|
|
138
|
+
- **Three families, one interface**
|
|
139
|
+
|
|
140
|
+
- **ODEs**
|
|
141
|
+
|
|
142
|
+
- **DDEs**
|
|
143
|
+
- **SDEs**
|
|
144
|
+
- **Discrete Maps**
|
|
145
|
+
|
|
146
|
+
- **151 built-in systems** with literature parameters.
|
|
147
|
+
|
|
148
|
+
- **Native engine**: equations lower to a Rust engine (an SSA-tape
|
|
149
|
+
interpreter, with a Cranelift JIT alongside) in-process; parameters are runtime
|
|
150
|
+
values, so changing them is free and there is no compile step or cache.
|
|
151
|
+
|
|
152
|
+
- **Composition** — a `PoincareMap` of a flow *is* a discrete map, so
|
|
153
|
+
`orbit_diagram(PoincareMap(Rossler(), ("y", 0.0)), "c", values)` draws the
|
|
154
|
+
bifurcation diagram of a *flow* in one line.
|
|
155
|
+
|
|
156
|
+
- **Backend-neutral plotting** — one `PlotSpec` IR renders to matplotlib, plotly
|
|
157
|
+
(interactive + animated HTML), three.js, or JSON, with a fluent styling/theming
|
|
158
|
+
vocabulary.
|
|
159
|
+
|
|
160
|
+
## Install
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
pip install tsdynamics # or: uv add tsdynamics
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
A prebuilt `abi3` wheel (manylinux / musllinux / macOS / Windows) bundles the
|
|
167
|
+
native Rust engine. No Rust toolchain and no C compiler needed to install or
|
|
168
|
+
run. Optional plotting extra: `tsdynamics[plot,interactive]` (matplotlib, plotly).
|
|
169
|
+
|
|
170
|
+
## Development
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
git clone https://github.com/El3ssar/TSDynamics && cd TSDynamics
|
|
174
|
+
uv sync --group dev --group docs
|
|
175
|
+
uv run pytest -m "not slow" --no-cov # fast tier
|
|
176
|
+
uv run pytest --no-cov # full local suite
|
|
177
|
+
TSD_DOCS_FIGURES=0 uv run mkdocs serve # docs preview
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Releases are automated: conventional-commit PR titles drive
|
|
181
|
+
[semantic-release](https://python-semantic-release.readthedocs.io/) on merge see [CONTRIBUTING](https://el3ssar.github.io/TSDynamics/project/contributing/).
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT © Daniel Estevez
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
//! ODE Lyapunov-spectrum entry point (stream `perf/ode-lyapunov-engine`).
|
|
2
|
+
//!
|
|
3
|
+
//! Thin bridge over the engine's [`tsdyn_engine::lyapunov`] kernel: build the
|
|
4
|
+
//! evaluator (interpreter or JIT) over the **extended** variational tape + resolve
|
|
5
|
+
//! the solver here (the cranelift edge lives in this crate, not the engine), then
|
|
6
|
+
//! run the whole burn-in + averaging Benettin loop in one call. The per-`dt`
|
|
7
|
+
//! integration is byte-for-byte the released `TangentSystem` ODE numerics, so
|
|
8
|
+
//! `interp == jit` and the spectrum matches the Python path to floating-point
|
|
9
|
+
//! tolerance.
|
|
10
|
+
//!
|
|
11
|
+
//! Shared plumbing (evaluator/solver builders, the `EngineError` enum) lives in
|
|
12
|
+
//! [`super::marshal`]; the renormalisation loop and the QR live in
|
|
13
|
+
//! [`tsdyn_engine::lyapunov`].
|
|
14
|
+
|
|
15
|
+
use tsdyn_engine::lyapunov::{lyapunov_spectrum_ode, LyapunovError, LyapunovOutcome};
|
|
16
|
+
use tsdyn_ir::Tape;
|
|
17
|
+
use tsdyn_solvers::Solver;
|
|
18
|
+
|
|
19
|
+
use super::marshal::{
|
|
20
|
+
build_evaluator, build_solver, require_jacobian_if_needed, resolve_solver, EngineError,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/// Map a [`LyapunovError`] (a kernel-side shape / divergence failure) to the
|
|
24
|
+
/// bridge's [`EngineError`] so the binding routes it to the matching Python
|
|
25
|
+
/// exception (`ValueError` / `RuntimeError`).
|
|
26
|
+
fn to_engine_err(e: LyapunovError) -> EngineError {
|
|
27
|
+
match e {
|
|
28
|
+
LyapunovError::BadShape(m) => EngineError::BadShape(m),
|
|
29
|
+
LyapunovError::Diverged(m) => EngineError::Diverged(m),
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Run the burn-in + averaging Benettin Lyapunov-spectrum loop over an
|
|
34
|
+
/// **extended variational** ODE tape.
|
|
35
|
+
///
|
|
36
|
+
/// The `tape` is the `dim*(k+1)`-wide extended variational system (base RHS ⊕ the
|
|
37
|
+
/// `k` tangent-vector RHS blocks) built by the Python
|
|
38
|
+
/// `tsdynamics.derived._variational.build_variational_tape`. `method` resolves
|
|
39
|
+
/// through the solver registry; an implicit kernel needs a Jacobian-carrying tape
|
|
40
|
+
/// (the extended tape carries one — rejected up front exactly as the integrate
|
|
41
|
+
/// path does, for the defence-in-depth message). `dt` is the renormalisation
|
|
42
|
+
/// interval, `burn_in` / `final_time` the two window lengths. `p` is the live
|
|
43
|
+
/// control-parameter vector. Returns the spectrum plus the final extended state.
|
|
44
|
+
#[allow(clippy::too_many_arguments)]
|
|
45
|
+
pub fn lyapunov_spectrum_ode_bridge(
|
|
46
|
+
tape: Tape,
|
|
47
|
+
p: &[f64],
|
|
48
|
+
method: &str,
|
|
49
|
+
rtol: f64,
|
|
50
|
+
atol: f64,
|
|
51
|
+
dim: usize,
|
|
52
|
+
k: usize,
|
|
53
|
+
z0: &[f64],
|
|
54
|
+
t0: f64,
|
|
55
|
+
dt: f64,
|
|
56
|
+
burn_in: f64,
|
|
57
|
+
final_time: f64,
|
|
58
|
+
jit: bool,
|
|
59
|
+
) -> Result<LyapunovOutcome, EngineError> {
|
|
60
|
+
// The extended tape is `n_state == ev.dim()` (a plain ODE-shaped tape over the
|
|
61
|
+
// stacked state), validated by the kernel against `dim*(k+1)`. Resolve the
|
|
62
|
+
// method + the Jacobian guard exactly like the integrate / basin paths.
|
|
63
|
+
let name = resolve_solver(method)?;
|
|
64
|
+
require_jacobian_if_needed(&tape, name)?;
|
|
65
|
+
let ev = build_evaluator(tape, jit)?;
|
|
66
|
+
// A fresh kernel per `dt` chunk (mirrors `OdeStepper::advance` / the basin
|
|
67
|
+
// march); the name is a registry name, so `build_solver` always succeeds.
|
|
68
|
+
let factory = move || -> Box<dyn Solver> { build_solver(name, rtol, atol) };
|
|
69
|
+
lyapunov_spectrum_ode(&*ev, factory, p, dim, k, z0, t0, dt, burn_in, final_time)
|
|
70
|
+
.map_err(to_engine_err)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#[cfg(test)]
|
|
74
|
+
mod tests {
|
|
75
|
+
use super::*;
|
|
76
|
+
use tsdyn_ir::TapeBuilder;
|
|
77
|
+
|
|
78
|
+
/// Extended variational tape of `dx = a x, dy = b y` with `k` tangents (the
|
|
79
|
+
/// Jacobian is the constant `diag(a, b)`). Spectrum is `[max(a,b), min(a,b)]`.
|
|
80
|
+
fn linear_extended(a: f64, b: f64, k: usize) -> Tape {
|
|
81
|
+
let dim = 2;
|
|
82
|
+
let mut bld = TapeBuilder::new();
|
|
83
|
+
let x = bld.state(0);
|
|
84
|
+
let y = bld.state(1);
|
|
85
|
+
let ac = bld.constant(a);
|
|
86
|
+
let bc = bld.constant(b);
|
|
87
|
+
let dx = bld.mul(ac, x);
|
|
88
|
+
let dy = bld.mul(bc, y);
|
|
89
|
+
let mut outs = vec![dx, dy];
|
|
90
|
+
for i in 0..k {
|
|
91
|
+
let base = dim + i * dim;
|
|
92
|
+
let w0 = bld.state(base);
|
|
93
|
+
let w1 = bld.state(base + 1);
|
|
94
|
+
let dw0 = bld.mul(ac, w0);
|
|
95
|
+
let dw1 = bld.mul(bc, w1);
|
|
96
|
+
outs.push(dw0);
|
|
97
|
+
outs.push(dw1);
|
|
98
|
+
}
|
|
99
|
+
let n = dim * (k + 1);
|
|
100
|
+
bld.finish(&outs, &[], n, 0).unwrap()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#[test]
|
|
104
|
+
fn bridge_spectrum_matches_analytic_and_interp_equals_jit() {
|
|
105
|
+
let (a, b, k) = (0.5, -2.0, 2);
|
|
106
|
+
let z0 = vec![1.0, 1.0, 1.0, 0.0, 0.0, 1.0];
|
|
107
|
+
let run = |jit| {
|
|
108
|
+
lyapunov_spectrum_ode_bridge(
|
|
109
|
+
linear_extended(a, b, k),
|
|
110
|
+
&[],
|
|
111
|
+
"rk45",
|
|
112
|
+
1e-9,
|
|
113
|
+
1e-11,
|
|
114
|
+
2,
|
|
115
|
+
k,
|
|
116
|
+
&z0,
|
|
117
|
+
0.0,
|
|
118
|
+
0.1,
|
|
119
|
+
5.0,
|
|
120
|
+
50.0,
|
|
121
|
+
jit,
|
|
122
|
+
)
|
|
123
|
+
.unwrap()
|
|
124
|
+
};
|
|
125
|
+
let interp = run(false);
|
|
126
|
+
let jit = run(true);
|
|
127
|
+
// interp == jit bit-for-bit (both drive the same &dyn Evaluator).
|
|
128
|
+
assert_eq!(interp.spectrum.len(), jit.spectrum.len());
|
|
129
|
+
for (i, (x, y)) in interp.spectrum.iter().zip(jit.spectrum.iter()).enumerate() {
|
|
130
|
+
assert_eq!(
|
|
131
|
+
x.to_bits(),
|
|
132
|
+
y.to_bits(),
|
|
133
|
+
"exponent {i}: jit {y} != interp {x}"
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
// and matches the analytic spectrum.
|
|
137
|
+
assert!((interp.spectrum[0] - 0.5).abs() < 1e-4);
|
|
138
|
+
assert!((interp.spectrum[1] - (-2.0)).abs() < 1e-4);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#[test]
|
|
142
|
+
fn bridge_rejects_unknown_method() {
|
|
143
|
+
let z0 = vec![1.0, 1.0, 1.0, 0.0, 0.0, 1.0];
|
|
144
|
+
let err = lyapunov_spectrum_ode_bridge(
|
|
145
|
+
linear_extended(0.5, -2.0, 2),
|
|
146
|
+
&[],
|
|
147
|
+
"no-such",
|
|
148
|
+
1e-6,
|
|
149
|
+
1e-9,
|
|
150
|
+
2,
|
|
151
|
+
2,
|
|
152
|
+
&z0,
|
|
153
|
+
0.0,
|
|
154
|
+
0.1,
|
|
155
|
+
1.0,
|
|
156
|
+
1.0,
|
|
157
|
+
false,
|
|
158
|
+
)
|
|
159
|
+
.unwrap_err();
|
|
160
|
+
assert!(matches!(err, EngineError::UnknownMethod(_)), "got {err:?}");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
//! iteration loop, shape contract and divergence behaviour are unchanged. Shared
|
|
6
6
|
//! plumbing lives in [`super::marshal`].
|
|
7
7
|
|
|
8
|
-
use tsdyn_engine::{
|
|
8
|
+
use tsdyn_engine::{
|
|
9
|
+
iterate_dense, iterate_ensemble_final as engine_iterate_ensemble, map_orbit_sweep, SweepError,
|
|
10
|
+
SweepOutcome,
|
|
11
|
+
};
|
|
9
12
|
use tsdyn_ir::Tape;
|
|
10
13
|
|
|
11
14
|
use super::marshal::{build_evaluator, map_diverge_msg, EngineError};
|
|
@@ -126,3 +129,88 @@ pub fn map_ensemble_final(
|
|
|
126
129
|
let result = engine_iterate_ensemble(&*ev, ics, &[], steps);
|
|
127
130
|
Ok(result.states)
|
|
128
131
|
}
|
|
132
|
+
|
|
133
|
+
/// Map a [`SweepError`] (a kernel-side shape failure) to the bridge's
|
|
134
|
+
/// [`EngineError`] so the binding routes it to the same Python `ValueError` the
|
|
135
|
+
/// other entry points use.
|
|
136
|
+
fn sweep_to_engine_err(e: SweepError) -> EngineError {
|
|
137
|
+
match e {
|
|
138
|
+
SweepError::BadShape(m) => EngineError::BadShape(m),
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// Run the whole map orbit-diagram parameter sweep in one call (stream
|
|
143
|
+
/// `perf/param-sweep-kernel`).
|
|
144
|
+
///
|
|
145
|
+
/// Unlike [`iterate_map`], the tape here keeps the **swept** parameter as its
|
|
146
|
+
/// single runtime `Param` input (`n_param == 1`) — the Python wiring lowers the
|
|
147
|
+
/// map once with the other parameters folded in, so a 1000-value sweep is one FFI
|
|
148
|
+
/// call instead of ~1000 `iterate_map` round-trips. For each value the kernel sets
|
|
149
|
+
/// `p[sweep_index] = value`, runs `transient + n` iterations (carrying the final
|
|
150
|
+
/// state across values when `carry_state`), and records the last `n` asymptotic
|
|
151
|
+
/// states' selected `components`.
|
|
152
|
+
///
|
|
153
|
+
/// Returns `(points, status)`: `points` is the flat row-major
|
|
154
|
+
/// `(n_values * n, n_components)` recorded buffer, and `status[k]` is `0` for a
|
|
155
|
+
/// value that ran finitely / `1` for a value that diverged (whose block is zero —
|
|
156
|
+
/// the binding drops it to an empty set and warns, mirroring the per-value path).
|
|
157
|
+
/// The per-iterate numerics are byte-for-byte the [`iterate_map`] path (a `Param`
|
|
158
|
+
/// leaf reads the same `f64` a folded `Const` would).
|
|
159
|
+
///
|
|
160
|
+
/// `jit` selects the evaluator (Cranelift native code vs the interpreter); both
|
|
161
|
+
/// are bit-for-bit identical on `eval`, so the swept diagram is numerically
|
|
162
|
+
/// identical either way.
|
|
163
|
+
#[allow(clippy::too_many_arguments)]
|
|
164
|
+
pub fn map_param_sweep(
|
|
165
|
+
tape: Tape,
|
|
166
|
+
base_params: &[f64],
|
|
167
|
+
sweep_index: usize,
|
|
168
|
+
values: &[f64],
|
|
169
|
+
ic: &[f64],
|
|
170
|
+
components: &[usize],
|
|
171
|
+
transient: usize,
|
|
172
|
+
n_record: usize,
|
|
173
|
+
carry_state: bool,
|
|
174
|
+
jit: bool,
|
|
175
|
+
) -> Result<(Vec<f64>, Vec<i64>), EngineError> {
|
|
176
|
+
// The sweep tape's next-state dimension equals its state dimension, and it
|
|
177
|
+
// carries exactly the swept parameter as a runtime input. Reject a tape whose
|
|
178
|
+
// shape disagrees (reachable only via a hand-built tape) so a mismatch is a
|
|
179
|
+
// clean error rather than a release-mode out-of-bounds inside the eval loop.
|
|
180
|
+
let dim = tape.dim();
|
|
181
|
+
if tape.n_state() != dim {
|
|
182
|
+
return Err(EngineError::BadShape(format!(
|
|
183
|
+
"map sweep expects n_state == dim (state and next-state share a \
|
|
184
|
+
dimension); got n_state = {} != dim = {dim}",
|
|
185
|
+
tape.n_state()
|
|
186
|
+
)));
|
|
187
|
+
}
|
|
188
|
+
if tape.n_param() != base_params.len() {
|
|
189
|
+
return Err(EngineError::BadShape(format!(
|
|
190
|
+
"map sweep base-parameter vector has length {}, but the tape declares \
|
|
191
|
+
n_param = {}",
|
|
192
|
+
base_params.len(),
|
|
193
|
+
tape.n_param()
|
|
194
|
+
)));
|
|
195
|
+
}
|
|
196
|
+
let ev = build_evaluator(tape, jit)?;
|
|
197
|
+
let SweepOutcome { points, status, .. } = map_orbit_sweep(
|
|
198
|
+
&*ev,
|
|
199
|
+
base_params,
|
|
200
|
+
sweep_index,
|
|
201
|
+
values,
|
|
202
|
+
ic,
|
|
203
|
+
components,
|
|
204
|
+
transient,
|
|
205
|
+
n_record,
|
|
206
|
+
carry_state,
|
|
207
|
+
)
|
|
208
|
+
.map_err(sweep_to_engine_err)?;
|
|
209
|
+
// Flatten the per-value fate to a plain i64 code (0 = ok, 1 = diverged) the
|
|
210
|
+
// FFI returns alongside the points buffer.
|
|
211
|
+
let status: Vec<i64> = status
|
|
212
|
+
.into_iter()
|
|
213
|
+
.map(|s| if s.is_ok() { 0 } else { 1 })
|
|
214
|
+
.collect();
|
|
215
|
+
Ok((points, status))
|
|
216
|
+
}
|