Skip to main content

quantype

test coverage PyPI Python 3.12+ SPEC 0

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 TemperatureDifference of 10 Δ°C, and k_B * T is an energy.
  • A product is named by its factors, in any order or grouping: m * v**2, m * v * v, and (m * v)**2 / m are all an Energy. A product of two kinds with no relation is a product class such as LengthTime, from quantype.products.
  • Custom units, defined locally with no global registry.

Documentation

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.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for quantype 0.2.0
File Size Uploaded
quantype-0.2.0.tar.gz 1.2 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for quantype 0.2.0
File Interpreter ABI Platform
quantype-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 4.2 MB

Release files / quantype-0.2.0.tar.gz

Download URL quantype-0.2.0.tar.gz
Size 1.2 MB
Tags Source
SHA-256 checksum
How to use checksums
72c209350de6e2c53afbe55e89b126d21ae7f3961b99139708fa196e1bc32351
BLAKE2b-256 checksum
How to use checksums
d65851905a80e992c942aa85a4917d0b163ba20cc3d4be6f0db28f24a315ef92
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.2.0-py3-none-any.whl

Download URL quantype-0.2.0-py3-none-any.whl
Size 3.1 MB
Tags Python 3
SHA-256 checksum
How to use checksums
b8c6ec5fc253ad4aad3a59902dc628a37be5cf156076c16ad5dadd7f95e1dab3
BLAKE2b-256 checksum
How to use checksums
aee3af9705fba7e399c6397b528a645112d4d3c7ee5fc1b93b692f68e3c09d7e
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 history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page