diff --git a/.gitignore b/.gitignore index 6cdafcdcea..48fb61470e 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,10 @@ docs/reference/atomate2.* .DS_Store +# vim extensions +*.swp +*.swo + # see https://github.com/materialsproject/atomate2/issues/345 *.doctrees* diff --git a/src/atomate2/qchem/flows/__init__.py b/src/atomate2/qchem/flows/__init__.py new file mode 100644 index 0000000000..ee1c9c1dab --- /dev/null +++ b/src/atomate2/qchem/flows/__init__.py @@ -0,0 +1 @@ +"""Flows for running Q-Chem calculations.""" diff --git a/src/atomate2/qchem/flows/core.py b/src/atomate2/qchem/flows/core.py new file mode 100644 index 0000000000..4f7b89d7d1 --- /dev/null +++ b/src/atomate2/qchem/flows/core.py @@ -0,0 +1,225 @@ +"""Define core QChem flows.""" + +from __future__ import annotations + +from copy import deepcopy +from dataclasses import dataclass, field +from typing import TYPE_CHECKING + +from jobflow import Flow, Maker, Response, job + +from atomate2.qchem.jobs.core import FreqMaker, OptMaker + +if TYPE_CHECKING: + from pathlib import Path + + from jobflow import Job + from pymatgen.core.structure import Molecule + + from atomate2.qchem.jobs.base import BaseQCMaker + + +@dataclass +class DoubleOptMaker(Maker): + """ + Maker to perform a double Qchem relaxation. + + Parameters + ---------- + name : str + Name of the flows produced by this maker. + relax_maker1 : .BaseVaspMaker + Maker to use to generate the first relaxation. + relax_maker2 : .BaseVaspMaker + Maker to use to generate the second relaxation. + """ + + name: str = "double opt" + opt_maker1: BaseQCMaker | None = field(default_factory=OptMaker) + opt_maker2: BaseQCMaker = field(default_factory=OptMaker) + + def make(self, molecule: Molecule, prev_dir: str | Path | None = None) -> Flow: + """ + Create a flow with two chained molecular optimizations. + + Parameters + ---------- + molecule : .Molecule + A pymatgen Molecule object. + prev_dir : str or Path or None + A previous QChem calculation directory to copy output files from. + + Returns + ------- + Flow + A flow containing two geometric optimizations. + """ + jobs: list[Job] = [] + if self.opt_maker1: + # Run a pre-relaxation + opt1 = self.opt_maker1.make(molecule, prev_dir=prev_dir) + opt1.name += " 1" + jobs += [opt1] + molecule = opt1.output.optimized_molecule + prev_dir = opt1.output.dir_name + + opt2 = self.opt_maker2.make(molecule, prev_dir=prev_dir) + opt2.name += " 2" + jobs += [opt2] + + return Flow(jobs, output=opt2.output, name=self.name) + + @classmethod + def from_opt_maker(cls, opt_maker: BaseQCMaker) -> DoubleOptMaker: + """ + Instantiate the DoubleRelaxMaker with two relax makers of the same type. + + Parameters + ---------- + opt_maker : .BaseQCMaker + Maker to use to generate the first and second geometric optimizations. + """ + return cls(relax_maker1=deepcopy(opt_maker), relax_maker2=deepcopy(opt_maker)) + + +@dataclass +class FrequencyOptMaker(Maker): + """ + Maker to perform a frequency calculation after an optimization. + + Parameters + ---------- + name : str + Name of the flows produced by this maker. + opt_maker : .BaseQCMaker + Maker to use to generate the opt maker + freq_maker : .BaseQCMaker + Maker to use to generate the freq maker + """ + + name: str = "opt frequency" + opt_maker: BaseQCMaker = field(default_factory=OptMaker) + freq_maker: BaseQCMaker = field(default_factory=FreqMaker) + + def make(self, molecule: Molecule, prev_dir: str | Path | None = None) -> Flow: + """ + Create a flow with optimization followed by frequency calculation. + + Parameters + ---------- + molecule : .Molecule + A pymatgen Molecule object. + prev_dir : str or Path or None + A previous QChem calculation directory to copy output files from. + + Returns + ------- + Flow + A flow containing with optimization and frequency calculation. + """ + jobs: list[Job] = [] + opt = self.opt_maker.make(molecule, prev_dir=prev_dir) + opt.name = "Geometry Optimization" + jobs += [opt] + + freq = self.freq_maker.make( + molecule=opt.output.output.optimized_molecule, + prev_dir=opt.output.dir_name, + ) + freq.name = "Frequency Analysis" + jobs += [freq] + + return Flow( + jobs, output={"opt": opt.output, "freq": freq.output}, name=self.name + ) + + +@dataclass +class FrequencyOptFlatteningMaker(Maker): + """ + Maker to perform a frequency calculation after an optimization. + + Parameters + ---------- + name : str + Name of the flows produced by this maker. + opt_maker : .BaseQCMaker + Maker to use to generate the opt maker + freq_maker : .BaseQCMaker + Maker to use to generate the freq maker + """ + + name: str = "frequency flattening opt" + opt_maker: BaseQCMaker = field(default_factory=OptMaker) + freq_maker: BaseQCMaker = field(default_factory=FreqMaker) + scale: float = 1.0 + max_ffopt_runs: int = 5 + + @job + def make( + self, + molecule: Molecule, + mode: list | None = None, + lowest_freq: float = -1.0, + ffopt_runs: int = 0, + overwrite_inputs: dict | None = None, + prev_dir: str | Path | None = None, + ) -> Flow: + """ + Optimize geometry and perturb negative frequency modes. + + Parameters + ---------- + molecule : .Molecule + A pymatgen Molecule object. + prev_dir : str or Path or None + A previous QChem calculation directory to copy output files from. + + Returns + ------- + Flow + A flow containing with optimization and frequency calculation. + """ + mode = mode or [[0.0, 0.0, 0.0] for _ in range(len(molecule))] + + if overwrite_inputs is not None: + self.opt_maker.input_set_generator.overwrite_inputs = overwrite_inputs + self.freq_maker.input_set_generator.overwrite_inputs = overwrite_inputs + + new_flow = None + new_output = None + + if (lowest_freq < 0) and (ffopt_runs < self.max_ffopt_runs): + jobs: list[Job] = [] + + for idx in range(len(molecule)): + molecule.translate_sites( + indices=[idx], vector=[self.scale * v for v in mode[idx]] + ) + + opt = self.opt_maker.make(molecule, prev_dir=prev_dir) + opt.name = "Geometry Optimization" + jobs += [opt] + molecule = opt.output.output.optimized_molecule + + freq = self.freq_maker.make(molecule, prev_dir=prev_dir) + freq.name = f"Frequency Analysis {ffopt_runs + 1}" + jobs += [freq] + + recursive = self.make( + molecule, + mode=freq.output.output.frequency_modes[0], + lowest_freq=freq.output.output.frequencies[0], + ffopt_runs=ffopt_runs + 1, + prev_dir=prev_dir, + ) + new_flow = Flow([*jobs, recursive], output=recursive.output) + new_output = recursive.output + + elif ffopt_runs == 0: + freq = self.freq_maker.make(molecule, prev_dir=prev_dir) + freq.name = f"Frequency Analysis {ffopt_runs + 1}" + new_flow = [freq] + new_output = freq.output + + return Response(replace=new_flow, output=new_output) diff --git a/src/atomate2/qchem/jobs/base.py b/src/atomate2/qchem/jobs/base.py index d67322917c..4de1d9499e 100644 --- a/src/atomate2/qchem/jobs/base.py +++ b/src/atomate2/qchem/jobs/base.py @@ -2,6 +2,7 @@ from __future__ import annotations +import logging from dataclasses import dataclass, field from pathlib import Path from typing import TYPE_CHECKING, Callable @@ -19,6 +20,8 @@ if TYPE_CHECKING: from pymatgen.core.structure import Molecule +logger = logging.getLogger(__name__) + def qchem_job(method: Callable) -> job: """ @@ -94,10 +97,14 @@ class BaseQCMaker(Maker): task_document_kwargs: dict = field(default_factory=dict) stop_children_kwargs: dict = field(default_factory=dict) write_additional_data: dict = field(default_factory=dict) + task_type: str | None = None @qchem_job def make( - self, molecule: Molecule, prev_qchem_dir: str | Path | None = None + self, + molecule: Molecule, + prev_dir: str | Path | None = None, + prev_qchem_dir: str | Path | None = None, ) -> Response: """Run a QChem calculation. @@ -105,19 +112,36 @@ def make( ---------- molecule : Molecule A pymatgen molecule object. - prev_qchem_dir : str or Path or None + prev_dir : str or Path or None + A previous calculation directory to copy output files from. + prev_qchem_dir (deprecated): str or Path or None A previous QChem calculation directory to copy output files from. """ # copy previous inputs - from_prev = prev_qchem_dir is not None if prev_qchem_dir is not None: - copy_qchem_outputs(prev_qchem_dir, **self.copy_qchem_kwargs) + logger.warning( + "`prev_qchem_dir` will be deprecated in a future release. " + "Please use `prev_dir` instead." + ) + if prev_dir is not None: + logger.warning( + "You set both `prev_dir` and `prev_qchem_dir`, " + "only `prev_dir` will be used." + ) + else: + prev_dir = prev_qchem_dir + + if from_prev := (prev_dir is not None): + copy_qchem_outputs(prev_dir, **self.copy_qchem_kwargs) self.write_input_set_kwargs.setdefault("from_prev", from_prev) # write qchem input files # self.input_set_generator.get_input_set(molecule).write_inputs() self.input_set_generator.get_input_set(molecule) + self.input_set_generator.get_input_set(molecule).write_input( + directory=Path.cwd() + ) # write any additional data for filename, data in self.write_additional_data.items(): @@ -129,7 +153,7 @@ def make( # parse qchem outputs task_doc = TaskDoc.from_directory(Path.cwd(), **self.task_document_kwargs) # task_doc.task_label = self.name - task_doc.task_type = self.name + task_doc.task_type = self.name if self.task_type is None else self.task_type # decide whether child jobs should proceed stop_children = should_stop_children(task_doc, **self.stop_children_kwargs) diff --git a/src/atomate2/qchem/jobs/core.py b/src/atomate2/qchem/jobs/core.py index b0d7987d1f..2f297be4e4 100644 --- a/src/atomate2/qchem/jobs/core.py +++ b/src/atomate2/qchem/jobs/core.py @@ -197,6 +197,7 @@ class FreqMaker(BaseQCMaker): name: str = "frequency" input_set_generator: QCInputGenerator = field(default_factory=FreqSetGenerator) + task_type: str = "Frequency Analysis" @dataclass diff --git a/src/atomate2/qchem/run.py b/src/atomate2/qchem/run.py index 9010dc7b68..1f4d67709a 100644 --- a/src/atomate2/qchem/run.py +++ b/src/atomate2/qchem/run.py @@ -21,7 +21,7 @@ from emmet.core.qc_tasks import TaskDoc -_DEFAULT_HANDLERS = (QChemErrorHandler,) +_DEFAULT_HANDLERS = (QChemErrorHandler(),) logger = logging.getLogger(__name__) @@ -95,7 +95,6 @@ def run_qchem( scratch_dir=scratch_dir, **custodian_kwargs, ) - logger.info("Running QChem using custodian.") c.run() diff --git a/src/atomate2/qchem/sets/base.py b/src/atomate2/qchem/sets/base.py index 9592b5dfef..29eb04db7c 100644 --- a/src/atomate2/qchem/sets/base.py +++ b/src/atomate2/qchem/sets/base.py @@ -66,11 +66,13 @@ def write_input( inputs.update(self.optional_files) for key, val in inputs.items(): - if val is not None and (overwrite or not (directory / key).exists()): - with zopen(directory / key, "wt") as file: + inp_key = "mol.qin" if key == "Input_Dict" else f"{key}.qin" + if val is not None and (overwrite or not (directory / inp_key).exists()): + # should this be open instead of zopen? can QChem inputs be gzipped? + with zopen(directory / inp_key, "wt") as file: file.write(str(val)) - elif not overwrite and (directory / key).exists(): - raise FileExistsError(f"{directory / key} already exists.") + elif not overwrite and (directory / inp_key).exists(): + raise FileExistsError(f"{directory / inp_key} already exists.") @staticmethod def from_directory( @@ -91,9 +93,9 @@ def from_directory( inputs = {} for name, obj in objs.items(): - if (directory / name).exists(): - inputs[name.lower()] = obj.from_file(directory / name) - + file_path = directory / ("mol.qin" if name == "Input_Dict" else name) + if file_path.exists(): + inputs[name.lower()] = obj.from_file(file_path) optional_inputs = {} if optional_files is not None: for name, obj in optional_files.items(): diff --git a/src/atomate2/settings.py b/src/atomate2/settings.py index 64157f7b60..0f987a0cbf 100644 --- a/src/atomate2/settings.py +++ b/src/atomate2/settings.py @@ -220,7 +220,7 @@ class Atomate2Settings(BaseSettings): # QChem specific settings QCHEM_CMD: str = Field( - "qchem_std", description="Command to run standard version of qchem." + "qchem", description="Command to run standard version of qchem." ) QCHEM_CUSTODIAN_MAX_ERRORS: int = Field( diff --git a/tests/qchem/conftest.py b/tests/qchem/conftest.py index d20ea563da..e8ced094f7 100644 --- a/tests/qchem/conftest.py +++ b/tests/qchem/conftest.py @@ -7,6 +7,7 @@ import pytest from jobflow import CURRENT_JOB +from pymatgen.core import Molecule from pymatgen.io.qchem.inputs import QCInput from pytest import MonkeyPatch @@ -26,6 +27,18 @@ _FAKE_RUN_QCHEM_KWARGS: dict[str, dict] = {} +@pytest.fixture +def h2o_molecule(): + return Molecule( + coords=[ + [0.0000, 0.0000, 0.12124], + [-0.78304, -0.00000, -0.48495], + [0.78304, -0.00000, -0.48495], + ], + species=["O", "H", "H"], + ) + + @pytest.fixture(scope="session") def qchem_test_dir(test_dir): return test_dir / "qchem" @@ -169,6 +182,9 @@ def check_qin( user_qin_path = script_directory / "opt.qin.gz" elif job_name == "water_frequency": user_qin_path = script_directory / "freq.qin.gz" + else: + user_qin_path = Path("mol.qin") + user_qin = QCInput.from_file(user_qin_path) keys_to_check = ( diff --git a/tests/qchem/flows/test_core.py b/tests/qchem/flows/test_core.py new file mode 100644 index 0000000000..2fadffcb00 --- /dev/null +++ b/tests/qchem/flows/test_core.py @@ -0,0 +1,87 @@ +from pathlib import Path + +import pytest +from jobflow import run_locally + +from atomate2.qchem.flows.core import FrequencyOptFlatteningMaker, FrequencyOptMaker + +fake_run_qchem_kwargs = {} + + +def test_frequency_opt_maker(mock_qchem, clean_dir, qchem_test_dir, h2o_molecule): + ref_paths = { + "Geometry Optimization": Path(qchem_test_dir) + / "ffopt" + / "geometry_optimization", + "Frequency Analysis": Path(qchem_test_dir) / "ffopt" / "frequency_analysis_1", + } + mock_qchem(ref_paths, fake_run_qchem_kwargs) + + flow = FrequencyOptMaker().make(h2o_molecule) + responses = run_locally(flow, create_folders=True, ensure_success=True) + + output = {job.name: responses[job.uuid][1].output for job in flow} + + ref_total_energy = -76.346601 + assert output["Geometry Optimization"].output.final_energy == pytest.approx( + ref_total_energy, rel=1e-6 + ) + + assert output["Frequency Analysis"].output.final_energy == pytest.approx( + ref_total_energy, rel=1e-6 + ) + ref_freq = [1587.39, 3864.9, 3969.87] + assert all( + freq == pytest.approx(ref_freq[i], abs=1e-2) + for i, freq in enumerate(output["Frequency Analysis"].output.frequencies) + ) + assert ( + output["Geometry Optimization"].output.optimized_molecule + == output["Frequency Analysis"].output.initial_molecule + ) + assert output["Frequency Analysis"].output.optimized_molecule is None + + +def test_frequency_opt_flattening_maker( + mock_qchem, clean_dir, qchem_test_dir, h2o_molecule +): + ref_paths = { + k: Path(qchem_test_dir) / "ffopt" / f"{k.lower().replace(' ','_')}" + for k in ("Geometry Optimization", "Frequency Analysis 1") + } + + mock_qchem(ref_paths, fake_run_qchem_kwargs) + flow = FrequencyOptFlatteningMaker().make(h2o_molecule) + responses = run_locally(flow, create_folders=True, ensure_success=True) + + # first get job name / uuid pairs from dynamic flow + uuid_to_name = {} + for resp in responses.values(): + if replace_flow := getattr(resp[1], "replace", None): + uuid_to_name.update({job.uuid: job.name for job in replace_flow.jobs}) + + # then get job output + output = {} + for uuid, job_name in uuid_to_name.items(): + output[job_name] = responses[uuid][1].output + + ref_total_energy = -76.346601 + assert output["Geometry Optimization"].output.final_energy == pytest.approx( + ref_total_energy, rel=1e-6 + ) + + # because the initial frequency analysis has no negative frequencies, + # the workflow only performs one frequency analysis + assert output["Frequency Analysis 1"].output.final_energy == pytest.approx( + ref_total_energy, rel=1e-6 + ) + ref_freq = [1587.39, 3864.9, 3969.87] + assert all( + freq == pytest.approx(ref_freq[i], abs=1e-2) + for i, freq in enumerate(output["Frequency Analysis 1"].output.frequencies) + ) + assert ( + output["Geometry Optimization"].output.optimized_molecule + == output["Frequency Analysis 1"].output.initial_molecule + ) + assert output["Frequency Analysis 1"].output.optimized_molecule is None diff --git a/tests/qchem/jobs/H2O.xyz b/tests/qchem/jobs/H2O.xyz deleted file mode 100644 index 212ca2ea8f..0000000000 --- a/tests/qchem/jobs/H2O.xyz +++ /dev/null @@ -1,5 +0,0 @@ -3 - -O 0.00000 0.00000 0.12124 -H -0.78304 -0.00000 -0.48495 -H 0.78304 0.00000 -0.48495 diff --git a/tests/qchem/jobs/test_core.py b/tests/qchem/jobs/test_core.py index 75b5907cf5..e6bf0058cf 100644 --- a/tests/qchem/jobs/test_core.py +++ b/tests/qchem/jobs/test_core.py @@ -1,19 +1,11 @@ -from pathlib import Path - from emmet.core.qc_tasks import TaskDoc from jobflow import run_locally -from pymatgen.core.structure import Molecule from pytest import approx from atomate2.qchem.jobs.core import FreqMaker, OptMaker, SinglePointMaker -current_directory = Path(__file__).resolve().parent -file_name = current_directory / "H2O.xyz" - -H2O = Molecule.from_file(file_name) - -def test_single_point_maker(mock_qchem, clean_dir): +def test_single_point_maker(mock_qchem, clean_dir, h2o_molecule): # mapping from job name to directory containing test files ref_paths = {"single point": "water_single_point"} @@ -25,7 +17,7 @@ def test_single_point_maker(mock_qchem, clean_dir): mock_qchem(ref_paths, fake_run_qchem_kwargs) # generate job - job = SinglePointMaker().make(H2O) + job = SinglePointMaker().make(h2o_molecule) # run the flow or job and ensure that it finished running successfully responses = run_locally(job, create_folders=True, ensure_success=True) @@ -36,12 +28,12 @@ def test_single_point_maker(mock_qchem, clean_dir): assert output1.output.final_energy == approx(-76.4451488262) -def test_opt_maker(mock_qchem, clean_dir): +def test_opt_maker(mock_qchem, clean_dir, h2o_molecule): ref_paths = {"optimization": "water_optimization"} fake_run_qchem_kwargs = {} mock_qchem(ref_paths, fake_run_qchem_kwargs) - job = OptMaker().make(H2O) + job = OptMaker().make(h2o_molecule) responses = run_locally(job, create_folders=True, ensure_success=True) opt_geometry = { @@ -83,12 +75,12 @@ def test_opt_maker(mock_qchem, clean_dir): assert output1.output.final_energy == approx(-76.450849061819) -def test_freq(mock_qchem, clean_dir): +def test_freq(mock_qchem, clean_dir, h2o_molecule): ref_paths = {"frequency": "water_frequency"} fake_run_qchem_kwargs = {} mock_qchem(ref_paths, fake_run_qchem_kwargs) - job = FreqMaker().make(H2O) + job = FreqMaker().make(h2o_molecule) responses = run_locally(job, create_folders=True, ensure_success=True) ref_freqs = [1643.03, 3446.82, 3524.32] diff --git a/tests/test_data/qchem/ffopt/frequency_analysis_1/inputs/mol.qin.gz b/tests/test_data/qchem/ffopt/frequency_analysis_1/inputs/mol.qin.gz new file mode 100644 index 0000000000..620b6644ba Binary files /dev/null and b/tests/test_data/qchem/ffopt/frequency_analysis_1/inputs/mol.qin.gz differ diff --git a/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/132.0.gz b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/132.0.gz new file mode 100644 index 0000000000..211246832e Binary files /dev/null and b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/132.0.gz differ diff --git a/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/53.0.gz b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/53.0.gz new file mode 100644 index 0000000000..c3ad1d1f67 Binary files /dev/null and b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/53.0.gz differ diff --git a/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/HESS.gz b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/HESS.gz new file mode 100644 index 0000000000..0904070344 Binary files /dev/null and b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/HESS.gz differ diff --git a/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/mol.qclog.gz b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/mol.qclog.gz new file mode 100644 index 0000000000..b622a3d580 Binary files /dev/null and b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/mol.qclog.gz differ diff --git a/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/mol.qin.gz b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/mol.qin.gz new file mode 100644 index 0000000000..66fe44ba9d Binary files /dev/null and b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/mol.qin.gz differ diff --git a/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/mol.qin.orig.gz b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/mol.qin.orig.gz new file mode 100644 index 0000000000..f287988a88 Binary files /dev/null and b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/mol.qin.orig.gz differ diff --git a/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/mol.qout.gz b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/mol.qout.gz new file mode 100644 index 0000000000..28b9364ffa Binary files /dev/null and b/tests/test_data/qchem/ffopt/frequency_analysis_1/outputs/mol.qout.gz differ diff --git a/tests/test_data/qchem/ffopt/geometry_optimization/inputs/mol.qin.gz b/tests/test_data/qchem/ffopt/geometry_optimization/inputs/mol.qin.gz new file mode 100644 index 0000000000..c7f5a8b83a Binary files /dev/null and b/tests/test_data/qchem/ffopt/geometry_optimization/inputs/mol.qin.gz differ diff --git a/tests/test_data/qchem/ffopt/geometry_optimization/outputs/131.0.gz b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/131.0.gz new file mode 100644 index 0000000000..397a93c774 Binary files /dev/null and b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/131.0.gz differ diff --git a/tests/test_data/qchem/ffopt/geometry_optimization/outputs/53.0.gz b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/53.0.gz new file mode 100644 index 0000000000..9f1dc737fe Binary files /dev/null and b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/53.0.gz differ diff --git a/tests/test_data/qchem/ffopt/geometry_optimization/outputs/GRAD.gz b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/GRAD.gz new file mode 100644 index 0000000000..fae21605fa Binary files /dev/null and b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/GRAD.gz differ diff --git a/tests/test_data/qchem/ffopt/geometry_optimization/outputs/mol.qclog.gz b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/mol.qclog.gz new file mode 100644 index 0000000000..0786e012e1 Binary files /dev/null and b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/mol.qclog.gz differ diff --git a/tests/test_data/qchem/ffopt/geometry_optimization/outputs/mol.qin.gz b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/mol.qin.gz new file mode 100644 index 0000000000..c7f5a8b83a Binary files /dev/null and b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/mol.qin.gz differ diff --git a/tests/test_data/qchem/ffopt/geometry_optimization/outputs/mol.qin.orig.gz b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/mol.qin.orig.gz new file mode 100644 index 0000000000..4c0e2586b2 Binary files /dev/null and b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/mol.qin.orig.gz differ diff --git a/tests/test_data/qchem/ffopt/geometry_optimization/outputs/mol.qout.gz b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/mol.qout.gz new file mode 100644 index 0000000000..8a9213943b Binary files /dev/null and b/tests/test_data/qchem/ffopt/geometry_optimization/outputs/mol.qout.gz differ