Skip to content

Add a one-process relaxed rotor scan to the ASE adapter - #1018

Open
alongd wants to merge 1 commit into
mainfrom
i002-ase-scan
Open

Add a one-process relaxed rotor scan to the ASE adapter#1018
alongd wants to merge 1 commit into
mainfrom
i002-ase-scan

Conversation

@alongd

@alongd alongd commented Aug 22, 2026

Copy link
Copy Markdown
Member

What

Implements job_type='scan' for ASEAdapter: 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. Previously ASEAdapter had no scan branch at all — a scan job fell through to the geometry-save else in ase_script.py, so output.yml carried no energies/angles and 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_scan does), 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 its scan keyword; 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.yml carries energies (Hartree) and angles (degrees), read back through the existing yaml parser adapter and rotors_dict['scan_path'] — no change to the statmech/Arkane side.

Design choices

  • Bidirectional relaxed walk. The torsion is swept 0→360 and 360→0 from the same starting geometry, keeping the lower energy at each grid point. A single directional walk is path-dependent: if a coupled coordinate flips partway round, that walk ends in a different conformer and its curve is not a torsional potential. Taking the pointwise minimum of both walks recovers the lowest torsional path.
  • Geometry propagation is hysteretic — each point starts from the previous relaxed geometry (which is exactly why both directions are walked).
  • Per-point constraint. After rotating the increment, the point's actual dihedral is measured and pinned with FixInternals; convergence is judged on the residual force with the constraint still applied (reading opt.converged() after clearing it re-exposes the free torsional force and falsely reports non-convergence at relaxed points).
  • Non-converged points are kept, never dropped — a hole in the periodic grid would corrupt the downstream Fourier fit. fmax_worst in the output records the largest residual.
  • Rotating group is derived by graph traversal (break the pivot bond, flood-fill from the second pivot), raising on a ring pivot.

Validation

Validated on ethane against an already-validated standalone MLIP scan (UMA uma-s-1p2), same species / torsion / starting geometry:

this PR (incore ASE scan) reference
barrier 10.6879 kJ/mol 10.6880 kJ/mol
points × resolution 46 × 8° 46 × 8°
max abs deviation 0.028 kJ/mol (mean 0.012)

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

scan only; additive and gated on job_type=='scan'. directed_scan and existing job types are untouched. Wiring rotor_scan_1d through the pipe is a follow-up.

@codecov

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

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

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     
Flag Coverage Δ
functionaltests 64.58% <ø> (+0.03%) ⬆️
unittests 64.58% <ø> (+0.03%) ⬆️

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.

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 serialize torsions and scan_res for scan jobs.
  • 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.

Comment thread arc/job/adapters/scripts/ase_script.py Outdated
Comment thread arc/job/adapters/scripts/ase_script.py
Comment thread arc/job/adapters/scripts/ase_script.py
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.
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.

2 participants