docs: "Systems of Quantities" chapter added

This commit is contained in:
Mateusz Pusz
2023-06-23 19:15:07 +02:00
parent a8a77ff4ae
commit 0c7505d7a3

View File

@@ -0,0 +1,296 @@
# Systems of Quantities
The physical units libraries on the market typically only scope on modeling one or more
[systems of units](../../../appendix/glossary/#system-of-units). However, this is not the
only system kind to model. Another, and maybe even more important, system kind is a
[system of quantities](../../../appendix/glossary/#system-of-quantities).
!!! info
Please note that the **mp-units** is probably the first library on the Open Source market
(in any programming language) that models the [ISQ](../../../appendix/glossary/#isq)
with all its definitions provided in ISO 80000. Please provide feedback if something
looks odd or could be improved.
## Dimension is not enough to describe a quantity
Most of the products on the market are aware of physical dimensions. However, a dimension is not
enough to describe a quantity. For example, let's see the following implementation:
```cpp
class Box {
area base_;
length height_;
public:
Box(length l, length w, length h) : base_(l * w), height_(h) {}
// ...
};
Box my_box(2 * m, 3 * m, 1 * m);
```
How do you like such an interface? It turns out that in most existing strongly-typed libraries
this is often the best we can do :woozy_face:
Another typical question many users ask is how to deal with energy and moment of force.
Both of those have the same dimension but are different quantities.
Another question is what should be the result of:
```cpp
auto res = 1 * Hz + 1 * Bq + 1 * Bd;
```
where:
- `Hz` (hertz) - unit of frequency
- `Bq` (becquerel) - unit of activity
- `Bd` (baud) - unit of modulation rate
All of those quantities have the same dimension, namely $\mathsf{T}^{-1}$, but probably it
is not wise to allow adding, subtracting, or comparing them, as they describe vastly different
physical properties.
!!! info
More than one quantity may be defined for the same dimension:
- quantities of _different kinds_ (i.e. frequency, modulation rate, activity, ...)
- quantities of _the same kind_ (i.e. length, width, altitude, distance, radius, wavelength, position vector, ...)
It turns out that the above issues can't be solved correctly without proper modeling of
a [system of quantities](../../../appendix/glossary/#system-of-quantities).
## Quantities of the same kind
!!! quote "ISO 80000-1"
- Quantities may be grouped together into categories of quantities that are
**mutually comparable**
- Mutually comparable quantities are called **quantities of the same kind**
- Two or more quantities **cannot be added or subtracted unless they belong to the same category
of mutually comparable quantities**
- Quantities of the **same kind** within a given system of quantities **have the same quantity
dimension**
- Quantities of the **same dimension are not necessarily of the same kind**
The above quotes from ISO 80000 answer to all the issues above. Two quantities can't be
added, subtracted, or compared unless they belong to the same [kind](../../../appendix/glossary/#kind).
As frequency, activity, and modulation rate are different kinds, the expression provided above should
not compile.
## System of quantities is not only about kinds
ISO 80000 specify hundreds of different quantities. There are plenty of different kinds provided
and often each kind contains more than one quantity. In fact, it turns out that such quantities
form a hierarchy of quantities of the same kind.
For example, here are all quantities of the kind length provided in the ISO 80000:
```mermaid
flowchart TD
length --- width[width, breadth]
length --- height[height, depth, altitude]
width --- thickness
width --- diameter
width --- radius
length --- path_length
path_length --- distance
distance --- radial_distance
length --- wavelength
length --- position_vector["position_vector\n{vector}"]
length --- displacement["displacement\n{vector}"]
radius --- radius_of_curvature
```
Each of the above quantities expresses some kind of length, and each can be measured with `si::metre`.
However, each of them has different properties, usage, and sometimes even a different
representation type (notice that `position_vector` and `displacement` are vector quantities).
Analyzing such a hierarchy can help us in defining arithmetics and conversion rules.
## Comparing, adding, and subtracting quantities
ISO 80000 explicitly states that `width` and `height` are quantities of the same kind, and as such they:
- are mutually comparable
- can be added and subtracted
If we take the above for granted, the only reasonable result of `1 * width + 1 * height` is `2 * length`,
where the result of `length` is known as a common quantity type. A result of such an equation is always
the first common branch in a hierarchy tree of the same kind. For example:
```cpp
static_assert(common_quantity_spec(isq::width, isq::height) == isq::length);
static_assert(common_quantity_spec(isq::thickness, isq::radius) == isq::width);
static_assert(common_quantity_spec(isq::distance, isq::path_length) == isq::path_length);
```
## Converting between quantities
Based on the same hierarchy of quantities of kind length, we can define quantity conversion rules.
1. **Implicit conversions**
- every `width` is a `length`
- every `radius` is a `width`
```cpp
static_assert(implicitly_convertible(isq::width, isq::length));
static_assert(implicitly_convertible(isq::radius, isq::length));
static_assert(implicitly_convertible(isq::radius, isq::width));
```
2. **Explicit conversions**
- not every `length` is a `width`
- not every `width` is a `radius`
```cpp
static_assert(!implicitly_convertible(isq::length, isq::width));
static_assert(!implicitly_convertible(isq::length, isq::radius));
static_assert(!implicitly_convertible(isq::width, isq::radius));
static_assert(explicitly_convertible(isq::length, isq::width));
static_assert(explicitly_convertible(isq::length, isq::radius));
static_assert(explicitly_convertible(isq::width, isq::radius));
```
3. **Explicit casts**
- `height` is not a `width`
- both `height` and `width` are quantities of kind `length`
```cpp
static_assert(!implicitly_convertible(isq::height, isq::width));
static_assert(!explicitly_convertible(isq::height, isq::width));
static_assert(castable(isq::height, isq::width));
```
4. **No conversion**
- `time` has nothing in common with `length`
```cpp
static_assert(!implicitly_convertible(isq::time, isq::length));
static_assert(!explicitly_convertible(isq::time, isq::length));
static_assert(!castable(isq::time, isq::length));
```
## Hierarchies of derived quantities
Derived quantity equations often do not automatically form a hierarchy tree. This is why it is
sometimes not obvious what such a tree should look like. Also, ISO explicitly states:
!!! quote "ISO/IEC Guide 99"
The division of quantity according to kind of quantity is, to some extent, arbitrary.
The below presents some arbitrary hierarchy of derived quantities of kind energy:
```mermaid
flowchart TD
energy["energy\n(mass * length^2 / time^2)"]
energy --- mechanical_energy
mechanical_energy --- potential_energy
potential_energy --- gravitational_potential_energy["gravitational_potential_energy\n(mass / acceleration_of_free_fall / height)"]
potential_energy --- elastic_potential_energy["elastic_potential_energy\n(spring_constant * amount_of_compression^2)"]
mechanical_energy --- kinetic_energy["kinetic_energy\n(mass * speed^2)"]
energy --- enthalpy
enthalpy --- internal_energy[internal_energy, thermodynamic_energy]
internal_energy --- Helmholtz_energy[Helmholtz_energy, Helmholtz_function]
enthalpy --- Gibbs_energy[Gibbs_energy, Gibbs_function]
energy --- active_energy
```
Notice, that even though all of those quantities have the same dimension and can be expressed
in the same units, they have different [quantity equations](../../../appendix/glossary/#quantity-equation)
used to create them implicitly:
- `energy` is the most generic one and thus can be created from base quantities of `mass`, `length`,
and `time`. As those are also the roots of quantities of their kinds and all other quantities are
implicitly convertible to them (we agreed on that "every `width` is a `length`" already), it means
that an `energy` can be implicitly constructed from any quantity of mass, length, and time.
```cpp
static_assert(implicitly_convertible(isq::mass * pow<2>(isq::length) / pow<2>(isq::time), isq::energy));
static_assert(implicitly_convertible(isq::mass * pow<2>(isq::height) / pow<2>(isq::time), isq::energy));
```
- `mechanical_energy` is a more "specialized" quantity than `energy` (not every `energy` is
a `mechanical_energy`). It is why an explicit cast is needed to convert from either `energy` or
the results of its [quantity equation](../../../appendix/glossary/#quantity-equation).
```cpp
static_assert(!implicitly_convertible(isq::energy, isq::mechanical_energy));
static_assert(explicitly_convertible(isq::energy, isq::mechanical_energy));
static_assert(!implicitly_convertible(isq::mass * pow<2>(isq::length) / pow<2>(isq::time),
isq::mechanical_energy));
static_assert(explicitly_convertible(isq::mass * pow<2>(isq::length) / pow<2>(isq::time),
isq::mechanical_energy));
```
- `gravitational_potential_energy` is not only even more specialized one but additionally,
it is special in a way that it provides its own "constrained"
[quantity equation](../../../appendix/glossary/#quantity-equation). Maybe not every
`mass * pow<2>(length) / pow<2>(time)` is a `gravitational_potential_energy`, but every
`mass * acceleration_of_free_fall * height` is.
```cpp
static_assert(!implicitly_convertible(isq::energy, gravitational_potential_energy));
static_assert(explicitly_convertible(isq::energy, gravitational_potential_energy));
static_assert(!implicitly_convertible(isq::mass * pow<2>(isq::length) / pow<2>(isq::time),
gravitational_potential_energy));
static_assert(explicitly_convertible(isq::mass * pow<2>(isq::length) / pow<2>(isq::time),
gravitational_potential_energy));
static_assert(implicitly_convertible(isq::mass * isq::acceleration_of_free_fall * isq::height,
gravitational_potential_energy));
```
## Modeling a quantity kind
In the physical units library, we also need an abstraction describing an entire family of
quantities of the same kind. Such quantities have not only the same dimension but also
can be expressed in the same units.
To annotate a quantity to represent its kind (and not just a hierarchy tree's root quantity)
we introduced a `kind_of<>` specifier. For example, to express any quantity of length, we need
to type `kind_of<isq::length>`.
!!! note
`isq::length` and `kind_of<isq::length>` are two different things.
Such an entity behaves as any quantity of its kind. This means that it is implicitly
convertible to any quantity in a tree.
```cpp
static_assert(!implicitly_convertible(isq::length, isq::height));
static_assert(implicitly_convertible(kind_of<isq::length>, isq::height));
```
Additionally, the result of operations on quantity kinds is also a quantity kind:
```cpp
static_assert(same_type<kind_of<isq::length> / kind_of<isq::time>, kind_of<isq::length / isq::time>>);
```
However, if at least one equation's operand is not a kind, the result becomes a "strong"
quantity where all the kinds are converted to the hierarchy tree's root quantities:
```cpp
static_assert(!same_type<kind_of<isq::length> / isq::time, kind_of<isq::length / isq::time>>);
static_assert(same_type<kind_of<isq::length> / isq::time, isq::length / isq::time>);
```
!!! note
Only a root quantity from the hierarchy tree or the one marked with `is_kind` specifier
in the `quantity_spec` definition can be put as a template parameter to the `kind_of`
specifier. For example, `kind_of<isq::width>` will fail to compile.