alkahest
Python API · Submodule

alkahest.units

alkahest.units — physical units, quantities and dimensional analysis.

A Unit is a product of named units with rational exponents over the seven SI base dimensions, carrying an exact scale relative to SI (1 inch = 127/5000 m, 1 deg = π/180 rad — never a float). A Quantity pairs a value (an Expr, int or Fraction) with a unit, and its arithmetic checks dimensions:

  • + / - need equal dimensions (UnitError E-UNIT-001 names both) and convert the right operand exactly into the left operand's unit;
  • * / / / ** (rational exponent) combine units;
  • exp(), log(), sin(), … need a dimensionless argument (E-UNIT-004); an angle in deg is converted to rad first;
  • degC / degF are affine scales: they can be convert()ed and nothing else (E-UNIT-003).

dimension_of() checks a whole symbolic formula given each symbol's unit, and diff() / integrate() carry units through calculus (∫ C dt of a concentration over time is in mg*h/L).

Example

Python
>>> from alkahest import ExprPool
>>> from alkahest.units import Quantity, convert
>>> convert(Quantity(1, "inch"), "m").value
Fraction(127, 5000)
class
class units.Quantity(value, unit='1')

A value together with its Unit.

Parameters
valueExpr, int, Fraction or float

The magnitude. Use int / Fraction / Expr for exact work; a float is kept as given (conversions never introduce one).

unitUnit or str, default "1"

The unit, e.g. "mg", "L/h", Unit("m/s^2").

Arithmetic checks dimensions (see the module docstring). Comparison with == is identity; compare convert(a, b.unit).value with b.value instead.

Examples
Python
>>> m = Quantity(2, "kg"); a = Quantity(3, "m/s^2")
>>> (m * a).unit == Unit("N")
True
Properties
dimension: str

The dimension of unit, e.g. "mass/length^3".

Methods
convert(unit, *, _like=None) → Quantity

Express in unit (exact). See convert().

to_base() → Quantity

Express in coherent SI base units (J → kg*m^2/s^2).

simplify_units(derived: bool=True, allowed=None) → Quantity

Merge factors of equal dimension (mg/kg*g → mg, km/m → 1) and, with derived=True (default), rename an exact match to a named SI derived unit (kg*m/s^2 → N). See simplify_units().

simplify() → Quantity

Simplify the value (alkahest.simplify); the unit is unchanged.

class
class units.Unit(spec)

A physical unit: a product of named units with rational exponents, an exact scale relative to SI, and a dimension over the seven SI base dimensions.

Build one from a string — Unit("mg*h/L"), Unit("m/s^2"), Unit("kg m s^-2"), Unit("m^(1/2)"), Unit("µg/mL") — or by combining units with *, / and ** (rational exponent).

Equality is semantic: Unit("N") == Unit("kg*m/s^2") is True; str(u) keeps the spelling. degC / degF are affine scales and may only stand alone and be converted (E-UNIT-003 otherwise).

Raises UnitError (E-UNIT-002) on an unknown or malformed unit.

Properties

The unit's spelling as a product, e.g. "mg*h/L".

dimension

The dimension, e.g. "mass/length^3" or "dimensionless".

dimension_exponents

{base_dimension: Fraction} for the non-zero exponents.

factors

[(name, Fraction exponent), …] as written.

scale

(ratio, pi_power): one of this unit is ratio · π**pi_power of the coherent SI unit of the same dimension (both Fraction).

is_affine

True for degC / degF.

is_pure_number

True when dimensionless with scale 1 (1, rad, m/m).

Methods
dimensionless()
staticmethod

The unit of a pure number, written 1.

same_dimension(other)

True when other has the same dimension.

check_same_dimension(other, operation="add")

Raise UnitError E-UNIT-001 (naming both dimensions) unless other has the same dimension.

require_pure_number(context)

Raise UnitError E-UNIT-004 unless this is a pure number; context names where the value is used ("argument of exp").

conversion(target)

The exact map x [self] = ratio·π**pi_power·x + shift [target] as (ratio, pi_power, shift) (Fractions; shift is 0 except between affine temperature scales). E-UNIT-001 on a dimension mismatch.

to_base()

The coherent SI unit of the same dimension in base units (Unit("J").to_base() is kg*m^2/s^2).

simplify(derived=False, allowed=None)

Merge factors of equal dimension (mg/kg*g → mg); returns (ratio, pi_power, unit) with x [self] = ratio·π**pi_power·x [unit].

With derived=True the merged unit is also renamed to the named SI derived unit it exactly equals (kg*m/s^2 → N), see to_derived(); allowed restricts the candidate names.

to_derived(allowed=None)

The named SI derived unit this unit exactly equals, or None.

Unit("kg*m/s^2").to_derived() is N; g*m/s^2 is mN (prefixes in steps of 1000 only); kg*cm/s^2 is not renamed (its scale is not a power of 1000 times N). The candidates are N, Pa, J, W, C, V, ohm, F, H, T, Wb, S, Hz` (`allowed restricts them). A unit that is already one named factor, is dimensionless or affine, or contains a dimensionless factor (rad/s is not Hz) is left alone, and Gy/Sv/Bq are never produced (they share a dimension with m^2/s^2 and Hz). N*m is a J.

exception
exception units.UnitError

No docstring.

function
units.apply(func, q, name: str | None=None) → Quantity

Apply a function that needs a pure-number argument (exp, log, trigonometric, …) to q.

A dimensionless q with a scale (deg, %) is converted to a pure number first, so sin(Quantity(90, "deg")) is sin(π/2). A dimensional q is refused with UnitError E-UNIT-004.

function
units.concentration_to_ph(c)

The pH of a hydrogen-ion concentration: -log10(c / (1 mol/L)). c is a Quantity of amount per volume (any unit; it is converted exactly to mol/L first). Exact for powers of ten (Fraction(1, 10**7) mol/L → 7), symbolic otherwise; UnitError E-UNIT-001 for another dimension, ValueError for a concentration ≤ 0.

function
units.convert(q: Quantity, unit) → Quantity

Express q in unit, exactly.

The value is multiplied by the exact ratio of the two scales (and, between degC/degF/K/degR, shifted by the exact offset).

Raises UnitError E-UNIT-001 when the dimensions differ.

Examples
Python
>>> convert(Quantity(100, "degC"), "degF").value
212
function
units.db_to_ratio(db, kind: str='power')

The ratio a level in decibels stands for: 10**(db/10) for a power quantity (kind="power", default) and 10**(db/20) for a field quantity (kind="field": voltage, current, pressure, amplitude).

Exact: db_to_ratio(30) is 1000 and db_to_ratio(3) the symbolic 10**(3/10) — never a float unless the input is one. db may be a number, an Expr or a pure-number Quantity. The inverse is ratio_to_db(). There is no dB unit and no dB arithmetic: Unit("dB") is refused with a pointer here.

Examples
Python
>>> db_to_ratio(20, "field")
10
function
units.diff(q, var) → Quantity

d q / d var with unit q.unit / var.unit.

var is a Quantity whose value is a symbol, e.g. Quantity(t, "h").

Examples
Python
>>> # x(t) in m, t in s  →  dx/dt in m/s
function
units.dimension_of(expr, units)

The unit of a symbolic formula, given the unit of each of its symbols.

Checks the dimensional consistency of expr and returns the unit its value is in when every symbol is given in its listed unit.

Parameters
exprExpr

The formula.

unitsdict[Expr, Unit | str]

The unit of each symbol. Keys may be any sub-expression, e.g. an applied undefined function C(t).

Returns
Unit
Raises
UnitError

E-UNIT-001 the terms of a sum have different dimensions (both are named); E-UNIT-004 the argument of exp/log/sin/… or a non-literal exponent is not a pure number (an angle must be in rad); E-UNIT-005 a symbol or function with no known unit; E-UNIT-006 the terms of a sum share a dimension but not a unit (h and min); E-UNIT-003 an affine temperature inside arithmetic.

Examples
Python
>>> p = ExprPool(); D, V = p.symbol("D"), p.symbol("V")
>>> dimension_of(D / V, {D: "mg", V: "L"})
Unit('mg/L')
function
units.integrate(q, var, lower=None, upper=None) → Quantity

∫ q d var with unit q.unit · var.unit.

var is a Quantity whose value is a symbol, e.g. Quantity(t, "h"). Bounds may be quantities (converted exactly into var's unit) or the unit-free 0 and ±oo; a bare number for a dimensional variable is refused (E-UNIT-001), because its unit is unknown.

Examples
Python
>>> # AUC: ∫_0^∞ C(t) dt with C in mg/L and t in h is in mg*h/L.
function
units.known_units()

Every unit name the parser knows (without SI prefixes), sorted.

function
units.np_to_ratio(np_, kind: str='field')

The ratio a level in nepers stands for: exp(np) for a field quantity (default) and exp(2·np) for a power quantity. Exact (exp(...) stays symbolic; 0 Np is 1).

function
units.parse_unit(spec)

Parse a unit string into a Unit.

Accepts products and quotients ("mg*h/L", "kg m s^-2", "kg*m^2/(s^2*A)"), exponents with ^ or ** (fractional ones in parentheses, "m^(1/2)") or Unicode superscripts ("m s⁻²"), SI prefixes from yocto to yotta (µ, μ and u all mean micro), and exact numeric factors ("1000*m"). / divides by the single factor that follows it.

Raises UnitError: E-UNIT-002 for a malformed string or an unknown name ("m2" is refused, not read as m^2), E-UNIT-003 for an affine temperature unit inside a compound unit ("degC/s").

Examples
Python
>>> parse_unit("mg*h/L").dimension
'mass*time/length^3'
function
units.ph_to_concentration(ph) → Quantity

The hydrogen-ion concentration of a pH as a Quantity in mol/L: 10**(-pH) (pH 7 is Fraction(1, 10**7)), taking the IUPAC standard state of 1 mol/L (activity ≈ concentration). The inverse is concentration_to_ph().

function
units.ratio_to_db(ratio, kind: str='power')

The level in decibels of a positive ratio: 10·log10(ratio) for a power quantity, 20·log10(ratio) for a field quantity. Exact for exact powers of ten (ratio_to_db(1000) is 30), symbolic otherwise (Expr in, 10*log(r)/log(10) out). ValueError for a ratio ≤ 0.

Examples
Python
>>> ratio_to_db(100, "field")
40
function
units.ratio_to_np(ratio, kind: str='field')

The level in nepers of a positive ratio: ln(ratio) for a field quantity, ln(ratio)/2 for a power quantity. ratio_to_np(1) is 0; ValueError for a ratio ≤ 0.

function
units.simplify_units(q: Quantity, derived: bool=True, allowed=None) → Quantity

Merge the factors of q.unit that share a dimension into the first one (mg/kg*g → mg, L/h*min → L, km/m → 1), folding the exact conversion ratio into the value; then, unless derived=False, rename the result to the named SI derived unit it exactly equals.

Parameters
qQuantity
derivedbool, default True

kg*m/s^2 → N, kg*m^2/s^2 → J, A*s → C, kg*m^2/(A*s^3) → V`; a power-of-1000 prefix is allowed (`g*m/s^2 → mN). The candidates are N, Pa, J, W, C, V, ohm, F, H, T, Wb, S, Hz (their dimensions are pairwise distinct, so there is no ambiguity among them; N*m is a J). A unit that is already a single named factor, carries a dimensionless factor (rad/s is not Hz) or whose scale is not a power of 1000 times the derived unit (kg*cm/s^2) is left as is. Gy, Sv and Bq are never produced.

allowediterable of str, optional

Restrict the candidates, e.g. allowed=["N", "J"].

Examples
Python
>>> simplify_units(Quantity(3, "kg*m/s^2")).unit
Unit('N')
>>> simplify_units(Quantity(3, "kg*m/s^2"), derived=False).unit
Unit('kg*m/s^2')
function
units.sqrt(q) → Quantity

q ** (1/2); the unit's scale must have an exact square root (E-UNIT-007 otherwise, e.g. km).

function
units.to_base(q: Quantity) → Quantity

Express q in coherent SI base units (kg, m, s, A, K, mol, cd); degC/degF go to K.

constant
units.acos
acos = _fn('acos')

No docstring.

constant
units.asin
asin = _fn('asin')

No docstring.

constant
units.atan
atan = _fn('atan')

No docstring.

constant
units.cos
cos = _fn('cos')

No docstring.

constant
units.cosh
cosh = _fn('cosh')

No docstring.

constant
units.exp
exp = _fn('exp')

No docstring.

constant
units.log
log = _fn('log')

No docstring.

constant
units.sin
sin = _fn('sin')

No docstring.

constant
units.sinh
sinh = _fn('sinh')

No docstring.

constant
units.tan
tan = _fn('tan')

No docstring.

constant
units.tanh
tanh = _fn('tanh')

No docstring.