Skip to content

Make queue-executed ASE (UMA/fairchem) jobs runnable on a cluster - #985

Open
alongd wants to merge 1 commit into
mainfrom
queue-executed-ase-adapter
Open

Make queue-executed ASE (UMA/fairchem) jobs runnable on a cluster#985
alongd wants to merge 1 commit into
mainfrom
queue-executed-ase-adapter

Conversation

@alongd

@alongd alongd commented Aug 15, 2026

Copy link
Copy Markdown
Member

What

Make queue-executed ASEAdapter jobs actually run on a remote cluster. The ASE adapter had only ever been exercised incore; its queue path was latent and broke on the first real submission. This lets ASE-backed calculators (e.g. UMA / fairchem MLIPs) run hindered-rotor and other scans as ordinary PBS/Slurm jobs.

Why

Running a real ARC job that delegates its hindered-rotor scans to an ASE calculator on a cluster queue crashed the moment the first scan job was dispatched:

arc.exceptions.InputError: Cannot upload a non-existing file.
Check why file in path .../directed_scan_a2781/submit.sh is missing.

Two independent defects in the queue path, both masked because ASE had only ever run incore:

  1. Files were never written. JobAdapter.execute() calls upload_files() before execute_queue(), and _initialize_adapter() calls set_files() while the job is constructed. The Gaussian/Orca/xTB adapters write submit.sh / input inside set_files(); ASEAdapter.set_files() only appended their names to files_to_upload and never wrote them, so the calcs dir was empty when ssh.upload_file() ran its os.path.isfile pre-flight.
  2. Submission was dead code. ASEAdapter.execute_queue() guarded on self.server_adapter, an attribute nothing in ARC sets (hasattrFalse), so even with the files present the job would never submit — the write fix alone just moves the crash one line down to an AttributeError.

Changes (confined to the ASE adapter)

  • set_files() now writes submit.sh and input.yml for non-incore jobs, mirroring the other adapters. Incore is unchanged — it still writes its input in execute_incore().
  • execute_queue() now delegates to the shared legacy_queue_execution() that every other adapter uses (which also records job_status / job_id), replacing the bespoke never-reached block.
  • write_submit_script()'s non-queue branch falls back to local_path when remote_path is None (a server-less job), fixing a latent crash surfaced by writing at construction time.
  • Adds test_set_files_writes_the_files_of_a_queue_job (red against the pre-fix adapter).

Verification

  • New/updated unit tests: pytest arc/job/adapters/ase_test.py -q -n010 passed (the file requires serial -n0, a pre-existing xdist setUpClass race unrelated to this change).
  • Incore path unregressed: full execute()execute_incore()ase_script.pyparse_results() reproduces the prior geometry/energy to 5 decimals.
  • Live cluster run: an ARC job (DFT opt/freq/sp on the queue, hindered-rotor scans via a UMA/ASE calculator) now runs end-to-end — the DFT stage completes and the rotor jobs are written with their submit.sh + input.yml and submitted to the queue, where the pre-fix run crashed.

The diff is limited to arc/job/adapters/ase_adapter.py, arc/job/adapters/ase_test.py, and a 1-indexed→0-indexed constraint fix in arc/job/adapters/scripts/ase_script.py (ASE FixInternals is 0-indexed; ARC constraints are 1-indexed). No other adapter's submit path is touched.

Comment thread arc/job/adapters/ase_adapter.py Fixed
@codecov

codecov Bot commented Aug 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 64.54%. Comparing base (db9136e) to head (6e97a32).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #985      +/-   ##
==========================================
- Coverage   64.56%   64.54%   -0.03%     
==========================================
  Files         119      119              
  Lines       39706    39751      +45     
  Branches    10289    10294       +5     
==========================================
+ Hits        25638    25657      +19     
- Misses      11085    11107      +22     
- Partials     2983     2987       +4     
Flag Coverage Δ
functionaltests 64.54% <ø> (-0.03%) ⬇️
unittests 64.54% <ø> (-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

This PR fixes the previously-latent “queue execution” path for ASEAdapter so ASE-backed calculators (e.g., UMA/fairchem MLIPs) can run as real cluster jobs, including rotor/directed-scan workloads that ARC dispatches via the scheduler.

Changes:

  • Write submit + input.yml during adapter construction for non-incore ASE jobs so upload_files() succeeds.
  • Replace ASE’s bespoke queue submission logic with the shared legacy_queue_execution() flow.
  • Correct ASE internal constraint indexing by translating ARC’s 1-indexed constraints to ASE FixInternals’ 0-indexed expectations.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
arc/job/adapters/ase_adapter.py Writes queue job files in set_files(), adds directed-scan constraints translation, and delegates queue submission to legacy_queue_execution().
arc/job/adapters/ase_test.py Adds unit coverage asserting queue-job construction writes/uploadables exist on disk (and that incore does not).
arc/job/adapters/scripts/ase_script.py Converts constraint atom indices from ARC’s 1-indexing to ASE’s 0-indexing before applying FixInternals.
Suppressed comments (1)

arc/job/adapters/ase_adapter.py:415

  • This method always writes the submit script to submit.sh. On Slurm, ARC submits submit.sl (see settings['submit_filenames']), so Slurm jobs will still error unless the script is written under the Slurm filename.
        with open(os.path.join(self.local_path, 'submit.sh'), 'w') as f:
            f.write(content)

💡 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/ase_adapter.py Outdated
@alongd

alongd commented Aug 15, 2026

Copy link
Copy Markdown
Member Author

Added a third commit (dc23057): fix a stale directed-scan parser call site — check_directed_scan_job called parser.parse_e_elect(path=...) but the refactored API is log_file_path= (as every other call site uses). Without it, ARC crashes assembling V(φ) the moment a directed-scan job's energy is parsed. Verified by parsing a real UMA rotor output (−500058.16 kJ/mol). This is needed for ASE/UMA rotor scans to run end-to-end.

Comment thread arc/job/adapters/ase_adapter.py Outdated
@alongd
alongd force-pushed the queue-executed-ase-adapter branch from 65e4d98 to 76b7b19 Compare August 21, 2026 20:08
@alongd
alongd requested a lite review from Copilot August 21, 2026 22:54
@alongd
alongd force-pushed the queue-executed-ase-adapter branch from 76b7b19 to 3489549 Compare August 21, 2026 22:55

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

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Suppressed comments (6)

arc/job/adapters/ase_adapter.py:284

  • Slurm jobs will still fail at submission: write_submit_script() writes submit.sh, and this entry uploads/registers that same name, but legacy_queue_execution() invokes the shared submit_filenames['Slurm'] (submit.sl; see arc/settings/settings.py:173-176). Thus sbatch submit.sl cannot find the file. Use the shared scheduler-specific filename consistently when writing and registering the script.
            self.files_to_upload.append(self.get_file_property_dictionary(file_name='submit.sh'))

arc/job/adapters/ase_adapter.py:354

  • max_job_time is not propagated into these templates: format_kwargs has no t_max, and the PBS/Slurm templates have no walltime directive. Unlike the shared JobAdapter.write_submit_script() path (arc/job/adapter.py:287-315), an ASE queue job will use the scheduler default limit and can be killed before ARC's requested time. Add the scheduler-specific walltime directive and pass the formatted value.
        format_kwargs = {'name': self.job_server_name, 'cpus': self.cpu_cores, 'memory': memory,
                         'remote_path': self.remote_path, 'env_setup': config['env_setup'], 'command': command}

arc/job/adapters/ase_adapter.py:376

  • queue_job is false for every non-incore scheduler other than PBS/Slurm, so OGE/HTCondor (and a no-queue local server) take the bare-script branch but are still submitted through legacy_queue_execution(). HTCondor then looks for submit.sub, while only submit.sh was written; other schedulers also lack their directives. Raise for unsupported schedulers or add templates instead of silently submitting an invalid script.
        queue_job = self.execution_type != 'incore' and cluster_soft in ('pbs', 'slurm')

arc/job/adapters/scripts/ase_script.py:127

  • The new index conversion is not covered by the ASE tests: there is no test for apply_constraints() or for determine_constraints() generating a directed-scan constraint. A regression here would silently apply a constraint to the wrong atoms, so add a test that writes a 1-indexed four-atom dihedral constraint and verifies the FixInternals indices are 0-indexed.
        indices = [index - 1 for index in constraint[0]]

arc/settings/submit.py:128

  • The PR description says the diff is confined to three ASE adapter files, but this change also adds arc/settings/submit.py and modifies arc/imports.py. Please update the description or explain these additional shared submission/configuration changes, since they materially expand the review scope.
# Submission scripts for queue-executed ASE (e.g. UMA/fairchem MLIP) jobs, keyed by cluster

arc/settings/submit.py:146

  • ARC invokes sbatch after changing into the submission directory, but this script changes into remote_path as if the batch job starts at the remote home. When the server has no path, remote_path is relative, so this can resolve to a nested runs/.../runs/... directory; for a local queue it also points away from local_path. Use the scheduler's submission directory for the job directory instead.
cd "{remote_path}"
JOB_DIR="$(pwd)"  # absolute path (the path above is relative to the remote home, where the job starts)

Comment thread arc/job/adapters/ase_adapter.py
Comment thread arc/job/adapters/ase_adapter.py Outdated
Comment thread arc/settings/submit.py
@alongd
alongd force-pushed the queue-executed-ase-adapter branch from 3489549 to cb346da Compare August 22, 2026 04:54
ASEAdapter.write_submit_script() emitted a bare two-line bash script: no
scheduler directives, no queue, no environment activation, and the ARC host's
conda python path, which does not exist on the server. It now composes a PBS
or Slurm script from args['block'] (queue, env_setup, gpu_resource, python),
pins the thread pools to the granted core count so torch cannot oversubscribe
a shared node, requests the job's walltime so a long scan is not killed at the
queue default, and stamps initial_time/final_time so ARC can report a run
time. The script body lives in a server-independent ase_submit template in
arc/settings/submit.py (keyed by cluster software, mirroring pipe_submit and
wired through arc.imports with the same local-override hook), so it can be
customized per cluster like every other submit script; the adapter only fills
in placeholders. The script is written under the scheduler's submit filename
(submit.sl for Slurm), which is the name submit_job() invokes, and cd's into
the submission directory (the local path for a 'local' server). The resolved
queue is recorded in attempted_queues, as JobAdapter does, so a failed
submission moves on to the next queue instead of retrying the same one.
Incore jobs keep the bare script.

A queue-executed ASE job also never wrote its submit script or input.yml.
set_files() only listed them for upload, but JobAdapter.execute() uploads
before it calls execute_queue(), so the upload died with "InputError: Cannot
upload a non-existing file". Write them in set_files(), where Gaussian, Orca
and xTB write theirs; the incore path still writes its input in
execute_incore(). execute_queue() then never submitted anything either: it
guarded on self.server_adapter, an attribute nothing sets. Use
legacy_queue_execution(), as every other adapter does, which also records the
job id and status.

Directed scans were also not constrained: Scheduler.run_job() always passes
constraints=None and hands the adapter torsions + dihedrals instead, so every
point of a brute_force_opt scan optimized freely and relaxed to the same
minimum. ASEAdapter.determine_constraints() derives the constraint, and
apply_constraints() converts ARC's 1-indexed atom indices to ASE's 0-indexed
FixInternals. The shared Scheduler/Gaussian side of that defect is left alone
here; a Gaussian directed_scan job needs its own fix.
@alongd
alongd force-pushed the queue-executed-ase-adapter branch from cb346da to 6e97a32 Compare August 23, 2026 05:39
alongd added a commit to alongd/ARC that referenced this pull request Aug 23, 2026
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.

3 participants