Repository navigation
net: refuse a .net file from which no block is read (#1511) - #1638
Conversation
A 0-byte file, one of blank or comment lines only, and one whose lines end in a bare CR behind BNG2.pl's header comment loaded as a model of 0 species and 0 reactions, with no error. run_network refuses each. The loader now refuses a file with none of the parameters, species, functions, reactions and groups blocks. A bare CR stays a blank inside its line, as BNG2.pl and run_network read it, and a refusal of a file that holds one says so. _unit_conversions_in now ends lines where the loader does, reading with newline="" rather than universal newlines.
…ctions and groups blocks too (lanl#1511) CLOSED_FORM (1e-6, measured 7.7e-09) and MESSAGE_ROOM (1000, measured 583, against 33,000 to 41,000 uncut) now say why they are what they are, as does the loader's 300-character cut. An empty functions block and an empty groups block are controls that load: a check counting only parameters, species and reactions passed every other case.
…ns lines (lanl#1511) The refusal of a file holding a bare CR cuts the reading's words at 300 bytes. A cut inside a multi-byte UTF-8 character left a byte Python cannot decode, and the refusal arrived as a UnicodeDecodeError that said nothing of it or of the CR. The cut now ends on a whole character, and " ..." marks words left unshown: words of exactly 300 characters were marked cut too. A CR inside a line, as in \r\r\n line endings, changes nothing, yet any refusal of such a file gets the hint. The hint now says where a CR joins two lines rather than that the lines on either side of this one are one. Three tests pin these, each failing on its mutant.
|
Montana, thanks. The fix reproduces here. On the PR head (44219fa, arm64 with MIR), the two test files' 61 cases pass; on its base (0b2055a), 14 fail and 47 pass, as the body says, and v0.17.1 fails the same 14. The branch merges cleanly with today's main (0354cd2), and onto it with #1639, #1642 and #1643, which also touch What else we checked:
We've pushed one commit on top of yours, 36114db, for items 1 to 3:
Not changed:
|
Fixes #1511.
Important
A
.netfile the loader reads no block from (a 0-byte file, comments only, or classic-Mac CR line endings) loaded as an empty model with no error. It is now refused, as run_network refuses it. A bare CR is still read as BNG2.pl and run_network read it, as a blank inside its line, and a refusal of a file that holds one now says so.The problem
A
.netfile from which the loader reads no block loaded as a model of 0 species and 0 reactions, with no error, and a run returned a trajectory with no columns. On main (0b2055a9), for the issue's network (A <-> B, A(0.4) = 3.839397):# Created by BioNetGenlinebegin model/end modelonly; a skipped block onlyend reactionsThe issue's fourth row (a reaction dropped, A(0.4) = 3.145303) was already closed by #1539: the marker is now read off the comment-stripped line, so the block's
endinside a comment is refused rather than taken. That refusal did not say why theendwas missing.What the referees do with a bare CR
The issue offers two fixes: end lines at a bare CR, or refuse it. Before choosing, I ran run_network 3.0 and BNG2.pl 2.9.3's
readFileon 21 edits of the network, plus 2 large CR files. Both read a bare CR as a blank inside its line, never as a line end:\r\r\n,\n\r. Both read 2 species and 2 reactions, and run_network gives A(0.4) = 3.839397 for each.Important
bngsim's loader already reads a CR this way, because
trimandsplit_wstake it as whitespace. On the same edits it gives the referees' results everywhere except the rows in the table. So the fix keeps that reading and adds the refusal the referees have.The fix
src/net_file_loader.cpp:blocks_seen.empty()after the main loop, and the message is "the .net file has no parameters, species, functions, reactions or groups block".test_net_reader_param_expressions.py:75), and species with an empty reactions block. run_network refuses those too, so that difference stays as it was.parse_net_file_structurenow wraps the reading. On the way out, and only then, it scans the file for a CR that is not followed by\n. If it finds one, it adds: "The file holds a carriage return that is not followed by a newline, which BioNetGen reads as a blank inside its line rather than as a line end, so the lines on either side of it are one line; save the file with LF or CRLF line endings".\r, and the reading's own message is cut at 300 characters plus " ...".fceri_gamma.net(2.4 MB) is refused in 649 characters. Without the cut, the 2,000-parameter test file was quoted whole.python/bngsim/_model.py,_unit_conversions_in(the sibling the issue names):read_text, whose universal newlines end a line at a bare CR, so it split lines the loader keeps whole.newline="", which makes its docstring ("Lines end where the loader's end, at a newline and nowhere else") true.#_R1andunit_conversion=1/Ve, main listed onlyVcinfrozen_params, soset_param("Ve", 20)again took the value and moved nothing (cBNGL (from_bngl / BNG2.pl .net): set_param, parameter_scan and sensitivities on a compartment-volume parameter are silently accepted but change nothing. The loader throws away BNG2.pl's '# unit_conversion=1/V' annotation and keeps the... #711). NowVeis listed and the write is refused, as in the LF file.Docs:
docs/user-guide/loading-models.md'sparse_net_filesection, which lists what the loader refuses "as run_network does", gets a paragraph on the no-block refusal and how a bare CR is read.Model.from_net's Raises section names the new refusal.How this was checked
parse_net_fileagrees withModel.from_neton every edit..netI have, 179 files (the repository's plus local copies), on main and here, onea5fcb6e, whose loader code is the final tree's: the last commit changes only a comment there.parse_net_filedictionary: species, reactions, parameters,frozen_params, and a digest of the whole dictionary.Model.from_neton the largest.nethere,fceri_gamma.net(3,744 species, 58,276 reactions), median of 7, two rounds each: 119.7 / 124.0 ms on main, 111.3 / 126.3 ms here.write_netwrites every block, empty or not. So a model of 0 species and 0 reactions (an SBML time-only model, which ModelBuilder accepts on purpose) loads back on main and here, and a control pins it.fceri_gammawritten and reloaded keeps its 3,744 species and 58,276 reactions.test_net_cr_line_endings_and_empty_files.py, 27 cases:Model.from_netandparse_net_file; the CR hint on the two files main already refused; the 2 large CR files, which main quotes whole; and the 7 no-block files.write_netround trip.test_cbngl_volume_folded_into_rates.py, 2 new edits: the CR in theunit_conversioncomment fails on main; CRLF line endings, a control, pass.\r(1, the line-outside-any-block file);ctest, 8 of 8 pass..net, onea5fcb6e, whose loader code is the final tree's: the last commit changes only a comment there:BNGSIM_SKIP_AUDIT=strict: 13,769 passed, 84 skipped;BNGSIM_CODEGEN_THRESHOLD=1): 13,755 passed, 98 skipped;test_cbngl_volume_folded_into_rates.py.0b2055a9, on the final tree: 19,392 passed, 140 skipped, 1 xfailed, 0 failed, so 19,533 collected, main's 19,504 plus these 29.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 fromea5fcb6ewith the[antimony,hdf5]extras and scipy):ea5fcb6e. The last commit adds the two empty-block controls, which have not run on Linux.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 run; none touches the loader.--all-filespasses on the final tree (13 hooks, clang-format, ruff and mypy among them).ci/changelog.py checkpasses, and localsphinx-build -Wexits 0.Which CI legs this PR runs
This PR changes
src/net_file_loader.cpp,python/bngsim/_model.py, two test files, a changelog fragment and a docs page:python-tests.yml(Ubuntu, macOS)native-tests.ymlsrc/**)ctest, 8 of 8 locallywindows-tail.yml(MSVC)_model.py, but its run list does not hold the new testsmir.yml.net(step 8)windows-klu.yml,windows-nfsim.yml.netreader, built and run under MSVC by the leg aboveChecked against past review findings
_unit_conversions_inis fixed and pinned.parse_net_fileis pinned through its own test.prepare_codegen(net_path)loads through the same core..net: the BNGL readers (_bngl_loader.py,convert/_protocol.py) split their text withsplitlines(), which ends a line at a bare CR too. BNG2.pl builds the network there, so only the Python reading of a protocol could differ. That is outside this issue.src/table_function.cpp's tfun reader already refuses a CR-only data file loudly ("fewer than 2 data rows"), andnfsim_simulator.cpp:622reads NFsim XML, not a.net.Model.from_netrefuses withModelErrorandparse_net_filewithValueError, as before.std::runtime_erroronly when the file holds a bare CR. Every caller (both bindings andbngsim_api.cpp) catchesstd::exception.from_net's docstring name the refusal, and the guide notes that an empty block still loads.src/net_file_loader.cpp, in the Hill rewrite. Trial merges with net: read a Hill law with 0 < h < 1 as 0 at zero substrate, as run_network does (#1497) #1629, cvode: take an output at the start of a plain run as reached, as the event-capable loop does (#1501) #1633, result: write the species a Result reports to .cdat, in the core's layout (#1505) #1635 and simulator: leave assignment-rule species at the value a run reports (#1510) #1637 are each clean, so this can merge in any order.newline="", and the one edit that useswrite_textkeeps its meaning, as a bare CR is left alone and an LF becomes CRLF.CLOSED_FORM = 1e-6(measured 7.7e-09) and the refusal'sMESSAGE_ROOM = 1000(measured 583, against 33,000 to 41,000 uncut) are named, with the measurement beside each, and the loader's 300-character cut says why it is 300.The diff (abridged)
The old body is now
read_net_file_blocks, unchanged apart from the check below. The scan runs only after a refusal, and a file with no bare CR rethrows the original exception as it was.blocks_seenis the set #803's once-per-block check already fills, so a block that is there and empty counts.The lines now end where the loader's do, as the docstring says. The
from_netdocstring's Raises section names the new refusal.Tests
python/tests/test_net_cr_line_endings_and_empty_files.py(new, 27 cases):Model.from_netandparse_net_file;\r;write_netand back.python/tests/test_cbngl_volume_folded_into_rates.py: 2 more edits inEDITED: a CR in theunit_conversioncomment, and CRLF line endings.