PyQn
PyQn is a lightweight Python package for parsing, validating, manipulating and converting physical units and quantities.
Units are written as plain strings in a compact syntax, such as kJ.mol-1, cm-1 or Pa-1/2. PyQn works out their dimensions, so conversions and arithmetic between them are checked. A few conventions allow conversions between units of different dimensions where that is usual practice, such as between wavenumbers and energies in spectroscopy.
PyQn is pure Python with no dependencies. It aims to be convenient within particular fields of science rather than to cover every unit in use.
The documentation, with worked examples for spectroscopy, atmospheric chemistry, physical chemistry and the validation of data, is at https://pyqn.readthedocs.io.
Installation
pip install pyqn
PyQn needs Python 3.10 or later.
Quick start
>>> from pyqn import Units, Quantity
>>> Units("km.hr-1").conversion("m.s-1")
0.2777777777777778
>>> Units("J/mol.K")
J.mol-1.K-1
>>> str(Units("kJ.mol-1").dims)
'L2.M.T-2.Q-1'
>>> Units("J") == Units("kg.m2.s-2")
True
>>> Units("m.s-1") * Units("s")
m
Converting between units of different dimensions is an error, unless a convention is asked for (see Conventions):
>>> Units("J").conversion("K")
Traceback (most recent call last):
...
UnitsError: Failure in units conversion: units J (L2.M.T-2) and K (Theta) have different dimensions
Unit syntax
A unit string is a sequence of atoms joined by . (multiply) or / (divide). Each atom is a unit symbol, perhaps with an SI prefix, followed by an optional exponent:
kg |
kilogram: prefix k, unit g |
s-1 |
per second |
nm3 |
cubic nanometre |
Pa-1/2 |
a rational exponent |
m10 |
metre to the power 10 |
The exponent follows the unit directly, as an integer or a ratio of integers: not m^2 or m**2.
Each / divides by everything after it up to the next /: J/mol.K is J mol⁻¹ K⁻¹, and kg/m2/s is kg m⁻² s⁻¹.
Spaces are not allowed: kg m-2 is an error, not kg m⁻².
1 is the unit of a dimensionless quantity, as in 1/s.
The SI prefixes are y z a f p n μ m c d h k M G T P E Z Y, but each unit takes only some of them: see the Prefixes column in the table of Units. Hecto (h) goes only with Pa, L, l, m and g, as in hPa. Deca (da) and the prefixes added in 2022 (ronna, quetta, ronto and quecto) are not supported.
pyqn 2.0 allowed every prefix but hecto on every unit. Prefixes that a unit no longer takes, as in kmin or μÅ, still work in pyqn 2.1 but give a DeprecationWarning; pyqn 3.0 will reject them.
μ may be typed as the Greek letter mu or as the micro sign µ; the ohm and angstrom signs are likewise read as Ω and Å. A plain u is the atomic mass unit, not micro.
Symbols are case-sensitive: K is the kelvin and k the kayser.
undef stands for undefined units. Products, ratios and powers involving undef are undef too; undef units cannot be converted, and quantities with them cannot be added or subtracted.
Every string reads in only one way. Because each unit takes only some prefixes, the standard symbols in (inch), yd (yard) and d (day) are safe to use: min is still the minute and cd the candela. pyqn prints these units with the symbols it used before version 2.1, inch, yard and dy, which every version reads (see Domains). The arcsecond is asec, because as is the attosecond.
Note also that b is the barn, so mb is the millibarn: the millibar is mbar. And ppt is always parts per trillion, never parts per thousand.
Domains
A domain is a named set of units, used when parsing unit strings, so that strings that make no sense in a field are rejected. The default domain, domains.everything, has every unit.
>>> from pyqn import domains
>>> Units("km.h-1", domain=domains.si)
km.h-1
>>> Units("ft", domain=domains.si)
Traceback (most recent call last):
...
UnitsError: Unrecognised unit in the si domain: ft (the tonne, t, does not take the prefix f)
domains.si has the SI units and the non-SI units accepted for use with them: the minute, hour, day, astronomical unit, degree, arcminute, arcsecond, hectare, litre, tonne, dalton and electronvolt.
Each unit has a canonical symbol, which means the same in every domain and is what pyqn prints, so what pyqn prints can be read back in any domain that has the unit. A domain can also accept other spellings of a unit, and its format() writes units with them:
>>> u = Units("km.d-1", domain=domains.si)
>>> u
km.dy-1
>>> domains.si.format(u)
'km.d-1'
Once parsed, units are the same whatever domain they came from: arithmetic, comparisons and conversions work in the same way, and a string in them, as in u.conversion("m.s-1"), is read in the default domain. Quantity and Quantity.parse() also take a domain.
You can make domains of your own. When a domain is made, pyqn checks that every string it accepts reads in only one way:
>>> from pyqn import Domain
>>> lab = Domain("lab", units=[*domains.si.units, domains.everything.stems["Torr"]])
>>> Units("mTorr", domain=lab)
mTorr
Built-in domains
pyqn has domains for several fields. Each has the units of si and those that the field uses with them, and some read a symbol in the field’s own way. In geology, D is the darcy, so mD is the millidarcy, which pyqn prints with the darcy’s canonical symbol; in radiation physics, rad is the unit of absorbed dose, and there is no radian.
>>> Units("mD", domain=domains.geology)
mdarcy
>>> domains.geology.format(Units("mD", domain=domains.geology))
'mD'
>>> Units("mD", domain=domains.spectroscopy)
mD
Gaussian (cgs) units belong to a system with three base dimensions, so pyqn defines them by the SI units they correspond to. They convert to those units, but the gauss and the oersted, for example, cannot be converted into each other, because the tesla and the ampere per metre have different dimensions:
>>> Units("G", domain=domains.gaussian_electromagnetism).conversion("T")
0.0001
Domain |
Other spellings |
Units |
|---|---|---|
domains.everything |
in (inch), yd (yard), d (dy) |
every unit (see Units) |
domains.si |
d (dy), au (AU) |
the SI units, and min, h, deg, arcmin, asec, ha, L, l, t, Da, u, eV, dy, AU |
domains.spectroscopy |
none |
those of si, and Å, D, k, Torr, atm, bar, erg, cal, molec |
domains.atomic_and_molecular_physics |
none |
those of si, and Å, D, E_h, Ry, a0, m_e, e, hbar, amu, b, molec |
domains.chemistry |
none |
those of si, and M, Å, D, cal, atm, bar, Torr, mmHg, molec, ppm, ppb, ppt, %, ppmw, ppbw, wt%, ppmv, ppbv, pptv, vol%, mol% |
domains.astronomy |
a (yr) |
those of si, and pc, ly, yr, Jy, erg, dyn, Å, G |
domains.geology |
a (yr), D (darcy), in (inch) |
those of si, and yr, darcy, Gal, psi, bbl, bar, atm, ft, inch, ppm, ppb, ppt, %, ppmw, ppbw, wt% |
domains.gaussian_electromagnetism |
Fr (statC) |
1, m, g, s, dyn, erg, G, Oe, Mx, statC, statV, γ |
domains.radiation_physics |
rad (rd) |
those of si except rad, and Ci, R, rem, rd |
domains.meteorology |
none |
those of si, and bar, atm, kn, NM, ft, %, ppm, ppb, ppt, molec, ppmv, ppbv, pptv, vol% |
Conventions
Units.conversion(other, force=...) can convert between units of different dimensions using conventions, each of which treats some physical constants as 1:
- force="spec"
Spectroscopy, with h and c: energy, wavenumber and frequency are interconverted with E = hν = hcν̃.
- force="mol"
Molar quantities, with the Avogadro constant: the amount dimension is added or removed, as between kJ.mol-1 and J (per molecule).
- force="kBT"
Energy and temperature, with the Boltzmann constant: E = kBT.
>>> Units("J").conversion("cm-1", force="spec")
5.0341165675427096e+22
>>> Units("eV").conversion("kJ.mol-1", force="mol")
96.48533212331002
>>> Units("K").conversion("J", force="kBT")
1.380649e-23
Conventions can be combined:
>>> Units("kJ.mol-1").conversion("cm-1", force=("mol", "spec"))
83.59347229111778
>>> Units("K").conversion("cm-1", force=("kBT", "spec"))
0.6950348004861274
pyqn finds the powers of the constants by which the dimensions of the two units differ. The constants have independent dimensions, so there is at most one way to use them, and a conversion that needs any other change of dimensions is an error. The units can have any dimensions, so pressure broadening coefficients, for example, convert too:
>>> Units("cm-1.atm-1").conversion("MHz.Torr-1", force="spec")
39.44637605263158
Two consequences: because spec treats c as 1, it also converts lengths to light-travel times (cm to s); and mol only adds or removes the amount dimension entirely, so mol cannot be converted to mol2. Wavelengths are not covered, because a wavelength is inversely proportional to frequency rather than a multiple of it.
Conversion factors use the exact values of the SI defining constants and the CODATA 2022 values of the others (such as the electron mass and the hartree).
Molecules and fractions
A molecule, molec, is a unit of amount: exactly 1/NA mol. So HITRAN’s line intensities, in cm-1/(molecule cm-2), convert to molar units directly, and to units per molecule with force="mol":
>>> s = Units("cm-1/molec.cm-2")
>>> s
cm.molec-1
>>> s.conversion("cm.mol-1")
6.02214076e+23
>>> s.conversion("cm", force="mol")
1.0
Mass, volume and amount fractions all have dimension 1 in the SI, but cannot be converted into each other without molar masses or densities. So the units for a particular kind of fraction have a pseudo-dimension, which cancels only against itself:
Pseudo-dimension |
Units |
|---|---|
mass fraction |
ppmw, ppbw, wt% |
volume fraction |
ppmv, ppbv, pptv, vol% |
amount fraction |
mol% |
>>> Units("wt%").conversion("ppmw")
10000.0
>>> Units("ppmv").conversion("ppmw")
Traceback (most recent call last):
...
UnitsError: Failure in units conversion: units ppmv ([volume fraction]) and ppmw ([mass fraction]) have different dimensions
The plain ppm, ppb, ppt and % have dimension 1, and can stand for any kind of fraction. A convention named after a pseudo-dimension treats it as dimensionless:
>>> Units("ppmv").conversion("μmol/mol", force="volume fraction")
1.0
Quantities
A Quantity has a value, units, and optionally a name and a standard uncertainty, sd. Arithmetic on quantities checks their units and propagates their uncertainties, assuming they are uncorrelated. An sd of 0 means the value is exact; an sd of None means its uncertainty is unknown, and so is the uncertainty of anything calculated from it.
>>> q = Quantity.parse("x = 1.5(2)e-3 m.s-1")
>>> q.value, q.sd
(0.0015, 0.0002)
>>> q.as_str()
'x = 0.00150(20) m.s-1'
>>> t = Quantity(name="t", value=10.0, units="s", sd=0.5)
>>> (q * t).as_str()
'0.0150(21) m'
>>> q.convert_units_to("km.hr-1").as_str()
'0.00540(72) km.hr-1'
Quantities can be added or subtracted only if their units are equal: J and kg.m2.s-2 can be added, but J and kJ cannot.
Limitations
Temperatures are absolute: there are no units with an offset, such as °C or °F.
Angles are dimensionless, so, for example, Hz and rad.s-1 are the same units.
There are no logarithmic units, such as the decibel.
Units
These are the units in the default domain, with their canonical symbols and the SI prefixes that each takes (“all except h” is every SI prefix except hecto). The default domain also reads in, yd and d as the inch, yard and day.
Unity
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
1 |
unity |
[dimensionless] |
none |
SI unit stems
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
m |
metre |
L |
all |
s |
second |
T |
all except h |
g |
gram |
M |
all |
K |
kelvin |
Theta |
all except h |
mol |
mole |
Q |
all except h |
A |
amp |
C |
all except h |
cd |
candela |
I |
all except h |
Derived SI units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
N |
newton |
L.M.T-2 |
all except h |
J |
joule |
L2.M.T-2 |
all except h |
W |
watt |
L2.M.T-3 |
all except h |
Pa |
pascal |
L-1.M.T-2 |
all |
C |
coulomb |
T.C |
all except h |
V |
volt |
L2.M.T-3.C-1 |
all except h |
Ω |
ohm |
L2.M.T-3.C-2 |
all except h |
F |
farad |
L-2.M-1.T4.C2 |
all except h |
Wb |
weber |
L2.M.T-2.C-1 |
all except h |
H |
henry |
L2.M.T-2.C-2 |
all except h |
S |
siemens |
L-2.M-1.T3.C2 |
all except h |
T |
tesla |
M.T-2.C-1 |
all except h |
Hz |
hertz |
T-1 |
all except h |
rad |
radian |
[dimensionless] |
all except h |
sr |
steradian |
[dimensionless] |
all except h |
lm |
lumen |
I |
all except h |
lx |
lux |
L-2.I |
all except h |
Bq |
becquerel |
T-1 |
all except h |
Gy |
gray |
L2.T-2 |
all except h |
Sv |
sievert |
L2.T-2 |
all except h |
kat |
katal |
T-1.Q |
all except h |
Non-SI pressure units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
bar |
bar |
L-1.M.T-2 |
all except h |
atm |
atmosphere |
L-1.M.T-2 |
none |
Torr |
torr |
L-1.M.T-2 |
all except h |
mmHg |
millimetres of mercury |
L-1.M.T-2 |
none |
cgs units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
dyn |
dyne |
L.M.T-2 |
all except h |
erg |
erg |
L2.M.T-2 |
all except h |
k |
kayser |
L-1 |
none |
D |
debye |
L.T.C |
m |
hbar |
hbar |
L2.M.T-1 |
none |
e |
electron charge |
T.C |
none |
Angular units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
deg |
degree |
[dimensionless] |
none |
arcmin |
arcminute |
[dimensionless] |
none |
asec |
arcsecond |
[dimensionless] |
μ, m |
Non-SI energy units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
eV |
electron volt |
L2.M.T-2 |
all except h |
E_h |
hartree |
L2.M.T-2 |
m |
cal |
thermochemical calorie |
L2.M.T-2 |
all except h |
Ry |
rydberg |
L2.M.T-2 |
m |
Non-SI mass units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
u |
atomic mass unit |
M |
μ |
amu |
atomic mass unit |
M |
none |
Da |
dalton |
M |
k, M |
t |
tonne |
M |
k, M, G |
m_e |
electron mass |
M |
none |
Non-SI units of length, area and volume
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
Å |
angstrom |
L |
none |
a0 |
bohr |
L |
none |
b |
barn |
L2 |
all except h |
ha |
hectare |
L2 |
none |
l |
litre |
L3 |
all |
L |
litre |
L3 |
all |
Non-SI time units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
min |
minute |
T |
none |
hr |
hour |
T |
none |
h |
hour |
T |
none |
dy |
day |
T |
none |
yr |
year |
T |
k, M, G |
Astronomical units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
AU |
astronomical unit |
L |
none |
pc |
parsec |
L |
k, M, G |
ly |
light-year |
L |
k |
Imperial, customary and US units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
inch |
inch |
L |
none |
ft |
foot |
L |
none |
yard |
yard |
L |
none |
fur |
furlong |
L |
none |
mi |
mile |
L |
none |
gal |
Imperial (UK) gallon |
L3 |
none |
pt |
Imperial (UK) pint |
L3 |
none |
USgal |
US liquid gallon |
L3 |
none |
USpt |
US liquid pint |
L3 |
none |
st |
stone |
M |
none |
lb |
pound |
M |
none |
oz |
ounce |
M |
none |
Maritime units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
NM |
nautical mile |
L |
none |
kn |
knot |
L.T-1 |
none |
Amounts, concentrations and fractions
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
molec |
molecule |
Q |
none |
M |
molar |
L-3.Q |
f, p, n, μ, m |
ppm |
parts per million |
[dimensionless] |
none |
ppb |
parts per billion |
[dimensionless] |
none |
ppt |
parts per trillion |
[dimensionless] |
none |
% |
percent |
[dimensionless] |
none |
ppmw |
parts per million by mass |
[mass fraction] |
none |
ppbw |
parts per billion by mass |
[mass fraction] |
none |
wt% |
percent by mass |
[mass fraction] |
none |
ppmv |
parts per million by volume |
[volume fraction] |
none |
ppbv |
parts per billion by volume |
[volume fraction] |
none |
pptv |
parts per trillion by volume |
[volume fraction] |
none |
vol% |
percent by volume |
[volume fraction] |
none |
mol% |
percent by amount |
[amount fraction] |
none |
Geological and petroleum units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
darcy |
darcy |
L2 |
m |
Gal |
gal (galileo) |
L.T-2 |
μ, m |
psi |
pound per square inch |
L-1.M.T-2 |
k |
bbl |
barrel (oil) |
L3 |
none |
Gaussian electromagnetic units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
G |
gauss |
M.T-2.C-1 |
μ, m, k |
Oe |
oersted |
L-1.C |
k |
Mx |
maxwell |
L2.M.T-2.C-1 |
none |
statC |
statcoulomb |
T.C |
none |
statV |
statvolt |
L2.M.T-3.C-1 |
none |
γ |
gamma |
M.T-2.C-1 |
none |
Radiation units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
Ci |
curie |
T-1 |
p, n, μ, m, k |
R |
röntgen |
M-1.T.C |
μ, m |
rem |
rem |
L2.T-2 |
m |
rd |
rad (absorbed dose) |
L2.T-2 |
m |
Silly units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
fir |
firkin |
M |
none |
ftn |
fortnight |
T |
none |
Miscellaneous units
Unit |
Name |
Dimensions |
Prefixes |
|---|---|---|---|
Td |
townsend |
L4.M.T-3.C-1 |
none |
Jy |
jansky |
M.T-2 |
all except h |
Development
Install the package in editable mode with its development tools (this needs pip 25.1 or later):
python -m pip install -e . --group dev
Then run the tests, which include the examples in this README, and check the formatting and the code:
pytest ruff format --check . ruff check .
python -m pyqn.list_base_units rst prints the table of units above, and a test checks that the README has the current version.
The documentation, in docs/, includes this README as its user guide. To build it:
python -m pip install -e . --group docs sphinx-build -W -b html docs docs/_build/html
Licence
PyQn is free software, released under the GNU General Public License, version 3 or later (see LICENSE).
Metadata
Release files for pyqn 2.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pyqn-2.1.0.tar.gz | 64.5 kB | With provenance | Details for pyqn-2.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyqn-2.1.0-py3-none-any.whl | Python 3 | none | any | Details for pyqn-2.1.0-py3-none-any.whl |
Total release size: 111.7 kB
Release files / pyqn-2.1.0.tar.gz
| Download URL | pyqn-2.1.0.tar.gz |
|---|---|
| Size | 64.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
06192ae9074c61942e4984057fb4cce8690beda114d183c14f1a1d3a570daa66
|
|
BLAKE2b-256 checksum How to use checksums |
b5192d32a1e3fa83c125c8314c0f83a59b18b8a0f83c8fc5eeb9a68cb0c8cd42
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logRelease files / pyqn-2.1.0-py3-none-any.whl
| Download URL | pyqn-2.1.0-py3-none-any.whl |
|---|---|
| Size | 47.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7b7030f953a67c1f7807cb41a6cbee7e8efe654b63ee227caf1e9f70f78cc071
|
|
BLAKE2b-256 checksum How to use checksums |
2ef1119a741335882c73a8aa9019f944b0354db16eec6bf545d5d78c346487c7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logSecurity for pyqn 2.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pyqn-2.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.