op-engine 0.3.0__tar.gz → 0.5.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.3.0 → op_engine-0.5.0}/CHANGELOG.md +103 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/PKG-INFO +36 -9
- {op_engine-0.3.0 → op_engine-0.5.0}/README.md +35 -8
- {op_engine-0.3.0 → op_engine-0.5.0}/docs/guides/backends.md +3 -0
- op_engine-0.5.0/docs/guides/bounded-thinning.md +112 -0
- op_engine-0.5.0/docs/guides/op-system-reactions.md +146 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/docs/guides/solver-methods.md +106 -6
- op_engine-0.5.0/docs/guides/steady-states.md +183 -0
- op_engine-0.5.0/flepimop2-op_engine/README.md +745 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/benchmarks/README.md +4 -2
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/benchmarks/prepared_execution.py +78 -19
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/pyproject.toml +17 -9
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/src/flepimop2/engine/op_engine/__init__.py +369 -31
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/src/flepimop2/engine/op_engine/config.py +48 -1
- op_engine-0.5.0/flepimop2-op_engine/src/flepimop2/engine/op_engine/reactions.py +119 -0
- op_engine-0.5.0/flepimop2-op_engine/tests/test_adaptive_tau_dependencies.py +145 -0
- op_engine-0.5.0/flepimop2-op_engine/tests/test_adaptive_tau_generated.py +173 -0
- op_engine-0.5.0/flepimop2-op_engine/tests/test_axis_less_reactions.py +210 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/tests/test_config.py +123 -0
- op_engine-0.5.0/flepimop2-op_engine/tests/test_coord_shift.py +335 -0
- op_engine-0.5.0/flepimop2-op_engine/tests/test_core_reaction_network.py +107 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/tests/test_engine.py +10 -2
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/tests/test_op_system_integration.py +497 -24
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/tests/test_reactions.py +33 -3
- op_engine-0.5.0/flepimop2-op_engine/tests/test_renewal.py +463 -0
- op_engine-0.5.0/flepimop2-op_engine/tests/test_routed_reactions.py +257 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/tests/test_solver_matrix_benchmark.py +36 -1
- op_engine-0.5.0/flepimop2-op_engine/tests/test_steady_state_op_system.py +94 -0
- op_engine-0.5.0/flepimop2-op_engine/tests/test_stochastic_coverage.py +198 -0
- op_engine-0.5.0/flepimop2-op_engine/tests/test_thinning.py +515 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/mkdocs.yml +6 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/pyproject.toml +2 -1
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/__init__.py +42 -0
- op_engine-0.5.0/src/op_engine/_forcing.py +53 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/adaptive_tau.py +160 -9
- op_engine-0.5.0/src/op_engine/reactions.py +965 -0
- op_engine-0.5.0/src/op_engine/steady_state.py +658 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/stochastic_solver.py +84 -26
- op_engine-0.5.0/src/op_engine/thinning_ssa.py +443 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_adaptive_tau.py +361 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_direct_ssa.py +262 -3
- op_engine-0.5.0/tests/op_engine/test_reactions.py +312 -0
- op_engine-0.5.0/tests/op_engine/test_steady_state.py +258 -0
- op_engine-0.5.0/tests/op_engine/test_tau_forcing.py +247 -0
- op_engine-0.5.0/tests/op_engine/test_thinning_ssa.py +509 -0
- op_engine-0.3.0/flepimop2-op_engine/README.md +0 -381
- op_engine-0.3.0/flepimop2-op_engine/src/flepimop2/engine/op_engine/reactions.py +0 -622
- {op_engine-0.3.0 → op_engine-0.5.0}/.github/workflows/ci.yaml +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/.github/workflows/gh-pages.yaml +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/.github/workflows/release.yaml +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/.gitignore +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/LICENSE +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/docs/api-reference/.gitignore +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/docs/assets/chemostat-split.svg +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/docs/assets/chemostat-summary.png +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/docs/development/creating-a-release.md +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/docs/development/fem-assembly-boundary.md +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/docs/guides/biogeochemical-network.md +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/docs/guides/chemostat-pde.md +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/docs/guides/getting-started.md +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/docs/guides/linear-multistep-methods.md +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/docs/guides/nonlinear-solvers.md +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/docs/index.md +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/examples/__init__.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/examples/biogeochemical_network.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/examples/chemostat_pde.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/examples/fem_diffusion.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/examples/simple_sir.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/examples/test_biogeochemical_network.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/examples/test_chemostat_pde.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/examples/test_fem_diffusion.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/LICENSE +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/benchmarks/__init__.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/benchmarks/adaptive_replay.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/benchmarks/solver_matrix.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/benchmarks/structured_blocks.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/src/flepimop2/engine/op_engine/explicit_operators.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/src/flepimop2/engine/op_engine/operators.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/src/flepimop2/engine/op_engine/py.typed +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/tests/conftest.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/tests/data/config.yaml +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/tests/data/sir.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/tests/test_integration_cli.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/flepimop2-op_engine/tests/test_operators.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/justfile +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/overrides/main.html +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/scripts/__init__.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/scripts/api-reference.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/scripts/release_validate.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/_additive_runge_kutta.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/_array.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/_multistep.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/_rosenbrock.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/_runge_kutta.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/_sdirk.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/_typing.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/core_solver.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/matrix_ops.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/model_core.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/nonlinear_solver.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/py.typed +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/src/op_engine/spatial_grid.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/conftest.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test__version__.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_additive_runge_kutta.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_array_namespace.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_configuration.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_core_solver_accuracy.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_core_solver_ark.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_core_solver_plumbing.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_core_solver_sdirk.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_matrix_ops.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_model_core.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_multistep.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_nonlinear_solver.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_rosenbrock.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_runge_kutta.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_sdirk.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_spatial_grid.py +0 -0
- {op_engine-0.3.0 → op_engine-0.5.0}/tests/op_engine/test_stochastic_solver.py +0 -0
|
@@ -5,6 +5,109 @@ versioning while the project remains pre-1.0.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.5.0] - 2026-10-06
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `op_engine.from_compiled_rhs(compiled, params)` and
|
|
13
|
+
`op_engine.compile_reaction_network(reactions, template_shapes=...,
|
|
14
|
+
axis_sizes=..., params=...)` build the flat stochastic reaction network from
|
|
15
|
+
op_system reaction artifacts in the core package, with no flepimop2
|
|
16
|
+
dependency. `from_compiled_rhs` refuses a RHS with reaction gaps unless
|
|
17
|
+
`allow_gaps=True` or a `reaction_names` partition is given. The new guide
|
|
18
|
+
covers the `mean_drift` consistency check against the deterministic RHS
|
|
19
|
+
(#191).
|
|
20
|
+
- `op_engine.steady_state(rhs, y0, ...)` finds equilibria by pseudo-transient
|
|
21
|
+
continuation, without a long burn-in. `fixed` holds absorbing states
|
|
22
|
+
(cumulative counters) at their starting values; `invariants` enforces
|
|
23
|
+
conserved totals exactly through a bordered step, so `dt` can grow without
|
|
24
|
+
the total drifting; and convergence requires a small Newton correction as
|
|
25
|
+
well as a small residual, which catches slowly decaying modes.
|
|
26
|
+
`conserved_quantities`, `linear_invariants` (including rate-balanced totals
|
|
27
|
+
such as births `mu * N`), and `sink_states` find what to pass.
|
|
28
|
+
Diagnostics stay arrays, and `loop=jax.lax.fori_loop` runs the solve under
|
|
29
|
+
`jax.jit` and `jax.vmap`. See the new steady-states guide (#192).
|
|
30
|
+
- `AdaptiveTauLeapingSolver` accepts `dependency_incidence` and
|
|
31
|
+
`propensity_orders` for reactions that are not mass action, such as
|
|
32
|
+
frequency-dependent infection. Each species a reaction reads gets a
|
|
33
|
+
Cao-Gillespie-Petzold scaling of at least the reaction's elasticity order,
|
|
34
|
+
so its propensity's relative change stays within the leap tolerance; orders
|
|
35
|
+
above three are allowed. `CompiledReactionNetwork` builds both arrays from
|
|
36
|
+
the `dependencies`, `propensity_order`, and `dependencies_complete` fields
|
|
37
|
+
op_system 0.7.0 publishes for `reactants: auto`, counts such reactions as
|
|
38
|
+
complete, and the provider passes them to the solver, so adaptive
|
|
39
|
+
tau-leaping runs frequency-dependent op_system models (#193).
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- The flepimop2 provider's `compile_reaction_network(system, params, ...)` is
|
|
44
|
+
now a thin adapter over the core function, with unchanged behavior and
|
|
45
|
+
error messages. `CompiledReactionNetwork` moved to `op_engine.reactions`
|
|
46
|
+
(still re-exported by the provider) (#191).
|
|
47
|
+
- The provider requires op_system 0.7.0. With it, a reaction whose rate reads
|
|
48
|
+
no state is complete without declarations, so constant-rate flows no longer
|
|
49
|
+
block adaptive tau-leaping, and the incomplete-reactants message suggests
|
|
50
|
+
`reactants: auto` / `catalysts: auto` (#191).
|
|
51
|
+
|
|
52
|
+
## [0.4.0] - 2026-10-02
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- Prepared Flepimop2 execution plans now support explicit PyTree and JAX block
|
|
57
|
+
layouts for fixed steps and frozen adaptive replay, including structured typed
|
|
58
|
+
operators, while preserving dynamic-value cache contracts (#168).
|
|
59
|
+
- Direct SSA accepts explicit forcing breakpoints for exact piecewise-constant
|
|
60
|
+
rates, including dormant zero-rate intervals, while retaining pending events
|
|
61
|
+
across observation times. The provider exposes this for pure direct SSA (#171).
|
|
62
|
+
- Fixed and adaptive tau-leaping stop at declared forcing changes, so each
|
|
63
|
+
leap segment uses its own starting rate; forcing boundaries take precedence
|
|
64
|
+
over tied critical or exact-fallback events (#174).
|
|
65
|
+
- Pure stochastic runs consume the producer's declared `forcing_breakpoints`
|
|
66
|
+
(op_system `time_interpolation: previous`), combined with explicit engine
|
|
67
|
+
boundaries. Hybrid mode reports producer forcing as unsupported (#175).
|
|
68
|
+
- `stochastic_method: thinning-ssa` gives exact bounded thinning for smoothly
|
|
69
|
+
time-varying rates. Supply a total-rate bound as `thinning_rate_bound` or a
|
|
70
|
+
`RateBoundFunction` through `run(..., rate_bound=...)`; `ThinningSSASolver`
|
|
71
|
+
is available in the core package (#173, #176, #177).
|
|
72
|
+
- Stochastic execution consumes axis-wide `coord_shift` reactions from
|
|
73
|
+
op_system: each source bin of an `offsets` axis becomes a channel that moves
|
|
74
|
+
one unit by the published step, and off-axis destinations only deplete the
|
|
75
|
+
donor. Direct SSA reproduces Poisson bin occupancy and Erlang exit times for
|
|
76
|
+
pure aging chains (#180).
|
|
77
|
+
- Pure stochastic validation and execution reject models with dynamics that
|
|
78
|
+
have no reaction artifact: transitions listed in the producer's
|
|
79
|
+
`reaction_gaps` coverage records, and typed operators. Each uncovered
|
|
80
|
+
transition is named by origin, selectors, and reason. Hybrid mode and
|
|
81
|
+
producers without coverage records are unchanged (#182).
|
|
82
|
+
- Stochastic execution supports reactions between templates with different
|
|
83
|
+
axes, including axis-less states: scalar S→I→R models, axis-less donors
|
|
84
|
+
depositing into a pinned cell, and templated sources collapsing into an
|
|
85
|
+
axis-less state. Targets are indexed with op_system's `to_full_axes`,
|
|
86
|
+
falling back to `full_axes` for older producers (#184).
|
|
87
|
+
- Stochastic execution runs routing and target-only fan-out reactions: each
|
|
88
|
+
source cell and routed target coordinate (op_system's `routed_axes`) is one
|
|
89
|
+
channel, so routing generators move discrete units between bins (#186).
|
|
90
|
+
- Adaptive tau-leaping validation reports incomplete reactant metadata before
|
|
91
|
+
running, and both validation and run name each incomplete reaction and the
|
|
92
|
+
remedy: `reactants` on ordinary op_system transitions, or `catalysts` on
|
|
93
|
+
`chain:` and `coord_shift` entries (#188).
|
|
94
|
+
|
|
95
|
+
### Changed
|
|
96
|
+
|
|
97
|
+
- The provider's optional `op_system` integration requires
|
|
98
|
+
`flepimop2-op-system>=0.6.0` and `op-system>=0.6.0`.
|
|
99
|
+
- Pure stochastic runs that previously dropped uncovered transitions or typed
|
|
100
|
+
operators silently now fail validation; use `mode: hybrid` or give each
|
|
101
|
+
transition a reaction artifact (#182).
|
|
102
|
+
|
|
103
|
+
### Fixed
|
|
104
|
+
|
|
105
|
+
- `PreparedExecution` can be passed directly to JAX transformations, including
|
|
106
|
+
mixed NumPy/JAX samples whose effective namespaces and dtypes are canonicalized
|
|
107
|
+
by JAX during tracing (#168).
|
|
108
|
+
- Pinned source-only renewal births are accepted: donor-axis coverage is
|
|
109
|
+
validated only for reactions with a donor (#178, #179).
|
|
110
|
+
|
|
8
111
|
## [0.3.0] - 2026-09-27
|
|
9
112
|
|
|
10
113
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: op_engine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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/
|
|
@@ -67,13 +67,17 @@ Operator-Partitioned Engine (OP Engine) is a lightweight multiphysics solver cor
|
|
|
67
67
|
- `ModelCore`: state/time manager; configure axes, dtype, and optional history.
|
|
68
68
|
- `CoreSolver`: portable explicit methods through Dormand--Prince 5(4), plus dense IMEX and linearly implicit methods; accepts `RunConfig` with `AdaptiveConfig`, `DtControllerConfig`, and `OperatorSpecs`.
|
|
69
69
|
- `DirectSSASolver`: exact Gillespie direct-method trajectories with injected, backend-specific exponential and categorical sampling.
|
|
70
|
-
- `AdaptiveTauLeapingSolver`: bounded adaptive tau-leaping with leap-condition control, exact critical events, and explicit post-leap rejection.
|
|
70
|
+
- `AdaptiveTauLeapingSolver`: bounded adaptive tau-leaping with leap-condition control, exact critical events, and explicit post-leap rejection. Non-mass-action propensities (frequency-dependent infection) are supported through per-reaction dependency incidence and elasticity orders.
|
|
71
71
|
- `TauLeapingSolver`: fixed-step stochastic reaction-network integration with injected, backend-specific Poisson sampling.
|
|
72
|
+
- `from_compiled_rhs` / `compile_reaction_network`: build the flat stochastic network (stoichiometry, reactant orders, propensity) from an op_system spec's reaction artifacts, with no flepimop2 dependency.
|
|
73
|
+
- `steady_state`: equilibria by pseudo-transient continuation, with absorbing states held fixed, conserved totals enforced exactly, and a Newton-correction convergence test; runs under `jax.jit`/`vmap`.
|
|
72
74
|
- `matrix_ops`: portable dense advection/diffusion, sparse Laplacian/Crank–Nicolson, implicit Euler/trapezoidal builders, predictor–corrector, implicit solve cache, Kronecker helpers, and grouped aggregations.
|
|
73
75
|
- Extras: `OperatorSpecs`, `RunConfig`, `AdaptiveConfig`, `DtControllerConfig`, `Operator`, `GridGeometry`, `DiffusionConfig`.
|
|
74
76
|
|
|
75
77
|
See the documentation guides for [solver selection](docs/guides/solver-methods.md),
|
|
76
78
|
[the biogeochemical splitting tutorial](docs/guides/biogeochemical-network.md),
|
|
79
|
+
[stochastic simulation from an op_system spec](docs/guides/op-system-reactions.md),
|
|
80
|
+
[steady states](docs/guides/steady-states.md),
|
|
77
81
|
and [backend boundaries](docs/guides/backends.md).
|
|
78
82
|
|
|
79
83
|
## Installation
|
|
@@ -88,6 +92,18 @@ With JAX as the array namespace:
|
|
|
88
92
|
pip install "op_engine[jax]"
|
|
89
93
|
```
|
|
90
94
|
|
|
95
|
+
For the other packaged Array-API backends:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pip install "op_engine[torch]"
|
|
99
|
+
pip install "op_engine[cupy]"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
NumPy is installed with the core package. Backend support is method-specific;
|
|
103
|
+
consult the [backend guide](docs/guides/backends.md) before assuming that JIT,
|
|
104
|
+
automatic differentiation, adaptivity, or stochastic sampling have identical
|
|
105
|
+
semantics across namespaces.
|
|
106
|
+
|
|
91
107
|
With flepimop2 adapter:
|
|
92
108
|
|
|
93
109
|
```bash
|
|
@@ -139,7 +155,8 @@ adaptive controller is eager Python control flow. See the
|
|
|
139
155
|
|
|
140
156
|
```python
|
|
141
157
|
import numpy as np
|
|
142
|
-
from op_engine import CoreSolver, ModelCore
|
|
158
|
+
from op_engine import CoreSolver, ModelCore
|
|
159
|
+
from op_engine.core_solver import OperatorSpecs, RunConfig
|
|
143
160
|
|
|
144
161
|
n = 4
|
|
145
162
|
times = np.linspace(0.0, 1.0, 11)
|
|
@@ -156,21 +173,31 @@ def rhs(t, y):
|
|
|
156
173
|
return -0.1 * y
|
|
157
174
|
|
|
158
175
|
|
|
159
|
-
solver = CoreSolver(core,
|
|
160
|
-
solver.run(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
176
|
+
solver = CoreSolver(core, operator_axis="state")
|
|
177
|
+
solver.run(
|
|
178
|
+
rhs,
|
|
179
|
+
config=RunConfig(
|
|
180
|
+
method="imex-heun-tr",
|
|
181
|
+
operators=ops,
|
|
182
|
+
),
|
|
183
|
+
)
|
|
164
184
|
```
|
|
165
185
|
|
|
186
|
+
This compact example uses identity left/right operators to show the API. For a
|
|
187
|
+
nontrivial implicit term, construct timestep-aware operator factories as in the
|
|
188
|
+
[solver guide](docs/guides/solver-methods.md#imex-methods-and-operator-factories).
|
|
189
|
+
|
|
166
190
|
## Public API
|
|
167
191
|
- `ModelCore`: state tensor + time grid manager; supports extra axes and optional history.
|
|
168
192
|
- `CoreSolver`: portable explicit and dense IMEX/implicit stepping selected by the state array namespace.
|
|
169
193
|
- `DirectSSASolver`: exact event-by-event stochastic reaction trajectories with NumPy, JAX, or another Array-API namespace supplying random draws.
|
|
170
194
|
- `AdaptiveTauLeapingSolver`: adaptive non-negative stochastic leaps using explicit reactant stoichiometry and injected Poisson plus exact-event sampling.
|
|
171
195
|
- `TauLeapingSolver`: portable stoichiometric updates with NumPy, JAX, or another Array-API namespace supplying Poisson samples.
|
|
196
|
+
- `op_engine.reactions`: `from_compiled_rhs(compiled, params)` and `compile_reaction_network(reactions, template_shapes=..., axis_sizes=..., params=...)` return a `CompiledReactionNetwork` whose `stoichiometry`, `reactant_stoichiometry`, and `propensity` feed the stochastic solvers; `mean_drift` checks the network against the deterministic RHS.
|
|
172
197
|
- 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.
|
|
173
|
-
- Configuration helpers: `RunConfig`, `OperatorSpecs`, `AdaptiveConfig`,
|
|
198
|
+
- Configuration helpers: `RunConfig`, `OperatorSpecs`, `AdaptiveConfig`, and
|
|
199
|
+
`DtControllerConfig` from `op_engine.core_solver` for method, IMEX, and
|
|
200
|
+
adaptive control.
|
|
174
201
|
- 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`.
|
|
175
202
|
|
|
176
203
|
## Development
|
|
@@ -15,13 +15,17 @@ Operator-Partitioned Engine (OP Engine) is a lightweight multiphysics solver cor
|
|
|
15
15
|
- `ModelCore`: state/time manager; configure axes, dtype, and optional history.
|
|
16
16
|
- `CoreSolver`: portable explicit methods through Dormand--Prince 5(4), plus dense IMEX and linearly implicit methods; accepts `RunConfig` with `AdaptiveConfig`, `DtControllerConfig`, and `OperatorSpecs`.
|
|
17
17
|
- `DirectSSASolver`: exact Gillespie direct-method trajectories with injected, backend-specific exponential and categorical sampling.
|
|
18
|
-
- `AdaptiveTauLeapingSolver`: bounded adaptive tau-leaping with leap-condition control, exact critical events, and explicit post-leap rejection.
|
|
18
|
+
- `AdaptiveTauLeapingSolver`: bounded adaptive tau-leaping with leap-condition control, exact critical events, and explicit post-leap rejection. Non-mass-action propensities (frequency-dependent infection) are supported through per-reaction dependency incidence and elasticity orders.
|
|
19
19
|
- `TauLeapingSolver`: fixed-step stochastic reaction-network integration with injected, backend-specific Poisson sampling.
|
|
20
|
+
- `from_compiled_rhs` / `compile_reaction_network`: build the flat stochastic network (stoichiometry, reactant orders, propensity) from an op_system spec's reaction artifacts, with no flepimop2 dependency.
|
|
21
|
+
- `steady_state`: equilibria by pseudo-transient continuation, with absorbing states held fixed, conserved totals enforced exactly, and a Newton-correction convergence test; runs under `jax.jit`/`vmap`.
|
|
20
22
|
- `matrix_ops`: portable dense advection/diffusion, sparse Laplacian/Crank–Nicolson, implicit Euler/trapezoidal builders, predictor–corrector, implicit solve cache, Kronecker helpers, and grouped aggregations.
|
|
21
23
|
- Extras: `OperatorSpecs`, `RunConfig`, `AdaptiveConfig`, `DtControllerConfig`, `Operator`, `GridGeometry`, `DiffusionConfig`.
|
|
22
24
|
|
|
23
25
|
See the documentation guides for [solver selection](docs/guides/solver-methods.md),
|
|
24
26
|
[the biogeochemical splitting tutorial](docs/guides/biogeochemical-network.md),
|
|
27
|
+
[stochastic simulation from an op_system spec](docs/guides/op-system-reactions.md),
|
|
28
|
+
[steady states](docs/guides/steady-states.md),
|
|
25
29
|
and [backend boundaries](docs/guides/backends.md).
|
|
26
30
|
|
|
27
31
|
## Installation
|
|
@@ -36,6 +40,18 @@ With JAX as the array namespace:
|
|
|
36
40
|
pip install "op_engine[jax]"
|
|
37
41
|
```
|
|
38
42
|
|
|
43
|
+
For the other packaged Array-API backends:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install "op_engine[torch]"
|
|
47
|
+
pip install "op_engine[cupy]"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
NumPy is installed with the core package. Backend support is method-specific;
|
|
51
|
+
consult the [backend guide](docs/guides/backends.md) before assuming that JIT,
|
|
52
|
+
automatic differentiation, adaptivity, or stochastic sampling have identical
|
|
53
|
+
semantics across namespaces.
|
|
54
|
+
|
|
39
55
|
With flepimop2 adapter:
|
|
40
56
|
|
|
41
57
|
```bash
|
|
@@ -87,7 +103,8 @@ adaptive controller is eager Python control flow. See the
|
|
|
87
103
|
|
|
88
104
|
```python
|
|
89
105
|
import numpy as np
|
|
90
|
-
from op_engine import CoreSolver, ModelCore
|
|
106
|
+
from op_engine import CoreSolver, ModelCore
|
|
107
|
+
from op_engine.core_solver import OperatorSpecs, RunConfig
|
|
91
108
|
|
|
92
109
|
n = 4
|
|
93
110
|
times = np.linspace(0.0, 1.0, 11)
|
|
@@ -104,21 +121,31 @@ def rhs(t, y):
|
|
|
104
121
|
return -0.1 * y
|
|
105
122
|
|
|
106
123
|
|
|
107
|
-
solver = CoreSolver(core,
|
|
108
|
-
solver.run(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
124
|
+
solver = CoreSolver(core, operator_axis="state")
|
|
125
|
+
solver.run(
|
|
126
|
+
rhs,
|
|
127
|
+
config=RunConfig(
|
|
128
|
+
method="imex-heun-tr",
|
|
129
|
+
operators=ops,
|
|
130
|
+
),
|
|
131
|
+
)
|
|
112
132
|
```
|
|
113
133
|
|
|
134
|
+
This compact example uses identity left/right operators to show the API. For a
|
|
135
|
+
nontrivial implicit term, construct timestep-aware operator factories as in the
|
|
136
|
+
[solver guide](docs/guides/solver-methods.md#imex-methods-and-operator-factories).
|
|
137
|
+
|
|
114
138
|
## Public API
|
|
115
139
|
- `ModelCore`: state tensor + time grid manager; supports extra axes and optional history.
|
|
116
140
|
- `CoreSolver`: portable explicit and dense IMEX/implicit stepping selected by the state array namespace.
|
|
117
141
|
- `DirectSSASolver`: exact event-by-event stochastic reaction trajectories with NumPy, JAX, or another Array-API namespace supplying random draws.
|
|
118
142
|
- `AdaptiveTauLeapingSolver`: adaptive non-negative stochastic leaps using explicit reactant stoichiometry and injected Poisson plus exact-event sampling.
|
|
119
143
|
- `TauLeapingSolver`: portable stoichiometric updates with NumPy, JAX, or another Array-API namespace supplying Poisson samples.
|
|
144
|
+
- `op_engine.reactions`: `from_compiled_rhs(compiled, params)` and `compile_reaction_network(reactions, template_shapes=..., axis_sizes=..., params=...)` return a `CompiledReactionNetwork` whose `stoichiometry`, `reactant_stoichiometry`, and `propensity` feed the stochastic solvers; `mean_drift` checks the network against the deterministic RHS.
|
|
120
145
|
- 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.
|
|
121
|
-
- Configuration helpers: `RunConfig`, `OperatorSpecs`, `AdaptiveConfig`,
|
|
146
|
+
- Configuration helpers: `RunConfig`, `OperatorSpecs`, `AdaptiveConfig`, and
|
|
147
|
+
`DtControllerConfig` from `op_engine.core_solver` for method, IMEX, and
|
|
148
|
+
adaptive control.
|
|
122
149
|
- 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`.
|
|
123
150
|
|
|
124
151
|
## Development
|
|
@@ -82,6 +82,9 @@ semantics.
|
|
|
82
82
|
`NumpySSASampler` and `NumpyPoissonSampler` are provided as conveniences.
|
|
83
83
|
JAX users can construct fresh keys from the stable direct-SSA `draw_index` or
|
|
84
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.
|
|
85
88
|
|
|
86
89
|
The current safety checks and stochastic event loops are eager, so these
|
|
87
90
|
stochastic solvers are not JIT-compatible paths. Adaptive selection also reads
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Bounded thinning SSA
|
|
2
|
+
|
|
3
|
+
`ThinningSSASolver` provides an exact waiting-time method for smoothly varying
|
|
4
|
+
propensities with user-certified bounds. This design addresses
|
|
5
|
+
[#173](https://github.com/ACCIDDA/op_engine/issues/173), including the flepimop2
|
|
6
|
+
provider's explicit `thinning-ssa` method.
|
|
7
|
+
|
|
8
|
+
## Bound contract
|
|
9
|
+
|
|
10
|
+
`ThinningSSASolver.run(propensity_func, thinning_sampler, *, rate_bound, config=None)`
|
|
11
|
+
uses the same stoichiometry, reaction axis, and batched propensity contract as
|
|
12
|
+
direct SSA. It permits arbitrary time dependence between events when a valid
|
|
13
|
+
upper bound is supplied.
|
|
14
|
+
|
|
15
|
+
`rate_bound` is either a finite non-negative real constant, or a
|
|
16
|
+
`RateBoundFunction(t, state, limit)` returning `TotalRateBound(rate, valid_until)`.
|
|
17
|
+
The bound covers the sum of **all** reaction and batch-cell propensities with
|
|
18
|
+
the supplied state held fixed, over `[t, valid_until)`. The callback chooses a
|
|
19
|
+
finite endpoint satisfying `t < valid_until <= limit`. `limit` is the earlier
|
|
20
|
+
of the next declared forcing change and the solve's final time; observations
|
|
21
|
+
do not shorten it. A constant bound applies to every state reached during the
|
|
22
|
+
solve, until the next forcing change or final time.
|
|
23
|
+
|
|
24
|
+
After an accepted event, the state changes and the callback is queried again.
|
|
25
|
+
A rejected candidate leaves the state and current bound unchanged. At expiry,
|
|
26
|
+
the solver discards a tied or later candidate, advances without firing, and
|
|
27
|
+
requests a new bound. The same rule applies at forcing changes, with
|
|
28
|
+
right-continuous propensities. No candidate fires exactly at an interval's
|
|
29
|
+
right endpoint, including the solve's final endpoint.
|
|
30
|
+
|
|
31
|
+
A zero bound declares the entire interval inactive. The solver checks rates
|
|
32
|
+
at the interval's start, advances to its endpoint without sampling, and can
|
|
33
|
+
resume with a positive bound. Zero instantaneous propensity with a positive
|
|
34
|
+
bound still produces candidates, allowing smooth rates to become active.
|
|
35
|
+
The caller must prove the bound over the whole interval. Checks at interval
|
|
36
|
+
starts and candidate times detect encountered violations but cannot certify
|
|
37
|
+
unsampled times. A violation fails visibly rather than clipping acceptance.
|
|
38
|
+
Bounds and cumulative rates use the state's floating dtype. Bounds that
|
|
39
|
+
overflow or become zero on conversion fail; tight bounds should allow for
|
|
40
|
+
floating-point rounding when summing many channels. Callback inputs must be
|
|
41
|
+
treated as read-only, and callbacks must not depend on observation storage.
|
|
42
|
+
|
|
43
|
+
## Sampling contract
|
|
44
|
+
|
|
45
|
+
`ThinningSampler(bound_rate, draw_index)` returns
|
|
46
|
+
`ThinningSample(waiting_time, uniform)`, both scalar arrays in the state array
|
|
47
|
+
namespace. The wait is finite and strictly positive, sampled exponentially at
|
|
48
|
+
`bound_rate`. The independent uniform lies in `[0, 1)`. Draw indices start at
|
|
49
|
+
zero and increase globally, including rejected and expired candidates.
|
|
50
|
+
`NumpyThinningSampler(seed)` supplies the NumPy implementation; other backends
|
|
51
|
+
inject their own sampler.
|
|
52
|
+
|
|
53
|
+
At a candidate time, the solver evaluates the actual channel rates. If
|
|
54
|
+
`uniform * bound_rate` is at least the actual total rate, it rejects the
|
|
55
|
+
candidate. Otherwise it selects the flattened channel/batch event whose
|
|
56
|
+
cumulative propensity contains that threshold. This uses one uniform for
|
|
57
|
+
acceptance and channel selection: each channel has probability
|
|
58
|
+
`channel_rate / bound_rate`, and the remaining probability rejects.
|
|
59
|
+
|
|
60
|
+
Candidates beyond an observation are retained with their sampled uniform;
|
|
61
|
+
their propensities are evaluated only when the solver reaches them. Inserting
|
|
62
|
+
observations with the same solve endpoints therefore preserves the seeded path
|
|
63
|
+
and callback/sampler history. `ThinningSSAConfig.max_candidates` bounds all
|
|
64
|
+
candidate draws during one run, including rejection and expiry. Invalid
|
|
65
|
+
sampler values, non-advancing clocks, invalid bounds, and impossible population
|
|
66
|
+
updates fail explicitly.
|
|
67
|
+
|
|
68
|
+
The construction follows
|
|
69
|
+
[Lewis and Shedler's thinning method](https://doi.org/10.1002/nav.3800260304).
|
|
70
|
+
The bound and expiry contract extends it to state-dependent reaction networks;
|
|
71
|
+
exactness remains conditional on the caller's bound and random sampling laws.
|
|
72
|
+
Existing direct SSA and tau-leaping behavior and samplers are unchanged.
|
|
73
|
+
|
|
74
|
+
## Example
|
|
75
|
+
|
|
76
|
+
For an independent birth channel in each batch cell with rate `2 * time`, the
|
|
77
|
+
integrated intensity over `[0, 1]` is one per cell. The total upper bound must
|
|
78
|
+
include every batch cell:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import numpy as np
|
|
82
|
+
|
|
83
|
+
from op_engine import ModelCore, NumpyThinningSampler, ThinningSSASolver
|
|
84
|
+
|
|
85
|
+
core = ModelCore(1, 100, np.linspace(0, 1, 11))
|
|
86
|
+
core.set_initial_state(np.zeros((1, 100)))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def smooth_birth(time, state):
|
|
90
|
+
return np.full(state.shape, 2 * time, dtype=state.dtype)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
ThinningSSASolver(core, np.asarray([[1]])).run(
|
|
94
|
+
smooth_birth, NumpyThinningSampler(seed=173), rate_bound=200,
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
A callback can instead return `TotalRateBound(2 * limit * state.shape[1], limit)`
|
|
99
|
+
for this example. For consuming reactions, a bound that depends on the current
|
|
100
|
+
state can be refreshed after each accepted event. A loose bound remains valid
|
|
101
|
+
but generates more rejected candidates.
|
|
102
|
+
|
|
103
|
+
The flepimop2 provider selects this method through
|
|
104
|
+
`mode: stochastic` and `stochastic_method: thinning-ssa`. Supply a constant
|
|
105
|
+
`thinning_rate_bound` in configuration, or leave it unset and pass a scalar or
|
|
106
|
+
`RateBoundFunction` through `run(..., rate_bound=...)`. Both interfaces cannot
|
|
107
|
+
be supplied together. `thinning_max_candidates` sets the per-run candidate
|
|
108
|
+
guard. NumPy has seeded sampling through `random_seed`; other backends inject
|
|
109
|
+
`thinning_sampler=`. Hybrid thinning is unsupported. See the
|
|
110
|
+
[provider examples](https://github.com/ACCIDDA/op_engine/tree/main/flepimop2-op_engine#bounded-thinning-ssa)
|
|
111
|
+
for configuration and callback usage. Existing direct SSA and tau-leaping
|
|
112
|
+
defaults remain unchanged.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Stochastic Simulation From An op_system Spec
|
|
2
|
+
|
|
3
|
+
An [op_system](https://github.com/ACCIDDA/op_system) spec compiles to typed
|
|
4
|
+
reaction artifacts, one per named transition (`CompiledRhs.reactions`).
|
|
5
|
+
`op_engine.from_compiled_rhs` turns them into the flat network that
|
|
6
|
+
`DirectSSASolver`, `TauLeapingSolver`, and `AdaptiveTauLeapingSolver`
|
|
7
|
+
consume. No flepimop2 installation is needed.
|
|
8
|
+
|
|
9
|
+
```shell
|
|
10
|
+
pip install op_engine op-system
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Build the network
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import numpy as np
|
|
17
|
+
from op_system import compile_spec
|
|
18
|
+
|
|
19
|
+
from op_engine import from_compiled_rhs
|
|
20
|
+
|
|
21
|
+
spec = {
|
|
22
|
+
"kind": "transitions",
|
|
23
|
+
"axes": [{"name": "age", "coords": ["child", "adult"]}],
|
|
24
|
+
"state": ["S[age]", "I[age]", "R[age]"],
|
|
25
|
+
"transitions": [
|
|
26
|
+
{
|
|
27
|
+
"name": "infect",
|
|
28
|
+
"from": "S[age]",
|
|
29
|
+
"to": "I[age]",
|
|
30
|
+
"rate": "b * I[age]",
|
|
31
|
+
"reactants": "auto",
|
|
32
|
+
},
|
|
33
|
+
{"name": "recover", "from": "I[age]", "to": "R[age]", "rate": "g"},
|
|
34
|
+
],
|
|
35
|
+
}
|
|
36
|
+
params = {"b": 0.002, "g": 0.1}
|
|
37
|
+
|
|
38
|
+
compiled = compile_spec(spec)
|
|
39
|
+
network = from_compiled_rhs(compiled, params)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Each reaction becomes one channel per source cell: `network.stoichiometry` is
|
|
43
|
+
`(n_state, n_channels)`, and `network.channel_names` labels the columns
|
|
44
|
+
(`infect[age=0]`, ...). The flat state follows `compiled.template_shapes`:
|
|
45
|
+
each template's cells in C order, templates in declaration order.
|
|
46
|
+
|
|
47
|
+
## Check the network against the RHS
|
|
48
|
+
|
|
49
|
+
The network's mean drift, `stoichiometry @ propensity`, must equal the
|
|
50
|
+
deterministic right-hand side. Check it once for each new model:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
rng = np.random.default_rng(0)
|
|
54
|
+
y = rng.uniform(1.0, 100.0, network.n_state)
|
|
55
|
+
np.testing.assert_allclose(
|
|
56
|
+
network.mean_drift(0.0, y),
|
|
57
|
+
compiled.eval_fn(0.0, y, **params),
|
|
58
|
+
rtol=1e-12,
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A mismatch means some dynamics have no reaction. `from_compiled_rhs` already
|
|
63
|
+
refuses a RHS whose `reaction_gaps` lists transitions without a reaction
|
|
64
|
+
artifact (an unnamed transition, for example). Pass `allow_gaps=True` only
|
|
65
|
+
when you intend to simulate the reactions alone.
|
|
66
|
+
|
|
67
|
+
## Simulate
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from op_engine import DirectSSASolver, ModelCore, NumpySSASampler
|
|
71
|
+
|
|
72
|
+
times = np.linspace(0.0, 30.0, 7)
|
|
73
|
+
core = ModelCore(network.n_state, 1, times)
|
|
74
|
+
core.set_initial_state(np.asarray([[95.0], [90.0], [5.0], [10.0], [0.0], [0.0]]))
|
|
75
|
+
DirectSSASolver(core, network.stoichiometry).run(
|
|
76
|
+
network.propensity, NumpySSASampler(seed=1)
|
|
77
|
+
)
|
|
78
|
+
trajectory = core.state_array[:, :, 0] # (time, state)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`TauLeapingSolver(core, network.stoichiometry)` runs the same network with a
|
|
82
|
+
`NumpyPoissonSampler`.
|
|
83
|
+
|
|
84
|
+
## Adaptive tau-leaping
|
|
85
|
+
|
|
86
|
+
`AdaptiveTauLeapingSolver` chooses each leap so that no propensity changes
|
|
87
|
+
by more than `leap_tolerance` relative to itself. It therefore needs to know
|
|
88
|
+
which states each propensity depends on, and how strongly. The network must
|
|
89
|
+
describe every reaction: `network.reactants_complete` is false, and
|
|
90
|
+
`network.incomplete_reactions` names the culprits, when a reaction might read
|
|
91
|
+
a state op_system has not declared. In op_system 0.7.0 and later:
|
|
92
|
+
|
|
93
|
+
- a reaction whose rate reads no state (`recover` above) is complete as is;
|
|
94
|
+
- `reactants: auto` infers the molecular reactants of a rate that is a single
|
|
95
|
+
product of states (`infect` above: `S` consumed, `I` catalytic);
|
|
96
|
+
- for any other `reactants: auto` rate, such as a frequency-dependent force
|
|
97
|
+
of infection `beta * S * sum(I) / N`, op_system publishes the states the
|
|
98
|
+
propensity reads and a bound `E` on its elasticity
|
|
99
|
+
`sum_i |d log a / d log x_i|` (3 for that example). The network turns them
|
|
100
|
+
into `dependency_incidence` and `propensity_orders`;
|
|
101
|
+
- an explicit `reactants:` list is always authoritative.
|
|
102
|
+
|
|
103
|
+
Pass all four arrays; the dependency arrays are `None` when every reaction is
|
|
104
|
+
mass action:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from op_engine import AdaptiveTauLeapingSolver, NumpyPoissonSampler
|
|
108
|
+
|
|
109
|
+
assert network.reactants_complete
|
|
110
|
+
core = ModelCore(network.n_state, 1, times)
|
|
111
|
+
core.set_initial_state(np.asarray([[95.0], [90.0], [5.0], [10.0], [0.0], [0.0]]))
|
|
112
|
+
AdaptiveTauLeapingSolver(
|
|
113
|
+
core,
|
|
114
|
+
network.stoichiometry,
|
|
115
|
+
network.reactant_stoichiometry,
|
|
116
|
+
dependency_incidence=network.dependency_incidence,
|
|
117
|
+
propensity_orders=network.propensity_orders,
|
|
118
|
+
).run(network.propensity, NumpyPoissonSampler(seed=2), NumpySSASampler(seed=3))
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
For a reaction described by dependencies, every species it reads gets a
|
|
122
|
+
Cao-Gillespie-Petzold scaling of at least `E`, so its propensity's relative
|
|
123
|
+
change stays within the tolerance. Orders above three are allowed. Its
|
|
124
|
+
`reactant_stoichiometry` column lists only the consumed source, which is what
|
|
125
|
+
can be exhausted for the critical-reaction check.
|
|
126
|
+
|
|
127
|
+
## Partitions and lower-level use
|
|
128
|
+
|
|
129
|
+
`from_compiled_rhs(compiled, params, reaction_names=[...])` compiles only the
|
|
130
|
+
named reactions, for a hybrid jump partition. `compile_reaction_network`
|
|
131
|
+
takes the artifacts and layout directly, for producers other than a
|
|
132
|
+
`CompiledRhs`:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from op_engine import compile_reaction_network
|
|
136
|
+
|
|
137
|
+
network = compile_reaction_network(
|
|
138
|
+
compiled.reactions,
|
|
139
|
+
template_shapes=compiled.template_shapes,
|
|
140
|
+
axis_sizes={"age": 2},
|
|
141
|
+
params=params,
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The flepimop2 provider (`flepimop2-op-engine`) builds its stochastic runs
|
|
146
|
+
through the same function.
|