Skip to content

Commit

Permalink
Refactored models. Moved common fields to parent classes. Created sta…
Browse files Browse the repository at this point in the history
…ndardized input and result interface.
  • Loading branch information
coltonbh committed Apr 6, 2022
1 parent cabec4a commit 199b324
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 284 deletions.
8 changes: 3 additions & 5 deletions qcelemental/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@

from . import types
from .align import AlignmentMill
from .basemodels import AutodocBaseSettings, ProtoModel
from .basemodels import AutodocBaseSettings, ProtoModel, Provenance
from .basis import BasisSet
from .common_models import ComputeError, DriverEnum, FailedOperation, Provenance
from .common_models import ComputeError, DriverEnum
from .molecule import Molecule
from .procedures import OptimizationInput, OptimizationResult
from .procedures import Optimization # scheduled for removal
from .procedures import OptimizationInput, OptimizationResult, FailedOperation, TorsionDriveInput, TorsionDriveResult
from .results import AtomicInput, AtomicResult, AtomicResultProperties
from .results import Result, ResultInput, ResultProperties # scheduled for removal


def qcschema_models():
Expand Down
91 changes: 91 additions & 0 deletions qcelemental/models/abcmodels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import abc
import json
from pathlib import Path
from typing import Any, Dict, Optional, Set, Union

import numpy as np
from pydantic import BaseModel, BaseSettings, Field
from typing_extensions import Literal

from qcelemental.testing import compare_recursive
from qcelemental.util import deserialize, serialize
from qcelemental.util.autodocs import AutoPydanticDocGenerator

from ..util import provenance_stamp
from .basemodels import ProtoModel, Provenance
from .molecule import Molecule


class InputResultBase(ProtoModel, abc.ABC):
"""Base class from which input and result models are derived
NOTE: Abstract implementation coming from Samuel Colvin's suggestion:
https://github.com/samuelcolvin/pydantic/discussions/2410#discussioncomment-408613
"""

@property
@abc.abstractmethod
def schema_name(self) -> str:
"""The QCSchema specification this model conforms to"""

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 computation.")
extras: Dict[str, Any] = Field(
{},
description="Additional information to bundle with the computation. Use for schema development and scratch space.",
)

provenance: Provenance = Field(..., description=str(Provenance.__doc__))


class InputBase(InputResultBase):
"""Base Class for all input objects"""

provenance: Provenance = Field(Provenance(**provenance_stamp(__name__)), description=str(Provenance.__doc__))


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

input_data: Any = Field(..., description="The input data supplied to generate the computation")
success: bool = Field(..., description="The success of program execution")

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 successfully returned result"""

success: Literal[True] = Field(
True,
description="A boolean indicator that the operation succeeded consistent with the model of successful operations. "
"Should always be True. Allows programmatic assessment of all operations regardless of if they failed or "
"succeeded",
)


class InputSpecificationBase(InputBase):
"""Input specification base"""

keywords: Dict[str, Any] = Field({}, description="The program specific keywords to be used.")
protocols: Any = Field(..., description="Protocols associated with the input")


class InputComputationBase(InputBase):
"""Base input directed at any computational chemistry program"""

input_spec: InputSpecificationBase = Field(..., description="The input specification for the computation")
molecule: Molecule = Field(..., description="The molecule to use in the computation.")

@property
def initial_molecule(self) -> Molecule:
"""To maintain backwards compatibility to access the 'initial_molecule' attribute"""
# NOTE: Useful? Still have to use 'molecule' for instantiating the object...
return self.molecule
23 changes: 22 additions & 1 deletion qcelemental/models/basemodels.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import abc
import json
from pathlib import Path
from typing import Any, Dict, Optional, Set, Union

import numpy as np
from pydantic import BaseModel, BaseSettings
from pydantic import BaseModel, BaseSettings, Field

from qcelemental.testing import compare_recursive
from qcelemental.util import deserialize, serialize
from qcelemental.util.autodocs import AutoPydanticDocGenerator

from ..util import provenance_stamp


def _repr(self) -> str:
return f'{self.__repr_name__()}({self.__repr_str__(", ")})'
Expand Down Expand Up @@ -189,6 +192,24 @@ def compare(self, other: Union["ProtoModel", BaseModel], **kwargs) -> bool:
return compare_recursive(self, other, **kwargs)


class Provenance(ProtoModel):
"""Provenance information."""

creator: str = Field(..., description="The name of the program, library, or person who created the object.")
version: str = Field(
"",
description="The version of the creator, blank otherwise. This should be sortable by the very broad [PEP 440](https://www.python.org/dev/peps/pep-0440/).",
)
routine: str = Field("", description="The name of the routine or function within the creator, blank otherwise.")

class Config(ProtoModel.Config):
canonical_repr = True
extra: str = "allow"

def schema_extra(schema, model):
schema["$schema"] = qcschema_draft


class AutodocBaseSettings(BaseSettings):
def __init_subclass__(cls) -> None:
cls.__doc__ = AutoPydanticDocGenerator(cls, always_apply=True)
Expand Down
58 changes: 4 additions & 54 deletions qcelemental/models/common_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,6 @@
ndarray_encoder = {np.ndarray: lambda v: v.flatten().tolist()}


class Provenance(ProtoModel):
"""Provenance information."""

creator: str = Field(..., description="The name of the program, library, or person who created the object.")
version: str = Field(
"",
description="The version of the creator, blank otherwise. This should be sortable by the very broad [PEP 440](https://www.python.org/dev/peps/pep-0440/).",
)
routine: str = Field("", description="The name of the routine or function within the creator, blank otherwise.")

class Config(ProtoModel.Config):
canonical_repr = True
extra: str = "allow"

def schema_extra(schema, model):
schema["$schema"] = qcschema_draft


class Model(ProtoModel):
"""The computational molecular sciences model to run."""

Expand Down Expand Up @@ -94,45 +76,13 @@ def __repr_args__(self) -> "ReprArgs":
return [("error_type", self.error_type), ("error_message", self.error_message)]


class FailedOperation(ProtoModel):
"""Record indicating that a given operation (program, procedure, etc.) has failed and containing the reason and input data which generated the failure."""

id: str = Field( # type: ignore
None,
description="A unique identifier which links this FailedOperation, often of the same Id of the operation "
"should it have been successful. This will often be set programmatically by a database such as "
"Fractal.",
)
input_data: Any = Field( # type: ignore
None,
description="The input data which was passed in that generated this failure. This should be the complete "
"input which when attempted to be run, caused the operation to fail.",
)
success: bool = Field( # type: ignore
False,
description="A boolean indicator that the operation failed consistent with the model of successful operations. "
"Should always be False. Allows programmatic assessment of all operations regardless of if they failed or "
"succeeded",
)
error: ComputeError = Field( # type: ignore
...,
description="A container which has details of the error that failed this operation. See the "
":class:`ComputeError` for more details.",
)
extras: Optional[Dict[str, Any]] = Field( # type: ignore
None,
description="Additional information to bundle with the failed operation. Details which pertain specifically "
"to a thrown error should be contained in the `error` field. See :class:`ComputeError` for details.",
)

def __repr_args__(self) -> "ReprArgs":
return [("error", self.error)]


qcschema_input_default = "qcschema_input"
qcschema_output_default = "qcschema_output"
qcschema_input_specification_default = "qcschema_input_specification"
qcschema_optimization_specification_default = "qcschema_optimization_specification"
qcschema_optimization_input_default = "qcschema_optimization_input"
qcschema_optimization_output_default = "qcschema_optimization_output"
qcschema_torsion_drive_input_default = "qcschema_torsion_drive_input"
qcschema_torsion_drive_output_default = "qcschema_torsion_drive_output"
qcschema_molecule_default = "qcschema_molecule"
qcschema_torsion_drive_specification_default = "qcschema_torsion_drive_specification"
qcschema_molecule_default = "qcschema_molecule"
11 changes: 3 additions & 8 deletions qcelemental/models/molecule.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
from ..physical_constants import constants
from ..testing import compare, compare_values
from ..util import deserialize, measure_coordinates, msgpackext_loads, provenance_stamp, which_import
from .basemodels import ProtoModel, qcschema_draft
from .common_models import Provenance, qcschema_molecule_default
from .basemodels import ProtoModel, Provenance, qcschema_draft
from .common_models import qcschema_molecule_default
from .types import Array

if TYPE_CHECKING:
Expand Down Expand Up @@ -113,12 +113,7 @@ class Molecule(ProtoModel):
"""

schema_name: constr(strip_whitespace=True, regex="^(qcschema_molecule)$") = Field( # type: ignore
qcschema_molecule_default,
description=(
f"The QCSchema specification to which this model conforms. Explicitly fixed as {qcschema_molecule_default}."
),
)
schema_name: constr(strip_whitespace=True, regex=qcschema_molecule_default) = qcschema_molecule_default # type: ignore
schema_version: int = Field( # type: ignore
2, description="The version number of ``schema_name`` to which this model conforms."
)
Expand Down
Loading

0 comments on commit 199b324

Please sign in to comment.