Skip to content

differential: NFsim against RuleMonkey in distribution (#1312) - #1323

Open
Montana wants to merge 20 commits into
lanl:mainfrom
Montana:fix/1312-nf-arm
Open

Montana wants to merge 20 commits into
lanl:mainfrom
Montana:fix/1312-nf-arm

Conversation

@Montana

@Montana Montana commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Fixes #1312.

Stacked on #1322 (#1310), which adds the method, n_rep and seed fields of Job and checks_for that this arm uses. Until #1322 merges, this PR also shows its commits. This PR's own are 01e46ff and 0e273d4. It adds one row to CHECKS (nf_reject_vs_exact), as the other differential PRs each add theirs. I'll resolve it.

What it adds

bngsim's two network-free engines, NFsim (nf_reject) and RuleMonkey (nf_exact), against each other, in distribution.

The models. An NF job is one of bng_nf's models, read from its pinned reference archive (reference_archive.py fetch bng_nf). Each entry holds the BNG-XML BNG2.pl wrote for the model and the run it was recorded for (t_start, t_end, n_steps, gml, nf_bscb, n_rep, seed_base). So the arm runs the NF track's own models and windows, and the job needs no BNG2.pl. Both engines are bngsim's, so the archive is inputs only, whatever the nightly's mode. A multi-segment entry (1 of 235) is left out, since one segment is not its run.

The check. nf_reject_vs_exact runs n_rep replicates on each engine, with the same seeds, block_same_complex_binding and molecule_limit. It judges the observables' ensembles with differ.ensemble_verdict at ensemble_k_for(n_rep), the SSA suites' verdict:

  • a DIFF is escalated the way bng_parity's NF track escalates one, by its own _run_escalation: 30, then 100 replicates each, stopping where agreement stalls. Finite-sample scatter clears, and a real divergence stays. Escalation runs within NF_ESCALATION_BUDGET (300 s), and a rung that would not fit is not walked; the row says so;
  • the note names the observable furthest apart, on its own peak;
  • NFsim failing is no_base (the NF track owns it);
  • RuleMonkey refusing by name is UNSUPPORTED: compartments, or "cannot honor this BNG XML";
  • the engines reporting different observables is DIFF.

Its own nightly job. "Network-free engine agreement" runs under its own baseline, differential_nf. Over the 234 models the two engines' ensembles cost 9,793 CPU-s here, 92% of it in 48 models, which the path-agreement job, already at 52 of its 180 minutes, could not also hold. The path-agreement job is unchanged.

+  differential-nf:
+    name: Network-free engine agreement
+    ...
+      - name: Fetch bng_nf's models
+        run: python3 parity_checks/nightly/reference_archive.py fetch bng_nf --dest "$RUNNER_TEMP/bng_nf"
+      - name: Run differential_run.py (NF arm)
+        run: |
+          uv run --no-sync python parity_checks/differential/differential_run.py \
+            --fd-sens-jobs '' --rr-jobs '' --ssa-jobs '' --no-locale \
+            --nf-archive "$RUNNER_TEMP/bng_nf" --workers 4 --timeout 900 \
+            --out "$RUNNER_TEMP/differential_nf.json" || true

Over the corpus

bng_nf's 234 single-segment models, local run (--workers 4 --timeout 900):

PASS DIFF UNSUPPORTED TIMEOUT
227 5 1 1

The run was made before the refusal fix, when ft_total_rate read EXCEPTION. I reran it on this head: UNSUPPORTED.

The 5 DIFFs:

model at 10 replicates escalation reading
original/.../ode/error.bngl 65% of cells 30 each: 67%, stalled a real divergence: max z 1137, Initiator_I2_SSA
glacial/.../corpus/fceri_ji.bngl 95.8% over budget (205 s for 10) unresolved
original/.../nf/fcr.bngl 98.7% over budget unresolved
original/.../ode/population_scaling.bngl 98.8% over budget unresolved
slow/.../corpus/tcr.bngl 98.5% over budget unresolved

The four unresolved ones sit just under the suites' 99% bar on 10 replicates, so they are likely sampling scatter. Their base ensembles take 200–300 s, too slow to escalate in the budget. With fixed seeds they are reproducible, so the baseline holds them steadily rather than flaking. error.bngl is worth an issue: the two engines disagree on Initiator_I2_SSA well past noise. I haven't yet attributed it to either engine.

TIMEOUT: AMPK_MTORC1_ULK1_v2.bngl, past 900 s.

Elsewhere simple_model_1 read DIFF at 10 replicates (96%) and cleared at 30 (100%): the escalation doing its job.

Tests (python/tests/test_differential_shapes.py, appended)

On tests/data/nfsim/simple_system.xml:

  • an NF job runs the NF check and no other;
  • NFsim and RuleMonkey agree in distribution;
  • negative control, as the issue asks: one rate (kcat) ×3 on RuleMonkey only reads DIFF. Escalation walks to 30 replicates and stalls (55%, 55%), with seeds 1–30, new draws, not the base's again;
  • an escalation past its budget is not walked, and the row says so;
  • engines reporting different observables is DIFF;
  • NFsim failing is no_base;
  • RuleMonkey refusing (compartments; TotalRate) is UNSUPPORTED;
  • the runner reads bng_nf's archive as NF jobs, leaving a multi-segment entry out.

The tests skip where the tree lacks the fixture or the build lacks RuleMonkey.

Mutations, each caught:

  • always pass;
  • NFsim both ways;
  • no escalation;
  • the budget ignored;
  • seeds reused;
  • the refusal not read;
  • the observable names unchecked;
  • NFsim's failure not read as no_base.

Suites: parity_checks/tests and the shapes give 1,108 passed, 33 skipped, 1 xfailed. One local failure, test_jacobian_characterization_gate, needs main's fill_dense_fd_jacobian_half_step (#1002), which my local build predates; CI builds fresh. ruff 0.14.0 is clean.

Still to do

The differential_nf baseline (parity_checks/nightly/baselines/differential_nf.json) comes from this PR's own nightly run, from its nightly-differential_nf artifact. That run also measures the job's cost on a CI runner, which I have only estimated.

…#1310)

An SSA job runs the same seeds once with the default Simulator (which
compiles the propensities of an eligible model) and once with
codegen=False. On an all-elementary model the two must agree draw for
draw; otherwise the ensembles must agree at ensemble_k_for(n_rep). A
second check holds ssa_diagnostics["propensity_backend"] to what was
asked for. differential_run.py reads rr_parity/ssa_jobs.json under
--ssa-jobs, capped at --max-reps replicates.
…ng (lanl#1310)

Most of rr_ssa's 268 models run interpreted whatever is asked, so both
SSA checks are decided by one replicate of each. On the overloaded first
corpus run 31 of the first 98 models timed out at 600 s with full
ensembles both ways; the nightly's job has a 180-minute cap.
An NF job is one of bng_nf's models from its reference archive: the
BNG-XML BNG2.pl wrote and the run it was recorded for, so the nightly's
differential job needs no BNG2.pl. nf_reject_vs_exact runs n_rep
replicates on each engine at the same seeds and options and judges the
observables' ensembles with differ.ensemble_verdict, escalating a DIFF
as bng_parity's NF track does (_run_escalation), within a 300 s budget.
…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: lanl#1315, lanl#1318 and lanl#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.
…fusal by name (lanl#1312)

Over bng_nf's 234 models the two engines' ensembles cost 9,793 CPU-s on
an M-series machine (48 models, 92% of it), which the path-agreement
job, already 52 of its 180 minutes, could not also hold. The arm runs in
its own job, "Network-free engine agreement", under its own baseline
(differential_nf); the path-agreement job is as it was.

"RuleMonkey cannot honor this BNG XML" (ft_total_rate's TotalRate over a
repeated component) is a refusal by name: UNSUPPORTED, not EXCEPTION.
…ty library (lanl#1310)

One replicate each way was still too much: 18 of the first 101 rr_ssa
models took a single replicate past the 900 s cap. The Simulator decides
before any run whether a model is eligible for compiled propensities,
and builds no library where it is not. Without one there is nothing
compiled to tell apart, and lanl#1213's class (codegen=False ignored) needs a
default that compiles: both checks are SKIP, with no replicate run.
With lanl#1321, PRs stacked on this one still read 11-12 regressions: main's
own checks on MODEL1006230049 and 0053 did not fit the 810 s deadline of
a 900 s cap. Across five runs of the same checks those models took
689-894 s, so the cap flipped their rows with runner speed. 1500 s puts
the deadline at 1350, and costs only the few models that use it.
…degen

check_all keeps lanl#1321's deadline and its verdicts-as-reached; an SSA job
is routed to check_ssa inside it, so its verdicts are kept the same way.
An NF job is routed to check_nf inside lanl#1321's check_all, as an SSA job
to check_ssa, so its verdicts are kept the same way.
…rash

With the 1500 s cap this PR's nightly read 2 new crashes, both
BIOMD0000000385: given the time to reach its codegen compile, 4.2 MB of C
ran past bngsim's 600 s compile limit, which codegen=True reports as a
RuntimeError. Those rows were TIMEOUT at 900 s, and a timeout they stay:
one anywhere in the cause chain, or in codegen=True's refusal, which
carries the compile's timeout in its text and not as its cause. The other
five rows of the model went TIMEOUT -> PASS.
… fix/1310-ssa-codegen

# Conflicts:
#	python/tests/test_differential_shapes.py
…ax-reps 0 (lanl#1310)

Job's n_rep defaults to 0. With no replicates both ensembles were empty:
ssa_cc_vs_interp read PASS, 'all 0 seeds identical', and
ssa_backend_as_asked read DIFF, 'codegen=False ran []'. Nothing was
compared, so both are SKIP, said why, and --max-reps below 1 is an error.
…es is SKIP (lanl#1312)

The new 'Network-free engine agreement' job compares against
baselines/differential_nf.json, which was not committed. ALLOW_MISSING_BASELINE
is set on pull requests only, so every scheduled night would have alerted
on a missing baseline. This is verdicts.json from run 40539506449 on this
branch's head 963b8c2 (225 PASS, 5 DIFF, 3 TIMEOUT, 1 UNSUPPORTED);
verdicts.py diff of that run's report against it: no regressions.

With n_rep 0 there was no ensemble to stack, which read as NFsim failing
(no_base, 'need at least one array to stack'): SKIP, said why, as the SSA
arm's is now.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

differential: NFsim (nf_reject) against RuleMonkey (nf_exact) in distribution

2 participants