forked from mpusz/mp-units
docs: initial version of "Affine Space" chapter
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user