Source code for qcmanybody.models.v2.many_body

from __future__ import annotations

import os
import re
from enum import Enum
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Union

try:
    from pydantic import ValidationInfo
except ImportError:
    from pydantic import FieldValidationInfo as ValidationInfo

from pydantic import Field, create_model, field_validator, model_validator
from qcelemental.models.v2 import (  # Array,
    AtomicProperties,
    AtomicProtocols,
    AtomicResult,
    AtomicSpecification,
    DriverEnum,
    Model,
    Molecule,
    ProtoModel,
    Provenance,
)
from qcelemental.models.v2.basemodels import ExtendedConfigDict, ProtoModel, check_convertible_version
from qcelemental.models.v2.types import Array  # return to above once qcel corrected

from ...utils import provenance_stamp

if TYPE_CHECKING:
    import qcmanybody


# ====  Misplaced & Next Models  ================================================


class ResultBase(ProtoModel):
    """Base class for all result classes"""

    # input_data: InputBase = Field(..., description=InputBase.__doc__)
    input_data: Any
    success: bool = Field(
        ...,
        description="A boolean indicator that the operation succeeded or failed. Allows programmatic assessment of "
        "all results regardless of if they failed or succeeded by checking `result.success`.",
    )
    stdout: Optional[str] = Field(
        None,
        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.")


class SuccessfulResultBase(ResultBase):
    """Base object for any successful result"""

    success: Literal[True] = Field(True, description="Always `True` for a successful result")


# ====  Protocols  ==============================================================


class ClusterResultsProtocolEnum(str, Enum):
    r"""Which component results to preserve in a many body result; usually AtomicResults."""

    all = "all"
    # max_nbody = "max_nbody"
    none = "none"


[docs] class ManyBodyProtocols(ProtoModel): """ Protocols regarding the manipulation of a ManyBody output data. """ schema_name: Literal["qcschema_many_body_protocols"] = "qcschema_many_body_protocols" cluster_results: ClusterResultsProtocolEnum = Field( ClusterResultsProtocolEnum.none, description=str(ClusterResultsProtocolEnum.__doc__) ) model_config = ExtendedConfigDict(force_skip_defaults=True)
[docs] def convert_v( self, target_version: int, / ) -> Union["qcmanybody.models.v1.ManyBodyProtocols", "qcmanybody.models.v2.ManyBodyProtocols"]: """Convert to instance of particular QCSchema version.""" import qcmanybody as qcmb if check_convertible_version(target_version, error="ManyBodyProtocols") == "self": return self dself = self.model_dump() if target_version == 1: # serialization is compact, so use model to assure value dself.pop("cluster_results", None) dself["component_results"] = self.cluster_results.value self_vN = qcmb.models.v1.ManyBodyProtocols(**dself) else: assert False, target_version return self_vN
# ==== Inputs =================================================================
[docs] class BsseEnum(str, Enum): """Available basis-set superposition error (BSSE) treatments.""" nocp = "nocp" # plain supramolecular interaction energy cp = "cp" # Boys-Bernardi counterpoise correction; site-site functional counterpoise (SSFC) vmfc = "vmfc" # Valiron-Mayer function counterpoise ssfc = "cp" mbe = "nocp" none = "nocp"
[docs] def formal(self): return { "nocp": "Non-Counterpoise Corrected", "cp": "Counterpoise Corrected", "vmfc": "Valiron-Mayer Function Counterpoise", }[self]
[docs] def abbr(self): return { "nocp": "NoCP", "cp": "CP", "vmfc": "VMFC", }[self]
FragBasIndex = Tuple[Tuple[int], Tuple[int]]
[docs] class ManyBodyKeywords(ProtoModel): """The many-body-specific keywords for user control.""" schema_name: Literal["qcschema_many_body_keywords"] = "qcschema_many_body_keywords" bsse_type: List[BsseEnum] = Field( [BsseEnum.cp], # definitive description description="Requested BSSE treatments. First in list determines which interaction or total " "energy/gradient/Hessian returned.", ) embedding_charges: Optional[Dict[int, List[float]]] = Field( None, description="Atom-centered point charges to be used on molecule fragments whose basis sets are not included in " "the computation. Keys: 1-based index of fragment. Values: list of atom charges for that fragment. " "At present, QCManyBody will only accept non-None values of this keyword if environment variable " "QCMANYBODY_EMBEDDING_CHARGES is set.", # TODO embedding charges should sum to fragment charge, right? enforce? # TODO embedding charges irrelevant to CP (basis sets always present)? json_schema_extra={ "shape": ["nfr", "<varies: nat in ifr>"], }, ) return_total_data: Optional[bool] = Field( None, validate_default=True, # definitive description description="When True, returns the total data (energy/gradient/Hessian) of the system, otherwise returns " "interaction data. Default is False for energies, True for gradients and Hessians. Note that the calculation " "of counterpoise corrected total energies implies the calculation of the energies of monomers in the monomer " "basis, hence specifying ``return_total_data = True`` may carry out more computations than " "``return_total_data = False``. For gradients and Hessians, ``return_total_data = False`` is rarely useful.", ) levels: Optional[Dict[Union[int, Literal["supersystem"]], str]] = Field( None, # definitive description. appended in Computer description="Dictionary of different levels of theory for different levels of expansion. Note that the primary " "method_string is not used when this keyword is given. ``supersystem`` computes all higher order n-body " "effects up to the number of fragments; this higher-order correction uses the nocp basis, regardless of " "bsse_type. A method fills in for any lower unlisted nbody levels. Note that if " "both this and max_nbody are provided, they must be consistent. Examples: " "SUPERSYSTEM definition suspect" "* {1: 'ccsd(t)', 2: 'mp2', 'supersystem': 'scf'} " "* {2: 'ccsd(t)/cc-pvdz', 3: 'mp2'} " "* Now invalid: {1: 2, 2: 'ccsd(t)/cc-pvdz', 3: 'mp2'} ", ) max_nbody: Optional[int] = Field( None, validate_default=True, # definitive description description="Maximum number of bodies to include in the many-body treatment. Possible: max_nbody <= nfragments. " "Default: max_nbody = nfragments.", ) supersystem_ie_only: Optional[bool] = Field( False, validate_default=True, # definitive description description="Target the supersystem total/interaction energy (IE) data over the many-body expansion (MBE) " "analysis, thereby omitting intermediate-body calculations. When False (default), compute each n-body level " "in the MBE up through ``max_nbody``. When True (only allowed for ``max_nbody = nfragments`` ), only compute " "enough for the overall interaction/total energy: max_nbody-body and 1-body. When True, properties " "``INTERACTION {driver} THROUGH {max_nbody}-BODY`` will always be available; " "``TOTAL {driver} THROUGH {max_nbody}-BODY`` will be available depending on ``return_total_data`` ; and " "``{max_nbody}-BODY CONTRIBUTION TO {driver}`` won't be available (except for dimers). This keyword produces " "no savings for a two-fragment molecule. But for the interaction energy of a three-fragment molecule, for " "example, 2-body subsystems can be skipped with ``supersystem_ie_only=True``. Do not use with ``vmfc`` in " "``bsse_type`` as it cannot produce savings.", )
[docs] @field_validator("bsse_type", mode="before") @classmethod def set_bsse_type(cls, v: Any) -> List[BsseEnum]: if not isinstance(v, list): v = [v] # emulate ordered set # * bt.lower() as return (w/i `list(dict.fromkeys([bt.lower() ...`) # works until aliases added to BsseEnum # * BsseEnum[bt].value as return works for good vals, but passing bad # vals through as bt lets pydantic raise a clearer error message return list( dict.fromkeys( [(BsseEnum[bt.lower()].value if bt.lower() in BsseEnum.__members__ else bt.lower()) for bt in v] ) )
[docs] class ManyBodySpecification(ProtoModel): """Combining the what (ManyBodyKeywords) with the how (AtomicSpecification).""" schema_name: Literal["qcschema_many_body_specification"] = "qcschema_many_body_specification" keywords: ManyBodyKeywords = Field(..., description=ManyBodyKeywords.__doc__) program: str = Field( "", description="Many Body Expansion CMS code / QCEngine procedure with which to run the MB decomposition." ) protocols: ManyBodyProtocols = Field(ManyBodyProtocols(), description=str(ManyBodyProtocols.__doc__)) driver: DriverEnum = Field( ..., description="The computation driver; i.e., energy, gradient, hessian.", ) # specification: Union[AtomicSpecification, Dict[str, AtomicSpecification]] = Field( specification: Dict[str, AtomicSpecification] = Field( ..., description="??? TODO expand to cbs, fd", ) extras: Dict[str, Any] = Field( {}, description="Additional information to bundle with the computation. Use for schema development and scratch space.", )
[docs] @field_validator("specification", mode="before") @classmethod def set_specification(cls, v: Any) -> Dict[str, AtomicSpecification]: # print(f"hit atomicspecification validator with {type(v)=} {v}", end="") # v could be model instance or dict if isinstance(v, AtomicSpecification) or "model" in v: v = {"(auto)": v} # print(f" ... setting v={v}") return v
@field_validator("program") @classmethod def _check_procedure(cls, v): return v.lower()
[docs] def convert_v( self, target_version: int, / ) -> Union["qcmanybody.models.v1.ManyBodySpecification", "qcmanybody.models.v2.ManyBodySpecification"]: """Convert to instance of particular QCSchema version.""" import qcmanybody as qcmb if check_convertible_version(target_version, error="ManyBodySpecification") == "self": return self dself = self.model_dump() if target_version == 1: dself.pop("schema_name") dself["keywords"].pop("schema_name") try: dself["specification"].pop("schema_name") except KeyError: for spec in dself["specification"].values(): spec.pop("schema_name") dself.pop("program") # not in v1 dself["protocols"] = self.protocols.convert_v(target_version) self_vN = qcmb.models.v1.ManyBodySpecification(**dself) else: assert False, target_version return self_vN
[docs] class ManyBodyInput(ProtoModel): """Combining the what and how (ManyBodySpecification) with the who (Molecule).""" schema_name: Literal["qcschema_many_body_input"] = "qcschema_many_body_input" schema_version: Literal[2] = Field( 2, description="The version number of ``schema_name`` to which this model conforms.", ) provenance: Provenance = Field(Provenance(**provenance_stamp(__name__)), description=str(Provenance.__doc__)) id: Optional[str] = None specification: ManyBodySpecification = Field( ..., description="???", ) molecule: Molecule = Field( ..., description="Target molecule for many-body expansion (MBE) or interaction energy (IE) analysis.", )
[docs] def convert_v( self, target_version: int, / ) -> Union["qcmanybody.models.v1.ManyBodyInput", "qcmanybody.models.v2.ManyBodyInput"]: """Convert to instance of particular QCSchema version.""" import qcmanybody as qcmb if check_convertible_version(target_version, error="ManyBodyInput") == "self": return self dself = self.model_dump() if target_version == 1: dself.pop("schema_name") dself.pop("schema_version") dself.pop("id") # not in v1 dself.pop("provenance") # not in v1 dself["molecule"] = self.molecule.convert_v(target_version) dself["specification"] = self.specification.convert_v(target_version) self_vN = qcmb.models.v1.ManyBodyInput(**dself) else: assert False, target_version return self_vN
# ==== Properties ============================================================= # class ManyBodyProperties defined through create_model manybodyproperties_doc = """ Named properties of manybody computations following the MolSSI QCSchema. All arrays are stored flat but must be reshapable into the dimensions in attribute ``shape``, with abbreviations as follows: * nat: number of atoms = :attr:`~qcmanybody.models.v2.ManyBodyProperties.calcinfo_natom` * nmc: number of model chemistries = :attr:`~qcmanybody.models.v2.ManyBodyProperties.calcinfo_nmc` * nfr: number of fragments = :attr:`~qcmanybody.models.v2.ManyBodyProperties.calcinfo_nfr` * nmbe: number of jobs = :attr:`~qcmanybody.models.v2.ManyBodyProperties.calcinfo_nmbe` """ MAX_NBODY = int(os.environ.get("QCMANYBODY_MAX_NBODY", 5)) # 5 covers tetramers json_schema_extras = { "energy": {"units": "E_h"}, "gradient": {"units": "E_h/a0", "shape": ["nat", 3]}, "Hessian": {"units": "E_h/a0^2", "shape": ["nat" * 3, "nat" * 3]}, } mbprop = {} mbprop["schema_name"] = ( Literal["qcschema_many_body_properties"], Field("qcschema_many_body_properties"), ) # ======== Calcinfo =========================================================== mbprop["calcinfo_nmc"] = ( Optional[int], Field( None, description="The number of model chemistries applied to n-body levels of the computation.", ), ) mbprop["calcinfo_nfr"] = ( Optional[int], Field( None, description="The number of fragments in the molecule for the computation.", ), ) mbprop["calcinfo_natom"] = ( Optional[int], Field( None, description="The number of atoms in the computation.", ), ) # alias nat mbprop["calcinfo_nmbe"] = ( Optional[int], Field( None, description="The number of real/ghost molecule patterns for the computation.", ), ) # alias NBODY NUMBER # ======== Canonical ========================================================== mbprop["nuclear_repulsion_energy"] = ( Optional[float], Field( None, description="The nuclear repulsion energy.", ), ) # ret_energy mbprop["return_energy"] = ( Optional[float], Field( None, description=f"The interaction energy of the requested method: IE or total (depending on return_total_data) with cp/nocp/vmfc treatment (dep. on first of bsse_type). Always available. Identical to :attr:`~qcelemental.models.ManyBodyResult.return_result` for :attr:`~qcelemental.models.AtomicInput.driver`\\ =\\ :attr:`~qcelemental.models.DriverEnum.energy` computations.", json_schema_extra={"units": "E_h"}, ), ) # ret_gradient mbprop["return_gradient"] = ( Optional[Array[float]], Field( None, description=f"The interaction gradient of the requested method: IE or total (depending on return_total_data) with cp/nocp/vmfc treatment (dep. on first of bsse_type). Available when driver is g/h. Identical to :attr:`~qcelemental.models.ManyBodyResult.return_result` for :attr:`~qcelemental.models.AtomicInput.driver`\\ =\\ :attr:`~qcelemental.models.DriverEnum.gradient` computations.", json_schema_extra=json_schema_extras["gradient"], ), ) # ret_hessian mbprop["return_hessian"] = ( Optional[Array[float]], Field( None, description=f"The interaction Hessian of the requested method: IE or total (depending on return_total_data) with cp/nocp/vmfc treatment (dep. on first of bsse_type). Available when driver is h. Identical to :attr:`~qcelemental.models.ManyBodyResult.return_result` for :attr:`~qcelemental.models.AtomicInput.driver`\\ =\\ :attr:`~qcelemental.models.DriverEnum.hessian` computations.", json_schema_extra=json_schema_extras["Hessian"], ), ) # ======== CP E/G/H summary data ============================================== for singular in ["energy", "gradient", "Hessian"]: jse = json_schema_extras[singular] egh = singular.lower() typ = float if singular == "energy" else Array[float] plural = "energies" if singular == "energy" else singular + "s" availability_of_derivative = { "energy": "", "gradient": " & driver is g/h", "Hessian": " & driver is h", }[singular] # CP-CORRECTED TOTAL ENERGY THROUGH {nb}-BODY for nb in range(1, MAX_NBODY): mbprop[f"cp_corrected_total_{egh}_through_{nb}_body"] = ( Optional[typ], Field( None, description=f"MBE sum of subsystems of {nb}-body or fewer (cumulative); summed are total {plural} w/ cp treatment. Available when cp in bsse_type & rtd=T & max_nbody>={nb}{availability_of_derivative}.", json_schema_extra=jse, ), ) # CP-CORRECTED TOTAL ENERGY mbprop[f"cp_corrected_total_{egh}"] = ( Optional[typ], Field( None, description=f"Best available total {singular} with cp treatment: cp_corrected_total_{egh}_through_{{max_nbody}}_body. Available when cp in bsse_type & rtd=T{availability_of_derivative}.", json_schema_extra=jse, ), ) # CP-CORRECTED INTERACTION ENERGY THROUGH {nb}-BODY for nb in range(1, MAX_NBODY): mbprop[f"cp_corrected_interaction_{egh}_through_{nb}_body"] = ( Optional[typ], Field( None, description=f"{nb}-body total data less 1-body total data for cumulative IE; inputs are total {plural} with cp treatment. Available when when cp in bsse_type & max_nbody>={nb}{availability_of_derivative}. The 1-body quantity is zero by definition.", json_schema_extra=jse, ), ) # CP-CORRECTED INTERACTION ENERGY mbprop[f"cp_corrected_interaction_{egh}"] = ( Optional[typ], Field( None, description=f"Best available interaction {singular} with cp treatment: cp_corrected_interaction_{egh}_through_{{max_nbody}}_body. Available when cp in bsse_type{availability_of_derivative}.", json_schema_extra=jse, ), ) # CP-CORRECTED {nb}-BODY CONTRIBUTION TO ENERGY for nb in range(2, MAX_NBODY): mbprop[f"cp_corrected_{nb}_body_contribution_to_{egh}"] = ( Optional[typ], Field( None, description=f"{nb}-body total data less ({nb}-1)-body data for partial IE; inputs are total {plural} w/ cp treat. Available when cp in bsse_type & max_nbody>={nb}{availability_of_derivative}.", json_schema_extra=jse, ), ) # ======== NOCP E/G/H summary data ============================================ # NOCP-CORRECTED TOTAL ENERGY THROUGH {nb}-BODY for nb in range(1, MAX_NBODY): mbprop[f"nocp_corrected_total_{egh}_through_{nb}_body"] = ( Optional[typ], Field( None, description=f"MBE sum of subsystems of {nb}-body or fewer (cumulative); summed are total {plural} without cp treatment. Available when nocp in bsse_type & max_nbody>={nb}{availability_of_derivative}.", json_schema_extra=jse, ), ) # NOCP-CORRECTED TOTAL ENERGY mbprop[f"nocp_corrected_total_{egh}"] = ( Optional[typ], Field( None, description=f"Best available total {singular} without cp treatment: nocp_corrected_total_{egh}_through_{{max_nbody}}_body. Available when nocp in bsse_type{availability_of_derivative}.", json_schema_extra=jse, ), ) # NOCP-CORRECTED INTERATION ENERGY THROUGH {nb}-BODY for nb in range(1, MAX_NBODY): mbprop[f"nocp_corrected_interaction_{egh}_through_{nb}_body"] = ( Optional[typ], Field( None, description=f"{nb}-body total data less 1-body total data for cumulative IE; inputs are total {plural} without cp treatment. Available when when nocp in bsse_type & max_nbody>={nb}{availability_of_derivative}. The 1-body quantity is zero by definition.", json_schema_extra=jse, ), ) # TODO note htat TOT 1BODY cp=nocp=vmfc # TODO note that summ INTERACTION ENERGY props (w/o explicit -BODY) return 0.0 for max_nbody=1 for completeness # NOCP-CORRECTED INTERACTION ENERGY mbprop[f"nocp_corrected_interaction_{egh}"] = ( Optional[typ], Field( None, description=f"Best available interaction {singular} without cp treatment: nocp_corrected_interaction_{egh}_through_{{max_nbody}}_body. Available when nocp in bsse_type{availability_of_derivative}.", json_schema_extra=jse, ), ) # NOCP-CORRECTED {nb}-BODY CONTRIBUTION TO ENERGY for nb in range(2, MAX_NBODY): mbprop[f"nocp_corrected_{nb}_body_contribution_to_{egh}"] = ( Optional[typ], Field( None, description=f"{nb}-body total data less ({nb}-1)-body data for partial IE; inputs are total {plural} w/o cp treatment. Available when nocp in bsse_type & max_nbody>={nb}{availability_of_derivative}.", json_schema_extra=jse, ), ) # ======== VMFC E/G/H summary data ============================================ # VMFC-CORRECTED TOTAL ENERGY THROUGH {nb}-BODY for nb in range(1, MAX_NBODY): mbprop[f"vmfc_corrected_total_{egh}_through_{nb}_body"] = ( Optional[typ], Field( None, description=f"MBE sum of subsystems of {nb}-body or fewer (cumulative); summed are total {plural} with vmfc treatment. Available when vmfc in bsse_type & max_nbody>={nb}{availability_of_derivative}.", json_schema_extra=jse, ), ) # VMFC-CORRECTED TOTAL ENERGY mbprop[f"vmfc_corrected_total_{egh}"] = ( Optional[typ], Field( None, description=f"Best available total {singular} with vmfc treatment: vmfc_corrected_total_{egh}_through_{{max_nbody}}_body. Available when vmfc in bsse_type{availability_of_derivative}.", json_schema_extra=jse, ), ) # VMFC-CORRECTED INTERATION ENERGY THROUGH {nb}-BODY for nb in range(1, MAX_NBODY): mbprop[f"vmfc_corrected_interaction_{egh}_through_{nb}_body"] = ( Optional[typ], Field( None, description=f"{nb}-body total data less 1-body total data for cumulative IE; inputs are total {plural} w/ vmfc treatment. Available when when vmfc in bsse_type & max_nbody>={nb}{availability_of_derivative}. The 1-body quantity is zero by definition.", json_schema_extra=jse, ), ) # VMFC-CORRECTED INTERACTION ENERGY mbprop[f"vmfc_corrected_interaction_{egh}"] = ( Optional[typ], Field( None, description=f"Best available interaction {singular} with vmfc treatment: vmfc_corrected_interaction_{egh}_through_{{max_nbody}}_body. Available when vmfc in bsse_type{availability_of_derivative}.", json_schema_extra=jse, ), ) # VMFC-CORRECTED {nb}-BODY CONTRIBUTION TO ENERGY for nb in range(2, MAX_NBODY): mbprop[f"vmfc_corrected_{nb}_body_contribution_to_{egh}"] = ( Optional[typ], Field( None, description=f"{nb}-body total data less ({nb}-1)-body total data for partial IE; inputs are total {plural} w/ vmfc treatment. Available when vmfc in bsse_type & max_nbody>={nb}{availability_of_derivative}.", json_schema_extra=jse, ), ) def _validate_arb_max_nbody_fieldnames(cls, values): ok_field_name = re.compile( # fmt: off r"^(?:cp|nocp|vmfc)_corrected_((?:total|interaction)_(?:energy|gradient|hessian)" + r"(?:_through_\d+_body)?|\d+_body_contribution_to_(?:energy|gradient|hessian))$" # fmt: on ) extra_fields = values.keys() - cls.model_fields.keys() baduns = [xtra for xtra in extra_fields if not ok_field_name.match(xtra)] if baduns: raise ValueError(f"Field names not allowed: {baduns}") return values class ProtoModelSkipDefaults(ProtoModel): # fields filtered in model_validator model_config = ExtendedConfigDict(serialize_skip_defaults=True, force_skip_defaults=True, extra="allow") if TYPE_CHECKING: ManyBodyProperties = ProtoModelSkipDefaults else: # if/else suppresses a warning about using a dynamically generated class as Field type in ManyBodyResults # * deprecated but works: root_validator(skip_on_failure=True)(_validate_arb_max_nbody_fieldnames) ManyBodyProperties = create_model( "ManyBodyProperties", # __doc__=manybodyproperties_doc, # needs later pydantic __base__=ProtoModelSkipDefaults, __validators__={"validator1": model_validator(mode="before")(_validate_arb_max_nbody_fieldnames)}, **mbprop, ) 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 ManyBodyProperties field names to QCVariable names, or reverse. """ qcvars_to_mbprop = {} for skprop in ManyBodyProperties.model_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}" qcvars_to_mbprop["NBODY NUMBER"] = "calcinfo_nmbe" if reverse: return qcvars_to_mbprop else: return {v: k for k, v in qcvars_to_mbprop.items()} ManyBodyProperties.to_qcvariables = classmethod(_qcvars_translator) # ==== Results ================================================================
[docs] class ManyBodyResult(SuccessfulResultBase): schema_name: Literal["qcschema_many_body_result"] = "qcschema_many_body_result" schema_version: Literal[2] = Field( 2, description="The version number of ``schema_name`` to which this model conforms.", ) id: Optional[str] = Field(None, description="The optional ID for the object.") extras: Dict[str, Any] = Field( {}, description="Additional information to bundle with the object. Use for schema development and scratch space.", ) provenance: Provenance = Field(..., description=str(Provenance.__doc__)) input_data: ManyBodyInput = Field( ..., ) success: Literal[True] = Field( True, description="A boolean indicator that the operation succeeded or failed. Allows programmatic assessment of " "all results regardless of if they failed or succeeded by checking `result.success`.", ) # native_files placeholder for when any mbe programs supply extra files or need an input file. no protocol at present native_files: Dict[str, Any] = Field({}, description="DSL files.") molecule: Molecule = Field( ..., description="The molecule in results fragmentation and frame. Since QCManyBody doesn't disrupt the mol, should be identical to input_data.molecule.", ) properties: ManyBodyProperties = Field(..., description=str(ManyBodyProperties.__doc__)) cluster_properties: Dict[str, AtomicProperties] = 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.", ) cluster_results: Dict[str, AtomicResult] = Field(..., description="Detailed results") 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.", ) stdout: Optional[str] = Field( None, 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.") @field_validator("cluster_results") def _cluster_results(cls, value, values: ValidationInfo): crp = values.data["input_data"].specification.protocols.cluster_results if crp == "all": return value elif crp == "none": return {} else: raise ValueError(f"Protocol `component_resutls:{crp}` is not understood")
[docs] def convert_v( self, target_version: int, / ) -> Union["qcmanybody.models.v1.ManyBodyResult", "qcmanybody.models.v2.ManyBodyResult"]: """Convert to instance of particular QCSchema version.""" import qcmanybody as qcmb if check_convertible_version(target_version, error="ManyBodyResult") == "self": return self dself = self.model_dump() if target_version == 1: dself.pop("schema_name") # changed in v1 dself.pop("schema_version") # changed in v1 # for input_data, work from model, not dict, to use convert_v dself["input_data"] = self.input_data.convert_v(1).model_dump() # exclude_unset=True, exclude_none=True dself.pop("native_files") dself.pop("molecule") dself["component_properties"] = dself.pop("cluster_properties") dself["component_results"] = { k: atres.convert_v(target_version) for k, atres in self.cluster_results.items() } dself.pop("cluster_results") self_vN = qcmb.models.v1.ManyBodyResult(**dself) else: assert False, target_version return self_vN