11"""Configuration from MM simulations."""
22
3- import functools
4- import typing
5-
6- import openff .units
73import openmm .unit
84import pydantic
9- import pydantic_core
5+ from pydantic_units import OpenMMQuantity , quantity_serializer
106
117_KCAL_PER_MOL = openmm .unit .kilocalories_per_mole
128_ANGSTROM = openmm .unit .angstrom
139_GRAMS_PER_ML = openmm .unit .grams / openmm .unit .milliliters
1410
1511
16- def _quantity_validator (
17- value : str | openmm .unit .Quantity | openff .units .unit .Quantity ,
18- expected_units : openmm .unit .Unit ,
19- ) -> openmm .unit .Quantity :
20- if isinstance (value , str ):
21- value = openff .units .Quantity (value )
22- if isinstance (value , openff .units .Quantity ):
23- value = openff .units .openmm .to_openmm (value )
24-
25- assert isinstance (value , openmm .unit .Quantity ), f"invalid type - { type (value )} "
26-
27- try :
28- return value .in_units_of (expected_units )
29- except TypeError as e :
30- raise ValueError (
31- f"invalid units { value .unit } - expected { expected_units } "
32- ) from e
33-
34-
35- def _quantity_serializer (value : openmm .unit .Quantity ) -> str :
36- unit_str = openff .units .openmm .openmm_unit_to_string (value .unit )
37- return f"{ value .value_in_unit (value .unit ):.8f} { unit_str } "
38-
39-
40- class _OpenMMQuantityAnnotation :
41- @classmethod
42- def __get_pydantic_core_schema__ (
43- cls ,
44- _source_type : typing .Any ,
45- _handler : pydantic .GetCoreSchemaHandler ,
46- ) -> pydantic_core .core_schema .CoreSchema :
47- from_value_schema = pydantic_core .core_schema .no_info_plain_validator_function (
48- lambda x : x
49- )
50-
51- return pydantic_core .core_schema .json_or_python_schema (
52- json_schema = from_value_schema ,
53- python_schema = from_value_schema ,
54- serialization = pydantic_core .core_schema .plain_serializer_function_ser_schema (
55- _quantity_serializer
56- ),
57- )
58-
59- @classmethod
60- def __get_pydantic_json_schema__ (
61- cls ,
62- _core_schema : pydantic_core .core_schema .CoreSchema ,
63- handler : pydantic .GetJsonSchemaHandler ,
64- ) -> "pydantic.json_schema.JsonSchemaValue" :
65- return handler (pydantic_core .core_schema .str_schema ())
66-
67-
68- class _OpenMMQuantityMeta (type ):
69- def __getitem__ (cls , item : openmm .unit .Unit ):
70- validator = functools .partial (_quantity_validator , expected_units = item )
71- return typing .Annotated [
72- openmm .unit .Quantity ,
73- _OpenMMQuantityAnnotation ,
74- pydantic .BeforeValidator (validator ),
75- ]
76-
77-
78- class OpenMMQuantity (openmm .unit .Quantity , metaclass = _OpenMMQuantityMeta ):
79- """A pydantic safe OpenMM quantity type validates unit compatibility."""
12+ if pydantic .__version__ .startswith ("1." ):
8013
14+ class BaseModel (pydantic .BaseModel ):
15+ class Config :
16+ json_encoders = {openmm .unit .Quantity : quantity_serializer }
8117
82- if typing . TYPE_CHECKING :
83- OpenMMQuantity = openmm . unit . Quantity # noqa: F811
18+ else :
19+ BaseModel = pydantic . BaseModel
8420
8521
86- class GenerateCoordsConfig (pydantic . BaseModel ):
22+ class GenerateCoordsConfig (BaseModel ):
8723 """Configure how coordinates should be generated for a system using PACKMOL."""
8824
8925 target_density : OpenMMQuantity [_GRAMS_PER_ML ] = pydantic .Field (
@@ -113,7 +49,7 @@ class GenerateCoordsConfig(pydantic.BaseModel):
11349 )
11450
11551
116- class MinimizationConfig (pydantic . BaseModel ):
52+ class MinimizationConfig (BaseModel ):
11753 """Configure how a system should be energy minimized."""
11854
11955 tolerance : OpenMMQuantity [_KCAL_PER_MOL / _ANGSTROM ] = pydantic .Field (
@@ -128,7 +64,7 @@ class MinimizationConfig(pydantic.BaseModel):
12864 )
12965
13066
131- class SimulationConfig (pydantic . BaseModel ):
67+ class SimulationConfig (BaseModel ):
13268 temperature : OpenMMQuantity [openmm .unit .kelvin ] = pydantic .Field (
13369 ...,
13470 description = "The temperature to simulate at." ,
0 commit comments