Repository navigation
sensitivity: root where the state leaves a rate-law equality, and decide there (#1279) - #1411
Merged
Merged
Conversation
…ty it stays on (lanl#1279) lanl#1240 read a held equality at the run's start alone. An event that set B = 0 under piecewise(kb, B == 0, 0) left the state on the surface, and the columns of whatever ended the stretch came back 0 with no warning. The solver now reads each such equality after every event, by the start check's own test, and raises SensitivityUnsupportedError where the state stays on it. A surface over nothing a reaction moves is left only at an event, whose jump is right, and is not refused.
…ide there (lanl#1279) lanl#1240 decided at the run's start whether a rate-law equality over state was held, and refused the run if so. An event that put the state on the surface mid-run was not seen (dY/da = 0 for -6.44), and every stretch the run never leaves, only an event ends, or whose leaving changes no rate law was refused, though main's columns were exact. Each such equality is now a CVODE root that fires where the residual leaves exactly 0. The run is refused there only where the flux of the reactions reading the equality changes, unless the state was put on the surface at the run's start or at an event whose time no column moves and held it for no time. An event at a moving time that left the state on the surface for an instant gave dY/dte = -1 for 0, and is refused. The start check and the first version's check at events are removed.
…y is that switch's (lanl#1279) Whether the inner switch's restart lands on the equality's surface, so the leaving is rooted and judged, or past it, is rounding: lanl#1228's parameters landed past it on macOS and on it on Linux, where the run was refused. The leaving at the instant such a switch is crossed, in the same return or right after its restart, is covered by that switch's jump. The test runs four parameter sets, three of which take the judged path on macOS.
This was referenced Oct 31, 2026
_apply_held_equalities found the equalities, the switches inside them and their reaction map in one try, and dropped all three if any step raised. The washout (B = 0 at t = 1 under piecewise(kb, B == 0, 0)) then came back [0, 0, 2.218876] for [-6.4378, -1, 2.2189] again, with only a warning about the map. The same happened when state_switch_conditions failed: _apply_state_switch_sens returned before the equalities were looked at, where lanl#1228's start check had not needed the conditions. Only finding the equalities can now leave them unwatched. Without the map the solver reads every reaction's flux (its existing fallback), and without the inner switches it covers no leaving by one; both refuse more, never less. The new test fails all three ways on the PR's head. Also: the exception translator's comment names the new type, and run()'s passthrough comment says the solver's mid-run refusal arrives there too.
wshlavacek
pushed a commit
that referenced
this pull request
Nov 3, 2026
… no column moves (#1445) #1411's judge let a leaving at once through only after the run's start or a fixed-time event, the only restarts that recorded whether their time moves. A crossing stop and a switch time no requested column moves are fixed restarts too: B made from time() > t3 beside if(Bobs==0, kb, 0) returns dY/d[kp, kb] = [0, t3] where it was refused at t3. With t3 requested it is still refused: the #48 jump reads Y's flow with B still on the surface.
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 #1279.
What was wrong
#1240 (#1228) decided once, at the run's start, whether a rate law's equality over state (
if(Bobs==0, kb, 0)) was held, and refused the run if so. That was wrong in both directions.Silent zeros: the start check missed them. These are #1279's case 1, plus one found while fixing it. dY/dθ, rtol 1e-10:
t0=false)tesets B = 0 when B is made at once: on the surface for an instant, at a moving timeThe last row is the event's jump reading Y's flow on the surface, where B sits for no time. It was wrong on main, and is wrong on #1411's first version too.
Over-refusals: #1279's case 2. Main returned the right columns for every one of these, and #1240 refused them:
fB() = 0)if(fB()==0, kb, 0), a rooted condition inside the equalityif(Bobs==0 && Aobs<1, kb, 0), never true as B leaveskb*Aobs, with B's production a clock switch at t = 3#1228's own model over (0, 8),
Bobs!=0, and the run from t = 5 are still refused, now at the point where the state leaves the surface.The change: decide at the exit
0.5while the residual is exactly 0 and−0.5otherwise, so it fires where the state leaves the surface or reaches it. An event's restart reads it afresh, so a stretch that only an event ends never fires it, and that event's own jump is right. Only runs with sensitivity columns register these roots.judge_held_equality_exitcompares the flux of the reactions that read the equality on the surface and just after (compute_flux_split, a gap > 1e-6 of the gross flux).Aobs==10until t = 1.5e-16.SensitivityUnsupportedError, naming the equality and the time.if(fB()==0, …)with fB gated byq-Aobs>0): its jump reads the equality's readers either side of its own surface. A leaving at the instant it is crossed, in the same root return or right after its restart, is covered. Python passes these switch conditions per equality (set_held_equality_inner).equalities_held_at_startand the start-time refusal;bngsim::SensitivityUnsupported, mapped toSensitivityUnsupportedError;compute_all_sensitivitiespassing that refusal through instead of wrapping it inSimulationError._apply_held_equalitiespasses the equalities (rate_law_equalities), their reactions, and the switches written inside them (set_held_equality_conditions,set_held_equality_reactions,set_held_equality_inner).docs/user-guide/sensitivities.mddescribes the refusal as it now stands, andchangelog.d/1279.fixed.mdis added.The diff (
src/cvode_simulator.cpp, abridged)Tests
test_equality_held_over_state.py(#1228's file, updated):.netstretches above, at rtol 1e-6 against the closed form;Bobs==0,Bobs!=0, the run from t = 5,run_batch,compute_all_sensitivities) is still refused, now at the exit;test_equality_landed_on_by_event.py:Mutation tests (the core rebuilt for each C++ mutant): every one is killed except the last.
Aobs==10/Aobs!=10Aobs==10compute_all_sensitivitiesNo change where the state never sits on a surface
Bobs!=0the same,Aobs==1000, and an equality only an output reads). Species and sensitivities are bit-identical to main, with the same step counts.Checks (macOS arm64)
python/tests: 16,871 passed, 0 failed.parity_checks/tests: 1,015 passed. The 2 failures (test_bngsim_provenanceand the reference-archive replay) also fail on main here: their subprocess can't importbngsimwithout my local import shim.ci/changelog.py requiredandcheckpass: the first push lacked the fragment, which is what turned "fragment staged" red.pre-commit run --all-filesandsphinx -Ware clean.Not verified
fB() = if(time()>5, kp, 0), while Y readsBobs==0. It is refused, but central differences give the exact [0, 0, 5]. Main refused it too, at the start.