Documentation updated

This commit is contained in:
Mateusz Pusz committed 2020-05-13 12:50:14 +02:00
1 parent cf7dab42c3
commit df6a0da9d3
11 files changed
+499 -996

No files matched your search

+188 -2
View File
@@ -3,17 +3,203 @@
Extending the Library
=====================
The library was designed with a simple extensibility in mind. It is easy to add new units,
dimensions, and prefixes. The systems of units are not closed (classes) but open (namespaces)
and can be easily extended, or its content can be partially/fully imported to other systems.
Custom Units
------------
It might happen that the user would like to use a unit that is not predefined by the library
or is predefined but the user would like to name it differently, assign a different symbol
to existing unit, or make it a base unit for prefixes.
Defining a New Unit
^^^^^^^^^^^^^^^^^^^
My working desk is of ``180 cm x 60 cm`` which gives an area of ``0.3 m²``. I would like to
make it a unit of area for my project::
struct desk : named_scaled_unit<desk, "desk", no_prefix, ratio<3, 10>, si::square_metre> {};
With the above I can define a quantity with the area of ``2 desks``::
auto d1 = si::area<desk>(2);
In case I feel it is too verbose to type the above every time I can easily create a custom
alias or an :abbr:`UDL (User Defined Literal)`::
// alias with fixed integral representation
using desks = si::area<desk, std::int64_t>;
// UDLs
constexpr auto operator"" _d(unsigned long long l) { return si::area<desk, std::int64_t>(l); }
constexpr auto operator"" _d(long double l) { return si::area<desk, long double>(l); }
Right now I am fully set up for my project and can start my work of tracking the area taken
by my desks::
auto d1 = si::area<desk>(2);
auto d2 = desks(3);
auto d3 = 1_d;
auto sum = d1 + d2 + d3;
std::cout << "Area: " << sum << '\n'; // prints 'Area: 6 desk'
In case I would like to check how much area ``6 desks`` take in SI units::
auto sum_si = quantity_cast<si::square_metre>(sum);
std::cout << "Area (SI): " << sum_si << '\n'; // prints 'Area (SI): 1.8 m²'
Enabling a Unit for Prefixing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In case I decide it is reasonable to express my desks with SI prefixes the only thing I have
to change in the above code is to replace `no_prefix` with `si_prefix`::
struct desk : named_scaled_unit<desk, "desk", si::prefix, ratio<3, 10>, si::square_metre> {};
Now I can define a new unit named ``kilodesk``::
struct kilodesk : prefixed_unit<kilodesk, si::kilo, desk> {};
static_assert(3_d * 1000 == si::area<kilodesk>(3));
But maybe SI prefixes are not good for me. Maybe I always pack ``6`` desks into one package
for shipment and ``40`` such packages fit into my lorry. To express this with prefixes a new
prefix family and prefixes are needed::
struct shipping_prefix : prefix_family {};
struct package : prefix<package, shipping_prefix, "pkg", ratio<6>> {};
struct lorry : prefix<lorry, shipping_prefix, "lorry", ratio<6 * 40>> {};
Now we can use it for our unit::
struct desk : named_scaled_unit<desk, "desk", shipping_prefix, ratio<3, 10>, si::square_metre> {};
struct packagedesk : prefixed_unit<packagedesk, package, desk> {};
struct lorrydesk : prefixed_unit<lorrydesk, lorry, desk> {};
With the above::
static_assert(6_d == si::area<packagedesk>(1));
static_assert(240_d == si::area<lorrydesk>(1));
std::cout << "Area: " << quantity_cast<packagedesk>(sum) << '\n'; // prints 'Area: 1 pkgdesk'
It is important to notice that with the definition of a custom prefix I did not loose SI
units compatibility. If I want to calculate how much area I can cover with desks delivered
by ``3 lorries`` I can do the following::
auto area = quantity_cast<si::square_metre>(si::area<lorrydesk>(3));
std::cout << "Area: " << area << '\n'; // prints 'Area: 216 m²'
Custom Dimensions
-----------------
Custom Base Dimensions
^^^^^^^^^^^^^^^^^^^^^^
There are cases were a custom unit is not enough and the user would like to define custom
dimensions. The most common case is to define a new derived dimension from other dimensions
already predefined in various systems. But in **mp-units** library it is also really easy to
define a new base dimension for a custom units system.
Custom Derived Dimensions
^^^^^^^^^^^^^^^^^^^^^^^^^
In case I want to track how many desks I can produce over time or what is the consumption
rate of wood during production I need to define a new derived dimension together with its
coherent unit::
// coherent unit must apply to the system rules (in this case SI)
struct square_metre_per_second : unit<square_metre_per_second> {};
// new derived dimensions
struct dim_desk_rate : derived_dimension<dim_desk_rate, square_metre_per_second,
exp<si::dim_area, 1>, exp<si::dim_time, -1>> {};
// our unit of interest for a new derived dimension
struct desk_per_hour : deduced_unit<desk_per_hour, dim_desk_rate, desk, si::hour> {};
// a quantity of our dimension
template<Unit U, Scalar Rep = double>
using desk_rate = quantity<dim_desk_rate, U, Rep>;
// a concept matching the above quantity
template<typename T>
concept DeskRate = QuantityOf<T, dim_desk_rate>;
With the above we can now check what is the production rate::
DeskRate auto rate = quantity_cast<desk_per_hour>(3._d / 20q_min);
std::cout << "Desk rate: " << rate << '\n'; // prints 'Desk rate: 9 desk/h'
and how much wood is being consumed over a unit of time::
auto wood_rate = quantity_cast<square_metre_per_second>(rate);
std::cout << "Wood rate: " << wood_rate << '\n'; // prints 'Wood rate: 0.00075 m²/s'
Custom Base Dimensions
^^^^^^^^^^^^^^^^^^^^^^
In case I want to monitor what is the average number of people sitting by one desk in
a customer's office I would need a unit called ``person_per_desk`` of a new derived
dimension. However, our library does not know what a ``person`` is. For this I need to
define a new base dimension, its units, quantity helper, concept, and UDLs::
struct person : named_unit<person, "person", no_prefix> {};
struct dim_people : base_dimension<"people", person> {};
template<Unit U, Scalar Rep = double>
using people = quantity<dim_people, U, Rep>;
template<typename T>
concept People = QuantityOf<T, dim_people>;
constexpr auto operator"" _p(unsigned long long l) { return people<person, std::int64_t>(l); }
constexpr auto operator"" _p(long double l) { return people<person, long double>(l); }
With the above we can now define a new derived dimension::
struct person_per_square_metre : unit<person_per_square_metre> {};
struct dim_occupancy_rate : derived_dimension<dim_occupancy_rate, person_per_square_metre,
exp<dim_people, 1>, exp<si::dim_area, -1>> {};
struct person_per_desk : deduced_unit<person_per_desk, dim_occupancy_rate, person, desk> {};
template<Unit U, Scalar Rep = double>
using occupancy_rate = quantity<dim_occupancy_rate, U, Rep>;
template<typename T>
concept OccupancyRate = QuantityOf<T, dim_occupancy_rate>;
Now we can play with our new feature::
People auto employees = 1450._p;
auto office_desks = 967_d;
OccupancyRate auto occupancy = employees / office_desks;
std::cout << "Occupancy: " << occupancy << '\n'; // prints 'Occupancy: 1.49948 person/desk'
Custom Systems
--------------
Being able to extend predefined systems is a mandatory feature of any physical
units library. Fortunately, for **mp-units** there is nothing special to do here.
A system is defined in terms of its base dimensions. If you are using only SI
base dimensions then you are in the boundaries of the SI system. If you are
adding new base dimensions, like we did in the `Custom Base Dimensions`_
chapter, you are defining a new system.
In **mp-units** library a custom system can either be constructed from
unique/new custom base dimensions or reuse dimensions of other systems. This
allows extending, mixing, reuse, and interoperation between different systems.
.. seealso::
More information on extending the library can be found in the
:ref:`Using Custom Representation Types` chapter.
+109
View File
@@ -0,0 +1,109 @@
.. namespace:: units
Working with Unknown Dimensions and Their Units
===============================================
From time to time the user of this library will face an `unknown_dimension` and
`unknown_coherent_unit` types. This chapters describes their purpose and usage in
detail.
What is an unknown dimension?
-----------------------------
As we learned in the :ref:`Dimensions` chapter, in most cases the result of multiplying
or dividing two quantities of specific dimensions is a quantity of yet another dimension.
If such a resulting dimension is predefined by the user (and a proper header file with its
definition is included in the current translation unit) :ref:`The Downcasting Facility`
will determine its type. The same applies to the resulting unit. For example:
.. code-block::
:emphasize-lines: 3,7-9
#include <units/physical/si/length.h>
#include <units/physical/si/time.h>
#include <units/physical/si/speed.h>
using namespace units::physical::si;
constexpr auto result = 144q_km / 2q_h;
static_assert(std::is_same_v<decltype(result)::dimension, dim_velocity>);
static_assert(std::is_same_v<decltype(result)::unit, kilometre_per_hour>);
However, if the resulting dimension is not predefined by the user the library framework
will create an instance of an `unknown_dimension`. The coherent unit of such an unknown
dimension is an `unknown_coherent_unit`. Let's see what happens with our example when
we forget to include a header file with the resulting dimension definition:
.. code-block::
:emphasize-lines: 3,9,11
#include <units/physical/si/length.h>
#include <units/physical/si/time.h>
// #include <units/physical/si/speed.h>
using namespace units::physical::si;
constexpr auto result = 144q_km / 2q_h;
static_assert(std::is_same_v<decltype(result)::dimension,
unknown_dimension<exp<dim_length, 1>, exp<dim_time, -1>>>);
static_assert(std::is_same_v<decltype(result)::unit,
scaled_unit<ratio<1, 36, 1>, unknown_coherent_unit>>);
Operations On Unknown Dimensions And Their Units
------------------------------------------------
For some cases we can eliminate the need to predefine a specific dimension and just use
the `unknown_dimension` instead. Let's play with the previous example a bit::
static_assert(result.count() == 72);
As we can see the value stored in this quantity can be easily obtained and contains a
correct result. However, if we try to print its value to the text output we will get::
std::cout << "Speed: " << result << '\n'; // prints 'Speed: 72 [1/36 × 10¹] m/s'
The output from above program should not be a surprise. It is an unknown dimensions with
a scaled unknown coherent unit. The library can't know what is the symbol of such unit
so it does its best and prints the unit in terms of units of base dimensions that formed
this particular unknown derived dimension.
In case we would like to print the result in terms of base units we can simply do the
following::
auto s = quantity_cast<unknown_coherent_unit>(result);
std::cout << "Speed: " << s << '\n'; // prints 'Speed: 20 m/s'
.. seealso::
Another good example of unknown dimension usage can be found in the
:ref:`box_example`::
std::cout << "float rise rate = " << box.fill_level(measured_mass) / fill_time << '\n';
Temporary Results
-----------------
In many cases there is nothing inherently wrong with having unknown dimensions and units
in your program. A typical example here are temporary results of a long calculation:
.. code-block::
:emphasize-lines: 5,7
auto some_long_calculation(Length auto d, Time auto t)
{
Speed auto s1 = avg_speed(d, t);
auto temp1 = s1 * 200q_km; // intermediate unknown dimension
Speed auto s2 = temp1 / 50q_km; // back to known dimensions again
Length auto d2 = s2 * 4q_h;
// ...
}
If a programmer wants to break the calculation to several lines/variables he/she does not
have to ensure that the intermediate results are of predefined dimensions or just a clear
science fiction :-) The final result will always be correct.
@@ -1,9 +0,0 @@
.. namespace:: units
Working with Unknown Units and Dimensions
=========================================
- what is an unknown unit?
- what is an unknown dimension?
- temporary result
- casting to the coherent unit