tsdynamics 5.2.2__tar.gz → 5.2.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 (268) hide show
  1. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/PKG-INFO +1 -1
  2. tsdynamics-5.2.4/crates/tsdyn-core/src/bridge/basin.rs +224 -0
  3. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-core/src/bridge/mod.rs +2 -0
  4. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-core/src/lib.rs +253 -0
  5. tsdynamics-5.2.4/crates/tsdyn-engine/src/basin.rs +828 -0
  6. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-engine/src/lib.rs +8 -0
  7. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-engine/src/map.rs +19 -0
  8. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/pyproject.toml +1 -1
  9. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/__init__.py +7 -6
  10. tsdynamics-5.2.4/src/tsdynamics/analysis/_plotbuilder.py +434 -0
  11. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/_result_array.py +32 -44
  12. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/_result_base.py +19 -25
  13. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/_result_collection.py +15 -21
  14. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/_result_scalar.py +18 -21
  15. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/_result_scaling.py +12 -35
  16. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/basins/attractors.py +270 -24
  17. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/basins/basins.py +54 -39
  18. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/basins/continuation.py +19 -25
  19. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/basins/metrics.py +10 -17
  20. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/chaos/gali.py +9 -13
  21. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/chaos/zero_one.py +8 -9
  22. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/dimensions/_common.py +11 -24
  23. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/dimensions/generalized.py +10 -12
  24. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/embedding/delay.py +9 -12
  25. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/embedding/dimension.py +16 -36
  26. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/embedding/embed.py +20 -31
  27. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/entropy/core.py +9 -9
  28. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/entropy/multiscale.py +8 -13
  29. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/fixedpoints/_common.py +29 -4
  30. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/fixedpoints/fixed.py +49 -63
  31. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/fixedpoints/periodic.py +55 -58
  32. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/lyapunov/__init__.py +8 -11
  33. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/lyapunov/from_data.py +14 -19
  34. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/orbits/orbit_diagram.py +11 -12
  35. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/orbits/return_map.py +22 -35
  36. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/recurrence/matrix.py +12 -14
  37. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/recurrence/rqa.py +10 -8
  38. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/recurrence/windowed.py +7 -8
  39. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/surrogate/generators.py +11 -20
  40. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/surrogate/hypothesis.py +12 -18
  41. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/derived/tangent.py +136 -26
  42. tsdynamics-5.2.4/src/tsdynamics/engine/_families.py +414 -0
  43. tsdynamics-5.2.4/src/tsdynamics/engine/run.py +807 -0
  44. tsdynamics-5.2.4/src/tsdynamics/engine/run_methods.py +143 -0
  45. tsdynamics-5.2.4/src/tsdynamics/engine/sde_run.py +143 -0
  46. tsdynamics-5.2.4/src/tsdynamics/engine/stepper.py +162 -0
  47. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/errors.py +29 -0
  48. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/registry.py +58 -14
  49. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/systems/continuous/chaotic_attractors.py +655 -1
  50. tsdynamics-5.2.4/src/tsdynamics/systems/continuous/chem_bio_systems.py +837 -0
  51. tsdynamics-5.2.4/src/tsdynamics/systems/continuous/climate_geophysics.py +428 -0
  52. tsdynamics-5.2.4/src/tsdynamics/systems/continuous/coupled_systems.py +667 -0
  53. tsdynamics-5.2.4/src/tsdynamics/systems/continuous/delayed_systems.py +172 -0
  54. tsdynamics-5.2.4/src/tsdynamics/systems/continuous/exotic_systems.py +554 -0
  55. tsdynamics-5.2.4/src/tsdynamics/systems/continuous/oscillatory_systems.py +354 -0
  56. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/systems/continuous/physical_systems.py +137 -1
  57. tsdynamics-5.2.4/src/tsdynamics/systems/continuous/population_dynamics.py +161 -0
  58. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/systems/continuous/spatial_fields.py +4 -0
  59. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/systems/discrete/chaotic_maps.py +113 -0
  60. tsdynamics-5.2.4/src/tsdynamics/systems/discrete/exotic_maps.py +322 -0
  61. tsdynamics-5.2.4/src/tsdynamics/systems/discrete/geometric_maps.py +178 -0
  62. tsdynamics-5.2.4/src/tsdynamics/systems/discrete/polynomial_maps.py +138 -0
  63. tsdynamics-5.2.4/src/tsdynamics/systems/discrete/population_maps.py +117 -0
  64. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/utils/grids.py +17 -0
  65. tsdynamics-5.2.2/src/tsdynamics/engine/run.py +0 -1376
  66. tsdynamics-5.2.2/src/tsdynamics/systems/continuous/chem_bio_systems.py +0 -403
  67. tsdynamics-5.2.2/src/tsdynamics/systems/continuous/climate_geophysics.py +0 -217
  68. tsdynamics-5.2.2/src/tsdynamics/systems/continuous/coupled_systems.py +0 -325
  69. tsdynamics-5.2.2/src/tsdynamics/systems/continuous/delayed_systems.py +0 -83
  70. tsdynamics-5.2.2/src/tsdynamics/systems/continuous/exotic_systems.py +0 -234
  71. tsdynamics-5.2.2/src/tsdynamics/systems/continuous/oscillatory_systems.py +0 -184
  72. tsdynamics-5.2.2/src/tsdynamics/systems/continuous/population_dynamics.py +0 -81
  73. tsdynamics-5.2.2/src/tsdynamics/systems/discrete/exotic_maps.py +0 -180
  74. tsdynamics-5.2.2/src/tsdynamics/systems/discrete/geometric_maps.py +0 -102
  75. tsdynamics-5.2.2/src/tsdynamics/systems/discrete/polynomial_maps.py +0 -62
  76. tsdynamics-5.2.2/src/tsdynamics/systems/discrete/population_maps.py +0 -62
  77. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/LICENSE +0 -0
  78. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/README.md +0 -0
  79. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/Cargo.toml +0 -0
  80. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-core/Cargo.lock +0 -0
  81. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-core/Cargo.toml +0 -0
  82. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-core/src/bridge/dde.rs +0 -0
  83. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-core/src/bridge/events.rs +0 -0
  84. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-core/src/bridge/map.rs +0 -0
  85. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-core/src/bridge/marshal.rs +0 -0
  86. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-core/src/bridge/ode.rs +0 -0
  87. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-core/src/bridge/sde.rs +0 -0
  88. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-core/src/bridge/stepper.rs +0 -0
  89. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-engine/Cargo.toml +0 -0
  90. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-engine/src/dde.rs +0 -0
  91. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-engine/src/ensemble.rs +0 -0
  92. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-engine/src/event.rs +0 -0
  93. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-engine/src/integrate.rs +0 -0
  94. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-engine/src/problem.rs +0 -0
  95. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-engine/src/rng.rs +0 -0
  96. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-engine/src/sde.rs +0 -0
  97. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-engine/src/testkit.rs +0 -0
  98. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/Cargo.toml +0 -0
  99. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/src/builder.rs +0 -0
  100. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/src/eval.rs +0 -0
  101. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/src/lib.rs +0 -0
  102. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/src/op.rs +0 -0
  103. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/src/reference.rs +0 -0
  104. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/src/tape.rs +0 -0
  105. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/tests/coverage_tape.rs +0 -0
  106. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/tests/fixtures/CircadianRhythm.txt +0 -0
  107. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/tests/fixtures/Lorenz.txt +0 -0
  108. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/tests/fixtures/SanUmSrisuchinwong.txt +0 -0
  109. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/tests/fixtures/SprottMore.txt +0 -0
  110. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/tests/fixtures/Torus.txt +0 -0
  111. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/tests/fixtures/YuWang2.txt +0 -0
  112. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/tests/gen_fixtures.py +0 -0
  113. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/tests/golden_fixtures.rs +0 -0
  114. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-ir/tests/roundtrip.rs +0 -0
  115. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-jit/Cargo.toml +0 -0
  116. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-jit/src/codegen.rs +0 -0
  117. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-jit/src/error.rs +0 -0
  118. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-jit/src/evaluator.rs +0 -0
  119. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-jit/src/lib.rs +0 -0
  120. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-jit/src/shims.rs +0 -0
  121. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-jit/tests/compile_latency.rs +0 -0
  122. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-jit/tests/golden_fixtures.rs +0 -0
  123. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-jit/tests/interpreter_equivalence.rs +0 -0
  124. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/Cargo.toml +0 -0
  125. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/caps.rs +0 -0
  126. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/adams.rs +0 -0
  127. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/bs3.rs +0 -0
  128. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/cashkarp.rs +0 -0
  129. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/control.rs +0 -0
  130. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/dop853.rs +0 -0
  131. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/euler.rs +0 -0
  132. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/heun.rs +0 -0
  133. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/heun_euler.rs +0 -0
  134. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/midpoint.rs +0 -0
  135. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/mod.rs +0 -0
  136. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/ralston.rs +0 -0
  137. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/rk4.rs +0 -0
  138. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/rk45.rs +0 -0
  139. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/rk4_38.rs +0 -0
  140. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/rkf45.rs +0 -0
  141. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/ssprk3.rs +0 -0
  142. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/testkit.rs +0 -0
  143. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/explicit/tsit5.rs +0 -0
  144. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/implicit/backward_euler.rs +0 -0
  145. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/implicit/bdf.rs +0 -0
  146. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/implicit/control.rs +0 -0
  147. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/implicit/implicit_midpoint.rs +0 -0
  148. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/implicit/linalg.rs +0 -0
  149. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/implicit/mod.rs +0 -0
  150. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/implicit/newton.rs +0 -0
  151. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/implicit/rosenbrock.rs +0 -0
  152. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/implicit/sdirk2.rs +0 -0
  153. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/implicit/trapezoid.rs +0 -0
  154. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/implicit/trbdf2.rs +0 -0
  155. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/lib.rs +0 -0
  156. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/registry.rs +0 -0
  157. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/sde/euler_maruyama.rs +0 -0
  158. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/sde/milstein.rs +0 -0
  159. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/sde/mod.rs +0 -0
  160. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/sde/testkit.rs +0 -0
  161. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/src/solver.rs +0 -0
  162. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/tests/explicit_accuracy.rs +0 -0
  163. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/tests/implicit_stiff.rs +0 -0
  164. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-solvers/tests/registry.rs +0 -0
  165. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-vm/Cargo.toml +0 -0
  166. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-vm/src/interp.rs +0 -0
  167. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-vm/src/lib.rs +0 -0
  168. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-vm/tests/golden_fixtures.rs +0 -0
  169. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/crates/tsdyn-vm/tests/oracle_equivalence.rs +0 -0
  170. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/_result_common.py +0 -0
  171. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/__init__.py +0 -0
  172. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/_result.py +0 -0
  173. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/_result_json.py +0 -0
  174. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/_result_viz.py +0 -0
  175. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/_tangent.py +0 -0
  176. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/basins/__init__.py +0 -0
  177. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/basins/_common.py +0 -0
  178. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/chaos/__init__.py +0 -0
  179. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/chaos/_common.py +0 -0
  180. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/chaos/expansion.py +0 -0
  181. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/dimensions/__init__.py +0 -0
  182. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/dimensions/_scaling.py +0 -0
  183. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/dimensions/correlation.py +0 -0
  184. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/dimensions/fixedmass.py +0 -0
  185. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/embedding/__init__.py +0 -0
  186. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/embedding/_common.py +0 -0
  187. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/entropy/__init__.py +0 -0
  188. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/entropy/dispersion.py +0 -0
  189. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/entropy/lz.py +0 -0
  190. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/entropy/permutation.py +0 -0
  191. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/entropy/sample.py +0 -0
  192. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/fixedpoints/__init__.py +0 -0
  193. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/orbits/__init__.py +0 -0
  194. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/orbits/poincare.py +0 -0
  195. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/recurrence/__init__.py +0 -0
  196. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/recurrence/_common.py +0 -0
  197. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/sampling/__init__.py +0 -0
  198. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/sampling/sagitta.py +0 -0
  199. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/surrogate/__init__.py +0 -0
  200. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/surrogate/_common.py +0 -0
  201. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/analysis/surrogate/statistics.py +0 -0
  202. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/data/__init__.py +0 -0
  203. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/data/sampling.py +0 -0
  204. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/data/trajectory.py +0 -0
  205. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/derived/__init__.py +0 -0
  206. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/derived/_base.py +0 -0
  207. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/derived/_crossings.py +0 -0
  208. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/derived/_variational.py +0 -0
  209. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/derived/ensemble.py +0 -0
  210. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/derived/poincare.py +0 -0
  211. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/derived/projected.py +0 -0
  212. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/derived/stroboscopic.py +0 -0
  213. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/derived/wrapped.py +0 -0
  214. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/engine/__init__.py +0 -0
  215. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/engine/compile.py +0 -0
  216. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/engine/events.py +0 -0
  217. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/engine/problem.py +0 -0
  218. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/engine/reference.py +0 -0
  219. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/engine/symbols.py +0 -0
  220. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/families/__init__.py +0 -0
  221. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/families/_accessors.py +0 -0
  222. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/families/_dde_lyapunov.py +0 -0
  223. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/families/_plottable.py +0 -0
  224. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/families/base.py +0 -0
  225. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/families/continuous.py +0 -0
  226. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/families/delay.py +0 -0
  227. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/families/discrete.py +0 -0
  228. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/families/protocol.py +0 -0
  229. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/families/stochastic.py +0 -0
  230. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/families/wrapped.py +0 -0
  231. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/plugins.py +0 -0
  232. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/py.typed +0 -0
  233. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/solvers/__init__.py +0 -0
  234. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/solvers/explicit.py +0 -0
  235. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/solvers/implicit.py +0 -0
  236. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/solvers/select.py +0 -0
  237. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/solvers/stochastic.py +0 -0
  238. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/systems/__init__.py +0 -0
  239. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/systems/continuous/__init__.py +0 -0
  240. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/systems/discrete/__init__.py +0 -0
  241. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/transforms/__init__.py +0 -0
  242. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/transforms/_common.py +0 -0
  243. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/transforms/_result.py +0 -0
  244. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/transforms/features.py +0 -0
  245. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/transforms/preprocessing.py +0 -0
  246. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/transforms/spectral.py +0 -0
  247. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/utils/__init__.py +0 -0
  248. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/__init__.py +0 -0
  249. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/compose.py +0 -0
  250. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/export.py +0 -0
  251. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/producers.py +0 -0
  252. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/__init__.py +0 -0
  253. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/caps.py +0 -0
  254. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/json.py +0 -0
  255. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/mpl/__init__.py +0 -0
  256. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/mpl/_anim.py +0 -0
  257. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/mpl/_core.py +0 -0
  258. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/mpl/_threed.py +0 -0
  259. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/plotly/__init__.py +0 -0
  260. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/plotly/_anim.py +0 -0
  261. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/plotly/_composite.py +0 -0
  262. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/plotly/_core.py +0 -0
  263. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/plotly/_html.py +0 -0
  264. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/plotly/_threed.py +0 -0
  265. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/threejs/__init__.py +0 -0
  266. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/render/threejs/_lower.py +0 -0
  267. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/spec.py +0 -0
  268. {tsdynamics-5.2.2 → tsdynamics-5.2.4}/src/tsdynamics/viz/style.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tsdynamics
3
- Version: 5.2.2
3
+ Version: 5.2.4
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Science/Research
6
6
  Classifier: Operating System :: OS Independent
@@ -0,0 +1,224 @@
1
+ //! Basin / attractor recurrence-FSM entry points (stream `perf/basin-march`).
2
+ //!
3
+ //! Thin bridge over the engine's [`tsdyn_engine::basin`] kernel: build the
4
+ //! evaluator (interpreter or JIT) + resolve the solver here (the cranelift edge
5
+ //! lives in this crate, not the engine), then drive the sequential FSM over every
6
+ //! seed in one call. The numerics per cell-check are byte-for-byte the per-`dt`
7
+ //! `ContinuousSystem.step()` (flows) / one map iteration (maps), so the located
8
+ //! labels are bit-identical to the Python `_AttractorMapper`.
9
+ //!
10
+ //! Shared plumbing (evaluator/solver builders, the `EngineError` enum) lives in
11
+ //! [`super::marshal`]; the FSM, the cell grid and the validation live in
12
+ //! [`tsdyn_engine::basin`].
13
+
14
+ use tsdyn_engine::basin::{
15
+ basin_march_flow, basin_march_map, BasinError, BasinMarchOutcome, MarchConfig,
16
+ };
17
+ use tsdyn_ir::Tape;
18
+ use tsdyn_solvers::Solver;
19
+
20
+ use super::marshal::{
21
+ build_evaluator, build_solver, guard_continuous, require_jacobian_if_needed, resolve_solver,
22
+ EngineError,
23
+ };
24
+
25
+ /// Map a [`BasinError`] (a kernel-side shape/grid failure) to the bridge's
26
+ /// [`EngineError`] so the binding layer routes it to the same Python exception the
27
+ /// other entry points use (`ValueError`).
28
+ fn to_engine_err(e: BasinError) -> EngineError {
29
+ match e {
30
+ BasinError::BadShape(m) => EngineError::BadShape(m),
31
+ }
32
+ }
33
+
34
+ /// Run the basin recurrence FSM over a **flow** (ODE) tape.
35
+ ///
36
+ /// Builds the evaluator over `tape`, resolves `method` (an implicit kernel needs a
37
+ /// Jacobian-carrying tape, rejected up front exactly as the integrate path does),
38
+ /// and marches every seed sequentially advancing one `dt` per cell check. `cfg`
39
+ /// carries the six FSM thresholds. Returns the per-seed labels + the accumulated
40
+ /// `att_cells`/`bas_cells`/`att_points` the Python wiring rebuilds the mapper from.
41
+ #[allow(clippy::too_many_arguments)]
42
+ pub fn basin_march_flow_bridge(
43
+ tape: Tape,
44
+ p: &[f64],
45
+ method: &str,
46
+ rtol: f64,
47
+ atol: f64,
48
+ dt: f64,
49
+ grid_lo: &[f64],
50
+ grid_hi: &[f64],
51
+ grid_counts: &[i64],
52
+ seeds: &[f64],
53
+ cfg: MarchConfig,
54
+ jit: bool,
55
+ ) -> Result<BasinMarchOutcome, EngineError> {
56
+ // An ODE-shaped tape only (a DDE/SDE tape has its own family path; the Python
57
+ // wiring already excludes DDE/SDE, but guard at the boundary too).
58
+ guard_continuous(&tape)?;
59
+ let name = resolve_solver(method)?;
60
+ require_jacobian_if_needed(&tape, name)?;
61
+ let ev = build_evaluator(tape, jit)?;
62
+ // A fresh kernel per `dt` segment (mirrors `OdeStepper::advance`); the name is
63
+ // a registry name, so `build_solver` always succeeds.
64
+ let factory = move || -> Box<dyn Solver> { build_solver(name, rtol, atol) };
65
+ basin_march_flow(
66
+ &*ev,
67
+ factory,
68
+ p,
69
+ dt,
70
+ grid_lo,
71
+ grid_hi,
72
+ grid_counts,
73
+ seeds,
74
+ cfg,
75
+ )
76
+ .map_err(to_engine_err)
77
+ }
78
+
79
+ /// Run the basin recurrence FSM over a **map** tape.
80
+ ///
81
+ /// As [`basin_march_flow_bridge`], but each cell check applies the lowered `_step`
82
+ /// map once (no solver, no `dt`). The map tape folds its parameters in, so `p` is
83
+ /// empty.
84
+ #[allow(clippy::too_many_arguments)]
85
+ pub fn basin_march_map_bridge(
86
+ tape: Tape,
87
+ p: &[f64],
88
+ grid_lo: &[f64],
89
+ grid_hi: &[f64],
90
+ grid_counts: &[i64],
91
+ seeds: &[f64],
92
+ cfg: MarchConfig,
93
+ jit: bool,
94
+ ) -> Result<BasinMarchOutcome, EngineError> {
95
+ let ev = build_evaluator(tape, jit)?;
96
+ basin_march_map(&*ev, p, grid_lo, grid_hi, grid_counts, seeds, cfg).map_err(to_engine_err)
97
+ }
98
+
99
+ #[cfg(test)]
100
+ mod tests {
101
+ use super::*;
102
+ use tsdyn_ir::TapeBuilder;
103
+
104
+ /// `x ← a x − x³`, `a` folded in (two fixed points at `±√(a−1)` for `1<a<2`).
105
+ fn cubic_map(a: f64) -> Tape {
106
+ let mut b = TapeBuilder::new();
107
+ let x = b.state(0);
108
+ let ac = b.constant(a);
109
+ let ax = b.mul(ac, x);
110
+ let x2 = b.mul(x, x);
111
+ let x3 = b.mul(x2, x);
112
+ let nx = b.sub(ax, x3);
113
+ b.finish(&[nx], &[], 1, 0).unwrap()
114
+ }
115
+
116
+ fn cfg() -> MarchConfig {
117
+ MarchConfig {
118
+ max_steps: 500,
119
+ mx_fnd: 8,
120
+ mx_loc: 5,
121
+ mx_att: 2,
122
+ mx_bas: 10,
123
+ mx_lost: 20,
124
+ }
125
+ }
126
+
127
+ #[test]
128
+ fn map_bridge_interp_equals_jit_bit_for_bit() {
129
+ // The bridge drives both evaluators; the FSM is deterministic and the
130
+ // per-step numerics are bit-identical, so interp == jit to the bit.
131
+ let seeds: Vec<f64> = (0..30).map(|i| -1.0 + 0.06 * i as f64).collect();
132
+ let run = |jit| {
133
+ basin_march_map_bridge(
134
+ cubic_map(1.5),
135
+ &[],
136
+ &[-2.0],
137
+ &[2.0],
138
+ &[300],
139
+ &seeds,
140
+ cfg(),
141
+ jit,
142
+ )
143
+ .unwrap()
144
+ .labels
145
+ };
146
+ assert_eq!(run(false), run(true));
147
+ }
148
+
149
+ #[test]
150
+ fn flow_bridge_decays_to_origin_and_interp_equals_jit() {
151
+ // dx=-x, dv=-v spirals to the origin; one attractor region. interp == jit.
152
+ let build = || {
153
+ let mut b = TapeBuilder::new();
154
+ let x = b.state(0);
155
+ let v = b.state(1);
156
+ let dx = b.neg(x);
157
+ let dv = b.neg(v);
158
+ b.finish(&[dx, dv], &[], 2, 0).unwrap()
159
+ };
160
+ let seeds = vec![0.5, 0.5, -0.5, -0.5, 0.3, -0.4];
161
+ let mcfg = MarchConfig {
162
+ max_steps: 2000,
163
+ mx_fnd: 20,
164
+ mx_loc: 10,
165
+ mx_att: 2,
166
+ mx_bas: 10,
167
+ mx_lost: 20,
168
+ };
169
+ let run = |jit| {
170
+ basin_march_flow_bridge(
171
+ build(),
172
+ &[],
173
+ "rk45",
174
+ 1e-6,
175
+ 1e-9,
176
+ 0.2,
177
+ &[-1.0, -1.0],
178
+ &[1.0, 1.0],
179
+ &[50, 50],
180
+ &seeds,
181
+ mcfg,
182
+ jit,
183
+ )
184
+ .unwrap()
185
+ .labels
186
+ };
187
+ let interp = run(false);
188
+ let jit = run(true);
189
+ assert_eq!(interp, jit, "interp vs jit basin labels differ");
190
+ // No seed diverged.
191
+ assert!(
192
+ interp.iter().all(|&l| l >= 1),
193
+ "a seed diverged: {interp:?}"
194
+ );
195
+ }
196
+
197
+ #[test]
198
+ fn flow_bridge_rejects_implicit_without_jacobian() {
199
+ // An implicit kernel over a tape with no Jacobian is rejected at the
200
+ // boundary (BadShape → ValueError), exactly as the integrate path does.
201
+ let build = || {
202
+ let mut b = TapeBuilder::new();
203
+ let x = b.state(0);
204
+ let dx = b.neg(x);
205
+ b.finish(&[dx], &[], 1, 0).unwrap()
206
+ };
207
+ let err = basin_march_flow_bridge(
208
+ build(),
209
+ &[],
210
+ "bdf",
211
+ 1e-6,
212
+ 1e-9,
213
+ 0.1,
214
+ &[-1.0],
215
+ &[1.0],
216
+ &[10],
217
+ &[0.5],
218
+ cfg(),
219
+ false,
220
+ )
221
+ .unwrap_err();
222
+ assert!(matches!(err, EngineError::BadShape(_)), "got {err:?}");
223
+ }
224
+ }
@@ -30,6 +30,7 @@
30
30
  //! Errors surface as [`EngineError`], a Python-free enum the binding layer maps
31
31
  //! onto the right Python exception type.
32
32
 
33
+ pub mod basin;
33
34
  pub mod dde;
34
35
  pub mod events;
35
36
  pub mod map;
@@ -42,6 +43,7 @@ pub mod stepper;
42
43
  // pre-split single-file module exposed it. `lib.rs` and the unit tests below
43
44
  // depend on this flat surface; keeping it here means the file split is invisible
44
45
  // to every caller.
46
+ pub use basin::{basin_march_flow_bridge, basin_march_map_bridge};
45
47
  pub use dde::integrate_dde_dense;
46
48
  pub use events::integrate_events_dense;
47
49
  pub use map::{iterate_map, map_ensemble_final};
@@ -710,6 +710,257 @@ fn integrate_events_dense<'py>(
710
710
  Ok((times, states, t_final, u_final, terminated))
711
711
  }
712
712
 
713
+ // ---------------------------------------------------------------------------
714
+ // Basin / attractor recurrence FSM (stream perf/basin-march)
715
+ // ---------------------------------------------------------------------------
716
+
717
+ use bridge::{basin_march_flow_bridge, basin_march_map_bridge};
718
+ use tsdyn_engine::basin::{BasinMarchOutcome, MarchConfig};
719
+
720
+ /// Marshal a [`BasinMarchOutcome`] into the flat NumPy tuple the Python wiring
721
+ /// rebuilds the mapper from.
722
+ ///
723
+ /// Returns `(labels, att_keys, att_ids, bas_keys, bas_ids, point_ids,
724
+ /// point_counts, points_flat, dim)`:
725
+ ///
726
+ /// - `labels` `(n_seeds,)` i64 — per-seed attractor id (or `-1` diverged);
727
+ /// - `att_keys`/`att_ids` `(A,)` i64 — the `att_cells` map (flat cell → id);
728
+ /// - `bas_keys`/`bas_ids` `(B,)` i64 — the `bas_cells` map;
729
+ /// - `point_ids`/`point_counts` `(P,)` i64 — one row per attractor id with its
730
+ /// point-cloud row count; `points_flat` `(sum(counts)*dim,)` f64 the row-major
731
+ /// clouds, sliced back per id by the cumulative counts; `dim` the row width.
732
+ #[allow(clippy::type_complexity)]
733
+ fn basin_outcome_to_py(
734
+ py: Python<'_>,
735
+ out: BasinMarchOutcome,
736
+ ) -> (
737
+ Bound<'_, PyArray1<i64>>,
738
+ Bound<'_, PyArray1<i64>>,
739
+ Bound<'_, PyArray1<i64>>,
740
+ Bound<'_, PyArray1<i64>>,
741
+ Bound<'_, PyArray1<i64>>,
742
+ Bound<'_, PyArray1<i64>>,
743
+ Bound<'_, PyArray1<i64>>,
744
+ Bound<'_, PyArray1<f64>>,
745
+ usize,
746
+ ) {
747
+ let labels = PyArray1::from_vec(py, out.labels);
748
+
749
+ let (att_keys, att_ids): (Vec<i64>, Vec<i64>) = out
750
+ .att_cells
751
+ .into_iter()
752
+ .map(|(k, v)| (k as i64, v))
753
+ .unzip();
754
+ let (bas_keys, bas_ids): (Vec<i64>, Vec<i64>) = out
755
+ .bas_cells
756
+ .into_iter()
757
+ .map(|(k, v)| (k as i64, v))
758
+ .unzip();
759
+
760
+ let mut point_ids: Vec<i64> = Vec::with_capacity(out.att_points.len());
761
+ let mut point_counts: Vec<i64> = Vec::with_capacity(out.att_points.len());
762
+ let mut points_flat: Vec<f64> = Vec::new();
763
+ for (id, m, flat) in out.att_points {
764
+ point_ids.push(id);
765
+ point_counts.push(m as i64);
766
+ points_flat.extend_from_slice(&flat);
767
+ }
768
+
769
+ (
770
+ labels,
771
+ PyArray1::from_vec(py, att_keys),
772
+ PyArray1::from_vec(py, att_ids),
773
+ PyArray1::from_vec(py, bas_keys),
774
+ PyArray1::from_vec(py, bas_ids),
775
+ PyArray1::from_vec(py, point_ids),
776
+ PyArray1::from_vec(py, point_counts),
777
+ PyArray1::from_vec(py, points_flat),
778
+ out.dim,
779
+ )
780
+ }
781
+
782
+ /// Build the six FSM thresholds from the positional ints the Python wiring passes.
783
+ fn march_config(
784
+ max_steps: usize,
785
+ mx_fnd: usize,
786
+ mx_loc: usize,
787
+ mx_att: usize,
788
+ mx_bas: usize,
789
+ mx_lost: usize,
790
+ ) -> MarchConfig {
791
+ MarchConfig {
792
+ max_steps,
793
+ mx_fnd,
794
+ mx_loc,
795
+ mx_att,
796
+ mx_bas,
797
+ mx_lost,
798
+ }
799
+ }
800
+
801
+ /// Run the basin recurrence FSM over a **flow** (ODE) — the whole per-IC march
802
+ /// (stepping + cell-binning + the shared-label early-out) for every seed in one
803
+ /// sequential engine call.
804
+ ///
805
+ /// The leading tape arrays are the usual `Tape.to_arrays()` tuple; `p` the live
806
+ /// control parameters; `method` resolves through the solver registry (an implicit
807
+ /// kernel needs `with_jacobian=True`, rejected up front); `dt` the per-cell-check
808
+ /// step (byte-for-byte the `ContinuousSystem.step(dt)` numerics); `grid_lo` /
809
+ /// `grid_hi` / `grid_counts` the recurrence cell tessellation; `seeds` an
810
+ /// `(n_seeds, dim)` array of initial conditions; the six `mx_*` ints the FSM
811
+ /// thresholds; `jit` selects the Cranelift evaluator (numerically identical).
812
+ ///
813
+ /// Returns the flat tuple [`basin_outcome_to_py`] documents — per-seed labels plus
814
+ /// the accumulated `att_cells`/`bas_cells`/`att_points` the Python wiring rebuilds
815
+ /// the mapper from. Divergence is a `-1` label, never an error; an engine
816
+ /// unavailability or a shape error raises (it must not become divergence).
817
+ #[pyfunction]
818
+ #[allow(clippy::too_many_arguments, clippy::type_complexity)]
819
+ fn basin_march_flow<'py>(
820
+ py: Python<'py>,
821
+ ops: PyReadonlyArray1<i32>,
822
+ a: PyReadonlyArray1<i32>,
823
+ b: PyReadonlyArray1<i32>,
824
+ imm: PyReadonlyArray1<f64>,
825
+ outputs: PyReadonlyArray1<i32>,
826
+ jac_outputs: PyReadonlyArray1<i32>,
827
+ n_state: usize,
828
+ n_param: usize,
829
+ p: PyReadonlyArray1<f64>,
830
+ method: String,
831
+ rtol: f64,
832
+ atol: f64,
833
+ dt: f64,
834
+ grid_lo: PyReadonlyArray1<f64>,
835
+ grid_hi: PyReadonlyArray1<f64>,
836
+ grid_counts: PyReadonlyArray1<i64>,
837
+ seeds: PyReadonlyArray2<f64>,
838
+ max_steps: usize,
839
+ mx_fnd: usize,
840
+ mx_loc: usize,
841
+ mx_att: usize,
842
+ mx_bas: usize,
843
+ mx_lost: usize,
844
+ jit: bool,
845
+ ) -> PyResult<(
846
+ Bound<'py, PyArray1<i64>>,
847
+ Bound<'py, PyArray1<i64>>,
848
+ Bound<'py, PyArray1<i64>>,
849
+ Bound<'py, PyArray1<i64>>,
850
+ Bound<'py, PyArray1<i64>>,
851
+ Bound<'py, PyArray1<i64>>,
852
+ Bound<'py, PyArray1<i64>>,
853
+ Bound<'py, PyArray1<f64>>,
854
+ usize,
855
+ )> {
856
+ let tape = OwnedTape::copy_in(&ops, &a, &b, &imm, &outputs, &jac_outputs, n_state, n_param)?;
857
+ let p = vec_f64("p", &p)?;
858
+ let grid_lo = vec_f64("grid_lo", &grid_lo)?;
859
+ let grid_hi = vec_f64("grid_hi", &grid_hi)?;
860
+ let grid_counts = grid_counts
861
+ .as_slice()
862
+ .map_err(|_| PyValueError::new_err("grid_counts must be a contiguous int64 array"))?
863
+ .to_vec();
864
+ let seeds_vec = seeds
865
+ .as_slice()
866
+ .map_err(|_| {
867
+ PyValueError::new_err("seeds must be a C-contiguous (n_seeds, dim) float64 array")
868
+ })?
869
+ .to_vec();
870
+ let cfg = march_config(max_steps, mx_fnd, mx_loc, mx_att, mx_bas, mx_lost);
871
+ let out = py
872
+ .detach(|| {
873
+ basin_march_flow_bridge(
874
+ tape.build()?,
875
+ &p,
876
+ &method,
877
+ rtol,
878
+ atol,
879
+ dt,
880
+ &grid_lo,
881
+ &grid_hi,
882
+ &grid_counts,
883
+ &seeds_vec,
884
+ cfg,
885
+ jit,
886
+ )
887
+ })
888
+ .map_err(to_py_err)?;
889
+ Ok(basin_outcome_to_py(py, out))
890
+ }
891
+
892
+ /// Run the basin recurrence FSM over a **map** (discrete system).
893
+ ///
894
+ /// As [`basin_march_flow`], but each cell check applies the lowered `_step` map
895
+ /// once (no `method`/`rtol`/`atol`/`dt`). The map tape folds its parameters in, so
896
+ /// `p` is empty.
897
+ #[pyfunction]
898
+ #[allow(clippy::too_many_arguments, clippy::type_complexity)]
899
+ fn basin_march_map<'py>(
900
+ py: Python<'py>,
901
+ ops: PyReadonlyArray1<i32>,
902
+ a: PyReadonlyArray1<i32>,
903
+ b: PyReadonlyArray1<i32>,
904
+ imm: PyReadonlyArray1<f64>,
905
+ outputs: PyReadonlyArray1<i32>,
906
+ jac_outputs: PyReadonlyArray1<i32>,
907
+ n_state: usize,
908
+ n_param: usize,
909
+ p: PyReadonlyArray1<f64>,
910
+ grid_lo: PyReadonlyArray1<f64>,
911
+ grid_hi: PyReadonlyArray1<f64>,
912
+ grid_counts: PyReadonlyArray1<i64>,
913
+ seeds: PyReadonlyArray2<f64>,
914
+ max_steps: usize,
915
+ mx_fnd: usize,
916
+ mx_loc: usize,
917
+ mx_att: usize,
918
+ mx_bas: usize,
919
+ mx_lost: usize,
920
+ jit: bool,
921
+ ) -> PyResult<(
922
+ Bound<'py, PyArray1<i64>>,
923
+ Bound<'py, PyArray1<i64>>,
924
+ Bound<'py, PyArray1<i64>>,
925
+ Bound<'py, PyArray1<i64>>,
926
+ Bound<'py, PyArray1<i64>>,
927
+ Bound<'py, PyArray1<i64>>,
928
+ Bound<'py, PyArray1<i64>>,
929
+ Bound<'py, PyArray1<f64>>,
930
+ usize,
931
+ )> {
932
+ let tape = OwnedTape::copy_in(&ops, &a, &b, &imm, &outputs, &jac_outputs, n_state, n_param)?;
933
+ let p = vec_f64("p", &p)?;
934
+ let grid_lo = vec_f64("grid_lo", &grid_lo)?;
935
+ let grid_hi = vec_f64("grid_hi", &grid_hi)?;
936
+ let grid_counts = grid_counts
937
+ .as_slice()
938
+ .map_err(|_| PyValueError::new_err("grid_counts must be a contiguous int64 array"))?
939
+ .to_vec();
940
+ let seeds_vec = seeds
941
+ .as_slice()
942
+ .map_err(|_| {
943
+ PyValueError::new_err("seeds must be a C-contiguous (n_seeds, dim) float64 array")
944
+ })?
945
+ .to_vec();
946
+ let cfg = march_config(max_steps, mx_fnd, mx_loc, mx_att, mx_bas, mx_lost);
947
+ let out = py
948
+ .detach(|| {
949
+ basin_march_map_bridge(
950
+ tape.build()?,
951
+ &p,
952
+ &grid_lo,
953
+ &grid_hi,
954
+ &grid_counts,
955
+ &seeds_vec,
956
+ cfg,
957
+ jit,
958
+ )
959
+ })
960
+ .map_err(to_py_err)?;
961
+ Ok(basin_outcome_to_py(py, out))
962
+ }
963
+
713
964
  // ---------------------------------------------------------------------------
714
965
  // Resumable ODE stepper handle (stream WS-STEPPER)
715
966
  // ---------------------------------------------------------------------------
@@ -926,6 +1177,8 @@ fn _rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
926
1177
  m.add_function(wrap_pyfunction!(integrate_sde_dense, m)?)?;
927
1178
  m.add_function(wrap_pyfunction!(integrate_sde_ensemble_final, m)?)?;
928
1179
  m.add_function(wrap_pyfunction!(integrate_events_dense, m)?)?;
1180
+ m.add_function(wrap_pyfunction!(basin_march_flow, m)?)?;
1181
+ m.add_function(wrap_pyfunction!(basin_march_map, m)?)?;
929
1182
  m.add_class::<PyOdeStepper>()?;
930
1183
  m.add_function(wrap_pyfunction!(solvers, m)?)?;
931
1184
  m.add_function(wrap_pyfunction!(_version, m)?)?;