Skip to content

Commit

Permalink
clear away version_stamp and placeholder Mol v3
Browse files Browse the repository at this point in the history
  • Loading branch information
loriab committed Dec 21, 2024
1 parent 499b802 commit d2a4f8f
Show file tree
Hide file tree
Showing 16 changed files with 93 additions and 141 deletions.
9 changes: 6 additions & 3 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@ New Features
Enhancements
++++++++++++
- (:pr:`364`)
- (:pr:`364`)
- (:pr:`364`)
- (:pr:`364`)
- (:pr:`364`) molparse learns to pass through schema v3, though no new field for Mol yet.
- (:pr:`364`) ``v2.FailedOperation`` gained schema_name and schema_version=2. unversioned in v1
- (:pr:`364`) ``v2.BasisSet.schema_version`` is now 2, with no layout change.
- (:pr:`364`) ``v2.Molecule.schema_version`` is now 3. convert_v of all the models learned to handle the new schema_version.
- (:pr:`364`) v2: standardizing on In/Res get versions, Ptcl/Kw/Spec get only schema_name. At, Opt, TD
- (:pr:`364`) v1/v2: removing the version_stamps from the models: At, Opt, TD, Fail, BAsis, Mol. so it will error rather than clobber if constructed with wrong version. convert_v now handles.
- (:pr:`364`) convert_v functions learned to handle model.basis=BasisSet, not just str.
- (:pr:`364`) ``Molecule`` and ``BasisSet`` and ``WavefunctionProperties`` learned to ``convert_v`` to interconvert between v1 and v2. No layout changes.
``BasisSet.schema_name`` standardized to ``qcschema_basis_set``.
Expand Down
5 changes: 1 addition & 4 deletions qcelemental/models/v1/basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,6 @@ class Config(ProtoModel.Config):
def schema_extra(schema, model):
schema["$schema"] = qcschema_draft

@validator("schema_version", pre=True)
def _version_stamp(cls, v):
return 1

@validator("atom_map")
def _check_atom_map(cls, v, values):
sv = set(v)
Expand Down Expand Up @@ -246,6 +242,7 @@ def convert_v(
dself = self.model_dump()
if target_version == 2:
dself.pop("schema_name") # changes in v2
dself.pop("schema_version") # changes in v2

self_vN = qcel.models.v2.BasisSet(**dself)
else:
Expand Down
10 changes: 4 additions & 6 deletions qcelemental/models/v1/molecule.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,12 +378,6 @@ def __init__(self, orient: bool = False, validate: Optional[bool] = None, **kwar
elif validate or geometry_prep:
values["geometry"] = float_prep(values["geometry"], geometry_noise)

@validator("schema_version", pre=True)
def _version_stamp(cls, v):
# seemingly unneeded, this lets conver_v re-label the model w/o discarding model and
# submodel version fields first.
return 2

@validator("geometry")
def _must_be_3n(cls, v, values, **kwargs):
n = len(values["symbols"])
Expand Down Expand Up @@ -1518,6 +1512,10 @@ def convert_v(
loss_store = {}
dself = self.model_dump()
if target_version == 2:
# below is assignment rather than popping so Mol() records as set and future Mol.model_dump() includes the field.
# QCEngine _build_model convert_v(2) can lose it otherwise, and molparse machinery wants to see the field.
dself["schema_version"] = 3

self_vN = qcel.models.v2.Molecule(**dself)
else:
assert False, target_version
Expand Down
41 changes: 12 additions & 29 deletions qcelemental/models/v1/procedures.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,6 @@ class QCInputSpecification(ProtoModel):
description="Additional information to bundle with the computation. Use for schema development and scratch space.",
)

@validator("schema_version", pre=True)
def _version_stamp(cls, v):
return 1

def convert_v(
self, target_version: int, /
) -> Union["qcelemental.models.v1.QCInputSpecification", "qcelemental.models.v2.AtomicSpecification"]:
Expand Down Expand Up @@ -121,10 +117,6 @@ def __repr_args__(self) -> "ReprArgs":
("molecule_hash", self.initial_molecule.get_hash()[:7]),
]

@validator("schema_version", pre=True)
def _version_stamp(cls, v):
return 1

def convert_v(
self, target_version: int, /
) -> Union["qcelemental.models.v1.OptimizationInput", "qcelemental.models.v2.OptimizationInput"]:
Expand All @@ -136,8 +128,11 @@ def convert_v(

dself = self.dict()
if target_version == 2:
dself.pop("schema_version") # changed in v2
dself.pop("hash_index", None) # no longer used, so dropped in v2

dself["initial_molecule"] = self.initial_molecule.convert_v(target_version)

spec = {}
spec["extras"] = dself.pop("extras")
spec["protocols"] = dself.pop("protocols")
Expand All @@ -160,7 +155,7 @@ class OptimizationResult(OptimizationInput):
schema_name: constr( # type: ignore
strip_whitespace=True, regex=qcschema_optimization_output_default
) = qcschema_optimization_output_default
schema_version: Literal[1] = 1
# Note no schema_version: Literal[1] = Field(1) b/c inherited from OptimizationInput

final_molecule: Optional[Molecule] = Field(..., description="The final molecule of the geometry optimization.")
trajectory: List[AtomicResult] = Field(
Expand Down Expand Up @@ -199,10 +194,6 @@ def _trajectory_protocol(cls, v, values):

return v

@validator("schema_version", pre=True)
def _version_stamp(cls, v):
return 1

def convert_v(
self,
target_version: int,
Expand Down Expand Up @@ -242,6 +233,7 @@ def convert_v(

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

v1_input_data = {
k: dself.pop(k)
Expand Down Expand Up @@ -272,6 +264,7 @@ def convert_v(
dself["input_data"] = v2_input_data
optsubptcl = None

dself["final_molecule"] = self.final_molecule.convert_v(target_version)
dself["properties"] = {
"return_energy": dself["energies"][-1],
"optimization_iterations": len(dself["energies"]),
Expand Down Expand Up @@ -318,15 +311,11 @@ class OptimizationSpecification(ProtoModel):
keywords: Dict[str, Any] = Field({}, description="The optimization specific keywords to be used.")
protocols: OptimizationProtocols = Field(OptimizationProtocols(), description=str(OptimizationProtocols.__doc__))

@validator("schema_version", pre=True)
def _version_stamp(cls, v):
return 1

@validator("procedure")
def _check_procedure(cls, v):
return v.lower()

# NOTE: def convert_v() is missing deliberately. Because the v1 schema has a minor and different role only for
# NOTE: def convert_v() is missing deliberately. Because the v1 schema has a different role only for
# TorsionDrive, it doesn't have nearly enough info to create a v2 schema.


Expand Down Expand Up @@ -397,10 +386,6 @@ def _check_input_specification(cls, value):
assert value.driver == DriverEnum.gradient, "driver must be set to gradient"
return value

@validator("schema_version", pre=True)
def _version_stamp(cls, v):
return 1

def convert_v(
self, target_version: int, /
) -> Union["qcelemental.models.v1.TorsionDriveInput", "qcelemental.models.v2.TorsionDriveInput"]:
Expand Down Expand Up @@ -436,7 +421,8 @@ def convert_v(

dtop = {}
dtop["provenance"] = dself.pop("provenance")
dtop["initial_molecules"] = dself.pop("initial_molecule")
dself.pop("initial_molecule")
dtop["initial_molecules"] = [mol.convert_v(target_version) for mol in self.initial_molecule]
dtop["specification"] = tdspec
dself.pop("schema_name")
dself.pop("schema_version")
Expand All @@ -458,7 +444,7 @@ class TorsionDriveResult(TorsionDriveInput):
"""

schema_name: constr(strip_whitespace=True, regex=qcschema_torsion_drive_output_default) = qcschema_torsion_drive_output_default # type: ignore
schema_version: Literal[1] = 1
# Note no schema_version: Literal[1] = Field(1) b/c inherited from TorsionDriveInput

final_energies: Dict[str, float] = Field(
..., description="The final energy at each angle of the TorsionDrive scan."
Expand All @@ -481,10 +467,6 @@ class TorsionDriveResult(TorsionDriveInput):
error: Optional[ComputeError] = Field(None, description=str(ComputeError.__doc__))
provenance: Provenance = Field(..., description=str(Provenance.__doc__))

@validator("schema_version", pre=True)
def _version_stamp(cls, v):
return 1

def convert_v(
self, target_version: int, /, *, external_input_data: "TorsionDriveInput" = None
) -> Union["qcelemental.models.v1.TorsionDriveResult", "qcelemental.models.v2.TorsionDriveResult"]:
Expand Down Expand Up @@ -536,7 +518,8 @@ def convert_v(
dtop["stderr"] = dself.pop("stderr")
dtop["success"] = dself.pop("success")
dtop["final_energies"] = dself.pop("final_energies")
dtop["final_molecules"] = dself.pop("final_molecules")
dself.pop("final_molecules")
dtop["final_molecules"] = {k: m.convert_v(target_version) for k, m in self.final_molecules.items()}
dtop["optimization_history"] = {
k: [opthist_class(**res).convert_v(target_version) for res in lst]
for k, lst in dself["optimization_history"].items()
Expand Down
19 changes: 8 additions & 11 deletions qcelemental/models/v1/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,12 +619,6 @@ def __repr_args__(self) -> "ReprArgs":
("molecule_hash", self.molecule.get_hash()[:7]),
]

@validator("schema_version", pre=True)
def _version_stamp(cls, v):
# seemingly unneeded, this lets conver_v re-label the model w/o discarding model and
# submodel version fields first.
return 1

def convert_v(
self, target_version: int, /
) -> Union["qcelemental.models.v1.AtomicInput", "qcelemental.models.v2.AtomicInput"]:
Expand All @@ -637,11 +631,14 @@ def convert_v(
dself = self.dict()
if target_version == 2:
dself.pop("schema_name") # changes in v2
dself.pop("schema_version") # changes in v2

# TODO consider Model.convert_v
model = dself.pop("model")
if isinstance(self.model.basis, BasisSet):
model["basis"] = self.model.basis.convert_v(target_version)
dself["molecule"] = self.molecule.convert_v(target_version)

spec = {}
spec["driver"] = dself.pop("driver")
spec["model"] = model
Expand Down Expand Up @@ -698,10 +695,6 @@ def _input_to_output(cls, v):
"which will be converted to {0}".format(qcschema_output_default, qcschema_input_default)
)

@validator("schema_version", pre=True)
def _version_stamp(cls, v):
return 1

@validator("return_result")
def _validate_return_result(cls, v, values):
if values["driver"] == "gradient":
Expand Down Expand Up @@ -854,6 +847,9 @@ def convert_v(
dself = self.dict()
if target_version == 2:
dself.pop("schema_name") # changes in v2
dself.pop("schema_version") # changes in v2

molecule = self.molecule.convert_v(target_version)

# remove harmless empty error field that v2 won't accept. if populated, pydantic will catch it.
if not dself.get("error", True):
Expand All @@ -863,7 +859,7 @@ def convert_v(
"specification": {
k: dself.pop(k) for k in list(dself.keys()) if k in ["driver", "keywords", "model", "protocols"]
},
"molecule": dself["molecule"], # duplicate since input mol has been overwritten
"molecule": molecule, # duplicate since input mol has been overwritten
}
in_extras = {
k: dself["extras"].pop(k) for k in list(dself["extras"].keys()) if k in []
Expand Down Expand Up @@ -893,6 +889,7 @@ def convert_v(
if external_protocols:
dself["input_data"]["specification"]["protocols"] = external_protocols

dself["molecule"] = molecule
if self.wavefunction is not None:
dself["wavefunction"] = self.wavefunction.convert_v(target_version).model_dump()

Expand Down
27 changes: 5 additions & 22 deletions qcelemental/models/v2/atomic.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ class AtomicProperties(ProtoModel):
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.AtomicProperties.schema_name` to which this model conforms.",
# TRIAL )

# ======== Calcinfo =======================================================

Expand Down Expand Up @@ -303,10 +299,6 @@ def _validate_derivs(cls, v, info):
raise ValueError(f"Derivative must be castable to shape {shape}!")
return v

# TRIAL @field_validator("schema_version", mode="before")
# TRIAL def _version_stamp(cls, v):
# TRIAL return 2

def dict(self, *args, **kwargs):
# pure-json dict repr for QCFractal compliance, see https://github.com/MolSSI/QCFractal/issues/579
# Sep 2021: commenting below for now to allow recomposing AtomicResult.properties for qcdb.
Expand Down Expand Up @@ -683,10 +675,7 @@ class AtomicSpecification(ProtoModel):
"""Specification for a single point QC calculation"""

schema_name: Literal["qcschema_atomic_specification"] = "qcschema_atomic_specification"
# schema_version: Literal[2] = Field(
# 2,
# description="The version number of ``schema_name`` to which this model conforms.",
# )

keywords: Dict[str, Any] = Field({}, description="The program specific keywords to be used.")
program: str = Field(
"", description="The program for which the Specification is intended."
Expand Down Expand Up @@ -767,12 +756,6 @@ def __repr_args__(self) -> "ReprArgs":
("molecule_hash", self.molecule.get_hash()[:7]),
]

@field_validator("schema_version", mode="before")
def _version_stamp(cls, v):
# seemingly unneeded, this lets conver_v re-label the model w/o discarding model and
# submodel version fields first.
return 2

def convert_v(
self, target_version: int, /
) -> Union["qcelemental.models.v1.AtomicInput", "qcelemental.models.v2.AtomicInput"]:
Expand All @@ -785,12 +768,14 @@ def convert_v(
dself = self.model_dump()
if target_version == 1:
dself.pop("schema_name")
dself.pop("schema_version")

# TODO consider Model.convert_v
model = dself["specification"].pop("model")
if isinstance(self.specification.model.basis, BasisSet):
model["basis"] = self.specification.model.basis.convert_v(target_version)

dself["molecule"] = self.molecule.convert_v(target_version).model_dump()
dself["driver"] = dself["specification"].pop("driver")
dself["model"] = model
dself["keywords"] = dself["specification"].pop("keywords", None)
Expand Down Expand Up @@ -848,10 +833,6 @@ class AtomicResult(ProtoModel):
description="Additional information to bundle with the computation. Use for schema development and scratch space.",
)

@field_validator("schema_version", mode="before")
def _version_stamp(cls, v):
return 2

@field_validator("return_result")
@classmethod
def _validate_return_result(cls, v, info):
Expand Down Expand Up @@ -995,6 +976,7 @@ def convert_v(
dself = self.model_dump()
if target_version == 1:
dself.pop("schema_name")
dself.pop("schema_version")

# for input_data, work from model, not dict, to use convert_v
dself.pop("input_data")
Expand All @@ -1003,6 +985,7 @@ def convert_v(
input_data.pop("provenance", None) # discard
if self.wavefunction is not None:
dself["wavefunction"] = self.wavefunction.convert_v(target_version).model_dump()
dself["molecule"] = self.molecule.convert_v(target_version)
dself["extras"] = {**input_data.pop("extras", {}), **dself.pop("extras", {})} # merge
dself = {**input_data, **dself}

Expand Down
5 changes: 1 addition & 4 deletions qcelemental/models/v2/basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,6 @@ def _calculate_nbf(cls, atom_map, center_data) -> int:

return ret

@field_validator("schema_version", mode="before")
def _version_stamp(cls, v):
return 2

def convert_v(
self, target_version: int, /
) -> Union["qcelemental.models.v1.BasisSet", "qcelemental.models.v2.BasisSet"]:
Expand All @@ -264,6 +260,7 @@ def convert_v(
dself = self.model_dump()
if target_version == 1:
dself.pop("schema_name") # changes in v1
dself.pop("schema_version") # changes in v1

self_vN = qcel.models.v1.BasisSet(**dself)
else:
Expand Down
4 changes: 0 additions & 4 deletions qcelemental/models/v2/failed_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,6 @@ class FailedOperation(ProtoModel):
def __repr_args__(self) -> "ReprArgs":
return [("error", self.error)]

@field_validator("schema_version", mode="before")
def _version_stamp(cls, v):
return 2

def convert_v(
self, target_version: int, /
) -> Union["qcelemental.models.v1.FailedOperation", "qcelemental.models.v2.FailedOperation"]:
Expand Down
Loading

0 comments on commit d2a4f8f

Please sign in to comment.