Repository navigation
fix(tr): refuse active species with an unevolvable namelist profile (#189) - #254
Merged
Merged
Conversation
…189) tr_tst2 + NSMAX=3 went NaN (WPT/BETA*/TAUE*) after one step. The issue blamed the equilibrium import copying RNU/RTU(...,3) from an eqdata built for two species; the tst2 fixture runs MDLUF=0, where tr_prof builds RN/RT for ns = 1..NSMAX from the namelist pairs PN/PNS and PT/PTS and RNU/RTU are unused. Species 3 simply keeps the tr_init defaults PN(3)=PNS(3)=0, so its density is identically zero and the transport step divides by it (every species' RT goes NaN). Reproduced on the fixture: PN[3]/PNS[3] set -> finite (even 1e-8), PN[3]=0 with PNS[3]>0 -> finite, PN[3]<0 -> NaN, PT[3]=0 or PTS[3]=0 -> NaN. NSMAX=2 with PN[2]=PNS[2]=0 failed inside tr_loop (ierr=3) instead. tr/tr_api.f90: private species_profile_violations (MDLUF=0, ns = 1..MIN(NSMAX, array extents)): negative PN/PNS -> OUT_OF_RANGE, both zero -> OUT_OF_RANGE_AFTER_DEP, PT<=0 / PTS<=0 -> OUT_OF_RANGE, named in the registry spelling (PN[3]). tr_api_run calls it whenever it is about to run tr_prep and returns TR_ERR_INVALID with one "XX tr_run: PN[3]: ..." line per finding on unit 6 (the fp_run contract from #242; the handle stays usable). tr_api_validate pushes the same findings as diagnostics. C-ABI layer only; trprof.f90 untouched. The tr_init defaults (NSMAX=2) and every fixture / sample input (iter01, m0904: NSMAX=4 with four positive values each) pass unchanged. Tests: test_NSMAX_in_range loses its strict xfail (the refusal is the tolerated controlled failure; #241's strict subTest keeps the NaN visible should it return); a positive test runs NSMAX=3/4 finite with the added species' profiles; python/trlib/tests/test_species_profile_ checks.py (16 cases, one per case): refusal matrix, accepted cases (incl. PN=0 with PNS>0 and handle reuse after a refusal) and validate() parity. Docs: python/trlib/README.md, docs/sphinx/modules/tr/{en,ja}/ parameter-setting.md (what validate() checks). Local (macOS, CI flags): trlib + tr_mcp + totlib 265 passed, 1 pre-existing py3.10 ExceptionGroup failure, 18 pre-existing skips; `make -C tr tr_api_check_so` OK (the non-PIC tr_api_check_all link needs the gsaf graphics library, absent on macOS; CI runs it). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TA2KahTs3qLMZL6yjSsWwT
Owner
Author
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 72fb0d5. Configure here.
This was referenced Oct 13, 2026
HengyuLi-Ozaki-lab
added a commit
to HengyuLi-Ozaki-lab/task
that referenced
this pull request
Oct 14, 2026
develop gained k-yoshimi#242, k-yoshimi#243, k-yoshimi#248, k-yoshimi#249, k-yoshimi#251, k-yoshimi#254 and k-yoshimi#255 since this branch left it. k-yoshimi#248 (fp_get_state bounds its copies by the allocated extents) touches the same declaration line of fp_api_get_state as this branch. One conflict, fp/fp_api.f90: the local variables of fp_api_get_state. Both sides added names to the same INTEGER statement; the merge keeps all of them (ntg1_last from this branch, nr_alloc and nsa_alloc from k-yoshimi#248). The two bodies do not overlap: k-yoshimi#248's copy of the six profiles is bounded by the allocated extents, and the global scalars of this branch were already bounded by SIZE(PIT, 1) and SIZE(PIT, 2). In the state k-yoshimi#248 is about (a count changed by fp_set_param after a run) the scalar block is not executed at all: fp_set_param clears g_prepared, and the scalars read zero, as this branch's documentation says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HengyuLi-Ozaki-lab
added a commit
to HengyuLi-Ozaki-lab/task
that referenced
this pull request
Oct 18, 2026
develop gained k-yoshimi#242, k-yoshimi#243, k-yoshimi#248, k-yoshimi#249, k-yoshimi#251, k-yoshimi#254, k-yoshimi#255, k-yoshimi#256, k-yoshimi#257, k-yoshimi#258, k-yoshimi#259, k-yoshimi#261, k-yoshimi#263 and k-yoshimi#264 since this branch left it. One conflict, in python/eqlib/_ffi.py, python/fplib/_ffi.py and python/trlib/_ffi.py alike: the end of load_library. k-yoshimi#261 (k-yoshimi#246) checks the state layout of the library just loaded and raises <Mod>libAbiError on a mismatch; this branch closes the directory it registered with os.add_dll_directory when the load fails. The check is now inside the block that cleans up, so a library refused for its layout does not leave its directory in the process-wide DLL search set either (or an entry in _DLL_DIR_HANDLES). Order as on develop: the layout check, then the prototypes. With the check on the load path, the cases of test_platform_libname.py that fake the loaded library (ctypes.CDLL patched) were refused by it: 24 of 39 failed. Each of the three files now has an autouse fixture that patches check_state_layout out for them (the check has its own cases in test_ffi.py, TestLibraryLayout), and one new case per module puts a check back that raises AbiMismatchError (with the prototypes passing, so that only the check can refuse the load) and asserts <Mod>libAbiError, the directory handle closed and nothing registered; with the check moved after the cleanup block, that case fails (measured on eq). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HengyuLi-Ozaki-lab
added a commit
to HengyuLi-Ozaki-lab/task
that referenced
this pull request
Oct 21, 2026
develop now carries the pieces split out of this branch (k-yoshimi#256 eq_save, k-yoshimi#257 the MCP fd isolation, k-yoshimi#265 the mcp pin) and k-yoshimi#232, k-yoshimi#233, k-yoshimi#240, k-yoshimi#242, k-yoshimi#243, k-yoshimi#248, k-yoshimi#249, k-yoshimi#251, k-yoshimi#254, k-yoshimi#255, k-yoshimi#258, k-yoshimi#259, k-yoshimi#261, k-yoshimi#263, k-yoshimi#264. 43 files conflicted; each was resolved as follows. - Taken from develop (36 files): every file where this branch only changed the mcp pin to "mcp>=0.9,<2" (develop has ">=1.2,<2", k-yoshimi#265): the seven pyproject.toml, six READMEs, the fp, ti, tot, wr and wrx servers, the Sphinx MCP pages, the trlib presentation and its builder, the 2026-04-18 MCP plan; and the eq_save pieces now in develop in their reviewed form: eq/eq_api.f90, eq/eq_api.h, python/eqlib/tests/test_save.py, eq_mcp/tests/test_save_tool.py; and tr_mcp/server.py, whose changes here were this branch's own fd isolation (develop's _fd_isolation replaces it) and the os._exit(0) at the end of main(). - The os._exit(0) that ended main() of tr_mcp and eq_mcp on this branch (to skip the Python teardown, cited in k-yoshimi#227 item 5) is gone with them: develop's main() returns 0, as approved in k-yoshimi#257. The abort it avoided was a double DEALLOCATE, which this branch fixed in tr/trcomm.f90 (DEALLOCATE_TRCOMM returns when nothing is allocated); that fix stays, merged without a conflict. - eq/eqfile.f90: develop's (EQSAVE without an argument, EQSAVE_IERR doing the work, and its kept EQDSKR error), plus this branch's MODELG=25 in EQ_READ's EQDSK branch (k-yoshimi#228-2). eq/eqmenu.f90 merged without a conflict but kept this branch's "CALL EQSAVE(IERR)"; it is develop's "CALL EQSAVE" again, as EQSAVE takes no argument. - python/eqlib/_ffi.py, eqlib.py, tests/test_eqlib.py: develop's, plus this branch's get_psi_rz (the eq_common_get_psi_rz_ prototype, the Eq.get_psi_rz method, its closed-handle assertion). - eq_mcp/server.py: develop's (the save tool of k-yoshimi#256, the shared fd isolation), plus this branch's get_psi_rz handler and tool and its --print-tools entry (13 tools). eq_mcp/tests/test_server.py: this branch's (13 tools with save and get_psi_rz), which contains develop's change. - python/mcp-servers/eq_mcp/tests/test_import_isolation.py (this branch only, no conflict) is removed: it pinned this branch's own isolation helper (_install_fd_isolation, _FD_ISOLATION_INSTALLED in eq_mcp and tr_mcp), which develop's python/_fd_isolation.py replaces; develop's tests/test_fd_isolation.py of each of the seven servers checks the same contract (an import leaves fd 1 and sys.stdout alone). - .github/workflows/python-tests.yml: develop's mcp pin; this branch's numpy install (the get_psi_rz tests import numpy) and ../task symlink step kept. - CHANGELOG.md did not conflict. The three k-yoshimi#228 entries this branch held back in d8b6e4d are added to [Unreleased] Fixed, after its first entry, where no other open pull request inserts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #189:
tr_runrefuses, beforetr_prep, an active species whose namelist profile the solver cannot evolve, andtr_validatereports the same findings. The strictxfailontest_NSMAX_in_rangeis removed.Root cause (corrects the issue text)
The issue attributed the NaN to the equilibrium import copying
RNU/RTU(...,3..NSMAX)from aneqdata.TST-2built for two species. That is not the path the tst2 fixture takes: it runs withMDLUF = 0, wheretr/trprof.f90buildsRN/RTforns = 1..NSMAXfrom the namelist pairsPN/PNSandPT/PTS(RNU/RTUare zero-filled and unused). Reproduced on the fixture:tr_initdefaultsPN=PNS=0)RN(:,3) = 0after prep, all species' RT and WPT/BETA/TAUE = NaN** after the first stepPN[3]=0.005, PNS[3]=0.0005PN[3]=0, PNS[3]=0.0005(5 steps)PN[3]=1e-8, PNS[3]=1e-9PN[3]<0PT[3]=0orPTS[3]=0(density set)So the transport step divides by the density / temperature of every active species; a species whose density is identically zero or negative, or whose temperature is non-positive at the centre or the edge, poisons the whole state.
NSMAX=2withPN[2]=PNS[2]=0happened to fail insidetr_loop(TrlibRunError); species 3/4 produced silent NaN.Change
tr/tr_api.f90: new privatespecies_profile_violations(everyMDLUFoutside the UFILE modes 1/2/3, i.e.tr_prof's namelist branch;ns = 1..MIN(NSMAX, array extents)): non-finitePN/PNS/PT/PTS->OUT_OF_RANGE;PN/PNSnegative ->OUT_OF_RANGE; both zero ->OUT_OF_RANGE_AFTER_DEP;PT <= 0/PTS <= 0->OUT_OF_RANGE; parameters named in the registry spelling (PN[3]).tr_api_runcalls it whenever it is about to runtr_prepand returnsTR_ERR_INVALIDwith oneXX tr_run: PN[3]: ...line per finding on unit 6 (the fix(fp): fp_run refuses the counts it cannot run with, before any work #242 contract: the handle stays usable, set the parameters and run again); it also refusesNSMAX > NSM(4) there, which onlyvalidate()used to report.tr_api_validatepushes the same findings as diagnostics. No physics code touched (C-ABI layer only).MDLUF = 1/2/3still take fromPN/PNS/PT/PTS, selected byMDNI/MODEP/RHOAinsidetr_prof; no UFILE fixture to verify a mirror against) and tot: tr: parameter updates after a run bypass tr_api's prepared flag (no re-prep, no #189 pre-run check) #253 (tot_set_param("tr:...")callstr_param_setdirectly, so after a first run tot mutations never cleartr_api's prepared flag: no re-prep and no pre-run check; changing that alters tot's coupled-pipeline semantics and needs its own decision).test_NSMAX_in_rangeloses itsxfail(the refusal is the tolerated "controlled failure"); a new positive test runs NSMAX=3/4 finite with the added species' profiles set;python/trlib/tests/test_species_profile_checks.py(21 cases, one per case): the refusal matrix (defaults, zero density, negative PN / PNS, zero / negative PT, zero PTS, NaN density, +Inf temperature, NSMAX=5 with all profiles set, MDLUF=4 (tr_prof's default branch), species-2 zero density now refused instead of failing in the loop), the accepted cases (profiles set,PN=0withPNS>0, the fixture as-is, handle usable after a refusal; finiteness asserted on the scalars and every RN/RT entry), andvalidate()parity (codes, parameter names, -Inf, clean when profiles are set, inactive species ignored).python/trlib/README.md,docs/sphinx/modules/tr/{en,ja}/parameter-setting.md(list of checksvalidate()performs),docs/sphinx/modules/tr/{en,ja}/mcp.md(error table: the NSMAX/PN/PT case is nowinvalid parameter, notcalculation failed).Default state (
NSMAX=2, species 1/2 atPN=0.5, PNS=0.05, PT=1.5, PTS=0.05) and every fixture / sample input (tr_iter01,tr_m0904: NSMAX=4 with four positive values each;tr_tst2: NSMAX=2) pass the check unchanged.Verification
Local (macOS, CI flags): the four touched trlib test files 50 passed; trlib + tr_mcp + totlib 269 passed, 1 pre-existing macOS/py3.10
ExceptionGroupfailure, 18 pre-existing skips;make -C tr tr_api_check_soOK (tr_api_check_all's non-PIC link needs the gsaf graphics library, absent on macOS; CI runs it). Every refusal case fails ondevelop(NaN orTrlibRunErrorinstead ofTrlibParamError).Review
In-house review + Codex (
codex-rescue) + ai-review-cycle Codex on the first version: their findings (MDLUF gate shape, non-finite inputs,NSMAX > NSMat run time, RN/RT finiteness in the positive tests, stale docblock and mcp.md rows) are in this commit; the UFILE-mode subset and the tot bypass became #252 / #253. The delta was re-reviewed by Codex (ship; three LOW wording/coverage items applied).Refs: #189, #143 (batch pre-run validation), #242 (the fp precedent).
🤖 Generated with Claude Code
https://claude.ai/code/session_01TA2KahTs3qLMZL6yjSsWwT
Note
Medium Risk
Changes pre-run validation in the C ABI entry path (not core physics), but alters when
run()fails (TrlibParamErrorvs silent NaN or mid-loop errors) and tightens NSMAX-at-run behavior callers may have relied on.Overview
Fixes #189 by rejecting active species whose namelist density/temperature profiles cannot be evolved, instead of letting the transport step divide by zero and poison the state with NaN.
tr/tr_api.f90addsspecies_profile_violations(non-UFILEMDLUF, species1..NSMAX): non-finite, negative, or all-zeroPN/PNS, and non-positivePT/PTS, reported asPN[ns]/PT[ns]/PTS[ns].tr_runcalls this (and blocksNSMAX > NSM) beforetr_prep, returningTR_ERR_INVALIDwith per-line stdout messages while keeping the handle usable.tr_validateemits the same rules as diagnostics.Tests drop the
test_NSMAX_in_rangexfail, add a positive NSMAX=3/4 case with profiles set, and addtest_species_profile_checks.pyfor run/validate parity. Docs (trlib README, Sphinx EN/JA parameter-setting and MCP troubleshooting) describe the new checks and retargetcalculation failedguidance towardDT/equilibrium rather than PN/PT.Reviewed by Cursor Bugbot for commit 72fb0d5. Bugbot is set up for automated code reviews on this repo. Configure here.