https://github.com/openforcefield/openff-units
A common units module for the OpenFF software stack
https://github.com/openforcefield/openff-units
Last synced: 5 months ago
JSON representation
A common units module for the OpenFF software stack
- Host: GitHub
- URL: https://github.com/openforcefield/openff-units
- Owner: openforcefield
- License: mit
- Created: 2021-01-28T21:30:44.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2025-10-14T19:58:55.000Z (8 months ago)
- Last Synced: 2025-10-21T20:58:12.143Z (7 months ago)
- Language: Python
- Size: 325 KB
- Stars: 9
- Watchers: 16
- Forks: 4
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- Contributing: .github/CONTRIBUTING.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
openff-units
==============================
[//]: # (Badges)
[](https://github.com/openforcefield/openff-units/actions?query=workflow%3ACI)
[](https://codecov.io/gh/openforcefield/openff-units/branch/main)
[](https://github.com/pre-commit/pre-commit)
[](https://results.pre-commit.ci/latest/github/openforcefield/openff-units/main)
[](https://anaconda.org/conda-forge/openff-units)
A common units module for the OpenFF software stack
**Please note that there may still be some changes to the API prior to a stable 1.0.0 release.**
This package provides a common unit registry for all OpenFF packages to use in order to ensure consistent unit definitions across the software ecosystem.
The unit definitions are currently sourced from the NIST 2018 [CODATA](https://physics.nist.gov/cuu/Constants/), but may be updated in future versions as new CODATA updates are made.
While this repository is based on [Pint](https://pint.readthedocs.io/en/0.16.1/), the main classes (`Unit`, `Quantity`, and `Measurement`) have been slightly modified in order to provide non-dynamic, more readily serialisable representations.
### Installation
Install via `mamba` or a replacement:
```shell
mamba install openff-units -c conda-forge
```
#### Developer installation
Clone, install dev dependencies via Conda into a new environment, and finally install in editable mode with Pip:
```shell
git clone https://github.com/openforcefield/openff-units.git
cd openff-units
conda create -n openff-units-dev -f devtools/conda-envs/test_env.yaml
conda run -n openff-units-dev pip install --no-deps -e .
```
The important detail, as of December 2025, is the `--no-deps` argument.
### Getting Started
Below shows how to tag a number with a unit (generating a `Quantity` object),
get its magnitude with and without units, convert to another unit, and also get its magnitude after converting to another unit.
```python3
>>> from openff.units import Quantity
>>> bond_length = Quantity(1.4, "angstrom")
>>> bond_length
>>> bond_length.magnitude
1.4
>>> bond_length.to("nanometer")
>>> bond_length.m_as("nanometer")
0.14
```
One could also do the [Pint tutorial](https://pint.readthedocs.io/en/0.16.1/tutorial.html#tutorial) using the `unit` object above as a drop-in replacement for `ureg` in the tutorial.
### Serialization
Scalar quantities can be serialized to strings using the built-in `str()` function and deserialized using the `unit.Quantity` constructor.
```python3
>>> k = Quantity(10, "kilocalorie / mol / nanometer**2")
>>> k
>>> str(k)
'10.0 kcal / mol / nm ** 2'
>>> Quantity(str(k))
```
### OpenMM Interoperability
For compatibility with [OpenMM units](http://docs.openmm.org/latest/api-python/app.html#units), a submodule (`openff.units.openmm`) with conversion functions (`to_openmm`, `from_openmm`) is also provided.
```python3
>>> from openff.units import Quantity
>>> from openff.units.openmm import to_openmm, from_openmm
>>> distance = Quantity(24.0, "meter")
>>> converted = to_openmm(distance)
>>> converted
24.0 m
>>> type(converted)
>>> roundtripped = from_openmm(converted)
>>> roundtripped
>>> type(roundtripped)
pint.Quantity
```
An effort is made to convert from OpenMM constructs, such as when OpenMM provides array-like data as a list of `Vec3` objects:into Pint's wrapped NumPy arrays:
```python3
>>> from openmm import app
>>> positions = app.PDBFile("top.pdb").getPositions()
>>> positions
Quantity(value=[Vec3(x=-0.07890000000000001, y=-0.0198, z=-0.0), Vec3(x=-0.0006000000000000001, y=0.039200000000000006, z=-0.0), Vec3(x=0.07950000000000002, y=-0.0194, z=0.0), Vec3(x=0.9211, y=0.9802, z=1.0), Vec3(x=0.9994000000000001, y=1.0392, z=1.0), Vec3(x=1.0795000000000001, y=0.9805999999999999, z=1.0)], unit=nanometer)
>>> type(positions)
>>> type(positions._value)
>>> type(positions._value[0])
>>> converted = from_openmm(positions)
>>> converted
>>> converted.m
array([[-7.8900e-02, -1.9800e-02, -0.0000e+00],
[-6.0000e-04, 3.9200e-02, -0.0000e+00],
[ 7.9500e-02, -1.9400e-02, 0.0000e+00],
[ 9.2110e-01, 9.8020e-01, 1.0000e+00],
[ 9.9940e-01, 1.0392e+00, 1.0000e+00],
[ 1.0795e+00, 9.8060e-01, 1.0000e+00]])
>>> type(converted)
>>> type(converted.m)
```
#### Dealing with multiple unit packages
You may find yourself needing to normalize a quantity to a particular unit package, while accepting inputs from either `openff.units` or `openmm.unit`. The [`ensure_quantity`] function simplifies this. It takes as arguments a quantity object from either unit solution and a string (`"openff"` or `"openmm"`) indicating the desired unit type, and returns a quantity from that package. If the quantity argument is already the requested type, the function short-circuits, so it should not introduce substantial overhead compared to simply requiring the target quantity type.
[`ensure_quantity`]: https://docs.openforcefield.org/projects/units/en/stable/api/generated/openff.units.ensure_quantity.html
```python3
>>> from openff.units import Quantity, ensure_quantity
>>> ensure_quantity(Quantity(4.0, "angstrom"), "openff")
# OpenFF
>>> ensure_quantity(Quantity(4.0, "angstrom"), "openmm")
4.0 A
>>>
>>> import openmm.unit
>>> ensure_quantity(openmm.unit.Quantity(4.0, openmm.unit.angstrom), "openmm")
4.0 A
>>> ensure_quantity(openmm.unit.Quantity(4.0, openmm.unit.angstrom), "openff")
# OpenFF
```
### Known issues
There is a quirk with cached unit registry definitions that could cause issues when running tests in parallel (i.e. with `pytest-xdist`). See [Issue #111](https://github.com/openforcefield/openff-units/issues/111) for more details. This was fixed in version 0.3.1.
### Copyright
Copyright (c) 2021, Open Force Field Initiative
#### Acknowledgements
Project based on the
[Computational Molecular Science Python Cookiecutter](https://github.com/molssi/cookiecutter-cms) version 1.5.