tsdynamics 5.2.4__tar.gz → 5.2.6__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 (264) hide show
  1. tsdynamics-5.2.6/PKG-INFO +221 -0
  2. tsdynamics-5.2.6/README.md +185 -0
  3. tsdynamics-5.2.6/crates/tsdyn-core/src/bridge/lyapunov.rs +162 -0
  4. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/map.rs +89 -1
  5. tsdynamics-5.2.6/crates/tsdyn-core/src/bridge/map_lyapunov.rs +142 -0
  6. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/marshal.rs +5 -2
  7. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/mod.rs +5 -1
  8. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/lib.rs +208 -0
  9. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/basin.rs +13 -5
  10. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/ensemble.rs +8 -4
  11. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/lib.rs +15 -0
  12. tsdynamics-5.2.6/crates/tsdyn-engine/src/lyapunov.rs +476 -0
  13. tsdynamics-5.2.6/crates/tsdyn-engine/src/map_lyapunov.rs +436 -0
  14. tsdynamics-5.2.6/crates/tsdyn-engine/src/param_sweep.rs +381 -0
  15. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/src/op.rs +6 -2
  16. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/dop853.rs +8 -3
  17. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/registry.rs +58 -0
  18. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/solver.rs +9 -0
  19. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/pyproject.toml +4 -1
  20. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/__init__.py +1 -1
  21. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_base.py +1 -0
  22. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_scaling.py +30 -9
  23. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/basins/metrics.py +17 -3
  24. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/chaos/expansion.py +1 -1
  25. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/chaos/zero_one.py +7 -1
  26. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/dimensions/_common.py +1 -1
  27. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/dimensions/correlation.py +12 -1
  28. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/embedding/delay.py +23 -8
  29. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/embedding/dimension.py +13 -0
  30. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/sample.py +11 -0
  31. tsdynamics-5.2.6/src/tsdynamics/analysis/fixedpoints/_interval.py +763 -0
  32. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/fixedpoints/fixed.py +107 -6
  33. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/fixedpoints/periodic.py +9 -1
  34. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/lyapunov/__init__.py +121 -3
  35. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/lyapunov/from_data.py +10 -2
  36. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/orbits/orbit_diagram.py +172 -54
  37. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/sampling/sagitta.py +24 -6
  38. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/surrogate/_common.py +15 -7
  39. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/projected.py +34 -16
  40. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/tangent.py +246 -25
  41. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/compile.py +168 -0
  42. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/events.py +10 -3
  43. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/run.py +278 -1
  44. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/continuous.py +80 -1
  45. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/discrete.py +50 -3
  46. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/wrapped.py +4 -3
  47. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/plugins.py +4 -3
  48. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/chaotic_attractors.py +6 -4
  49. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/climate_geophysics.py +11 -2
  50. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/exotic_systems.py +6 -0
  51. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/oscillatory_systems.py +18 -4
  52. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/discrete/chaotic_maps.py +4 -6
  53. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/discrete/exotic_maps.py +14 -8
  54. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/discrete/geometric_maps.py +10 -1
  55. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/transforms/spectral.py +3 -1
  56. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/compose.py +23 -6
  57. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/mpl/_core.py +78 -8
  58. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/plotly/_anim.py +80 -9
  59. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/threejs/_lower.py +7 -5
  60. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/spec.py +8 -2
  61. tsdynamics-5.2.4/PKG-INFO +0 -160
  62. tsdynamics-5.2.4/README.md +0 -124
  63. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/LICENSE +0 -0
  64. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/Cargo.toml +0 -0
  65. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/Cargo.lock +0 -0
  66. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/Cargo.toml +0 -0
  67. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/basin.rs +0 -0
  68. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/dde.rs +0 -0
  69. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/events.rs +0 -0
  70. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/ode.rs +0 -0
  71. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/sde.rs +0 -0
  72. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-core/src/bridge/stepper.rs +0 -0
  73. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/Cargo.toml +0 -0
  74. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/dde.rs +0 -0
  75. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/event.rs +0 -0
  76. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/integrate.rs +0 -0
  77. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/map.rs +0 -0
  78. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/problem.rs +0 -0
  79. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/rng.rs +0 -0
  80. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/sde.rs +0 -0
  81. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-engine/src/testkit.rs +0 -0
  82. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/Cargo.toml +0 -0
  83. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/src/builder.rs +0 -0
  84. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/src/eval.rs +0 -0
  85. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/src/lib.rs +0 -0
  86. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/src/reference.rs +0 -0
  87. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/src/tape.rs +0 -0
  88. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/coverage_tape.rs +0 -0
  89. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/fixtures/CircadianRhythm.txt +0 -0
  90. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/fixtures/Lorenz.txt +0 -0
  91. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/fixtures/SanUmSrisuchinwong.txt +0 -0
  92. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/fixtures/SprottMore.txt +0 -0
  93. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/fixtures/Torus.txt +0 -0
  94. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/fixtures/YuWang2.txt +0 -0
  95. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/gen_fixtures.py +0 -0
  96. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/golden_fixtures.rs +0 -0
  97. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-ir/tests/roundtrip.rs +0 -0
  98. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/Cargo.toml +0 -0
  99. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/src/codegen.rs +0 -0
  100. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/src/error.rs +0 -0
  101. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/src/evaluator.rs +0 -0
  102. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/src/lib.rs +0 -0
  103. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/src/shims.rs +0 -0
  104. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/tests/compile_latency.rs +0 -0
  105. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/tests/golden_fixtures.rs +0 -0
  106. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-jit/tests/interpreter_equivalence.rs +0 -0
  107. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/Cargo.toml +0 -0
  108. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/caps.rs +0 -0
  109. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/adams.rs +0 -0
  110. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/bs3.rs +0 -0
  111. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/cashkarp.rs +0 -0
  112. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/control.rs +0 -0
  113. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/euler.rs +0 -0
  114. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/heun.rs +0 -0
  115. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/heun_euler.rs +0 -0
  116. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/midpoint.rs +0 -0
  117. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/mod.rs +0 -0
  118. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/ralston.rs +0 -0
  119. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/rk4.rs +0 -0
  120. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/rk45.rs +0 -0
  121. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/rk4_38.rs +0 -0
  122. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/rkf45.rs +0 -0
  123. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/ssprk3.rs +0 -0
  124. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/testkit.rs +0 -0
  125. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/explicit/tsit5.rs +0 -0
  126. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/backward_euler.rs +0 -0
  127. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/bdf.rs +0 -0
  128. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/control.rs +0 -0
  129. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/implicit_midpoint.rs +0 -0
  130. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/linalg.rs +0 -0
  131. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/mod.rs +0 -0
  132. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/newton.rs +0 -0
  133. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/rosenbrock.rs +0 -0
  134. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/sdirk2.rs +0 -0
  135. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/trapezoid.rs +0 -0
  136. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/implicit/trbdf2.rs +0 -0
  137. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/lib.rs +0 -0
  138. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/sde/euler_maruyama.rs +0 -0
  139. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/sde/milstein.rs +0 -0
  140. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/sde/mod.rs +0 -0
  141. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/src/sde/testkit.rs +0 -0
  142. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/tests/explicit_accuracy.rs +0 -0
  143. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/tests/implicit_stiff.rs +0 -0
  144. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-solvers/tests/registry.rs +0 -0
  145. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-vm/Cargo.toml +0 -0
  146. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-vm/src/interp.rs +0 -0
  147. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-vm/src/lib.rs +0 -0
  148. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-vm/tests/golden_fixtures.rs +0 -0
  149. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/crates/tsdyn-vm/tests/oracle_equivalence.rs +0 -0
  150. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/_result_common.py +0 -0
  151. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/__init__.py +0 -0
  152. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_plotbuilder.py +0 -0
  153. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result.py +0 -0
  154. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_array.py +0 -0
  155. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_collection.py +0 -0
  156. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_json.py +0 -0
  157. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_scalar.py +0 -0
  158. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_result_viz.py +0 -0
  159. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/_tangent.py +0 -0
  160. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/basins/__init__.py +0 -0
  161. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/basins/_common.py +0 -0
  162. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/basins/attractors.py +0 -0
  163. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/basins/basins.py +0 -0
  164. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/basins/continuation.py +0 -0
  165. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/chaos/__init__.py +0 -0
  166. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/chaos/_common.py +0 -0
  167. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/chaos/gali.py +0 -0
  168. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/dimensions/__init__.py +0 -0
  169. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/dimensions/_scaling.py +0 -0
  170. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/dimensions/fixedmass.py +0 -0
  171. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/dimensions/generalized.py +0 -0
  172. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/embedding/__init__.py +0 -0
  173. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/embedding/_common.py +0 -0
  174. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/embedding/embed.py +0 -0
  175. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/__init__.py +0 -0
  176. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/core.py +0 -0
  177. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/dispersion.py +0 -0
  178. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/lz.py +0 -0
  179. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/multiscale.py +0 -0
  180. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/entropy/permutation.py +0 -0
  181. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/fixedpoints/__init__.py +0 -0
  182. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/fixedpoints/_common.py +0 -0
  183. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/orbits/__init__.py +0 -0
  184. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/orbits/poincare.py +0 -0
  185. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/orbits/return_map.py +0 -0
  186. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/recurrence/__init__.py +0 -0
  187. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/recurrence/_common.py +0 -0
  188. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/recurrence/matrix.py +0 -0
  189. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/recurrence/rqa.py +0 -0
  190. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/recurrence/windowed.py +0 -0
  191. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/sampling/__init__.py +0 -0
  192. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/surrogate/__init__.py +0 -0
  193. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/surrogate/generators.py +0 -0
  194. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/surrogate/hypothesis.py +0 -0
  195. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/analysis/surrogate/statistics.py +0 -0
  196. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/data/__init__.py +0 -0
  197. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/data/sampling.py +0 -0
  198. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/data/trajectory.py +0 -0
  199. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/__init__.py +0 -0
  200. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/_base.py +0 -0
  201. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/_crossings.py +0 -0
  202. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/_variational.py +0 -0
  203. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/ensemble.py +0 -0
  204. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/poincare.py +0 -0
  205. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/stroboscopic.py +0 -0
  206. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/derived/wrapped.py +0 -0
  207. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/__init__.py +0 -0
  208. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/_families.py +0 -0
  209. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/problem.py +0 -0
  210. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/reference.py +0 -0
  211. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/run_methods.py +0 -0
  212. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/sde_run.py +0 -0
  213. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/stepper.py +0 -0
  214. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/engine/symbols.py +0 -0
  215. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/errors.py +0 -0
  216. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/__init__.py +0 -0
  217. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/_accessors.py +0 -0
  218. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/_dde_lyapunov.py +0 -0
  219. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/_plottable.py +0 -0
  220. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/base.py +0 -0
  221. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/delay.py +0 -0
  222. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/protocol.py +0 -0
  223. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/families/stochastic.py +0 -0
  224. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/py.typed +0 -0
  225. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/registry.py +0 -0
  226. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/solvers/__init__.py +0 -0
  227. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/solvers/explicit.py +0 -0
  228. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/solvers/implicit.py +0 -0
  229. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/solvers/select.py +0 -0
  230. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/solvers/stochastic.py +0 -0
  231. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/__init__.py +0 -0
  232. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/__init__.py +0 -0
  233. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/chem_bio_systems.py +0 -0
  234. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/coupled_systems.py +0 -0
  235. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/delayed_systems.py +0 -0
  236. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/physical_systems.py +0 -0
  237. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/population_dynamics.py +0 -0
  238. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/continuous/spatial_fields.py +0 -0
  239. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/discrete/__init__.py +0 -0
  240. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/discrete/polynomial_maps.py +0 -0
  241. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/systems/discrete/population_maps.py +0 -0
  242. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/transforms/__init__.py +0 -0
  243. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/transforms/_common.py +0 -0
  244. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/transforms/_result.py +0 -0
  245. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/transforms/features.py +0 -0
  246. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/transforms/preprocessing.py +0 -0
  247. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/utils/__init__.py +0 -0
  248. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/utils/grids.py +0 -0
  249. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/__init__.py +0 -0
  250. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/export.py +0 -0
  251. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/producers.py +0 -0
  252. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/__init__.py +0 -0
  253. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/caps.py +0 -0
  254. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/json.py +0 -0
  255. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/mpl/__init__.py +0 -0
  256. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/mpl/_anim.py +0 -0
  257. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/mpl/_threed.py +0 -0
  258. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/plotly/__init__.py +0 -0
  259. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/plotly/_composite.py +0 -0
  260. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/plotly/_core.py +0 -0
  261. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/plotly/_html.py +0 -0
  262. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/plotly/_threed.py +0 -0
  263. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/render/threejs/__init__.py +0 -0
  264. {tsdynamics-5.2.4 → tsdynamics-5.2.6}/src/tsdynamics/viz/style.py +0 -0
@@ -0,0 +1,221 @@
1
+ Metadata-Version: 2.4
2
+ Name: tsdynamics
3
+ Version: 5.2.6
4
+ Classifier: Development Status :: 4 - Beta
5
+ Classifier: Intended Audience :: Science/Research
6
+ Classifier: Operating System :: OS Independent
7
+ Classifier: Programming Language :: Python :: 3.12
8
+ Classifier: Programming Language :: Python :: 3.13
9
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
10
+ Classifier: Topic :: Scientific/Engineering :: Physics
11
+ Classifier: Typing :: Typed
12
+ Requires-Dist: numpy>=2.0,<3.0
13
+ Requires-Dist: scipy>=1.14,<2.0
14
+ Requires-Dist: symengine>=0.13,<1.0
15
+ Requires-Dist: sympy>=1.13,<2.0
16
+ Requires-Dist: plotly>=6,<7 ; extra == 'interactive'
17
+ Requires-Dist: lzcomplexity>=0.9 ; extra == 'lz'
18
+ Requires-Dist: matplotlib>=3.9,<5 ; extra == 'plot'
19
+ Requires-Dist: matplotlib>=3.9,<5 ; extra == 'viz'
20
+ Provides-Extra: interactive
21
+ Provides-Extra: lz
22
+ Provides-Extra: plot
23
+ Provides-Extra: viz
24
+ License-File: LICENSE
25
+ Summary: Zero-warmup Rust-engine ODE/DDE/SDE integration, discrete maps, and chaos analysis for dynamical systems.
26
+ Keywords: dynamical systems,ODE,DDE,chaos,Lyapunov exponents,nonlinear dynamics,bifurcation,attractors,reservoir computing
27
+ Author-email: Daniel Estevez <kemossabee@gmail.com>
28
+ License-Expression: MIT
29
+ Requires-Python: >=3.12, <4.0
30
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
31
+ Project-URL: Changelog, https://github.com/El3ssar/TSDynamics/releases
32
+ Project-URL: Documentation, https://el3ssar.github.io/TSDynamics/
33
+ Project-URL: Issues, https://github.com/El3ssar/TSDynamics/issues
34
+ Project-URL: Source, https://github.com/El3ssar/TSDynamics
35
+
36
+ # TSDynamics
37
+
38
+ [![Python](https://img.shields.io/pypi/pyversions/tsdynamics)](https://pypi.org/project/tsdynamics/)
39
+ [![CI](https://github.com/El3ssar/TSDynamics/actions/workflows/ci.yml/badge.svg)](https://github.com/El3ssar/TSDynamics/actions/workflows/ci.yml)
40
+ [![Release](https://github.com/El3ssar/TSDynamics/actions/workflows/release.yml/badge.svg)](https://github.com/El3ssar/TSDynamics/actions/workflows/release.yml)
41
+ [![Docs](https://github.com/El3ssar/TSDynamics/actions/workflows/docs.yml/badge.svg)](https://el3ssar.github.io/TSDynamics/)
42
+ [![PyPI](https://img.shields.io/pypi/v/tsdynamics)](https://pypi.org/project/tsdynamics/)
43
+ [![codecov](https://codecov.io/gh/El3ssar/TSDynamics/graph/badge.svg)](https://codecov.io/gh/El3ssar/TSDynamics)
44
+
45
+ **Dynamical systems in Python: 151 built-in systems, a native Rust integration
46
+ engine, and a chaos-analysis toolkit — with the simplest system-definition
47
+ contract anywhere.**
48
+
49
+ You write the math (one symbolic method); TSDynamics lowers it to a native Rust
50
+ engine and handles integration, Lyapunov spectra, bifurcation diagrams, Poincaré
51
+ sections, attractors & basins — and even the documentation page for your system.
52
+
53
+ <p align="center">
54
+ <img src="docs/assets/readme/lorenz_spin.gif" width="440" alt="A spinning Lorenz attractor"><br>
55
+ <em>A built-in Lorenz attractor — integrated, spun, and saved to a GIF (code below).</em>
56
+ </p>
57
+
58
+ ```python
59
+ import tsdynamics as ts
60
+
61
+ lor = ts.Lorenz()
62
+ traj = lor.integrate(final_time=100.0, dt=0.01)
63
+ traj["x"] # named component access
64
+ lor.lyapunov_spectrum() # → [0.91, ~0, -14.57]
65
+ ts.kaplan_yorke_dimension(_) # → ~2.06
66
+ ```
67
+
68
+ 📖 **Documentation: <https://el3ssar.github.io/TSDynamics/>**
69
+
70
+ ---
71
+
72
+ ## Define your own system
73
+
74
+ ```python
75
+ import tsdynamics as ts
76
+
77
+ class Rossler(ts.ContinuousSystem):
78
+ params = {"a": 0.2, "b": 0.2, "c": 5.7}
79
+ dim = 3
80
+ variables = ("x", "y", "z") # optional niceties
81
+
82
+ @staticmethod
83
+ def _equations(y, t, *, a, b, c):
84
+ x, yv, z = y(0), y(1), y(2)
85
+ return (-yv - z, x + a * yv, b + z * (x - c))
86
+ ```
87
+
88
+ That's the whole contract. The class auto-registers: every analysis tool works
89
+ on it, the test-suite sweeps it, and the docs build renders its equations
90
+ (LaTeX, straight from the symbolics) and its attractor — zero extra steps. Delay
91
+ systems use `y(0, t - tau)`; maps implement `_step`/`_jacobian` (signature order
92
+ validated at import); SDEs add a `_diffusion` term.
93
+
94
+ ## From equations to figures
95
+
96
+ Every system produces a `Trajectory`, and every `Trajectory` knows how to plot
97
+ itself (`to_plot_spec` auto-detects the right kind from the data). The plot is a
98
+ backend-neutral *spec* you tweak fluently, then `render` or `save` to matplotlib,
99
+ plotly (interactive), or a three.js / JSON export.
100
+
101
+ **Bifurcation diagram of the logistic map**, with the period-doubling onsets
102
+ marked — `orbit_diagram` is one call, and `.bifurcation_points()` finds the
103
+ cascade ($r_1 = 3$, $r_2 = 1 + \sqrt6 \approx 3.449$, …):
104
+
105
+ ```python
106
+ import numpy as np, tsdynamics as ts
107
+ from tsdynamics.viz import Annotation
108
+
109
+ orbit = ts.orbit_diagram(ts.Logistic(), "r", np.linspace(2.8, 4.0, 2000))
110
+ pts = orbit.bifurcation_points()
111
+
112
+ spec = orbit.to_plot_spec().relabel(x="r", y="x*", title="Logistic bifurcation")
113
+ spec.style(color="k", s=0.2, alpha=0.5) # tiny semi-transparent dots
114
+ spec.annotations = [
115
+ Annotation("vline", x=pts[i], text=lbl, style={"color": "red", "linestyle": "--"})
116
+ for i, lbl in [(0, " r₁"), (1, " r₂"), (3, " r₄")]
117
+ ]
118
+ spec.save("bifurcation.png", size=(1600, 700))
119
+ ```
120
+
121
+ ![Logistic bifurcation diagram](docs/assets/readme/logistic_bifurcation.png)
122
+
123
+ **A PDE, too** — the Kuramoto–Sivashinsky equation is a built-in spatially
124
+ extended system; its space–time field is auto-detected and drawn as a heatmap:
125
+
126
+ ```python
127
+ ks = ts.KuramotoSivashinsky(N=128, L=22.0)
128
+ traj = ks.integrate(final_time=200.0, dt=0.25)
129
+ traj.to_plot_spec().save("ks.png") # 128-mode space–time field
130
+ ```
131
+
132
+
133
+
134
+ <p align="center">
135
+ <img src="docs/assets/readme/ks_spacetime.png" alt="Kuramoto–Sivashinsky space–time field">
136
+ </p>
137
+
138
+
139
+ The spinning attractor at the top is the same `to_plot_spec`, animated:
140
+
141
+ ```python
142
+ traj = ts.Lorenz().integrate(final_time=100.0, dt=0.01)
143
+ spec = traj.to_plot_spec()
144
+ spec.style(axes=False).trail(None).camera(spin=0.4) # full curve, no axes, rotate
145
+ spec.animate(fps=30, duration=10, loop=True)
146
+ spec.save("lorenz.gif")
147
+ ```
148
+
149
+ ## A taste of the analysis layer
150
+
151
+ ```python
152
+ import numpy as np, tsdynamics as ts
153
+
154
+ # Poincaré section of the Rössler attractor (root-refined crossings)
155
+ section = ts.poincare_section(ts.Rossler(), plane=("y", 0.0, "up"), n=500)
156
+
157
+ # Fixed points of the Hénon map, with stability
158
+ ts.fixed_points(ts.Henon())
159
+ # [FixedPoint([-1.1314 -0.3394], unstable), FixedPoint([0.6314 0.1894], unstable)]
160
+
161
+ # Maximal Lyapunov exponent — no Jacobian needed
162
+ ts.max_lyapunov(ts.Lorenz(ic=[1, 1, 1]), dt=0.05) # ≈ 0.9
163
+ ```
164
+
165
+ Plus: **attractors & basins** of any flow or map, correlation/Rényi **fractal
166
+ dimensions**, **permutation/sample/dispersion entropy**, **RQA** (recurrence
167
+ quantification), **surrogate** hypothesis tests, **delay embedding** (Takens,
168
+ optimal τ, Cao/FNN), GALI & the 0–1 chaos test, and Lyapunov exponents **from a
169
+ bare time series** (Kantz/Rosenstein).
170
+
171
+ ## Highlights
172
+
173
+ - **Three families, one interface**
174
+
175
+ - **ODEs**
176
+
177
+ - **DDEs**
178
+ - **SDEs**
179
+ - **Discrete Maps**
180
+
181
+ - **151 built-in systems** with literature parameters.
182
+
183
+ - **Native engine**: equations lower to a Rust engine (an SSA-tape
184
+ interpreter, with a Cranelift JIT alongside) in-process; parameters are runtime
185
+ values, so changing them is free and there is no compile step or cache.
186
+
187
+ - **Composition** — a `PoincareMap` of a flow *is* a discrete map, so
188
+ `orbit_diagram(PoincareMap(Rossler(), ("y", 0.0)), "c", values)` draws the
189
+ bifurcation diagram of a *flow* in one line.
190
+
191
+ - **Backend-neutral plotting** — one `PlotSpec` IR renders to matplotlib, plotly
192
+ (interactive + animated HTML), three.js, or JSON, with a fluent styling/theming
193
+ vocabulary.
194
+
195
+ ## Install
196
+
197
+ ```bash
198
+ pip install tsdynamics # or: uv add tsdynamics
199
+ ```
200
+
201
+ A prebuilt `abi3` wheel (manylinux / musllinux / macOS / Windows) bundles the
202
+ native Rust engine. No Rust toolchain and no C compiler needed to install or
203
+ run. Optional plotting extra: `tsdynamics[plot,interactive]` (matplotlib, plotly).
204
+
205
+ ## Development
206
+
207
+ ```bash
208
+ git clone https://github.com/El3ssar/TSDynamics && cd TSDynamics
209
+ uv sync --group dev --group docs
210
+ uv run pytest -m "not slow" --no-cov # fast tier
211
+ uv run pytest --no-cov # full local suite
212
+ TSD_DOCS_FIGURES=0 uv run mkdocs serve # docs preview
213
+ ```
214
+
215
+ Releases are automated: conventional-commit PR titles drive
216
+ [semantic-release](https://python-semantic-release.readthedocs.io/) on merge see [CONTRIBUTING](https://el3ssar.github.io/TSDynamics/project/contributing/).
217
+
218
+ ## License
219
+
220
+ MIT © Daniel Estevez
221
+
@@ -0,0 +1,185 @@
1
+ # TSDynamics
2
+
3
+ [![Python](https://img.shields.io/pypi/pyversions/tsdynamics)](https://pypi.org/project/tsdynamics/)
4
+ [![CI](https://github.com/El3ssar/TSDynamics/actions/workflows/ci.yml/badge.svg)](https://github.com/El3ssar/TSDynamics/actions/workflows/ci.yml)
5
+ [![Release](https://github.com/El3ssar/TSDynamics/actions/workflows/release.yml/badge.svg)](https://github.com/El3ssar/TSDynamics/actions/workflows/release.yml)
6
+ [![Docs](https://github.com/El3ssar/TSDynamics/actions/workflows/docs.yml/badge.svg)](https://el3ssar.github.io/TSDynamics/)
7
+ [![PyPI](https://img.shields.io/pypi/v/tsdynamics)](https://pypi.org/project/tsdynamics/)
8
+ [![codecov](https://codecov.io/gh/El3ssar/TSDynamics/graph/badge.svg)](https://codecov.io/gh/El3ssar/TSDynamics)
9
+
10
+ **Dynamical systems in Python: 151 built-in systems, a native Rust integration
11
+ engine, and a chaos-analysis toolkit — with the simplest system-definition
12
+ contract anywhere.**
13
+
14
+ You write the math (one symbolic method); TSDynamics lowers it to a native Rust
15
+ engine and handles integration, Lyapunov spectra, bifurcation diagrams, Poincaré
16
+ sections, attractors & basins — and even the documentation page for your system.
17
+
18
+ <p align="center">
19
+ <img src="docs/assets/readme/lorenz_spin.gif" width="440" alt="A spinning Lorenz attractor"><br>
20
+ <em>A built-in Lorenz attractor — integrated, spun, and saved to a GIF (code below).</em>
21
+ </p>
22
+
23
+ ```python
24
+ import tsdynamics as ts
25
+
26
+ lor = ts.Lorenz()
27
+ traj = lor.integrate(final_time=100.0, dt=0.01)
28
+ traj["x"] # named component access
29
+ lor.lyapunov_spectrum() # → [0.91, ~0, -14.57]
30
+ ts.kaplan_yorke_dimension(_) # → ~2.06
31
+ ```
32
+
33
+ 📖 **Documentation: <https://el3ssar.github.io/TSDynamics/>**
34
+
35
+ ---
36
+
37
+ ## Define your own system
38
+
39
+ ```python
40
+ import tsdynamics as ts
41
+
42
+ class Rossler(ts.ContinuousSystem):
43
+ params = {"a": 0.2, "b": 0.2, "c": 5.7}
44
+ dim = 3
45
+ variables = ("x", "y", "z") # optional niceties
46
+
47
+ @staticmethod
48
+ def _equations(y, t, *, a, b, c):
49
+ x, yv, z = y(0), y(1), y(2)
50
+ return (-yv - z, x + a * yv, b + z * (x - c))
51
+ ```
52
+
53
+ That's the whole contract. The class auto-registers: every analysis tool works
54
+ on it, the test-suite sweeps it, and the docs build renders its equations
55
+ (LaTeX, straight from the symbolics) and its attractor — zero extra steps. Delay
56
+ systems use `y(0, t - tau)`; maps implement `_step`/`_jacobian` (signature order
57
+ validated at import); SDEs add a `_diffusion` term.
58
+
59
+ ## From equations to figures
60
+
61
+ Every system produces a `Trajectory`, and every `Trajectory` knows how to plot
62
+ itself (`to_plot_spec` auto-detects the right kind from the data). The plot is a
63
+ backend-neutral *spec* you tweak fluently, then `render` or `save` to matplotlib,
64
+ plotly (interactive), or a three.js / JSON export.
65
+
66
+ **Bifurcation diagram of the logistic map**, with the period-doubling onsets
67
+ marked — `orbit_diagram` is one call, and `.bifurcation_points()` finds the
68
+ cascade ($r_1 = 3$, $r_2 = 1 + \sqrt6 \approx 3.449$, …):
69
+
70
+ ```python
71
+ import numpy as np, tsdynamics as ts
72
+ from tsdynamics.viz import Annotation
73
+
74
+ orbit = ts.orbit_diagram(ts.Logistic(), "r", np.linspace(2.8, 4.0, 2000))
75
+ pts = orbit.bifurcation_points()
76
+
77
+ spec = orbit.to_plot_spec().relabel(x="r", y="x*", title="Logistic bifurcation")
78
+ spec.style(color="k", s=0.2, alpha=0.5) # tiny semi-transparent dots
79
+ spec.annotations = [
80
+ Annotation("vline", x=pts[i], text=lbl, style={"color": "red", "linestyle": "--"})
81
+ for i, lbl in [(0, " r₁"), (1, " r₂"), (3, " r₄")]
82
+ ]
83
+ spec.save("bifurcation.png", size=(1600, 700))
84
+ ```
85
+
86
+ ![Logistic bifurcation diagram](docs/assets/readme/logistic_bifurcation.png)
87
+
88
+ **A PDE, too** — the Kuramoto–Sivashinsky equation is a built-in spatially
89
+ extended system; its space–time field is auto-detected and drawn as a heatmap:
90
+
91
+ ```python
92
+ ks = ts.KuramotoSivashinsky(N=128, L=22.0)
93
+ traj = ks.integrate(final_time=200.0, dt=0.25)
94
+ traj.to_plot_spec().save("ks.png") # 128-mode space–time field
95
+ ```
96
+
97
+
98
+
99
+ <p align="center">
100
+ <img src="docs/assets/readme/ks_spacetime.png" alt="Kuramoto–Sivashinsky space–time field">
101
+ </p>
102
+
103
+
104
+ The spinning attractor at the top is the same `to_plot_spec`, animated:
105
+
106
+ ```python
107
+ traj = ts.Lorenz().integrate(final_time=100.0, dt=0.01)
108
+ spec = traj.to_plot_spec()
109
+ spec.style(axes=False).trail(None).camera(spin=0.4) # full curve, no axes, rotate
110
+ spec.animate(fps=30, duration=10, loop=True)
111
+ spec.save("lorenz.gif")
112
+ ```
113
+
114
+ ## A taste of the analysis layer
115
+
116
+ ```python
117
+ import numpy as np, tsdynamics as ts
118
+
119
+ # Poincaré section of the Rössler attractor (root-refined crossings)
120
+ section = ts.poincare_section(ts.Rossler(), plane=("y", 0.0, "up"), n=500)
121
+
122
+ # Fixed points of the Hénon map, with stability
123
+ ts.fixed_points(ts.Henon())
124
+ # [FixedPoint([-1.1314 -0.3394], unstable), FixedPoint([0.6314 0.1894], unstable)]
125
+
126
+ # Maximal Lyapunov exponent — no Jacobian needed
127
+ ts.max_lyapunov(ts.Lorenz(ic=[1, 1, 1]), dt=0.05) # ≈ 0.9
128
+ ```
129
+
130
+ Plus: **attractors & basins** of any flow or map, correlation/Rényi **fractal
131
+ dimensions**, **permutation/sample/dispersion entropy**, **RQA** (recurrence
132
+ quantification), **surrogate** hypothesis tests, **delay embedding** (Takens,
133
+ optimal τ, Cao/FNN), GALI & the 0–1 chaos test, and Lyapunov exponents **from a
134
+ bare time series** (Kantz/Rosenstein).
135
+
136
+ ## Highlights
137
+
138
+ - **Three families, one interface**
139
+
140
+ - **ODEs**
141
+
142
+ - **DDEs**
143
+ - **SDEs**
144
+ - **Discrete Maps**
145
+
146
+ - **151 built-in systems** with literature parameters.
147
+
148
+ - **Native engine**: equations lower to a Rust engine (an SSA-tape
149
+ interpreter, with a Cranelift JIT alongside) in-process; parameters are runtime
150
+ values, so changing them is free and there is no compile step or cache.
151
+
152
+ - **Composition** — a `PoincareMap` of a flow *is* a discrete map, so
153
+ `orbit_diagram(PoincareMap(Rossler(), ("y", 0.0)), "c", values)` draws the
154
+ bifurcation diagram of a *flow* in one line.
155
+
156
+ - **Backend-neutral plotting** — one `PlotSpec` IR renders to matplotlib, plotly
157
+ (interactive + animated HTML), three.js, or JSON, with a fluent styling/theming
158
+ vocabulary.
159
+
160
+ ## Install
161
+
162
+ ```bash
163
+ pip install tsdynamics # or: uv add tsdynamics
164
+ ```
165
+
166
+ A prebuilt `abi3` wheel (manylinux / musllinux / macOS / Windows) bundles the
167
+ native Rust engine. No Rust toolchain and no C compiler needed to install or
168
+ run. Optional plotting extra: `tsdynamics[plot,interactive]` (matplotlib, plotly).
169
+
170
+ ## Development
171
+
172
+ ```bash
173
+ git clone https://github.com/El3ssar/TSDynamics && cd TSDynamics
174
+ uv sync --group dev --group docs
175
+ uv run pytest -m "not slow" --no-cov # fast tier
176
+ uv run pytest --no-cov # full local suite
177
+ TSD_DOCS_FIGURES=0 uv run mkdocs serve # docs preview
178
+ ```
179
+
180
+ Releases are automated: conventional-commit PR titles drive
181
+ [semantic-release](https://python-semantic-release.readthedocs.io/) on merge see [CONTRIBUTING](https://el3ssar.github.io/TSDynamics/project/contributing/).
182
+
183
+ ## License
184
+
185
+ MIT © Daniel Estevez
@@ -0,0 +1,162 @@
1
+ //! ODE Lyapunov-spectrum entry point (stream `perf/ode-lyapunov-engine`).
2
+ //!
3
+ //! Thin bridge over the engine's [`tsdyn_engine::lyapunov`] kernel: build the
4
+ //! evaluator (interpreter or JIT) over the **extended** variational tape + resolve
5
+ //! the solver here (the cranelift edge lives in this crate, not the engine), then
6
+ //! run the whole burn-in + averaging Benettin loop in one call. The per-`dt`
7
+ //! integration is byte-for-byte the released `TangentSystem` ODE numerics, so
8
+ //! `interp == jit` and the spectrum matches the Python path to floating-point
9
+ //! tolerance.
10
+ //!
11
+ //! Shared plumbing (evaluator/solver builders, the `EngineError` enum) lives in
12
+ //! [`super::marshal`]; the renormalisation loop and the QR live in
13
+ //! [`tsdyn_engine::lyapunov`].
14
+
15
+ use tsdyn_engine::lyapunov::{lyapunov_spectrum_ode, LyapunovError, LyapunovOutcome};
16
+ use tsdyn_ir::Tape;
17
+ use tsdyn_solvers::Solver;
18
+
19
+ use super::marshal::{
20
+ build_evaluator, build_solver, require_jacobian_if_needed, resolve_solver, EngineError,
21
+ };
22
+
23
+ /// Map a [`LyapunovError`] (a kernel-side shape / divergence failure) to the
24
+ /// bridge's [`EngineError`] so the binding routes it to the matching Python
25
+ /// exception (`ValueError` / `RuntimeError`).
26
+ fn to_engine_err(e: LyapunovError) -> EngineError {
27
+ match e {
28
+ LyapunovError::BadShape(m) => EngineError::BadShape(m),
29
+ LyapunovError::Diverged(m) => EngineError::Diverged(m),
30
+ }
31
+ }
32
+
33
+ /// Run the burn-in + averaging Benettin Lyapunov-spectrum loop over an
34
+ /// **extended variational** ODE tape.
35
+ ///
36
+ /// The `tape` is the `dim*(k+1)`-wide extended variational system (base RHS ⊕ the
37
+ /// `k` tangent-vector RHS blocks) built by the Python
38
+ /// `tsdynamics.derived._variational.build_variational_tape`. `method` resolves
39
+ /// through the solver registry; an implicit kernel needs a Jacobian-carrying tape
40
+ /// (the extended tape carries one — rejected up front exactly as the integrate
41
+ /// path does, for the defence-in-depth message). `dt` is the renormalisation
42
+ /// interval, `burn_in` / `final_time` the two window lengths. `p` is the live
43
+ /// control-parameter vector. Returns the spectrum plus the final extended state.
44
+ #[allow(clippy::too_many_arguments)]
45
+ pub fn lyapunov_spectrum_ode_bridge(
46
+ tape: Tape,
47
+ p: &[f64],
48
+ method: &str,
49
+ rtol: f64,
50
+ atol: f64,
51
+ dim: usize,
52
+ k: usize,
53
+ z0: &[f64],
54
+ t0: f64,
55
+ dt: f64,
56
+ burn_in: f64,
57
+ final_time: f64,
58
+ jit: bool,
59
+ ) -> Result<LyapunovOutcome, EngineError> {
60
+ // The extended tape is `n_state == ev.dim()` (a plain ODE-shaped tape over the
61
+ // stacked state), validated by the kernel against `dim*(k+1)`. Resolve the
62
+ // method + the Jacobian guard exactly like the integrate / basin paths.
63
+ let name = resolve_solver(method)?;
64
+ require_jacobian_if_needed(&tape, name)?;
65
+ let ev = build_evaluator(tape, jit)?;
66
+ // A fresh kernel per `dt` chunk (mirrors `OdeStepper::advance` / the basin
67
+ // march); the name is a registry name, so `build_solver` always succeeds.
68
+ let factory = move || -> Box<dyn Solver> { build_solver(name, rtol, atol) };
69
+ lyapunov_spectrum_ode(&*ev, factory, p, dim, k, z0, t0, dt, burn_in, final_time)
70
+ .map_err(to_engine_err)
71
+ }
72
+
73
+ #[cfg(test)]
74
+ mod tests {
75
+ use super::*;
76
+ use tsdyn_ir::TapeBuilder;
77
+
78
+ /// Extended variational tape of `dx = a x, dy = b y` with `k` tangents (the
79
+ /// Jacobian is the constant `diag(a, b)`). Spectrum is `[max(a,b), min(a,b)]`.
80
+ fn linear_extended(a: f64, b: f64, k: usize) -> Tape {
81
+ let dim = 2;
82
+ let mut bld = TapeBuilder::new();
83
+ let x = bld.state(0);
84
+ let y = bld.state(1);
85
+ let ac = bld.constant(a);
86
+ let bc = bld.constant(b);
87
+ let dx = bld.mul(ac, x);
88
+ let dy = bld.mul(bc, y);
89
+ let mut outs = vec![dx, dy];
90
+ for i in 0..k {
91
+ let base = dim + i * dim;
92
+ let w0 = bld.state(base);
93
+ let w1 = bld.state(base + 1);
94
+ let dw0 = bld.mul(ac, w0);
95
+ let dw1 = bld.mul(bc, w1);
96
+ outs.push(dw0);
97
+ outs.push(dw1);
98
+ }
99
+ let n = dim * (k + 1);
100
+ bld.finish(&outs, &[], n, 0).unwrap()
101
+ }
102
+
103
+ #[test]
104
+ fn bridge_spectrum_matches_analytic_and_interp_equals_jit() {
105
+ let (a, b, k) = (0.5, -2.0, 2);
106
+ let z0 = vec![1.0, 1.0, 1.0, 0.0, 0.0, 1.0];
107
+ let run = |jit| {
108
+ lyapunov_spectrum_ode_bridge(
109
+ linear_extended(a, b, k),
110
+ &[],
111
+ "rk45",
112
+ 1e-9,
113
+ 1e-11,
114
+ 2,
115
+ k,
116
+ &z0,
117
+ 0.0,
118
+ 0.1,
119
+ 5.0,
120
+ 50.0,
121
+ jit,
122
+ )
123
+ .unwrap()
124
+ };
125
+ let interp = run(false);
126
+ let jit = run(true);
127
+ // interp == jit bit-for-bit (both drive the same &dyn Evaluator).
128
+ assert_eq!(interp.spectrum.len(), jit.spectrum.len());
129
+ for (i, (x, y)) in interp.spectrum.iter().zip(jit.spectrum.iter()).enumerate() {
130
+ assert_eq!(
131
+ x.to_bits(),
132
+ y.to_bits(),
133
+ "exponent {i}: jit {y} != interp {x}"
134
+ );
135
+ }
136
+ // and matches the analytic spectrum.
137
+ assert!((interp.spectrum[0] - 0.5).abs() < 1e-4);
138
+ assert!((interp.spectrum[1] - (-2.0)).abs() < 1e-4);
139
+ }
140
+
141
+ #[test]
142
+ fn bridge_rejects_unknown_method() {
143
+ let z0 = vec![1.0, 1.0, 1.0, 0.0, 0.0, 1.0];
144
+ let err = lyapunov_spectrum_ode_bridge(
145
+ linear_extended(0.5, -2.0, 2),
146
+ &[],
147
+ "no-such",
148
+ 1e-6,
149
+ 1e-9,
150
+ 2,
151
+ 2,
152
+ &z0,
153
+ 0.0,
154
+ 0.1,
155
+ 1.0,
156
+ 1.0,
157
+ false,
158
+ )
159
+ .unwrap_err();
160
+ assert!(matches!(err, EngineError::UnknownMethod(_)), "got {err:?}");
161
+ }
162
+ }
@@ -5,7 +5,10 @@
5
5
  //! iteration loop, shape contract and divergence behaviour are unchanged. Shared
6
6
  //! plumbing lives in [`super::marshal`].
7
7
 
8
- use tsdyn_engine::{iterate_dense, iterate_ensemble_final as engine_iterate_ensemble};
8
+ use tsdyn_engine::{
9
+ iterate_dense, iterate_ensemble_final as engine_iterate_ensemble, map_orbit_sweep, SweepError,
10
+ SweepOutcome,
11
+ };
9
12
  use tsdyn_ir::Tape;
10
13
 
11
14
  use super::marshal::{build_evaluator, map_diverge_msg, EngineError};
@@ -126,3 +129,88 @@ pub fn map_ensemble_final(
126
129
  let result = engine_iterate_ensemble(&*ev, ics, &[], steps);
127
130
  Ok(result.states)
128
131
  }
132
+
133
+ /// Map a [`SweepError`] (a kernel-side shape failure) to the bridge's
134
+ /// [`EngineError`] so the binding routes it to the same Python `ValueError` the
135
+ /// other entry points use.
136
+ fn sweep_to_engine_err(e: SweepError) -> EngineError {
137
+ match e {
138
+ SweepError::BadShape(m) => EngineError::BadShape(m),
139
+ }
140
+ }
141
+
142
+ /// Run the whole map orbit-diagram parameter sweep in one call (stream
143
+ /// `perf/param-sweep-kernel`).
144
+ ///
145
+ /// Unlike [`iterate_map`], the tape here keeps the **swept** parameter as its
146
+ /// single runtime `Param` input (`n_param == 1`) — the Python wiring lowers the
147
+ /// map once with the other parameters folded in, so a 1000-value sweep is one FFI
148
+ /// call instead of ~1000 `iterate_map` round-trips. For each value the kernel sets
149
+ /// `p[sweep_index] = value`, runs `transient + n` iterations (carrying the final
150
+ /// state across values when `carry_state`), and records the last `n` asymptotic
151
+ /// states' selected `components`.
152
+ ///
153
+ /// Returns `(points, status)`: `points` is the flat row-major
154
+ /// `(n_values * n, n_components)` recorded buffer, and `status[k]` is `0` for a
155
+ /// value that ran finitely / `1` for a value that diverged (whose block is zero —
156
+ /// the binding drops it to an empty set and warns, mirroring the per-value path).
157
+ /// The per-iterate numerics are byte-for-byte the [`iterate_map`] path (a `Param`
158
+ /// leaf reads the same `f64` a folded `Const` would).
159
+ ///
160
+ /// `jit` selects the evaluator (Cranelift native code vs the interpreter); both
161
+ /// are bit-for-bit identical on `eval`, so the swept diagram is numerically
162
+ /// identical either way.
163
+ #[allow(clippy::too_many_arguments)]
164
+ pub fn map_param_sweep(
165
+ tape: Tape,
166
+ base_params: &[f64],
167
+ sweep_index: usize,
168
+ values: &[f64],
169
+ ic: &[f64],
170
+ components: &[usize],
171
+ transient: usize,
172
+ n_record: usize,
173
+ carry_state: bool,
174
+ jit: bool,
175
+ ) -> Result<(Vec<f64>, Vec<i64>), EngineError> {
176
+ // The sweep tape's next-state dimension equals its state dimension, and it
177
+ // carries exactly the swept parameter as a runtime input. Reject a tape whose
178
+ // shape disagrees (reachable only via a hand-built tape) so a mismatch is a
179
+ // clean error rather than a release-mode out-of-bounds inside the eval loop.
180
+ let dim = tape.dim();
181
+ if tape.n_state() != dim {
182
+ return Err(EngineError::BadShape(format!(
183
+ "map sweep expects n_state == dim (state and next-state share a \
184
+ dimension); got n_state = {} != dim = {dim}",
185
+ tape.n_state()
186
+ )));
187
+ }
188
+ if tape.n_param() != base_params.len() {
189
+ return Err(EngineError::BadShape(format!(
190
+ "map sweep base-parameter vector has length {}, but the tape declares \
191
+ n_param = {}",
192
+ base_params.len(),
193
+ tape.n_param()
194
+ )));
195
+ }
196
+ let ev = build_evaluator(tape, jit)?;
197
+ let SweepOutcome { points, status, .. } = map_orbit_sweep(
198
+ &*ev,
199
+ base_params,
200
+ sweep_index,
201
+ values,
202
+ ic,
203
+ components,
204
+ transient,
205
+ n_record,
206
+ carry_state,
207
+ )
208
+ .map_err(sweep_to_engine_err)?;
209
+ // Flatten the per-value fate to a plain i64 code (0 = ok, 1 = diverged) the
210
+ // FFI returns alongside the points buffer.
211
+ let status: Vec<i64> = status
212
+ .into_iter()
213
+ .map(|s| if s.is_ok() { 0 } else { 1 })
214
+ .collect();
215
+ Ok((points, status))
216
+ }