op-engine 0.2.0__tar.gz → 0.4.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.
- {op_engine-0.2.0 → op_engine-0.4.0}/.gitignore +2 -1
- op_engine-0.4.0/CHANGELOG.md +199 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/PKG-INFO +36 -10
- {op_engine-0.2.0 → op_engine-0.4.0}/README.md +32 -9
- op_engine-0.4.0/docs/assets/chemostat-split.svg +41 -0
- op_engine-0.4.0/docs/assets/chemostat-summary.png +0 -0
- op_engine-0.4.0/docs/development/fem-assembly-boundary.md +102 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/docs/guides/backends.md +21 -9
- op_engine-0.4.0/docs/guides/bounded-thinning.md +112 -0
- op_engine-0.4.0/docs/guides/chemostat-pde.md +97 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/docs/guides/getting-started.md +12 -6
- {op_engine-0.2.0 → op_engine-0.4.0}/docs/guides/solver-methods.md +171 -13
- {op_engine-0.2.0 → op_engine-0.4.0}/docs/index.md +3 -0
- op_engine-0.4.0/examples/chemostat_pde.py +614 -0
- op_engine-0.4.0/examples/fem_diffusion.py +189 -0
- op_engine-0.4.0/examples/test_chemostat_pde.py +107 -0
- op_engine-0.4.0/examples/test_fem_diffusion.py +79 -0
- op_engine-0.4.0/flepimop2-op_engine/README.md +745 -0
- op_engine-0.4.0/flepimop2-op_engine/benchmarks/README.md +95 -0
- op_engine-0.4.0/flepimop2-op_engine/benchmarks/__init__.py +17 -0
- op_engine-0.4.0/flepimop2-op_engine/benchmarks/adaptive_replay.py +319 -0
- op_engine-0.4.0/flepimop2-op_engine/benchmarks/prepared_execution.py +370 -0
- op_engine-0.4.0/flepimop2-op_engine/benchmarks/solver_matrix.py +944 -0
- op_engine-0.4.0/flepimop2-op_engine/benchmarks/structured_blocks.py +266 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/flepimop2-op_engine/pyproject.toml +15 -11
- op_engine-0.4.0/flepimop2-op_engine/src/flepimop2/engine/op_engine/__init__.py +3904 -0
- op_engine-0.4.0/flepimop2-op_engine/src/flepimop2/engine/op_engine/config.py +404 -0
- op_engine-0.4.0/flepimop2-op_engine/src/flepimop2/engine/op_engine/explicit_operators.py +300 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/flepimop2-op_engine/src/flepimop2/engine/op_engine/operators.py +314 -13
- {op_engine-0.2.0 → op_engine-0.4.0}/flepimop2-op_engine/src/flepimop2/engine/op_engine/reactions.py +351 -53
- op_engine-0.4.0/flepimop2-op_engine/tests/test_adaptive_tau_generated.py +155 -0
- op_engine-0.4.0/flepimop2-op_engine/tests/test_axis_less_reactions.py +210 -0
- op_engine-0.4.0/flepimop2-op_engine/tests/test_config.py +608 -0
- op_engine-0.4.0/flepimop2-op_engine/tests/test_coord_shift.py +335 -0
- op_engine-0.4.0/flepimop2-op_engine/tests/test_engine.py +1318 -0
- op_engine-0.4.0/flepimop2-op_engine/tests/test_op_system_integration.py +2159 -0
- op_engine-0.4.0/flepimop2-op_engine/tests/test_operators.py +696 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/flepimop2-op_engine/tests/test_reactions.py +50 -0
- op_engine-0.4.0/flepimop2-op_engine/tests/test_renewal.py +463 -0
- op_engine-0.4.0/flepimop2-op_engine/tests/test_routed_reactions.py +257 -0
- op_engine-0.4.0/flepimop2-op_engine/tests/test_solver_matrix_benchmark.py +134 -0
- op_engine-0.4.0/flepimop2-op_engine/tests/test_stochastic_coverage.py +198 -0
- op_engine-0.4.0/flepimop2-op_engine/tests/test_thinning.py +515 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/justfile +1 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/mkdocs.yml +5 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/pyproject.toml +5 -1
- {op_engine-0.2.0 → op_engine-0.4.0}/src/op_engine/__init__.py +24 -1
- {op_engine-0.2.0 → op_engine-0.4.0}/src/op_engine/_additive_runge_kutta.py +3 -2
- op_engine-0.4.0/src/op_engine/_array.py +33 -0
- op_engine-0.4.0/src/op_engine/_forcing.py +53 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/src/op_engine/_multistep.py +2 -14
- {op_engine-0.2.0 → op_engine-0.4.0}/src/op_engine/_runge_kutta.py +66 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/src/op_engine/_sdirk.py +1 -1
- op_engine-0.4.0/src/op_engine/_typing.py +36 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/src/op_engine/adaptive_tau.py +30 -5
- {op_engine-0.2.0 → op_engine-0.4.0}/src/op_engine/core_solver.py +492 -136
- {op_engine-0.2.0 → op_engine-0.4.0}/src/op_engine/matrix_ops.py +179 -99
- {op_engine-0.2.0 → op_engine-0.4.0}/src/op_engine/model_core.py +3 -17
- {op_engine-0.2.0 → op_engine-0.4.0}/src/op_engine/nonlinear_solver.py +2 -14
- op_engine-0.4.0/src/op_engine/spatial_grid.py +252 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/src/op_engine/stochastic_solver.py +86 -41
- op_engine-0.4.0/src/op_engine/thinning_ssa.py +443 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_adaptive_tau.py +207 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_array_namespace.py +244 -2
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_configuration.py +22 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_core_solver_accuracy.py +80 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_core_solver_plumbing.py +88 -2
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_direct_ssa.py +262 -3
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_matrix_ops.py +153 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_runge_kutta.py +41 -0
- op_engine-0.4.0/tests/op_engine/test_spatial_grid.py +140 -0
- op_engine-0.4.0/tests/op_engine/test_tau_forcing.py +247 -0
- op_engine-0.4.0/tests/op_engine/test_thinning_ssa.py +509 -0
- op_engine-0.2.0/CHANGELOG.md +0 -51
- op_engine-0.2.0/flepimop2-op_engine/README.md +0 -120
- op_engine-0.2.0/flepimop2-op_engine/src/flepimop2/engine/op_engine/__init__.py +0 -868
- op_engine-0.2.0/flepimop2-op_engine/src/flepimop2/engine/op_engine/config.py +0 -193
- op_engine-0.2.0/flepimop2-op_engine/tests/test_config.py +0 -250
- op_engine-0.2.0/flepimop2-op_engine/tests/test_engine.py +0 -363
- op_engine-0.2.0/flepimop2-op_engine/tests/test_op_system_integration.py +0 -947
- op_engine-0.2.0/flepimop2-op_engine/tests/test_operators.py +0 -259
- op_engine-0.2.0/src/op_engine/_typing.py +0 -38
- {op_engine-0.2.0 → op_engine-0.4.0}/.github/workflows/ci.yaml +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/.github/workflows/gh-pages.yaml +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/.github/workflows/release.yaml +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/LICENSE +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/docs/api-reference/.gitignore +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/docs/development/creating-a-release.md +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/docs/guides/biogeochemical-network.md +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/docs/guides/linear-multistep-methods.md +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/docs/guides/nonlinear-solvers.md +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/examples/__init__.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/examples/biogeochemical_network.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/examples/simple_sir.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/examples/test_biogeochemical_network.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/flepimop2-op_engine/LICENSE +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/flepimop2-op_engine/src/flepimop2/engine/op_engine/py.typed +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/flepimop2-op_engine/tests/conftest.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/flepimop2-op_engine/tests/data/config.yaml +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/flepimop2-op_engine/tests/data/sir.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/flepimop2-op_engine/tests/test_integration_cli.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/overrides/main.html +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/scripts/__init__.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/scripts/api-reference.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/scripts/release_validate.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/src/op_engine/_rosenbrock.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/src/op_engine/py.typed +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/conftest.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test__version__.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_additive_runge_kutta.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_core_solver_ark.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_core_solver_sdirk.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_model_core.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_multistep.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_nonlinear_solver.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_rosenbrock.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_sdirk.py +0 -0
- {op_engine-0.2.0 → op_engine-0.4.0}/tests/op_engine/test_stochastic_solver.py +0 -0
|
@@ -174,6 +174,8 @@ __marimo__/
|
|
|
174
174
|
*.png
|
|
175
175
|
*.pdf
|
|
176
176
|
*.svg
|
|
177
|
+
!docs/assets/*.png
|
|
178
|
+
!docs/assets/*.svg
|
|
177
179
|
|
|
178
180
|
# Numpy saved arrays
|
|
179
181
|
*.npy
|
|
@@ -184,4 +186,3 @@ __marimo__/
|
|
|
184
186
|
|
|
185
187
|
# Optional example output directory convention
|
|
186
188
|
examples/output/
|
|
187
|
-
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable user-facing changes are recorded here. Versions follow semantic
|
|
4
|
+
versioning while the project remains pre-1.0.
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.4.0] - 2026-10-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Prepared Flepimop2 execution plans now support explicit PyTree and JAX block
|
|
13
|
+
layouts for fixed steps and frozen adaptive replay, including structured typed
|
|
14
|
+
operators, while preserving dynamic-value cache contracts (#168).
|
|
15
|
+
- Direct SSA accepts explicit forcing breakpoints for exact piecewise-constant
|
|
16
|
+
rates, including dormant zero-rate intervals, while retaining pending events
|
|
17
|
+
across observation times. The provider exposes this for pure direct SSA (#171).
|
|
18
|
+
- Fixed and adaptive tau-leaping stop at declared forcing changes, so each
|
|
19
|
+
leap segment uses its own starting rate; forcing boundaries take precedence
|
|
20
|
+
over tied critical or exact-fallback events (#174).
|
|
21
|
+
- Pure stochastic runs consume the producer's declared `forcing_breakpoints`
|
|
22
|
+
(op_system `time_interpolation: previous`), combined with explicit engine
|
|
23
|
+
boundaries. Hybrid mode reports producer forcing as unsupported (#175).
|
|
24
|
+
- `stochastic_method: thinning-ssa` gives exact bounded thinning for smoothly
|
|
25
|
+
time-varying rates. Supply a total-rate bound as `thinning_rate_bound` or a
|
|
26
|
+
`RateBoundFunction` through `run(..., rate_bound=...)`; `ThinningSSASolver`
|
|
27
|
+
is available in the core package (#173, #176, #177).
|
|
28
|
+
- Stochastic execution consumes axis-wide `coord_shift` reactions from
|
|
29
|
+
op_system: each source bin of an `offsets` axis becomes a channel that moves
|
|
30
|
+
one unit by the published step, and off-axis destinations only deplete the
|
|
31
|
+
donor. Direct SSA reproduces Poisson bin occupancy and Erlang exit times for
|
|
32
|
+
pure aging chains (#180).
|
|
33
|
+
- Pure stochastic validation and execution reject models with dynamics that
|
|
34
|
+
have no reaction artifact: transitions listed in the producer's
|
|
35
|
+
`reaction_gaps` coverage records, and typed operators. Each uncovered
|
|
36
|
+
transition is named by origin, selectors, and reason. Hybrid mode and
|
|
37
|
+
producers without coverage records are unchanged (#182).
|
|
38
|
+
- Stochastic execution supports reactions between templates with different
|
|
39
|
+
axes, including axis-less states: scalar S→I→R models, axis-less donors
|
|
40
|
+
depositing into a pinned cell, and templated sources collapsing into an
|
|
41
|
+
axis-less state. Targets are indexed with op_system's `to_full_axes`,
|
|
42
|
+
falling back to `full_axes` for older producers (#184).
|
|
43
|
+
- Stochastic execution runs routing and target-only fan-out reactions: each
|
|
44
|
+
source cell and routed target coordinate (op_system's `routed_axes`) is one
|
|
45
|
+
channel, so routing generators move discrete units between bins (#186).
|
|
46
|
+
- Adaptive tau-leaping validation reports incomplete reactant metadata before
|
|
47
|
+
running, and both validation and run name each incomplete reaction and the
|
|
48
|
+
remedy: `reactants` on ordinary op_system transitions, or `catalysts` on
|
|
49
|
+
`chain:` and `coord_shift` entries (#188).
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- The provider's optional `op_system` integration requires
|
|
54
|
+
`flepimop2-op-system>=0.6.0` and `op-system>=0.6.0`.
|
|
55
|
+
- Pure stochastic runs that previously dropped uncovered transitions or typed
|
|
56
|
+
operators silently now fail validation; use `mode: hybrid` or give each
|
|
57
|
+
transition a reaction artifact (#182).
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- `PreparedExecution` can be passed directly to JAX transformations, including
|
|
62
|
+
mixed NumPy/JAX samples whose effective namespaces and dtypes are canonicalized
|
|
63
|
+
by JAX during tracing (#168).
|
|
64
|
+
- Pinned source-only renewal births are accepted: donor-axis coverage is
|
|
65
|
+
validated only for reactions with a donor (#178, #179).
|
|
66
|
+
|
|
67
|
+
## [0.3.0] - 2026-09-27
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
|
|
71
|
+
- A trait-structured chemostat PDE example combines a curvature-adapted
|
|
72
|
+
non-uniform finite-volume grid, no-flux implicit diffusion, nonlinear
|
|
73
|
+
resource coupling, structured trait moments, an RK45 reference, and a
|
|
74
|
+
documented three-panel diagnostic (#47).
|
|
75
|
+
- The Flepimop provider compiles typed `jump_integral` descriptors into
|
|
76
|
+
conservative Array-API operators for implicit/IMEX and structured explicit
|
|
77
|
+
execution. It preserves dynamic rates and kernel matrices under JAX,
|
|
78
|
+
applies declared direction and continuous target quadrature, and reuses
|
|
79
|
+
selector/multi-axis lifting with real op_system integration coverage (#94).
|
|
80
|
+
- A finite-element boundary design note and tested 1D P1 diffusion proof of
|
|
81
|
+
concept show how externally assembled mass/stiffness systems use the existing
|
|
82
|
+
stage-operator contract, without adding mesh or element abstractions to core
|
|
83
|
+
(#107).
|
|
84
|
+
- Curvature-weighted 1D spatial-grid generators accept vectorized profile
|
|
85
|
+
callables or sampled data, with optional curvature smoothing and a feasible
|
|
86
|
+
minimum-spacing constraint. They return explicit NumPy geometry for use by
|
|
87
|
+
any runtime array backend (#46).
|
|
88
|
+
- Dense Array-API and sparse SciPy diffusion builders now accept monotone
|
|
89
|
+
non-uniform cell-center grids with conservative no-flux boundaries. The
|
|
90
|
+
flepimop2 provider uses the same geometry for flat, PyTree, and block
|
|
91
|
+
explicit or IMEX execution while retaining JAX differentiation (#45).
|
|
92
|
+
- Explicit adaptive JAX discovery now keeps attempted-step acceptance and
|
|
93
|
+
controller state in a bounded device loop, including conservative shared-mesh
|
|
94
|
+
discovery for block layouts. Compact frozen-mesh replay reports array-valued
|
|
95
|
+
local-error freshness diagnostics and can reject materially stale schedules
|
|
96
|
+
at an outer orchestration boundary without compromising JIT or gradients;
|
|
97
|
+
the solver matrix now benchmarks discovery in each row's actual backend
|
|
98
|
+
(#148).
|
|
99
|
+
- Fixed flat-state JAX DOPRI5 now computes only its high-order solution, keeps
|
|
100
|
+
FSAL reuse, and carries a requested-output buffer instead of materializing
|
|
101
|
+
every hidden internal state. Optional `fixed_checkpoint: step` and `chunk`
|
|
102
|
+
policies control reverse-mode rematerialization; RK4 retains its smaller
|
|
103
|
+
compile-sensitive scan. The solver matrix records the resulting forward and
|
|
104
|
+
gradient IR, compile, memory, runtime, and accuracy changes (#147).
|
|
105
|
+
- The flepimop2 provider can prepare and cache stable flat explicit execution
|
|
106
|
+
plans for fixed stepping and frozen adaptive replay. Prepared callables keep
|
|
107
|
+
array values dynamic for NumPy or caller-owned JAX transforms while reusing
|
|
108
|
+
system binding, packing contracts, step grids, and execution structure;
|
|
109
|
+
explicit cache invalidation and matched staging benchmarks are included (#146).
|
|
110
|
+
- A machine-readable solver benchmark now separates provider construction,
|
|
111
|
+
adaptive discovery, JAX trace/lower/compile, first and warm execution, and
|
|
112
|
+
value-and-gradient phases while reporting analytic accuracy, work counts,
|
|
113
|
+
memory, revision, and environment metadata. SciPy and optional Diffrax
|
|
114
|
+
references use explicit solver, controller, and adjoint settings (#145).
|
|
115
|
+
- Numerical namespace discovery now uses `array-api-compat`, including native
|
|
116
|
+
PyTorch tensors that do not implement `__array_namespace__`. A public
|
|
117
|
+
`array_namespace` helper and optional Torch contract test preserve JAX
|
|
118
|
+
JIT/grad behavior while extending eager fixed-step autograd support (#144).
|
|
119
|
+
- Explicit fixed-step runs can bound their internal integration step with
|
|
120
|
+
`fixed_max_step` independently of requested output times, including compact
|
|
121
|
+
differentiable JAX provider trajectories (#130).
|
|
122
|
+
- The flepimop2 provider exposes SDIRK2 with explicit full-RHS Jacobian and
|
|
123
|
+
nonlinear-solver options, plus adaptive initial-step and safety limits. A
|
|
124
|
+
method-surface parity test now requires explicit provider decisions for new
|
|
125
|
+
core methods, and frozen schedules tolerate representational rounding when
|
|
126
|
+
replayed on a lower-precision backend (#131).
|
|
127
|
+
- Compact JAX fixed-step provider trajectories use a single `jax.lax.scan`
|
|
128
|
+
while the core retains backend-neutral functional step kernels (#123).
|
|
129
|
+
- The flepimop2 provider exposes eager adaptive-schedule discovery and
|
|
130
|
+
validated frozen-mesh replay for conditional JAX JIT and differentiation
|
|
131
|
+
workflows (#127).
|
|
132
|
+
|
|
133
|
+
### Fixed
|
|
134
|
+
|
|
135
|
+
- Sparse implicit-solver cache entries retain and verify their exact operator
|
|
136
|
+
objects, preventing stale factorizations when Python recycles an object ID
|
|
137
|
+
for a different same-shape matrix (#164).
|
|
138
|
+
- IMEX Heun--trapezoidal now assembles its endpoint predictor and corrector as
|
|
139
|
+
additive solves, so nonlinear and non-commuting explicit/implicit splits
|
|
140
|
+
retain second-order convergence across NumPy and Array-API execution (#162).
|
|
141
|
+
- The public `Array` protocol now matches `array-api-compat` namespace
|
|
142
|
+
discovery and accepts native tensors such as `torch.Tensor` without requiring
|
|
143
|
+
a direct `__array_namespace__` method (#155).
|
|
144
|
+
- `flepimop2-op-engine` now types public trajectories with Flepimop2's
|
|
145
|
+
backend-neutral `Array` protocol and returns the native NumPy or JAX result
|
|
146
|
+
without a NumPy-only cast (#154).
|
|
147
|
+
- Explicit flepimop2 provider methods now add typed op_system axis-kernel,
|
|
148
|
+
advection, and diffusion drift at every Runge--Kutta stage. Flat, PyTree,
|
|
149
|
+
and block layouts share the same namespace-preserving operator path instead
|
|
150
|
+
of silently omitting operators outside IMEX execution (#140).
|
|
151
|
+
- Typed advection and transport descriptors now honor op_system's explicit
|
|
152
|
+
`increasing`/`decreasing` direction contract in both IMEX and explicit
|
|
153
|
+
provider paths while preserving JAX differentiation through dynamic
|
|
154
|
+
coefficients (#142).
|
|
155
|
+
|
|
156
|
+
## [0.2.0] - 2026-09-26
|
|
157
|
+
|
|
158
|
+
### Added
|
|
159
|
+
|
|
160
|
+
- Array-API-native state, explicit Runge--Kutta, dense implicit, and IMEX
|
|
161
|
+
paths preserve the namespace selected by the initial state. Portable
|
|
162
|
+
fixed-step methods support JAX tracing and differentiation without adding a
|
|
163
|
+
backend-specific solver to the core method surface (#75, #76, #85, #87,
|
|
164
|
+
#89, #90).
|
|
165
|
+
- Reusable explicit Runge--Kutta tableaus through Dormand--Prince 5(4),
|
|
166
|
+
adaptive step control with differentiable accepted-schedule replay, and
|
|
167
|
+
validated public run configuration (#95, #100, #108).
|
|
168
|
+
- A portable nonlinear-solver contract plus corrected ROS2, reusable
|
|
169
|
+
multistep BDF2, SDIRK2 diagnostics, and higher-order paired IMEX
|
|
170
|
+
Runge--Kutta integration (#113, #114, #115, #117, #118, #119).
|
|
171
|
+
- Backend-neutral fixed tau-leaping, exact direct SSA, and bounded adaptive
|
|
172
|
+
tau-leaping with injected random samplers (#102, #120, #121).
|
|
173
|
+
- Portable advection and typed diffusion/advection operator compilation for
|
|
174
|
+
flepimop2, including dynamic Array-API parameters (#81, #91, #92, #93).
|
|
175
|
+
- The flepimop2 provider consumes typed op_system reaction artifacts and can
|
|
176
|
+
execute deterministic, stochastic, or hybrid reaction networks without
|
|
177
|
+
parsing raw model configuration (#122).
|
|
178
|
+
|
|
179
|
+
### Changed
|
|
180
|
+
|
|
181
|
+
- Provider development and integration coverage now targets the op_system
|
|
182
|
+
0.4 typed operator/reaction contract.
|
|
183
|
+
- `flepimop2-op-engine` declares its actual `flepimop2>=0.3.0` compatibility
|
|
184
|
+
floor. Clean-wheel release validation installs the lowest published direct
|
|
185
|
+
dependencies instead of installing flepimop2 from its development branch.
|
|
186
|
+
- The documented flepimop2 installation uses the separately distributed
|
|
187
|
+
`flepimop2-op-engine` package. Its `op-system` extra installs the matching
|
|
188
|
+
system provider integration.
|
|
189
|
+
|
|
190
|
+
### Fixed
|
|
191
|
+
|
|
192
|
+
- Explicit Euler once again performs one Euler update per requested output
|
|
193
|
+
interval (#79).
|
|
194
|
+
- Solver-method documentation now matches the implemented biogeochemical
|
|
195
|
+
splitting order and backend boundaries (#98).
|
|
196
|
+
|
|
197
|
+
## [0.1.1] - 2026-04-27
|
|
198
|
+
|
|
199
|
+
- Initial PyPI release of `op-engine` and `flepimop2-op-engine`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: op_engine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Multiphysics integration engine for ODEs and PDEs using implicit-explicit operator splitting
|
|
5
5
|
Project-URL: Repository, https://github.com/ACCIDDA/op_engine
|
|
6
6
|
Project-URL: Documentation, https://accidda.github.io/op_engine/
|
|
@@ -39,12 +39,15 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
39
39
|
Classifier: Topic :: Scientific/Engineering
|
|
40
40
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
41
41
|
Requires-Python: <3.15,>=3.11
|
|
42
|
+
Requires-Dist: array-api-compat>=1.12
|
|
42
43
|
Requires-Dist: numpy>=2.0
|
|
43
44
|
Requires-Dist: scipy>=1.11
|
|
44
45
|
Provides-Extra: cupy
|
|
45
46
|
Requires-Dist: cupy>=13; extra == 'cupy'
|
|
46
47
|
Provides-Extra: jax
|
|
47
48
|
Requires-Dist: jax>=0.4.38; extra == 'jax'
|
|
49
|
+
Provides-Extra: torch
|
|
50
|
+
Requires-Dist: torch>=2.6; extra == 'torch'
|
|
48
51
|
Description-Content-Type: text/markdown
|
|
49
52
|
|
|
50
53
|
# op_engine
|
|
@@ -85,6 +88,18 @@ With JAX as the array namespace:
|
|
|
85
88
|
pip install "op_engine[jax]"
|
|
86
89
|
```
|
|
87
90
|
|
|
91
|
+
For the other packaged Array-API backends:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pip install "op_engine[torch]"
|
|
95
|
+
pip install "op_engine[cupy]"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
NumPy is installed with the core package. Backend support is method-specific;
|
|
99
|
+
consult the [backend guide](docs/guides/backends.md) before assuming that JIT,
|
|
100
|
+
automatic differentiation, adaptivity, or stochastic sampling have identical
|
|
101
|
+
semantics across namespaces.
|
|
102
|
+
|
|
88
103
|
With flepimop2 adapter:
|
|
89
104
|
|
|
90
105
|
```bash
|
|
@@ -124,9 +139,10 @@ solution = core.state_array # shape (n_timesteps, state, subgroup)
|
|
|
124
139
|
### Array namespaces
|
|
125
140
|
|
|
126
141
|
`ModelCore` infers its numerical namespace from the initial state through
|
|
127
|
-
`
|
|
142
|
+
`array_api_compat.array_namespace()`. The state, stored history, solver stages,
|
|
128
143
|
and dense solver operations stay in that namespace. There is no `xp=` or
|
|
129
|
-
backend option.
|
|
144
|
+
backend option. This supports standard Array-API objects and native arrays such
|
|
145
|
+
as `torch.Tensor`. Fixed-step methods can be differentiated by JAX; the built-in
|
|
130
146
|
adaptive controller is eager Python control flow. See the
|
|
131
147
|
[backend guide](docs/guides/backends.md) for the precise contract and the
|
|
132
148
|
[solver guide](docs/guides/solver-methods.md) for method-specific examples.
|
|
@@ -135,7 +151,8 @@ adaptive controller is eager Python control flow. See the
|
|
|
135
151
|
|
|
136
152
|
```python
|
|
137
153
|
import numpy as np
|
|
138
|
-
from op_engine import CoreSolver, ModelCore
|
|
154
|
+
from op_engine import CoreSolver, ModelCore
|
|
155
|
+
from op_engine.core_solver import OperatorSpecs, RunConfig
|
|
139
156
|
|
|
140
157
|
n = 4
|
|
141
158
|
times = np.linspace(0.0, 1.0, 11)
|
|
@@ -152,13 +169,20 @@ def rhs(t, y):
|
|
|
152
169
|
return -0.1 * y
|
|
153
170
|
|
|
154
171
|
|
|
155
|
-
solver = CoreSolver(core,
|
|
156
|
-
solver.run(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
172
|
+
solver = CoreSolver(core, operator_axis="state")
|
|
173
|
+
solver.run(
|
|
174
|
+
rhs,
|
|
175
|
+
config=RunConfig(
|
|
176
|
+
method="imex-heun-tr",
|
|
177
|
+
operators=ops,
|
|
178
|
+
),
|
|
179
|
+
)
|
|
160
180
|
```
|
|
161
181
|
|
|
182
|
+
This compact example uses identity left/right operators to show the API. For a
|
|
183
|
+
nontrivial implicit term, construct timestep-aware operator factories as in the
|
|
184
|
+
[solver guide](docs/guides/solver-methods.md#imex-methods-and-operator-factories).
|
|
185
|
+
|
|
162
186
|
## Public API
|
|
163
187
|
- `ModelCore`: state tensor + time grid manager; supports extra axes and optional history.
|
|
164
188
|
- `CoreSolver`: portable explicit and dense IMEX/implicit stepping selected by the state array namespace.
|
|
@@ -166,7 +190,9 @@ solver.run(rhs, config=None) # defaults: method="heun" (explicit)
|
|
|
166
190
|
- `AdaptiveTauLeapingSolver`: adaptive non-negative stochastic leaps using explicit reactant stoichiometry and injected Poisson plus exact-event sampling.
|
|
167
191
|
- `TauLeapingSolver`: portable stoichiometric updates with NumPy, JAX, or another Array-API namespace supplying Poisson samples.
|
|
168
192
|
- Operator utilities (`matrix_ops`): portable upwind advection, Laplacian, Crank–Nicolson/implicit Euler/trapezoidal operators, predictor-corrector builders, implicit solve cache, Kronecker helpers, and grouped aggregation utilities.
|
|
169
|
-
- Configuration helpers: `RunConfig`, `OperatorSpecs`, `AdaptiveConfig`,
|
|
193
|
+
- Configuration helpers: `RunConfig`, `OperatorSpecs`, `AdaptiveConfig`, and
|
|
194
|
+
`DtControllerConfig` from `op_engine.core_solver` for method, IMEX, and
|
|
195
|
+
adaptive control.
|
|
170
196
|
- Adapters: optional flepimop2 integration (extra dependency) via entrypoints in the adapter package. The adapter merges any `mixing_kernels` already computed by op_system (no automatic generation) and consumes config-supplied IMEX operator specs (dict or `OperatorSpecs`), forwarding the chosen `operator_axis` to `CoreSolver`.
|
|
171
197
|
|
|
172
198
|
## Development
|
|
@@ -36,6 +36,18 @@ With JAX as the array namespace:
|
|
|
36
36
|
pip install "op_engine[jax]"
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
For the other packaged Array-API backends:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install "op_engine[torch]"
|
|
43
|
+
pip install "op_engine[cupy]"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
NumPy is installed with the core package. Backend support is method-specific;
|
|
47
|
+
consult the [backend guide](docs/guides/backends.md) before assuming that JIT,
|
|
48
|
+
automatic differentiation, adaptivity, or stochastic sampling have identical
|
|
49
|
+
semantics across namespaces.
|
|
50
|
+
|
|
39
51
|
With flepimop2 adapter:
|
|
40
52
|
|
|
41
53
|
```bash
|
|
@@ -75,9 +87,10 @@ solution = core.state_array # shape (n_timesteps, state, subgroup)
|
|
|
75
87
|
### Array namespaces
|
|
76
88
|
|
|
77
89
|
`ModelCore` infers its numerical namespace from the initial state through
|
|
78
|
-
`
|
|
90
|
+
`array_api_compat.array_namespace()`. The state, stored history, solver stages,
|
|
79
91
|
and dense solver operations stay in that namespace. There is no `xp=` or
|
|
80
|
-
backend option.
|
|
92
|
+
backend option. This supports standard Array-API objects and native arrays such
|
|
93
|
+
as `torch.Tensor`. Fixed-step methods can be differentiated by JAX; the built-in
|
|
81
94
|
adaptive controller is eager Python control flow. See the
|
|
82
95
|
[backend guide](docs/guides/backends.md) for the precise contract and the
|
|
83
96
|
[solver guide](docs/guides/solver-methods.md) for method-specific examples.
|
|
@@ -86,7 +99,8 @@ adaptive controller is eager Python control flow. See the
|
|
|
86
99
|
|
|
87
100
|
```python
|
|
88
101
|
import numpy as np
|
|
89
|
-
from op_engine import CoreSolver, ModelCore
|
|
102
|
+
from op_engine import CoreSolver, ModelCore
|
|
103
|
+
from op_engine.core_solver import OperatorSpecs, RunConfig
|
|
90
104
|
|
|
91
105
|
n = 4
|
|
92
106
|
times = np.linspace(0.0, 1.0, 11)
|
|
@@ -103,13 +117,20 @@ def rhs(t, y):
|
|
|
103
117
|
return -0.1 * y
|
|
104
118
|
|
|
105
119
|
|
|
106
|
-
solver = CoreSolver(core,
|
|
107
|
-
solver.run(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
solver = CoreSolver(core, operator_axis="state")
|
|
121
|
+
solver.run(
|
|
122
|
+
rhs,
|
|
123
|
+
config=RunConfig(
|
|
124
|
+
method="imex-heun-tr",
|
|
125
|
+
operators=ops,
|
|
126
|
+
),
|
|
127
|
+
)
|
|
111
128
|
```
|
|
112
129
|
|
|
130
|
+
This compact example uses identity left/right operators to show the API. For a
|
|
131
|
+
nontrivial implicit term, construct timestep-aware operator factories as in the
|
|
132
|
+
[solver guide](docs/guides/solver-methods.md#imex-methods-and-operator-factories).
|
|
133
|
+
|
|
113
134
|
## Public API
|
|
114
135
|
- `ModelCore`: state tensor + time grid manager; supports extra axes and optional history.
|
|
115
136
|
- `CoreSolver`: portable explicit and dense IMEX/implicit stepping selected by the state array namespace.
|
|
@@ -117,7 +138,9 @@ solver.run(rhs, config=None) # defaults: method="heun" (explicit)
|
|
|
117
138
|
- `AdaptiveTauLeapingSolver`: adaptive non-negative stochastic leaps using explicit reactant stoichiometry and injected Poisson plus exact-event sampling.
|
|
118
139
|
- `TauLeapingSolver`: portable stoichiometric updates with NumPy, JAX, or another Array-API namespace supplying Poisson samples.
|
|
119
140
|
- Operator utilities (`matrix_ops`): portable upwind advection, Laplacian, Crank–Nicolson/implicit Euler/trapezoidal operators, predictor-corrector builders, implicit solve cache, Kronecker helpers, and grouped aggregation utilities.
|
|
120
|
-
- Configuration helpers: `RunConfig`, `OperatorSpecs`, `AdaptiveConfig`,
|
|
141
|
+
- Configuration helpers: `RunConfig`, `OperatorSpecs`, `AdaptiveConfig`, and
|
|
142
|
+
`DtControllerConfig` from `op_engine.core_solver` for method, IMEX, and
|
|
143
|
+
adaptive control.
|
|
121
144
|
- Adapters: optional flepimop2 integration (extra dependency) via entrypoints in the adapter package. The adapter merges any `mixing_kernels` already computed by op_system (no automatic generation) and consumes config-supplied IMEX operator specs (dict or `OperatorSpecs`), forwarding the chosen `operator_axis` to `CoreSolver`.
|
|
122
145
|
|
|
123
146
|
## Development
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="280" viewBox="0 0 900 280" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">Chemostat PDE operator split</title>
|
|
3
|
+
<desc id="desc">Population density and resource enter explicit reaction and implicit diffusion partitions, which are combined by the IMEX solver into observables.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
|
6
|
+
<path d="M0,0 L0,6 L9,3 z" fill="#4051b5"/>
|
|
7
|
+
</marker>
|
|
8
|
+
<style>
|
|
9
|
+
.box { fill: #f7f8fc; stroke: #4051b5; stroke-width: 2; rx: 10; }
|
|
10
|
+
.accent { fill: #eef7f3; stroke: #16866b; stroke-width: 2; rx: 10; }
|
|
11
|
+
.text { font: 16px sans-serif; fill: #202124; text-anchor: middle; }
|
|
12
|
+
.small { font: 13px sans-serif; fill: #4b4d52; text-anchor: middle; }
|
|
13
|
+
.arrow { stroke: #4051b5; stroke-width: 2.5; fill: none; marker-end: url(#arrow); }
|
|
14
|
+
</style>
|
|
15
|
+
</defs>
|
|
16
|
+
<rect class="box" x="25" y="75" width="190" height="120"/>
|
|
17
|
+
<text class="text" x="120" y="110">State</text>
|
|
18
|
+
<text class="small" x="120" y="140">trait density n(theta)</text>
|
|
19
|
+
<text class="small" x="120" y="165">resource S</text>
|
|
20
|
+
|
|
21
|
+
<path class="arrow" d="M215 115 H295"/>
|
|
22
|
+
<path class="arrow" d="M215 165 H295"/>
|
|
23
|
+
|
|
24
|
+
<rect class="accent" x="300" y="25" width="245" height="95"/>
|
|
25
|
+
<text class="text" x="422" y="58">Explicit partition</text>
|
|
26
|
+
<text class="small" x="422" y="83">growth + dilution</text>
|
|
27
|
+
<text class="small" x="422" y="103">resource coupling</text>
|
|
28
|
+
|
|
29
|
+
<rect class="accent" x="300" y="155" width="245" height="95"/>
|
|
30
|
+
<text class="text" x="422" y="188">Implicit partition</text>
|
|
31
|
+
<text class="small" x="422" y="213">non-uniform no-flux</text>
|
|
32
|
+
<text class="small" x="422" y="233">trait diffusion</text>
|
|
33
|
+
|
|
34
|
+
<path class="arrow" d="M545 73 C600 73 600 112 640 125"/>
|
|
35
|
+
<path class="arrow" d="M545 203 C600 203 600 164 640 151"/>
|
|
36
|
+
|
|
37
|
+
<rect class="box" x="645" y="75" width="225" height="120"/>
|
|
38
|
+
<text class="text" x="757" y="110">IMEX trajectory</text>
|
|
39
|
+
<text class="small" x="757" y="140">biomass + resource</text>
|
|
40
|
+
<text class="small" x="757" y="165">mean + central moments</text>
|
|
41
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Finite-element assembly boundary
|
|
2
|
+
|
|
3
|
+
Finite elements are a spatial discretization and assembly system, not a new
|
|
4
|
+
`CoreSolver` time-method name. The current decision is **not to add a mesh,
|
|
5
|
+
element, or quadrature framework to `op_engine` core and not to create an
|
|
6
|
+
`op_engine-fem` provider yet**. Externally assembled linear finite-element
|
|
7
|
+
systems can already use the stage-operator interface.
|
|
8
|
+
|
|
9
|
+
## Supported semi-discrete form
|
|
10
|
+
|
|
11
|
+
Consider the linear system
|
|
12
|
+
|
|
13
|
+
\[
|
|
14
|
+
M \dot y = K y + g(t, y).
|
|
15
|
+
\]
|
|
16
|
+
|
|
17
|
+
An operator factory can map its implicit linear part into the existing solve
|
|
18
|
+
|
|
19
|
+
\[
|
|
20
|
+
L y_{n+1} = R x.
|
|
21
|
+
\]
|
|
22
|
+
|
|
23
|
+
For example, the factory for a trapezoidal stage of size \(h\) returns
|
|
24
|
+
|
|
25
|
+
\[
|
|
26
|
+
L = M - \tfrac{h}{2}K,
|
|
27
|
+
\qquad
|
|
28
|
+
R = M + \tfrac{h}{2}K.
|
|
29
|
+
\]
|
|
30
|
+
|
|
31
|
+
An implicit-Euler stage instead returns \(L=M-hK\) and \(R=M\). This supports
|
|
32
|
+
fixed or variable stage sizes and permits the existing IMEX Euler,
|
|
33
|
+
Heun--trapezoidal, and TR-BDF2 control flow to advance an externally assembled
|
|
34
|
+
mass/stiffness pair.
|
|
35
|
+
|
|
36
|
+
The explicit callable still has derivative units. A finite-element load vector
|
|
37
|
+
`g` must therefore be supplied as `solve(M, g)`; the current API does not inject
|
|
38
|
+
an unscaled weak-form load directly. Likewise, `imex-ark3` currently consumes
|
|
39
|
+
only the left implicit solve at each additive stage, so a non-identity mass
|
|
40
|
+
matrix must be resolved to an operator such as `solve(M, K)` before using that
|
|
41
|
+
method. Changing either contract should be motivated and designed separately,
|
|
42
|
+
not hidden in an FEM adapter.
|
|
43
|
+
|
|
44
|
+
## Ownership
|
|
45
|
+
|
|
46
|
+
The assembly package or application owns:
|
|
47
|
+
|
|
48
|
+
- mesh topology and geometry;
|
|
49
|
+
- basis functions, quadrature, and element integration;
|
|
50
|
+
- degree-of-freedom maps and constraint elimination;
|
|
51
|
+
- essential and natural boundary-condition assembly;
|
|
52
|
+
- mass, stiffness, load, and nonlinear residual assembly;
|
|
53
|
+
- any matrix reassembly or preconditioner lifecycle.
|
|
54
|
+
|
|
55
|
+
`op_engine` owns time integration, stage context, operator-axis application,
|
|
56
|
+
accepted-step control, and the `L @ y_next = R @ x` solve boundary. This keeps
|
|
57
|
+
optional SciPy, PETSc, FEniCSx, and JAX FEM ecosystems from becoming core
|
|
58
|
+
dependencies.
|
|
59
|
+
|
|
60
|
+
## Sparse operators and differentiation
|
|
61
|
+
|
|
62
|
+
SciPy CSR pairs use the registered sparse adapter. A provider should reuse
|
|
63
|
+
operator objects when their values do not change so the factorization cache is
|
|
64
|
+
effective. Other array backends can supply dense native arrays and use the
|
|
65
|
+
Array-API linear solve. A JAX-native dense assembly remains differentiable
|
|
66
|
+
through matrix construction and solve; eager NumPy/SciPy assembly and
|
|
67
|
+
factorization do not. An external PETSc or FEniCSx provider would own any
|
|
68
|
+
custom differentiation rule for its solver.
|
|
69
|
+
|
|
70
|
+
Nonlinear forms such as \(M(y,t)\dot y=r(t,y)\), differential-algebraic
|
|
71
|
+
constraints, and external Krylov/preconditioner callbacks are outside the
|
|
72
|
+
current stage-operator contract. They require a concrete use case before a new
|
|
73
|
+
portable solver interface is added.
|
|
74
|
+
|
|
75
|
+
## Proof of concept
|
|
76
|
+
|
|
77
|
+
[`examples/fem_diffusion.py`](https://github.com/ACCIDDA/op_engine/blob/main/examples/fem_diffusion.py)
|
|
78
|
+
assembles a consistent P1 mass matrix and diffusion stiffness matrix on a 1D
|
|
79
|
+
mesh, eliminates homogeneous Dirichlet boundary degrees of freedom, and runs
|
|
80
|
+
the result with fixed-step `imex-heun-tr`. Assembly uses SciPy only because the
|
|
81
|
+
example is demonstrating the external sparse boundary; it is not a required
|
|
82
|
+
FEM representation.
|
|
83
|
+
|
|
84
|
+
The example intentionally uses fixed steps. For pure implicit diffusion,
|
|
85
|
+
Heun--trapezoidal's embedded low/high pair differs only in its explicit term
|
|
86
|
+
and therefore cannot estimate the implicit truncation error. Adaptive FEM runs
|
|
87
|
+
should currently use a method with a suitable estimator, such as TR-BDF2 step
|
|
88
|
+
doubling, or supply and validate a fixed/replayed mesh at a higher layer.
|
|
89
|
+
|
|
90
|
+
## When to reconsider a provider
|
|
91
|
+
|
|
92
|
+
Reconsider a separate provider, rather than extending core, when at least one
|
|
93
|
+
real unstructured-domain model identifies all of the following:
|
|
94
|
+
|
|
95
|
+
1. a specific external assembly and linear-solver ecosystem;
|
|
96
|
+
2. required boundary-condition and constraint semantics;
|
|
97
|
+
3. sparse/device and differentiation expectations;
|
|
98
|
+
4. nonlinear and reassembly requirements;
|
|
99
|
+
5. a benchmark showing that the existing `(L, R)` boundary is insufficient.
|
|
100
|
+
|
|
101
|
+
Until then, the proof of concept is the supported design pattern and a general
|
|
102
|
+
FEM abstraction would add dependencies and policy without a validated user.
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Backend and solve-strategy boundaries
|
|
2
2
|
|
|
3
|
-
`op_engine` selects the numerical namespace from the state array
|
|
3
|
+
`op_engine` selects the numerical namespace from the state array with
|
|
4
|
+
`array_api_compat.array_namespace()`. This supports arrays with the standard
|
|
5
|
+
`__array_namespace__` protocol and native arrays such as `torch.Tensor`
|
|
6
|
+
through compatibility namespaces. Explicit,
|
|
4
7
|
dense linearly implicit, and IMEX methods use that namespace's Array-API
|
|
5
8
|
operations, including `linalg.solve`. The numerical method does not change when
|
|
6
9
|
the array namespace changes.
|
|
@@ -9,6 +12,10 @@ Namespace portability and automatic differentiation are related but distinct.
|
|
|
9
12
|
The Array API does not define `grad`, `jit`, or traced control flow. A namespace
|
|
10
13
|
such as JAX can nevertheless differentiate the ordinary `op_engine` methods
|
|
11
14
|
because their fixed-step numerical operations remain in that namespace.
|
|
15
|
+
The optional `torch` dependency group qualifies representative eager
|
|
16
|
+
fixed-step kernels with PyTorch autograd. Compiler integration and complete
|
|
17
|
+
method qualification remain provider/backend capabilities rather than
|
|
18
|
+
consequences of namespace selection.
|
|
12
19
|
|
|
13
20
|
## Portable fixed-step methods
|
|
14
21
|
|
|
@@ -47,15 +54,17 @@ parameters move, and always refresh it after a material model or tolerance
|
|
|
47
54
|
change. A schedule is validated against the output grid, while matching the
|
|
48
55
|
method and other configuration is the caller's responsibility.
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
Direct `CoreSolver` schedule replay statically unrolls accepted steps when JAX
|
|
58
|
+
traces it. The optional flepimop2 provider consumes the same functional kernels
|
|
59
|
+
but defaults explicit JAX replay to one compact `lax.scan`. Its forced compact
|
|
60
|
+
mode can also checkpoint/rematerialize each step for long reverse-mode solves;
|
|
61
|
+
non-JAX and nonlinear-diagnostic paths retain portable core replay.
|
|
53
62
|
|
|
54
|
-
Projects that require a compiled adaptive controller
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
Projects that require a compiled adaptive controller or other solver-specific
|
|
64
|
+
capabilities can provide those at an external plugin or provider boundary. A
|
|
65
|
+
specialized integration may return a complete trajectory and adopt it through
|
|
66
|
+
`ModelCore.apply_trajectory`; it does not need to add a backend-specific method
|
|
67
|
+
to `CoreSolver`.
|
|
59
68
|
|
|
60
69
|
This keeps optional packages such as Diffrax out of the core dependency and
|
|
61
70
|
method surfaces. The portable fixed-step methods and their differentiation
|
|
@@ -73,6 +82,9 @@ semantics.
|
|
|
73
82
|
`NumpySSASampler` and `NumpyPoissonSampler` are provided as conveniences.
|
|
74
83
|
JAX users can construct fresh keys from the stable direct-SSA `draw_index` or
|
|
75
84
|
tau-leaping `step_index`.
|
|
85
|
+
Direct SSA's optional forcing schedule is static Python configuration. It
|
|
86
|
+
preserves the state namespace and uses fresh draw indices when a forcing
|
|
87
|
+
boundary invalidates a pending event; observation times retain that event.
|
|
76
88
|
|
|
77
89
|
The current safety checks and stochastic event loops are eager, so these
|
|
78
90
|
stochastic solvers are not JIT-compatible paths. Adaptive selection also reads
|