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 gravitykinetic_energy- energy of motionmechanical_energy- total mechanical work requiredmotor_energy- actual energy the motor must provide (including losses)
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_energyvalues is valid (same type) - Adding
gravitational_potential_energy+kinetic_energy→mechanical_energy(both are subtypes) - Dividing
mechanical_energybymotor_efficiency→motor_energy(equation-based conversion) - Passing
motor_energywherekinetic_energyis 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:
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>"]
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 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.
// 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
#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¶
Takeaways¶
- Semantic typing: Custom
quantity_spectypes 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::heightforgravitational_potential_energymakes 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