From dc8fd035e4ae114a807fe6bd81f13501d2e278b4 Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Sun, 5 May 2024 19:03:53 -0400 Subject: [PATCH 1/2] work wih qcmanybody --- optking/compute_wrappers.py | 20 +++++++++++++++++--- optking/optwrapper.py | 14 +++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/optking/compute_wrappers.py b/optking/compute_wrappers.py index 0355700..785b7f3 100644 --- a/optking/compute_wrappers.py +++ b/optking/compute_wrappers.py @@ -60,6 +60,13 @@ def generate_schema_input(self, driver): return inp + def generate_schema_input_for_procedure(self, driver): + molecule = Molecule(**self.molecule) + mbspec = self.keywords + mbspec["driver"] = driver + + return {"molecule": molecule, "specification": mbspec} + def _compute(self, driver): """Abstract style method for child classes""" pass @@ -149,8 +156,6 @@ def _compute(self, driver): import qcengine - inp = self.generate_schema_input(driver) - local_options = {} if self.program == "psi4": import psi4 @@ -158,7 +163,16 @@ def _compute(self, driver): local_options["memory"] = psi4.core.get_memory() / 1000000000 local_options["ncores"] = psi4.core.get_num_threads() - ret = qcengine.compute(inp, self.program, True, local_options) + if self.model == "(proc_spec_in_options)": + logger.debug("QCEngineComputer.path: ManyBody") + inp = self.generate_schema_input_for_procedure(driver) + ret = qcengine.compute_procedure(inp, "qcmanybody", True, local_options) + + else: + logger.debug("QCEngineComputer.path: Atomic") + inp = self.generate_schema_input(driver) + ret = qcengine.compute(inp, self.program, True, local_options) + return ret diff --git a/optking/optwrapper.py b/optking/optwrapper.py index f93ea3a..adb5bd5 100644 --- a/optking/optwrapper.py +++ b/optking/optwrapper.py @@ -201,9 +201,17 @@ def make_computer(opt_input: dict, computer_type): # This gets updated so it shouldn't be a reference molecule = copy.deepcopy(opt_input["initial_molecule"]) - qc_input = opt_input["input_specification"] - options = qc_input["keywords"] - model = qc_input["model"] + + # Sorting by spec_schema_name isn't foolproof b/c opt_input might not be a + # constructed model at this point if it's not arriving through QCEngine. + spec_schema_name = opt_input["input_specification"].get("schema_name", "qcschema_input") + if spec_schema_name == "qcschema_manybodyspecification": + model = "(proc_spec_in_options)" + options = opt_input["input_specification"] + else: + qc_input = opt_input["input_specification"] + options = qc_input["keywords"] + model = qc_input["model"] if computer_type == "psi4": # Please note that program is not actually used here From 77822845586650c7e1c96fc05726a46f1ef1e786 Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Thu, 6 Jun 2024 19:17:43 -0400 Subject: [PATCH 2/2] more updates --- optking/tests/json_lif_cp.json | 17 ++++++++++++++ optking/tests/json_lif_nocp.json | 17 ++++++++++++++ optking/tests/psi4_helper.py | 5 ++++ optking/tests/test_jsoninput.py | 40 +++++++++++++++++++++++++------- 4 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 optking/tests/json_lif_cp.json create mode 100644 optking/tests/json_lif_nocp.json diff --git a/optking/tests/json_lif_cp.json b/optking/tests/json_lif_cp.json new file mode 100644 index 0000000..caa5303 --- /dev/null +++ b/optking/tests/json_lif_cp.json @@ -0,0 +1,17 @@ +{"initial_molecule": {"fragment_charges": [1, -1], "fragments": [[0], [1]], "geometry": [0.0, 0.0, 0.0, 0.0, 0.0, 3.0], "symbols": ["Li", "F"]}, + "input_specification": {"driver": "energy", + "schema_name": "qcschema_manybodyspecification", + "keywords": {"bsse_type": "cp", "supersystem_ie_only": true}, + "protocols": {"component_results": "all"}, + "specification": {"driver": "energy", + "extras": {"psiapi": true}, + "keywords": {}, + "model": {"basis": "6-31g", "method": "hf"}, + "program": "psi4", + "protocols": {"stdout": false}}}, + "keywords": {"g_convergence": "interfrag_tight", "program": "psi4"}, + "protocols": {"trajectory": "final"}, + "provenance": {"creator": "optking", "routine": "optimize_qcengine", "version": "0.2.1+14.gdc8fd03.dirty"}, + "schema_name": "qcschema_generalizedoptimizationinput", + "schema_version": 1} + diff --git a/optking/tests/json_lif_nocp.json b/optking/tests/json_lif_nocp.json new file mode 100644 index 0000000..034e55c --- /dev/null +++ b/optking/tests/json_lif_nocp.json @@ -0,0 +1,17 @@ +{"initial_molecule": {"fragment_charges": [1, -1], "fragments": [[0], [1]], "geometry": [0.0, 0.0, 0.0, 0.0, 0.0, 3.0], "symbols": ["Li", "F"]}, + "input_specification": {"driver": "energy", + "keywords": {"bsse_type": "nocp", "supersystem_ie_only": true}, + "protocols": {"component_results": "all"}, + "schema_name": "qcschema_manybodyspecification", + "specification": {"driver": "energy", + "extras": {"psiapi": true}, + "keywords": {}, + "model": {"basis": "6-31g", "method": "hf"}, + "program": "psi4", + "protocols": {"stdout": false}}}, + "keywords": {"g_convergence": "gau_verytight", "program": "psi4"}, + "protocols": {"trajectory": "final"}, + "provenance": {"creator": "optking", "routine": "optimize_qcengine", "version": "0.2.1+14.gdc8fd03.dirty"}, + "schema_name": "qcschema_generalizedoptimizationinput", + "schema_version": 1} + diff --git a/optking/tests/psi4_helper.py b/optking/tests/psi4_helper.py index 02155dd..32baaa1 100644 --- a/optking/tests/psi4_helper.py +++ b/optking/tests/psi4_helper.py @@ -4,6 +4,7 @@ import numpy import pytest +from qcelemental.util import which_import # Try to pull in Psi4 try: @@ -15,3 +16,7 @@ # Wrap Psi4 in ifden using_psi4 = pytest.mark.skipif(found_psi4, reason="Psi4 not found, skipping.") +using_qcmanybody = pytest.mark.skipif( + which_import("qcmanybody", return_bool=True) is False, + reason="cound not find qcmanybody. please install the package to enable tests", +) diff --git a/optking/tests/test_jsoninput.py b/optking/tests/test_jsoninput.py index 2bf197b..aaaaec8 100644 --- a/optking/tests/test_jsoninput.py +++ b/optking/tests/test_jsoninput.py @@ -6,41 +6,63 @@ import psi4 from qcelemental.models import OptimizationInput +from qcelemental.testing import compare_values from .utils import utils +from .psi4_helper import using_qcmanybody # Varying number of repulsion energy decimals to check. @pytest.mark.parametrize( "inp,expected,num_steps", [ - ("json_h2o.json", (8.9064890670, -74.965901192, 3), 5), - ("json_betapinene.json", (568.2219045869, -383.38105559, 1), 4), - ("json_hooh_frozen.json", (37.969354880, -150.786372411, 2), 6), + pytest.param("json_h2o.json", (8.9064890670, -74.965901192, 3), 5), + pytest.param("json_betapinene.json", (568.2219045869, -383.38105559, 1), 4), + pytest.param("json_hooh_frozen.json", (37.969354880, -150.786372411, 2), 6), + pytest.param("json_lif_cp.json", (8.95167, -106.8867587, 2, 3.016), 4, marks=using_qcmanybody), + pytest.param("json_lif_nocp.json", (9.09281, -106.9208785, 2, 2.969), 5, marks=using_qcmanybody), ], ) def test_input_through_json(inp, expected, num_steps, check_iter): with open(os.path.join(os.path.dirname(__file__), inp)) as input_data: input_copy = json.load(input_data) - opt_schema = OptimizationInput(**input_copy) + if "lif" in inp: + from qcmanybody.models.generalized_optimization import GeneralizedOptimizationInput + opt_schema = GeneralizedOptimizationInput(**input_copy) + else: + opt_schema = OptimizationInput(**input_copy) + + # Note it's important to have `input_specification.schema_name = "qcschema_manybodyspecification"` + # in your json for a MBE optimization. Or you can explicitly construct a + # GeneralizedOptimizationInput like above. # optking.run_json_file(os.path.join(os.path.dirname(__file__), inp)) json_dict = optking.optimize_qcengine(input_copy) + if "lif" in inp: + assert inp, json_dict["trajectory"][-1]["schema_name"] == "qcschema_manybodyresult" + else: + assert inp, json_dict["trajectory"][-1]["schema_name"] == "qcschema_output" + # For testing purposes. If this works, we have properly returned the output, and added the result # to the original file. In order to preserve the form of the test suite, we now resore the input # to its original state # with open(os.path.join(os.path.dirname(__file__), inp)) as input_data: # json_dict = json.load(input_data) - assert psi4.compare_values( + + # LAB: for the MBE optimizations, psi4.compare_values strangely segfaults python, so using compare_values from qcel + assert compare_values( expected[0], json_dict["trajectory"][-1]["properties"]["nuclear_repulsion_energy"], - expected[2], - "Nuclear repulsion energy", + atol=1.0 * 10**-expected[2], + label="Nuclear repulsion energy", ) - assert psi4.compare_values( - expected[1], json_dict["trajectory"][-1]["properties"]["return_energy"], 6, "Reference energy" + assert compare_values( + expected[1], json_dict["trajectory"][-1]["properties"]["return_energy"], atol=1.e-6, label="Reference energy" ) utils.compare_iterations(json_dict, num_steps, check_iter) + if len(expected) > 3: + assert compare_values(expected[3], json_dict["final_molecule"]["geometry"][5] - json_dict["final_molecule"]["geometry"][2], atol=1.e-3, label="bond length") + # with open(os.path.join(os.path.dirname(__file__), inp), 'r+') as input_data: # input_data.seek(0) # input_data.truncate()