# 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:
```mermaid
flowchart TD
mass["isq::mass
[kg]"]
mass --- cabin_mass["cabin_mass"]
mass --- passenger_mass["passenger_mass"]
mass --- total_mass["total_mass"]
height["isq::height
[m]"]
height --- travel_height["travel_height"]
speed["isq::speed
[m/s]"]
speed --- cruising_speed["cruising_speed"]
```
```mermaid
flowchart TD
efficiency["isq::mechanical_efficiency
[one]"]
efficiency --- motor_efficiency["motor_efficiency"]
mechanical_energy["isq::mechanical_energy
[J]"]
mechanical_energy --- motor_energy["motor_energy
(isq::mechanical_energy / motor_efficiency)"]
potential_energy["isq::potential_energy
[J]"]
potential_energy --- gravitational_potential_energy["gravitational_potential_energy
(isq::mass * isq::acceleration * isq::height)"]
```
!!! 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
#include
#include
#include
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 cabin_mass;
quantity passenger_load;
quantity height;
quantity speed;
quantity efficiency;
};
constexpr quantity total_mass(quantity cabin,
quantity passengers)
{
return quantity_cast(cabin + passengers);
}
constexpr quantity lifting_energy(quantity mass,
quantity height)
{
constexpr quantity g = isq::acceleration(1 * si::standard_gravity);
return mass * g * height;
}
constexpr quantity acceleration_energy(quantity mass,
quantity speed)
{
return 0.5 * mass * pow<2>(speed);
}
constexpr quantity mechanical_energy(quantity ep,
quantity ek)
{
return ep + ek;
}
constexpr quantity required_input_energy(quantity e_mech,
quantity 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
#include
#include
#include
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
namespace qs {
inline constexpr struct cabin_mass final : quantity_spec {} cabin_mass;
inline constexpr struct passenger_mass final : quantity_spec {} passenger_mass;
inline constexpr struct total_mass final : quantity_spec {} total_mass;
inline constexpr struct travel_height final : quantity_spec {} travel_height;
inline constexpr struct cruising_speed final : quantity_spec {} cruising_speed;
inline constexpr struct motor_efficiency final : quantity_spec {} motor_efficiency;
inline constexpr struct motor_energy final : quantity_spec {} motor_energy;
inline constexpr struct gravitational_potential_energy final : quantity_spec {} gravitational_potential_energy;
}
struct elevator_trip {
quantity cabin_mass;
quantity passenger_load;
quantity height;
quantity speed;
quantity efficiency;
};
constexpr quantity total_mass(quantity cabin,
quantity passengers)
{
return quantity_cast(cabin + passengers);
}
constexpr quantity lifting_energy(quantity mass,
quantity height)
{
constexpr quantity g = isq::acceleration(1 * si::standard_gravity);
return mass * g * height;
}
constexpr quantity acceleration_energy(quantity mass,
quantity speed)
{
return 0.5 * mass * pow<2>(speed);
}
constexpr quantity mechanical_energy(quantity ep,
quantity ek)
{
return ep + ek;
}
constexpr quantity required_input_energy(quantity e_mech,
quantity 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