tsdynamics 5.2.4__tar.gz → 5.2.5__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.4 → tsdynamics-5.2.5}/PKG-INFO +1 -1
- tsdynamics-5.2.5/crates/tsdyn-core/src/bridge/lyapunov.rs +162 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-core/src/bridge/map.rs +89 -1
- tsdynamics-5.2.5/crates/tsdyn-core/src/bridge/map_lyapunov.rs +142 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-core/src/bridge/mod.rs +5 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-core/src/lib.rs +208 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-engine/src/lib.rs +15 -0
- tsdynamics-5.2.5/crates/tsdyn-engine/src/lyapunov.rs +476 -0
- tsdynamics-5.2.5/crates/tsdyn-engine/src/map_lyapunov.rs +436 -0
- tsdynamics-5.2.5/crates/tsdyn-engine/src/param_sweep.rs +381 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/pyproject.toml +1 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/__init__.py +1 -1
- tsdynamics-5.2.5/src/tsdynamics/analysis/fixedpoints/_interval.py +717 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/fixedpoints/fixed.py +107 -6
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/lyapunov/__init__.py +96 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/orbits/orbit_diagram.py +120 -49
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/derived/tangent.py +203 -24
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/engine/compile.py +168 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/engine/run.py +278 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/families/discrete.py +14 -1
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/LICENSE +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/README.md +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-core/Cargo.lock +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-core/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-core/src/bridge/basin.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-core/src/bridge/dde.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-core/src/bridge/events.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-core/src/bridge/marshal.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-core/src/bridge/ode.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-core/src/bridge/sde.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-core/src/bridge/stepper.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-engine/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-engine/src/basin.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-engine/src/dde.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-engine/src/ensemble.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-engine/src/event.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-engine/src/integrate.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-engine/src/map.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-engine/src/problem.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-engine/src/rng.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-engine/src/sde.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-engine/src/testkit.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/src/builder.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/src/eval.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/src/lib.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/src/op.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/src/reference.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/src/tape.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/tests/coverage_tape.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/tests/fixtures/CircadianRhythm.txt +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/tests/fixtures/Lorenz.txt +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/tests/fixtures/SanUmSrisuchinwong.txt +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/tests/fixtures/SprottMore.txt +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/tests/fixtures/Torus.txt +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/tests/fixtures/YuWang2.txt +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/tests/gen_fixtures.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/tests/golden_fixtures.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-ir/tests/roundtrip.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-jit/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-jit/src/codegen.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-jit/src/error.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-jit/src/evaluator.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-jit/src/lib.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-jit/src/shims.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-jit/tests/compile_latency.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-jit/tests/golden_fixtures.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-jit/tests/interpreter_equivalence.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/caps.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/adams.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/bs3.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/cashkarp.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/control.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/dop853.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/euler.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/heun.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/heun_euler.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/midpoint.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/mod.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/ralston.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/rk4.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/rk45.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/rk4_38.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/rkf45.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/ssprk3.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/testkit.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/explicit/tsit5.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/implicit/backward_euler.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/implicit/bdf.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/implicit/control.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/implicit/implicit_midpoint.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/implicit/linalg.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/implicit/mod.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/implicit/newton.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/implicit/rosenbrock.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/implicit/sdirk2.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/implicit/trapezoid.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/implicit/trbdf2.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/lib.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/registry.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/sde/euler_maruyama.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/sde/milstein.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/sde/mod.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/sde/testkit.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/src/solver.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/tests/explicit_accuracy.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/tests/implicit_stiff.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-solvers/tests/registry.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-vm/Cargo.toml +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-vm/src/interp.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-vm/src/lib.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-vm/tests/golden_fixtures.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/crates/tsdyn-vm/tests/oracle_equivalence.rs +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/_result_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/_plotbuilder.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/_result.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/_result_array.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/_result_base.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/_result_collection.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/_result_json.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/_result_scalar.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/_result_scaling.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/_result_viz.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/_tangent.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/basins/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/basins/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/basins/attractors.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/basins/basins.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/basins/continuation.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/basins/metrics.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/chaos/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/chaos/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/chaos/expansion.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/chaos/gali.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/chaos/zero_one.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/dimensions/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/dimensions/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/dimensions/_scaling.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/dimensions/correlation.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/dimensions/fixedmass.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/dimensions/generalized.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/embedding/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/embedding/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/embedding/delay.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/embedding/dimension.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/embedding/embed.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/entropy/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/entropy/core.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/entropy/dispersion.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/entropy/lz.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/entropy/multiscale.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/entropy/permutation.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/entropy/sample.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/fixedpoints/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/fixedpoints/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/fixedpoints/periodic.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/lyapunov/from_data.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/orbits/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/orbits/poincare.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/orbits/return_map.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/recurrence/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/recurrence/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/recurrence/matrix.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/recurrence/rqa.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/recurrence/windowed.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/sampling/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/sampling/sagitta.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/surrogate/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/surrogate/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/surrogate/generators.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/surrogate/hypothesis.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/analysis/surrogate/statistics.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/data/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/data/sampling.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/data/trajectory.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/derived/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/derived/_base.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/derived/_crossings.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/derived/_variational.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/derived/ensemble.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/derived/poincare.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/derived/projected.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/derived/stroboscopic.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/derived/wrapped.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/engine/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/engine/_families.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/engine/events.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/engine/problem.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/engine/reference.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/engine/run_methods.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/engine/sde_run.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/engine/stepper.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/engine/symbols.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/errors.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/families/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/families/_accessors.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/families/_dde_lyapunov.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/families/_plottable.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/families/base.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/families/continuous.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/families/delay.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/families/protocol.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/families/stochastic.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/families/wrapped.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/plugins.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/py.typed +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/registry.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/solvers/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/solvers/explicit.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/solvers/implicit.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/solvers/select.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/solvers/stochastic.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/continuous/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/continuous/chaotic_attractors.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/continuous/chem_bio_systems.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/continuous/climate_geophysics.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/continuous/coupled_systems.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/continuous/delayed_systems.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/continuous/exotic_systems.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/continuous/oscillatory_systems.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/continuous/physical_systems.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/continuous/population_dynamics.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/continuous/spatial_fields.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/discrete/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/discrete/chaotic_maps.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/discrete/exotic_maps.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/discrete/geometric_maps.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/discrete/polynomial_maps.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/systems/discrete/population_maps.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/transforms/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/transforms/_common.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/transforms/_result.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/transforms/features.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/transforms/preprocessing.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/transforms/spectral.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/utils/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/utils/grids.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/compose.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/export.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/producers.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/caps.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/json.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/mpl/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/mpl/_anim.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/mpl/_core.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/mpl/_threed.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/plotly/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/plotly/_anim.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/plotly/_composite.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/plotly/_core.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/plotly/_html.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/plotly/_threed.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/threejs/__init__.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/render/threejs/_lower.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/spec.py +0 -0
- {tsdynamics-5.2.4 → tsdynamics-5.2.5}/src/tsdynamics/viz/style.py +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
//! Discrete-map Lyapunov-spectrum entry point (stream `perf/map-lyapunov-kernel`).
|
|
2
|
+
//!
|
|
3
|
+
//! Thin bridge over the engine's [`tsdyn_engine::map_lyapunov`] kernel: build the
|
|
4
|
+
//! evaluator (interpreter or JIT) here (the cranelift edge lives in this crate, not
|
|
5
|
+
//! the engine), then run the whole QR tangent-map iteration in one call. The map
|
|
6
|
+
//! tape must carry its Jacobian (`with_jacobian=True`) — the tangent map needs
|
|
7
|
+
//! `∂f/∂u`; a Jacobian-less tape is rejected as the engine error
|
|
8
|
+
//! [`MapLyapunovError::NoJacobian`].
|
|
9
|
+
//!
|
|
10
|
+
//! Driven over the same lowered IR tape, the interpreter and JIT agree bit-for-bit;
|
|
11
|
+
//! against the released Python `TangentSystem._accumulate_map` the result differs
|
|
12
|
+
//! only by the lowered-IR vs pure-Python `_step`/`_jacobian` float order (the
|
|
13
|
+
//! WS-MAPITER caveat) — the same attractor, the same spectrum to tolerance.
|
|
14
|
+
|
|
15
|
+
use tsdyn_engine::map_lyapunov::{map_lyapunov, MapLyapunovError, MapLyapunovOutcome};
|
|
16
|
+
use tsdyn_ir::Tape;
|
|
17
|
+
|
|
18
|
+
use super::marshal::{build_evaluator, EngineError};
|
|
19
|
+
|
|
20
|
+
/// Map a [`MapLyapunovError`] (a kernel-side shape / Jacobian / divergence failure)
|
|
21
|
+
/// to the bridge's [`EngineError`] so the binding layer routes it to the same
|
|
22
|
+
/// Python exception the other entry points use (`ValueError` / `RuntimeError`). The
|
|
23
|
+
/// Jacobian-less case carries the actionable "recompile with a Jacobian" message.
|
|
24
|
+
fn to_engine_err(e: MapLyapunovError) -> EngineError {
|
|
25
|
+
match e {
|
|
26
|
+
MapLyapunovError::BadShape(m) => EngineError::BadShape(m),
|
|
27
|
+
MapLyapunovError::NoJacobian => EngineError::BadShape(
|
|
28
|
+
"map Lyapunov needs the step Jacobian, but the map tape was compiled without one \
|
|
29
|
+
(with_jacobian=False). Recompile the map problem with a Jacobian."
|
|
30
|
+
.to_string(),
|
|
31
|
+
),
|
|
32
|
+
MapLyapunovError::Diverged(m) => EngineError::Diverged(m),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Run the discrete-map QR tangent-map Lyapunov spectrum over a **map** tape.
|
|
37
|
+
///
|
|
38
|
+
/// Builds the evaluator over `tape` (which must carry its step Jacobian), seeds the
|
|
39
|
+
/// deviation frame to the leading `k` identity columns, iterates `steps` map steps
|
|
40
|
+
/// propagating the frame through `J(x_n)` (pre-image convention) with a QR
|
|
41
|
+
/// reorthonormalisation every `reortho_interval` steps, and returns the `k`
|
|
42
|
+
/// time-averaged exponents (largest first) plus the completed-interval count. The
|
|
43
|
+
/// map tape folds its parameters in, so `p` is empty. `jit` selects the Cranelift
|
|
44
|
+
/// evaluator (numerically identical to the interpreter).
|
|
45
|
+
#[allow(clippy::too_many_arguments)]
|
|
46
|
+
pub fn map_lyapunov_bridge(
|
|
47
|
+
tape: Tape,
|
|
48
|
+
p: &[f64],
|
|
49
|
+
ic: &[f64],
|
|
50
|
+
steps: usize,
|
|
51
|
+
k: usize,
|
|
52
|
+
reortho_interval: usize,
|
|
53
|
+
jit: bool,
|
|
54
|
+
) -> Result<MapLyapunovOutcome, EngineError> {
|
|
55
|
+
let ev = build_evaluator(tape, jit)?;
|
|
56
|
+
map_lyapunov(&*ev, p, ic, steps, k, reortho_interval).map_err(to_engine_err)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#[cfg(test)]
|
|
60
|
+
mod tests {
|
|
61
|
+
use super::*;
|
|
62
|
+
use tsdyn_ir::TapeBuilder;
|
|
63
|
+
|
|
64
|
+
/// Hénon `(x, y) ← (1 - a x² + y, b x)` carrying its analytic Jacobian.
|
|
65
|
+
fn henon_jac(a: f64, bcoef: f64) -> Tape {
|
|
66
|
+
let mut b = TapeBuilder::new();
|
|
67
|
+
let x = b.state(0);
|
|
68
|
+
let y = b.state(1);
|
|
69
|
+
let one = b.constant(1.0);
|
|
70
|
+
let ac = b.constant(a);
|
|
71
|
+
let bc = b.constant(bcoef);
|
|
72
|
+
let xx = b.mul(x, x);
|
|
73
|
+
let axx = b.mul(ac, xx);
|
|
74
|
+
let omaxx = b.sub(one, axx);
|
|
75
|
+
let nx = b.add(omaxx, y);
|
|
76
|
+
let ny = b.mul(bc, x);
|
|
77
|
+
let two = b.constant(2.0);
|
|
78
|
+
let twoa = b.mul(two, ac);
|
|
79
|
+
let twoax = b.mul(twoa, x);
|
|
80
|
+
let neg_twoax = b.neg(twoax);
|
|
81
|
+
let zero = b.constant(0.0);
|
|
82
|
+
b.finish(&[nx, ny], &[neg_twoax, one, bc, zero], 2, 0)
|
|
83
|
+
.unwrap()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// A Jacobian-less map tape (the rejected case).
|
|
87
|
+
fn henon_no_jac(a: f64, bcoef: f64) -> Tape {
|
|
88
|
+
let mut b = TapeBuilder::new();
|
|
89
|
+
let x = b.state(0);
|
|
90
|
+
let y = b.state(1);
|
|
91
|
+
let one = b.constant(1.0);
|
|
92
|
+
let ac = b.constant(a);
|
|
93
|
+
let bc = b.constant(bcoef);
|
|
94
|
+
let xx = b.mul(x, x);
|
|
95
|
+
let axx = b.mul(ac, xx);
|
|
96
|
+
let omaxx = b.sub(one, axx);
|
|
97
|
+
let nx = b.add(omaxx, y);
|
|
98
|
+
let ny = b.mul(bc, x);
|
|
99
|
+
b.finish(&[nx, ny], &[], 2, 0).unwrap()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#[test]
|
|
103
|
+
fn bridge_interp_equals_jit_bit_for_bit() {
|
|
104
|
+
// The bridge drives both evaluators over the same lowered tape; the
|
|
105
|
+
// iteration is deterministic and eval/eval_jac are bit-identical between
|
|
106
|
+
// interpreter and JIT, so the exponents agree to the last bit.
|
|
107
|
+
let run = |jit| {
|
|
108
|
+
map_lyapunov_bridge(henon_jac(1.4, 0.3), &[], &[0.1, 0.1], 5000, 2, 1, jit)
|
|
109
|
+
.unwrap()
|
|
110
|
+
.exponents
|
|
111
|
+
};
|
|
112
|
+
let interp = run(false);
|
|
113
|
+
let jit = run(true);
|
|
114
|
+
assert_eq!(interp.len(), jit.len());
|
|
115
|
+
for (a, b) in interp.iter().zip(jit.iter()) {
|
|
116
|
+
assert_eq!(a.to_bits(), b.to_bits(), "interp {a} != jit {b}");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[test]
|
|
121
|
+
fn bridge_reproduces_henon_literature() {
|
|
122
|
+
let out = map_lyapunov_bridge(henon_jac(1.4, 0.3), &[], &[0.1, 0.1], 10_000, 2, 1, false)
|
|
123
|
+
.unwrap();
|
|
124
|
+
assert!(
|
|
125
|
+
(out.exponents[0] - 0.419).abs() < 0.05,
|
|
126
|
+
"{:?}",
|
|
127
|
+
out.exponents
|
|
128
|
+
);
|
|
129
|
+
assert!(
|
|
130
|
+
(out.exponents[1] - (-1.623)).abs() < 0.05,
|
|
131
|
+
"{:?}",
|
|
132
|
+
out.exponents
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#[test]
|
|
137
|
+
fn bridge_rejects_jacobianless_tape() {
|
|
138
|
+
let err = map_lyapunov_bridge(henon_no_jac(1.4, 0.3), &[], &[0.1, 0.1], 100, 2, 1, false)
|
|
139
|
+
.unwrap_err();
|
|
140
|
+
assert!(matches!(err, EngineError::BadShape(_)), "got {err:?}");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
pub mod basin;
|
|
34
34
|
pub mod dde;
|
|
35
35
|
pub mod events;
|
|
36
|
+
pub mod lyapunov;
|
|
36
37
|
pub mod map;
|
|
38
|
+
pub mod map_lyapunov;
|
|
37
39
|
pub mod marshal;
|
|
38
40
|
pub mod ode;
|
|
39
41
|
pub mod sde;
|
|
@@ -46,7 +48,9 @@ pub mod stepper;
|
|
|
46
48
|
pub use basin::{basin_march_flow_bridge, basin_march_map_bridge};
|
|
47
49
|
pub use dde::integrate_dde_dense;
|
|
48
50
|
pub use events::integrate_events_dense;
|
|
49
|
-
pub use
|
|
51
|
+
pub use lyapunov::lyapunov_spectrum_ode_bridge;
|
|
52
|
+
pub use map::{iterate_map, map_ensemble_final, map_param_sweep};
|
|
53
|
+
pub use map_lyapunov::map_lyapunov_bridge;
|
|
50
54
|
#[cfg(test)]
|
|
51
55
|
pub use marshal::build_tape;
|
|
52
56
|
pub use marshal::EngineError;
|
|
@@ -281,6 +281,79 @@ fn iterate_ensemble_final<'py>(
|
|
|
281
281
|
PyArray1::from_vec(py, flat).reshape([n_ic, dim])
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
/// Run a whole map orbit-diagram parameter sweep in one call (stream
|
|
285
|
+
/// `perf/param-sweep-kernel`), returning the recorded asymptotic states and each
|
|
286
|
+
/// value's fate.
|
|
287
|
+
///
|
|
288
|
+
/// The leading tape arrays describe the lowered map with the **swept** parameter
|
|
289
|
+
/// kept as its single runtime `Param` input (`n_param == 1`) — the other
|
|
290
|
+
/// parameters fold into constants — so the sweep needs no per-value re-lowering or
|
|
291
|
+
/// per-value FFI round-trip. `base_params` is the full runtime parameter vector;
|
|
292
|
+
/// `sweep_index` which of its entries each `values[k]` overwrites; `ic` the base
|
|
293
|
+
/// initial condition; `components` the state-component indices to record;
|
|
294
|
+
/// `transient` / `n_record` the discard / record counts; `carry_state` whether
|
|
295
|
+
/// each value resumes from the previous value's final state; `jit` selects the
|
|
296
|
+
/// Cranelift evaluator (numerically identical to the interpreter).
|
|
297
|
+
///
|
|
298
|
+
/// Returns `(points, status)`: `points` is the `(n_values * n_record,
|
|
299
|
+
/// n_components)` recorded array, and `status` an `(n_values,)` i64 array of `0`
|
|
300
|
+
/// (finite) / `1` (diverged — the value's block is zero, dropped to an empty set
|
|
301
|
+
/// by the Python wiring). The per-iterate numerics are byte-for-byte the
|
|
302
|
+
/// [`iterate_map`] path.
|
|
303
|
+
#[pyfunction]
|
|
304
|
+
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
|
305
|
+
fn map_param_sweep<'py>(
|
|
306
|
+
py: Python<'py>,
|
|
307
|
+
ops: PyReadonlyArray1<i32>,
|
|
308
|
+
a: PyReadonlyArray1<i32>,
|
|
309
|
+
b: PyReadonlyArray1<i32>,
|
|
310
|
+
imm: PyReadonlyArray1<f64>,
|
|
311
|
+
outputs: PyReadonlyArray1<i32>,
|
|
312
|
+
jac_outputs: PyReadonlyArray1<i32>,
|
|
313
|
+
n_state: usize,
|
|
314
|
+
n_param: usize,
|
|
315
|
+
base_params: PyReadonlyArray1<f64>,
|
|
316
|
+
sweep_index: usize,
|
|
317
|
+
values: PyReadonlyArray1<f64>,
|
|
318
|
+
ic: PyReadonlyArray1<f64>,
|
|
319
|
+
components: PyReadonlyArray1<i64>,
|
|
320
|
+
transient: usize,
|
|
321
|
+
n_record: usize,
|
|
322
|
+
carry_state: bool,
|
|
323
|
+
jit: bool,
|
|
324
|
+
) -> PyResult<(Bound<'py, PyArray2<f64>>, Bound<'py, PyArray1<i64>>)> {
|
|
325
|
+
let tape = OwnedTape::copy_in(&ops, &a, &b, &imm, &outputs, &jac_outputs, n_state, n_param)?;
|
|
326
|
+
let base_params = vec_f64("base_params", &base_params)?;
|
|
327
|
+
let values = vec_f64("values", &values)?;
|
|
328
|
+
let ic = vec_f64("ic", &ic)?;
|
|
329
|
+
let components: Vec<usize> = components
|
|
330
|
+
.as_slice()
|
|
331
|
+
.map_err(|_| PyValueError::new_err("components must be a contiguous int64 array"))?
|
|
332
|
+
.iter()
|
|
333
|
+
.map(|&c| c as usize)
|
|
334
|
+
.collect();
|
|
335
|
+
let n_values = values.len();
|
|
336
|
+
let n_components = components.len();
|
|
337
|
+
let (points, status) = py
|
|
338
|
+
.detach(|| {
|
|
339
|
+
bridge::map_param_sweep(
|
|
340
|
+
tape.build()?,
|
|
341
|
+
&base_params,
|
|
342
|
+
sweep_index,
|
|
343
|
+
&values,
|
|
344
|
+
&ic,
|
|
345
|
+
&components,
|
|
346
|
+
transient,
|
|
347
|
+
n_record,
|
|
348
|
+
carry_state,
|
|
349
|
+
jit,
|
|
350
|
+
)
|
|
351
|
+
})
|
|
352
|
+
.map_err(to_py_err)?;
|
|
353
|
+
let points = PyArray1::from_vec(py, points).reshape([n_values * n_record, n_components])?;
|
|
354
|
+
Ok((points, PyArray1::from_vec(py, status)))
|
|
355
|
+
}
|
|
356
|
+
|
|
284
357
|
// ---------------------------------------------------------------------------
|
|
285
358
|
// Integration
|
|
286
359
|
// ---------------------------------------------------------------------------
|
|
@@ -961,6 +1034,138 @@ fn basin_march_map<'py>(
|
|
|
961
1034
|
Ok(basin_outcome_to_py(py, out))
|
|
962
1035
|
}
|
|
963
1036
|
|
|
1037
|
+
// ---------------------------------------------------------------------------
|
|
1038
|
+
// Discrete-map Lyapunov spectrum (stream perf/map-lyapunov-kernel)
|
|
1039
|
+
// ---------------------------------------------------------------------------
|
|
1040
|
+
|
|
1041
|
+
use bridge::map_lyapunov_bridge;
|
|
1042
|
+
|
|
1043
|
+
/// Compute a discrete-map Lyapunov spectrum on the engine — the whole QR
|
|
1044
|
+
/// tangent-map iteration (iterate `f`, propagate `k` deviation vectors through
|
|
1045
|
+
/// `J(x_n)` at the pre-image, reorthonormalise, accumulate `log|R_ii|`) in one call.
|
|
1046
|
+
///
|
|
1047
|
+
/// The leading tape arrays are the usual `Tape.to_arrays()` tuple for the **map**
|
|
1048
|
+
/// step, which **must carry its Jacobian** (`with_jacobian=True`; rejected up front
|
|
1049
|
+
/// otherwise). `ic` is the `(dim,)` start state; `steps` the iterate budget; `k` the
|
|
1050
|
+
/// number of exponents (`1 ≤ k ≤ dim`); `reortho_interval` the QR cadence. The map
|
|
1051
|
+
/// tape folds its parameters in, so there is no parameter vector. `jit` selects the
|
|
1052
|
+
/// Cranelift evaluator (numerically identical to the interpreter).
|
|
1053
|
+
///
|
|
1054
|
+
/// Returns `(exponents, intervals)`: the `k` time-averaged exponents (largest
|
|
1055
|
+
/// first) and the number of completed reorthonormalisation intervals. A divergence
|
|
1056
|
+
/// before the budget is exhausted raises `RuntimeError` (the "diverge loudly"
|
|
1057
|
+
/// contract); a malformed call / Jacobian-less tape raises `ValueError`.
|
|
1058
|
+
#[pyfunction]
|
|
1059
|
+
#[allow(clippy::too_many_arguments)]
|
|
1060
|
+
fn map_lyapunov_spectrum<'py>(
|
|
1061
|
+
py: Python<'py>,
|
|
1062
|
+
ops: PyReadonlyArray1<i32>,
|
|
1063
|
+
a: PyReadonlyArray1<i32>,
|
|
1064
|
+
b: PyReadonlyArray1<i32>,
|
|
1065
|
+
imm: PyReadonlyArray1<f64>,
|
|
1066
|
+
outputs: PyReadonlyArray1<i32>,
|
|
1067
|
+
jac_outputs: PyReadonlyArray1<i32>,
|
|
1068
|
+
n_state: usize,
|
|
1069
|
+
n_param: usize,
|
|
1070
|
+
ic: PyReadonlyArray1<f64>,
|
|
1071
|
+
steps: usize,
|
|
1072
|
+
k: usize,
|
|
1073
|
+
reortho_interval: usize,
|
|
1074
|
+
jit: bool,
|
|
1075
|
+
) -> PyResult<(Bound<'py, PyArray1<f64>>, usize)> {
|
|
1076
|
+
let tape = OwnedTape::copy_in(&ops, &a, &b, &imm, &outputs, &jac_outputs, n_state, n_param)?;
|
|
1077
|
+
let ic = vec_f64("ic", &ic)?;
|
|
1078
|
+
let out = py
|
|
1079
|
+
.detach(|| map_lyapunov_bridge(tape.build()?, &[], &ic, steps, k, reortho_interval, jit))
|
|
1080
|
+
.map_err(to_py_err)?;
|
|
1081
|
+
Ok((PyArray1::from_vec(py, out.exponents), out.intervals))
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// ODE Lyapunov spectrum (stream perf/ode-lyapunov-engine)
|
|
1085
|
+
// ---------------------------------------------------------------------------
|
|
1086
|
+
|
|
1087
|
+
use bridge::lyapunov_spectrum_ode_bridge;
|
|
1088
|
+
|
|
1089
|
+
/// Run the whole burn-in + averaging Benettin Lyapunov-spectrum loop for an ODE
|
|
1090
|
+
/// flow in one engine call.
|
|
1091
|
+
///
|
|
1092
|
+
/// The leading tape arrays are the usual `Tape.to_arrays()` tuple — here the
|
|
1093
|
+
/// **extended** variational tape (`dim*(k+1)` inputs/outputs: the base RHS stacked
|
|
1094
|
+
/// with the `k` tangent-vector RHS blocks, built by
|
|
1095
|
+
/// `tsdynamics.derived._variational.build_variational_tape`). `p` is the live
|
|
1096
|
+
/// control-parameter vector; `method` resolves through the solver registry
|
|
1097
|
+
/// (an implicit kernel needs a Jacobian-carrying tape — the extended tape carries
|
|
1098
|
+
/// one); `dim` the **base** system dimension; `k` the number of tangent vectors;
|
|
1099
|
+
/// `z0` the extended initial state (`embed_extended`: base IC ⊕ the seed tangent
|
|
1100
|
+
/// frame, column-major); `t0` the start time; `dt` the renormalisation interval;
|
|
1101
|
+
/// `burn_in` / `final_time` the two window lengths; `jit` selects the Cranelift
|
|
1102
|
+
/// evaluator (numerically identical to the interpreter).
|
|
1103
|
+
///
|
|
1104
|
+
/// Each `dt` chunk is integrated by a *fresh* two-node `integrate_grid([t, t+dt])`
|
|
1105
|
+
/// (byte-for-byte the released `TangentSystem._step_ode_engine` numerics, so
|
|
1106
|
+
/// `interp == jit`), then the `(dim, k)` tangent block is QR-reorthonormalised
|
|
1107
|
+
/// (modified Gram–Schmidt) and `Σ log|diag R|` accumulated over the averaging
|
|
1108
|
+
/// window. Returns `(spectrum, final_state, last_growths)` — the `k` exponents,
|
|
1109
|
+
/// the final extended state, and the last per-step log-stretch contributions. A
|
|
1110
|
+
/// divergence raises (it must not become a plausible-looking spectrum).
|
|
1111
|
+
#[pyfunction]
|
|
1112
|
+
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
|
1113
|
+
fn lyapunov_spectrum_ode<'py>(
|
|
1114
|
+
py: Python<'py>,
|
|
1115
|
+
ops: PyReadonlyArray1<i32>,
|
|
1116
|
+
a: PyReadonlyArray1<i32>,
|
|
1117
|
+
b: PyReadonlyArray1<i32>,
|
|
1118
|
+
imm: PyReadonlyArray1<f64>,
|
|
1119
|
+
outputs: PyReadonlyArray1<i32>,
|
|
1120
|
+
jac_outputs: PyReadonlyArray1<i32>,
|
|
1121
|
+
n_state: usize,
|
|
1122
|
+
n_param: usize,
|
|
1123
|
+
p: PyReadonlyArray1<f64>,
|
|
1124
|
+
method: String,
|
|
1125
|
+
rtol: f64,
|
|
1126
|
+
atol: f64,
|
|
1127
|
+
dim: usize,
|
|
1128
|
+
k: usize,
|
|
1129
|
+
z0: PyReadonlyArray1<f64>,
|
|
1130
|
+
t0: f64,
|
|
1131
|
+
dt: f64,
|
|
1132
|
+
burn_in: f64,
|
|
1133
|
+
final_time: f64,
|
|
1134
|
+
jit: bool,
|
|
1135
|
+
) -> PyResult<(
|
|
1136
|
+
Bound<'py, PyArray1<f64>>,
|
|
1137
|
+
Bound<'py, PyArray1<f64>>,
|
|
1138
|
+
Bound<'py, PyArray1<f64>>,
|
|
1139
|
+
)> {
|
|
1140
|
+
let tape = OwnedTape::copy_in(&ops, &a, &b, &imm, &outputs, &jac_outputs, n_state, n_param)?;
|
|
1141
|
+
let p = vec_f64("p", &p)?;
|
|
1142
|
+
let z0 = vec_f64("z0", &z0)?;
|
|
1143
|
+
let out = py
|
|
1144
|
+
.detach(|| {
|
|
1145
|
+
lyapunov_spectrum_ode_bridge(
|
|
1146
|
+
tape.build()?,
|
|
1147
|
+
&p,
|
|
1148
|
+
&method,
|
|
1149
|
+
rtol,
|
|
1150
|
+
atol,
|
|
1151
|
+
dim,
|
|
1152
|
+
k,
|
|
1153
|
+
&z0,
|
|
1154
|
+
t0,
|
|
1155
|
+
dt,
|
|
1156
|
+
burn_in,
|
|
1157
|
+
final_time,
|
|
1158
|
+
jit,
|
|
1159
|
+
)
|
|
1160
|
+
})
|
|
1161
|
+
.map_err(to_py_err)?;
|
|
1162
|
+
Ok((
|
|
1163
|
+
PyArray1::from_vec(py, out.spectrum),
|
|
1164
|
+
PyArray1::from_vec(py, out.final_state),
|
|
1165
|
+
PyArray1::from_vec(py, out.last_growths),
|
|
1166
|
+
))
|
|
1167
|
+
}
|
|
1168
|
+
|
|
964
1169
|
// ---------------------------------------------------------------------------
|
|
965
1170
|
// Resumable ODE stepper handle (stream WS-STEPPER)
|
|
966
1171
|
// ---------------------------------------------------------------------------
|
|
@@ -1171,6 +1376,7 @@ fn _rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
1171
1376
|
m.add_function(wrap_pyfunction!(eval_jac, m)?)?;
|
|
1172
1377
|
m.add_function(wrap_pyfunction!(iterate_map, m)?)?;
|
|
1173
1378
|
m.add_function(wrap_pyfunction!(iterate_ensemble_final, m)?)?;
|
|
1379
|
+
m.add_function(wrap_pyfunction!(map_param_sweep, m)?)?;
|
|
1174
1380
|
m.add_function(wrap_pyfunction!(integrate_dense, m)?)?;
|
|
1175
1381
|
m.add_function(wrap_pyfunction!(integrate_dde_dense, m)?)?;
|
|
1176
1382
|
m.add_function(wrap_pyfunction!(integrate_ensemble_final, m)?)?;
|
|
@@ -1179,6 +1385,8 @@ fn _rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
1179
1385
|
m.add_function(wrap_pyfunction!(integrate_events_dense, m)?)?;
|
|
1180
1386
|
m.add_function(wrap_pyfunction!(basin_march_flow, m)?)?;
|
|
1181
1387
|
m.add_function(wrap_pyfunction!(basin_march_map, m)?)?;
|
|
1388
|
+
m.add_function(wrap_pyfunction!(map_lyapunov_spectrum, m)?)?;
|
|
1389
|
+
m.add_function(wrap_pyfunction!(lyapunov_spectrum_ode, m)?)?;
|
|
1182
1390
|
m.add_class::<PyOdeStepper>()?;
|
|
1183
1391
|
m.add_function(wrap_pyfunction!(solvers, m)?)?;
|
|
1184
1392
|
m.add_function(wrap_pyfunction!(_version, m)?)?;
|