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

Update Q-Chem schema #1393

Merged
merged 23 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
67f2d93
Update Q-Chem schema
Andrew-S-Rosen Dec 20, 2023
d13cd24
style: format code with Black, isort and Prettier
deepsource-autofix[bot] Dec 20, 2023
7ffe45e
Merge branch 'main' into qchemschemaupdate
Andrew-S-Rosen Dec 29, 2023
9b85989
Merge branch 'main' into qchemschemaupdate
Andrew-S-Rosen Jan 1, 2024
5e4e64c
Merge branch 'main' into qchemschemaupdate
Andrew-S-Rosen Jan 12, 2024
48d1fb8
refactor: autofix issues in 1 file
deepsource-autofix[bot] Jan 25, 2024
cc4ab0d
Merge branch 'main' into qchemschemaupdate
Andrew-S-Rosen Jan 25, 2024
d8812ff
Update io.py
Andrew-S-Rosen Jan 25, 2024
ed59021
Update test_qchem_recipes.py
Andrew-S-Rosen Jan 25, 2024
10178b8
style: format code with Black, isort and Prettier
deepsource-autofix[bot] Jan 25, 2024
2782498
fix
Andrew-S-Rosen Jan 25, 2024
ac5c4e1
fix
Andrew-S-Rosen Jan 25, 2024
7d20601
Merge branch 'main' into qchemschemaupdate
Andrew-S-Rosen Jan 25, 2024
bce96c6
Update requirements.txt
Andrew-S-Rosen Feb 7, 2024
d86529c
Update pyproject.toml
Andrew-S-Rosen Feb 7, 2024
8b594c2
Merge branch 'main' into qchemschemaupdate
Andrew-S-Rosen Feb 7, 2024
056f939
Update io.py
Andrew-S-Rosen Feb 7, 2024
517f788
Update qchem.py
Andrew-S-Rosen Feb 7, 2024
77a0035
Update test_qchem_recipes.py
Andrew-S-Rosen Feb 7, 2024
f61f9c2
Update test_qchem_recipes.py
Andrew-S-Rosen Feb 7, 2024
466d945
Update test_qchem.py
Andrew-S-Rosen Feb 7, 2024
bbb2d24
Merge branch 'main' into qchemschemaupdate
Andrew-S-Rosen Feb 7, 2024
35c996f
Update io.py
Andrew-S-Rosen Feb 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading