fix: guard against a None species.thermo in parse_arkane_thermo_output - #995
fix: guard against a None species.thermo in parse_arkane_thermo_output#995alongd wants to merge 1 commit into
Conversation
eb877a2 to
b702b7d
Compare
|
Root cause found; PR updated. Two corrections to what this PR originally claimed. 1. The "restore the 2. The
The producer is a caller's subclass. T3's This PR is still worth merging as defence in depth, and I've kept it. ARC should not discard converged QM results because a caller violated an invariant ARC never advertised — and this loop was the odd one out, since Changes since the first push:
Tests unchanged: 45 passed, 2 pre-existing failures ( |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #995 +/- ##
==========================================
- Coverage 64.46% 64.45% -0.01%
==========================================
Files 119 119
Lines 39636 39639 +3
Branches 10276 10276
==========================================
- Hits 25550 25549 -1
- Misses 11102 11106 +4
Partials 2984 2984
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR prevents a crash in ArkaneAdapter.parse_arkane_thermo_output when a species unexpectedly reaches thermo-result assignment with spc.thermo is None, by restoring a ThermoData() container before populating Arkane results, and adds a regression test covering that scenario.
Changes:
- Guard
parse_arkane_thermo_outputagainstspc.thermo is Noneby instantiatingThermoData()before assignment. - Add a regression unit test ensuring parsing succeeds and populates thermo for both a
None-thermo species and a normal species.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
arc/statmech/arkane.py |
Adds a None guard for spc.thermo during Arkane thermo result assignment and logs a warning when recovering. |
arc/statmech/arkane_test.py |
Adds a regression test that reproduces the crash condition and verifies thermo assignment still succeeds. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
b702b7d to
ec910be
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (2)
arc/statmech/arkane_test.py:166
- The fixture writes an empty
output.py, which deliberately bypasses the earlierparse_species_thermo()path. Consequently this test reaches the new YAML-assignment guard, but it would not catch the sameNonecrash for the normal Arkane output that contains aThermoData(...)block; include such a block so the regression test exercises the full method path.
with open(os.path.join(statmech_dir, 'output.py'), 'w') as f:
f.write('')
arc/statmech/arkane.py:515
- This guard is reached only after
parse_species_thermo()has already run for every species at line 483. For a normal Arkaneoutput.pycontaining athermo(..., ThermoData(...))block, that helper callsspecies.thermo.update(...)at line 1266, so aNonecontainer still raises before this new branch and the reported crash is not prevented. Initialize the container before the parse loop or add the same guard insideparse_species_thermo()before callingupdate().
if spc.thermo is None:
# ``ARCSpecies.__init__`` sets ``self.thermo = ThermoData()`` unconditionally,
# so no species ARC builds itself can reach this loop with ``None``. A caller's
# subclass can, though, by re-assigning the attribute after ``super().__init__()``
# -- which is exactly how T3's ``T3Species`` produced the crash this guards
ec910be to
2d6366f
Compare
``parse_arkane_thermo_output`` assigns straight through the attribute::
spc.thermo.H298 = content[lbl]['H298']
with no None check, and crashes with
AttributeError: 'NoneType' object has no attribute 'H298'
when a species reaches that loop with ``.thermo`` set to None. This fires at
the *reporting* stage, after every QM job in the run has already converged --
so the cost is an entire successful computation discarded at the last step.
No ARC code produces that state. ``ARCSpecies.__init__`` sets
``self.thermo = ThermoData()`` unconditionally, ``from_dict`` never touches the
attribute, and it is not serialised into the restart file. The None arrives
from a *caller's subclass* re-assigning the attribute after
``super().__init__()``; T3's ``T3Species`` did exactly that, and is fixed at the
source in T3 PR #187.
Guard here anyway, as defence in depth: ARC should not lose converged results
because a caller violated an invariant it never advertised. Other call sites
(``ArkaneAdapter.set_reaction_dh_rxn``, ``processor.process_arc_project``,
``output.get_species_output_dict``) already tolerate a None thermo, so this
loop was the odd one out.
The guard warns rather than repairing silently, so a caller re-introducing the
state stays visible instead of being absorbed.
Real-run crash (traceback in the PR body): arc/statmech/arkane.py:509, inside
process_arc_project's first (non-e0_only) compute_thermo() call.
2d6366f to
5c2eb25
Compare
Crash
Real T3 campaign run (
~/runs/t3-pes-CH2O2/) died after all QM jobs converged, during thermoreporting:
Root cause
ARCSpecies.__init__always setsself.thermo = ThermoData()as a default. Somewhere betweenspecies construction and this loop, a species can end up with
spc.thermo is Noneagain — I couldnot pin the exact assignment site that clears it in the live run, but the codebase itself already
treats this as an expected, guarded-against state in three independent places:
ArkaneAdapter.set_reaction_dh_rxn(arc/statmech/arkane.py)processor.process_arc_project(arc/processor.py:198)output.get_species_output_dict(arc/output.py:510)parse_arkane_thermo_output's result-assignment loop was the one place that assumedspc.thermocan never be
Noneand crashed instead of guarding — classifying this as case (a): a specieslegitimately reaching this code with no thermo container, not a
valid_labelsfiltering bug (Ifound no evidence
valid_labelsincludes species it should exclude).Fix
Restore the
ThermoData()default (matching the exact construction used inARCSpecies.__init__)immediately before populating it with the real, successfully-computed Arkane results, instead of
crashing and discarding those results:
Sibling kinetics path
Checked
parse_arkane_kinetics_output/parse_reaction_kineticsfor the same unguarded-attributepattern: none found.
parse_reaction_kineticsalways does one wholesalereaction.kinetics = kineticsdict assignment rather than mutating attributes on an existing kinetics sub-object, sothere's no equivalent
None-target crash risk there.Containment at the process_arc_project/compute_thermo boundary
Deliberately did not add a broader try/except there. The crash's root data problem is now fixed
at its source (the offending species gets a real
ThermoDatacontainer populated with its actualresults, not skipped). A blanket guard at the reporting boundary would risk swallowing genuinely
new data-integrity bugs in future runs rather than surfacing them — better handled as its own
decision if a distinct need for it shows up.
Testing
Added
test_parse_arkane_thermo_output_recovers_missing_thermo_containertoarc/statmech/arkane_test.py, covering a species with.thermo is Nonereaching the loop and awell-formed sibling species processed normally in the same call.
git stash, unmodified branch):44 passed, 2 failed. The 2 failures(
test_generate_arkane_input,test_run_statmech_using_molecular_properties) are pre-existing,caused by a missing
arkanemodule in thermg_envconda environment ARC shells out to — unrelatedto this change.
45 passed, 2 failed(same 2 pre-existing failures, +1 new passing test).arc/statmech/arkane.pytoHEAD and re-ran it in isolation — it failed with the exact same traceback signature as the real
crash above, then passed again after reapplying the fix.