mirror of
https://github.com/mpusz/mp-units.git
synced 2025-07-30 02:17:16 +02:00
docs: ISQ parts 1-3 improved
This commit is contained in:
@ -73,8 +73,8 @@ with other such systems. For example:
|
||||
Both **systems of units** above agree on the unit of _time_, but chose different units for other
|
||||
quantities. In the above example, SI chose a non-prefixed unit of metre for a base quantity of _length_
|
||||
while CGS chose a scaled centimetre. On the other hand, SI chose a scaled kilogram over the gram used
|
||||
in the CGS. Those decisions also result in a need for different units for derived quantities.
|
||||
For example:
|
||||
in the CGS. Those decisions also result in a need for different [coherent units](https://jcgm.bipm.org/vim/en/1.12.html)
|
||||
for derived quantities. For example:
|
||||
|
||||
| Quantity | SI | CGS |
|
||||
|------------|---------------|-----------------|
|
||||
@ -87,7 +87,7 @@ For example:
|
||||
|
||||
Often, there is no way to state which one is correct or which one is wrong. Each
|
||||
**system of units** has the freedom to choose whichever unit suits its engineering requirements
|
||||
and constraints the best.
|
||||
and constraints the best for a specific quantity.
|
||||
|
||||
## ISQ vs SI
|
||||
|
||||
|
@ -19,25 +19,25 @@ systems, in this article, we will talk about the benefits we get from modeling i
|
||||
|
||||
The issues described in this article do not apply to the **mp-units** library. Its interfaces,
|
||||
even if when we decide only to use [simple quantities](../../users_guide/framework_basics/simple_and_typed_quantities.md)
|
||||
that only use units, those are still backed up by quantity kinds under the framework's hood._
|
||||
that only use units, those are still backed up by quantity kinds under the framework's hood.
|
||||
|
||||
## Articles from this series
|
||||
|
||||
Previous:
|
||||
|
||||
- [Part 1 - Introduction](isq-part-1-introduction.md)
|
||||
- Part 2 - Problems when ISQ is not used
|
||||
|
||||
|
||||
## Limitations of units-only solutions
|
||||
|
||||
Units-only is not a good design for a quantities and units library. It works to some extent, but
|
||||
plenty of use cases can't be addressed, and for those that somehow work, we miss important safety improvements provided by additional abstractions in this article.
|
||||
plenty of use cases can't be addressed, and for those that somehow work, we miss important safety
|
||||
improvements provided by additional abstractions in this article series.
|
||||
|
||||
### No way to specify a quantity type in generic interfaces
|
||||
|
||||
A common requirement in the domain is to write unit-agnostic generic interfaces. For example,
|
||||
let's try to implement a generic `avg_speed` function template that takes a quantity of any
|
||||
unit and produces the result. So if we call it with _distance_ in `km` and _time_ in `h`, we will
|
||||
unit and produces the result. If we call it with _distance_ in `km` and _time_ in `h`, we will
|
||||
get `km/h` as a result, but if we call it with `mi` and `h`, we expect `mi/h` to be returned.
|
||||
|
||||
```cpp
|
||||
@ -71,29 +71,36 @@ avg_speed(120 * km, 2 * h).in(km / h);
|
||||
Despite being safer, the above code decreased the performance because we always pay for the
|
||||
conversion at the function's input and output.
|
||||
|
||||
We could try to provide concepts like `ScaledUnitOf<si::metre>` that will try to constrain
|
||||
the arguments somehow, but it leads to even more problems with the unit definitions. For example,
|
||||
are `Hz` and `Bq` just scaled versions of `1/s`? What about radian and steradian or a litre and
|
||||
a cubic meter?
|
||||
|
||||
Moreover, in a good library, the above code should not compile. The reason for this is that
|
||||
even though the conversion from `km` to `m` and from `h` to `s` is considered value-preserving,
|
||||
it is not true in the opposite direction. When we try to convert the result stored in an
|
||||
integral type from the unit of `m/s` to `km/h`, we will inevitably lose some data.
|
||||
|
||||
We could try to provide concepts like `ScaledUnitOf<si::metre>` that would take a set of units
|
||||
while trying to constrain them somehow, but it leads to even more problems with the unit
|
||||
definitions. For example, are `Hz` and `Bq` just scaled versions of `1/s`? If we constrain the
|
||||
interface to just prefixed units, then litre and a cubic metre or kilometre and mile will be
|
||||
incompatible. What about radian and steradian or a litre per 100 kilometre (popular unit of
|
||||
a fuel consumption) and a squared metre? Should those be compatible?
|
||||
|
||||
|
||||
### Disjoint units of the same quantity type do not work
|
||||
|
||||
Sometimes, we need to define several units describing the same quantity but which do not convert
|
||||
to each other. A typical example can be a currency use case. A user may want to define EURO and
|
||||
USD as units of currency, but do not provide any predefined conversion factor and handle such
|
||||
a conversion at runtime with custom logic (e.g., using an additional time point function argument).
|
||||
In such a case, how can we specify that EURO and USD are quantities of the same type/dimension?
|
||||
Sometimes, we need to define several units describing the same quantity but which should not
|
||||
convert to each other in the library's framework. A typical example here is currency. A user
|
||||
may want to define EURO and USD as units of currency, so both of them can be used for such
|
||||
quantities. However, it is impossible to predefine one fixed conversion factor for those,
|
||||
as a currency exchange rate varies over time, and the library's framework can't provide such
|
||||
an information as an input to the built-in conversion function. User's application may have more
|
||||
information in this domain and handle such a conversion at runtime with custom logic
|
||||
(e.g., using an additional time point function argument). If we would like to model that
|
||||
in a unit-only solution, how can we specify that EURO and USD are units of quantities of
|
||||
currency, but are not convertible to each other?
|
||||
|
||||
|
||||
## Dimensions to the rescue?
|
||||
|
||||
To prevent the above issues, most of the libraries on the market introduce dimension abstraction.
|
||||
To resolve the above issues, most of the libraries on the market introduce dimension abstraction.
|
||||
Thanks to that, we could solve the first issue of the previous chapter with:
|
||||
|
||||
```cpp
|
||||
@ -149,7 +156,7 @@ For example:
|
||||
steradian (sr) is a unit of _solid angle_ defined as $m^2/m^2$.
|
||||
Both are quantities of dimension one, which also has its own units like one (1) and percent (%).
|
||||
|
||||
There are many more similar examples in the ISO 80000 series. For example, _storage capacity_
|
||||
There are many more similar examples in the ISO/IEC 80000 series. For example, _storage capacity_
|
||||
quantity can be measured in units of one, bit, octet, and byte.
|
||||
|
||||
The above conflicts can't be solved with dimensions, and they yield many safety issues. For example,
|
||||
@ -177,12 +184,32 @@ Again, we don't want to accidentally mix those.
|
||||
|
||||
### Various quantities of the same dimension and kinds
|
||||
|
||||
Even if we somehow address all the above, there are still plenty of use cases that still can't be
|
||||
safely implemented with such abstractions.
|
||||
Even if we somehow address all the above, there are plenty of use cases that still can't be safely
|
||||
implemented with such abstractions.
|
||||
|
||||
Let's consider that we want to implement a freight transport application to position cargo in the
|
||||
container. In such a scenario, we need to be able to discriminate between _length_, _width_, and
|
||||
_height_ of the package. Also, often, we can find a "This side up" arrow on the box.
|
||||
container. In majority of the products on the market we will end up with something like:
|
||||
|
||||
```cpp
|
||||
class Box {
|
||||
length length_;
|
||||
length width_;
|
||||
length height_;
|
||||
public:
|
||||
Box(length l, length w, length h): length_(l), width_(w), height_(h) {}
|
||||
area floor() const { return length_ * width_; }
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
```cpp
|
||||
Box my_box(2 * m, 3 * m, 1 * m);
|
||||
```
|
||||
|
||||
Such interfaces are not much safer than just using plain fundamental types (e.g., `double`). One
|
||||
of the main reasons of using a quantities and units library was to introduce strong-type interfaces
|
||||
to prevent such issues. In this scenario, we need to be able to discriminate between _length_,
|
||||
_width_, and _height_ of the package.
|
||||
|
||||
A similar but also really important use case is in aviation. The current _altitude_ is a totally
|
||||
different quantity than the _distance_ to the destination. The same is true for _forward speed_
|
||||
@ -197,6 +224,22 @@ to make it clear that something potentially unsafe is being done in the code. Al
|
||||
be able to assign a _potential energy_ to a quantity of _kinetic energy_. However, both of them
|
||||
(possibly accumulated with each other) should be convertible to a _mechanical energy_ quantity.
|
||||
|
||||
```cpp
|
||||
mass m = 1 * kg;
|
||||
length l = 1 * m;
|
||||
time t = 1 * s;
|
||||
acceleration_of_free_fall g = 9.81 * m / s2;
|
||||
height h = 1 * m;
|
||||
speed v = 1 * m / s;
|
||||
energy e = m * pow<2>(l) / pow<2>(t); // OK
|
||||
potential_energy ep1 = e; // should not compile
|
||||
potential_energy ep2 = static_cast<potential_energy>(e); // OK
|
||||
potential_energy ep3 = m * g * h; // OK
|
||||
kinetic_energy ek1 = m * pow<2>(v) / 2; // OK
|
||||
kinetic_energy ek2 = ep3 + ek1; // should not compile
|
||||
mechanical_energy me = ep3 + ek1; // OK
|
||||
```
|
||||
|
||||
Yet another example comes from the audio industry. In the audio software, we want to treat specific
|
||||
counts (e.g., _beats_, _samples_) as separate quantities. We could assign dedicated base dimensions
|
||||
to them. However, if we divide them by _duration_, we should obtain a quantity convertible to
|
||||
@ -205,10 +248,10 @@ approach, this wouldn't work as the dimension of frequency is just $T^{-1}$, whi
|
||||
the results of our dimensional equations. This is why we can't assign dedicated dimensions to such
|
||||
counts.
|
||||
|
||||
The last example that we want to mention here comes from finance. This time, we need to model _volume_
|
||||
as a special quantity of _currency_. _volume_ can be obtained by multiplying _currency_ by the
|
||||
dimensionless _market quantity_. Of course, both _currency_ and _volume_ should be expressed in
|
||||
the same units (e.g., USD).
|
||||
The last example that we want to mention here comes from finance. This time, we need to model
|
||||
_currency volume_ as a special quantity of _currency_. _currency volume_ can be obtained by
|
||||
multiplying _currency_ by the dimensionless _market quantity_. Of course, both _currency_ and
|
||||
_currency volume_ should be expressed in the same units (e.g., USD).
|
||||
|
||||
None of the above scenarios can be addressed with just units and dimensions. We need a better
|
||||
abstraction to safely implement them.
|
||||
@ -216,4 +259,4 @@ abstraction to safely implement them.
|
||||
## To be continued...
|
||||
|
||||
In the next part of this series, we will introduce the main ideas behind the International
|
||||
System of Quantities and provide solutions to the problems described above.
|
||||
System of Quantities and describe how we can model it in the programming language.
|
||||
|
@ -23,16 +23,16 @@ language.
|
||||
|
||||
## Articles from this series
|
||||
|
||||
Previous:
|
||||
|
||||
- [Part 1 - Introduction](isq-part-1-introduction.md)
|
||||
- [Part 2 - Problems when ISQ is not used](isq-part-2-problems-when-isq-is-not-used.md)
|
||||
- Part 3 - Modelling ISQ
|
||||
|
||||
|
||||
## 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:
|
||||
enough to describe a quantity. Let's repeat briefly some of the problems described in more detail
|
||||
in the previous article. For example, let's see the following implementation:
|
||||
|
||||
```cpp
|
||||
class Box {
|
||||
@ -47,10 +47,10 @@ 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:
|
||||
this is often the best we can do. :woozy_face:
|
||||
|
||||
Another typical question many users ask is how to deal with _work_ and _torque_.
|
||||
Both of those have the same dimension but are different quantities.
|
||||
Both of those have the same dimension but are distinct quantities.
|
||||
|
||||
A similar issue is related to figuring out what should be the result of:
|
||||
|
||||
@ -68,19 +68,15 @@ All of those quantities have the same dimension, namely $\mathsf{T}^{-1}$, but p
|
||||
is not wise to allow adding, subtracting, or comparing them, as they describe vastly different
|
||||
physical properties.
|
||||
|
||||
If the above example seems too abstract, let's consider _fuel consumption_ (fuel _volume_
|
||||
divided by _distance_, e.g., `6.7 l/km`) and an _area_. Again, both have the same dimension
|
||||
$\mathsf{L}^{2}$, but probably it wouldn't be wise to allow adding, subtracting, or comparing
|
||||
a _fuel consumption_ of a car and the _area_ of a football field. Such an operation does not
|
||||
have any physical sense and should fail to compile.
|
||||
If the above example seems too abstract, let's consider Gy (gray - unit of _absorbed dose_)
|
||||
and Sv (sievert - unit of _dose equivalent_), or radian and steradian. All of them have the
|
||||
same dimensions.
|
||||
|
||||
!!! important
|
||||
|
||||
More than one quantity may be defined for the same dimension:
|
||||
|
||||
- quantities of **different kinds** (e.g. _frequency_, _modulation rate_, _activity_, ...)
|
||||
- quantities of **the same kind** (e.g. _length_, _width_, _altitude_, _distance_, _radius_,
|
||||
_wavelength_, _position vector_, ...)
|
||||
Another example here is _fuel consumption_ (fuel _volume_ divided by _distance_, e.g.,
|
||||
`6.7 l/100km`) and an _area_. Again, both have the same dimension $\mathsf{L}^{2}$, but probably
|
||||
it wouldn't be wise to allow adding, subtracting, or comparing a _fuel consumption_ of a car
|
||||
and the _area_ of a football field. Such an operation does not have any physical sense and should
|
||||
fail to compile.
|
||||
|
||||
It turns out that the above issues can't be solved correctly without proper modeling of
|
||||
a [system of quantities](../../appendix/glossary.md#system-of-quantities).
|
||||
@ -89,9 +85,8 @@ a [system of quantities](../../appendix/glossary.md#system-of-quantities).
|
||||
## Quantities of the same kind
|
||||
|
||||
As it was described in the previous article, dimension is not enough to describe a quantity.
|
||||
We need a better abstraction to ensure the safety of our calculations.
|
||||
|
||||
The ISO 80000-1:2009 says:
|
||||
We need a better abstraction to ensure the safety of our calculations. It turns out that
|
||||
ISO/IEC 80000 comes with the answer:
|
||||
|
||||
!!! quote "ISO 80000-1:2009"
|
||||
|
||||
@ -125,9 +120,9 @@ article.
|
||||
|
||||
More than one quantity may be defined for the same dimension:
|
||||
|
||||
- quantities of different kinds (e.g., _frequency_, _modulation rate_, _activity_)
|
||||
- quantities of different kinds (e.g., _frequency_, _modulation rate_, _activity_).
|
||||
- quantities of the same kind (e.g., _length_, _width_, _altitude_, _distance_, _radius_,
|
||||
_wavelength_, _position vector_)
|
||||
_wavelength_, _position vector_).
|
||||
|
||||
Two quantities can't be added, subtracted, or compared unless they belong to
|
||||
the same [kind](../../appendix/glossary.md#kind). As _frequency_, _activity_, and _modulation rate_
|
||||
@ -140,7 +135,7 @@ ISO/IEC 80000 specifies hundreds of different quantities. Plenty of various kind
|
||||
and often, each kind contains more than one quantity. 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-1:
|
||||
For example, here are all quantities of the kind length provided in the ISO 80000-3:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
@ -186,12 +181,12 @@ Based on the hierarchy above, we can define the following quantity conversion ru
|
||||
Implicit conversions are allowed on copy-initialization:
|
||||
|
||||
```cpp
|
||||
void foo(quantity<isq::length<m>> q);
|
||||
void foo(quantity<isq::length[m]> q);
|
||||
```
|
||||
|
||||
```cpp
|
||||
quantity<isq::width<m>> q1 = 42 * m;
|
||||
quantity<isq::length<m>> q2 = q1; // implicit quantity conversion
|
||||
quantity<isq::width[m]> q1 = 42 * m;
|
||||
quantity<isq::length[m]> q2 = q1; // implicit quantity conversion
|
||||
foo(q1); // implicit quantity conversion
|
||||
```
|
||||
|
||||
@ -213,12 +208,12 @@ Based on the hierarchy above, we can define the following quantity conversion ru
|
||||
type:
|
||||
|
||||
```cpp
|
||||
void foo(quantity<isq::height<m>> q);
|
||||
void foo(quantity<isq::height[m]> q);
|
||||
```
|
||||
|
||||
```cpp
|
||||
quantity<isq::length<m>> q1 = 42 * m;
|
||||
quantity<isq::height<m>> q2 = isq::height(q1); // explicit quantity conversion
|
||||
quantity<isq::length[m]> q1 = 42 * m;
|
||||
quantity<isq::height[m]> q2 = isq::height(q1); // explicit quantity conversion
|
||||
foo(isq::height(q1)); // explicit quantity conversion
|
||||
```
|
||||
|
||||
@ -236,12 +231,12 @@ Based on the hierarchy above, we can define the following quantity conversion ru
|
||||
Explicit casts are forced with a dedicated `quantity_cast` function:
|
||||
|
||||
```cpp
|
||||
void foo(quantity<isq::height<m>> q);
|
||||
void foo(quantity<isq::height[m]> q);
|
||||
```
|
||||
|
||||
```cpp
|
||||
quantity<isq::width<m>> q1 = 42 * m;
|
||||
quantity<isq::height<m>> q2 = quantity_cast<isq::height>(q1); // explicit quantity cast
|
||||
quantity<isq::width[m]> q1 = 42 * m;
|
||||
quantity<isq::height[m]> q2 = quantity_cast<isq::height>(q1); // explicit quantity cast
|
||||
foo(quantity_cast<isq::height>(q1)); // explicit quantity cast
|
||||
```
|
||||
|
||||
@ -262,7 +257,7 @@ Based on the hierarchy above, we can define the following quantity conversion ru
|
||||
```
|
||||
|
||||
```cpp
|
||||
quantity<isq::length<m>> q1 = 42 * s; // Compile-time error
|
||||
quantity<isq::length[m]> q1 = 42 * s; // Compile-time error
|
||||
foo(quantity_cast<isq::length>(42 * s)); // Compile-time error
|
||||
```
|
||||
|
||||
@ -272,7 +267,7 @@ Based on the hierarchy above, we can define the following quantity conversion ru
|
||||
ISO/IEC 80000 explicitly states that _width_ and _height_ are quantities of the same kind,
|
||||
and as such they:
|
||||
|
||||
- are mutually comparable, and
|
||||
- are mutually comparable,
|
||||
- can be added and subtracted.
|
||||
|
||||
This means that we should be allowed to compare any quantities from the same tree (as long as
|
||||
@ -301,7 +296,7 @@ quantities of the same kind. Such quantities have not only the same dimension bu
|
||||
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
|
||||
we introduced a `kind_of<>` specifier. For example, to express any quantity of _length_, we need
|
||||
to type `kind_of<isq::length>`.
|
||||
|
||||
!!! important
|
||||
@ -344,11 +339,12 @@ static_assert(same_type<kind_of<isq::length> / isq::time, isq::length / isq::tim
|
||||
|
||||
## How do systems of units benefit from the ISQ and quantity kinds?
|
||||
|
||||
Modeling a system of units is the most essential feature and a selling point of every
|
||||
physical units library. Thanks to that, the library can protect users from performing invalid
|
||||
operations on quantities and provide automated conversion factors between various compatible units.
|
||||
Modeling a system of units is the most essential feature and a selling point of every physical
|
||||
units library. Thanks to that, the library can protect users from assigning, adding, subtracting,
|
||||
or comparing incompatible units and provide automated conversion factors between various compatible
|
||||
units.
|
||||
|
||||
Probably all the libraries in the wild model the SI, or at least most of it, and many of them
|
||||
Probably all the libraries in the wild model the SI (or at least most of it), and many of them
|
||||
provide support for additional units belonging to various other systems (e.g., imperial).
|
||||
|
||||
### Systems of units are based on systems of quantities
|
||||
@ -377,9 +373,9 @@ the amount of any quantity of kind _length_.
|
||||
where both _length_ and _time_ will be measured in seconds, and _speed_ will be a quantity
|
||||
measured with the unit `one`. In such case, the definition will look as follows:
|
||||
|
||||
```cpp
|
||||
inline constexpr struct second final : named_unit<"s"> {} second;
|
||||
```
|
||||
```cpp
|
||||
inline constexpr struct second final : named_unit<"s"> {} second;
|
||||
```
|
||||
|
||||
### Constraining a derived unit to work only with a specific derived quantity
|
||||
|
||||
@ -406,7 +402,7 @@ for quantities of _activity_:
|
||||
```cpp
|
||||
quantity<isq::frequency[Hz]> q1 = 60 * Bq; // Compile-time error
|
||||
quantity<isq::activity[Hz]> q2; // Compile-time error
|
||||
quantity<isq::frequency[Hz]> q3 = 60 * Hz;
|
||||
quantity<isq::frequency[Hz]> q3 = 60 * Hz; // OK
|
||||
std::cout << q3.in(Bq) << "\n"; // Compile-time error
|
||||
```
|
||||
|
||||
@ -418,10 +414,10 @@ specific kinds only:
|
||||
auto q = 1 * Hz + 1 * Bq; // Fails to compile
|
||||
```
|
||||
|
||||
All of the above features improve the safety of our library and the products that use it.
|
||||
All of the above features improve the safety of our library and the products that are using it.
|
||||
|
||||
|
||||
## To be continued...
|
||||
|
||||
In the next part of this series, we will discuss the challenges and issues related to the modelling
|
||||
of the ISQ with a programming language.
|
||||
In the next part of this series, we will present how our ISQ model helps to address the remaining
|
||||
issues described in the [Part 2](isq-part-2-problems-when-isq-is-not-used.md) of our series.
|
||||
|
Reference in New Issue
Block a user