yabi-torch 0.1.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 (199) hide show
  1. yabi_torch-0.1.0/.clang-format +10 -0
  2. yabi_torch-0.1.0/.github/workflows/ci.yml +53 -0
  3. yabi_torch-0.1.0/.gitignore +24 -0
  4. yabi_torch-0.1.0/BASELINE.md +121 -0
  5. yabi_torch-0.1.0/BUILD.bazel +8 -0
  6. yabi_torch-0.1.0/CMakeLists.txt +425 -0
  7. yabi_torch-0.1.0/LICENSE +126 -0
  8. yabi_torch-0.1.0/NOTICE +7 -0
  9. yabi_torch-0.1.0/PKG-INFO +1054 -0
  10. yabi_torch-0.1.0/README.md +1034 -0
  11. yabi_torch-0.1.0/benchmarks/TELEMETRY.md +15 -0
  12. yabi_torch-0.1.0/benchmarks/report.py +98 -0
  13. yabi_torch-0.1.0/benchmarks/telemetry.cpp +192 -0
  14. yabi_torch-0.1.0/benchmarks/telemetry.csv +9 -0
  15. yabi_torch-0.1.0/benchmarks/work_precision.png +0 -0
  16. yabi_torch-0.1.0/bindings/python/test_autograd.py +66 -0
  17. yabi_torch-0.1.0/bindings/python/test_batch_parallel.py +88 -0
  18. yabi_torch-0.1.0/bindings/python/test_cuda.py +89 -0
  19. yabi_torch-0.1.0/bindings/python/test_nn_module.py +125 -0
  20. yabi_torch-0.1.0/bindings/python/test_solve_ivp.py +121 -0
  21. yabi_torch-0.1.0/bindings/python/test_yabi.py +42 -0
  22. yabi_torch-0.1.0/bindings/python/torch_wrapper.py +79 -0
  23. yabi_torch-0.1.0/bindings/python/yabi.cpp +222 -0
  24. yabi_torch-0.1.0/examples/basic.cpp +48 -0
  25. yabi_torch-0.1.0/examples/dump_trajectories.cpp +160 -0
  26. yabi_torch-0.1.0/examples/heat1d_dense_analytic_jac.cpp +42 -0
  27. yabi_torch-0.1.0/examples/heat1d_dense_literature.cpp +40 -0
  28. yabi_torch-0.1.0/examples/heat1d_umfpack_literature.cpp +42 -0
  29. yabi_torch-0.1.0/examples/heat1d_umfpack_sens2.cpp +40 -0
  30. yabi_torch-0.1.0/examples/heat1d_umfpack_sens3.cpp +38 -0
  31. yabi_torch-0.1.0/examples/heat1d_umfpack_sens_dense_equiv.cpp +40 -0
  32. yabi_torch-0.1.0/examples/heat1d_umfpack_sens_simple.cpp +38 -0
  33. yabi_torch-0.1.0/examples/heat_1d_sparse_sens_umfpack.cpp +46 -0
  34. yabi_torch-0.1.0/examples/heat_1d_sparse_umfpack.cpp +43 -0
  35. yabi_torch-0.1.0/examples/heat_1d_umfpack_forward_sens.cpp +44 -0
  36. yabi_torch-0.1.0/examples/nonautonomous.cpp +52 -0
  37. yabi_torch-0.1.0/examples/plot_brusselator_traj.cpp +25 -0
  38. yabi_torch-0.1.0/examples/plot_heat_traj.cpp +43 -0
  39. yabi_torch-0.1.0/examples/plot_oscillator_traj.cpp +34 -0
  40. yabi_torch-0.1.0/examples/plot_robertson_traj.cpp +47 -0
  41. yabi_torch-0.1.0/examples/sensitivity_demo.cpp +300 -0
  42. yabi_torch-0.1.0/include/yabi/abstract_rhs.hpp +203 -0
  43. yabi_torch-0.1.0/include/yabi/amgcl_linear_solver.hpp +72 -0
  44. yabi_torch-0.1.0/include/yabi/bdf_integrator.hpp +433 -0
  45. yabi_torch-0.1.0/include/yabi/cppad_rhs.hpp +386 -0
  46. yabi_torch-0.1.0/include/yabi/csc_matrix.hpp +159 -0
  47. yabi_torch-0.1.0/include/yabi/cuda_krylov_solver.hpp +440 -0
  48. yabi_torch-0.1.0/include/yabi/cuda_linear_solver.hpp +176 -0
  49. yabi_torch-0.1.0/include/yabi/cudss_linear_solver.hpp +135 -0
  50. yabi_torch-0.1.0/include/yabi/detail/adjoint_engine.hpp +850 -0
  51. yabi_torch-0.1.0/include/yabi/detail/continuous_adjoint.hpp +436 -0
  52. yabi_torch-0.1.0/include/yabi/detail/core.ipp +1120 -0
  53. yabi_torch-0.1.0/include/yabi/detail/dense_linear_solver.ipp +76 -0
  54. yabi_torch-0.1.0/include/yabi/detail/integrator_config.hpp +79 -0
  55. yabi_torch-0.1.0/include/yabi/detail/klu_linear_solver.ipp +145 -0
  56. yabi_torch-0.1.0/include/yabi/detail/newton.ipp +50 -0
  57. yabi_torch-0.1.0/include/yabi/detail/newton_solver.hpp +1933 -0
  58. yabi_torch-0.1.0/include/yabi/detail/nordsieck_state.hpp +259 -0
  59. yabi_torch-0.1.0/include/yabi/detail/replay_tape.hpp +78 -0
  60. yabi_torch-0.1.0/include/yabi/detail/sensitivity_engine.hpp +1107 -0
  61. yabi_torch-0.1.0/include/yabi/detail/sensitivity_forward.ipp +40 -0
  62. yabi_torch-0.1.0/include/yabi/detail/sensitivity_reverse.ipp +37 -0
  63. yabi_torch-0.1.0/include/yabi/detail/sensitivity_setup.ipp +111 -0
  64. yabi_torch-0.1.0/include/yabi/detail/step_control.ipp +119 -0
  65. yabi_torch-0.1.0/include/yabi/detail/step_controller.hpp +598 -0
  66. yabi_torch-0.1.0/include/yabi/detail/umfpack_linear_solver.ipp +167 -0
  67. yabi_torch-0.1.0/include/yabi/external_sparse_solver.hpp +115 -0
  68. yabi_torch-0.1.0/include/yabi/ginkgo_linear_solver.hpp +94 -0
  69. yabi_torch-0.1.0/include/yabi/jacobian_backend.hpp +42 -0
  70. yabi_torch-0.1.0/include/yabi/linear_algebra.hpp +177 -0
  71. yabi_torch-0.1.0/include/yabi/linear_solver.hpp +158 -0
  72. yabi_torch-0.1.0/include/yabi/log.hpp +92 -0
  73. yabi_torch-0.1.0/include/yabi/matrix_free_operator.hpp +45 -0
  74. yabi_torch-0.1.0/include/yabi/matrix_free_solver.hpp +631 -0
  75. yabi_torch-0.1.0/include/yabi/ode_formulation.hpp +13 -0
  76. yabi_torch-0.1.0/include/yabi/preconditioner.hpp +101 -0
  77. yabi_torch-0.1.0/include/yabi/sensitivity_linearization.hpp +61 -0
  78. yabi_torch-0.1.0/include/yabi/sensitivity_manual.hpp +14 -0
  79. yabi_torch-0.1.0/include/yabi/sparsity_pattern.hpp +48 -0
  80. yabi_torch-0.1.0/include/yabi/user_ode.hpp +23 -0
  81. yabi_torch-0.1.0/include/yabi/user_ode_rhs.hpp +358 -0
  82. yabi_torch-0.1.0/pyplot/plot_all.py +217 -0
  83. yabi_torch-0.1.0/pyproject.toml +45 -0
  84. yabi_torch-0.1.0/python/yabi_torch/__init__.py +17 -0
  85. yabi_torch-0.1.0/python/yabi_torch/_solver.py +194 -0
  86. yabi_torch-0.1.0/reproduce_benchmarks.py +88 -0
  87. yabi_torch-0.1.0/run_examples.sh +52 -0
  88. yabi_torch-0.1.0/run_tests.sh +47 -0
  89. yabi_torch-0.1.0/src/bdf_integrator.cpp +24 -0
  90. yabi_torch-0.1.0/src/bdf_integrator_pure.cpp +17 -0
  91. yabi_torch-0.1.0/tests/CMakeLists.txt +204 -0
  92. yabi_torch-0.1.0/tests/README.md +111 -0
  93. yabi_torch-0.1.0/tests/component/amgcl_solver.cpp +93 -0
  94. yabi_torch-0.1.0/tests/component/cuda_krylov_solver.cpp +155 -0
  95. yabi_torch-0.1.0/tests/component/cuda_parallel.cpp +124 -0
  96. yabi_torch-0.1.0/tests/component/cuda_solver.cpp +95 -0
  97. yabi_torch-0.1.0/tests/component/cudss_solver.cpp +94 -0
  98. yabi_torch-0.1.0/tests/component/ginkgo_solver.cpp +93 -0
  99. yabi_torch-0.1.0/tests/component/matrix_free_jfree.cpp +251 -0
  100. yabi_torch-0.1.0/tests/component/matrix_free_newton.cpp +227 -0
  101. yabi_torch-0.1.0/tests/component/matrix_free_pure.cpp +220 -0
  102. yabi_torch-0.1.0/tests/component/native_sparse.cpp +200 -0
  103. yabi_torch-0.1.0/tests/component/parallel_sensitivity.cpp +126 -0
  104. yabi_torch-0.1.0/tests/component/rhs_backend_agreement.cpp +106 -0
  105. yabi_torch-0.1.0/tests/component/sensitivity_agreement.cpp +179 -0
  106. yabi_torch-0.1.0/tests/component/solver_matrix.cpp +165 -0
  107. yabi_torch-0.1.0/tests/component/suitesparse_sparse.cpp +207 -0
  108. yabi_torch-0.1.0/tests/system/01_linear_autonomous.cpp +43 -0
  109. yabi_torch-0.1.0/tests/system/02_linear_system.cpp +46 -0
  110. yabi_torch-0.1.0/tests/system/03_oscillator.cpp +42 -0
  111. yabi_torch-0.1.0/tests/system/04_nonautonomous.cpp +42 -0
  112. yabi_torch-0.1.0/tests/system/05_stiff_robertson_autonomous.cpp +76 -0
  113. yabi_torch-0.1.0/tests/system/06_van_der_pol_autonomous.cpp +64 -0
  114. yabi_torch-0.1.0/tests/system/07_sensitivity_forward_reverse_simultaneous.cpp +105 -0
  115. yabi_torch-0.1.0/tests/system/08_robertson_mdt_style.cpp +89 -0
  116. yabi_torch-0.1.0/tests/system/09_van_der_pol_mdt_style.cpp +78 -0
  117. yabi_torch-0.1.0/tests/system/10_zero_state_guard.cpp +111 -0
  118. yabi_torch-0.1.0/tests/system/11_tolerance_and_order.cpp +171 -0
  119. yabi_torch-0.1.0/tests/system/12_brusselator_autonomous.cpp +44 -0
  120. yabi_torch-0.1.0/tests/system/13_hires_autonomous.cpp +51 -0
  121. yabi_torch-0.1.0/tests/system/14_prothero_robinson_autonomous.cpp +39 -0
  122. yabi_torch-0.1.0/tests/system/15_oregonator_autonomous.cpp +59 -0
  123. yabi_torch-0.1.0/tests/system/16_damped_pendulum_stiff.cpp +34 -0
  124. yabi_torch-0.1.0/tests/system/17_polanyi_wigner_large_sparse.cpp +36 -0
  125. yabi_torch-0.1.0/tests/system/18_network_large_parametrized.cpp +36 -0
  126. yabi_torch-0.1.0/tests/system/20_brusselator_sensitivity.cpp +71 -0
  127. yabi_torch-0.1.0/tests/system/21_hi_res_sensitivity.cpp +80 -0
  128. yabi_torch-0.1.0/tests/system/22_prothero_robinson_sensitivity.cpp +71 -0
  129. yabi_torch-0.1.0/tests/system/23_oregonator_sensitivity.cpp +72 -0
  130. yabi_torch-0.1.0/tests/system/24_damped_pendulum_sensitivity.cpp +72 -0
  131. yabi_torch-0.1.0/tests/system/25_polanyi_wigner_sensitivity.cpp +74 -0
  132. yabi_torch-0.1.0/tests/system/26_network_sensitivity.cpp +86 -0
  133. yabi_torch-0.1.0/tests/system/27_prothero_robinson_central_diff.cpp +59 -0
  134. yabi_torch-0.1.0/tests/system/28_second_order_sensitivity.cpp +86 -0
  135. yabi_torch-0.1.0/tests/system/29_diagnostics.cpp +191 -0
  136. yabi_torch-0.1.0/tests/system/30_e5_stiff_autonomous.cpp +55 -0
  137. yabi_torch-0.1.0/tests/system/31_dense_output.cpp +141 -0
  138. yabi_torch-0.1.0/tests/system/32_fixed_order.cpp +120 -0
  139. yabi_torch-0.1.0/tests/system/33_higher_order_sensitivity.cpp +193 -0
  140. yabi_torch-0.1.0/tests/system/34_continuous_adjoint.cpp +196 -0
  141. yabi_torch-0.1.0/tests/system/35_second_order_continuous_adjoint.cpp +116 -0
  142. yabi_torch-0.1.0/tests/system/36_second_order_forward.cpp +87 -0
  143. yabi_torch-0.1.0/tests/system/37_jacobian_backends.cpp +141 -0
  144. yabi_torch-0.1.0/tests/system/38_higher_order_forward.cpp +123 -0
  145. yabi_torch-0.1.0/tests/system/39_generated_higher_order.cpp +77 -0
  146. yabi_torch-0.1.0/tests/system/40_heat_equation.cpp +89 -0
  147. yabi_torch-0.1.0/tests/system/41_two_body_orbit.cpp +74 -0
  148. yabi_torch-0.1.0/tests/system/42_medakzo.cpp +110 -0
  149. yabi_torch-0.1.0/tests/system/43_pollu.cpp +138 -0
  150. yabi_torch-0.1.0/tests/system/44_beam.cpp +171 -0
  151. yabi_torch-0.1.0/tests/system/45_emep.cpp +551 -0
  152. yabi_torch-0.1.0/tests/system/46_stiffness_sweep.cpp +76 -0
  153. yabi_torch-0.1.0/tests/system/47_line_search.cpp +80 -0
  154. yabi_torch-0.1.0/tests/system/48_parallel_sensitivity.cpp +123 -0
  155. yabi_torch-0.1.0/tests/system/README.md +42 -0
  156. yabi_torch-0.1.0/tests/system/cuda_krylov_heat.cpp +95 -0
  157. yabi_torch-0.1.0/tests/system/generated_cubic.hpp +60 -0
  158. yabi_torch-0.1.0/tests/system/manual_sensitivity_demo.cpp +15 -0
  159. yabi_torch-0.1.0/tests/system/manual_sensitivity_pure.cpp +30 -0
  160. yabi_torch-0.1.0/tests/system/pure_12_brusselator_autonomous.cpp +55 -0
  161. yabi_torch-0.1.0/tests/system/pure_13_hires_autonomous.cpp +122 -0
  162. yabi_torch-0.1.0/tests/system/pure_14_prothero_robinson_autonomous.cpp +4 -0
  163. yabi_torch-0.1.0/tests/system/pure_15_oregonator_autonomous.cpp +4 -0
  164. yabi_torch-0.1.0/tests/system/pure_16_damped_pendulum_stiff.cpp +4 -0
  165. yabi_torch-0.1.0/tests/system/pure_17_polanyi_wigner_large_sparse.cpp +4 -0
  166. yabi_torch-0.1.0/tests/system/pure_18_network_large_parametrized.cpp +4 -0
  167. yabi_torch-0.1.0/tests/system/pure_19_continuous_adjoint.cpp +57 -0
  168. yabi_torch-0.1.0/tests/system/pure_20_nonautonomous_adjoint.cpp +69 -0
  169. yabi_torch-0.1.0/tests/system/pure_21_second_order_adjoint.cpp +63 -0
  170. yabi_torch-0.1.0/tests/system/pure_22_second_order_forward.cpp +30 -0
  171. yabi_torch-0.1.0/tests/system/pure_23_jacobian_backends.cpp +144 -0
  172. yabi_torch-0.1.0/tests/system/pure_24_higher_order_forward.cpp +63 -0
  173. yabi_torch-0.1.0/tests/unit/engine_fixture.hpp +107 -0
  174. yabi_torch-0.1.0/tests/unit/test_abstract_rhs.cpp +110 -0
  175. yabi_torch-0.1.0/tests/unit/test_adjoint_engine.cpp +43 -0
  176. yabi_torch-0.1.0/tests/unit/test_config_statistics.cpp +60 -0
  177. yabi_torch-0.1.0/tests/unit/test_continuous_adjoint_rhs.cpp +99 -0
  178. yabi_torch-0.1.0/tests/unit/test_cppad_rhs_hooks.cpp +121 -0
  179. yabi_torch-0.1.0/tests/unit/test_cppad_rhs_sparsity.cpp +293 -0
  180. yabi_torch-0.1.0/tests/unit/test_csc_matrix.cpp +114 -0
  181. yabi_torch-0.1.0/tests/unit/test_external_sparse_solver.cpp +90 -0
  182. yabi_torch-0.1.0/tests/unit/test_krylov_solver.cpp +276 -0
  183. yabi_torch-0.1.0/tests/unit/test_linear_algebra.cpp +134 -0
  184. yabi_torch-0.1.0/tests/unit/test_linear_solver.cpp +121 -0
  185. yabi_torch-0.1.0/tests/unit/test_matrix_free_operator.cpp +54 -0
  186. yabi_torch-0.1.0/tests/unit/test_newton_solver.cpp +43 -0
  187. yabi_torch-0.1.0/tests/unit/test_nordsieck_state.cpp +100 -0
  188. yabi_torch-0.1.0/tests/unit/test_preconditioner.cpp +373 -0
  189. yabi_torch-0.1.0/tests/unit/test_property_sparse.cpp +103 -0
  190. yabi_torch-0.1.0/tests/unit/test_replay_tape.cpp +50 -0
  191. yabi_torch-0.1.0/tests/unit/test_sensitivity_engine.cpp +61 -0
  192. yabi_torch-0.1.0/tests/unit/test_sensitivity_linearization.cpp +268 -0
  193. yabi_torch-0.1.0/tests/unit/test_sparsity_pattern.cpp +50 -0
  194. yabi_torch-0.1.0/tests/unit/test_step_controller.cpp +39 -0
  195. yabi_torch-0.1.0/tests/unit/test_support.hpp +46 -0
  196. yabi_torch-0.1.0/tests/unit/test_trajectory.cpp +102 -0
  197. yabi_torch-0.1.0/tests/unit/test_user_ode_rhs.cpp +214 -0
  198. yabi_torch-0.1.0/tests/unit/test_user_ode_rhs_hooks.cpp +138 -0
  199. yabi_torch-0.1.0/tools/generate_derivatives.py +191 -0
@@ -0,0 +1,10 @@
1
+ BasedOnStyle: LLVM
2
+ IndentWidth: 4
3
+ ColumnLimit: 100
4
+ BreakBeforeBraces: Attach
5
+ AllowShortFunctionsOnASingleLine: None
6
+ IndentCaseLabels: false
7
+ SpaceAfterCStyleCast: false
8
+ IndentPPDirectives: BeforeHash
9
+ KeepEmptyLinesAtTheStartOfBlocks: false
10
+ MaxEmptyLinesToKeep: 1
@@ -0,0 +1,53 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ build-test:
9
+ name: build-test (CppAD=${{ matrix.cppad }})
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ # Build and test both configurations: the full CppAD build and the
15
+ # pure (YABI_USE_CPPAD=OFF) build. The pure configuration also runs
16
+ # the CppAD-free unit and system tests.
17
+ cppad: ["ON", "OFF"]
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Install dependencies
22
+ run: |
23
+ sudo apt-get update
24
+ sudo apt-get install -y build-essential cmake pkg-config \
25
+ liblapack-dev libblas-dev libcppad-dev
26
+ # SuiteSparse enables the UMFPACK/KLU symbolic-reuse component test.
27
+ if [ "${{ matrix.cppad }}" = "ON" ]; then
28
+ sudo apt-get install -y libsuitesparse-dev
29
+ fi
30
+
31
+ - name: Configure
32
+ run: cmake -S . -B build -DYABI_USE_CPPAD=${{ matrix.cppad }}
33
+
34
+ - name: Build
35
+ run: cmake --build build -j"$(nproc)"
36
+
37
+ - name: Test unit tier
38
+ run: ctest --test-dir build -L unit --output-on-failure --no-tests=error
39
+
40
+ - name: Test component tier
41
+ # The component tier also runs in the pure build: matrix_free_pure /
42
+ # matrix_free_jfree exercise the CppAD-free matrix-free Newton path.
43
+ run: ctest --test-dir build -L component --output-on-failure --no-tests=error
44
+
45
+ - name: Test system tier
46
+ run: ctest --test-dir build -L system --output-on-failure --no-tests=error
47
+
48
+ - name: Test benchmark (evidence) tier
49
+ if: matrix.cppad == 'ON'
50
+ run: ctest --test-dir build -L benchmark --output-on-failure --no-tests=error
51
+
52
+ - name: Test examples
53
+ run: ctest --test-dir build -L example --output-on-failure --no-tests=error
@@ -0,0 +1,24 @@
1
+ build/
2
+ build-pure/
3
+ build-py/
4
+ build-py-cuda/
5
+ dist/
6
+ bin/
7
+ build-cmake/
8
+ cmake-build-*/
9
+ *.o
10
+ *.a
11
+ *.so
12
+ *.so.*
13
+ *.dylib
14
+ *.exe
15
+ compile_commands.json
16
+ CMakeCache.txt
17
+ CMakeFiles/
18
+ cmake_install.cmake
19
+ plots/
20
+ trajectories/
21
+ work_precision_diagram.png
22
+
23
+ __pycache__/
24
+ *.pyc
@@ -0,0 +1,121 @@
1
+ # YABI-BDF numerical baseline (T30 decomposition — behaviour-preservation anchor)
2
+
3
+ Captured 2026-11-04, before the T30 `BDFIntegrator` decomposition refactor. **These values must not differ after the
4
+ refactor.** The refactor is purely structural (separation of responsibilities); any numeric drift is a regression.
5
+
6
+ ## Provenance — comparison against the initial commit (`cb357e3`)
7
+
8
+ The initial commit was built in isolation and its outputs compared against this baseline. **They differ**, and the
9
+ difference is fully explained (no unexplained regression):
10
+
11
+ | anchor | initial `cb357e3` | baseline (`main`) |
12
+ |---|---|---|
13
+ | `yabi_basic` error | `4.32592239807406e-10` | `6.53219478508049e-10` |
14
+ | `13_hires` y1 | `0.000737133157726` | `0.000737133286353` |
15
+ | `05_robertson` | `y1=0.107300429907 …` | identical |
16
+ | `12_brusselator` | `y1(5) = 0.938299 …` | identical |
17
+ | `15_oregonator` | `t=1 => (112559.4758, …)` | identical |
18
+
19
+ The change is introduced by the **first commit after the initial one**, `8f5492c`
20
+ `fix(step-control): reduce BDF order on rejected steps` — an intentional correctness fix. Every
21
+ commit in the history was built and run: there are exactly two value regimes, the switch occurs at `8f5492c`, and no
22
+ later commit (through `main`) changes either anchor.
23
+
24
+ **Do not read the smaller initial error as “better”.** The initial `yabi_basic` error (`4.33e-10`) is *smaller* than the
25
+ baseline (`6.53e-10`), but both are ~`1e-10` absolute against `y(2) ≈ 0.368` (≈0.6 ppm) — at the requested tolerance
26
+ floor. A different sequence of accepted steps/orders simply lands closer by coincidence; global error is not minimised
27
+ by the controller. The current algorithm is the correct one (order reduction on rejection), so the
28
+ baseline is `main` from `8f5492c` onward, **not** `cb357e3`. Testbenches `31`/`32` did not exist at the initial commit,
29
+ and `28` was a different (framework-only) program, so they have no initial-commit counterpart.
30
+
31
+ ## Test suites
32
+
33
+ Tests are organised in three tiers below `tests/` and selected with ctest labels:
34
+
35
+ | tier | label | contents |
36
+ |---|---|---|
37
+ | unit | `unit` | isolated component contracts (config, replay tape, Nordsieck state, step controller, Newton, sensitivity, adjoint, dense/CSC solver, sparsity, CscMatrix, CppAD RHS sparsity/batch `J·V`, `UserODERHS` sparsity/`J·v`/batch `J·V`, Krylov solver, pluggable preconditioner/forcing, sensitivity linearization) |
38
+ | component | `component` | cross-component integration (`native_sparse`, `matrix_free_newton`, `matrix_free_pure`, `matrix_free_jfree`, plus `suitesparse_sparse` when SuiteSparse is available) |
39
+ | system | `system` | mathematical benchmarks and regressions (`tests/system/`) |
40
+
41
+ - `ctest --test-dir build` (CppAD): **64/64 pass** — 15 unit, 5 component (including
42
+ `suitesparse_sparse` when SuiteSparse is available), 41 system, 3 example.
43
+ - `ctest --test-dir build-pure` (no CppAD): **26/26 pass** — 14 unit, 2 component,
44
+ 7 pure system, 3 example.
45
+
46
+ T32c (pluggable/J-free preconditioners and forcing) and T32d (batch/sparse/matrix-free sensitivity interfaces)
47
+ are additive: every pre-existing numeric anchor below is unchanged, `yabi_basic` remains
48
+ `6.53219478508049e-10`, and the sensitivity replay values are identical.
49
+
50
+ T29 (RAII memory management) and the arbitrary-order (3+) forward/reverse sensitivity work are likewise
51
+ behaviour-preserving for the *nominal* trajectory: every anchor below except `28_second_order_sensitivity`
52
+ is bit-identical after both. `28` itself was extended from second order to orders 1–4 and its `forward(2)`
53
+ now runs the full-Taylor replay, so its printed `forward(2)` relative error moved from `5.84e-09` to
54
+ `9.95e-09` (the value `8` is unchanged, still ≈1e-9).
55
+
56
+ ## Key numeric anchors (representative outputs, verbatim)
57
+
58
+ `yabi_basic` (decay `y'=-y`, t=2):
59
+ ```
60
+ y(2) = 0.367879440518223
61
+ exact = 0.367879441171442
62
+ error = 6.53219478508049e-10
63
+ ```
64
+
65
+ `05_stiff_robertson_autonomous` (t=1e4):
66
+ ```
67
+ y1=0.107300429907 y2=4.800167041e-07 y3=0.892699090076 sum=1
68
+ ```
69
+
70
+ `12_brusselator_autonomous` (t=5):
71
+ ```
72
+ y1(5) = 0.938299, y2(5) = 4.52728
73
+ ```
74
+
75
+ `13_hires_autonomous` (t=321.812):
76
+ ```
77
+ y = (0.000737133286353,0.000144248972571,5.88876753153e-05,0.00117565512229,0.00238641718911,0.00623915986836,0.00285004111321,0.00284995888679)
78
+ ```
79
+
80
+ `15_oregonator_autonomous`:
81
+ ```
82
+ t=1 => y=(112559.4758,0.05952720969,6712.895565) OK
83
+ t=5 => y=(1.001753339,571.3421806,23962.69912) OK
84
+ t=10 => y=(1.000670524,1492.35853,10713.98688) OK
85
+ ```
86
+
87
+ `28_second_order_sensitivity` (orders 1–4):
88
+ ```
89
+ forward(1) = 4 (exact 4) rel=1.55578e-09
90
+ forward(2) = 8 (exact 8) rel=9.95208e-09
91
+ forward(3) = 24 (exact 24) rel=4.2256e-08
92
+ forward(4) = 96 (exact 96) rel=1.32408e-07
93
+ reverse(4) = [order1 4 order2 8 order3 24 order4 96]
94
+ ```
95
+
96
+ `31_dense_output` (interior points of the last step):
97
+ ```
98
+ theta=-0.50 t=2.997174190003 y=0.049927955809049 exact=0.049927956130660 err=3.216e-10 OK
99
+ theta=-0.25 t=2.998587095001 y=0.049857462162821 exact=0.049857462484014 err=3.212e-10 OK
100
+ ```
101
+
102
+ `32_fixed_order` (per-order convergence slopes):
103
+ ```
104
+ order 5: slope 5.229 order 6: slope 5.983
105
+ ```
106
+
107
+ ## Additive test/evidence + opt-in damped Newton (T41–T45) — anchors unchanged
108
+
109
+ The test-coverage and evidence effort (T41–T45: unit/component/system tiers, the CWI stiff-chemistry benchmarks
110
+ POLLU/BEAM/EMEP/MEDAKZO, the telemetry harness, the EMEP robustness investigation, and the damped-Newton feature) is
111
+ **behaviour-preserving for the defaults**. It added `tests/`, `benchmarks/`, and CI wiring, plus one opt-in feature:
112
+ a damped Newton (backtracking line search) behind `setNewtonLineSearch(bool)` — a new config flag that is **off by
113
+ default** and gated to the plain, non-taped state solve, so the historical full-step behaviour is reproduced
114
+ bit-for-bit when it is not enabled. Consequently every anchor above remains **byte-identical**, including `yabi_basic`
115
+ error `6.53219478508049e-10`. The EMEP benchmark (stiffness ratio ~1e14) originally needed a conservative
116
+ *configuration* (`setFixedOrder(4)` + fresh Jacobian per iteration + `setNewtonLineSearch(true)`); that was a
117
+ work-around for a one-character boundary bug in the minimal-step acceptance (`h < 4·eps·|t|` vs the floor's exact
118
+ `h = 4·eps·|t|`). Fixing the test to `h <= 4·eps·|t|` lets EMEP match its reference with defaults (max order 5, no
119
+ line search, no fixed order) — see README "Extreme-stiffness behaviour (EMEP)" — and is likewise a no-op on
120
+ well-behaved problems, so it leaves every anchor above untouched.
121
+
@@ -0,0 +1,8 @@
1
+ package(default_visibility = ["//visibility:public"])
2
+
3
+ cc_library(
4
+ name = "yabi",
5
+ hdrs = glob(["include/yabi/**/*.h", "include/yabi/**/*.hpp", "include/yabi/**/*.ipp"]),
6
+ includes = ["include"],
7
+ deps = [],
8
+ )
@@ -0,0 +1,425 @@
1
+ cmake_minimum_required(VERSION 3.16)
2
+ project(YABI VERSION 0.2.0 LANGUAGES CXX)
3
+
4
+ option(YABI_BUILD_EXAMPLES "Build YABI example programs" ON)
5
+ option(YABI_BUILD_TESTS "Build the unit/component/system test suite" ON)
6
+ option(YABI_INSTALL_CORE "Install the C++ library + headers + CMake config (OFF for Python-only wheels)" ON)
7
+ # Backwards-compatible alias for the old flat testbench (now tests/system).
8
+ # Only consulted when supplied, so fresh configurations see a single toggle.
9
+ if(DEFINED YABI_BUILD_TESTBENCH)
10
+ message(DEPRECATION "YABI_BUILD_TESTBENCH is deprecated; use YABI_BUILD_TESTS")
11
+ set(YABI_BUILD_TESTS "${YABI_BUILD_TESTBENCH}")
12
+ endif()
13
+
14
+ # The examples self-check (they return non-zero on failure), so they are
15
+ # registered as ctest cases just like the test suite. enable_testing() is
16
+ # therefore unconditional: disabling tests must not silently disable the
17
+ # example checks.
18
+ enable_testing()
19
+
20
+ set(CMAKE_CXX_STANDARD 17)
21
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
22
+ set(CMAKE_CXX_EXTENSIONS OFF)
23
+
24
+ find_package(BLAS REQUIRED)
25
+ find_package(LAPACK REQUIRED)
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # CppAD is an external dependency and is deliberately not bundled with YABI.
29
+ # It is located through pkg-config, through CPPAD_ROOT, or through the usual
30
+ # compiler search paths.
31
+ # ---------------------------------------------------------------------------
32
+ set(CPPAD_ROOT "" CACHE PATH
33
+ "Root of a CppAD installation (the directory containing include/ and lib/)")
34
+
35
+ find_package(PkgConfig QUIET)
36
+ if(PkgConfig_FOUND)
37
+ pkg_check_modules(CPPAD_PC QUIET cppad)
38
+ endif()
39
+
40
+ if(CPPAD_PC_FOUND)
41
+ set(CPPAD_INCLUDE_DIR ${CPPAD_PC_INCLUDE_DIRS})
42
+ set(CPPAD_LINK_LIBRARIES ${CPPAD_PC_LIBRARIES})
43
+ link_directories(${CPPAD_PC_LIBRARY_DIRS})
44
+ else()
45
+ find_path(CPPAD_INCLUDE_DIR cppad/cppad.hpp
46
+ HINTS ${CPPAD_ROOT} $ENV{CPPAD_ROOT}
47
+ PATH_SUFFIXES include)
48
+ find_library(CPPAD_LIBRARY NAMES cppad_lib
49
+ HINTS ${CPPAD_ROOT} $ENV{CPPAD_ROOT}
50
+ PATH_SUFFIXES lib lib64 lib/${CMAKE_LIBRARY_ARCHITECTURE})
51
+ if(CPPAD_LIBRARY)
52
+ set(CPPAD_LINK_LIBRARIES ${CPPAD_LIBRARY})
53
+ endif()
54
+ endif()
55
+
56
+ option(YABI_USE_CPPAD "Enable CppAD-based integrator (default ON)" ON)
57
+ if(YABI_USE_CPPAD)
58
+ if(NOT CPPAD_INCLUDE_DIR)
59
+ message(FATAL_ERROR
60
+ "CppAD was not found.\n"
61
+ "Install CppAD (https://github.com/coin-or/CppAD) and make sure "
62
+ "cppad/cppad.hpp is on the include path, or configure with "
63
+ "-DCPPAD_ROOT=/path/to/cppad.")
64
+ endif()
65
+ message(STATUS "CppAD headers: ${CPPAD_INCLUDE_DIR}")
66
+ if(CPPAD_LINK_LIBRARIES)
67
+ message(STATUS "CppAD library: ${CPPAD_LINK_LIBRARIES}")
68
+ endif()
69
+ else()
70
+ message(STATUS "CppAD disabled (-DYABI_USE_CPPAD=OFF). Pure/manual formulation available.")
71
+ endif()
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # Experimental CUDA (cuSOLVER) linear solver (T36). OFF by default; the default
75
+ # build stays CUDA-free. Only the host-side cuSOLVER/cuBLAS C API is used (no
76
+ # nvcc, no CUDA language), so we just need the headers + libcusolver + libcudart.
77
+ # ---------------------------------------------------------------------------
78
+ option(YABI_USE_CUDA "Build the experimental cuSOLVER linear solver + GPU test (T36)" OFF)
79
+ if(YABI_USE_CUDA)
80
+ find_path(CUDA_INCLUDE_DIR cusolverDn.h
81
+ PATHS /usr/local/cuda-12.6/include /usr/local/cuda/include $ENV{CUDA_HOME}
82
+ PATH_SUFFIXES include)
83
+ find_library(CUSOLVER_LIBRARY cusolver
84
+ PATHS /usr/local/cuda-12.6/lib64 /usr/local/cuda/lib64 $ENV{CUDA_HOME}
85
+ PATH_SUFFIXES lib64 lib)
86
+ find_library(CUSPARSE_LIBRARY cusparse
87
+ PATHS /usr/local/cuda-12.6/lib64 /usr/local/cuda/lib64 $ENV{CUDA_HOME}
88
+ PATH_SUFFIXES lib64 lib)
89
+ find_library(CUBLAS_LIBRARY cublas
90
+ PATHS /usr/local/cuda-12.6/lib64 /usr/local/cuda/lib64 $ENV{CUDA_HOME}
91
+ PATH_SUFFIXES lib64 lib)
92
+ find_library(CUDART_LIBRARY cudart
93
+ PATHS /usr/local/cuda-12.6/lib64 /usr/local/cuda/lib64 $ENV{CUDA_HOME}
94
+ PATH_SUFFIXES lib64 lib)
95
+ if(CUDA_INCLUDE_DIR AND CUSOLVER_LIBRARY AND CUSPARSE_LIBRARY AND CUBLAS_LIBRARY AND CUDART_LIBRARY)
96
+ message(STATUS "CUDA (cuSOLVER/cuSPARSE/cuBLAS) found: ${CUDA_INCLUDE_DIR}")
97
+ else()
98
+ message(FATAL_ERROR
99
+ "YABI_USE_CUDA=ON but CUDA (cusolverDn.h, libcusolver, libcusparse, libcublas, libcudart) was not found.")
100
+ endif()
101
+ endif()
102
+
103
+ # ---------------------------------------------------------------------------
104
+ # External open-source sparse solver adapter (T36d): AMGCL (header-only, MIT) +
105
+ # Boost headers. OFF by default; the default build stays dependency-free. The
106
+ # adapter is an EXAMPLE of the open solver architecture (swap the solver by
107
+ # swapping the adapter object).
108
+ # ---------------------------------------------------------------------------
109
+ option(YABI_USE_AMGCL "Build the external AMGCL sparse-solver adapter + test (T36d)" OFF)
110
+ if(YABI_USE_AMGCL)
111
+ find_path(AMGCL_INCLUDE_DIR amgcl/backend/builtin.hpp
112
+ PATHS $ENV{HOME}/.local/opt/amgcl /usr/local/include /usr/include
113
+ PATH_SUFFIXES include)
114
+ find_path(BOOST_INCLUDE_DIR boost/version.hpp
115
+ PATHS $ENV{HOME}/.local/opt/boost /usr/local/include /usr/include
116
+ PATH_SUFFIXES include)
117
+ find_package(OpenMP QUIET)
118
+ if(AMGCL_INCLUDE_DIR AND BOOST_INCLUDE_DIR)
119
+ message(STATUS "AMGCL found: ${AMGCL_INCLUDE_DIR} (Boost: ${BOOST_INCLUDE_DIR})")
120
+ else()
121
+ message(FATAL_ERROR
122
+ "YABI_USE_AMGCL=ON but AMGCL or Boost headers were not found (see README for provisioning).")
123
+ endif()
124
+ endif()
125
+
126
+ option(YABI_USE_GINKGO "Build the external Ginkgo sparse-solver adapter + test (T36d)" OFF)
127
+ if(YABI_USE_GINKGO)
128
+ find_path(GINKGO_INCLUDE_DIR ginkgo/ginkgo.hpp
129
+ PATHS $ENV{HOME}/.local/opt/ginkgo /usr/local/include /usr/include
130
+ PATH_SUFFIXES include)
131
+ find_library(GINKGO_LIBRARY ginkgo PATHS $ENV{HOME}/.local/opt/ginkgo PATH_SUFFIXES lib)
132
+ find_library(GINKGO_DEVICE_LIBRARY ginkgo_device
133
+ PATHS $ENV{HOME}/.local/opt/ginkgo PATH_SUFFIXES lib)
134
+ find_library(GINKGO_OMP_LIBRARY ginkgo_omp
135
+ PATHS $ENV{HOME}/.local/opt/ginkgo PATH_SUFFIXES lib)
136
+ find_package(OpenMP REQUIRED)
137
+ if(GINKGO_INCLUDE_DIR AND GINKGO_LIBRARY AND GINKGO_OMP_LIBRARY)
138
+ get_filename_component(GINKGO_LIBRARY_DIR "${GINKGO_LIBRARY}" DIRECTORY)
139
+ message(STATUS "Ginkgo found: ${GINKGO_INCLUDE_DIR} (lib: ${GINKGO_LIBRARY_DIR})")
140
+ else()
141
+ message(FATAL_ERROR
142
+ "YABI_USE_GINKGO=ON but Ginkgo was not found (see README for provisioning).")
143
+ endif()
144
+ endif()
145
+
146
+ option(YABI_USE_CUDSS "Build the cuDSS sparse direct solver adapter + test (T36d)" OFF)
147
+ if(YABI_USE_CUDSS)
148
+ find_path(CUDSS_INCLUDE_DIR cudss.h
149
+ PATHS $ENV{HOME}/.local/opt/cudss /usr/local/include /usr/include PATH_SUFFIXES include)
150
+ find_library(CUDSS_LIBRARY cudss
151
+ PATHS $ENV{HOME}/.local/opt/cudss PATH_SUFFIXES lib lib64)
152
+ # cuDSS needs CUDA (cudart) for the host-side cudaMalloc/cudaMemcpy.
153
+ find_path(CUDA_INCLUDE_DIR cuda_runtime.h
154
+ PATHS /usr/local/cuda-12.6/include /usr/local/cuda/include $ENV{CUDA_HOME} PATH_SUFFIXES include)
155
+ find_library(CUDART_LIBRARY cudart
156
+ PATHS /usr/local/cuda-12.6/lib64 /usr/local/cuda/lib64 $ENV{CUDA_HOME} PATH_SUFFIXES lib64 lib)
157
+ if(CUDSS_INCLUDE_DIR AND CUDSS_LIBRARY AND CUDA_INCLUDE_DIR AND CUDART_LIBRARY)
158
+ get_filename_component(CUDSS_LIBRARY_DIR "${CUDSS_LIBRARY}" DIRECTORY)
159
+ message(STATUS "cuDSS found: ${CUDSS_INCLUDE_DIR} (lib: ${CUDSS_LIBRARY_DIR})")
160
+ else()
161
+ message(FATAL_ERROR
162
+ "YABI_USE_CUDSS=ON but cuDSS or CUDA was not found (see README for provisioning).")
163
+ endif()
164
+ endif()
165
+
166
+ # ---------------------------------------------------------------------------
167
+ # Library
168
+ # ---------------------------------------------------------------------------
169
+ add_library(yabi src/bdf_integrator.cpp)
170
+ add_library(YABI::yabi ALIAS yabi)
171
+
172
+ target_include_directories(yabi
173
+ PUBLIC
174
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
175
+ $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
176
+ )
177
+ target_include_directories(yabi SYSTEM PUBLIC ${CPPAD_INCLUDE_DIR})
178
+ target_link_libraries(yabi
179
+ PUBLIC
180
+ ${LAPACK_LIBRARIES}
181
+ ${BLAS_LIBRARIES}
182
+ ${CPPAD_LINK_LIBRARIES}
183
+ )
184
+ set_target_properties(yabi PROPERTIES
185
+ POSITION_INDEPENDENT_CODE ON
186
+ VERSION ${PROJECT_VERSION}
187
+ SOVERSION 0
188
+ )
189
+
190
+ # Pure library: the same integrator core compiled without CppAD (YABI_NO_CPPAD
191
+ # drops the cppad_rhs.hpp include and the CppAD::ADFun convenience constructor),
192
+ # so a user_ode.hpp / UserODERHS consumer can build and run without <cppad/cppad.hpp>.
193
+ add_library(yabi_pure src/bdf_integrator_pure.cpp)
194
+ target_include_directories(yabi_pure
195
+ PUBLIC
196
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
197
+ $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
198
+ )
199
+ target_compile_definitions(yabi_pure PUBLIC YABI_NO_CPPAD)
200
+ target_link_libraries(yabi_pure
201
+ PUBLIC
202
+ ${LAPACK_LIBRARIES}
203
+ ${BLAS_LIBRARIES}
204
+ )
205
+ set_target_properties(yabi_pure PROPERTIES POSITION_INDEPENDENT_CODE ON)
206
+
207
+ # ---------------------------------------------------------------------------
208
+ # Python bindings (T37) — pybind11 `yabi` module on the analytic (pure) path.
209
+ # OFF by default so the default build stays Python-free. Requires pybind11
210
+ # (>= 3.0 for Python 3.14): `pip install pybind11` and re-configure.
211
+ # ---------------------------------------------------------------------------
212
+ option(YABI_BUILD_PYTHON "Build the pybind11 `yabi` Python module (T37)" OFF)
213
+ if(YABI_BUILD_PYTHON)
214
+ find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
215
+ # Locate a pip-installed pybind11 (>= 3.0) via its Python helper, so the
216
+ # configure step finds it wherever pip put it (no hand-set pybind11_DIR).
217
+ execute_process(COMMAND ${Python3_EXECUTABLE} -m pybind11 --cmakedir
218
+ OUTPUT_VARIABLE pybind11_DIR
219
+ OUTPUT_STRIP_TRAILING_WHITESPACE
220
+ RESULT_VARIABLE pybind11_HELPER_RC)
221
+ if(NOT pybind11_HELPER_RC EQUAL 0)
222
+ message(FATAL_ERROR
223
+ "pybind11 not found: `pip install pybind11` (>= 3.0) and re-configure.")
224
+ endif()
225
+ find_package(pybind11 CONFIG REQUIRED)
226
+ pybind11_add_module(yabi_python bindings/python/yabi.cpp)
227
+ # Importable as `import yabi` (target name stays yabi_python to avoid the
228
+ # C++ `yabi` library target).
229
+ set_target_properties(yabi_python PROPERTIES OUTPUT_NAME "yabi")
230
+ target_link_libraries(yabi_python PRIVATE yabi_pure)
231
+ # T54 — optional CUDA (cuSOLVER) Newton solve in the Python module. Reuses
232
+ # the CUDA detection above; the default build stays CUDA-free.
233
+ if(YABI_USE_CUDA AND CUDA_INCLUDE_DIR AND CUSOLVER_LIBRARY AND CUDART_LIBRARY)
234
+ target_include_directories(yabi_python PRIVATE ${CUDA_INCLUDE_DIR})
235
+ target_link_libraries(yabi_python PRIVATE ${CUSOLVER_LIBRARY} ${CUDART_LIBRARY})
236
+ target_compile_definitions(yabi_python PRIVATE YABI_HAS_CUDA)
237
+ message(STATUS "yabi_python: CUDA Newton solve enabled (cuSOLVER)")
238
+ endif()
239
+ # Install the compiled module into the wheel/site-packages root so `import yabi`
240
+ # resolves (used by scikit-build-core packaging; harmless in a plain build).
241
+ install(TARGETS yabi_python LIBRARY DESTINATION .)
242
+ # CI-verifiable smoke test (only when the module is built).
243
+ add_test(NAME python_bindings
244
+ COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/bindings/python/test_yabi.py)
245
+ set_tests_properties(python_bindings PROPERTIES
246
+ ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:yabi_python>"
247
+ LABELS "python")
248
+ # T38 — forward/reverse (adjoint) bindings vs central differences (no torch needed).
249
+ add_test(NAME python_autograd
250
+ COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/bindings/python/test_autograd.py)
251
+ set_tests_properties(python_autograd PROPERTIES
252
+ ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:yabi_python>"
253
+ LABELS "python")
254
+ # T51 — batched differentiable solve_ivp (needs torch; registered only when
255
+ # torch is importable so a torch-less build doesn't fail).
256
+ execute_process(COMMAND ${Python3_EXECUTABLE} -c "import torch"
257
+ RESULT_VARIABLE YABI_TORCH_AVAILABLE)
258
+ if(YABI_TORCH_AVAILABLE EQUAL 0)
259
+ add_test(NAME python_solve_ivp
260
+ COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/bindings/python/test_solve_ivp.py)
261
+ set_tests_properties(python_solve_ivp PROPERTIES
262
+ ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:yabi_python>:${CMAKE_CURRENT_SOURCE_DIR}/python"
263
+ LABELS "python")
264
+ # T52 — nn.Module f with automatic Jacobian (dense + matrix-free).
265
+ add_test(NAME python_nn_module
266
+ COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/bindings/python/test_nn_module.py)
267
+ set_tests_properties(python_nn_module PROPERTIES
268
+ ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:yabi_python>:${CMAKE_CURRENT_SOURCE_DIR}/python"
269
+ LABELS "python")
270
+ # T53 — parallel batch (threads=N bit-identical to serial).
271
+ add_test(NAME python_batch_parallel
272
+ COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/bindings/python/test_batch_parallel.py)
273
+ set_tests_properties(python_batch_parallel PROPERTIES
274
+ ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:yabi_python>:${CMAKE_CURRENT_SOURCE_DIR}/python"
275
+ LABELS "python")
276
+ # T54 — GPU (device="cuda"): skips cleanly when torch CUDA / a CUDA build
277
+ # of the module is absent.
278
+ add_test(NAME python_cuda
279
+ COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/bindings/python/test_cuda.py)
280
+ set_tests_properties(python_cuda PROPERTIES
281
+ ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:yabi_python>:${CMAKE_CURRENT_SOURCE_DIR}/python"
282
+ LABELS "python")
283
+ endif()
284
+ endif()
285
+
286
+ # T44 — telemetry harness: emits the machine-readable evidence table consumed by
287
+ # benchmarks/report.py, and self-checks tolerance adherence (error decreases as
288
+ # rtol is tightened; the tightest rtol reaches < 1e-6), so the evidence layer is
289
+ # CI-verified as a ctest under the "benchmark" label.
290
+ if(YABI_USE_CPPAD)
291
+ add_executable(yabi_telemetry benchmarks/telemetry.cpp)
292
+ target_link_libraries(yabi_telemetry PRIVATE yabi)
293
+ add_test(NAME telemetry COMMAND yabi_telemetry)
294
+ set_tests_properties(telemetry PROPERTIES LABELS "benchmark")
295
+ endif()
296
+
297
+ # ---------------------------------------------------------------------------
298
+ # Examples
299
+ # ---------------------------------------------------------------------------
300
+ if(YABI_BUILD_EXAMPLES)
301
+ foreach(example basic nonautonomous sensitivity_demo)
302
+ add_executable(yabi_${example} examples/${example}.cpp)
303
+ target_link_libraries(yabi_${example} PRIVATE yabi)
304
+ list(APPEND YABI_EXAMPLE_TARGETS yabi_${example})
305
+ # Each example checks its own result and returns non-zero on failure.
306
+ add_test(NAME example_${example} COMMAND yabi_${example})
307
+ set_tests_properties(example_${example} PROPERTIES LABELS "example")
308
+ endforeach()
309
+ # Trajectory dumper for the benchmark gallery (utility, not a ctest).
310
+ add_executable(dump_trajectories examples/dump_trajectories.cpp)
311
+ target_link_libraries(dump_trajectories PRIVATE yabi)
312
+ endif()
313
+
314
+ # ---------------------------------------------------------------------------
315
+ # Test suite
316
+ # ---------------------------------------------------------------------------
317
+ # tests/unit isolated component contracts (CppAD-free where possible)
318
+ # tests/component cross-component integration (native sparse, SuiteSparse)
319
+ # tests/system mathematical benchmarks (canonical ODE problems)
320
+ # add_subdirectory(tests) is at the end so the optional SuiteSparse probes have
321
+ # run before the component tests are configured.
322
+
323
+ # ---------------------------------------------------------------------------
324
+ # Install
325
+ # ---------------------------------------------------------------------------
326
+ include(GNUInstallDirs)
327
+ if(YABI_INSTALL_CORE)
328
+ if(YABI_USE_CPPAD)
329
+ install(TARGETS yabi
330
+ EXPORT YABITargets
331
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
332
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
333
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
334
+ else()
335
+ install(TARGETS yabi_pure
336
+ EXPORT YABITargets
337
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
338
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
339
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
340
+ endif()
341
+ install(DIRECTORY include/yabi DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
342
+ install(EXPORT YABITargets
343
+ NAMESPACE YABI::
344
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/YABI
345
+ )
346
+ endif()
347
+
348
+ # Optional: UMFPACK sparse solver example (if SuiteSparse available)
349
+ find_path(UMFPACK_INCLUDE_DIR umfpack.h PATHS /usr/include/suitesparse)
350
+ find_library(UMFPACK_LIBRARY umfpack)
351
+ if(UMFPACK_INCLUDE_DIR AND UMFPACK_LIBRARY)
352
+ add_executable(yabi_heat1d_umfpack examples/heat_1d_sparse_umfpack.cpp)
353
+ target_include_directories(yabi_heat1d_umfpack PRIVATE ${UMFPACK_INCLUDE_DIR})
354
+ target_link_libraries(yabi_heat1d_umfpack PRIVATE yabi ${UMFPACK_LIBRARY} amd cholmod colamd ccolamd camd suitesparseconfig lapack blas cppad_lib m)
355
+ endif()
356
+ if(UMFPACK_INCLUDE_DIR AND UMFPACK_LIBRARY)
357
+ add_executable(yabi_heat1d_umfpack_sens examples/heat_1d_umfpack_forward_sens.cpp)
358
+ target_include_directories(yabi_heat1d_umfpack_sens PRIVATE ${UMFPACK_INCLUDE_DIR})
359
+ target_link_libraries(yabi_heat1d_umfpack_sens PRIVATE yabi ${UMFPACK_LIBRARY} amd cholmod colamd ccolamd camd suitesparseconfig lapack blas cppad_lib m)
360
+ endif()
361
+ if(UMFPACK_INCLUDE_DIR AND UMFPACK_LIBRARY)
362
+ add_executable(yabi_heat1d_umfpack_sens2 examples/heat1d_umfpack_sens_simple.cpp)
363
+ target_include_directories(yabi_heat1d_umfpack_sens2 PRIVATE ${UMFPACK_INCLUDE_DIR})
364
+ target_link_libraries(yabi_heat1d_umfpack_sens2 PRIVATE yabi ${UMFPACK_LIBRARY} amd cholmod colamd ccolamd camd suitesparseconfig lapack blas cppad_lib m)
365
+ endif()
366
+ if(UMFPACK_INCLUDE_DIR AND UMFPACK_LIBRARY)
367
+ add_executable(yabi_heat1d_umfpack_sens3 examples/heat1d_umfpack_sens2.cpp)
368
+ target_include_directories(yabi_heat1d_umfpack_sens3 PRIVATE ${UMFPACK_INCLUDE_DIR})
369
+ target_link_libraries(yabi_heat1d_umfpack_sens3 PRIVATE yabi ${UMFPACK_LIBRARY} amd cholmod colamd ccolamd camd suitesparseconfig lapack blas cppad_lib m)
370
+ endif()
371
+ if(UMFPACK_INCLUDE_DIR AND UMFPACK_LIBRARY)
372
+ add_executable(yabi_heat1d_umfpack_sens4 examples/heat1d_umfpack_sens3.cpp)
373
+ target_include_directories(yabi_heat1d_umfpack_sens4 PRIVATE ${UMFPACK_INCLUDE_DIR})
374
+ target_link_libraries(yabi_heat1d_umfpack_sens4 PRIVATE yabi ${UMFPACK_LIBRARY} amd cholmod colamd ccolamd camd suitesparseconfig lapack blas cppad_lib m)
375
+ endif()
376
+ add_executable(yabi_heat1d_dense_lit examples/heat1d_dense_literature.cpp)
377
+ target_link_libraries(yabi_heat1d_dense_lit PRIVATE yabi cppad_lib m)
378
+ add_executable(yabi_heat1d_dense_analytic examples/heat1d_dense_analytic_jac.cpp)
379
+ target_link_libraries(yabi_heat1d_dense_analytic PRIVATE yabi cppad_lib m)
380
+ if(UMFPACK_INCLUDE_DIR AND UMFPACK_LIBRARY)
381
+ add_executable(yabi_heat1d_umfpack_lit examples/heat1d_umfpack_literature.cpp)
382
+ target_include_directories(yabi_heat1d_umfpack_lit PRIVATE ${UMFPACK_INCLUDE_DIR})
383
+ target_link_libraries(yabi_heat1d_umfpack_lit PRIVATE yabi ${UMFPACK_LIBRARY} amd cholmod colamd ccolamd camd suitesparseconfig lapack blas cppad_lib m)
384
+ endif()
385
+ if(UMFPACK_INCLUDE_DIR AND UMFPACK_LIBRARY)
386
+ add_executable(yabi_heat1d_umfpack_sens_dense_api examples/heat1d_umfpack_sens_dense_equiv.cpp)
387
+ target_include_directories(yabi_heat1d_umfpack_sens_dense_api PRIVATE ${UMFPACK_INCLUDE_DIR})
388
+ target_link_libraries(yabi_heat1d_umfpack_sens_dense_api PRIVATE yabi ${UMFPACK_LIBRARY} amd cholmod colamd ccolamd camd suitesparseconfig lapack blas cppad_lib m)
389
+ endif()
390
+
391
+ # Optional SuiteSparse solvers for the component test tier.
392
+ find_path(KLU_INCLUDE_DIR klu.h PATHS /usr/include/suitesparse)
393
+ find_library(KLU_LIBRARY klu)
394
+
395
+ # -----------------------------------------------------------------
396
+ # Plot runner (matplotlib / numpy / pandas) — external, not required
397
+ # -----------------------------------------------------------------
398
+ find_package(Python3 COMPONENTS Interpreter)
399
+ if(Python3_FOUND)
400
+ find_path(PYTHON_MATPLOTLIB matplotlib __init__.py PATHS /usr/lib/python3/dist-packages /usr/local/lib/python3.14/site-packages)
401
+ if(PYTHON_MATPLOTLIB)
402
+ add_custom_target(plot_all
403
+ COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/pyplot/plot_all.py
404
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
405
+ COMMENT "Plotting yabi-bdf examples to plots/ (requires matplotlib/numpy/pandas)")
406
+ else()
407
+ message(STATUS "matplotlib not found in python3 — plot_all target skipped; install with pip install matplotlib numpy pandas")
408
+ endif()
409
+ endif()
410
+
411
+ # CSV dump for plotting
412
+ # CSV dump for plotting
413
+
414
+ # Plot examples (additional targets, not part of tests)
415
+ add_executable(plot_brusselator_traj examples/plot_brusselator_traj.cpp)
416
+ target_link_libraries(plot_brusselator_traj PRIVATE yabi cppad_lib m)
417
+ add_executable(plot_robertson_traj examples/plot_robertson_traj.cpp)
418
+ target_link_libraries(plot_robertson_traj PRIVATE yabi cppad_lib m)
419
+
420
+ # ---------------------------------------------------------------------------
421
+ # Tests (unit / component / system)
422
+ # ---------------------------------------------------------------------------
423
+ if(YABI_BUILD_TESTS)
424
+ add_subdirectory(tests)
425
+ endif()