tsdynamics 3.1.2__tar.gz → 3.1.4__tar.gz

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