# 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