Skip to content

Commit

Permalink
component data (#9)
Browse files Browse the repository at this point in the history
* tidying

* more misc

* poor fix of tests

* fix 1b

* rename stuff

* test component

* test resize fns
  • Loading branch information
loriab authored Apr 16, 2024
1 parent 7f54841 commit 0ac94f9
Show file tree
Hide file tree
Showing 10 changed files with 368 additions and 53 deletions.
2 changes: 1 addition & 1 deletion qcmanybody/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

from .manybody import ManyBodyCalculator
from .models import BsseEnum
from .utils import labeler, delabeler
from .utils import labeler, delabeler, resize_gradient, resize_hessian

__version__ = version("qcmanybody")
64 changes: 45 additions & 19 deletions qcmanybody/manybody.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import logging
import math
from collections import defaultdict
from typing import Set, Dict, Tuple, Union, Literal, Mapping, Any, Sequence

import numpy as np
Expand All @@ -19,8 +20,8 @@
find_shape,
shaped_zero,
all_same_shape,
expand_hessian,
expand_gradient,
resize_hessian,
resize_gradient,
)

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -126,11 +127,11 @@ def compute_map(self) -> Dict[str, Dict[str, Dict[int, Set[FragBasIndex]]]]:

return self.mc_compute_dict

def expand_gradient(self, grad: np.ndarray, bas: Tuple[int, ...]) -> np.ndarray:
return expand_gradient(grad, bas, self.fragment_size_dict, self.fragment_slice_dict)
def resize_gradient(self, grad: np.ndarray, bas: Tuple[int, ...], *, reverse: bool = False) -> np.ndarray:
return resize_gradient(grad, bas, self.fragment_size_dict, self.fragment_slice_dict, reverse=reverse)

def expand_hessian(self, hess: np.ndarray, bas: Tuple[int, ...]) -> np.ndarray:
return expand_hessian(hess, bas, self.fragment_size_dict, self.fragment_slice_dict)
def resize_hessian(self, hess: np.ndarray, bas: Tuple[int, ...], *, reverse: bool = False) -> np.ndarray:
return resize_hessian(hess, bas, self.fragment_size_dict, self.fragment_slice_dict, reverse=reverse)

def iterate_molecules(self) -> Tuple[str, str, Molecule]:
"""Iterate over all the molecules needed for the computation.
Expand Down Expand Up @@ -206,7 +207,7 @@ def _assemble_nbody_components(
first_key = next(iter(component_results.keys()))
property_shape = find_shape(component_results[first_key])

# Final dictionaries
# Accumulation dictionaries
# * {bsse_type}_by_level is filled by sum_cluster_data to contain for NOCP
# & CP the summed total energies (or other property) of each nb-body. That is:
# * NOCP: {1: 1b@1b, 2: 2b@2b, ..., max_nbody: max_nbody-b@max_nbody-b} and
Expand All @@ -218,6 +219,17 @@ def _assemble_nbody_components(
nocp_by_level = {n: shaped_zero(property_shape) for n in range(1, nbodies[-1] + 1)}
vmfc_by_level = {n: shaped_zero(property_shape) for n in range(1, nbodies[-1] + 1)}

# * {bsse_type}_body_dict is usually filled with total energies (or other property).
# Multiple model chemistry levels may be involved.
# Generally, all consecutive keys between 1 and max_nbody will be present in the body_dict,
# but if supersystem_ie_only=T, only 1b and nfr-b are present, or if "supersystem" in levels, ???
# * TOT: {1: 1b@1b, 2: 2b tot prop with bsse_type treatment, ..., max_nbody: max_nbody-b tot prop with bsse_type treatment}
# If 1b@1b (monomers in monomer basis) aren't available, which can happen when return_total_data=F
# and 1b@1b aren't otherwise needed, body_dict contains interaction energies (or other property).
# * IE: {1: shaped_zero, 2: 2b interaction prop using bsse_type, ..., max_nbody: max_nbody-b interaction prop using bsse_type}
# For both TOT and IE cases, body_dict values are cummulative, not additive. For TOT, total,
# interaction, and contribution data in ManyBodyResultProperties can be computed in
# collect_vars. For IE, interaction and contribution data can be computed.
cp_body_dict = {n: shaped_zero(property_shape) for n in range(1, nbodies[-1] + 1)}
nocp_body_dict = {n: shaped_zero(property_shape) for n in range(1, nbodies[-1] + 1)}
vmfc_body_dict = {n: shaped_zero(property_shape) for n in range(1, nbodies[-1] + 1)}
Expand Down Expand Up @@ -321,6 +333,7 @@ def _analyze(

# results per model chemistry
mc_results = {}
species_results = {}

# sort by nbody level, ignore supersystem
sorted_nbodies = [(k, v) for k, v in self.nbodies_per_mc_level.items() if v != ["supersystem"]]
Expand Down Expand Up @@ -435,7 +448,8 @@ def analyze(
"""

# All properties that were passed to us
available_properties = set()
# * seed with "energy" so free/no-op jobs can process
available_properties = set(["energy"])
for property_data in component_results.values():
available_properties.update(property_data.keys())

Expand All @@ -455,23 +469,25 @@ def analyze(
component_results_inv["energy"] = {'["dummy", [1000], [1000]]': 0.0}

# Actually analyze
is_embedded = bool(self.embedding_charges)
component_properties = defaultdict(dict)
all_results = {}
nbody_dict = {}
# all_results["energy_body_dict"] = {"cp": {1: 0.0}}

for property_label, property_results in component_results_inv.items():
# Expand gradient and hessian
if property_label == "gradient":
property_results = {k: self.expand_gradient(v, delabeler(k)[2]) for k, v in property_results.items()}
property_results = {k: self.resize_gradient(v, delabeler(k)[2]) for k, v in property_results.items()}
if property_label == "hessian":
property_results = {k: self.expand_hessian(v, delabeler(k)[2]) for k, v in property_results.items()}
property_results = {k: self.resize_hessian(v, delabeler(k)[2]) for k, v in property_results.items()}

r = self._analyze(property_label, property_results)
for k, v in property_results.items():
component_properties[k]["calcinfo_natom"] = len(self.molecule.symbols)
component_properties[k][f"return_{property_label}"] = v
all_results.update(r)

# Analyze the total results
nbody_dict = {}

is_embedded = bool(self.embedding_charges)

for bt in self.bsse_type:
print_nbody_energy(
all_results["energy_body_dict"][bt],
Expand All @@ -480,12 +496,22 @@ def analyze(
is_embedded,
)

if not self.has_supersystem: # skipped levels?
nbody_dict.update(
collect_vars(bt.upper(), all_results["energy_body_dict"][bt], self.max_nbody, is_embedded, self.supersystem_ie_only)
)
for property_label in available_properties:
for bt in self.bsse_type:
if not self.has_supersystem: # skipped levels?
nbody_dict.update(
collect_vars(
bt.upper(),
property_label.upper(),
all_results[f"{property_label}_body_dict"][bt],
self.max_nbody,
is_embedded,
self.supersystem_ie_only,
)
)

all_results["results"] = nbody_dict
all_results["component_properties"] = component_properties

# Make dictionary with "1cp", "2cp", etc
ebd = all_results["energy_body_dict"]
Expand Down
9 changes: 8 additions & 1 deletion qcmanybody/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
from .manybody_v1 import BsseEnum, FragBasIndex
from .manybody_pydv1 import (
BsseEnum,
FragBasIndex,
ManyBodyInput,
ManyBodyKeywords,
ManyBodyResult,
ManyBodyResultProperties,
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#from .basemodels import ExtendedConfigDict, ProtoModel
from qcelemental.models.common_models import Model
from qcelemental.models.molecule import Molecule
from qcelemental.models.results import AtomicResultProtocols
from qcelemental.models.results import AtomicResultProperties, AtomicResultProtocols
from qcelemental.models import DriverEnum, ProtoModel, Provenance


Expand Down Expand Up @@ -516,6 +516,11 @@ class ManyBodyResult(SuccessfulResultBase):
"all results regardless of if they failed or succeeded by checking `result.success`.",
)
properties: ManyBodyResultProperties = Field(..., description=str(ManyBodyResultProperties.__doc__))
component_properties: Dict[str, AtomicResultProperties] = Field(
...,
description="The key results for each subsystem species computed. Keys contain modelchem, real and ghost information (e.g., `'[\"(auto)\", [2], [1, 2, 3]]'`). Values are total e/g/H/property results. Array values, if present, are sized and shaped for the full supersystem.",

)
return_result: Union[float, Array[float], Dict[str, Any]] = Field(
...,
description="The primary return specified by the :attr:`~qcelemental.models.AtomicInput.driver` field. Scalar if energy; array if gradient or hessian; dictionary with property keys if properties.",
Expand Down
5 changes: 3 additions & 2 deletions qcmanybody/models/qcng_computer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from qcelemental.models import FailedOperation, Molecule, DriverEnum, ProtoModel, AtomicResult, AtomicInput
import qcengine as qcng
from .manybody_v1 import BsseEnum, ManyBodyKeywords, ManyBodyInput, ManyBodyResult, ManyBodyResultProperties
from .manybody_pydv1 import BsseEnum, ManyBodyKeywords, ManyBodyInput, ManyBodyResult, ManyBodyResultProperties
from qcmanybody import ManyBodyCalculator
from qcmanybody.utils import delabeler, provenance_stamp

Expand Down Expand Up @@ -741,6 +741,7 @@ def get_results(self, client: Optional["qcportal.FractalClient"] = None, externa
ret_ptype = ret_energy if self.driver == "energy" else external_results.pop(f"ret_{self.driver.name}")
ret_gradient = external_results.pop("ret_gradient", None)
nbody_number = external_results.pop("nbody_number")
component_properties = external_results.pop("component_properties")

# load QCVariables
qcvars = {
Expand Down Expand Up @@ -845,10 +846,10 @@ def get_results(self, client: Optional["qcportal.FractalClient"] = None, externa
#'molecule': self.molecule,
# v2: 'properties': {**atprop.model_dump(), **properties},
'properties': {**atprop.dict(), **properties},
'component_properties': component_properties,
'provenance': provenance_stamp(__name__),
'extras': {
'qcvars': qcvars,
# 'component_results': component_results,
},
'return_result': ret_ptype,
'success': True,
Expand Down
2 changes: 1 addition & 1 deletion qcmanybody/models/test_mbe_he4_multilevel.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# v2: from qcelemental.models.procedures_manybody import AtomicSpecification, ManyBodyKeywords, ManyBodyInput
from qcelemental.testing import compare_values

from qcmanybody.models.manybody_v1 import AtomicSpecification, ManyBodyKeywords, ManyBodyInput
from qcmanybody.models.manybody_pydv1 import AtomicSpecification, ManyBodyKeywords, ManyBodyInput
from qcmanybody.models.qcng_computer import ManyBodyComputerQCNG, qcvars_to_manybodyproperties

import qcengine as qcng
Expand Down
12 changes: 11 additions & 1 deletion qcmanybody/models/test_mbe_he4_singlelevel.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# v2: from qcelemental.models.procedures_manybody import AtomicSpecification, ManyBodyKeywords, ManyBodyInput
from qcelemental.testing import compare_values

from qcmanybody.models.manybody_v1 import AtomicSpecification, ManyBodyKeywords, ManyBodyInput
from qcmanybody.models.manybody_pydv1 import AtomicSpecification, ManyBodyKeywords, ManyBodyInput
from qcmanybody.models.qcng_computer import ManyBodyComputerQCNG, qcvars_to_manybodyproperties

import qcengine as qcng
Expand All @@ -18,6 +18,12 @@ def skprop(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),
"VM": ('[\"(auto)\", [1, 4], [1, 2, 4]]', -5.765439283351071),
}

he4_refs_conv = {
"CP-CORRECTED TOTAL ENERGY THROUGH 1-BODY": -11.530668717083888,
"CP-CORRECTED TOTAL ENERGY THROUGH 2-BODY": -11.522467757090013,
Expand Down Expand Up @@ -490,6 +496,10 @@ def test_nbody_he4_single(program, basis, keywords, mbe_keywords, anskey, bodyke
assert qcv not in ret.extras["qcvars"]["nbody"], f"[y] {qcv=} wrongly present"
assert getattr(ret.properties, skp) is None

if "3b" in kwdsln and not progln.endswith("df"):
k, v = he4_refs_species[anskey[:2]]
assert compare_values(v, ret.component_properties[k].return_energy, atol=atol, label=f"[h] species {k}")

for qcv, ref in {
"CURRENT ENERGY": ans,
}.items():
Expand Down
2 changes: 1 addition & 1 deletion qcmanybody/models/test_mbe_keywords.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pydantic import ValidationError

from qcelemental.models import DriverEnum, Molecule
from qcmanybody.models.manybody_v1 import BsseEnum, ManyBodyInput
from qcmanybody.models.manybody_pydv1 import BsseEnum, ManyBodyInput

import qcengine as qcng

Expand Down
Loading

0 comments on commit 0ac94f9

Please sign in to comment.