Skip to content

Measurement-formula namespace advertises SBML assignment-rule variables that aren't resolvable at materialize → fit fails mid-run #464

Closed

Description

@wshlavacek

Summary

A measurement-model observable: <id>, formula: <expr> (ADR-0036) that references an SBML assignment-rule variable passes config-time validation but then fails at runtime, mid-fit, because assignment-rule variables are in the formula-validation namespace yet are never simulation-output columns. The validation namespace and the set of symbols actually resolvable at materialize are out of sync: validation says yes, the run says no.

This is distinct from #463 (which is about .ant models routing through parse_bngl). This one affects any SBML model (.xml and .ant), fails late (during the fit) rather than at config build, and is the opposite timing/path. Surfaced while finishing #386 deliverable 6 / #462.

Mechanism

  1. Configuration._model_expression_namespace (pybnf/config.py:1858) builds the allowed-symbol set for an SBML model from pybnf.petab._sbml.parse_model(...).namespace_symbols, which includes assignment-rule targets (e.g. the Becker model's Epo_medium := Epo + dEpoe, Epo_cells := Epo_EpoRi + dEpoi).
  2. _load_measurement_models (pybnf/config.py:1764) validates each observableFormula's free symbols against that set — so a formula referencing an assignment-rule variable passes and a MeasurementModel is built.
  3. At fit time, MeasurementModel.materialize (pybnf/measurement/base.py:101) resolves each symbol to a simulation-output column, a PSet value, or a constant. bngsim's SBML simulation outputs species only (not assignment-rule variables), and the symbol is neither a free parameter nor a constant — so it raises:
PybnfError: Measurement model '<id>' references 'Epo_cells', which is neither a
simulation-output column (...) nor a fit/model parameter. The measurement model
cannot be evaluated.

Reproduction

With tests/sbml_files/becker_epor.xml (sbml_backend = bngsim):

edition = 2
job_type = trf
objective = chi_sq
sbml_backend = bngsim
model: becker_epor.xml
observable: Epo_cells, formula: Epo_cells     # Epo_cells is an SBML assignment rule
experiment: tc, data: tc.exp
uniform_var = kon 1e-5 1e-2
uniform_var = koff 1e-3 1e-1
  • parse_sbml(...).namespace_symbols contains Epo_cells → True.
  • Configuration(...) builds successfully (the measurement model Epo_cells is created).
  • Running the fit raises the materialize error above (the bngsim SBML output columns are time, EpoR, Epo, Epo_EpoR, Epo_EpoRi, dEpoi, dEpoe — no Epo_cells / Epo_medium).

The working alternative is to reconstruct the observable from species — formula: Epo_EpoRi + dEpoi — which is the intended observation-layer path and what tests/test_gradient_optimizer.py::test_trf_multistart_smoke_on_becker_epor_sbml_measurement_layer (#462) does. The bug is specifically that referencing the assignment rule directly is accepted and then fails late.

Why it matters

  • Config validation is supposed to be the typo-guard ("an unknown symbol is an error, not a new free parameter") but here it green-lights a symbol the run can't resolve, so the error lands deep in a fit instead of at load.
  • It's a natural thing to write: SBML authors define exactly these convenience variables (the D2D Epo_cells / Epo_medium observables are assignment rules), so reaching for them by name is the obvious first attempt.

Suggested fix

Reconcile the validation namespace with what's resolvable at materialize. Either:

  1. Exclude un-resolvable assignment-rule variables from namespace_symbols (or have _load_measurement_models validate against species ∪ parameters ∪ compartments ∪ free-params only), so referencing one fails at config build with the existing pointed message; or
  2. Make assignment-rule variables resolvable — output them as columns (roadrunner already exposes them), or evaluate the rule in the measurement layer — so formula: Epo_cells simply works.

Option 1 is the smaller, safer fix (fail fast, clear message); option 2 is the more user-friendly one. Either way, add a test for an assignment-rule symbol in a formula.

References

Activity

  1. wshlavacek commented on Jun 30, 2026

    @wshlavacek
    CollaboratorAuthor

    Fixed in e462ace (option 1: the smaller, safer fail-fast). _sbml.parse_model now records <assignmentRule> targets (with their <ci> referents) and excludes them from the formula namespace; _load_measurement_models rejects a reference at config build with a pointed reconstruct-from-species message instead of failing mid-fit. Tests cover the scanner-level exclusion and config-build rejection on a crafted SBML and on the real becker_epor.xml fixture.

    Option 2 (make formula: Epo_cells just work by resolving assignment-rule observables) is tracked separately in #465.

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions