Repository navigation
net: refuse a species index that is not its line (#1512) - #1639
Conversation
A .net species line's index was skipped and the species read by its line, so with '2 B() 5' above '1 A() 1' a reaction on species 1 ran on B(): Aobs(1) = 1.839397 for the file's 0.367879, with no error. run_network places a species at its declared index and BNG2.pl's readFile at its line, so such a file has two readings. parse_species now refuses an index that is not its 1-based line, compared as a number. An unindexed line keeps its place.
CLOSED_FORM (1e-7) is measured at 3.0e-09 on every control and the round trip; a species read on the wrong line is 400% off.
lanl#1512) The index is a species' position among species: a comment or blank line between two species is neither. No case had one, so a loader counting every line of the block passed all 18. Two cases pin it, both read alike by run_network and BNG2.pl's readFile: a comment and a blank line between 1 and 2 load (0.367879), and a gap to 3 past a comment is refused (run_network SIGSEGV). A counter of text lines fails both. Drop a replace in the seed-species test that matched nothing.
|
Montana, thanks. The fix reproduces here. On the PR head (98eced0, arm64 with MIR), the new file's 18 cases pass; on its base (0b2055a), 10 fail and the 8 controls pass, as the body says, and v0.17.1 fails the same 10. The branch merges cleanly with today's main (10c117b), which now holds #1638's changes to the same loader, and onto it with #1642, which also touches the loading guide. What else we checked:
We've pushed one commit on top of yours, bb6f7cd, for item 1:
Not changed:
|
Fixes #1512.
Important
A
.netspecies index that is not its line was skipped, and the species read by its line, so2 B() 5above1 A() 1ran the file's reaction on species 1 against B(): Aobs(1) = 1.839397 for the file's 0.367879, with no error. BioNetGen's two readers disagree on such a file, so it is now refused rather than read one way.The problem
On main (
0b2055a9), the issue's file:k = 1, one reaction1 1 0 kdegrading species 1, and the groupAobson species 1.readFile2 B() 5,1 A() 11 A() 1,3 B() 5(a gap)1 A() 1,1 B() 5(a duplicate)0 A() 1,1 B() 5(0-based)2 A() 1,3 B() 5, reaction and group on 2B() 5,1 A() 11 A() 1,3 C() 2,2 B() 5, reaction and group on 2The closed form for species 1 = A() is e^-1 = 0.367879. Nothing was raised, warned or logged.
What the referees do
run_network places each species at the index it declares.
BNG2.pl's
readFileplaces it at its line:SpeciesList.pmstrips the index and numbers by position, andwriteNetworkrenumbers the file.On 18 shapes I ran through both (the issue's 6 and 12 more), they agree exactly where the index is its line:
2 …;1 …followed by an unindexed line;01,02, and001,2);Both read each of those to 0.367879.
Important
So the one rule both referees agree on is index = line, and that is the rule the loader now holds a species line to. Remapping to the declared index would contradict
readFile, and keeping the line would contradict run_network.The fix
src/net_file_loader.cpp,parse_species:01on the first line loads, as both referees read it.seed speciesblock is held to the same rule, since BNG2.pl and the loader read it as the species block.4294967297 A() 1on line 1, run_network's integer wraps the index to 1 and both referees read the file alike. Here it is refused, as an index that is not its line.Model.from_net(codegen on and off),Model.load,parse_net_fileandnet_to_sbmlall read throughparse_species.net_to_sbmlused to return a report that called the misread model fine, because its check compares bngsim with itself; it now raises.Docs:
docs/user-guide/loading-models.md'sparse_net_filesection gets a paragraph on the species line's index.How this was checked
+1and1.0as an index: BNG2.pl refuses them ("Invalid Molecule name"). bngsim refused them before this change and still does, as an unparsable initial concentration.Model.from_net, with and without codegen,Model.load,parse_net_file(ValueError) andnet_to_sbml. All five loaded it on main..netI have, 179 files (the repository's plus local copies), on main and here. 179 of 179 give the same model and the sameparse_net_filedictionary, and all 179 load on both.test_net_species_index_is_its_line.py, 18 cases.seed speciesblock.write_netround trip of an unindexed block.ctest, 8 of 8 pass..net, on the final tree:BNGSIM_SKIP_AUDIT=strict: 13,760 passed, 84 skipped;BNGSIM_CODEGEN_THRESHOLD=1): 13,746 passed, 98 skipped;0b2055a9, on the final tree: 19,381 passed, 140 skipped, 1 xfailed, 0 failed, so 19,522 collected, main's 19,504 plus these 18.PYTEST_COLLECTED_FLOOR(19,451), inside the 500 slack.PYTEST_SKIPPED_CEILING(252). The new tests have no skip.python:3.12,--platform linux/amd64, gcc 14, built from the final tree with the[antimony,hdf5]extras and scipy):test_codegen_compile_reaping,test_codegen_toolchain_verdict,test_run_tests_sh,test_vendor_script_git_cwd), which fail the same way on main in it and on simulator: leave assignment-rule species at the value a run reports (#1510) #1637's and net: refuse a .net file from which no block is read (#1511) #1638's runs; none touches the loader.--all-filespasses (13 hooks, clang-format, ruff and mypy among them).ci/changelog.py checkpasses, and localsphinx-build -Wexits 0.Which CI legs this PR runs
python-tests.yml(Ubuntu, macOS)native-tests.ymlsrc/**)ctest, 8 of 8 locallywindows-tail.yml(MSVC)src/net_file_loader.cppwindows-tail.yml; until that merges, this change reaches MSVC only throughwheels.ymlon a push to mainmir.yml.net(step 8)windows-klu.yml,windows-nfsim.ymlChecked against past review findings
.netgoes throughparse_species, and each is pinned.parity_checks/bng_parity/_bng_common.py:145,benchmarks/suites/forward_sens/run.py:456) assume index = line. That is now guaranteed, so they are left alone.Model.from_netandModel.loadraiseModelError, andparse_net_fileraisesValueError, as for any refused file.net_to_sbmlnow raisesModelErroron such a file where it returned a report.src/net_file_loader.cppanddocs/user-guide/loading-models.md, in other places. Trial merges with each open PR, and on top of net: read a Hill law with 0 < h < 1 as 0 at zero substrate, as run_network does (#1497) #1629 and net: refuse a .net file from which no block is read (#1511) #1638 merged together, are clean.CLOSED_FORM = 1e-7is named, with its measurement beside it: 3.0e-09 on every control and the round trip, where a species read on the wrong line is 400% off.The diff (abridged)
@@ src/net_file_loader.cpp — parse_species if (field >= tokens.size()) { throw std::runtime_error("species line '" + stripped + "' has an index but no species pattern"); } + if (field == 1) { + const std::string position = std::to_string(species.size() + 1); + const auto first = tokens[0].find_first_not_of('0'); + const std::string index = + first == std::string::npos ? std::string("0") : tokens[0].substr(first); + if (index != position) + throw std::runtime_error( + "species line '" + stripped + "' has index " + tokens[0] + " on the " + block + + " block's line " + position + + ": reactions and groups name a species by its index, and BioNetGen's " + "two readers place a species differently when its index is not its " + "line (run_network at its index, BNG2.pl's readFile at its line). ..."); + }field == 1means the line has an index; an unindexed line skips the check.species.size() + 1is the line's 1-based number in the block, since every species line is appended. The leading zeros are stripped, so the comparison is of numbers, and a line of all zeros reads as 0.Tests
python/tests/test_net_species_index_is_its_line.py(new, 18 cases):parse_net_file,Model.load,net_to_sbml);1, three species permuted, and an index past 2^32;seed speciesblock.2,1then an unindexed line, leading zeros (two ways), and a tab after the index, each to e^-1; and awrite_netround trip.