From 551c9db58eff78de3cfb89cd02c167504b3dbfea Mon Sep 17 00:00:00 2001 From: Jinzhe Zeng Date: Fri, 16 Dec 2022 06:52:40 -0500 Subject: [PATCH 1/5] add gaussian Signed-off-by: Jinzhe Zeng --- dpgen2/fp/__init__.py | 12 +++- dpgen2/fp/gaussian.py | 158 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 dpgen2/fp/gaussian.py diff --git a/dpgen2/fp/__init__.py b/dpgen2/fp/__init__.py index 70135a33..9b5e6a91 100644 --- a/dpgen2/fp/__init__.py +++ b/dpgen2/fp/__init__.py @@ -3,11 +3,21 @@ PrepVasp, RunVasp, ) +from .gaussian import ( + GaussianInputs, + PrepGaussian, + RunGaussian, +) fp_styles = { "vasp" : { "inputs" : VaspInputs, "prep" : PrepVasp, "run" : RunVasp, - } + }, + "gaussian" : { + "inputs" : GaussianInputs, + "prep" : PrepGaussian, + "run" : RunGaussian, + }, } diff --git a/dpgen2/fp/gaussian.py b/dpgen2/fp/gaussian.py new file mode 100644 index 00000000..f3754411 --- /dev/null +++ b/dpgen2/fp/gaussian.py @@ -0,0 +1,158 @@ +"""Prep and Run Gaussian tasks.""" +from dflow.python import ( + TransientError, +) +from typing import ( + Tuple, + List, + Any, +) +import dpdata +from dargs import ( + dargs, + Argument, +) + +from .prep_fp import PrepFp +from .run_fp import RunFp +from dpgen2.constants import ( + fp_default_out_data_name, +) +from dpgen2.utils.run_command import run_command + +# global static variables +gaussian_input_name = 'task.gjf' +# this output name is generated by Gaussian +gaussian_output_name = 'task.log' + + +class GaussianInputs: + @staticmethod + def args() -> List[Argument]: + r"""The arguments of the GaussianInputs class.""" + doc_keywords = "Gaussian keywords, e.g. force b3lyp/6-31g**. If a list, run multiple steps." + doc_multiplicity = ( + "spin multiplicity state. It can be a number. If auto, multiplicity will be detected " + "automatically, with the following rules:\n\n" + "fragment_guesses=True multiplicity will +1 for each radical, and +2 for each oxygen molecule\n\n" + "fragment_guesses=False multiplicity will be 1 or 2, but +2 for each oxygen molecule." + ) + doc_charge = "molecule charge. Only used when charge is not provided by the system" + doc_basis_set = "custom basis set" + doc_keywords_high_multiplicity = ( + "keywords for points with multiple raicals. multiplicity should be auto. " + "If not set, fallback to normal keywords" + ) + doc_fragment_guesses = ( + "initial guess generated from fragment guesses. If True, multiplicity should be auto" + ) + doc_nproc = "Number of CPUs to use" + + return [ + Argument('keywords', [str, list], optional=False, doc=doc_keywords), + Argument('multiplicity', [int, str], optional=True, default="auto", doc=doc_multiplicity), + Argument('charge', int, optional=True, default=0, doc=doc_charge), + Argument('basis_set', str, optional=True, doc=doc_basis_set), + Argument('keywords_high_multiplicity', str, optional=True, doc=doc_keywords_high_multiplicity), + Argument('fragment_guesses', bool, optional=True, default=False, doc=doc_fragment_guesses), + Argument('nproc', int, optional=True, default=1, doc=doc_nproc), + ] + + def __init__(self, **kwargs: Any): + self.data = kwargs + + +class PrepGaussian(PrepFp): + def prep_task( + self, + conf_frame: dpdata.System, + inputs: GaussianInputs, + ): + r"""Define how one Gaussian task is prepared. + + Parameters + ---------- + conf_frame : dpdata.System + One frame of configuration in the dpdata format. + inputs: GaussianInputs + The GaussianInputs object handels all other input files of the task. + """ + + conf_frame.to('gaussian/gjf', gaussian_input_name, **inputs.data) + + +class RunGaussian(RunFp): + def input_files(self) -> List[str]: + r"""The mandatory input files to run a Gaussian task. + + Returns + ------- + files: List[str] + A list of madatory input files names. + + """ + return [gaussian_input_name] + + def optional_input_files(self) -> List[str]: + r"""The optional input files to run a Gaussian task. + + Returns + ------- + files: List[str] + A list of optional input files names. + + """ + return [] + + def run_task( + self, + command : str, + out: str, + ) -> Tuple[str, str]: + r"""Defines how one FP task runs + + Parameters + ---------- + command: str + The command of running gaussian task + out: str + The name of the output data file. + + Returns + ------- + out_name: str + The file name of the output data in the dpdata.LabeledSystem format. + log_name: str + The file name of the log. + """ + # run gaussian + command = ' '.join([command, gaussian_input_name]) + ret, out, err = run_command(command, shell=True) + if ret != 0: + raise TransientError( + 'gaussian failed\n', + 'out msg', out, '\n', + 'err msg', err, '\n' + ) + # convert the output to deepmd/npy format + sys = dpdata.LabeledSystem(gaussian_output_name, fmt='gaussian/log') + sys.to('deepmd/npy', out) + return out, gaussian_output_name + + + @staticmethod + def args() -> List[dargs.Argument]: + r"""The argument definition of the `run_task` method. + + Returns + ------- + arguments: List[dargs.Argument] + List of dargs.Argument defines the arguments of `run_task` method. + """ + + doc_gaussian_cmd = "The command of Gaussian" + doc_gaussian_out = "The output dir name of labeled data. In `deepmd/npy` format provided by `dpdata`." + return [ + Argument("command", str, optional=True, default='g16', doc=doc_gaussian_cmd), + Argument("out", str, optional=True, default=fp_default_out_data_name, doc=doc_gaussian_out), + ] From 90c8ecfc6225e57872f1fef08dc7683859384377 Mon Sep 17 00:00:00 2001 From: Jinzhe Zeng Date: Wed, 21 Dec 2022 07:26:19 -0500 Subject: [PATCH 2/5] add tests Signed-off-by: Jinzhe Zeng --- .github/workflows/test.yml | 2 +- dpgen2/fp/gaussian.py | 8 +++--- pyproject.toml | 3 +++ tests/test_prep_run_gaussian.py | 48 +++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 tests/test_prep_run_gaussian.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9b619d90..8bbc68ab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - pip install -e . + pip install -e .[test] pip install mock coverage - name: Test run: SKIP_UT_WITH_DFLOW=0 DFLOW_DEBUG=1 coverage run --source=./dpgen2 -m unittest && coverage report diff --git a/dpgen2/fp/gaussian.py b/dpgen2/fp/gaussian.py index f3754411..e48a7049 100644 --- a/dpgen2/fp/gaussian.py +++ b/dpgen2/fp/gaussian.py @@ -107,7 +107,7 @@ def optional_input_files(self) -> List[str]: def run_task( self, command : str, - out: str, + out_name: str, ) -> Tuple[str, str]: r"""Defines how one FP task runs @@ -115,7 +115,7 @@ def run_task( ---------- command: str The command of running gaussian task - out: str + out_name: str The name of the output data file. Returns @@ -136,8 +136,8 @@ def run_task( ) # convert the output to deepmd/npy format sys = dpdata.LabeledSystem(gaussian_output_name, fmt='gaussian/log') - sys.to('deepmd/npy', out) - return out, gaussian_output_name + sys.to('deepmd/npy', out_name) + return out_name, gaussian_output_name @staticmethod diff --git a/pyproject.toml b/pyproject.toml index 2b4361eb..6c4dba27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,9 @@ docs = [ 'sphinx-argparse', "dargs>=0.3.1", ] +test = [ + 'fakegaussian>=0.0.3', +] [tool.setuptools.packages.find] include = ["dpgen2*"] diff --git a/tests/test_prep_run_gaussian.py b/tests/test_prep_run_gaussian.py new file mode 100644 index 00000000..7b278440 --- /dev/null +++ b/tests/test_prep_run_gaussian.py @@ -0,0 +1,48 @@ +import numpy as np +import unittest + +from dpgen2.fp.gaussian import ( + GaussianInputs, + PrepGaussian, + RunGaussian, + dpdata, +) +from dargs import Argument + +class TestPrepGaussian(unittest.TestCase): + def test_prep_gaussian(self): + inputs = GaussianInputs( + keywords="force b3lyp/6-31g*", + ) + ta = GaussianInputs.args() + base = Argument("base", dict, ta) + data = base.normalize_value(inputs.data, trim_pattern="_*") + base.check_value(data, strict=True) + system = dpdata.LabeledSystem(data={ + 'atom_names': ['H'], + 'atom_numbs': [1], + 'atom_types': np.zeros(1, dtype=int), + 'cells': np.eye(3).reshape(1, 3, 3), + 'coords': np.zeros((1, 1, 3)), + 'energies': np.zeros(1), + 'forces': np.zeros((1, 1, 3)), + 'orig': np.zeros(3), + 'nopbc': True, + }) + prep_gaussian = PrepGaussian() + prep_gaussian.prep_task( + conf_frame=system, + inputs=inputs, + ) + + +class TestRunGaussian(unittest.TestCase): + def test_run_gaussian(self): + run_gaussian = RunGaussian() + output = 'mock_output' + out_name, log_name = run_gaussian.run_task( + 'g16', + output, + ) + assert out_name == output + assert log_name == 'task.log' From 0b885d690d48e7ab353c89c7de892df410f40fba Mon Sep 17 00:00:00 2001 From: Jinzhe Zeng Date: Wed, 21 Dec 2022 16:18:16 -0500 Subject: [PATCH 3/5] fix tests Signed-off-by: Jinzhe Zeng --- tests/test_prep_run_gaussian.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/test_prep_run_gaussian.py b/tests/test_prep_run_gaussian.py index 7b278440..d086d5ed 100644 --- a/tests/test_prep_run_gaussian.py +++ b/tests/test_prep_run_gaussian.py @@ -6,6 +6,8 @@ PrepGaussian, RunGaussian, dpdata, + gaussian_input_name, + gaussian_output_name, ) from dargs import Argument @@ -13,6 +15,7 @@ class TestPrepGaussian(unittest.TestCase): def test_prep_gaussian(self): inputs = GaussianInputs( keywords="force b3lyp/6-31g*", + multicity=1, ) ta = GaussianInputs.args() base = Argument("base", dict, ta) @@ -38,6 +41,21 @@ def test_prep_gaussian(self): class TestRunGaussian(unittest.TestCase): def test_run_gaussian(self): + dpdata.LabeledSystem(data={ + 'atom_names': ['H'], + 'atom_numbs': [1], + 'atom_types': np.zeros(1, dtype=int), + 'cells': np.eye(3).reshape(1, 3, 3), + 'coords': np.zeros((1, 1, 3)), + 'energies': np.zeros(1), + 'forces': np.zeros((1, 1, 3)), + 'orig': np.zeros(3), + 'nopbc': True, + }).to_gaussian_gjf( + gaussian_input_name, + keywords="force b3lyp/6-31g*", + multicity=1 + ) run_gaussian = RunGaussian() output = 'mock_output' out_name, log_name = run_gaussian.run_task( @@ -45,4 +63,4 @@ def test_run_gaussian(self): output, ) assert out_name == output - assert log_name == 'task.log' + assert log_name == gaussian_output_name From 96557ec0844cdc52dbc1d6ffbdf7c974c811e8f0 Mon Sep 17 00:00:00 2001 From: Jinzhe Zeng Date: Wed, 21 Dec 2022 17:18:38 -0500 Subject: [PATCH 4/5] fix typo --- tests/test_prep_run_gaussian.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_prep_run_gaussian.py b/tests/test_prep_run_gaussian.py index d086d5ed..cab45d2f 100644 --- a/tests/test_prep_run_gaussian.py +++ b/tests/test_prep_run_gaussian.py @@ -15,7 +15,7 @@ class TestPrepGaussian(unittest.TestCase): def test_prep_gaussian(self): inputs = GaussianInputs( keywords="force b3lyp/6-31g*", - multicity=1, + multiplicity=1, ) ta = GaussianInputs.args() base = Argument("base", dict, ta) @@ -54,7 +54,7 @@ def test_run_gaussian(self): }).to_gaussian_gjf( gaussian_input_name, keywords="force b3lyp/6-31g*", - multicity=1 + multiplicity=1 ) run_gaussian = RunGaussian() output = 'mock_output' From 9fc7ec18c7b62c8a3ef81a15f71ad26fa3da31dd Mon Sep 17 00:00:00 2001 From: Jinzhe Zeng Date: Wed, 21 Dec 2022 23:41:44 -0500 Subject: [PATCH 5/5] add assert --- tests/test_prep_run_gaussian.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_prep_run_gaussian.py b/tests/test_prep_run_gaussian.py index cab45d2f..f6aff1fc 100644 --- a/tests/test_prep_run_gaussian.py +++ b/tests/test_prep_run_gaussian.py @@ -1,5 +1,6 @@ import numpy as np import unittest +from pathlib import Path from dpgen2.fp.gaussian import ( GaussianInputs, @@ -37,6 +38,7 @@ def test_prep_gaussian(self): conf_frame=system, inputs=inputs, ) + assert Path(gaussian_input_name).exists() class TestRunGaussian(unittest.TestCase):