Summary
A measurement-model observable formula (observable: <id>, formula: <expr>, ADR-0036) fails on an Antimony (.ant) model even when the referenced species exist, because the formula's allowed-symbol namespace is built by parsing the .ant file as BNGL, not SBML. The species are therefore absent from the namespace and every formula symbol is rejected as "not a known model entity" — surprising, since the rest of the pipeline treats .ant and .xml identically (an .ant model is converted to SBML at load).
Where
Configuration._model_expression_namespace (pybnf/config.py:1858) dispatches on extension:
if mf.endswith('.xml'):
ent = parse_sbml(text) # species ∪ parameters ∪ compartments
...
else: # .bngl (the BNGL ParamList; .ant antimony carries no formula observables)
ent = parse_bngl(text) # <-- a .ant file lands here and parses as BNGL
...
A .ant file matches neither .xml nor (meaningfully) the BNGL grammar, so parse_bngl returns an essentially empty namespace and the formula validation in _load_measurement_models raises.
Reproduction
With tests/sbml_files/becker_epor.ant (the Becker EpoR model) and a measurement formula over two of its species:
edition = 2
job_type = trf
model: becker_epor.ant
observable: obs_cells, formula: Epo_EpoRi + dEpoi
experiment: tc, data: tc.exp
uniform_var = kon 1e-5 1e-2
uniform_var = koff 1e-3 1e-1
raises:
PybnfError: The observableFormula references 'Epo_EpoRi', which is not a known model entity
(species / parameter / observable / function). ...
Measurement model 'obs_cells': allowed symbols are the model's species/parameters/observables/
functions, the fit's free parameters ['koff', 'kon'], and any row-varying placeholder.
The same model in SBML form (becker_epor.xml, sbml_backend = bngsim) with the identical formula works end to end (verified by tests/test_gradient_optimizer.py::test_trf_multistart_smoke_on_becker_epor_sbml_measurement_layer, #462). A .ant model works only when the data column matches a bare species (no formula), which is the dependency-free path the sibling smoke test takes.
Why it matters
.ant and .xml are otherwise interchangeable — an .ant model is converted to SBML at load (bngsim_antimony_model.py, antimony.getSBMLString), so the species/parameter namespace a formula needs is fully available; it's just not surfaced to _model_expression_namespace.
- The failure mode is misleading: the error says the species "is not a known model entity" when it demonstrably is one — a user can't tell it's an unsupported-path issue rather than a typo.
- It blocks the natural becker workflow on the canonical
.ant fixture (the D2D observables Epo_cells / Epo_medium are exactly such formulas), forcing a detour through the SBML form.
Suggested fix
Route .ant through the SBML namespace path. Either:
- branch
mf.endswith('.ant') to convert Antimony → SBML text (reusing the same antimony.getSBMLString conversion the model loader already does) and parse_sbml the result; or
- generalize the dispatch so anything that loads as SBML (
.xml and .ant) shares the parse_sbml branch.
Either way, drop the stale ".ant antimony carries no formula observables" comment and add a measurement-formula-on-.ant test (the SBML sibling already exists).
References
Summary
A measurement-model observable formula (
observable: <id>, formula: <expr>, ADR-0036) fails on an Antimony (.ant) model even when the referenced species exist, because the formula's allowed-symbol namespace is built by parsing the.antfile as BNGL, not SBML. The species are therefore absent from the namespace and every formula symbol is rejected as "not a known model entity" — surprising, since the rest of the pipeline treats.antand.xmlidentically (an.antmodel is converted to SBML at load).Where
Configuration._model_expression_namespace(pybnf/config.py:1858) dispatches on extension:A
.antfile matches neither.xmlnor (meaningfully) the BNGL grammar, soparse_bnglreturns an essentially empty namespace and the formula validation in_load_measurement_modelsraises.Reproduction
With
tests/sbml_files/becker_epor.ant(the Becker EpoR model) and a measurement formula over two of its species:raises:
The same model in SBML form (
becker_epor.xml,sbml_backend = bngsim) with the identical formula works end to end (verified bytests/test_gradient_optimizer.py::test_trf_multistart_smoke_on_becker_epor_sbml_measurement_layer, #462). A.antmodel works only when the data column matches a bare species (no formula), which is the dependency-free path the sibling smoke test takes.Why it matters
.antand.xmlare otherwise interchangeable — an.antmodel is converted to SBML at load (bngsim_antimony_model.py,antimony.getSBMLString), so the species/parameter namespace a formula needs is fully available; it's just not surfaced to_model_expression_namespace..antfixture (the D2D observablesEpo_cells/Epo_mediumare exactly such formulas), forcing a detour through the SBML form.Suggested fix
Route
.antthrough the SBML namespace path. Either:mf.endswith('.ant')to convert Antimony → SBML text (reusing the sameantimony.getSBMLStringconversion the model loader already does) andparse_sbmlthe result; or.xmland.ant) shares theparse_sbmlbranch.Either way, drop the stale "
.antantimony carries no formula observables" comment and add a measurement-formula-on-.anttest (the SBML sibling already exists).References
.antgap that test had to route around.pybnf/config.py:1858(_model_expression_namespace),pybnf/config.py:1764(_load_measurement_models).