Repository navigation
Conversation
…s there (lanl#1276) A sensitivity run takes a row as reached from up to 1e-12 short of it after a step. Stopped that close before the sample by another root, it took neither the plain run's branch nor lanl#1182's read-ahead (which needs t_now == t_out), and the row held the state from before an event that crosses at the sample: [S] 5.5 at t = 2.4999999999999 against the plain run's 2.75 at 2.5. The read-ahead now takes that row too: the state ahead is a step of f from where the run stands (CVODE's history ends there), what rises fires at the row with its jump, the run is restarted there, and the row is taken at t_out. Its columns are read with CVodeGetSensDky at the row: after that restart CVODE's last return time is the earlier t_now, and CVodeGetSens refused it, leaving the row's columns at 0.
|
Thanks. We reviewed this independently, Opus 5.5 and Fable 5.1, each on our own build of 14da732 merged onto main c0877fa (clean), on macOS arm64 with KLU on. The fix is right for what it targets, and we found no correctness defect in it. A row a sensitivity run takes as reached up to 1e-12 short of a sample is now read ahead from where the run stands. What crosses there fires with its jump, and the columns are read at the row after the restart. Two things to settle before merge:
What we checked comes first, so it needn't be redone. What we checked
1. With #1353 merged, its
|
time >= 2.5 |
time > 2.5 |
|
|---|---|---|
| main | row at 2.4999999999999, S = 5.5 and R = 0, recorded before both fires (the #1276 defect), no error | row at 2.4999999999999, S = 5.5, R = 0 |
| this PR | refused: "events 'E1' and 'E2' fire at the same instant t=2.5 … (issue #49)" | row at 2.5, S = 5.5, as the plain run |
| this PR + #1353 | refused (#49) | refused (#1353's cross-batch check) |
| plain run | row at 2.5, S = 2.75 | row at 2.5, S = 5.5 |
The refusal is consistent with #49 and #1353. Neither tests whether the two events commute (here they do: E1 writes S and E2 writes R). Still, it's a change from a run main completed, and nothing pins it.
We'd suggest a test for a clock trigger at a row reached short, covering both >= and >, and a line in the body.
Low
- Merge order with sens: read a row where the run stands when CVODES returns tout from a step that does not span it (#1301) #1343. As above, taking sens: read a row where the run stands when CVODES returns tout from a step that does not span it (#1301) #1343's form of that line leaves this PR's flag unused. A sentence on the two PRs would keep the second rebase from being a guess.
- Tolerances. The new cases' bounds carry no reason: rtol 1e-6 against the closed form, atol 1e-6 on the columns, rtol 1e-12 against the plain run. q and S are linear in t, so the measured errors above would give each its reason, or tighter bounds.
- Simplification. The new Euler-step block is the
h_last == 0.0block witht_nowin place oft_out[i]. One block, witht_base = short_of_row ? t_now : t_out[i]underif (short_of_row || h_last == 0.0), says it once. - A comment. The branch's outer comment says the row "is read ahead the same way from there", but it's an Euler step of f, not CVODE's interpolant; the inner comment says so.
Not checked: the fd_sens sweep (the body reports 106 PASS on both cores), Linux or Windows on today's main, and perf_ab (one RHS evaluation per sensitivity row reached short of a sample).
Closes #1276.
What was wrong
#1205 (#1182) gave a sensitivity run the read-ahead a plain run makes at an output. At a sample it reads the triggers with the state half a root tolerance ahead, and fires what rises there with its sensitivity jump. That holds only where the run stands exactly on the sample (
t_now == t_out[i]).A sensitivity run also takes a row as reached from up to 1e-12 short of it after a step (
reached_output). There it took neither branch: the plain-run branch issens.n_total == 0only, and the read-ahead branch needs equality. So where another root stops the run that close before the sample, the row holds the state from before an event that crosses at the sample.#1276's model:
E1: at (q >= 2.5): S = S/2andE2: at (q >= 2.5 − off): R = 1, withq' = kand a sample at 2.5. On main 9a26f5b:The fix (
src/cvode_simulator.cpp, the per-row event block)#1276's first option:
sens.n_total > 0 && t_now < t_out[i]). CVODE's history ends att_now, soCVodeGetDkyrefuses the time ahead, and the state there is a step of f from where the run stands. That is the same as the existing restart case (h_last == 0), and within 1e-12 well inside the tolerances.t_out[i]with its jump, as A sensitivity run records the row before an event that a plain run records after it (#1133's read-ahead is off with sensitivities) #1182's path already does. The run is restarted there, and the row is taken att_out[i].t_now, now before where the run starts.CVodeGetSensrefused it, and the row's columns were left at 0. For a row restarted this way the columns are read withCVodeGetSensDky(t_ret). Every other row reads as before.A row where nothing rises is left as it was, at the time the run stands at.
Tests (
test_state_trigger_on_a_sample.py)The oracles are closed forms: q = k·t, so E1 is at t* = 2.5/k, and S = (3 + t*)/2 + (t − t*) after it.
test_a_row_reached_from_short_of_the_sample_holds_the_event[1e-13, 5e-13, 2e-12]: the row is at t = 2.5 exactly, [S] equals the plain run's (3, 4, 5, 2.75, 3.25), and [R] = 1. 2e-12 is the control, outside the 1e-12 a row is taken from; it passed before.test_its_columns_jump_at_the_row[1e-13, 5e-13]: dS/dk = 1.25 and dS/dS0 = 0.5 from the row on, 0 and 1 before it.On main: the four new short-of-row cases fail, and the 2e-12 control passes. Here: all 35 tests in the file pass.
Mutations, each caught:
CVodeGetSensas beforeMeasured
fd_sens: the nightly sweep (106 jobs on 53.netmodels, sensitivities against central differences), run on main's core and on this one. Both give 106 PASS, with identical verdicts.test_build_provenance.pyskips a case whenBNGSIM_ALLOW_STALE_COREis set, as it was for this run.