Add a one-process relaxed rotor scan to the ASE adapter - #1018
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1018 +/- ##
==========================================
+ Coverage 64.55% 64.58% +0.03%
==========================================
Files 119 119
Lines 39788 39791 +3
Branches 10307 10308 +1
==========================================
+ Hits 25684 25701 +17
+ Misses 11123 11107 -16
- Partials 2981 2983 +2
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
Adds first-class support for job_type='scan' in ARC’s ASE adapter by running a full 1D relaxed torsional scan within a single ASE script process and emitting energies/angles into output.yml for ARC’s existing YAML scan parser.
Changes:
- Implements relaxed 1D torsion scan machinery in
ase_script.py(rotating-top detection, bidirectional hysteretic walks, merge + diagnostics, Hartree output). - Wires
ASEAdapter.write_input_file()to serializetorsionsandscan_resforscanjobs. - Adds hermetic unit tests (EMT-based) covering rotating-top resolution, scan grid/endpoint behavior, parser round-trip, and guardrails.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
arc/job/adapters/scripts/ase_script.py |
Adds the in-process relaxed torsion scan implementation and hooks it into job_type == 'scan' execution/output shaping. |
arc/job/adapters/ase_adapter.py |
Ensures scan-specific inputs (torsions, scan_res) are written to input.yml for ASE script execution. |
arc/job/adapters/ase_test.py |
Adds unit tests validating scan input writing, rotating group detection, scan execution with EMT, and YAML parser round-tripping. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Implement job_type='scan' for ASEAdapter: a full 1D relaxed torsional scan run in a single process, sweeping one torsion in fixed increments and relaxing all other coordinates at each point, so an MLIP rotor scan costs one task per rotor instead of one job per dihedral point. The scan loop lives in ase_script.py (runs in the calculator's env): a bidirectional relaxed walk (0->360 and 360->0 from the same start, keeping the lower branch at each grid point) ported from the validated benchmark harness. Non-converged points are kept, not dropped, so the periodic grid stays complete. Output is written to output.yml as energies (Hartree) + angles (deg), which ARC's existing YAML scan parser reads via rotors_dict['scan_path']. The adapter passes torsions and scan_res into the input file for scan jobs. Validated against the benchmark UMA V(phi) for ethane: max abs deviation 0.028 kJ/mol, barrier 10.688 vs 10.688 kJ/mol.
What
Implements
job_type='scan'forASEAdapter: a complete 1D relaxed torsional scan run in a single process, sweeping one torsion in fixed increments and relaxing all other degrees of freedom at each point. PreviouslyASEAdapterhad noscanbranch at all — ascanjob fell through to the geometry-saveelseinase_script.py, sooutput.ymlcarried noenergies/anglesand ARC's YAML scan parser returned(None, None)silently (a quiet empty potential, not an error).Why one process per rotor, not one job per point
A rotor scan can be decomposed into one constrained optimization per dihedral point (as
directed_scandoes), but for a machine-learned potential (e.g. UMA) each such job re-loads the model checkpoint to do a few seconds of work — for a large corpus that is hundreds of thousands of tiny jobs dominated by fixed startup. An ESS engine like Gaussian sweeps the whole rotor internally via itsscankeyword; this gives the ASE adapter the same capability, so a full rotor is one in-process task instead of ~46 jobs. The model load is then paid once per worker and amortized across every rotor it handles.The scan loop lives in
ase_script.py(which runs in the calculator's own environment); the adapter only writes the input (torsions,scan_res) and parses the output.output.ymlcarriesenergies(Hartree) andangles(degrees), read back through the existingyamlparser adapter androtors_dict['scan_path']— no change to the statmech/Arkane side.Design choices
FixInternals; convergence is judged on the residual force with the constraint still applied (readingopt.converged()after clearing it re-exposes the free torsional force and falsely reports non-convergence at relaxed points).fmax_worstin the output records the largest residual.Validation
Validated on ethane against an already-validated standalone MLIP scan (UMA
uma-s-1p2), same species / torsion / starting geometry:scan)The two V(φ) profiles are the same curve; the residual is at the floating-point/optimizer-noise level (the reference ran on GPU, this on CPU).
Tests
Adds hermetic unit tests using ASE's EMT calculator (no MLIP needed in CI): rotating-group detection incl. the ring-pivot error, a full 46-point relaxed scan (grid, endpoint duplication, finiteness), output round-tripping through ARC's YAML scan parser, and the single-torsion guard.
Scope
scanonly; additive and gated onjob_type=='scan'.directed_scanand existing job types are untouched. Wiringrotor_scan_1dthrough the pipe is a follow-up.