Skip to content

Pydantic Units

Support for OpenMM units as pydantic fields

ci coverage license


The pydantic-units framework aims to offer a convenient way to work with OpenMM units in Pydantic models, in a way that is backwards compatible with both Pydantic v1 and v2. It is lightweight and has no dependencies beyond Pydantic and OpenMM.

All units that are available in OpenMM are supported except for 'ampere' whose symbol conflicts with the angstrom unit symbol, making parsing ambiguous. Angstroms can be specified as either 'A', or 'Å'.

Installation#

This package can be installed using conda (or mamba, a faster version of conda):

mamba install -c conda-forge pydantic-units

Getting Started#

The OpenMMQuantity class can be used as a field in a Pydantic model to represent a quantity with units. It will both handle parsing quantities with units from strings and checking that parsed units are compatible with the expected units.

Quantities can either be passed into models (see below for how to define models) with such a field directly:

from openmm import unit
MyModel(temperature=300.0 * unit.kelvin)

or they can be passed in as strings, in which case the unit can be specified either fully

MyModel(temperature='300.0 kelvin')

or with the unit symbol

MyModel(temperature='300.0 K')

The field also handles validation of the unit, so that the following will raise a validation error:

MyModel(temperature='300.0 A')
# raises: Value error, invalid units angstrom - expected angstrom kelvin

Pydantic V1#

Due to limitations of custom fields in Pydantic v1, v1 models must define a custom JSON encoder if the model needs to be serialised to JSON:

from pydantic import BaseModel
from openmm import unit

from pydantic_units import OpenMMQuantity, quantity_serializer

class Model(BaseModel):
    class Config:
        json_encoders = {unit.Quantity: quantity_serializer}

    a: OpenMMQuantity[unit.angstrom]
    b: OpenMMQuantity[unit.kelvin]

model = Model(a=1.0 * unit.nanometer, b='298.0 K')
model.json()
# '{"a": "10.0 A", "b": "298.0 K"}'

Pydantic V2#

Pydantic v2 supports custom fields with custom encoders, so the OpenMMQuantity field can be used directly in the model without needing to define a custom JSON encoder:

from pydantic import BaseModel
from openmm import unit

from pydantic_units import OpenMMQuantity

class Model(BaseModel):
    a: OpenMMQuantity[unit.angstrom]
    b: OpenMMQuantity[unit.kelvin]

model = Model(a=1.0 * unit.nanometer, b='298.0 K')
model.model_dump_json()
# '{"a": "10.0 A", "b": "298.0 K"}'

Backwards compatibility with Pydantic v1 is also maintained:

from pydantic.v1 import BaseModel
from openmm import unit

from pydantic_units import quantity_serializer
from pydantic_units.v1 import OpenMMQuantity

class Model(BaseModel):
    class Config:
        json_encoders = {unit.Quantity: quantity_serializer}

    a: OpenMMQuantity[unit.angstrom]
    b: OpenMMQuantity[unit.kelvin]

model = Model(a=1.0 * unit.nanometer, b='298.0 K')
model.json()
# '{"a": "10.0 A", "b": "298.0 K"}'

(De)Serialization#

Quantity fields will be serialized into JSON as strings with the unit symbol, e.g.

from pydantic_units import quantity_serializer
from openmm import unit

print(quantity_serializer(1.0 * unit.angstrom))
# '1 A'
print(quantity_serializer(1.0 * unit.kilojoules_per_mole))
# '1.0 kJ * mol**-1'

Likewise, when instantiating a field from a string (either through the constructor or through parsing JSON), the unit should be specified in the string as either the full unit name or the unit symbol:

from pydantic_units import quantity_validator
from openmm import unit

quantity_validator('1.0 angstrom', unit.angstrom)
# Quantity(value=1.0, unit=angstrom)
quantity_validator('1.0 A', unit.angstrom)
# Quantity(value=1.0, unit=angstrom)
quantity_validator('1.0 kJ/mol', unit.kilojoules_per_mole)
# Quantity(value=1.0, unit=kilojoule/mole)

The leading * can be either included or omitted, and whitespace is usually ignored, such that the following are all equivalent:

from pydantic_units import quantity_validator
from openmm import unit

quantity_validator('1.0 A', unit.angstrom)
quantity_validator('1.0A', unit.angstrom)
quantity_validator('1.0 * A', unit.angstrom)
quantity_validator('1.0*A', unit.angstrom)