-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: initial version of "Affine Space" chapter
- Loading branch information
Showing
1 changed file
with
267 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Reference auto R, | ||
PointOriginFor<get_quantity_spec(R)> auto PO = absolute_point_origin<get_quantity_spec(R)>{}, | ||
RepresentationOf<get_quantity_spec(R).character> 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<isq::altitude[m]> 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<isq::altitude[m]> 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<isq::altitude> {} mean_sea_level; | ||
|
||
constexpr quantity_point<isq::altitude[m], mean_sea_level> 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<isq::altitude[m], everest_base_camp, std::uint8_t> 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<isq::distance> {} home_location; | ||
|
||
quantity_point<isq::distance[km], home_location> home{}; | ||
quantity_point<isq::distance[km], home_location> home_airport = home + 15 * km; | ||
quantity_point<isq::distance[km], home_location> fra_airport = home_airport + 829 * km; | ||
quantity_point<isq::distance[km], home_location> den_airport = fra_airport + 8115 * km; | ||
quantity_point<isq::distance[km], home_location> 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<isq::distance[km]> total = cppcon_venue - home; | ||
quantity<isq::distance[km]> 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<isq::distance[km]> taxi1 = home_airport - home; | ||
quantity<isq::distance[km]> taxi2 = cppcon_venue - den_airport; | ||
quantity<isq::distance[km]> 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<isq::thermodynamic_temperature[K]>{273.15 * K}; | ||
using Celsius_point = quantity_point<isq::Celsius_temperature[deg_C], ice_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<isq::Celsius_temperature[deg_C], room_reference_temperature>; | ||
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 | ||
|