Skip to main content

License: MIT Documentation

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)

Source distribution for quantia 0.2.2
File Size Uploaded
quantia-0.2.2.tar.gz 83.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for quantia 0.2.2
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.2.2 This release

2 release files

0.2.1

2 release files

0.2.0

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