tsdynamics 6.0.2__tar.gz → 6.0.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-6.0.2 → tsdynamics-6.0.4}/PKG-INFO +1 -1
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/src/bridge/lyapunov.rs +19 -3
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/src/bridge/map_lyapunov.rs +2 -2
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/src/bridge/marshal.rs +6 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/src/bridge/mod.rs +17 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/src/lib.rs +3 -1
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/lib.rs +2 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/lyapunov.rs +116 -1
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/map_lyapunov.rs +4 -7
- tsdynamics-6.0.4/crates/tsdyn-engine/src/tangent_frame.rs +82 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/pyproject.toml +1 -1
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/__init__.py +1 -1
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_engine/run.py +5 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_engine/run_methods.py +4 -2
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_solvers/__init__.py +7 -9
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_solvers/select.py +2 -1
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_utils/escape.py +14 -6
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/__init__.py +18 -3
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/_common.py +33 -1
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/_discovery.py +133 -2
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/_public.py +1 -1
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/_tangent.py +35 -1
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/basins/__init__.py +4 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/basins/_common.py +8 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/basins/attractors.py +17 -15
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/basins/basins.py +2 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/basins/metrics.py +19 -19
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/chaos/__init__.py +18 -8
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/chaos/_common.py +4 -4
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/chaos/expansion.py +19 -20
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/chaos/gali.py +50 -87
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/dimensions/_scaling.py +4 -4
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/dimensions/fixedmass.py +13 -11
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/dimensions/generalized.py +18 -5
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/embedding/dimension.py +44 -13
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/embedding/embed.py +16 -12
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/fixedpoints/__init__.py +9 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/fixedpoints/_interval.py +4 -4
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/fixedpoints/fixed.py +64 -16
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/fixedpoints/periodic.py +22 -8
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/lyapunov/__init__.py +115 -34
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/lyapunov/from_data.py +2 -32
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/orbits/__init__.py +6 -3
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/orbits/poincare.py +6 -2
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/orbits/return_map.py +29 -15
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/planar.py +97 -47
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/recurrence/rqa.py +4 -2
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/recurrence/windowed.py +15 -10
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/data/sampling.py +57 -27
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/derived/_crossings.py +1 -1
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/derived/tangent.py +75 -15
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/continuous.py +6 -5
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/delay.py +1 -2
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/discrete.py +1 -2
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/stochastic.py +24 -8
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/continuous/chem_bio_systems.py +4 -5
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/continuous/exotic_systems.py +13 -6
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/continuous/oscillatory_systems.py +27 -14
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/discrete/exotic_maps.py +22 -12
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/export.py +105 -13
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/__init__.py +34 -2
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/caps.py +20 -2
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/mpl/_anim.py +206 -24
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/mpl/_core.py +34 -18
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/mpl/_threed.py +49 -21
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/plotly/__init__.py +9 -0
- tsdynamics-6.0.4/src/tsdynamics/viz/render/plotly/_anim.py +666 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/plotly/_composite.py +3 -18
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/plotly/_core.py +49 -18
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/plotly/_threed.py +20 -37
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/threejs/__init__.py +23 -1
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/threejs/_assets/tsdyn-threejs-loader.js +155 -319
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/threejs/_lower.py +94 -32
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/spec.py +79 -46
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/style.py +83 -14
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/transforms/_base.py +35 -8
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/transforms/_data.py +40 -9
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/transforms/_frontdoor.py +15 -2
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/transforms/_registry.py +4 -3
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/transforms/fields.py +13 -14
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/transforms/hilbert.py +33 -9
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/transforms/planar.py +39 -22
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/transforms/results.py +35 -9
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/transforms/series.py +22 -10
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/transforms/spectra.py +136 -41
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/transforms/stability.py +30 -12
- tsdynamics-6.0.2/src/tsdynamics/viz/render/plotly/_anim.py +0 -751
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/LICENSE +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/README.md +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/Cargo.toml +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/Cargo.lock +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/Cargo.toml +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/src/bridge/basin.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/src/bridge/dde.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/src/bridge/events.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/src/bridge/map.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/src/bridge/ode.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/src/bridge/sde.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-core/src/bridge/stepper.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/Cargo.toml +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/benches/engine.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/alloc.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/basin.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/dde.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/ensemble.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/event.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/integrate.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/interrupt.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/map.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/param_sweep.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/pool.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/problem.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/rng.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/sde.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-engine/src/testkit.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/Cargo.toml +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/src/builder.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/src/eval.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/src/lib.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/src/op.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/src/reference.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/src/tape.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/tests/coverage_tape.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/tests/fixtures/CircadianRhythm.txt +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/tests/fixtures/Lorenz.txt +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/tests/fixtures/SanUmSrisuchinwong.txt +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/tests/fixtures/SprottMore.txt +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/tests/fixtures/Torus.txt +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/tests/fixtures/YuWang2.txt +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/tests/gen_fixtures.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/tests/golden_fixtures.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-ir/tests/roundtrip.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-jit/Cargo.toml +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-jit/benches/compile.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-jit/src/cache.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-jit/src/codegen.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-jit/src/error.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-jit/src/evaluator.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-jit/src/lib.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-jit/src/shims.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-jit/tests/compile_latency.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-jit/tests/golden_fixtures.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-jit/tests/interpreter_equivalence.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/Cargo.toml +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/benches/kernels.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/caps.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/adams.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/bs3.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/cashkarp.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/control.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/dop853.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/euler.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/heun.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/heun_euler.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/midpoint.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/mod.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/ralston.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/rk4.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/rk45.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/rk4_38.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/rkf45.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/ssprk3.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/testkit.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/explicit/tsit5.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/implicit/backward_euler.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/implicit/bdf.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/implicit/control.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/implicit/implicit_midpoint.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/implicit/linalg.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/implicit/mod.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/implicit/newton.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/implicit/rosenbrock.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/implicit/sdirk2.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/implicit/trapezoid.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/implicit/trbdf2.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/lib.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/registry.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/sde/euler_maruyama.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/sde/milstein.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/sde/mod.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/sde/testkit.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/src/solver.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/tests/explicit_accuracy.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/tests/implicit_stiff.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-solvers/tests/registry.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-vm/Cargo.toml +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-vm/benches/interp.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-vm/src/interp.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-vm/src/lib.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-vm/tests/golden_fixtures.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/crates/tsdyn-vm/tests/oracle_equivalence.rs +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_engine/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_engine/_families.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_engine/compile.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_engine/events.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_engine/problem.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_engine/reference.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_engine/sde_run.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_engine/stepper.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_engine/symbols.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_redirects.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_solvers/explicit.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_solvers/implicit.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_solvers/stochastic.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_utils/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_utils/grids.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_utils/lookup.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_utils/plot_namespace.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/_utils/tolerances.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/_plotbuilder.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/_result.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/_result_array.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/_result_base.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/_result_collection.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/_result_json.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/_result_scalar.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/_result_scaling.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/_result_viz.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/basins/continuation.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/chaos/zero_one.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/dimensions/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/dimensions/_common.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/dimensions/correlation.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/embedding/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/embedding/_common.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/embedding/delay.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/fixedpoints/_common.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/orbits/orbit_diagram.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/recurrence/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/recurrence/_common.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/recurrence/matrix.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/results.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/sampling/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/analysis/sampling/sagitta.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/data/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/data/trajectory.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/derived/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/derived/_base.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/derived/_variational.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/derived/ensemble.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/derived/poincare.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/derived/projected.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/derived/stroboscopic.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/derived/wrapped.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/errors.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/_dde_lyapunov.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/_derive.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/_hidden.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/_info.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/_kwargs.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/_params.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/_plottable.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/base.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/protocol.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/families/wrapped.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/plugins.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/py.typed +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/registry.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/_declared_params.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/continuous/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/continuous/chaotic_attractors.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/continuous/climate_geophysics.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/continuous/coupled_systems.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/continuous/delayed_systems.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/continuous/physical_systems.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/continuous/population_dynamics.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/continuous/spatial_fields.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/continuous/stochastic_systems.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/discrete/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/discrete/chaotic_maps.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/discrete/geometric_maps.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/discrete/polynomial_maps.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/systems/discrete/population_maps.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/_frames.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/_resample.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/_tweaks.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/_visibility.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/compose.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/producers.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/json.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/mpl/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/plotly/_html.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/render/threejs/_page.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/transforms/__init__.py +0 -0
- {tsdynamics-6.0.2 → tsdynamics-6.0.4}/src/tsdynamics/viz/transforms/_primitives.py +0 -0
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
//! [`super::marshal`]; the renormalisation loop and the QR live in
|
|
13
13
|
//! [`tsdyn_engine::lyapunov`].
|
|
14
14
|
|
|
15
|
-
use tsdyn_engine::lyapunov::{
|
|
15
|
+
use tsdyn_engine::lyapunov::{lyapunov_spectrum_ode_guarded, LyapunovError, LyapunovOutcome};
|
|
16
16
|
use tsdyn_ir::Tape;
|
|
17
17
|
use tsdyn_solvers::Solver;
|
|
18
18
|
|
|
@@ -59,6 +59,7 @@ pub fn lyapunov_spectrum_ode_bridge(
|
|
|
59
59
|
burn_in: f64,
|
|
60
60
|
final_time: f64,
|
|
61
61
|
jit: bool,
|
|
62
|
+
escape_limit: f64,
|
|
62
63
|
) -> Result<LyapunovOutcome, EngineError> {
|
|
63
64
|
// The extended tape is `n_state == ev.dim()` (a plain ODE-shaped tape over the
|
|
64
65
|
// stacked state), validated by the kernel against `dim*(k+1)`. Resolve the
|
|
@@ -71,8 +72,20 @@ pub fn lyapunov_spectrum_ode_bridge(
|
|
|
71
72
|
// march); the name is a registry name and the tolerances are validated, so
|
|
72
73
|
// `build_solver` always succeeds.
|
|
73
74
|
let factory = move || -> Box<dyn Solver> { build_solver(name, tol) };
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
lyapunov_spectrum_ode_guarded(
|
|
76
|
+
&*ev,
|
|
77
|
+
factory,
|
|
78
|
+
p,
|
|
79
|
+
dim,
|
|
80
|
+
k,
|
|
81
|
+
z0,
|
|
82
|
+
t0,
|
|
83
|
+
dt,
|
|
84
|
+
burn_in,
|
|
85
|
+
final_time,
|
|
86
|
+
escape_limit,
|
|
87
|
+
)
|
|
88
|
+
.map_err(to_engine_err)
|
|
76
89
|
}
|
|
77
90
|
|
|
78
91
|
#[cfg(test)]
|
|
@@ -124,6 +137,7 @@ mod tests {
|
|
|
124
137
|
5.0,
|
|
125
138
|
50.0,
|
|
126
139
|
jit,
|
|
140
|
+
f64::INFINITY,
|
|
127
141
|
)
|
|
128
142
|
.unwrap()
|
|
129
143
|
};
|
|
@@ -160,6 +174,7 @@ mod tests {
|
|
|
160
174
|
1.0,
|
|
161
175
|
1.0,
|
|
162
176
|
false,
|
|
177
|
+
f64::INFINITY,
|
|
163
178
|
)
|
|
164
179
|
.unwrap_err();
|
|
165
180
|
assert!(matches!(err, EngineError::UnknownMethod(_)), "got {err:?}");
|
|
@@ -186,6 +201,7 @@ mod tests {
|
|
|
186
201
|
1.0,
|
|
187
202
|
1.0,
|
|
188
203
|
false,
|
|
204
|
+
f64::INFINITY,
|
|
189
205
|
)
|
|
190
206
|
.unwrap_err();
|
|
191
207
|
assert!(
|
|
@@ -37,10 +37,10 @@ fn to_engine_err(e: MapLyapunovError) -> EngineError {
|
|
|
37
37
|
/// Run the discrete-map QR tangent-map Lyapunov spectrum over a **map** tape.
|
|
38
38
|
///
|
|
39
39
|
/// Builds the evaluator over `tape` (which must carry its step Jacobian), seeds the
|
|
40
|
-
/// deviation frame to
|
|
40
|
+
/// deviation frame to a deterministic mixed orthonormal frame, iterates `steps` map steps
|
|
41
41
|
/// propagating the frame through `J(x_n)` (pre-image convention) with a QR
|
|
42
42
|
/// reorthonormalisation every `reortho_interval` steps, and returns the `k`
|
|
43
|
-
/// time-averaged exponents
|
|
43
|
+
/// time-averaged exponents in QR order plus the completed-interval count. The
|
|
44
44
|
/// map tape folds its parameters in, so `p` is empty. `jit` selects the Cranelift
|
|
45
45
|
/// evaluator (numerically identical to the interpreter).
|
|
46
46
|
#[allow(clippy::too_many_arguments)]
|
|
@@ -544,6 +544,12 @@ pub(super) fn validate_grid(t_eval: &[f64]) -> Result<(), EngineError> {
|
|
|
544
544
|
w[0]
|
|
545
545
|
)));
|
|
546
546
|
}
|
|
547
|
+
if !(w[1] - w[0]).is_finite() {
|
|
548
|
+
return Err(EngineError::InvalidParameter(format!(
|
|
549
|
+
"t_eval gap {i} is not finite: {} - {} exceeds float64 range",
|
|
550
|
+
w[1], w[0]
|
|
551
|
+
)));
|
|
552
|
+
}
|
|
547
553
|
}
|
|
548
554
|
Ok(())
|
|
549
555
|
}
|
|
@@ -730,6 +730,23 @@ mod tests {
|
|
|
730
730
|
);
|
|
731
731
|
}
|
|
732
732
|
|
|
733
|
+
#[test]
|
|
734
|
+
fn integrate_dense_rejects_overflowing_finite_grid_gap() {
|
|
735
|
+
let err = integrate_dense(
|
|
736
|
+
decay_tape(),
|
|
737
|
+
&[1.0],
|
|
738
|
+
&[2.0],
|
|
739
|
+
&[-f64::MAX, f64::MAX],
|
|
740
|
+
"rk45",
|
|
741
|
+
1e-6,
|
|
742
|
+
1e-9,
|
|
743
|
+
false,
|
|
744
|
+
)
|
|
745
|
+
.unwrap_err();
|
|
746
|
+
assert!(matches!(err, EngineError::InvalidParameter(_)), "{err:?}");
|
|
747
|
+
assert!(err.to_string().contains("gap"));
|
|
748
|
+
}
|
|
749
|
+
|
|
733
750
|
#[test]
|
|
734
751
|
fn ensemble_rejects_non_finite_times() {
|
|
735
752
|
let err = ensemble_final(
|
|
@@ -1245,7 +1245,7 @@ use bridge::lyapunov_spectrum_ode_bridge;
|
|
|
1245
1245
|
/// window. Returns `(spectrum, final_state, last_growths)` — the `k` exponents,
|
|
1246
1246
|
/// the final extended state, and the last per-step log-stretch contributions. A
|
|
1247
1247
|
/// divergence raises (it must not become a plausible-looking spectrum).
|
|
1248
|
-
#[pyfunction]
|
|
1248
|
+
#[pyfunction(signature = (ops, a, b, imm, outputs, jac_outputs, n_state, n_param, p, method, rtol, atol, dim, k, z0, t0, dt, burn_in, final_time, jit, escape_limit=None))]
|
|
1249
1249
|
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
|
1250
1250
|
fn lyapunov_spectrum_ode<'py>(
|
|
1251
1251
|
py: Python<'py>,
|
|
@@ -1269,6 +1269,7 @@ fn lyapunov_spectrum_ode<'py>(
|
|
|
1269
1269
|
burn_in: f64,
|
|
1270
1270
|
final_time: f64,
|
|
1271
1271
|
jit: bool,
|
|
1272
|
+
escape_limit: Option<f64>,
|
|
1272
1273
|
) -> PyResult<(
|
|
1273
1274
|
Bound<'py, PyArray1<f64>>,
|
|
1274
1275
|
Bound<'py, PyArray1<f64>>,
|
|
@@ -1292,6 +1293,7 @@ fn lyapunov_spectrum_ode<'py>(
|
|
|
1292
1293
|
burn_in,
|
|
1293
1294
|
final_time,
|
|
1294
1295
|
jit,
|
|
1296
|
+
escape_limit.unwrap_or(f64::INFINITY),
|
|
1295
1297
|
)
|
|
1296
1298
|
})
|
|
1297
1299
|
.map_err(to_py_err)?;
|
|
@@ -188,7 +188,7 @@ pub struct LyapunovOutcome {
|
|
|
188
188
|
/// - `dim` — the **base** system dimension.
|
|
189
189
|
/// - `k` — the number of tangent vectors (`1 ≤ k ≤ dim`).
|
|
190
190
|
/// - `z0` — the initial extended state (length `dim*(k+1)`): base IC ⊕ the seed
|
|
191
|
-
/// tangent frame (
|
|
191
|
+
/// tangent frame (column-major), exactly the Python `embed_extended`.
|
|
192
192
|
/// - `t0` — the start time.
|
|
193
193
|
/// - `dt` — the renormalisation interval (the Python `dt`).
|
|
194
194
|
/// - `burn_in` — discard this much time before accumulating (`max(0, burn_in)`).
|
|
@@ -213,6 +213,44 @@ pub fn lyapunov_spectrum_ode<F>(
|
|
|
213
213
|
burn_in: f64,
|
|
214
214
|
final_time: f64,
|
|
215
215
|
) -> Result<LyapunovOutcome, LyapunovError>
|
|
216
|
+
where
|
|
217
|
+
F: Fn() -> Box<dyn Solver>,
|
|
218
|
+
{
|
|
219
|
+
lyapunov_spectrum_ode_guarded(
|
|
220
|
+
ev,
|
|
221
|
+
solver_factory,
|
|
222
|
+
p,
|
|
223
|
+
dim,
|
|
224
|
+
k,
|
|
225
|
+
z0,
|
|
226
|
+
t0,
|
|
227
|
+
dt,
|
|
228
|
+
burn_in,
|
|
229
|
+
final_time,
|
|
230
|
+
f64::INFINITY,
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/// The Benettin loop with a caller-supplied base-orbit escape limit.
|
|
235
|
+
///
|
|
236
|
+
/// Only the first `dim` state components are checked: a growing tangent vector
|
|
237
|
+
/// is not an escaping base orbit. Python derives this limit from its shared
|
|
238
|
+
/// scale-and-growth policy; the unguarded Rust entry point remains available
|
|
239
|
+
/// for callers measuring non-attractor finite-time growth.
|
|
240
|
+
#[allow(clippy::too_many_arguments)]
|
|
241
|
+
pub fn lyapunov_spectrum_ode_guarded<F>(
|
|
242
|
+
ev: &dyn Evaluator,
|
|
243
|
+
solver_factory: F,
|
|
244
|
+
p: &[f64],
|
|
245
|
+
dim: usize,
|
|
246
|
+
k: usize,
|
|
247
|
+
z0: &[f64],
|
|
248
|
+
t0: f64,
|
|
249
|
+
dt: f64,
|
|
250
|
+
burn_in: f64,
|
|
251
|
+
final_time: f64,
|
|
252
|
+
escape_limit: f64,
|
|
253
|
+
) -> Result<LyapunovOutcome, LyapunovError>
|
|
216
254
|
where
|
|
217
255
|
F: Fn() -> Box<dyn Solver>,
|
|
218
256
|
{
|
|
@@ -257,6 +295,11 @@ where
|
|
|
257
295
|
"final_time must be finite and positive, got {final_time}"
|
|
258
296
|
)));
|
|
259
297
|
}
|
|
298
|
+
if escape_limit.is_nan() || escape_limit <= 0.0 {
|
|
299
|
+
return Err(LyapunovError::BadShape(
|
|
300
|
+
"escape_limit must be positive (or infinite)".to_string(),
|
|
301
|
+
));
|
|
302
|
+
}
|
|
260
303
|
|
|
261
304
|
let mut z = z0.to_vec();
|
|
262
305
|
let mut t = t0;
|
|
@@ -277,6 +320,7 @@ where
|
|
|
277
320
|
while t < t_burn - EPS {
|
|
278
321
|
let h = dt.min(t_burn - t);
|
|
279
322
|
advance_chunk(ev, &solver_factory, &mut z, t, h, p, &mut poll).map_err(classify)?;
|
|
323
|
+
check_base_orbit(&z[..dim], t + h, escape_limit)?;
|
|
280
324
|
if !renorm_step(&mut z, dim, k, &mut last_growths)? {
|
|
281
325
|
return Err(LyapunovError::Diverged(
|
|
282
326
|
"extended variational state went non-finite during burn-in".to_string(),
|
|
@@ -291,6 +335,7 @@ where
|
|
|
291
335
|
while t < t_end - EPS {
|
|
292
336
|
let h = dt.min(t_end - t);
|
|
293
337
|
advance_chunk(ev, &solver_factory, &mut z, t, h, p, &mut poll).map_err(classify)?;
|
|
338
|
+
check_base_orbit(&z[..dim], t + h, escape_limit)?;
|
|
294
339
|
if !renorm_step(&mut z, dim, k, &mut last_growths)? {
|
|
295
340
|
return Err(LyapunovError::Diverged(
|
|
296
341
|
"extended variational state went non-finite during the averaging window"
|
|
@@ -317,6 +362,20 @@ where
|
|
|
317
362
|
})
|
|
318
363
|
}
|
|
319
364
|
|
|
365
|
+
fn check_base_orbit(state: &[f64], t: f64, limit: f64) -> Result<(), LyapunovError> {
|
|
366
|
+
let peak = state
|
|
367
|
+
.iter()
|
|
368
|
+
.fold(0.0_f64, |peak, value| peak.max(value.abs()));
|
|
369
|
+
if peak >= limit {
|
|
370
|
+
return Err(LyapunovError::Diverged(format!(
|
|
371
|
+
"Lyapunov base orbit escaped at t={t:.6}: state magnitude {peak:.6e} \
|
|
372
|
+
reached the scale-and-growth limit {limit:.6e}. Use an initial condition \
|
|
373
|
+
on a bounded orbit before estimating attractor exponents."
|
|
374
|
+
)));
|
|
375
|
+
}
|
|
376
|
+
Ok(())
|
|
377
|
+
}
|
|
378
|
+
|
|
320
379
|
/// Unpack the tangent block of the extended state `z`, QR-reorthonormalise it in
|
|
321
380
|
/// place, and re-embed the orthonormal frame — the per-chunk renormalisation.
|
|
322
381
|
///
|
|
@@ -460,6 +519,62 @@ mod tests {
|
|
|
460
519
|
assert!((out.spectrum[0] - 0.5).abs() < 1e-4, "{}", out.spectrum[0]);
|
|
461
520
|
}
|
|
462
521
|
|
|
522
|
+
#[test]
|
|
523
|
+
fn guarded_spectrum_refuses_base_escape_but_not_growing_tangents() {
|
|
524
|
+
let ev = VmEval::new(linear_extended(200.0, -2.0, 1));
|
|
525
|
+
let bounded = lyapunov_spectrum_ode_guarded(
|
|
526
|
+
&ev,
|
|
527
|
+
rk45_factory,
|
|
528
|
+
&[],
|
|
529
|
+
2,
|
|
530
|
+
1,
|
|
531
|
+
&[0.0, 0.0, 1.0, 0.0],
|
|
532
|
+
0.0,
|
|
533
|
+
0.1,
|
|
534
|
+
0.0,
|
|
535
|
+
0.2,
|
|
536
|
+
10.0,
|
|
537
|
+
)
|
|
538
|
+
.unwrap();
|
|
539
|
+
assert!((bounded.spectrum[0] - 200.0).abs() < 1e-4);
|
|
540
|
+
let err = lyapunov_spectrum_ode_guarded(
|
|
541
|
+
&ev,
|
|
542
|
+
rk45_factory,
|
|
543
|
+
&[],
|
|
544
|
+
2,
|
|
545
|
+
1,
|
|
546
|
+
&[1.0, 0.0, 1.0, 0.0],
|
|
547
|
+
0.0,
|
|
548
|
+
0.1,
|
|
549
|
+
0.0,
|
|
550
|
+
0.2,
|
|
551
|
+
10.0,
|
|
552
|
+
)
|
|
553
|
+
.unwrap_err();
|
|
554
|
+
assert!(matches!(err, LyapunovError::Diverged(_)));
|
|
555
|
+
assert!(err.to_string().contains("base orbit escaped at t="));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
#[test]
|
|
559
|
+
fn guarded_spectrum_accepts_large_units_with_a_relative_limit() {
|
|
560
|
+
let ev = VmEval::new(linear_extended(0.0, 0.0, 1));
|
|
561
|
+
let out = lyapunov_spectrum_ode_guarded(
|
|
562
|
+
&ev,
|
|
563
|
+
rk45_factory,
|
|
564
|
+
&[],
|
|
565
|
+
2,
|
|
566
|
+
1,
|
|
567
|
+
&[1e12, -1e12, 1.0, 0.0],
|
|
568
|
+
0.0,
|
|
569
|
+
0.1,
|
|
570
|
+
0.0,
|
|
571
|
+
1.0,
|
|
572
|
+
1e15,
|
|
573
|
+
)
|
|
574
|
+
.unwrap();
|
|
575
|
+
assert_eq!(out.spectrum, vec![0.0]);
|
|
576
|
+
}
|
|
577
|
+
|
|
463
578
|
#[test]
|
|
464
579
|
fn rejects_bad_k_and_shapes() {
|
|
465
580
|
let ev = VmEval::new(linear_extended(0.5, -2.0, 2));
|
|
@@ -92,7 +92,7 @@ impl std::error::Error for MapLyapunovError {}
|
|
|
92
92
|
/// The accumulated spectrum a [`map_lyapunov`] run returns.
|
|
93
93
|
#[derive(Clone, Debug, PartialEq)]
|
|
94
94
|
pub struct MapLyapunovOutcome {
|
|
95
|
-
/// The `k` Lyapunov
|
|
95
|
+
/// The `k` Lyapunov estimates in QR order — the time-averaged
|
|
96
96
|
/// `log|R_ii|`.
|
|
97
97
|
pub exponents: Vec<f64>,
|
|
98
98
|
/// The number of reorthonormalisation intervals completed (the `_elapsed` the
|
|
@@ -215,12 +215,9 @@ pub fn map_lyapunov(
|
|
|
215
215
|
// Live state and the next-state buffer (a map's RHS writes the next state).
|
|
216
216
|
let mut x = ic[..dim].to_vec();
|
|
217
217
|
let mut x_next = vec![0.0; dim];
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
let mut w =
|
|
221
|
-
for j in 0..k {
|
|
222
|
-
w[j * dim + j] = 1.0;
|
|
223
|
-
}
|
|
218
|
+
// A mixed frame avoids getting stuck in a coordinate-aligned contracting
|
|
219
|
+
// subspace. The Python reference uses the identical fixed stream and QR.
|
|
220
|
+
let mut w = crate::tangent_frame::initial_frame(dim, k);
|
|
224
221
|
// The propagated frame W' = J · W (column-major dim × k), a scratch buffer.
|
|
225
222
|
let mut w_prop = vec![0.0; dim * k];
|
|
226
223
|
let mut jac = vec![0.0; dim * dim];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
//! Deterministic, mixed initial tangent frames (mirrored in Python tangent.py).
|
|
2
|
+
|
|
3
|
+
use crate::rng::SplitMix64;
|
|
4
|
+
|
|
5
|
+
/// Column-major orthonormal frame with no privileged coordinate direction.
|
|
6
|
+
///
|
|
7
|
+
/// Identity columns can remain forever inside a contracting invariant subspace,
|
|
8
|
+
/// missing a maximal exponent in another component. A fixed, local SplitMix64
|
|
9
|
+
/// stream supplies a generic dense frame without changing global RNG state or
|
|
10
|
+
/// introducing run-to-run variation. Two-pass modified Gram--Schmidt controls
|
|
11
|
+
/// orthogonality even when many directions are requested.
|
|
12
|
+
pub(crate) fn initial_frame(dim: usize, k: usize) -> Vec<f64> {
|
|
13
|
+
let mut rng = SplitMix64::new(0x5441_4e47_454e_5453);
|
|
14
|
+
let mut w = vec![0.0; dim * k];
|
|
15
|
+
for j in 0..k {
|
|
16
|
+
for d in 0..dim {
|
|
17
|
+
w[j * dim + d] = 2.0 * rng.next_f64() - 1.0;
|
|
18
|
+
}
|
|
19
|
+
let mut norm = orthogonalise(&mut w, dim, j);
|
|
20
|
+
if norm <= f64::EPSILON {
|
|
21
|
+
// A numerically dependent draw: choose the coordinate least covered
|
|
22
|
+
// by the existing columns, then project it into their complement.
|
|
23
|
+
let mut best = 0;
|
|
24
|
+
let mut least = f64::INFINITY;
|
|
25
|
+
for d in 0..dim {
|
|
26
|
+
let used: f64 = (0..j).map(|i| w[i * dim + d].powi(2)).sum();
|
|
27
|
+
if used < least {
|
|
28
|
+
least = used;
|
|
29
|
+
best = d;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
w[j * dim..(j + 1) * dim].fill(0.0);
|
|
33
|
+
w[j * dim + best] = 1.0;
|
|
34
|
+
norm = orthogonalise(&mut w, dim, j);
|
|
35
|
+
}
|
|
36
|
+
for d in 0..dim {
|
|
37
|
+
w[j * dim + d] /= norm;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
w
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn orthogonalise(w: &mut [f64], dim: usize, j: usize) -> f64 {
|
|
44
|
+
for _ in 0..2 {
|
|
45
|
+
for i in 0..j {
|
|
46
|
+
let mut dot = 0.0;
|
|
47
|
+
for d in 0..dim {
|
|
48
|
+
dot += w[i * dim + d] * w[j * dim + d];
|
|
49
|
+
}
|
|
50
|
+
for d in 0..dim {
|
|
51
|
+
w[j * dim + d] -= dot * w[i * dim + d];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
let mut norm2 = 0.0;
|
|
56
|
+
for d in 0..dim {
|
|
57
|
+
norm2 += w[j * dim + d] * w[j * dim + d];
|
|
58
|
+
}
|
|
59
|
+
norm2.sqrt()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#[cfg(test)]
|
|
63
|
+
mod tests {
|
|
64
|
+
use super::*;
|
|
65
|
+
|
|
66
|
+
#[test]
|
|
67
|
+
fn mixed_frame_is_reproducible_and_orthonormal() {
|
|
68
|
+
for dim in [1, 2, 3, 10, 64] {
|
|
69
|
+
let w = initial_frame(dim, dim);
|
|
70
|
+
assert_eq!(w, initial_frame(dim, dim));
|
|
71
|
+
assert_eq!(&w[..dim], initial_frame(dim, 1).as_slice());
|
|
72
|
+
for i in 0..dim {
|
|
73
|
+
for j in 0..dim {
|
|
74
|
+
let dot: f64 = (0..dim).map(|d| w[i * dim + d] * w[j * dim + d]).sum();
|
|
75
|
+
let expected = if i == j { 1.0 } else { 0.0 };
|
|
76
|
+
assert!((dot - expected).abs() < 1e-13);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
assert!(w[..dim].iter().all(|value| *value != 0.0));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tsdynamics"
|
|
7
|
-
version = "6.0.
|
|
7
|
+
version = "6.0.4"
|
|
8
8
|
description = "Rust-backed 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"
|
|
@@ -190,7 +190,7 @@ from .families import (
|
|
|
190
190
|
)
|
|
191
191
|
|
|
192
192
|
# Single source of truth for the package version; rewritten by python-semantic-release.
|
|
193
|
-
__version__ = "6.0.
|
|
193
|
+
__version__ = "6.0.4"
|
|
194
194
|
|
|
195
195
|
#: The curated top level. **Sorted, one name per line** — the same mechanical
|
|
196
196
|
#: policy every public package in this library follows, because a list grouped by
|
|
@@ -1182,6 +1182,7 @@ def lyapunov_spectrum_ode(
|
|
|
1182
1182
|
rtol: float,
|
|
1183
1183
|
atol: float,
|
|
1184
1184
|
jit: bool,
|
|
1185
|
+
escape_limit: float = float("inf"),
|
|
1185
1186
|
) -> dict[str, Any]:
|
|
1186
1187
|
"""Run the whole ODE Benettin Lyapunov-spectrum loop in the Rust engine.
|
|
1187
1188
|
|
|
@@ -1226,6 +1227,9 @@ def lyapunov_spectrum_ode(
|
|
|
1226
1227
|
Adaptive-kernel tolerances.
|
|
1227
1228
|
jit : bool
|
|
1228
1229
|
Select the Cranelift evaluator.
|
|
1230
|
+
escape_limit : float, optional
|
|
1231
|
+
Upper bound on base-state magnitude, derived by the caller from its
|
|
1232
|
+
scale-and-growth escape policy. Tangent components are not checked.
|
|
1229
1233
|
|
|
1230
1234
|
Returns
|
|
1231
1235
|
-------
|
|
@@ -1256,6 +1260,7 @@ def lyapunov_spectrum_ode(
|
|
|
1256
1260
|
float(burn_in),
|
|
1257
1261
|
float(final_time),
|
|
1258
1262
|
bool(jit),
|
|
1263
|
+
float(escape_limit),
|
|
1259
1264
|
)
|
|
1260
1265
|
return {
|
|
1261
1266
|
"spectrum": np.asarray(spectrum, dtype=np.float64),
|
|
@@ -142,7 +142,9 @@ def _resolve_method_and_prepare(
|
|
|
142
142
|
``rebuild_extra`` carries entry-point-specific build kwargs (``integrate``
|
|
143
143
|
passes ``ic=``; ``ensemble`` seeds each trajectory later, so it passes none).
|
|
144
144
|
|
|
145
|
-
Returns the
|
|
145
|
+
Returns the implementation kernel name and the (possibly rebuilt) problem.
|
|
146
|
+
A plugin's selectable name can differ from its ``SolverSpec.kernel``;
|
|
147
|
+
the compiled engine dispatches the implementation, not that Python name.
|
|
146
148
|
"""
|
|
147
149
|
from .run import _as_problem
|
|
148
150
|
|
|
@@ -157,7 +159,7 @@ def _resolve_method_and_prepare(
|
|
|
157
159
|
problem = _as_problem(
|
|
158
160
|
system_or_problem, with_jacobian=True, **rebuild_extra, **build_kwargs
|
|
159
161
|
)
|
|
160
|
-
return resolution.
|
|
162
|
+
return resolution.kernel, problem
|
|
161
163
|
|
|
162
164
|
|
|
163
165
|
def __dir__() -> list[str]:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Solver metadata, capability checks, and installed solver discovery.
|
|
2
2
|
|
|
3
3
|
.. note::
|
|
4
4
|
**Advanced / internal.** Reachable as :mod:`tsdynamics._solvers` to inspect or
|
|
5
5
|
extend the available integrators, but most users only ever pass a solver by
|
|
6
|
-
name (``
|
|
6
|
+
name (``system.run(10.0, solver="rk45")``) rather than importing this package.
|
|
7
7
|
|
|
8
8
|
This package is the Python-side mirror of the Rust link-time solver registry in
|
|
9
9
|
the ``tsdyn-solvers`` crate. It holds a ``name -> SolverSpec`` table of solver
|
|
@@ -16,12 +16,10 @@ auto-populates it from two sources at import time:
|
|
|
16
16
|
2. **out-of-tree plugins** declared under the ``tsdynamics._solvers`` entry-point
|
|
17
17
|
group (see :mod:`tsdynamics.plugins`).
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
implementation; its :attr:`SolverSpec.kernel` names the Rust kernel to dispatch
|
|
24
|
-
to once the engine bindings (stream E7) land.
|
|
19
|
+
A :class:`SolverSpec` carries metadata rather than an implementation. Its
|
|
20
|
+
:attr:`SolverSpec.kernel` names the executable kernel selected by the engine.
|
|
21
|
+
An installed plugin may expose an existing kernel under a new name; adding an
|
|
22
|
+
algorithm requires implementing and rebuilding the compiled engine as well.
|
|
25
23
|
"""
|
|
26
24
|
|
|
27
25
|
from __future__ import annotations
|
|
@@ -44,7 +42,7 @@ _VALID_KINDS = frozenset({"explicit", "implicit"})
|
|
|
44
42
|
class SolverCaps:
|
|
45
43
|
"""A solver's capabilities — the Python mirror of ``tsdyn_solvers::Caps``.
|
|
46
44
|
|
|
47
|
-
Drives
|
|
45
|
+
Drives solver resolution and the auto-stiffness layer.
|
|
48
46
|
The four flags are how the selection layer reasons about a kernel without
|
|
49
47
|
running it: ``kind`` is the auto-stiffness axis (:func:`~select.select`
|
|
50
48
|
returns an ``"implicit"`` kernel only on a stiff RHS), ``needs_jacobian``
|
|
@@ -187,7 +187,8 @@ class Resolution:
|
|
|
187
187
|
Attributes
|
|
188
188
|
----------
|
|
189
189
|
name : str
|
|
190
|
-
The
|
|
190
|
+
The registered solver name. Plugins can select an implementation with a
|
|
191
|
+
different name through :attr:`kernel`.
|
|
191
192
|
spec : SolverSpec
|
|
192
193
|
The registered spec for :attr:`name`.
|
|
193
194
|
family : str or None
|
|
@@ -111,17 +111,25 @@ def detect_unbounded(y: np.ndarray) -> Unbounded | None:
|
|
|
111
111
|
)
|
|
112
112
|
|
|
113
113
|
|
|
114
|
-
def escaped(peak: float | None) -> bool:
|
|
115
|
-
"""Whether
|
|
114
|
+
def escaped(peak: float | None, *, start: float | None = None) -> bool:
|
|
115
|
+
"""Whether recorded orbit magnitudes satisfy the shared escape policy.
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
With a recorded starting scale, both :data:`ESCAPE_SCALE` and
|
|
118
|
+
:data:`ESCAPE_GROWTH` must be reached. The legacy one-number form retains
|
|
119
|
+
its absolute-scale check when no starting scale was recorded. ``None`` for
|
|
120
|
+
the peak means there is no evidence of escape.
|
|
120
121
|
"""
|
|
121
122
|
if peak is None:
|
|
122
123
|
return False
|
|
123
124
|
value = float(peak)
|
|
124
|
-
|
|
125
|
+
if not np.isfinite(value):
|
|
126
|
+
return True
|
|
127
|
+
if start is None:
|
|
128
|
+
return value >= ESCAPE_SCALE
|
|
129
|
+
initial = float(start)
|
|
130
|
+
return not np.isfinite(initial) or (
|
|
131
|
+
value >= ESCAPE_SCALE and value / max(initial, 1.0) >= ESCAPE_GROWTH
|
|
132
|
+
)
|
|
125
133
|
|
|
126
134
|
|
|
127
135
|
def __dir__() -> list[str]:
|
|
@@ -15,6 +15,7 @@ from __future__ import annotations
|
|
|
15
15
|
from typing import Any as _Any
|
|
16
16
|
|
|
17
17
|
from .. import registry as _registry
|
|
18
|
+
from ..families import ContinuousSystem as _ContinuousSystem
|
|
18
19
|
from ..plugins import ANALYSES_GROUP, register_entry_points
|
|
19
20
|
from . import _discovery
|
|
20
21
|
|
|
@@ -145,7 +146,14 @@ globals()["basins"] = _basins_fn
|
|
|
145
146
|
# picture) had no door at all.
|
|
146
147
|
for _name, _subjects, _area, _keywords in PLANAR_ANALYSES:
|
|
147
148
|
register(
|
|
148
|
-
getattr(planar, _name),
|
|
149
|
+
getattr(planar, _name),
|
|
150
|
+
subjects=_subjects,
|
|
151
|
+
area=_area,
|
|
152
|
+
keywords=_keywords,
|
|
153
|
+
families=("ode",) if "flow" in _subjects else None,
|
|
154
|
+
system_types=(_ContinuousSystem,) if "flow" in _subjects else None,
|
|
155
|
+
min_dim=2 if "flow" in _subjects else None,
|
|
156
|
+
replace=True,
|
|
149
157
|
)
|
|
150
158
|
del _name, _subjects, _area, _keywords
|
|
151
159
|
|
|
@@ -215,6 +223,8 @@ def find(what: _Any = None, /) -> AnalysisList:
|
|
|
215
223
|
``what`` is a **string** (free-text search over the name, area, keywords and
|
|
216
224
|
summary), or a **subject** — a system, a :class:`~tsdynamics.data.Trajectory`,
|
|
217
225
|
an array, a result, or any of their classes — or omitted, for all of them.
|
|
226
|
+
System instances are filtered by the registered family, state dimension and
|
|
227
|
+
concrete autonomy restrictions. A class query cannot check parameter values.
|
|
218
228
|
Returns a plain ``list`` of the analysis *functions*, whose repr is the
|
|
219
229
|
grouped table.
|
|
220
230
|
|
|
@@ -224,7 +234,7 @@ def find(what: _Any = None, /) -> AnalysisList:
|
|
|
224
234
|
>>> sorted(f.__name__ for f in ts.analysis.find("multistability"))[:3]
|
|
225
235
|
['attractors', 'basin_fractions', 'basins']
|
|
226
236
|
>>> len(ts.analysis.find(ts.systems.Henon())) # a map has no vector field
|
|
227
|
-
|
|
237
|
+
11
|
|
228
238
|
>>> len(ts.analysis.find(ts.systems.Lorenz()))
|
|
229
239
|
20
|
|
230
240
|
"""
|
|
@@ -246,7 +256,12 @@ def find(what: _Any = None, /) -> AnalysisList:
|
|
|
246
256
|
f" ts.analysis.find(traj) # a subject\n"
|
|
247
257
|
f" ts.analysis.find() # everything"
|
|
248
258
|
)
|
|
249
|
-
hits = [
|
|
259
|
+
hits = [
|
|
260
|
+
e
|
|
261
|
+
for e in entries
|
|
262
|
+
if set(tokens) & set(e.metadata.get("subjects", ()))
|
|
263
|
+
and _discovery.supports_subject(e, what)
|
|
264
|
+
]
|
|
250
265
|
subject = what.__name__ if isinstance(what, type) else type(what).__name__
|
|
251
266
|
return AnalysisList(hits, f"{len(hits)} analyses take a {subject}")
|
|
252
267
|
|