Unit-aware arithmetic with Monte Carlo uncertainty propagation
Project description
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.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file quantia-0.2.2.tar.gz.
File metadata
- Download URL: quantia-0.2.2.tar.gz
- Upload date:
- Size: 83.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd9c27e9bf733802c1f2bfe11c6f6f4f077c2265b3907c46b52610b004087cde
|
|
| MD5 |
ad4ad0a6fdbd3184aa53a8e7f7805973
|
|
| BLAKE2b-256 |
40955944c2e18b22af9999e2c15adc466095677dd5efd93c7ba203e0d355b808
|
File details
Details for the file quantia-0.2.2-py3-none-any.whl.
File metadata
- Download URL: quantia-0.2.2-py3-none-any.whl
- Upload date:
- Size: 98.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d3951e73f88f69f73c1e1cd0db426cccf4b5fffd7f8e4aa5b5af06173911b67
|
|
| MD5 |
55aeff404c212e0affa47a9d12c3750b
|
|
| BLAKE2b-256 |
3965fc5d4b85e03ce1251b249605e8828682686f9267b641da6439ed10ab63c7
|