Skip to content

Commit

Permalink
Add support for directory for Q-Chem (#318)
Browse files Browse the repository at this point in the history
* Add support for `directory` for Q-Chem

* fix

* pre-commit auto-fixes

* Update handlers.py

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
Andrew-S-Rosen and pre-commit-ci[bot] committed Mar 11, 2024
1 parent 41bd7d5 commit 30576c6
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 26 deletions.
20 changes: 12 additions & 8 deletions custodian/qchem/handlers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""This module implements new error handlers for QChem runs."""
"""This module implements error handlers for QChem runs."""

import os

Expand Down Expand Up @@ -55,9 +55,10 @@ def __init__(
self.errors = []
self.opt_error_history = []

def check(self):
def check(self, directory="./"):
"""Checks output file for errors."""
self.outdata = QCOutput(self.output_file).data
self._output_path = os.path.join(directory, self.output_file)
self.outdata = QCOutput(self._output_path).data
self.errors = self.outdata.get("errors")
self.warnings = self.outdata.get("warnings")
# If we aren't out of optimization cycles, but we were in the past, reset the history
Expand All @@ -68,11 +69,13 @@ def check(self):
return False
return len(self.errors) > 0

def correct(self):
def correct(self, directory="./"):
"""Perform corrections."""
backup({self.input_file, self.output_file})
self._input_path = os.path.join(directory, self.input_file)
self._output_path = os.path.join(directory, self.output_file)
backup({self._input_path, self._output_path})
actions = []
self.qcinp = QCInput.from_file(self.input_file)
self.qcinp = QCInput.from_file(self._input_path)

if "SCF_failed_to_converge" in self.errors:
# Given defaults, the first handlers will typically be skipped.
Expand Down Expand Up @@ -380,6 +383,7 @@ def correct(self):
) and str(self.qcinp.geom_opt["initial_hessian"]).lower() == "read":
del self.qcinp.geom_opt["initial_hessian"]
actions.append({"initial_hessian": "deleted"})
os.replace(self.input_file, f"{self.input_file}.last")
self.qcinp.write_file(self.input_file)

os.replace(self._input_path, os.path.join(directory, self.input_file + ".last"))
self.qcinp.write_file(self._input_path)
return {"errors": self.errors, "warnings": self.warnings, "actions": actions}
29 changes: 18 additions & 11 deletions custodian/qchem/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import shutil
import subprocess
import warnings
from pathlib import Path

import numpy as np
from pymatgen.core import Molecule
Expand Down Expand Up @@ -99,26 +100,29 @@ def __init__(
print("SLURM_CPUS_ON_NODE not in environment")

@property
def current_command(self):
def current_command(self, directory="./"):
"""The command to run QChem."""
self._input_path = os.path.join(directory, self.input_file)
self._output_path = os.path.join(directory, self.output_file)
multi = {"openmp": "-nt", "mpi": "-np"}
if self.multimode not in multi:
raise RuntimeError("ERROR: Multimode should only be set to openmp or mpi")
command = [multi[self.multimode], str(self.max_cores), self.input_file, self.output_file, "scratch"]
command = [multi[self.multimode], str(self.max_cores), self._input_path, self._output_path, "scratch"]
command = self.qchem_command + command
return " ".join(command)

def setup(self):
def setup(self, directory="./"):
"""Sets up environment variables necessary to efficiently run QChem."""
self._input_path = os.path.join(directory, self.input_file)
if self.backup:
shutil.copy(self.input_file, f"{self.input_file}.orig")
shutil.copy(self._input_path, os.path.join(directory, f"{self.input_file}.orig"))
if self.multimode == "openmp":
os.environ["QCTHREADS"] = str(self.max_cores)
os.environ["OMP_NUM_THREADS"] = str(self.max_cores)
os.environ["QCSCRATCH"] = os.getcwd()
os.environ["QCSCRATCH"] = str(Path(directory).resolve())
if self.calc_loc is not None:
os.environ["QCLOCALSCR"] = self.calc_loc
qcinp = QCInput.from_file(self.input_file)
qcinp = QCInput.from_file(self._input_path)
if (
qcinp.rem.get("run_nbo6", "none").lower() == "true"
or qcinp.rem.get("nbo_external", "none").lower() == "true"
Expand All @@ -128,17 +132,20 @@ def setup(self):
raise RuntimeError("Trying to run NBO7 without providing NBOEXE in fworker! Exiting...")
os.environ["NBOEXE"] = self.nboexe

def postprocess(self):
def postprocess(self, directory="./"):
"""Renames and removes scratch files after running QChem."""
self._input_path = os.path.join(directory, self.input_file)
self._output_path = os.path.join(directory, self.output_file)
self._qclog_path = os.path.join(directory, self.qclog_file)
scratch_dir = os.path.join(os.environ["QCSCRATCH"], "scratch")
for file in ["HESS", "GRAD", "plots/dens.0.cube", "131.0", "53.0", "132.0"]:
file_path = os.path.join(scratch_dir, file)
if os.path.isfile(file_path):
shutil.copy(file_path, os.getcwd())
shutil.copy(file_path, directory)
if self.suffix != "":
shutil.move(self.input_file, self.input_file + self.suffix)
shutil.move(self.output_file, self.output_file + self.suffix)
shutil.move(self.qclog_file, self.qclog_file + self.suffix)
shutil.move(self._input_path, os.path.join(directory, self.input_file + self.suffix))
shutil.move(self._output_path, os.path.join(directory, self.output_file + self.suffix))
shutil.move(self._qclog_path, os.path.join(directory, self.qclog_file + self.suffix))
for file in ["HESS", "GRAD", "dens.0.cube"]:
if os.path.isfile(file):
shutil.move(file, file + self.suffix)
Expand Down
14 changes: 7 additions & 7 deletions tests/qchem/test_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ def tearDown(self):
def test_defaults(self):
with patch("custodian.qchem.jobs.shutil.copy") as copy_patch:
job = QCJob(qchem_command="qchem", max_cores=32)
assert job.current_command == "qchem -nt 32 mol.qin mol.qout scratch"
assert job.current_command == "qchem -nt 32 ./mol.qin ./mol.qout scratch"
job.setup()
assert copy_patch.call_args_list[0][0][0] == "mol.qin"
assert copy_patch.call_args_list[0][0][1] == "mol.qin.orig"
assert copy_patch.call_args_list[0][0][0] == "./mol.qin"
assert copy_patch.call_args_list[0][0][1] == "./mol.qin.orig"
assert os.environ["QCSCRATCH"] == os.getcwd()
assert os.environ["QCTHREADS"] == "32"
assert os.environ["OMP_NUM_THREADS"] == "32"
Expand All @@ -63,7 +63,7 @@ def test_not_defaults(self):
nboexe="/path/to/nbo7.i4.exe",
backup=False,
)
assert job.current_command == "qchem -slurm -np 12 different.qin not_default.qout scratch"
assert job.current_command == "qchem -slurm -np 12 ./different.qin ./not_default.qout scratch"
job.setup()
assert os.environ["QCSCRATCH"] == os.getcwd()
assert os.environ["QCLOCALSCR"] == "/not/default/"
Expand All @@ -78,10 +78,10 @@ def test_save_scratch(self):
calc_loc="/tmp/scratch",
save_scratch=True,
)
assert job.current_command == "qchem -slurm -nt 32 mol.qin mol.qout scratch"
assert job.current_command == "qchem -slurm -nt 32 ./mol.qin ./mol.qout scratch"
job.setup()
assert copy_patch.call_args_list[0][0][0] == "mol.qin"
assert copy_patch.call_args_list[0][0][1] == "mol.qin.orig"
assert copy_patch.call_args_list[0][0][0] == "./mol.qin"
assert copy_patch.call_args_list[0][0][1] == "./mol.qin.orig"
assert os.environ["QCSCRATCH"] == os.getcwd()
assert os.environ["QCTHREADS"] == "32"
assert os.environ["OMP_NUM_THREADS"] == "32"
Expand Down

0 comments on commit 30576c6

Please sign in to comment.