CREST-based TS conformer search adapter - #921
Conversation
8e09a0a to
45a03ee
Compare
45a03ee to
39d7c01
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 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
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:
|
e6a47d8 to
174199b
Compare
d2b0575 to
f4ae037
Compare
b31f635 to
b2e2076
Compare
b2e2076 to
89ba170
Compare
31231d0 to
d70196a
Compare
c988137 to
bba78a3
Compare
23beab3 to
03321d8
Compare
LeenFahoum
left a comment
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
@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: |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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
03321d8 to
91b7afe
Compare
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
CREST: settings, constants, and installermake install-crest,angstrom_to_bohrCREST: TS-search adapter (H-abstraction) + seed hubCREST: XY-addition four-center seed builderCREST: document the TS-search adapterdocs/source/TS_search.rstEvery 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_familyand present in the globalts_adapterslist;crestis deliberatelyabsent from the default list, alongside
goflowandrits, because it needs its own environmentand an external binary. Enable it per run:
Constraints are implemented for:
H_Abstractioncarbonyl_based_hydrolysis,ether_hydrolysis,nitrile_hydrolysis— ARC-native families, alwaysclassified, so these are what make CREST reachable in a normal run
XY_Addition_MultipleBond— caveat: this family's group definition requires a halogen at*4(
[F1s,Cl1s,Br1s]), so RMG files it under thehalogensfamily set, notdefault. In a defaultrun
ARCReaction.familyisNonefor these reactions and neither the seed builder nor CREST isreached. It needs
rmg_family_set: 'all'. (get_all_families(['default', 'halogens'])returns anested 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.2to reference single-bond lengths, which makes the coreelement-dependent. Replaced with
BREAKING_BOND_LENGTH = 1.29andFORMING_BOND_LENGTH = 1.36Å,medians over 1670 DFT-optimised H-abstraction TSs:
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_stretchare passed explicitly, and is still covered by the originalfull-geometry golden test.
The measured median D–H–A angle is 174°, which is not applied here:
is_angle_linearhas a0.9° tolerance, so 174° flips
combine_coordinates_with_redundant_atomsonto a differentconstruction path. That is separate work.
Constraints pin distances only. For a three-atom H-abstraction core,
d(A–H),d(H–B)andd(A–B)determine the angle by the law of cosines, so an additionalangle:restraint is redundantand 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 duplicatebohr_to_angstrom = 0.529177that shadoweda0 * 1e10;added
angstrom_to_bohras its reciprocal.arc/scheduler.py—successful_methodsnow credits every entry in a guess'smethod_sources,deduplicated case-insensitively, so a geometry found by several adapters credits all of them.
arc/species/converter.py— newreorder_xyz_string; empty input now raisesConverterErrorrather 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:arc/job/adapters/ts/crest_test.pyarc/job/adapters/ts/xy_addition_test.pyarc/settings/crest_test.pyarc/job/adapters/ts/heuristics_test.pyarc/species/converter_test.pyarc/scheduler_test.pyVerified: 1123 passed, 0 failed across
arc/job/adapters/ts/,arc/settings/,converter_test,scheduler_test,output_testandmain_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 werepreviously mutable with the suite green), as are
--chrg/--uhf, the*3/*4orientation theasymmetric XY factors depend on, and the properness of the antiparallel rotation branch (returning
−Ithere is a mirror image, det = −1, i.e. a chirality inversion).Upgrade note
arc/constants.pxdgainsangstrom_to_bohr. A stale compiledarc.constantsshadows the source andlacks the symbol, so re-run
make compileafter pulling — otherwise the first CREST job failswith an
AttributeErrorat runtime rather than at import.Known limitations
XY_Addition_MultipleBondis unreachable withoutrmg_family_set: 'all', per above.at
MAX_CREST_SEEDSmost-distinct seeds, with the number dropped logged.submit_job()inarc/job/local.pysignals failure as either(None, None)or('errored', '').The adapter handles both, but the convention is worth normalising repo-wide.