Repository navigation
convert: carry an assignment-rule species through cBNGL instead of refusing it (#1400) - #1401
Merged
wshlavacek merged 5 commits intoNov 1, 2026
Merged
Conversation
…fusing it (lanl#1400) write_bngl refused every model with a species an SBML assignment rule sets, saying the round trip cannot carry the rule. It was carried, as the function its rate laws read; only the species' own column was lost, held by BNG2.pl and by bngsim reloading BNG2.pl's .net, whose pattern name never matched the rule's. The writer now names the molecule __ar_<rule>, the reader pairs it with that function, and validate="bng2" checks the column too (max_ar_report_delta). It is a bngsim_only warning, as on the .net path. A model with no reactions, which generate_network aborts on, is refused.
…e a changed species count by name What the review found on top of the PR's change, which round-trips every edge case tried (amount-valued at V=1, alias and sum rules, a rule nothing reads, a chain, a parameter-only rule, two compartments, time): - The cBNGL corpus harness (parity_checks/rr_parity/cbngl_bng2_validate.py) says it measures "exactly as the production sbml_to_bngl(validate="bng2") gate does", but still called roundtrip_rhs_delta, which drops the column check. The 175 models this PR newly accepts were certified on a probe that cannot see their column (dy/dt = 0 on both sides), and BIOMD0000000664, which the gate refuses, came out "faithful". It calls roundtrip_deltas now, with an ar_report_mismatch status. - roundtrip_deltas promises a ConversionError whenever faithfulness cannot be established. A reload with a species count the source does not have raised an IndexError from the new column check (yb[back_perm] = y), ahead of the right-hand side's ValueError, so sbml_to_bngl(validate="bng2") leaked it and the harness, which catches ConversionError only, crashed instead of writing the species_mismatch row it has a branch for. _name_aligned_perm refuses the count by name. - ConversionReport.summary() printed the RHS delta only: a --gate run refused for the column alone printed "max|Δ dy/dt| = 0.00e+00" and exited 1. It prints max_ar_report_delta too. - Stale text: the writer's comment still said these species are refused and the gate cannot see them; _ar_report_map_from_net's docstring said a BNG2.pl .net never pairs; test_sbml_to_bngl's docstring listed the species among the refusals; the sbml_to_bngl docstring, the --gate help and the interchange guide called the gate RHS-only; the bngsim_only note said "function" where an alias or sum rule is a Molecules observable. - BIOMD0000000664's t = 0 difference, left open in the PR, is BNG2.pl rewriting the pulse time()>=0.0 to time()>0.0, the artifact _bng2.py documents; the column really reads 0 for 1 in row 0, so the refusal is right. The changelog says why. - test_bngl_rule_species's BNG2.pl helper left a mkdtemp directory behind per test; it writes under tmp_path. Three tests, each failing on the PR's head: the harness's status for a column-only, an RHS-only and a clean delta; the summary line; the refused species count.
…tation the column check uses _name_aligned_perm returns perm[i] = the reload's index of source species i. The new column check reads the reload at the same state the right way (yb[back_perm] = y), but _rhs_delta, the gate's other half, fed the reload y[perm] and read its derivative back as bb[perm] = b: the inverse, twice. For a reorder that is its own inverse the two agree, and the PR's reorder test used one ([2, 1, 0]), so it could not tell them apart. On a reload in the 3-cycle order [C, B, A] for the source's [A, C, B] (perm [2, 0, 1]), a faithful network read |Δ dy/dt| = 1.0 through _rhs_delta and 0.0 through the column check. _rhs_delta now builds yr[perm] = y and reads b[perm]. BNG2.pl keeps the species block's order for this writer's ground patterns, so the round trips measured so far had the identity permutation and do not move (BIOMD0000000009, 321, 664, MODEL1112110002 as before). The reorder test is that 3-cycle now, and pins both probes and _name_aligned_perm's direction: through the permutation each agrees, through its inverse each does not. It fails on the previous _rhs_delta. Left as is: the column check probes at t = 0, where BNG2.pl's >= -> > rewrite moves BIOMD0000000664's pulse, while the right-hand side probes only at t > 0. A derivative at one instant leaves the trajectory alone; the reported column at t = 0 is row 0 of every run, and it reads 0 where the source reads 1, so that refusal stands.
The suite legs have no BNG2.pl, so test_bngl_rule_species.py's six round trips through it skip there, and BIOMD0000000009 left test_refuses_fail_loud, a corpus case those legs skip: 5 more on every leg (ubuntu 228 -> 233, lapack 202 -> 207, macos-15 190 -> 195; main at 0c1732d, run 41309929676). ubuntu-latest crossed the 230 ceiling. Raised by that 5, as for lanl#1215's.
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.
Closes #1400: both items, the wording and the carried column.
What was wrong
bngl_capability_reportrefused every model with a species an SBML assignment rule sets, saying "the flat .net round-trip cannot carry the rule ... (GH #205/#223)".B() 2.0*__sp1), and every rate law reads that, so the other species' dynamics are exact.$-fixed, and BNG2.pl holds it at its initial value, as does bngsim reloading BNG2.pl's.net. That.netnames the species as a pattern (@comp1::$B()), which never equals its rule's name, so_ar_report_map_from_net(sbml_to_net's default L2 gate certifies a species assignment rule that the reloaded .net never applies; validate="full" catches it #515) never found the pair.bng2gate could not see it: its probe is the right-hand side, and a held species has dy/dt = 0 on both sides.The fix
Item 1, the wording. The note and its comment state the real mechanism, the stale citations are gone, and the test module's docstring and refusal row are corrected.
Item 2, the column is carried, as the issue sketches:
__ar_<rule>(AR_MOLECULE_PREFIX), the name of the function or observable that carries its rule:@comp1:$__ar_B()besideB()._molecule_namestakes the carriers, so the writer and the round-trip's aligner (_name_aligned_perm) build the same names. A species whose mark would collide with an existing name is left unmarked and keeps the refusal._ar_report_map_from_netpairs a fixed@comp::__ar_X()with the function or non-identity observableX. An unmarked fixed species keeps the exact-name rule it had: a BNGL species name is a pattern, and only the mark says it is a rule's. An unfixed marked species is not paired.sbml_to_bngl(validate="bng2")now also runs_ar_report_delta, which gainsback_namesandback_permbecause BNG2.pl's names and order are not the source's. The report carriesmax_ar_report_delta, andrhs_faithfulrequires both deltas, as on the.netpath.roundtrip_deltasreturns all three;roundtrip_rhs_deltakeeps its signature.bngsim_onlynote, warned and never refused, as net writer: warn when a rule species is written so only bngsim reads it as meant (#1274) #1345 made it on the.netpath, because BNG2.pl's own run still holds the column. The docs say so (interchange.md).generate_networkaborts on one ("Nothing to do: no reaction rules defined"). BIOMD0000000566 and 567 are rule species alone: refused for those before, they would now be written as files that do not build.Measured
The issue's models,
write_bngl→ BNG2.plgenerate_network→Model.from_net, againstModel.from_sbml(t = 0-5, rtol 1e-10):B := 2*A, C reads BB := A*A$Bconsumed byJ3: B -> D; k3*BB := 2*Ain a compartment of volume 2The corpus: every model under
rr_parity/modelswith a rule species (289), throughsbml_to_bngl(validate="bng2", strict=False):g2_0as 1 at t = 0 in the source and 0 in the reload, and both agree afterwards. Understrictit is refused, as it should be.Tests
test_bngl_rule_species.py(11), every one failing on main:__ar_BbesideB()and warned, not refused;$Y()besideY(), nor an unfixed__ar_Z();simulatestill holds it (.cdat: 20.0, the others to 1e-6). That last one flags thebngsim_onlynote as stale if BNG2.pl ever carries the column.test_sbml_to_bngl.py:test_validate_bng2_gatepatches the new seam and adds a forced rule-column divergence, which raises as an RHS one does;test_refuses_fail_loud, since it is no longer refused for this.Mutations
test_validate_bng2_gateback_permChecked
pre-commit run --all-filesandsphinx -Ware clean.Not in this PR
.gdatcarries the rule's value withprint_functions=>1). Only bngsim reads the pair back; that is thebngsim_onlynote.g2_0 := g2): the gate refuses it, and why the source reports 1 there is a question of its own.