quantype
quantype makes physical dimensions and unit systems part of your types. mypy, Pyright, Pyrefly, and ty check them without a plugin, and the values underneath are still plain floats or NumPy, JAX, and Torch arrays.
force = energy / length # Force[float]
energy + length # type error, and a TypeError at runtime
gradient = ujax.grad(potential)(positions) # Force[jax.Array]
Install
uv add quantype
uv add 'quantype[jax]' # JAX transformations and autodiff
uv add 'quantype[torch]' # Torch tensors and autograd
python -m pip install quantype also works in an activated virtual environment.
quantype needs Python 3.12 or newer, and its only core dependencies are NumPy
and Pydantic v2. It is alpha software, and the API may change before 1.0.
Quantities and their types
from typing import assert_type
from quantype import Energy, EnergyDensity, Force, Length, Pressure, u
length = Length[float](2, u.nm)
energy = Energy[float](3, u.eV)
assert length.magnitude() == 2.0 # in the unit it was given in
assert f"{length:.1f}" == "2.0 nm"
force = energy / length
assert_type(force, Force[float])
assert force.magnitude(u.eV_per_angstrom) == 0.15
assert_type(energy / force, Length[float])
pressure = force / length**2
density = energy / length**3
assert_type(pressure, Pressure[float])
assert_type(density, EnergyDensity[float])
assert_type(Pressure.reinterpret(density), Pressure[float])
.magnitude() gives the number in the unit you constructed with, or in any unit
you pass it.
Result types come from relations declared in the catalogue. Force * Length is
Energy, so Energy / Force is Length and Energy / Length is Force.
Pressure and EnergyDensity have the same dimensions but are separate kinds.
To turn one into the other, call reinterpret.
Unit systems
A unit system is the set of units a quantity stores its numbers in, and it is
the second type argument. Storing everything in one set gives .value a single
meaning, keeps arithmetic free of conversions, and lets JAX compile a function
once, whatever units its inputs were written in. The default is Atomistic
(Å, eV, and fs). You can choose SI, CGS, Hartree Atomic, LAMMPS's Metal
or Real, or define your own, and a type checker tracks
which system each value uses.
from typing import assert_type
from quantype import Energy, Force, Length, u
from quantype.systems import SI, Atomistic
a = Length[float](2, u.nm)
b = Length[float](20, u.angstrom)
assert a.value == b.value == 20.0 # Atomistic stores ångströms
assert str(a) == "2.0 nm" # and shows the unit it was given in
force = (3 * u.eV) / a
assert force.value == 0.15 # eV/Å, with no conversion in the division
x = Length[float, SI](2, u.nm)
assert x.value == 2e-9 # SI stores meters
assert_type(Energy[float, SI](3, u.eV) / x, Force[float, SI]) # newtons
assert (a + x.to_system(Atomistic)).magnitude(u.nm) == 4.0
Length[float] is short for Length[float, Atomistic]. Adding an SI length to
an Atomistic one is a type error and a runtime TypeError, so convert one of
them with .to_system(...) first. Metal and Real store each kind in the
unit LAMMPS documents for it, so a Metal pressure is in bar.
NumPy, JAX, and Torch arrays
import numpy as np
import numpy.typing as npt
import quantype.numpy as qnp
from quantype import Length, u
positions = Length[npt.NDArray[np.float64]]([[0, 0, 0], [3, 4, 0]], u.nm)
distances = qnp.linalg.norm(positions, axis=-1) # a Length
np.testing.assert_allclose(distances.magnitude(u.nm), [0, 5])
assert distances.max() == 5 * u.nm
quantype.numpy, imported as qnp, has NumPy's function names with unit rules,
and works on NumPy, JAX, and Torch arrays.
Any plain number or array scales a quantity. To pass data to another library,
take .value or .magnitude(unit). np.asarray(q) raises.
Autodiff
import jax
from typing import assert_type
from quantype import Energy, Force, ForceConstant, Length, u, ujax
def spring(x: Length[jax.Array], k: ForceConstant[float]) -> Energy[jax.Array]:
return 0.5 * k * (x**2).sum()
x = Length[jax.Array]([1, 2, 3], u.angstrom)
k = ForceConstant[float](2, u.eV_per_angstrom_squared)
energy, gradient = ujax.jit(ujax.value_and_grad(spring))(x, k)
assert_type(gradient, Force[jax.Array])
force = -gradient
The gradient of an Energy with respect to a Length is a Force, under jit
too. Arguments after the first, such as k, pass through unchanged. The
gradient is the positive derivative, so the physical force is -gradient.
Torch autograd, several inputs, and Hessians work the same
way.
Physical constants
from typing import assert_type
from quantype import Energy, Temperature, constants, u
from quantype.systems import SI
thermal = constants.k_B * Temperature[float, SI](300, u.K)
assert_type(thermal, Energy[float, SI])
assert repr(constants.k_B.to_system(SI)) == "Entropy(1.380649e-23 J/K, SI)"
Constants have no unit system of their own. They take the system of the
quantity they combine with, so k_B times an SI temperature is an SI energy.
Values are from CODATA 2022, vendored with quantype. CODATA 2014 and 2018 are
one setting away.
Built-in kinds
| Area | Kinds |
|---|---|
| Geometry and time | Length, Area, Volume, Angle, Time, Velocity, Acceleration, Frequency, InverseTime |
| Energy and stress | Energy, EnergyPerAtom, Force, ForceConstant, Pressure, EnergyDensity, EnergyPerVolume, Action |
| Mechanics | Mass, MassDensity, Momentum |
| Electrostatics | Charge, ElectricPotential, ElectricField, DipoleMoment |
| Thermal | Temperature, TemperatureDifference, TemperatureRate, Entropy |
| Magnetism and counts | MagneticMoment, Magnetization, AtomCount, ElectronCount, ParticleDensity, ElectronDensity, Dimensionless |
The catalogue reference lists every unit, the named products and quotients, and each system's units. You can generate your own catalogue with new kinds and relations.
Also included
quantype.testing.assert_allclose, which checks kind and unit system before comparing numbers.- JSON, Pydantic, and NPZ serialization, with no pickle. Values keep the unit
they were given in, so a configuration's
"0.5 nm"is saved as 0.5 nanometers. - Temperatures: 30 °C minus 20 °C is a
TemperatureDifferenceof 10 Δ°C, andk_B * Tis an energy. - A product is named by its factors, in any order or grouping:
m * v**2,m * v * v, and(m * v)**2 / mare all anEnergy. A product of two kinds with no relation is a product class such asLengthTime, fromquantype.products. - Custom units, defined locally with no global registry.
Documentation
- Getting started
- Units, unit systems, and physical algebra
- Kinds and units reference
- Defining unit systems and system-generic code
- Serialization: JSON, Pydantic, NPZ, and NPY
- JAX and Torch autodiff
- Custom catalogues
- Development and conformance checks
License
quantype is dual-licensed under the MIT and Apache 2.0 licenses, at your option.
Development
Install uv, then
run these from the repository root. just and the type checkers come with the
development dependencies.
uv sync --all-extras
uv run just generate
uv run just check-generated
uv run just test
uv run just typecheck
uv run just stubcheck
uv run just lint
uv build
To test the core alone, run uv sync and uv run pytest tests/runtime. The
development guide covers hooks and CI.
Metadata
Release files for quantype 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| quantype-0.1.0.tar.gz | 1.2 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| quantype-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 4.2 MB
Release files / quantype-0.1.0.tar.gz
| Download URL | quantype-0.1.0.tar.gz |
|---|---|
| Size | 1.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
73e9542321e50e00cd1d9d3b69944cca968e05e2c00b44273dcae17f37fb2e73
|
|
BLAKE2b-256 checksum How to use checksums |
b7659a08041929769de11ab4462fe3d497ac9a203dcd9872c46159e2c91e0c82
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / quantype-0.1.0-py3-none-any.whl
| Download URL | quantype-0.1.0-py3-none-any.whl |
|---|---|
| Size | 3.1 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e19aa8ae1408e3813b9677b4b3d36241b84cbfc63e6d6fa8780e16981f78ef3a
|
|
BLAKE2b-256 checksum How to use checksums |
9d29da8a22070378ee996d593af3fc544241667926e62c1f64136c99ee5238bd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|