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¶
Expressions are parsed and validated with a conservative AST whitelist. RHS and latent-observable expressions use cached functions with explicit runtime bindings. Their constructor evaluates a generated lambda once; separate reaction callbacks retain their existing restricted code-object evaluation.
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
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 | |
__setstate__(state)
¶
Restore by recompiling from the pickled :class:NormalizedRhs.
Source code in src/op_system/compile.py
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 669 670 | |
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
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 | |
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
590 591 592 593 594 595 596 597 598 599 | |
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
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 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 | |