Source code for openff.bespokefit.schema.results
import abc
from typing import Any, Dict, List, Optional
from openff.toolkit.typing.engines.smirnoff import ForceField
from openff.units import unit
from typing_extensions import Literal
from openff.bespokefit._pydantic import Field, SchemaBase
from openff.bespokefit.schema import Error, Status
from openff.bespokefit.schema.fitting import (
BespokeOptimizationSchema,
OptimizationSchema,
)
from openff.bespokefit.schema.smirnoff import BaseSMIRKSParameter
[docs]class OptimizationStageResults(SchemaBase, abc.ABC):
"""The base class for data models which store the results of an optimization."""
provenance: Dict[str, str] = Field(
{}, description="The versions of the software used to generate the results."
)
status: Status = Field("waiting", description="The status of the optimization.")
error: Optional[Error] = Field(
None, description="The error, if any, that was raised while running."
)
refit_force_field: Optional[str] = Field(
None, description="The XML contents of the refit force field."
)
# TODO: Other fields which would be good to include.
# objective_function: List[float] = Field(
# ..., description="The value of the objective function at each iteration."
# )
[docs]class BaseOptimizationResults(SchemaBase, abc.ABC):
"""A class for storing the results of a general force field optimization."""
type: Literal["base-results"] = "base-results"
input_schema: Optional[Any] = Field(
None, description="The schema defining the input to the optimization."
)
stages: List[OptimizationStageResults] = Field(
[], description="The results of each of the fitting states."
)
@property
def initial_parameter_values(
self,
) -> Optional[Dict[BaseSMIRKSParameter, Dict[str, unit.Quantity]]]:
"""A list of the refit force field parameters."""
return (
None
if self.input_schema is None
else self.input_schema.initial_parameter_values
)
@property
def refit_force_field(self) -> Optional[str]:
"""Return the final refit force field."""
return (
None if not self.status == "success" else self.stages[-1].refit_force_field
)
@property
def refit_parameter_values(
self,
) -> Optional[Dict[BaseSMIRKSParameter, Dict[str, unit.Quantity]]]:
"""A list of the refit force field parameters."""
if self.input_schema is None or not self.status == "success":
return None
refit_force_field = ForceField(self.stages[-1].refit_force_field)
return {
parameter: {
attribute: getattr(
refit_force_field[parameter.type].parameters[parameter.smirks],
attribute,
)
for attribute in parameter.attributes
}
for stage in self.input_schema.stages
for parameter in stage.parameters
}
@property
def status(self) -> Status:
if (
len(self.stages) == 0
or all(stage.status == "waiting" for stage in self.stages)
or self.input_schema is None
):
return "waiting"
if any(stage.status == "errored" for stage in self.stages):
return "errored"
if len(self.stages) == len(self.input_schema.stages) and all(
stage.status == "success" for stage in self.stages
):
return "success"
return "running"
[docs]class OptimizationResults(BaseOptimizationResults):
"""A class for storing the results of a general force field optimization."""
type: Literal["general"] = "general"
input_schema: Optional[OptimizationSchema] = Field(
None, description="The schema defining the input to the optimization."
)
[docs]class BespokeOptimizationResults(BaseOptimizationResults):
"""A class for storing the results of a bespoke force field optimization."""
type: Literal["bespoke"] = "bespoke"
input_schema: Optional[BespokeOptimizationSchema] = Field(
None, description="The schema defining the input to the optimization."
)