Skip to content

/uncertainty-and-units

Track physical units and propagate measurement uncertainty in scientific calculations using pint and uncertainties. Use for unit conversion and dimensional checking, GUM uncertainty budgets, Type A and Type B evaluation, coverage factors and expanded uncertainty, Monte Carlo

One skill from scientific-agent-skills.

shell
$ npx -y skills add K-Dense-AI/scientific-agent-skills --skill uncertainty-and-units --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfClaude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/uncertainty-and-units

Context preview

The summary Claude sees to decide when to auto-load this skill.

Track physical units and propagate measurement uncertainty in scientific calculations using pint and uncertainties. Use for unit conversion and dimensional checking, GUM uncertainty budgets, Type A and Type B evaluation, coverage factors and expanded uncertainty, Monte Carlo

Stats

Stars32,086
Forks3,184
LanguagePython
LicenseMIT

Ships with scientific-agent-skills

@K-Dense-AI@K-Dense-AIGet the whole plugin, auto-invoked

What is in the file

Measured from the uncertainty-and-units.SKILL.md below.

  • 2,376 words, about a 12 minute read.
  • Organized into 25 sections.
  • Includes 16 worked examples.
  • Uses a reference table.
  • Ships with 157 other skills in this plugin, and is the 11th longest of them.

SKILL.md

uncertainty-and-units.SKILL.md
---
name: uncertainty-and-units
description: Track physical units and propagate measurement uncertainty in scientific calculations using pint and uncertainties. Use for unit conversion and dimensional checking, GUM uncertainty budgets, Type A and Type B evaluation, coverage factors and expanded uncertainty, Monte Carlo propagation, significant-figure and plus-minus reporting, error propagation through curve fits, CODATA constants, auditing Python code for stripped units or broken uncertainty propagation, and order-of-magnitude plausibility checks using dimensionless groups (Reynolds, Peclet, Damkohler, Knudsen, Biot, Womersley), characteristic scales such as diffusion time or Debye length, and observed magnitude ranges. Trigger on "is this number physically reasonable", "sanity check these units", "what regime is this flow in", or a result that looks off by orders of magnitude.
license: MIT
compatibility: Requires Python 3.12+. The numeric CLIs need pint, uncertainties, NumPy, and SciPy; the static auditor is standard-library only. All bundled tooling runs locally with no network access.
allowed-tools: Read Write Edit Bash
metadata:
  version: "1.0"
  skill-author: K-Dense Inc.
---

# Uncertainty and units

## Scope

Use this skill whenever a calculation carries physical units or a reported number needs
an uncertainty. Concretely:

- converting between units, including conversions that need a physical context
  (wavelength to photon energy, mass to amount of substance, energy to temperature);
- propagating uncertainty through a measurement model, with or without correlated inputs;
- building a GUM uncertainty budget from calibration certificates, specifications, and
  repeatability data;
- choosing a coverage factor and deciding whether `k = 2` is defensible;
- rounding and writing a result so a reader knows what the `±` means;
- extracting parameter uncertainties from a curve fit without discarding correlations;
- reviewing existing analysis code for silent unit and uncertainty defects;
- checking that a dimensionally consistent answer is also physically possible — the
  order of magnitude, the dimensionless group, and the regime it implies.

This skill covers the metrology and the two libraries that implement it. It does not
cover statistical inference, model selection, or study design — see `statistical-analysis`,
`statistical-power`, and `experimental-design`.

## Current release and installation

Verified 2026-07-26:

- **pint 0.25.3**, released 2026-03-19; requires Python 3.11+.
- **uncertainties 3.2.3**, released 2025-04-21; requires Python 3.8+.
- **NumPy 2.5.1** and **SciPy 1.18.0**; both require Python 3.12+.
- `scipy.constants` in SciPy 1.18.0 serves **CODATA 2022**. SciPy 1.11 and earlier
  served CODATA 2018, and several recommended values differ between them.

```bash
uv venv --python 3.13
source .venv/bin/activate
uv pip install "pint==0.25.3" "uncertainties==3.2.3" "numpy==2.5.1" "scipy==1.18.0"
```

`pint-pandas` and `pint-xarray` add unit-aware columns and arrays and are separate
installs.

## Non-negotiable workflow

1. **Attach units at input and strip them only at output.** Convert at function
   boundaries with `ureg.wraps` or `m_as("unit")`, never mid-calculation.
2. **Write the measurement model explicitly** before computing anything, including
   corrections whose estimated value is zero. A correction left out of the model leaves
   its uncertainty out of the budget.
3. **Give every input four things**: an estimate, a standard uncertainty, the
   distribution the uncertainty came from, and its degrees of freedom.
4. **Convert Type B statements with the right divisor.** A certificate's expanded
   uncertainty divides by its stated `k`; rectangular limits divide by `sqrt(3)`.
5. **Identify correlations before combining.** Inputs calibrated against the same
   standard, measured on the same instrument, or drawn from the same fit are correlated.
6. **Compute sensitivity coefficients**, and read the budget from `c_i * u(x_i)` rather
   than from the raw uncertainties.
7. **Check the linearization.** Run Monte Carlo alongside the GUM framework and apply
   the JCGM 101 clause 8 comparison. Report the Monte Carlo result when it fails.
8. **Choose `k` from the effective degrees of freedom**, not by habit.
9. **Round the uncertainty first, then the value to the same decimal place.**
10. **State what the `±` is** — standard or expanded, with `k`, the coverage probability,
    and the method.
11. **Sanity-check the magnitude before reporting.** A dimensionally consistent result can
    still be impossible. Compare it against a known scale or a dimensionless group, and
    confirm every assumption you relied on still holds in that regime.

## The failures this skill exists to prevent

Each of the following runs without error and produces a plausible number.

### A unit stripped at an unknown scale

```python
length = (12.7 * ureg.mm).magnitude          # 12.7 -- of what?
length = (12.7 * ureg.mm).m_as("m")          # 0.0127 metres, stated
```

`.magnitude` returns whatever the quantity happened to be carrying. Name the unit at the
point of extraction, every time.

### Offset temperature arithmetic

```python
Q(20, "degC") + Q(5, "degC")     # OffsetUnitCalculusError -- correctly refused
Q(20, "degC") + Q(5, "delta_degC")   # 25 degree_Celsius
Q(25, "degC") - Q(20, "degC")        # 5 delta_degree_Celsius
```

Celsius and Fahrenheit are interval scales. An uncertainty on a temperature is always a
difference and belongs in a `delta_` unit: converting `20 ± 0.5 degC` to Fahrenheit
gives `68 degF ± 0.9 delta_degF`, two different conversions on one line.

### Logarithmic units that add by multiplying

```python
Q(10, "dBm") + Q(10, "dBm")   # 0.0001 kilogram**2 * meter**4 / second**6
```

That is 10 mW × 10 mW, not 20 mW and not 13 dBm. Nothing raises. Convert to a linear
unit before any arithmetic.

### A correlation destroyed by a round trip

```python
x = ufloat(1.0, 0.1)
x - x                                     # 0.0+/-0
x - ufloat(x.nominal_value, x.std_dev)    # 0.00+/-0.14
```

Rebuilding a variable from its nominal value and standard deviation creates an
independent variable. So does any serialization that passes through a pair of floats.
Use `correlated_values(values, covariance_matrix)` to rebuild a correlated set.

### A covariance matrix silently rescaled

```python
popt, pcov = curve_fit(f, x, y, sigma=sigma)                        # default
popt, pcov = curve_fit(f, x, y, sigma=sigma, absolute_sigma=True)
```

The default rescales `pcov` by the reduced chi-square, so the parameter uncertainties
absorb the goodness of fit and match what you would get by passing no `sigma` at all. On
one synthetic straight-line fit the two give `[0.0364, 0.2154]` and `[0.0477, 0.2820]` —
a 31% difference. Pass `absolute_sigma=True` whenever `sigma` holds real standard
uncertainties.

### A linearization that was never checked

For `y = x²` with `x = 1.0 ± 0.5`, the GUM framework gives `y = 1.0`, `u_c = 1.0`, and a
95% interval of `[-0.96, 2.96]` — mostly negative, for a squared quantity. Monte Carlo
gives a mean of 1.25, `u_c = 1.06`, and a shortest 95% interval of `[0, 3.32]`. Nothing
in a linear-propagation library will tell you this happened.

## Bundled local CLIs

All helpers run offline, reject URLs and symlinks, bound their inputs, write output
atomically with private permissions, and refuse to overwrite without `--force`.

```bash
python skills/uncertainty-and-units/scripts/propagate_uncertainty.py --help
python skills/uncertainty-and-units/scripts/uncertainty_budget.py --help
python skills/uncertainty-and-units/scripts/format_result.py --help
python skills/uncertainty-and-units/scripts/convert_units.py --help
python skills/uncertainty-and-units/scripts/audit_units.py --help
python skills/uncertainty-and-units/scripts/check_plausibility.py --help
```

### propagate_uncertainty.py

Runs both propagation methods on the same model and applies the JCGM 101 clause 8
validation test.

```bash
python skills/uncertainty-and-units/scripts/propagate_uncertainty.py \
  --expression "m / (pi * (d / 2) ** 2 * h)" \
  --variable "m=250.0,0.05" \
  --variable "d=20.0,0.02,rectangular" \
  --variable "h=40.0,0.05,rectangular" \
  --measurand density --unit "g/cm3" --format markdown
```

Each `--variable` is `name=value,standard_uncertainty[,distribution[,dof]]`, where the
distribution is `normal`, `rectangular`, `triangular`, `arcsine`, or `exact` and controls
Monte Carlo sampling only. Correlations go in as `--correlation "a,b=0.9"`. A JSON
`--spec` file holds the same model for anything long-lived.

The expression is parsed into an abstract syntax tree and reduced by an explicit walk
over `+ - * / **` and a fixed list of functions. It is never compiled or executed.

The report gives the estimate, `u_c`, sensitivity coefficients, the budget in percent,
effective degrees of freedom, `k`, `U`, both Monte Carlo coverage intervals, and the
verdict on whether the linearized result may be reported.

### uncertainty_budget.py

Combines components stated the way certificates and data sheets state them.

```bash
python skills/uncertainty-and-units/scripts/uncertainty_budget.py --template > budget.json
python skills/uncertainty-and-units/scripts/uncertainty_budget.py --spec budget.json --format markdown
```

Each component names a `distribution` that fixes its divisor — `expanded` divides by its
`coverage_factor`, `rectangular` by `sqrt(3)`, `triangular` by `sqrt(6)`, `arcsine` by
`sqrt(2)`, `normal` by 1 — with an optional `sensitivity`, `dof`, and `relative: true`.
The tool computes `u_c`, the Welch-Satterthwaite effective degrees of freedom, `k` from
the t-distribution, and `U`, and warns when a Type A component has no degrees of
freedom, when `nu_eff` is small enough that `k = 2` is wrong, when one component
dominates, and when a Type B component declared `normal` is probably an undivided
expanded uncertainty.

### format_result.py

```bash
python skills/uncertainty-and-units/scripts/format_result.py \
  --value 12.34567 --uncertainty 0.02345 --unit mm \
  --coverage-factor 2.26 --coverage-probability 0.95
```

Returns `12.346 ± 0.023 mm`, `12.346(23) mm`, the scientific and LaTeX forms, and the
sentence that has to accompany the number. Warns when one significant digit is requested
for an uncertainty beginning in 1 or 2, and when the uncertainty exceeds the estimate.

### convert_units.py

```bash
python skills/uncertainty-and-units/scripts/convert_units.py \
  --value 532 --unit nm --to eV --context spectroscopy --uncertainty 0.5

python skills/uncertainty-and-units/scripts/convert_units.py \
  --value 1.0 --unit g --to mol --context chemistry --context-parameter "mw=180.156 g/mol"
```

Carries the uncertainty through the conversion's local derivative, which matters because
context conversions are reciprocal rather than proportional. Names the context in the
error message when a conversion needs one, and flags offset and logarithmic units.
`--list-contexts` shows what the registry defines.

### audit_units.py

Static review of existing analysis code. Parses, never imports or runs.

```bash
python skills/uncertainty-and-units/scripts/audit_units.py \
  --input analysis.py --format markdown --fail-on medium
```

| Rule | Severity | Detects |
| --- | --- | --- |
| `UNIT001` | medium | a second `UnitRegistry` in one module — cross-registry `ValueError` |
| `UNIT002` | medium | offset temperature units with no `delta_` unit anywhere |
| `UNIT003` | high | `.magnitude` without a preceding `.to(...)` or `.m_as(...)` |
| `UNIT004` | medium | logarithmic units, whose `+` multiplies |
| `UNC001` | high | `curve_fit` without `absolute_sigma` |
| `UNC002` | medium | `np.std` / `np.var` without `ddof` |
| `UNC003` | medium | `math` or `numpy` functions in a module that uses `uncertainties` |
| `UNC004` | high | a `ufloat` rebuilt from `.nominal_value` and `.std_dev` |
| `CONST001` | low | a literal within 0.1% of a CODATA constant |

Exit status is 1 when a finding meets `--fail-on` (default `high`), which makes it usable
as a pre-commit or CI check.

The rules are heuristics, so a false positive is suppressed with a directive comment —
trailing to cover its own line, or alone on a line to cover the next one:

```python
value = quantity.magnitude  # audit-units: ignore UNIT003 -- already converted upstream

# audit-units: ignore UNC003 -- the argument here is a plain float array
scaled = np.log10(counts)
```

`# audit-units: ignore-file CONST001` covers a whole module, and naming no rule
suppresses all of them. Suppressions are counted in the report rather than hidden, so a
file that silences everything still says so.

### check_plausibility.py

Dimensional consistency is not physical possibility. A cell 2 m across and a Reynolds
number of 4e7 in a capillary both pass every unit check. This tool tests a set of
quantities against dimensionless groups, characteristic scales, and curated magnitude
bands, and verifies each formula's dimensionality before reporting a number.

```bash
python skills/uncertainty-and-units/scripts/check_plausibility.py \
  --quantity "density=1060 kg/m**3" --quantity "velocity=0.5 mm/s" \
  --quantity "length=8 um" --quantity "viscosity=3.5 mPa*s" \
  --group reynolds --format markdown
# Re = 0.001211 -- laminar (circular pipe, length = diameter)

python skills/uncertainty-and-units/scripts/check_plausibility.py \
  --quantity "diameter=2 m" --band "eukaryotic_cell_diameter=diameter"
# implausible: 4.3 decades outside the 5-100 um range
```

`--group` evaluates one of 14 dimensionless groups and names the regime it places the
system in; `--scale` computes a characteristic scale such as a diffusion time, Debye
length, or Stokes settling velocity; `--band` compares a supplied quantity against an
observed range. `--list` prints the whole catalogue with the inputs each formula needs.

Physical constants (`k_B`, `N_A`, `R_gas`, `g_earth`, and the rest) are available to every
formula without being supplied, and are read from `scipy.constants` at run time rather
than written as literals, so they track the CODATA release SciPy ships.

The dimensionality check is the point. Passing a kinematic viscosity where the formula
needs a dynamic one — both called "viscosity", both tabulated for water, differing by a
factor of ρ — is refused before any number is computed:

```
error: viscosity must have dimensionality [mass] / ([length] * [time]),
       but m²/s is [length] ** 2 / [time]
```

Exit status is 1 when the verdict meets `--fail-on` (default `implausible`; a value
within one decade of a band is `questionable`). The thresholds are conventions with soft
edges and assume the geometry their correlation was fitted for — see
`references/plausibility-scales.md` for the characteristic length to use in each case.

## Choosing a propagation method

| Situation | Method |
| --- | --- |
| Linear or near-linear model, normal-ish inputs, large dof | GUM framework alone |
| Any nonlinearity across ±2u of an input | run both, apply the clause 8 test |
| Relative uncertainty above ~20% on any input | Monte Carlo |
| Dominant rectangular or otherwise non-normal component | Monte Carlo |
| Output bounded below (variance, concentration, squared quantity) | Monte Carlo |
| Asymmetric output distribution | Monte Carlo, shortest coverage interval |
| Correlated inputs | either, but supply the covariance matrix, not the standard uncertainties alone |

A model dominated by rectangular contributions fails the clause 8 test even when it is
perfectly linear: the framework's `k = 1.96` over-covers a nearly trapezoidal output.
The estimate and `u_c` are still right; only the interval is too wide.

## Constants

Never type a constant from memory. The 2019 SI redefinition fixed `c`, `h`, `e`, `k`,
and `N_A` exactly, so their relative standard uncertainty is zero; everything else is a
measured value that moves between CODATA releases.

```python
import scipy.constants as constants

constants.value("electron mass")        # 9.1093837139e-31
constants.unit("electron mass")         # kg
constants.precision("electron mass")    # 3.07e-10, relative standard uncertainty
constants.precision("Planck constant")  # 0.0, exact by definition
```

`precision` returns a *relative* standard uncertainty; multiply by the value for the
absolute one.

## Reference files

- `references/gum-methodology.md` — Type A and Type B evaluation, distribution divisors,
  the law of propagation, Welch-Satterthwaite, when the framework fails, the Monte Carlo
  procedure, and the clause 8 validation test.
- `references/pint-recipes.md` — registries, offset and logarithmic units, contexts,
  boundary enforcement with `wraps` and `check`, NumPy interoperability, custom units,
  formatting.
- `references/uncertainties-recipes.md` — variable identity and correlation,
  `correlated_values`, `umath` and `unumpy`, format specs, fit covariance matrices, and
  the package's limits.
- `references/domain-conversions.md` — the energy ladder, spectroscopy, concentration,
  pressure, radiation and magnetism, mass spectrometry, logarithmic quantities, and the
  pairs that share dimensions without sharing meaning.
- `references/reporting-rules.md` — rounding, notations, the sentence that must
  accompany a result, SD versus SEM versus CI in figures, non-detects, and conformity
  decision rules.
- `references/plausibility-scales.md` — choosing the characteristic length, the
  dimensionless groups and the modelling assumption each one gates, characteristic
  scales, the observed magnitude bands and their sources, and the caveats on every
  threshold.

## Dated sources

Checked 2026-07-26:

- [JCGM 100:2008, Evaluation of measurement data — Guide to the expression of
  uncertainty in measurement](https://www.bipm.org/documents/20126/2071204/JCGM_100_2008_E.pdf)
- [JCGM 101:2008, Supplement 1 — Propagation of distributions using a Monte Carlo
  method](https://www.bipm.org/documents/20126/2071204/JCGM_101_2008_E.pdf)
- [NIST Technical Note 1297](https://nvlpubs.nist.gov/nistpubs/Legacy/TN/nbstechnicalnote1297.pdf)
- [CODATA internationally recommended values](https://physics.nist.gov/cuu/Constants/)
- [Pint on PyPI](https://pypi.org/project/Pint/) — 0.25.3, released 2026-03-19.
- [Pint documentation](https://pint.readthedocs.io/en/stable/), including
  [non-multiplicative units](https://pint.readthedocs.io/en/stable/user/nonmult.html)
  and [contexts](https://pint.readthedocs.io/en/stable/user/contexts.html).
- [uncertainties on PyPI](https://pypi.org/project/uncertainties/) — 3.2.3, released
  2025-04-21.
- [uncertainties documentation](https://uncertainties.readthedocs.io/en/latest/)
- [scipy.constants reference](https://docs.scipy.org/doc/scipy/reference/constants.html)