tsdynamics 3.1.2__tar.gz → 3.1.4__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-3.1.2 → tsdynamics-3.1.4}/PKG-INFO +2 -2
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/README.md +1 -1
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-engine/src/sde.rs +101 -11
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/pyproject.toml +9 -6
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/__init__.py +11 -3
- tsdynamics-3.1.4/src/tsdynamics/analysis/_tangent.py +155 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/basins/_common.py +5 -3
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/basins/attractors.py +15 -3
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/basins/basins.py +12 -3
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/basins/continuation.py +6 -4
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/basins/metrics.py +84 -14
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/chaos/_common.py +19 -130
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/chaos/expansion.py +4 -3
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/chaos/gali.py +5 -4
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/dimensions/_common.py +14 -5
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/dimensions/correlation.py +51 -3
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/dimensions/generalized.py +2 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/embedding/_common.py +14 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/embedding/delay.py +6 -5
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/embedding/dimension.py +1 -10
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/embedding/embed.py +5 -10
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/entropy/core.py +0 -4
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/entropy/lz.py +3 -2
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/fixedpoints/_common.py +32 -126
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/fixedpoints/fixed.py +3 -3
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/fixedpoints/periodic.py +6 -4
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/lyapunov/__init__.py +1 -3
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/orbits/return_map.py +4 -1
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/surrogate/generators.py +7 -7
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/surrogate/hypothesis.py +1 -1
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/data/trajectory.py +9 -4
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/derived/__init__.py +6 -1
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/derived/_variational.py +4 -4
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/engine/compile.py +13 -13
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/engine/problem.py +9 -4
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/engine/run.py +83 -20
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/errors.py +7 -4
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/families/_dde_lyapunov.py +27 -6
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/families/continuous.py +18 -1
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/families/delay.py +39 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/families/discrete.py +12 -8
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/families/stochastic.py +94 -22
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/solvers/explicit.py +2 -2
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/continuous/chaotic_attractors.py +11 -14
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/continuous/chem_bio_systems.py +5 -3
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/continuous/exotic_systems.py +4 -6
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/continuous/oscillatory_systems.py +0 -3
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/discrete/chaotic_maps.py +25 -20
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/discrete/exotic_maps.py +27 -15
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/discrete/geometric_maps.py +11 -12
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/discrete/polynomial_maps.py +8 -7
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/discrete/population_maps.py +7 -7
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/utils/__init__.py +0 -2
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/utils/sagitta_dt.py +32 -18
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/viz/spec.py +296 -8
- tsdynamics-3.1.2/src/tsdynamics/utils/general.py +0 -26
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/LICENSE +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/Cargo.toml +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-core/Cargo.lock +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-core/Cargo.toml +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-core/src/bridge.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-core/src/lib.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-engine/Cargo.toml +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-engine/src/dde.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-engine/src/ensemble.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-engine/src/event.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-engine/src/integrate.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-engine/src/lib.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-engine/src/map.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-engine/src/problem.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-engine/src/rng.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-engine/src/testkit.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/Cargo.toml +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/src/builder.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/src/eval.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/src/lib.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/src/op.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/src/reference.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/src/tape.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/tests/coverage_tape.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/tests/fixtures/CircadianRhythm.txt +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/tests/fixtures/Lorenz.txt +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/tests/fixtures/SanUmSrisuchinwong.txt +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/tests/fixtures/SprottMore.txt +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/tests/fixtures/Torus.txt +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/tests/fixtures/YuWang2.txt +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/tests/gen_fixtures.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/tests/golden_fixtures.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-ir/tests/roundtrip.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-jit/Cargo.toml +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-jit/src/codegen.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-jit/src/error.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-jit/src/evaluator.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-jit/src/lib.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-jit/src/shims.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-jit/tests/compile_latency.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-jit/tests/golden_fixtures.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-jit/tests/interpreter_equivalence.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/Cargo.toml +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/caps.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/explicit/control.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/explicit/dop853.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/explicit/mod.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/explicit/rk4.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/explicit/rk45.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/explicit/testkit.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/explicit/tsit5.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/implicit/bdf.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/implicit/control.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/implicit/linalg.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/implicit/mod.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/implicit/rosenbrock.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/implicit/trbdf2.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/lib.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/registry.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/sde/euler_maruyama.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/sde/milstein.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/sde/mod.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/sde/testkit.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/src/solver.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/tests/explicit_accuracy.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/tests/implicit_stiff.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-solvers/tests/registry.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-vm/Cargo.toml +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-vm/src/interp.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-vm/src/lib.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-vm/tests/golden_fixtures.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/crates/tsdyn-vm/tests/oracle_equivalence.rs +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/_result.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/basins/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/chaos/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/chaos/zero_one.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/dimensions/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/dimensions/_scaling.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/dimensions/fixedmass.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/embedding/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/entropy/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/entropy/dispersion.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/entropy/multiscale.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/entropy/permutation.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/entropy/sample.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/fixedpoints/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/lyapunov/from_data.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/orbits/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/orbits/orbit_diagram.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/orbits/poincare.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/recurrence/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/recurrence/_common.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/recurrence/matrix.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/recurrence/rqa.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/recurrence/windowed.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/surrogate/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/surrogate/_common.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/analysis/surrogate/statistics.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/data/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/data/sampling.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/derived/_base.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/derived/_crossings.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/derived/ensemble.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/derived/poincare.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/derived/projected.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/derived/stroboscopic.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/derived/tangent.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/derived/wrapped.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/engine/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/engine/symbols.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/families/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/families/_accessors.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/families/base.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/families/protocol.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/families/wrapped.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/plugins.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/py.typed +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/registry.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/solvers/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/solvers/implicit.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/solvers/select.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/solvers/stochastic.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/continuous/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/continuous/climate_geophysics.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/continuous/coupled_systems.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/continuous/delayed_systems.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/continuous/physical_systems.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/continuous/population_dynamics.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/systems/discrete/__init__.py +2 -2
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/transforms/__init__.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/transforms/_common.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/transforms/features.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/transforms/preprocessing.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/transforms/spectral.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/utils/grids.py +0 -0
- {tsdynamics-3.1.2 → tsdynamics-3.1.4}/src/tsdynamics/viz/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tsdynamics
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.4
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Science/Research
|
|
6
6
|
Classifier: Operating System :: OS Independent
|
|
@@ -124,7 +124,7 @@ od = ts.orbit_diagram(ts.Logistic(), "r", np.linspace(2.5, 4.0, 600))
|
|
|
124
124
|
x, y = od.flat()
|
|
125
125
|
|
|
126
126
|
# Poincaré section of the Rössler attractor (root-refined crossings)
|
|
127
|
-
section = ts.poincare_section(ts.Rossler(), plane=(1, 0.0),
|
|
127
|
+
section = ts.poincare_section(ts.Rossler(), plane=(1, 0.0), n=500)
|
|
128
128
|
|
|
129
129
|
# Fixed points of the Hénon map, with stability
|
|
130
130
|
ts.fixed_points(ts.Henon())
|
|
@@ -93,7 +93,7 @@ od = ts.orbit_diagram(ts.Logistic(), "r", np.linspace(2.5, 4.0, 600))
|
|
|
93
93
|
x, y = od.flat()
|
|
94
94
|
|
|
95
95
|
# Poincaré section of the Rössler attractor (root-refined crossings)
|
|
96
|
-
section = ts.poincare_section(ts.Rossler(), plane=(1, 0.0),
|
|
96
|
+
section = ts.poincare_section(ts.Rossler(), plane=(1, 0.0), n=500)
|
|
97
97
|
|
|
98
98
|
# Fixed points of the Hénon map, with stability
|
|
99
99
|
ts.fixed_points(ts.Henon())
|
|
@@ -27,12 +27,15 @@
|
|
|
27
27
|
//! # Fixed step, sub-stepping to the grid
|
|
28
28
|
//!
|
|
29
29
|
//! These schemes are fixed-step: the step size *is* `dt`, and `dt` sets the noise
|
|
30
|
-
//! scale `√dt`. Output at a requested time that is
|
|
31
|
-
//! produced by a shorter final sub-step whose increment is drawn `~ N(0, h)`
|
|
32
|
-
//! that `h` — a valid Euler–Maruyama/Milstein step on a non-uniform partition
|
|
30
|
+
//! scale `√dt`. Output at a requested time that is genuinely off the `dt` lattice
|
|
31
|
+
//! is produced by a shorter final sub-step whose increment is drawn `~ N(0, h)`
|
|
32
|
+
//! for that `h` — a valid Euler–Maruyama/Milstein step on a non-uniform partition
|
|
33
33
|
//! (Brownian increments over sub-intervals are independent and sum correctly), so
|
|
34
|
-
//! the discretization stays consistent.
|
|
35
|
-
//!
|
|
34
|
+
//! the discretization stays consistent. A nominally `dt`-wide segment whose width
|
|
35
|
+
//! only *appears* to exceed `dt` by floating-point roundoff is absorbed into one
|
|
36
|
+
//! canonical `dt` step (a tolerant landing, [`LANDING_REL_TOL`]) rather than
|
|
37
|
+
//! spawning a spurious sub-ULP sliver. As with the ODE driver, a non-finite state
|
|
38
|
+
//! is reported, never returned as plausible data ([`SdeError`]).
|
|
36
39
|
|
|
37
40
|
use rayon::prelude::*;
|
|
38
41
|
use tsdyn_ir::Evaluator;
|
|
@@ -42,6 +45,20 @@ use tsdyn_solvers::{SolverState, StepOutcome};
|
|
|
42
45
|
use crate::integrate::DEFAULT_MAX_STEPS;
|
|
43
46
|
use crate::rng::{fill_wiener, seed_for, SplitMix64};
|
|
44
47
|
|
|
48
|
+
/// Relative tolerance for the fixed-step *landing* decision (mirror of
|
|
49
|
+
/// `_LANDING_REL_TOL` in `src/tsdynamics/families/stochastic.py` — keep the two
|
|
50
|
+
/// in lock-step so the engine and the pure-Python reference draw their Wiener
|
|
51
|
+
/// streams in identical order).
|
|
52
|
+
///
|
|
53
|
+
/// Output grids are uniform in `dt`, but floating-point roundoff makes a
|
|
54
|
+
/// nominally `dt`-wide segment compute a `remaining = t_end - t` that is `dt`
|
|
55
|
+
/// plus a few ULP. The naive `dt >= remaining` test then takes a full `dt` step
|
|
56
|
+
/// and a **spurious sub-ULP sliver step** — drawing an extra Wiener increment
|
|
57
|
+
/// (desyncing the noise stream) and adding a meaningless `N(0, ~ULP)` kick.
|
|
58
|
+
/// Absorbing that roundoff into a single canonical `dt` step makes the
|
|
59
|
+
/// discretisation exactly "one `N(0, dt)` increment per step".
|
|
60
|
+
const LANDING_REL_TOL: f64 = 1e-9;
|
|
61
|
+
|
|
45
62
|
/// Knobs for the fixed-step SDE loop.
|
|
46
63
|
///
|
|
47
64
|
/// Build with [`SdeConfig::new`] (the step `dt` plus the default step cap) and
|
|
@@ -111,10 +128,13 @@ impl std::error::Error for SdeError {}
|
|
|
111
128
|
/// Advance `st` from its current time to `t_end` with fixed step `cfg.dt`,
|
|
112
129
|
/// drawing one diagonal Wiener increment per step from `rng` into `dw`.
|
|
113
130
|
///
|
|
114
|
-
///
|
|
115
|
-
/// remaining
|
|
116
|
-
///
|
|
117
|
-
///
|
|
131
|
+
/// The landing (final) step of the span reuses the canonical `cfg.dt` when
|
|
132
|
+
/// `remaining` is within [`LANDING_REL_TOL`] of it (so a uniform-`dt` grid takes
|
|
133
|
+
/// exactly one `N(0, dt)` increment per segment and never a spurious sub-ULP
|
|
134
|
+
/// sliver), and only a genuinely shorter span draws its increment `~ N(0, h)`
|
|
135
|
+
/// for that smaller `h`; the time is then pinned exactly to `t_end` so grid
|
|
136
|
+
/// points never drift. `dw` is a caller-owned scratch of length `dim`, reused
|
|
137
|
+
/// across steps.
|
|
118
138
|
#[allow(clippy::too_many_arguments)] // drift + diffusion + the state/rng/dw/time/cfg the loop threads.
|
|
119
139
|
fn sde_advance_to(
|
|
120
140
|
drift: &dyn Evaluator,
|
|
@@ -139,8 +159,21 @@ fn sde_advance_to(
|
|
|
139
159
|
return Err(SdeError::StepLimit { t: st.t, steps });
|
|
140
160
|
}
|
|
141
161
|
let remaining = t_end - st.t;
|
|
142
|
-
|
|
143
|
-
|
|
162
|
+
// Tolerant landing: absorb a roundoff-scale overshoot so a uniform-dt grid
|
|
163
|
+
// takes exactly one canonical dt step per segment (no spurious sub-ULP
|
|
164
|
+
// sliver step). Only a genuinely shorter final span uses `remaining`. See
|
|
165
|
+
// LANDING_REL_TOL; the Python reference mirrors this predicate so the two
|
|
166
|
+
// draw their Wiener streams in identical order.
|
|
167
|
+
let landing = remaining <= cfg.dt * (1.0 + LANDING_REL_TOL);
|
|
168
|
+
let h = if landing {
|
|
169
|
+
if remaining >= cfg.dt * (1.0 - LANDING_REL_TOL) {
|
|
170
|
+
cfg.dt
|
|
171
|
+
} else {
|
|
172
|
+
remaining
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
cfg.dt
|
|
176
|
+
};
|
|
144
177
|
steps += 1;
|
|
145
178
|
|
|
146
179
|
fill_wiener(rng, h, dw);
|
|
@@ -900,6 +933,63 @@ mod tests {
|
|
|
900
933
|
assert!(out.iter().all(|x| x.is_finite()));
|
|
901
934
|
}
|
|
902
935
|
|
|
936
|
+
#[test]
|
|
937
|
+
fn tolerant_landing_kills_the_spurious_substep_on_a_roundoff_grid() {
|
|
938
|
+
// FIX-SDE-WIENER regression. A uniform grid t_eval[k] = k·dt built the way
|
|
939
|
+
// NumPy's arange does (k as f64 * dt) makes many segments compute a width a
|
|
940
|
+
// few ULP above dt. The old `dt >= remaining` landing then took a full dt
|
|
941
|
+
// step *plus a spurious sub-ULP sliver* — an extra Wiener draw that desynced
|
|
942
|
+
// the stream. With the tolerant landing the grid takes exactly one clean dt
|
|
943
|
+
// step per segment, so marching the grid and marching the whole span in one
|
|
944
|
+
// shot draw the identical N increments and reach the SAME final state
|
|
945
|
+
// bit-for-bit (GBM is autonomous, so the pinned-vs-accumulated time is moot).
|
|
946
|
+
let drift = gbm_drift(0.12);
|
|
947
|
+
let diffusion = gbm_diffusion(0.3);
|
|
948
|
+
let dt = 0.02;
|
|
949
|
+
let n = 50usize;
|
|
950
|
+
let cfg = SdeConfig::new(dt);
|
|
951
|
+
let t_eval: Vec<f64> = (0..=n).map(|k| k as f64 * dt).collect();
|
|
952
|
+
// At least one segment must actually overshoot dt, else the test is vacuous.
|
|
953
|
+
let overshoot = (1..=n).filter(|&k| t_eval[k] - t_eval[k - 1] > dt).count();
|
|
954
|
+
assert!(overshoot > 0, "grid did not provoke the roundoff overshoot");
|
|
955
|
+
|
|
956
|
+
let mut kernel_g = EulerMaruyama::new();
|
|
957
|
+
let mut rng_g = SplitMix64::new(20240614);
|
|
958
|
+
let grid = sde_integrate_grid(
|
|
959
|
+
&drift,
|
|
960
|
+
&diffusion,
|
|
961
|
+
&mut kernel_g,
|
|
962
|
+
&mut rng_g,
|
|
963
|
+
&[1.0],
|
|
964
|
+
&[],
|
|
965
|
+
&t_eval,
|
|
966
|
+
&cfg,
|
|
967
|
+
)
|
|
968
|
+
.unwrap();
|
|
969
|
+
let grid_final = grid[grid.len() - 1];
|
|
970
|
+
|
|
971
|
+
let mut kernel_f = EulerMaruyama::new();
|
|
972
|
+
let mut rng_f = SplitMix64::new(20240614);
|
|
973
|
+
let one_shot = sde_integrate_final(
|
|
974
|
+
&drift,
|
|
975
|
+
&diffusion,
|
|
976
|
+
&mut kernel_f,
|
|
977
|
+
&mut rng_f,
|
|
978
|
+
&[1.0],
|
|
979
|
+
&[],
|
|
980
|
+
0.0,
|
|
981
|
+
t_eval[n],
|
|
982
|
+
&cfg,
|
|
983
|
+
)
|
|
984
|
+
.unwrap();
|
|
985
|
+
|
|
986
|
+
assert_eq!(
|
|
987
|
+
grid_final.to_bits(),
|
|
988
|
+
one_shot[0].to_bits(),
|
|
989
|
+
"grid march != one-shot march — a spurious sliver desynced the noise stream"
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
|
|
903
993
|
#[test]
|
|
904
994
|
fn divergence_is_reported_not_silently_returned() {
|
|
905
995
|
// f(x) = x², g = 0 ⇒ deterministic finite-time blow-up past t = 1 from
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tsdynamics"
|
|
7
|
-
version = "3.1.
|
|
7
|
+
version = "3.1.4"
|
|
8
8
|
description = "Zero-warmup Rust-engine ODE/DDE/SDE integration, discrete maps, and chaos analysis for dynamical systems."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12,<4.0"
|
|
@@ -88,7 +88,6 @@ module-name = "tsdynamics._rust"
|
|
|
88
88
|
python-source = "src"
|
|
89
89
|
manifest-path = "crates/tsdyn-core/Cargo.toml"
|
|
90
90
|
features = ["extension-module"]
|
|
91
|
-
exclude = ["src/tsdynamics/_native/**"]
|
|
92
91
|
|
|
93
92
|
# ── Release automation — python-semantic-release ───────────────────────────────
|
|
94
93
|
# Version bumps are computed from conventional commit messages on main:
|
|
@@ -128,7 +127,7 @@ ignore = [
|
|
|
128
127
|
"B008", # do not perform function calls in default args
|
|
129
128
|
# ── Math-notation conventions (intentional) ─────────────────────────────
|
|
130
129
|
"N803", # argument name should be lowercase — Y, N, L are standard math notation
|
|
131
|
-
"N805", # first argument should be 'self' — false positive on @staticmethod
|
|
130
|
+
"N805", # first argument should be 'self' — false positive on @staticmethod
|
|
132
131
|
"N806", # variable in function should be lowercase — X, Y, N, L are math convention
|
|
133
132
|
# ── Module/package docstrings (optional for math-system collections) ────
|
|
134
133
|
"D100", # missing docstring in public module
|
|
@@ -179,9 +178,9 @@ ignore_missing_imports = true
|
|
|
179
178
|
# catalogue only — `override` (system-specific signatures), `no-untyped-def` (the
|
|
180
179
|
# math kernels themselves) and `no-untyped-call` (calls between those untyped
|
|
181
180
|
# kernels and their nested helpers). Every other check (and the entire core
|
|
182
|
-
# library) stays strict.
|
|
183
|
-
# checker (
|
|
184
|
-
#
|
|
181
|
+
# library) stays strict. Map kernels are plain `@staticmethod`, so the type
|
|
182
|
+
# checker reads their first parameter as state (not `self`) and this list need
|
|
183
|
+
# not grow.
|
|
185
184
|
[[tool.mypy.overrides]]
|
|
186
185
|
module = ["tsdynamics.systems.*"]
|
|
187
186
|
disable_error_code = ["override", "no-untyped-def", "no-untyped-call"]
|
|
@@ -203,9 +202,13 @@ markers = [
|
|
|
203
202
|
"slow: marks tests that run long simulations or compile the engine extension (deselect with: pytest -m 'not slow')",
|
|
204
203
|
"full: exhaustive sweeps over every built-in system; deselected by default, run nightly (select with: pytest -m full)",
|
|
205
204
|
"engine: requires the compiled tsdynamics._rust extension; auto-applied to modules that import it and selected by the engine CI job with -m engine (stream I-XVAL)",
|
|
205
|
+
"doctest: the executable-documentation gate — docstring doctests + doc-page python fences (stream DOCS-DOCTEST-GATE); curated-fast by default, broad sweep under -m full",
|
|
206
206
|
]
|
|
207
207
|
filterwarnings = [
|
|
208
208
|
"error",
|
|
209
209
|
"ignore:.*License classifiers have been superseded.*:UserWarning",
|
|
210
210
|
"ignore:.*divide by zero encountered in divide.*:RuntimeWarning",
|
|
211
|
+
# DiscreteMap.iterate warns when an off-basin random IC diverges and it retries
|
|
212
|
+
# — expected, benign resilience; do not let it trip the `error` filter.
|
|
213
|
+
"ignore:.*Retrying from a new random initial condition.*:RuntimeWarning",
|
|
211
214
|
]
|
|
@@ -21,8 +21,7 @@ so the top-level namespace stays focused on the base classes, the derived-system
|
|
|
21
21
|
wrappers, the analysis functions, and the submodules. For backwards
|
|
22
22
|
compatibility ``tsdynamics.Lorenz`` (and ``from tsdynamics import Lorenz``) still
|
|
23
23
|
resolve lazily. See :mod:`tsdynamics.registry` for programmatic access. Internal
|
|
24
|
-
helpers (``ParamSet``, ``SystemBase
|
|
25
|
-
``tsdynamics.families`` / ``tsdynamics.utils``.
|
|
24
|
+
helpers (``ParamSet``, ``SystemBase``) live under ``tsdynamics.families``.
|
|
26
25
|
|
|
27
26
|
Curated top level (stream WS-NAMESPACE)
|
|
28
27
|
---------------------------------------
|
|
@@ -105,15 +104,24 @@ from .analysis import (
|
|
|
105
104
|
from .analysis import (
|
|
106
105
|
FixedPoint as FixedPoint,
|
|
107
106
|
)
|
|
107
|
+
from .analysis import (
|
|
108
|
+
FixedPointSet as FixedPointSet,
|
|
109
|
+
)
|
|
108
110
|
from .analysis import (
|
|
109
111
|
GALIResult as GALIResult,
|
|
110
112
|
)
|
|
111
113
|
from .analysis import (
|
|
112
114
|
LyapunovFromData as LyapunovFromData,
|
|
113
115
|
)
|
|
116
|
+
from .analysis import (
|
|
117
|
+
LyapunovSpectrum as LyapunovSpectrum,
|
|
118
|
+
)
|
|
114
119
|
from .analysis import (
|
|
115
120
|
OrbitDiagram as OrbitDiagram,
|
|
116
121
|
)
|
|
122
|
+
from .analysis import (
|
|
123
|
+
OrbitSet as OrbitSet,
|
|
124
|
+
)
|
|
117
125
|
from .analysis import (
|
|
118
126
|
PeriodicOrbit as PeriodicOrbit,
|
|
119
127
|
)
|
|
@@ -354,7 +362,7 @@ bifurcation_diagram = orbit_diagram #: discoverable spelling of :func:`orbit_di
|
|
|
354
362
|
basins = basins_of_attraction #: short alias of :func:`basins_of_attraction`
|
|
355
363
|
|
|
356
364
|
# Single source of truth for the package version; rewritten by python-semantic-release.
|
|
357
|
-
__version__ = "3.1.
|
|
365
|
+
__version__ = "3.1.4"
|
|
358
366
|
|
|
359
367
|
# Built-in system classes are NOT bound into this namespace — that would bury the
|
|
360
368
|
# submodules (``analysis``, ``data``, ``systems``, …) under ~149 model names in
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
Shared tangent-dynamics primitives for the analysis subpackages.
|
|
3
|
+
|
|
4
|
+
The fixed-point/periodic-orbit (**A-FP**) and chaos-indicator (**A-CHAOS**)
|
|
5
|
+
streams each need the same self-contained tangent-dynamics building blocks: a
|
|
6
|
+
``(step, jac)`` pair for a :class:`~tsdynamics.families.DiscreteMap` (compiled
|
|
7
|
+
``_step`` / ``_jacobian``), a ``(rhs, jac)`` pair for a
|
|
8
|
+
:class:`~tsdynamics.families.ContinuousSystem` (SymEngine-lambdified numeric RHS
|
|
9
|
+
/ Jacobian), and the classic / augmented (state ⊕ fundamental matrix) RK4 steps.
|
|
10
|
+
|
|
11
|
+
These were previously copy-pasted into both ``fixedpoints/_common.py`` and
|
|
12
|
+
``chaos/_common.py`` (drifting independently); they live here once. Each
|
|
13
|
+
subpackage re-exports them under its existing names so callers are unchanged.
|
|
14
|
+
All primitives stay engine-free (no tape lowering), so the detectors keep
|
|
15
|
+
running in the fast tier.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from collections.abc import Callable
|
|
21
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from tsdynamics.families import ContinuousSystem, DiscreteMap
|
|
27
|
+
from tsdynamics.families.base import ParamSet
|
|
28
|
+
|
|
29
|
+
# ── state coercion ────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def to_native(x: np.ndarray, dim: int) -> float | np.ndarray:
|
|
33
|
+
"""Present the state to a compiled ``_step``/``_jacobian`` in its native form.
|
|
34
|
+
|
|
35
|
+
One-dimensional maps are written for a scalar argument (``x = X``);
|
|
36
|
+
higher-dimensional maps unpack an array (``x, y = X[0], X[1]``).
|
|
37
|
+
"""
|
|
38
|
+
a = np.asarray(x, dtype=float).ravel()
|
|
39
|
+
return float(a[0]) if dim == 1 else a
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ── tangent dynamics: maps ───────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def map_fns(
|
|
46
|
+
system: DiscreteMap,
|
|
47
|
+
) -> tuple[Callable[[np.ndarray], np.ndarray], Callable[[np.ndarray], np.ndarray]]:
|
|
48
|
+
"""Return ``(step, jac)`` callables for a discrete map.
|
|
49
|
+
|
|
50
|
+
``step(x) -> ndarray (dim,)`` advances one iteration; ``jac(x) -> ndarray
|
|
51
|
+
(dim, dim)`` is the Jacobian at ``x``. Both wrap the class's compiled
|
|
52
|
+
``_step`` / ``_jacobian`` (the convention
|
|
53
|
+
:class:`~tsdynamics.derived.TangentSystem` uses). A map without an analytic
|
|
54
|
+
``_jacobian`` falls back to a forward finite difference of ``_step``.
|
|
55
|
+
"""
|
|
56
|
+
cls = type(system)
|
|
57
|
+
dim = int(cast("int", system.dim)) # dim resolved at construction
|
|
58
|
+
# ``_step`` / ``_jacobian`` accept the native scalar-or-array form from
|
|
59
|
+
# ``to_native`` (1-D maps take a float); treat them as untyped callables.
|
|
60
|
+
step_raw: Callable[..., Any] = cls._step
|
|
61
|
+
jac_raw: Callable[..., Any] | None = getattr(cls, "_jacobian", None)
|
|
62
|
+
params = tuple(cast("ParamSet", system.params).as_tuple())
|
|
63
|
+
|
|
64
|
+
def step(x: np.ndarray) -> np.ndarray:
|
|
65
|
+
return np.asarray(step_raw(to_native(x, dim), *params), dtype=float).ravel()
|
|
66
|
+
|
|
67
|
+
if jac_raw is not None:
|
|
68
|
+
|
|
69
|
+
def jac(x: np.ndarray) -> np.ndarray:
|
|
70
|
+
j = np.asarray(jac_raw(to_native(x, dim), *params), dtype=float)
|
|
71
|
+
return np.atleast_2d(j).reshape(dim, dim)
|
|
72
|
+
|
|
73
|
+
else:
|
|
74
|
+
|
|
75
|
+
def jac(x: np.ndarray) -> np.ndarray:
|
|
76
|
+
return finite_diff_jac(step, x, dim)
|
|
77
|
+
|
|
78
|
+
return step, jac
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def finite_diff_jac(
|
|
82
|
+
step: Callable[[np.ndarray], np.ndarray], x: np.ndarray, dim: int, eps: float = 1e-7
|
|
83
|
+
) -> np.ndarray:
|
|
84
|
+
"""Forward finite-difference Jacobian of a map step (fallback only)."""
|
|
85
|
+
x = np.asarray(x, dtype=float).ravel()
|
|
86
|
+
base = step(x)
|
|
87
|
+
out = np.empty((dim, dim))
|
|
88
|
+
for j in range(dim):
|
|
89
|
+
xp = x.copy()
|
|
90
|
+
h = eps * (1.0 + abs(xp[j]))
|
|
91
|
+
xp[j] += h
|
|
92
|
+
out[:, j] = (step(xp) - base) / h
|
|
93
|
+
return out
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ── tangent dynamics: flows ──────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def flow_fns(
|
|
100
|
+
system: ContinuousSystem,
|
|
101
|
+
) -> tuple[Callable[..., np.ndarray], Callable[..., np.ndarray]]:
|
|
102
|
+
"""Return ``(rhs, jac)`` for a flow: ``rhs(u, t)`` and ``jac(u, t)``.
|
|
103
|
+
|
|
104
|
+
Both come from the SymEngine-lambdified numeric forms
|
|
105
|
+
(:meth:`ContinuousSystem._rhs_numeric` / :meth:`ContinuousSystem.jacobian`),
|
|
106
|
+
so the shooting/monodromy/variational integrator is self-contained (no
|
|
107
|
+
engine tape lowering) and runs in the fast tier. Parameters are captured at
|
|
108
|
+
call time.
|
|
109
|
+
"""
|
|
110
|
+
rhs = system._rhs_numeric()
|
|
111
|
+
|
|
112
|
+
def jac(u: np.ndarray, t: float = 0.0) -> np.ndarray:
|
|
113
|
+
return np.asarray(system.jacobian(u, t), dtype=float)
|
|
114
|
+
|
|
115
|
+
return rhs, jac
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def rk4_state(rhs: Callable[..., np.ndarray], x: np.ndarray, t: float, h: float) -> np.ndarray:
|
|
119
|
+
"""One classic RK4 step of ``dx/dt = rhs(x, t)`` (state only)."""
|
|
120
|
+
k1 = rhs(x, t)
|
|
121
|
+
k2 = rhs(x + 0.5 * h * k1, t + 0.5 * h)
|
|
122
|
+
k3 = rhs(x + 0.5 * h * k2, t + 0.5 * h)
|
|
123
|
+
k4 = rhs(x + h * k3, t + h)
|
|
124
|
+
return cast("np.ndarray", x + (h / 6.0) * (k1 + 2.0 * k2 + 2.0 * k3 + k4))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def rk4_variational(
|
|
128
|
+
rhs: Callable[..., np.ndarray],
|
|
129
|
+
jac: Callable[..., np.ndarray],
|
|
130
|
+
x: np.ndarray,
|
|
131
|
+
m: np.ndarray,
|
|
132
|
+
t: float,
|
|
133
|
+
h: float,
|
|
134
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
135
|
+
r"""One RK4 step of the augmented system ``dx/dt = f``, ``dM/dt = J(x) M``.
|
|
136
|
+
|
|
137
|
+
``M`` holds the tangent vectors as columns (shape ``(dim, ncol)``); started
|
|
138
|
+
at the identity it accumulates the monodromy matrix. The same
|
|
139
|
+
fundamental-matrix flow drives the shooting monodromy, GALI and expansion
|
|
140
|
+
entropy.
|
|
141
|
+
"""
|
|
142
|
+
k1x = rhs(x, t)
|
|
143
|
+
k1m = jac(x, t) @ m
|
|
144
|
+
x2, m2, t2 = x + 0.5 * h * k1x, m + 0.5 * h * k1m, t + 0.5 * h
|
|
145
|
+
k2x = rhs(x2, t2)
|
|
146
|
+
k2m = jac(x2, t2) @ m2
|
|
147
|
+
x3, m3 = x + 0.5 * h * k2x, m + 0.5 * h * k2m
|
|
148
|
+
k3x = rhs(x3, t2)
|
|
149
|
+
k3m = jac(x3, t2) @ m3
|
|
150
|
+
x4, m4, t4 = x + h * k3x, m + h * k3m, t + h
|
|
151
|
+
k4x = rhs(x4, t4)
|
|
152
|
+
k4m = jac(x4, t4) @ m4
|
|
153
|
+
x_new = x + (h / 6.0) * (k1x + 2.0 * k2x + 2.0 * k3x + k4x)
|
|
154
|
+
m_new = m + (h / 6.0) * (k1m + 2.0 * k2m + 2.0 * k3m + k4m)
|
|
155
|
+
return x_new, m_new
|
|
@@ -102,7 +102,9 @@ class _CellGrid:
|
|
|
102
102
|
# ---------------------------------------------------------------------------
|
|
103
103
|
|
|
104
104
|
|
|
105
|
-
def _recurrence_grid(
|
|
105
|
+
def _recurrence_grid(
|
|
106
|
+
region: Box | Ball | Grid, resolution: int | tuple[int, ...] = 100
|
|
107
|
+
) -> _CellGrid:
|
|
106
108
|
"""
|
|
107
109
|
Build the recurrence :class:`_CellGrid` covering ``region``.
|
|
108
110
|
|
|
@@ -113,9 +115,9 @@ def _recurrence_grid(region: Box | Ball | Grid, resolution: int | tuple[int, ...
|
|
|
113
115
|
Parameters
|
|
114
116
|
----------
|
|
115
117
|
region : Box, Ball, or Grid
|
|
116
|
-
resolution : int or tuple of int
|
|
118
|
+
resolution : int or tuple of int, default 100
|
|
117
119
|
Cells per axis for a Box/Ball region (ignored for a Grid, which carries
|
|
118
|
-
its own ``counts``).
|
|
120
|
+
its own ``counts`` — so the default is never consulted for a Grid).
|
|
119
121
|
"""
|
|
120
122
|
if isinstance(region, Grid):
|
|
121
123
|
return _CellGrid.from_grid(region)
|
|
@@ -228,8 +228,9 @@ class _AttractorMapper:
|
|
|
228
228
|
"""Advance one step; ``None`` if the trajectory blew up (raised / non-finite)."""
|
|
229
229
|
try:
|
|
230
230
|
state = np.asarray(self.system.step(self._step_arg), dtype=float).reshape(-1)
|
|
231
|
-
except (RuntimeError,
|
|
231
|
+
except (RuntimeError, ArithmeticError):
|
|
232
232
|
# maps raise on divergence; flows may overflow — both mean "gone".
|
|
233
|
+
# (ArithmeticError covers FloatingPointError and OverflowError.)
|
|
233
234
|
return None
|
|
234
235
|
return state if np.all(np.isfinite(state)) else None
|
|
235
236
|
|
|
@@ -445,8 +446,7 @@ def find_attractors(
|
|
|
445
446
|
G. Datseris and A. Wagemakers, "Effortless estimation of basins of
|
|
446
447
|
attraction", *Chaos* **32**, 023104 (2022).
|
|
447
448
|
"""
|
|
448
|
-
|
|
449
|
-
raise TypeError("find_attractors supports maps and flows, not delay/stochastic systems.")
|
|
449
|
+
_reject_unsupported(system, "find_attractors")
|
|
450
450
|
|
|
451
451
|
grid = _recurrence_grid(region, resolution)
|
|
452
452
|
mapper = _AttractorMapper(system, grid, dt=dt, max_steps=max_steps, **fsm)
|
|
@@ -478,6 +478,18 @@ def _looks_unsupported(system: Any) -> bool:
|
|
|
478
478
|
return hasattr(system, "_drift") or hasattr(system, "history") or hasattr(system, "_delays")
|
|
479
479
|
|
|
480
480
|
|
|
481
|
+
def _reject_unsupported(system: Any, fn_name: str) -> None:
|
|
482
|
+
"""Raise a uniform ``TypeError`` for delay / stochastic systems.
|
|
483
|
+
|
|
484
|
+
Shared by the basin entry points (``find_attractors``,
|
|
485
|
+
``basins_of_attraction``, ``basin_fractions``, ``continuation``) so an
|
|
486
|
+
unsupported system fails early with one clear message instead of opaquely
|
|
487
|
+
inside the step loop.
|
|
488
|
+
"""
|
|
489
|
+
if getattr(system, "is_discrete", False) is False and _looks_unsupported(system):
|
|
490
|
+
raise TypeError(f"{fn_name} supports maps and flows, not delay/stochastic systems.")
|
|
491
|
+
|
|
492
|
+
|
|
481
493
|
def __dir__() -> list[str]:
|
|
482
494
|
"""Expose only the curated public API (``__all__``) to ``dir()`` / autocomplete."""
|
|
483
495
|
return sorted(__all__)
|
|
@@ -31,7 +31,13 @@ import numpy as np
|
|
|
31
31
|
from ...data import Ball, Box, Grid, grid_points, sampler
|
|
32
32
|
from .._result import AnalysisResult
|
|
33
33
|
from ._common import _apply_merge, _recurrence_grid
|
|
34
|
-
from .attractors import
|
|
34
|
+
from .attractors import (
|
|
35
|
+
DIVERGED,
|
|
36
|
+
AttractorSet,
|
|
37
|
+
_AttractorMapper,
|
|
38
|
+
_reject_unsupported,
|
|
39
|
+
resolve_merge_tol,
|
|
40
|
+
)
|
|
35
41
|
|
|
36
42
|
__all__ = [
|
|
37
43
|
"BasinFractions",
|
|
@@ -265,8 +271,10 @@ def basins_of_attraction(
|
|
|
265
271
|
G. Datseris and A. Wagemakers, "Effortless estimation of basins of
|
|
266
272
|
attraction", *Chaos* **32**, 023104 (2022).
|
|
267
273
|
"""
|
|
274
|
+
_reject_unsupported(system, "basins_of_attraction")
|
|
268
275
|
if recurrence is None:
|
|
269
|
-
|
|
276
|
+
# region is a Grid → keeps its own counts (resolution arg is ignored).
|
|
277
|
+
cellgrid = _recurrence_grid(region)
|
|
270
278
|
else:
|
|
271
279
|
cellgrid = _recurrence_grid(recurrence, recurrence_resolution)
|
|
272
280
|
mapper = _AttractorMapper(system, cellgrid, dt=dt, max_steps=max_steps, **fsm)
|
|
@@ -287,7 +295,7 @@ def basins_of_attraction(
|
|
|
287
295
|
labels=labels,
|
|
288
296
|
grid=region,
|
|
289
297
|
attractors=attractors,
|
|
290
|
-
meta=AnalysisResult.build_meta(system, analysis="basins_of_attraction"),
|
|
298
|
+
meta=AnalysisResult.build_meta(system, analysis="basins_of_attraction", seed=seed),
|
|
291
299
|
)
|
|
292
300
|
|
|
293
301
|
|
|
@@ -345,6 +353,7 @@ def basin_fractions(
|
|
|
345
353
|
P. J. Menck, J. Heitzig, N. Marwan and J. Kurths, "How basin stability
|
|
346
354
|
complements the linear-stability paradigm", *Nature Physics* **9**, 89 (2013).
|
|
347
355
|
"""
|
|
356
|
+
_reject_unsupported(system, "basin_fractions")
|
|
348
357
|
cellgrid = _recurrence_grid(region, resolution)
|
|
349
358
|
mapper = _AttractorMapper(system, cellgrid, dt=dt, max_steps=max_steps, **fsm)
|
|
350
359
|
draw = sampler(region, seed=seed)
|
|
@@ -22,7 +22,7 @@ import numpy as np
|
|
|
22
22
|
|
|
23
23
|
from ...data import Ball, Box, Grid, set_distance
|
|
24
24
|
from .._result import AnalysisResult, CollectionResult
|
|
25
|
-
from .attractors import Attractor
|
|
25
|
+
from .attractors import Attractor, _reject_unsupported
|
|
26
26
|
from .basins import basin_fractions
|
|
27
27
|
|
|
28
28
|
if TYPE_CHECKING:
|
|
@@ -145,6 +145,7 @@ def continuation(
|
|
|
145
145
|
G. Datseris, K. L. Rossi and A. Wagemakers, "Framework for global stability
|
|
146
146
|
analysis of dynamical systems", *Chaos* **33**, 073151 (2023).
|
|
147
147
|
"""
|
|
148
|
+
_reject_unsupported(system, "continuation")
|
|
148
149
|
values = np.asarray(values, dtype=float)
|
|
149
150
|
fractions: dict[int, list[float]] = {}
|
|
150
151
|
per_value: list[dict[int, Attractor]] = []
|
|
@@ -247,9 +248,10 @@ def tipping_points(result: ContinuationResult, *, threshold: float = 0.0) -> Col
|
|
|
247
248
|
|
|
248
249
|
Returns
|
|
249
250
|
-------
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
``
|
|
251
|
+
CollectionResult
|
|
252
|
+
Behaves as a ``list of dict``; each event is
|
|
253
|
+
``{"value", "attractor", "kind", "before", "after"}`` with ``kind`` in
|
|
254
|
+
``{"appear", "disappear"}``, sorted by parameter value.
|
|
253
255
|
"""
|
|
254
256
|
events: list[dict[str, Any]] = []
|
|
255
257
|
vals = result.values
|