tsdynamics 3.1.4__tar.gz → 3.2.0__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 (209) hide show
  1. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/PKG-INFO +1 -1
  2. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-core/src/bridge.rs +16 -4
  3. tsdynamics-3.2.0/crates/tsdyn-solvers/src/explicit/adams.rs +453 -0
  4. tsdynamics-3.2.0/crates/tsdyn-solvers/src/explicit/bs3.rs +189 -0
  5. tsdynamics-3.2.0/crates/tsdyn-solvers/src/explicit/cashkarp.rs +205 -0
  6. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/explicit/control.rs +1 -0
  7. tsdynamics-3.2.0/crates/tsdyn-solvers/src/explicit/euler.rs +99 -0
  8. tsdynamics-3.2.0/crates/tsdyn-solvers/src/explicit/heun.rs +101 -0
  9. tsdynamics-3.2.0/crates/tsdyn-solvers/src/explicit/heun_euler.rs +175 -0
  10. tsdynamics-3.2.0/crates/tsdyn-solvers/src/explicit/midpoint.rs +99 -0
  11. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/explicit/mod.rs +22 -0
  12. tsdynamics-3.2.0/crates/tsdyn-solvers/src/explicit/ralston.rs +100 -0
  13. tsdynamics-3.2.0/crates/tsdyn-solvers/src/explicit/rk4_38.rs +106 -0
  14. tsdynamics-3.2.0/crates/tsdyn-solvers/src/explicit/rkf45.rs +203 -0
  15. tsdynamics-3.2.0/crates/tsdyn-solvers/src/explicit/ssprk3.rs +104 -0
  16. tsdynamics-3.2.0/crates/tsdyn-solvers/src/implicit/backward_euler.rs +161 -0
  17. tsdynamics-3.2.0/crates/tsdyn-solvers/src/implicit/implicit_midpoint.rs +179 -0
  18. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/implicit/mod.rs +9 -0
  19. tsdynamics-3.2.0/crates/tsdyn-solvers/src/implicit/newton.rs +175 -0
  20. tsdynamics-3.2.0/crates/tsdyn-solvers/src/implicit/sdirk2.rs +228 -0
  21. tsdynamics-3.2.0/crates/tsdyn-solvers/src/implicit/trapezoid.rs +160 -0
  22. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/implicit/trbdf2.rs +5 -83
  23. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/tests/explicit_accuracy.rs +40 -2
  24. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/tests/implicit_stiff.rs +36 -0
  25. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/pyproject.toml +1 -1
  26. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/__init__.py +1 -1
  27. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/chaos/gali.py +58 -25
  28. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/chaos/zero_one.py +5 -2
  29. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/solvers/explicit.py +16 -0
  30. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/solvers/implicit.py +4 -0
  31. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/solvers/select.py +38 -2
  32. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/continuous/chaotic_attractors.py +20 -0
  33. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/continuous/coupled_systems.py +19 -0
  34. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/LICENSE +0 -0
  35. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/README.md +0 -0
  36. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/Cargo.toml +0 -0
  37. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-core/Cargo.lock +0 -0
  38. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-core/Cargo.toml +0 -0
  39. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-core/src/lib.rs +0 -0
  40. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-engine/Cargo.toml +0 -0
  41. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-engine/src/dde.rs +0 -0
  42. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-engine/src/ensemble.rs +0 -0
  43. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-engine/src/event.rs +0 -0
  44. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-engine/src/integrate.rs +0 -0
  45. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-engine/src/lib.rs +0 -0
  46. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-engine/src/map.rs +0 -0
  47. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-engine/src/problem.rs +0 -0
  48. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-engine/src/rng.rs +0 -0
  49. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-engine/src/sde.rs +0 -0
  50. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-engine/src/testkit.rs +0 -0
  51. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/Cargo.toml +0 -0
  52. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/src/builder.rs +0 -0
  53. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/src/eval.rs +0 -0
  54. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/src/lib.rs +0 -0
  55. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/src/op.rs +0 -0
  56. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/src/reference.rs +0 -0
  57. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/src/tape.rs +0 -0
  58. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/tests/coverage_tape.rs +0 -0
  59. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/tests/fixtures/CircadianRhythm.txt +0 -0
  60. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/tests/fixtures/Lorenz.txt +0 -0
  61. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/tests/fixtures/SanUmSrisuchinwong.txt +0 -0
  62. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/tests/fixtures/SprottMore.txt +0 -0
  63. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/tests/fixtures/Torus.txt +0 -0
  64. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/tests/fixtures/YuWang2.txt +0 -0
  65. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/tests/gen_fixtures.py +0 -0
  66. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/tests/golden_fixtures.rs +0 -0
  67. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-ir/tests/roundtrip.rs +0 -0
  68. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-jit/Cargo.toml +0 -0
  69. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-jit/src/codegen.rs +0 -0
  70. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-jit/src/error.rs +0 -0
  71. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-jit/src/evaluator.rs +0 -0
  72. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-jit/src/lib.rs +0 -0
  73. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-jit/src/shims.rs +0 -0
  74. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-jit/tests/compile_latency.rs +0 -0
  75. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-jit/tests/golden_fixtures.rs +0 -0
  76. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-jit/tests/interpreter_equivalence.rs +0 -0
  77. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/Cargo.toml +0 -0
  78. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/caps.rs +0 -0
  79. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/explicit/dop853.rs +0 -0
  80. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/explicit/rk4.rs +0 -0
  81. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/explicit/rk45.rs +0 -0
  82. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/explicit/testkit.rs +0 -0
  83. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/explicit/tsit5.rs +0 -0
  84. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/implicit/bdf.rs +0 -0
  85. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/implicit/control.rs +0 -0
  86. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/implicit/linalg.rs +0 -0
  87. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/implicit/rosenbrock.rs +0 -0
  88. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/lib.rs +0 -0
  89. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/registry.rs +0 -0
  90. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/sde/euler_maruyama.rs +0 -0
  91. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/sde/milstein.rs +0 -0
  92. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/sde/mod.rs +0 -0
  93. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/sde/testkit.rs +0 -0
  94. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/src/solver.rs +0 -0
  95. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-solvers/tests/registry.rs +0 -0
  96. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-vm/Cargo.toml +0 -0
  97. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-vm/src/interp.rs +0 -0
  98. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-vm/src/lib.rs +0 -0
  99. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-vm/tests/golden_fixtures.rs +0 -0
  100. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/crates/tsdyn-vm/tests/oracle_equivalence.rs +0 -0
  101. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/__init__.py +0 -0
  102. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/_result.py +0 -0
  103. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/_tangent.py +0 -0
  104. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/basins/__init__.py +0 -0
  105. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/basins/_common.py +0 -0
  106. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/basins/attractors.py +0 -0
  107. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/basins/basins.py +0 -0
  108. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/basins/continuation.py +0 -0
  109. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/basins/metrics.py +0 -0
  110. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/chaos/__init__.py +0 -0
  111. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/chaos/_common.py +0 -0
  112. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/chaos/expansion.py +0 -0
  113. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/dimensions/__init__.py +0 -0
  114. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/dimensions/_common.py +0 -0
  115. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/dimensions/_scaling.py +0 -0
  116. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/dimensions/correlation.py +0 -0
  117. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/dimensions/fixedmass.py +0 -0
  118. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/dimensions/generalized.py +0 -0
  119. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/embedding/__init__.py +0 -0
  120. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/embedding/_common.py +0 -0
  121. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/embedding/delay.py +0 -0
  122. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/embedding/dimension.py +0 -0
  123. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/embedding/embed.py +0 -0
  124. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/entropy/__init__.py +0 -0
  125. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/entropy/core.py +0 -0
  126. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/entropy/dispersion.py +0 -0
  127. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/entropy/lz.py +0 -0
  128. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/entropy/multiscale.py +0 -0
  129. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/entropy/permutation.py +0 -0
  130. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/entropy/sample.py +0 -0
  131. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/fixedpoints/__init__.py +0 -0
  132. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/fixedpoints/_common.py +0 -0
  133. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/fixedpoints/fixed.py +0 -0
  134. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/fixedpoints/periodic.py +0 -0
  135. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/lyapunov/__init__.py +0 -0
  136. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/lyapunov/from_data.py +0 -0
  137. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/orbits/__init__.py +0 -0
  138. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/orbits/orbit_diagram.py +0 -0
  139. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/orbits/poincare.py +0 -0
  140. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/orbits/return_map.py +0 -0
  141. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/recurrence/__init__.py +0 -0
  142. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/recurrence/_common.py +0 -0
  143. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/recurrence/matrix.py +0 -0
  144. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/recurrence/rqa.py +0 -0
  145. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/recurrence/windowed.py +0 -0
  146. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/surrogate/__init__.py +0 -0
  147. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/surrogate/_common.py +0 -0
  148. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/surrogate/generators.py +0 -0
  149. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/surrogate/hypothesis.py +0 -0
  150. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/analysis/surrogate/statistics.py +0 -0
  151. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/data/__init__.py +0 -0
  152. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/data/sampling.py +0 -0
  153. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/data/trajectory.py +0 -0
  154. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/derived/__init__.py +0 -0
  155. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/derived/_base.py +0 -0
  156. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/derived/_crossings.py +0 -0
  157. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/derived/_variational.py +0 -0
  158. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/derived/ensemble.py +0 -0
  159. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/derived/poincare.py +0 -0
  160. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/derived/projected.py +0 -0
  161. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/derived/stroboscopic.py +0 -0
  162. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/derived/tangent.py +0 -0
  163. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/derived/wrapped.py +0 -0
  164. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/engine/__init__.py +0 -0
  165. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/engine/compile.py +0 -0
  166. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/engine/problem.py +0 -0
  167. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/engine/run.py +0 -0
  168. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/engine/symbols.py +0 -0
  169. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/errors.py +0 -0
  170. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/families/__init__.py +0 -0
  171. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/families/_accessors.py +0 -0
  172. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/families/_dde_lyapunov.py +0 -0
  173. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/families/base.py +0 -0
  174. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/families/continuous.py +0 -0
  175. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/families/delay.py +0 -0
  176. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/families/discrete.py +0 -0
  177. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/families/protocol.py +0 -0
  178. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/families/stochastic.py +0 -0
  179. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/families/wrapped.py +0 -0
  180. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/plugins.py +0 -0
  181. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/py.typed +0 -0
  182. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/registry.py +0 -0
  183. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/solvers/__init__.py +0 -0
  184. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/solvers/stochastic.py +0 -0
  185. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/__init__.py +0 -0
  186. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/continuous/__init__.py +0 -0
  187. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/continuous/chem_bio_systems.py +0 -0
  188. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/continuous/climate_geophysics.py +0 -0
  189. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/continuous/delayed_systems.py +0 -0
  190. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/continuous/exotic_systems.py +0 -0
  191. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/continuous/oscillatory_systems.py +0 -0
  192. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/continuous/physical_systems.py +0 -0
  193. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/continuous/population_dynamics.py +0 -0
  194. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/discrete/__init__.py +0 -0
  195. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/discrete/chaotic_maps.py +0 -0
  196. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/discrete/exotic_maps.py +0 -0
  197. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/discrete/geometric_maps.py +0 -0
  198. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/discrete/polynomial_maps.py +0 -0
  199. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/systems/discrete/population_maps.py +0 -0
  200. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/transforms/__init__.py +0 -0
  201. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/transforms/_common.py +0 -0
  202. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/transforms/features.py +0 -0
  203. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/transforms/preprocessing.py +0 -0
  204. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/transforms/spectral.py +0 -0
  205. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/utils/__init__.py +0 -0
  206. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/utils/grids.py +0 -0
  207. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/utils/sagitta_dt.py +0 -0
  208. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/viz/__init__.py +0 -0
  209. {tsdynamics-3.1.4 → tsdynamics-3.2.0}/src/tsdynamics/viz/spec.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tsdynamics
3
- Version: 3.1.4
3
+ Version: 3.2.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Science/Research
6
6
  Classifier: Operating System :: OS Independent
@@ -35,8 +35,10 @@ use tsdyn_engine::{
35
35
  };
36
36
  use tsdyn_ir::{Evaluator, Tape};
37
37
  use tsdyn_jit::JitEvaluator;
38
- use tsdyn_solvers::explicit::{Dop853, Rk45, Tsit5};
39
- use tsdyn_solvers::implicit::{Bdf, RosenbrockW, TrBdf2};
38
+ use tsdyn_solvers::explicit::{Bs3, CashKarp, Dop853, HeunEuler, Rk45, Rkf45, Tsit5};
39
+ use tsdyn_solvers::implicit::{
40
+ BackwardEuler, Bdf, ImplicitMidpoint, RosenbrockW, Sdirk2, TrBdf2, Trapezoid,
41
+ };
40
42
  use tsdyn_solvers::sde::{self, SdeKernel};
41
43
  use tsdyn_solvers::{Solver, SolverKind};
42
44
  use tsdyn_vm::Interpreter;
@@ -224,11 +226,21 @@ pub fn build_solver(name: &'static str, rtol: f64, atol: f64) -> Box<dyn Solver>
224
226
  "rk45" => Box::new(Rk45::with_tolerances(rtol, atol)),
225
227
  "tsit5" => Box::new(Tsit5::with_tolerances(rtol, atol)),
226
228
  "dop853" => Box::new(Dop853::with_tolerances(rtol, atol)),
229
+ "heun_euler" => Box::new(HeunEuler::with_tolerances(rtol, atol)),
230
+ "bs3" => Box::new(Bs3::with_tolerances(rtol, atol)),
231
+ "rkf45" => Box::new(Rkf45::with_tolerances(rtol, atol)),
232
+ "cashkarp" => Box::new(CashKarp::with_tolerances(rtol, atol)),
227
233
  "rosenbrock" => Box::new(RosenbrockW::with_tolerances(rtol, atol)),
228
234
  "trbdf2" => Box::new(TrBdf2::with_tolerances(rtol, atol)),
229
235
  "bdf" => Box::new(Bdf::with_tolerances(rtol, atol)),
230
- // The fixed-step `rk4` (no tolerances) and any out-of-tree plugin kernel
231
- // build through the registry factory. `name` came from the registry
236
+ "backward_euler" => Box::new(BackwardEuler::with_tolerances(rtol, atol)),
237
+ "implicit_midpoint" => Box::new(ImplicitMidpoint::with_tolerances(rtol, atol)),
238
+ "trapezoid" => Box::new(Trapezoid::with_tolerances(rtol, atol)),
239
+ "sdirk2" => Box::new(Sdirk2::with_tolerances(rtol, atol)),
240
+ // The fixed-step kernels (`euler`/`midpoint`/`heun`/`ralston`/`rk4`/
241
+ // `rk4_38`/`ssprk3`), the Adams multistep kernels (`ab3`/`ab4`/`abm4`,
242
+ // which take no tolerances), and any out-of-tree plugin kernel build
243
+ // through the registry factory. `name` came from the registry
232
244
  // (via `resolve_solver`), so `make` is guaranteed `Some`.
233
245
  other => tsdyn_solvers::make(other)
234
246
  .expect("a name returned by resolve_solver is always registered"),
@@ -0,0 +1,453 @@
1
+ //! The explicit linear-multistep (Adams) family: Adams–Bashforth and the
2
+ //! Adams–Bashforth–Moulton predictor–corrector.
3
+ //!
4
+ //! Unlike a Runge–Kutta method, a multistep method reuses the right-hand-side
5
+ //! values it already computed at *previous* steps, so it advances with only **one**
6
+ //! (Adams–Bashforth) or **two** (predictor–corrector) new RHS evaluations per step
7
+ //! regardless of order. That makes them efficient on non-stiff problems with an
8
+ //! expensive `f` — e.g. the large ODE systems that arise from
9
+ //! method-of-lines/spectral discretisations of PDEs. They are fixed-step here and
10
+ //! self-start with RK4 until enough history has accumulated.
11
+ //!
12
+ //! | name | method | order | RHS/step |
13
+ //! |--------|------------------------------------------|-------|----------|
14
+ //! | `ab3` | Adams–Bashforth 3-step | 3 | 1 |
15
+ //! | `ab4` | Adams–Bashforth 4-step | 4 | 1 |
16
+ //! | `abm4` | Adams–Bashforth–Moulton (PECE) | 4 | 2 |
17
+ //!
18
+ //! References: F. Bashforth & J. C. Adams, *An Attempt to Test the Theories of
19
+ //! Capillary Action…* (Cambridge Univ. Press, 1883) — the Adams–Bashforth and
20
+ //! Adams–Moulton formulas; E. Hairer, S. P. Nørsett & G. Wanner, *Solving
21
+ //! Ordinary Differential Equations I*, 2nd ed. (1993), §III.1 (Adams methods and
22
+ //! predictor–corrector pairs).
23
+ //!
24
+ //! # Why fixed step (and how variable `h` is handled)
25
+ //!
26
+ //! The classic Adams coefficients assume a *uniform* past mesh. In the engine's
27
+ //! grid integration each output segment is one step of the running `h`, so the
28
+ //! mesh is uniform; the kernel detects a change in `h` (the final landing step of
29
+ //! a non-commensurate grid, or a `step()` driven at a new `dt`) and transparently
30
+ //! **restarts** the history with an RK4 step until `k−1` uniform slopes have
31
+ //! re-accumulated. Variable-coefficient Adams methods are a possible future
32
+ //! refinement; they would slot in behind this same kernel without an interface
33
+ //! change.
34
+
35
+ use std::collections::VecDeque;
36
+
37
+ use crate::{
38
+ register_solver, Caps, Evaluator, ProblemKind, ProblemKinds, Solver, SolverState, StepOutcome,
39
+ };
40
+
41
+ use super::control::{fixed_step, RkWork};
42
+
43
+ // Classic RK4 tableau for the self-start / history-restart bootstrap.
44
+ const RK4_C: &[f64] = &[0.0, 0.5, 0.5, 1.0];
45
+ const RK4_A: &[&[f64]] = &[&[], &[0.5], &[0.0, 0.5], &[0.0, 0.0, 1.0]];
46
+ const RK4_B: &[f64] = &[1.0 / 6.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 6.0];
47
+
48
+ /// Relative tolerance for the "is `h` the same as the previous step?" check, so
49
+ /// ULP-level grid roundoff does not spuriously restart the history every step.
50
+ const UNIFORM_REL_TOL: f64 = 1e-9;
51
+
52
+ /// Shared multistep state: the ring buffer of past slopes (most-recent first),
53
+ /// per-step scratch, and the RK4 bootstrap work. Grown to the system dimension on
54
+ /// first use and reused with no per-step allocation after warm-up.
55
+ #[derive(Default)]
56
+ struct AdamsState {
57
+ /// Past slopes `f_{n-1}, f_{n-2}, …` (most recent at the front).
58
+ hist: VecDeque<Vec<f64>>,
59
+ /// `f` at the current point, recomputed each step.
60
+ fc: Vec<f64>,
61
+ /// `f` at the predicted point (predictor–corrector only).
62
+ fp: Vec<f64>,
63
+ /// Proposed next state.
64
+ u_new: Vec<f64>,
65
+ /// RK4 bootstrap buffers.
66
+ rk: RkWork,
67
+ /// The step size of the previous accepted step (`NaN` until the first).
68
+ h_prev: f64,
69
+ }
70
+
71
+ impl AdamsState {
72
+ fn new() -> Self {
73
+ AdamsState {
74
+ h_prev: f64::NAN,
75
+ ..AdamsState::default()
76
+ }
77
+ }
78
+
79
+ fn ensure(&mut self, n: usize) {
80
+ if self.fc.len() != n {
81
+ self.fc = vec![0.0; n];
82
+ self.fp = vec![0.0; n];
83
+ self.u_new = vec![0.0; n];
84
+ self.hist.clear();
85
+ }
86
+ }
87
+
88
+ fn uniform(&self, h: f64) -> bool {
89
+ self.h_prev.is_finite() && (h - self.h_prev).abs() <= UNIFORM_REL_TOL * (1.0 + h.abs())
90
+ }
91
+
92
+ /// Push the current slope `fc` to the front of the history, capping the buffer
93
+ /// at `keep` entries (reusing the evicted buffer to avoid per-step allocation).
94
+ fn record(&mut self, keep: usize) {
95
+ let mut buf = if self.hist.len() >= keep {
96
+ self.hist
97
+ .pop_back()
98
+ .unwrap_or_else(|| vec![0.0; self.fc.len()])
99
+ } else {
100
+ vec![0.0; self.fc.len()]
101
+ };
102
+ buf.copy_from_slice(&self.fc);
103
+ self.hist.push_front(buf);
104
+ while self.hist.len() > keep {
105
+ self.hist.pop_back();
106
+ }
107
+ }
108
+ }
109
+
110
+ /// One explicit-multistep step. `ab` are the Adams–Bashforth weights applied to
111
+ /// `[f_c, f_{n-1}, …]` (the predictor); `corrector`, when present, are the
112
+ /// Adams–Moulton weights applied to `[f_pred, f_c, f_{n-1}, …]` (the PECE
113
+ /// corrector). `keep = order − 1` past slopes are retained. The history restarts
114
+ /// with an RK4 step whenever it is too short or `h` changed.
115
+ fn adams_step(
116
+ ev: &dyn Evaluator,
117
+ st: &mut SolverState,
118
+ h: f64,
119
+ state: &mut AdamsState,
120
+ keep: usize,
121
+ ab: &[f64],
122
+ corrector: Option<&[f64]>,
123
+ ) -> StepOutcome {
124
+ let n = st.u.len();
125
+ state.ensure(n);
126
+
127
+ // f at the current point.
128
+ ev.eval(&st.u, &st.p, st.t, &mut st.scratch, &mut state.fc);
129
+ if !state.fc.iter().all(|x| x.is_finite()) {
130
+ return StepOutcome::Failed;
131
+ }
132
+
133
+ if state.hist.len() < keep || !state.uniform(h) {
134
+ // A step-size *change* invalidates the uniform-mesh history (the classic
135
+ // Adams coefficients assume a uniform past spacing): discard it so the
136
+ // `hist.len() < keep` guard forces `keep` consecutive RK4 starter steps at
137
+ // the new `h` before the Adams formula resumes — without this, the next
138
+ // step would apply uniform-`h` weights to mixed-spacing slopes. A genuine
139
+ // bootstrap (`h_prev` not yet finite) or an ongoing refill leaves the
140
+ // (already new-`h`) history intact.
141
+ if state.h_prev.is_finite() && !state.uniform(h) {
142
+ state.hist.clear();
143
+ }
144
+ // Self-start / restart: one RK4 step. `fixed_step` advances st (and is the
145
+ // exact RK4 used by the `rk4` kernel, so the bootstrap is 4th-order).
146
+ match fixed_step(ev, st, h, RK4_C, RK4_A, RK4_B, &mut state.rk) {
147
+ StepOutcome::Accepted { .. } => {}
148
+ other => return other,
149
+ }
150
+ state.record(keep);
151
+ state.h_prev = h;
152
+ return StepOutcome::Accepted { h_next: h };
153
+ }
154
+
155
+ // Adams–Bashforth predictor: u_p = u + h·(ab[0]·f_c + Σ_j ab[1+j]·f_{n-1-j}).
156
+ for d in 0..n {
157
+ let mut acc = ab[0] * state.fc[d];
158
+ for (j, hslot) in state.hist.iter().enumerate() {
159
+ acc += ab[1 + j] * hslot[d];
160
+ }
161
+ state.u_new[d] = st.u[d] + h * acc;
162
+ }
163
+
164
+ if let Some(am) = corrector {
165
+ // Evaluate at the predicted point, then correct (PECE).
166
+ ev.eval(
167
+ &state.u_new,
168
+ &st.p,
169
+ st.t + h,
170
+ &mut st.scratch,
171
+ &mut state.fp,
172
+ );
173
+ if !state.fp.iter().all(|x| x.is_finite()) {
174
+ return StepOutcome::Rejected { h_next: 0.5 * h };
175
+ }
176
+ for d in 0..n {
177
+ // u_{n+1} = u + h·(am[0]·f_p + am[1]·f_c + Σ_j am[2+j]·f_{n-1-j}).
178
+ let mut acc = am[0] * state.fp[d] + am[1] * state.fc[d];
179
+ for (j, hslot) in state.hist.iter().enumerate() {
180
+ if 2 + j >= am.len() {
181
+ break;
182
+ }
183
+ acc += am[2 + j] * hslot[d];
184
+ }
185
+ state.u_new[d] = st.u[d] + h * acc;
186
+ }
187
+ }
188
+
189
+ if !state.u_new.iter().all(|x| x.is_finite()) {
190
+ return StepOutcome::Rejected { h_next: 0.5 * h };
191
+ }
192
+ st.u.copy_from_slice(&state.u_new);
193
+ st.t += h;
194
+ state.record(keep);
195
+ state.h_prev = h;
196
+ StepOutcome::Accepted { h_next: h }
197
+ }
198
+
199
+ // Adams–Bashforth weights [f_c, f_{n-1}, …].
200
+ const AB3: &[f64] = &[23.0 / 12.0, -16.0 / 12.0, 5.0 / 12.0];
201
+ const AB4: &[f64] = &[55.0 / 24.0, -59.0 / 24.0, 37.0 / 24.0, -9.0 / 24.0];
202
+ // Adams–Moulton-4 corrector weights [f_pred, f_c, f_{n-1}, f_{n-2}].
203
+ const AM4: &[f64] = &[9.0 / 24.0, 19.0 / 24.0, -5.0 / 24.0, 1.0 / 24.0];
204
+
205
+ /// Adams–Bashforth 3-step explicit multistep kernel (order 3).
206
+ #[derive(Default)]
207
+ pub struct Ab3 {
208
+ state: AdamsState,
209
+ }
210
+
211
+ impl Ab3 {
212
+ /// A fresh kernel with empty history (self-starts with RK4).
213
+ pub fn new() -> Self {
214
+ Ab3 {
215
+ state: AdamsState::new(),
216
+ }
217
+ }
218
+ }
219
+
220
+ impl Solver for Ab3 {
221
+ fn name(&self) -> &'static str {
222
+ "ab3"
223
+ }
224
+
225
+ fn caps(&self) -> Caps {
226
+ Caps::explicit(ProblemKinds::of(ProblemKind::Ode))
227
+ }
228
+
229
+ fn step(&mut self, ev: &dyn Evaluator, st: &mut SolverState, h: f64) -> StepOutcome {
230
+ adams_step(ev, st, h, &mut self.state, 2, AB3, None)
231
+ }
232
+ }
233
+
234
+ register_solver!(
235
+ "ab3",
236
+ Caps::explicit(ProblemKinds::of(ProblemKind::Ode)),
237
+ || Box::new(Ab3::new())
238
+ );
239
+
240
+ /// Adams–Bashforth 4-step explicit multistep kernel (order 4).
241
+ #[derive(Default)]
242
+ pub struct Ab4 {
243
+ state: AdamsState,
244
+ }
245
+
246
+ impl Ab4 {
247
+ /// A fresh kernel with empty history (self-starts with RK4).
248
+ pub fn new() -> Self {
249
+ Ab4 {
250
+ state: AdamsState::new(),
251
+ }
252
+ }
253
+ }
254
+
255
+ impl Solver for Ab4 {
256
+ fn name(&self) -> &'static str {
257
+ "ab4"
258
+ }
259
+
260
+ fn caps(&self) -> Caps {
261
+ Caps::explicit(ProblemKinds::of(ProblemKind::Ode))
262
+ }
263
+
264
+ fn step(&mut self, ev: &dyn Evaluator, st: &mut SolverState, h: f64) -> StepOutcome {
265
+ adams_step(ev, st, h, &mut self.state, 3, AB4, None)
266
+ }
267
+ }
268
+
269
+ register_solver!(
270
+ "ab4",
271
+ Caps::explicit(ProblemKinds::of(ProblemKind::Ode)),
272
+ || Box::new(Ab4::new())
273
+ );
274
+
275
+ /// Adams–Bashforth–Moulton predictor–corrector kernel (PECE, order 4).
276
+ #[derive(Default)]
277
+ pub struct Abm4 {
278
+ state: AdamsState,
279
+ }
280
+
281
+ impl Abm4 {
282
+ /// A fresh kernel with empty history (self-starts with RK4).
283
+ pub fn new() -> Self {
284
+ Abm4 {
285
+ state: AdamsState::new(),
286
+ }
287
+ }
288
+ }
289
+
290
+ impl Solver for Abm4 {
291
+ fn name(&self) -> &'static str {
292
+ "abm4"
293
+ }
294
+
295
+ fn caps(&self) -> Caps {
296
+ Caps::explicit(ProblemKinds::of(ProblemKind::Ode))
297
+ }
298
+
299
+ fn step(&mut self, ev: &dyn Evaluator, st: &mut SolverState, h: f64) -> StepOutcome {
300
+ adams_step(ev, st, h, &mut self.state, 3, AB4, Some(AM4))
301
+ }
302
+ }
303
+
304
+ register_solver!(
305
+ "abm4",
306
+ Caps::explicit(ProblemKinds::of(ProblemKind::Ode)),
307
+ || Box::new(Abm4::new())
308
+ );
309
+
310
+ #[cfg(test)]
311
+ mod tests {
312
+ use super::*;
313
+ use crate::explicit::testkit::{max_abs_diff, HarmonicEval};
314
+
315
+ /// Drive a kernel through a uniform fixed-step loop to `t_final`.
316
+ fn integrate_uniform(
317
+ solver: &mut dyn Solver,
318
+ ev: &dyn Evaluator,
319
+ u0: Vec<f64>,
320
+ t_final: f64,
321
+ h: f64,
322
+ ) -> Vec<f64> {
323
+ let mut st = SolverState::for_evaluator(ev, u0, 0.0, vec![]);
324
+ let n = (t_final / h).round() as usize;
325
+ for _ in 0..n {
326
+ match solver.step(ev, &mut st, h) {
327
+ StepOutcome::Accepted { .. } => {}
328
+ other => panic!("unexpected outcome {other:?}"),
329
+ }
330
+ }
331
+ st.u
332
+ }
333
+
334
+ /// Empirical convergence order from two final-state errors under h-halving.
335
+ fn measure_order(make: impl Fn() -> Box<dyn Solver>, hs: &[f64]) -> f64 {
336
+ let ev = HarmonicEval { omega: 1.0 };
337
+ let t_final = 5.0;
338
+ let mut errs = Vec::new();
339
+ for &h in hs {
340
+ let got = integrate_uniform(&mut *make(), &ev, vec![1.0, 0.0], t_final, h);
341
+ let exact = vec![t_final.cos(), -t_final.sin()];
342
+ errs.push((h, max_abs_diff(&got, &exact)));
343
+ }
344
+ let (h1, e1) = errs[errs.len() - 2];
345
+ let (h2, e2) = errs[errs.len() - 1];
346
+ (e1 / e2).ln() / (h1 / h2).ln()
347
+ }
348
+
349
+ #[test]
350
+ fn caps_are_explicit_non_adaptive_ode() {
351
+ for s in [
352
+ Box::new(Ab3::new()) as Box<dyn Solver>,
353
+ Box::new(Ab4::new()),
354
+ Box::new(Abm4::new()),
355
+ ] {
356
+ assert!(!s.caps().adaptive);
357
+ assert!(!s.caps().needs_jacobian);
358
+ assert!(s.caps().supports(ProblemKind::Ode));
359
+ }
360
+ assert_eq!(Ab3::new().name(), "ab3");
361
+ assert_eq!(Ab4::new().name(), "ab4");
362
+ assert_eq!(Abm4::new().name(), "abm4");
363
+ }
364
+
365
+ #[test]
366
+ fn ab3_is_third_order() {
367
+ let order = measure_order(|| Box::new(Ab3::new()), &[0.04, 0.02, 0.01]);
368
+ assert!((order - 3.0).abs() < 0.4, "measured AB3 order {order}");
369
+ }
370
+
371
+ #[test]
372
+ fn ab4_is_fourth_order() {
373
+ let order = measure_order(|| Box::new(Ab4::new()), &[0.04, 0.02, 0.01]);
374
+ assert!((order - 4.0).abs() < 0.4, "measured AB4 order {order}");
375
+ }
376
+
377
+ #[test]
378
+ fn abm4_is_fourth_order() {
379
+ let order = measure_order(|| Box::new(Abm4::new()), &[0.04, 0.02, 0.01]);
380
+ assert!((order - 4.0).abs() < 0.4, "measured ABM4 order {order}");
381
+ }
382
+
383
+ #[test]
384
+ fn ab4_matches_analytic_harmonic_solution() {
385
+ // A wrong coefficient breaks accuracy against the exact cos/sin solution.
386
+ let ev = HarmonicEval { omega: 1.0 };
387
+ let got = integrate_uniform(&mut Ab4::new(), &ev, vec![1.0, 0.0], 6.0, 0.005);
388
+ let exact = vec![(6.0_f64).cos(), -(6.0_f64).sin()];
389
+ assert!(
390
+ max_abs_diff(&got, &exact) < 1e-6,
391
+ "AB4 error {}",
392
+ max_abs_diff(&got, &exact)
393
+ );
394
+ }
395
+
396
+ #[test]
397
+ fn step_size_change_discards_stale_history() {
398
+ // After an h-change the next `keep` steps must be pure RK4 restarts over a
399
+ // freshly-rebuilt uniform mesh — NOT Adams steps over a contaminated
400
+ // mixed-spacing history. We assert the post-change AB4 steps are
401
+ // bit-for-bit identical to a standalone RK4 from the same state, which only
402
+ // holds if the stale h1 history was discarded (regression for the restart
403
+ // bug: record() alone never shrank the history below `keep`).
404
+ use crate::explicit::Rk4;
405
+ let ev = HarmonicEval { omega: 1.0 };
406
+ let mut ab = Ab4::new();
407
+ let mut st = SolverState::for_evaluator(&ev, vec![1.0, 0.0], 0.0, vec![]);
408
+ for _ in 0..50 {
409
+ ab.step(&ev, &mut st, 0.01); // fill a full uniform-h1 history
410
+ }
411
+ // Branch a standalone RK4 from the exact current state.
412
+ let mut rk = Rk4::new();
413
+ let mut st_rk = SolverState::for_evaluator(&ev, st.u.clone(), st.t, vec![]);
414
+ let h2 = 0.017; // a genuine step-size change
415
+ for _ in 0..3 {
416
+ // keep = 3 for AB4
417
+ ab.step(&ev, &mut st, h2);
418
+ rk.step(&ev, &mut st_rk, h2);
419
+ }
420
+ assert_eq!(
421
+ st.u, st_rk.u,
422
+ "post-h-change AB4 steps must be exact RK4 restarts (stale history discarded)"
423
+ );
424
+ }
425
+
426
+ #[test]
427
+ fn restarts_cleanly_when_step_size_changes() {
428
+ // Drive with one h, then a different h: the kernel must restart its history
429
+ // (RK4) and still integrate accurately rather than corrupt the multistep
430
+ // formula with a non-uniform mesh.
431
+ let ev = HarmonicEval { omega: 1.0 };
432
+ let mut s = Ab4::new();
433
+ let mut st = SolverState::for_evaluator(&ev, vec![1.0, 0.0], 0.0, vec![]);
434
+ for _ in 0..100 {
435
+ assert!(matches!(
436
+ s.step(&ev, &mut st, 0.01),
437
+ StepOutcome::Accepted { .. }
438
+ ));
439
+ }
440
+ for _ in 0..100 {
441
+ assert!(matches!(
442
+ s.step(&ev, &mut st, 0.013),
443
+ StepOutcome::Accepted { .. }
444
+ ));
445
+ }
446
+ let t = st.t;
447
+ let exact = vec![t.cos(), -t.sin()];
448
+ assert!(
449
+ max_abs_diff(&st.u, &exact) < 1e-4,
450
+ "post-restart error too large"
451
+ );
452
+ }
453
+ }
@@ -0,0 +1,189 @@
1
+ //! Adaptive Bogacki–Shampine 3(2) — the `ode23` pair, registered as `bs3`.
2
+ //!
3
+ //! A three-stage, third-order method with an embedded second-order estimate and
4
+ //! the FSAL ("first same as last") structure: the fourth stage is `f` at the
5
+ //! proposed solution point (`c₄ = 1`, `a₄ = b`), which gives the embedded
6
+ //! 2nd-order estimate for step control. It is efficient at crude-to-moderate
7
+ //! tolerances and is the method MATLAB exposes as `ode23`. (FSAL *reuse* of the
8
+ //! last stage as the next step's first stage — saving one evaluation per step —
9
+ //! is not exploited by the shared [`adaptive_step`](super::control::adaptive_step)
10
+ //! loop, which recomputes stage 0 each step; this kernel therefore costs four
11
+ //! evaluations per accepted step.)
12
+ //!
13
+ //! Reference: P. Bogacki & L. F. Shampine, "A 3(2) pair of Runge–Kutta
14
+ //! formulas", *Appl. Math. Lett.* **2**(4) (1989) 321–325.
15
+ //!
16
+ //! The tableau is written in the 4-stage FSAL form (`c₄ = 1`, `a₄ = b`), so the
17
+ //! error-weight vector `e = b − b̂` over the four stages is the embedded
18
+ //! 2nd-order estimate.
19
+
20
+ use crate::{
21
+ register_solver, Caps, Evaluator, ProblemKind, ProblemKinds, Solver, SolverState, StepOutcome,
22
+ };
23
+
24
+ use super::control::{adaptive_step, RkWork};
25
+
26
+ // Bogacki–Shampine 3(2) nodes (c₄ = 1; the 4th is the FSAL solution stage).
27
+ const C: &[f64] = &[0.0, 0.5, 0.75, 1.0];
28
+
29
+ // Lower-triangular stage coefficients; a[3] equals B (FSAL solution stage).
30
+ const A: &[&[f64]] = &[
31
+ &[],
32
+ &[0.5],
33
+ &[0.0, 0.75],
34
+ &[2.0 / 9.0, 1.0 / 3.0, 4.0 / 9.0],
35
+ ];
36
+
37
+ // 3rd-order solution weights (b₄ = 0; the FSAL stage carries no solution weight).
38
+ const B: &[f64] = &[2.0 / 9.0, 1.0 / 3.0, 4.0 / 9.0, 0.0];
39
+
40
+ // Error weights e = b(3rd) − b̂(2nd), with b̂ = [7/24, 1/4, 1/3, 1/8].
41
+ const E: &[f64] = &[
42
+ 2.0 / 9.0 - 7.0 / 24.0,
43
+ 1.0 / 3.0 - 1.0 / 4.0,
44
+ 4.0 / 9.0 - 1.0 / 3.0,
45
+ 0.0 - 1.0 / 8.0,
46
+ ];
47
+
48
+ // Controller exponent −1/(error_estimator_order + 1) with estimator order 2.
49
+ const ERR_EXPONENT: f64 = -1.0 / 3.0;
50
+
51
+ const DEFAULT_RTOL: f64 = 1e-3;
52
+ const DEFAULT_ATOL: f64 = 1e-6;
53
+
54
+ /// Adaptive Bogacki–Shampine 3(2) kernel.
55
+ pub struct Bs3 {
56
+ rtol: f64,
57
+ atol: f64,
58
+ work: RkWork,
59
+ }
60
+
61
+ impl Bs3 {
62
+ /// A kernel with the default tolerances (`rtol = 1e-3`, `atol = 1e-6`).
63
+ pub fn new() -> Self {
64
+ Bs3::with_tolerances(DEFAULT_RTOL, DEFAULT_ATOL)
65
+ }
66
+
67
+ /// A kernel with explicit tolerances (see [`Rk45::with_tolerances`]).
68
+ ///
69
+ /// [`Rk45::with_tolerances`]: super::rk45::Rk45::with_tolerances
70
+ pub fn with_tolerances(rtol: f64, atol: f64) -> Self {
71
+ Bs3 {
72
+ rtol,
73
+ atol,
74
+ work: RkWork::new(),
75
+ }
76
+ }
77
+ }
78
+
79
+ impl Default for Bs3 {
80
+ fn default() -> Self {
81
+ Bs3::new()
82
+ }
83
+ }
84
+
85
+ impl Solver for Bs3 {
86
+ fn name(&self) -> &'static str {
87
+ "bs3"
88
+ }
89
+
90
+ fn caps(&self) -> Caps {
91
+ Caps::explicit(ProblemKinds::of(ProblemKind::Ode)).adaptive()
92
+ }
93
+
94
+ fn step(&mut self, ev: &dyn Evaluator, st: &mut SolverState, h: f64) -> StepOutcome {
95
+ adaptive_step(
96
+ ev,
97
+ st,
98
+ h,
99
+ C,
100
+ A,
101
+ B,
102
+ E,
103
+ ERR_EXPONENT,
104
+ self.rtol,
105
+ self.atol,
106
+ &mut self.work,
107
+ )
108
+ }
109
+ }
110
+
111
+ register_solver!(
112
+ "bs3",
113
+ Caps::explicit(ProblemKinds::of(ProblemKind::Ode)).adaptive(),
114
+ || Box::new(Bs3::new())
115
+ );
116
+
117
+ #[cfg(test)]
118
+ mod tests {
119
+ use super::*;
120
+ use crate::explicit::testkit::{
121
+ converges_at_order, fixed_propagate, integrate_adaptive, max_abs_diff, HarmonicEval,
122
+ };
123
+
124
+ #[test]
125
+ fn caps_are_explicit_adaptive_ode() {
126
+ let s = Bs3::new();
127
+ assert_eq!(s.name(), "bs3");
128
+ assert!(s.caps().adaptive);
129
+ }
130
+
131
+ #[test]
132
+ fn tableau_is_internally_consistent() {
133
+ let sum_b: f64 = B.iter().sum();
134
+ assert!((sum_b - 1.0).abs() < 1e-15, "Σb = {sum_b}");
135
+ let sum_e: f64 = E.iter().sum();
136
+ assert!(sum_e.abs() < 1e-15, "Σe = {sum_e}");
137
+ for (i, row) in A.iter().enumerate() {
138
+ let s: f64 = row.iter().sum();
139
+ assert!((s - C[i]).abs() < 1e-15, "row {i}: Σa = {s}, c = {}", C[i]);
140
+ }
141
+ }
142
+
143
+ #[test]
144
+ fn third_order_convergence_of_the_propagated_solution() {
145
+ let ev = HarmonicEval { omega: 1.0 };
146
+ let order = converges_at_order(
147
+ |st, h, work| fixed_propagate(&ev, st, h, C, A, B, work),
148
+ &ev,
149
+ vec![1.0, 0.0],
150
+ 2.0,
151
+ &[0.1, 0.05, 0.025],
152
+ |t| vec![t.cos(), -t.sin()],
153
+ );
154
+ assert!((order - 3.0).abs() < 0.35, "measured BS3 order {order}");
155
+ }
156
+
157
+ #[test]
158
+ fn embedded_estimate_is_second_order() {
159
+ let ev = HarmonicEval { omega: 1.0 };
160
+ let bhat: Vec<f64> = B.iter().zip(E).map(|(b, e)| b - e).collect();
161
+ let order = converges_at_order(
162
+ |st, h, work| fixed_propagate(&ev, st, h, C, A, &bhat, work),
163
+ &ev,
164
+ vec![1.0, 0.0],
165
+ 2.0,
166
+ &[0.05, 0.025, 0.0125],
167
+ |t| vec![t.cos(), -t.sin()],
168
+ );
169
+ assert!(
170
+ (order - 2.0).abs() < 0.35,
171
+ "measured BS3 embedded order {order}"
172
+ );
173
+ }
174
+
175
+ #[test]
176
+ fn adaptive_run_matches_analytic_harmonic_solution() {
177
+ let ev = HarmonicEval { omega: 1.0 };
178
+ let mut s = Bs3::with_tolerances(1e-10, 1e-12);
179
+ let t_final = 12.0;
180
+ let mut st = SolverState::for_evaluator(&ev, vec![1.0, 0.0], 0.0, vec![]);
181
+ integrate_adaptive(&mut s, &ev, &mut st, t_final, 0.05);
182
+ let exact = vec![t_final.cos(), -t_final.sin()];
183
+ assert!(
184
+ max_abs_diff(&st.u, &exact) < 1e-5,
185
+ "adaptive BS3 error {}",
186
+ max_abs_diff(&st.u, &exact)
187
+ );
188
+ }
189
+ }