From ddefb75bb646aabeef9cda2a3d04c9bbb787c34b Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Mon, 3 Jun 2024 12:59:40 -0400 Subject: [PATCH] mbresprop as primary, not qcvars (#32) * mbresprop as primary, not qcvars * changelog * align run_qcengine args * no double success --- docs/changelog.md | 12 +++ qcmanybody/__init__.py | 1 + qcmanybody/computer.py | 68 +--------------- qcmanybody/core.py | 4 +- qcmanybody/models/manybody_output_pydv1.py | 33 +++++++- qcmanybody/tests/test_mbe_he4_multilevel.py | 54 +++++++------ qcmanybody/tests/test_mbe_he4_singlelevel.py | 36 ++++----- qcmanybody/tests/test_mbe_het4_grad.py | 36 +++++---- qcmanybody/tests/utils.py | 22 +++++- qcmanybody/utils.py | 83 ++++++++++++++------ 10 files changed, 195 insertions(+), 154 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 49439d9..466823e 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -28,9 +28,21 @@ from `manybody.py` to `core.py` but it was already a top-level import. @loriab * [\#30](https://github.com/MolSSI/QCManyBody/pull/30) Intf -- low-level "core" interface now requires named arguments beyond the first recognizable ones (mol, bsse_type, levels). @loriab + * [\#32](https://github.com/MolSSI/QCManyBody/pull/32) Intf -- "high-level" interface now no longer stores QCVariables + (or any other results dicts) in extras @loriab + * [\#32](https://github.com/MolSSI/QCManyBody/pull/32) Utils -- `qcmanybody.utils.collect_vars` now returns with keys + from ManyBodyResultProperties rather than QCVariables. @loriab + * [\#32](https://github.com/MolSSI/QCManyBody/pull/32) Utils -- arguments rearranged in + `qcmanybody.tests.utils.run_qcengine` (use serial backend for core interface) to align with `ManyBodyCore` init + arguments. @loriab #### New Features + * [\#32](https://github.com/MolSSI/QCManyBody/pull/32) QCSchema -- a new function + `ManyBodyResultProperties.to_qcvariables()` returns a translation map to QCVariables keys. @loriab + * [\#32](https://github.com/MolSSI/QCManyBody/pull/32) QCSchema -- a new function + `qcmanybody.utils.translate_qcvariables(map)` switches between QCVariable and QCSchema keys. @loriab + #### Enhancements * [\#28](https://github.com/MolSSI/QCManyBody/pull/28) Intf -- high-level interface is now importable from the top level diff --git a/qcmanybody/__init__.py b/qcmanybody/__init__.py index 6caacfd..bb77f84 100644 --- a/qcmanybody/__init__.py +++ b/qcmanybody/__init__.py @@ -10,3 +10,4 @@ from .utils import delabeler, labeler, resize_gradient, resize_hessian __version__ = version("qcmanybody") +del version diff --git a/qcmanybody/computer.py b/qcmanybody/computer.py index ac46cec..89194c6 100644 --- a/qcmanybody/computer.py +++ b/qcmanybody/computer.py @@ -32,6 +32,9 @@ import qcportal +__all__ = ["ManyBodyComputer"] + + class BaseComputerQCNG(ProtoModel): """Base class for "computers" that plan, run, and process QC tasks.""" @@ -505,12 +508,6 @@ def get_results( component_properties = external_results.pop("component_properties") stdout = external_results.pop("stdout") - # load QCVariables - qcvars = { - "NUCLEAR REPULSION ENERGY": self.molecule.nuclear_repulsion_energy(), - "NBODY NUMBER": nbody_number, - } - properties = { "calcinfo_nmc": len(self.nbodies_per_mc_level), "calcinfo_nfr": self.nfragments, # or len(self.molecule.fragments) @@ -520,27 +517,12 @@ def get_results( "return_energy": ret_energy, } - for k, val in external_results.items(): - if k == "results": - k = "nbody" - qcvars[k] = val - - qcvars["CURRENT ENERGY"] = ret_energy if self.driver == "gradient": - qcvars["CURRENT GRADIENT"] = ret_ptype properties["return_gradient"] = ret_ptype elif self.driver == "hessian": - qcvars["CURRENT GRADIENT"] = ret_gradient - qcvars["CURRENT HESSIAN"] = ret_ptype properties["return_gradient"] = ret_gradient properties["return_hessian"] = ret_ptype - # build_out(qcvars) - atprop = build_manybodyproperties(qcvars["nbody"]) - # print("ATPROP") - # v2: pp.pprint(atprop.model_dump()) - # pp.pprint(atprop.dict()) - # output_data = { # "schema_version": 1, # "molecule": gamessmol, # overwrites with outfile Cartesians in case fix_*=F @@ -559,30 +541,20 @@ def get_results( # print("QCVARS PRESCREEN") # pp.pprint(qcvars) - for qcv, val in qcvars.items(): - if not isinstance(val, dict): - qcvars[qcv] = val - # v2: component_results = self.model_dump()['task_list'] # TODO when/where include the indiv outputs # ?component_results = self.dict()['task_list'] # TODO when/where include the indiv outputs # for k, val in component_results.items(): # val['molecule'] = val['molecule'].to_schema(dtype=2) - # print("QCVARS") - # pp.pprint(qcvars) - nbody_model = ManyBodyResult( **{ "input_data": self.input_data, #'molecule': self.molecule, # v2: 'properties': {**atprop.model_dump(), **properties}, - "properties": {**atprop.dict(), **properties}, + "properties": {**external_results["results"], **properties}, "component_properties": component_properties, "component_results": component_results, "provenance": provenance_stamp(__name__), - "extras": { - "qcvars": qcvars, - }, "return_result": ret_ptype, "stdout": stdout, "success": True, @@ -592,35 +564,3 @@ def get_results( # logger.debug('\nNBODY QCSchema:\n' + pp.pformat(nbody_model.model_dump())) return nbody_model - - -qcvars_to_manybodyproperties = {} -# v2: for skprop in ManyBodyResultProperties.model_fields.keys(): -for skprop in ManyBodyResultProperties.__fields__.keys(): - qcvar = skprop.replace("_body", "-body").replace("_corr", "-corr").replace("_", " ").upper() - qcvars_to_manybodyproperties[qcvar] = skprop -qcvars_to_manybodyproperties["CURRENT ENERGY"] = "return_energy" -qcvars_to_manybodyproperties["CURRENT GRADIENT"] = "return_gradient" -qcvars_to_manybodyproperties["CURRENT HESSIAN"] = "return_hessian" - - -def build_manybodyproperties(qcvars: Mapping) -> ManyBodyResultProperties: - """For results extracted from QC output in QCDB terminology, translate to QCSchema terminology. - - Parameters - ---------- - qcvars : PreservingDict - Dictionary of calculation information in QCDB QCVariable terminology. - - Returns - ------- - atprop : ManyBodyResultProperties - Object of calculation information in QCSchema ManyBodyResultProperties terminology. - - """ - atprop = {} - for pv, dpv in qcvars.items(): - if pv in qcvars_to_manybodyproperties: - atprop[qcvars_to_manybodyproperties[pv]] = dpv - - return ManyBodyResultProperties(**atprop) diff --git a/qcmanybody/core.py b/qcmanybody/core.py index e89f033..66deb6c 100644 --- a/qcmanybody/core.py +++ b/qcmanybody/core.py @@ -565,8 +565,8 @@ def analyze( for bt in self.bsse_type: nbody_dict.update( collect_vars( - bt.upper(), - property_label.upper(), + bt, + property_label, all_results[f"{property_label}_body_dict"][bt], self.max_nbody, is_embedded, diff --git a/qcmanybody/models/manybody_output_pydv1.py b/qcmanybody/models/manybody_output_pydv1.py index e5aa6f6..ffd2be2 100644 --- a/qcmanybody/models/manybody_output_pydv1.py +++ b/qcmanybody/models/manybody_output_pydv1.py @@ -312,6 +312,37 @@ class Config(ProtoModel.Config): ) +def _qcvars_translator(cls, reverse: bool = False) -> Dict[str, str]: + """Form translation map between many-body results QCSchema and Psi4/QCDB terminologies. + + Parameters + ---------- + reverse + Keys are QCVariable names (`reverse=True`) rather than QCSchema names (default; `reverse=False`). + + Returns + ------- + dict + Map from ManyBodyResultProperties field names to QCVariable names, or reverse. + + """ + qcvars_to_mbprop = {} + # v2: for skprop in ManyBodyResultProperties.model_fields.keys(): + for skprop in cls.__fields__.keys(): + qcvar = skprop.replace("_body", "-body").replace("_corr", "-corr").replace("_", " ").upper() + qcvars_to_mbprop[qcvar] = skprop + for ret in ["energy", "gradient", "hessian"]: + qcvars_to_mbprop[f"CURRENT {ret.upper()}"] = f"return_{ret}" + + if reverse: + return qcvars_to_mbprop + else: + return {v: k for k, v in qcvars_to_mbprop.items()} + + +ManyBodyResultProperties.to_qcvariables = classmethod(_qcvars_translator) + + # ==== Results ================================================================ @@ -353,7 +384,7 @@ class ManyBodyResult(SuccessfulResultBase): description="The primary logging output of the program, whether natively standard output or a file. Presence vs. absence (or null-ness?) configurable by protocol.", ) stderr: Optional[str] = Field(None, description="The standard error of the program execution.") - success: Literal[True] = Field(True, description="Always `True` for a successful result") + # v2: success: Literal[True] = Field(True, description="Always `True` for a successful result") @validator("component_results") def _component_results(cls, value, values): diff --git a/qcmanybody/tests/test_mbe_he4_multilevel.py b/qcmanybody/tests/test_mbe_he4_multilevel.py index 61db54e..db11e1c 100644 --- a/qcmanybody/tests/test_mbe_he4_multilevel.py +++ b/qcmanybody/tests/test_mbe_he4_multilevel.py @@ -9,18 +9,14 @@ # v2: from qcelemental.models.procedures_manybody import AtomicSpecification, ManyBodyKeywords, ManyBodyInput from qcelemental.testing import compare_recursive, compare_values -from qcmanybody.computer import ManyBodyComputer, qcvars_to_manybodyproperties -from qcmanybody.models import AtomicSpecification, ManyBodyInput, ManyBodyKeywords +from qcmanybody.computer import ManyBodyComputer +from qcmanybody.models import AtomicSpecification, ManyBodyInput, ManyBodyKeywords, ManyBodyResultProperties +from qcmanybody.utils import translate_qcvariables from .addons import using, uusing from .test_mbe_he4_singlelevel import sumdict as sumdict_single -def skprop(qcvar): - # qcng: return qcng.procedures.manybody.qcvars_to_manybodyproperties[qcvar] - return qcvars_to_manybodyproperties[qcvar] - - @pytest.fixture(scope="function") def mbe_data_multilevel_631g(): # note that spherical/cartesian irrelevant for He & 6-31G, and fc/ae irrelevant for He @@ -731,6 +727,12 @@ def test_nbody_he4_multi(levels, mbe_keywords, anskey, bodykeys, outstrs, calcin print(f"MMMMMMM {request.node.name}") pprint.pprint(ret.dict(), width=200) + # don't want QCVariables stashed in extras, but prepare the qcvars translation, and check it + assert ret.extras == {}, f"[w] extras wrongly present: {ret.extras.keys()}" + qcvars = translate_qcvariables(ret.properties.dict()) + + skprop = ManyBodyResultProperties.to_qcvariables(reverse=True) + refs = he4_refs_conv_multilevel_631g[pattern] ans = refs[anskey] ref_nmbe = calcinfo_nmbe[pattern] @@ -739,29 +741,29 @@ def test_nbody_he4_multi(levels, mbe_keywords, anskey, bodykeys, outstrs, calcin atol = 2.5e-8 for qcv, ref in refs.items(): - skp = skprop(qcv) + skp = skprop[qcv] if qcv in ref_bodykeys: - assert compare_values(ref, ret.extras["qcvars"]["nbody"][qcv], atol=atol, label=f"[a] qcvars {qcv}") + assert compare_values(ref, qcvars[qcv], atol=atol, label=f"[a] qcvars {qcv}") assert compare_values(ref, getattr(ret.properties, skp), atol=atol, label=f"[b] skprop {skp}") else: - assert qcv not in ret.extras["qcvars"]["nbody"], f"[z] {qcv=} wrongly present" + assert qcv not in qcvars, f"[z] {qcv=} wrongly present" assert getattr(ret.properties, skp) is None for qcv in sumdict["4b_all"]: - skp = skprop(qcv) + skp = skprop[qcv] if qcv in ref_sumdict: ref = refs[ref_sumdict[qcv]] - assert compare_values(ref, ret.extras["qcvars"]["nbody"][qcv], atol=atol, label=f"[c] qcvars {qcv}") + assert compare_values(ref, qcvars[qcv], atol=atol, label=f"[c] qcvars {qcv}") assert compare_values(ref, getattr(ret.properties, skp), atol=atol, label=f"[d] skprop {skp}") else: - assert qcv not in ret.extras["qcvars"]["nbody"], f"[y] {qcv=} wrongly present" + assert qcv not in qcvars, f"[y] {qcv=} wrongly present" assert getattr(ret.properties, skp) is None for qcv, ref in { "CURRENT ENERGY": ans, }.items(): - skp = skprop(qcv) - assert compare_values(ref, ret.extras["qcvars"][qcv], atol=atol, label=f"[e] qcvars {qcv}") + skp = skprop[qcv] + assert compare_values(ref, qcvars[qcv], atol=atol, label=f"[e] qcvars {qcv}") assert compare_values(ref, getattr(ret.properties, skp), atol=atol, label=f"[f] skprop {skp}") assert compare_values(ans, ret.return_result, atol=atol, label=f"[g] ret") @@ -825,6 +827,12 @@ def test_nbody_he4_supersys(levels, mbe_keywords, anskey, bodykeys, outstrs, cal print(f"MMMMMMM {request.node.name}") pprint.pprint(ret.dict(), width=200) + # don't want QCVariables stashed in extras, but prepare the qcvars translation, and check it + assert ret.extras == {}, f"[w] extras wrongly present: {ret.extras.keys()}" + qcvars = translate_qcvariables(ret.properties.dict()) + + skprop = ManyBodyResultProperties.to_qcvariables(reverse=True) + refs = he4_refs_conv_multilevel_631g[pattern] ans = refs[anskey] ref_nmbe = calcinfo_nmbe[pattern] @@ -833,29 +841,29 @@ def test_nbody_he4_supersys(levels, mbe_keywords, anskey, bodykeys, outstrs, cal atol = 2.5e-8 for qcv, ref in refs.items(): - skp = skprop(qcv) + skp = skprop[qcv] if qcv in ref_bodykeys: - assert compare_values(ref, ret.extras["qcvars"]["nbody"][qcv], atol=atol, label=f"[a] qcvars {qcv}") + assert compare_values(ref, qcvars[qcv], atol=atol, label=f"[a] qcvars {qcv}") assert compare_values(ref, getattr(ret.properties, skp), atol=atol, label=f"[b] skprop {skp}") else: - assert qcv not in ret.extras["qcvars"]["nbody"], f"[z] {qcv=} wrongly present" + assert qcv not in qcvars, f"[z] {qcv=} wrongly present" assert getattr(ret.properties, skp) is None for qcv in sumdict["4b_all"]: - skp = skprop(qcv) + skp = skprop[qcv] if qcv in ref_sumdict: ref = refs[ref_sumdict[qcv]] - assert compare_values(ref, ret.extras["qcvars"]["nbody"][qcv], atol=atol, label=f"[c] qcvars {qcv}") + assert compare_values(ref, qcvars[qcv], atol=atol, label=f"[c] qcvars {qcv}") assert compare_values(ref, getattr(ret.properties, skp), atol=atol, label=f"[d] skprop {skp}") else: - assert qcv not in ret.extras["qcvars"]["nbody"], f"[y] {qcv=} wrongly present" + assert qcv not in qcvars, f"[y] {qcv=} wrongly present" assert getattr(ret.properties, skp) is None for qcv, ref in { "CURRENT ENERGY": ans, }.items(): - skp = skprop(qcv) - assert compare_values(ref, ret.extras["qcvars"][qcv], atol=atol, label=f"[e] qcvars {qcv}") + skp = skprop[qcv] + assert compare_values(ref, qcvars[qcv], atol=atol, label=f"[e] qcvars {qcv}") assert compare_values(ref, getattr(ret.properties, skp), atol=atol, label=f"[f] skprop {skp}") assert compare_values(ans, ret.return_result, atol=atol, label=f"[g] ret") diff --git a/qcmanybody/tests/test_mbe_he4_singlelevel.py b/qcmanybody/tests/test_mbe_he4_singlelevel.py index 766e6eb..86f17c3 100644 --- a/qcmanybody/tests/test_mbe_he4_singlelevel.py +++ b/qcmanybody/tests/test_mbe_he4_singlelevel.py @@ -9,17 +9,11 @@ from qcelemental.testing import compare_recursive, compare_values from qcmanybody import ManyBodyComputer -from qcmanybody.computer import qcvars_to_manybodyproperties -from qcmanybody.models import AtomicSpecification, ManyBodyInput, ManyBodyKeywords +from qcmanybody.models import AtomicSpecification, ManyBodyInput, ManyBodyKeywords, ManyBodyResultProperties +from qcmanybody.utils import translate_qcvariables from .addons import using, uusing - -def skprop(qcvar): - # qcng: return qcng.procedures.manybody.qcvars_to_manybodyproperties[qcvar] - return qcvars_to_manybodyproperties[qcvar] - - he4_refs_species = { "NO": ('[\"(auto)\", [1, 2, 4], [1, 2, 4]]', -8.644153798224503), "CP": ('[\"(auto)\", [1, 2, 4], [1, 2, 3, 4]]', -8.64425850181438), @@ -236,7 +230,7 @@ def skprop(qcvar): } - + sumdict = { "4b_all": { @@ -586,7 +580,7 @@ def he_tetramer(): {"bsse_type": "nocp", "return_total_data": False, "max_nbody": 1}, "NOCP-CORRECTED INTERACTION ENERGY THROUGH 1-BODY", [k for k in he4_refs_conv if (k.startswith("NOCP-") and ("1-BODY" in k))], - None, + None, 4, # maybe TODO this could be 0 but rtd hasn't be used to winnow nocp id="1b_nocp"), pytest.param( @@ -632,6 +626,12 @@ def test_nbody_he4_single(program, basis, keywords, mbe_keywords, anskey, bodyke # v2: pprint.pprint(ret.model_dump(), width=200) pprint.pprint(ret.dict(), width=200) + # don't want QCVariables stashed in extras, but prepare the qcvars translation, and check it + assert ret.extras == {}, f"[w] extras wrongly present: {ret.extras.keys()}" + qcvars = translate_qcvariables(ret.properties.dict()) + + skprop = ManyBodyResultProperties.to_qcvariables(reverse=True) + _inner = request.node.name.split("[")[1].split("]")[0] kwdsln, progln = _inner.split("-") refs = he4_refs_df if progln == "psi4_df" else he4_refs_conv @@ -640,22 +640,22 @@ def test_nbody_he4_single(program, basis, keywords, mbe_keywords, anskey, bodyke atol = 1.0e-8 for qcv, ref in refs.items(): - skp = skprop(qcv) + skp = skprop[qcv] if qcv in bodykeys: - assert compare_values(ref, ret.extras["qcvars"]["nbody"][qcv], atol=atol, label=f"[a] qcvars {qcv}") + assert compare_values(ref, qcvars[qcv], atol=atol, label=f"[a] qcvars {qcv}") assert compare_values(ref, getattr(ret.properties, skp), atol=atol, label=f"[b] skprop {skp}") else: - assert qcv not in ret.extras["qcvars"]["nbody"], f"[z] {qcv=} wrongly present" + assert qcv not in qcvars, f"[z] {qcv=} wrongly present" assert getattr(ret.properties, skp) is None for qcv in sumdict["4b_all"]: - skp = skprop(qcv) + skp = skprop[qcv] if qcv in sumdict[kwdsln]: ref = refs[sumdict[kwdsln][qcv]] - assert compare_values(ref, ret.extras["qcvars"]["nbody"][qcv], atol=atol, label=f"[c] qcvars {qcv}") + assert compare_values(ref, qcvars[qcv], atol=atol, label=f"[c] qcvars {qcv}") assert compare_values(ref, getattr(ret.properties, skp), atol=atol, label=f"[d] skprop {skp}") else: - assert qcv not in ret.extras["qcvars"]["nbody"], f"[y] {qcv=} wrongly present" + assert qcv not in qcvars, f"[y] {qcv=} wrongly present" assert getattr(ret.properties, skp) is None if "3b" in kwdsln and not progln.endswith("df"): @@ -665,8 +665,8 @@ def test_nbody_he4_single(program, basis, keywords, mbe_keywords, anskey, bodyke for qcv, ref in { "CURRENT ENERGY": ans, }.items(): - skp = skprop(qcv) - assert compare_values(ref, ret.extras["qcvars"][qcv], atol=atol, label=f"[e] qcvars {qcv}") + skp = skprop[qcv] + assert compare_values(ref, qcvars[qcv], atol=atol, label=f"[e] qcvars {qcv}") assert compare_values(ref, getattr(ret.properties, skp), atol=atol, label=f"[f] skprop {skp}") assert compare_values(ans, ret.return_result, atol=atol, label=f"[g] ret") diff --git a/qcmanybody/tests/test_mbe_het4_grad.py b/qcmanybody/tests/test_mbe_het4_grad.py index c529588..3785df3 100644 --- a/qcmanybody/tests/test_mbe_het4_grad.py +++ b/qcmanybody/tests/test_mbe_het4_grad.py @@ -6,17 +6,13 @@ from qcelemental.models import Molecule from qcelemental.testing import compare_values -from qcmanybody.computer import ManyBodyComputer, qcvars_to_manybodyproperties -from qcmanybody.models import ManyBodyInput, ManyBodyKeywords +from qcmanybody.computer import ManyBodyComputer +from qcmanybody.models import ManyBodyInput, ManyBodyKeywords, ManyBodyResultProperties +from qcmanybody.utils import translate_qcvariables from .addons import uusing -def skprop(qcvar): - # qcng: return qcng.procedures.manybody.qcvars_to_manybodyproperties[qcvar] - return qcvars_to_manybodyproperties[qcvar] - - @pytest.fixture(scope="function") def mbe_data_grad_dtz(): # note that spherical/cartesian irrelevant for He & 6-31G, and fc/ae irrelevant for He @@ -270,37 +266,43 @@ def test_nbody_het4_grad(mbe_keywords, anskeyE, anskeyG, bodykeys, outstrs, calc ref_sumdict = sumdict_grad[kwdsln] atol = 2.5e-8 + # don't want QCVariables stashed in extras, but prepare the qcvars translation, and check it + assert ret.extras == {}, f"[w] extras wrongly present: {ret.extras.keys()}" + qcvars = translate_qcvariables(ret.properties.dict()) + + skprop = ManyBodyResultProperties.to_qcvariables(reverse=True) + for qcv, ref in refs.items(): - skp = skprop(qcv) + skp = skprop[qcv] if qcv in ref_bodykeys: - assert compare_values(ref, ret.extras["qcvars"]["nbody"][qcv], atol=atol, label=f"[a] qcvars {qcv}") + assert compare_values(ref, qcvars[qcv], atol=atol, label=f"[a] qcvars {qcv}") assert compare_values(ref, getattr(ret.properties, skp), atol=atol, label=f"[b] skprop {skp}") else: - assert qcv not in ret.extras["qcvars"]["nbody"], f"[z] {qcv=} wrongly present" + assert qcv not in qcvars, f"[z] {qcv=} wrongly present" assert getattr(ret.properties, skp) is None for qcv in sumdict_grad["4b_all"]: - skp = skprop(qcv) + skp = skprop[qcv] if qcv in ref_sumdict: ref = refs[ref_sumdict[qcv]] - assert compare_values(ref, ret.extras["qcvars"]["nbody"][qcv], atol=atol, label=f"[c] qcvars {qcv}") + assert compare_values(ref, qcvars[qcv], atol=atol, label=f"[c] qcvars {qcv}") assert compare_values(ref, getattr(ret.properties, skp), atol=atol, label=f"[d] skprop {skp}") else: - assert qcv not in ret.extras["qcvars"]["nbody"], f"[y] {qcv=} wrongly present" + assert qcv not in qcvars, f"[y] {qcv=} wrongly present" assert getattr(ret.properties, skp) is None for qcv, ref in { "CURRENT ENERGY": ansE, }.items(): - skp = skprop(qcv) - assert compare_values(ref, ret.extras["qcvars"][qcv], atol=atol, label=f"[e] qcvars {qcv}") + skp = skprop[qcv] + assert compare_values(ref, qcvars[qcv], atol=atol, label=f"[e] qcvars {qcv}") assert compare_values(ref, getattr(ret.properties, skp), atol=atol, label=f"[f] skprop {skp}") for qcv, ref in { "CURRENT GRADIENT": ansG, }.items(): - skp = skprop(qcv) - assert compare_values(ref, ret.extras["qcvars"][qcv], atol=atol, label=f"[e] G qcvars {qcv}") + skp = skprop[qcv] + assert compare_values(ref, qcvars[qcv], atol=atol, label=f"[e] G qcvars {qcv}") assert compare_values(ref, getattr(ret.properties, skp), atol=atol, label=f"[f] G skprop {skp}") assert compare_values(ansG, ret.return_result, atol=atol, label=f"[g] G ret") diff --git a/qcmanybody/tests/utils.py b/qcmanybody/tests/utils.py index 4796257..fe3d4f7 100644 --- a/qcmanybody/tests/utils.py +++ b/qcmanybody/tests/utils.py @@ -9,6 +9,7 @@ from qcmanybody import ManyBodyCore, delabeler from qcmanybody.models import BsseEnum +from qcmanybody.utils import translate_qcvariables _my_dir = os.path.dirname(os.path.realpath(__file__)) @@ -65,9 +66,18 @@ def load_component_data(file_base): def generate_component_data( - mol, levels, specifications, bsse_type, return_total_data, out_filename, supsersytem_ie_only=False + mol, + levels, + specifications, + bsse_type, + return_total_data, + out_filename, + supsersytem_ie_only=False, + embedding_charges=None, ): - mc, component_results = run_qcengine(mol, levels, specifications, bsse_type, return_total_data, supsersytem_ie_only) + mc, component_results = run_qcengine( + specifications, mol, bsse_type, levels, return_total_data, supsersytem_ie_only, embedding_charges + ) component_results = jsonify(component_results) filepath = os.path.join(_my_dir, "component_data", out_filename + ".json.zst") @@ -130,6 +140,10 @@ def compare_results(qcmb_results, ref_results, levels): if not res: return + # res (from ManyBodyCore) keys are ManyBodyResultProperties fields, while ref_results keys are + # Psi4.core.Wavefunction.variables() keys, so need to translate former for compare() calls below + res = translate_qcvariables(res) + if not f"NOCP-CORRECTED TOTAL ENERGY" in ref_results: # Psi4 used during the bootstrapping tests does not have data for multi+ss return @@ -164,10 +178,10 @@ def compare_results(qcmb_results, ref_results, levels): def run_qcengine( - molecule: Molecule, - levels: Mapping[Union[int, Literal["supersystem"]], str], specifications: Mapping[str, Mapping[str, Any]], + molecule: Molecule, bsse_type: Iterable[BsseEnum], + levels: Mapping[Union[int, Literal["supersystem"]], str], return_total_data: bool, supersystem_ie_only: bool, embedding_charges: Optional[Mapping[int, list]], diff --git a/qcmanybody/utils.py b/qcmanybody/utils.py index 2319517..f7a0da8 100644 --- a/qcmanybody/utils.py +++ b/qcmanybody/utils.py @@ -17,6 +17,7 @@ "resize_gradient", "resize_hessian", # "sum_cluster_data", + "translate_qcvariables", ] @@ -123,7 +124,8 @@ def resize_hessian( ---------- hess Hessian matrix of natural size for *bas*, (3 * __, 3 * __). - If `reverse=True`, Hessian matrix of supersystem size, (3 * __, 3 * __). + If `reverse=True`, Hessian matrix of supersystem size, (3 * __, + 3 * __). bas 1-indexed fragments active in *hess*. If `reverse=True`, 1-indexed fragments to be extracted from *hess*. @@ -139,8 +141,9 @@ def resize_hessian( Returns ------- ndarray - Hessian array padded with zeros to supersystem size, (3 * __, 3 * __). - If reverse=True, Hessian array extracted to natural size, (3 * __, 3 * __). + Hessian array padded with zeros to supersystem size, (3 * __, + 3 * __). If reverse=True, Hessian array extracted to natural size, + (3 * __, 3 * __). """ if reverse: @@ -355,29 +358,36 @@ def collect_vars( supersystem_ie_only: bool = False, has_supersystem: bool = False, ) -> Dict: - """From *body_dict*, construct QCVariables. + """From *body_dict*, construct data for ManyBodyResultProperties. Parameters ---------- bsse - Uppercase label for a single many-body treatment, generally a value of BsseEnum. + Label for a single many-body treatment, generally a value of BsseEnum. prop - Uppercase label for a single property, generally a value of DriverEnum. + Label for a single property, generally a value of DriverEnum. body_dict - Dictionary of minimal per-body info already specialized for property *prop* and treatment *bsse*. May contain either total data or interaction data, both cummulative not additive, from 1-body to max_nbody-body (see also *supersystem_ie_only*). Interaction data signaled by zero float or array for 1-body. May contain multiple model chemistry levels. + Dictionary of minimal per-body info already specialized for property *prop* and treatment + *bsse*. May contain either total data or interaction data (cummulative, not additive) from + 1-body to max_nbody-body (see also *supersystem_ie_only*). Interaction data signaled by zero + float or array for 1-body. May contain data from multiple model chemistry levels. max_nbody _description_ embedding Is charge embedding enabled, by default False? supersystem_ie_only - Is data available in *body_dict* only for 1-body (possibly zero) and nfr-body levels? By default False: data is available for consecutive levels, up to max_nbody-body. + Is data available in *body_dict* only for 1-body (possibly zero) and nfr-body levels? + By default False: data is available for consecutive levels, up to max_nbody-body. has_supersystem Whether contributions higher than max_nbody are a summary correction. + Returns ------- dict _description_. Empty return if *embedding* enabled. """ + bsse = bsse.lower() + prop = prop.lower() previous_e = body_dict[1] property_shape = find_shape(previous_e) tot_e = bool(np.count_nonzero(previous_e)) @@ -386,42 +396,42 @@ def collect_vars( res = {} if tot_e: - res[f"{bsse}-CORRECTED TOTAL {prop}"] = body_dict[max_nbody] - res[f"{bsse}-CORRECTED INTERACTION {prop}"] = body_dict[max_nbody] - body_dict[1] - res[f"{bsse}-CORRECTED INTERACTION {prop} THROUGH 1-BODY"] = shaped_zero(property_shape) + res[f"{bsse}_corrected_total_{prop}"] = body_dict[max_nbody] + res[f"{bsse}_corrected_interaction_{prop}"] = body_dict[max_nbody] - body_dict[1] + res[f"{bsse}_corrected_interaction_{prop}_through_1_body"] = shaped_zero(property_shape) if supersystem_ie_only: nfr = nbody_range[-1] for nb in [nfr]: - res[f"{bsse}-CORRECTED INTERACTION {prop} THROUGH {nb}-BODY"] = body_dict[nb] - body_dict[1] + res[f"{bsse}_corrected_interaction_{prop}_through_{nb}_body"] = body_dict[nb] - body_dict[1] if nb == 2: - res[f"{bsse}-CORRECTED {nb}-BODY CONTRIBUTION TO {prop}"] = body_dict[nb] - body_dict[nb - 1] + res[f"{bsse}_corrected_{nb}_body_contribution_to_{prop}"] = body_dict[nb] - body_dict[nb - 1] if tot_e: for nb in [1, nfr]: - res[f"{bsse}-CORRECTED TOTAL {prop} THROUGH {nb}-BODY"] = body_dict[nb] + res[f"{bsse}_corrected_total_{prop}_through_{nb}_body"] = body_dict[nb] elif has_supersystem: nfr = nbody_range[-1] - res[f"{bsse}-CORRECTED INTERACTION {prop}"] = body_dict[nfr] - body_dict[1] # reset + res[f"{bsse}_corrected_interaction_{prop}"] = body_dict[nfr] - body_dict[1] # reset for nb in range(2, max_nbody + 1): - res[f"{bsse}-CORRECTED INTERACTION {prop} THROUGH {nb}-BODY"] = body_dict[nb] - body_dict[1] - res[f"{bsse}-CORRECTED {nb}-BODY CONTRIBUTION TO {prop}"] = body_dict[nb] - body_dict[nb - 1] + res[f"{bsse}_corrected_interaction_{prop}_through_{nb}_body"] = body_dict[nb] - body_dict[1] + res[f"{bsse}_corrected_{nb}_body_contribution_to_{prop}"] = body_dict[nb] - body_dict[nb - 1] for nb in [nfr]: - res[f"{bsse}-CORRECTED INTERACTION {prop} THROUGH {nb}-BODY"] = body_dict[nb] - body_dict[1] - res[f"{bsse}-CORRECTED {nb}-BODY CONTRIBUTION TO {prop}"] = body_dict[nb] - body_dict[max_nbody] + res[f"{bsse}_corrected_interaction_{prop}_through_{nb}_body"] = body_dict[nb] - body_dict[1] + res[f"{bsse}_corrected_{nb}_body_contribution_to_{prop}"] = body_dict[nb] - body_dict[max_nbody] if tot_e: - res[f"{bsse}-CORRECTED TOTAL {prop}"] = body_dict[nfr] # reset + res[f"{bsse}_corrected_total_{prop}"] = body_dict[nfr] # reset for nb in nbody_range: - res[f"{bsse}-CORRECTED TOTAL {prop} THROUGH {nb}-BODY"] = body_dict[nb] + res[f"{bsse}_corrected_total_{prop}_through_{nb}_body"] = body_dict[nb] else: for nb in range(2, max(nbody_range) + 1): - res[f"{bsse}-CORRECTED INTERACTION {prop} THROUGH {nb}-BODY"] = body_dict[nb] - body_dict[1] - res[f"{bsse}-CORRECTED {nb}-BODY CONTRIBUTION TO {prop}"] = body_dict[nb] - body_dict[nb - 1] + res[f"{bsse}_corrected_interaction_{prop}_through_{nb}_body"] = body_dict[nb] - body_dict[1] + res[f"{bsse}_corrected_{nb}_body_contribution_to_{prop}"] = body_dict[nb] - body_dict[nb - 1] if tot_e: for nb in nbody_range: - res[f"{bsse}-CORRECTED TOTAL {prop} THROUGH {nb}-BODY"] = body_dict[nb] + res[f"{bsse}_corrected_total_{prop}_through_{nb}_body"] = body_dict[nb] if embedding: - res = {k: v for k, v in res.items() if "INTERACTION" not in k} + res = {k: v for k, v in res.items() if "interaction" not in k} return res @@ -440,3 +450,26 @@ def provenance_stamp(routine: str) -> Dict[str, str]: import qcmanybody return {"creator": "QCManyBody", "version": qcmanybody.__version__, "routine": routine} + + +def translate_qcvariables(varsmap: Mapping[str, Any]) -> Dict[str, Any]: + """Translate between ManyBody results in Psi4/QCDB terminology (qcvars) and QCSchema terminology (skprops). + + Parameters + ---------- + varsmap + Dictionary with keys all members of QCVariables or ManyBodyResultProperties and arbitrary values. + + Returns + ------- + dict + varsmap with keys swapped to other set. Untranslatable keys are omitted. + + """ + from qcmanybody.models import ManyBodyResultProperties + + # identify direction of translation + qcv2skp = any([" " in lbl for lbl in varsmap]) + labelmap = ManyBodyResultProperties.to_qcvariables(reverse=qcv2skp) + + return {labelmap[lbl]: data for lbl, data in varsmap.items() if lbl in labelmap}