Skip to content

CREST-based TS conformer search adapter - #921

Open
calvinp0 wants to merge 4 commits into
mainfrom
crest_adapter_clean
Open

CREST-based TS conformer search adapter#921
calvinp0 wants to merge 4 commits into
mainfrom
crest_adapter_clean

Conversation

@calvinp0

@calvinp0 calvinp0 commented Jul 23, 2026

Copy link
Copy Markdown
Member

Adds CREST as a TS-search adapter. CREST does not search for a saddle itself — it takes the TS
seeds ARC already generates heuristically and runs constrained GFN2-xTB metadynamics around them,
so the reactive core is pinned while the spectator degrees of freedom are conformationally sampled.
The result is a better-converged starting geometry for the subsequent DFT TS optimisation.

Commits

commit scope
CREST: settings, constants, and installer binary discovery, env activation, make install-crest, angstrom_to_bohr
CREST: TS-search adapter (H-abstraction) + seed hub the adapter, the neutral seed/constraint hub, hydrolysis constraints, shared-code changes
CREST: XY-addition four-center seed builder four-centre concerted XY-addition seed geometry
CREST: document the TS-search adapter docs/source/TS_search.rst

Every file is touched by exactly one commit.

Family support and gating

CREST is opt-in. An adapter runs only if it is both registered for the family in
ts_adapters_by_rmg_family and present in the global ts_adapters list; crest is deliberately
absent from the default list, alongside goflow and rits, because it needs its own environment
and an external binary. Enable it per run:

ts_adapters:
  - heuristics
  - crest

Constraints are implemented for:

  • H_Abstraction
  • carbonyl_based_hydrolysis, ether_hydrolysis, nitrile_hydrolysis — ARC-native families, always
    classified, so these are what make CREST reachable in a normal run
  • XY_Addition_MultipleBondcaveat: this family's group definition requires a halogen at *4
    ([F1s,Cl1s,Br1s]), so RMG files it under the halogens family set, not default. In a default
    run ARCReaction.family is None for these reactions and neither the seed builder nor CREST is
    reached. It needs rmg_family_set: 'all'. (get_all_families(['default', 'halogens']) returns a
    nested list and silently drops every RMG family — a separate core bug, not addressed here.)

Chemistry decisions

H-abstraction reactive core is now absolute, not multiplicative. ARC applied
r1_stretch = r2_stretch = 1.2 to reference single-bond lengths, which makes the core
element-dependent. Replaced with BREAKING_BOND_LENGTH = 1.29 and FORMING_BOND_LENGTH = 1.36 Å,
medians over 1670 DFT-optimised H-abstraction TSs:

system acceptor forming bond, before after
CH₄ + OH, C₂H₆ + OH O 1.1628 1.3600
CH₄ + H H 0.8904 1.3600

The H-acceptor case was the worst: a forming H···H distance of 0.89 Å is only ~0.15 Å beyond an
equilibrium H₂ bond. A GFN2-xTB Hessian on the CH₄ + OH seed improves from −462.8 cm⁻¹ to
−775.1 cm⁻¹
, one imaginary mode in both cases. The multiplicative path is preserved when
r1_stretch / r2_stretch are passed explicitly, and is still covered by the original
full-geometry golden test.

The measured median D–H–A angle is 174°, which is not applied here: is_angle_linear has a
0.9° tolerance, so 174° flips combine_coordinates_with_redundant_atoms onto a different
construction path. That is separate work.

Constraints pin distances only. For a three-atom H-abstraction core, d(A–H), d(H–B) and
d(A–B) determine the angle by the law of cosines, so an additional angle: restraint is redundant
and adds a competing biasing term — and it is ill-conditioned exactly where this family lives, since
∂θ/∂cos θ = −1/sin θ diverges at 180°.

Hydrolysis uses all six pairwise distances among the electrophilic centre, leaving group, water
oxygen and transferring water hydrogen. Four atoms have 6 internal degrees of freedom and there are
C(4,2) = 6 pairwise distances, so this is exactly determining; a smaller set leaves the reactive core
free to relax away from the saddle during sampling.

Changes outside the CREST paths

These affect runs that never use CREST:

  • arc/constants.py — removed a duplicate bohr_to_angstrom = 0.529177 that shadowed a0 * 1e10;
    added angstrom_to_bohr as its reciprocal.
  • arc/scheduler.pysuccessful_methods now credits every entry in a guess's method_sources,
    deduplicated case-insensitively, so a geometry found by several adapters credits all of them.
  • arc/species/converter.py — new reorder_xyz_string; empty input now raises ConverterError
    rather than IndexError, and blank interior lines are skipped per the documented contract.
  • arc/job/adapters/ts/heuristics.py — the reactive-core constants above.

Testing

99 new tests against origin/main:

file before after
arc/job/adapters/ts/crest_test.py 40
arc/job/adapters/ts/xy_addition_test.py 14
arc/settings/crest_test.py 14
arc/job/adapters/ts/heuristics_test.py 43 65
arc/species/converter_test.py 72 79
arc/scheduler_test.py 43 45

Verified: 1123 passed, 0 failed across arc/job/adapters/ts/, arc/settings/,
converter_test, scheduler_test, output_test and main_test.

The suite was validated by mutation testing rather than by coverage percentage — each new test was
checked to fail under a plausible corruption of the code it covers (flipped comparisons, swapped
atom indices, ±10% on physical constants, disabled guards, removed unit conversions). Notable
results: the coords.ref Å→Bohr conversion and the TURBOMOLE column order are now pinned (both were
previously mutable with the suite green), as are --chrg/--uhf, the *3/*4 orientation the
asymmetric XY factors depend on, and the properness of the antiparallel rotation branch (returning
−I there is a mirror image, det = −1, i.e. a chirality inversion).

Upgrade note

arc/constants.pxd gains angstrom_to_bohr. A stale compiled arc.constants shadows the source and
lacks the symbol, so re-run make compile after pulling — otherwise the first CREST job fails
with an AttributeError at runtime rather than at import.

Known limitations

  • XY_Addition_MultipleBond is unreachable without rmg_family_set: 'all', per above.
  • The D–H–A angle target (174°) is not applied.
  • Each seed becomes its own CREST job; a heuristics sweep can produce dozens, so submission is capped
    at MAX_CREST_SEEDS most-distinct seeds, with the number dropped logged.
  • submit_job() in arc/job/local.py signals failure as either (None, None) or ('errored', '').
    The adapter handles both, but the convention is worth normalising repo-wide.

Copilot AI review requested due to automatic review settings July 23, 2026 11:33

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.

Comment thread arc/job/adapters/ts/crest.py Fixed
Comment thread arc/job/adapters/ts/heuristics_test.py Fixed
Comment thread arc/settings/settings.py Fixed
Comment thread arc/species/converter.py Fixed
@calvinp0
calvinp0 force-pushed the crest_adapter_clean branch from 45a03ee to 39d7c01 Compare July 23, 2026 17:55
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 64.88%. Comparing base (45d73a0) to head (03321d8).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #921      +/-   ##
==========================================
+ Coverage   64.60%   64.88%   +0.27%     
==========================================
  Files         119      123       +4     
  Lines       39785    40587     +802     
  Branches    10307    10480     +173     
==========================================
+ Hits        25703    26334     +631     
- Misses      11105    11233     +128     
- Partials     2977     3020      +43     
Flag Coverage Δ
functionaltests 64.88% <ø> (+0.27%) ⬆️
unittests 64.88% <ø> (+0.27%) ⬆️

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.

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.

@calvinp0
calvinp0 force-pushed the crest_adapter_clean branch 4 times, most recently from e6a47d8 to 174199b Compare August 14, 2026 08:50
Comment thread arc/settings/settings.py Fixed
Comment thread arc/settings/settings.py Fixed
Comment thread arc/settings/settings.py Fixed
Comment thread arc/species/converter_test.py Fixed
@calvinp0
calvinp0 force-pushed the crest_adapter_clean branch 2 times, most recently from d2b0575 to f4ae037 Compare August 14, 2026 12:13
@calvinp0
calvinp0 force-pushed the crest_adapter_clean branch 2 times, most recently from b31f635 to b2e2076 Compare August 14, 2026 17:38
Comment thread arc/settings/settings.py Fixed
Comment thread arc/settings/settings.py Fixed
@calvinp0
calvinp0 force-pushed the crest_adapter_clean branch from b2e2076 to 89ba170 Compare August 14, 2026 18:42
Comment thread arc/job/adapters/ts/heuristics.py Fixed
Comment thread arc/job/adapters/ts/seed_hub.py Fixed
@calvinp0 calvinp0 changed the title CREST CREST-based TS conformer search adapter Aug 14, 2026
@calvinp0
calvinp0 force-pushed the crest_adapter_clean branch 2 times, most recently from 31231d0 to d70196a Compare August 14, 2026 19:14
Comment thread arc/settings/settings.py Dismissed
Comment thread arc/settings/settings.py Dismissed
@calvinp0
calvinp0 force-pushed the crest_adapter_clean branch 3 times, most recently from c988137 to bba78a3 Compare August 22, 2026 06:40
@calvinp0
calvinp0 force-pushed the crest_adapter_clean branch 2 times, most recently from 23beab3 to 03321d8 Compare August 23, 2026 12:13

@LeenFahoum LeenFahoum 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.

Looks good overall! I left a few comments.

CREST_REACTIVE_CORE_SIZES = {'H_Abstraction': 3,
'carbonyl_based_hydrolysis': 4,
'ether_hydrolysis': 4,
'nitrile_hydrolysis': 4,

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.

Should we also add XY_Addition_MultipleBond: 4 here? The CREST constraints for this family use all four *1-*4 atoms, so it looks like its reactive core size should also be 4. If so, the [2, 2] case in test_tiny_system_gate_skips_crest_only_for_whole_molecule_core should also expect True

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@LeenFahoum Yup! it is my intention but right now #978 needs to go in before this PR if we want to have XY_Addition_MultipleBond because right now ARC loads RMG-DB as default so it does not load in that family. The #978 PR is to change default to all and then I can adjust this PR to include it too

if os.path.isfile(stale_best_path):
try:
os.remove(stale_best_path)
except OSError as e:

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.

If removing the previous crest_best.xyz fails, could we return None here instead of continuing?
Otherwise process_completed_jobs() may later pick up the old geometry and treat it as the result of the new run, since it only checks whether the file exists

if job_info["status"] not in TERMINAL_JOB_STATUSES:
try:
job_info["status"] = check_job_status(job_id)
except Exception as e:

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.

A temporary scheduler error could mark the job as errored even if it’s still running. I think its better to just log the exception here and keep the current status unchanged

selected_hydrogen = None
selected_heavy_atoms = None
for hydrogen_index in hydrogen_indices:
heavy_atoms = sorted(

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.

This filter removes all H atoms, even though a free H can be valid in H-abstraction. I think it would be better to use the same free-vs-bound H check as _is_valid_h_abs_atom_assignment() so bonded H atoms are still excluded

@calvinp0
calvinp0 force-pushed the crest_adapter_clean branch from 03321d8 to 91b7afe Compare August 27, 2026 14:32
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.

4 participants