OpenFF quantities#

The optional openff.units form uses OpenFF’s own unit registry. Install its official Conda distribution in Python 3.12–3.14:

conda install -c conda-forge openff-units=0.4.0

OpenFF 0.4.0 requires Pint >=0.24,<0.26. PyUnitWizard’s base installation keeps its existing Python 3.11–3.14 and Pint requirements; OpenFF’s constraints apply when that optional backend is installed. The current PyPI upload is yanked, so there is no advertised pyunitwizard[openff] pip extra. The complete optional test environment is devtools/conda-envs/openff_env.yaml.

import numpy as np
import pyunitwizard as puw
from openff.units import Quantity

source = Quantity(np.arange(6, dtype=np.float32).reshape(2, 3), "angstrom")
puw.get_form(source)                         # 'openff.units'
shared = puw.convert(source, to_form="pint")
back = puw.convert(shared, to_form="openff.units")
positions = puw.convert(source, to_form="openmm.unit")

The bridge preserves scalars, array shapes and dtype when units are unchanged. Conversion that rescales a value follows backend numeric rules. Before crossing registries it checks named base units, dimensionality, scale and affine offset with the same 1e-12 tolerance as foreign Pint quantities. Changed or missing definitions raise ValueError; equal-looking strings alone do not establish agreement. Definition differences such as CODATA revisions beyond that tolerance are refused, rather than silently accepted. Native OpenMM conversions use the official OpenFF tools; Vec3 lists return NumPy arrays with their original shape.

Construction, unit extraction, dimensional checks, scalar/array strings and reader session policies use the existing PyUnitWizard API. OpenFF is detected from its owning registry, since its generated classes may be named pint.Quantity. Importing PyUnitWizard does not import OpenFF or change its application registry. On Python 3.11 or when OpenFF is absent, an explicit request gives an installation/version diagnostic. Existing adapters still determine which unit spellings they can translate to unyt or Astropy.

Unsealed val/unit data#

OpenFF-style {val, unit} JSON has no integrity seal or application field. Use explicit boundary tools rather than treating any dictionary as a quantity:

from pyunitwizard.forms.api_openff_units import from_dict, to_dict
from pyunitwizard.record import QuantityRecord

raw = {"val": [[1.0, 2.0], [3.0, 4.0]], "unit": "angstrom"}
quantity = from_dict(raw, unit="nanometer", dimensionality={"[L]": 1})
record = QuantityRecord.from_quantity(quantity, field="coordinates")
stored = record.to_dict()
read = QuantityRecord.from_dict(stored).to_quantity(
    field="coordinates", unit="angstrom", dimensionality={"[L]": 1}, form="openff.units"
)
exported = to_dict(read)  # Only val and unit: leaves the verified domain.

The raw importer requires exactly two keys, an explicit unit and finite numeric values. It checks definitions and any supplied dimensional/unit expectations; the caller binds the trusted application field when sealing. Raw JSON cannot prove what field it belongs to. Export discards seals and field association; subsequent edits have no integrity guarantee. Existing qrec/0.3 stored bytes and reader handshakes remain unchanged. JSON carries no exact dtype metadata; use QuantityRecord when that matters.

See OpenFF’s API, 0.4.0 dependencies and #86.