Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for directory for Q-Chem #318

Merged
merged 12 commits into from
Mar 11, 2024
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
Loading