units

analysis.units

Dimensional analysis of a model’s equations.

The symbolic layer knows a quantity is real. This is what lets it know a quantity is a voltage: units travel beside the expression, as a map from the scope’s own symbols to the unit each was declared in, and propagate through the equation rather than being multiplied into it.

Beside, not inside, for three reasons. 87% of quantities in the curated database declare no unit, so an expression carrying Quantity factors would be inconsistent by construction for almost every model; every consumer of the symbolic layer would have to strip those factors before printing; and the frozen codegen corpus would move. None of that buys anything a separate map does not.

The verdict is three-valued — consistent, inconsistent, underdetermined — because “I cannot tell” is a different claim from “this is wrong”, and collapsing them is how a checker becomes noise. An undeclared quantity is the common case, and treating it as dimensionless silently corrupts the answer: hhcell_1 declares units for 92% of its symbols and reports dv/dt as 1/capacitance — not voltage over time — purely because its one undeclared symbol was assumed dimensionless.

Two strictnesses over one map (U2). dimensional compares base-dimension vectors, so mV/ms and V/s agree; exact compares the full quantity, so they do not, and the disagreement is reported as the exact rational ratio 1 vs 1000. The ratio is the part that names the bug — a boolean does not.

Attributes

Name Description
CONSISTENT
INCONSISTENT
UNDERDETERMINED

Classes

Name Description
DimensionalClash Two quantities that must agree dimensionally do not.
Term One addend of an equation, with the unit propagation assigned to it.
Verdict The dimensional standing of one equation.

DimensionalClash

analysis.units.DimensionalClash()

Two quantities that must agree dimensionally do not.

Term

analysis.units.Term(expression, unit=None, unknown=None)

One addend of an equation, with the unit propagation assigned to it.

Verdict

analysis.units.Verdict(
    name,
    equation,
    status,
    detail='',
    unit=None,
    ratios=dict(),
    inferred=dict(),
    undeclared=(),
)

The dimensional standing of one equation.

Functions

Name Description
check_units Per-equation dimensional verdicts for model.
declared_units The third projection of the symbolic layer: {scope symbol: declared unit}.
dimension_exponents ISO 80000-1 base-dimension exponents of a declared unit, for reporting (U25).
quantity_name The name of the quantity expression stands for.

check_units

analysis.units.check_units(model, strictness='dimensional', time_unit=None)

Per-equation dimensional verdicts for model.

Parameters

Name Type Description Default
model A Dynamics, read through its symbolic projection. required
strictness str "dimensional" compares base-dimension vectors, so mV/ms and V/s agree. "exact" compares the whole quantity, so they do not, and the ratio is reported. 'dimensional'
time_unit str | None The scope’s time unit; resolved via time_unit_of when omitted. None

Returns

Name Type Description
list[Verdict] One Verdict per state equation, each consistent, inconsistent or
list[Verdict] underdetermined, carrying the derived unit, any inferred units, and the exact
list[Verdict] ratios of any disagreement.

declared_units

analysis.units.declared_units(model, scope=None)

The third projection of the symbolic layer: {scope symbol: declared unit}.

Keyed by the scope’s own symbols, like Dynamics.symbolic["parameters"] — rebuilt keys look identical, compare unequal, and would silently match nothing. A caller that already holds the analysis scope passes it, so the map is keyed by the very table its equations were parsed against rather than by whichever one the model resolves to.

dimension_exponents

analysis.units.dimension_exponents(unit)

ISO 80000-1 base-dimension exponents of a declared unit, for reporting (U25).

dim Q = L²MT⁻³I⁻¹ is what makes an inconsistency legible: L²MT⁻³I⁻¹ against L²MT⁻⁴I⁻¹ shows where the discrepancy is, where “voltage vs something else” does not.

quantity_name

analysis.units.quantity_name(expression)

The name of the quantity expression stands for.

y0 for all of y0, y0(t) and Derivative(y0(t), t) — a quantity is the same quantity however the notation renders it, and every caller (a verdict’s label, a report row keyed by parameter name) wants that one name.