Skip to content

Don't crash h_abstraction() on empty product_dicts - #993

Open
alongd wants to merge 1 commit into
mainfrom
h-abstraction-empty-product-dicts
Open

Don't crash h_abstraction() on empty product_dicts#993
alongd wants to merge 1 commit into
mainfrom
h-abstraction-empty-product-dicts

Conversation

@alongd

@alongd alongd commented Aug 17, 2026

Copy link
Copy Markdown
Member

The crash

h_abstraction() in arc/job/adapters/ts/heuristics.py indexes
reaction.product_dicts[0] before the loop that iterates over the same
list:

xyz_guesses = list()
dihedral_increment = dihedral_increment or DIHEDRAL_INCREMENT
reactants_reversed, products_reversed = are_h_abs_wells_reversed(rxn=reaction, product_dict=reaction.product_dicts[0])
for product_dict in reaction.product_dicts:
    ...

When reaction.product_dicts is empty (no matching H_Abstraction family
products were identified for the reaction), the for loop below would
simply not execute and leave xyz_guesses empty — a correct "no guesses"
outcome. But the unguarded [0] on the preceding line raises
IndexError: list index out of range first, turning "no guesses" into a
fatal crash.

Real-run evidence

Observed in a multi-hour T3 PDep→QM ARC run. The exception propagated all
the way up through Scheduler.__init__ and killed the whole run:

  File "/home/alon/Code/ARC/arc/scheduler.py", line 557, in __init__
    self.schedule_jobs()
  File "/home/alon/Code/ARC/arc/scheduler.py", line 633, in schedule_jobs
    self.spawn_ts_jobs()  # If all reactants/products are already known (Arkane yml or restart), spawn TS searches.
  File "/home/alon/Code/ARC/arc/scheduler.py", line 1795, in spawn_ts_jobs
    self.run_job(job_type='tsg', ...)
  File "/home/alon/Code/ARC/arc/scheduler.py", line 1072, in run_job
    job.execute()
  File "/home/alon/Code/ARC/arc/job/adapter.py", line 229, in execute
    self.execute_incore()
  File "/home/alon/Code/ARC/arc/job/adapters/ts/heuristics.py", line 266, in execute_incore
    xyzs = h_abstraction(reaction=rxn, dihedral_increment=self.dihedral_increment)
  File "/home/alon/Code/ARC/arc/job/adapters/ts/heuristics.py", line 879, in h_abstraction
    reactants_reversed, products_reversed = are_h_abs_wells_reversed(rxn=reaction, product_dict=reaction.product_dicts[0])
IndexError: list index out of range

Root cause of empty product_dicts

ARCReaction.product_dicts lazily calls get_product_dicts()
get_reaction_family_products(), which returns an empty list whenever no
RMG reaction-family template matches the reaction (e.g. atom-mapping
ambiguity, an unusual/edge-case reaction, or a family that legitimately
doesn't apply). This is an expected, already-handled emptiness elsewhere in
the file: get_products_and_check_families() (around line 981) has its own
if not product_dicts: branch for the case where a specifically-requested
family produced no products. There's no evidence of a deeper upstream defect
here — a TS-guess heuristic simply has to tolerate "no guesses could be
constructed" as a valid outcome rather than treating it as fatal, since ARC
has other TS search methods to fall back on.

Fix

Guard the [0] access: if reaction.product_dicts is empty, log a warning
naming the reaction and return an empty guess list immediately, before ever
touching [0]. This matches the existing logging/docstring style in the
file (logger.warning, numpydoc-ish Returns section).

Sibling-pattern search

Searched the rest of heuristics.py for the same "unguarded [0]-index
before a loop over the same list" pattern in the other family heuristic
functions (hydrolysis, get_products_and_check_families,
get_reaction_family_products, etc.). None found — h_abstraction() was
the only function with this defect.

Tests

Added test_h_abstraction_with_empty_product_dicts to
arc/job/adapters/ts/heuristics_test.py, which builds a real H_Abstraction
ARCReaction, forces product_dicts = [] via the existing setter, calls
h_abstraction() directly, and asserts it returns [] instead of raising.

Baseline (before fix): 43 passed (heuristics_test.py)
After fix: 44 passed (heuristics_test.py)

Test plan

  • python -m pytest arc/job/adapters/ts/heuristics_test.py -q — 44 passed (was 43 before this change; new test added)

h_abstraction() indexed reaction.product_dicts[0] before the loop that
iterates over the same list, so an empty product_dicts (no matching
H_Abstraction family products identified for the reaction) raised an
unguarded IndexError instead of yielding no TS guesses. That crash
propagated out of Scheduler.__init__ and killed an entire multi-hour
ARC run. Return an empty guess list with a warning instead, so other
TS search methods can still be attempted.
@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 64.23%. Comparing base (06c6ce6) to head (df98d7b).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #993      +/-   ##
==========================================
+ Coverage   64.20%   64.23%   +0.02%     
==========================================
  Files         119      119              
  Lines       39578    39580       +2     
  Branches    10266    10266              
==========================================
+ Hits        25412    25424      +12     
+ Misses      11196    11186      -10     
  Partials     2970     2970              
Flag Coverage Δ
functionaltests 64.23% <ø> (+0.02%) ⬆️
unittests 64.23% <ø> (+0.02%) ⬆️

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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant