OpenFF Units
Units of measure for biomolecular software.
OpenFF Units is based on Pint. Its Quantity
, Unit
, and Measurement
types inherit from Pint’s, and add improved support for serialization and deserialization. OpenFF Units improves support for biomolecular software by providing a system of units that are compatible with OpenMM and providing functions to convert to OpenMM units and back. It also provides atomic masses with units, as well as some other useful maps.
Installation
We recommend installing OpenFF Units with the Conda package manager. If you don’t yet have a Conda distribution installed, we recommend MambaForge for most users. The openff-units
package can be installed from Conda Forge:
conda install -c conda-forge openff-units
Using OpenFF Units
OpenFF Units provides the Quantity
class, which represents a numerical value with units. A Quantity
can be created by providing a value and units:
>>> from openff.units import unit, Quantity
>>>
>>> Quantity(1.007, unit.amu)
<Quantity(1.007, 'unified_atomic_mass_unit')>
The unit
singleton value is a registry of units, but also exposes the Quantity
, Unit
, and Measurement
classes so you don’t have to import them individually. Even easier, multiplying a number by the appropriate unit also provides a Quantity
:
>>> mass_proton = 1.007 * unit.amu
>>> mass_proton == unit.Quantity(1.007, unit.amu)
True
Quantity
can also wrap NumPy arrays. It’s best to wrap an array of floats in a quantity, rather than have an array of quantities:
>>> import numpy as np
>>>
>>> box_vectors = np.array([
... [5.0, 0.0, 0.0],
... [0.0, 5.0, 0.0],
... [0.0, 0.0, 5.0],
... ]) * unit.nanometer
When constructed like this, Quantity
is transparent; it will pass any attributes it doesn’t have through to the inner value. This means that an quantity-wrapped array can be used exactly as though it were an array — the units are just checked silently in the background:
>>> from numpy.random import rand
>>>
>>> trajectory = 10 * rand(10, 10000, 3) * unit.nanometer
>>> centroids = trajectory.mean(axis=1)[..., None]
>>> last_water = trajectory[:, 97:99, :]
>>> last_water_recentered = last_water - centroids
This transparency works with most container types, so it’s usually best to have Quantity
be the outermost wrapper type.
Complex units can be constructed by combining units with the usual arithmetic operations:
>>> boltzmann_constant = 8.314462618e-3 * unit.kilojoule / unit.kelvin / unit.avogadro_number
Some common constants are provided as units as well:
>>> boltzmann_constant = 1.0 * unit.boltzmann_constant
Adding or subtracting different units with the same dimensions just works:
>>> 1.0 * unit.angstrom + 1.0 * unit.nanometer
<Quantity(11.0, 'angstrom')>
But quantities with different dimensions raise an exception:
>>> 1.0 * unit.angstrom + 1.0 * unit.nanojoule
Traceback (most recent call last):
...
pint.errors.DimensionalityError: Cannot convert from 'angstrom' ([length]) to 'nanojoule' ([length] ** 2 * [mass] / [time] ** 2)
Quantities can be converted between units with the .to()
method:
>>> (1.0 * unit.nanometer).to(unit.angstrom)
<Quantity(10.0, 'angstrom')>
Or with the .ito()
method for in-place transformations:
>>> quantity = 10.0 * unit.angstrom
>>> quantity.ito(unit.nanometer)
>>> quantity
<Quantity(1.0, 'nanometer')>
The underlying value without units can be retrieved with the .m
or .magnitude
properties. Just make sure it’s in the units you expect first:
>>> quantity = (1.0 * unit.k_B).to_base_units()
>>> assert quantity.units == unit.kilogram * unit.meter**2 / unit.kelvin / unit.second**2
>>> quantity.magnitude
1.380649e-23
Alternatively, specify the target units of the output magnitude with .m_as
:
>>> quantity = 1.0 * unit.k_B
>>> quantity.m_as(unit.kilogram * unit.meter**2 / unit.kelvin / unit.second**2)
1.380649e-23
OpenFF Units also provides the from_openmm
and to_openmm
functions to convert between OpenFF quantities and OpenMM quantities:
>>> from openff.units.openmm import from_openmm, to_openmm
>>>
>>> quantity = 10.0 * unit.angstrom
>>> omm_quant = to_openmm(quantity)
>>> omm_quant
Quantity(value=10.0, unit=angstrom)
>>> type(omm_quant)
<class 'openmm.unit.quantity.Quantity'>
>>> quant_roundtrip = from_openmm(omm_quant)
>>> quant_roundtrip
<Quantity(10.0, 'angstrom')>
>>> type(quant_roundtrip)
<class 'openff.units.units.Quantity'>
For more details, see the API reference.