Repository navigation
result: write the species a Result reports to .cdat, in the core's layout (#1505) - #1635
Conversation
…yout (lanl#1505) Simulator._stamp corrects species columns in Python after the solve (a linear assignment-rule species, a resized compartment's concentrations), and the core's copy is never updated. Result.to_cdat handed off to Result::to_cdat, which wrote that copy, so the .cdat disagreed with Result.species, with to_csv(kind="species") and with the .cdat the same Result wrote after save/load: S := 2*A read 20 at every sample. Result::to_cdat's formatter becomes bngsim::write_cdat, over pointers, and an internal binding _write_cdat hands it numpy's buffers. Result.to_cdat always writes Result.species through it: a Result with no corrected species writes the bytes it wrote, and a loaded Result now writes the run's bytes rather than a second layout. A squeezed Result, one trajectory per row, is refused by name (ValueError) where string formatting failed (TypeError).
…usal for a path Every byte check in the file compared two files from the one writer, so a change to bngsim::write_cdat's widths, precision or header passed them all (a mutant writing setw(19) did). The header and t = 0 row main wrote (0b2055a), initial conditions exactly and a three-digit exponent among them, now pin the format as a change detector. A loaded Result's to_cdat raised FileNotFoundError for a path it could not open, from its own writer; it now raises the run's RuntimeError. That is a second exception type this change moves, so the changelog says so and a test pins both kinds of Result. Each tolerance says why, and to_cdat's docstring has a Raises section.
|
Montana, thanks. The fix reproduces here. On the PR head (6eb2a7f, arm64 with MIR and h5py), the new file's 10 cases pass; on its base (0b2055a), 8 fail and the two controls pass, as the body says. The 18 nearest files pass: 496 passed and 7 skipped, the same under forced MIR with What else we checked:
We've pushed one commit on top of yours, f273bea, for items 1 to 3:
Not changed:
|
Fixes #1505.
The problem
Result.to_cdat()wrote species from before bngsim's post-solve corrections, so the.cdatdisagreed withResult.species.Simulator._stampcorrects species columns in Python after the solve:S := 2*A) gets the rule's live value;The C++ core's copy is never updated, and
to_cdathanded off toResult::to_cdat, which wrote that copy. On main (0b2055a9), against each model's closed form:Result.species,to_csv(kind="species"),Result.load(save).to_cdatto_cdaton mainS := 2*A, ODESo one run wrote two different
.cdatfiles depending on whether it went through HDF5.to_csv(kind="species"), which its docstring says matches.cdat, did not.The fix
One
.cdatwriter, handed the species the Result reports. The formatter inResult::to_cdatmoves into a free function,bngsim::write_cdat, that reads pointers to time, species and names.Result::to_cdatcalls it with its own stored data, so its output is unchanged. A new internal binding,_bngsim_core._write_cdat, passes numpy's buffers straight through, without copies.Result.to_cdatnow always calls it withResult.speciesand its names: the same arraysto_csv,saveandto_xarrayread. This gives:run_batchrow) took a second, Python writer with another layout (# time …, 20-wide fields), so its values were right but not its bytes. It now writes the run's bytes.A squeezed
run_batchorrun_replicatesResult is refused by name. It holds one trajectory per row, and a.cdatfile holds one. It used to fail inside string formatting with aTypeErrorthat named neither the method nor the reason. It now raises aValueErrorthat says both. That is the one exception type this PR changes.How this was checked
Reproduced on main with the issue's script: every row of the table above.
Fixed. On the same script,
to_cdatnow equals the closed form,Result.species,to_csv(kind="species")and the save/load.cdaton all three cases.Corpus
.cdatbytes, every.netin the repository plus 300 BioModels (seeded sample), 485 models, written from an ODE and an SSA run and hashed on main and here:totp53,Myc_total,Total_lymphocyte_count, …) and resized compartments. In each of these, 46 of 47, the new file isResult.speciesto 1e-11 and main's was the core's uncorrected copy..cdatdiffered under load, a different model each time, and not alone. That suggests something wall-clock dependent in SSA, like rr_ssa: a wall-time replicate cap lets runner speed decide MODEL2003200001's verdict #1603's replicate cap, and is not changed here.Cost:
to_cdatof a 100,001 × 60 result (116 MB) takes 1.35–1.43 s on both main and here, best of 5, alternating. A first version copied the arrays on the way in and cost 5%; it now passes numpy's buffers through.New tests fail on main for the right reason.
test_cdat_writes_reported_species.pyhas 10 cases:Result.species; the three save/load byte checks, whose layouts differ; every route with a core (run_batch,parameter_scan,run_replicatesrows andcopy.copy); and the squeezed refusal.Mutation tests: 5 mutants, all killed:
Result::to_cdatignoring the fix(codegen): a zero-arg observable call emits uncompilable C, and differentiates to zero #71 projection.The projection mutant survived the first set of tests, since Python no longer calls
Result::to_cdat. The file now pins it through the core's own writer.Existing tests nearest the change: the 16 files that name
to_cdat,.cdat,_write_bng_fileorResult.load, with the new file and the stub-coverage test, 18 files. 520 passed, the same under forced cc, forced MIR andBNGSIM_SKIP_AUDIT=strict.The stub:
python/bngsim/_bngsim_core.pyiwas regenerated withpybind11_stubgen2.5.5 andscripts/rebuild_editable.py's own normalization. It differs from main by the one new function.test_core_stub_covers_bindings.pypasses.Full Python suite, macOS arm64, no corpus (as CI runs it), on
0b2055a9: 19,372 passed, 141 skipped, 1 xfailed, 0 failed, so 19,514 collected.PYTEST_COLLECTED_FLOOR(19,451), inside the 500 slack.C++ unit tests: ctest 8 of 8 passed.
Docker, Linux x86-64 (
python:3.12,--platform linux/amd64, gcc 14, built from this tree with the[antimony]extra and scipy):test_run_tests_shandtest_vendor_script_git_cwd, which need a git work tree the tarball does not have.h5py, becauseResult.saveneeds it. The case now callspytest.importorskip("h5py"), as the suite's other save tests do. CI installs--extra dev, which carrieshdf5, so it runs and does not skip there.[antimony,hdf5]: the new file passes, 10 of 10. A probe of the run and its reloaded copy shows the same names, dtypes and values, and byte-identical.cdatfiles.pre-commit
--all-filespasses on the final tree, clang-format included.ci/changelog.py checkandrequiredpass, and localsphinx-build -Wexits 0.Checked against past review findings
Sibling sites. Every route the issue says reaches the stale writer is pinned:
Simulator.run;run_batch(squeeze=False)rows andrun_replicatesrows;parameter_scanrows;copy.copy(r), which shares the core;to_gdathands off to the core the same way. It is right today, because no pass rewrites_observablesor_expressions, and it is left as is. A Python-side fix for SSA observables and expressions read count/V_static in a resized compartment: B := 2*X reports 64 for 32 after c: 2 -> 4 #1464 would make it a sibling, as the issue says.One writer, not two. The
.cdatlayout now has a single definition, which a run, a loaded Result and the C++ API all reach. A Python copy of the format would have had to matchstd::scientificedge cases (NaN, infinities, three-digit exponents) by hand.Bytes where nothing was corrected: measured across the corpus (step 3), and pinned for a plain and a fix(codegen): a zero-arg observable call emits uncompilable C, and differentiates to zero #71-projected model (step 5).
Cost at scale (cvodes: restore nominal parameters after each difference-quotient probe, so nothing outside the RHS reads p − Δ (#690) #832): measured, and a 5% copy cost removed before opening (step 4).
Exception types (net: read block markers as BNG2.pl reads them, and refuse what it refuses (#1498) #1539): one changed, deliberately, and stated: a squeezed Result's
to_cdatraisesValueErrorwhere it raised an accidentalTypeErrorfrom string formatting. No test pinned the old one.Docstrings:
to_csvsays its"species"output matches.cdat, and it does now.docs/user-guide/results.mddescribes.cdatas species export, which is unchanged.Count bounds, both directions: step 9. The one gate the new file adds is
h5py, present on every CI leg (step 11).Claims are measured: each number here comes from a run above.
The diff (abridged)
The formatting lines move out unchanged, so
Result::to_cdatwrites what it wrote.speciesis exactly the columns to write:Result.speciesis already the reported projection._write_bng_fileis still used byto_gdatfor a Result without a core, which this PR leaves as it was.Tests
python/tests/test_cdat_writes_reported_species.py(new, 10 cases):Result.speciesandto_csv(kind="species").