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
v.to_si() # always works
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.733…, 'm')
heights.sum()
heights.to("cm")
# Boolean mask filtering
tall = heights[heights > qu.Q(1.78, "m")] # UnitArray([1.80], 'm')
Uncertainty propagation
with qu.config(n_samples=2000, seed=42):
efficiency = qu.ProbUnitFloat.uniform(0.88, 0.95, "1")
power_in = qu.ProbUnitFloat.normal(500.0, 10.0, "W")
power_out = efficiency * power_in
power_out.mean() # UnitFloat(~457 W)
power_out.std()
power_out.interval(0.95) # (lo, hi) 95% CI as UnitFloat tuple
power_out.percentile(10)
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
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")
qu.Q(1.0, "kg·m/s^2") # · or * for multiplication
qu.Q(4.0, "m^(1/2)") # rational exponents
qu.Q(1.0, "kg/m^2/s") # chained division
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("Sm3_res", "m3", "reservoir")
register_tagged("Sm3_st", "m3", "stock_tank")
Rs = qu.Q(150.0, "Sm3_res") / qu.Q(1.0, "Sm3_st")
# UnitFloat(150.0, 'Sm3_res/Sm3_st') — 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 mmath
mmath.log10(x) # float, UnitFloat, ProbUnitFloat, UnitArray, ProbUnitArray
mmath.exp(x)
mmath.sqrt(x) # preserves units: sqrt(m^2) → m
mmath.sin(x) # requires angle unit on UnitFloat; raises DimensionError otherwise
mmath.cos(x)
mmath.atan2(y, x)
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")
# Dict round-trip
d = power_out.to_dict()
pf2 = qu.from_dict(d)
CSV export
# UnitArray — single column of values
heights.to_csv("heights.csv")
# 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
mmath.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 | ~1ms |
ProbUnitFloat.normal construction |
~4ms |
mean() |
~0.1ms |
interval(0.95) |
~0ms (cached sort) |
| Gaussian copula k=3 | ~33ms |
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.1.tar.gz.
File metadata
- Download URL: quantia-0.2.1.tar.gz
- Upload date:
- Size: 73.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c2a4fb2e2991c510878fdbf561594410c0aa4f631a12b614ff8aa0c789fb8349
|
|
| MD5 |
c2430e3f31a1b1dbd220a7bccd0bef20
|
|
| BLAKE2b-256 |
1c593b4e56485bdbc173be9c61d500fc144bf0bca4cffab6e7137df08be30f2e
|
File details
Details for the file quantia-0.2.1-py3-none-any.whl.
File metadata
- Download URL: quantia-0.2.1-py3-none-any.whl
- Upload date:
- Size: 88.4 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 |
6b711d23d85409201870cdfa3e0a11b8c680633a244f2e5ca72059ff17180836
|
|
| MD5 |
4bcee964e3bf52f1a23cfe08d0e94a4b
|
|
| BLAKE2b-256 |
79fc74d7c8bfda479ac7322b6dd0964087847e5b8615a2c10f3a4f651c908057
|