Skip to content

Commit a4ff2a4

Browse files
authored
Merge pull request #821 from mpusz/codata-complete-coverage
Complete CODATA coverage with published uncertainties
2 parents 6111211 + 6ba04f8 commit a4ff2a4

62 files changed

Lines changed: 8365 additions & 725 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci-clang-tidy.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ concurrency:
6666
jobs:
6767
generate-matrix:
6868
name: "Generate build matrix for ${{ github.workflow }}"
69+
# A PR whose head branch lives in this repository is already covered by the `push` event above,
70+
# whose path filters match only the commits just pushed - unlike `pull_request`, which matches
71+
# them against the whole base...head diff and so re-runs everything for a docs-only push. Fork
72+
# PRs never fire `push` here, so they are what this event exists to serve.
73+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
6974
outputs:
7075
matrix: ${{ steps.set-matrix.outputs.matrix }}
7176
seed: ${{ steps.set-matrix.outputs.seed }}

‎.github/workflows/ci-compile-time.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ env:
8282

8383
jobs:
8484
instantiations:
85+
# A PR whose head branch lives in this repository is already covered by the `push` event above,
86+
# whose path filters match only the commits just pushed - unlike `pull_request`, which matches
87+
# them against the whole base...head diff and so re-runs everything for a docs-only push. Fork
88+
# PRs never fire `push` here, so they are what this event exists to serve.
89+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
8590
# One gate per supported language standard: counts differ per -std, and a regression can appear
8691
# in one standard alone. Each has its own reviewed baselines in the benchmarks repository.
8792
strategy:

‎.github/workflows/ci-conan.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ concurrency:
6868
jobs:
6969
generate-matrix:
7070
name: "Generate build matrix for ${{ github.workflow }}"
71+
# A PR whose head branch lives in this repository is already covered by the `push` event above,
72+
# whose path filters match only the commits just pushed - unlike `pull_request`, which matches
73+
# them against the whole base...head diff and so re-runs everything for a docs-only push. Fork
74+
# PRs never fire `push` here, so they are what this event exists to serve.
75+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
7176
outputs:
7277
matrix: ${{ steps.set-matrix.outputs.matrix }}
7378
seed: ${{ steps.set-matrix.outputs.seed }}

‎.github/workflows/ci-formatting.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ concurrency:
3333

3434
jobs:
3535
check:
36+
# A PR whose head branch lives in this repository is already covered by the `push` event above.
37+
# Fork PRs never fire `push` here, so they are what this event exists to serve.
38+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
3639
runs-on: ubuntu-24.04
3740
steps:
3841
- uses: actions/checkout@v5

‎.github/workflows/ci-freestanding.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ concurrency:
6464
jobs:
6565
generate-matrix:
6666
name: "Generate build matrix for ${{ github.workflow }}"
67+
# A PR whose head branch lives in this repository is already covered by the `push` event above,
68+
# whose path filters match only the commits just pushed - unlike `pull_request`, which matches
69+
# them against the whole base...head diff and so re-runs everything for a docs-only push. Fork
70+
# PRs never fire `push` here, so they are what this event exists to serve.
71+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
6772
outputs:
6873
matrix: ${{ steps.set-matrix.outputs.matrix }}
6974
seed: ${{ steps.set-matrix.outputs.seed }}

‎.github/workflows/ci-test-package-cmake.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ concurrency:
6262
jobs:
6363
generate-matrix:
6464
name: "Generate build matrix for ${{ github.workflow }}"
65+
# A PR whose head branch lives in this repository is already covered by the `push` event above,
66+
# whose path filters match only the commits just pushed - unlike `pull_request`, which matches
67+
# them against the whole base...head diff and so re-runs everything for a docs-only push. Fork
68+
# PRs never fire `push` here, so they are what this event exists to serve.
69+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
6570
outputs:
6671
matrix: ${{ steps.set-matrix.outputs.matrix }}
6772
seed: ${{ steps.set-matrix.outputs.seed }}

‎.github/workflows/citation.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ concurrency:
1919

2020
jobs:
2121
validate-citation-cff:
22+
# A PR whose head branch lives in this repository is already covered by the `push` event above.
23+
# Fork PRs never fire `push` here, so they are what this event exists to serve.
24+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
2225
runs-on: ubuntu-latest
2326
timeout-minutes: 10
2427
name: Validate CITATION.cff

‎.pre-commit-config.yaml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ repos:
1010
hooks:
1111
- id: trailing-whitespace
1212
args: [--markdown-linebreak-ext=md]
13+
# the NIST CODATA tables are checked in byte-verbatim; nothing may rewrite them
14+
exclude: ^scripts/codata/
1315
- id: end-of-file-fixer
16+
exclude: ^scripts/codata/
1417
- id: check-yaml
1518
args: [--unsafe] # mkdocs.yml uses !ENV and !!python/name: tags
1619
- id: check-json

‎CHANGELOG.md‎

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,51 @@ This page documents the version history and changes for the **mp-units** library
1414
- fix: `uncertain<T>::relative_uncertainty()` now divides by the absolute value. GUM and
1515
the VIM define the relative standard uncertainty as `u(x)/|x|`, so it is never
1616
negative; it used to come out negative for a negative central value
17+
- feat: compile-time factorization now handles composites of large primes: `mul_mod`
18+
uses a double-wide (128-bit) product where the compiler provides one, and
19+
`find_first_factor` finds factors with a batched-Brent Pollard's rho instead of
20+
unbounded trial division (which needed ~266k `constexpr` iterations for real CODATA
21+
mantissas, over GCC's default loop limit). Mirrors the approach validated in Au
22+
(aurora-opensource/au#686); trial division remains as the last-resort fallback
23+
- perf: the whole `unit_magnitude` interface (operators and all queries) moved to hidden
24+
friends of a new non-template `unit_magnitude_interface` base, the same shape as
25+
`unit_interface` and `quantity_spec_interface`. A hidden friend of a class template
26+
is redeclared by every specialization, and magnitude-heavy code instantiates
27+
thousands of specializations per translation unit, so those declarations dominated
28+
compile times; in a non-template base they are declared once and cost nothing while
29+
keeping all hidden-friend benefits. A TU including `si` units and constants dropped
30+
from ~2.6 s to ~1.6 s, and the complete `codata2022.h` from ~40 s to ~4.1 s
31+
(GCC 15; Clang 21 shows 44 s -> 6.8 s)
1732
- (!) feat: `pi` and `π` is now a unit constant
1833
- (!) feat: `iau::newtonian_constant_of_gravitation` is now imported from
1934
`codata::newtonian_constant_of_gravitation` instead of being defined by the IAU system,
2035
and the `iau::codata2018` inline namespace is gone. IAU 2015 Resolution B3 asks for
2136
the current best estimate of G, so the value now follows whichever adjustment is
2237
current. The value is unchanged today, since CODATA 2022 kept G at 6.674 30(15)e-11
23-
- feat: CODATA fundamental physical constants added as a separate system in
24-
`mp-units/systems/codata.h` (`codata::codata2014`, `codata::codata2018`, and the
25-
`inline` `codata::codata2022`). Deliberately not part of `si`, so that
26-
`mp-units/systems/si.h` does not carry constants most code never names
38+
- feat: CODATA fundamental physical constants added as a separate system with one
39+
namespace and one header per adjustment (`codata::codata2014`, `codata::codata2018`,
40+
and the `inline` `codata::codata2022`), GENERATED from the NIST "allascii" tables by
41+
`scripts/codata_constants.py` for complete coverage (~230 constants per adjustment):
42+
measured constants transcribe the published value and absolute standard uncertainty
43+
digit for digit, exact non-terminating values are defined by their exact symbolic
44+
relations, and an independent verification step recomputes every emitted value
45+
against its source row. Each adjustment ships in two tiers:
46+
`mp-units/systems/codata/codataYYYY_essential.h` (the NIST "frequently used"
47+
selection, cheap to compile) and `mp-units/systems/codata/codataYYYY.h` (the complete
48+
table, including the essential tier), with `mp-units/systems/codata.h` as the
49+
umbrella. Deliberately not part of `si`, so that `mp-units/systems/si.h` does not
50+
carry constants most code never names (#820)
51+
- deprecated: `si::standard_gravity`. It is not an SI defining constant but a conventional
52+
value adopted by CGPM 3 (1901), which is what NIST's "adopted values" CODATA category
53+
holds; its home is now `codata::standard_gravity` from
54+
`<mp-units/systems/codata/adopted_values.h>`, a single adjustment-invariant entity
55+
shared by all CODATA namespaces (as are the standard atmosphere, the standard-state
56+
pressure, and the conventional Josephson and von Klitzing values). `yard_pound`'s
57+
`pound_force` is now defined through the codata entity
58+
- deprecated: `si::reduced_planck_constant`. ℏ is not an SI defining constant either; its
59+
home is `codata::reduced_planck_constant`, defined as the exact `h / 2π` relation in
60+
the post-2019 adjustments and as the measured "Planck constant over 2 pi" in
61+
`codata2014`
2762
- deprecated: `si::magnetic_constant`. The pre-2019 SI defined the ampere through it,
2863
which made it exactly 4*pi*1e-7 H/m; since the redefinition it is measured. Its
2964
value is unchanged, but use `codata::magnetic_constant` for the current adjustment,
@@ -54,11 +89,15 @@ This page documents the version history and changes for the **mp-units** library
5489
- (!) feat: IAU system definition improved
5590
- feat: `uncertain<T>` representation type added to `mp-units::utility` (first-order
5691
uncertainty propagation for independent values) (#464)
57-
- feat: measured constants may now declare their `relative_standard_uncertainty` (exact
58-
magnitude metadata on `named_constant`); `MeasuredConstant` concept,
59-
`get_relative_standard_uncertainty`, and `measurement_of` added; declared for
60-
`iau::newtonian_constant_of_gravitation` and all measured constants in the three
61-
`hep` CODATA namespaces (#464)
92+
- feat: measured constants may now declare their standard uncertainty in the form their
93+
source publishes it (exact metadata on `named_constant`): `standard_uncertainty`
94+
(absolute, in the constant's own dimension, transcribing the CODATA table row
95+
verbatim) or `relative_standard_uncertainty` (unit-invariant, for constants whose
96+
defining unit is not tabulated); `MeasuredConstant` concept,
97+
`get_standard_uncertainty`, `get_relative_standard_uncertainty`, and
98+
`measurement_of` added, with each accessor deriving the undeclared form on demand;
99+
declared for `iau::newtonian_constant_of_gravitation` and all measured constants in
100+
the three `hep` CODATA namespaces (#464, #820)
62101
- feat: unit conversion factors built from measured constants carry their relative standard
63102
uncertainty; the conversion engine folds it into representation types that opt in
64103
via `fold_conversion_uncertainty` (e.g., `uncertain<T>`), with measured constants

‎docs/blog/posts/measurement-uncertainty-and-measured-constants.md‎

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
---
2-
date: 2026-08-05
2+
date: 2026-08-06
33
authors:
44
- mpusz
55
categories:
66
- Metrology
77
comments: true
8-
draft: true
98
---
109

1110
# Measurement uncertainty and measured constants
@@ -20,8 +19,8 @@ happily prints ten significant digits of a solar mass computed from a constant t
2019
guarantees four.
2120

2221
**mp-units** now models this honestly. This post introduces the `uncertain<T>` representation
23-
type, the `relative_standard_uncertainty` metadata for measured constants, and the
24-
`measurement_of` helper that connects the two.
22+
type, the `standard_uncertainty` and `relative_standard_uncertainty` metadata for measured
23+
constants, and the `measurement_of` helper that connects them.
2524

2625
<!-- more -->
2726

@@ -116,20 +115,42 @@ adjective-wrapper naming pattern as `std::optional<T>` and `std::expected<T>`.
116115
117116
## Measured constants carry their uncertainty
118117
119-
A measured constant now declares its relative standard uncertainty right in its definition,
120-
as an exact symbolic magnitude, using the exact term the CODATA table uses:
118+
A measured constant now declares its standard uncertainty right in its definition, as an
119+
exact symbolic expression, using the exact term the CODATA table uses. This is how NIST
120+
publishes `G` in the machine-readable
121+
[CODATA 2018 table](https://physics.nist.gov/cuu/Constants/ArchiveASCII/allascii_2018.txt),
122+
with a quantity name, a value, a standard uncertainty, and a unit:
123+
124+
```text
125+
Quantity Value Uncertainty Unit
126+
-----------------------------------------------------------------------------
127+
Newtonian constant of gravitation 6.674 30 e-11 0.000 15 e-11 m^3 kg^-1 s^-2
128+
```
129+
130+
and this is how that row splits into code:
121131

122132
```cpp
123133
inline constexpr struct newtonian_constant_of_gravitation final :
124134
named_constant<"G", mag_ratio<667'430, 100'000> * mag_power<10, -11> * cubic(si::metre) / si::kilogram / square(si::second),
125-
relative_standard_uncertainty{mag_ratio<22, 10> * mag_power<10, -5>}> {}
135+
standard_uncertainty{mag_ratio<15, 10> * mag_power<10, -15> * cubic(si::metre) / si::kilogram / square(si::second)}> {}
126136
newtonian_constant_of_gravitation;
127137
```
128138
129-
The definition reads like the CODATA entry it transcribes: the value `6.674 30 × 10⁻¹¹`, and
130-
the relative standard uncertainty `2.2 × 10⁻⁵`. No representation type appears anywhere. The
131-
metadata is an annotation only. It never participates in unit equality, conversion factors,
132-
or symbolic simplification, so nothing about the existing constant behavior changes.
139+
Every column of the row lands in the definition: the value `6.674 30 × 10⁻¹¹` and the
140+
standard uncertainty `0.000 15 × 10⁻¹¹` digit for digit, both in the constant's own unit
141+
`m³ kg⁻¹ s⁻²`. Transcribing the pair verbatim matters more than it may look. NIST
142+
publishes the value and the uncertainty mutually rounded, each to two significant digits
143+
of the uncertainty, so no derived form reproduces them exactly. Storing the relative
144+
uncertainty instead (an earlier iteration of this design) reconstructed a σ that was off by
145+
6.1% for the fine-structure constant. σ is the number `uncertain<T>` carries, prints, and
146+
propagates, so it is the wrong number to get wrong.
147+
148+
A second wrapper, `relative_standard_uncertainty`, remains for constants whose source
149+
publishes only the relative form. A measured constant declares exactly one of the two, and
150+
the accessors derive the missing form on demand as an exact ratio of canonical magnitudes.
151+
No representation type appears anywhere. The metadata is an annotation only. It never
152+
participates in unit equality, conversion factors, or symbolic simplification, so nothing
153+
about the existing constant behavior changes.
133154
134155
Exact constants simply do not have this parameter. This makes exact-vs-measured a
135156
distinction the type system can see, expressed by the new `MeasuredConstant` concept.
@@ -157,8 +178,8 @@ const quantity two_suns = 2.0 * iau::unit_symbols::M_SUN;
157178
158179
std::cout << two_suns << "\n"; // 2 M_☉
159180
std::cout << two_suns.in(kg) << "\n"; // 3.97682e+30 kg
160-
std::cout << two_suns.in<uncertain<double>>(kg) << "\n"; // 3.97682e+30 ± 8.749e+25 kg
161-
std::cout << value_cast<kg, uncertain<double>>(two_suns) << "\n"; // 3.97682e+30 ± 8.749e+25 kg
181+
std::cout << two_suns.in<uncertain<double>>(kg) << "\n"; // 3.97682e+30 ± 8.93761e+25 kg
182+
std::cout << value_cast<kg, uncertain<double>>(two_suns) << "\n"; // 3.97682e+30 ± 8.93761e+25 kg
162183
```
163184

164185
The first two lines are exact: `two_suns` in its own unit, and the sanctioned central
@@ -175,7 +196,7 @@ const quantity two_suns = uncertain<double>{2.0} * iau::unit_symbols::M_SUN;
175196

176197
std::cout << two_suns << "\n"; // 2 ± 0 M_☉
177198
std::cout << two_suns.in(iau::unit_symbols::M_EARTH) << "\n"; // 665892 ± 0 M_⊕
178-
std::cout << two_suns.in(kg) << "\n"; // 3.97682e+30 ± 8.749e+25 kg
199+
std::cout << two_suns.in(kg) << "\n"; // 3.97682e+30 ± 8.93761e+25 kg
179200
```
180201
181202
`two_suns` is exactly two suns even in an uncertainty-capable representation. Both masses
@@ -211,8 +232,8 @@ std::cout << "M_sun = " << solar_mass.in(kg) << "\n";
211232
```
212233

213234
```text
214-
G = 6.6743e-11 ± 1.46835e-15 m³ kg⁻¹ s⁻²
215-
M_sun = 1.98841e+30 ± 4.3745e+25 kg
235+
G = 6.6743e-11 ± 1.5e-15 m³ kg⁻¹ s⁻²
236+
M_sun = 1.98841e+30 ± 4.46881e+25 kg
216237
```
217238

218239
The solar mass example is the payoff. The IAU defines the nominal solar mass parameter
@@ -249,23 +270,29 @@ both units is fully correlated with itself, and it cancels symbolically before t
249270
is ever applied, which is exactly why the solar-to-Earth-mass conversion reports zero rather
250271
than a small non-zero number.
251272

252-
**Coverage.** `iau::G` and every measured constant in the three HEP CODATA namespaces carry
253-
the uncertainty published by their own release. Constants that are exact by definition carry
254-
nothing at all, which is what makes the distinction visible to the type system in the first
255-
place.
273+
**Coverage.** Every measured constant in the three HEP CODATA namespaces carries the
274+
uncertainty published by its own release. The new
275+
[`codata`](../../users_guide/systems/codata.md) system goes further: its headers are
276+
generated from the NIST tables and cover every constant of the 2014, 2018, and 2022
277+
adjustments (roughly 230 per adjustment), each transcribing the published value and
278+
standard uncertainty digit for digit, with an independent verification step recomputing
279+
every emitted value against its source row. `iau::G` now imports the CODATA constant
280+
rather than duplicating it. Constants that are exact by definition carry nothing at all,
281+
which is what makes the distinction visible to the type system in the first place.
256282

257283
## Printing what the standard prints
258284

259285
The default text output is the `value ± σ` form, the notation engineers read every day:
260286

261287
```cpp
262-
std::cout << G.in(m3 / kg / s2) << "\n"; // 6.6743e-11 ± 1.46835e-15 m³ kg⁻¹ s⁻²
288+
std::cout << G.in(m3 / kg / s2) << "\n"; // 6.6743e-11 ± 1.5e-15 m³ kg⁻¹ s⁻²
263289
```
264290

265291
ISO 80000-1:2022, 7.2.4 specifies a different one. The value is quoted to the last
266292
significant digit of the uncertainty, and the uncertainty follows in parentheses, counted
267-
in units of that digit. Appending `~` to the format spec selects it. The tilde marks the
268-
output as an approximation, because unlike the default form this one rounds the value:
293+
in units of that digit. Appending `~` to the format spec selects it. Where the default form
294+
prints what the object holds, this one quotes the value to the precision the uncertainty
295+
justifies, which is how measurement results are reported:
269296

270297
```cpp
271298
std::println("{::N[~]}", G.in(m3 / kg / s2)); // 6.67430(15)e-11 m³ kg⁻¹ s⁻²
@@ -303,6 +330,9 @@ documentation:
303330
it: the mass of the planet from a pendulum, a radius, and `G`
304331
- [Faster-than-lightspeed constants](../../users_guide/framework_basics/faster_than_lightspeed_constants.md)
305332
explains why constants are units in the first place
333+
- [The CODATA system](../../users_guide/systems/codata.md) documents the generated
334+
constants: one namespace and one header per adjustment, and the essential and complete
335+
tiers
306336
- [CODATA fundamental physical constants](https://physics.nist.gov/cuu/Constants/) are the
307337
source of the values and uncertainties transcribed into the systems
308338
- [JCGM 100 (GUM)](https://www.bipm.org/en/committees/jc/jcgm/publications) is the

0 commit comments

Comments
 (0)