Compile¶
compile
¶
op_system.compile.
Compile normalized RHS specifications into efficient, runnable callables.
This module is domain-agnostic and intentionally does not import flepimop2.
Contract¶
- Accepts
NormalizedRhs(fromop_system.specs) and produces aCompiledRhsobject containing aneval_fnsuitable for numerical backends. - Raises built-in exceptions with standardized messages.
Security note¶
This module uses eval() on code objects compiled from user-provided strings.
To reduce risk, expressions are parsed and validated with a conservative AST
whitelist, and evaluation runs with empty builtins.
BodyEvalFn
¶
Bases: Protocol
Callable that evaluates history signal bodies at (t, y, **params).
Returns a mapping from signal_id to the evaluated body array/value.
CompiledReactant(state_base, state_axes, full_axes, pinned, order)
dataclass
¶
Molecular reactant metadata aligned to an expanded reaction channel.
The record is numerical-array neutral. Providers combine state_axes
with the enclosing reaction's channel coordinates and pinned indices
to locate a state cell, then place order in their reactant-order
matrix. Catalysts appear here even when their net stoichiometric change is
zero.
CompiledReaction(name, from_base, from_axes, full_axes, to_base, to_axes, sum_axes, pinned, from_pinned, propensity_fn, reactants=(), reactants_complete=False, offsets=(), to_full_axes=None, routed_axes=(), dependencies=(), propensity_order=None, dependencies_complete=False, resolved_propensity_fn=None, time_varying_params=(), independent_axes=(), uses_time=False)
dataclass
¶
Compiled propensity + axis bookkeeping for one named transition.
Produced from :class:op_system._reactions.ReactionArtifactIR at
compile time. A firing event at from-cell index i (in the natural
N-D shape given by from_axes) depletes 1 from from_base at that
cell and adds 1 to to_base at the cell obtained by: keeping every
axis in to_axes at i's value for that axis, and using the fixed
coordinate index from pinned for every axis in pinned (this
includes both axes that are wildcard on from_axes and pinned on
to -- a "collapse to a fixed target" transition, where multiple
source cells share one destination and the axis also appears in
sum_axes -- and axes pinned on BOTH from and to, a
"point-to-point" shift between two specific coordinates on the same
axis, e.g. a vaccination-dose-progression transition, which do NOT
appear in sum_axes since there's only ever one source value on
that axis). Consumers that need a single combined scatter/reduce
target (e.g. an engine applying many simultaneous firings) sum over
sum_axes when depositing into to_base -- op_system does not do
that summation itself, since whether/how to combine simultaneous
firings across a summed axis is an execution-semantics decision for
the consumer, not a compile-time one.
from_base is None for a SOURCE-ONLY (from: null) reaction
-- an exogenous hazard with no compartment to deplete (e.g.
cross-district case importation). A consumer must special-case this:
skip every from-side depletion/clamp step and apply only the
to_base scatter, using from_axes/full_axes (which are the
to-side template's own axes in this case, not a nonexistent
from-side one -- see ReactionArtifactIR).
An axis-wide coord_shift publishes one reaction per state template
with a non-empty offsets: the destination index on each offset axis
is the source index plus its step (see offsets). The destination
cell is then built from to_axes (copied), pinned (fixed), and
offsets (shifted), which together cover full_axes.
Examples:
>>> import numpy as np
>>> from op_system import compile_spec
>>> compiled = compile_spec({
... "kind": "transitions",
... "state": ["S", "I"],
... "transitions": [
... {"name": "infect", "from": "S", "to": "I", "rate": "k"},
... ],
... })
>>> reaction = compiled.reactions[0]
>>> float(
... reaction.propensity_fn(
... 0.0, {"S": np.array(10.0), "I": np.array(0.0)}, k=0.2
... )
... )
2.0
>>> reaction.time_varying_params, reaction.uses_time
((), False)
CompiledRhs(state_names, param_names, eval_fn, meta=(lambda: MappingProxyType({}))(), operators=tuple(), factorize_axes=tuple(), block_axes=tuple(), pytree_eval_fn=None, template_shapes=None, block_pytree_eval_fn=None, block_template_shapes=None, history_requirements=tuple(), history_eval_fn=None, body_eval_fn=None, block_history_eval_fn=None, block_body_eval_fn=None, reactions=tuple(), reaction_gaps=tuple(), _rhs=None, param_meta=(lambda: MappingProxyType({}))(), state_meta=(lambda: MappingProxyType({}))(), axis_meta=(lambda: MappingProxyType({}))(), observables=(lambda: MappingProxyType({}))(), gauges=None, gauge_fix=None)
dataclass
¶
Container for a compiled RHS evaluation function.
Instances produced by :func:compile_rhs retain a private reference to
their source :class:NormalizedRhs so the container can be pickled and
re-hydrated by re-running the compile pipeline on load. eval_fn
itself is a closure (and on the vectorized path captures compiled code
objects), so it is dropped from the pickle and rebuilt by
:func:compile_rhs in :meth:__setstate__. Round-tripping a
CompiledRhs therefore costs one compile on load and yields a
functionally equivalent instance whose eval_fn produces identical
outputs for identical inputs.
__getstate__()
¶
Return picklable state.
The compiled eval_fn is a closure (and on the vectorized path
captures compiled :class:types.CodeType objects), which is not
portably picklable. Instead we serialize just the source
:class:NormalizedRhs and let :meth:__setstate__ recompile.
Raises:
| Type | Description |
|---|---|
TypeError
|
If the source |
Source code in src/op_system/compile.py
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 | |
__setstate__(state)
¶
Restore by recompiling from the pickled :class:NormalizedRhs.
Source code in src/op_system/compile.py
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 | |
bind(params)
¶
Bind parameter values and return a 2-arg RHS: rhs(t, y) -> dydt.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
params
|
Mapping[str, object]
|
Mapping of parameter names to values. |
required |
Returns:
| Type | Description |
|---|---|
Callable[[object, object], Float64Array]
|
A callable |
Source code in src/op_system/compile.py
599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 | |
coordinate_view()
¶
Build deterministic evaluators in the declared state coordinates.
Returns:
| Type | Description |
|---|---|
StateCoordinateView
|
An explicit view with initial-state encoders and natural-scale |
StateCoordinateView
|
observations. The original evaluator/reactions remain natural. |
Source code in src/op_system/compile.py
588 589 590 591 592 593 594 595 596 597 | |
EvalFn
¶
Bases: Protocol
Callable RHS evaluator supporting runtime parameter kwargs.
Accepts a flat (n_state,) state array and returns a flat
(n_state,) derivative array in the same array namespace.
HistoryEvalFn
¶
Bases: Protocol
Callable RHS evaluator with history provider support.
Accepts y as a StateDict and a history_provider object
implementing a query(signal_id, body, **options) method.
Returns a StateDict of derivatives.
PytreeEvalFn
¶
Bases: Protocol
Callable RHS evaluator operating on shaped PyTree state dicts.
Accepts y as a StateDict (mapping from state-template base name
to a shaped array with the template's natural N-D shape) and returns a
StateDict of the same structure containing the derivative.
Enables the engine to skip the flatten/unflatten step entirely and
expose the full tensor structure to JAX/XLA.
ReactionPropensityFn
¶
Bases: Protocol
Callable propensity evaluator for one named reaction/transition.
Accepts y as a StateDict and returns an array shaped like the
reaction's from_axes (one independent rate per source cell) --
see :class:CompiledReaction.
compile_rhs(rhs, *, xp=None, detect_gauges=False, gauge_fix=None)
¶
Compile a normalized RHS into a runnable evaluation function.
Always uses the vectorized eval path that operates on shaped buffers
(one tensor expression per state template) for specs that declare axes.
Specs without axes (genuinely scalar models) fall back to the scalar path.
Raising :class:UnsupportedFeatureError if an axis-indexed spec cannot be
vectorized, rather than silently falling back to the catastrophically slow
scalar path.
The returned eval_fn is namespace-polymorphic: it infers its
array namespace from the input y at call time
(through array_api_compat.array_namespace), and returns arrays in that same
namespace. Calling it with JAX arrays (or tracers) yields a JAX-native
computation suitable for jax.jit / jax.vmap without any
correctness wrapping.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rhs
|
NormalizedRhs
|
Normalized RHS produced by |
required |
detect_gauges
|
bool
|
Opt in to conservative structural scaling detection. |
False
|
gauge_fix
|
Mapping[str, float] | None
|
Positive scalar anchors for the reported scaling action. |
None
|
xp
|
object | None
|
Deprecated. Formerly the compile-time array namespace.
Now ignored — the namespace is resolved per call
from the input |
None
|
Returns:
| Type | Description |
|---|---|
CompiledRhs
|
A |
CompiledRhs
|
For axis-indexed specs the returned object also carries |
CompiledRhs
|
|
CompiledRhs
|
axes but the vectorizer cannot build a plan an |
CompiledRhs
|
|
CompiledRhs
|
message) rather than silently degrading to the scalar path. |
Source code in src/op_system/compile.py
2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 | |