Skip to content

Commit

Permalink
Update Q-Chem schema (#1393)
Browse files Browse the repository at this point in the history
Use Emmet's Qchem TaskDoc. Closes #956.

Requires: materialsproject/emmet#929,
materialsproject/emmet#930

---------

Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
  • Loading branch information
Andrew-S-Rosen and deepsource-autofix[bot] authored Feb 7, 2024
1 parent 3dd2d06 commit 9a5a4dc
Show file tree
Hide file tree
Showing 6 changed files with 29 additions and 51 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ dependencies = [
"ase>3.22.1", # for Atoms object and calculators
"cclib>=1.8", # for I/O parsing of molecular DFT codes
"custodian>=2023.6.5", # for automated error corrections
"emmet-core>=0.69.6", # for pre-made schemas
"emmet-core>=0.77.1", # for pre-made schemas
"maggma>=0.57.0", # for database handling
"monty>=2023.9.25", # miscellaneous Python utilities
"numpy>=1.25.0", # for array handling
Expand Down
22 changes: 5 additions & 17 deletions src/quacc/calculators/qchem/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
from typing import TYPE_CHECKING

from ase import units
from emmet.core.qc_tasks import TaskDoc
from emmet.core.tasks import _parse_custodian
from pymatgen.io.qchem.inputs import QCInput
from pymatgen.io.qchem.outputs import (
QCOutput,
gradient_parser,
hessian_parser,
orbital_coeffs_parser,
Expand Down Expand Up @@ -68,24 +68,14 @@ def read_qchem(directory: Path | str = ".") -> tuple[Results, list[float]]:
"""
directory = Path(directory)

qc_input = QCInput.from_file(directory / "mol.qin")
qc_output = QCOutput(directory / "mol.qout").data
task_doc = TaskDoc.from_directory(directory).model_dump()

results: Results = {
"energy": qc_output["final_energy"] * units.Hartree,
"qc_output": qc_output,
"qc_input": qc_input.as_dict(),
"energy": task_doc["output"]["final_energy"] * units.Hartree,
"taskdoc": task_doc,
"custodian": _parse_custodian(directory),
}

# Parse thermo properties
if "total_enthalpy" in qc_output:
results["enthalpy"] = qc_output["total_enthalpy"] * (units.kcal / units.mol)
if "total_entropy" in qc_output:
results["entropy"] = qc_output["total_entropy"] * (
0.001 * units.kcal / units.mol
)

# Read the gradient scratch file in 8 byte chunks
grad_scratch = directory / "131.0"
if grad_scratch.exists() and grad_scratch.stat().st_size > 0:
Expand All @@ -96,9 +86,7 @@ def read_qchem(directory: Path | str = ".") -> tuple[Results, list[float]]:
# Read Hessian scratch file in 8 byte chunks
hessian_scratch = directory / "132.0"
if hessian_scratch.exists() and hessian_scratch.stat().st_size > 0:
reshaped_hess = hessian_parser(
hessian_scratch, n_atoms=len(qc_output["species"])
)
reshaped_hess = hessian_parser(hessian_scratch, n_atoms=task_doc["natoms"])
results["hessian"] = reshaped_hess * (units.Hartree / units.Bohr**2)

# Read orbital coefficients scratch file in 8 byte chunks
Expand Down
14 changes: 2 additions & 12 deletions src/quacc/calculators/qchem/qchem.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,7 @@ class Results(TypedDict, total=False):
energy: float # electronic energy in eV
forces: NDArray # forces in eV/A
hessian: NDArray # Hessian in eV/A^2/amu
enthalpy: float # total enthalpy in eV
entropy: float # total entropy in eV/K
qc_output: dict[
str, Any
] # Output from `pymatgen.io.qchem.outputs.QCOutput.data`
qc_input: dict[
str, Any
] # Input from `pymatgen.io.qchem.inputs.QCInput.as_dict()`
taskdoc: dict[str, Any] # Output from `emmet.core.qc_tasks.TaskDoc`
custodian: dict[str, Any] # custodian.json file metadata


Expand All @@ -40,10 +33,7 @@ class QChem(FileIOCalculator):
"energy",
"forces",
"hessian",
"enthalpy",
"entropy",
"qc_output",
"qc_input",
"taskdoc",
"custodian",
]
results: ClassVar[Results] = {}
Expand Down
19 changes: 14 additions & 5 deletions tests/core/calculators/qchem/test_qchem.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

from quacc.calculators.qchem import QChem

try:
import openbabel as ob
except ImportError:
ob = None
FILE_DIR = Path(__file__).parent


Expand Down Expand Up @@ -191,6 +195,7 @@ def test_qchem_write_input_freq(tmp_path, monkeypatch, test_atoms):
assert qcinp.as_dict() == ref_qcinp.as_dict()


@pytest.mark.skipif(ob is None, reason="openbabel needed")
def test_qchem_read_results_basic_and_write_53(tmp_path, monkeypatch, test_atoms):
calc = QChem(
test_atoms,
Expand All @@ -217,6 +222,7 @@ def test_qchem_read_results_basic_and_write_53(tmp_path, monkeypatch, test_atoms
assert qcinp.rem.get("scf_guess") == "read"


@pytest.mark.skipif(ob is None, reason="openbabel needed")
def test_qchem_read_results_intermediate(tmp_path, monkeypatch, test_atoms):
monkeypatch.chdir(tmp_path)
calc = QChem(test_atoms)
Expand All @@ -228,6 +234,7 @@ def test_qchem_read_results_intermediate(tmp_path, monkeypatch, test_atoms):
assert calc.prev_orbital_coeffs is not None


@pytest.mark.skipif(ob is None, reason="openbabel needed")
def test_qchem_read_results_advanced(tmp_path, monkeypatch, test_atoms):
monkeypatch.chdir(tmp_path)
calc = QChem(test_atoms)
Expand All @@ -240,7 +247,9 @@ def test_qchem_read_results_advanced(tmp_path, monkeypatch, test_atoms):
assert calc.results.get("hessian") is None


@pytest.mark.skipif(ob is None, reason="openbabel needed")
def test_qchem_read_results_freq(tmp_path, monkeypatch, test_atoms):
monkeypatch.chdir(tmp_path)
calc = QChem(test_atoms, job_type="freq")
monkeypatch.chdir(FILE_DIR / "examples" / "freq")
calc.read_results()
Expand All @@ -250,8 +259,8 @@ def test_qchem_read_results_freq(tmp_path, monkeypatch, test_atoms):
assert calc.prev_orbital_coeffs is not None
assert len(calc.results["hessian"]) == 42
assert len(calc.results["hessian"][0]) == 42
assert calc.results["qc_output"]["frequencies"][0] == -340.2
assert len(calc.results["qc_output"]["frequencies"]) == 36
assert len(calc.results["qc_output"]["frequency_mode_vectors"]) == 36
assert len(calc.results["qc_output"]["frequency_mode_vectors"][0]) == 14
assert len(calc.results["qc_output"]["frequency_mode_vectors"][0][0]) == 3
assert calc.results["taskdoc"]["output"]["frequencies"][0] == -340.2
assert len(calc.results["taskdoc"]["output"]["frequencies"]) == 36
assert len(calc.results["taskdoc"]["output"]["frequency_modes"]) == 36
assert len(calc.results["taskdoc"]["output"]["frequency_modes"][0]) == 14
assert len(calc.results["taskdoc"]["output"]["frequency_modes"][0][0]) == 3
19 changes: 5 additions & 14 deletions tests/core/recipes/qchem_recipes/test_qchem_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def test_static_job_v1(monkeypatch, tmp_path, test_atoms):
qcin = QCInput.from_file("mol.qin.gz")
ref_qcin = QCInput.from_file(str(QCHEM_DIR / "mol.qin.basic"))
qcinput_nearly_equal(qcin, ref_qcin)
qcinput_nearly_equal(ref_qcin, QCInput.from_dict(output["results"]["qc_input"]))
assert output["results"]["taskdoc"]


def test_static_job_v2(monkeypatch, tmp_path, test_atoms):
Expand Down Expand Up @@ -160,7 +160,7 @@ def test_static_job_v2(monkeypatch, tmp_path, test_atoms):
qcin = QCInput.from_file("mol.qin.gz")
ref_qcin = QCInput.from_file(str(QCHEM_DIR / "mol.qin.intermediate"))
qcinput_nearly_equal(qcin, ref_qcin)
qcinput_nearly_equal(ref_qcin, QCInput.from_dict(output["results"]["qc_input"]))
assert output["results"]["taskdoc"]


def test_static_job_v3(monkeypatch, tmp_path, test_atoms):
Expand All @@ -187,7 +187,7 @@ def test_static_job_v3(monkeypatch, tmp_path, test_atoms):
qcin = QCInput.from_file("mol.qin.gz")
ref_qcin = QCInput.from_file(str(QCHEM_DIR / "mol.qin.alternate"))
qcinput_nearly_equal(qcin, ref_qcin)
qcinput_nearly_equal(ref_qcin, QCInput.from_dict(output["results"]["qc_input"]))
assert output["results"]["taskdoc"]


def test_static_job_v4(monkeypatch, tmp_path, os_atoms):
Expand Down Expand Up @@ -236,7 +236,7 @@ def test_relax_job_v1(monkeypatch, tmp_path, test_atoms):
qcin = QCInput.from_file("mol.qin.gz")
ref_qcin = QCInput.from_file(str(QCHEM_DIR / "mol.qin.basic.sella_opt_iter1"))
qcinput_nearly_equal(qcin, ref_qcin)
assert len(output["results"]["qc_input"]) > 1
assert len(output["results"]["taskdoc"]["input"]) > 1


def test_relax_job_v2(monkeypatch, tmp_path, test_atoms):
Expand Down Expand Up @@ -326,16 +326,7 @@ def test_freq_job_v1(monkeypatch, tmp_path, test_atoms):
assert output["parameters"]["spin_multiplicity"] == 2
assert output["results"]["energy"] == pytest.approx(-605.6859554019 * units.Hartree)
assert output["results"].get("hessian") is not None
assert output["results"]["enthalpy"] == pytest.approx(
output["results"]["qc_output"]["total_enthalpy"] * (units.kcal / units.mol)
)
assert (
output["results"]["entropy"]
== output["results"]["qc_output"]["total_entropy"]
* 0.001
* units.kcal
/ units.mol
)
assert output["results"]["taskdoc"]["output"]["enthalpy"] is not None


def test_ts_job_v1(monkeypatch, tmp_path, test_atoms):
Expand Down
4 changes: 2 additions & 2 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
git+https://gitlab.com/ase/ase.git
cclib==1.8
custodian==2023.10.9
emmet-core==0.77.0
emmet-core==0.77.1
maggma==0.62.1
monty==2024.2.2
numpy==1.26.3
psutil==5.9.8
pydantic==2.6.1
pydantic-settings==2.1.0
pymatgen==2023.12.18
typer[all]==0.9.0
typer[all]==0.9.0

0 comments on commit 9a5a4dc

Please sign in to comment.