Repository navigation
Conversation
…he RHS (lanl#1308) sparsity_covers_jacobian checks that every entry differences of the RHS find nonzero has a slot; nothing checked the values in those slots, and a wrong analytic entry slows Newton without moving the answer much. jacobian_values compares Model.jacobian (where its source is analytical) with central differences of Model.rhs at the start, middle and end states _states collects. Each entry is differenced over a ladder of steps, 1e-2 to 1e-10 of its column's scale, each step bounded by its gap to its neighbours. An entry is a finding only where every resolvable step (bound under 1%) contradicts the analytic one by more than ten bounds plus 1e-4 of its column's peak and 1e-8 of the matrix's, no step heard at 10% bears it out within two bounds, and it is not on a kink (one-sided differences apart at the finest resolvable step). Each of those guards answers a false alarm the corpus produced: rounding in a large f (blbr), a near-zero column (mm_tqssa_stiff), kinks at t = 0 (12 models), saturations narrower than the coarse steps (BIOMD0000000498, 480), and lanl#533's rounding plateau (BIOMD0000000393). Negative controls in test_differential_shapes.py: a 10% and a 1% wrong entry, a spurious entry, a non-finite one, and a real shape with its largest entry 10% off read DIFF; a differenced Jacobian, rounding, a noise column, a kink, a narrow feature and a rounded-away slope do not. Every guard's mutation fails its control, but for judging the best step alone instead of every one, which the veto makes equivalent outside a grey zone. Over the corpus (1,353 rows): 1,279 PASS, 72 SKIP (no analytic Jacobian), 2 DIFF. BIOMD0000000608 sits at t = 0 on a 0/0 (x_3 = 0 in its denominators), which no difference can judge; MODEL2310250001's d(GL_e_0)/d(_6PGL_0) is 1.8e16 where its RHS does not move, in a state past the blow-up both engines share. On the 53 .net rows, a 1.1x, 1.01x, zeroed or added error in the largest entry is caught wherever it changes the matrix.
This was referenced Oct 27, 2026
) The 1,353 jacobian_values rows that run added, recorded as lanl#1308 asks: 1,278 PASS, 68 SKIP (no analytic Jacobian), 5 TIMEOUT (the corpus's largest models), and the two DIFFs triaged in the PR, BIOMD0000000608 (a 0/0 at t = 0) and MODEL2310250001 (Model.jacobian against Model.rhs, past its blow-up). Against the committed baseline the run had no regressions: no row dropped and no row's outcome changed.
lanl#1291's review commits re-recorded the differential baseline (run 40440170341) under the forced-dense klu_vs_dense. The merged baseline is theirs for every existing row, plus this PR's 1,353 jacobian_values rows from run 40448597362, with the tally recomputed; nothing else differs from lanl#1291's. The runner's docstring takes both edits (lanl#1291's 8x-grid check, this PR's fourth rule).
This was referenced Oct 27, 2026
… fix/1308-jacobian-values
… fix/1308-jacobian-values
… fix/1308-jacobian-values
wshlavacek
pushed a commit
that referenced
this pull request
Oct 27, 2026
…inished The pool kills a model's worker at its cap (900 s on the nightly), and every row of the model with it. MODEL1006230049, 0053 and 0077 take 716-751 s of that on main, so any check a pull request added to every model turned all their rows TIMEOUT: #1315, #1318 and #1319 each read 27-36 regressions, none of them a finding. check_all now takes a deadline, 0.9 of the cap. Every run has until it (run(timeout=)), no run or check starts past it, and a check that does not finish is its own TIMEOUT, said why. Each verdict is written as it is reached, so a worker the pool still kills (one compiling C past the deadline, which no run's timeout bounds) leaves the runner every row it finished; only the rest are TIMEOUT.
…n() (lanl#1308) Since lanl#1337, Model.jacobian() refuses a difference quotient whose step decides an entry instead of returning it. jacobian_misfits called it first and read .source after, so on current main seven BioModels this PR's baseline holds as SKIP (BIOMD0000000053, 446, 451, 465, 466, 468, MODEL2109110001) read EXCEPTION, which the nightly alerts as a new crash. prepare_analytical_jacobian() is the same decision, asked first.
) verdicts.json of this PR's Path-agreement run 40721476951, on 18c677c merged with main at 9a26f5b. Every row main's checks give is the same as in main's scheduled night 40712357505 (lanl#1367 re-baselines main from it), plus this PR's 1,353 jacobian_values rows. Against the old file: - BIOMD0000000338|klu_vs_dense PASS -> EXCEPTION, from main (lanl#1302, lanl#1304); - BIOMD0000000385|ajac_vs_fdjac and |klu_vs_dense TIMEOUT -> PASS (lanl#1321); - MODEL1112050001|jacobian_values SKIP -> PASS (its base ran this time); - MODEL1006230049|jacobian_values TIMEOUT -> SKIP, no analytic Jacobian; - the seven models Model.jacobian() refuses stay SKIP (18c677c).
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 #1308.
What it adds
A
jacobian_valuescheck. At the three states_statesalready collects, it comparesModel.jacobian(y, t)(wheresource == "analytical"; otherwise SKIP) with central differences ofModel.rhs. Columns are scaled assparsity_covers_jacobianscales them, and each entry is judged on its column's own peak.How an entry is judged. It is differenced over a ladder of steps, from 1e-2 to 1e-10 of its column's scale. Each step's bound is the larger of its gaps to the neighbouring steps. An entry is a finding only where all of these hold:
A non-finite analytic entry is a finding wherever some step resolves the entry.
Why each rule is there
The first drafts each produced false alarms on the corpus, and each rule answers one:
max(0, x)/if()kinks: central differences give the mean of the two slopessqrt(1+u)−1with u ≈ 1e-6, a step under ulp·N/4 rounds the term's slope away, so the fine steps agree on 0.661 where the entry is −1.96e-4; the coarse steps (2–3% noisy) read −1.96e-4Results over the whole corpus
The run covered 1,353 rows: the 53
.netjobs and the BioModelsdifferential_run.pyreads, run locally.Model.jacobiandifferences the RHS)The two DIFFs, triaged:
x_5/x_3,/x_3/x_3). The state is a 0/0 point, where the rates are not differentiable, and a single-step probe there reads 0.1004. Not a bngsim defect, and not something a difference can judge. It stays in the baseline as a known row.Model.rhshad that slope, a step of 4e-5 would movefby about 7e11, far over its rounding, and it does not move. SoModel.jacobianandModel.rhsdisagree. The two analytic entries in that row stand in the ratio −S₁/S₀ (1.78e16 / −1.18e16 = −1.503). That is the shape ofv8·S₁/Stot², but withStotacting like 6.6e-9 rather than 1.0, and it is the same whichever ofrhsorjacobianis called first. The state lies past the blow-up both engines share (the job'sknown_artifact: "BOTH engines blow up to ~−5e16"), so it is a low-priority lead rather than a filed issue.Power, on the 53
.netrows. The largest entry of the analytic Jacobian was corrupted, and the check run on the result:The seven "no effect" rows are the pulse and constant models, whose Jacobian is exactly zero (their rates read only the clock).
Tests (
python/tests/test_differential_shapes.py)CHECKS, and all 15 shapes pass it.derived-log10) with its largest entry 10% off. The test asserts the corruption changes the matrix, since the triangle pulses' Jacobians are all 0.None;−|b−2|at b = 2;1e-9·tanh(b/1e-9), with a constant that makes the coarse plateau the tightest;Mutations. Each guard was mutated, and every mutation fails its control:
The one survivor is judging only the best step instead of every resolvable one. With the veto in place, the two differ only when a resolvable step is between 2 and 10 bounds from the analytic entry, so I kept the stricter rule. I also removed a rounding term (
4·eps·|f|/h) that the ladder's gaps made redundant. Without it, every control and every hard corpus case reads the same.Suites:
test_differential_shapes.pyandparity_checks/tests/test_differential_run.py: 194 passed, 1 xfailed (#1291's #1254 xfail). ruff 0.14.0 is clean. The branch merges cleanly with the current lanlmain.Known limits
tanhof width 1e-10 under a constant of 10.Baseline
Recorded from this PR's own nightly run, 40448597362, in 8920baa, as #1308 asks. It holds the 1,353
jacobian_valuesrows that run added:CI matched the local run. Against the committed baseline the run had no regressions: no row dropped, and no existing row's outcome changed.
Changes (this PR's commit only)
Plus 198 lines of tests in
python/tests/test_differential_shapes.py. No changelog fragment: this changesparity_checks/and tests only, whichci/changelog.pydoes not ask an entry for.