Repository navigation
Conversation
…er (lanl#1306) The differential checks run fixed models, so they are oracles only over the expressions those models hold. differential_generate.py draws models instead, seeded, from the grammar bngsim accepts, runs each through check_all, and shrinks every DIFF or EXCEPTION to a ready reproducer. _generate.py draws a network of 2-4 species and 1-4 reactions whose laws are random trees over arithmetic, guarded powers and quotients, exp/log/ln/ log10/log2/sqrt, abs/floor/ceil/round/rint/trunc/sign, tanh/sin/cos/atan/erf, min/max/avg/clamp, if() on every comparison and on &&/||, the clock, and in SBML piecewise and rateOf of a driver species; renders it as a .net or as SBML; and shrinks a model by dropping reactions and replacing subexpressions by their children or constants while the same check finds the same thing. Laws stay finite by construction (logs and roots of 1 + x^2, bases of at least 1, guarded quotients), with squares, not abs(), as guards so the sensitivity checks keep the analytic path; each enters its reaction as k*g^2/(1 + g^2). Each seed runs in its own process under a wall cap and a step budget (20,000: a law that switches on the clock and on rateOf can crawl). A model whose base does not run is no finding. First run, seeds 0-199 in both formats (400 models, 430 s): 389 run, 3 no base, 8 timeouts, 2 findings, both grid and both genuine. net 72 (shrunk 20 -> 10, floor(2*O1) and round(O2)): at rtol 1e-8 161 output points read 4.9e-5 of the peak off the converged answer where 21 read 4.4e-8. net 174 (35 -> 13, trunc(O2) as O2 slides to 1): 8.4e-3 at 161 points, 3.1e-8 at 21; its rtol 1e-12 run fails (CV_CONV_FAILURE at t = 8.5) on 21 points and runs on 161. Tests: a seed is its model; every grammar entry, alone in a law with a negative first argument, loads and integrates finitely in each format it has; generated models stay finite; the shrinker cuts a model with a floor() to one reaction of size <= 3; a planted cc_vs_interp defect is found and shrunk to a one-reaction reproducer, and none is reported where every check passes. Mutations caught: a shrinker that cuts nothing, DIFF not counted as a finding, an unguarded log or sqrt.
…anl#1306) The SBML half skipped the nine .net-only entries (rint, erf, avg...) with an undeclared reason, which the suite's skip audit fails on every shard: a .net-only entry is never drawn for SBML, so there is nothing of it to test there. 65 cases now, none skipped.
# Conflicts: # python/tests/test_differential_shapes.py
…runk; the shrinker takes a number for a named leaf (lanl#1306) run() reached its status only with res None, which happens only when the queue was empty, so 'dead' was unreachable: a worker gone without a result (a segfault in bngsim) read 'timeout', at once. Now dead, with its exit code. The seed's cap covers the shrink (up to its budget of check runs), and the reproducer was written only after it: a seed killed mid-shrink left none. The model as found is written first, its note saying it is not shrunk. A parameter, species or time() leaf replaced by a number is no fewer nodes, so shrink's size test refused every such cut and a reproducer kept them. Fewer named leaves now breaks a tie in size. The branch also merges lanl/main, which it conflicted with (tests).
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 #1306.
What it adds
A generator, a shrinker, and a runner that joins them. The differential checks run fixed models, so they are oracles only over the expressions those models hold. This turns the harness from checking models into searching for bugs.
parity_checks/differential/_generate.pygenerate(seed, fmt)draws a network of 2–4 species and 1–4 reactions whose laws are random trees over the grammar bngsim accepts:exp/log/ln/log10/log2/sqrt;abs/floor/ceil/round/rint/trunc/sign;tanh/sin/cos/atan/erf;min/max/avg/clamp;if()on every comparison and on&&/||;piecewiseandrateOf(of a driver species, so no law reads its own species' rate).It renders the model as a
.net(functional rate laws) or as SBML (libsbml,parseL3Formula). A seed is its model.Laws stay finite by construction, so a finding is a disagreement, not a domain error, as the issue asks:
1 + x², powers have bases of at least 1, and quotients are guarded;k·g²/(1 + g²), which is bounded but still reads every node ofg.The guards are squares, not
abs(). bngsim declines the analytic sensitivity RHS forabs()under a call, and anabs()guard in every law kept the sensitivity checks off the analytic path in a first draft.absis in the grammar as itself.shrink(model, holds)delta-debugs: it drops reactions, then replaces each subexpression by one of its children or a constant, for as long asholdsstill holds. A finding'sholdsis "the same check gives the same status, and for an exception the same class".parity_checks/differential/differential_generate.pyruns the seeds, each in its own process under a wall cap and a step budget (20,000, a fifth of the corpus's; a law that switches on the clock and onrateOfcan crawl). It runs each model throughcheck_all, shrinks each DIFF or EXCEPTION, and writes out every reproducer beside a JSON note. A model whose base does not run is not a finding.First run
Seeds 0–199 in both formats: 400 models, 430 s on 8 workers locally. 389 ran, 3 had no base, 8 timed out, and there were 2 findings:
ajac_vs_fdjac,klu_vs_dense,cc_vs_interp,sparsity_covers_jacobian,backend_as_askedsens_runs/sens_vs_plainfloor,ceil,ifon the clock…)cc_vs_mirgridBoth findings are genuine, judged against converged runs:
netseed 72, shrunk from size 20 to 10: one reaction,f1 = k1·g²/(1+g²)withg = floor(O1 + O1) − (round(O2) − O1).At rtol 1e-8, asking for 8× the output points moves the answer by a thousand times the tolerance.
netseed 174, shrunk from size 35 to 13: two reactions, readingtrunc(O2)as O2 slides up to 1 (0.99999991 at t = 10), andif(time() > 1, O1, …).CV_CONV_FAILUREat t = 8.5) on 21 points, and runs on 161.Both are switches on the state, crossed or stepped over depending on where the output stops fall: the class #907, #1192 and #926 track. They are filed as one issue with both reproducers, for a maintainer to judge whether they are new or existing cases: #1342.
Tests (
python/tests/test_differential_shapes.py, appended)test_a_seed_is_its_modeltest_every_grammar_entry_is_one_bngsim_reads[fmt-op]test_generated_models_load_and_stay_finite[fmt]test_the_shrinker_cuts_down_to_what_still_findstest_a_planted_defect_is_found_and_shrunk_to_a_reproducercc_vs_interpmade to read DIFF on any model containingrint(,investigatereports it and shrinks it to a one-reaction reproducer that still has therint(test_no_finding_is_reported_where_every_check_passesMutations, each caught:
log;sqrt.The grammar test originally fed every entry positive inputs and missed the last two. Its first argument is now negative.
Suites:
test_differential_shapes.pyandtest_differential_run.pygive 239 passed, 1 xfailed. ruff 0.14.0 is clean. The branch merges cleanly with lanlmain, #1315 and #1316.Also:
parity_checks/differential/.gitignoreignoresruns/, where both this runner anddifferential_run.pywrite by default, asamici_parity/.gitignoredoes for its own.CI notes
.net-only entries under SBML with an undeclared reason. It is now parametrized over spelled entries only (second commit).n_steps,n_rhs_evals,n_jac_evalsall at ratio 1.0), but wall time was 2.15x on the same CPU model. This PR changes no engine code, and the same job passed on amici_sens: judge each sensitivity parameter on its own scale (#1261) #1303, differential: the analytic Jacobian's values against differences of the RHS (#1308) #1315 and differential: metamorphic transforms of the model file (#1305) #1316. A rerun should clear it; I don't have the rights to trigger one.Nightly
The issue allows "nightly or on demand", and this is on demand. A run is reproducible from its seeds, and the first one took about 7 minutes. Two things would make a nightly job worth adding, as a follow-up:
That is why no
differential.jsonbaseline is re-recorded here: the runner adds no row to the nightly report.Usage
No changelog fragment: this changes
parity_checks/and tests only.