Skip to content

Unit test: SBML prefixed-unit factor parser correctness #11

Description

@jeliason

Add a unit test in `tests/` that pins the SBML prefix-unit → SI factor mapping computed by `SbmlModel._parse_units()` (src/qsp_codegen/codegen.py).

Why

Currently the parser pulls SI factors from `` elements with logic like:

```python
component = (mult * (10.0 ** scale) * base) ** exp
factor *= component
```

For a species in `MWDERIVEDUNIT_nanomolarity_milliliter` and a parameter in `MWBUILTINPREFIX_pico_MWBUILTINUNIT_molarity`, the parser computes very different SI factors (~1e-12 vs ~1e-9) — both correct for the respective declared units but visually similar enough that a regression that swaps prefixes silently could go unnoticed.

Proposed test

Build a tiny synthetic SBML with prefixed-molarity unit definitions matching SimBiology's export pattern (`pico`, `nano`, `micro`, `milli`):

```python
SBML_FIXTURE = '''<sbml xmlns="http://www.sbml.org/sbml/level3/version1/core\" level="3" version="1">


<unitDefinition id="MWBUILTINPREFIX_pico_MWBUILTINUNIT_molarity" name="picomolarity">

<unit kind="metre" exponent="-3"/>
<unit kind="mole" exponent="1"/>
<unit kind="dimensionless" multiplier="1e-09"/>





'''

EXPECTED = {
"MWBUILTINPREFIX_pico_MWBUILTINUNIT_molarity": 1e-9, # pM → mol/m³
"MWBUILTINPREFIX_nano_MWBUILTINUNIT_molarity": 1e-6,
"MWBUILTINPREFIX_micro_MWBUILTINUNIT_molarity": 1e-3,
"MWBUILTINPREFIX_milli_MWBUILTINUNIT_molarity": 1.0,
}

def test_prefixed_molarity_factors():
sbml = SbmlModel.from_string(SBML_FIXTURE)
for uid, expected in EXPECTED.items():
assert math.isclose(sbml.unit_defs[uid], expected, rel_tol=1e-12), uid
```

Also assert the corresponding `MWDERIVEDUNIT_*_milliliter` (substanceUnit-style) factors come out as `×` to mol amount, e.g., `nanomolarity_milliliter` → 1e-12 mol.

Why this isn't covered today

Existing tests (per a quick grep) exercise generation/compile/simulate end-to-end on the live PDAC SBML, which can pass even when individual unit factors drift, because the rate equations harmonize them downstream and small numerical differences fall under rtol. A targeted parser-level pin would catch silent factor swaps without depending on simulation behavior.

Origin

Surfaced 2026-05-05 during a pdac-build SBI investigation of a candidate codegen unit bug. Diagnosis was retracted (the parser is correct), but the experience showed how easy it is to misread unit factors during analysis. A pinned parser test would have ruled out the codegen-bug hypothesis in seconds.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions