From 0f374f6e6a7f418559f42c2dc3e7a4479706a42e Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Mon, 27 Oct 2025 11:27:27 -0500 Subject: [PATCH 01/13] Introduced `base_unit_of` and `is_base_unit_v` in the quantities utilities. They provide direct information about the base unit of a quantity, interval, point or unit. --- lardataalg/Utilities/quantities.h | 183 +++++++++++++++++++----------- 1 file changed, 114 insertions(+), 69 deletions(-) diff --git a/lardataalg/Utilities/quantities.h b/lardataalg/Utilities/quantities.h index 8353a9e4..5f4ed5ee 100644 --- a/lardataalg/Utilities/quantities.h +++ b/lardataalg/Utilities/quantities.h @@ -252,6 +252,14 @@ namespace util::quantities { //------------------------------------------------------------------------ //--- Unit-related //------------------------------------------------------------------------ + /// Implementation of `is_base_unit_v`. + template + struct is_base_unit : std::false_type {}; + + /// Implementation of `base_unit_of`. + template + struct base_unit_extactor; + /// Trait: `true_type` if `U` is a `ScaledUnit`-based object. template struct has_unit; @@ -378,6 +386,19 @@ namespace util::quantities { }; // struct Prefix + /// Trait: `T` implements a `BaseUnit` interface. + template + constexpr bool is_base_unit_v = details::is_base_unit::value; + + /** + * @brief Trait: base unit of the specified object. + * + * Supports `Quantity`, `Unit`, `Interval` and `Point`. + * It also sort-of-supports `UnitBase` and the like. + */ + template + using base_unit_of = typename details::base_unit_extactor::type; + template > struct ScaledUnit { @@ -458,7 +479,7 @@ namespace util::quantities { template static constexpr bool sameBaseUnitAs() { - return std::is_same(); + return std::is_same>(); } /// Returns whether scaled unit `U` has the same base unit as this one. @@ -592,6 +613,71 @@ namespace util::quantities { /// Explicit conversion to the base quantity. explicit constexpr operator value_t() const { return value(); } + // -- BEGIN Access to the scaled unit ------------------------------------ + /// @name Access to the scaled unit. + /// @{ + + /// Returns an object with as type the scaled unit (`unit_t`). + static constexpr unit_t unit() { return {}; } + + /// Returns an object with as type the base unit (`baseunit_t`). + static constexpr baseunit_t baseUnit() { return {}; } + + /// Returns the full name of the unit, in a string-like object. + static auto unitName() { return unit_t::name(); } + + /// Returns the symbol of the unit, in a string-like object. + static auto unitSymbol() { return unit_t::symbol(); } + + /** + * @brief Returns whether this quantity has the same base unit as `OU`. + * @param OU any type with `baseunit_t` type + * (including `ScaledUnit`, `Quantity`, `Interval`...) + */ + template + static constexpr bool sameBaseUnitAs() + { + return unit_t::template sameBaseUnitAs(); + } + + /** + * @brief Returns whether this quantity has same unit and scale as `OU`. + * @param OU any type with `unit_t` type + * (including `ScaledUnit`, `Quantity`, `Interval`...) + */ + template + static constexpr bool sameUnitAs() + { + return unit_t::template sameUnitAs(); + } + + /// Whether `U` is a value type compatible with `value_t`. + template + static constexpr bool is_compatible_value_v = details::is_value_compatible_with_v; + + /// Whether `U` has (or is) a value type compatible with `value_t`. + template + static constexpr bool has_compatible_value_v = + details::has_value_compatible_with_v; + + /// Returns whether `U` is a value type compatible with `value_t`. + template + static constexpr bool isCompatibleValue() + { + return quantity_t::is_compatible_value_v; + } + + /// Returns whether `U` has (or is) a value type compatible with + /// `value_t`. + template + static constexpr bool hasCompatibleValue() + { + return quantity_t::has_compatible_value_v; + } + + /// @} + // -- END Access to the scaled unit -------------------------------------- + // -- BEGIN Asymmetric arithmetic operations ----------------------------- /** * @name Asymmetric operand arithmetic operations @@ -720,71 +806,6 @@ namespace util::quantities { /// @} // -- END Asymmetric arithmetic operations ------------------------------- - // -- BEGIN Access to the scaled unit ------------------------------------ - /// @name Access to the scaled unit. - /// @{ - - /// Returns an object with as type the scaled unit (`unit_t`). - static constexpr unit_t unit() { return {}; } - - /// Returns an object with as type the base unit (`baseunit_t`). - static constexpr baseunit_t baseUnit() { return {}; } - - /// Returns the full name of the unit, in a string-like object. - static auto unitName() { return unit_t::name(); } - - /// Returns the symbol of the unit, in a string-like object. - static auto unitSymbol() { return unit_t::symbol(); } - - /** - * @brief Returns whether this quantity has the same base unit as `OU`. - * @param OU any type with `baseunit_t` type - * (including `ScaledUnit`, `Quantity`, `Interval`...) - */ - template - static constexpr bool sameBaseUnitAs() - { - return unit_t::template sameBaseUnitAs(); - } - - /** - * @brief Returns whether this quantity has same unit and scale as `OU`. - * @param OU any type with `unit_t` type - * (including `ScaledUnit`, `Quantity`, `Interval`...) - */ - template - static constexpr bool sameUnitAs() - { - return unit_t::template sameUnitAs(); - } - - /// Whether `U` is a value type compatible with `value_t`. - template - static constexpr bool is_compatible_value_v = details::is_value_compatible_with_v; - - /// Whether `U` has (or is) a value type compatible with `value_t`. - template - static constexpr bool has_compatible_value_v = - details::has_value_compatible_with_v; - - /// Returns whether `U` is a value type compatible with `value_t`. - template - static constexpr bool isCompatibleValue() - { - return quantity_t::is_compatible_value_v; - } - - /// Returns whether `U` has (or is) a value type compatible with - /// `value_t`. - template - static constexpr bool hasCompatibleValue() - { - return quantity_t::has_compatible_value_v; - } - - /// @} - // -- END Access to the scaled unit -------------------------------------- - /// Convert this quantity into the specified one. template constexpr OQ convertInto() const @@ -1213,6 +1234,32 @@ namespace util::quantities::concepts::details { //------------------------------------------------------------------------------ //--- template implementation +//------------------------------------------------------------------------------ +/// Implementation of `is_base_unit_v` for actual base unit objects +/// (e.g. `BaseUnit`): asks the complete `BaseUnit` mandatory interface. +template +struct util::quantities::concepts::details::is_base_unit< + U, + std::enable_if_t, std::string_view> & + std::is_same_v, std::string_view>>> + : std::true_type {}; + +/// Implementation of `base_unit_of` supporting `Quantity`, `Unit`, `Interval` +/// and `Point` (and any class accidentally declaring a `baseunit_t` type). +template +struct util::quantities::concepts::details:: + base_unit_extactor> { + using type = typename Q::baseunit_t; +}; + +/// Implementation of `base_unit_of` supporting a base unit itself +/// (e.g. `BaseUnit`) by asking the complete `BaseUnit` mandatory interface. +template +struct util::quantities::concepts::details:: + base_unit_extactor>> { + using type = U; +}; + //------------------------------------------------------------------------------ //--- util::quantities::concepts::Prefix //------------------------------------------------------------------------------ @@ -1303,10 +1350,8 @@ constexpr auto util::quantities::concepts::Quantity::minus(Quantity template constexpr auto util::quantities::concepts::Quantity::operator/(Quantity const q) const - -> value_t + -> std::enable_if_t(), value_t> { - static_assert(sameBaseUnitAs(), "Can't divide quantities with different base unit"); - // if the two quantities have the same *scaled* unit, divide if constexpr (sameUnitAs()) { return value() / q.value(); } else { From 4fe098b3d7f629758be105c7f53c3fdd03d0297a Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Mon, 27 Oct 2025 11:31:49 -0500 Subject: [PATCH 02/13] Added Quantity::asValueType() to convert a quantity object to a different value type Idiomatically mean to explicitly allow for narrowing assignments (e.g. by explicitly converting a double-based quantity intoa float-based one before assignment). --- lardataalg/Utilities/quantities.h | 10 ++++++++++ test/Utilities/quantities_test.cc | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lardataalg/Utilities/quantities.h b/lardataalg/Utilities/quantities.h index 5f4ed5ee..61a1bf7f 100644 --- a/lardataalg/Utilities/quantities.h +++ b/lardataalg/Utilities/quantities.h @@ -598,6 +598,9 @@ namespace util::quantities { * Quantities are required to be in the same unit (unit scale may differ). * The value in `q` is converted from its native scale into the one of * this quantity. + * + * This assignment prevents narrowing. To force narrowing, convert the + * operand quantity to a number or "cast" it with `asValueType<>()`. */ template >* = nullptr> constexpr Quantity(Q q) @@ -813,6 +816,13 @@ namespace util::quantities { return OQ(*this); } + /// Returns a new quantity with same unit and value using value type `OT`. + template + constexpr Quantity asValueType() const + { + return Quantity{static_cast(value())}; + } + /** * @brief Returns a new quantity initialized with the specified value * @tparam U type to initialize the quantity with diff --git a/test/Utilities/quantities_test.cc b/test/Utilities/quantities_test.cc index ffc064c2..4570862c 100644 --- a/test/Utilities/quantities_test.cc +++ b/test/Utilities/quantities_test.cc @@ -120,7 +120,11 @@ void test_quantities_conversions() static_assert(std::is_same()), util::quantities::microseconds>()); - BOOST_TEST(t.convertInto() == 7'000'000_us); + BOOST_TEST(t_s.convertInto() == 7'000'000_us); + + static_assert( + std::is_same_v()), util::quantities::seconds_as>); + static_assert(t_s.asValueType() == t_s); } // test_quantities_conversions() From 54ce01d9953429e51289fa1331ce8117e38a87ef Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Mon, 27 Oct 2025 11:34:55 -0500 Subject: [PATCH 03/13] Added missing suffixes in quantities library --- lardataalg/Utilities/quantities.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lardataalg/Utilities/quantities.h b/lardataalg/Utilities/quantities.h index 61a1bf7f..fcb1c191 100644 --- a/lardataalg/Utilities/quantities.h +++ b/lardataalg/Utilities/quantities.h @@ -1276,6 +1276,8 @@ struct util::quantities::concepts::details:: template constexpr auto util::quantities::concepts::Prefix::names(bool Long /* = false */) { + if constexpr (std::is_same()) return Long ? "peta"sv : "E"sv; + if constexpr (std::is_same()) return Long ? "peta"sv : "P"sv; if constexpr (std::is_same()) return Long ? "tera"sv : "T"sv; if constexpr (std::is_same()) return Long ? "giga"sv : "G"sv; if constexpr (std::is_same()) return Long ? "mega"sv : "M"sv; @@ -1288,7 +1290,7 @@ constexpr auto util::quantities::concepts::Prefix::names(bool Long /* = false if constexpr (std::is_same()) return Long ? "nano"sv : "n"sv; if constexpr (std::is_same()) return Long ? "pico"sv : "p"sv; if constexpr (std::is_same()) return Long ? "femto"sv : "f"sv; - // TODO complete the long list of prefixes + if constexpr (std::is_same()) return Long ? "atto"sv : "a"sv; // backup; can't use `to_string()` because of `constexpr` requirement return Long ? "???"sv : "?"sv; From 9459b970c1242cb1a2dd787081341f5c65a9d44a Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Mon, 27 Oct 2025 11:38:09 -0500 Subject: [PATCH 04/13] Added support for user-enabled product and ratio between quantity objects Allows multiplicative math between two quantity objects (for example a millivolt quantity could be divided by a kiloohm one, resulting into a microampere one). Only binary operations are supported (e.g. no N/m/m = Pa). Each quantity relation needs to be explicitly enabled. --- .../Utilities/details/quantity_products.h | 288 ++++++++++++++++++ lardataalg/Utilities/quantities.h | 17 +- 2 files changed, 296 insertions(+), 9 deletions(-) create mode 100644 lardataalg/Utilities/details/quantity_products.h diff --git a/lardataalg/Utilities/details/quantity_products.h b/lardataalg/Utilities/details/quantity_products.h new file mode 100644 index 00000000..a5004e9d --- /dev/null +++ b/lardataalg/Utilities/details/quantity_products.h @@ -0,0 +1,288 @@ +/** + * @file lardataalg/Utilities/details/quantity_products.h + * @brief Infrastructure to describe products of quantity objects + * @author Gianluca Petrillo (petrillo@slac.stanford.edu) + * @date October 25, 2025 + * @see lardataalg/Utilities/quantities.h + * + * Purpose + * -------- + * + * The idea here is that with a simple declaration by the users of a relation + * between three units, e.g. that Volt is product of Ohm and Ampere units, + * mathematical operators are enabled to allow the two products (commutative) + * and the two ratios between quantity objects of those types. + * + * The declaration is performed via a preprocessor macro, + * `DECLARE_PRODUCT_INFO()`. + * + * Because C++ operators are binary, three-unit relations are in principle + * enough to describe all is needed. In case of relations between two groups + * of units of arbitrary size (for example, a one-to-three like W Ω = V V) + * intermediate opaque quantities may be needed (in the example, to express + * watts W = ((V Ω) V) we may need an intermediate X = (V Ω), which + * may be an actual quantity, like ampere in this case, or just a bridge to the + * next product W = X V). The automatic implementation is very tricky, if at all + * possible, due to the increasing combinatorics. If such use case is really + * needed, the current workaround is to declare all the intermediate quantities + * needed as actual quantities and then their relations. + * + * + * Implementation details + * ----------------------- + * + * The core element is an object that describes the relation between three + * quantities (A times B equal to P). This is a template specialization of + * `util::quantities::concepts::details::ProductDataType`, which includes the + * type of the product of its arguments: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * template <> struct UnitProductResult { + * using type = P; + * }; + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * The `+1` is the exponent of `B`, that means that `A` and `B` are being + * multiplied (`A * B = P`). For the ratio relationship (`A / B = P`), `-1` + * needs to be used instead. + * A complete set of relations should be declared: A * B = P, B * A = P, + * P / A = B and P / B = A. The macro `DECLARE_PRODUCT_INFO()` does that. + * + * + * + * + */ + +#ifndef LARDATAALG_UTILITIES_QUANTITIES_H +#error \ + "lardataalg/Utilities/details/quantity_products.h should only be included by lardataalg/Utilities/quantities.h" +#endif + +#ifndef LARDATAALG_UTILITIES_QUANTITIES_QUANTITY_PRODUCTS_H +#define LARDATAALG_UTILITIES_QUANTITIES_QUANTITY_PRODUCTS_H + +// C/C++ standard libraries +#include +#include +#include // std::declval() + +namespace util::quantities::concepts::details { + + // --------------------------------------------------------------------------- + /** + * @brief Type describing a product relation between three units. + * @tparam UA type of the first unit in the product + * @tparam UB type of the second unit in the product + * @tparam BExp (default: `+1`) exponent of `UB` in the product + * @see `UTIL_QUANTITIES_UNITPRODUCT()` + * + * The existence of a class specialization of this type declares a relation + * between three base units. If `BExp` is `+1`, the relation is `UA * UB = P`, + * while if `BExp` is `-1` the relation is `UA / UB = P`. + * The types `UA` and `UB` are specified (only) as template parameters, while + * the type `P` must be defined in the body of the template specialization, + * as a type called... `type`: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * template<> struct ProductType { + * using type = P; + * }; + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * For example: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * template<> struct ProductType { + * using type = Volt; + * }; + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Note that this does not implies related relationships (like + * `Ohm` * `Ampere` = `Volt`, `Volt` / `Ohm` = `Ampere` etc.), which need to + * be explicitly specified (that is `UTIL_QUANTITIES_UNITPRODUCT()` for you). + * + */ + template + struct UnitProductResult; + + // --------------------------------------------------------------------------- + // The dance to access `UnitProductResult` properties is open... + + /// Direct access to the base unit of the product of `QA` and `QB` + /// (ratio if `BExp == -1`). + template + using BaseUnitProductResult_t = + typename UnitProductResult, base_unit_of, BExp>::type; + + /// Direct access to the base unit of the ratio of `QN` and `QD`. + template + using BaseUnitRatioResult_t = BaseUnitProductResult_t; + + /// Trait: there is a product/ratio defined between the units of `QA` and `QB`. + template + struct CanMultiplyQuantities : std::false_type {}; + + /// Implementation of `CanMultiplyQuantities` for supported quantities. + template + struct CanMultiplyQuantities>> + : std::true_type {}; + + /// Shortcut trait to query of quantity ratio. + template + using CanDivideQuantities = CanMultiplyQuantities; + + /** + * @brief Full set of traits for the product between two quantities. + * @tparam QA first quantity to be multiplied + * @tparam QB second quantity to be multiplied (or denominator if `BExp == -1`) + * @tparam BExp (default: `+1`) `+1` for product, `-1` for ratio + * + * See `QuantitiesProductResult_t` and `QuantitiesRatioResult_t` for details. + */ + template + struct QuantitiesProductResult; + + /// Implementation of `QuantitiesProductResult` when the product is valid. + template + struct QuantitiesProductResult::value>> { + // I needed to separate division and product because GCC 12.1 instantiating + // std::conditional would complain about + // the true branch being out of range... + using baseunit_t = BaseUnitProductResult_t; + using ratio_t = std::ratio_multiply; + using value_t = + decltype(std::declval() * std::declval()); + using type = Quantity, value_t>; + }; // QuantitiesProductResult + + /// Implementation of `QuantitiesProductResult` when the ratio is valid. + template + struct QuantitiesProductResult::value>> { + using baseunit_t = BaseUnitProductResult_t; + using ratio_t = std::ratio_divide; + using value_t = + decltype(std::declval() / std::declval()); + using type = Quantity, value_t>; + }; // QuantitiesProductResult + + /// Shortcut trait to query of quantity ratio result information. + template + using QuantitiesRatioResult = QuantitiesProductResult; + + /** + * @brief Full set of traits for the product between two quantities. + * @tparam QA first quantity to be multiplied + * @tparam QB second quantity to be multiplied (or denominator if `BExp == -1`) + * @tparam BExp (default: `+1`) `+1` for product, `-1` for ratio + * + * The class is defined only if the requested operation (`QA*QB` or `QA/QB`, + * depending on `BExp`) is valid (i.e. if it was declared with + * `UTIL_QUANTITIES_UNITPRODUCT()` or equivalent). + * + * The information of the result of the operation includes: + * * `type`: type of the `Quantity` result of the product or ratio. + * * `baseunit_t`: type of the base unit of the result. + * * `ratio_t`: the scaling of the base unit. + * * `value_t`: type of the value contained in the quantity. + * + */ + template + using QuantitiesProductResult_t = typename QuantitiesProductResult::type; + + /// Shortcut trait to query of quantity ratio `QA/QB`. + /// @see `QuantitiesProductResult_t` + template + using QuantitiesRatioResult_t = QuantitiesProductResult_t; + + // --------------------------------------------------------------------------- + +} // namespace util::quantities::concepts::details + +namespace util::quantities::concepts { + + // --------------------------------------------------------------------------- + /// Evaluates to `T` if that's a valid type, otherwise fails to compile. + template + using only_if_exists_t = std::enable_if_t, void>, T>; + + // --------------------------------------------------------------------------- + /// Returns the product of two quantity objects, if that's declared. + /// @see `UTIL_QUANTITIES_UNITPRODUCT()` + template + constexpr only_if_exists_t> operator*(QA a, QB b) + { + + using ResultInfo = details::QuantitiesProductResult; + using value_t = typename ResultInfo::value_t; + using quantity_t = typename ResultInfo::type; + + return quantity_t{static_cast(a.value() * b.value())}; + + } // operator* (quantity, quantity) + + /// Returns the ratio of two quantity objects, if that's declared. + /// @see `UTIL_QUANTITIES_UNITPRODUCT()` + template + constexpr only_if_exists_t> operator/(QN num, QD den) + { + + using ResultInfo = details::QuantitiesRatioResult; + using value_t = typename ResultInfo::value_t; + using quantity_t = typename ResultInfo::type; + + return quantity_t{static_cast(num.value() / den.value())}; + + } // operator/ (quantity, quantity) + + // --------------------------------------------------------------------------- + +} // namespace util::quantities::concepts + +// ----------------------------------------------------------------------------- +/** + * @brief Declares a relation between units. + * @tparam UA type of the first unit in the product + * @tparam UB type of the second unit in the product + * @tparam UP type of the unit of the product `UA * UB` + * + * It defines `ProductType` template specialization like: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * template<> struct ProductType { using type = UP; }; + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * that describe the four relations between the three units (`UA * UB = UP`, + * `UB * UA = UP`, `UP / UA = UB`, `UP / UB = UA`). + * + * It requires all the operands to be base unit types (no `ScaledUnit`), + * and that the product unit is neither of the factor ones. + */ +#define UTIL_QUANTITIES_UNITPRODUCT(UA, UB, UP) \ + namespace util::quantities::concepts::details { \ + static_assert(is_base_unit_v, \ + "The first type does not meet base unit class requirements."); \ + static_assert(is_base_unit_v, \ + "The second type does not meet base unit class requirements."); \ + static_assert(is_base_unit_v, \ + "The third type does not meet base unit class requirements."); \ + static_assert(!std::is_same_v, "A factor can't have the same unit as the product."); \ + static_assert(!std::is_same_v, "B factor can't have the same unit as the product."); \ + template <> \ + struct UnitProductResult { \ + using type = UP; \ + }; \ + template <> \ + struct UnitProductResult { \ + using type = UP; \ + }; \ + template <> \ + struct UnitProductResult { \ + using type = UB; \ + }; \ + template <> \ + struct UnitProductResult { \ + using type = UA; \ + }; \ + } + +// ----------------------------------------------------------------------------- + +#endif // LARDATAALG_UTILITIES_QUANTITIES_QUANTITY_PRODUCTS_H diff --git a/lardataalg/Utilities/quantities.h b/lardataalg/Utilities/quantities.h index fcb1c191..9d0dab41 100644 --- a/lardataalg/Utilities/quantities.h +++ b/lardataalg/Utilities/quantities.h @@ -732,7 +732,8 @@ namespace util::quantities { /// Division by a quantity, returns a pure number. template - constexpr value_t operator/(Quantity q) const; + constexpr std::enable_if_t(), value_t> operator/( + Quantity q) const; /// Add the `other` quantity (possibly concerted) to this one. template @@ -824,7 +825,7 @@ namespace util::quantities { } /** - * @brief Returns a new quantity initialized with the specified value + * @brief Returns a new quantity initialized with the specified value. * @tparam U type to initialize the quantity with * @param value the value to initialize the quantity with * @return a new `Quantity` object initialized with `value` @@ -908,13 +909,6 @@ namespace util::quantities { } //@} - //@{ - /// Multiplication between quantities is forbidden. - template - constexpr auto operator*(Quantity, Quantity) - -> decltype(std::declval() * std::declval()) = delete; - //@} - //@{ // Division by a scalar. template @@ -1695,6 +1689,11 @@ namespace std { } // namespace std +//------------------------------------------------------------------------------ +//--- products of quantities implementation + +#include "details/quantity_products.h" + //------------------------------------------------------------------------------ #endif // LARDATAALG_UTILITIES_QUANTITIES_H From 8e15eda9deb1a47d8669cdb127599bdf0e0eb86d Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Mon, 27 Oct 2025 11:45:40 -0500 Subject: [PATCH 05/13] Added new electromagnetism quantities: ampere, ohm. Also introduced a unit test for them, and some relations among them and optionally (when all relevant headers are included) relation RC = t. --- .../Utilities/quantities/electromagnetism.h | 459 +++++++++++++++++- lardataalg/Utilities/quantities/spacetime.h | 14 + test/Utilities/CMakeLists.txt | 5 + test/Utilities/electromagnetism_test.cc | 384 +++++++++++++++ test/Utilities/quantities_test.cc | 2 +- 5 files changed, 862 insertions(+), 2 deletions(-) create mode 100644 test/Utilities/electromagnetism_test.cc diff --git a/lardataalg/Utilities/quantities/electromagnetism.h b/lardataalg/Utilities/quantities/electromagnetism.h index 1914e1c7..b7b1b2ab 100644 --- a/lardataalg/Utilities/quantities/electromagnetism.h +++ b/lardataalg/Utilities/quantities/electromagnetism.h @@ -9,10 +9,17 @@ * are defined based on the following units: * * coulomb (fC, pC, nC, uC, mC, C) * * volt (uV, mV, V, kV, MV, GV) + * * ampere (pA, nA, uA, mA, A, kA) + * * ohm (Ω, kΩ, MΩ, GΩ) + * * farad (fF, pF, nF, uF, mF, F) + * + * Some relations between them (e.g. Ω·A = V) are also registered. * * This is a header-only library. * - * @todo Also belong here: ampere, volt, farad, ohm... + * @note If the following headers are loaded, relationship between these and + * some of their quantities are registered: + * * `spacetime.h` (e.g. Ω·F = s). * */ @@ -43,6 +50,21 @@ namespace util::quantities { static constexpr auto name = "volt"sv; }; + struct Ampere : public concepts::UnitBase { + static constexpr auto symbol = "A"sv; + static constexpr auto name = "ampere"sv; + }; + + struct Ohm : public concepts::UnitBase { + static constexpr auto symbol = "Ω"sv; + static constexpr auto name = "ohm"sv; + }; + + struct Farad : public concepts::UnitBase { + static constexpr auto symbol = "F"sv; + static constexpr auto name = "farad"sv; + }; + } // namespace units // -- BEGIN Charge ----------------------------------------------------------- @@ -212,6 +234,236 @@ namespace util::quantities { /// @} // -- END Electric potential ------------------------------------------------- + // -- BEGIN Current ---------------------------------------------------------- + /** + * @name Current quantities + * + * These current quantities are tied to `util::quantities::units::Ampere`. + * A few options are provided: + * + * * most general template, `scaled_ampere`, allowing to choose both the + * scale of the unit (e.g. `std::pico` for picoampere) and the type of + * the numerical representation + * * generic templates (e.g. `ampere_as`), allowing to choose which numerical + * representation to use + * * double precision (e.g. `ampere`), ready for use + * + */ + /// @{ + + /// The most generic `units::Ampere`-based quantity. + template + using scaled_ampere = concepts::scaled_quantity; + + // + // ampere + // + /// Type of current stored in ampere. + template + using ampere_as = scaled_ampere, T>; + + /// Type of current stored in amperes, in double precision. + using ampere = ampere_as<>; + + // + // milliampere + // + /// Type of current stored in milliampere. + template + using milliampere_as = concepts::rescale, std::milli>; + + /// Type of current stored in milliampere, in double precision. + using milliampere = milliampere_as<>; + + // + // kiloampere + // + /// Type of current stored in kiloampere. + template + using kiloampere_as = concepts::rescale, std::kilo>; + + /// Type of current stored in kiloampere, in double precision. + using kiloampere = kiloampere_as<>; + + // + // microampere + // + /// Type of current stored in microampere. + template + using microampere_as = concepts::rescale, std::micro>; + + /// Type of current stored in microampere, in double precision. + using microampere = microampere_as<>; + + // + // nanoampere + // + /// Type of current stored in nanoampere. + template + using nanoampere_as = concepts::rescale, std::nano>; + + /// Type of current stored in nanoampere, in double precision. + using nanoampere = nanoampere_as<>; + + // + // picoampere + // + /// Type of current stored in picoampere. + template + using picoampere_as = concepts::rescale, std::pico>; + + /// Type of current stored in picoampere, in double precision. + using picoampere = picoampere_as<>; + + // -- END Current ------------------------------------------------------------ + + // -- BEGIN Impedance -------------------------------------------------------- + /** + * @name Impedance and resistance quantities + * + * These impedance quantities are tied to `util::quantities::units::Ohm`. + * A few options are provided: + * + * * most general template, `scaled_ohm`, allowing to choose both the + * scale of the unit (e.g. `std::kilo` for kiloohm) and the type of + * the numerical representation + * * generic templates (e.g. `ohm_as`), allowing to choose which numerical + * representation to use + * * double precision (e.g. `ohm`), ready for use + * + */ + /// @{ + + /// The most generic `units::Ohm`-based quantity. + template + using scaled_ohm = concepts::scaled_quantity; + + // + // ohm + // + /// Type of potential stored in ohm. + template + using ohm_as = scaled_ohm, T>; + + /// Type of potential stored in ohms, in double precision. + using ohm = ohm_as<>; + + // + // kiloohm + // + /// Type of potential stored in kiloohm. + template + using kiloohm_as = concepts::rescale, std::kilo>; + + /// Type of potential stored in kiloohm, in double precision. + using kiloohm = kiloohm_as<>; + + // + // megaohm + // + /// Type of potential stored in megaohm. + template + using megaohm_as = concepts::rescale, std::mega>; + + /// Type of potential stored in megaohm, in double precision. + using megaohm = megaohm_as<>; + + // + // gigaohm + // + /// Type of potential stored in gigaohm. + template + using gigaohm_as = concepts::rescale, std::giga>; + + /// Type of potential stored in gigaohm, in double precision. + using gigaohm = gigaohm_as<>; + + /// @} + // -- END Impedance ---------------------------------------------------------- + + // -- BEGIN Capacitance ------------------------------------------------------ + /** + * @name Capacitance quantities + * + * These capacitance quantities are tied to `util::quantities::units::Farad`. + * A few options are provided: + * + * * most general template, `scaled_farad`, allowing to choose both the + * scale of the unit (e.g. `std::pico` for picofarad) and the type of + * the numerical representation + * * generic templates (e.g. `farad_as`), allowing to choose which numerical + * representation to use + * * double precision (e.g. `farad`), ready for use + * + */ + /// @{ + + /// The most generic `units::Farad`-based quantity. + template + using scaled_farad = concepts::scaled_quantity; + + // + // farad + // + /// Type of charge stored in farad. + template + using farad_as = scaled_farad, T>; + + /// Type of charge stored in farads, in double precision. + using farad = farad_as<>; + + // + // millifarad + // + /// Type of charge stored in millifarad. + template + using millifarad_as = concepts::rescale, std::milli>; + + /// Type of charge stored in millifarad, in double precision. + using millifarad = millifarad_as<>; + + // + // microfarad + // + /// Type of charge stored in microfarad. + template + using microfarad_as = concepts::rescale, std::micro>; + + /// Type of charge stored in microfarad, in double precision. + using microfarad = microfarad_as<>; + + // + // nanofarad + // + /// Type of charge stored in nanofarad. + template + using nanofarad_as = concepts::rescale, std::nano>; + + /// Type of charge stored in nanofarad, in double precision. + using nanofarad = nanofarad_as<>; + + // + // picofarad + // + /// Type of charge stored in picofarad. + template + using picofarad_as = concepts::rescale, std::pico>; + + /// Type of charge stored in picofarad, in double precision. + using picofarad = picofarad_as<>; + + // + // femtofarad + // + /// Type of charge stored in femtofarad. + template + using femtofarad_as = concepts::rescale, std::femto>; + + /// Type of charge stored in femtofarad, in double precision. + using femtofarad = femtofarad_as<>; + + // -- END Capacitance -------------------------------------------------------- + /** * @brief Literal constants for quantities. * @@ -376,10 +628,215 @@ namespace util::quantities { } // @} + // @{ + /// Literal ampere value. + constexpr ampere operator""_A(long double v) + { + return ampere{static_cast(v)}; + } + constexpr ampere operator""_A(unsigned long long int v) + { + return ampere{static_cast(v)}; + } + // @} + + // @{ + /// Literal kiloampere value. + constexpr kiloampere operator""_kA(long double v) + { + return kiloampere{static_cast(v)}; + } + constexpr kiloampere operator""_kA(unsigned long long int v) + { + return kiloampere{static_cast(v)}; + } + // @} + + // @{ + /// Literal milliampere value. + constexpr milliampere operator""_mA(long double v) + { + return milliampere{static_cast(v)}; + } + constexpr milliampere operator""_mA(unsigned long long int v) + { + return milliampere{static_cast(v)}; + } + // @} + + // @{ + /// Literal microampere value. + constexpr microampere operator""_uA(long double v) + { + return microampere{static_cast(v)}; + } + constexpr microampere operator""_uA(unsigned long long int v) + { + return microampere{static_cast(v)}; + } + // @} + + // @{ + /// Literal nanoampere value. + constexpr nanoampere operator""_nA(long double v) + { + return nanoampere{static_cast(v)}; + } + constexpr nanoampere operator""_nA(unsigned long long int v) + { + return nanoampere{static_cast(v)}; + } + // @} + + // @{ + /// Literal picoampere value. + constexpr picoampere operator""_pA(long double v) + { + return picoampere{static_cast(v)}; + } + constexpr picoampere operator""_pA(unsigned long long int v) + { + return picoampere{static_cast(v)}; + } + // @} + + // @{ + /// Literal ohm value. + constexpr ohm operator""_ohm(long double v) + { + return ohm{static_cast(v)}; + } + constexpr ohm operator""_ohm(unsigned long long int v) + { + return ohm{static_cast(v)}; + } + // @} + + // @{ + /// Literal kiloohm value. + constexpr kiloohm operator""_kohm(long double v) + { + return kiloohm{static_cast(v)}; + } + constexpr kiloohm operator""_kohm(unsigned long long int v) + { + return kiloohm{static_cast(v)}; + } + // @} + + // @{ + /// Literal megaohm value. + constexpr megaohm operator""_Mohm(long double v) + { + return megaohm{static_cast(v)}; + } + constexpr megaohm operator""_Mohm(unsigned long long int v) + { + return megaohm{static_cast(v)}; + } + // @} + + // @{ + /// Literal gigaohm value. + constexpr gigaohm operator""_Gohm(long double v) + { + return gigaohm{static_cast(v)}; + } + constexpr gigaohm operator""_Gohm(unsigned long long int v) + { + return gigaohm{static_cast(v)}; + } + // @} + + // @{ + /// Literal farad value. + constexpr farad operator""_F(long double v) + { + return farad{static_cast(v)}; + } + constexpr farad operator""_F(unsigned long long int v) + { + return farad{static_cast(v)}; + } + // @} + + // @{ + /// Literal millifarad value. + constexpr millifarad operator""_mF(long double v) + { + return millifarad{static_cast(v)}; + } + constexpr millifarad operator""_mF(unsigned long long int v) + { + return millifarad{static_cast(v)}; + } + // @} + + // @{ + /// Literal microfarad value. + constexpr microfarad operator""_uF(long double v) + { + return microfarad{static_cast(v)}; + } + constexpr microfarad operator""_uF(unsigned long long int v) + { + return microfarad{static_cast(v)}; + } + // @} + + // @{ + /// Literal nanofarad value. + constexpr nanofarad operator""_nF(long double v) + { + return nanofarad{static_cast(v)}; + } + constexpr nanofarad operator""_nF(unsigned long long int v) + { + return nanofarad{static_cast(v)}; + } + // @} + + // @{ + /// Literal picofarad value. + constexpr picofarad operator""_pF(long double v) + { + return picofarad{static_cast(v)}; + } + constexpr picofarad operator""_pF(unsigned long long int v) + { + return picofarad{static_cast(v)}; + } + // @} + + // @{ + /// Literal femtofarad value. + constexpr femtofarad operator""_fF(long double v) + { + return femtofarad{static_cast(v)}; + } + constexpr femtofarad operator""_fF(unsigned long long int v) + { + return femtofarad{static_cast(v)}; + } + // @} + } // electromagnetism_literals } // namespace util::quantities +// --- BEGIN Special relations ------------------------------------------------- + +// unit relations (they are defined in `util::quantities::concepts::details`, +// so the `util::quantities::units` namespace can be shortened) +UTIL_QUANTITIES_UNITPRODUCT(units::Farad, units::Volt, units::Coulomb); +UTIL_QUANTITIES_UNITPRODUCT(units::Ohm, units::Ampere, units::Volt); + +#ifdef LARDATAALG_UTILITIES_QUANTITIES_SPACETIME_H +UTIL_QUANTITIES_UNITPRODUCT(units::Ohm, units::Farad, units::Second); +#endif + +// --- END Special relations --------------------------------------------------- + //------------------------------------------------------------------------------ #endif // LARDATAALG_UTILITIES_QUANTITIES_ELECTROMAGNETISM_H diff --git a/lardataalg/Utilities/quantities/spacetime.h b/lardataalg/Utilities/quantities/spacetime.h index 8ead1788..6aa77eb1 100644 --- a/lardataalg/Utilities/quantities/spacetime.h +++ b/lardataalg/Utilities/quantities/spacetime.h @@ -9,6 +9,10 @@ * defined based on the following units: * * seconds (ps, ns, us, ms, s), with intervals in `intervals` namespace * + * @note If the following headers are loaded, relationship between these and + * some of their quantities are registered: + * * `electromagnetism.h` (e.g. Ω·F = s). + * * This is a header-only library. * */ @@ -850,6 +854,16 @@ namespace util::quantities { } // namespace util::quantities +// --- BEGIN Special relations ------------------------------------------------- + +// unit relations (they are defined in `util::quantities::concepts::details`, +// so the `util::quantities::units` namespace can be shortened) +#ifdef LARDATAALG_UTILITIES_QUANTITIES_ELECTROMAGNETISM_H +UTIL_QUANTITIES_UNITPRODUCT(units::Ohm, units::Farad, units::Second); +#endif + +// --- END Special relations --------------------------------------------------- + //------------------------------------------------------------------------------ #endif // LARDATAALG_UTILITIES_QUANTITIES_SPACETIME_H diff --git a/test/Utilities/CMakeLists.txt b/test/Utilities/CMakeLists.txt index a5773b44..bfc2ea58 100644 --- a/test/Utilities/CMakeLists.txt +++ b/test/Utilities/CMakeLists.txt @@ -56,6 +56,11 @@ cet_test(energy_test USE_BOOST_UNIT lardataalg::UtilitiesHeaders ) +cet_test(electromagnetism_test USE_BOOST_UNIT + LIBRARIES PRIVATE + lardataalg::UtilitiesHeaders +) + cet_test(datasize_test USE_BOOST_UNIT LIBRARIES PRIVATE lardataalg::UtilitiesHeaders diff --git a/test/Utilities/electromagnetism_test.cc b/test/Utilities/electromagnetism_test.cc new file mode 100644 index 00000000..970080e7 --- /dev/null +++ b/test/Utilities/electromagnetism_test.cc @@ -0,0 +1,384 @@ +/** + * @file test/Utilities/electromagnetism_test.cc + * @brief Unit test for `lardataalg/Utilities/quantities/electromagnetism.h` header. + * @author Gianluca Petrillo (petrillo@slac.stanford.edu) + * @date January 13, 2020 + * @see `lardataalg/Utilities/quantities.h` + * + * This test covers only the space units of `electromagnetism.h`. + * It's not overwhelmingly complete, either. + */ + +// Boost libraries +#define BOOST_TEST_MODULE (electromagnetism_test) +#include + +// LArSoft libraries +#include "lardataalg/Utilities/quantities/electromagnetism.h" +#include "lardataalg/Utilities/quantities/spacetime.h" + +// C/C++ standard libraries +#include +#include // std::decay_t<> + +// ----------------------------------------------------------------------------- +template +constexpr bool equalityTest(T v, R ref) +{ + // if the reference value is 0, `v` must be below tolerance; + // otherwise, `v` relative difference from reference must be below tolerance + constexpr double tol = 1e-7; + + auto abs = [](auto v) { return v < decltype(v){0} ? -v : v; }; + + if (ref == static_cast(0)) + return abs(v) <= static_cast(tol); + else + return abs(v / ref - 1.0) <= tol; +} + +// ----------------------------------------------------------------------------- +void test_charge_literals() +{ + + using namespace util::quantities::electromagnetism_literals; + + // the charge out of a single photoelectron of an ICARUS PMT (gain ~4x10^6) + constexpr auto Q_pC = 0.640870612_pC; + static_assert(std::is_same()); + BOOST_TEST(Q_pC.value() == 0.640870612); + static_assert(equalityTest(Q_pC.value(), 0.640870612)); + std::cout << "Tested " << Q_pC << std::endl; + + constexpr auto Q_C = 6.40870612e-13_C; + static_assert(std::is_same()); + BOOST_TEST(Q_C.value() == 6.40870612e-13); + static_assert(equalityTest(Q_C.value(), 6.40870612e-13)); + static_assert(equalityTest(Q_C, Q_pC)); + std::cout << "Tested " << Q_C << std::endl; + + constexpr auto Q_mC = 6.40870612e-10_mC; + static_assert(std::is_same()); + BOOST_TEST(Q_mC.value() == 6.40870612e-10); + static_assert(equalityTest(Q_mC.value(), 6.40870612e-10)); + static_assert(equalityTest(Q_mC, Q_pC)); + std::cout << "Tested " << Q_mC << std::endl; + + constexpr auto Q_uC = 6.40870612e-7_uC; + static_assert(std::is_same()); + BOOST_TEST(Q_uC.value() == 6.40870612e-7); + static_assert(equalityTest(Q_uC.value(), 6.40870612e-7)); + static_assert(equalityTest(Q_uC, Q_pC)); + std::cout << "Tested " << Q_uC << std::endl; + + constexpr auto Q_nC = 0.000640870612_nC; + static_assert(std::is_same()); + BOOST_TEST(Q_nC.value() == 0.000640870612); + static_assert(equalityTest(Q_nC.value(), 0.000640870612)); + static_assert(equalityTest(Q_nC, Q_pC)); + std::cout << "Tested " << Q_nC << std::endl; + + constexpr auto Q_fC = 640.870612_fC; + static_assert(std::is_same()); + BOOST_TEST(Q_fC.value() == 640.870612); + static_assert(equalityTest(Q_fC.value(), 640.870612)); + static_assert(equalityTest(Q_fC, Q_pC)); + std::cout << "Tested " << Q_fC << std::endl; + +} // test_charge_literals() + +// ----------------------------------------------------------------------------- +void test_potential_literals() +{ + + using namespace util::quantities::electromagnetism_literals; + + // bias voltage of the collection plane of ICARUS detector + // (apparently too low so that it affects wire plane "transparency") + constexpr auto V_V = 250_V; + static_assert(std::is_same()); + BOOST_TEST(V_V.value() == 250); + static_assert(equalityTest(V_V.value(), 250)); + std::cout << "Tested " << V_V << std::endl; + + constexpr auto V_mV = 250'000_mV; + static_assert(std::is_same()); + BOOST_TEST(V_mV.value() == 250'000); + static_assert(equalityTest(V_mV.value(), 250'000)); + static_assert(equalityTest(V_mV, V_V)); + std::cout << "Tested " << V_mV << std::endl; + + constexpr auto V_uV = 250'000'000.0_uV; + static_assert(std::is_same()); + BOOST_TEST(V_uV.value() == 250'000'000); + static_assert(equalityTest(V_uV.value(), 250'000'000)); + static_assert(equalityTest(V_uV, V_V)); + std::cout << "Tested " << V_uV << std::endl; + + constexpr auto V_kV = 0.25_kV; + static_assert(std::is_same()); + BOOST_TEST(V_kV.value() == 0.25); + static_assert(equalityTest(V_kV.value(), 0.25)); + static_assert(equalityTest(V_kV, V_V)); + std::cout << "Tested " << V_kV << std::endl; + + constexpr auto V_MV = 2.5e-4_MV; + static_assert(std::is_same()); + BOOST_TEST(V_MV.value() == 2.5e-4); + static_assert(equalityTest(V_MV.value(), 2.5e-4)); + static_assert(equalityTest(V_MV, V_V)); + std::cout << "Tested " << V_MV << std::endl; + + constexpr auto V_GV = 2.5e-7_GV; + static_assert(std::is_same()); + BOOST_TEST(V_GV.value() == 2.5e-7); + static_assert(equalityTest(V_GV.value(), 2.5e-7)); + static_assert(equalityTest(V_GV, V_V)); + std::cout << "Tested " << V_GV << std::endl; + +} // test_potential_literals() + +// ----------------------------------------------------------------------------- +void test_current_literals() +{ + + using namespace util::quantities::electromagnetism_literals; + + // current by a single drifting electron in MicroBooNE + constexpr auto i_pA = -5.681e-5_pA; + static_assert(std::is_same()); + BOOST_TEST(i_pA.value() == -5.681e-5); + static_assert(equalityTest(i_pA.value(), -5.681e-5)); + std::cout << "Tested " << i_pA << std::endl; + + constexpr auto i_nA = -5.681e-8_nA; + static_assert(std::is_same()); + BOOST_TEST(i_nA.value() == -5.681e-8); + static_assert(equalityTest(i_nA.value(), -5.681e-8)); + static_assert(equalityTest(i_nA, i_pA)); + std::cout << "Tested " << i_nA << std::endl; + + constexpr auto i_uA = -5.681e-11_uA; + static_assert(std::is_same()); + BOOST_TEST(i_uA.value() == -5.681e-11); + static_assert(equalityTest(i_uA.value(), -5.681e-11)); + static_assert(equalityTest(i_uA, i_pA)); + std::cout << "Tested " << i_uA << std::endl; + + constexpr auto i_mA = -5.681e-14_mA; + static_assert(std::is_same()); + BOOST_TEST(i_mA.value() == -5.681e-14); + static_assert(equalityTest(i_mA.value(), -5.681e-14)); + static_assert(equalityTest(i_mA, i_pA)); + std::cout << "Tested " << i_mA << std::endl; + + constexpr auto i_A = -5.681e-17_A; + static_assert(std::is_same()); + BOOST_TEST(i_A.value() == -5.681e-17); + static_assert(equalityTest(i_A.value(), -5.681e-17)); + static_assert(equalityTest(i_A, i_pA)); + std::cout << "Tested " << i_A << std::endl; + +} // test_current_literals() + +// ----------------------------------------------------------------------------- +void test_resistance_literals() +{ + + using namespace util::quantities::electromagnetism_literals; + + // series resistance in ICARUS PMT dynode multiplication circuit + // (M. Babicz et al 2018 JINST 13 P10030) + constexpr auto R_ohm = 3.76e6_ohm; + static_assert(std::is_same()); + BOOST_TEST(R_ohm.value() == 3.76e6); + static_assert(equalityTest(R_ohm.value(), 3.76e6)); + std::cout << "Tested " << R_ohm << std::endl; + + constexpr auto R_kohm = 3.76e3_kohm; + static_assert(std::is_same()); + BOOST_TEST(R_kohm.value() == 3.76e3); + static_assert(equalityTest(R_kohm.value(), 3.76e3)); + static_assert(equalityTest(R_kohm, R_ohm)); + std::cout << "Tested " << R_kohm << std::endl; + + constexpr auto R_Mohm = 3.76_Mohm; + static_assert(std::is_same()); + BOOST_TEST(R_Mohm.value() == 3.76); + static_assert(equalityTest(R_Mohm.value(), 3.76)); + static_assert(equalityTest(R_Mohm, R_ohm)); + std::cout << "Tested " << R_Mohm << std::endl; + + constexpr auto R_Gohm = 3.76e-3_Gohm; + static_assert(std::is_same()); + BOOST_TEST(R_Gohm.value() == 3.76e-3); + static_assert(equalityTest(R_Gohm.value(), 3.76e-3)); + static_assert(equalityTest(R_Gohm, R_ohm)); + std::cout << "Tested " << R_Gohm << std::endl; + +} // test_resistance_literals() + +// ----------------------------------------------------------------------------- +void test_capacity_literals() +{ + + using namespace util::quantities::electromagnetism_literals; + + // capacitance of a ICARUS TPC (relative permittivity from Shene, William R., + // "Measurement of the Dielectric Constants of Liquid Argon and Oxygen" (1966). + // Theses and Dissertations. 3964.) + constexpr auto C_pF = 542.5872864_pF; + static_assert(std::is_same()); + BOOST_TEST(C_pF.value() == 542.5872864); + static_assert(equalityTest(C_pF.value(), 542.5872864)); + std::cout << "Tested " << C_pF << std::endl; + + constexpr auto C_fF = 542587.2864_fF; + static_assert(std::is_same()); + BOOST_TEST(C_fF.value() == 542587.2864); + static_assert(equalityTest(C_fF.value(), 542587.2864)); + static_assert(equalityTest(C_fF, C_pF)); + std::cout << "Tested " << C_fF << std::endl; + + constexpr auto C_nF = 0.5425872864_nF; + static_assert(std::is_same()); + BOOST_TEST(C_nF.value() == 0.5425872864); + static_assert(equalityTest(C_nF.value(), 0.5425872864)); + static_assert(equalityTest(C_nF, C_pF)); + std::cout << "Tested " << C_nF << std::endl; + + constexpr auto C_uF = 542.5872864e-6_uF; + static_assert(std::is_same()); + BOOST_TEST(C_uF.value() == 542.5872864e-6); + static_assert(equalityTest(C_uF.value(), 542.5872864e-6)); + static_assert(equalityTest(C_uF, C_pF)); + std::cout << "Tested " << C_uF << std::endl; + + constexpr auto C_mF = 542.5872864e-9_mF; + static_assert(std::is_same()); + BOOST_TEST(C_mF.value() == 542.5872864e-9); + static_assert(equalityTest(C_mF.value(), 542.5872864e-9)); + static_assert(equalityTest(C_mF, C_pF)); + std::cout << "Tested " << C_mF << std::endl; + + constexpr auto C_F = 542.5872864e-12_F; + static_assert(std::is_same()); + BOOST_TEST(C_F.value() == 542.5872864e-12); + static_assert(equalityTest(C_F.value(), 542.5872864e-12)); + static_assert(equalityTest(C_F, C_pF)); + std::cout << "Tested " << C_F << std::endl; + +} // test_capacity_literals() + +// ----------------------------------------------------------------------------- +void test_product_RiV() +{ + + using namespace util::quantities::electromagnetism_literals; + + constexpr util::quantities::millivolt V = 10.0_V; + constexpr util::quantities::kiloohm R = 5.0_kohm; + constexpr util::quantities::microampere i = 2.0_mA; + + constexpr auto const V1 = R * i; + static_assert(std::is_same_v); + static_assert(equalityTest(V1, V)); + + constexpr auto const V2 = i * R; + static_assert(std::is_same_v); + static_assert(equalityTest(V2, V)); + + constexpr auto const i1 = V / R; + static_assert(std::is_same_v); + static_assert(equalityTest(i1, i)); + + constexpr auto const R2 = V / i; + static_assert(std::is_same_v); + static_assert(equalityTest(R2, R)); + +} // test_product_RiV() + +// ----------------------------------------------------------------------------- +void test_product_CVQ() +{ + + using namespace util::quantities::electromagnetism_literals; + + constexpr util::quantities::nanocoulomb Q = 100.0_nC; + constexpr util::quantities::picofarad C = 50.0_pF; + constexpr util::quantities::kilovolt V = 2.0_kV; + + constexpr auto const Q1 = C * V; + static_assert(std::is_same_v); + static_assert(equalityTest(Q1, Q)); + + constexpr auto const Q2 = V * C; + static_assert(std::is_same_v); + static_assert(equalityTest(Q2, Q)); + + constexpr auto const C1 = Q / V; + static_assert(std::is_same_v); + static_assert(equalityTest(C1, C)); + + constexpr auto const V2 = Q / C; + static_assert(std::is_same_v); + static_assert(equalityTest(V2, V)); + +} // test_product_CVQ() + +// ----------------------------------------------------------------------------- +void test_product_RCt() +{ + + using namespace util::quantities::electromagnetism_literals; + using namespace util::quantities::time_literals; + + constexpr util::quantities::nanosecond t = 10.0_ns; + constexpr util::quantities::kiloohm R = 5.0_kohm; + constexpr util::quantities::picofarad C = 2.0_pF; + + constexpr auto const t1 = R * C; + static_assert(std::is_same_v); + static_assert(equalityTest(t1, t)); + + constexpr auto const t2 = C * R; + static_assert(std::is_same_v); + static_assert(equalityTest(t2, t)); + + constexpr auto const R1 = t / C; + static_assert(std::is_same_v); + static_assert(equalityTest(R1, R)); + + constexpr auto const C2 = t / R; + static_assert(std::is_same_v); + static_assert(equalityTest(C2, C)); + +} // test_product_RCt() + +// ----------------------------------------------------------------------------- +// BEGIN Test cases ----------------------------------------------------------- +// ----------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE(literal_testcase) +{ + + test_charge_literals(); + test_potential_literals(); + test_current_literals(); + test_resistance_literals(); + test_capacity_literals(); + +} // BOOST_AUTO_TEST_CASE(literal_testcase) + +// ----------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE(products_testcase) +{ + + test_product_RiV(); + test_product_CVQ(); + test_product_RCt(); + +} // BOOST_AUTO_TEST_CASE(literal_testcase) + +// ----------------------------------------------------------------------------- +// END Test cases ------------------------------------------------------------- +// ----------------------------------------------------------------------------- diff --git a/test/Utilities/quantities_test.cc b/test/Utilities/quantities_test.cc index 4570862c..979f7109 100644 --- a/test/Utilities/quantities_test.cc +++ b/test/Utilities/quantities_test.cc @@ -107,7 +107,7 @@ void test_quantities_conversions() // // conversions to other scales // - util::quantities::seconds t_s{7.0}; + constexpr util::quantities::seconds t_s{7.0}; BOOST_TEST(t_s.value() == 7.0); From f931509b15da1c57956c78488b495dc01f8e0fec Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Mon, 27 Oct 2025 16:33:26 -0500 Subject: [PATCH 06/13] quantities_test now depends only on quantities.h --- test/Utilities/quantities_test.cc | 267 +++++++++++++++++++++--------- 1 file changed, 187 insertions(+), 80 deletions(-) diff --git a/test/Utilities/quantities_test.cc b/test/Utilities/quantities_test.cc index 979f7109..0fe16ee2 100644 --- a/test/Utilities/quantities_test.cc +++ b/test/Utilities/quantities_test.cc @@ -14,14 +14,121 @@ // LArSoft libraries #include "larcorealg/CoreUtils/StdUtils.h" // util::to_string() #include "lardataalg/Utilities/quantities.h" -#include "lardataalg/Utilities/quantities/spacetime.h" -#include "test/Utilities/disable_boost_fpc_tolerance.hpp" // C/C++ standard libraries +#include #include // std::decay_t<> using boost::test_tools::tolerance; +// ----------------------------------------------------------------------------- +// test units +namespace util::quantities { + + namespace units { + + using namespace std::string_view_literals; // for operator""sv() + + struct TestSecond : public concepts::UnitBase { + static constexpr auto symbol = "s"sv; + static constexpr auto name = "second"sv; + }; + + } + + // The most generic `units::TestSecond`-based quantity. + template + using scaled_second = concepts::scaled_quantity; + + /// Type of time stored in second. + template + using second_as = scaled_second, T>; + + /// Type of time stored in second, in double precision. + using second = second_as<>; + + /// Type of time stored in millisecond. + template + using millisecond_as = concepts::rescale, std::milli>; + + /// Type of time stored in millisecond, in double precision. + using millisecond = millisecond_as<>; + + /// Type of time stored in microsecond. + template + using microsecond_as = concepts::rescale, std::micro>; + + /// Type of time stored in microsecond, in double precision. + using microsecond = microsecond_as<>; + + template + using nanosecond_as = concepts::rescale, std::nano>; + + /// Type of time stored in nanosecond, in double precision. + using nanosecond = nanosecond_as<>; + + namespace unit_literals { + + // Literal second value. + constexpr second operator""_s(long double v) + { + return second{static_cast(v)}; + } + constexpr second operator""_s(unsigned long long int v) + { + return second{static_cast(v)}; + } + + // Literal microsecond value. + constexpr millisecond operator""_ms(long double v) + { + return millisecond{static_cast(v)}; + } + constexpr millisecond operator""_ms(unsigned long long int v) + { + return millisecond{static_cast(v)}; + } + + // Literal microsecond value. + constexpr microsecond operator""_us(long double v) + { + return microsecond{static_cast(v)}; + } + constexpr microsecond operator""_us(unsigned long long int v) + { + return microsecond{static_cast(v)}; + } + + // Literal nanosecond value. + constexpr nanosecond operator""_ns(long double v) + { + return nanosecond{static_cast(v)}; + } + constexpr nanosecond operator""_ns(unsigned long long int v) + { + return nanosecond{static_cast(v)}; + } + + } // unit_literals + +} // util::quantities::units + +// Because util::quantites::seconds (etc.) has std::numeric_limits<> +// specializations, the Boost unit test suite assumes they are +// suitable for floating-point comparisons, particularly tolerance +// testing (due to the inexact nature of the representation). The +// following specializations disable such tolerance testing. +namespace boost::math::fpc { + template <> + struct tolerance_based : std::false_type {}; + template <> + struct tolerance_based : std::false_type {}; + template <> + struct tolerance_based : std::false_type {}; + template <> + struct tolerance_based : std::false_type {}; +} + // ----------------------------------------------------------------------------- // --- implementation detail tests @@ -31,29 +138,29 @@ struct EmptyClass {}; static_assert(!util::quantities::concepts::details::has_unit_v); static_assert(!util::quantities::concepts::details::has_unit_v>); static_assert(util::quantities::concepts::details::has_unit_v< - util::quantities::concepts::ScaledUnit>); -static_assert(util::quantities::concepts::details::has_unit_v); -static_assert(util::quantities::concepts::details::has_unit_v); + util::quantities::concepts::ScaledUnit>); +static_assert(util::quantities::concepts::details::has_unit_v); +static_assert(util::quantities::concepts::details::has_unit_v); static_assert( - util::quantities::concepts::details::has_unit_v>); + util::quantities::concepts::details::has_unit_v>); static_assert(!util::quantities::concepts::details::is_quantity_v); static_assert(!util::quantities::concepts::details::is_quantity_v>); static_assert(!util::quantities::concepts::details::is_quantity_v< - util::quantities::concepts::ScaledUnit>); -static_assert(util::quantities::concepts::details::is_quantity_v); -static_assert(util::quantities::concepts::details::is_quantity_v); + util::quantities::concepts::ScaledUnit>); +static_assert(util::quantities::concepts::details::is_quantity_v); +static_assert(util::quantities::concepts::details::is_quantity_v); static_assert( - util::quantities::concepts::details::is_quantity_v>); + util::quantities::concepts::details::is_quantity_v>); static_assert(!util::quantities::concepts::details::has_quantity_v); static_assert(!util::quantities::concepts::details::has_quantity_v>); static_assert(!util::quantities::concepts::details::has_quantity_v< - util::quantities::concepts::ScaledUnit>); -static_assert(util::quantities::concepts::details::has_quantity_v); -static_assert(util::quantities::concepts::details::has_quantity_v); + util::quantities::concepts::ScaledUnit>); +static_assert(util::quantities::concepts::details::has_quantity_v); +static_assert(util::quantities::concepts::details::has_quantity_v); static_assert( - util::quantities::concepts::details::has_quantity_v>); + util::quantities::concepts::details::has_quantity_v>); static_assert(util::quantities::second::isCompatibleValue()); static_assert(util::quantities::second::isCompatibleValue()); @@ -81,18 +188,18 @@ static_assert(!util::quantities::microsecond::sameUnitAs(), + static_assert(std::is_same(), "Positive sign converts to a different type!"); BOOST_TEST(+t == -4_us); - static_assert(std::is_same(), + static_assert(std::is_same(), "Negative sign converts to a different type!"); BOOST_TEST(-t == 4_us); - static_assert(std::is_same(), + static_assert(std::is_same(), "Negative sign converts to a different type!"); BOOST_TEST(t.abs() == 4.0_us); @@ -102,28 +209,28 @@ void test_quantities_sign() void test_quantities_conversions() { - using namespace util::quantities::time_literals; + using namespace util::quantities::unit_literals; // // conversions to other scales // - constexpr util::quantities::seconds t_s{7.0}; + constexpr util::quantities::second t_s{7.0}; BOOST_TEST(t_s.value() == 7.0); - util::quantities::microseconds t_us(t_s); + util::quantities::microsecond t_us(t_s); t_us = t_s; BOOST_TEST(t_us == 7'000'000.0_us); - util::quantities::seconds t(t_us); + util::quantities::second t(t_us); BOOST_TEST(t == 7.0_s); - static_assert(std::is_same()), - util::quantities::microseconds>()); - BOOST_TEST(t_s.convertInto() == 7'000'000_us); + static_assert(std::is_same()), + util::quantities::microsecond>()); + BOOST_TEST(t_s.convertInto() == 7'000'000_us); static_assert( - std::is_same_v()), util::quantities::seconds_as>); + std::is_same_v()), util::quantities::second_as>); static_assert(t_s.asValueType() == t_s); } // test_quantities_conversions() @@ -134,7 +241,7 @@ void test_quantities_comparisons() // // comparisons between quantities // - util::quantities::microseconds t_us{7.0}; + util::quantities::microsecond t_us{7.0}; BOOST_TEST(t_us == t_us); BOOST_TEST(!(t_us != t_us)); BOOST_TEST((t_us >= t_us)); @@ -142,7 +249,7 @@ void test_quantities_comparisons() BOOST_TEST(!(t_us > t_us)); BOOST_TEST(!(t_us < t_us)); - util::quantities::nanoseconds t_ns{7.0}; + util::quantities::nanosecond t_ns{7.0}; BOOST_TEST(t_us != t_ns); BOOST_TEST(!(t_us == t_ns)); BOOST_TEST((t_us != t_ns)); @@ -151,7 +258,7 @@ void test_quantities_comparisons() BOOST_TEST((t_us > t_ns)); BOOST_TEST(!(t_us < t_ns)); - util::quantities::nanoseconds t2_ns{7000.0}; + util::quantities::nanosecond t2_ns{7000.0}; BOOST_TEST(t_us == t2_ns); BOOST_TEST(!(t_us != t2_ns)); BOOST_TEST((t_us >= t2_ns)); @@ -176,23 +283,23 @@ void test_quantities_multiply_scalar() // multiplication and division by scalar // - using namespace util::quantities::time_literals; + using namespace util::quantities::unit_literals; // 5_s * 6_s; // ERROR // 5_s * 6_us; // ERROR - util::quantities::seconds const t{3.0}; + util::quantities::second const t{3.0}; auto const twice_t = 2.0 * t; - static_assert(std::is_same, util::quantities::seconds>(), + static_assert(std::is_same, util::quantities::second>(), "Multiplication by a scalar converts to a different type!"); BOOST_TEST(twice_t == 6.0_s); auto const t_twice = t * 2.0; - static_assert(std::is_same, util::quantities::seconds>(), + static_assert(std::is_same, util::quantities::second>(), "Multiplication by a scalar converts to a different type!"); BOOST_TEST(twice_t == 6.0_s); - static_assert(std::is_same(), + static_assert(std::is_same(), "Division by a scalar converts to a different type!"); BOOST_TEST(twice_t / 2.0 == 3.0_s); @@ -210,7 +317,7 @@ void test_quantities_multiply_scalar() void test_quantities_addition() { - using namespace util::quantities::time_literals; + using namespace util::quantities::unit_literals; // // sum and difference @@ -219,21 +326,21 @@ void test_quantities_addition() // 5_s + 700_ms; // ERROR! // 5_s + 0.7; // ERROR! - static_assert(std::is_same, util::quantities::seconds>(), + static_assert(std::is_same, util::quantities::second>(), "Addition converts to a different type!"); BOOST_TEST(45_s + 5_s == 50_s); - static_assert(std::is_same(), + static_assert(std::is_same(), "Subtraction converts to a different type!"); BOOST_TEST(5_s - 55_s == -50_s); - constexpr util::quantities::seconds t = 45_s; - static_assert(std::is_same, util::quantities::seconds>(), + constexpr util::quantities::second t = 45_s; + static_assert(std::is_same, util::quantities::second>(), "Addition converts to a different type!"); BOOST_TEST(t.plus(5000_ms) == 50_s); BOOST_TEST(t == 45_s); - static_assert(std::is_same(), + static_assert(std::is_same(), "Subtraction converts to a different type!"); BOOST_TEST(t.minus(55000_ms) == -10_s); BOOST_TEST(t == 45_s); @@ -244,30 +351,30 @@ void test_quantities_addition() void test_quantities_increment() { - using namespace util::quantities::time_literals; + using namespace util::quantities::unit_literals; // // increment and decrement by a quantity // - util::quantities::seconds t{0.05}; + util::quantities::second t{0.05}; t += 0.05_s; - static_assert(std::is_same(), + static_assert(std::is_same(), "Increment converts to a different type!"); BOOST_TEST(t == 0.1_s); t -= 0.05_s; - static_assert(std::is_same(), + static_assert(std::is_same(), "Decrement converts to a different type!"); BOOST_TEST(t == 0.05_s); t += 50_ms; - static_assert(std::is_same(), + static_assert(std::is_same(), "Increment converts to a different type!"); BOOST_TEST(t == 0.1_s); t -= 50_ms; - static_assert(std::is_same(), + static_assert(std::is_same(), "Decrement converts to a different type!"); BOOST_TEST(t == 0.05_s); @@ -277,19 +384,19 @@ void test_quantities_increment() void test_quantities_scale() { - using namespace util::quantities::time_literals; + using namespace util::quantities::unit_literals; - util::quantities::microseconds t{11.0}; + util::quantities::microsecond t{11.0}; // // scaling // t *= 2.0; - static_assert(std::is_same(), + static_assert(std::is_same(), "Scaling converts to a different type!"); BOOST_TEST(t == 22.0_us); t /= 2.0; - static_assert(std::is_same(), + static_assert(std::is_same(), "Scaling (division) converts to a different type!"); BOOST_TEST(t == 11.0_us); @@ -299,7 +406,7 @@ void test_quantities_scale() void test_quantities_literals() { - using namespace util::quantities::time_literals; + using namespace util::quantities::unit_literals; constexpr util::quantities::second t1 = 7_s; static_assert(t1.value() == 7.0, "Literal assignment failed."); @@ -320,19 +427,19 @@ void test_quantities_literals() void test_quantities() { - using namespace util::quantities::time_literals; + using namespace util::quantities::unit_literals; // --------------------------------------------------------------------------- // default constructor // // BOOST_TEST_CHECKPOINT("Default constructor"); - util::quantities::microseconds t1; // can't do much with this except assigning + util::quantities::microsecond t1; // can't do much with this except assigning // --------------------------------------------------------------------------- // assignment // // t1 = 4.0; // error! - t1 = util::quantities::microseconds{4.0}; + t1 = util::quantities::microsecond{4.0}; BOOST_TEST(util::to_string(t1.unit()) == "us"); BOOST_TEST(util::to_string(t1) == "4.000000 us"); BOOST_TEST(t1.value() == 4.0); @@ -340,7 +447,7 @@ void test_quantities() // --------------------------------------------------------------------------- // value constructor // - util::quantities::microseconds t2{7.0}; + util::quantities::microsecond t2{7.0}; BOOST_TEST(t2 == 7.0_us); // --------------------------------------------------------------------------- @@ -351,12 +458,12 @@ void test_quantities() void test_constexpr_operations() { - using namespace util::quantities::time_literals; + using namespace util::quantities::unit_literals; - constexpr util::quantities::microseconds t1{10.0}; - constexpr util::quantities::microseconds t2{20.0}; - constexpr util::quantities::nanoseconds t_ns{500.0}; - constexpr util::quantities::nanoseconds t1_ns = t1; // convert + constexpr util::quantities::microsecond t1{10.0}; + constexpr util::quantities::microsecond t2{20.0}; + constexpr util::quantities::nanosecond t_ns{500.0}; + constexpr util::quantities::nanosecond t1_ns = t1; // convert static_assert(t1.value() == 10.0, "value()"); static_assert(double(t1) == 10.0, "explicit conversion to plain number"); @@ -406,58 +513,58 @@ void test_constexpr_operations() void test_makeQuantity() { - using namespace util::quantities::time_literals; - using util::quantities::milliseconds; + using namespace util::quantities::unit_literals; + using util::quantities::millisecond; constexpr auto expected = 3.0_ms; - static_assert(std::is_same, milliseconds>()); + static_assert(std::is_same, millisecond>()); - auto q = util::quantities::makeQuantity("3.0 ms"); - static_assert(std::is_same, milliseconds>()); + auto q = util::quantities::makeQuantity("3.0 ms"); + static_assert(std::is_same, millisecond>()); auto const tol = 1e-7 % tolerance(); BOOST_TEST(q.value() == expected.value(), tol); - q = util::quantities::makeQuantity(" 3.0ms "); + q = util::quantities::makeQuantity(" 3.0ms "); BOOST_TEST(q.value() == expected.value(), tol); - q = util::quantities::makeQuantity("3ms"); + q = util::quantities::makeQuantity("3ms"); BOOST_TEST(q.value() == expected.value(), tol); - q = util::quantities::makeQuantity("3000 us"); + q = util::quantities::makeQuantity("3000 us"); BOOST_TEST(q.value() == expected.value(), tol); - q = util::quantities::makeQuantity("0.03e+2 ms"); + q = util::quantities::makeQuantity("0.03e+2 ms"); BOOST_TEST(q.value() == expected.value(), tol); - q = util::quantities::makeQuantity("+3ms"); + q = util::quantities::makeQuantity("+3ms"); BOOST_TEST(q.value() == expected.value(), tol); - q = util::quantities::makeQuantity("+3E-3s"); + q = util::quantities::makeQuantity("+3E-3s"); BOOST_TEST(q.value() == expected.value(), tol); - q = util::quantities::makeQuantity("3", true); + q = util::quantities::makeQuantity("3", true); BOOST_TEST(q.value() == expected.value(), tol); - q = util::quantities::makeQuantity("3.0", true); + q = util::quantities::makeQuantity("3.0", true); BOOST_TEST(q.value() == expected.value(), tol); - q = util::quantities::makeQuantity("30e-1", true); + q = util::quantities::makeQuantity("30e-1", true); BOOST_TEST(q.value() == expected.value(), tol); - BOOST_CHECK_THROW(util::quantities::makeQuantity("3"), + BOOST_CHECK_THROW(util::quantities::makeQuantity("3"), util::quantities::MissingUnit); - BOOST_CHECK_THROW(util::quantities::makeQuantity("3 kg"), + BOOST_CHECK_THROW(util::quantities::makeQuantity("3 kg"), util::quantities::MissingUnit); - BOOST_CHECK_THROW(util::quantities::makeQuantity("3 dumbs"), + BOOST_CHECK_THROW(util::quantities::makeQuantity("3 dumbs"), util::quantities::ExtraCharactersError); - BOOST_CHECK_THROW(util::quantities::makeQuantity("three ms"), + BOOST_CHECK_THROW(util::quantities::makeQuantity("three ms"), util::quantities::ValueError); - BOOST_CHECK_THROW(util::quantities::makeQuantity("3.zero ms"), + BOOST_CHECK_THROW(util::quantities::makeQuantity("3.zero ms"), util::quantities::ExtraCharactersError); } // test_makeQuantity() From 89cbace176727880c260ca2eea8f1977416d6c30 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Mon, 27 Oct 2025 17:35:37 -0500 Subject: [PATCH 07/13] Fix the unlikely case of same-unity arguments in UTIL_QUANTITIES_UNITPRODUCT() --- .../Utilities/details/quantity_products.h | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lardataalg/Utilities/details/quantity_products.h b/lardataalg/Utilities/details/quantity_products.h index a5004e9d..7c17e50b 100644 --- a/lardataalg/Utilities/details/quantity_products.h +++ b/lardataalg/Utilities/details/quantity_products.h @@ -254,6 +254,9 @@ namespace util::quantities::concepts { * * It requires all the operands to be base unit types (no `ScaledUnit`), * and that the product unit is neither of the factor ones. + * + * @note For product of the same unit, use `UTIL_QUANTITIES_UNITSQUARE()` + * instead. */ #define UTIL_QUANTITIES_UNITPRODUCT(UA, UB, UP) \ namespace util::quantities::concepts::details { \ @@ -265,6 +268,8 @@ namespace util::quantities::concepts { "The third type does not meet base unit class requirements."); \ static_assert(!std::is_same_v, "A factor can't have the same unit as the product."); \ static_assert(!std::is_same_v, "B factor can't have the same unit as the product."); \ + static_assert(!std::is_same_v, \ + "Use UTIL_QUANTITIES_UNITSQUARE() for square relation."); \ template <> \ struct UnitProductResult { \ using type = UP; \ @@ -283,6 +288,41 @@ namespace util::quantities::concepts { }; \ } +/** + * @brief Declares that a unit is the square of another. + * @tparam UF type of the unit to be squared + * @tparam UP type of the unit of the product `UA * UB` + * + * It defines `ProductType` template specialization like: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * template<> struct ProductType { using type = UP; }; + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * that describe the two relations between the two units (`UF * UF = UP`, + * and `UP / UF = UF`). + * + * It requires all the operands to be base unit types (no `ScaledUnit`), + * and that the product unit is not the same as the factor one. + * + * @note For product of different units, use `UTIL_QUANTITIES_UNITPRODUCT()` + * instead. + */ +#define UTIL_QUANTITIES_UNITSQUARE(UF, UP) \ + namespace util::quantities::concepts::details { \ + static_assert(is_base_unit_v, \ + "The factor type does not meet base unit class requirements."); \ + static_assert(is_base_unit_v, \ + "The product type does not meet base unit class requirements."); \ + static_assert(!std::is_same_v, "The factor can't have the same unit as the product."); \ + template <> \ + struct UnitProductResult { \ + using type = UP; \ + }; \ + template <> \ + struct UnitProductResult { \ + using type = UF; \ + }; \ + } + // ----------------------------------------------------------------------------- #endif // LARDATAALG_UTILITIES_QUANTITIES_QUANTITY_PRODUCTS_H From c711d85f33d5cbf9bca98ee49aa21e32cc5e3b6b Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 29 Oct 2025 17:01:00 -0500 Subject: [PATCH 08/13] util::quantities::is_quantity_v promoted to interface --- lardataalg/Utilities/quantities.h | 18 +++++++++--------- test/Utilities/intervals_test.cc | 15 ++++++--------- test/Utilities/quantities_test.cc | 12 ++++++------ 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/lardataalg/Utilities/quantities.h b/lardataalg/Utilities/quantities.h index 9d0dab41..ca5dd030 100644 --- a/lardataalg/Utilities/quantities.h +++ b/lardataalg/Utilities/quantities.h @@ -260,6 +260,10 @@ namespace util::quantities { template struct base_unit_extactor; + /// Implementation of `is_quantity_v`. + template + struct is_quantity; + /// Trait: `true_type` if `U` is a `ScaledUnit`-based object. template struct has_unit; @@ -268,14 +272,6 @@ namespace util::quantities { template constexpr bool has_unit_v = has_unit(); - /// Trait: `true_type` if `Q` is a `Quantity` specialization. - template - struct is_quantity; - - /// Trait: `true` if `Q` is a `Quantity` specialization. - template - constexpr bool is_quantity_v = is_quantity(); - /// Trait: `true_type` if `Q` is a `Quantity`-based object. template struct has_quantity; @@ -501,6 +497,10 @@ namespace util::quantities { return out << unit_t::prefix_t::symbol() << unit_t::baseunit_t::symbol; } + /// Trait: `true` if `Q` is a `Quantity` specialization. + template + constexpr bool is_quantity_v = details::is_quantity(); + /** ************************************************************************ * @brief A value measured in the specified unit. * @tparam Unit the scaled unit type representing the unit of this quantity @@ -602,7 +602,7 @@ namespace util::quantities { * This assignment prevents narrowing. To force narrowing, convert the * operand quantity to a number or "cast" it with `asValueType<>()`. */ - template >* = nullptr> + template >* = nullptr> constexpr Quantity(Q q) : fValue{unit_t::template fromRepr(q.value())} { diff --git a/test/Utilities/intervals_test.cc b/test/Utilities/intervals_test.cc index 83de4a1d..b6d77664 100644 --- a/test/Utilities/intervals_test.cc +++ b/test/Utilities/intervals_test.cc @@ -63,12 +63,11 @@ static_assert( static_assert(util::quantities::concepts::details::has_unit_v< util::quantities::intervals::microseconds_as>); +static_assert(!util::quantities::concepts::is_quantity_v); static_assert( - !util::quantities::concepts::details::is_quantity_v); + !util::quantities::concepts::is_quantity_v); static_assert( - !util::quantities::concepts::details::is_quantity_v); -static_assert(!util::quantities::concepts::details::is_quantity_v< - util::quantities::intervals::microseconds_as>); + !util::quantities::concepts::is_quantity_v>); static_assert( util::quantities::concepts::details::has_quantity_v); @@ -83,12 +82,10 @@ static_assert( static_assert( util::quantities::concepts::details::has_unit_v>); +static_assert(!util::quantities::concepts::is_quantity_v); +static_assert(!util::quantities::concepts::is_quantity_v); static_assert( - !util::quantities::concepts::details::is_quantity_v); -static_assert( - !util::quantities::concepts::details::is_quantity_v); -static_assert(!util::quantities::concepts::details::is_quantity_v< - util::quantities::points::microsecond_as>); + !util::quantities::concepts::is_quantity_v>); static_assert( util::quantities::concepts::details::has_quantity_v); diff --git a/test/Utilities/quantities_test.cc b/test/Utilities/quantities_test.cc index 0fe16ee2..1322c849 100644 --- a/test/Utilities/quantities_test.cc +++ b/test/Utilities/quantities_test.cc @@ -144,14 +144,14 @@ static_assert(util::quantities::concepts::details::has_unit_v>); -static_assert(!util::quantities::concepts::details::is_quantity_v); -static_assert(!util::quantities::concepts::details::is_quantity_v>); -static_assert(!util::quantities::concepts::details::is_quantity_v< +static_assert(!util::quantities::concepts::is_quantity_v); +static_assert(!util::quantities::concepts::is_quantity_v>); +static_assert(!util::quantities::concepts::is_quantity_v< util::quantities::concepts::ScaledUnit>); -static_assert(util::quantities::concepts::details::is_quantity_v); -static_assert(util::quantities::concepts::details::is_quantity_v); +static_assert(util::quantities::concepts::is_quantity_v); +static_assert(util::quantities::concepts::is_quantity_v); static_assert( - util::quantities::concepts::details::is_quantity_v>); + util::quantities::concepts::is_quantity_v>); static_assert(!util::quantities::concepts::details::has_quantity_v); static_assert(!util::quantities::concepts::details::has_quantity_v>); From d1fe2f09c92924b32712167be6dcd53b68a85642 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 29 Oct 2025 17:14:13 -0500 Subject: [PATCH 09/13] Quantities product system rehauled Added support of product of quantities resulting into a scalar. --- .../Utilities/details/quantity_products.h | 454 +++++++++++++----- test/Utilities/quantities_test.cc | 238 +++++++-- 2 files changed, 536 insertions(+), 156 deletions(-) diff --git a/lardataalg/Utilities/details/quantity_products.h b/lardataalg/Utilities/details/quantity_products.h index 7c17e50b..ddb55e89 100644 --- a/lardataalg/Utilities/details/quantity_products.h +++ b/lardataalg/Utilities/details/quantity_products.h @@ -36,7 +36,7 @@ * `util::quantities::concepts::details::ProductDataType`, which includes the * type of the product of its arguments: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * template <> struct UnitProductResult { + * template <> struct UnitBinaryOpResult { * using type = P; * }; * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -44,10 +44,23 @@ * multiplied (`A * B = P`). For the ratio relationship (`A / B = P`), `-1` * needs to be used instead. * A complete set of relations should be declared: A * B = P, B * A = P, - * P / A = B and P / B = A. The macro `DECLARE_PRODUCT_INFO()` does that. + * P / A = B and P / B = A. The macro `UTIL_QUANTITIES_UNITPRODUCT()` does that. * * + * Naming conventions + * ------------------- * + * In this file: + * * data structures (`struct`, `class`) are one-word camelcase; + * * type trait aliases (`using`, also when templated) have + * underscore-separated, all small letter identifiers, and suffix `_t`; + * * boolean traits (`constexpr bool`) have the same conventions as type traits + * but with suffix `_v` instead of `_t`; + * * macros have all capital, underscore-separated names starting with + * `UTIL_QUANTITIES_`. + * + * Note that other files (noticeably, `quantities.h`) are not bound to these + * rules. * */ @@ -64,120 +77,243 @@ #include #include // std::declval() +// ----------------------------------------------------------------------------- +namespace util::quantities::units { + + using namespace std::string_view_literals; + + /// Special unit representing a pure number (of unspecified type). + struct Unity : public concepts::UnitBase { + + /// Symbol of the unit (e.g. "A"). + static constexpr std::string_view symbol = "1"sv; + /// Long name of unit (e.g. "ampere"). + static constexpr std::string_view name = "unity"sv; + }; + +} // namespace util::quantities::units + namespace util::quantities::concepts::details { // --------------------------------------------------------------------------- + /// Special quantity-like object representing a pure number of specified type. + template , typename = void> + struct TypedUnity; + + /// Trait: `T` is a `TypedUnit` template specialization. + template + struct IsTypedUnit : std::false_type {}; + + /// Implementation of `IsTypedUnit` for `TypedUnit` types. + template + struct IsTypedUnit> : std::true_type {}; + + /// Implementation of `TypedUnit` (preventing recursiveness). + template + struct TypedUnity::value>> { + + public: + // mock-up of needed Quantity interface: + using value_t = T; + struct unit_t { // ScaledUnit mockup + using baseunit_t = units::Unity; + using ratio = R; + using prefix_t = Prefix; ///< The prefix of the unit. + }; + using quantity_t = value_t; // special: decays to a plain number + using baseunit_t = typename unit_t::baseunit_t; + + constexpr TypedUnity(value_t value = static_cast(1)) noexcept : fValue{value} {} + + /// Returns the contained value (always just the ratio). + constexpr value_t value() const { return fValue; } + + template + static constexpr bool sameBaseUnitAs() + { + return std::is_same>(); + } + + private: + value_t fValue; ///< Value of the + + }; // TypedUnity + /** - * @brief Type describing a product relation between three units. + * @brief Type describing a binary relation between three units. * @tparam UA type of the first unit in the product * @tparam UB type of the second unit in the product - * @tparam BExp (default: `+1`) exponent of `UB` in the product + * @tparam OpCat type of operation (e.g. '*', '/') * @see `UTIL_QUANTITIES_UNITPRODUCT()` * * The existence of a class specialization of this type declares a relation - * between three base units. If `BExp` is `+1`, the relation is `UA * UB = P`, - * while if `BExp` is `-1` the relation is `UA / UB = P`. + * between three base units: `UA op UB = P`. The "op" operation is encoded + * in `OpCat`, but this type does not define how the operation works: it only + * sanctions the legitimacy of that operation between types and declares its + * result type. + * As an example, `OpCat` value `'*'` is here used to describe a product, + * `UA * UB = P`, and value `'/'` is used to describe the ratio `UA / UB = P`. + * * The types `UA` and `UB` are specified (only) as template parameters, while * the type `P` must be defined in the body of the template specialization, * as a type called... `type`: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * template<> struct ProductType { + * template<> struct BinaryOpType { * using type = P; * }; * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * For example: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * template<> struct ProductType { + * template<> struct BinaryOpType { * using type = Volt; * }; * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * Note that this does not implies related relationships (like + * Note that this does not declares related relationships (like * `Ohm` * `Ampere` = `Volt`, `Volt` / `Ohm` = `Ampere` etc.), which need to * be explicitly specified (that is `UTIL_QUANTITIES_UNITPRODUCT()` for you). * */ - template - struct UnitProductResult; - - // --------------------------------------------------------------------------- - // The dance to access `UnitProductResult` properties is open... + template + struct UnitBinaryOpResult; - /// Direct access to the base unit of the product of `QA` and `QB` - /// (ratio if `BExp == -1`). - template - using BaseUnitProductResult_t = - typename UnitProductResult, base_unit_of, BExp>::type; + /// Type trait: `T` is the type `units::Unity`. + template + constexpr bool is_unity_v = std::is_same_v; - /// Direct access to the base unit of the ratio of `QN` and `QD`. - template - using BaseUnitRatioResult_t = BaseUnitProductResult_t; + /// Trait: `T` is either a `Quantity`) or a `TypedUnity` template instance. + template + constexpr bool is_typed_unity_or_quantity_v = IsTypedUnit::value || is_quantity_v; - /// Trait: there is a product/ratio defined between the units of `QA` and `QB`. - template - struct CanMultiplyQuantities : std::false_type {}; + /** + * Trait: `T` is either `Quantity` or a `TypedUnity` instance, `U` is quantity, + * and they do not share the same base unit. + * + * This is useful to enable "special" operations declared by + * `UnitBinaryOpResult` instances. + * The request of not sharing the base unit originates by the fact that + * typically same-unit operations are implemented directly in the `Quantity` + * object, and it is necessary to avoid collisions with more generic/special + * implementations. + * Preventing the second type from being a unit type limits the usefulness of + * this trait, excluding hypothetical operations where the second operand is + * a constant and the result is not a quantity with the same base unit as the + * first type. However, as of LArSoft v10_10, no operations of this type are + * implemented nor known. There _is_ one operation of that type implemented + * with the first operant being a constant and the result being a different + * type, that is the "inverse" operation `'/'` (for example, the inverse + * `1 / hz` of a frequency giving a time quantity). + */ + template + constexpr bool are_two_different_typed_unities_or_quantities_v = + is_typed_unity_or_quantity_v && is_quantity_v && !(T::template sameBaseUnitAs()); - /// Implementation of `CanMultiplyQuantities` for supported quantities. - template - struct CanMultiplyQuantities>> - : std::true_type {}; + /// Trait: all template types are `Quantity` or `TypedUnity` objects. + template + constexpr bool all_quantities_or_types_units_v = + (true && ... && is_typed_unity_or_quantity_v); - /// Shortcut trait to query of quantity ratio. - template - using CanDivideQuantities = CanMultiplyQuantities; + /// Whether the binary operation is acting on quantities and is "special" + /// (meaning that is supported only by `UnitBinaryOpResult` registered types). + template + struct IsSpecialQuantityOperation : std::false_type {}; /** - * @brief Full set of traits for the product between two quantities. - * @tparam QA first quantity to be multiplied - * @tparam QB second quantity to be multiplied (or denominator if `BExp == -1`) - * @tparam BExp (default: `+1`) `+1` for product, `-1` for ratio + * Implementation for "special" operations on valid quantity-like operands. * - * See `QuantitiesProductResult_t` and `QuantitiesRatioResult_t` for details. + * Operations that are not "special" include division between same-unit + * quantities and product of a quantity and a unit. + * These are supported directly in `Quantity` object. */ - template - struct QuantitiesProductResult; + template + struct IsSpecialQuantityOperation>> + : std::bool_constant<(OpCat != '/') || !(OpA::template sameBaseUnitAs())> {}; + + // --------------------------------------------------------------------------- + // The dance to access `UnitBinaryOpResult` properties is open... - /// Implementation of `QuantitiesProductResult` when the product is valid. + /// Direct access to the base unit of the product of `QA` and `QB` + /// (`OpCat`: `*` for product, `/` for ratio). + template + using BaseUnitBinaryOpResult_t = + typename UnitBinaryOpResult, base_unit_of, OpCat>::type; + + /// Trait: there is an operation `OpCat` defined between the units of `QA` and `QB`. + template + struct CanCombineQuantities : std::false_type {}; + + /// Implementation of `CanCombineQuantities` for supported quantities. + template + struct CanCombineQuantities>> + : std::true_type {}; + + /** + * @brief Full set of traits for a binary operation between two quantities. + * @tparam QA first quantity (or `TypedUnit` type) operand + * @tparam QB second quantity operand + * @tparam OpCat operation category (e.g. `'*'` for product, `'/'` for ratio) + * + * See `quantity_binaryop_result_t` for details. + */ + template + struct QuantityBinaryOpResult; + + /// Implementation of `QuantityBinaryOpResult` when the spacial operation is + /// not registered. + template + struct QuantityBinaryOpResult::value && + !(CanCombineQuantities::value)>> { + // specific error messages for different known cases; + // either way, a long list of compilation errors will follow these messages. + static_assert((OpCat != '*'), "No product is registered between these quantities."); + static_assert((OpCat != '/') || IsTypedUnit::value, + "No ratio is registered between the first and the second quantity."); + static_assert((OpCat != '/') || !IsTypedUnit::value, + "No inversion operation is registered for this quantity."); + }; + + /// Implementation of `QuantityBinaryOpResult` for product (`OpCat` `'*'`) when it is valid. template - struct QuantitiesProductResult::value>> { - // I needed to separate division and product because GCC 12.1 instantiating - // std::conditional would complain about - // the true branch being out of range... - using baseunit_t = BaseUnitProductResult_t; + struct QuantityBinaryOpResult::value && + CanCombineQuantities::value>> { + using baseunit_t = BaseUnitBinaryOpResult_t; using ratio_t = std::ratio_multiply; using value_t = decltype(std::declval() * std::declval()); - using type = Quantity, value_t>; - }; // QuantitiesProductResult + using type = std::conditional_t, + typename TypedUnity::value_t, + Quantity, value_t>>; + }; // QuantityBinaryOpResult<'*'> - /// Implementation of `QuantitiesProductResult` when the ratio is valid. - template - struct QuantitiesProductResult::value>> { - using baseunit_t = BaseUnitProductResult_t; - using ratio_t = std::ratio_divide; + /// Implementation of `QuantityBinaryOpResult` for ratio (`OpCat` `'/'`) when it is valid. + template + struct QuantityBinaryOpResult::value>> { + using baseunit_t = BaseUnitBinaryOpResult_t; + using ratio_t = std::ratio_divide; using value_t = - decltype(std::declval() / std::declval()); + decltype(std::declval() / std::declval()); using type = Quantity, value_t>; - }; // QuantitiesProductResult - - /// Shortcut trait to query of quantity ratio result information. - template - using QuantitiesRatioResult = QuantitiesProductResult; + }; // QuantityBinaryOpResult<'/'> /** - * @brief Full set of traits for the product between two quantities. - * @tparam QA first quantity to be multiplied - * @tparam QB second quantity to be multiplied (or denominator if `BExp == -1`) - * @tparam BExp (default: `+1`) `+1` for product, `-1` for ratio + * @brief Full set of traits for a binary operation between two quantities. + * @tparam QA first quantity (or `TypedUnit` type) operand + * @tparam QB second quantity operand + * @tparam OpCat operation category (e.g. `'*'` for product, `'/'` for ratio) * - * The class is defined only if the requested operation (`QA*QB` or `QA/QB`, - * depending on `BExp`) is valid (i.e. if it was declared with - * `UTIL_QUANTITIES_UNITPRODUCT()` or equivalent). + * The class is defined only if the requested operation is valid (i.e. if it + * was declared with `UTIL_QUANTITIES_UNITPRODUCT()` or equivalent). * * The information of the result of the operation includes: * * `type`: type of the `Quantity` result of the product or ratio. @@ -185,14 +321,79 @@ namespace util::quantities::concepts::details { * * `ratio_t`: the scaling of the base unit. * * `value_t`: type of the value contained in the quantity. * + * Each operation (including the operation category and its two operant types) + * has an independent implementation. Operation categories include + * * `'*'`: product. + * * `'/'`: ratio. */ - template - using QuantitiesProductResult_t = typename QuantitiesProductResult::type; + template + using quantity_binaryop_result_t = typename QuantityBinaryOpResult::type; + + /// Trait: type of result of quantity combination, or `AltT` if none. + /// Because sometimes we still need a type no matter what. + /// @see `quantities_combo_result_or_void_t` + template + struct QuantitiesComboResultOrVoid { + using type = AltT; + }; + + /// Implementation of `QuantitiesComboResultOrVoid` for sanctioned operations. + template + struct QuantitiesComboResultOrVoid::value>> { + using type = quantity_binaryop_result_t; + }; + + /// Trait: type of result of quantity combination, or `AltT` if none. + /// Because sometimes we still need a type no matter what. + template + using quantities_combo_result_or_void_t = + typename QuantitiesComboResultOrVoid::type; - /// Shortcut trait to query of quantity ratio `QA/QB`. - /// @see `QuantitiesProductResult_t` - template - using QuantitiesRatioResult_t = QuantitiesProductResult_t; + /** + * @brief Type of result of special operation between two quantities. + * @tparam QA first quantity (or `TypedUnit` type) operand + * @tparam QB second quantity operand + * @tparam OpCat operation category (e.g. `'*'` for product, `'/'` for ratio) + * @tparam ExtraReq (default: `true`) additional requirement (disabled if `false`) + * @tparam AltT (default: `void`) special type for unsanctioned operations + * + * This construct can effectively have three states: disabled (that is, + * incomplete type failing to compile), placeholder type (`AltT`) or special + * operation result type. + * + * The concept is that trying to combine two quantities in an unsanctioned way + * is a straight error, while combination that are not about quantities or + * not special should not be not attempted at all. + * + * Using this type as a return value for a function allows to enable the + * function only for quantity operations, but still passing through an + * unsanctioned combination so that the implementation may report an error + * message specific to the way the operation is not sanctioned. + * + * It yield a "disabled" state if QA o QB are not quantity-like + * (in which case no special operation should be considered) + * or if they can be combined with a "standard" operation `OpCat` + * (in which case special operations should be ignored and not compete with the standard), + * or if `ExtraReq` is `false` (there is a user-provided reason to ignore this); + * type `AltT` (`void` by default) if they can't be combined with operation `OpCat`; + * and the type of the result of the combination if they instead can. + * + * Implementation + * --------------- + * + * This is implemented in two layers (`quantities_combo_result_or_void_t`) + * because of the necessity to separate the decision on enable/disable from + * the one on sanctioned/unsanctioned. Because I could not find a way to have + * it both in single place. + */ + template + using special_quantities_combo_result_or_void_t = + std::enable_if_t, + quantities_combo_result_or_void_t>; // --------------------------------------------------------------------------- @@ -200,61 +401,81 @@ namespace util::quantities::concepts::details { namespace util::quantities::concepts { - // --------------------------------------------------------------------------- - /// Evaluates to `T` if that's a valid type, otherwise fails to compile. - template - using only_if_exists_t = std::enable_if_t, void>, T>; - // --------------------------------------------------------------------------- /// Returns the product of two quantity objects, if that's declared. /// @see `UTIL_QUANTITIES_UNITPRODUCT()` template - constexpr only_if_exists_t> operator*(QA a, QB b) + constexpr details::special_quantities_combo_result_or_void_t operator*(QA a, QB b) { - - using ResultInfo = details::QuantitiesProductResult; + using ResultInfo = details::QuantityBinaryOpResult; using value_t = typename ResultInfo::value_t; using quantity_t = typename ResultInfo::type; - return quantity_t{static_cast(a.value() * b.value())}; + value_t const p = a.value() * b.value(); + if constexpr (details::is_unity_v) { + // product to scalar: need to explicitly take ratio into account + using ratio_t = typename ResultInfo::ratio_t; + return static_cast(details::applyRatioToValue(p)); + } + else + return quantity_t{static_cast(p)}; // actual quantity } // operator* (quantity, quantity) /// Returns the ratio of two quantity objects, if that's declared. /// @see `UTIL_QUANTITIES_UNITPRODUCT()` template - constexpr only_if_exists_t> operator/(QN num, QD den) + constexpr details::special_quantities_combo_result_or_void_t operator/(QN num, + QD den) { - - using ResultInfo = details::QuantitiesRatioResult; + using ResultInfo = details::QuantityBinaryOpResult; using value_t = typename ResultInfo::value_t; using quantity_t = typename ResultInfo::type; return quantity_t{static_cast(num.value() / den.value())}; - } // operator/ (quantity, quantity) + /// Returns the ratio of a scalar and a quantity object, if that's declared. + /// @see `UTIL_QUANTITIES_UNITPRODUCT()` + template + constexpr details::special_quantities_combo_result_or_void_t< + details::TypedUnity, + Quantity, + '/', + !details::is_typed_unity_or_quantity_v> + operator/(T num, Quantity const den) + { + return details::TypedUnity{num} / den; // delegate to the other operator/ + } // --------------------------------------------------------------------------- } // namespace util::quantities::concepts // ----------------------------------------------------------------------------- /** - * @brief Declares a relation between units. - * @tparam UA type of the first unit in the product - * @tparam UB type of the second unit in the product + * @brief Declares a product relation between units. + * @tparam UA type of the first unit in the product (factor) + * @tparam UB type of the second unit in the product (factor) * @tparam UP type of the unit of the product `UA * UB` * - * It defines `ProductType` template specialization like: + * It defines `BinaryOpType` template specialization like: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * template<> struct ProductType { using type = UP; }; + * template<> struct BinaryOpType { using type = UP; }; * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * that describe the four relations between the three units (`UA * UB = UP`, - * `UB * UA = UP`, `UP / UA = UB`, `UP / UB = UA`). + * that sanctions as valid the four relations between the three units + * (`UA * UB = UP`, `UB * UA = UP`, `UP / UA = UB`, `UP / UB = UA`). * * It requires all the operands to be base unit types (no `ScaledUnit`), * and that the product unit is neither of the factor ones. * + * The declarations are inserted in `util::quantities::concepts::details` + * namespace, and as such namespace shortcuts are possible (for example, + * to access `util::quantities::units::Second` it is enough to write + * `units::Second`); + * + * The product unit can be a scalar (`units::Unit`), in which case the factor + * units are defined to be inverse one of the other. + * * @note For product of the same unit, use `UTIL_QUANTITIES_UNITSQUARE()` * instead. */ @@ -266,24 +487,26 @@ namespace util::quantities::concepts { "The second type does not meet base unit class requirements."); \ static_assert(is_base_unit_v, \ "The third type does not meet base unit class requirements."); \ + static_assert(!is_unity_v, "The first type can't be a pure number (Unity)"); \ + static_assert(!is_unity_v, "The second type can't be a pure number (Unity)"); \ static_assert(!std::is_same_v, "A factor can't have the same unit as the product."); \ static_assert(!std::is_same_v, "B factor can't have the same unit as the product."); \ static_assert(!std::is_same_v, \ "Use UTIL_QUANTITIES_UNITSQUARE() for square relation."); \ template <> \ - struct UnitProductResult { \ + struct UnitBinaryOpResult { \ using type = UP; \ }; \ template <> \ - struct UnitProductResult { \ + struct UnitBinaryOpResult { \ using type = UP; \ }; \ template <> \ - struct UnitProductResult { \ + struct UnitBinaryOpResult { \ using type = UB; \ }; \ template <> \ - struct UnitProductResult { \ + struct UnitBinaryOpResult { \ using type = UA; \ }; \ } @@ -292,10 +515,11 @@ namespace util::quantities::concepts { * @brief Declares that a unit is the square of another. * @tparam UF type of the unit to be squared * @tparam UP type of the unit of the product `UA * UB` + * @see `UTIL_QUANTITIES_UNITPRODUCT()` * - * It defines `ProductType` template specialization like: + * It defines `BinaryOpType` template specialization like: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * template<> struct ProductType { using type = UP; }; + * template<> struct BinaryOpType { using type = UP; }; * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * that describe the two relations between the two units (`UF * UF = UP`, * and `UP / UF = UF`). @@ -305,22 +529,28 @@ namespace util::quantities::concepts { * * @note For product of different units, use `UTIL_QUANTITIES_UNITPRODUCT()` * instead. + * + * See `UTIL_QUANTITIES_UNITPRODUCT()` for more details: the macros are + * functionally equivalent, with this one just dealing with the different + * combinatorics of its case. + * */ -#define UTIL_QUANTITIES_UNITSQUARE(UF, UP) \ - namespace util::quantities::concepts::details { \ - static_assert(is_base_unit_v, \ - "The factor type does not meet base unit class requirements."); \ - static_assert(is_base_unit_v, \ - "The product type does not meet base unit class requirements."); \ +#define UTIL_QUANTITIES_UNITSQUARE(UF, UP) \ + namespace util::quantities::concepts::details { \ + static_assert(is_base_unit_v, \ + "The factor type does not meet base unit class requirements."); \ + static_assert(is_base_unit_v, \ + "The product type does not meet base unit class requirements."); \ + static_assert(!is_unity_v, "The first type can't be a pure number (Unity)"; \ static_assert(!std::is_same_v, "The factor can't have the same unit as the product."); \ template <> \ - struct UnitProductResult { \ + struct UnitBinaryOpResult { \ using type = UP; \ }; \ template <> \ - struct UnitProductResult { \ + struct UnitBinaryOpResult { \ using type = UF; \ - }; \ + }; \ } // ----------------------------------------------------------------------------- diff --git a/test/Utilities/quantities_test.cc b/test/Utilities/quantities_test.cc index 1322c849..fc8ade21 100644 --- a/test/Utilities/quantities_test.cc +++ b/test/Utilities/quantities_test.cc @@ -34,6 +34,21 @@ namespace util::quantities { static constexpr auto name = "second"sv; }; + struct TestHertz : public concepts::UnitBase { + static constexpr auto symbol = "Hz"sv; + static constexpr auto name = "hertz"sv; + }; + + struct TestOhm : public concepts::UnitBase { + static constexpr auto symbol = "Ω"sv; + static constexpr auto name = "ohm"sv; + }; + + struct TestFarad : public concepts::UnitBase { + static constexpr auto symbol = "F"sv; + static constexpr auto name = "farad"sv; + }; + } // The most generic `units::TestSecond`-based quantity. @@ -67,6 +82,63 @@ namespace util::quantities { /// Type of time stored in nanosecond, in double precision. using nanosecond = nanosecond_as<>; + // hertz + // The most generic `units::TestHertz`-based quantity. + template + using scaled_hertz = concepts::scaled_quantity; + + /// Type of frequency stored in hertz. + template + using hertz_as = scaled_hertz, T>; + + /// Type of frequency stored in hertz, in double precision. + using hertz = hertz_as<>; + + /// Type of frequency stored in kilohertz. + template + using kilohertz_as = concepts::rescale, std::kilo>; + + /// Type of frequency stored in kilohertz, in double precision. + using kilohertz = kilohertz_as<>; + + // ohm + // The most generic `units::TestOhm`-based quantity. + template + using scaled_ohm = concepts::scaled_quantity; + + /// Type of frequency stored in ohm. + template + using ohm_as = scaled_ohm, T>; + + /// Type of frequency stored in ohm, in double precision. + using ohm = ohm_as<>; + + /// Type of frequency stored in kiloohm. + template + using kiloohm_as = concepts::rescale, std::kilo>; + + /// Type of frequency stored in kiloohm, in double precision. + using kiloohm = kiloohm_as<>; + + // farad + // The most generic `units::TestFarad`-based quantity. + template + using scaled_farad = concepts::scaled_quantity; + + /// Type of frequency stored in farad. + template + using farad_as = scaled_farad, T>; + + /// Type of frequency stored in farad, in double precision. + using farad = farad_as<>; + + /// Type of frequency stored in nanofarad. + template + using nanofarad_as = concepts::rescale, std::nano>; + + /// Type of frequency stored in nanofarad, in double precision. + using nanofarad = nanofarad_as<>; + namespace unit_literals { // Literal second value. @@ -109,10 +181,53 @@ namespace util::quantities { return nanosecond{static_cast(v)}; } + // Literal hertz value. + constexpr hertz operator""_Hz(long double v) + { + return hertz{static_cast(v)}; + } + constexpr hertz operator""_Hz(unsigned long long int v) + { + return hertz{static_cast(v)}; + } + + // Literal kilohertz value. + constexpr kilohertz operator""_kHz(long double v) + { + return kilohertz{static_cast(v)}; + } + constexpr kilohertz operator""_kHz(unsigned long long int v) + { + return kilohertz{static_cast(v)}; + } + + // Literal kiloohm value. + constexpr kiloohm operator""_kohm(long double v) + { + return kiloohm{static_cast(v)}; + } + constexpr kiloohm operator""_kohm(unsigned long long int v) + { + return kiloohm{static_cast(v)}; + } + + // Literal nanofarad value. + constexpr nanofarad operator""_nF(long double v) + { + return nanofarad{static_cast(v)}; + } + constexpr nanofarad operator""_nF(unsigned long long int v) + { + return nanofarad{static_cast(v)}; + } + } // unit_literals } // util::quantities::units +UTIL_QUANTITIES_UNITPRODUCT(units::TestHertz, units::TestSecond, units::Unity); +UTIL_QUANTITIES_UNITPRODUCT(units::TestOhm, units::TestFarad, units::TestSecond); + // Because util::quantites::seconds (etc.) has std::numeric_limits<> // specializations, the Boost unit test suite assumes they are // suitable for floating-point comparisons, particularly tolerance @@ -135,46 +250,67 @@ namespace boost::math::fpc { template struct EmptyClass {}; -static_assert(!util::quantities::concepts::details::has_unit_v); -static_assert(!util::quantities::concepts::details::has_unit_v>); -static_assert(util::quantities::concepts::details::has_unit_v< - util::quantities::concepts::ScaledUnit>); -static_assert(util::quantities::concepts::details::has_unit_v); -static_assert(util::quantities::concepts::details::has_unit_v); -static_assert( - util::quantities::concepts::details::has_unit_v>); - -static_assert(!util::quantities::concepts::is_quantity_v); -static_assert(!util::quantities::concepts::is_quantity_v>); -static_assert(!util::quantities::concepts::is_quantity_v< - util::quantities::concepts::ScaledUnit>); -static_assert(util::quantities::concepts::is_quantity_v); -static_assert(util::quantities::concepts::is_quantity_v); -static_assert( - util::quantities::concepts::is_quantity_v>); - -static_assert(!util::quantities::concepts::details::has_quantity_v); -static_assert(!util::quantities::concepts::details::has_quantity_v>); -static_assert(!util::quantities::concepts::details::has_quantity_v< - util::quantities::concepts::ScaledUnit>); -static_assert(util::quantities::concepts::details::has_quantity_v); -static_assert(util::quantities::concepts::details::has_quantity_v); -static_assert( - util::quantities::concepts::details::has_quantity_v>); - -static_assert(util::quantities::second::isCompatibleValue()); -static_assert(util::quantities::second::isCompatibleValue()); -static_assert(util::quantities::second::isCompatibleValue()); -static_assert(!util::quantities::second::isCompatibleValue()); -static_assert(!util::quantities::second::isCompatibleValue()); -static_assert(!util::quantities::second::isCompatibleValue>()); - -static_assert(util::quantities::second::hasCompatibleValue()); -static_assert(util::quantities::second::hasCompatibleValue()); -static_assert(util::quantities::second::hasCompatibleValue()); -static_assert(util::quantities::second::hasCompatibleValue()); -static_assert(util::quantities::second::hasCompatibleValue()); -static_assert(!util::quantities::second::hasCompatibleValue>()); +namespace util::quantities::concepts::details { + + static_assert(!has_unit_v); + static_assert(!has_unit_v>); + static_assert(has_unit_v>); + static_assert(has_unit_v); + static_assert(has_unit_v); + static_assert(has_unit_v>); + + static_assert(!is_quantity_v); + static_assert(!is_quantity_v>); + static_assert(!is_quantity_v>); + static_assert(is_quantity_v); + static_assert(is_quantity_v); + static_assert(is_quantity_v>); + + static_assert(!has_quantity_v); + static_assert(!has_quantity_v>); + static_assert(!has_quantity_v>); + static_assert(has_quantity_v); + static_assert(has_quantity_v); + static_assert(has_quantity_v>); + + static_assert(second::isCompatibleValue()); + static_assert(second::isCompatibleValue()); + static_assert(second::isCompatibleValue()); + static_assert(!second::isCompatibleValue()); + static_assert(!second::isCompatibleValue()); + static_assert(!second::isCompatibleValue>()); + + static_assert(second::hasCompatibleValue()); + static_assert(second::hasCompatibleValue()); + static_assert(second::hasCompatibleValue()); + static_assert(second::hasCompatibleValue()); + static_assert(second::hasCompatibleValue()); + static_assert(!second::hasCompatibleValue>()); + + static_assert(microsecond::sameBaseUnitAs()); + static_assert(!microsecond::sameBaseUnitAs()); + + static_assert(CanCombineQuantities::value); + + static_assert(std::is_same_v, units::TestSecond>); + static_assert(std::is_same_v, units::TestHertz>); + static_assert(std::is_same_v::type, + units::Unity>); + + static_assert(std::is_same_v, double>); + static_assert(IsSpecialQuantityOperation::value); + + static_assert( + std::is_same_v::type, double>); + static_assert( + std::is_same_v, double>); + + static_assert(all_quantities_or_types_units_v); + + static_assert( + std::is_same_v, double>); + +} // namespace util::quantities::concepts::details // ----------------------------------------------------------------------------- // --- Quantity tests @@ -304,12 +440,12 @@ void test_quantities_multiply_scalar() BOOST_TEST(twice_t / 2.0 == 3.0_s); static_assert(std::is_same(), - "Division by a scalar is not the base type!"); - BOOST_TEST(twice_t / t == 2.0); + "Division by same quantity is not the base type!"); + BOOST_TEST((twice_t / t) == 2.0); // TODO are parentheses still necessary? static_assert(std::is_same(), - "Division by a scalar is not the base type!"); - BOOST_TEST(t / 300_us == 10'000.0); + "Division by a same-unit quantity is not the base type!"); + BOOST_TEST((t / 300_us) == 10'000.0); // TODO are parentheses still necessary? } // test_quantities_multiply_scalar() @@ -505,6 +641,20 @@ void test_constexpr_operations() static_assert(2.0 * t1 == 20.0_us, "scaling"); static_assert(t1 / 2.0 == 5.0_us, "scaling"); + constexpr util::quantities::kilohertz f1{5.0}; + constexpr double counts = 0.05; // 10 us * 5 kHz + + static_assert(t1 * f1 == counts); + static_assert(f1 * t1 == counts); + static_assert(std::is_same_v); + static_assert(counts / f1 == t1); + static_assert(counts / t1 == f1); + + constexpr util::quantities::kiloohm R1{5.0}; + constexpr util::quantities::nanofarad C1{2.0}; + + static_assert(R1 * C1 == t1); + // --------------------------------------------------------------------------- } // test_constexpr_operations() From b98a386befcb5c62315f369dceb935a2814c0237 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 29 Oct 2025 17:15:47 -0500 Subject: [PATCH 10/13] Updated quantity relations with the new system implementation. --- lardataalg/Utilities/quantities/frequency.h | 130 ++------------------ lardataalg/Utilities/quantities/spacetime.h | 11 ++ test/Utilities/frequency_test.cc | 1 + 3 files changed, 25 insertions(+), 117 deletions(-) diff --git a/lardataalg/Utilities/quantities/frequency.h b/lardataalg/Utilities/quantities/frequency.h index 301264b8..f8a33715 100644 --- a/lardataalg/Utilities/quantities/frequency.h +++ b/lardataalg/Utilities/quantities/frequency.h @@ -10,12 +10,10 @@ * quantities are defined based on the following units: * * hertz (Hz, kHz, MHz, GHz) * - * Also, special operations with units from`util::quantities::unit::Second` are - * supported: - * * _t_ x _f_ = _s_ - * * _s_ / _t_ = _f_ - * * _s_ / _f_ = _t_ - * where _t_ is a time, _f_ a frequency and _s_ a pure number. + * @note If the following headers are loaded, relationship between these and + * some of their quantities are registered: + * * `spacetime.h` (e.g. s·Hz = 1). + * * * This is a header-only library. * @@ -26,7 +24,6 @@ // LArSoft libraries #include "lardataalg/Utilities/quantities.h" -#include "lardataalg/Utilities/quantities/spacetime.h" // ...::units::Second // C/C++ standard libraries #include @@ -60,6 +57,8 @@ namespace util::quantities { * representation to use * * double precision (e.g. `hertz`), ready for use * + * @note If `spacetime.h` header is loaded, additional relationships hold: + * * `Second` * `Hertz` = constant (and similar) */ /// @{ @@ -178,121 +177,18 @@ namespace util::quantities { /// @} // -- END Frequency ---------------------------------------------------------- - // -- BEGIN Special operations between Second and Hertz units ---------------- - /** - * @name Special operations between Second and Hertz units. - * - * The following operations are supported: - * * _t_ x _f_ = _s_ - * * _s_ / _t_ = _f_ // TODO - * * _s_ / _f_ = _t_ // TODO - * where _t_ is a time, _f_ a frequency and _s_ a pure number. - * - */ - /// @{ - - namespace concepts { - //@{ - /** - * @brief Returns the product (as scalar) of a time and a frequency. - * @tparam TR type of time unit scale (e.g. `std::micro`) - * @tparam TT type of time value representation - * @tparam FR type of frequency unit scale (e.g. `std::mega`) - * @tparam FT type of frequency value representation - * @param t time quantity, based on `util::quantities::units::Second` - * @param f frequency quantity, based on `util::quantities::units::Hertz` - * @return the product of the two (using the C++ type of `TT * FT`) - * - */ - template - constexpr auto operator*(scaled_quantity t, - scaled_quantity f) - -> decltype(std::declval() * std::declval()); - template - constexpr auto operator*(scaled_quantity f, - scaled_quantity t) - { - return t * f; - } - //@} - - /** - * @brief Returns a frequency as the inverse of a time. - * @tparam T type of pure number - * @tparam TR type of time unit scale (e.g. `std::micro`) - * @tparam TT type of time value representation - * @param v scalar value to be divided - * @param t time quantity, based on `util::quantities::unit::Second` - * @return a frequency _f_ so that `f * t` equals `v` - * - * The scale of the frequency unit is the inverse of the time one (e.g., - * a division by `util::quantities::millisecond` gives - * `util::quantities::kilohertz`). - */ - template - constexpr auto operator/(T v, scaled_second t) -> std::enable_if_t< - std::is_convertible_v, - scaled_hertz, decltype(std::declval() / std::declval())>>; - - /** - * @brief Returns a time as the inverse of a frequency. - * @tparam T type of pure number - * @tparam FR type of frequency unit scale (e.g. `std::mega`) - * @tparam FT type of frequency value representation - * @param v scalar value to be divided - * @param t frequency quantity, based on `util::quantities::unit::Hertz` - * @return a time _t_ so that `t * f` equals `v` - * - * The scale of the time unit is the inverse of the frequency one (e.g., - * a division by `util::quantities::kilohertz` gives - * `util::quantities::millisecond`). - */ - template - constexpr auto operator/(T v, scaled_hertz f) -> std::enable_if_t< - std::is_convertible_v, - scaled_second, decltype(std::declval() / std::declval())>>; - - } // namespace concepts - - /// @} - // -- END Special operations between Second and Hertz units ------------------ - } // namespace util::quantities -//------------------------------------------------------------------------------ -//--- template implementation -template -constexpr auto util::quantities::concepts::operator*( - scaled_quantity t, - scaled_quantity f) - -> decltype(std::declval() * std::declval()) -{ - return details::applyRatioToValue>(t.value() * f.value()); -} // util::quantities::operator*(Second, Hertz) +// --- BEGIN Special relations ------------------------------------------------- -//------------------------------------------------------------------------------ -template -constexpr auto util::quantities::concepts::operator/(T v, scaled_second t) - -> std::enable_if_t< - std::is_convertible_v, - scaled_hertz, decltype(std::declval() / std::declval())>> -{ - return scaled_hertz, - decltype(std::declval() / std::declval())>::castFrom(v / t.value()); -} // util::quantities::operator/(Second) +// unit relations (they are defined in `util::quantities::concepts::details`, +// so the `util::quantities::units` namespace can be shortened) -//------------------------------------------------------------------------------ -template -constexpr auto util::quantities::concepts::operator/(T v, scaled_hertz f) - -> std::enable_if_t< - std::is_convertible_v, - scaled_second, decltype(std::declval() / std::declval())>> -{ - return scaled_second, - decltype(std::declval() / std::declval())>::castFrom(v / f.value()); -} // util::quantities::operator/(Hertz) +#ifdef LARDATAALG_UTILITIES_QUANTITIES_SPACETIME_H +UTIL_QUANTITIES_UNITPRODUCT(units::Second, units::Hertz, units::Unity); +#endif -//------------------------------------------------------------------------------ +// --- END Special relations --------------------------------------------------- //------------------------------------------------------------------------------ diff --git a/lardataalg/Utilities/quantities/spacetime.h b/lardataalg/Utilities/quantities/spacetime.h index 6aa77eb1..04da6bf5 100644 --- a/lardataalg/Utilities/quantities/spacetime.h +++ b/lardataalg/Utilities/quantities/spacetime.h @@ -11,6 +11,7 @@ * * @note If the following headers are loaded, relationship between these and * some of their quantities are registered: + * * `frequency.h` (e.g. s·Hz = 1). * * `electromagnetism.h` (e.g. Ω·F = s). * * This is a header-only library. @@ -64,6 +65,11 @@ namespace util::quantities { * For this unit in particular, additional options are provided to accommodate * the custom of using the unit in plural form: `seconds_as` and `seconds` * are exactly equivalent to the singular-named counterparts. + * + * @note Additional relationships hold if the following headers are included: + * * `frequency.h`: `Second * Hertz` = constant (and derived) + * * `electromagnetism.h`: `Ohm * Farad` = `Second` (and derived) + * */ /// @{ @@ -858,6 +864,11 @@ namespace util::quantities { // unit relations (they are defined in `util::quantities::concepts::details`, // so the `util::quantities::units` namespace can be shortened) + +#ifdef LARDATAALG_UTILITIES_QUANTITIES_FREQUENCY_H +UTIL_QUANTITIES_UNITPRODUCT(units::Second, units::Hertz, units::Unity); +#endif + #ifdef LARDATAALG_UTILITIES_QUANTITIES_ELECTROMAGNETISM_H UTIL_QUANTITIES_UNITPRODUCT(units::Ohm, units::Farad, units::Second); #endif diff --git a/test/Utilities/frequency_test.cc b/test/Utilities/frequency_test.cc index e5368c83..e0ff0151 100644 --- a/test/Utilities/frequency_test.cc +++ b/test/Utilities/frequency_test.cc @@ -13,6 +13,7 @@ // LArSoft libraries #include "lardataalg/Utilities/quantities/frequency.h" +#include "lardataalg/Utilities/quantities/spacetime.h" // C/C++ standard libraries #include // std::decay_t<> From f2a97bd297de0521786df3e6d63007df0d68163d Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 29 Oct 2025 19:14:08 -0500 Subject: [PATCH 11/13] Extended quantity parsing It now accepts extended units (e.g. "millisecond") too. --- lardataalg/Utilities/quantities.h | 107 ++++++++++++++++++++++-------- test/Utilities/quantities_test.cc | 24 ++++++- 2 files changed, 103 insertions(+), 28 deletions(-) diff --git a/lardataalg/Utilities/quantities.h b/lardataalg/Utilities/quantities.h index ca5dd030..ab54d7ba 100644 --- a/lardataalg/Utilities/quantities.h +++ b/lardataalg/Utilities/quantities.h @@ -1513,6 +1513,17 @@ namespace util::quantities::details { * @throw ValueError the numerical value in `s` is not parseable * @throw ExtraCharactersError spurious characters after the numeric value * (including an unrecognised unit prefix) + * + * Supported formats: + * * `[spaces]` (e.g. `"5.5 Hz"); + * * `[spaces]` (e.g. `"5.5hertz"); + * * `[spaces]` (e.g. `"5.5kHz"); + * * `[spaces]` (e.g. `"5.5 kilohertz"). + * + * Spaces are optional. + * + * If `unitOptional` is `true`, additional format: + * * `` (e.g. `"5.5"`). */ template std::pair readUnit(std::string const& str, @@ -1536,23 +1547,38 @@ std::pair util::quantities::details::re using PrefixMap_t = std::map; using PrefixValue_t = typename PrefixMap_t::value_type; - static PrefixMap_t const factors{PrefixValue_t{"a"s, 1e-18}, - PrefixValue_t{"f"s, 1e-15}, - PrefixValue_t{"p"s, 1e-12}, - PrefixValue_t{"n"s, 1e-09}, - PrefixValue_t{"u"s, 1e-06}, - PrefixValue_t{"m"s, 1e-03}, - PrefixValue_t{"c"s, 1e-02}, - PrefixValue_t{"d"s, 1e-01}, - PrefixValue_t{""s, 1e+00}, - PrefixValue_t{"da"s, 1e+01}, - PrefixValue_t{"h"s, 1e+02}, - PrefixValue_t{"k"s, 1e+03}, - PrefixValue_t{"M"s, 1e+06}, - PrefixValue_t{"G"s, 1e+09}, - PrefixValue_t{"T"s, 1e+12}, - PrefixValue_t{"P"s, 1e+15}, - PrefixValue_t{"E"s, 1e+18}}; // factors + static PrefixMap_t const shortFactors{PrefixValue_t{"a"s, 1e-18}, + PrefixValue_t{"f"s, 1e-15}, + PrefixValue_t{"p"s, 1e-12}, + PrefixValue_t{"n"s, 1e-09}, + PrefixValue_t{"u"s, 1e-06}, + PrefixValue_t{"m"s, 1e-03}, + PrefixValue_t{"c"s, 1e-02}, + PrefixValue_t{"d"s, 1e-01}, + PrefixValue_t{"da"s, 1e+01}, + PrefixValue_t{"h"s, 1e+02}, + PrefixValue_t{"k"s, 1e+03}, + PrefixValue_t{"M"s, 1e+06}, + PrefixValue_t{"G"s, 1e+09}, + PrefixValue_t{"T"s, 1e+12}, + PrefixValue_t{"P"s, 1e+15}, + PrefixValue_t{"E"s, 1e+18}}; // shortFactors + static PrefixMap_t const longFactors{PrefixValue_t{"atto"s, 1e-18}, + PrefixValue_t{"femto"s, 1e-15}, + PrefixValue_t{"pico"s, 1e-12}, + PrefixValue_t{"nano"s, 1e-09}, + PrefixValue_t{"micro"s, 1e-06}, + PrefixValue_t{"milli"s, 1e-03}, + PrefixValue_t{"centi"s, 1e-02}, + PrefixValue_t{"deci"s, 1e-01}, + PrefixValue_t{"deca"s, 1e+01}, + PrefixValue_t{"hecto"s, 1e+02}, + PrefixValue_t{"kilo"s, 1e+03}, + PrefixValue_t{"mega"s, 1e+06}, + PrefixValue_t{"giga"s, 1e+09}, + PrefixValue_t{"tera"s, 1e+12}, + PrefixValue_t{"peta"s, 1e+15}, + PrefixValue_t{"exa"s, 1e+18}}; // factors static auto const composePrefixPattern = [](auto b, auto e) -> std::string { std::string pattern = "("; if (b != e) { @@ -1564,11 +1590,25 @@ std::pair util::quantities::details::re } return pattern += ")"; }; - static std::string const prefixPattern = composePrefixPattern(factors.begin(), factors.end()); + static std::string const shortPrefixPattern = + composePrefixPattern(shortFactors.begin(), shortFactors.end()); + static std::string const longPrefixPattern = + composePrefixPattern(longFactors.begin(), longFactors.end()); // --- END -- static initialization ------------------------------------------ - std::regex const unitPattern{"[[:blank:]]*(" + prefixPattern + "?" + - util::to_string(baseunit_t::symbol) + ")[[:blank:]]*$"}; + std::regex const unitPattern{"[[:blank:]]*(" + shortPrefixPattern + "?(" + + util::to_string(baseunit_t::symbol) + ")|" + longPrefixPattern + + "?(" + util::to_string(baseunit_t::name) + "))[[:blank:]]*$"}; + /* + * [0] full match [1] unit [2] short prefix [3] symbol [4] long prefix [5] name: + * " 7 centimeter" => [0] " 7 centimeter" [1] "centimeter" [2] "" [3] "" [4] "centi" [5] "meter" + * " 7 cm " => [0] " cm " [1] "cm" [2] "c" [3] "m" [4] "" [5] "" + */ + static constexpr int kMatchUnit = 1; + static constexpr int kMatchShortPx = 2; + static constexpr int kMatchSymbol = 3; + static constexpr int kMatchLongPx = 4; + static constexpr int kMatchName = 5; std::smatch unitMatch; if (!std::regex_search(str, unitMatch, unitPattern)) { @@ -1582,16 +1622,31 @@ std::pair util::quantities::details::re // // we do have a unit: // + std::string unitPrefix; + PrefixMap_t const* unitPool = nullptr; + if (!unitMatch.str(kMatchSymbol).empty()) { // unit symbol specified + assert(unitMatch.str(kMatchName).empty()); + unitPrefix = unitMatch.str(kMatchShortPx); + unitPool = &shortFactors; + } + else if (!unitMatch.str(kMatchName).empty()) { // unit name specified + assert(unitMatch.str(kMatchSymbol).empty()); + unitPrefix = unitMatch.str(kMatchLongPx); + unitPool = &longFactors; + } - // " 7 cm " => [0] full match (" cm ") [1] unit ("cm") [2] unit prefix ("c") - auto const iFactor = factors.find(unitMatch.str(2U)); - if (iFactor == factors.end()) { - throw InvalidUnitPrefix("Unit '" + unitMatch.str(1U) + "' has unsupported prefix '" + - unitMatch.str(2U) + "' (parsing '" + str + "')"); + auto scale = static_cast(1); + if (unitPool && !unitPrefix.empty()) { + auto iFactor = unitPool->find(unitPrefix); + if (iFactor == unitPool->end()) { + throw InvalidUnitPrefix("Unit '" + unitMatch.str(kMatchUnit) + "' has unsupported prefix '" + + unitPrefix + "' (parsing '" + str + "')"); + } + scale = iFactor->second; } return {str.substr(0U, str.length() - unitMatch.length()), - static_cast(unit_t::scale(iFactor->second))}; + static_cast(unit_t::scale(scale))}; } // util::quantities::details::readUnit() diff --git a/test/Utilities/quantities_test.cc b/test/Utilities/quantities_test.cc index fc8ade21..ab555a18 100644 --- a/test/Utilities/quantities_test.cc +++ b/test/Utilities/quantities_test.cc @@ -678,6 +678,9 @@ void test_makeQuantity() q = util::quantities::makeQuantity(" 3.0ms "); BOOST_TEST(q.value() == expected.value(), tol); + q = util::quantities::makeQuantity(" 3.0millisecond"); + BOOST_TEST(q.value() == expected.value(), tol); + q = util::quantities::makeQuantity("3ms"); BOOST_TEST(q.value() == expected.value(), tol); @@ -693,6 +696,9 @@ void test_makeQuantity() q = util::quantities::makeQuantity("+3E-3s"); BOOST_TEST(q.value() == expected.value(), tol); + q = util::quantities::makeQuantity("+3E-3second"); + BOOST_TEST(q.value() == expected.value(), tol); + q = util::quantities::makeQuantity("3", true); BOOST_TEST(q.value() == expected.value(), tol); @@ -705,18 +711,32 @@ void test_makeQuantity() BOOST_CHECK_THROW(util::quantities::makeQuantity("3"), util::quantities::MissingUnit); - BOOST_CHECK_THROW(util::quantities::makeQuantity("3 kg"), - util::quantities::MissingUnit); + // for the following three errors, InvalidUnitPrefix exception would be more + // appropriate but the current implementation does not try to distinguish + BOOST_CHECK_THROW(util::quantities::makeQuantity("3 msecond"), + util::quantities::ExtraCharactersError); + + BOOST_CHECK_THROW(util::quantities::makeQuantity("3 millis"), + util::quantities::ExtraCharactersError); + + BOOST_CHECK_THROW(util::quantities::makeQuantity("3 milli second"), + util::quantities::ExtraCharactersError); BOOST_CHECK_THROW(util::quantities::makeQuantity("3 dumbs"), util::quantities::ExtraCharactersError); + BOOST_CHECK_THROW(util::quantities::makeQuantity("3 kg"), + util::quantities::MissingUnit); + BOOST_CHECK_THROW(util::quantities::makeQuantity("three ms"), util::quantities::ValueError); BOOST_CHECK_THROW(util::quantities::makeQuantity("3.zero ms"), util::quantities::ExtraCharactersError); + BOOST_CHECK_THROW(util::quantities::makeQuantity("3.zero ms"), + util::quantities::ExtraCharactersError); + } // test_makeQuantity() // ----------------------------------------------------------------------------- From 1bf64ef196904c0de88c30e96c92344234adafc4 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 29 Oct 2025 20:03:34 -0500 Subject: [PATCH 12/13] Quantity library documentation fixes --- .../Utilities/details/quantity_products.h | 24 ++++-- lardataalg/Utilities/quantities.h | 76 +++++++++---------- 2 files changed, 56 insertions(+), 44 deletions(-) diff --git a/lardataalg/Utilities/details/quantity_products.h b/lardataalg/Utilities/details/quantity_products.h index ddb55e89..89cc1029 100644 --- a/lardataalg/Utilities/details/quantity_products.h +++ b/lardataalg/Utilities/details/quantity_products.h @@ -33,19 +33,31 @@ * * The core element is an object that describes the relation between three * quantities (A times B equal to P). This is a template specialization of - * `util::quantities::concepts::details::ProductDataType`, which includes the + * `util::quantities::concepts::details::UnitBinaryOpResult`, which includes the * type of the product of its arguments: * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * template <> struct UnitBinaryOpResult { + * template <> struct UnitBinaryOpResult { * using type = P; * }; * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * The `+1` is the exponent of `B`, that means that `A` and `B` are being - * multiplied (`A * B = P`). For the ratio relationship (`A / B = P`), `-1` - * needs to be used instead. + * The `'*'` is the type of operation (our convention is `'*'` means + * multiplication), that is, `A` and `B` are being multiplied (`A * B = P`). + * For the ratio relationship (`A / B = P`), `'/'` needs to be used instead. * A complete set of relations should be declared: A * B = P, B * A = P, * P / A = B and P / B = A. The macro `UTIL_QUANTITIES_UNITPRODUCT()` does that. * + * A special case is when one of the three units is a pure number. That is + * stated by using `units::Unity` as unit. In case of product, only the product + * type can be a `Unit`, while in case of ratio only the first one can. + * The other cases are already always supported when involve only one unit + * (we call them "normal" operations), and are not supported if they involve two + * different units. + * + * About the "special" operations (that is, not the "normal" ones): + * only product and ratio are implemented so far. + * Collisions with the normal `operator*` and `operator/` are reduced (I hope: + * prevented) by namespace plus argument-dependent lookup intertwined sometimes + * with intense metaprogramming. * * Naming conventions * ------------------- @@ -473,7 +485,7 @@ namespace util::quantities::concepts { * to access `util::quantities::units::Second` it is enough to write * `units::Second`); * - * The product unit can be a scalar (`units::Unit`), in which case the factor + * The product unit can be a scalar (`units::Unity`), in which case the factor * units are defined to be inverse one of the other. * * @note For product of the same unit, use `UTIL_QUANTITIES_UNITSQUARE()` diff --git a/lardataalg/Utilities/quantities.h b/lardataalg/Utilities/quantities.h index ab54d7ba..24f1db3c 100644 --- a/lardataalg/Utilities/quantities.h +++ b/lardataalg/Utilities/quantities.h @@ -382,7 +382,7 @@ namespace util::quantities { }; // struct Prefix - /// Trait: `T` implements a `BaseUnit` interface. + /// Trait: `T` implements a `UnitBase` interface. template constexpr bool is_base_unit_v = details::is_base_unit::value; @@ -512,49 +512,49 @@ namespace util::quantities { * * * a `Quantity` type will carry the information of its unit with the type * * quantities must be assigned other quantities: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using util::quantities::milliampere; + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * using util::quantities::milliampere; * - * milliampere i; - * i = 2.5; // ERROR! what is 2.5? - * i = milliampere(2.5); + * milliampere i; + * i = 2.5; // ERROR! what is 2.5? + * i = milliampere(2.5); * - * milliampere i2 { 2.5 }; // SPECIAL, allowed only in construction - * i2 = i1; - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * milliampere i2 { 2.5 }; // SPECIAL, allowed only in construction + * i2 = i1; + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * can be converted, implicitly or explicitly, to its plain value: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using util::quantities::milliampere; + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * using util::quantities::milliampere; * - * milliampere i { 6.0 }; - * double v = i; // implicit conversion - * v = double(i); // explicit conversion - * v = i.value(); // even more explicit conversion - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * milliampere i { 6.0 }; + * double v = i; // implicit conversion + * v = double(i); // explicit conversion + * v = i.value(); // even more explicit conversion + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * weakly resists attempts to mess with units - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * milliampere mi { 4.0 }; - * microampere ui { 500.0 }; - * mi = ui; // now mi == 0.5 - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * milliampere mi { 4.0 }; + * microampere ui { 500.0 }; + * mi = ui; // now mi == 0.5 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * weakly attempts to preserve the unit information - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using namespace util::quantities; - * using namespace util::quantities::electronics_literals; + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * using namespace util::quantities; + * using namespace util::quantities::electronics_literals; * - * milliampere mi { 4.0 }; - * microampere ui { 500.0 }; + * milliampere mi { 4.0 }; + * microampere ui { 500.0 }; * - * mi += ui; // 4.5 mA - * mi *= ui; // ERROR! what does this even mean?? - * mi += 5.0; // ERROR! - * mi += milliampere(3.0); // 7.5 mA - * mi += 2.0_ma; // 9.5 mA - * mi + ui; // ERROR! (arbitrary whether to represent in mA or uA) - * mi + 5.0; // ERROR! (as above) - * mi / 5.0; // milliampere{1.9} - * mi - 5_mA; // milliampere{4.5} - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * mi += ui; // 4.5 mA + * mi *= ui; // ERROR! what does this even mean?? + * mi += 5.0; // ERROR! + * mi += milliampere(3.0); // 7.5 mA + * mi += 2.0_ma; // 9.5 mA + * mi + ui; // ERROR! (arbitrary whether to represent in mA or uA) + * mi + 5.0; // ERROR! (as above) + * mi / 5.0; // milliampere{1.9} + * mi - 5_mA; // milliampere{4.5} + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * (`milliampere` and `microampere` are hypotetical instantiations of * `Quantity` template class from `util::quantities`, and they also have @@ -1240,7 +1240,7 @@ namespace util::quantities::concepts::details { //--- template implementation //------------------------------------------------------------------------------ /// Implementation of `is_base_unit_v` for actual base unit objects -/// (e.g. `BaseUnit`): asks the complete `BaseUnit` mandatory interface. +/// (e.g. `UnitBase`): asks the complete `UnitBase` mandatory interface. template struct util::quantities::concepts::details::is_base_unit< U, @@ -1257,7 +1257,7 @@ struct util::quantities::concepts::details:: }; /// Implementation of `base_unit_of` supporting a base unit itself -/// (e.g. `BaseUnit`) by asking the complete `BaseUnit` mandatory interface. +/// (e.g. `UnitBase`) by asking the complete `UnitBase` mandatory interface. template struct util::quantities::concepts::details:: base_unit_extactor>> { From bcd2e4f8ed71c2b39e5565b8e682c9aa65e75df5 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 5 Nov 2025 18:20:16 -0600 Subject: [PATCH 13/13] New headers are better when installed. --- lardataalg/Utilities/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lardataalg/Utilities/CMakeLists.txt b/lardataalg/Utilities/CMakeLists.txt index 91f4711a..e22d0b2e 100644 --- a/lardataalg/Utilities/CMakeLists.txt +++ b/lardataalg/Utilities/CMakeLists.txt @@ -8,11 +8,12 @@ cet_make_library(LIBRARY_NAME UtilitiesHeaders INTERFACE quantities_fhicl.h quantities.h StatCollector.h + details/quantity_products.h LIBRARIES INTERFACE larcorealg::headers fhiclcpp::fhiclcpp Boost::boost ) -install_headers(SUBDIRS quantities) -install_source(SUBDIRS quantities) +install_headers(SUBDIRS quantities details) +install_source(SUBDIRS quantities details)