diff --git a/docs/blog/posts/isq-part-1-introduction.md b/docs/blog/posts/isq-part-1-introduction.md index bcc6d5663..8745a7f10 100644 --- a/docs/blog/posts/isq-part-1-introduction.md +++ b/docs/blog/posts/isq-part-1-introduction.md @@ -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 diff --git a/docs/blog/posts/isq-part-2-problems-when-isq-is-not-used.md b/docs/blog/posts/isq-part-2-problems-when-isq-is-not-used.md index 06ca46c95..6f4cfe717 100644 --- a/docs/blog/posts/isq-part-2-problems-when-isq-is-not-used.md +++ b/docs/blog/posts/isq-part-2-problems-when-isq-is-not-used.md @@ -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` 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` 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(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. diff --git a/docs/blog/posts/isq-part-3-modelling-isq.md b/docs/blog/posts/isq-part-3-modelling-isq.md index 59e0ecc4a..21bc31a31 100644 --- a/docs/blog/posts/isq-part-3-modelling-isq.md +++ b/docs/blog/posts/isq-part-3-modelling-isq.md @@ -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. - -!!! important +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. - 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> q); + void foo(quantity q); ``` ```cpp - quantity> q1 = 42 * m; - quantity> q2 = q1; // implicit quantity conversion + quantity q1 = 42 * m; + quantity 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> q); + void foo(quantity q); ``` ```cpp - quantity> q1 = 42 * m; - quantity> q2 = isq::height(q1); // explicit quantity conversion + quantity q1 = 42 * m; + quantity 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> q); + void foo(quantity q); ``` ```cpp - quantity> q1 = 42 * m; - quantity> q2 = quantity_cast(q1); // explicit quantity cast + quantity q1 = 42 * m; + quantity q2 = quantity_cast(q1); // explicit quantity cast foo(quantity_cast(q1)); // explicit quantity cast ``` @@ -262,7 +257,7 @@ Based on the hierarchy above, we can define the following quantity conversion ru ``` ```cpp - quantity> q1 = 42 * s; // Compile-time error + quantity q1 = 42 * s; // Compile-time error foo(quantity_cast(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`. !!! important @@ -344,11 +339,12 @@ static_assert(same_type / 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 q1 = 60 * Bq; // Compile-time error quantity q2; // Compile-time error -quantity q3 = 60 * Hz; +quantity 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.