From d8f646a1e4dc4b34f71ae7e808a86cbff7d5ab13 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Wed, 23 Aug 2023 15:43:19 +0200 Subject: [PATCH] docs: "The Affine Space" chapter updated with the latest changes --- .../framework_basics/the_affine_space.md | 267 +++++++++--------- 1 file changed, 136 insertions(+), 131 deletions(-) diff --git a/docs/users_guide/framework_basics/the_affine_space.md b/docs/users_guide/framework_basics/the_affine_space.md index 0b1411c2f..64864fb7e 100644 --- a/docs/users_guide/framework_basics/the_affine_space.md +++ b/docs/users_guide/framework_basics/the_affine_space.md @@ -26,6 +26,7 @@ Here are the primary operations one can do in the affine space: - _vector_ / scalar -> _vector_ - _point_ - _point_ -> _vector_ - _point_ + _vector_ -> _point_ +- _vector_ + _point_ -> _point_ - _point_ - _vector_ -> _point_ !!! note @@ -50,95 +51,76 @@ As we already know, a `quantity` type provides all operations required for _vect the affine space. -## _Point_ is modeled by `quantity_point` +## _Point_ is modeled by `PointOrigin` and `quantity_point` -A _point_ is an absolute quantity with respect to an origin and is represented in the library with a -`quantity_point` class template: +In the **mp-units** library the _point_ abstraction is modelled by: -```cpp -template auto PO = absolute_point_origin{}, - RepresentationOf Rep = double> -class quantity_point; -``` - -As we can see above, the `quantity_point` class template exposes one additional parameter compared -to `quantity`. The `PO` parameter satisfies a [`PointOriginFor` concept](basic_concepts.md#pointoriginfor) -and specifies the origin of our scale. +- [`PointOrigin` concept](basic_concepts.md#PointOrigin) that specifies measurement origin, +- `quantity_point` class template that specifies a _point_ relative to a specific predefined origin. +### Absolute _point_ origin -### The origin +The **absolute point origin** specifies where the "zero" of our measurement's scale is. User can +specify such an origin by deriving from the `absolute_point_origin` class template: -The **origin** specifies where the "zero" of our measurement's scale is. +```cpp +constexpr struct mean_sea_level : absolute_point_origin {} mean_sea_level; +``` -Please notice that a _point_ can be represented with a _vector_ from the origin. This is why in -the **mp-units** library, a `quantity_point` gets a `quantity` in its constructor. Such a `quantity`: +### `quantity_point` -- specifies the relative distance of a specific point from the scale origin, -- is the only data member of the `quantity_point` class template, -- can be obtained with the `relative()` member function. +The `quantity_point` class template specifies an absolute quantity with respect to an origin: ```cpp -constexpr quantity_point everest_base_camp_alt{5364 * m}; -static_assert(everest_base_camp_alt.relative() == 5364 * m); +template auto PO, + RepresentationOf Rep = double> +class quantity_point; ``` -!!! note - - As the constructor is explicit, the quantity point object can only be created from a quantity via - direct initialization. This is why the code below that uses copy initialization does not compile: - - ```cpp - quantity_point everest_base_camp_alt = 5364 * m; // ERROR - ``` +As we can see above, the `quantity_point` class template exposes one additional parameter compared +to `quantity`. The `PO` parameter satisfies a [`PointOriginFor` concept](basic_concepts.md#PointOriginFor) +and specifies the origin of our measurement scale. -In the **mp-units** library, the origin is either provided implicitly (as above) or can be predefined -by the user and then provided explicitly as the `quantity_point` class template argument: +As a _point_ can be represented with a _vector_ from the origin, a `quantity_point` class +template can be created with the following operations: ```cpp -constexpr struct mean_sea_level : absolute_point_origin {} mean_sea_level; - -constexpr quantity_point everest_base_camp_alt{5364 * m}; -static_assert(everest_base_camp_alt.relative() == 5364 * m); +quantity_point qp1 = mean_sea_level + 42 * m; +quantity_point qp2 = 42 * m + mean_sea_level; +quantity_point qp3 = mean_sea_level - 42 * m; ``` !!! note - The `mean_sea_level` and the default `absolute_point_origin` origins are distinct from - each other, which means that _points_ defined with them are not compatible (can't be subtracted or - compared). - + [It is not allowed to subtract a _point_ from a _vector_](#operations-in-the-affine-space) + thus `42 * m - mean_sea_level` is an invalid operation. -### Class Template Argument Deduction (CTAD) +Similarly to [creation of a quantity](../../getting_started/quick_start.md#creating-a-quantity), +if someone does not like the operator-based syntax to create a `quantity_point`, the same results +can be achieved with `make_quantity_point` factory function: -Typing the entire `quantity_point` type may sometimes be quite verbose. Also, please note that we -"accidentally" used `double` as a representation type in the above examples, even though we operated -only on integral values. This was done for the convenience of saving typing. - -To improve the developer's experience, the `quantity_point` class template comes with the user-defined -class template argument deduction guides. Thanks to them, the above definitions can be rewritten as -follows: - -- implicit default origin - - ```cpp - constexpr quantity_point everest_base_camp_alt{isq::altitude(5364 * m)}; - ``` +```cpp +quantity_point qp4 = make_quantity_point(42 * m); +quantity_point qp5 = make_quantity_point(-42 * m); +``` -- explicit origin +The provided `quantity` representing an offset from the origin is stored inside the `quantity_point` +class template and can be obtained with a `quantity_from_origin()` member function: - ```cpp - constexpr quantity_point everest_base_camp_alt{isq::altitude(5364 * m), mean_sea_level}; - ``` +```cpp +constexpr quantity_point everest_base_camp_alt = mean_sea_level + isq::altitude(5364 * m); +static_assert(everest_base_camp_alt.quantity_from_origin() == 5364 * m); +``` -### Relative _point_ origins +### Relative _point_ origin We often do not have only one ultimate "zero" point when we measure things. Continuing the Mount Everest trip example above, measuring all daily hikes from the `mean_sea_level` might not be efficient. Maybe we know that we are not good climbers, so all our climbs can be -represented with an 8-bit integer type which will allow us to save memory in our database of climbs? +represented with an 8-bit integer type allowing us to save memory in our database of climbs? Why not use `everest_base_camp_alt` as our reference point? For this purpose, we can define a `relative_point_origin` in the following way: @@ -150,31 +132,18 @@ constexpr struct everest_base_camp : relative_point_origin first_climb_alt{42 * m}; -static_assert(first_climb_alt.relative() == 42 * m); +constexpr quantity_point first_climb_alt = everest_base_camp + isq::altitude(std::uint8_t{42} * m); +static_assert(first_climb_alt.quantity_from_origin() == 42 * m); ``` -As we can see above, the `relative()` member function returns a relative distance from the current -point origin. In case we would like to know the absolute altitude that we reached on this climb, -we can either: - -- add the two relative heights from both _points_ - - ```cpp - static_assert(first_climb_alt.relative() + everest_base_camp_alt.relative() == 5406 * m); - ``` - -- subtract the "zero altitude" _point_ from the current _point_ +As we can see above, the `quantity_from_origin()` member function returns a relative distance from +the current point origin. In case we would like to know the absolute altitude that we reached on this climb, +we can subtract the absolute point origin from the current _point_: - ```cpp - static_assert(first_climb_alt - quantity_point{0 * m, mean_sea_level} == 5406 * m); - ``` - -- call `absolute()` member function on the current _point_ - - ```cpp - static_assert(first_climb_alt.absolute() == 5406 * m); - ``` +```cpp +static_assert(first_climb_alt - mean_sea_level == 5406 * m); +static_assert(first_climb_alt - first_climb_alt.absolute_point_origin == 5406 * m); +``` ### Converting between different representations of the same _point_ @@ -188,22 +157,23 @@ For this purpose, we can either use: - a converting constructor: ```cpp - static_assert(quantity_point{first_climb_alt}.relative() == 5406 * m); + constexpr quantity_point qp = first_climb_alt; + static_assert(qp.quantity_from_origin() == 5406 * m); ``` - a dedicated conversion interface: ```cpp - constexpr QuantityPoint auto qp = first_climb_alt.point_from(mean_sea_level); - static_assert(qp.relative() == 5406 * m); + constexpr quantity_point qp = first_climb_alt.point_for(mean_sea_level); + static_assert(qp.quantity_from_origin() == 5406 * m); ``` !!! note - It is allowed to only covert between various origins defined in terms of the same + It is only allowed to convert between various origins defined in terms of the same `absolute_point_origin`. Even if it is possible to express the same _point_ as a _vector_ - from another `absolute_point_origin`, the **mp-units** library will not allow it, and - a custom user-defined conversion function will be needed to provide such a functionality. + from another `absolute_point_origin`, the library will not provide such a conversion. + A custom user-defined conversion function will be needed to add this functionality. Said otherwise, in the **mp-units** library, there is no way to spell how two distinct `absolute_point_origin` types relate to each other. @@ -213,19 +183,19 @@ For this purpose, we can either use: Let's assume we will attend the CppCon conference hosted in Aurora, CO, and we want to estimate the distance we will travel. We have to take a taxi to a local airport, fly to DEN airport with -a stopover in FRA, and in the end, get a cab to the Gaylord Rockies Resort & Convention Center: +a stopover in FRA, and, in the end, get a cab to the Gaylord Rockies Resort & Convention Center: ```cpp -constexpr struct home_location : absolute_point_origin {} home_location; +constexpr struct home : absolute_point_origin {} home; -quantity_point home{}; -quantity_point home_airport = home + 15 * km; -quantity_point fra_airport = home_airport + 829 * km; -quantity_point den_airport = fra_airport + 8115 * km; -quantity_point cppcon_venue = den_airport + 10.1 * mi; +quantity_point home_airport = home + 15 * km; +quantity_point fra_airport = home_airport + 829 * km; +quantity_point den_airport = fra_airport + 8115 * km; +quantity_point cppcon_venue = den_airport + 10.1 * mi; ``` -As we can see above, we can easily get a new point by adding a quantity to another quantity point. +As we can see above, we can easily get a new point by adding a quantity to an origin or another +quantity point. If we want to find out the distance traveled between two points, we simply subtract them: @@ -234,8 +204,8 @@ quantity total = cppcon_venue - home; quantity flight = den_airport - home_airport; ``` -If we would like to find out the total distance traveled by taxi as well, we have to do more -calculations: +If we would like to find out the total distance traveled by taxi as well, we have to do a bit +more calculations: ```cpp quantity taxi1 = home_airport - home; @@ -243,7 +213,7 @@ quantity taxi2 = cppcon_venue - den_airport; quantity taxi = taxi1 + taxi2; ``` -Now if we will print the results: +Now, if we print the results: ```cpp std::cout << "Total distance: " << total << "\n"; @@ -259,53 +229,85 @@ Flight distance: 8944 km Taxi distance: 31.2544 km ``` +!!! note + + It is not allowed to subtract two point origins defined in terms of `absolute_point_origin` + (i.e. `mean_sea_level - mean_sea_level`) as those do not contain information about the unit + so we are not able to determine a resulting `quantity` type. + ### Temperature support Another important example of [relative point origins](#relative-point-origins) is support of temperature quantity points in units different than kelvin [`K`]. -For example, the degree Celsius scale can be implemented as follows: +The [SI](../../appendix/glossary.md#si) system definition in the **mp-units** library provides +two predefined point origins: + +```cpp +namespace mp_units::si { + +inline constexpr struct absolute_zero : absolute_point_origin {} absolute_zero; +inline constexpr struct ice_point : relative_point_origin {} ice_point; + +} +``` + +With the above, we can be explicit what is the origin of our temperature point. For example, if +we want to implement the degree Celsius scale we can do it as follows: ```cpp -constexpr struct ice_point : relative_point_origin{273.15 * K}> {} ice_point; -using Celsius_point = quantity_point; +using Celsius_point = quantity_point; ``` !!! note Notice that while stacking point origins, we can use not only different representation types - but also different units for an origin and a _point_. + but also different units for an origin and a _point_. In the above example, the relative + point origin is defined in terms of `si::kelvin`, while the quantity point uses + `si::degree_Celsius`. -With the above, for example, if we want to implement a room temperature controller, we can type: +To play a bit w temperatures we can implement a simple room's AC temperature controller in +the following way: ```cpp -constexpr struct room_reference_temperature : relative_point_origin {} room_reference_temperature; -using room_temperature = quantity_point; +constexpr struct room_reference_temp : relative_point_origin {} room_reference_temp; +using room_temp = quantity_point; -constexpr auto step_delta = isq::thermodynamic_temperature(0.5 * deg_C); +constexpr auto step_delta = isq::Celsius_temperature(0.5 * deg_C); constexpr int number_of_steps = 6; -room_temperature room_default{}; -room_temperature room_low = room_default - number_of_steps * step_delta; -room_temperature room_high = room_default + number_of_steps * step_delta; +room_temp room_low = room_reference_temp - number_of_steps * step_delta; +room_temp room_high = room_reference_temp + number_of_steps * step_delta; + +std::println("| {:<14} | {:^18} | {:^18} | {:^18} |", "Temperature", "Room reference", "Ice point", "Absolute zero"); +std::println("|{0:=^16}|{0:=^20}|{0:=^20}|{0:=^20}|", ""); + +auto print = [&](std::string_view label, auto v){ + std::println("| {:<14} | {:^18} | {:^18} | {:^18} |", + label, v - room_reference_temp, v - si::ice_point, v - si::absolute_zero); +}; -std::cout << "Lowest temp: " << room_low.relative() << " (" << room_low - Celsius_point::zero() << ")\n"; -std::cout << "Highest temp: " << room_high.relative() << " (" << room_high - Celsius_point::zero() << ")\n"; +print("Lowest", room_low); +print("Default", room_reference_temp); +print("Highest", room_high); ``` The above prints: ```text -Lowest temp: -3 °C (18 °C) -Highest temp: 3 °C (24 °C) +| Temperature | Room reference | Ice point | Absolute zero | +|================|====================|====================|====================| +| Lowest | -3 °C | 18 °C | 291.15 °C | +| Default | 0 °C | 21 °C | 294.15 °C | +| Highest | 3 °C | 24 °C | 297.15 °C | ``` ### No text output for _points_ The library does not provide a text output for quantity points, as printing just a number and a unit -is not enough to adequately describe a quantity point. Often an additional postfix is required. +is not enough to adequately describe a quantity point. Often, an additional postfix is required. For example, the text output of `42 m` may mean many things and can also be confused with an output of a regular quantity. On the other hand, printing `42 m AMSL` for altitudes above mean sea level is @@ -316,23 +318,26 @@ a much better solution, but the library does not have enough information to prin The following operations are not allowed in the affine space: -- **add** two `quantity_point` objects (It is physically impossible to add positions of home - and Denver airports), -- **subtract** a `quantity_point` from a `quantity` (What would it mean to subtract DEN airport - location from the distance to it?), -- **multiply/divide** a `quantity_point` with a scalar (What is the position of `2x` DEN airport location?). -- **multiply/divide** a `quantity_point` with a quantity (What would multiplying the distance with the - DEN airport location mean?). -- **multiply/divide** two `quantity_point` objects (What would multiplying home and DEN airport location mean?). -- **mix** `quantity_points` of different quantity kinds (It is physically impossible to subtract time - from length), -- **mix** `quantity_points` of inconvertible quantities (What does it mean to subtract a distance - point to DEN airport from the Mount Everest base camp altitude?), -- **mix** `quantity_points` of convertible quantities but with unrelated origins (How to subtract - a point on our trip to CppCon measured relatively to our home location from a point measured - relative to the center of the Solar System?). +- **adding** two `quantity_point` objects + - It is physically impossible to add positions of home and Denver airports. +- **subtracting** a `quantity_point` from a `quantity` + - What would it mean to subtract DEN airport location from the distance to it? +- **multiplying/dividing** a `quantity_point` with a scalar + - What is the position of `2 *` DEN airport location? +- **multiplying/dividing** a `quantity_point` with a quantity + - What would multiplying the distance with the DEN airport location mean? +- **multiplying/dividing** two `quantity_point` objects + - What would multiplying home and DEN airport location mean? +- **mixing** `quantity_points` of different quantity kinds + - It is physically impossible to subtract time from length. +- **mixing** `quantity_points` of inconvertible quantities + - What does it mean to subtract a distance point to DEN airport from the Mount Everest base camp + altitude? +- **mixing** `quantity_points` of convertible quantities but with unrelated origins + - How to subtract a point on our trip to CppCon measured relatively to our home location from + a point measured relative to the center of the Solar System? !!! note - The usage of `quantity_point`, and affine space types in general, improves expressiveness and + The usage of `quantity_point` and affine space types in general, improves expressiveness and type-safety of the code we write.