Skip to content

feat: measurement uncertainty for quantities and measured constants - #819

Merged
mpusz merged 3 commits into
masterfrom
feat/measurement-uncertainty
Aug 3, 2026
Merged

feat: measurement uncertainty for quantities and measured constants#819
mpusz merged 3 commits into
masterfrom
feat/measurement-uncertainty

Conversation

@mpusz

@mpusz mpusz commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Resolves #464.

Adds uncertain<T>, a representation type carrying a value and its standard uncertainty, and teaches the library which physical constants are measured rather than exact by definition.

The model

Uncertainty is relational: in its own unit a measured constant is exactly 1, and it is the conversion factor between units that carries the uncertainty. So the conversion engine derives a factor's relative standard uncertainty from the measured constants in both units' definitions. A constant appearing on both sides cancels symbolically, which no value-level scheme can get right:

const quantity two_suns = uncertain<double>{2.0} * iau::unit_symbols::M_SUN;

std::cout << two_suns << "\n";                            // 2 ± 0 M_☉
std::cout << two_suns.in(iau::unit_symbols::M_EARTH) << "\n";  // 665892 ± 0 M_⊕   (G cancels)
std::cout << two_suns.in(kg) << "\n";                     // 3.97682e+30 ± 8.749e+25 kg

Representations opt in through the new fold_conversion_uncertainty customization point. Every other representation converts exactly and the whole mechanism, including the derivation, compiles away.

Constants

A measured constant declares its published uncertainty in its definition, as an exact magnitude, so no representation type is imposed:

named_constant<"G", mag_ratio<667'430, 100'000> * mag_power<10, -11> * ...,
               relative_standard_uncertainty{mag_ratio<22, 10> * mag_power<10, -5>}>

This makes exact-vs-measured visible to the type system via MeasuredConstant, which gates measurement_of and get_relative_standard_uncertainty. Asking for the measurement of a constant that is exact by definition does not compile: "exact by definition" and "measured infinitely precisely" are different statements.

Values are transcribed from the published CODATA tables, not recomputed from the rounded value/uncertainty pairs, which disagree in the last digit (2022 α is 1.6, not 1.5 × 10⁻¹⁰). Annotated: iau::newtonian_constant_of_gravitation and every measured constant across the three hep CODATA namespaces.

Two metrological corrections fall out:

  • k_B is annotated for CODATA 2014 only, being exact since the 2019 SI redefinition.
  • hep vacuum magnetic permeability moves out of the "exact across all releases" section into the per-release namespaces, for the opposite reason: exactly 4π×10⁻⁷ before 2019, measured since. The old definition was 3.6σ from the CODATA 2018 value.

Example

measurement is replaced by weighing_the_earth, a scenario rather than a feature tour: determine the mass of the Earth from a pendulum, a radius, and G, then print an uncertainty budget showing that G is some sixty times below the stopwatch noise, and that the modelling bias of a uniform sphere is larger than the random uncertainty.

Testing

Compile-time assertions pin the derivation rules (symbolic cancellation, quadrature, fractional powers, annotated constants not recursed). Runtime tests cover propagation, all three conversion spellings (.in(U), .in<Rep>(U), value_cast<U, Rep>), and the per-release CODATA values.

Verified locally on gcc-12, gcc-15, clang-16, and clang-21, plus header-set verification and the full suite (130,142 assertions). Opened as a PR so CI can confirm the rest of the matrix.

🤖 Generated with Claude Code

mpusz and others added 3 commits August 3, 2026 09:17
A failure said which workflows grew and by how much, leaving the useful question - what got more
expensive - to be answered by hand afterwards. The suite's baselines record the sha they were
measured from, so the job can now check out that exact tree and attribute the offending workflows
against it, naming the templates whose instantiation counts moved.

The gate step becomes non-fatal so the explanation runs before the job ends, and a final step fails
it as before. Capped at three workflows, and it says so when there is no recorded sha to compare
against rather than guessing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds `uncertain<T>`, a representation type carrying a value and its standard
uncertainty, and teaches the library which physical constants are measured
rather than exact by definition.

Uncertainty is modelled as a relational property: in its own unit a measured
constant is exactly `1`, and it is the conversion factor between units that
carries the uncertainty. The conversion engine therefore derives a factor's
relative standard uncertainty from the measured constants in both units'
definitions, so a constant appearing on both sides cancels symbolically (a
solar mass expressed in Earth masses stays exact) and only the surviving
contributions combine in quadrature. Representations opt in through the new
`fold_conversion_uncertainty` customization point; every other representation
converts exactly and the machinery compiles away.

Constants declare their published uncertainty in their definition:

  named_constant<"G", ..., relative_standard_uncertainty{...}>

which makes exact-vs-measured visible to the type system through the
`MeasuredConstant` concept and gates `measurement_of` and
`get_relative_standard_uncertainty`. Asking for the measurement of a constant
that is exact by definition does not compile.

Values are transcribed from the published CODATA tables rather than recomputed
from the rounded value/uncertainty pairs, which disagree in the last digit.
Annotated: `iau::newtonian_constant_of_gravitation` and every measured constant
in the three `hep` CODATA namespaces. `k_B` is annotated for CODATA 2014 only,
being exact since the 2019 SI redefinition. `hep` vacuum magnetic permeability
moves out of the exact section into the per-release namespaces for the opposite
reason: exactly 4π×10⁻⁷ before 2019, measured since.

The `measurement` example is replaced by `weighing_the_earth`, which determines
the mass of the Earth from a pendulum, a radius, and `G`, and prints an
uncertainty budget showing that `G` is negligible next to the stopwatch.

Resolves #464

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The static tests asserted exact floating-point equality between a published
`u_r` literal and the value materialized from the constant's exact magnitude.
Those need not agree bit for bit across platforms, so Apple Clang, Clang-18 on
arm64, MSVC, and GCC-12 Debug failed while local Release builds passed.

Uses the existing `approx_equal` test helper on dimensionless quantities
instead, which also replaces two ad-hoc tolerance helpers. The derivation
checks pass an explicit tolerance, being computed through the consteval root
rather than read straight from a magnitude.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mpusz
mpusz merged commit 27a70ff into master Aug 3, 2026
177 checks passed
@mpusz
mpusz deleted the feat/measurement-uncertainty branch August 3, 2026 19:43
@mpusz mpusz mentioned this pull request Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Measurement in IAU

1 participant