Domain-agnostic RHS specification and compilation utilities for scientific simulation systems.
op_system provides a lightweight, backend-neutral way to:
- Define system dynamics in a YAML/JSON-friendly format
- Normalize those specifications into a structured representation
- Compile them into fast callable right-hand-side (RHS) functions usable by numerical engines
It is designed to integrate cleanly with op_engine and external orchestration systems (e.g. flepimop2) while remaining standalone and dependency-minimal.
Users define system dynamics using a dictionary. Inline specifications are passed directly to the API functions.
Two RHS styles are supported in v1:
Directly specify derivatives:
spec = {
"kind": "expr",
"state": ["S", "I", "R"],
"aliases": {"N": "S + I + R"},
"equations": {
"S": "-beta * S * I / N",
"I": "beta * S * I / N - gamma * I",
"R": "gamma * I",
},
}Diagram-oriented hazard formulation:
spec = {
"kind": "transitions",
"state": ["S", "I", "R"],
"aliases": {"N": "S + I + R"},
"transitions": [
{"from": "S", "to": "I", "rate": "beta * I / N"},
{"from": "I", "to": "R", "rate": "gamma"},
],
}This is internally converted to conservation-law equations:
dS/dt -= (beta * I / N) * S
dI/dt += (beta * I / N) * S - gamma * I
dR/dt += gamma * I
- Axes: categorical or continuous. Continuous axes can be specified via explicit
coordsor viadomain+size+spacing(linear/log/geom). Resolved axis names, coords, and sizes are placed inNormalizedRhs.meta["axes"]. - Mixing kernels: optional
mixingblocks supportformvalueserfc,gaussian,exponential,gamma,power_law, andcustom_value, with validated parameters. Normalized kernels are placed inNormalizedRhs.meta["mixing"]for adapters to consume (e.g., flepimop2 buildsmixing_kernels). - Operators:
operatorsmetadata is normalized and preserved inNormalizedRhs.meta["operators"]; wiring into solvers is future-facing (not yet consumed by op_engine).
from op_system import compile_spec
compiled = compile_spec(spec)
dydt = compiled.eval_fn(
t=0.0,
y=[999.0, 1.0, 0.0],
beta=0.3,
gamma=0.1,
)from op_system import normalize_rhs, compile_rhs
rhs = normalize_rhs(spec)
compiled = compile_rhs(rhs)
dydt = compiled.eval_fn(0.0, [999, 1, 0], beta=0.3, gamma=0.1)While compile_spec doesn't directly load files, you can easily load YAML/JSON files using standard Python libraries:
# sir.yaml
kind: expr
state: [S, I, R]
aliases:
N: "S + I + R"
equations:
S: "-beta * S * I / N"
I: "beta * S * I / N - gamma * I"
R: "gamma * I"import yaml
from op_system import compile_spec
with open("sir.yaml") as f:
spec = yaml.safe_load(f)
compiled = compile_spec(spec)
dydt = compiled.eval_fn(0.0, [999, 1, 0], beta=0.3, gamma=0.1)The NormalizedRhs object (returned by normalize_rhs) includes a meta field that contains:
axes: Normalized axis definitions with resolved coords and sizesstate_axes: Mapping of state variables to their axis dependenciesmixing: Normalized mixing kernel specificationsoperators: Operator metadata for future IMEX integration- Reserved blocks:
sources,couplings,constraints(passthrough for future use)
This metadata is preserved for consumption by adapters and orchestration layers.
from op_system import normalize_rhs
spec = {
"kind": "expr",
"state": ["S", "I"],
"equations": {"S": "-beta * S * I", "I": "beta * S * I - gamma * I"},
"axes": [
{"name": "space", "type": "continuous", "domain": {"lb": 0, "ub": 10}, "size": 5},
],
"mixing": [
{"name": "K", "axes": ["space", "space"], "form": "gaussian", "params": {"sigma": 1.5, "scale": 1.0}},
],
}
rhs = normalize_rhs(spec)
# Access normalized metadata
axes_meta = rhs.meta["axes"] # List of normalized axis definitions
mixing_meta = rhs.meta["mixing"] # List of normalized mixing kernelsExpressions are parsed with Python ast and restricted to:
- Arithmetic operations
- Named variables
- Selected NumPy math functions (
np.exp,np.log, etc)
Disallowed operations (imports, attribute access, function injection) are rejected.
Validation errors raise descriptive built-in exceptions (ValueError, TypeError); no custom error wrapper is shipped.
op_system:
- Does NOT import flepimop2
- Does NOT depend on op_engine
- Does NOT impose solver semantics
It only defines RHS structure and compilation.
Compiled RHS functions are compatible with:
- op_engine
- scipy.integrate
- custom solvers
- GPU backends (via wrapping)
Reserved fields are preserved during normalization and exposed via NormalizedRhs.meta so future multiphysics extensions and adapters can read axes, mixing kernels, and operator metadata without breaking the API.
- The flepimop2 system adapter can consume inline specs, builds
mixing_kernelsfromNormalizedRhs.meta["mixing"], and exposes them for downstream engines (e.g., op_engine adapter). Operator metadata is preserved in meta but not yet wired into op_engine IMEX.
compile_spec(spec: dict) -> CompiledRhs
One-step entrypoint that validates, normalizes, and compiles a RHS specification. Recommended for most users.
- Args:
spec- Raw RHS specification mapping (YAML/JSON friendly) - Returns:
CompiledRhsobject with compiled evaluation function
normalize_rhs(spec: dict) -> NormalizedRhs
Validates and normalizes a RHS specification into a structured representation.
- Args:
spec- Raw RHS specification mapping - Returns:
NormalizedRhsobject with normalized equations and metadata
compile_rhs(rhs: NormalizedRhs) -> CompiledRhs
Compiles a normalized RHS into an efficient callable evaluation function.
- Args:
rhs- Normalized RHS fromnormalize_rhs - Returns:
CompiledRhsobject with compiled evaluation function
normalize_expr_rhs(spec: dict) -> NormalizedRhs
Specialized normalization for expression-style (kind: expr) specifications.
normalize_transitions_rhs(spec: dict) -> NormalizedRhs
Specialized normalization for transition-style (kind: transitions) specifications.
CompiledRhs
Container for a compiled RHS evaluation function.
-
Fields:
state_names: tuple[str, ...]- State variable namesparam_names: tuple[str, ...]- Parameter nameseval_fn: Callable- Compiled RHS function with signature(t, y, **params) -> dydt
-
Methods:
bind(params: dict) -> Callable- Returns a 2-arg RHS functionrhs(t, y) -> dydtwith parameters bound
NormalizedRhs
Normalized RHS representation suitable for compilation.
- Fields:
kind: str- RHS kind ("expr"or"transitions")state_names: tuple[str, ...]- State variable namesequations: tuple[str, ...]- Normalized equation expressionsaliases: Mapping[str, str]- Alias definitionsparam_names: tuple[str, ...]- Sorted parameter namesall_symbols: frozenset[str]- All symbols used in equationsmeta: Mapping- Metadata (axes, mixing, operators, reserved blocks)
__version__ - Current version string ("0.1.0")
SUPPORTED_RHS_KINDS - Tuple of supported RHS kinds: ("expr", "transitions")
EXPERIMENTAL_FEATURES - Frozenset of experimental features (currently empty)
Version: 0.1.0
Current scope:
- ODE RHS
- Algebraic expressions
- Compartment transitions
Planned:
- Wire operator metadata into op_engine IMEX paths
- PDE operators / operator splitting
- Multiphysics coupling and constraints/couplings blocks
- Explicit compatibility/guardrail checks with op_engine adapters