quantia
A pure-Python library for unit-aware arithmetic with first-class support for Monte Carlo uncertainty propagation.
No numpy required. No dependencies beyond the standard library.
Installation
pip install quantia
The Four Types
| Type | Use when |
|---|---|
UnitFloat |
exact scalar with a unit |
UnitArray |
exact vector with a unit |
ProbUnitFloat |
uncertain scalar (Monte Carlo samples) |
ProbUnitArray |
uncertain vector of the same quantity |
Quick Start
Exact scalars
import quantia as qu
d = qu.Q(100.0, "m")
t = qu.Q(9.81, "s")
v = d / t # UnitFloat(10.19..., 'm/s')
v.to("km/h") # unit conversion: UnitFloat(36.69..., 'km/h')
v.to_si() # always works: UnitFloat(10.19..., 'm/s')
qu.Q(100.0, "°C").to("K") # UnitFloat(373.15, 'K')
qu.Q(100.0, "°C").to("°F") # UnitFloat(212.0, '°F')
Exact arrays
heights = qu.QA([1.75, 1.80, 1.65], "m")
heights.mean() # UnitFloat(1.73..., 'm')
heights.sum() # UnitFloat(5.2, 'm')
heights.to("cm") # UnitArray([175.0, 180.0, 165.0], 'cm')
# Boolean mask filtering
tall = heights[heights > qu.Q(1.78, "m")] # UnitArray([1.8], 'm')
Uncertainty propagation
with qu.config(n_samples=2000, seed=42):
efficiency = qu.ProbUnitFloat.uniform(0.88, 0.95, "1") # ProbUnitFloat(mean=0.9154, std=0.01999, unit='1', n=2000)
power_in = qu.ProbUnitFloat.normal(500.0, 10.0, "W") # ProbUnitFloat(mean=500.1, std=10.02, unit='W', n=2000)
power_out = efficiency * power_in # ProbUnitFloat(mean=457.8, std=13.54, unit='W', n=2000
power_out.mean() # UnitFloat(457.76..., 'W')
power_out.std() # UnitFloat(13.54..., 'W')
# (lo, hi) 95% CI as UnitFloat tuple
power_out.interval(0.95) # (UnitFloat(432.72...,'W'), UnitFloat(484.46..., 'W'))
power_out.percentile(10) # UnitFloat(439.82..., 'W')
Correlated inputs
When inputs are not independent, use a Gaussian copula:
src = qu.CorrelatedSource(n_vars=2, rho=0.8)
x = src.draw(0, "normal", "m", mean=10, std=1)
y = src.draw(1, "uniform", "s", low=1, high=3)
speed = x / y # ProbUnitFloat, correlated samples
speed # ProbUnitFloat(mean=5.385, std=1.325, unit='m/s', n=1000)
Or pass a full correlation matrix:
src = qu.CorrelatedSource(corr_matrix=[
[1.0, 0.7, 0.4],
[0.7, 1.0, 0.3],
[0.4, 0.3, 1.0],
])
Unit Expressions
quantia parses unit strings with a full tokenizer:
qu.Q(9.81, "m/s^2")
# UnitFloat(9.81, 'm/s^2')
qu.Q(1.0, "kg·m/s^2") # · or * for multiplication
# UnitFloat(1.0, 'N')
qu.Q(4.0, "m^(1/2)") # rational exponents
# UnitFloat(4.0, 'm^(1/2)')
qu.Q(1.0, "kg/m^2/s") # chained division
# UnitFloat(1.0, 'kg/m^2·s')
Built-in unit domains
| Domain | Examples |
|---|---|
| SI base | m, kg, s, K, A, mol |
| SI derived | N, J, W, Pa, V, Ω, Hz |
| Temperature | °C, °F, K |
| Imperial | ft, lb, psi, BTU, hp, mph |
| Petroleum | bbl, Mbbl, psi_g, scf, Mscf, °API |
| Data | B, KB, MB, GB, TB, bit |
Semantic / tagged units
Dimensionally equal but semantically distinct units that won't cancel:
from quantia import register_tagged
register_tagged("m3_res", "m3", "reservoir")
register_tagged("m3_sc", "m3", "standar condition")
Bo = qu.Q(1.1, "m3_res") / qu.Q(1.0, "m3_sc")
# UnitFloat(1.1, 'm3_res/m3_sc') — does not reduce to 1
Math Functions
quantia.math is a drop-in replacement for the stdlib math module that dispatches transparently on all four types:
import quantia.math as qmath
x = qu.QA([2, 3], "m^2")
# UnitArray([2.0, 3.0], 'm^2')
qmath.log10(x) # float, UnitFloat, ProbUnitFloat, UnitArray, ProbUnitArray
# UnitArray([0.301..., 0.477...], '1')
qmath.exp(x)
# UnitArray([7.38..., 20.08...], '1')
qmath.sqrt(x) # preserves units: sqrt(m^2) → m
# UnitArray([1.41..., 1.73...], 'm')
qmath.sin(x)
# UnitArray([0.909..., 0.141...], '1')
qmath.cos(x)
# UnitArray([-0.416..., -0.989...], '1')
Configuration
with qu.config(n_samples=5000, seed=42):
x = qu.ProbUnitFloat.normal(10.0, 1.0, "m")
y = qu.ProbUnitFloat.uniform(0.0, 1.0, "s")
# config restored on exit; contexts nest cleanly
Serialization
# Save and load any quantia object
qu.save(power_out, "result.json")
result = qu.load("result.json")
result
# ProbUnitFloat(mean=457.8, std=13.54, unit='W', n=2000)
# Dict round-trip
d = power_out.to_dict()
pf2 = qu.from_dict(d)
pf2
# ProbUnitFloat(mean=457.8, std=13.54, unit='W', n=2000)
CSV export
# UnitArray — single column of values
heights.to_csv("heights.csv")
t1 = qu.QP(2, 4, "m", 10)
t2 = qu.QP(3, 4, "m", 10)
t3 = qu.QP(2, 5, "m", 10)
# ProbUnitArray — mean, std, CI bounds per element
layer_thicknesses = qu.QPA([t1, t2, t3]) # must be same unit
layer_thicknesses.to_csv("stats.csv", confidence=0.90)
layer_thicknesses.samples_to_csv("raw.csv")
Error Handling
quantia raises named exceptions from quantia._exceptions:
from quantia import IncompatibleUnitsError, DimensionError, UnknownUnitError
qu.Q(1.0, "m") + qu.Q(1.0, "s") # → IncompatibleUnitsError
qmath.sin(qu.Q(1.0, "m")) # → DimensionError
qu.Q(1.0, "furlongs") # → UnknownUnitError
Running Tests
pip install pytest
pytest tests/ -v
465 tests covering scalar arithmetic, array operations, probabilistic math, unit algebra invariants, serialization, and the config system.
Benchmarks
python -m quantia.profiling.benchmark
python -m quantia.profiling.benchmark --profile # cProfile detail at n=10k
python -m quantia.profiling.benchmark --n 10000 # single sample size
Typical throughput on a modern laptop (stdlib backend, no numpy):
| Operation | n=10k |
|---|---|
| Scalar arithmetic chain | ~10ms |
ProbUnitFloat.normal construction |
~4ms |
mean() |
~0.1ms |
interval(0.95) |
~0ms (cached sort) |
| Gaussian copula k=5 | ~50ms |
License
MIT — see LICENSE.
Metadata
Release files for quantia 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| quantia-0.2.2.tar.gz | 83.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| quantia-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 181.1 kB
Release files / quantia-0.2.2.tar.gz
| Download URL | quantia-0.2.2.tar.gz |
|---|---|
| Size | 83.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fd9c27e9bf733802c1f2bfe11c6f6f4f077c2265b3907c46b52610b004087cde
|
|
BLAKE2b-256 checksum How to use checksums |
40955944c2e18b22af9999e2c15adc466095677dd5efd93c7ba203e0d355b808
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.3
|
Release files / quantia-0.2.2-py3-none-any.whl
| Download URL | quantia-0.2.2-py3-none-any.whl |
|---|---|
| Size | 98.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3d3951e73f88f69f73c1e1cd0db426cccf4b5fffd7f8e4aa5b5af06173911b67
|
|
BLAKE2b-256 checksum How to use checksums |
3965fc5d4b85e03ce1251b249605e8828682686f9267b641da6439ed10ab63c7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.3
|