Skip to content

Make the IRC verdict meaningful, and never publish a rate silently from an IRC-invalid TS - #937

Open
calvinp0 wants to merge 3 commits into
mainfrom
fix_irc_gated_kinetics
Open

Make the IRC verdict meaningful, and never publish a rate silently from an IRC-invalid TS#937
calvinp0 wants to merge 3 commits into
mainfrom
fix_irc_gated_kinetics

Conversation

@calvinp0

@calvinp0 calvinp0 commented Aug 2, 2026

Copy link
Copy Markdown
Member

Bottom of stack #939. The follow-up #938 (fix_enforce_irc_check) targets this branch and must merge after it.

What went wrong

Benchmark reaction r3_16 (nitroethane ⇌ ethyl nitrite, intra_NO2_ONO_conversion) produced a fitted Arrhenius expression of Ea = 65.8 kJ/mol. The correct barrier is ~253.6 kJ/mol — the published rate coefficient was wrong by ~9 orders of magnitude in k at 1000 K.

ARC had already determined the TS was invalid: ts_checks['IRC'] = False, because both optimized IRC endpoints re-perceive as the product (the "TS" is a torsional saddle of ethyl nitrite, connecting product↔product). That verdict was written to restart.yml — and then the Arrhenius fit was written to RMG_kinetics.yml, rate_plots.pdf and the kinetics library with no indication that validation had failed.

Arkane is not at fault: its E0 values are self-consistent and reproduce the fitted Ea exactly (R1 −81.43, P1 −73.74, TS −18.07 kJ/mol). It correctly computed the rate for the structure it was given; the TS search supplied the wrong structure.

Two changes

1. ts_checks['IRC'] = False now means only "checked and failed".

check_irc_species_and_rxn pessimistically set False up front and only wrote True on success. An early return — rxn.get_bonds() raising in the fallback — left False in place having compared nothing, so False conflated "checked and failed" with "could not check". It now starts at None and writes False only where a comparison actually happened. (A second no-comparison path also existed purely because of the up-front False: if check_xyz_dict() or get_reactants_and_products() raised, the exception propagated out with a "failed" verdict already recorded.)

2. Every kinetics artifact carries the failed-validation marker.

When ts_checks['IRC'] is False, the marker is stamped onto the Arrhenius comment, the RMG_kinetics.yml entry, the kinetics-library longDesc, and the rate_plots.pdf title, and an ERROR is logged naming the reaction.

The rate is deliberately NOT suppressed — it is diagnostically valuable (it is how this bug was found). It is computed, recorded, and clearly labelled.

ts_checks['IRC'] is None (IRC not run, e.g. job_types: irc: false) is treated as unknown: not marked, not suppressed. Many users skip IRC because it is expensive; they are unaffected.

Evidence

  • 139 tests pass (ts_test, common_test, processor_test, plotter_test, arkane_test).
  • The new test_check_irc_identical_endpoints uses assertIs(..., False) rather than assertFalse(...)assertFalse passes on None too, so the pre-existing IRC test could not have caught this regression class.
  • Ea = 253.639 kJ/mol from a re-run that found the correct saddle, versus the RMG NOx2018 library value of 253.8 — agreement to 0.06%.

Notes for review

  • except Exception in the fallback was kept (not narrowed) and made loud rather than silent. get_bonds() reaches atom_mapmap_reaction, spc.copy() and .index() lookups, so the raisable surface is genuinely unbounded; narrowing it wrongly would turn a benign "cannot atom-map" into a scheduler crash mid-run.
  • The TCKDB upload gate is deliberately out of scope: arc/tckdb/ does not exist on main.

calvinp0 added 2 commits July 30, 2026 01:55
check_irc_species_and_rxn() pessimistically set ts_checks['IRC'] = False before
performing any comparison, and one path (the bond-list fallback failing to obtain
the reaction's connectivity) returned while leaving that False in place. A False
IRC verdict therefore conflated "the IRC endpoints were compared and do not match
the reactants/products" with "the IRC endpoints could not be compared at all",
which makes False unusable as a gate.

The check now starts from None (unknown, the ts_checks default) and only writes
False where a comparison was actually carried out and did not match: after a
failed isomorphism comparison, and after a failed bond-list comparison. The
no-comparison path leaves None and logs the reason, including the exception.
…hem silently

A TS whose IRC endpoints re-perceive as something other than the reaction's
reactants and products does not describe that reaction, yet ARC still fitted and
published an Arrhenius expression for it with nothing in the output indicating
that validation had failed (benchmark reaction nitroethane <=> ethyl nitrite,
family intra_NO2_ONO_conversion: ts_checks IRC False, k off by ~9 orders of
magnitude at 1000 K).

The rate is deliberately still computed and reported - it is diagnostically
valuable and it is how this class of bug is found - but every artifact carrying
it is now labeled, via the new common.get_ts_validation_comment() helper:

- Arkane parse_reaction_kinetics(): a 'ts_validation' key on reaction.kinetics
  and the marker appended to the Arrhenius comment.
- processor.compare_rates(): a 'ts_validation' field in output/RMG_kinetics.yml.
- plotter.save_kinetics_lib(): the marker as the Arrhenius comment and at the top
  of the entry's longDesc in the RMG kinetics library.
- plotter.draw_kinetics_plots(): the marker in the rate_plots.pdf plot title.

Each of these also logs an error naming the reaction. Only a ts_checks['IRC']
value of False (checked and failed) triggers the marker; None (not checked, e.g.
job_types irc: false) and True are left untouched.
@calvinp0 calvinp0 changed the title fix irc gated kinetics Make the IRC verdict meaningful, and never publish a rate silently from an IRC-invalid TS Aug 2, 2026
Comment thread arc/common.py Fixed
Comment thread arc/statmech/arkane_test.py Fixed
Two changes, both confined to lines this PR already touched:

1. Drop the TYPE_CHECKING import of ARCSpecies from arc.common. The annotation
   on get_ts_validation_comment is already a quoted string, so the import is not
   needed at runtime or for type checking to resolve lazily. Its only effect was
   to add a common -> species import edge that CodeQL reads as a module-level
   cycle (arc.common -> arc.species.species -> arc.common), cascading into 41
   py/unsafe-cyclic-import alerts across every module in that cycle.

2. Drop ARC_PATH from the arc.common import in arc/statmech/arkane_test.py. It
   was already unused on main; adding TS_IRC_FAILED_MARKER to that line made
   CodeQL count the pre-existing py/unused-import as new in changed code.
@calvinp0
calvinp0 force-pushed the fix_irc_gated_kinetics branch from 414d0d3 to 723df52 Compare August 2, 2026 08:51
@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 63.40%. Comparing base (9787770) to head (723df52).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #937      +/-   ##
==========================================
- Coverage   63.53%   63.40%   -0.13%     
==========================================
  Files         114      114              
  Lines       38325    38366      +41     
  Branches    10030    10038       +8     
==========================================
- Hits        24348    24325      -23     
- Misses      11068    11113      +45     
- Partials     2909     2928      +19     
Flag Coverage Δ
functionaltests 63.40% <ø> (-0.13%) ⬇️
unittests 63.40% <ø> (-0.13%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@calvinp0
calvinp0 marked this pull request as ready for review August 2, 2026 11:36
Copilot AI review requested due to automatic review settings August 2, 2026 11:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants