diff --git a/docs/users_guide/framework_basics/the_affine_space.md b/docs/users_guide/framework_basics/the_affine_space.md index e69de29bb..1d5a70ec2 100644 --- a/docs/users_guide/framework_basics/the_affine_space.md +++ b/docs/users_guide/framework_basics/the_affine_space.md @@ -0,0 +1,267 @@ +# The Affine Space + +The affine space has two types of entities: + +- **_point_** - a position specified with coordinate values (i.e. location, address, etc.) +- **_vector_** - the difference between two points (i.e. shift, offset, displacement, duration, etc.) + + +!!! note + + The _vector_ described here is specific to the affine space theory and is not the same thing + as the quantity of a vector character that we discussed in the + ["Scalars, vectors, and tensors" chapter](character_of_a_quantity/#scalars-vectors-and-tensors) + (although, in some cases, those terms may overlap). + + +## Operations in the affine space + +Here are the primary operations one can do in the affine space: + +- _vector_ + _vector_ -> _vector_ +- _vector_ - _vector_ -> _vector_ +- -_vector_ -> _vector_ +- _vector_ * scalar -> _vector_ +- scalar * _vector_ -> _vector_ +- _vector_ / scalar -> _vector_ +- _point_ - _point_ -> _vector_ +- _point_ + _vector_ -> _point_ +- _point_ - _vector_ -> _point_ + +!!! note + + It is not possible to: + + - add two _points_, + - subtract a _point_ from a _vector_, + - multiply nor divide _points_ with anything else. + + +## _Vector_ is modeled by `quantity` + +Up until now, each time when we used a `quantity` in our code, we were modeling some kind of a +difference between two things: + +- the distance between two points +- duration between two time points +- the difference in speed (even if relative to `0`) + +As we already know, a `quantity` type provides all operations required for _vector_ type in +the affine space. + + +## _Point_ is modeled by `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: + +```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/#pointoriginfor) +and specifies the origin of our scale. + + +### The origin + +The **origin** specifies where the "zero" of our measurement's scale is. + +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`: + +- 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. + +```cpp +constexpr quantity_point everest_base_camp{5364 * m}; +static_assert(everest_base_camp.relative() == 5364 * m); +``` + +!!! 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 = 5364 * m; // ERROR + ``` + +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: + +```cpp +constexpr struct mean_sea_level : absolute_point_origin {} mean_sea_level; + +constexpr quantity_point everest_base_camp{5364 * m}; +static_assert(everest_base_camp.relative() == 5364 * m); +``` + + +### Stacking _point_ origins + +We often do not have 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? +Why not use `everest_base_camp` as our reference point? + +It turns out that in the **mp-units** library, you can use a predefined at compile-time `quantity_point` +as an origin as well: + +```cpp +constexpr quantity_point first_climb_alt{42 * m}; +static_assert(first_climb_alt.relative() == 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.relative() == 5406 * m); + ``` + +- do the same but in a slightly different way: + + ```cpp + static_assert(first_climb_alt.relative() + first_climb_alt.point_origin.relative() == 5406 * m); + ``` + +- call `absolute()` member function + + ```cpp + static_assert(first_climb_alt.absolute() == 5406 * m); + ``` + + +### _Point_ arithmetics + +Let's assume we are going to attend the CppCon conference that is hosted in Aurora, CO, and we +want to estimate the distance we are going to 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 taxi to the Gaylord Rockies Resort & Convention +Center: + +```cpp +constexpr struct home_location : absolute_point_origin {} home_location; + +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; +``` + +As we can see above, we can easily get a new point by adding a quantity to another quantity point. + +If we want to find out the distance traveled between two points, we simply subtract them: + +```cpp +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: + +```cpp +quantity taxi1 = home_airport - home; +quantity taxi2 = cppcon_venue - den_airport; +quantity taxi = taxi1 + taxi2; +``` + +Now it will print the results: + +```cpp +std::cout << "Total distance: " << total << "\n"; +std::cout << "Flight distance: " << flight << "\n"; +std::cout << "Taxi distance: " << taxi << "\n"; +``` + +we will see the following output: + +```text +Total distance: 8975.25 km +Flight distance: 8944 km +Taxi distance: 31.2544 km +``` + + +### Temperature support + +Another important example of [stacking point origins](#stacking-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: + +```cpp +constexpr auto ice_point = quantity_point{273.15 * K}; +using Celsius_point = quantity_point; +``` + +!!! note + + While [stacking point origins](#stacking-point-origins) we can use not only different + representation types but also different units for an origin and a _point_. + +With the above, for example, if we want to implement a room temperature controller, we can type: + +```cpp +constexpr Celsius_point room_reference_temperature{21 * deg_C}; +using room_temperature = quantity_point; + +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; + +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"; +``` + +The above prints: + +```text +Lowest temp: -3 °C (18 °C) +Highest temp: 3 °C (24 °C) +``` + + +## The affine space is about type-safety + +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?). + +!!! note + + The usage of `quantity_point`, and affine space types in general, improves expressiveness and + type-safety of the code we write. + + +## Handling temperature points +