Files
mp-units/docs/tutorials/custom_quantity_specifications.md
Mateusz Pusz adda270c33 docs: a large refactoring of TOC
Some chapters moved to the top level and other moved to other places
2026-01-14 09:56:52 +01:00

316 lines
13 KiB
Markdown

# Tutorial 9: Custom Quantity Specifications
While the ISQ (International System of Quantities) provides many standard quantity types,
real-world applications often require domain-specific distinctions between quantities that
share the same dimension. For example, in an elevator system, both _cabin mass_ and
_passenger mass_ are masses (kg), but treating them as interchangeable could lead to
incorrect calculations or unsafe designs.
This tutorial teaches you how to define your own custom `quantity_spec` types to create
semantic distinctions within your domain. You'll learn to extend the ISQ hierarchy with
application-specific types that make APIs self-documenting and prevent subtle bugs at
compile time.
## Problem statement
Consider designing an elevator system. Engineers must calculate energy requirements
to size the motor properly:
- **Lifting energy** (potential): $E_p = m \cdot g \cdot h$
- **Acceleration energy** (kinetic): $E_k = \frac{1}{2} m v^2$
- **Mechanical energy**: $E_{mech} = E_p + E_k$
- **Motor input energy**: $E_{input} = E_{mech} / \eta$ (accounting for motor efficiency)
All these are energies measured in joules, but they represent different physical concepts:
- `gravitational_potential_energy` - energy stored by lifting mass against gravity
- `kinetic_energy` - energy of motion
- `mechanical_energy` - total mechanical work required
- `motor_energy` - actual energy the motor must provide (including losses)
!!! info "Why separate types matter?"
Without distinct quantity specifications, the type system cannot prevent you from accidentally
passing `kinetic_energy` where `gravitational_potential_energy` is expected, or using
`mechanical_energy` directly in place of `motor_energy`. These are all energies with
the same unit, but mixing them up leads to incorrect calculations.
**Well-defined conversion rules** ensure type safety while allowing valid operations:
- Adding two `gravitational_potential_energy` values is valid (same type)
- Adding `gravitational_potential_energy` + `kinetic_energy``mechanical_energy` (both are subtypes)
- Dividing `mechanical_energy` by `motor_efficiency``motor_energy` (equation-based conversion)
- Passing `motor_energy` where `kinetic_energy` is expected should fail at compile time
The same principle applies to masses: _cabin mass_, _passenger mass_, and _total mass_ are
all masses, but each has specific semantics that should be enforced by the type system.
## Your task
Complete the `qs` namespace by defining custom `quantity_spec` types following this
hierarchy:
<!-- TODO `total_mass` should be defined as `cabin_mass + passenger_mass` without the need for `quantity_cast` -->
```mermaid
flowchart TD
mass["<b>isq::mass</b><br>[kg]"]
mass --- cabin_mass["<b>cabin_mass</b>"]
mass --- passenger_mass["<b>passenger_mass</b>"]
mass --- total_mass["<b>total_mass</b>"]
height["<b>isq::height</b><br>[m]"]
height --- travel_height["<b>travel_height</b>"]
speed["<b>isq::speed</b><br>[m/s]"]
speed --- cruising_speed["<b>cruising_speed</b>"]
```
```mermaid
flowchart TD
efficiency["<b>isq::mechanical_efficiency</b><br>[one]"]
efficiency --- motor_efficiency["<b>motor_efficiency</b>"]
mechanical_energy["<b>isq::mechanical_energy</b><br>[J]"]
mechanical_energy --- motor_energy["<b>motor_energy</b><br><i>(isq::mechanical_energy / motor_efficiency)</i>"]
potential_energy["<b>isq::potential_energy</b><br>[J]"]
potential_energy --- gravitational_potential_energy["<b>gravitational_potential_energy</b><br><i>(isq::mass * isq::acceleration * isq::height)</i>"]
```
!!! tip
See
[Hierarchies of Derived Quantities](../users_guide/framework_basics/systems_of_quantities.md#hierarchies-of-derived-quantities)
for more details.
The rest of the code is provided—your task is to properly define these quantity
specifications so the type system enforces semantic correctness.
```cpp
// ce-embed height=650 compiler=clang2110 flags="-std=c++23 -stdlib=libc++ -O3" mp-units=trunk
#include <mp-units/core.h>
#include <mp-units/systems/isq.h>
#include <mp-units/systems/si.h>
#include <iostream>
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
namespace qs {
// TODO: Define custom quantity_spec types for:
// - cabin_mass
// - passenger_mass
// - total_mass
// - travel_height
// - cruising_speed
// - motor_efficiency
// - motor_energy
// - gravitational_potential_energy
}
struct elevator_trip {
quantity<qs::cabin_mass[kg]> cabin_mass;
quantity<qs::passenger_mass[kg]> passenger_load;
quantity<qs::travel_height[m]> height;
quantity<qs::cruising_speed[m/s]> speed;
quantity<qs::motor_efficiency[one]> efficiency;
};
constexpr quantity<qs::total_mass[kg]> total_mass(quantity<qs::cabin_mass[kg]> cabin,
quantity<qs::passenger_mass[kg]> passengers)
{
return quantity_cast<qs::total_mass>(cabin + passengers);
}
constexpr quantity<qs::gravitational_potential_energy[J]> lifting_energy(quantity<qs::total_mass[kg]> mass,
quantity<qs::travel_height[m]> height)
{
constexpr quantity g = isq::acceleration(1 * si::standard_gravity);
return mass * g * height;
}
constexpr quantity<isq::kinetic_energy[J]> acceleration_energy(quantity<qs::total_mass[kg]> mass,
quantity<qs::cruising_speed[m/s]> speed)
{
return 0.5 * mass * pow<2>(speed);
}
constexpr quantity<isq::mechanical_energy[J]> mechanical_energy(quantity<qs::gravitational_potential_energy[J]> ep,
quantity<isq::kinetic_energy[J]> ek)
{
return ep + ek;
}
constexpr quantity<qs::motor_energy[J]> required_input_energy(quantity<isq::mechanical_energy[J]> e_mech,
quantity<qs::motor_efficiency[one]> efficiency)
{
return qs::motor_energy(e_mech / efficiency);
}
int main()
{
elevator_trip trip{
.cabin_mass = 800.0 * kg,
.passenger_load = 400.0 * kg, // ~5 passengers
.height = 30.0 * m, // ~10 floors
.speed = 2.5 * m / s,
.efficiency = 0.85 // 85% efficient motor
};
auto m_total = total_mass(trip.cabin_mass, trip.passenger_load);
auto E_lift = lifting_energy(m_total, trip.height);
auto E_accel = acceleration_energy(m_total, trip.speed);
auto E_mech = mechanical_energy(E_lift, E_accel);
auto E_input = required_input_energy(E_mech, trip.efficiency);
std::cout << "Elevator trip analysis:\n";
std::cout << "- Cabin mass: " << trip.cabin_mass << '\n';
std::cout << "- Passenger load: " << trip.passenger_load << '\n';
std::cout << "- Total mass: " << m_total << '\n';
std::cout << "- Travel height: " << trip.height << '\n';
std::cout << "- Cruising speed: " << trip.speed << '\n';
std::cout << "- Motor efficiency: " << trip.efficiency.in(percent) << "\n\n";
std::cout << "Energy requirements:\n";
std::cout << "- Lifting energy (potential): " << E_lift.in(kJ) << '\n';
std::cout << "- Acceleration energy (kinetic): " << E_accel.in(kJ) << '\n';
std::cout << "- Mechanical energy: " << E_mech.in(kJ) << '\n';
std::cout << "- Required input energy: " << E_input.in(kJ) << "\n\n";
auto trip_duration = trip.height / trip.speed;
auto avg_power = E_input / trip_duration;
std::cout << "Estimated trip duration: " << trip_duration.in(s) << '\n';
std::cout << "Average power requirement: " << avg_power.in(kW) << '\n';
}
```
??? "Solution"
```cpp
#include <mp-units/core.h>
#include <mp-units/systems/isq.h>
#include <mp-units/systems/si.h>
#include <iostream>
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
namespace qs {
inline constexpr struct cabin_mass final : quantity_spec<isq::mass> {} cabin_mass;
inline constexpr struct passenger_mass final : quantity_spec<isq::mass> {} passenger_mass;
inline constexpr struct total_mass final : quantity_spec<isq::mass> {} total_mass;
inline constexpr struct travel_height final : quantity_spec<isq::height> {} travel_height;
inline constexpr struct cruising_speed final : quantity_spec<isq::speed> {} cruising_speed;
inline constexpr struct motor_efficiency final : quantity_spec<isq::mechanical_efficiency> {} motor_efficiency;
inline constexpr struct motor_energy final : quantity_spec<isq::mechanical_energy, isq::mechanical_energy / motor_efficiency> {} motor_energy;
inline constexpr struct gravitational_potential_energy final : quantity_spec<isq::potential_energy, isq::mass * isq::acceleration * isq::height> {} gravitational_potential_energy;
}
struct elevator_trip {
quantity<qs::cabin_mass[kg]> cabin_mass;
quantity<qs::passenger_mass[kg]> passenger_load;
quantity<qs::travel_height[m]> height;
quantity<qs::cruising_speed[m/s]> speed;
quantity<qs::motor_efficiency[one]> efficiency;
};
constexpr quantity<qs::total_mass[kg]> total_mass(quantity<qs::cabin_mass[kg]> cabin,
quantity<qs::passenger_mass[kg]> passengers)
{
return quantity_cast<qs::total_mass>(cabin + passengers);
}
constexpr quantity<qs::gravitational_potential_energy[J]> lifting_energy(quantity<qs::total_mass[kg]> mass,
quantity<qs::travel_height[m]> height)
{
constexpr quantity g = isq::acceleration(1 * si::standard_gravity);
return mass * g * height;
}
constexpr quantity<isq::kinetic_energy[J]> acceleration_energy(quantity<qs::total_mass[kg]> mass,
quantity<qs::cruising_speed[m/s]> speed)
{
return 0.5 * mass * pow<2>(speed);
}
constexpr quantity<isq::mechanical_energy[J]> mechanical_energy(quantity<qs::gravitational_potential_energy[J]> ep,
quantity<isq::kinetic_energy[J]> ek)
{
return ep + ek;
}
constexpr quantity<qs::motor_energy[J]> required_input_energy(quantity<isq::mechanical_energy[J]> e_mech,
quantity<qs::motor_efficiency[one]> efficiency)
{
return qs::motor_energy(e_mech / efficiency);
}
int main()
{
elevator_trip trip{
.cabin_mass = 800.0 * kg,
.passenger_load = 400.0 * kg, // ~5 passengers
.height = 30.0 * m, // ~10 floors
.speed = 2.5 * m / s,
.efficiency = 0.85 // 85% efficient motor
};
// Calculate total mass and energy components
auto m_total = total_mass(trip.cabin_mass, trip.passenger_load);
auto E_lift = lifting_energy(m_total, trip.height);
auto E_accel = acceleration_energy(m_total, trip.speed);
auto E_mech = mechanical_energy(E_lift, E_accel);
auto E_input = required_input_energy(E_mech, trip.efficiency);
std::cout << "Elevator trip analysis:\n";
std::cout << "- Cabin mass: " << trip.cabin_mass << '\n';
std::cout << "- Passenger load: " << trip.passenger_load << '\n';
std::cout << "- Total mass: " << m_total << '\n';
std::cout << "- Travel height: " << trip.height << '\n';
std::cout << "- Cruising speed: " << trip.speed << '\n';
std::cout << "- Motor efficiency: " << trip.efficiency.in(percent) << "\n\n";
std::cout << "Energy requirements:\n";
std::cout << "- Lifting energy (potential): " << E_lift.in(kJ) << '\n';
std::cout << "- Acceleration energy (kinetic): " << E_accel.in(kJ) << '\n';
std::cout << "- Mechanical energy: " << E_mech.in(kJ) << '\n';
std::cout << "- Required input energy: " << E_input.in(kJ) << "\n\n";
// Practical information for motor sizing
auto trip_duration = trip.height / trip.speed; // simplified: assumes constant speed
auto avg_power = E_input / trip_duration;
std::cout << "Estimated trip duration: " << trip_duration.in(s) << '\n';
std::cout << "Average power requirement: " << avg_power.in(kW) << '\n';
}
```
## References
- [User's Guide: Systems of Quantities](../users_guide/framework_basics/systems_of_quantities.md)
- [API Reference](../reference/api_reference.md)
## Takeaways
- **Semantic typing**: Custom `quantity_spec` types distinguish between semantically
different quantities that share the same dimension (_mass_, _energy_, etc.)
- **Compile-time safety**: The type system prevents mixing up _cabin mass_ with
_total mass_, or _gravitational potential energy_ with generic _energy_
- **Self-documenting APIs**: Function signatures clearly communicate what kind of
quantity each parameter expects
- **Equation-based specifications**: Using equation forms like
`isq::mass * isq::acceleration * isq::height` for `gravitational_potential_energy`
makes the physics explicit in the type system and requires correct ingredients
to be used in a quantity equation to get the desired outcome
- **Practical engineering**: This approach is valuable for real applications like
motor sizing, where mixing up different _masses_ or _energy_ types could lead to
incorrect designs