From 92c70f1a4e1779f3bb12208f0181354e65df3148 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Fri, 16 Dec 2022 18:15:48 +0100 Subject: [PATCH] feat: `quantity_spec` support added --- example/box_example.cpp | 15 +- .../glide_computer/include/glide_computer.h | 2 +- example/hello_units.cpp | 6 +- src/core/CMakeLists.txt | 9 +- src/core/include/units/bits/algorithm.h | 21 + .../include/units/bits/expression_template.h | 47 +- .../include/units/bits/external/type_traits.h | 2 +- src/core/include/units/concepts.h | 25 +- src/core/include/units/dimension.h | 317 +++-------- .../include/units/generic/dimensionless.h | 2 +- src/core/include/units/math.h | 244 +++------ src/core/include/units/quantity.h | 23 +- src/core/include/units/quantity_cast.h | 43 +- src/core/include/units/quantity_spec.h | 518 ++++++++++++++++++ src/core/include/units/reference.h | 66 +-- src/core/include/units/system_reference.h | 36 +- src/core/include/units/unit.h | 84 +-- src/systems/isq/CMakeLists.txt | 5 +- .../{base_dimensions.h => base_quantities.h} | 28 +- src/systems/isq/include/units/isq/isq.h | 4 +- src/systems/isq/include/units/isq/mechanics.h | 116 ++-- .../isq/include/units/isq/space_and_time.h | 92 ++-- .../isq/include/units/isq/thermodynamics.h | 4 +- .../si/include/units/si/unit_symbols.h | 1 + src/systems/si/include/units/si/units.h | 2 +- test/unit_test/runtime/CMakeLists.txt | 4 +- test/unit_test/static/CMakeLists.txt | 8 +- test/unit_test/static/dimension_test.cpp | 169 +++--- test/unit_test/static/quantity_spec_test.cpp | 355 ++++++++++++ test/unit_test/static/reference_test.cpp | 71 +-- test/unit_test/static/test_tools.h | 16 +- test/unit_test/static/unit_test.cpp | 16 +- 32 files changed, 1524 insertions(+), 827 deletions(-) create mode 100644 src/core/include/units/quantity_spec.h rename src/systems/isq/include/units/isq/{base_dimensions.h => base_quantities.h} (52%) create mode 100644 test/unit_test/static/quantity_spec_test.cpp diff --git a/example/box_example.cpp b/example/box_example.cpp index 3683d6aa..c91ffdf2 100644 --- a/example/box_example.cpp +++ b/example/box_example.cpp @@ -34,15 +34,14 @@ namespace { using namespace units; -using namespace units::si; using namespace units::si::unit_symbols; -inline constexpr auto g = 1 * standard_gravity; +inline constexpr auto g = 1 * si::standard_gravity; inline constexpr auto air_density = 1.225 * isq::mass_density[kg / m3]; class Box { quantity base_; - quantity height_; + quantity height_; quantity density_ = air_density; public: constexpr Box(const quantity& length, const quantity& width, @@ -51,11 +50,11 @@ public: { } - [[nodiscard]] constexpr auto filled_weight() const + [[nodiscard]] constexpr quantity_of auto filled_weight() const { const weak_quantity_of auto volume = base_ * height_; - const quantity_of auto mass = density_ * volume; - return mass * g; + const weak_quantity_of auto mass = density_ * volume; + return quantity_cast(mass * g); } [[nodiscard]] constexpr quantity fill_level(const quantity& measured_mass) const @@ -80,7 +79,9 @@ public: int main() { - const auto mm = isq::length[unit_symbols::mm]; // helper reference value + using namespace units::si; + + constexpr auto mm = isq::length[unit_symbols::mm]; // helper reference object const auto height = (200.0 * mm)[metre]; auto box = Box(1000.0 * mm, 500.0 * mm, height); diff --git a/example/glide_computer/include/glide_computer.h b/example/glide_computer/include/glide_computer.h index c846b72d..d127943b 100644 --- a/example/glide_computer/include/glide_computer.h +++ b/example/glide_computer/include/glide_computer.h @@ -152,7 +152,7 @@ public: leg(const waypoint& b, const waypoint& e) noexcept : begin_(&b), end_(&e) {} constexpr const waypoint& begin() const { return *begin_; }; constexpr const waypoint& end() const { return *end_; } - constexpr const distance get_length() const { return length_; } + constexpr distance get_length() const { return length_; } }; using legs = std::vector; diff --git a/example/hello_units.cpp b/example/hello_units.cpp index bc838b30..f41d2e21 100644 --- a/example/hello_units.cpp +++ b/example/hello_units.cpp @@ -30,7 +30,7 @@ using namespace units; -constexpr quantity_of auto avg_speed(quantity_of auto d, quantity_of auto t) +constexpr quantity_of auto avg_speed(quantity_of auto d, quantity_of auto t) { return quantity_cast(d / t); } @@ -42,8 +42,8 @@ int main() constexpr auto v1 = 110 * isq::speed[km / h]; constexpr auto v2 = 70. * isq::speed[mph]; - constexpr auto v3 = avg_speed(220 * isq::length[km], 2 * isq::time[h]); - constexpr auto v4 = avg_speed(quantity{140}, quantity{2}); + constexpr auto v3 = avg_speed(220 * isq::distance[km], 2 * isq::duration[h]); + constexpr auto v4 = avg_speed(quantity{140}, quantity{2}); constexpr auto v5 = quantity_cast>(v3); constexpr auto v6 = quantity_cast(v4); constexpr auto v7 = quantity_cast(v6); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 045af906..ef00b8e3 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -42,18 +42,19 @@ add_library( include/units/customization_points.h include/units/dimension.h # include/units/generic/angle.h - # include/units/generic/dimensionless.h + include/units/generic/dimensionless.h # include/units/generic/solid_angle.h # include/units/kind.h include/units/magnitude.h - # include/units/math.h + include/units/math.h # include/units/point_origin.h include/units/quantity.h - # include/units/quantity_cast.h + include/units/quantity_cast.h # include/units/quantity_kind.h # include/units/quantity_point.h # include/units/quantity_point_kind.h - # include/units/random.h + include/units/quantity_spec.h + include/units/random.h include/units/ratio.h include/units/reference.h include/units/symbol_text.h diff --git a/src/core/include/units/bits/algorithm.h b/src/core/include/units/bits/algorithm.h index 92aa4200..c8d730d9 100644 --- a/src/core/include/units/bits/algorithm.h +++ b/src/core/include/units/bits/algorithm.h @@ -24,6 +24,7 @@ #include // IWYU pragma: keep #include +#include #include #include @@ -111,6 +112,26 @@ constexpr auto lexicographical_compare_three_way(I1 f1, I1 l1, I2 f2, I2 l2) return ::units::detail::lexicographical_compare_three_way(f1, l1, f2, l2, std::compare_three_way()); } +template +constexpr ForwardIt max_element(ForwardIt first, ForwardIt last) +{ + if (first == last) return last; + + ForwardIt largest = first; + ++first; + + for (; first != last; ++first) + if (*largest < *first) largest = first; + + return largest; +} + +template +constexpr T max(std::initializer_list ilist) +{ + return *max_element(ilist.begin(), ilist.end()); +} + template struct in_out_result { [[no_unique_address]] I in; diff --git a/src/core/include/units/bits/expression_template.h b/src/core/include/units/bits/expression_template.h index a4dbc71f..fcf41745 100644 --- a/src/core/include/units/bits/expression_template.h +++ b/src/core/include/units/bits/expression_template.h @@ -104,6 +104,19 @@ struct power { namespace detail { +template +struct expr_type_impl : std::type_identity {}; + +template +struct expr_type_impl> : std::type_identity {}; + +} // namespace detail + +template +using expr_type = TYPENAME detail::expr_type_impl::type; + +namespace detail { + template inline constexpr bool is_specialization_of_power = false; @@ -256,16 +269,7 @@ struct expr_simplify, NRest...>, type_list typename Pred> -struct expr_less_impl : Pred {}; - -template typename Pred> -struct expr_less_impl, power, Pred> : Pred {}; - -template typename Pred> -struct expr_less_impl, Rhs, Pred> : Pred {}; - -template typename Pred> -struct expr_less_impl, Pred> : Pred {}; +struct expr_less_impl : Pred, expr_type> {}; template typename Pred> struct expr_less_impl, Pred> : std::true_type {}; @@ -504,19 +508,29 @@ template typename Proj> concept expr_projectable = requires { typename T::_num_; typename T::_den_; - requires type_list_size + type_list_size > 0; requires expr_projectable_impl; requires expr_projectable_impl; }; +template +[[nodiscard]] consteval auto map_power(T t) +{ + return t; +} + +template +[[nodiscard]] consteval auto map_power(power) +{ + return pow(T{}); +} + template typename Proj, template typename To, typename OneType, template typename Pred, expr_type_projectable... Nums, expr_type_projectable... Dens> [[nodiscard]] consteval auto expr_map_impl(type_list, type_list) { - using nums = type_list_sort, Proj>::type...>, Pred>; - using dens = type_list_sort, Proj>::type...>, Pred>; - return detail::get_optimized_expression(); + return (OneType{} * ... * map_power(typename expr_type_map, Proj>::type{})) / + (OneType{} * ... * map_power(typename expr_type_map, Proj>::type{})); } /** @@ -532,7 +546,10 @@ template typename Proj, template typename To, ty template typename Pred, expr_projectable T> [[nodiscard]] consteval auto expr_map(T) { - return expr_map_impl(typename T::_num_{}, typename T::_den_{}); + if constexpr (type_list_size + type_list_size == 0) + return OneType{}; + else + return expr_map_impl(typename T::_num_{}, typename T::_den_{}); } } // namespace detail diff --git a/src/core/include/units/bits/external/type_traits.h b/src/core/include/units/bits/external/type_traits.h index 6d9df5df..aa935255 100644 --- a/src/core/include/units/bits/external/type_traits.h +++ b/src/core/include/units/bits/external/type_traits.h @@ -78,6 +78,6 @@ template typename Type> concept is_derived_from_specialization_of = requires(T* t) { detail::to_base_specialization_of(t); }; template -concept one_of = (... || std::same_as); +concept one_of = (false || ... || std::same_as); } // namespace units diff --git a/src/core/include/units/concepts.h b/src/core/include/units/concepts.h index 49d963e1..19cf418b 100644 --- a/src/core/include/units/concepts.h +++ b/src/core/include/units/concepts.h @@ -22,17 +22,14 @@ #pragma once -// IWYU pragma: begin_exports -// #include -// #include -// IWYU pragma: end_exports #include #include +#include #include namespace units { -template +template struct reference; namespace detail { @@ -40,8 +37,8 @@ namespace detail { template inline constexpr bool is_specialization_of_reference = false; -template -inline constexpr bool is_specialization_of_reference> = true; +template +inline constexpr bool is_specialization_of_reference> = true; } // namespace detail @@ -92,11 +89,15 @@ class quantity; namespace detail { +// TODO make the below code from the comment to compile and replace it template inline constexpr bool is_quantity> = true; +// template +// void to_base_specialization_of_quantity(const volatile quantity*); + // template -// requires units::is_derived_from_specialization_of +// requires requires(T* t) { to_base_specialization_of_quantity(t); } // inline constexpr bool is_quantity = true; } // namespace detail @@ -109,6 +110,7 @@ inline constexpr bool is_quantity> = true; */ template concept quantity_of = Quantity && ((Dimension> && Q::dimension == V) || + (QuantitySpec> && Q::quantity_spec == V) || (Reference> && Q::reference == V)); /** @@ -119,8 +121,9 @@ concept quantity_of = Quantity && ((Dimension concept weak_quantity_of = Quantity && - ((Dimension> && interconvertible(Q::dimension, V)) || - (Reference> && - interconvertible(Q::dimension, V.dimension) && Q::unit == V.unit)); + ((Dimension> && Q::dimension == V) || + (QuantitySpec> && interconvertible(Q::quantity_spec, V)) || + (Reference> && Q::dimension == V.dimension && + Q::unit == V.unit)); } // namespace units diff --git a/src/core/include/units/dimension.h b/src/core/include/units/dimension.h index 3c1a166b..f001d448 100644 --- a/src/core/include/units/dimension.h +++ b/src/core/include/units/dimension.h @@ -23,49 +23,11 @@ #pragma once #include -#include #include -#include +#include namespace units { -namespace detail { - -template -inline constexpr bool is_derived_dimension = false; - -} - -/** - * @brief A concept matching all derived dimensions in the library. - * - * Satisfied by all dimension types either being a specialization of `derived_dimension` - * or derived from it. - */ -template -concept DerivedDimension = detail::is_derived_dimension; - -/** - * @brief A concept matching all dimensions in the library. - * - * Satisfied by all dimension types for which either `BaseDimension` or `DerivedDimension` is `true`. - */ -template -concept Dimension = BaseDimension || DerivedDimension; - -namespace detail { - -template -inline constexpr bool is_valid_unit_for_dimension = false; - -} - -template -concept valid_unit_for_dimension = Unit && Dimension && detail::is_valid_unit_for_dimension; - -template -struct reference; - /** * @brief A dimension of a base quantity * @@ -74,17 +36,17 @@ struct reference; * being mutually independent since a base quantity cannot be expressed as a product of powers of the other base * quantities. * - * Symbol template parameters is an unique identifier of the base dimension. The same identifiers can be multiplied - * and divided which will result with an adjustment of its factor in an exponent of a derived_dimension + * `Symbol` template parameter is an unique identifier of the base dimension. The same identifiers can be multiplied + * and divided which will result with an adjustment of its factor in an exponent of a `derived_dimension` * (in case of zero the dimension will be simplified and removed from further analysis of current expresion). * * User should derive a strong type from this class template rather than use it directly in the source code. * For example: * * @code{.cpp} - * inline constexpr struct length : base_dimension<"L"> {} length; - * inline constexpr struct time : base_dimension<"T"> {} time; - * inline constexpr struct mass : base_dimension<"M"> {} mass; + * inline constexpr struct dim_length : base_dimension<"L"> {} dim_length; + * inline constexpr struct dim_time : base_dimension<"T"> {} dim_time; + * inline constexpr struct dim_mass : base_dimension<"M"> {} dim_mass; * @endcode * * @note A common convention in this library is to assign the same name for a type and an object of this type. @@ -94,28 +56,35 @@ struct reference; * * @tparam Symbol an unique identifier of the base dimension used to provide dimensional analysis support */ -#ifdef __cpp_explicit_this_parameter -template -#else -template -#endif +template struct base_dimension { static constexpr auto symbol = Symbol; ///< Unique base dimension identifier - -#ifdef __cpp_explicit_this_parameter - template U> - [[nodiscard]] constexpr auto operator[](this const Self, U) -#else - template U> - [[nodiscard]] constexpr auto operator[](U) const -#endif - { - return reference{}; - } }; namespace detail { +template +void to_base_base_dimension(const volatile base_dimension*); + +template +inline constexpr bool is_specialization_of_base_dimension = false; + +template +inline constexpr bool is_specialization_of_base_dimension> = true; + +} // namespace detail + +/** + * @brief A concept matching all named base dimensions in the library. + * + * Satisfied by all dimension types derived from a specialization of `base_dimension`. + */ +template +concept BaseDimension = requires(T* t) { detail::to_base_base_dimension(t); } && + (!detail::is_specialization_of_base_dimension); + +namespace detail { + template struct base_dimension_less : std::bool_constant<(Lhs::symbol < Rhs::symbol)> {}; @@ -140,22 +109,9 @@ inline constexpr bool is_per_of_dims> = } // namespace detail template -concept DerivedDimensionSpec = +concept DerivedDimensionExpr = BaseDimension || detail::is_dimension_one || detail::is_power_of_dim || detail::is_per_of_dims; -template -struct derived_dimension; - -namespace detail { - -template -struct derived_dimension_impl : detail::expr_fractions, Ds...> { - using _type_ = derived_dimension; // exposition only -}; - -} // namespace detail - - /** * @brief A dimension of a derived quantity * @@ -172,99 +128,41 @@ struct derived_dimension_impl : detail::expr_fractions, Ds.. * For example: * * @code{.cpp} - * inline constexpr struct frequency : decltype(1 / time) {} frequency; - * inline constexpr struct speed : decltype(length / time) {} speed; - * inline constexpr struct acceleration : decltype(speed / time) {} acceleration; - * inline constexpr struct force : decltype(mass * acceleration) {} force; - * inline constexpr struct energy : decltype(force * length) {} energy; - * inline constexpr struct moment_of_force : decltype(length * force) {} moment_of_force; - * inline constexpr struct torque : decltype(moment_of_force) {} torque; + * using frequency = decltype(1 / dim_time); + * using speed = decltype(dim_length / dim_time); + * using acceleration = decltype(dim_speed / dim_time); + * using force = decltype(dim_mass * dim_acceleration); + * using energy = decltype(dim_force * dim_length); + * using moment_of_force = decltype(dim_length * dim_force); + * using torque = decltype(dim_moment_of_force); * @endcode * - * - `frequency` will be derived from type `derived_dimension>` - * - `speed` will be derived from type `derived_dimension>` - * - `acceleration` will be derived from type `derived_dimension>>` - * - `force` will be derived from type `derived_dimension>>` - * - `energy` will be derived from type `derived_dimension, mass, per>>` + * - `frequency` will be derived from type `derived_dimension>` + * - `speed` will be derived from type `derived_dimension>` + * - `acceleration` will be derived from type `derived_dimension>>` + * - `force` will be derived from type `derived_dimension>>` + * - `energy` will be derived from type `derived_dimension, dim_mass, per>>` * * @note A common convention in this library is to assign the same name for a type and an object of this type. * Besides defining them user never works with the dimension types in the source code. All operations * are done on the objects. Contrarily, the dimension types are the only one visible in the compilation * errors. Having them of the same names improves user experience and somehow blurs those separate domains. * - * Two dimensions are deemed equal when they are of the same type. With that strong type `speed` and - * `derived_dimension>` are considered not equal. They are convertible though. - * User can implicitly convert up and down the inheritance hierarchy between those two. - * `torque` and `moment_of_force` are convertible as well. However, `energy` and `torque` - * are not convertible as they do not inherit from each other. They are from two separate branches of - * dimensionally equivalent quantities. - * * @tparam Ds a parameter pack consisting tokens allowed in the dimension specification * (base dimensions, `dimension_one`, `power`, `per<...>`) * * @note User should not instantiate this type! It is not exported from the C++ module. The library will * instantiate this type automatically based on the dimensional arithmetic equation provided by the user. */ -#ifdef __cpp_explicit_this_parameter - -template -struct derived_dimension : detail::derived_dimension_impl { - template - requires valid_unit_for_dimension || (sizeof...(Ds) == 0 && interconvertible(U{}, one)) - [[nodiscard]] constexpr auto operator[](this const Self, U) - { - return reference{}; - } -}; - -#else - -template -struct derived_dimension; - -template -struct derived_dimension : detail::derived_dimension_impl { - template - requires valid_unit_for_dimension || (sizeof...(Ds) == 0 && interconvertible(U{}, one)) - [[nodiscard]] constexpr auto operator[](U) const - { - return reference{}; - } -}; - -template -struct derived_dimension : D { - template - requires valid_unit_for_dimension || - (interconvertible(derived_dimension{}, derived_dimension<>{}) && interconvertible(U{}, one)) - [[nodiscard]] constexpr auto operator[](U) const - { - return reference{}; - } -}; - -#endif - -namespace detail { - -template -void to_base_specialization_of_derived_dimension(const volatile derived_dimension*); - -template -inline constexpr bool is_derived_from_specialization_of_derived_dimension = - requires(T * t) { to_base_specialization_of_derived_dimension(t); }; - -template - requires is_derived_from_specialization_of_derived_dimension -inline constexpr bool is_derived_dimension = true; - -} // namespace detail +template +struct derived_dimension : detail::expr_fractions, Ds...> {}; /** * @brief Dimension one * * Dimension for which all the exponents of the factors corresponding to the base - * dimensions are zero. Also commonly named as "dimensionless". + * dimensions are zero. It is a dimension of a quantity of dimension one also known as + * "dimensionless". */ inline constexpr struct dimension_one : derived_dimension<> { } dimension_one; @@ -274,21 +172,32 @@ namespace detail { template<> inline constexpr bool is_dimension_one = true; -template -struct dim_type_impl { - using type = T; -}; +template +void to_base_specialization_of_derived_dimension(const volatile derived_dimension*); -template -struct dim_type_impl { - using type = TYPENAME T::_type_; -}; - -template -using dim_type = TYPENAME dim_type_impl::type; +template +inline constexpr bool is_derived_from_specialization_of_derived_dimension = + requires(T * t) { to_base_specialization_of_derived_dimension(t); }; } // namespace detail +/** + * @brief A concept matching all derived dimensions in the library. + * + * Satisfied by all dimension types either being a specialization of `derived_dimension` + * or derived from it (inheritance needed to properly handle `dimension_one`). + */ +template +concept DerivedDimension = detail::is_derived_from_specialization_of_derived_dimension; + +/** + * @brief A concept matching all dimensions in the library. + * + * Satisfied by all dimension types for which either `BaseDimension` or `DerivedDimension` is `true`. + */ +template +concept Dimension = BaseDimension || DerivedDimension; + // Operators @@ -296,21 +205,21 @@ template [[nodiscard]] consteval Dimension auto operator*(Lhs, Rhs) { return detail::expr_multiply( - detail::dim_type{}, detail::dim_type{}); + Lhs{}, Rhs{}); } template [[nodiscard]] consteval Dimension auto operator/(Lhs, Rhs) { - return detail::expr_divide( - detail::dim_type{}, detail::dim_type{}); + return detail::expr_divide(Lhs{}, + Rhs{}); } template [[nodiscard]] consteval Dimension auto operator/(int value, D) { gsl_Expects(value == 1); - return detail::expr_invert(detail::dim_type{}); + return detail::expr_invert(D{}); } template @@ -322,31 +231,6 @@ template return is_same_v; } -template -[[nodiscard]] consteval bool interconvertible(D1, D2) -{ - return std::derived_from || std::derived_from; -} - -[[nodiscard]] consteval auto common_dimension(Dimension auto d) { return d; } - -template -[[nodiscard]] consteval auto common_dimension(D1 d1, D2 d2) - requires(interconvertible(d1, d2)) -{ - if constexpr (std::derived_from) - return d1; - else - return d2; -} - -[[nodiscard]] consteval auto common_dimension(Dimension auto d1, Dimension auto d2, Dimension auto d3, - Dimension auto... rest) - requires requires { common_dimension(common_dimension(d1, d2), d3, rest...); } -{ - return common_dimension(common_dimension(d1, d2), d3, rest...); -} - /** * @brief Computes the value of a dimension raised to the `Num/Den` power * @@ -370,63 +254,6 @@ template detail::type_list_of_base_dimension_less>(d); } -namespace detail { - -template -[[nodiscard]] consteval Dimension auto get_dimension_for_impl(U) - requires requires { U::base_dimension; } -{ - return U::base_dimension; -} - -template - requires requires { U::base_dimension; } -using to_base_dimension = std::remove_const_t; - -template -[[nodiscard]] consteval Dimension auto get_dimension_for_impl(const derived_unit& u) - requires detail::expr_projectable, to_base_dimension> -{ - return detail::expr_map(u); -} - -template -concept associated_unit = Unit && requires(U u) { get_dimension_for_impl(get_canonical_unit(u).reference_unit); }; - -[[nodiscard]] consteval Dimension auto get_dimension_for(associated_unit auto u) -{ - return get_dimension_for_impl(get_canonical_unit(u).reference_unit); -} - -template - requires requires { detail::get_dimension_for(U); } && (interconvertible(Dim, detail::get_dimension_for(U))) -inline constexpr bool is_valid_unit_for_dimension = true; - -} // namespace detail - // TODO consider adding the support for text output of the dimensional equation } // namespace units - -#ifdef __cpp_explicit_this_parameter - -#define BASE_DIMENSION(name, symbol) \ - inline constexpr struct name : base_dimension { \ - } name - -#define DERIVED_DIMENSION(name, base) \ - inline constexpr struct name : base { \ - } name - -#else - -#define BASE_DIMENSION(name, symbol) \ - inline constexpr struct name : base_dimension { \ - } name - -#define DERIVED_DIMENSION(name, base) \ - inline constexpr struct name : derived_dimension { \ - } name - -#endif diff --git a/src/core/include/units/generic/dimensionless.h b/src/core/include/units/generic/dimensionless.h index 92a171ac..da6dc0ae 100644 --- a/src/core/include/units/generic/dimensionless.h +++ b/src/core/include/units/generic/dimensionless.h @@ -27,7 +27,7 @@ namespace units { -struct dimension_one; // defined in +struct dimensionless; // defined in struct one; // defined in // clang-format off diff --git a/src/core/include/units/math.h b/src/core/include/units/math.h index 20a111d2..1add7346 100644 --- a/src/core/include/units/math.h +++ b/src/core/include/units/math.h @@ -22,10 +22,7 @@ #pragma once -#include #include -#include -#include #include #include @@ -39,9 +36,9 @@ namespace units { /** - * @brief Computes the value of a quantity raised to the power `N` + * @brief Computes the value of a quantity raised to the `Num/Den` power * - * Both the quantity value and its dimension are the base of the operation. + * Both the quantity value and its quantity specification are the base of the operation. * * @tparam Num Exponent numerator * @tparam Den Exponent denominator @@ -56,11 +53,11 @@ template using rep = TYPENAME Q::rep; if constexpr (Num == 0) { return rep(1); + } else if constexpr (ratio{Num, Den} == 1) { + return q; } else { - using dim = dimension_pow; - using unit = downcast_unit(Q::unit::mag)>; using std::pow; - return quantity( + return quantity(Q::quantity_spec), pow(Q::unit)>{}, rep>( static_cast(pow(q.number(), static_cast(Num) / static_cast(Den)))); } } @@ -68,7 +65,7 @@ template /** * @brief Computes the square root of a quantity * - * Both the quantity value and its dimension are the base of the operation. + * Both the quantity value and its quantity specification are the base of the operation. * * @param q Quantity being the base of the operation * @return Quantity The result of computation @@ -77,17 +74,16 @@ template [[nodiscard]] inline Quantity auto sqrt(const Q& q) noexcept requires requires { sqrt(q.number()); } || requires { std::sqrt(q.number()); } { - using dim = dimension_pow; - using unit = downcast_unit(Q::unit::mag)>; using rep = TYPENAME Q::rep; using std::sqrt; - return quantity(static_cast(sqrt(q.number()))); + return quantity(Q::quantity_spec), pow<1, 2>(Q::unit)>{}, rep>( + static_cast(sqrt(q.number()))); } /** * @brief Computes the cubic root of a quantity * - * Both the quantity value and its dimension are the base of the operation. + * Both the quantity value and its quantity specification are the base of the operation. * * @param q Quantity being the base of the operation * @return Quantity The result of computation @@ -96,11 +92,10 @@ template [[nodiscard]] inline Quantity auto cbrt(const Q& q) noexcept requires requires { cbrt(q.number()); } || requires { std::cbrt(q.number()); } { - using dim = dimension_pow; - using unit = downcast_unit(Q::unit::mag)>; using rep = TYPENAME Q::rep; using std::cbrt; - return quantity(static_cast(cbrt(q.number()))); + return quantity(Q::quantity_spec), pow<1, 3>(Q::unit)>{}, rep>( + static_cast(cbrt(q.number()))); } /** @@ -111,12 +106,12 @@ template * @param q Quantity being the base of the operation * @return Quantity The value of the same quantity type */ -template -[[nodiscard]] inline dimensionless exp(const dimensionless& q) +template Q, typename Rep> +[[nodiscard]] inline Q exp(const Q& q) requires requires { exp(q.number()); } || requires { std::exp(q.number()); } { using std::exp; - return quantity_cast(dimensionless(exp(quantity_cast(q).number()))); + return quantity_cast(exp(quantity_cast(q).number()) * dimensionless[one]); } /** @@ -125,12 +120,12 @@ template * @param q Quantity being the base of the operation * @return Quantity The absolute value of a provided quantity */ -template -[[nodiscard]] inline quantity abs(const quantity& q) noexcept +template +[[nodiscard]] inline Q abs(const Q& q) noexcept requires requires { abs(q.number()); } || requires { std::abs(q.number()); } { using std::abs; - return quantity(abs(q.number())); + return Q(abs(q.number())); } /** @@ -141,11 +136,11 @@ template * @tparam Q Quantity type being the base of the operation * @return Quantity The epsilon value for quantity's representation type */ -template - requires requires { std::numeric_limits::epsilon(); } -[[nodiscard]] constexpr Quantity auto epsilon() noexcept +template + requires requires { std::numeric_limits::epsilon(); } +[[nodiscard]] constexpr Quantity auto epsilon(R r) noexcept { - return Q(std::numeric_limits::epsilon()); + return std::numeric_limits::epsilon() * r; } /** @@ -154,14 +149,14 @@ template * @tparam q Quantity being the base of the operation * @return Quantity The rounded quantity with unit type To */ -template -[[nodiscard]] constexpr quantity floor(const quantity& q) noexcept +template +[[nodiscard]] constexpr quantity{}, Rep> floor(const quantity& q) noexcept requires((!treat_as_floating_point) || requires { floor(q.number()); } || requires { std::floor(q.number()); }) && - (std::same_as || requires { - ::units::quantity_cast(q); - quantity::one(); - }) + (To == R.unit || requires { + ::units::quantity_cast(q); + quantity{}, Rep>::one(); + }) { const auto handle_signed_results = [&](const T& res) { if (res > q) { @@ -171,46 +166,34 @@ template }; if constexpr (treat_as_floating_point) { using std::floor; - if constexpr (std::is_same_v) { - return quantity(floor(q.number())); + if constexpr (To == R.unit) { + return quantity{}, Rep>(floor(q.number())); } else { - return handle_signed_results(quantity(floor(quantity_cast(q).number()))); + return handle_signed_results( + quantity{}, Rep>(floor(quantity_cast(q).number()))); } } else { - if constexpr (std::is_same_v) { - return q; + if constexpr (To == R.unit) { + return quantity_cast(q); } else { return handle_signed_results(quantity_cast(q)); } } } -/** - * @brief Overload of @c ::units::floor() using the unit type of To - * - * @tparam q Quantity being the base of the operation - * @return Quantity The rounded quantity with unit type of quantity To - */ -template D, typename U, std::same_as Rep> -[[nodiscard]] constexpr quantity floor(const quantity& q) noexcept - requires requires { ::units::floor(q); } -{ - return ::units::floor(q); -} - /** * @brief Computes the smallest quantity with integer representation and unit type To with its number not less than q * * @tparam q Quantity being the base of the operation * @return Quantity The rounded quantity with unit type To */ -template -[[nodiscard]] constexpr quantity ceil(const quantity& q) noexcept +template +[[nodiscard]] constexpr quantity{}, Rep> ceil(const quantity& q) noexcept requires((!treat_as_floating_point) || requires { ceil(q.number()); } || requires { std::ceil(q.number()); }) && - (std::same_as || requires { - ::units::quantity_cast(q); - quantity::one(); - }) + (To == R.unit || requires { + ::units::quantity_cast(q); + quantity{}, Rep>::one(); + }) { const auto handle_signed_results = [&](const T& res) { if (res < q) { @@ -220,33 +203,21 @@ template }; if constexpr (treat_as_floating_point) { using std::ceil; - if constexpr (std::is_same_v) { - return quantity(ceil(q.number())); + if constexpr (To == R.unit) { + return quantity{}, Rep>(ceil(q.number())); } else { - return handle_signed_results(quantity(ceil(quantity_cast(q).number()))); + return handle_signed_results( + quantity{}, Rep>(ceil(quantity_cast(q).number()))); } } else { - if constexpr (std::is_same_v) { - return q; + if constexpr (To == R.unit) { + return quantity_cast(q); } else { return handle_signed_results(quantity_cast(q)); } } } -/** - * @brief Overload of @c ::units::ceil() using the unit type of To - * - * @tparam q Quantity being the base of the operation - * @return Quantity The rounded quantity with unit type of quantity To - */ -template D, typename U, std::same_as Rep> -[[nodiscard]] constexpr quantity ceil(const quantity& q) noexcept - requires requires { ::units::ceil(q); } -{ - return ::units::ceil(q); -} - /** * @brief Computes the nearest quantity with integer representation and unit type To to q * @@ -255,25 +226,25 @@ template D, typename U, std::s * @tparam q Quantity being the base of the operation * @return Quantity The rounded quantity with unit type To */ -template -[[nodiscard]] constexpr quantity round(const quantity& q) noexcept +template +[[nodiscard]] constexpr quantity{}, Rep> round(const quantity& q) noexcept requires((!treat_as_floating_point) || requires { round(q.number()); } || requires { std::round(q.number()); }) && - (std::same_as || requires { - ::units::floor(q); - quantity::one(); - }) + (To == R.unit || requires { + ::units::floor(q); + quantity{}, Rep>::one(); + }) { - if constexpr (std::is_same_v) { + if constexpr (To == R.unit) { if constexpr (treat_as_floating_point) { using std::round; - return quantity(round(q.number())); + return quantity{}, Rep>(round(q.number())); } else { - return q; + return quantity_cast(q); } } else { const auto res_low = units::floor(q); - const auto res_high = res_low + decltype(res_low)::one(); + const auto res_high = res_low + res_low.one(); const auto diff0 = q - res_low; const auto diff1 = res_high - q; if (diff0 == diff1) { @@ -289,35 +260,20 @@ template } } -/** - * @brief Overload of @c ::units::round() using the unit type of To - * - * @tparam q Quantity being the base of the operation - * @return Quantity The rounded quantity with unit type of quantity To - */ -template D, typename U, std::same_as Rep> -[[nodiscard]] constexpr quantity round(const quantity& q) noexcept - requires requires { ::units::round(q); } -{ - return ::units::round(q); -} - /** * @brief Computes the square root of the sum of the squares of x and y, * without undue overflow or underflow at intermediate stages of the computation */ template -[[nodiscard]] inline std::common_type_t hypot(const Q1& x, const Q2& y) noexcept - requires requires { typename std::common_type_t; } && - requires(std::common_type_t q) { - requires requires { hypot(q.number(), q.number()); } || requires { std::hypot(q.number(), q.number()); }; - } +[[nodiscard]] inline quantity_of auto hypot(const Q1& x, + const Q2& y) noexcept + requires requires { common_reference(Q1::reference, Q2::reference); } && + ( + requires { hypot(x.number(), y.number()); } || requires { std::hypot(x.number(), y.number()); }) { - using type = std::common_type_t; - type xx = x; - type yy = y; using std::hypot; - return type(hypot(xx.number(), yy.number())); + using type = quantity; + return type{hypot(x.number(), y.number())}; } /** @@ -325,75 +281,17 @@ template * without undue overflow or underflow at intermediate stages of the computation */ template -[[nodiscard]] inline std::common_type_t hypot(const Q1& x, const Q2& y, const Q3& z) noexcept - requires requires { typename std::common_type_t; } && - requires(std::common_type_t q) { - requires requires { hypot(q.number(), q.number(), q.number()); } || - requires { std::hypot(q.number(), q.number(), q.number()); }; - } +[[nodiscard]] inline quantity_of auto hypot( + const Q1& x, const Q2& y, const Q3& z) noexcept + requires requires { common_reference(Q1::reference, Q2::reference, Q3::reference); } && + ( + requires { hypot(x.number(), y.number(), z.number()); } || + requires { std::hypot(x.number(), y.number(), z.number()); }) { - using type = std::common_type_t; - type xx = x; - type yy = y; - type zz = z; using std::hypot; - return type(hypot(xx.number(), yy.number(), zz.number())); -} - - -template - requires treat_as_floating_point -[[nodiscard]] inline dimensionless sin(const angle& q) noexcept - requires requires { sin(q.number()); } || requires { std::sin(q.number()); } -{ - using std::sin; - return sin(quantity_cast(q).number()); -} - -template - requires treat_as_floating_point -[[nodiscard]] inline dimensionless cos(const angle& q) noexcept - requires requires { cos(q.number()); } || requires { std::cos(q.number()); } -{ - using std::cos; - return cos(quantity_cast(q).number()); -} - -template - requires treat_as_floating_point -[[nodiscard]] inline dimensionless tan(const angle& q) noexcept - requires requires { tan(q.number()); } || requires { std::tan(q.number()); } -{ - using std::tan; - return tan(quantity_cast(q).number()); -} - - -template - requires treat_as_floating_point -[[nodiscard]] inline angle asin(const dimensionless& q) noexcept - requires requires { asin(q.number()); } || requires { std::asin(q.number()); } -{ - using std::asin; - return angle(asin(quantity_cast(q).number())); -} - -template - requires treat_as_floating_point -[[nodiscard]] inline angle acos(const dimensionless& q) noexcept - requires requires { acos(q.number()); } || requires { std::acos(q.number()); } -{ - using std::acos; - return angle(acos(quantity_cast(q).number())); -} - -template - requires treat_as_floating_point -[[nodiscard]] inline angle atan(const dimensionless& q) noexcept - requires requires { atan(q.number()); } || requires { std::atan(q.number()); } -{ - using std::atan; - return angle(atan(quantity_cast(q).number())); + using type = quantity; + return type{hypot(x.number(), y.number(), z.number())}; } } // namespace units diff --git a/src/core/include/units/quantity.h b/src/core/include/units/quantity.h index 38fda71d..f8a98b5b 100644 --- a/src/core/include/units/quantity.h +++ b/src/core/include/units/quantity.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -115,9 +116,10 @@ class quantity { public: // member types and values using rep = Rep; - static constexpr auto reference = R; - static constexpr auto dimension = R.dimension; - static constexpr auto unit = R.unit; + static constexpr Reference auto reference = R; + static constexpr QuantitySpec auto quantity_spec = R.quantity_spec; + static constexpr Dimension auto dimension = R.dimension; + static constexpr Unit auto unit = R.unit; // static member functions [[nodiscard]] static constexpr quantity zero() noexcept @@ -176,10 +178,10 @@ public: [[nodiscard]] constexpr const rep&& number() const&& noexcept { return std::move(number_); } template - requires quantity_convertible_to_{}, Rep>> - [[nodiscard]] constexpr quantity<::units::reference{}, Rep> operator[](U) const + requires quantity_convertible_to_{}, Rep>> + [[nodiscard]] constexpr quantity<::units::reference{}, Rep> operator[](U) const { - return quantity<::units::reference{}, Rep>{*this}; + return quantity<::units::reference{}, Rep>{*this}; } // member unary operators @@ -424,7 +426,7 @@ public: requires(!Quantity) && invoke_result_convertible_to_, const Value&, rep> [[nodiscard]] friend constexpr Quantity auto operator/(const Value& v, const quantity& q) { - return detail::make_quantity(v / q.number()); + return detail::make_quantity(v / q.number()); } template @@ -459,16 +461,11 @@ public: }; // CTAD -#if !UNITS_COMP_CLANG || UNITS_COMP_CLANG > 16 -template -explicit(false) quantity(Rep&&) -> quantity; -#endif - template explicit(false) quantity(quantity) -> quantity; template -explicit(false) quantity(Rep)->quantity; +explicit(false) quantity(Rep)->quantity; template explicit quantity(Q) -> quantity::dimension, quantity_like_traits::unit>{}, diff --git a/src/core/include/units/quantity_cast.h b/src/core/include/units/quantity_cast.h index 32df13ac..d903040e 100644 --- a/src/core/include/units/quantity_cast.h +++ b/src/core/include/units/quantity_cast.h @@ -52,11 +52,12 @@ class quantity; * @brief Explicit cast of a quantity * * Implicit conversions between quantities of different types are allowed only for "safe" - * (i.e. non-truncating) conversion. In such cases an explicit cast have to be used. + * (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used. * * This cast gets the target quantity type to cast to. For example: * - * auto q1 = units::quantity_cast>(1_q_ms); + * auto q1 = 1234. * isq::length[mm]; + * auto q2 = quantity_cast>(q1); * * @tparam To a target quantity type to cast to */ @@ -110,17 +111,13 @@ template * @brief Explicit cast of a quantity * * Implicit conversions between quantities of different types are allowed only for "safe" - * (i.e. non-truncating) conversion. In such cases an explicit cast have to be used. + * (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used. * - * This cast gets both the target dimension and unit to cast to. For example: + * This cast gets a target reference to cast to. For example: * - * auto q1 = units::quantity_cast(v1); + * auto v = quantity_cast(q); * - * @note This cast is especially useful when working with quantities of unknown dimensions - * (@c unknown_dimension). - * - * @tparam ToD a dimension type to use for a target quantity - * @tparam ToU a unit type to use for a target quantity + * @tparam ToR a reference to use for a target quantity */ template requires(interconvertible(ToR, R)) @@ -134,19 +131,19 @@ template * @brief Explicit cast of a quantity * * Implicit conversions between quantities of different types are allowed only for "safe" - * (i.e. non-truncating) conversion. In such cases an explicit cast have to be used. + * (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used. * - * This cast gets only the target dimension to cast to. For example: + * This cast gets only the target quantity specification to cast to. For example: * - * auto q1 = units::quantity_cast(200_q_Gal); + * auto v = quantity_cast(120 * isq::length[km] / (2 * isq::time[h])); * - * @tparam ToD a dimension type to use for a target quantity + * @tparam ToQS a quantity specification to use for a target quantity */ -template - requires(interconvertible(ToD, R.dimension)) +template + requires(interconvertible(ToQS, R.quantity_spec)) [[nodiscard]] constexpr auto quantity_cast(const quantity& q) { - constexpr reference::unit> r; + constexpr reference::unit> r; return quantity_cast>(q); } @@ -154,19 +151,19 @@ template * @brief Explicit cast of a quantity * * Implicit conversions between quantities of different types are allowed only for "safe" - * (i.e. non-truncating) conversion. In such cases an explicit cast have to be used. + * (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used. * * This cast gets only the target unit to cast to. For example: * - * auto q1 = units::quantity_cast(1_q_ms); + * auto d = quantity_cast(1234 * isq::time[ms]); * - * @tparam ToU a unit type to use for a target quantity + * @tparam ToU a unit to use for a target quantity */ template requires(interconvertible(ToU, R.unit)) [[nodiscard]] constexpr auto quantity_cast(const quantity& q) { - constexpr reference::dimension, ToU> r; + constexpr reference::quantity_spec, ToU> r; return quantity_cast>(q); } @@ -174,11 +171,11 @@ template * @brief Explicit cast of a quantity * * Implicit conversions between quantities of different types are allowed only for "safe" - * (i.e. non-truncating) conversion. In such cases an explicit cast have to be used. + * (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used. * * This cast gets only representation to cast to. For example: * - * auto q1 = units::quantity_cast(1_q_ms); + * auto q = quantity_cast(1.23 * isq::time[ms]); * * @tparam ToRep a representation type to use for a target quantity */ diff --git a/src/core/include/units/quantity_spec.h b/src/core/include/units/quantity_spec.h new file mode 100644 index 00000000..7ceda6de --- /dev/null +++ b/src/core/include/units/quantity_spec.h @@ -0,0 +1,518 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace units { + +/** + * @brief Quantity character + * + * Scalars, vectors and tensors are mathematical objects that can be used to + * denote certain physical quantities and their values. They are as such + * independent of the particular choice of a coordinate system, whereas + * each scalar component of a vector or a tensor and each component vector and + * component tensor depend on that choice. + * + * A scalar is a physical quantity that has magnitude but no direction. + * + * Vectors are physical quantities that possess both magnitude and direction + * and whose operations obey the axioms of a vector space. + * + * Tensors can be used to describe more general physical quantities. + * For example, the Cauchy stress tensor possess magnitude, direction, + * and orientation qualities. + */ +enum class quantity_character { scalar, vector, tensor }; + +namespace detail { + +// TODO revise the note in the below comment +/** + * @brief Returns the most restrictive character from the list + * + * @note `vector * vector` returns vector (not tensor) + */ +template... Ts> +[[nodiscard]] consteval quantity_character common_quantity_character(Ts... args) +{ + return detail::max({args...}); +} + +template +[[nodiscard]] consteval quantity_character derived_quantity_character(const type_list&, + const type_list&) +{ + quantity_character num = common_quantity_character(quantity_character::scalar, expr_type::character...); + quantity_character den = common_quantity_character(quantity_character::scalar, expr_type::character...); + return common_quantity_character(num, den); +} + +/** + * @brief Initializes quantity character + * + * If a quantity character value is present in template parameters, this value will be used. + * Otherwise, an inherited/derived value provided through the function argument is returned. + */ +template +[[nodiscard]] consteval quantity_character quantity_character_init(quantity_character ch) +{ + if constexpr (one_of...>) + return std::get(std::make_tuple(Args...)); + else + return ch; +} + +template +inline constexpr bool is_specialization_of_derived_quantity_spec = false; + +template +inline constexpr bool is_dimensionless = false; + +template +inline constexpr bool is_power_of_quantity_spec = requires { + requires is_specialization_of_power && + (NamedQuantitySpec || is_dimensionless); +}; + +template +inline constexpr bool is_per_of_quantity_specs = false; + +template +inline constexpr bool is_per_of_quantity_specs> = + (... && (NamedQuantitySpec || is_dimensionless || is_power_of_quantity_spec)); + +} // namespace detail + +/** + * @brief Concept matching quantity specification types + * + * Satisfied by all `derived_quantity_spec` specializations. + */ +template +concept DerivedQuantitySpec = detail::is_specialization_of_derived_quantity_spec; + +template +concept QuantitySpec = NamedQuantitySpec || DerivedQuantitySpec; + +template +concept DerivedQuantitySpecExpr = NamedQuantitySpec || detail::is_dimensionless || + detail::is_power_of_quantity_spec || detail::is_per_of_quantity_specs; + +namespace detail { + +template +struct quantity_spec_less : std::bool_constant() < type_name()> {}; + +template +using type_list_of_quantity_spec_less = expr_less; + +template + requires requires { Q::dimension; } +using to_dimension = std::remove_const_t; + +template + requires requires { U::base_quantity.dimension; } +using to_base_dimension = std::remove_const_t; + +template +[[nodiscard]] consteval Dimension auto get_dimension_for_impl(U) + requires requires { U::base_quantity.dimension; } +{ + return U::base_quantity.dimension; +} + +template +[[nodiscard]] consteval Dimension auto get_dimension_for_impl(const derived_unit& u) + requires detail::expr_projectable, to_base_dimension> +{ + return detail::expr_map(u); +} + +template +concept associated_unit = Unit && requires(U u) { get_dimension_for_impl(get_canonical_unit(u).reference_unit); }; + +[[nodiscard]] consteval Dimension auto get_dimension_for(associated_unit auto u) +{ + return get_dimension_for_impl(get_canonical_unit(u).reference_unit); +} + +} // namespace detail + +template +struct reference; + +/** + * @brief A specification of a derived quantity + * + * Derived quantity is a quantity, in a system of quantities, defined in terms of other quantities of that system. + * Its dimension is an expression of the dependence of a quantity on the base quantities of a system of + * quantities as a product of powers of factors corresponding to the base quantities, omitting any numerical factors. + * + * Instead of using a raw list of exponents this library decided to use expression template syntax to make types + * more digestable for the user both for quantity specification and its dimension. The positive exponents are ordered + * first and all negative exponents are put as a list into the `per<...>` class template. If a power of exponent + * is different than `1` the dimension type is enclosed in `power` class template. Otherwise, it is + * just put directly in the list without any wrapper. In case all of the exponents are negative than the + * `dimensionless`/`dimension_one` is put in the front to increase the readability. + * + * For example: + * + * @code{.cpp} + * auto frequency = 1 / period_duration; + * auto area = pow<2>(length); + * auto speed = distance / duration; + * auto velocity = position_vector / duration; + * auto acceleration = velocity / duration; + * @endcode + * + * - the type of `frequency` is `derived_quantity_spec>` + * - the dimension type of `frequency` is `derived_dimension>` + * - the type of `area` is `derived_quantity_spec>` + * - the dimension type of `area` is `derived_dimension>` + * - the type of `speed` is `derived_quantity_spec>` + * - the dimension type of `speed` is `derived_dimension>` + * - the type of `velocity` is `derived_quantity_spec>` + * - the dimension type of `velocity` is `derived_dimension>` + * - the type of `acceleration` is `derived_quantity_spec>` + * - the dimension type of `acceleration` is `derived_dimension>>` + * + * @note A common convention in this library is to assign the same name for a type and an object of this type. + * Besides defining them user never works with the dimension types in the source code. All operations + * are done on the objects. Contrarily, the dimension types are the only one visible in the compilation + * errors. Having them of the same names improves user experience and somehow blurs those separate domains. + * + * Derived quantity specification will have a character being the strongest ones from its ingredients. + * + * Binding a proper unit to a quantity specification via an indexing operator (`operator[]`) results + * in a quantity reference. + * + * @tparam Qs a parameter pack consisting tokens allowed in the quantity specification + * (named quantity specification, `dimensionless`, `power`, `per<...>`) + * + * @note User should not instantiate this type! It is not exported from the C++ module. The library will + * instantiate this type automatically based on the dimensional arithmetic equation provided by the user. + */ +template +struct derived_quantity_spec : detail::expr_fractions, Qs...> { + using base = detail::expr_fractions, Qs...>; + + static constexpr Dimension auto dimension = + detail::expr_map(base{}); + static constexpr quantity_character character = + detail::derived_quantity_character(typename base::_num_{}, typename base::_num_{}); + +#ifdef __cpp_explicit_this_parameter + template + [[nodiscard]] constexpr auto operator[](this const Self, U u) + requires(dimension == detail::get_dimension_for(u)) + { + return reference{}; + } +#else + template + [[nodiscard]] constexpr auto operator[](U u) const + requires(dimension == detail::get_dimension_for(u)) + { + return reference{}; + } +#endif +}; + +namespace detail { + +template +inline constexpr bool is_specialization_of_derived_quantity_spec> = true; + +} + +/** + * @brief Quantity Specification + * + * This type specifies all the properties of a quantity and allow modeling most of the quantities in the ISO 80000. + * It serves to define base and derived quantities as well as quantity kinds. Each quantity specification + * provides an information on how this quantity relates to other quantities, specifies its dimension and character. + * + * Quantity character can be derived from other quantities or explicitly overriden through a template parameter. + * + * Binding a proper unit to a quantity specification via an indexing operator (`operator[]`) results + * in a quantity reference. + */ +#ifdef __cpp_explicit_this_parameter +template +#else +template +#endif +struct quantity_spec; + +/** + * @brief Specialization defining a base quantity + * + * Base quantity is a quantity in a conventionally chosen subset of a given system of quantities, where no quantity + * in the subset can be expressed in terms of the other quantities within that subset. They are referred to as + * being mutually independent since a base quantity cannot be expressed as a product of powers of the other base + * quantities. + * + * Base quantities have scalar character by default. + * + * User should derive a strong type from this class template rather than use it directly in the source code. + * For example: + * + * @code{.cpp} + * inline constexpr struct dim_length : base_dimension<"L"> {} dim_length; + * inline constexpr struct dim_mass : base_dimension<"M"> {} dim_mass; + * inline constexpr struct dim_time : base_dimension<"T"> {} dim_time; + * + * inline constexpr struct length : quantity_spec {} length; + * inline constexpr struct mass : quantity_spec {} mass; + * inline constexpr struct time : quantity_spec {} time; + * @endcode + * + * @note A common convention in this library is to assign the same name for a type and an object of this type. + * Besides defining them user never works with the dimension types in the source code. All operations + * are done on the objects. Contrarily, the dimension types are the only one visible in the compilation + * errors. Having them of the same names improves user experience and somehow blurs those separate domains. + * + * @tparam BaseDimension base dimension for which a base quantity is being defined + * @tparam Args optionally a value of a `quantity_character` in case the base quantity should not be scalar + */ +#ifdef __cpp_explicit_this_parameter +template auto... Args> +struct quantity_spec { +#else +template auto... Args> +struct quantity_spec { +#endif + static constexpr BaseDimension auto dimension = Dim; + static constexpr quantity_character character = detail::quantity_character_init(quantity_character::scalar); + +#ifdef __cpp_explicit_this_parameter + template + [[nodiscard]] constexpr auto operator[](this const Self, U u) + requires(dimension == detail::get_dimension_for(u)) +#else + template + [[nodiscard]] constexpr auto operator[](U u) const + requires(dimension == detail::get_dimension_for(u)) +#endif + { + return reference{}; + } +}; + +/** + * @brief Specialization defining a named derived quantity or a quantity kind + * + * The division of the concept "quantity" into several kinds is to some extent arbitrary. + * For example the quantities diameter, circumference, and wavelength are generally considered + * to be quantities of the same kind, namely, of the kind of quantity called length. + * + * Quantities of the same kind within a given system of quantities have the same quantity dimension. + * However, quantities of the same dimension are not necessarily of the same kind. + * + * The character of those quantities is derived from ingredients or overriden with a template parameter. + * + * User should derive a strong type from this class template rather than use it directly in the source code. + * For example: + * + * @code{.cpp} + * // quantity kinds + * inline constexpr struct width : quantity_spec {} width; + * inline constexpr struct height : quantity_spec {} height; + * inline constexpr struct thickness : quantity_spec {} thickness; + * inline constexpr struct diameter : quantity_spec {} diameter; + * inline constexpr struct position_vector : quantity_spec {} position_vector; + * + * // derived quantities + * inline constexpr struct area : quantity_spec(length)> {} area; + * inline constexpr struct volume : quantity_spec(length)> {} volume; + * inline constexpr struct velocity : quantity_spec {} velocity; // vector + * inline constexpr struct speed : quantity_spec {} speed; + * inline constexpr struct force : quantity_spec {} force; + * inline constexpr struct power : quantity_spec {} power; + * @endcode + * + * Two quantity specifications are deemed equal when they are of the same type. With that, both strong + * types `speed` and `velocity` are considered not equal to `derived_dimension>` or + * to each other. However, they are both convertible to `derived_dimension>`. + * User can implicitly convert up and down the inheritance hierarchy between them. However, `speed` and + * `velocity` are not convertible as they do not inherit from each other. They are from two separate branches + * of dimensionally equivalent quantities. + * + * @tparam Q quantity specification of a parent quantity + * @tparam Args optionally a value of a `quantity_character` in case the base quantity should not be scalar + */ +#ifdef __cpp_explicit_this_parameter +template auto... Args> +struct quantity_spec : std::remove_const_t { +#else +template auto... Args> +struct quantity_spec : std::remove_const_t { +#endif + static constexpr auto kind_of = Q; + static constexpr quantity_character character = detail::quantity_character_init(Q.character); + +#ifndef __cpp_explicit_this_parameter + template + [[nodiscard]] constexpr auto operator[](U u) const + requires(this->dimension == detail::get_dimension_for(u)) + { + return reference{}; + } +#endif +}; + +#ifdef __cpp_explicit_this_parameter + +#define QUANTITY_SPEC(name, ...) \ + inline constexpr struct name : quantity_spec<##__VA_ARGS__> { \ + } name + +#else + +#define QUANTITY_SPEC(name, ...) \ + inline constexpr struct name : quantity_spec { \ + } name + +#endif + +// TODO Should a quantity_cast be allowed to cast between speed and velocity? + + +/** + * @brief Quantity of dimension one + * + * Quantity of dimension one also commonly named as "dimensionless" is a quantity with a dimension + * for which all the exponents of the factors corresponding to the base dimensions are zero. + */ +QUANTITY_SPEC(dimensionless, derived_quantity_spec<>{}); + +namespace detail { + +template<> +inline constexpr bool is_dimensionless = true; + +} // namespace detail + + +// Operators + +template +[[nodiscard]] consteval QuantitySpec auto operator*(Lhs lhs, Rhs rhs) +{ + return detail::expr_multiply( + lhs, rhs); +} + +template +[[nodiscard]] consteval QuantitySpec auto operator/(Lhs lhs, Rhs rhs) +{ + return detail::expr_divide(lhs, + rhs); +} + +template +[[nodiscard]] consteval QuantitySpec auto operator/(int value, Q q) +{ + gsl_Expects(value == 1); + return detail::expr_invert(q); +} + +template +[[nodiscard]] consteval QuantitySpec auto operator/(Q, int) = delete; + +template +[[nodiscard]] consteval bool operator==(Lhs, Rhs) +{ + return is_same_v; +} + +template +[[nodiscard]] consteval bool interconvertible(Q1, Q2) +{ + if constexpr (NamedQuantitySpec && NamedQuantitySpec) + // check if quantity kinds are convertible (across one hierarchy) + return std::derived_from || std::derived_from; + else { + // check weather the quantity spec's dimension is the same + // TODO Can we improve that to account for quantity kinds (i.e. `altitude` / `time` -> `sink_rate`) + return Q1::dimension == Q2::dimension; + } +} + +[[nodiscard]] consteval auto common_quantity_spec(QuantitySpec auto q) { return q; } + +template +[[nodiscard]] consteval auto common_quantity_spec(Q1 q1, Q2 q2) + requires(interconvertible(q1, q2)) +{ + if constexpr (std::derived_from) + return q1; + else if constexpr (std::derived_from) + return q2; + else if constexpr (NamedQuantitySpec) + return q1; + else + return q2; +} + +[[nodiscard]] consteval auto common_quantity_spec(QuantitySpec auto q1, QuantitySpec auto q2, QuantitySpec auto q3, + QuantitySpec auto... rest) + requires requires { common_quantity_spec(common_quantity_spec(q1, q2), q3, rest...); } +{ + return common_quantity_spec(common_quantity_spec(q1, q2), q3, rest...); +} + +/** + * @brief Computes the value of a quantity specification raised to the `Num/Den` power + * + * @tparam Num Exponent numerator + * @tparam Den Exponent denominator + * @param q Quantity specification being the base of the operation + * + * @return QuantitySpec The result of computation + */ +template + requires detail::non_zero +[[nodiscard]] consteval QuantitySpec auto pow(Q q) +{ + if constexpr (BaseQuantitySpec) { + if constexpr (Den == 1) + return derived_quantity_spec>{}; + else + return derived_quantity_spec>{}; + } else + return detail::expr_pow(q); +} + +} // namespace units diff --git a/src/core/include/units/reference.h b/src/core/include/units/reference.h index 6524b84a..296c0191 100644 --- a/src/core/include/units/reference.h +++ b/src/core/include/units/reference.h @@ -23,72 +23,52 @@ #pragma once #include -#include +#include #include namespace units { /** - * @brief The type for quantity references + * @brief Quantity reference type * - * This type is intended to be used in the quantity references definition: + * Quantity reference describes all the properties of a quantity besides its + * representation type. + * + * In most cases this class template is not explicitly instantiated by the user. + * It is implicitly instantiated by the library's framework while binding a quantity + * specification with a compatible unit. * * @code{.cpp} - * namespace length_references { - * - * inline constexpr auto m = reference{}; - * inline constexpr auto km = reference{}; - * - * } - * - * namespace references { - * - * using namespace length_references; - * - * } - * @endcode - * - * Quantity references simplify quantity creation: - * - * @code{.cpp} - * using namespace units::isq::si::references; - * - * auto d = 123 * m; - * auto v = 70 * (km / h); - * @endcode - * - * Also, it is allowed to define custom quantity references from existing ones: - * - * @code{.cpp} - * constexpr auto Nm = N * m; - * constexpr auto mph = mi / h; + * Reference auto kmph = isq::speed[km / h]; + * quantity_of auto speed = 90 * kmph; * @endcode * * The following syntaxes are not allowed: - * `2 / s`, `km * 3`, `s / 4`, `70 * km / h`. + * `2 / kmph`, `kmph * 3`, `kmph / 4`, `70 * isq::length[km] / isq:time[h]`. */ -template +template struct reference { - static constexpr auto dimension = D; - static constexpr auto unit = U; + static constexpr QuantitySpec auto quantity_spec = Q; + static constexpr Dimension auto dimension = Q.dimension; + static constexpr Unit auto unit = U; }; // Reference template -[[nodiscard]] consteval reference operator*(M, R) +[[nodiscard]] consteval reference operator*(M, R) { return {}; } template -[[nodiscard]] consteval reference operator*(R1, R2) +[[nodiscard]] consteval reference operator*(R1, R2) { return {}; } template -[[nodiscard]] consteval reference operator/(R1, R2) +[[nodiscard]] consteval reference operator/(R1, R2) { return {}; } @@ -104,26 +84,26 @@ void /*Use `q * (1 * r)` rather than `q * r`.*/ operator*(Quantity auto, Referen template [[nodiscard]] consteval bool operator==(R1, R2) { - return R1::dimension == R2::dimension && R1::unit == R2::unit; + return R1::quantity_spec == R2::quantity_spec && R1::unit == R2::unit; } template [[nodiscard]] consteval bool interconvertible(R1, R2) { - return interconvertible(R1::dimension, R2::dimension) && interconvertible(R1::unit, R2::unit); + return interconvertible(R1::quantity_spec, R2::quantity_spec) && interconvertible(R1::unit, R2::unit); } [[nodiscard]] consteval auto common_reference(Reference auto r1, Reference auto r2, Reference auto... rest) requires requires { { - common_dimension(r1.dimension, r2.dimension, rest.dimension...) - } -> Dimension; + common_quantity_spec(r1.quantity_spec, r2.quantity_spec, rest.quantity_spec...) + } -> QuantitySpec; { common_unit(r1.unit, r2.unit, rest.unit...) } -> Unit; } { - return reference{}; } diff --git a/src/core/include/units/system_reference.h b/src/core/include/units/system_reference.h index c82f924f..f3605622 100644 --- a/src/core/include/units/system_reference.h +++ b/src/core/include/units/system_reference.h @@ -23,20 +23,48 @@ #pragma once #include +#include #include #include namespace units { -template - requires(!detail::associated_unit>) +/** + * @brief System-specific reference + * + * This type is used in rare cases where more than one base quantity in a specific + * system of units uses the same unit. For example in a hypothetical system of natural units + * where constant for speed of light `c = 1`, length and time could be measured in seconds. + * In such cases `system_reference` has to be used to explicitly express such a binding. + * + * For example: + * + * @code{.cpp} + * // hypothetical natural system of units for c=1 + * + * inline constexpr struct second : named_unit<"s"> {} second; + * inline constexpr struct minute : named_unit<"min", mag<60> * second> {} minute; + * inline constexpr struct gram : named_unit<"g"> {} gram; + * inline constexpr struct kilogram : decltype(si::kilo) {} kilogram; + * + * inline constexpr struct time : system_reference {} time; + * inline constexpr struct length : system_reference {} length; + * inline constexpr struct speed : system_reference {} speed; + * inline constexpr struct force : system_reference {} force; + * @endcode + * + * @tparam Q quantity for which a unit is being assigned + * @tparam CoU coherent unit for a quantity in this system + */ +template + requires(!detail::associated_unit>) || (CoU == one) struct system_reference { - static constexpr auto dimension = Dim; + static constexpr auto quantity_spec = Q; static constexpr auto coherent_unit = CoU; template requires(interconvertible(coherent_unit, U{})) - [[nodiscard]] constexpr reference operator[](U) const + [[nodiscard]] constexpr reference operator[](U) const { return {}; } diff --git a/src/core/include/units/unit.h b/src/core/include/units/unit.h index 13fb9268..c36c402a 100644 --- a/src/core/include/units/unit.h +++ b/src/core/include/units/unit.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -37,43 +38,57 @@ namespace units { #ifdef __cpp_explicit_this_parameter -template +template #else -template +template #endif -struct base_dimension; +struct quantity_spec; namespace detail { #ifdef __cpp_explicit_this_parameter -template -void to_base_base_dimension(const volatile base_dimension*); +template +void to_base_specialization_of_quantity_spec(const volatile quantity_spec*); #else -template -void to_base_base_dimension(const volatile base_dimension*); +template +void to_base_specialization_of_quantity_spec(const volatile quantity_spec*); +#endif + +#ifdef __cpp_explicit_this_parameter +template +template +void to_base_specialization_of_base_quantity_spec(const volatile quantity_spec*); +#else +template +void to_base_specialization_of_base_quantity_spec(const volatile quantity_spec*); #endif template -inline constexpr bool is_specialization_of_base_dimension = false; +inline constexpr bool is_specialization_of_quantity_spec = false; #ifdef __cpp_explicit_this_parameter -template -inline constexpr bool is_specialization_of_base_dimension> = true; +template +inline constexpr bool is_specialization_of_quantity_spec> = true; #else -template -inline constexpr bool is_specialization_of_base_dimension> = true; +template +inline constexpr bool is_specialization_of_quantity_spec> = true; #endif } // namespace detail /** - * @brief A concept matching all named base dimensions in the library. + * @brief Concept matching quantity specification types * - * Satisfied by all dimension types derived from a specialization of `base_dimension`. + * Satisfied by all types that derive from `quantity_spec`. */ template -concept BaseDimension = requires(T* t) { detail::to_base_base_dimension(t); } && - (!detail::is_specialization_of_base_dimension); +concept NamedQuantitySpec = requires(T* t) { detail::to_base_specialization_of_quantity_spec(t); } && + (!detail::is_specialization_of_quantity_spec); + +template +concept BaseQuantitySpec = + NamedQuantitySpec && requires(T* t) { detail::to_base_specialization_of_base_quantity_spec(t); }; + namespace detail { @@ -146,12 +161,12 @@ template struct named_unit; /** - * @brief Specialization for unit of a specified base dimension + * @brief Specialization for unit of a specified base quantity * - * Associates a unit with a specified base dimension. + * Associates a unit with a specified base quantity. * For example `si::metre` is a unit to measure `isq::length` in the SI system. * - * @note This is the preferred way to define a measurement unit for a specific base dimension. + * @note This is the preferred way to define a measurement unit for a specific base quantity. * * @note It does not have to (or sometimes even can't) be a proper system's base unit. For example * a base unit of mass in the SI is `si::kilogram` but here you are about to provide an `si::gram` @@ -159,19 +174,19 @@ struct named_unit; * the `cgs::centimetre` that is a base unit for `isq::length` in the CGS system. * * @tparam Symbol a short text representation of the unit - * @tparam BaseDimension base dimension measured with this unit + * @tparam BaseQuantitySpec base quantity measured with this unit */ -template +template requires(!Symbol.empty()) -struct named_unit { +struct named_unit { static constexpr auto symbol = Symbol; ///< Unique base unit identifier - static constexpr auto base_dimension = D; + static constexpr auto base_quantity = Q; }; /** - * @brief Specialization for a unit that can be reused by several base dimensions + * @brief Specialization for a unit that can be reused by several base quantities * - * This specialization is used in rare cases where more than one base dimension in a specific + * This specialization is used in rare cases where more than one base quantity in a specific * system of units uses the same unit. For example in a hypothetical system of natural units * where constant for speed of light `c = 1`, length and time could be measured in seconds. * In such cases `system_reference` has to be used to explicitly express such a binding. @@ -328,7 +343,7 @@ inline constexpr bool is_per_of_units> = (... && (Unit || is_powe } // namespace detail template -concept DerivedUnitSpec = Unit || detail::is_power_of_unit || detail::is_per_of_units; +concept DerivedUnitExpr = Unit || detail::is_power_of_unit || detail::is_per_of_units; /** * @brief Measurement unit for a derived quantity @@ -375,7 +390,7 @@ concept DerivedUnitSpec = Unit || detail::is_power_of_unit || detail::is_p * @note User should not instantiate this type! It is not exported from the C++ module. The library will * instantiate this type automatically based on the unit arithmetic equation provided by the user. */ -template +template struct derived_unit : detail::expr_fractions, Us...> {}; /** @@ -424,8 +439,8 @@ struct canonical_unit { U reference_unit; }; -template -[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit&); +template +[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit&); template [[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit&); @@ -446,8 +461,8 @@ template return canonical_unit{M * base.mag, base.reference_unit}; } -template -[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit&) +template +[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit&) { return canonical_unit{mag<1>, t}; } @@ -498,6 +513,7 @@ template (!is_derived_from_specialization_of_constant_unit && !is_derived_from_specialization_of_constant_unit)) return type_name() < type_name(); else + // put constants at the front of units list in the expression return is_derived_from_specialization_of_constant_unit; } @@ -804,21 +820,21 @@ constexpr auto unit_symbol_impl(Out out, const power&, unit_symb } } -template Out, DerivedUnitSpec M> +template Out, DerivedUnitExpr M> constexpr Out unit_symbol_impl(Out out, M m, std::size_t Idx, unit_symbol_formatting fmt, bool negative_power) { if (Idx > 0) out = print_separator(out, fmt); return unit_symbol_impl(out, m, fmt, negative_power); } -template Out, DerivedUnitSpec... Ms, std::size_t... Idxs> +template Out, DerivedUnitExpr... Ms, std::size_t... Idxs> constexpr Out unit_symbol_impl(Out out, const type_list&, std::index_sequence, unit_symbol_formatting fmt, bool negative_power) { return (..., (out = unit_symbol_impl(out, Ms{}, Idxs, fmt, negative_power))); } -template Out, DerivedUnitSpec... Nums, DerivedUnitSpec... Dens> +template Out, DerivedUnitExpr... Nums, DerivedUnitExpr... Dens> constexpr Out unit_symbol_impl(Out out, const type_list& nums, const type_list& dens, unit_symbol_formatting fmt) { diff --git a/src/systems/isq/CMakeLists.txt b/src/systems/isq/CMakeLists.txt index 8824cf29..a412ad4f 100644 --- a/src/systems/isq/CMakeLists.txt +++ b/src/systems/isq/CMakeLists.txt @@ -23,6 +23,7 @@ cmake_minimum_required(VERSION 3.19) add_units_module( - isq DEPENDENCIES mp-units::core HEADERS include/units/isq/base_dimensions.h include/units/isq/isq.h - include/units/isq/mechanics.h include/units/isq/space_and_time.h + isq DEPENDENCIES mp-units::core + HEADERS include/units/isq/base_quantities.h include/units/isq/isq.h include/units/isq/mechanics.h + include/units/isq/space_and_time.h include/units/isq/thermodynamics.h ) diff --git a/src/systems/isq/include/units/isq/base_dimensions.h b/src/systems/isq/include/units/isq/base_quantities.h similarity index 52% rename from src/systems/isq/include/units/isq/base_dimensions.h rename to src/systems/isq/include/units/isq/base_quantities.h index a0e7b2ad..f7996379 100644 --- a/src/systems/isq/include/units/isq/base_dimensions.h +++ b/src/systems/isq/include/units/isq/base_quantities.h @@ -23,16 +23,28 @@ #pragma once #include +#include namespace units::isq { -BASE_DIMENSION(length, "L"); -BASE_DIMENSION(mass, "M"); -BASE_DIMENSION(time, "T"); -BASE_DIMENSION(electric_current, "I"); -// TODO Should the below use basic_symbol_text? How to name it for ASCII? -BASE_DIMENSION(thermodynamic_temperature, "Θ"); -BASE_DIMENSION(amount_of_substance, "N"); -BASE_DIMENSION(luminous_intensity, "J"); +// clang-format off +// dimensions of base quantities +inline constexpr struct dim_length : base_dimension<"L"> {} dim_length; +inline constexpr struct dim_mass : base_dimension<"M"> {} dim_mass; +inline constexpr struct dim_time : base_dimension<"T"> {} dim_time; +inline constexpr struct dim_electric_current : base_dimension<"I"> {} dim_electric_current; +inline constexpr struct dim_thermodynamic_temperature : base_dimension {} dim_thermodynamic_temperature; +inline constexpr struct dim_amount_of_substance : base_dimension<"N"> {} dim_amount_of_substance; +inline constexpr struct dim_luminous_intensity : base_dimension<"J"> {} dim_luminous_intensity; +// clang-format on + +// base quantities +QUANTITY_SPEC(length, dim_length); +QUANTITY_SPEC(mass, dim_mass); +QUANTITY_SPEC(time, dim_time); +QUANTITY_SPEC(electric_current, dim_electric_current); +QUANTITY_SPEC(thermodynamic_temperature, dim_thermodynamic_temperature); +QUANTITY_SPEC(amount_of_substance, dim_amount_of_substance); +QUANTITY_SPEC(luminous_intensity, dim_luminous_intensity); } // namespace units::isq diff --git a/src/systems/isq/include/units/isq/isq.h b/src/systems/isq/include/units/isq/isq.h index b32b820c..35185fc7 100644 --- a/src/systems/isq/include/units/isq/isq.h +++ b/src/systems/isq/include/units/isq/isq.h @@ -23,6 +23,8 @@ #pragma once // IWYU pragma: begin_exports -#include +#include +#include #include +#include // IWYU pragma: end_exports diff --git a/src/systems/isq/include/units/isq/mechanics.h b/src/systems/isq/include/units/isq/mechanics.h index 91fdf423..018eeb65 100644 --- a/src/systems/isq/include/units/isq/mechanics.h +++ b/src/systems/isq/include/units/isq/mechanics.h @@ -23,50 +23,86 @@ #pragma once #include -#include +#include #include namespace units::isq { // inline constexpr struct mass : base_dimension<"M"> {} mass; -DERIVED_DIMENSION(mass_density, decltype(mass / volume)); -DERIVED_DIMENSION(specific_volume, decltype(1 / mass_density)); -DERIVED_DIMENSION(relative_mass_density, decltype(mass_density / mass_density)); -DERIVED_DIMENSION(surface_mass_density, decltype(mass / area)); -DERIVED_DIMENSION(linear_mass_density, decltype(mass / length)); -DERIVED_DIMENSION(momentum, decltype(mass * speed)); // TODO velocity? -DERIVED_DIMENSION(force, decltype(mass * acceleration)); // TODO what is a correct equation here? -// DERIVED_DIMENSION(weight, decltype(mass * acceleration)); // TODO should we add it as a quantity or should it be a -// quantity_kind? -// TODO Should we add other forces as well: static_friction_force, kinematic_friction_force, rolling_resistance, -// drag_force -DERIVED_DIMENSION(impulse, decltype(force / time)); -DERIVED_DIMENSION(angular_momentum, decltype(length * momentum)); // TODO position_vector -DERIVED_DIMENSION(moment_of_inertia, decltype(angular_momentum * angular_velocity)); -DERIVED_DIMENSION(moment_of_force, decltype(length * force)); // TODO position_vector -DERIVED_DIMENSION(torque, decltype(moment_of_force)); // TODO angle? -DERIVED_DIMENSION(angular_impulse, decltype(moment_of_force * time)); -DERIVED_DIMENSION(pressure, decltype(force / area)); -DERIVED_DIMENSION(stress, decltype(pressure)); // TODO tensor? -DERIVED_DIMENSION(normal_stress, decltype(force / area)); -DERIVED_DIMENSION(strain, decltype(stress / stress)); // TODO what is a correct equation here? -DERIVED_DIMENSION(poisson_number, decltype(length / length)); // TODO width? -// TODO modulus quantities -DERIVED_DIMENSION(compressibility, decltype(volume / volume / pressure)); -DERIVED_DIMENSION(second_axial_moment_of_area, decltype(area * area)); // TODO what is a correct equation here? -DERIVED_DIMENSION(section_modulus, decltype(second_axial_moment_of_area / length)); // TODO radial distance -// TODO friction coefficients? -DERIVED_DIMENSION(dynamic_viscosity, decltype(stress * length / speed)); // TODO shear stress, velocity -DERIVED_DIMENSION(kinematic_viscosity, decltype(dynamic_viscosity / mass_density)); -DERIVED_DIMENSION(surface_tension, decltype(force / length)); // TODO what is a correct equation here? -DERIVED_DIMENSION(power, decltype(force * speed)); -// TODO what about energy (potential and kinetic as separate quantities will prevent an equation for mechanical one, is -// it expected?) -DERIVED_DIMENSION(efficiency, decltype(power / power)); -DERIVED_DIMENSION(mass_flow, decltype(mass_density * speed)); // TODO velocity -DERIVED_DIMENSION(mass_flow_rate, decltype(mass_flow * area)); -DERIVED_DIMENSION(mass_change_rate, decltype(mass / time)); -DERIVED_DIMENSION(volume_flow_rate, decltype(speed * area)); // TODO velocity -// DERIVED_DIMENSION(action, decltype(energy * time)); // TODO make it compile +QUANTITY_SPEC(mass_density, mass / volume); +inline constexpr auto density = mass_density; +QUANTITY_SPEC(specific_volume, 1 / mass_density); +QUANTITY_SPEC(relative_mass_density, mass_density / mass_density); +inline constexpr auto relative_density = relative_mass_density; +QUANTITY_SPEC(surface_mass_density, mass / area); +inline constexpr auto surface_density = surface_mass_density; +QUANTITY_SPEC(linear_mass_density, mass / length); +inline constexpr auto linear_density = linear_mass_density; +QUANTITY_SPEC(momentum, mass* velocity); +QUANTITY_SPEC(force, mass* acceleration); // vector // TODO what is a correct equation here? +QUANTITY_SPEC(weight, force); // vector // TODO g? +QUANTITY_SPEC(static_friction_force, force); // vector +inline constexpr auto static_friction = static_friction_force; +QUANTITY_SPEC(kinetic_friction_force, force); // vector +inline constexpr auto dynamic_friction_force = kinetic_friction_force; +QUANTITY_SPEC(rolling_resistance, force); // vector +inline constexpr auto rolling_drag = rolling_resistance; +inline constexpr auto rolling_friction_force = rolling_resistance; +QUANTITY_SPEC(drag_force, force); // vector +QUANTITY_SPEC(impulse, force* time); // vector +QUANTITY_SPEC(angular_momentum, position_vector* momentum); // vector +QUANTITY_SPEC(moment_of_inertia, angular_momentum / angular_velocity, quantity_character::tensor); +QUANTITY_SPEC(moment_of_force, position_vector* force); // vector +QUANTITY_SPEC(torque, moment_of_force, quantity_character::scalar); +QUANTITY_SPEC(angular_impulse, moment_of_force* time); // vector +QUANTITY_SPEC(pressure, force / area, quantity_character::scalar); +QUANTITY_SPEC(gauge_pressure, pressure); +QUANTITY_SPEC(stress, pressure, quantity_character::tensor); +QUANTITY_SPEC(normal_stress, pressure, quantity_character::scalar); +QUANTITY_SPEC(shear_stress, pressure, quantity_character::scalar); +QUANTITY_SPEC(strain, dimensionless, quantity_character::tensor); +QUANTITY_SPEC(relative_linear_strain, length / length); +QUANTITY_SPEC(shear_strain, displacement / thickness); +QUANTITY_SPEC(relative_volume_strain, volume / volume); +QUANTITY_SPEC(Poisson_number, width / length); +QUANTITY_SPEC(modulus_of_elasticity, normal_stress / relative_linear_strain); +inline constexpr auto Young_modulus = modulus_of_elasticity; +QUANTITY_SPEC(modulus_of_rigidity, shear_stress / shear_strain); +inline constexpr auto shear_modulus = modulus_of_rigidity; +QUANTITY_SPEC(modulus_of_compression, pressure / relative_volume_strain); +// QUANTITY_SPEC(modulus_of_compression, -pressure / relative_volume_strain); // TODO how to handle "negative" part +inline constexpr auto bulk_modulus = modulus_of_compression; +QUANTITY_SPEC(compressibility, 1 / volume * (volume / pressure)); +// QUANTITY_SPEC(compressibility, -1 / volume * (volume / pressure)); // TODO how to handle "negative" part +QUANTITY_SPEC(second_axial_moment_of_area, pow<2>(radial_distance) * area); +QUANTITY_SPEC(second_polar_moment_of_area, pow<2>(radial_distance) * area); +QUANTITY_SPEC(section_modulus, second_axial_moment_of_area / radial_distance); +QUANTITY_SPEC(static_friction_coefficient, static_friction_force / force, quantity_character::scalar); +inline constexpr auto static_friction_factor = static_friction_coefficient; +inline constexpr auto coefficient_of_static_friction = static_friction_coefficient; +QUANTITY_SPEC(kinetic_friction_factor, kinetic_friction_force / force, quantity_character::scalar); +inline constexpr auto dynamic_friction_factor = kinetic_friction_factor; +QUANTITY_SPEC(rolling_resistance_factor, force / force, quantity_character::scalar); +// QUANTITY_SPEC(drag_coefficient, mag<2>* drag_force / (mass_density * pow<2>(speed) * area)); +// inline constexpr auto drag_factor = drag_coefficient; +QUANTITY_SPEC(dynamic_viscosity, shear_stress* length / velocity); +QUANTITY_SPEC(kinematic_viscosity, dynamic_viscosity / mass_density); +QUANTITY_SPEC(surface_tension, force / length, quantity_character::scalar); // TODO what is a correct equation here? +QUANTITY_SPEC(power, force* velocity, quantity_character::scalar); +// QUANTITY_SPEC(energy, force* length); +QUANTITY_SPEC(potential_energy, mass* acceleration* height); // TODO what is a correct equation here? +QUANTITY_SPEC(kinetic_energy, mass* pow<2>(speed)); +// QUANTITY_SPEC(kinetic_energy, mag<1, 2>* mass* pow<2>(speed)); +// TODO how to implement that? +// QUANTITY_SPEC(mechanical_energy, potential_energy + kinetic_energy); +QUANTITY_SPEC(mechanical_energy, potential_energy); +QUANTITY_SPEC(mechanical_work, force* displacement, quantity_character::scalar); +inline constexpr auto work = mechanical_work; +QUANTITY_SPEC(efficiency, power / power); +QUANTITY_SPEC(mass_flow, mass_density* velocity); // vector +QUANTITY_SPEC(mass_flow_rate, mass_flow* area, quantity_character::scalar); +QUANTITY_SPEC(mass_change_rate, mass / time); +QUANTITY_SPEC(volume_flow_rate, velocity* area, quantity_character::scalar); +QUANTITY_SPEC(action, mechanical_energy* time); } // namespace units::isq diff --git a/src/systems/isq/include/units/isq/space_and_time.h b/src/systems/isq/include/units/isq/space_and_time.h index 829bf052..6dafb6b3 100644 --- a/src/systems/isq/include/units/isq/space_and_time.h +++ b/src/systems/isq/include/units/isq/space_and_time.h @@ -23,44 +23,64 @@ #pragma once #include -#include +#include namespace units::isq { -// inline constexpr struct length : base_dimension<"L"> {} length; -DERIVED_DIMENSION(curvature, decltype(1 / length)); -DERIVED_DIMENSION(area, decltype(length * length)); -DERIVED_DIMENSION(volume, decltype(length * length * length)); -DERIVED_DIMENSION(angular_measure, decltype(length / length)); -DERIVED_DIMENSION(angular_displacement, decltype(length / length)); -DERIVED_DIMENSION(phase_angle, decltype(length / length)); -inline constexpr struct solid_angular_measure : decltype(area / (length * length)) { -} solid_angular_measure; -// inline constexpr struct time : base_dimension<"T"> {} time; // TODO called duration in ISO 80000 -// TODO there is also a velocity in ISO 80000 -DERIVED_DIMENSION(speed, decltype(length / time)); -DERIVED_DIMENSION(acceleration, decltype(speed / time)); -DERIVED_DIMENSION(angular_velocity, decltype(angular_displacement / time)); -DERIVED_DIMENSION(angular_acceleration, decltype(angular_velocity / time)); -inline constexpr struct period_duration : time { -} period_duration; -inline constexpr struct time_constant : time { -} time_constant; -inline constexpr struct rotation : angular_displacement { -} rotation; -DERIVED_DIMENSION(frequency, decltype(1 / time)); -DERIVED_DIMENSION(rotational_frequency, decltype(rotation / time)); -DERIVED_DIMENSION(angular_frequency, decltype(angular_measure / time)); -inline constexpr struct wavelength : length { -} wavelength; -DERIVED_DIMENSION(repetency, decltype(1 / wavelength)); -DERIVED_DIMENSION(wave_vector, decltype(1 / length)); -DERIVED_DIMENSION(angular_repetency, decltype(1 / wavelength)); -DERIVED_DIMENSION(phase_velocity, decltype(angular_frequency / angular_repetency)); -DERIVED_DIMENSION(damping_coefficient, decltype(1 / time_constant)); -DERIVED_DIMENSION(logarithmic_decrement, decltype(damping_coefficient * period_duration)); -DERIVED_DIMENSION(attenuation, decltype(1 / length)); -DERIVED_DIMENSION(phase_coefficient, decltype(phase_angle / length)); -DERIVED_DIMENSION(propagation_coefficient, decltype(1 / length)); +// clang-format off +QUANTITY_SPEC(width, length); +inline constexpr auto breadth = width; +QUANTITY_SPEC(height, length); +inline constexpr auto depth = height; +inline constexpr auto altitude = height; +QUANTITY_SPEC(thickness, width); +QUANTITY_SPEC(diameter, width); +// QUANTITY_SPEC(radius, mag * diameter); +QUANTITY_SPEC(radius, diameter); +QUANTITY_SPEC(path_length, length); +inline constexpr auto arc_length = path_length; +QUANTITY_SPEC(distance, path_length); +QUANTITY_SPEC(radial_distance, distance); +QUANTITY_SPEC(position_vector, length, quantity_character::vector); +QUANTITY_SPEC(displacement, length, quantity_character::vector); +QUANTITY_SPEC(radius_of_curvature, radius); +QUANTITY_SPEC(curvature, 1 / radius_of_curvature); +QUANTITY_SPEC(area, pow<2>(length)); +QUANTITY_SPEC(volume, pow<3>(length)); +QUANTITY_SPEC(angular_measure, arc_length / radius); +QUANTITY_SPEC(rotational_displacement, path_length / radius); +inline constexpr auto angular_displacement = rotational_displacement; +QUANTITY_SPEC(phase_angle, angular_measure); +QUANTITY_SPEC(solid_angular_measure, angular_measure * angular_measure); +inline constexpr auto duration = time; +QUANTITY_SPEC(velocity, position_vector / duration); // vector +QUANTITY_SPEC(speed, distance / duration); // TODO length, path_length? +QUANTITY_SPEC(acceleration, velocity / duration); // vector +QUANTITY_SPEC(angular_velocity, angular_displacement / duration, quantity_character::vector); +QUANTITY_SPEC(angular_acceleration, angular_velocity / duration); +QUANTITY_SPEC(period_duration, duration); +inline constexpr auto period = period_duration; +QUANTITY_SPEC(time_constant, duration); +QUANTITY_SPEC(rotation, rotational_displacement); +QUANTITY_SPEC(frequency, 1 / period_duration); +QUANTITY_SPEC(rotational_frequency, rotation / duration); +QUANTITY_SPEC(angular_frequency, phase_angle / duration); +QUANTITY_SPEC(wavelength, length); +QUANTITY_SPEC(repetency, 1 / wavelength); +inline constexpr auto wavenumber = repetency; +QUANTITY_SPEC(wave_vector, repetency, quantity_character::vector); +QUANTITY_SPEC(angular_repetency, 1 / wavelength); +inline constexpr auto angular_wavenumber = angular_repetency; +QUANTITY_SPEC(phase_velocity, angular_frequency / angular_repetency); +inline constexpr auto phase_speed = phase_velocity; +QUANTITY_SPEC(group_velocity, angular_frequency / angular_repetency); +inline constexpr auto group_speed = group_velocity; +QUANTITY_SPEC(damping_coefficient, 1 / time_constant); +QUANTITY_SPEC(logarithmic_decrement, damping_coefficient * period_duration); +QUANTITY_SPEC(attenuation, 1 / distance); +inline constexpr auto extinction = attenuation; +QUANTITY_SPEC(phase_coefficient, phase_angle / path_length); +QUANTITY_SPEC(propagation_coefficient, 1 / length); // γ = α + iβ where α denotes attenuation and β the phase coefficient of a plane wave +// clang-format on } // namespace units::isq diff --git a/src/systems/isq/include/units/isq/thermodynamics.h b/src/systems/isq/include/units/isq/thermodynamics.h index 80956a3b..f26acae3 100644 --- a/src/systems/isq/include/units/isq/thermodynamics.h +++ b/src/systems/isq/include/units/isq/thermodynamics.h @@ -23,7 +23,7 @@ #pragma once #include -#include +#include #include namespace units::isq { @@ -34,6 +34,6 @@ namespace units::isq { // DERIVED_DIMENSION(mass_density, decltype(mass / volume)); -DERIVED_DIMENSION(energy, decltype(force * length)); +// DERIVED_DIMENSION(energy, decltype(force * length)); // defined in a mechanics header } // namespace units::isq diff --git a/src/systems/si/include/units/si/unit_symbols.h b/src/systems/si/include/units/si/unit_symbols.h index 7f832023..2b740885 100644 --- a/src/systems/si/include/units/si/unit_symbols.h +++ b/src/systems/si/include/units/si/unit_symbols.h @@ -641,5 +641,6 @@ inline constexpr auto d = day; inline constexpr auto m2 = square; inline constexpr auto m3 = cubic; inline constexpr auto s2 = square; +inline constexpr auto s3 = cubic; } // namespace units::si::unit_symbols diff --git a/src/systems/si/include/units/si/units.h b/src/systems/si/include/units/si/units.h index 1c7e5000..ee4fb0a8 100644 --- a/src/systems/si/include/units/si/units.h +++ b/src/systems/si/include/units/si/units.h @@ -22,7 +22,7 @@ #pragma once -#include +#include #include #include diff --git a/test/unit_test/runtime/CMakeLists.txt b/test/unit_test/runtime/CMakeLists.txt index 9d8d4156..b5cea543 100644 --- a/test/unit_test/runtime/CMakeLists.txt +++ b/test/unit_test/runtime/CMakeLists.txt @@ -26,9 +26,7 @@ find_package(Catch2 3 CONFIG REQUIRED) add_executable( unit_tests_runtime - distribution_test.cpp - fmt_test.cpp - + distribution_test.cpp fmt_test.cpp # fmt_units_test.cpp math_test.cpp ) diff --git a/test/unit_test/static/CMakeLists.txt b/test/unit_test/static/CMakeLists.txt index f32f4a5f..332059e3 100644 --- a/test/unit_test/static/CMakeLists.txt +++ b/test/unit_test/static/CMakeLists.txt @@ -35,7 +35,6 @@ cmake_minimum_required(VERSION 3.2) add_library( unit_tests_static dimension_test.cpp - # angle_test.cpp # cgs_test.cpp # chrono_test.cpp @@ -43,27 +42,26 @@ add_library( # custom_rep_test_min_expl.cpp # custom_unit_test.cpp # dimension_op_test.cpp + dimension_test.cpp # dimensions_concepts_test.cpp # fixed_string_test.cpp # fps_test.cpp # iec80000_test.cpp # kind_test.cpp magnitude_test.cpp - # math_test.cpp # point_origin_test.cpp # prime_test.cpp + quantity_spec_test.cpp ratio_test.cpp reference_test.cpp - - # si_test.cpp # si_cgs_test.cpp # si_fps_test.cpp # si_hep_test.cpp + # si_test.cpp # symbol_text_test.cpp type_list_test.cpp unit_test.cpp - # us_test.cpp ) diff --git a/test/unit_test/static/dimension_test.cpp b/test/unit_test/static/dimension_test.cpp index 21bd2926..543dc6b5 100644 --- a/test/unit_test/static/dimension_test.cpp +++ b/test/unit_test/static/dimension_test.cpp @@ -33,37 +33,37 @@ using namespace units; using dimension_one_ = struct dimension_one; // clang-format off -BASE_DIMENSION_(length, "L"); -BASE_DIMENSION_(time, "T"); -BASE_DIMENSION_(mass, "M"); +inline constexpr struct length_ : base_dimension<"L"> {} length; +inline constexpr struct mass_ : base_dimension<"M"> {} mass; +inline constexpr struct time_ : base_dimension<"T"> {} time; -inline constexpr struct second_ : named_unit<"s", time> {} second; +QUANTITY_SPEC_(q_time, time); +inline constexpr struct second_ : named_unit<"s", q_time> {} second; -DERIVED_DIMENSION_(frequency, decltype(1 / time)); -DERIVED_DIMENSION_(action, decltype(1 / time)); -DERIVED_DIMENSION_(area, decltype(length * length)); -DERIVED_DIMENSION_(volume, decltype(area * length)); -DERIVED_DIMENSION_(speed, decltype(length / time)); -inline constexpr struct velocity_ : speed_ {} velocity; -DERIVED_DIMENSION_(acceleration, decltype(speed / time)); -DERIVED_DIMENSION_(force, decltype(mass * acceleration)); -DERIVED_DIMENSION_(moment_of_force, decltype(length * force)); -DERIVED_DIMENSION_(torque, decltype(moment_of_force)); -DERIVED_DIMENSION_(pressure, decltype(force / area)); -DERIVED_DIMENSION_(stress, decltype(pressure)); -DERIVED_DIMENSION_(strain, decltype(stress / stress)); -DERIVED_DIMENSION_(power, decltype(force * speed)); -DERIVED_DIMENSION_(efficiency, decltype(power / power)); -DERIVED_DIMENSION_(energy, decltype(force * length)); +inline constexpr auto frequency = 1 / time; +inline constexpr auto action = 1 / time; +inline constexpr auto area = length * length; +inline constexpr auto volume = area * length; +inline constexpr auto speed = length / time; +inline constexpr auto acceleration = speed / time; +inline constexpr auto force = mass * acceleration; +inline constexpr auto moment_of_force = length * force; +inline constexpr auto torque = moment_of_force; +inline constexpr auto pressure = force / area; +inline constexpr auto stress = pressure; +inline constexpr auto strain = stress / stress; +inline constexpr auto power = force * speed; +inline constexpr auto efficiency = power / power; +inline constexpr auto energy = force * length; // clang-format on // concepts verification static_assert(BaseDimension); -static_assert(!BaseDimension); +static_assert(!BaseDimension>); static_assert(!DerivedDimension); -static_assert(DerivedDimension); +static_assert(DerivedDimension>); static_assert(Dimension); -static_assert(Dimension); +static_assert(Dimension>); static_assert(DerivedDimension); static_assert(DerivedDimension); // dimension_one @@ -137,29 +137,29 @@ concept invalid_operations = requires { requires !requires { 2 == t; }; requires !requires { t < 2; }; requires !requires { 2 < t; }; - requires !requires { t + time[second]; }; - requires !requires { t - time[second]; }; - requires !requires { t* time[second]; }; - requires !requires { t / time[second]; }; - requires !requires { t == time[second]; }; - requires !requires { t < time[second]; }; - requires !requires { time[second] + t; }; - requires !requires { time[second] - t; }; - requires !requires { time[second] * t; }; - requires !requires { time[second] / t; }; - requires !requires { time[second] == t; }; - requires !requires { time[second] < t; }; - requires !requires { t + 1 * time[second]; }; - requires !requires { t - 1 * time[second]; }; - requires !requires { t * 1 * time[second]; }; - requires !requires { t / 1 * time[second]; }; - requires !requires { t == 1 * time[second]; }; - requires !requires { t == 1 * time[second]; }; - requires !requires { 1 * time[second] + t; }; - requires !requires { 1 * time[second] - t; }; - requires !requires { 1 * time[second] * t; }; - requires !requires { 1 * time[second] == t; }; - requires !requires { 1 * time[second] < t; }; + requires !requires { t + q_time[second]; }; + requires !requires { t - q_time[second]; }; + requires !requires { t* q_time[second]; }; + requires !requires { t / q_time[second]; }; + requires !requires { t == q_time[second]; }; + requires !requires { t < q_time[second]; }; + requires !requires { q_time[second] + t; }; + requires !requires { q_time[second] - t; }; + requires !requires { q_time[second] * t; }; + requires !requires { q_time[second] / t; }; + requires !requires { q_time[second] == t; }; + requires !requires { q_time[second] < t; }; + requires !requires { t + 1 * q_time[second]; }; + requires !requires { t - 1 * q_time[second]; }; + requires !requires { t * 1 * q_time[second]; }; + requires !requires { t / 1 * q_time[second]; }; + requires !requires { t == 1 * q_time[second]; }; + requires !requires { t == 1 * q_time[second]; }; + requires !requires { 1 * q_time[second] + t; }; + requires !requires { 1 * q_time[second] - t; }; + requires !requires { 1 * q_time[second] * t; }; + requires !requires { 1 * q_time[second] == t; }; + requires !requires { 1 * q_time[second] < t; }; }; static_assert(invalid_operations