From 566db4abcc622a53ce3eb88a7ef8c724f9346aac Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Wed, 5 Jun 2024 19:05:47 -0400 Subject: [PATCH] geometric and tests --- qcengine/procedures/base.py | 3 +- qcengine/procedures/geometric.py | 65 +++++++++++++ qcengine/procedures/optking.py | 4 +- qcengine/procedures/qcmanybody.py | 5 +- qcengine/programs/cfour/harvester.py | 2 +- qcengine/testing.py | 2 + .../{test_mbe_ne2.py => test_qcmanybody.py} | 95 +++++++++++++++++-- 7 files changed, 162 insertions(+), 14 deletions(-) rename qcengine/tests/{test_mbe_ne2.py => test_qcmanybody.py} (71%) diff --git a/qcengine/procedures/base.py b/qcengine/procedures/base.py index 72b4b3bf2..0c2c23e0c 100644 --- a/qcengine/procedures/base.py +++ b/qcengine/procedures/base.py @@ -6,7 +6,7 @@ from ..exceptions import InputError, ResourceError from .berny import BernyProcedure -from .geometric import GeometricProcedure +from .geometric import GeometricProcedure, GenGeometricProcedure from .qcmanybody import QCManyBodyProcedure from .nwchem_opt import NWChemDriverProcedure from .optking import OptKingProcedure, GenOptKingProcedure @@ -68,6 +68,7 @@ def list_available_procedures() -> Set[str]: register_procedure(GeometricProcedure()) +register_procedure(GenGeometricProcedure()) register_procedure(OptKingProcedure()) register_procedure(GenOptKingProcedure()) register_procedure(BernyProcedure()) diff --git a/qcengine/procedures/geometric.py b/qcengine/procedures/geometric.py index 59bc26c86..efb60446d 100644 --- a/qcengine/procedures/geometric.py +++ b/qcengine/procedures/geometric.py @@ -68,3 +68,68 @@ def compute(self, input_model: "OptimizationInput", config: "TaskConfig") -> "Op output_data = OptimizationResult(**output_data) return output_data + + +class GenGeometricProcedure(GeometricProcedure): + +# # note that "procedure" value below not used + _defaults = {"name": "GenGeometric", "procedure": "genoptimization"} + + version_cache: Dict[str, str] = {} + + def found(self, raise_error: bool = False) -> bool: + qc = which_import( + "geometric", + return_bool=True, + raise_error=raise_error, + raise_msg="Please install via `conda install geometric -c conda-forge`.", + ) + dep = which_import( + "qcmanybody", + return_bool=True, + raise_error=raise_error, + raise_msg="For GenGeometric harness, please install via `conda install qcmanybody -c conda-forge`.", + ) + + return qc and dep + + def build_input_model( + self, data: Union[Dict[str, Any], "GeneralizedOptimizationInput"] + ) -> "GeneralizedOptimizationInput": + from qcmanybody.models.generalized_optimization import GeneralizedOptimizationInput + + return self._build_model(data, GeneralizedOptimizationInput) + + def compute( + self, input_model: "GeneralizedOptimizationInput", config: "TaskConfig" + ) -> "GeneralizedOptimizationResult": + self.found(raise_error=True) + + import geometric + from qcmanybody.models.generalized_optimization import GeneralizedOptimizationResult + + input_data = input_model.dict() + + # Temporary patch for geomeTRIC + input_data["initial_molecule"]["symbols"] = list(input_data["initial_molecule"]["symbols"]) + + # Set retries to two if zero while respecting local_config + local_config = config.dict() + local_config["retries"] = local_config.get("retries", 2) or 2 + input_data["input_specification"]["extras"]["_qcengine_local_config"] = local_config + + # Run the program + output_data = geometric.run_json.geometric_run_json(input_data) + + output_data["provenance"] = { + "creator": "geomeTRIC", + "routine": "geometric.run_json.geometric_run_json", + "version": geometric.__version__, + } + + output_data["schema_name"] = "qcschema_generalizedoptimizationresult" + output_data["input_specification"]["extras"].pop("_qcengine_local_config", None) + if output_data["success"]: + output_data = GeneralizedOptimizationResult(**output_data) + + return output_data \ No newline at end of file diff --git a/qcengine/procedures/optking.py b/qcengine/procedures/optking.py index 1886cf852..a9a4ac574 100644 --- a/qcengine/procedures/optking.py +++ b/qcengine/procedures/optking.py @@ -106,13 +106,13 @@ def compute( # Set retries to two if zero while respecting local_config local_config = config.dict() local_config["retries"] = local_config.get("retries", 2) or 2 - # TODO input_data["input_specification"]["extras"]["_qcengine_local_config"] = local_config + input_data["input_specification"]["extras"]["_qcengine_local_config"] = local_config # Run the program output_data = optking.optwrapper.optimize_qcengine(input_data) output_data["schema_name"] = "qcschema_generalizedoptimizationresult" - # TODO output_data["input_specification"]["extras"].pop("_qcengine_local_config", None) + output_data["input_specification"]["extras"].pop("_qcengine_local_config", None) if output_data["success"]: output_data = GeneralizedOptimizationResult(**output_data) diff --git a/qcengine/procedures/qcmanybody.py b/qcengine/procedures/qcmanybody.py index 46d37e044..b663bb242 100644 --- a/qcengine/procedures/qcmanybody.py +++ b/qcengine/procedures/qcmanybody.py @@ -3,7 +3,6 @@ from qcelemental.util import safe_version, which_import from .model import ProcedureHarness -from ..exceptions import UnknownError if TYPE_CHECKING: from ..config import TaskConfig @@ -45,8 +44,8 @@ def get_version(self) -> str: return self.version_cache[which_prog] def compute(self, input_model: "ManyBodyInput", config: "TaskConfig") -> "ManyBodyResult": - from qcmanybody.qcng_computer import ManyBodyComputerQCNG + from qcmanybody import ManyBodyComputer - output_model = ManyBodyComputerQCNG.from_manybodyinput(input_model) + output_model = ManyBodyComputer.from_manybodyinput(input_model) return output_model diff --git a/qcengine/programs/cfour/harvester.py b/qcengine/programs/cfour/harvester.py index 773d6d3d3..d6dcc3da6 100644 --- a/qcengine/programs/cfour/harvester.py +++ b/qcengine/programs/cfour/harvester.py @@ -1004,7 +1004,7 @@ def harvest_outfile_pass(outtext): # Process atom geometry mobj = re.search(r"^\s+" + r"@GETXYZ-I, 1 atoms read from ZMAT." + r"\s*$", outtext, re.MULTILINE) mobj2 = re.search( - r"^([A-Z]+)#1" + r"\s+" + NUMBER + r"\s+" + NUMBER + r"\s+" + NUMBER + r"\s*$", outtext, re.MULTILINE + r"^\s*([A-Z]+)" + r"\s*" + r"#1" + r"\s+" + NUMBER + r"\s+" + NUMBER + r"\s+" + NUMBER + r"\s*$", outtext, re.MULTILINE ) if mobj and mobj2: logger.debug("matched atom2") # unsavory for when atom never printed except for basis file diff --git a/qcengine/testing.py b/qcengine/testing.py index de17af9db..e71b7a55a 100644 --- a/qcengine/testing.py +++ b/qcengine/testing.py @@ -164,6 +164,7 @@ def get_job(self): "mctc-gcp": is_program_new_enough("mctc-gcp", "2.3.0"), "gcp": which("gcp", return_bool=True), "geometric": which_import("geometric", return_bool=True), + "geometric_genopt": is_program_new_enough("geometric", "1.1.0"), "berny": which_import("berny", return_bool=True), "mdi": is_mdi_new_enough("1.2"), "molpro": is_program_new_enough("molpro", "2018.1"), @@ -171,6 +172,7 @@ def get_job(self): "mp2d": which("mp2d", return_bool=True), "nwchem": which("nwchem", return_bool=True), "optking": which_import("optking", return_bool=True), + "optking_genopt": is_program_new_enough("optking", "0.3.0"), "psi4": is_program_new_enough("psi4", "1.2"), "psi4_runqcsk": is_program_new_enough("psi4", "1.4a2.dev160"), "psi4_mp2qcsk": is_program_new_enough("psi4", "1.4a2.dev580"), diff --git a/qcengine/tests/test_mbe_ne2.py b/qcengine/tests/test_qcmanybody.py similarity index 71% rename from qcengine/tests/test_mbe_ne2.py rename to qcengine/tests/test_qcmanybody.py index 6a073c5e1..78a61c1d2 100644 --- a/qcengine/tests/test_mbe_ne2.py +++ b/qcengine/tests/test_qcmanybody.py @@ -20,7 +20,7 @@ pytest.param("psi4", marks=using("psi4")), ], ) -def test_tu6_cp_ne2(qcprog): +def test_bsse_ene_tu6_cp_ne2(qcprog): """ from https://github.com/psi4/psi4/blob/master/tests/tu6-cp-ne2/input.dat Example potential energy surface scan and CP-correction for Ne2 @@ -136,14 +136,18 @@ def test_mbe_error(): "optimizer,bsse_type,sio", [ pytest.param("optking", "none", None, marks=using("optking")), - pytest.param("genoptking", "none", None, marks=using("optking")), - pytest.param("genoptking", "nocp", False, marks=using("optking")), - # pytest.param("genoptking", "nocp", True, marks=using("optking")), - # pytest.param("genoptking", "cp", False, marks=using("optking")), - pytest.param("genoptking", "cp", True, marks=using("optking")), + pytest.param("genoptking", "none", None, marks=using("optking_genopt")), + pytest.param("genoptking", "nocp", False, marks=using("optking_genopt")), + # pytest.param("genoptking", "nocp", True, marks=using("optking_genopt")), + # pytest.param("genoptking", "cp", False, marks=using("optking_genopt")), + + pytest.param("geometric", "none", None, marks=using("geometric")), + pytest.param("gengeometric", "none", None, marks=using("geometric_genopt")), + pytest.param("gengeometric", "nocp", False, marks=using("geometric_genopt")), + pytest.param("gengeometric", "cp", True, marks=using("geometric_genopt")), ], ) -def test_optimization_qcmanybody(optimizer, bsse_type, sio): +def test_bsse_opt_hf_trimer(optimizer, bsse_type, sio): initial_molecule = Molecule.from_data( """ @@ -237,3 +241,80 @@ def test_optimization_qcmanybody(optimizer, bsse_type, sio): assert ( ret.trajectory[-1].component_results['["(auto)", [1, 2, 3], [1, 2, 3]]'].stdout is None ), f"atomic protocol did not take" + + +@using("qcmanybody") +@pytest.mark.parametrize("bsse_type", ["mbe", "ssfc"]) # aka nocp, cp +@pytest.mark.parametrize("qcprog,qc_keywords", [ + pytest.param("psi4", {}, marks=using("psi4")), + pytest.param("cfour", {}, marks=using("cfour")), + pytest.param("nwchem", {}, marks=using("nwchem")), +]) +@pytest.mark.parametrize("optimizer,opt_keywords", [ + pytest.param("optking", {}, marks=using("optking_genopt")), + pytest.param("geometric", {"convergence_set": "interfrag_tight", "maxiter": 15}, marks=using("geometric_genopt")), +]) +def test_bsse_opt_lif_dimer(optimizer, opt_keywords, bsse_type, qcprog, qc_keywords): + # in geomeTRIC: test_lif_bsse + + lif = { + "symbols": ["Li", "F"], + "geometry": [0, 0, 0, 0, 0, 3], + "fragments": [[0], [1]], + "fragment_charges": [+1, -1], + } + + mbe_spec = { + "schema_name": "qcschema_manybodyspecification", + "specification": { + "model": { + "method": "hf", + "basis": "6-31G", + }, + "driver": "energy", + "program": qcprog, + "keywords": qc_keywords, + "protocols": { + "stdout": False, + }, + }, + "keywords": { + "bsse_type": bsse_type, + "supersystem_ie_only": True, + }, + "protocols": { + "component_results": "all", + }, + "driver": "energy", + } + + opt_data = { + "initial_molecule": lif, + "input_specification": mbe_spec, + "keywords": { + "program": "nonsense", + **opt_keywords, + }, + "protocols": { + "trajectory": "final", + }, + } + + ret = qcng.compute_procedure(opt_data, "gen" + optimizer, raise_error=True) + + # printing will show up if job fails + import pprint + pprint.pprint(ret.dict(), width=200) + + assert ret.success + + assert len(ret.trajectory[0].component_properties) == (5 if bsse_type == "ssfc" else 3) + + assert ret.provenance.creator.lower() == optimizer + assert ret.trajectory[0].provenance.creator == "QCManyBody" + atres = list(ret.trajectory[0].component_results.values())[0] + assert atres.provenance.creator.lower() == qcprog + + Rlif = ret.final_molecule.measure([0, 1]) + Rref = 3.016 if bsse_type == "ssfc" else 2.969 + assert compare_values(Rlif, Rref, "bond length", atol=1.e-3) \ No newline at end of file