From caefc430b317e88704dfb4da6f75cb8eb9177d43 Mon Sep 17 00:00:00 2001 From: Lori Burns Date: Fri, 31 May 2024 17:07:57 -0400 Subject: [PATCH] in-class mol formation, req'd kwargs for core --- docs/changelog.md | 4 ++++ qcmanybody/__init__.py | 2 +- qcmanybody/computer.py | 13 +++++++++---- qcmanybody/core.py | 29 +++++++++++++++++++++++++++-- qcmanybody/tests/common.py | 2 ++ qcmanybody/tests/test_multi_ss.py | 3 ++- qcmanybody/tests/test_single.py | 15 +++++++++++++-- 7 files changed, 58 insertions(+), 10 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 4d09ddb..ba46964 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -26,6 +26,8 @@ * [\#28](https://github.com/MolSSI/QCManyBody/pull/28) Intf -- low-level "core" interface renamed from `ManyBodyCalculator` to `ManyBodyCore`. The old name will continue to work for a few months. Also, its file changed 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 #### New Features @@ -35,6 +37,8 @@ module. @loriab * [\#29](https://github.com/MolSSI/QCManyBody/pull/29) Maint -- QCEngine is needed only for the continuous running function of the high-level interface, so making it an optional dependency. @loriab + * [\#30](https://github.com/MolSSI/QCManyBody/pull/30) Intf -- low-level "core" interface now accepts a molecule in + partial schema dictionary format rather than requiring a constructed `qcelemental.Molecule` object. #### Bug Fixes diff --git a/qcmanybody/__init__.py b/qcmanybody/__init__.py index 3595eac..a6063f5 100644 --- a/qcmanybody/__init__.py +++ b/qcmanybody/__init__.py @@ -1,7 +1,7 @@ from importlib.metadata import version from .core import ManyBodyCore -from .core import ManyBodyCore as ManyBodyCalculator # legacy alias +from .core import ManyBodyCalculator # legacy near-alias to ManyBodyCore from .computer import ManyBodyComputer from .models import BsseEnum from .utils import labeler, delabeler, resize_gradient, resize_hessian diff --git a/qcmanybody/computer.py b/qcmanybody/computer.py index f90cb0d..fadf60b 100644 --- a/qcmanybody/computer.py +++ b/qcmanybody/computer.py @@ -402,15 +402,20 @@ def from_manybodyinput(cls, input_model: ManyBodyInput, build_tasks: bool = True computer_model.molecule, computer_model.bsse_type, comp_levels, - computer_model.return_total_data, - computer_model.supersystem_ie_only, - computer_model.embedding_charges, + return_total_data=computer_model.return_total_data, + supersystem_ie_only=computer_model.supersystem_ie_only, + embedding_charges=computer_model.embedding_charges, ) if not build_tasks: return computer_model - import qcengine as qcng + try: + import qcengine as qcng + except ModuleNotFoundError: + raise ModuleNotFoundError( + "Python module qcengine not found. Solve by installing it: " + "`conda install qcengine -c conda-forge` or `pip install qcengine`") component_properties = {} component_results = {} diff --git a/qcmanybody/core.py b/qcmanybody/core.py index 8292168..15fb433 100644 --- a/qcmanybody/core.py +++ b/qcmanybody/core.py @@ -33,16 +33,23 @@ def __init__( molecule: Molecule, bsse_type: Sequence[BsseEnum], levels: Mapping[Union[int, Literal["supersystem"]], str], + *, return_total_data: bool, supersystem_ie_only: bool, embedding_charges: Mapping[int, Sequence[float]], ): self.embedding_charges = embedding_charges - self.molecule = molecule + if isinstance(molecule, dict): + mol = Molecule(**molecule) + elif isinstance(molecule, Molecule): + mol = molecule.copy() + else: + raise ValueError(f"Molecule input type of {type(molecule)} not understood.") + self.molecule = mol self.bsse_type = [BsseEnum(x) for x in bsse_type] self.return_total_data = return_total_data self.supersystem_ie_only = supersystem_ie_only - self.nfragments = len(molecule.fragments) + self.nfragments = len(self.molecule.fragments) self.levels = levels @@ -561,3 +568,21 @@ def analyze( all_results["stdout"] = stdout return all_results + + +class ManyBodyCalculator(ManyBodyCore): + # retire after a grace period + def __init__( + self, + molecule: Molecule, + bsse_type: Sequence[BsseEnum], + levels: Mapping[Union[int, Literal["supersystem"]], str], + return_total_data: bool, + supersystem_ie_only: bool, + embedding_charges: Mapping[int, Sequence[float]], + ): + super().__init__(molecule, bsse_type, levels, + return_total_data=return_total_data, + supersystem_ie_only=supersystem_ie_only, + embedding_charges=embedding_charges, + ) diff --git a/qcmanybody/tests/common.py b/qcmanybody/tests/common.py index 63fae80..e5ad157 100644 --- a/qcmanybody/tests/common.py +++ b/qcmanybody/tests/common.py @@ -21,6 +21,8 @@ """ ) +mol_h2o_3_dict = {k: v for k, v in mol_h2o_3.dict().items() if k in ["symbols", "geometry", "fragments"]} + specifications = { "e_scf": { "program": "psi4", diff --git a/qcmanybody/tests/test_multi_ss.py b/qcmanybody/tests/test_multi_ss.py index 360149d..00835b7 100644 --- a/qcmanybody/tests/test_multi_ss.py +++ b/qcmanybody/tests/test_multi_ss.py @@ -28,6 +28,7 @@ def test_h2o_trimer_multi_ss(levels, component_file, ref_file): component_results = load_component_data(component_file) ref_data = load_ref_data(ref_file) - mc = ManyBodyCore(mol_h2o_3, [BsseEnum.cp, BsseEnum.nocp, BsseEnum.vmfc], levels, True, False, None) + mc = ManyBodyCore(mol_h2o_3, [BsseEnum.cp, BsseEnum.nocp, BsseEnum.vmfc], levels, + return_total_data=True, supersystem_ie_only=False, embedding_charges=None) nbody_results = mc.analyze(component_results) compare_results(nbody_results, ref_data, levels) diff --git a/qcmanybody/tests/test_single.py b/qcmanybody/tests/test_single.py index 4c2cede..3d80e0b 100644 --- a/qcmanybody/tests/test_single.py +++ b/qcmanybody/tests/test_single.py @@ -1,8 +1,9 @@ import pytest +import qcelemental from qcmanybody import ManyBodyCore from qcmanybody.models import BsseEnum -from .common import mol_h2o_3 +from .common import mol_h2o_3_dict from .utils import load_ref_data, compare_results, load_component_data @@ -25,6 +26,16 @@ def test_h2o_trimer_single(levels, component_file, ref_file): component_results = load_component_data(component_file) ref_data = load_ref_data(ref_file) - mc = ManyBodyCore(mol_h2o_3, [BsseEnum.cp, BsseEnum.nocp, BsseEnum.vmfc], levels, True, False, None) + mc = ManyBodyCore(mol_h2o_3_dict, [BsseEnum.cp, BsseEnum.nocp, BsseEnum.vmfc], levels, + return_total_data=True, supersystem_ie_only=False, embedding_charges=None) nbody_results = mc.analyze(component_results) compare_results(nbody_results, ref_data, levels) + + +def test_core_mol_error(): + # check sensible error from internal Molecule construction + odd_mol = mol_h2o_3_dict.copy() + odd_mol["symbols"][0] = "A" + with pytest.raises(qcelemental.exceptions.NotAnElementError): + ManyBodyCore(odd_mol, [BsseEnum.nocp], {1: "asdf"}, + return_total_data=False, supersystem_ie_only=False, embedding_charges=None)