Repository navigation
Conversation
Every path the differential suite compares is built from one loaded model, so a bug in the loader, the model build or the parser makes them agree on the wrong answer. Four checks transform the file instead, where the answer is known to follow, and run it again (_transforms.py; libsbml for SBML, the .net blocks rewritten as text): * reorder: species and reactions (and SBML's parameters and species references) in reverse. Unchanged by name. * rename: parameters, observables, functions and species (SBML: species, parameters, compartments) to fresh names. Unchanged by the map back. * split: each reaction into two at half the rate (a parameter rate through a new parameter, a function's through a new function, BNG's MM/Sat/Hill through their first argument). Unchanged. * rescale_time: every rate and rate rule times 3 and the window divided by 3, where the model reads no clock, delay, rateOf, event or reaction rate. x'(t) = x(3t). Each is judged with Base.compare, aligned by name; one that does not keep the answer for a model is a SKIP that says why. Base keeps its run's species names: a Result leaves out a parameter an event promotes. Over the corpus (5,412 rows): 4,413 PASS, 997 SKIP, 0 DIFF, 2 EXCEPTION, both reorder. BIOMD0000000908 blows up past t = 141 on a reordered sum in both bngsim and RoadRunner. MODEL1812040005 is a finding: with its species reversed bngsim lets P go below 0, where a*P^z is NaN, and fails at t = 0.106, where in the order written it holds P at 0; RoadRunner runs the reversed file to 3.6e-8 of bngsim's original answer. Controls: each transform keeps an autonomous SBML model's answer (it has a rate rule, so the rescale's is exercised), and done wrong on a .net and on that model it reads DIFF (values left in the old order, two names crossed, the whole rate on each half, the rates scaled and not the window). Each fix the first corpus run asked for has a control that fails without it: a rate rule reading a reaction rate refused (BIOMD0000000556 came out 9x), MM and a spaced expression in a .net, an id bngsim respells as _ant_<id>.
This was referenced Oct 27, 2026
This was referenced Oct 27, 2026
…onent of a number alone (lanl#1305) A model's checks share its cap (lanl#1321), and every check added since runs after main's. The transforms ran in main's loop, ahead of locale: four more runs that could tip a slow model's locale row (MODEL1006230049, 0053, 0077 already take 689-894 s), which the baseline holds as PASS, to TIMEOUT. _subst matched names inside numeric literals: with a parameter named e1, 2e1*e1 became 2mt_p1*mt_p1, which does not load. The branch also merges lanl/main, which it conflicted with (check_all's deadline, lanl#1321).
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1305.
What it adds
Four metamorphic checks. Every path #1291 compares starts from one loaded model, so a bug in the loader, the model build or the parser makes them agree on the wrong answer. These transform the file instead, so the loader reads each model a second time, differently. They are in a new
parity_checks/differential/_transforms.py, using libsbml for SBML and rewriting the blocks of a.netas text.reorderrenamesplitMM/Sat/Hillthrough their first argumentrescale_timex'(t) = x(3t)Each is judged with #1291's gate (
Base.compare), aligned by species name, as the issue asks, since a transform changes the order of floating-point sums. Where a transform would not keep the answer for a model, the row is SKIP and says why:rescale_timeskips a model with events, the clock, a delay,rateOf, or math that reads a reaction's rate;splitskips a reaction whose rate is read by name elsewhere.Basenow also keeps its run's species names, because aResultleaves out a parameter that an event promotes into the state.Over the corpus
5,412 rows: the four checks over the 53
.netjobs and the BioModels, run locally.Every SKIP gives its reason:
rescale_time: events (187) and the clock or a delay (174);split: no reaction whose rate is not read by name elsewhere (185);reorder: fewer than two species and reactions (85; mostly rate-rule-only models);The two exceptions, triaged:
BIOMD0000000908: the model, not bngsim. With its species reversed, it blows up past t ≈ 141 (states near −1e262) in both bngsim and RoadRunner (1.4e8 off). The base sits at the edge of an instability that reordering a sum is enough to cross.
MODEL1812040005: a finding, filed as sbml: reversing listOfSpecies makes MODEL1812040005 fail (P below 0, a*P^z NaN) where the order written runs; RoadRunner runs both #1340. Reversing
listOfSpeciesalone is enough to reproduce it:a_hat = a*P^zis NaN, and fails at t = 0.106 (CV_REPTD_RHSFUNC_ERR).codegen=Falseand withjacobian="fd", andModel.rhsis identical in both orders at t = 0.So bngsim's answer depends on species order. That gets its own issue (to follow).
What the first corpus run caught in the transforms themselves. Each is now fixed and has a control:
renameDIFFs: ids likenull,In,time,NaNare reported as_ant_<id>, so the alignment now follows that spelling..netrate-law keywords (MM kcat Km) were treated as expressions..netvalue with spaces was only partly renamed.stoichiometryMathis not in libsbml'sgetListOfAllElements, so it is now walked explicitly.rescale_timeDIFFs (BIOMD0000000556, 245, 575, MODEL2306170002, up to 2.0 off) came from math reading a reaction's rate. A rate rule that reads one was scaled twice: BIOMD0000000556's APP came out 9×. RoadRunner showed the identical gaps, so this was the transform and not bngsim, and these models are now SKIP.Tests (
python/tests/test_differential_shapes.py)rateOf..net(equality-dwell) and on that SBML model:reorderwith the values left in the old order;renamewith two names crossed;splitwith the whole rate on each half;rescale_timewith the rates scaled and not the window.rescale_time;.netwithMM kcat Kmand a spaced parameter expression keeps its answer under all four;null→_ant_null) aligns underrename;rescale_time, by name.Mutations, each caught: in the check, the window not divided, the name map ignored, no alignment by name; in the transforms, rate rules not rescaled, events not refused, SBML references not renamed; and each of the four fixes above.
Suites:
test_differential_shapes.pyandparity_checks/tests/test_differential_run.pygive 247 passed, 1 xfailed (#1291's #1254 xfail). ruff 0.14.0 is clean. The branch merges cleanly with the current lanlmain.Cost
Four more runs per model, on the base's window. Locally, the four checks over the whole corpus (5,412 rows) took 220 s on 8 workers (an M3 Max). I have not timed them on the CI runner.
Baseline
parity_checks/nightly/baselines/differential.jsonwill be re-recorded from this PR's own nightly run, as #1305 asks, in a follow-up commit here. Until then the new rows read as new, whichALLOW_MISSING_BASELINEallows on a pull request.Changes (this PR's commit, less the new
_transforms.pyand tests)Plus
parity_checks/differential/_transforms.py(new, 507 lines) and 177 lines of tests. No changelog fragment: this changesparity_checks/and tests only, whichci/changelog.pydoes not ask an entry for.