State representations and physical measures¶
An optional state_metadata block describes state values. Declarations use
state base names, such as rho for rho[space], and work in expression and
transition specifications. Partial declarations are allowed. Metadata leaves
equations, runtime values, constraints and existing reductions unchanged.
The optional scale: log field requests a separate deterministic
coordinate view. It leaves the primary evaluator
and stochastic reactions in physical coordinates; callers explicitly encode
initial states and select the view's callbacks.
kind: expr
axes:
- name: space
type: continuous
coords: [0.05, 0.25, 0.7]
units: m
measure:
kind: cell
weights: [0.1, 0.3, 0.6]
units: m
state: ["rho[space]", "mass[space]", counter]
equations:
rho[space]: "0"
mass[space]: "0"
counter: "sum_state()"
state_metadata:
rho:
representation: density
units: items/m
description: Cell-average density on the unit interval
measure_axes: [space]
mass:
representation: counts
units: items
measure_axes: []
counter:
representation: cumulative
units: items
The illustrative counter derivative exposes the existing total reduction; choose scientific counter equations explicitly in an actual model.
| Representation | Stored value | Physical total over represented cells |
|---|---|---|
counts |
Extensive count per cell/category | Unweighted sum |
cell_mass |
Extensive amount or mass per cell | Unweighted sum |
density |
Amount per declared spatial measure | Sum after multiplying by that measure |
concentration |
Amount per declared volume or reference measure | Sum after multiplying by that measure |
cumulative |
Extensive accumulated counter | Unweighted spatial sum; time differences require a separately defined observation |
Counts are annotations, rather than an integer execution constraint.
cumulative neither proves monotonic equations nor turns an instantaneous
derivative into an interval count. Missing representation remains unknown.
For intensive states, omitted measure_axes means integration requirements
are undeclared; [] explicitly requires no measure axes. Listed axes must
belong to the state template or its legacy state_axes declaration and have
explicit measures. Extensive representations reject nonempty measure_axes
to avoid weighting an amount twice.
CompiledRhs.state_meta is a read-only mapping from declared bases to exported
immutable StateDeclaration records; undeclared bases are absent.
CompiledRhs.axis_meta contains all declared axes as AxisDeclaration records
with coordinate units and optional AxisMeasure. Undeclared units/measures
are None, including on continuous axes. Trusted pickle round trips rebuild
the detached snapshots alongside the evaluator.
Supported measure kinds are:
counting: unit weight per coordinate; omitweights.cell: explicit cell widths, areas or volumes aligned with coordinates.quadrature: explicit point weights aligned with coordinates.
Cell/quadrature weights must match the coordinate count and be finite, strictly
positive real numbers. Boolean, zero, negative and nonfinite weights fail.
Signed quadrature, zero-measure masks, runtime measure buffers and nonseparable
multidimensional geometry are outside this initial interface. Coordinate and
measure units are independent: coordinates may use m while volumes use
m^3. Annotations do not verify dimensions or geometry.
The discretization owner supplies the measure. It is never inferred from
centers or substituted for existing continuous-axis deltas. Those deltas
retain point-trapezoidal semantics over the coordinate span. Centers
[0.05, 0.25, 0.7] span 0.65; the declared volumes [0.1, 0.3, 0.6] cover
the unit interval. These describe different discretizations.
This runnable example explicitly reduces a density using the exported measure:
import numpy as np
from op_system import array_namespace, compile_spec
compiled = compile_spec({
"axes": [
{
"name": "space",
"type": "continuous",
"coords": [0.05, 0.25, 0.7],
"measure": {"kind": "cell", "weights": [0.1, 0.3, 0.6], "units": "m"},
}
],
"state": ["rho[space]"],
"equations": {"rho[space]": "0"},
"state_metadata": {
"rho": {
"representation": "density",
"units": "items/m",
"measure_axes": ["space"],
}
},
})
density = np.asarray([1.1, 1.5, 2.4]) # Cell averages of 1+2*x on [0,1].
xp = array_namespace(density)
measure = compiled.axis_meta["space"].measure
assert measure is not None
weights = xp.asarray(measure.weights, dtype=density.dtype)
cell_counts = xp.multiply(density, weights)
physical_total = xp.sum(cell_counts) # 2: exact integral over [0,1].
unweighted_density_total = xp.sum(density) # 5: a different quantity.
These operations preserve NumPy/JAX/Torch namespace and floating dtype;
required backend qualification executes their numerical expectations.
Metadata supplies no new expression helper: sum_state()
remains unweighted, and existing integration helpers retain their point weights.
Named latent observables can describe measured quantities.
Numerical consumers must check representation, measure coverage, unit assumptions
and actual discretization accuracy before interpreting an observable as a physical
integral or conserved quantity. This metadata does not implement mesh transfer.
Existing specs need no migration. When annotating a state, review whether its values are extensive or intensive and supply the measure explicitly. This interface does not convert representations, constrain signs or counters, or certify scientific validity.
Use named latent observables to compile these explicit physical reductions into independently discoverable fixed-mesh outputs.
Conditional scalar reaction balances retain these declarations without inferring physical mass or spatial weights. Known unit or representation conflicts within a proposed total remain visible coverage gaps; unknown labels remain assumptions.