Skip to content

Commit c30a637

Browse files
committed
refactor: 💥 zeroth-names deprecated; quantity_from_unit_zero(); natural_point_origin
1 parent 5000468 commit c30a637

File tree

18 files changed

+304
-238
lines changed

18 files changed

+304
-238
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ This page documents the version history and changes for the **mp-units** library
1616
- feat: `hep` system extended with new constants and specialized quantities
1717
- feat: `unit_for`, `reference_for`, and `rep_for` added
1818
- feat: type conversions improved to raise compile-time warnings on truncation
19+
- feat: `natural_point_origin<QuantitySpec>` added (replaces `zeroth_point_origin<QuantitySpec>`)
20+
- feat: `is_natural_point_origin<T>` added (replaces `is_zeroth_point_origin<T>`)
21+
- feat: `quantity_from_unit_zero()` member function added (replaces `quantity_from_zero()`)
22+
- feat: `fahrenheit_zero` point origin added (replaces `zeroth_degree_Fahrenheit`)
1923
- feat: dimensionless quantities with unit one can now be created with `quantity_spec::op(Val)`
2024
explicit conversions
2125
- feat: explicit construction from number enabled for `dimensionless` subkinds
@@ -29,6 +33,13 @@ This page documents the version history and changes for the **mp-units** library
2933
- feat: explicit `quantity_spec` conversions added for `quantity_point`
3034
- (!) refactor: `pi` magnitude constant renamed to `pi_c`
3135
- (!) refactor: `international` system renamed to `yard_pound`
36+
- (!) refactor: `zeroth_point_origin<QuantitySpec>` deprecated (use `natural_point_origin<QuantitySpec>`)
37+
- (!) refactor: `is_zeroth_point_origin<T>` deprecated (use `is_natural_point_origin<T>`)
38+
- (!) refactor: `quantity_from_zero()` deprecated (use `quantity_from_unit_zero()`)
39+
- (!) refactor: `zeroth_kelvin` deprecated (use `absolute_zero`)
40+
- (!) refactor: `zeroth_degree_Celsius` deprecated (use `ice_point`)
41+
- (!) refactor: `zeroth_degree_Fahrenheit` deprecated (use `fahrenheit_zero`)
42+
- (!) refactor: `zeroth_rankine` deprecated (use `si::absolute_zero`)
3243
- refactor: CGS <-> SI interop improved with using declarations
3344
- refactor: `isq::time` swapped with `isq::duration`
3445
- refactor: deprecation warnings now include the release version

docs/blog/posts/introducing-absolute-quantities.md

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ current model, motivating the need for a new abstraction.
8686

8787
1. **Limited arithmetic for points** – Points can’t be multiplied, divided, or accumulated.
8888
This often forces the users to convert the quantity point to a delta with either
89-
`qp.quantity_from_zero()` or `qp.quantity_from(some_origin)` member functions, which is
90-
at least cumbersome.
89+
`qp.quantity_from_unit_zero()` or `qp.quantity_from(some_origin)` member functions,
90+
which is at least cumbersome.
9191
2. **No text output for points** – A point's textual representation depends on its origin,
9292
which is often implicit or user-defined. As of today, we do not have the means to
9393
provide a text symbol for the point origin. Moreover, points may contain both an
@@ -139,18 +139,18 @@ current model, motivating the need for a new abstraction.
139139
quantity_point<kg> total)
140140
{
141141
gsl_Expects(is_gt_zero(total));
142-
return water_lost / total.quantity_from_zero();
142+
return water_lost / total.quantity_from_unit_zero();
143143
}
144144

145145
quantity_point<kg> initial[] = { point<kg>(2.34), point<kg>(1.93), point<kg>(2.43) };
146146
quantity_point<kg> dried[] = { point<kg>(1.89), point<kg>(1.52), point<kg>(1.92) };
147147

148-
auto point_plus = [](QuantityPoint auto a, QuantityPoint auto b){ return a + b.quantity_from_zero(); };
148+
auto point_plus = [](QuantityPoint auto a, QuantityPoint auto b){ return a + b.quantity_from_unit_zero(); };
149149
quantity_point total_initial = std::reduce(std::cbegin(initial), std::cend(initial), point<kg>(0.), point_plus);
150150
quantity_point total_dried = std::reduce(std::cbegin(dried), std::cend(dried), point<kg>(0.), point_plus);
151151

152-
std::cout << "Initial product mass: " << total_initial.quantity_from_zero() << "\n";
153-
std::cout << "Dried product mass: " << total_dried.quantity_from_zero() << "\n";
152+
std::cout << "Initial product mass: " << total_initial.quantity_from_unit_zero() << "\n";
153+
std::cout << "Dried product mass: " << total_dried.quantity_from_unit_zero() << "\n";
154154
std::cout << "Moisture content change: " << moisture_content_change(total_initial - total_dried, total_initial) << "\n";
155155
```
156156

@@ -891,43 +891,77 @@ with:
891891
```cpp
892892
quantity temp_cold = point<K>(300.);
893893
quantity temp_hot = point<K>(500.);
894-
quantity carnot_eff_1 = 1. - temp_cold.quantity_from_zero() / temp_hot.quantity_from_zero();
895-
quantity carnot_eff_2 = (temp_hot - temp_cold) / temp_hot.quantity_from_zero();
894+
quantity carnot_eff_1 = 1. - temp_cold.quantity_from_unit_zero() / temp_hot.quantity_from_unit_zero();
895+
quantity carnot_eff_2 = (temp_hot - temp_cold) / temp_hot.quantity_from_unit_zero();
896896
```
897897

898898
It worked, but was far from being physically pure and pretty.
899899

900900

901-
### Why Not Just Use `(T − T₀)` as a Workaround?
901+
### Why Obvious Workarounds Fall Short?
902902

903-
A common suggestion is to work around the absence of a distinct Absolute type by
904-
replacing every absolute temperature $T$ with the explicit expression $(T - T_0)$, where
905-
$T_0$ is a `quantity_point` at absolute zero. For example:
903+
Two workaround approaches exist, each with its own caveat.
904+
905+
#### Approach 1: `quantity_from_unit_zero()`
906+
907+
`quantity_from_unit_zero()` returns the displacement of a quantity point from its unit's
908+
origin. For Kelvin this is `si::absolute_zero`, so the result is exactly the
909+
thermodynamic temperature:
910+
911+
```cpp
912+
point<K>(294.15).quantity_from_unit_zero(); // 294.15 K ✓
913+
```
914+
915+
For Celsius, however, the unit's origin is `si::ice_point` — the _ice point_.
916+
The function therefore returns the displacement from the ice point, not from absolute
917+
zero:
906918

907919
```cpp
908-
quantity<point<K>> temp_cold = point<K>(300.);
909-
quantity<point<K>> temp_hot = point<K>(500.);
910-
// Force deltas from zero explicitly every time:
911-
quantity carnot_eff = 1. - temp_cold.quantity_from_zero() / temp_hot.quantity_from_zero();
920+
point<deg_C>(21).quantity_from_unit_zero(); // 21 ℃ — displacement from ice point, not 294.15 K!
912921
```
913922

914-
While this produces the correct numerical answer, it has several drawbacks:
915-
916-
1. **Manual burden** — the user must remember to apply the `quantity_from_zero()` call
917-
every time a thermodynamic formula requires ratio-scale semantics.
918-
2. **No call-site enforcement** — generic function interfaces cannot require an
919-
Absolute at the type level; a `quantity_point<deg_C>` can slip through silently.
920-
3. **Lost semantic intent** — the type `quantity_point<K>` says "a location on the
921-
temperature scale," not "a thermodynamic energy magnitude." The distinction between
922-
an interval-scale location and a ratio-scale magnitude disappears.
923-
4. **Verbose code** — the workaround turns clean physics equations into manual
924-
conversions, defeating the purpose of a high-level units library.
925-
926-
The V3 Absolute Quantity abstraction internalizes this conversion. `300 * K` is an
927-
Absolute Quantity by default and is directly usable in multiplicative expressions.
928-
When an offset-unit measurement must enter a ratio-scale equation, the explicit
929-
`.in(K).absolute()` chain makes the conversion visible and type-safe — exactly once,
930-
at the boundary, rather than scattered throughout the codebase.
923+
This is a silent pitfall: the call compiles and returns a plausible-looking value for any
924+
temperature unit, but is only thermodynamically meaningful when the point is already in
925+
Kelvin. Explicit conversion before the call fixes it:
926+
927+
```cpp
928+
point<deg_C>(21).in(K).quantity_from_unit_zero(); // 294.15 K ✓
929+
```
930+
931+
#### Approach 2: Subtract `si::absolute_zero`
932+
933+
Subtracting the absolute-zero origin always gives the correct thermodynamic temperature
934+
regardless of the unit the point was stored in:
935+
936+
```cpp
937+
quantity_point temp = point<deg_C>(21);
938+
quantity T = temp - si::absolute_zero; // always correct
939+
```
940+
941+
The only thing to be aware of is the resulting unit. Because `si::ice_point` (the origin
942+
of the Celsius scale) is defined internally in `milli<kelvin>`, the subtraction yields
943+
`mK` rather than `K`:
944+
945+
```text
946+
T == 294150 mK // correct value, can be rescaled with .in(K)
947+
```
948+
949+
The value is correct and rescales cleanly to any unit. In a physical equation like the
950+
ideal gas law, `p` will come out in `mPa` instead of `Pa`, but it will convert
951+
automatically on the first assignment to a typed quantity such as `quantity<Pa>`.
952+
953+
#### The bottom line
954+
955+
Both approaches work correctly when used with care. `quantity_from_unit_zero()` is concise but
956+
requires the point to already be in Kelvin; subtraction from `si::absolute_zero` is
957+
always safe but carries an `mK` unit until rescaled. In either case, the right idiom —
958+
`.in(K).quantity_from_unit_zero()` — must be remembered and applied at every call site, and
959+
there is no way to enforce it through the type system.
960+
961+
V3 Absolute Quantities address this: `300 * K` is already an Absolute Quantity, directly
962+
usable in any multiplicative expression. When an offset-unit point must enter a
963+
thermodynamic equation, the explicit `.in(K).absolute()` chain makes the conversion
964+
visible and type-safe — exactly once, at the boundary.
931965

932966

933967
### Design Philosophy and Standardization

docs/getting_started/quick_start.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,8 @@ quantities. This introduces an additional type-safety.
268268

269269
quantity_point temp = point<deg_C>(20.);
270270
std::println("Temperature: {} ({})",
271-
temp.quantity_from_zero(),
272-
temp.in(deg_F).quantity_from_zero());
271+
temp.quantity_from_unit_zero(),
272+
temp.in(deg_F).quantity_from_unit_zero());
273273
}
274274
```
275275

@@ -288,8 +288,8 @@ quantities. This introduces an additional type-safety.
288288

289289
quantity_point temp = point<deg_C>(20.);
290290
std::println("Temperature: {} ({})",
291-
temp.quantity_from_zero(),
292-
temp.in(deg_F).quantity_from_zero());
291+
temp.quantity_from_unit_zero(),
292+
temp.in(deg_F).quantity_from_unit_zero());
293293
}
294294
```
295295

docs/how_to_guides/integration/interoperability_with_other_libraries.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ int main()
296296
// ✅ Implicit conversion (import)
297297
quantity_point qp = ts;
298298

299-
std::cout << qp.quantity_from_zero() << "\n";
299+
std::cout << qp.quantity_from_unit_zero() << "\n";
300300

301301
// ✅ Explicit conversion (export)
302302
print(Timestamp(qp));
@@ -360,7 +360,7 @@ auto tp3 = to_chrono_time_point(qp3);
360360
quantity_point qp4 = my_origin + 1 * s;
361361
auto tp4 = to_chrono_time_point(qp4);
362362

363-
// ❌ Compile error: zeroth_point_origin not related to chrono_point_origin
363+
// ❌ Compile error: natural_point_origin not related to chrono_point_origin
364364
quantity_point qp5{1 * s};
365365
auto tp5 = to_chrono_time_point(qp5);
366366
```

docs/how_to_guides/integration/using_custom_representation_types.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ void example()
373373
quantity delta_scaled = 2 * delta; // {6, 6, 6} m
374374
375375
// Magnitude (returns scalar quantity)
376-
quantity distance = pos1.quantity_from_zero();
376+
quantity distance = pos1.quantity_from_unit_zero();
377377
quantity mag = magnitude(distance); // sqrt(1² + 2² + 3²) m
378378
379379
// Scalar product (dot product)

docs/reference/api_reference/src/quantities.tex

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -592,13 +592,13 @@
592592
template<@\libconcept{QuantityPoint}@ auto QP>
593593
struct relative_point_origin;
594594

595-
// \ref{qty.zeroth.pt.orig}, zeroth
595+
// \ref{qty.natural.pt.orig}, natural
596596

597597
template<@\libconcept{QuantitySpec}@ auto QS>
598-
struct zeroth_point_origin_;
598+
struct natural_point_origin_;
599599

600600
template<@\libconcept{QuantitySpec}@ auto QS>
601-
constexpr zeroth_point_origin_<QS> @\libglobal{zeroth_point_origin}@{};
601+
constexpr natural_point_origin_<QS> @\libglobal{natural_point_origin}@{};
602602

603603
// \ref{qty.def.pt.orig}, default
604604

@@ -5514,7 +5514,7 @@
55145514
A \defnadj{relative}{origin} is an origin
55155515
of a subspace\irefiev{102-03-03}.
55165516
A specialization of \tcode{relative_point_origin} is used as a base type when defining a relative origin \placeholder{O}.
5517-
\placeholder{O} is offset from \tcode{QP.absolute_point_origin} by \tcode{QP.quantity_from_zero()}.
5517+
\placeholder{O} is offset from \tcode{QP.absolute_point_origin} by \tcode{QP.quantity_from_unit_zero()}.
55185518
55195519
\pnum
55205520
The member \exposid{quantity-spec} is equal to
@@ -5525,19 +5525,19 @@
55255525
is satisfied, and
55265526
to \tcode{QP.\exposidnc{quantity-spec}} otherwise.
55275527
5528-
\rSec4[qty.zeroth.pt.orig]{Zeroth}
5528+
\rSec4[qty.natural.pt.orig]{Natural}
55295529
55305530
\begin{codeblock}
55315531
namespace mp_units {
55325532
55335533
template<@\libconcept{QuantitySpec}@ auto QS>
5534-
struct @\libglobal{zeroth_point_origin_}@ final : absolute_point_origin<QS> {};
5534+
struct @\libglobal{natural_point_origin_}@ final : absolute_point_origin<QS> {};
55355535
55365536
}
55375537
\end{codeblock}
55385538
55395539
\pnum
5540-
\tcode{zeroth_point_origin_<QS>} represents an origin
5540+
\tcode{natural_point_origin_<QS>} represents an origin
55415541
chosen by convention as the value $0$ of the quantity \tcode{QS}.
55425542
55435543
\rSec3[qty.pt.orig.ops]{Operations}
@@ -5648,18 +5648,18 @@
56485648
if constexpr (@\exposidnc{is-derived-from-specialization-of}@<PO1, absolute_point_origin>() &&
56495649
@\exposidnc{is-derived-from-specialization-of}@<PO2, absolute_point_origin>())
56505650
return std::is_same_v<PO1, PO2> ||
5651-
(@\exposidnc{is-specialization-of}@<PO1, zeroth_point_origin>() &&
5652-
@\exposidnc{is-specialization-of}@<PO2, zeroth_point_origin>() &&
5651+
(@\exposidnc{is-specialization-of}@<PO1, natural_point_origin>() &&
5652+
@\exposidnc{is-specialization-of}@<PO2, natural_point_origin>() &&
56535653
interconvertible(po1.@\exposidnc{quantity-spec}@, po2.@\exposidnc{quantity-spec}@));
56545654
else if constexpr (@\exposidnc{is-derived-from-specialization-of}@<PO1, relative_point_origin>() &&
56555655
@\exposidnc{is-derived-from-specialization-of}@<PO2, relative_point_origin>())
56565656
return PO1::@\exposidnc{quantity-point}@ == PO2::@\exposidnc{quantity-point}@;
56575657
else if constexpr (@\exposidnc{is-derived-from-specialization-of}@<PO1, relative_point_origin>())
56585658
return @\exposidnc{same-absolute-point-origins}@(po1, po2) &&
5659-
is_eq_zero(PO1::@\exposidnc{quantity-point}@.quantity_from_zero());
5659+
is_eq_zero(PO1::@\exposidnc{quantity-point}@.quantity_from_unit_zero());
56605660
else if constexpr (@\exposidnc{is-derived-from-specialization-of}@<PO2, relative_point_origin>())
56615661
return @\exposidnc{same-absolute-point-origins}@(po1, po2) &&
5662-
is_eq_zero(PO2::@\exposidnc{quantity-point}@.quantity_from_zero());
5662+
is_eq_zero(PO2::@\exposidnc{quantity-point}@.quantity_from_unit_zero());
56635663
\end{codeblock}
56645664
\end{itemdescr}
56655665
@@ -5709,7 +5709,7 @@
57095709
if constexpr (requires { get_unit(R{}).@\exposidnc{point-origin}@; })
57105710
return get_unit(R{}).@\exposidnc{point-origin}@;
57115711
else
5712-
return zeroth_point_origin<get_quantity_spec(R{})>;
5712+
return natural_point_origin<get_quantity_spec(R{})>;
57135713
\end{codeblock}
57145714
\end{itemdescr}
57155715
@@ -5847,7 +5847,7 @@
58475847
template<@\libconcept{QuantityPointOf}@<absolute_point_origin> QP>
58485848
constexpr @\libconcept{Quantity}@ auto quantity_from(const QP&) const;
58495849
5850-
constexpr @\libconcept{Quantity}@ auto quantity_from_zero() const;
5850+
constexpr @\libconcept{Quantity}@ auto quantity_from_unit_zero() const;
58515851
58525852
// \ref{qty.pt.conv.ops}, conversion operations
58535853
template<typename QP_, @\libconcept{QuantityPointLike}@ QP = std::remove_cvref_t<QP_>>
@@ -6156,9 +6156,9 @@
61566156
\tcode{return *this - rhs;}
61576157
\end{itemdescr}
61586158
6159-
\indexlibrarymember{quantity_from_zero}{quantity_point}
6159+
\indexlibrarymember{quantity_from_unit_zero}{quantity_point}
61606160
\begin{itemdecl}
6161-
constexpr @\libconcept{Quantity}@ auto quantity_from_zero() const;
6161+
constexpr @\libconcept{Quantity}@ auto quantity_from_unit_zero() const;
61626162
\end{itemdecl}
61636163
61646164
\begin{itemdescr}
@@ -6346,7 +6346,7 @@
63466346
\effects
63476347
Equivalent to:
63486348
\begin{codeblock}
6349-
if constexpr (@\exposidnc{is-specialization-of}@<PO, zeroth_point_origin>())
6349+
if constexpr (@\exposidnc{is-specialization-of}@<PO, natural_point_origin>())
63506350
return ::mp_units::quantity_point{qp.quantity_ref_from(PO) @\atsign@ q};
63516351
else
63526352
return ::mp_units::quantity_point{qp.quantity_ref_from(PO) @\atsign@ q, PO};

docs/tutorials/affine_space/point_origins.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ int main()
113113
114114
std::cout << "Both from UTC:\n";
115115
std::cout << " PST: " << pst_event.quantity_from(utc) << "\n";
116-
std::cout << " JST: " << jst_event.quantity_from_zero() << "\n\n";
116+
std::cout << " JST: " << jst_event.quantity_from_unit_zero() << "\n\n";
117117
118118
// Prove they're the same point: subtract them
119119
quantity time_diff = jst_event - pst_event;
@@ -147,7 +147,7 @@ All origins branching from the same absolute origin share the same affine space,
147147

148148
3. **Practice arithmetic**: Create two _altitude_ points from different relative origins
149149
(both based on the same absolute origin). Subtract them to prove they can interoperate,
150-
and use `.quantity_from_zero()` to show they reference the same absolute origin.
150+
and use `.quantity_from_unit_zero()` to show they reference the same absolute origin.
151151

152152
## What You Learned?
153153

docs/tutorials/affine_space/points_and_quantities.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ int main()
5151
// Calculate next week's meeting (same time, 7 days later)
5252
quantity_point next_meeting = meeting_start + 7 * non_si::day;
5353
std::cout << "Next week's meeting: "
54-
<< next_meeting.quantity_from_zero() << " from origin\n";
54+
<< next_meeting.quantity_from_unit_zero() << " from origin\n";
5555

5656
// These would NOT compile (uncomment to see):
5757
// auto wrong = meeting_start + meeting_end; // ❌ Can't add two points!

0 commit comments

Comments
 (0)