Skip to content

Commit

Permalink
first tidying
Browse files Browse the repository at this point in the history
  • Loading branch information
loriab committed Dec 17, 2024
1 parent c5f9d7c commit 4fc14bb
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 54 deletions.
9 changes: 7 additions & 2 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ New Features

Enhancements
++++++++++++
- (:pr:`363`)
- (:pr:`363`)
- (:pr:``)
- (:pr:``)
- (:pr:``)
- (:pr:``) ``schema_name`` output chanded to result ``qcschema_output`` to ``qcschema_atomic_result``. also opt
- (:pr:``) ``TDKeywords`` renamed to ``TorsionDriveKeywords``
- (:pr:``) ``AtomicResultProtocols`` renamed to ``AtomicProtocols`` and ``AtomicResultProperties`` to ``AtomicProperties``
- (:pr:``) new ``v2.TorsionDriveProtocols`` model with field ``scan_results`` to control all/none/lowest saving of optimizationresults at each grid point. Use "all" for proper conversion to v1.
- (:pr:`363`) ``v2.TorsionDriveResult`` no longer inherits from Input and now has indep id and extras and new native_files.
- (:pr:`363`) ``v2.TorsionDriveInput.initial_molecule`` now ``initial_molecules`` as it's a list of >=1 molecules. keep change?
- (:pr:`363`) ``v2. TorsionDriveSpecification`` is a new model. instead of ``v2.TorsionDriveInput`` having a ``input_specification`` and an ``optimization_spec`` fields, it has a ``specification`` field that is a ``TorsionDriveSpecification`` which in turn hold opt info and in turn gradient/atomic info.
Expand Down
2 changes: 2 additions & 0 deletions qcelemental/models/v1/procedures.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ def convert_v(
dself.pop("error")

dself.pop("hash_index", None) # no longer used, so dropped in v2
dself.pop("schema_name") # changed in v2

v1_input_data = {
k: dself.pop(k)
Expand Down Expand Up @@ -422,6 +423,7 @@ def convert_v(
tdspec["program"] = "torsiondrive"
tdspec["extras"] = dself.pop("extras")
tdspec["keywords"] = dself.pop("keywords")
tdspec["protocols"] = {"scan_results": "all"}
tdspec["specification"] = optspec

dtop = {}
Expand Down
9 changes: 5 additions & 4 deletions qcelemental/models/v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@
OptimizationProtocols,
OptimizationResult,
OptimizationSpecification,
TDKeywords,
TorsionDriveInput,
TorsionDriveKeywords,
TorsionDriveProtocols,
TorsionDriveResult,
TorsionDriveSpecification,
)
from .results import (
AtomicInput,
AtomicProperties,
AtomicProtocols,
AtomicResult,
AtomicResultProperties,
AtomicResultProtocols,
AtomicSpecification,
WavefunctionProperties,
)
Expand All @@ -29,7 +30,7 @@ def qcschema_models():
return [
AtomicInput,
AtomicResult,
AtomicResultProperties,
AtomicProperties,
BasisSet,
Molecule,
Provenance,
Expand Down
85 changes: 74 additions & 11 deletions qcelemental/models/v2/procedures.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from .basemodels import ExtendedConfigDict, ProtoModel
from .common_models import ComputeError, DriverEnum, Model, Provenance, check_convertible_version
from .molecule import Molecule
from .results import AtomicResult, AtomicResultProperties, AtomicSpecification
from .results import AtomicProperties, AtomicResult, AtomicSpecification
from .types import Array

if TYPE_CHECKING:
Expand Down Expand Up @@ -58,7 +58,7 @@ class OptimizationProtocols(ProtoModel):
]

OptSubProps = Annotated[
Union[AtomicResultProperties], # , ManyBodyProperties],
Union[AtomicProperties], # , ManyBodyProperties],
Field(
discriminator="schema_name",
description="An abridged single-geometry property set. Either an ordinary atomic/single-point or a many-body properties.",
Expand Down Expand Up @@ -233,7 +233,7 @@ class OptimizationProperties(ProtoModel):
class OptimizationResult(ProtoModel):
"""QCSchema results model for geometry optimization."""

schema_name: Literal["qcschema_optimization_output"] = "qcschema_optimization_output" # TODO _result?
schema_name: Literal["qcschema_optimization_result"] = "qcschema_optimization_result"
schema_version: Literal[2] = Field(
2,
description="The version number of ``schema_name`` to which this model conforms.",
Expand All @@ -245,7 +245,7 @@ class OptimizationResult(ProtoModel):
trajectory_results: List[AtomicResult] = Field(
..., description="A list of ordered Result objects for each step in the optimization."
)
trajectory_properties: List[AtomicResultProperties] = Field(
trajectory_properties: List[AtomicProperties] = Field(
..., description="A list of ordered energies and other properties for each step in the optimization."
)

Expand Down Expand Up @@ -310,6 +310,7 @@ def convert_v(
# for input_data, work from model, not dict, to use convert_v
dself.pop("input_data")
input_data = self.input_data.convert_v(1).model_dump() # exclude_unset=True, exclude_none=True
input_data.pop("schema_name") # prevent inheriting

dself.pop("properties") # new in v2
dself.pop("native_files") # new in v2
Expand All @@ -322,6 +323,7 @@ def convert_v(
dself.pop("trajectory_properties")

dself["extras"] = {**input_data.pop("extras", {}), **dself.pop("extras", {})} # merge
dself.pop("schema_name") # changed in v1
dself = {**input_data, **dself}

self_vN = qcel.models.v1.OptimizationResult(**dself)
Expand All @@ -332,10 +334,35 @@ def convert_v(


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


class ScanResultsProtocolEnum(str, Enum):
"""
Which gradient evaluations to keep in an optimization trajectory.
"""

all = "all" # use this if instance might be converted to v1
lowest = "lowest" # discard any optimizations at each scan point that did not find the lowest energy
none = "none"


class TorsionDriveProtocols(ProtoModel):
"""
Protocols regarding the manipulation of a Torsion Drive subcalculation history.
"""

schema_name: Literal["qcschema_torsion_drive_protocols"] = "qcschema_torsion_drive_protocols"
scan_results: ScanResultsProtocolEnum = Field(
ScanResultsProtocolEnum.none, description=str(ScanResultsProtocolEnum.__doc__)
)

model_config = ExtendedConfigDict(force_skip_defaults=True)


# ==== Inputs (Kw/Spec/In) ====================================================


class TDKeywords(ProtoModel):
class TorsionDriveKeywords(ProtoModel):
"""
TorsionDriveRecord options
Expand Down Expand Up @@ -389,8 +416,8 @@ class TorsionDriveSpecification(ProtoModel):
program: str = Field(
"", description="Torsion Drive CMS code / QCEngine procedure with which to run the torsion scan."
)
keywords: TDKeywords = Field(..., description="The torsion drive specific keywords to be used.")
# protocols: TorsionDriveProtocols = Field(TorsionDriveProtocols(), description=str(TorsionDriveProtocols.__doc__))
keywords: TorsionDriveKeywords = Field(..., description="The torsion drive specific keywords to be used.")
protocols: TorsionDriveProtocols = Field(TorsionDriveProtocols(), description=str(TorsionDriveProtocols.__doc__))
extras: Dict[str, Any] = Field(
{},
description="Additional information to bundle with the computation. Use for schema development and scratch space.",
Expand Down Expand Up @@ -461,6 +488,7 @@ def convert_v(
dself["specification"].pop("schema_name")

td_program = dself["specification"].pop("program")
dself["specification"].pop("protocols") # lost
assert not dself["specification"], dself["specification"]
dself.pop("specification") # now empty

Expand All @@ -482,7 +510,7 @@ def convert_v(
class TorsionDriveResult(ProtoModel):
"""Results from running a torsion drive."""

schema_name: Literal["qcschema_torsion_drive_output"] = "qcschema_torsion_drive_output"
schema_name: Literal["qcschema_torsion_drive_result"] = "qcschema_torsion_drive_result"
schema_version: Literal[2] = Field(
2,
description="The version number of ``schema_name`` to which this model conforms.",
Expand Down Expand Up @@ -525,23 +553,58 @@ class TorsionDriveResult(ProtoModel):
def _version_stamp(cls, v):
return 2

@field_validator("optimization_history") # TODO "scan_results")
@classmethod
def _scan_protocol(cls, v, info):
# Do not propogate validation errors
if "input_data" not in info.data:
raise ValueError("Input_data was not properly formed.")

keep_enum = info.data["input_data"].specification.protocols.scan_results
if keep_enum == "all":
pass
elif keep_enum == "lowest":
if not all(len(vv) == 1 for vv in v.values()):
v_trunc = {}
for scan_pt, optres_list in v.items():
final_energies = [optres.properties.return_energy for optres in optres_list]
lowest_energy_idx = final_energies.index(min(final_energies))
v_trunc[scan_pt] = [optres_list[lowest_energy_idx]]
v = v_trunc
elif keep_enum == "none":
v = {}
else:
raise ValueError(f"Protocol `scan_results:{keep_enum}` is not understood.")

return v

def convert_v(
self, target_version: int, /
) -> Union["qcelemental.models.v1.TorsionDriveResult", "qcelemental.models.v2.TorsionDriveResult"]:
"""Convert to instance of particular QCSchema version."""
"""Convert to instance of particular QCSchema version.
Notes
-----
* Use TorsionDriveProtocols.scan_results=all for full conversion to v1.
"""
import qcelemental as qcel

if check_convertible_version(target_version, error="TorsionDriveResult") == "self":
return self

dself = self.model_dump()
if target_version == 1:
opthist_class = next(iter(self.optimization_history.values()))[0].__class__
try:
opthist_class = next(iter(self.optimization_history.values()))[0].__class__
except StopIteration:
opthist_class = None
dtop = {}

# for input_data, work from model, not dict, to use convert_v
dself.pop("input_data")
input_data = self.input_data.convert_v(target_version).model_dump()
input_data.pop("schema_name") # prevent inheriting

dtop["final_energies"] = dself.pop("final_energies")
dtop["final_molecules"] = dself.pop("final_molecules")
Expand All @@ -558,7 +621,7 @@ def convert_v(
dtop["stderr"] = dself.pop("stderr")
dtop["success"] = dself.pop("success")
dtop["extras"] = {**input_data.pop("extras", {}), **dself.pop("extras", {})} # merge
dtop["schema_name"] = dself.pop("schema_name") # otherwise merge below uses TDIn schema_name
dself.pop("schema_name") # otherwise merge below uses TDIn schema_name
dself.pop("schema_version")
assert not dself, dself

Expand Down
22 changes: 11 additions & 11 deletions qcelemental/models/v2/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,22 @@
# ==== Properties =============================================================


class AtomicResultProperties(ProtoModel):
class AtomicProperties(ProtoModel):
r"""
Named properties of quantum chemistry computations following the MolSSI QCSchema.
All arrays are stored flat but must be reshapable into the dimensions in attribute ``shape``, with abbreviations as follows:
* nao: number of atomic orbitals = :attr:`~qcelemental.models.AtomicResultProperties.calcinfo_nbasis`
* nmo: number of molecular orbitals = :attr:`~qcelemental.models.AtomicResultProperties.calcinfo_nmo`
* nao: number of atomic orbitals = :attr:`~qcelemental.models.AtomicProperties.calcinfo_nbasis`
* nmo: number of molecular orbitals = :attr:`~qcelemental.models.AtomicProperties.calcinfo_nmo`
"""

schema_name: Literal["qcschema_atomic_properties"] = Field(
"qcschema_atomic_properties", description=(f"The QCSchema specification to which this model conforms.")
)
# TRIAL schema_version: Literal[2] = Field(
# TRIAL 2,
# TRIAL description="The version number of :attr:`~qcelemental.models.AtomicResultProperties.schema_name` to which this model conforms.",
# TRIAL description="The version number of :attr:`~qcelemental.models.AtomicProperties.schema_name` to which this model conforms.",
# TRIAL )

# ======== Calcinfo =======================================================
Expand Down Expand Up @@ -632,7 +632,7 @@ class NativeFilesProtocolEnum(str, Enum):
none = "none"


class AtomicResultProtocols(ProtoModel):
class AtomicProtocols(ProtoModel):
r"""Protocols regarding the manipulation of computational result data."""

schema_name: Literal["qcschema_atomic_protocols"] = "qcschema_atomic_protocols"
Expand Down Expand Up @@ -669,9 +669,9 @@ class AtomicSpecification(ProtoModel):
) # TODO interaction with cmdline
driver: DriverEnum = Field(..., description=DriverEnum.__doc__)
model: Model = Field(..., description=Model.__doc__)
protocols: AtomicResultProtocols = Field(
AtomicResultProtocols(),
description=AtomicResultProtocols.__doc__,
protocols: AtomicProtocols = Field(
AtomicProtocols(),
description=AtomicProtocols.__doc__,
)
extras: Dict[str, Any] = Field(
{},
Expand Down Expand Up @@ -784,8 +784,8 @@ def convert_v(
class AtomicResult(ProtoModel):
r"""Results from a CMS program execution."""

schema_name: Literal["qcschema_atomic_output"] = Field(
"qcschema_atomic_output", description=(f"The QCSchema specification to which this model conforms.")
schema_name: Literal["qcschema_atomic_result"] = Field(
"qcschema_atomic_result", description=(f"The QCSchema specification to which this model conforms.")
)
schema_version: Literal[2] = Field(
2,
Expand All @@ -794,7 +794,7 @@ class AtomicResult(ProtoModel):
id: Optional[str] = Field(None, description="The optional ID for the computation.")
input_data: AtomicInput = Field(..., description=str(AtomicInput.__doc__))
molecule: Molecule = Field(..., description="The molecule with frame and orientation of the results.")
properties: AtomicResultProperties = Field(..., description=str(AtomicResultProperties.__doc__))
properties: AtomicProperties = Field(..., description=str(AtomicProperties.__doc__))
wavefunction: Optional[WavefunctionProperties] = Field(None, description=str(WavefunctionProperties.__doc__))

return_result: Union[float, Array[float], Dict[str, Any]] = Field(
Expand Down
Empty file.
10 changes: 7 additions & 3 deletions qcelemental/tests/test_model_general.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@


def test_result_properties_default_skip(request, schema_versions):
AtomicResultProperties = schema_versions.AtomicResultProperties
AtomicResultProperties = (
schema_versions.AtomicProperties if ("v2" in request.node.name) else schema_versions.AtomicResultProperties
)

obj = AtomicResultProperties(scf_one_electron_energy="-5.0")
drop_qcsk(obj, request.node.name)
Expand All @@ -14,8 +16,10 @@ def test_result_properties_default_skip(request, schema_versions):
assert obj.dict().keys() == {"scf_one_electron_energy"}


def test_result_properties_default_repr(schema_versions):
AtomicResultProperties = schema_versions.AtomicResultProperties
def test_result_properties_default_repr(request, schema_versions):
AtomicResultProperties = (
schema_versions.AtomicProperties if ("v2" in request.node.name) else schema_versions.AtomicResultProperties
)

obj = AtomicResultProperties(scf_one_electron_energy="-5.0")
assert "none" not in str(obj).lower()
Expand Down
Loading

0 comments on commit 4fc14bb

Please sign in to comment.