From edec240b604000ea2df873f588f7f84515c1bc18 Mon Sep 17 00:00:00 2001 From: Julia Smerdel Date: Sat, 11 Oct 2025 21:31:33 +0200 Subject: [PATCH 01/18] add % operation and aliases --- src/core/include/mp-units/cartesian_vector.h | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index 1ee005e6ca..0dbd0222e1 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -124,6 +124,17 @@ struct cartesian_vector_iface { lhs._coordinates_[2] * rhs._coordinates_[0] - lhs._coordinates_[0] * rhs._coordinates_[2], lhs._coordinates_[0] * rhs._coordinates_[1] - lhs._coordinates_[1] * rhs._coordinates_[0]}; } + + template + requires requires(T t, U u) { t % u; } + [[nodiscard]] friend constexpr auto operator%(const cartesian_vector& lhs, const cartesian_vector& rhs) + { + return ::mp_units::cartesian_vector{ + lhs._coordinates_[0] % rhs._coordinates_[0], + lhs._coordinates_[1] % rhs._coordinates_[1], + lhs._coordinates_[2] % rhs._coordinates_[2] + }; + } }; } // namespace detail @@ -280,6 +291,23 @@ cartesian_vector(Arg, Args...) -> cartesian_vector +[[nodiscard]] constexpr auto dot(const cartesian_vector& lhs, const cartesian_vector& rhs) { + return scalar_product(lhs, rhs); +} + +template +[[nodiscard]] constexpr auto cross(const cartesian_vector& lhs, const cartesian_vector& rhs) { + return vector_product(lhs, rhs); +} + +template + requires treat_as_floating_point +[[nodiscard]] constexpr auto norm(const cartesian_vector& vec) { + return magnitude(vec); +} + #if MP_UNITS_HOSTED // TODO use parse and use formatter for the underlying type template From ab3c2e85ac00de66e4fa5a810999cd08e0350c47 Mon Sep 17 00:00:00 2001 From: Julia Smerdel Date: Sat, 18 Oct 2025 17:02:09 +0200 Subject: [PATCH 02/18] add header with vector and tensors --- src/core/include/mp-units/cartesian_vector.h | 557 +++++++++++++++---- 1 file changed, 435 insertions(+), 122 deletions(-) diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index 0dbd0222e1..11bcba56df 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -23,155 +23,186 @@ #pragma once #include -// #include #include #include +#include +#include #if MP_UNITS_HOSTED -#include + #include #endif #ifndef MP_UNITS_IN_MODULE_INTERFACE -#ifdef MP_UNITS_IMPORT_STD -import std; -#else -#include -#include -#include -#if MP_UNITS_HOSTED -#include -#endif -#endif +# ifdef MP_UNITS_IMPORT_STD + import std; +# else +# include +# include +# include +# include +# if MP_UNITS_HOSTED +# include +# endif +# endif #endif +// ============================================================================ +// Cartesian 3D vector & up-to-3x3 tensor reps + quantity-level LA operations +// ============================================================================ + namespace mp_units { MP_UNITS_EXPORT template class cartesian_vector; +MP_UNITS_EXPORT template +class cartesian_tensor; + +// --------------------------- cartesian_vector ------------------------------- + namespace detail { -struct cartesian_vector_iface { +struct cartesian_vector_iface { // -> how it behaves (math) + // elementwise A + B template - requires requires(T t, U u) { t + u; } - [[nodiscard]] friend constexpr auto operator+(const cartesian_vector& lhs, const cartesian_vector& rhs) + requires requires(const T& t, const U& u) { t + u; } //only compile this if the underlying number types can be added + [[nodiscard]] friend constexpr auto operator+(const cartesian_vector& lhs, + const cartesian_vector& rhs) { return ::mp_units::cartesian_vector{lhs._coordinates_[0] + rhs._coordinates_[0], lhs._coordinates_[1] + rhs._coordinates_[1], lhs._coordinates_[2] + rhs._coordinates_[2]}; } + // elementwise A - B template - requires requires(T t, U u) { t - u; } - [[nodiscard]] friend constexpr auto operator-(const cartesian_vector& lhs, const cartesian_vector& rhs) + requires requires(const T& t, const U& u) { t - u; } + [[nodiscard]] friend constexpr auto operator-(const cartesian_vector& lhs, + const cartesian_vector& rhs) { return ::mp_units::cartesian_vector{lhs._coordinates_[0] - rhs._coordinates_[0], lhs._coordinates_[1] - rhs._coordinates_[1], lhs._coordinates_[2] - rhs._coordinates_[2]}; } + // elementwise A % B (integral: %, floating: fmod) template - requires requires(T t, U u) { t * u; } - [[nodiscard]] friend constexpr auto operator*(const cartesian_vector& lhs, const U& rhs) + requires requires(const T& t, const U& u) { t % u; } || (std::floating_point && std::floating_point) + [[nodiscard]] friend constexpr auto operator%(const cartesian_vector& lhs, + const cartesian_vector& rhs) { - return ::mp_units::cartesian_vector{lhs._coordinates_[0] * rhs, lhs._coordinates_[1] * rhs, - lhs._coordinates_[2] * rhs}; + if constexpr (std::floating_point && std::floating_point) { + using std::fmod; + return ::mp_units::cartesian_vector{ + fmod(static_cast(lhs._coordinates_[0]), static_cast(rhs._coordinates_[0])), + fmod(static_cast(lhs._coordinates_[1]), static_cast(rhs._coordinates_[1])), + fmod(static_cast(lhs._coordinates_[2]), static_cast(rhs._coordinates_[2]))}; + } else { + return ::mp_units::cartesian_vector{ + lhs._coordinates_[0] % rhs._coordinates_[0], + lhs._coordinates_[1] % rhs._coordinates_[1], + lhs._coordinates_[2] % rhs._coordinates_[2]}; + } } - template - requires requires(T t, U u) { t * u; } - [[nodiscard]] friend constexpr auto operator*(const T& lhs, const cartesian_vector& rhs) + // scalar multiply (vector * scalar) + template + requires requires(const T& t, const S& s) { t * s; } + [[nodiscard]] friend constexpr auto operator*(const cartesian_vector& vector, const S& scalar) { - return rhs * lhs; + return ::mp_units::cartesian_vector{vector._coordinates_[0] * scalar, + vector._coordinates_[1] * scalar, + vector._coordinates_[2] * scalar}; } - template - requires requires(T t, U u) { t / u; } - [[nodiscard]] friend constexpr auto operator/(const cartesian_vector& lhs, const U& rhs) + // scalar multiply (scalar * vector) + template + requires requires(const S& s, const T& t) { s * t; } + [[nodiscard]] friend constexpr auto operator*(const S& scalar, const cartesian_vector& vector) { - return ::mp_units::cartesian_vector{lhs._coordinates_[0] / rhs, lhs._coordinates_[1] / rhs, - lhs._coordinates_[2] / rhs}; + return vector * scalar; } + // scalar divide (vector / scalar) + template + requires requires(const T& t, const S& s) { t / s; } + [[nodiscard]] friend constexpr auto operator/(const cartesian_vector& vector, const S& scalar) + { + return ::mp_units::cartesian_vector{vector._coordinates_[0] / scalar, + vector._coordinates_[1] / scalar, + vector._coordinates_[2] / scalar}; + } + + // equality template U> - [[nodiscard]] friend constexpr bool operator==(const cartesian_vector& lhs, const cartesian_vector& rhs) + [[nodiscard]] friend constexpr bool operator==(const cartesian_vector& lhs, + const cartesian_vector& rhs) { - return lhs._coordinates_[0] == rhs._coordinates_[0] && lhs._coordinates_[1] == rhs._coordinates_[1] && + return lhs._coordinates_[0] == rhs._coordinates_[0] && + lhs._coordinates_[1] == rhs._coordinates_[1] && lhs._coordinates_[2] == rhs._coordinates_[2]; } + // Dot product (scalar product of vectors) template - requires requires(T t, U u, decltype(t * u) v) { - t * u; - v + v; - } - [[nodiscard]] friend constexpr auto scalar_product(const cartesian_vector& lhs, const cartesian_vector& rhs) + requires requires(const T& t, const U& u, decltype(t * u) v) { t * u; v + v; } + [[nodiscard]] friend constexpr auto scalar_product(const cartesian_vector& lhs, + const cartesian_vector& rhs) { - return lhs._coordinates_[0] * rhs._coordinates_[0] + lhs._coordinates_[1] * rhs._coordinates_[1] + + return lhs._coordinates_[0] * rhs._coordinates_[0] + + lhs._coordinates_[1] * rhs._coordinates_[1] + lhs._coordinates_[2] * rhs._coordinates_[2]; } + // Cross product (vector product) — 3D only template - requires requires(T t, U u, decltype(t * u) v) { - t * u; - v - v; - } - [[nodiscard]] friend constexpr auto vector_product(const cartesian_vector& lhs, const cartesian_vector& rhs) + requires requires(const T& t, const U& u, decltype(t * u) v) { t * u; v - v; } + [[nodiscard]] friend constexpr auto vector_product(const cartesian_vector& lhs, + const cartesian_vector& rhs) { return ::mp_units::cartesian_vector{ lhs._coordinates_[1] * rhs._coordinates_[2] - lhs._coordinates_[2] * rhs._coordinates_[1], lhs._coordinates_[2] * rhs._coordinates_[0] - lhs._coordinates_[0] * rhs._coordinates_[2], lhs._coordinates_[0] * rhs._coordinates_[1] - lhs._coordinates_[1] * rhs._coordinates_[0]}; } - - template - requires requires(T t, U u) { t % u; } - [[nodiscard]] friend constexpr auto operator%(const cartesian_vector& lhs, const cartesian_vector& rhs) - { - return ::mp_units::cartesian_vector{ - lhs._coordinates_[0] % rhs._coordinates_[0], - lhs._coordinates_[1] % rhs._coordinates_[1], - lhs._coordinates_[2] % rhs._coordinates_[2] - }; - } }; -} // namespace detail +} // namespace detail MP_UNITS_EXPORT template -class cartesian_vector : public detail::cartesian_vector_iface { +class cartesian_vector : public detail::cartesian_vector_iface { // -> what it is (the data) public: - // public members required to satisfy structural type requirements :-( - T _coordinates_[3]; - using value_type = T; + T _coordinates_[3]{}; + using value_type = T; //number type inside the vector + cartesian_vector() = default; cartesian_vector(const cartesian_vector&) = default; cartesian_vector(cartesian_vector&&) = default; cartesian_vector& operator=(const cartesian_vector&) = default; cartesian_vector& operator=(cartesian_vector&&) = default; template - requires(... && std::constructible_from) - constexpr explicit(!(... && std::convertible_to)) cartesian_vector(Args&&... args) : - _coordinates_{static_cast(std::forward(args))...} - { - } + requires (sizeof...(Args) == 3) && (... && std::constructible_from) + constexpr explicit(!(... && std::convertible_to)) + cartesian_vector(Args&&... args) + : _coordinates_{static_cast(std::forward(args))...} {} template - requires std::constructible_from - constexpr explicit(!std::convertible_to) cartesian_vector(const cartesian_vector& other) : - _coordinates_{static_cast(other[0]), static_cast(other[1]), static_cast(other[2])} - { - } + requires std::constructible_from + constexpr explicit(!std::convertible_to) + cartesian_vector(const cartesian_vector& other) + : _coordinates_{static_cast(other[0]), + static_cast(other[1]), + static_cast(other[2])} {} template - requires std::constructible_from - constexpr explicit(!std::convertible_to) cartesian_vector(cartesian_vector&& other) : - _coordinates_{static_cast(std::move(other[0])), static_cast(std::move(other[1])), - static_cast(std::move(other[2]))} - { - } + requires std::constructible_from + constexpr explicit(!std::convertible_to) + cartesian_vector(cartesian_vector&& other) + : _coordinates_{static_cast(std::move(other[0])), + static_cast(std::move(other[1])), + static_cast(std::move(other[2]))} {} template U> constexpr cartesian_vector& operator=(const cartesian_vector& other) @@ -208,7 +239,7 @@ class cartesian_vector : public detail::cartesian_vector_iface { return *this / magnitude(); } - [[nodiscard]] constexpr T& operator[](std::size_t i) { return _coordinates_[i]; } + [[nodiscard]] constexpr T& operator[](std::size_t i) { return _coordinates_[i]; } [[nodiscard]] constexpr const T& operator[](std::size_t i) const { return _coordinates_[i]; } [[nodiscard]] constexpr cartesian_vector operator+() const { return *this; } @@ -217,10 +248,7 @@ class cartesian_vector : public detail::cartesian_vector_iface { return {-_coordinates_[0], -_coordinates_[1], -_coordinates_[2]}; } - template - requires requires(T t, U u) { - { t += u } -> std::same_as; - } + template requires requires(T& t, const U& u) { t += u; } constexpr cartesian_vector& operator+=(const cartesian_vector& other) { _coordinates_[0] += other[0]; @@ -229,11 +257,8 @@ class cartesian_vector : public detail::cartesian_vector_iface { return *this; } - template - requires requires(T t, U u) { - { t -= u } -> std::same_as; - } - constexpr cartesian_vector& operator-=(const cartesian_vector& other) + template requires requires(T& t, const U& u) { t -= u; } + constexpr cartesian_vector& operator-=(const cartesian_vector& v) { _coordinates_[0] -= other[0]; _coordinates_[1] -= other[1]; @@ -241,10 +266,7 @@ class cartesian_vector : public detail::cartesian_vector_iface { return *this; } - template - requires requires(T t, U u) { - { t *= u } -> std::same_as; - } + template requires requires(T& t, const U& u) { t *= u; } constexpr cartesian_vector& operator*=(const U& value) { _coordinates_[0] *= value; @@ -253,70 +275,361 @@ class cartesian_vector : public detail::cartesian_vector_iface { return *this; } - template - requires requires(T t, U u) { - { t /= u } -> std::same_as; - } - constexpr cartesian_vector& operator/=(const U& value) + template requires requires(T& t, const U& u) { t /= u; } + constexpr cartesian_vector& operator/=(const S& s) { - _coordinates_[0] /= value; - _coordinates_[1] /= value; - _coordinates_[2] /= value; + _coordinates_[0] /= s; _coordinates_[1] /= s; _coordinates_[2] /= s; return *this; } [[nodiscard]] friend constexpr T magnitude(const cartesian_vector& vec) requires treat_as_floating_point - { - return vec.magnitude(); - } + { return vec.magnitude(); } [[nodiscard]] friend constexpr cartesian_vector unit_vector(const cartesian_vector& vec) requires treat_as_floating_point - { - return vec.unit(); - } + { return vec.unit(); } #if MP_UNITS_HOSTED - friend constexpr std::ostream& operator<<(std::ostream& os, const cartesian_vector& vec) + friend std::ostream& operator<<(std::ostream& os, const cartesian_vector& vec) { return os << '[' << vec[0] << ", " << vec[1] << ", " << vec[2] << ']'; } #endif }; +// CTAD guide: deduce value_type from ctor args template - requires(sizeof...(Args) <= 2) && requires { typename std::common_type_t; } + requires (sizeof...(Args) == 2) && requires { typename std::common_type_t; } cartesian_vector(Arg, Args...) -> cartesian_vector>; -} // namespace mp_units +// Friendly aliases for numeric-level ops +template +[[nodiscard]] constexpr auto dot(const cartesian_vector& lhs, const cartesian_vector& rhs) +{ return scalar_product(lhs, rhs); } -//ALIASES template -[[nodiscard]] constexpr auto dot(const cartesian_vector& lhs, const cartesian_vector& rhs) { - return scalar_product(lhs, rhs); +[[nodiscard]] constexpr auto cross(const cartesian_vector& lhs, const cartesian_vector& rhs) +{ return vector_product(lhs, rhs); } + +template + requires treat_as_floating_point +[[nodiscard]] constexpr auto norm(const cartesian_vector& vec) +{ return magnitude(vec); } + +// Register as a vector representation type +template +inline constexpr bool is_vector> = true; + +// ---------------------------- cartesian_tensor ------------------------------ + +namespace detail { +template struct type_identity { using type = T; }; +template using type_identity_t = typename type_identity::type; +} // namespace detail + +MP_UNITS_EXPORT template +class cartesian_tensor { + static_assert(R >= 1 && R <= 3 && C >= 1 && C <= 3, "cartesian_tensor supports sizes up to 3x3"); +public: + using value_type = T; + static constexpr std::size_t rows_v = R; + static constexpr std::size_t cols_v = C; + + T _data_[R * C]{}; + + cartesian_tensor() = default; + cartesian_tensor(const cartesian_tensor&) = default; + cartesian_tensor(cartesian_tensor&&) = default; + cartesian_tensor& operator=(const cartesian_tensor&) = default; + cartesian_tensor& operator=(cartesian_tensor&&) = default; + + template + requires (sizeof...(Args) == R * C) && (... && std::constructible_from) + constexpr explicit(!(... && std::convertible_to)) + cartesian_tensor(Args&&... args) : _data_{static_cast(std::forward(args))...} {} + + [[nodiscard]] constexpr T& operator()(std::size_t r, std::size_t c) { return _data_[r * C + c]; } + [[nodiscard]] constexpr const T& operator()(std::size_t r, std::size_t c) const { return _data_[r * C + c]; } + + template + requires requires(const T& t, const U& u) { t + u; } + [[nodiscard]] friend constexpr auto operator+(const cartesian_tensor& lhs, const cartesian_tensor& rhs) + { + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(lhs._data_[i]) + static_cast(rhs._data_[i]); + return Rm; + } + + template + requires requires(const T& t, const U& u) { t - u; } + [[nodiscard]] friend constexpr auto operator-(const cartesian_tensor& lhs, const cartesian_tensor& rhs) + { + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(lhs._data_[i]) - static_cast(rhs._data_[i]); + return Rm; + } + + template + requires (requires(const T& t, const U& u) { t % u; } || (std::floating_point && std::floating_point)) + [[nodiscard]] friend constexpr auto operator%(const cartesian_tensor& lhs, const cartesian_tensor& rhs) + { + using CT = std::conditional_t<(std::floating_point || std::floating_point), long double, std::common_type_t>; + cartesian_tensor Rm{}; + if constexpr (std::floating_point || std::floating_point) { + using std::fmod; + for (std::size_t i = 0; i < R * C; ++i) + Rm._data_[i] = static_cast(fmod(static_cast(lhs._data_[i]), + static_cast(rhs._data_[i]))); + } else { + for (std::size_t i = 0; i < R * C; ++i) + Rm._data_[i] = static_cast(lhs._data_[i] % rhs._data_[i]); + } + return Rm; + } + + template + requires requires(const T& t, const S& s) { t * s; } + [[nodiscard]] friend constexpr auto operator*(const cartesian_tensor& tensor, const S& scalar) + { + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(tensor._data_[i]) * static_cast(scalar); + return Rm; + } + + template + requires requires(const S& s, const T& t) { s * t; } + [[nodiscard]] friend constexpr auto operator*(const S& scalar, const cartesian_tensor& tensor) { return tensor * scalar; } + + template + requires requires(const T& t, const S& s) { t / s; } + [[nodiscard]] friend constexpr auto operator/(const cartesian_tensor& tensor, const S& scalar) + { + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(tensor._data_[i]) / static_cast(scalar); + return Rm; + } + +#if MP_UNITS_HOSTED + friend std::ostream& operator<<(std::ostream& os, const cartesian_tensor& tensor) + { + for (std::size_t r = 0; r < R; ++r) { + os << (r == 0 ? "[[" : " ["); + for (std::size_t c = 0; c < C; ++c) { + os << tensor(r, c); + if (c + 1 != C) os << ", "; + } + os << (r + 1 == R ? "]]" : "]\n"); + } + return os; + } +#endif +}; + +// Register as a tensor representation type +template +inline constexpr bool is_tensor> = true; + +// ---------------------- Numeric-level helpers for tensors ------------------- + +template +[[nodiscard]] constexpr auto matmul(const cartesian_tensor& tensor, + const cartesian_tensor& other) +{ + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t r = 0; r < R; ++r) + for (std::size_t c = 0; c < C; ++c) { + CT acc{}; + for (std::size_t k = 0; k < K; ++k) acc += static_cast(tensor(r, k)) * static_cast(other(k, c)); + Rm(r, c) = acc; + } + return Rm; } template -[[nodiscard]] constexpr auto cross(const cartesian_vector& lhs, const cartesian_vector& rhs) { - return vector_product(lhs, rhs); +[[nodiscard]] constexpr auto matvec(const cartesian_tensor& tensor, + const cartesian_vector& vector) +{ + using CT = std::common_type_t; + cartesian_vector Rv{}; + for (std::size_t r = 0; r < 3; ++r) { + CT acc{}; + for (std::size_t c = 0; c < 3; ++c) acc += static_cast(tensor(r, c)) * static_cast(vector._coordinates_[c]); + Rv[r] = acc; + } + return Rv; } -template - requires treat_as_floating_point -[[nodiscard]] constexpr auto norm(const cartesian_vector& vec) { - return magnitude(vec); +template +[[nodiscard]] constexpr auto double_contraction(const cartesian_tensor& tensor, + const cartesian_tensor& other) +{ + using CT = std::common_type_t; + CT acc{}; + for (std::size_t i = 0; i < R * C; ++i) acc += static_cast(tensor._data_[i]) * static_cast(other._data_[i]); + return acc; +} + +template +[[nodiscard]] constexpr auto outer_numeric(const cartesian_vector& vector, + const cartesian_vector& other) +{ + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < 3; ++i) + for (std::size_t j = 0; j < 3; ++j) + Rm(i, j) = static_cast(vector._coordinates_[i]) * static_cast(other._coordinates_[j]); + return Rm; +} + +// ---------------------- Quantity-level LA operations ------------------------ + +namespace detail { + template + [[nodiscard]] consteval auto ref_of() + { + return std::remove_cvref_t::reference; + } +} // namespace detail + +// ---- Vectors ---- + +// dot: a ⋅ b -> scalar quantity (units multiply) +template + requires (Quantity && Quantity && + is_vector::rep> && + is_vector::rep>) +[[nodiscard]] constexpr auto dot(const QA& a, const QB& b) +{ + using A = std::remove_cvref_t; + using B = std::remove_cvref_t; + constexpr auto RA = detail::ref_of(); + constexpr auto RB = detail::ref_of(); + const auto av = a.numerical_value_in(RA); + const auto bv = b.numerical_value_in(RB); + return quantity{ scalar_product(av, bv), RA * RB }; +} + +// cross: a × b -> vector quantity (units multiply) +template + requires (Quantity && is_vector::rep>) +[[nodiscard]] constexpr auto cross(const QV& a, const QV& b) +{ + using VQ = std::remove_cvref_t; + constexpr auto R = detail::ref_of(); + const auto av = a.numerical_value_in(R); + const auto bv = b.numerical_value_in(R); + return quantity{ vector_product(av, bv), R * R }; +} + +// |a|: magnitude -> scalar quantity (sqrt(a⋅a)) +template + requires (Quantity && is_vector::rep>) +[[nodiscard]] constexpr auto magnitude(const QV& a) +{ + auto aa = dot(a, a); // scalar quantity with R*R + using mp_units::sqrt; + return sqrt(aa); // scalar quantity with R } +// ---- Tensor / Vector & Tensor / Tensor ---- + +// tensor product: a ⊗ b (vector ⊗ vector -> tensor) +template + requires (Quantity && Quantity && + is_vector::rep> && + is_vector::rep>) +[[nodiscard]] constexpr auto outer(const QA& a, const QB& b) +{ + using A = std::remove_cvref_t; + using B = std::remove_cvref_t; + constexpr auto RA = detail::ref_of(); + constexpr auto RB = detail::ref_of(); + const auto av = a.numerical_value_in(RA); + const auto bv = b.numerical_value_in(RB); + return quantity{ outer_numeric(av, bv), RA * RB }; +} + +// inner: A ⋅ x (tensor ⋅ vector) -> vector +template + requires (Quantity && Quantity && + is_tensor::rep> && + is_vector::rep>) +[[nodiscard]] constexpr auto inner(const QT& A, const QV& x) +{ + using TQ = std::remove_cvref_t; + using VQ = std::remove_cvref_t; + constexpr auto RT = detail::ref_of(); + constexpr auto RX = detail::ref_of(); + const auto Av = A.numerical_value_in(RT); + const auto xv = x.numerical_value_in(RX); + return quantity{ matvec(Av, xv), RT * RX }; +} + +// inner: A ⋅ B (tensor ⋅ tensor) -> tensor +template + requires (Quantity && Quantity && + is_tensor::rep> && + is_tensor::rep>) +[[nodiscard]] constexpr auto inner(const QA& A, const QB& B) +{ + using TA = std::remove_cvref_t; + using TB = std::remove_cvref_t; + constexpr auto RA = detail::ref_of(); + constexpr auto RB = detail::ref_of(); + const auto Av = A.numerical_value_in(RA); + const auto Bv = B.numerical_value_in(RB); + return quantity{ matmul(Av, Bv), RA * RB }; +} + +// scalar product: A : B (double contraction) -> scalar +template + requires (Quantity && Quantity && + is_tensor::rep> && + is_tensor::rep>) +[[nodiscard]] constexpr auto scalar_product(const QA& A, const QB& B) +{ + using TA = std::remove_cvref_t; + using TB = std::remove_cvref_t; + constexpr auto RA = detail::ref_of(); + constexpr auto RB = detail::ref_of(); + const auto Av = A.numerical_value_in(RA); + const auto Bv = B.numerical_value_in(RB); + return quantity{ double_contraction(Av, Bv), RA * RB }; +} + +} // namespace mp_units + #if MP_UNITS_HOSTED -// TODO use parse and use formatter for the underlying type template -struct MP_UNITS_STD_FMT::formatter, Char> : - formatter, Char> { - template - auto format(const mp_units::cartesian_vector& vec, FormatContext& ctx) const - { - return format_to(ctx.out(), "[{}, {}, {}]", vec[0], vec[1], vec[2]); +struct MP_UNITS_STD_FMT::formatter, Char> + : formatter, Char> { + template + auto format(const mp_units::cartesian_vector& v, Ctx& ctx) const { + return format_to(ctx.out(), "[{}, {}, {}]", v._coordinates_[0], v._coordinates_[1], v._coordinates_[2]); + } +}; + +template +struct MP_UNITS_STD_FMT::formatter, Char> + : formatter, Char> { + template + auto format(const mp_units::cartesian_tensor& A, Ctx& ctx) const { + auto out = ctx.out(); + for (std::size_t r = 0; r < R; ++r) { + out = format_to(out, r == 0 ? "[[" : " ["); + for (std::size_t c = 0; c < C; ++c) { + out = format_to(out, "{}", A(r, c)); + if (c + 1 != C) out = format_to(out, ", "); + } + out = format_to(out, r + 1 == R ? "]]" : "]\n"); + } + return out; } }; #endif From a4f068205e5507844f1aac0e36caac250cc2d662 Mon Sep 17 00:00:00 2001 From: Julia Smerdel Date: Sun, 19 Oct 2025 19:12:03 +0200 Subject: [PATCH 03/18] spli into 2 files --- CMakeLists.txt | 3 + src/core/include/mp-units/cartesian_tensor.h | 232 +++++++++ src/core/include/mp-units/cartesian_vector.h | 483 +++---------------- 3 files changed, 311 insertions(+), 407 deletions(-) create mode 100644 src/core/include/mp-units/cartesian_tensor.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 83ea37c3b5..81ffd47739 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,3 +82,6 @@ endif() # add unit tests enable_testing() add_subdirectory(test) +find_package(mp-units REQUIRED) +target_link_libraries(my_units_app PRIVATE mp-units::mp-units) + diff --git a/src/core/include/mp-units/cartesian_tensor.h b/src/core/include/mp-units/cartesian_tensor.h new file mode 100644 index 0000000000..c25fa3a191 --- /dev/null +++ b/src/core/include/mp-units/cartesian_tensor.h @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +#pragma once + +#include +#include +#include +#include +#include // for matvec/outer_numeric + +#if MP_UNITS_HOSTED + #include +#endif + +#ifndef MP_UNITS_IN_MODULE_INTERFACE +# ifdef MP_UNITS_IMPORT_STD + import std; +# else +# include +# include +# include +# include +# if MP_UNITS_HOSTED +# include +# endif +# endif +#endif + +namespace mp_units { + +// Forward decl (exported) +MP_UNITS_EXPORT template +class cartesian_tensor; + +// ================================ tensor rep ================================ + +MP_UNITS_EXPORT template +class cartesian_tensor { + static_assert(R >= 1 && R <= 3 && C >= 1 && C <= 3, + "cartesian_tensor supports sizes up to 3x3"); +public: + using value_type = T; + static constexpr std::size_t rows_v = R; + static constexpr std::size_t cols_v = C; + + T _data_[R * C]{}; + + cartesian_tensor() = default; + cartesian_tensor(const cartesian_tensor&) = default; + cartesian_tensor(cartesian_tensor&&) = default; + cartesian_tensor& operator=(const cartesian_tensor&) = default; + cartesian_tensor& operator=(cartesian_tensor&&) = default; + + // fill ctor (row-major R*C) + template + requires (sizeof...(Args) == R * C) && (... && std::constructible_from) + constexpr explicit(!(... && std::convertible_to)) + cartesian_tensor(Args&&... args) + : _data_{ static_cast(std::forward(args))... } {} + + // element access + [[nodiscard]] constexpr T& operator()(std::size_t r, std::size_t c) { return _data_[r * C + c]; } + [[nodiscard]] constexpr const T& operator()(std::size_t r, std::size_t c) const { return _data_[r * C + c]; } + + // elementwise +, -, % + template + requires requires(const T& t, const U& u) { t + u; } + [[nodiscard]] friend constexpr auto operator+(const cartesian_tensor& A, const cartesian_tensor& B) + { + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < R * C; ++i) + Rm._data_[i] = static_cast(A._data_[i]) + static_cast(B._data_[i]); + return Rm; + } + + template + requires requires(const T& t, const U& u) { t - u; } + [[nodiscard]] friend constexpr auto operator-(const cartesian_tensor& A, const cartesian_tensor& B) + { + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < R * C; ++i) + Rm._data_[i] = static_cast(A._data_[i]) - static_cast(B._data_[i]); + return Rm; + } + + template + requires (requires(const T& t, const U& u) { t % u; } || + (std::floating_point && std::floating_point)) + [[nodiscard]] friend constexpr auto operator%(const cartesian_tensor& A, const cartesian_tensor& B) + { + using CT = std::common_type_t; + cartesian_tensor Rm{}; + if constexpr (std::floating_point || std::floating_point) { + using std::fmod; + for (std::size_t i = 0; i < R * C; ++i) + Rm._data_[i] = static_cast(fmod(static_cast(A._data_[i]), + static_cast(B._data_[i]))); + } else { + for (std::size_t i = 0; i < R * C; ++i) + Rm._data_[i] = static_cast(A._data_[i] % B._data_[i]); + } + return Rm; + } + + // scalar *, / + template + requires requires(const T& t, const S& s) { t * s; } + [[nodiscard]] friend constexpr auto operator*(const cartesian_tensor& tensor, const S& scalar) + { + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < R * C; ++i) + Rm._data_[i] = static_cast(tensor._data_[i]) * static_cast(scalar); + return Rm; + } + + template + requires requires(const S& s, const T& t) { s * t; } + [[nodiscard]] friend constexpr auto operator*(const S& scalar, const cartesian_tensor& tensor) { return tensor * scalar; } + + template + requires requires(const T& t, const S& s) { t / s; } + [[nodiscard]] friend constexpr auto operator/(const cartesian_tensor& tensor, const S& scalar) + { + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < R * C; ++i) + Rm._data_[i] = static_cast(tensor._data_[i]) / static_cast(scalar); + return Rm; + } + +#if MP_UNITS_HOSTED + friend std::ostream& operator<<(std::ostream& os, const cartesian_tensor& A) + { + for (std::size_t r = 0; r < R; ++r) { + os << (r == 0 ? "[[" : " ["); + for (std::size_t c = 0; c < C; ++c) { + os << A(r, c); + if (c + 1 != C) os << ", "; + } + os << (r + 1 == R ? "]]" : "]\n"); + } + return os; + } +#endif +}; + +// Register as tensor rep +template +inline constexpr bool is_tensor> = true; + +// ======================== numeric helpers (no units) ======================== + +// Matrix × Matrix +template +[[nodiscard]] constexpr auto matmul(const cartesian_tensor& tensor, + const cartesian_tensor& other) +{ + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t r = 0; r < R; ++r) + for (std::size_t c = 0; c < C; ++c) { + CT acc{}; + for (std::size_t k = 0; k < K; ++k) acc += static_cast(tensor(r, k)) * static_cast(other(k, c)); + Rm(r, c) = acc; + } + return Rm; +} + +// Matrix × Vector (3×3) +template +[[nodiscard]] constexpr auto matvec(const cartesian_tensor& tensor, + const cartesian_vector& vector) +{ + using CT = std::common_type_t; + cartesian_vector Rv{}; + for (std::size_t r = 0; r < 3; ++r) { + CT acc{}; + for (std::size_t c = 0; c < 3; ++c) acc += static_cast(tensor(r, c)) * static_cast(vector[c]); + Rv[r] = acc; + } + return Rv; +} + +// Double contraction: A : B +template +[[nodiscard]] constexpr auto double_contraction(const cartesian_tensor& tensor, + const cartesian_tensor& other) +{ + using CT = std::common_type_t; + CT acc{}; + for (std::size_t i = 0; i < R * C; ++i) + acc += static_cast(tensor._data_[i]) * static_cast(other._data_[i]); + return acc; // numeric scalar +} + +// Outer product: vector ⊗ vector -> 3x3 matrix +template +[[nodiscard]] constexpr auto outer_numeric(const cartesian_vector& vector, + const cartesian_vector& other) +{ + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < 3; ++i) + for (std::size_t j = 0; j < 3; ++j) + Rm(i, j) = static_cast(vector[i]) * static_cast(other[j]); + return Rm; +} + +} // namespace mp_units + +#if MP_UNITS_HOSTED +// fmt/format support +template +struct MP_UNITS_STD_FMT::formatter, Char> + : formatter, Char> { + template + auto format(const mp_units::cartesian_tensor& A, Ctx& ctx) const { + auto out = ctx.out(); + for (std::size_t r = 0; r < R; ++r) { + out = format_to(out, r == 0 ? "[[" : " ["); + for (std::size_t c = 0; c < C; ++c) { + out = format_to(out, "{}", A(r, c)); + if (c + 1 != C) out = format_to(out, ", "); + } + out = format_to(out, r + 1 == R ? "]]" : "]\n"); + } + return out; + } +}; +#endif diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index 11bcba56df..b641d92e9d 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -1,33 +1,10 @@ -// 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. - +// SPDX-License-Identifier: MIT #pragma once #include #include #include #include -#include -#include #if MP_UNITS_HOSTED #include @@ -47,91 +24,91 @@ # endif #endif -// ============================================================================ -// Cartesian 3D vector & up-to-3x3 tensor reps + quantity-level LA operations -// ============================================================================ - namespace mp_units { -MP_UNITS_EXPORT template +MP_UNITS_EXPORT template class cartesian_vector; -MP_UNITS_EXPORT template -class cartesian_tensor; - -// --------------------------- cartesian_vector ------------------------------- - namespace detail { -struct cartesian_vector_iface { // -> how it behaves (math) - // elementwise A + B +struct cartesian_vector_iface { + // A + B template - requires requires(const T& t, const U& u) { t + u; } //only compile this if the underlying number types can be added + requires requires(const T& t, const U& u) { t + u; } [[nodiscard]] friend constexpr auto operator+(const cartesian_vector& lhs, const cartesian_vector& rhs) { - return ::mp_units::cartesian_vector{lhs._coordinates_[0] + rhs._coordinates_[0], - lhs._coordinates_[1] + rhs._coordinates_[1], - lhs._coordinates_[2] + rhs._coordinates_[2]}; + return ::mp_units::cartesian_vector{ + lhs._coordinates_[0] + rhs._coordinates_[0], + lhs._coordinates_[1] + rhs._coordinates_[1], + lhs._coordinates_[2] + rhs._coordinates_[2]}; } - // elementwise A - B + // A - B template requires requires(const T& t, const U& u) { t - u; } [[nodiscard]] friend constexpr auto operator-(const cartesian_vector& lhs, const cartesian_vector& rhs) { - return ::mp_units::cartesian_vector{lhs._coordinates_[0] - rhs._coordinates_[0], - lhs._coordinates_[1] - rhs._coordinates_[1], - lhs._coordinates_[2] - rhs._coordinates_[2]}; + return ::mp_units::cartesian_vector{ + lhs._coordinates_[0] - rhs._coordinates_[0], + lhs._coordinates_[1] - rhs._coordinates_[1], + lhs._coordinates_[2] - rhs._coordinates_[2]}; } - // elementwise A % B (integral: %, floating: fmod) + // A % B (integral: %, floating: fmod into CT) template - requires requires(const T& t, const U& u) { t % u; } || (std::floating_point && std::floating_point) + requires (requires(const T& t, const U& u) { t % u; }) || + (std::floating_point && std::floating_point) [[nodiscard]] friend constexpr auto operator%(const cartesian_vector& lhs, const cartesian_vector& rhs) { + using CT = std::common_type_t; if constexpr (std::floating_point && std::floating_point) { using std::fmod; - return ::mp_units::cartesian_vector{ - fmod(static_cast(lhs._coordinates_[0]), static_cast(rhs._coordinates_[0])), - fmod(static_cast(lhs._coordinates_[1]), static_cast(rhs._coordinates_[1])), - fmod(static_cast(lhs._coordinates_[2]), static_cast(rhs._coordinates_[2]))}; + return ::mp_units::cartesian_vector{ + static_cast(fmod(static_cast(lhs._coordinates_[0]), + static_cast(rhs._coordinates_[0]))), + static_cast(fmod(static_cast(lhs._coordinates_[1]), + static_cast(rhs._coordinates_[1]))), + static_cast(fmod(static_cast(lhs._coordinates_[2]), + static_cast(rhs._coordinates_[2])))}; } else { - return ::mp_units::cartesian_vector{ - lhs._coordinates_[0] % rhs._coordinates_[0], - lhs._coordinates_[1] % rhs._coordinates_[1], - lhs._coordinates_[2] % rhs._coordinates_[2]}; + return ::mp_units::cartesian_vector{ + static_cast(lhs._coordinates_[0] % rhs._coordinates_[0]), + static_cast(lhs._coordinates_[1] % rhs._coordinates_[1]), + static_cast(lhs._coordinates_[2] % rhs._coordinates_[2])}; } } - // scalar multiply (vector * scalar) + // (vector * scalar) template requires requires(const T& t, const S& s) { t * s; } [[nodiscard]] friend constexpr auto operator*(const cartesian_vector& vector, const S& scalar) { - return ::mp_units::cartesian_vector{vector._coordinates_[0] * scalar, - vector._coordinates_[1] * scalar, - vector._coordinates_[2] * scalar}; + return ::mp_units::cartesian_vector{ + vector._coordinates_[0] * scalar, + vector._coordinates_[1] * scalar, + vector._coordinates_[2] * scalar}; } - // scalar multiply (scalar * vector) - template - requires requires(const S& s, const T& t) { s * t; } - [[nodiscard]] friend constexpr auto operator*(const S& scalar, const cartesian_vector& vector) + // (scalar * vector) + template + requires requires(const S& s, const U& u) { s * u; } + [[nodiscard]] friend constexpr auto operator*(const S& s, const cartesian_vector& v) { - return vector * scalar; + return v * s; } - // scalar divide (vector / scalar) + // (vector / scalar) template requires requires(const T& t, const S& s) { t / s; } - [[nodiscard]] friend constexpr auto operator/(const cartesian_vector& vector, const S& scalar) + [[nodiscard]] friend constexpr auto operator/(const cartesian_vector& v, const S& s) { - return ::mp_units::cartesian_vector{vector._coordinates_[0] / scalar, - vector._coordinates_[1] / scalar, - vector._coordinates_[2] / scalar}; + return ::mp_units::cartesian_vector{ + v._coordinates_[0] / s, + v._coordinates_[1] / s, + v._coordinates_[2] / s}; } // equality @@ -144,7 +121,7 @@ struct cartesian_vector_iface { // -> how it behaves (math) lhs._coordinates_[2] == rhs._coordinates_[2]; } - // Dot product (scalar product of vectors) + // dot (numeric) template requires requires(const T& t, const U& u, decltype(t * u) v) { t * u; v + v; } [[nodiscard]] friend constexpr auto scalar_product(const cartesian_vector& lhs, @@ -155,7 +132,7 @@ struct cartesian_vector_iface { // -> how it behaves (math) lhs._coordinates_[2] * rhs._coordinates_[2]; } - // Cross product (vector product) — 3D only + // cross (numeric) — 3D template requires requires(const T& t, const U& u, decltype(t * u) v) { t * u; v - v; } [[nodiscard]] friend constexpr auto vector_product(const cartesian_vector& lhs, @@ -170,11 +147,11 @@ struct cartesian_vector_iface { // -> how it behaves (math) } // namespace detail -MP_UNITS_EXPORT template -class cartesian_vector : public detail::cartesian_vector_iface { // -> what it is (the data) +MP_UNITS_EXPORT template +class cartesian_vector : public detail::cartesian_vector_iface { public: + using value_type = T; T _coordinates_[3]{}; - using value_type = T; //number type inside the vector cartesian_vector() = default; cartesian_vector(const cartesian_vector&) = default; @@ -186,27 +163,26 @@ class cartesian_vector : public detail::cartesian_vector_iface { // -> what it i requires (sizeof...(Args) == 3) && (... && std::constructible_from) constexpr explicit(!(... && std::convertible_to)) cartesian_vector(Args&&... args) - : _coordinates_{static_cast(std::forward(args))...} {} + : _coordinates_{ static_cast(std::forward(args))... } {} template requires std::constructible_from constexpr explicit(!std::convertible_to) cartesian_vector(const cartesian_vector& other) - : _coordinates_{static_cast(other[0]), - static_cast(other[1]), - static_cast(other[2])} {} + : _coordinates_{ static_cast(other[0]), + static_cast(other[1]), + static_cast(other[2]) } {} template requires std::constructible_from constexpr explicit(!std::convertible_to) cartesian_vector(cartesian_vector&& other) - : _coordinates_{static_cast(std::move(other[0])), - static_cast(std::move(other[1])), - static_cast(std::move(other[2]))} {} + : _coordinates_{ static_cast(std::move(other[0])), + static_cast(std::move(other[1])), + static_cast(std::move(other[2])) } {} template U> - constexpr cartesian_vector& operator=(const cartesian_vector& other) - { + constexpr cartesian_vector& operator=(const cartesian_vector& other) { _coordinates_[0] = other[0]; _coordinates_[1] = other[1]; _coordinates_[2] = other[2]; @@ -214,14 +190,14 @@ class cartesian_vector : public detail::cartesian_vector_iface { // -> what it i } template U> - constexpr cartesian_vector& operator=(cartesian_vector&& other) - { + constexpr cartesian_vector& operator=(cartesian_vector&& other) { _coordinates_[0] = std::move(other[0]); _coordinates_[1] = std::move(other[1]); _coordinates_[2] = std::move(other[2]); return *this; } + // magnitude / unit (floating/complex-like) [[nodiscard]] constexpr T magnitude() const requires treat_as_floating_point { @@ -235,22 +211,18 @@ class cartesian_vector : public detail::cartesian_vector_iface { // -> what it i [[nodiscard]] constexpr cartesian_vector unit() const requires treat_as_floating_point - { - return *this / magnitude(); - } + { return *this / magnitude(); } [[nodiscard]] constexpr T& operator[](std::size_t i) { return _coordinates_[i]; } [[nodiscard]] constexpr const T& operator[](std::size_t i) const { return _coordinates_[i]; } [[nodiscard]] constexpr cartesian_vector operator+() const { return *this; } - [[nodiscard]] constexpr cartesian_vector operator-() const - { - return {-_coordinates_[0], -_coordinates_[1], -_coordinates_[2]}; + [[nodiscard]] constexpr cartesian_vector operator-() const { + return cartesian_vector{-_coordinates_[0], -_coordinates_[1], -_coordinates_[2]}; } template requires requires(T& t, const U& u) { t += u; } - constexpr cartesian_vector& operator+=(const cartesian_vector& other) - { + constexpr cartesian_vector& operator+=(const cartesian_vector& other) { _coordinates_[0] += other[0]; _coordinates_[1] += other[1]; _coordinates_[2] += other[2]; @@ -258,27 +230,27 @@ class cartesian_vector : public detail::cartesian_vector_iface { // -> what it i } template requires requires(T& t, const U& u) { t -= u; } - constexpr cartesian_vector& operator-=(const cartesian_vector& v) - { + constexpr cartesian_vector& operator-=(const cartesian_vector& other) { _coordinates_[0] -= other[0]; _coordinates_[1] -= other[1]; _coordinates_[2] -= other[2]; return *this; } - template requires requires(T& t, const U& u) { t *= u; } - constexpr cartesian_vector& operator*=(const U& value) + template requires requires(T& t, const S& s) { t *= s; } + constexpr cartesian_vector& operator*=(const S& scalar) { - _coordinates_[0] *= value; - _coordinates_[1] *= value; - _coordinates_[2] *= value; + _coordinates_[0] *= scalar; + _coordinates_[1] *= scalar; + _coordinates_[2] *= scalar; return *this; } - template requires requires(T& t, const U& u) { t /= u; } - constexpr cartesian_vector& operator/=(const S& s) - { - _coordinates_[0] /= s; _coordinates_[1] /= s; _coordinates_[2] /= s; + template requires requires(T& t, const S& s) { t /= s; } + constexpr cartesian_vector& operator/=(const S& scalar) { + _coordinates_[0] /= scalar; + _coordinates_[1] /= scalar; + _coordinates_[2] /= scalar; return *this; } @@ -298,12 +270,10 @@ class cartesian_vector : public detail::cartesian_vector_iface { // -> what it i #endif }; -// CTAD guide: deduce value_type from ctor args template requires (sizeof...(Args) == 2) && requires { typename std::common_type_t; } cartesian_vector(Arg, Args...) -> cartesian_vector>; -// Friendly aliases for numeric-level ops template [[nodiscard]] constexpr auto dot(const cartesian_vector& lhs, const cartesian_vector& rhs) { return scalar_product(lhs, rhs); } @@ -317,292 +287,9 @@ template [[nodiscard]] constexpr auto norm(const cartesian_vector& vec) { return magnitude(vec); } -// Register as a vector representation type template inline constexpr bool is_vector> = true; -// ---------------------------- cartesian_tensor ------------------------------ - -namespace detail { -template struct type_identity { using type = T; }; -template using type_identity_t = typename type_identity::type; -} // namespace detail - -MP_UNITS_EXPORT template -class cartesian_tensor { - static_assert(R >= 1 && R <= 3 && C >= 1 && C <= 3, "cartesian_tensor supports sizes up to 3x3"); -public: - using value_type = T; - static constexpr std::size_t rows_v = R; - static constexpr std::size_t cols_v = C; - - T _data_[R * C]{}; - - cartesian_tensor() = default; - cartesian_tensor(const cartesian_tensor&) = default; - cartesian_tensor(cartesian_tensor&&) = default; - cartesian_tensor& operator=(const cartesian_tensor&) = default; - cartesian_tensor& operator=(cartesian_tensor&&) = default; - - template - requires (sizeof...(Args) == R * C) && (... && std::constructible_from) - constexpr explicit(!(... && std::convertible_to)) - cartesian_tensor(Args&&... args) : _data_{static_cast(std::forward(args))...} {} - - [[nodiscard]] constexpr T& operator()(std::size_t r, std::size_t c) { return _data_[r * C + c]; } - [[nodiscard]] constexpr const T& operator()(std::size_t r, std::size_t c) const { return _data_[r * C + c]; } - - template - requires requires(const T& t, const U& u) { t + u; } - [[nodiscard]] friend constexpr auto operator+(const cartesian_tensor& lhs, const cartesian_tensor& rhs) - { - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(lhs._data_[i]) + static_cast(rhs._data_[i]); - return Rm; - } - - template - requires requires(const T& t, const U& u) { t - u; } - [[nodiscard]] friend constexpr auto operator-(const cartesian_tensor& lhs, const cartesian_tensor& rhs) - { - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(lhs._data_[i]) - static_cast(rhs._data_[i]); - return Rm; - } - - template - requires (requires(const T& t, const U& u) { t % u; } || (std::floating_point && std::floating_point)) - [[nodiscard]] friend constexpr auto operator%(const cartesian_tensor& lhs, const cartesian_tensor& rhs) - { - using CT = std::conditional_t<(std::floating_point || std::floating_point), long double, std::common_type_t>; - cartesian_tensor Rm{}; - if constexpr (std::floating_point || std::floating_point) { - using std::fmod; - for (std::size_t i = 0; i < R * C; ++i) - Rm._data_[i] = static_cast(fmod(static_cast(lhs._data_[i]), - static_cast(rhs._data_[i]))); - } else { - for (std::size_t i = 0; i < R * C; ++i) - Rm._data_[i] = static_cast(lhs._data_[i] % rhs._data_[i]); - } - return Rm; - } - - template - requires requires(const T& t, const S& s) { t * s; } - [[nodiscard]] friend constexpr auto operator*(const cartesian_tensor& tensor, const S& scalar) - { - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(tensor._data_[i]) * static_cast(scalar); - return Rm; - } - - template - requires requires(const S& s, const T& t) { s * t; } - [[nodiscard]] friend constexpr auto operator*(const S& scalar, const cartesian_tensor& tensor) { return tensor * scalar; } - - template - requires requires(const T& t, const S& s) { t / s; } - [[nodiscard]] friend constexpr auto operator/(const cartesian_tensor& tensor, const S& scalar) - { - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(tensor._data_[i]) / static_cast(scalar); - return Rm; - } - -#if MP_UNITS_HOSTED - friend std::ostream& operator<<(std::ostream& os, const cartesian_tensor& tensor) - { - for (std::size_t r = 0; r < R; ++r) { - os << (r == 0 ? "[[" : " ["); - for (std::size_t c = 0; c < C; ++c) { - os << tensor(r, c); - if (c + 1 != C) os << ", "; - } - os << (r + 1 == R ? "]]" : "]\n"); - } - return os; - } -#endif -}; - -// Register as a tensor representation type -template -inline constexpr bool is_tensor> = true; - -// ---------------------- Numeric-level helpers for tensors ------------------- - -template -[[nodiscard]] constexpr auto matmul(const cartesian_tensor& tensor, - const cartesian_tensor& other) -{ - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t r = 0; r < R; ++r) - for (std::size_t c = 0; c < C; ++c) { - CT acc{}; - for (std::size_t k = 0; k < K; ++k) acc += static_cast(tensor(r, k)) * static_cast(other(k, c)); - Rm(r, c) = acc; - } - return Rm; -} - -template -[[nodiscard]] constexpr auto matvec(const cartesian_tensor& tensor, - const cartesian_vector& vector) -{ - using CT = std::common_type_t; - cartesian_vector Rv{}; - for (std::size_t r = 0; r < 3; ++r) { - CT acc{}; - for (std::size_t c = 0; c < 3; ++c) acc += static_cast(tensor(r, c)) * static_cast(vector._coordinates_[c]); - Rv[r] = acc; - } - return Rv; -} - -template -[[nodiscard]] constexpr auto double_contraction(const cartesian_tensor& tensor, - const cartesian_tensor& other) -{ - using CT = std::common_type_t; - CT acc{}; - for (std::size_t i = 0; i < R * C; ++i) acc += static_cast(tensor._data_[i]) * static_cast(other._data_[i]); - return acc; -} - -template -[[nodiscard]] constexpr auto outer_numeric(const cartesian_vector& vector, - const cartesian_vector& other) -{ - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < 3; ++i) - for (std::size_t j = 0; j < 3; ++j) - Rm(i, j) = static_cast(vector._coordinates_[i]) * static_cast(other._coordinates_[j]); - return Rm; -} - -// ---------------------- Quantity-level LA operations ------------------------ - -namespace detail { - template - [[nodiscard]] consteval auto ref_of() - { - return std::remove_cvref_t::reference; - } -} // namespace detail - -// ---- Vectors ---- - -// dot: a ⋅ b -> scalar quantity (units multiply) -template - requires (Quantity && Quantity && - is_vector::rep> && - is_vector::rep>) -[[nodiscard]] constexpr auto dot(const QA& a, const QB& b) -{ - using A = std::remove_cvref_t; - using B = std::remove_cvref_t; - constexpr auto RA = detail::ref_of(); - constexpr auto RB = detail::ref_of(); - const auto av = a.numerical_value_in(RA); - const auto bv = b.numerical_value_in(RB); - return quantity{ scalar_product(av, bv), RA * RB }; -} - -// cross: a × b -> vector quantity (units multiply) -template - requires (Quantity && is_vector::rep>) -[[nodiscard]] constexpr auto cross(const QV& a, const QV& b) -{ - using VQ = std::remove_cvref_t; - constexpr auto R = detail::ref_of(); - const auto av = a.numerical_value_in(R); - const auto bv = b.numerical_value_in(R); - return quantity{ vector_product(av, bv), R * R }; -} - -// |a|: magnitude -> scalar quantity (sqrt(a⋅a)) -template - requires (Quantity && is_vector::rep>) -[[nodiscard]] constexpr auto magnitude(const QV& a) -{ - auto aa = dot(a, a); // scalar quantity with R*R - using mp_units::sqrt; - return sqrt(aa); // scalar quantity with R -} - -// ---- Tensor / Vector & Tensor / Tensor ---- - -// tensor product: a ⊗ b (vector ⊗ vector -> tensor) -template - requires (Quantity && Quantity && - is_vector::rep> && - is_vector::rep>) -[[nodiscard]] constexpr auto outer(const QA& a, const QB& b) -{ - using A = std::remove_cvref_t; - using B = std::remove_cvref_t; - constexpr auto RA = detail::ref_of(); - constexpr auto RB = detail::ref_of(); - const auto av = a.numerical_value_in(RA); - const auto bv = b.numerical_value_in(RB); - return quantity{ outer_numeric(av, bv), RA * RB }; -} - -// inner: A ⋅ x (tensor ⋅ vector) -> vector -template - requires (Quantity && Quantity && - is_tensor::rep> && - is_vector::rep>) -[[nodiscard]] constexpr auto inner(const QT& A, const QV& x) -{ - using TQ = std::remove_cvref_t; - using VQ = std::remove_cvref_t; - constexpr auto RT = detail::ref_of(); - constexpr auto RX = detail::ref_of(); - const auto Av = A.numerical_value_in(RT); - const auto xv = x.numerical_value_in(RX); - return quantity{ matvec(Av, xv), RT * RX }; -} - -// inner: A ⋅ B (tensor ⋅ tensor) -> tensor -template - requires (Quantity && Quantity && - is_tensor::rep> && - is_tensor::rep>) -[[nodiscard]] constexpr auto inner(const QA& A, const QB& B) -{ - using TA = std::remove_cvref_t; - using TB = std::remove_cvref_t; - constexpr auto RA = detail::ref_of(); - constexpr auto RB = detail::ref_of(); - const auto Av = A.numerical_value_in(RA); - const auto Bv = B.numerical_value_in(RB); - return quantity{ matmul(Av, Bv), RA * RB }; -} - -// scalar product: A : B (double contraction) -> scalar -template - requires (Quantity && Quantity && - is_tensor::rep> && - is_tensor::rep>) -[[nodiscard]] constexpr auto scalar_product(const QA& A, const QB& B) -{ - using TA = std::remove_cvref_t; - using TB = std::remove_cvref_t; - constexpr auto RA = detail::ref_of(); - constexpr auto RB = detail::ref_of(); - const auto Av = A.numerical_value_in(RA); - const auto Bv = B.numerical_value_in(RB); - return quantity{ double_contraction(Av, Bv), RA * RB }; -} - } // namespace mp_units #if MP_UNITS_HOSTED @@ -611,25 +298,7 @@ struct MP_UNITS_STD_FMT::formatter, Char> : formatter, Char> { template auto format(const mp_units::cartesian_vector& v, Ctx& ctx) const { - return format_to(ctx.out(), "[{}, {}, {}]", v._coordinates_[0], v._coordinates_[1], v._coordinates_[2]); - } -}; - -template -struct MP_UNITS_STD_FMT::formatter, Char> - : formatter, Char> { - template - auto format(const mp_units::cartesian_tensor& A, Ctx& ctx) const { - auto out = ctx.out(); - for (std::size_t r = 0; r < R; ++r) { - out = format_to(out, r == 0 ? "[[" : " ["); - for (std::size_t c = 0; c < C; ++c) { - out = format_to(out, "{}", A(r, c)); - if (c + 1 != C) out = format_to(out, ", "); - } - out = format_to(out, r + 1 == R ? "]]" : "]\n"); - } - return out; + return format_to(ctx.out(), "[{}, {}, {}]", v[0], v[1], v[2]); } }; #endif From 73d8554d124f18036fe1b06bcfc67972bd34d715 Mon Sep 17 00:00:00 2001 From: Julia Smerdel <97158770+Jullija@users.noreply.github.com> Date: Fri, 24 Oct 2025 19:53:46 +0000 Subject: [PATCH 04/18] add tests --- CMakeLists.txt | 5 +- src/core/include/mp-units/cartesian_tensor.h | 84 +-- src/core/include/mp-units/cartesian_vector.h | 205 +++---- test/runtime/CMakeLists.txt | 1 + test/runtime/cartesian_tensor_test.cpp | 140 +++++ test/runtime/cartesian_vector_test.cpp | 555 ++++++------------- 6 files changed, 472 insertions(+), 518 deletions(-) create mode 100644 test/runtime/cartesian_tensor_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 81ffd47739..c4052ce4c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,7 +81,4 @@ endif() # add unit tests enable_testing() -add_subdirectory(test) -find_package(mp-units REQUIRED) -target_link_libraries(my_units_app PRIVATE mp-units::mp-units) - +add_subdirectory(test) \ No newline at end of file diff --git a/src/core/include/mp-units/cartesian_tensor.h b/src/core/include/mp-units/cartesian_tensor.h index c25fa3a191..0886db74dc 100644 --- a/src/core/include/mp-units/cartesian_tensor.h +++ b/src/core/include/mp-units/cartesian_tensor.h @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT #pragma once -#include #include +#include +#include // for matvec/outer_numeric #include #include -#include // for matvec/outer_numeric #if MP_UNITS_HOSTED #include @@ -15,10 +15,10 @@ # ifdef MP_UNITS_IMPORT_STD import std; # else +# include +# include # include # include -# include -# include # if MP_UNITS_HOSTED # include # endif @@ -27,7 +27,7 @@ namespace mp_units { -// Forward decl (exported) +// Forward declaration (exported) MP_UNITS_EXPORT template class cartesian_tensor; @@ -35,8 +35,8 @@ class cartesian_tensor; MP_UNITS_EXPORT template class cartesian_tensor { - static_assert(R >= 1 && R <= 3 && C >= 1 && C <= 3, - "cartesian_tensor supports sizes up to 3x3"); + static_assert(R >= 1 && R <= 3 && C >= 1 && C <= 3, "cartesian_tensor supports sizes up to 3x3"); + public: using value_type = T; static constexpr std::size_t rows_v = R; @@ -61,7 +61,7 @@ class cartesian_tensor { [[nodiscard]] constexpr T& operator()(std::size_t r, std::size_t c) { return _data_[r * C + c]; } [[nodiscard]] constexpr const T& operator()(std::size_t r, std::size_t c) const { return _data_[r * C + c]; } - // elementwise +, -, % + // elementwise +, - template requires requires(const T& t, const U& u) { t + u; } [[nodiscard]] friend constexpr auto operator+(const cartesian_tensor& A, const cartesian_tensor& B) @@ -84,9 +84,10 @@ class cartesian_tensor { return Rm; } + // elementwise % (integral uses %, floating uses fmod) template - requires (requires(const T& t, const U& u) { t % u; } || - (std::floating_point && std::floating_point)) + requires (requires(const T& t, const U& u) { t % u; }) || + (std::floating_point && std::floating_point) [[nodiscard]] friend constexpr auto operator%(const cartesian_tensor& A, const cartesian_tensor& B) { using CT = std::common_type_t; @@ -94,8 +95,9 @@ class cartesian_tensor { if constexpr (std::floating_point || std::floating_point) { using std::fmod; for (std::size_t i = 0; i < R * C; ++i) - Rm._data_[i] = static_cast(fmod(static_cast(A._data_[i]), - static_cast(B._data_[i]))); + Rm._data_[i] = + static_cast(fmod(static_cast(A._data_[i]), + static_cast(B._data_[i]))); } else { for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(A._data_[i] % B._data_[i]); @@ -103,9 +105,8 @@ class cartesian_tensor { return Rm; } - // scalar *, / - template - requires requires(const T& t, const S& s) { t * s; } + // scalar *, / (constrained to numeric scalars to avoid recursive constraints) + template [[nodiscard]] friend constexpr auto operator*(const cartesian_tensor& tensor, const S& scalar) { using CT = std::common_type_t; @@ -115,12 +116,13 @@ class cartesian_tensor { return Rm; } - template - requires requires(const S& s, const T& t) { s * t; } - [[nodiscard]] friend constexpr auto operator*(const S& scalar, const cartesian_tensor& tensor) { return tensor * scalar; } + template + [[nodiscard]] friend constexpr auto operator*(const S& scalar, const cartesian_tensor& tensor) + { + return tensor * scalar; + } - template - requires requires(const T& t, const S& s) { t / s; } + template [[nodiscard]] friend constexpr auto operator/(const cartesian_tensor& tensor, const S& scalar) { using CT = std::common_type_t; @@ -154,15 +156,16 @@ inline constexpr bool is_tensor> = true; // Matrix × Matrix template -[[nodiscard]] constexpr auto matmul(const cartesian_tensor& tensor, - const cartesian_tensor& other) +[[nodiscard]] constexpr auto matmul(const cartesian_tensor& A, + const cartesian_tensor& B) { using CT = std::common_type_t; cartesian_tensor Rm{}; for (std::size_t r = 0; r < R; ++r) for (std::size_t c = 0; c < C; ++c) { CT acc{}; - for (std::size_t k = 0; k < K; ++k) acc += static_cast(tensor(r, k)) * static_cast(other(k, c)); + for (std::size_t k = 0; k < K; ++k) + acc += static_cast(A(r, k)) * static_cast(B(k, c)); Rm(r, c) = acc; } return Rm; @@ -170,48 +173,49 @@ template // Matrix × Vector (3×3) template -[[nodiscard]] constexpr auto matvec(const cartesian_tensor& tensor, - const cartesian_vector& vector) +[[nodiscard]] constexpr auto matvec(const cartesian_tensor& M, + const cartesian_vector& x) { using CT = std::common_type_t; - cartesian_vector Rv{}; + cartesian_vector y{}; for (std::size_t r = 0; r < 3; ++r) { CT acc{}; - for (std::size_t c = 0; c < 3; ++c) acc += static_cast(tensor(r, c)) * static_cast(vector[c]); - Rv[r] = acc; + for (std::size_t c = 0; c < 3; ++c) + acc += static_cast(M(r, c)) * static_cast(x[c]); + y[r] = acc; } - return Rv; + return y; } // Double contraction: A : B template -[[nodiscard]] constexpr auto double_contraction(const cartesian_tensor& tensor, - const cartesian_tensor& other) +[[nodiscard]] constexpr auto double_contraction(const cartesian_tensor& A, + const cartesian_tensor& B) { using CT = std::common_type_t; CT acc{}; for (std::size_t i = 0; i < R * C; ++i) - acc += static_cast(tensor._data_[i]) * static_cast(other._data_[i]); - return acc; // numeric scalar + acc += static_cast(A._data_[i]) * static_cast(B._data_[i]); + return acc; // numeric scalar } // Outer product: vector ⊗ vector -> 3x3 matrix template -[[nodiscard]] constexpr auto outer_numeric(const cartesian_vector& vector, - const cartesian_vector& other) +[[nodiscard]] constexpr auto outer_numeric(const cartesian_vector& a, + const cartesian_vector& b) { using CT = std::common_type_t; cartesian_tensor Rm{}; for (std::size_t i = 0; i < 3; ++i) for (std::size_t j = 0; j < 3; ++j) - Rm(i, j) = static_cast(vector[i]) * static_cast(other[j]); + Rm(i, j) = static_cast(a[i]) * static_cast(b[j]); return Rm; } } // namespace mp_units #if MP_UNITS_HOSTED -// fmt/format support +// fmt/format (or std::format) support template struct MP_UNITS_STD_FMT::formatter, Char> : formatter, Char> { @@ -219,12 +223,12 @@ struct MP_UNITS_STD_FMT::formatter, Char> auto format(const mp_units::cartesian_tensor& A, Ctx& ctx) const { auto out = ctx.out(); for (std::size_t r = 0; r < R; ++r) { - out = format_to(out, r == 0 ? "[[" : " ["); + out = format_to(out, "{}", (r == 0 ? "[[" : " [")); for (std::size_t c = 0; c < C; ++c) { out = format_to(out, "{}", A(r, c)); - if (c + 1 != C) out = format_to(out, ", "); + if (c + 1 != C) out = format_to(out, "{}", ", "); } - out = format_to(out, r + 1 == R ? "]]" : "]\n"); + out = format_to(out, "{}", (r + 1 == R ? "]]" : "]\n")); } return out; } diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index b641d92e9d..ad92356d86 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -1,27 +1,27 @@ // SPDX-License-Identifier: MIT #pragma once -#include #include +#include #include #include #if MP_UNITS_HOSTED - #include +#include #endif #ifndef MP_UNITS_IN_MODULE_INTERFACE -# ifdef MP_UNITS_IMPORT_STD - import std; -# else -# include -# include -# include -# include -# if MP_UNITS_HOSTED -# include -# endif -# endif +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#include +#include +#include +#if MP_UNITS_HOSTED +#include +#endif +#endif #endif namespace mp_units { @@ -35,49 +35,41 @@ struct cartesian_vector_iface { // A + B template requires requires(const T& t, const U& u) { t + u; } - [[nodiscard]] friend constexpr auto operator+(const cartesian_vector& lhs, - const cartesian_vector& rhs) + [[nodiscard]] friend constexpr auto operator+(const cartesian_vector& lhs, const cartesian_vector& rhs) { - return ::mp_units::cartesian_vector{ - lhs._coordinates_[0] + rhs._coordinates_[0], - lhs._coordinates_[1] + rhs._coordinates_[1], - lhs._coordinates_[2] + rhs._coordinates_[2]}; + return ::mp_units::cartesian_vector{lhs._coordinates_[0] + rhs._coordinates_[0], + lhs._coordinates_[1] + rhs._coordinates_[1], + lhs._coordinates_[2] + rhs._coordinates_[2]}; } // A - B template requires requires(const T& t, const U& u) { t - u; } - [[nodiscard]] friend constexpr auto operator-(const cartesian_vector& lhs, - const cartesian_vector& rhs) + [[nodiscard]] friend constexpr auto operator-(const cartesian_vector& lhs, const cartesian_vector& rhs) { - return ::mp_units::cartesian_vector{ - lhs._coordinates_[0] - rhs._coordinates_[0], - lhs._coordinates_[1] - rhs._coordinates_[1], - lhs._coordinates_[2] - rhs._coordinates_[2]}; + return ::mp_units::cartesian_vector{lhs._coordinates_[0] - rhs._coordinates_[0], + lhs._coordinates_[1] - rhs._coordinates_[1], + lhs._coordinates_[2] - rhs._coordinates_[2]}; } // A % B (integral: %, floating: fmod into CT) template - requires (requires(const T& t, const U& u) { t % u; }) || - (std::floating_point && std::floating_point) - [[nodiscard]] friend constexpr auto operator%(const cartesian_vector& lhs, - const cartesian_vector& rhs) + requires(requires(const T& t, const U& u) { t % u; }) || (std::floating_point && std::floating_point) + [[nodiscard]] friend constexpr auto operator%(const cartesian_vector& lhs, const cartesian_vector& rhs) { using CT = std::common_type_t; if constexpr (std::floating_point && std::floating_point) { using std::fmod; - return ::mp_units::cartesian_vector{ - static_cast(fmod(static_cast(lhs._coordinates_[0]), - static_cast(rhs._coordinates_[0]))), - static_cast(fmod(static_cast(lhs._coordinates_[1]), - static_cast(rhs._coordinates_[1]))), - static_cast(fmod(static_cast(lhs._coordinates_[2]), - static_cast(rhs._coordinates_[2])))}; + return ::mp_units::cartesian_vector{static_cast(fmod(static_cast(lhs._coordinates_[0]), + static_cast(rhs._coordinates_[0]))), + static_cast(fmod(static_cast(lhs._coordinates_[1]), + static_cast(rhs._coordinates_[1]))), + static_cast(fmod(static_cast(lhs._coordinates_[2]), + static_cast(rhs._coordinates_[2])))}; } else { - return ::mp_units::cartesian_vector{ - static_cast(lhs._coordinates_[0] % rhs._coordinates_[0]), - static_cast(lhs._coordinates_[1] % rhs._coordinates_[1]), - static_cast(lhs._coordinates_[2] % rhs._coordinates_[2])}; + return ::mp_units::cartesian_vector{static_cast(lhs._coordinates_[0] % rhs._coordinates_[0]), + static_cast(lhs._coordinates_[1] % rhs._coordinates_[1]), + static_cast(lhs._coordinates_[2] % rhs._coordinates_[2])}; } } @@ -86,10 +78,8 @@ struct cartesian_vector_iface { requires requires(const T& t, const S& s) { t * s; } [[nodiscard]] friend constexpr auto operator*(const cartesian_vector& vector, const S& scalar) { - return ::mp_units::cartesian_vector{ - vector._coordinates_[0] * scalar, - vector._coordinates_[1] * scalar, - vector._coordinates_[2] * scalar}; + return ::mp_units::cartesian_vector{vector._coordinates_[0] * scalar, vector._coordinates_[1] * scalar, + vector._coordinates_[2] * scalar}; } // (scalar * vector) @@ -105,38 +95,36 @@ struct cartesian_vector_iface { requires requires(const T& t, const S& s) { t / s; } [[nodiscard]] friend constexpr auto operator/(const cartesian_vector& v, const S& s) { - return ::mp_units::cartesian_vector{ - v._coordinates_[0] / s, - v._coordinates_[1] / s, - v._coordinates_[2] / s}; + return ::mp_units::cartesian_vector{v._coordinates_[0] / s, v._coordinates_[1] / s, v._coordinates_[2] / s}; } // equality template U> - [[nodiscard]] friend constexpr bool operator==(const cartesian_vector& lhs, - const cartesian_vector& rhs) + [[nodiscard]] friend constexpr bool operator==(const cartesian_vector& lhs, const cartesian_vector& rhs) { - return lhs._coordinates_[0] == rhs._coordinates_[0] && - lhs._coordinates_[1] == rhs._coordinates_[1] && + return lhs._coordinates_[0] == rhs._coordinates_[0] && lhs._coordinates_[1] == rhs._coordinates_[1] && lhs._coordinates_[2] == rhs._coordinates_[2]; } // dot (numeric) template - requires requires(const T& t, const U& u, decltype(t * u) v) { t * u; v + v; } - [[nodiscard]] friend constexpr auto scalar_product(const cartesian_vector& lhs, - const cartesian_vector& rhs) + requires requires(const T& t, const U& u, decltype(t * u) v) { + t * u; + v + v; + } + [[nodiscard]] friend constexpr auto scalar_product(const cartesian_vector& lhs, const cartesian_vector& rhs) { - return lhs._coordinates_[0] * rhs._coordinates_[0] + - lhs._coordinates_[1] * rhs._coordinates_[1] + + return lhs._coordinates_[0] * rhs._coordinates_[0] + lhs._coordinates_[1] * rhs._coordinates_[1] + lhs._coordinates_[2] * rhs._coordinates_[2]; } // cross (numeric) — 3D template - requires requires(const T& t, const U& u, decltype(t * u) v) { t * u; v - v; } - [[nodiscard]] friend constexpr auto vector_product(const cartesian_vector& lhs, - const cartesian_vector& rhs) + requires requires(const T& t, const U& u, decltype(t * u) v) { + t * u; + v - v; + } + [[nodiscard]] friend constexpr auto vector_product(const cartesian_vector& lhs, const cartesian_vector& rhs) { return ::mp_units::cartesian_vector{ lhs._coordinates_[1] * rhs._coordinates_[2] - lhs._coordinates_[2] * rhs._coordinates_[1], @@ -145,7 +133,7 @@ struct cartesian_vector_iface { } }; -} // namespace detail +} // namespace detail MP_UNITS_EXPORT template class cartesian_vector : public detail::cartesian_vector_iface { @@ -160,29 +148,30 @@ class cartesian_vector : public detail::cartesian_vector_iface { cartesian_vector& operator=(cartesian_vector&&) = default; template - requires (sizeof...(Args) == 3) && (... && std::constructible_from) - constexpr explicit(!(... && std::convertible_to)) - cartesian_vector(Args&&... args) - : _coordinates_{ static_cast(std::forward(args))... } {} + requires(sizeof...(Args) <= 3) && (... && std::constructible_from) + constexpr explicit(!(... && std::convertible_to)) cartesian_vector(Args&&... args) : + _coordinates_{static_cast(std::forward(args))...} + { + } template requires std::constructible_from - constexpr explicit(!std::convertible_to) - cartesian_vector(const cartesian_vector& other) - : _coordinates_{ static_cast(other[0]), - static_cast(other[1]), - static_cast(other[2]) } {} + constexpr explicit(!std::convertible_to) cartesian_vector(const cartesian_vector& other) : + _coordinates_{static_cast(other[0]), static_cast(other[1]), static_cast(other[2])} + { + } template requires std::constructible_from - constexpr explicit(!std::convertible_to) - cartesian_vector(cartesian_vector&& other) - : _coordinates_{ static_cast(std::move(other[0])), - static_cast(std::move(other[1])), - static_cast(std::move(other[2])) } {} + constexpr explicit(!std::convertible_to) cartesian_vector(cartesian_vector&& other) : + _coordinates_{static_cast(std::move(other[0])), static_cast(std::move(other[1])), + static_cast(std::move(other[2]))} + { + } template U> - constexpr cartesian_vector& operator=(const cartesian_vector& other) { + constexpr cartesian_vector& operator=(const cartesian_vector& other) + { _coordinates_[0] = other[0]; _coordinates_[1] = other[1]; _coordinates_[2] = other[2]; @@ -190,7 +179,8 @@ class cartesian_vector : public detail::cartesian_vector_iface { } template U> - constexpr cartesian_vector& operator=(cartesian_vector&& other) { + constexpr cartesian_vector& operator=(cartesian_vector&& other) + { _coordinates_[0] = std::move(other[0]); _coordinates_[1] = std::move(other[1]); _coordinates_[2] = std::move(other[2]); @@ -211,33 +201,41 @@ class cartesian_vector : public detail::cartesian_vector_iface { [[nodiscard]] constexpr cartesian_vector unit() const requires treat_as_floating_point - { return *this / magnitude(); } + { + return *this / magnitude(); + } - [[nodiscard]] constexpr T& operator[](std::size_t i) { return _coordinates_[i]; } + [[nodiscard]] constexpr T& operator[](std::size_t i) { return _coordinates_[i]; } [[nodiscard]] constexpr const T& operator[](std::size_t i) const { return _coordinates_[i]; } [[nodiscard]] constexpr cartesian_vector operator+() const { return *this; } - [[nodiscard]] constexpr cartesian_vector operator-() const { + [[nodiscard]] constexpr cartesian_vector operator-() const + { return cartesian_vector{-_coordinates_[0], -_coordinates_[1], -_coordinates_[2]}; } - template requires requires(T& t, const U& u) { t += u; } - constexpr cartesian_vector& operator+=(const cartesian_vector& other) { + template + requires requires(T& t, const U& u) { t += u; } + constexpr cartesian_vector& operator+=(const cartesian_vector& other) + { _coordinates_[0] += other[0]; _coordinates_[1] += other[1]; _coordinates_[2] += other[2]; return *this; } - template requires requires(T& t, const U& u) { t -= u; } - constexpr cartesian_vector& operator-=(const cartesian_vector& other) { + template + requires requires(T& t, const U& u) { t -= u; } + constexpr cartesian_vector& operator-=(const cartesian_vector& other) + { _coordinates_[0] -= other[0]; _coordinates_[1] -= other[1]; _coordinates_[2] -= other[2]; return *this; } - template requires requires(T& t, const S& s) { t *= s; } + template + requires requires(T& t, const S& s) { t *= s; } constexpr cartesian_vector& operator*=(const S& scalar) { _coordinates_[0] *= scalar; @@ -246,8 +244,10 @@ class cartesian_vector : public detail::cartesian_vector_iface { return *this; } - template requires requires(T& t, const S& s) { t /= s; } - constexpr cartesian_vector& operator/=(const S& scalar) { + template + requires requires(T& t, const S& s) { t /= s; } + constexpr cartesian_vector& operator/=(const S& scalar) + { _coordinates_[0] /= scalar; _coordinates_[1] /= scalar; _coordinates_[2] /= scalar; @@ -256,11 +256,15 @@ class cartesian_vector : public detail::cartesian_vector_iface { [[nodiscard]] friend constexpr T magnitude(const cartesian_vector& vec) requires treat_as_floating_point - { return vec.magnitude(); } + { + return vec.magnitude(); + } [[nodiscard]] friend constexpr cartesian_vector unit_vector(const cartesian_vector& vec) requires treat_as_floating_point - { return vec.unit(); } + { + return vec.unit(); + } #if MP_UNITS_HOSTED friend std::ostream& operator<<(std::ostream& os, const cartesian_vector& vec) @@ -271,33 +275,40 @@ class cartesian_vector : public detail::cartesian_vector_iface { }; template - requires (sizeof...(Args) == 2) && requires { typename std::common_type_t; } + requires(sizeof...(Args) == 2) && requires { typename std::common_type_t; } cartesian_vector(Arg, Args...) -> cartesian_vector>; template [[nodiscard]] constexpr auto dot(const cartesian_vector& lhs, const cartesian_vector& rhs) -{ return scalar_product(lhs, rhs); } +{ + return scalar_product(lhs, rhs); +} template [[nodiscard]] constexpr auto cross(const cartesian_vector& lhs, const cartesian_vector& rhs) -{ return vector_product(lhs, rhs); } +{ + return vector_product(lhs, rhs); +} template requires treat_as_floating_point [[nodiscard]] constexpr auto norm(const cartesian_vector& vec) -{ return magnitude(vec); } +{ + return magnitude(vec); +} template inline constexpr bool is_vector> = true; -} // namespace mp_units +} // namespace mp_units #if MP_UNITS_HOSTED template -struct MP_UNITS_STD_FMT::formatter, Char> - : formatter, Char> { +struct MP_UNITS_STD_FMT::formatter, Char> : + formatter, Char> { template - auto format(const mp_units::cartesian_vector& v, Ctx& ctx) const { + auto format(const mp_units::cartesian_vector& v, Ctx& ctx) const + { return format_to(ctx.out(), "[{}, {}, {}]", v[0], v[1], v[2]); } }; diff --git a/test/runtime/CMakeLists.txt b/test/runtime/CMakeLists.txt index a0a78f3ce9..d28471f8fc 100644 --- a/test/runtime/CMakeLists.txt +++ b/test/runtime/CMakeLists.txt @@ -26,6 +26,7 @@ add_executable( unit_tests_runtime atomic_test.cpp cartesian_vector_test.cpp + cartesian_tensor_test.cpp distribution_test.cpp fixed_string_test.cpp fmt_test.cpp diff --git a/test/runtime/cartesian_tensor_test.cpp b/test/runtime/cartesian_tensor_test.cpp new file mode 100644 index 0000000000..3d6621e5b8 --- /dev/null +++ b/test/runtime/cartesian_tensor_test.cpp @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT + +#include +#include + +#ifdef MP_UNITS_MODULES +import mp_units; +#else + #include + #include + #if MP_UNITS_HOSTED + #include + #include + #endif +#endif + +using namespace mp_units; +using Catch::Matchers::WithinAbs; + +TEST_CASE("cartesian_tensor construction and access", "[tensor]") { + // 2x3 fill-ctor order is row-major + cartesian_tensor A{1,2,3, 4,5,6}; + REQUIRE(A(0,0) == 1); + REQUIRE(A(0,1) == 2); + REQUIRE(A(0,2) == 3); + REQUIRE(A(1,0) == 4); + REQUIRE(A(1,1) == 5); + REQUIRE(A(1,2) == 6); +} + +TEST_CASE("elementwise + and - with common_type", "[tensor]") { + cartesian_tensor A{1,2, 3,4}; + cartesian_tensor B{0.5,1.5, 2.5,3.5}; + + auto S = A + B; + static_assert(std::is_same_v>); + REQUIRE(S(0,0) == 1.5); + REQUIRE(S(0,1) == 3.5); + REQUIRE(S(1,0) == 5.5); + REQUIRE(S(1,1) == 7.5); + + auto D = B - A; +REQUIRE_THAT(D(0,0), WithinAbs(-0.5, 1e-12)); +REQUIRE_THAT(D(0,1), WithinAbs(-0.5, 1e-12)); +REQUIRE_THAT(D(1,0), WithinAbs(-0.5, 1e-12)); +REQUIRE_THAT(D(1,1), WithinAbs(-0.5, 1e-12)); +} + +TEST_CASE("elementwise modulo", "[tensor]") { + SECTION("integral % integral") { + cartesian_tensor A{10,11,12, 13,14,15}; + cartesian_tensor B{ 4, 5, 7, 4, 5, 7}; + auto R = A % B; + REQUIRE(R(0,0) == 2); REQUIRE(R(0,1) == 1); REQUIRE(R(0,2) == 5); + REQUIRE(R(1,0) == 1); REQUIRE(R(1,1) == 4); REQUIRE(R(1,2) == 1); + } + + SECTION("floating uses fmod, result in common_type") { + cartesian_tensor A{5.5, 7.25, 9.0}; + cartesian_tensor B{2.0, 2.50, 4.0}; + auto R = A % B; + REQUIRE_THAT(R(0,0), WithinAbs(1.5, 1e-12)); + REQUIRE_THAT(R(0,1), WithinAbs(2.25, 1e-12)); + REQUIRE_THAT(R(0,2), WithinAbs(1.0, 1e-12)); + } +} + +TEST_CASE("scalar multiply/divide", "[tensor]") { + cartesian_tensor A{1,2,3,4}; + + auto T1 = A * 2.0; + REQUIRE(T1(0,0) == 2.0); REQUIRE(T1(0,1) == 4.0); + REQUIRE(T1(1,0) == 6.0); REQUIRE(T1(1,1) == 8.0); + + auto T2 = 2.0 * A; + REQUIRE(T2(0,0) == 2.0); REQUIRE(T2(0,1) == 4.0); + REQUIRE(T2(1,0) == 6.0); REQUIRE(T2(1,1) == 8.0); + + auto T3 = A / 2.0; + REQUIRE(T3(0,0) == 0.5); REQUIRE(T3(0,1) == 1.0); + REQUIRE(T3(1,0) == 1.5); REQUIRE(T3(1,1) == 2.0); +} + +TEST_CASE("matmul (R×K) * (K×C) -> (R×C)", "[tensor]") { + cartesian_tensor A{1,2,3, 4,5,6}; + cartesian_tensor B{7,8, 9,10, 11,12}; + auto C = matmul(A, B); // 2x2 + + static_assert(std::is_same_v>); + REQUIRE(C(0,0) == 58); REQUIRE(C(0,1) == 64); + REQUIRE(C(1,0) == 139); REQUIRE(C(1,1) == 154); +} + +TEST_CASE("matvec (3x3) * vector -> vector", "[tensor][vector]") { + cartesian_tensor M{ 1,2,3, + 0,1,4, + 5,6,0 }; + cartesian_vector x{1,2,3}; + auto y = matvec(M, x); + REQUIRE_THAT(y[0], WithinAbs(14.0, 1e-12)); // 1*1 + 2*2 + 3*3 + REQUIRE_THAT(y[1], WithinAbs(14.0, 1e-12)); // 0*1 + 1*2 + 4*3 + REQUIRE_THAT(y[2], WithinAbs(17.0, 1e-12)); // 5*1 + 6*2 + 0*3 +} + +TEST_CASE("double contraction A : B", "[tensor]") { + cartesian_tensor A{1,2, 3,4}; + cartesian_tensor B{5,6, 7,8}; + // 1*5 + 2*6 + 3*7 + 4*8 = 70 + auto s = double_contraction(A, B); + REQUIRE(s == 70); +} + +TEST_CASE("outer_numeric (vector ⊗ vector)", "[tensor][vector]") { + cartesian_vector a{1,2,3}; + cartesian_vector b{4,5,6}; + auto T = outer_numeric(a, b); // 3x3 + REQUIRE(T(0,0) == 4); REQUIRE(T(0,1) == 5); REQUIRE(T(0,2) == 6); + REQUIRE(T(1,0) == 8); REQUIRE(T(1,1) == 10); REQUIRE(T(1,2) == 12); + REQUIRE(T(2,0) == 12); REQUIRE(T(2,1) == 15); REQUIRE(T(2,2) == 18); +} + +#if MP_UNITS_HOSTED +TEST_CASE("text output (ostream + fmt)", "[tensor][fmt][ostream]") { + cartesian_tensor A{1,2,3,4}; + + std::ostringstream os; + os << A; + CHECK(os.str() == "[[1, 2]\n [3, 4]]"); + + CHECK(MP_UNITS_STD_FMT::format("{}", A) == os.str()); +} +#endif + +TEST_CASE("constexpr basics", "[tensor][constexpr]") { + constexpr cartesian_tensor A{1,2,3}; + constexpr cartesian_tensor B{4,5,6}; + constexpr auto C = A + B; + static_assert(C(0,0) == 5 && C(0,1) == 7 && C(0,2) == 9); + (void)C; +} diff --git a/test/runtime/cartesian_vector_test.cpp b/test/runtime/cartesian_vector_test.cpp index 6aac39f229..30bb30511c 100644 --- a/test/runtime/cartesian_vector_test.cpp +++ b/test/runtime/cartesian_vector_test.cpp @@ -1,121 +1,86 @@ -// 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. - -#include "almost_equals.h" +// SPDX-License-Identifier: MIT + #include #include -#include -#include -#ifdef MP_UNITS_IMPORT_STD -import std; -#else -#include -#endif + #ifdef MP_UNITS_MODULES import mp_units; #else -#include + #include + #if MP_UNITS_HOSTED + #include + #include + #endif #endif using namespace mp_units; -using namespace Catch::Matchers; - -TEST_CASE("cartesian_vector operations", "[vector]") -{ - SECTION("cartesian_vector initialization and access") - { - SECTION("no arguments") - { - cartesian_vector v; - REQUIRE(v[0] == 0); - REQUIRE(v[1] == 0); - REQUIRE(v[2] == 0); - } - - SECTION("zero arguments") - { - cartesian_vector v{}; - REQUIRE(v[0] == 0); - REQUIRE(v[1] == 0); - REQUIRE(v[2] == 0); - } - - SECTION("one argument") - { - cartesian_vector v{1.0}; - REQUIRE(v[0] == 1.0); - REQUIRE(v[1] == 0); - REQUIRE(v[2] == 0); - } - - SECTION("two arguments") - { - cartesian_vector v{1.0, 2.0}; - REQUIRE(v[0] == 1.0); - REQUIRE(v[1] == 2.0); - REQUIRE(v[2] == 0); - } - - SECTION("all arguments") - { - cartesian_vector v{1.0, 2.0, 3.0}; - REQUIRE(v[0] == 1.0); - REQUIRE(v[1] == 2.0); - REQUIRE(v[2] == 3.0); - } - - SECTION("convertible arguments") - { - cartesian_vector v{1, 2, 3}; - REQUIRE(v[0] == 1.0); - REQUIRE(v[1] == 2.0); - REQUIRE(v[2] == 3.0); - } +using Catch::Matchers::WithinAbs; + +TEST_CASE("cartesian_vector initialization and access", "[vector]") { + SECTION("no arguments") { + cartesian_vector v; + REQUIRE(v[0] == 0); + REQUIRE(v[1] == 0); + REQUIRE(v[2] == 0); + } + + SECTION("zero arguments") { + cartesian_vector v{}; + REQUIRE(v[0] == 0); + REQUIRE(v[1] == 0); + REQUIRE(v[2] == 0); + } + + SECTION("one argument") { + cartesian_vector v{1.0}; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 0); + REQUIRE(v[2] == 0); + } + + SECTION("two arguments") { + cartesian_vector v{1.0, 2.0}; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 2.0); + REQUIRE(v[2] == 0); + } + + SECTION("all arguments") { + cartesian_vector v{1.0, 2.0, 3.0}; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 2.0); + REQUIRE(v[2] == 3.0); + } + + SECTION("convertible arguments") { + cartesian_vector v{1, 2, 3}; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 2.0); + REQUIRE(v[2] == 3.0); + } +} + +TEST_CASE("convertibility from another vector", "[vector]") { + cartesian_vector v1{1, 2, 3}; + + SECTION("construction") { + cartesian_vector v2 = v1; + REQUIRE(v2[0] == 1.0); + REQUIRE(v2[1] == 2.0); + REQUIRE(v2[2] == 3.0); } - SECTION("convertibility from another vector") - { - cartesian_vector v1{1, 2, 3}; - - SECTION("construction") - { - cartesian_vector v2 = v1; - REQUIRE(v2[0] == 1.0); - REQUIRE(v2[1] == 2.0); - REQUIRE(v2[2] == 3.0); - } - - SECTION("assignment") - { - cartesian_vector v2{3.0, 2.0, 1.0}; - v2 = v1; - REQUIRE(v2[0] == 1.0); - REQUIRE(v2[1] == 2.0); - REQUIRE(v2[2] == 3.0); - } + SECTION("assignment") { + cartesian_vector v2{3.0, 2.0, 1.0}; + v2 = v1; + REQUIRE(v2[0] == 1.0); + REQUIRE(v2[1] == 2.0); + REQUIRE(v2[2] == 3.0); } +} - SECTION("cartesian_vector compound assignment addition") - { +TEST_CASE("compound assignments", "[vector]") { + SECTION("operator+=") { cartesian_vector v1{1.0, 2.0, 3.0}; cartesian_vector v2{4.0, 5.0, 6.0}; v1 += v2; @@ -124,8 +89,7 @@ TEST_CASE("cartesian_vector operations", "[vector]") REQUIRE(v1[2] == 9.0); } - SECTION("cartesian_vector compound assignment subtraction") - { + SECTION("operator-=") { cartesian_vector v1{4.0, 5.0, 6.0}; cartesian_vector v2{1.0, 2.0, 3.0}; v1 -= v2; @@ -134,8 +98,7 @@ TEST_CASE("cartesian_vector operations", "[vector]") REQUIRE(v1[2] == 3.0); } - SECTION("cartesian_vector compound assignment scalar multiplication") - { + SECTION("operator*=") { cartesian_vector v{1.0, 2.0, 3.0}; v *= 2.0; REQUIRE(v[0] == 2.0); @@ -143,301 +106,139 @@ TEST_CASE("cartesian_vector operations", "[vector]") REQUIRE(v[2] == 6.0); } - SECTION("cartesian_vector compound assignment scalar division") - { + SECTION("operator/=") { cartesian_vector v{2.0, 4.0, 6.0}; v /= 2.0; REQUIRE(v[0] == 1.0); REQUIRE(v[1] == 2.0); REQUIRE(v[2] == 3.0); } +} - SECTION("cartesian_vector addition") - { - SECTION("double + double") - { - cartesian_vector v1{1.0, 2.0, 3.0}; - cartesian_vector v2{4.0, 5.0, 6.0}; - cartesian_vector result = v1 + v2; - REQUIRE(result[0] == 5.0); - REQUIRE(result[1] == 7.0); - REQUIRE(result[2] == 9.0); - } - - SECTION("double + int") - { - cartesian_vector v1{1.0, 2.0, 3.0}; - cartesian_vector v2{4, 5, 6}; - cartesian_vector result = v1 + v2; - REQUIRE(result[0] == 5.0); - REQUIRE(result[1] == 7.0); - REQUIRE(result[2] == 9.0); - } - - SECTION("int + double") - { - cartesian_vector v1{1, 2, 3}; - cartesian_vector v2{4.0, 5.0, 6.0}; - cartesian_vector result = v1 + v2; - REQUIRE(result[0] == 5.0); - REQUIRE(result[1] == 7.0); - REQUIRE(result[2] == 9.0); - } - - SECTION("int + int") - { - cartesian_vector v1{1, 2, 3}; - cartesian_vector v2{4, 5, 6}; - cartesian_vector result = v1 + v2; - REQUIRE(result[0] == 5); - REQUIRE(result[1] == 7); - REQUIRE(result[2] == 9); - } +TEST_CASE("binary ops + and -", "[vector]") { + SECTION("double + double") { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4.0, 5.0, 6.0}; + cartesian_vector result = v1 + v2; + REQUIRE(result[0] == 5.0); + REQUIRE(result[1] == 7.0); + REQUIRE(result[2] == 9.0); } - SECTION("cartesian_vector subtraction") - { - SECTION("double - double") - { - cartesian_vector v1{4.0, 5.0, 6.0}; - cartesian_vector v2{1.0, 2.0, 3.0}; - cartesian_vector result = v1 - v2; - REQUIRE(result[0] == 3.0); - REQUIRE(result[1] == 3.0); - REQUIRE(result[2] == 3.0); - } - - SECTION("double - int") - { - cartesian_vector v1{4.0, 5.0, 6.0}; - cartesian_vector v2{1, 2, 3}; - cartesian_vector result = v1 - v2; - REQUIRE(result[0] == 3.0); - REQUIRE(result[1] == 3.0); - REQUIRE(result[2] == 3.0); - } - - SECTION("int - double") - { - cartesian_vector v1{4, 5, 6}; - cartesian_vector v2{1.0, 2.0, 3.0}; - cartesian_vector result = v1 - v2; - REQUIRE(result[0] == 3.0); - REQUIRE(result[1] == 3.0); - REQUIRE(result[2] == 3.0); - } - - SECTION("int - int") - { - cartesian_vector v1{4, 5, 6}; - cartesian_vector v2{1, 2, 3}; - cartesian_vector result = v1 - v2; - REQUIRE(result[0] == 3); - REQUIRE(result[1] == 3); - REQUIRE(result[2] == 3); - } + SECTION("int - int") { + cartesian_vector v1{4, 5, 6}; + cartesian_vector v2{1, 2, 3}; + cartesian_vector result = v1 - v2; + REQUIRE(result[0] == 3); + REQUIRE(result[1] == 3); + REQUIRE(result[2] == 3); } +} - SECTION("cartesian_vector scalar multiplication") - { - SECTION("double * double") - { - cartesian_vector v{1.0, 2.0, 3.0}; - cartesian_vector result = v * 2.0; - REQUIRE(result[0] == 2.0); - REQUIRE(result[1] == 4.0); - REQUIRE(result[2] == 6.0); - } - - SECTION("double * int") - { - cartesian_vector v{1.0, 2.0, 3.0}; - cartesian_vector result = v * 2; - REQUIRE(result[0] == 2.0); - REQUIRE(result[1] == 4.0); - REQUIRE(result[2] == 6.0); - } - - SECTION("int * double") - { - cartesian_vector v{1, 2, 3}; - cartesian_vector result = v * 2.0; - REQUIRE(result[0] == 2.0); - REQUIRE(result[1] == 4.0); - REQUIRE(result[2] == 6.0); - } - - SECTION("int * int") - { - cartesian_vector v{1, 2, 3}; - cartesian_vector result = v * 2; - REQUIRE(result[0] == 2); - REQUIRE(result[1] == 4); - REQUIRE(result[2] == 6); - } +TEST_CASE("elementwise modulo", "[vector]") { + SECTION("integral % integral") { + cartesian_vector a{10, 11, 12}; + cartesian_vector b{ 4, 5, 7}; + auto r = a % b; + REQUIRE(r[0] == 2); + REQUIRE(r[1] == 1); + REQUIRE(r[2] == 5); } - SECTION("cartesian_vector scalar division") - { - SECTION("double / double") - { - cartesian_vector v{2.0, 4.0, 6.0}; - cartesian_vector result = v / 2.0; - REQUIRE(result[0] == 1.0); - REQUIRE(result[1] == 2.0); - REQUIRE(result[2] == 3.0); - } - - SECTION("double / int") - { - cartesian_vector v{2.0, 4.0, 6.0}; - cartesian_vector result = v / 2; - REQUIRE(result[0] == 1.0); - REQUIRE(result[1] == 2.0); - REQUIRE(result[2] == 3.0); - } - - SECTION("int / double") - { - cartesian_vector v{2, 4, 6}; - cartesian_vector result = v / 2.0; - REQUIRE(result[0] == 1.0); - REQUIRE(result[1] == 2.0); - REQUIRE(result[2] == 3.0); - } - - SECTION("int / int") - { - cartesian_vector v{2, 4, 6}; - cartesian_vector result = v / 2; - REQUIRE(result[0] == 1); - REQUIRE(result[1] == 2); - REQUIRE(result[2] == 3); - } + SECTION("floating uses fmod") { + cartesian_vector a{ 5.5, 7.25, 9.0}; + cartesian_vector b{ 2.0, 2.50, 4.0}; + auto r = a % b; + REQUIRE_THAT(r[0], WithinAbs(1.5, 1e-12)); + REQUIRE_THAT(r[1], WithinAbs(2.25, 1e-12)); + REQUIRE_THAT(r[2], WithinAbs(1.0, 1e-12)); } +} - SECTION("cartesian_vector magnitude") - { - cartesian_vector v1{3.0, 4.0, 0.0}; - cartesian_vector v2{2.0, 3.0, 6.0}; - REQUIRE(v1.magnitude() == 5.0); - REQUIRE(v2.magnitude() == 7.0); +TEST_CASE("scalar multiply/divide", "[vector]") { + cartesian_vector v{1, 2, 3}; + + SECTION("v * s") { + auto r = v * 2.0; + REQUIRE(r[0] == 2.0); + REQUIRE(r[1] == 4.0); + REQUIRE(r[2] == 6.0); } - SECTION("cartesian_vector unit vector") - { - cartesian_vector v{3.0, 4.0, 0.0}; - cartesian_vector unit_v = v.unit(); - REQUIRE_THAT(unit_v.magnitude(), WithinULP(1.0, 1)); + SECTION("s * v") { + auto r = 2.0 * v; + REQUIRE(r[0] == 2.0); + REQUIRE(r[1] == 4.0); + REQUIRE(r[2] == 6.0); } - SECTION("cartesian_vector equality") - { - cartesian_vector v1{1.0, 2.0, 3.0}; - cartesian_vector v2{1, 2, 3}; - cartesian_vector v3{1.1, 2.0, 3.0}; - cartesian_vector v4{1.0, 2.1, 3.0}; - cartesian_vector v5{1.0, 2.0, 3.1}; - REQUIRE(v1 == v2); - REQUIRE(v1 != v3); - REQUIRE(v1 != v4); - REQUIRE(v1 != v5); + SECTION("v / s") { + auto r = v / 2.0; + REQUIRE(r[0] == 0.5); + REQUIRE(r[1] == 1.0); + REQUIRE(r[2] == 1.5); } +} + +TEST_CASE("equality and inequality", "[vector]") { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{1, 2, 3}; + cartesian_vector v3{1.1, 2.0, 3.0}; + REQUIRE(v1 == v2); + REQUIRE(v1 != v3); +} + +TEST_CASE("dot and cross (numeric level)", "[vector]") { + cartesian_vector a{1, 2, 3}; + cartesian_vector b{4, 5, 6}; - SECTION("cartesian_vector scalar product") - { - SECTION("double * double") - { - cartesian_vector v1{1.0, 2.0, 3.0}; - cartesian_vector v2{4.0, 5.0, 6.0}; - REQUIRE(scalar_product(v1, v2) == 32.0); - } - - SECTION("double * int") - { - cartesian_vector v1{1.0, 2.0, 3.0}; - cartesian_vector v2{4, 5, 6}; - REQUIRE(scalar_product(v1, v2) == 32.0); - } - - SECTION("int * double") - { - cartesian_vector v1{1, 2, 3}; - cartesian_vector v2{4.0, 5.0, 6.0}; - REQUIRE(scalar_product(v1, v2) == 32.0); - } - - SECTION("int * int") - { - cartesian_vector v1{1, 2, 3}; - cartesian_vector v2{4, 5, 6}; - REQUIRE(scalar_product(v1, v2) == 32); - } + SECTION("scalar_product and alias dot") { + REQUIRE(scalar_product(a, b) == 32); + REQUIRE(dot(a, b) == 32); } - SECTION("cartesian_vector vector product") - { - SECTION("double * double") - { - cartesian_vector v1{1.0, 2.0, 3.0}; - cartesian_vector v2{4.0, 5.0, 6.0}; - cartesian_vector result = vector_product(v1, v2); - REQUIRE(result[0] == -3.0); - REQUIRE(result[1] == 6.0); - REQUIRE(result[2] == -3.0); - } - - SECTION("double * int") - { - cartesian_vector v1{1.0, 2.0, 3.0}; - cartesian_vector v2{4, 5, 6}; - cartesian_vector result = vector_product(v1, v2); - REQUIRE(result[0] == -3.0); - REQUIRE(result[1] == 6.0); - REQUIRE(result[2] == -3.0); - } - - SECTION("int * double") - { - cartesian_vector v1{1, 2, 3}; - cartesian_vector v2{4.0, 5.0, 6.0}; - cartesian_vector result = vector_product(v1, v2); - REQUIRE(result[0] == -3.0); - REQUIRE(result[1] == 6.0); - REQUIRE(result[2] == -3.0); - } - - SECTION("int * int") - { - cartesian_vector v1{1, 2, 3}; - cartesian_vector v2{4, 5, 6}; - cartesian_vector result = vector_product(v1, v2); - REQUIRE(result[0] == -3); - REQUIRE(result[1] == 6); - REQUIRE(result[2] == -3); - } + SECTION("vector_product and alias cross") { + auto r = vector_product(a, b); + REQUIRE(r[0] == -3); + REQUIRE(r[1] == 6); + REQUIRE(r[2] == -3); + + auto r2 = cross(a, b); + REQUIRE(r2[0] == -3); + REQUIRE(r2[1] == 6); + REQUIRE(r2[2] == -3); } } -TEST_CASE("cartesian_vector text output", "[vector][fmt][ostream]") -{ - std::ostringstream os; +TEST_CASE("magnitude and unit (floating types)", "[vector]") { + cartesian_vector v{3.0, 4.0, 0.0}; + REQUIRE_THAT(v.magnitude(), WithinAbs(5.0, 1e-12)); - SECTION("integral representation") - { - cartesian_vector v{1, 2, 3}; - os << v; + auto u = v.unit(); + REQUIRE_THAT(u.magnitude(), WithinAbs(1.0, 1e-12)); + REQUIRE_THAT(u[0], WithinAbs(3.0/5.0, 1e-12)); + REQUIRE_THAT(u[1], WithinAbs(4.0/5.0, 1e-12)); + REQUIRE_THAT(u[2], WithinAbs(0.0, 1e-12)); +} - SECTION("iostream") { CHECK(os.str() == "[1, 2, 3]"); } - SECTION("fmt with default format {}") { CHECK(MP_UNITS_STD_FMT::format("{}", v) == os.str()); } - } +#if MP_UNITS_HOSTED +TEST_CASE("text output (ostream + fmt)", "[vector][fmt][ostream]") { + cartesian_vector v{1, 2, 3}; - SECTION("floating-point representation") - { - cartesian_vector v{1.2, 2.3, 3.4}; - os << v; + // iostream + std::ostringstream os; + os << v; + CHECK(os.str() == "[1, 2, 3]"); - SECTION("iostream") { CHECK(os.str() == "[1.2, 2.3, 3.4]"); } - SECTION("fmt with default format {}") { CHECK(MP_UNITS_STD_FMT::format("{}", v) == os.str()); } - } + // fmt (via MP_UNITS_STD_FMT wrapper) + CHECK(MP_UNITS_STD_FMT::format("{}", v) == os.str()); +} +#endif + +TEST_CASE("constexpr basics", "[vector][constexpr]") { + constexpr cartesian_vector a{1,2,3}; + constexpr cartesian_vector b{4,5,6}; + constexpr auto c = a + b; + static_assert(c._coordinates_[0] == 5 && c._coordinates_[1] == 7 && c._coordinates_[2] == 9); + (void)c; } From fa57a3a2f678de337f019a43384b79ab42120530 Mon Sep 17 00:00:00 2001 From: Julia Smerdel <97158770+Jullija@users.noreply.github.com> Date: Sun, 26 Oct 2025 14:10:06 +0000 Subject: [PATCH 05/18] small changes --- src/core/include/mp-units/cartesian_tensor.h | 23 +++++++++++++++++++- src/core/include/mp-units/cartesian_vector.h | 23 +++++++++++++++++++- src/core/include/mp-units/core.h | 1 + test/runtime/cartesian_tensor_test.cpp | 22 ++++++++++++++++++- test/runtime/cartesian_vector_test.cpp | 22 ++++++++++++++++++- 5 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/core/include/mp-units/cartesian_tensor.h b/src/core/include/mp-units/cartesian_tensor.h index 0886db74dc..ff4a740d75 100644 --- a/src/core/include/mp-units/cartesian_tensor.h +++ b/src/core/include/mp-units/cartesian_tensor.h @@ -1,4 +1,25 @@ -// SPDX-License-Identifier: MIT +// 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 diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index ad92356d86..3fee7c8448 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -1,4 +1,25 @@ -// SPDX-License-Identifier: MIT +// 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 diff --git a/src/core/include/mp-units/core.h b/src/core/include/mp-units/core.h index 269507a794..6f4d60824e 100644 --- a/src/core/include/mp-units/core.h +++ b/src/core/include/mp-units/core.h @@ -29,6 +29,7 @@ #if MP_UNITS_HOSTED #include +#include #include #include #endif diff --git a/test/runtime/cartesian_tensor_test.cpp b/test/runtime/cartesian_tensor_test.cpp index 3d6621e5b8..747866e185 100644 --- a/test/runtime/cartesian_tensor_test.cpp +++ b/test/runtime/cartesian_tensor_test.cpp @@ -1,4 +1,24 @@ -// SPDX-License-Identifier: MIT +// 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. #include #include diff --git a/test/runtime/cartesian_vector_test.cpp b/test/runtime/cartesian_vector_test.cpp index 30bb30511c..a0171fe3c9 100644 --- a/test/runtime/cartesian_vector_test.cpp +++ b/test/runtime/cartesian_vector_test.cpp @@ -1,4 +1,24 @@ -// SPDX-License-Identifier: MIT +// 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. #include #include From 7bc1e1890aa2836b59b3c995ce4fe5da61bd319f Mon Sep 17 00:00:00 2001 From: Julia Smerdel <97158770+Jullija@users.noreply.github.com> Date: Sun, 26 Oct 2025 16:27:09 +0000 Subject: [PATCH 06/18] formatting --- CMakeLists.txt | 2 +- src/core/include/mp-units/cartesian_tensor.h | 89 ++++----- src/core/include/mp-units/core.h | 2 +- test/runtime/cartesian_tensor_test.cpp | 185 +++++++++++-------- test/runtime/cartesian_vector_test.cpp | 126 ++++++++----- 5 files changed, 226 insertions(+), 178 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c4052ce4c2..83ea37c3b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,4 +81,4 @@ endif() # add unit tests enable_testing() -add_subdirectory(test) \ No newline at end of file +add_subdirectory(test) diff --git a/src/core/include/mp-units/cartesian_tensor.h b/src/core/include/mp-units/cartesian_tensor.h index ff4a740d75..7a657836ac 100644 --- a/src/core/include/mp-units/cartesian_tensor.h +++ b/src/core/include/mp-units/cartesian_tensor.h @@ -29,21 +29,21 @@ #include #if MP_UNITS_HOSTED - #include +#include #endif #ifndef MP_UNITS_IN_MODULE_INTERFACE -# ifdef MP_UNITS_IMPORT_STD - import std; -# else -# include -# include -# include -# include -# if MP_UNITS_HOSTED -# include -# endif -# endif +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#include +#include +#include +#if MP_UNITS_HOSTED +#include +#endif +#endif #endif namespace mp_units { @@ -73,13 +73,14 @@ class cartesian_tensor { // fill ctor (row-major R*C) template - requires (sizeof...(Args) == R * C) && (... && std::constructible_from) - constexpr explicit(!(... && std::convertible_to)) - cartesian_tensor(Args&&... args) - : _data_{ static_cast(std::forward(args))... } {} + requires(sizeof...(Args) == R * C) && (... && std::constructible_from) + constexpr explicit(!(... && std::convertible_to)) cartesian_tensor(Args&&... args) : + _data_{static_cast(std::forward(args))...} + { + } // element access - [[nodiscard]] constexpr T& operator()(std::size_t r, std::size_t c) { return _data_[r * C + c]; } + [[nodiscard]] constexpr T& operator()(std::size_t r, std::size_t c) { return _data_[r * C + c]; } [[nodiscard]] constexpr const T& operator()(std::size_t r, std::size_t c) const { return _data_[r * C + c]; } // elementwise +, - @@ -89,8 +90,7 @@ class cartesian_tensor { { using CT = std::common_type_t; cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) - Rm._data_[i] = static_cast(A._data_[i]) + static_cast(B._data_[i]); + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(A._data_[i]) + static_cast(B._data_[i]); return Rm; } @@ -100,15 +100,13 @@ class cartesian_tensor { { using CT = std::common_type_t; cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) - Rm._data_[i] = static_cast(A._data_[i]) - static_cast(B._data_[i]); + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(A._data_[i]) - static_cast(B._data_[i]); return Rm; } // elementwise % (integral uses %, floating uses fmod) template - requires (requires(const T& t, const U& u) { t % u; }) || - (std::floating_point && std::floating_point) + requires(requires(const T& t, const U& u) { t % u; }) || (std::floating_point && std::floating_point) [[nodiscard]] friend constexpr auto operator%(const cartesian_tensor& A, const cartesian_tensor& B) { using CT = std::common_type_t; @@ -117,11 +115,9 @@ class cartesian_tensor { using std::fmod; for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = - static_cast(fmod(static_cast(A._data_[i]), - static_cast(B._data_[i]))); + static_cast(fmod(static_cast(A._data_[i]), static_cast(B._data_[i]))); } else { - for (std::size_t i = 0; i < R * C; ++i) - Rm._data_[i] = static_cast(A._data_[i] % B._data_[i]); + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(A._data_[i] % B._data_[i]); } return Rm; } @@ -132,8 +128,7 @@ class cartesian_tensor { { using CT = std::common_type_t; cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) - Rm._data_[i] = static_cast(tensor._data_[i]) * static_cast(scalar); + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(tensor._data_[i]) * static_cast(scalar); return Rm; } @@ -148,8 +143,7 @@ class cartesian_tensor { { using CT = std::common_type_t; cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) - Rm._data_[i] = static_cast(tensor._data_[i]) / static_cast(scalar); + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(tensor._data_[i]) / static_cast(scalar); return Rm; } @@ -177,16 +171,14 @@ inline constexpr bool is_tensor> = true; // Matrix × Matrix template -[[nodiscard]] constexpr auto matmul(const cartesian_tensor& A, - const cartesian_tensor& B) +[[nodiscard]] constexpr auto matmul(const cartesian_tensor& A, const cartesian_tensor& B) { using CT = std::common_type_t; cartesian_tensor Rm{}; for (std::size_t r = 0; r < R; ++r) for (std::size_t c = 0; c < C; ++c) { CT acc{}; - for (std::size_t k = 0; k < K; ++k) - acc += static_cast(A(r, k)) * static_cast(B(k, c)); + for (std::size_t k = 0; k < K; ++k) acc += static_cast(A(r, k)) * static_cast(B(k, c)); Rm(r, c) = acc; } return Rm; @@ -194,15 +186,13 @@ template // Matrix × Vector (3×3) template -[[nodiscard]] constexpr auto matvec(const cartesian_tensor& M, - const cartesian_vector& x) +[[nodiscard]] constexpr auto matvec(const cartesian_tensor& M, const cartesian_vector& x) { using CT = std::common_type_t; cartesian_vector y{}; for (std::size_t r = 0; r < 3; ++r) { CT acc{}; - for (std::size_t c = 0; c < 3; ++c) - acc += static_cast(M(r, c)) * static_cast(x[c]); + for (std::size_t c = 0; c < 3; ++c) acc += static_cast(M(r, c)) * static_cast(x[c]); y[r] = acc; } return y; @@ -210,38 +200,35 @@ template // Double contraction: A : B template -[[nodiscard]] constexpr auto double_contraction(const cartesian_tensor& A, - const cartesian_tensor& B) +[[nodiscard]] constexpr auto double_contraction(const cartesian_tensor& A, const cartesian_tensor& B) { using CT = std::common_type_t; CT acc{}; - for (std::size_t i = 0; i < R * C; ++i) - acc += static_cast(A._data_[i]) * static_cast(B._data_[i]); + for (std::size_t i = 0; i < R * C; ++i) acc += static_cast(A._data_[i]) * static_cast(B._data_[i]); return acc; // numeric scalar } // Outer product: vector ⊗ vector -> 3x3 matrix template -[[nodiscard]] constexpr auto outer_numeric(const cartesian_vector& a, - const cartesian_vector& b) +[[nodiscard]] constexpr auto outer_numeric(const cartesian_vector& a, const cartesian_vector& b) { using CT = std::common_type_t; cartesian_tensor Rm{}; for (std::size_t i = 0; i < 3; ++i) - for (std::size_t j = 0; j < 3; ++j) - Rm(i, j) = static_cast(a[i]) * static_cast(b[j]); + for (std::size_t j = 0; j < 3; ++j) Rm(i, j) = static_cast(a[i]) * static_cast(b[j]); return Rm; } -} // namespace mp_units +} // namespace mp_units #if MP_UNITS_HOSTED // fmt/format (or std::format) support template -struct MP_UNITS_STD_FMT::formatter, Char> - : formatter, Char> { +struct MP_UNITS_STD_FMT::formatter, Char> : + formatter, Char> { template - auto format(const mp_units::cartesian_tensor& A, Ctx& ctx) const { + auto format(const mp_units::cartesian_tensor& A, Ctx& ctx) const + { auto out = ctx.out(); for (std::size_t r = 0; r < R; ++r) { out = format_to(out, "{}", (r == 0 ? "[[" : " [")); diff --git a/src/core/include/mp-units/core.h b/src/core/include/mp-units/core.h index 6f4d60824e..fa36bd3661 100644 --- a/src/core/include/mp-units/core.h +++ b/src/core/include/mp-units/core.h @@ -28,8 +28,8 @@ #include #if MP_UNITS_HOSTED -#include #include +#include #include #include #endif diff --git a/test/runtime/cartesian_tensor_test.cpp b/test/runtime/cartesian_tensor_test.cpp index 747866e185..d29247e9d6 100644 --- a/test/runtime/cartesian_tensor_test.cpp +++ b/test/runtime/cartesian_tensor_test.cpp @@ -26,122 +26,150 @@ #ifdef MP_UNITS_MODULES import mp_units; #else - #include - #include - #if MP_UNITS_HOSTED - #include - #include - #endif +#include +#include +#include +#if MP_UNITS_HOSTED +#include +#include +#endif #endif using namespace mp_units; using Catch::Matchers::WithinAbs; -TEST_CASE("cartesian_tensor construction and access", "[tensor]") { +TEST_CASE("cartesian_tensor construction and access", "[tensor]") +{ // 2x3 fill-ctor order is row-major - cartesian_tensor A{1,2,3, 4,5,6}; - REQUIRE(A(0,0) == 1); - REQUIRE(A(0,1) == 2); - REQUIRE(A(0,2) == 3); - REQUIRE(A(1,0) == 4); - REQUIRE(A(1,1) == 5); - REQUIRE(A(1,2) == 6); + cartesian_tensor A{1, 2, 3, 4, 5, 6}; + REQUIRE(A(0, 0) == 1); + REQUIRE(A(0, 1) == 2); + REQUIRE(A(0, 2) == 3); + REQUIRE(A(1, 0) == 4); + REQUIRE(A(1, 1) == 5); + REQUIRE(A(1, 2) == 6); } -TEST_CASE("elementwise + and - with common_type", "[tensor]") { - cartesian_tensor A{1,2, 3,4}; - cartesian_tensor B{0.5,1.5, 2.5,3.5}; +TEST_CASE("elementwise + and - with common_type", "[tensor]") +{ + cartesian_tensor A{1, 2, 3, 4}; + cartesian_tensor B{0.5, 1.5, 2.5, 3.5}; auto S = A + B; - static_assert(std::is_same_v>); - REQUIRE(S(0,0) == 1.5); - REQUIRE(S(0,1) == 3.5); - REQUIRE(S(1,0) == 5.5); - REQUIRE(S(1,1) == 7.5); + static_assert(std::is_same_v>); + REQUIRE(S(0, 0) == 1.5); + REQUIRE(S(0, 1) == 3.5); + REQUIRE(S(1, 0) == 5.5); + REQUIRE(S(1, 1) == 7.5); auto D = B - A; -REQUIRE_THAT(D(0,0), WithinAbs(-0.5, 1e-12)); -REQUIRE_THAT(D(0,1), WithinAbs(-0.5, 1e-12)); -REQUIRE_THAT(D(1,0), WithinAbs(-0.5, 1e-12)); -REQUIRE_THAT(D(1,1), WithinAbs(-0.5, 1e-12)); + REQUIRE_THAT(D(0, 0), WithinAbs(-0.5, 1e-12)); + REQUIRE_THAT(D(0, 1), WithinAbs(-0.5, 1e-12)); + REQUIRE_THAT(D(1, 0), WithinAbs(-0.5, 1e-12)); + REQUIRE_THAT(D(1, 1), WithinAbs(-0.5, 1e-12)); } -TEST_CASE("elementwise modulo", "[tensor]") { - SECTION("integral % integral") { - cartesian_tensor A{10,11,12, 13,14,15}; - cartesian_tensor B{ 4, 5, 7, 4, 5, 7}; +TEST_CASE("elementwise modulo", "[tensor]") +{ + SECTION("integral % integral") + { + cartesian_tensor A{10, 11, 12, 13, 14, 15}; + cartesian_tensor B{4, 5, 7, 4, 5, 7}; auto R = A % B; - REQUIRE(R(0,0) == 2); REQUIRE(R(0,1) == 1); REQUIRE(R(0,2) == 5); - REQUIRE(R(1,0) == 1); REQUIRE(R(1,1) == 4); REQUIRE(R(1,2) == 1); + REQUIRE(R(0, 0) == 2); + REQUIRE(R(0, 1) == 1); + REQUIRE(R(0, 2) == 5); + REQUIRE(R(1, 0) == 1); + REQUIRE(R(1, 1) == 4); + REQUIRE(R(1, 2) == 1); } - SECTION("floating uses fmod, result in common_type") { - cartesian_tensor A{5.5, 7.25, 9.0}; - cartesian_tensor B{2.0, 2.50, 4.0}; + SECTION("floating uses fmod, result in common_type") + { + cartesian_tensor A{5.5, 7.25, 9.0}; + cartesian_tensor B{2.0, 2.50, 4.0}; auto R = A % B; - REQUIRE_THAT(R(0,0), WithinAbs(1.5, 1e-12)); - REQUIRE_THAT(R(0,1), WithinAbs(2.25, 1e-12)); - REQUIRE_THAT(R(0,2), WithinAbs(1.0, 1e-12)); + REQUIRE_THAT(R(0, 0), WithinAbs(1.5, 1e-12)); + REQUIRE_THAT(R(0, 1), WithinAbs(2.25, 1e-12)); + REQUIRE_THAT(R(0, 2), WithinAbs(1.0, 1e-12)); } } -TEST_CASE("scalar multiply/divide", "[tensor]") { - cartesian_tensor A{1,2,3,4}; +TEST_CASE("scalar multiply/divide", "[tensor]") +{ + cartesian_tensor A{1, 2, 3, 4}; auto T1 = A * 2.0; - REQUIRE(T1(0,0) == 2.0); REQUIRE(T1(0,1) == 4.0); - REQUIRE(T1(1,0) == 6.0); REQUIRE(T1(1,1) == 8.0); + REQUIRE(T1(0, 0) == 2.0); + REQUIRE(T1(0, 1) == 4.0); + REQUIRE(T1(1, 0) == 6.0); + REQUIRE(T1(1, 1) == 8.0); auto T2 = 2.0 * A; - REQUIRE(T2(0,0) == 2.0); REQUIRE(T2(0,1) == 4.0); - REQUIRE(T2(1,0) == 6.0); REQUIRE(T2(1,1) == 8.0); + REQUIRE(T2(0, 0) == 2.0); + REQUIRE(T2(0, 1) == 4.0); + REQUIRE(T2(1, 0) == 6.0); + REQUIRE(T2(1, 1) == 8.0); auto T3 = A / 2.0; - REQUIRE(T3(0,0) == 0.5); REQUIRE(T3(0,1) == 1.0); - REQUIRE(T3(1,0) == 1.5); REQUIRE(T3(1,1) == 2.0); + REQUIRE(T3(0, 0) == 0.5); + REQUIRE(T3(0, 1) == 1.0); + REQUIRE(T3(1, 0) == 1.5); + REQUIRE(T3(1, 1) == 2.0); } -TEST_CASE("matmul (R×K) * (K×C) -> (R×C)", "[tensor]") { - cartesian_tensor A{1,2,3, 4,5,6}; - cartesian_tensor B{7,8, 9,10, 11,12}; - auto C = matmul(A, B); // 2x2 - - static_assert(std::is_same_v>); - REQUIRE(C(0,0) == 58); REQUIRE(C(0,1) == 64); - REQUIRE(C(1,0) == 139); REQUIRE(C(1,1) == 154); +TEST_CASE("matmul (R×K) * (K×C) -> (R×C)", "[tensor]") +{ + cartesian_tensor A{1, 2, 3, 4, 5, 6}; + cartesian_tensor B{7, 8, 9, 10, 11, 12}; + auto C = matmul(A, B); // 2x2 + + static_assert(std::is_same_v>); + REQUIRE(C(0, 0) == 58); + REQUIRE(C(0, 1) == 64); + REQUIRE(C(1, 0) == 139); + REQUIRE(C(1, 1) == 154); } -TEST_CASE("matvec (3x3) * vector -> vector", "[tensor][vector]") { - cartesian_tensor M{ 1,2,3, - 0,1,4, - 5,6,0 }; - cartesian_vector x{1,2,3}; +TEST_CASE("matvec (3x3) * vector -> vector", "[tensor][vector]") +{ + cartesian_tensor M{1, 2, 3, 0, 1, 4, 5, 6, 0}; + cartesian_vector x{1, 2, 3}; auto y = matvec(M, x); - REQUIRE_THAT(y[0], WithinAbs(14.0, 1e-12)); // 1*1 + 2*2 + 3*3 - REQUIRE_THAT(y[1], WithinAbs(14.0, 1e-12)); // 0*1 + 1*2 + 4*3 - REQUIRE_THAT(y[2], WithinAbs(17.0, 1e-12)); // 5*1 + 6*2 + 0*3 + REQUIRE_THAT(y[0], WithinAbs(14.0, 1e-12)); // 1*1 + 2*2 + 3*3 + REQUIRE_THAT(y[1], WithinAbs(14.0, 1e-12)); // 0*1 + 1*2 + 4*3 + REQUIRE_THAT(y[2], WithinAbs(17.0, 1e-12)); // 5*1 + 6*2 + 0*3 } -TEST_CASE("double contraction A : B", "[tensor]") { - cartesian_tensor A{1,2, 3,4}; - cartesian_tensor B{5,6, 7,8}; +TEST_CASE("double contraction A : B", "[tensor]") +{ + cartesian_tensor A{1, 2, 3, 4}; + cartesian_tensor B{5, 6, 7, 8}; // 1*5 + 2*6 + 3*7 + 4*8 = 70 auto s = double_contraction(A, B); REQUIRE(s == 70); } -TEST_CASE("outer_numeric (vector ⊗ vector)", "[tensor][vector]") { - cartesian_vector a{1,2,3}; - cartesian_vector b{4,5,6}; - auto T = outer_numeric(a, b); // 3x3 - REQUIRE(T(0,0) == 4); REQUIRE(T(0,1) == 5); REQUIRE(T(0,2) == 6); - REQUIRE(T(1,0) == 8); REQUIRE(T(1,1) == 10); REQUIRE(T(1,2) == 12); - REQUIRE(T(2,0) == 12); REQUIRE(T(2,1) == 15); REQUIRE(T(2,2) == 18); +TEST_CASE("outer_numeric (vector ⊗ vector)", "[tensor][vector]") +{ + cartesian_vector a{1, 2, 3}; + cartesian_vector b{4, 5, 6}; + auto T = outer_numeric(a, b); // 3x3 + REQUIRE(T(0, 0) == 4); + REQUIRE(T(0, 1) == 5); + REQUIRE(T(0, 2) == 6); + REQUIRE(T(1, 0) == 8); + REQUIRE(T(1, 1) == 10); + REQUIRE(T(1, 2) == 12); + REQUIRE(T(2, 0) == 12); + REQUIRE(T(2, 1) == 15); + REQUIRE(T(2, 2) == 18); } #if MP_UNITS_HOSTED -TEST_CASE("text output (ostream + fmt)", "[tensor][fmt][ostream]") { - cartesian_tensor A{1,2,3,4}; +TEST_CASE("text output (ostream + fmt)", "[tensor][fmt][ostream]") +{ + cartesian_tensor A{1, 2, 3, 4}; std::ostringstream os; os << A; @@ -151,10 +179,11 @@ TEST_CASE("text output (ostream + fmt)", "[tensor][fmt][ostream]") { } #endif -TEST_CASE("constexpr basics", "[tensor][constexpr]") { - constexpr cartesian_tensor A{1,2,3}; - constexpr cartesian_tensor B{4,5,6}; +TEST_CASE("constexpr basics", "[tensor][constexpr]") +{ + constexpr cartesian_tensor A{1, 2, 3}; + constexpr cartesian_tensor B{4, 5, 6}; constexpr auto C = A + B; - static_assert(C(0,0) == 5 && C(0,1) == 7 && C(0,2) == 9); + static_assert(C(0, 0) == 5 && C(0, 1) == 7 && C(0, 2) == 9); (void)C; } diff --git a/test/runtime/cartesian_vector_test.cpp b/test/runtime/cartesian_vector_test.cpp index a0171fe3c9..1ef5c37ea3 100644 --- a/test/runtime/cartesian_vector_test.cpp +++ b/test/runtime/cartesian_vector_test.cpp @@ -26,53 +26,60 @@ #ifdef MP_UNITS_MODULES import mp_units; #else - #include - #if MP_UNITS_HOSTED - #include - #include - #endif +#include +#if MP_UNITS_HOSTED +#include +#include +#endif #endif using namespace mp_units; using Catch::Matchers::WithinAbs; -TEST_CASE("cartesian_vector initialization and access", "[vector]") { - SECTION("no arguments") { +TEST_CASE("cartesian_vector initialization and access", "[vector]") +{ + SECTION("no arguments") + { cartesian_vector v; REQUIRE(v[0] == 0); REQUIRE(v[1] == 0); REQUIRE(v[2] == 0); } - SECTION("zero arguments") { + SECTION("zero arguments") + { cartesian_vector v{}; REQUIRE(v[0] == 0); REQUIRE(v[1] == 0); REQUIRE(v[2] == 0); } - SECTION("one argument") { + SECTION("one argument") + { cartesian_vector v{1.0}; REQUIRE(v[0] == 1.0); REQUIRE(v[1] == 0); REQUIRE(v[2] == 0); } - SECTION("two arguments") { + SECTION("two arguments") + { cartesian_vector v{1.0, 2.0}; REQUIRE(v[0] == 1.0); REQUIRE(v[1] == 2.0); REQUIRE(v[2] == 0); } - SECTION("all arguments") { + SECTION("all arguments") + { cartesian_vector v{1.0, 2.0, 3.0}; REQUIRE(v[0] == 1.0); REQUIRE(v[1] == 2.0); REQUIRE(v[2] == 3.0); } - SECTION("convertible arguments") { + SECTION("convertible arguments") + { cartesian_vector v{1, 2, 3}; REQUIRE(v[0] == 1.0); REQUIRE(v[1] == 2.0); @@ -80,17 +87,20 @@ TEST_CASE("cartesian_vector initialization and access", "[vector]") { } } -TEST_CASE("convertibility from another vector", "[vector]") { +TEST_CASE("convertibility from another vector", "[vector]") +{ cartesian_vector v1{1, 2, 3}; - SECTION("construction") { + SECTION("construction") + { cartesian_vector v2 = v1; REQUIRE(v2[0] == 1.0); REQUIRE(v2[1] == 2.0); REQUIRE(v2[2] == 3.0); } - SECTION("assignment") { + SECTION("assignment") + { cartesian_vector v2{3.0, 2.0, 1.0}; v2 = v1; REQUIRE(v2[0] == 1.0); @@ -99,8 +109,10 @@ TEST_CASE("convertibility from another vector", "[vector]") { } } -TEST_CASE("compound assignments", "[vector]") { - SECTION("operator+=") { +TEST_CASE("compound assignments", "[vector]") +{ + SECTION("operator+=") + { cartesian_vector v1{1.0, 2.0, 3.0}; cartesian_vector v2{4.0, 5.0, 6.0}; v1 += v2; @@ -109,7 +121,8 @@ TEST_CASE("compound assignments", "[vector]") { REQUIRE(v1[2] == 9.0); } - SECTION("operator-=") { + SECTION("operator-=") + { cartesian_vector v1{4.0, 5.0, 6.0}; cartesian_vector v2{1.0, 2.0, 3.0}; v1 -= v2; @@ -118,7 +131,8 @@ TEST_CASE("compound assignments", "[vector]") { REQUIRE(v1[2] == 3.0); } - SECTION("operator*=") { + SECTION("operator*=") + { cartesian_vector v{1.0, 2.0, 3.0}; v *= 2.0; REQUIRE(v[0] == 2.0); @@ -126,7 +140,8 @@ TEST_CASE("compound assignments", "[vector]") { REQUIRE(v[2] == 6.0); } - SECTION("operator/=") { + SECTION("operator/=") + { cartesian_vector v{2.0, 4.0, 6.0}; v /= 2.0; REQUIRE(v[0] == 1.0); @@ -135,8 +150,10 @@ TEST_CASE("compound assignments", "[vector]") { } } -TEST_CASE("binary ops + and -", "[vector]") { - SECTION("double + double") { +TEST_CASE("binary ops + and -", "[vector]") +{ + SECTION("double + double") + { cartesian_vector v1{1.0, 2.0, 3.0}; cartesian_vector v2{4.0, 5.0, 6.0}; cartesian_vector result = v1 + v2; @@ -145,7 +162,8 @@ TEST_CASE("binary ops + and -", "[vector]") { REQUIRE(result[2] == 9.0); } - SECTION("int - int") { + SECTION("int - int") + { cartesian_vector v1{4, 5, 6}; cartesian_vector v2{1, 2, 3}; cartesian_vector result = v1 - v2; @@ -155,19 +173,22 @@ TEST_CASE("binary ops + and -", "[vector]") { } } -TEST_CASE("elementwise modulo", "[vector]") { - SECTION("integral % integral") { +TEST_CASE("elementwise modulo", "[vector]") +{ + SECTION("integral % integral") + { cartesian_vector a{10, 11, 12}; - cartesian_vector b{ 4, 5, 7}; + cartesian_vector b{4, 5, 7}; auto r = a % b; REQUIRE(r[0] == 2); REQUIRE(r[1] == 1); REQUIRE(r[2] == 5); } - SECTION("floating uses fmod") { - cartesian_vector a{ 5.5, 7.25, 9.0}; - cartesian_vector b{ 2.0, 2.50, 4.0}; + SECTION("floating uses fmod") + { + cartesian_vector a{5.5, 7.25, 9.0}; + cartesian_vector b{2.0, 2.50, 4.0}; auto r = a % b; REQUIRE_THAT(r[0], WithinAbs(1.5, 1e-12)); REQUIRE_THAT(r[1], WithinAbs(2.25, 1e-12)); @@ -175,24 +196,28 @@ TEST_CASE("elementwise modulo", "[vector]") { } } -TEST_CASE("scalar multiply/divide", "[vector]") { +TEST_CASE("scalar multiply/divide", "[vector]") +{ cartesian_vector v{1, 2, 3}; - SECTION("v * s") { + SECTION("v * s") + { auto r = v * 2.0; REQUIRE(r[0] == 2.0); REQUIRE(r[1] == 4.0); REQUIRE(r[2] == 6.0); } - SECTION("s * v") { + SECTION("s * v") + { auto r = 2.0 * v; REQUIRE(r[0] == 2.0); REQUIRE(r[1] == 4.0); REQUIRE(r[2] == 6.0); } - SECTION("v / s") { + SECTION("v / s") + { auto r = v / 2.0; REQUIRE(r[0] == 0.5); REQUIRE(r[1] == 1.0); @@ -200,7 +225,8 @@ TEST_CASE("scalar multiply/divide", "[vector]") { } } -TEST_CASE("equality and inequality", "[vector]") { +TEST_CASE("equality and inequality", "[vector]") +{ cartesian_vector v1{1.0, 2.0, 3.0}; cartesian_vector v2{1, 2, 3}; cartesian_vector v3{1.1, 2.0, 3.0}; @@ -208,41 +234,46 @@ TEST_CASE("equality and inequality", "[vector]") { REQUIRE(v1 != v3); } -TEST_CASE("dot and cross (numeric level)", "[vector]") { +TEST_CASE("dot and cross (numeric level)", "[vector]") +{ cartesian_vector a{1, 2, 3}; cartesian_vector b{4, 5, 6}; - SECTION("scalar_product and alias dot") { + SECTION("scalar_product and alias dot") + { REQUIRE(scalar_product(a, b) == 32); REQUIRE(dot(a, b) == 32); } - SECTION("vector_product and alias cross") { + SECTION("vector_product and alias cross") + { auto r = vector_product(a, b); REQUIRE(r[0] == -3); - REQUIRE(r[1] == 6); + REQUIRE(r[1] == 6); REQUIRE(r[2] == -3); auto r2 = cross(a, b); REQUIRE(r2[0] == -3); - REQUIRE(r2[1] == 6); + REQUIRE(r2[1] == 6); REQUIRE(r2[2] == -3); } } -TEST_CASE("magnitude and unit (floating types)", "[vector]") { +TEST_CASE("magnitude and unit (floating types)", "[vector]") +{ cartesian_vector v{3.0, 4.0, 0.0}; REQUIRE_THAT(v.magnitude(), WithinAbs(5.0, 1e-12)); auto u = v.unit(); REQUIRE_THAT(u.magnitude(), WithinAbs(1.0, 1e-12)); - REQUIRE_THAT(u[0], WithinAbs(3.0/5.0, 1e-12)); - REQUIRE_THAT(u[1], WithinAbs(4.0/5.0, 1e-12)); - REQUIRE_THAT(u[2], WithinAbs(0.0, 1e-12)); + REQUIRE_THAT(u[0], WithinAbs(3.0 / 5.0, 1e-12)); + REQUIRE_THAT(u[1], WithinAbs(4.0 / 5.0, 1e-12)); + REQUIRE_THAT(u[2], WithinAbs(0.0, 1e-12)); } #if MP_UNITS_HOSTED -TEST_CASE("text output (ostream + fmt)", "[vector][fmt][ostream]") { +TEST_CASE("text output (ostream + fmt)", "[vector][fmt][ostream]") +{ cartesian_vector v{1, 2, 3}; // iostream @@ -255,9 +286,10 @@ TEST_CASE("text output (ostream + fmt)", "[vector][fmt][ostream]") { } #endif -TEST_CASE("constexpr basics", "[vector][constexpr]") { - constexpr cartesian_vector a{1,2,3}; - constexpr cartesian_vector b{4,5,6}; +TEST_CASE("constexpr basics", "[vector][constexpr]") +{ + constexpr cartesian_vector a{1, 2, 3}; + constexpr cartesian_vector b{4, 5, 6}; constexpr auto c = a + b; static_assert(c._coordinates_[0] == 5 && c._coordinates_[1] == 7 && c._coordinates_[2] == 9); (void)c; From 90a371c70b1221b7fc33067797cfd63fdd2657ee Mon Sep 17 00:00:00 2001 From: Julia Smerdel <97158770+Jullija@users.noreply.github.com> Date: Sun, 26 Oct 2025 20:15:43 +0000 Subject: [PATCH 07/18] vector fixes after review --- src/core/include/mp-units/cartesian_vector.h | 77 +--- test/runtime/cartesian_tensor_test.cpp | 2 +- test/runtime/cartesian_vector_test.cpp | 415 ++++++++----------- 3 files changed, 205 insertions(+), 289 deletions(-) diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index 3fee7c8448..85f5541886 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -47,13 +47,12 @@ import std; namespace mp_units { -MP_UNITS_EXPORT template +MP_UNITS_EXPORT template class cartesian_vector; namespace detail { struct cartesian_vector_iface { - // A + B template requires requires(const T& t, const U& u) { t + u; } [[nodiscard]] friend constexpr auto operator+(const cartesian_vector& lhs, const cartesian_vector& rhs) @@ -63,7 +62,6 @@ struct cartesian_vector_iface { lhs._coordinates_[2] + rhs._coordinates_[2]}; } - // A - B template requires requires(const T& t, const U& u) { t - u; } [[nodiscard]] friend constexpr auto operator-(const cartesian_vector& lhs, const cartesian_vector& rhs) @@ -73,28 +71,16 @@ struct cartesian_vector_iface { lhs._coordinates_[2] - rhs._coordinates_[2]}; } - // A % B (integral: %, floating: fmod into CT) template - requires(requires(const T& t, const U& u) { t % u; }) || (std::floating_point && std::floating_point) + requires(!treat_as_floating_point && !treat_as_floating_point && requires(const T& t, const U& u) { t % u; }) [[nodiscard]] friend constexpr auto operator%(const cartesian_vector& lhs, const cartesian_vector& rhs) { using CT = std::common_type_t; - if constexpr (std::floating_point && std::floating_point) { - using std::fmod; - return ::mp_units::cartesian_vector{static_cast(fmod(static_cast(lhs._coordinates_[0]), - static_cast(rhs._coordinates_[0]))), - static_cast(fmod(static_cast(lhs._coordinates_[1]), - static_cast(rhs._coordinates_[1]))), - static_cast(fmod(static_cast(lhs._coordinates_[2]), - static_cast(rhs._coordinates_[2])))}; - } else { - return ::mp_units::cartesian_vector{static_cast(lhs._coordinates_[0] % rhs._coordinates_[0]), - static_cast(lhs._coordinates_[1] % rhs._coordinates_[1]), - static_cast(lhs._coordinates_[2] % rhs._coordinates_[2])}; - } + return ::mp_units::cartesian_vector{static_cast(lhs._coordinates_[0] % rhs._coordinates_[0]), + static_cast(lhs._coordinates_[1] % rhs._coordinates_[1]), + static_cast(lhs._coordinates_[2] % rhs._coordinates_[2])}; } - // (vector * scalar) template requires requires(const T& t, const S& s) { t * s; } [[nodiscard]] friend constexpr auto operator*(const cartesian_vector& vector, const S& scalar) @@ -103,23 +89,21 @@ struct cartesian_vector_iface { vector._coordinates_[2] * scalar}; } - // (scalar * vector) template requires requires(const S& s, const U& u) { s * u; } - [[nodiscard]] friend constexpr auto operator*(const S& s, const cartesian_vector& v) + [[nodiscard]] friend constexpr auto operator*(const S& scalar, const cartesian_vector& vector) { - return v * s; + return vector * scalar; } - // (vector / scalar) template requires requires(const T& t, const S& s) { t / s; } - [[nodiscard]] friend constexpr auto operator/(const cartesian_vector& v, const S& s) + [[nodiscard]] friend constexpr auto operator/(const cartesian_vector& vector, const S& scalar) { - return ::mp_units::cartesian_vector{v._coordinates_[0] / s, v._coordinates_[1] / s, v._coordinates_[2] / s}; + return ::mp_units::cartesian_vector{vector._coordinates_[0] / scalar, vector._coordinates_[1] / scalar, + vector._coordinates_[2] / scalar}; } - // equality template U> [[nodiscard]] friend constexpr bool operator==(const cartesian_vector& lhs, const cartesian_vector& rhs) { @@ -127,7 +111,6 @@ struct cartesian_vector_iface { lhs._coordinates_[2] == rhs._coordinates_[2]; } - // dot (numeric) template requires requires(const T& t, const U& u, decltype(t * u) v) { t * u; @@ -139,7 +122,6 @@ struct cartesian_vector_iface { lhs._coordinates_[2] * rhs._coordinates_[2]; } - // cross (numeric) — 3D template requires requires(const T& t, const U& u, decltype(t * u) v) { t * u; @@ -156,11 +138,13 @@ struct cartesian_vector_iface { } // namespace detail -MP_UNITS_EXPORT template +MP_UNITS_EXPORT template class cartesian_vector : public detail::cartesian_vector_iface { public: + // public members required to satisfy structural type requirements :-( + T _coordinates_[3]; using value_type = T; - T _coordinates_[3]{}; + cartesian_vector() = default; cartesian_vector(const cartesian_vector&) = default; @@ -208,7 +192,6 @@ class cartesian_vector : public detail::cartesian_vector_iface { return *this; } - // magnitude / unit (floating/complex-like) [[nodiscard]] constexpr T magnitude() const requires treat_as_floating_point { @@ -236,7 +219,9 @@ class cartesian_vector : public detail::cartesian_vector_iface { } template - requires requires(T& t, const U& u) { t += u; } + requires requires(T& t, const U& u) { + { t += u } -> std::same_as; + } constexpr cartesian_vector& operator+=(const cartesian_vector& other) { _coordinates_[0] += other[0]; @@ -296,41 +281,23 @@ class cartesian_vector : public detail::cartesian_vector_iface { }; template - requires(sizeof...(Args) == 2) && requires { typename std::common_type_t; } + requires(sizeof...(Args) <= 2) && requires { typename std::common_type_t; } cartesian_vector(Arg, Args...) -> cartesian_vector>; -template -[[nodiscard]] constexpr auto dot(const cartesian_vector& lhs, const cartesian_vector& rhs) -{ - return scalar_product(lhs, rhs); -} - -template -[[nodiscard]] constexpr auto cross(const cartesian_vector& lhs, const cartesian_vector& rhs) -{ - return vector_product(lhs, rhs); -} - -template - requires treat_as_floating_point -[[nodiscard]] constexpr auto norm(const cartesian_vector& vec) -{ - return magnitude(vec); -} - template inline constexpr bool is_vector> = true; } // namespace mp_units #if MP_UNITS_HOSTED +// TODO use parse and use formatter for the underlying type template struct MP_UNITS_STD_FMT::formatter, Char> : formatter, Char> { - template - auto format(const mp_units::cartesian_vector& v, Ctx& ctx) const + template + auto format(const mp_units::cartesian_vector& vector, FormatContext& ctx) const { - return format_to(ctx.out(), "[{}, {}, {}]", v[0], v[1], v[2]); + return format_to(ctx.out(), "[{}, {}, {}]", vector[0], vector[1], vector[2]); } }; #endif diff --git a/test/runtime/cartesian_tensor_test.cpp b/test/runtime/cartesian_tensor_test.cpp index d29247e9d6..798e0e8c10 100644 --- a/test/runtime/cartesian_tensor_test.cpp +++ b/test/runtime/cartesian_tensor_test.cpp @@ -28,7 +28,7 @@ import mp_units; #else #include #include -#include +#include #if MP_UNITS_HOSTED #include #include diff --git a/test/runtime/cartesian_vector_test.cpp b/test/runtime/cartesian_vector_test.cpp index 1ef5c37ea3..e826ba5fb9 100644 --- a/test/runtime/cartesian_vector_test.cpp +++ b/test/runtime/cartesian_vector_test.cpp @@ -27,155 +27,136 @@ import mp_units; #else #include -#if MP_UNITS_HOSTED #include #include #endif -#endif using namespace mp_units; using Catch::Matchers::WithinAbs; -TEST_CASE("cartesian_vector initialization and access", "[vector]") +TEST_CASE("cartesian_vector", "[vector]") { - SECTION("no arguments") - { - cartesian_vector v; - REQUIRE(v[0] == 0); - REQUIRE(v[1] == 0); - REQUIRE(v[2] == 0); - } - - SECTION("zero arguments") - { - cartesian_vector v{}; - REQUIRE(v[0] == 0); - REQUIRE(v[1] == 0); - REQUIRE(v[2] == 0); - } - - SECTION("one argument") - { - cartesian_vector v{1.0}; - REQUIRE(v[0] == 1.0); - REQUIRE(v[1] == 0); - REQUIRE(v[2] == 0); - } - - SECTION("two arguments") - { - cartesian_vector v{1.0, 2.0}; - REQUIRE(v[0] == 1.0); - REQUIRE(v[1] == 2.0); - REQUIRE(v[2] == 0); - } - - SECTION("all arguments") - { - cartesian_vector v{1.0, 2.0, 3.0}; - REQUIRE(v[0] == 1.0); - REQUIRE(v[1] == 2.0); - REQUIRE(v[2] == 3.0); - } - - SECTION("convertible arguments") - { - cartesian_vector v{1, 2, 3}; - REQUIRE(v[0] == 1.0); - REQUIRE(v[1] == 2.0); - REQUIRE(v[2] == 3.0); - } -} - -TEST_CASE("convertibility from another vector", "[vector]") -{ - cartesian_vector v1{1, 2, 3}; - - SECTION("construction") - { - cartesian_vector v2 = v1; - REQUIRE(v2[0] == 1.0); - REQUIRE(v2[1] == 2.0); - REQUIRE(v2[2] == 3.0); - } - - SECTION("assignment") - { - cartesian_vector v2{3.0, 2.0, 1.0}; - v2 = v1; - REQUIRE(v2[0] == 1.0); - REQUIRE(v2[1] == 2.0); - REQUIRE(v2[2] == 3.0); - } -} - -TEST_CASE("compound assignments", "[vector]") -{ - SECTION("operator+=") - { - cartesian_vector v1{1.0, 2.0, 3.0}; - cartesian_vector v2{4.0, 5.0, 6.0}; - v1 += v2; - REQUIRE(v1[0] == 5.0); - REQUIRE(v1[1] == 7.0); - REQUIRE(v1[2] == 9.0); - } - - SECTION("operator-=") - { - cartesian_vector v1{4.0, 5.0, 6.0}; - cartesian_vector v2{1.0, 2.0, 3.0}; - v1 -= v2; - REQUIRE(v1[0] == 3.0); - REQUIRE(v1[1] == 3.0); - REQUIRE(v1[2] == 3.0); - } - - SECTION("operator*=") - { - cartesian_vector v{1.0, 2.0, 3.0}; - v *= 2.0; - REQUIRE(v[0] == 2.0); - REQUIRE(v[1] == 4.0); - REQUIRE(v[2] == 6.0); - } - - SECTION("operator/=") - { - cartesian_vector v{2.0, 4.0, 6.0}; - v /= 2.0; - REQUIRE(v[0] == 1.0); - REQUIRE(v[1] == 2.0); - REQUIRE(v[2] == 3.0); - } -} - -TEST_CASE("binary ops + and -", "[vector]") -{ - SECTION("double + double") - { - cartesian_vector v1{1.0, 2.0, 3.0}; - cartesian_vector v2{4.0, 5.0, 6.0}; - cartesian_vector result = v1 + v2; - REQUIRE(result[0] == 5.0); - REQUIRE(result[1] == 7.0); - REQUIRE(result[2] == 9.0); - } - - SECTION("int - int") - { - cartesian_vector v1{4, 5, 6}; - cartesian_vector v2{1, 2, 3}; - cartesian_vector result = v1 - v2; - REQUIRE(result[0] == 3); - REQUIRE(result[1] == 3); - REQUIRE(result[2] == 3); - } -} - -TEST_CASE("elementwise modulo", "[vector]") -{ - SECTION("integral % integral") + SECTION("construction & access") + { + SECTION("value-initialization yields zeros") + { + cartesian_vector v{}; + REQUIRE(v[0] == 0); + REQUIRE(v[1] == 0); + REQUIRE(v[2] == 0); + } + SECTION("one argument -> [x,0,0]") + { + cartesian_vector v{1.0}; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 0.0); + REQUIRE(v[2] == 0.0); + } + SECTION("two arguments -> [x,y,0]") + { + cartesian_vector v{1.0, 2.0}; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 2.0); + REQUIRE(v[2] == 0.0); + } + SECTION("three arguments") + { + cartesian_vector v{1.0, 2.0, 3.0}; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 2.0); + REQUIRE(v[2] == 3.0); + } + SECTION("convertible arguments") + { + cartesian_vector v{1, 2, 3}; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 2.0); + REQUIRE(v[2] == 3.0); + } + } + + SECTION("convertibility from another vector") + { + cartesian_vector v1{1, 2, 3}; + + SECTION("construction from other rep") + { + cartesian_vector v2 = v1; + REQUIRE(v2[0] == 1.0); + REQUIRE(v2[1] == 2.0); + REQUIRE(v2[2] == 3.0); + } + SECTION("assignment from other rep") + { + cartesian_vector v2{3.0, 2.0, 1.0}; + v2 = v1; + REQUIRE(v2[0] == 1.0); + REQUIRE(v2[1] == 2.0); + REQUIRE(v2[2] == 3.0); + } + } + + SECTION("compound assignments") + { + SECTION("operator+=") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4.0, 5.0, 6.0}; + v1 += v2; + REQUIRE(v1[0] == 5.0); + REQUIRE(v1[1] == 7.0); + REQUIRE(v1[2] == 9.0); + } + SECTION("operator-=") + { + cartesian_vector v1{4.0, 5.0, 6.0}; + cartesian_vector v2{1.0, 2.0, 3.0}; + v1 -= v2; + REQUIRE(v1[0] == 3.0); + REQUIRE(v1[1] == 3.0); + REQUIRE(v1[2] == 3.0); + } + SECTION("operator*=") + { + cartesian_vector v{1.0, 2.0, 3.0}; + v *= 2.0; + REQUIRE(v[0] == 2.0); + REQUIRE(v[1] == 4.0); + REQUIRE(v[2] == 6.0); + } + SECTION("operator/=") + { + cartesian_vector v{2.0, 4.0, 6.0}; + v /= 2.0; + REQUIRE(v[0] == 1.0); + REQUIRE(v[1] == 2.0); + REQUIRE(v[2] == 3.0); + } + } + + SECTION("binary + and -") + { + SECTION("double + double") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4.0, 5.0, 6.0}; + auto r = v1 + v2; + REQUIRE(r[0] == 5.0); + REQUIRE(r[1] == 7.0); + REQUIRE(r[2] == 9.0); + } + SECTION("int - int") + { + cartesian_vector v1{4, 5, 6}; + cartesian_vector v2{1, 2, 3}; + auto r = v1 - v2; + REQUIRE(r[0] == 3); + REQUIRE(r[1] == 3); + REQUIRE(r[2] == 3); + } + } + + SECTION("elementwise modulo (integral only)") { cartesian_vector a{10, 11, 12}; cartesian_vector b{4, 5, 7}; @@ -185,112 +166,80 @@ TEST_CASE("elementwise modulo", "[vector]") REQUIRE(r[2] == 5); } - SECTION("floating uses fmod") - { - cartesian_vector a{5.5, 7.25, 9.0}; - cartesian_vector b{2.0, 2.50, 4.0}; - auto r = a % b; - REQUIRE_THAT(r[0], WithinAbs(1.5, 1e-12)); - REQUIRE_THAT(r[1], WithinAbs(2.25, 1e-12)); - REQUIRE_THAT(r[2], WithinAbs(1.0, 1e-12)); - } -} - -TEST_CASE("scalar multiply/divide", "[vector]") -{ - cartesian_vector v{1, 2, 3}; - - SECTION("v * s") - { - auto r = v * 2.0; - REQUIRE(r[0] == 2.0); - REQUIRE(r[1] == 4.0); - REQUIRE(r[2] == 6.0); - } - - SECTION("s * v") + SECTION("scalar multiply/divide") { - auto r = 2.0 * v; - REQUIRE(r[0] == 2.0); - REQUIRE(r[1] == 4.0); - REQUIRE(r[2] == 6.0); - } - - SECTION("v / s") + cartesian_vector v{1, 2, 3}; + SECTION("v * s") + { + auto r = v * 2.0; + REQUIRE(r[0] == 2.0); + REQUIRE(r[1] == 4.0); + REQUIRE(r[2] == 6.0); + } + SECTION("s * v") + { + auto r = 2.0 * v; + REQUIRE(r[0] == 2.0); + REQUIRE(r[1] == 4.0); + REQUIRE(r[2] == 6.0); + } + SECTION("v / s") + { + auto r = v / 2.0; + REQUIRE(r[0] == 0.5); + REQUIRE(r[1] == 1.0); + REQUIRE(r[2] == 1.5); + } + } + + SECTION("equality and inequality") { - auto r = v / 2.0; - REQUIRE(r[0] == 0.5); - REQUIRE(r[1] == 1.0); - REQUIRE(r[2] == 1.5); + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{1, 2, 3}; + cartesian_vector v3{1.1, 2.0, 3.0}; + REQUIRE(v1 == v2); + REQUIRE(v1 != v3); } -} -TEST_CASE("equality and inequality", "[vector]") -{ - cartesian_vector v1{1.0, 2.0, 3.0}; - cartesian_vector v2{1, 2, 3}; - cartesian_vector v3{1.1, 2.0, 3.0}; - REQUIRE(v1 == v2); - REQUIRE(v1 != v3); -} - -TEST_CASE("dot and cross (numeric level)", "[vector]") -{ - cartesian_vector a{1, 2, 3}; - cartesian_vector b{4, 5, 6}; - - SECTION("scalar_product and alias dot") + SECTION("scalar_product and vector_product") { + cartesian_vector a{1, 2, 3}; + cartesian_vector b{4, 5, 6}; REQUIRE(scalar_product(a, b) == 32); - REQUIRE(dot(a, b) == 32); - } - - SECTION("vector_product and alias cross") - { auto r = vector_product(a, b); REQUIRE(r[0] == -3); REQUIRE(r[1] == 6); REQUIRE(r[2] == -3); - - auto r2 = cross(a, b); - REQUIRE(r2[0] == -3); - REQUIRE(r2[1] == 6); - REQUIRE(r2[2] == -3); } -} - -TEST_CASE("magnitude and unit (floating types)", "[vector]") -{ - cartesian_vector v{3.0, 4.0, 0.0}; - REQUIRE_THAT(v.magnitude(), WithinAbs(5.0, 1e-12)); - auto u = v.unit(); - REQUIRE_THAT(u.magnitude(), WithinAbs(1.0, 1e-12)); - REQUIRE_THAT(u[0], WithinAbs(3.0 / 5.0, 1e-12)); - REQUIRE_THAT(u[1], WithinAbs(4.0 / 5.0, 1e-12)); - REQUIRE_THAT(u[2], WithinAbs(0.0, 1e-12)); -} + SECTION("magnitude and unit (floating types)") + { + cartesian_vector v{3.0, 4.0, 0.0}; + REQUIRE_THAT(v.magnitude(), WithinAbs(5.0, 1e-12)); + auto u = v.unit(); + REQUIRE_THAT(u.magnitude(), WithinAbs(1.0, 1e-12)); + REQUIRE_THAT(u[0], WithinAbs(3.0 / 5.0, 1e-12)); + REQUIRE_THAT(u[1], WithinAbs(4.0 / 5.0, 1e-12)); + REQUIRE_THAT(u[2], WithinAbs(0.0, 1e-12)); + } #if MP_UNITS_HOSTED -TEST_CASE("text output (ostream + fmt)", "[vector][fmt][ostream]") -{ - cartesian_vector v{1, 2, 3}; - - // iostream - std::ostringstream os; - os << v; - CHECK(os.str() == "[1, 2, 3]"); - - // fmt (via MP_UNITS_STD_FMT wrapper) - CHECK(MP_UNITS_STD_FMT::format("{}", v) == os.str()); -} + SECTION("text output (ostream + fmt)") + { + cartesian_vector v{1, 2, 3}; + std::ostringstream os; + os << v; + CHECK(os.str() == "[1, 2, 3]"); + CHECK(MP_UNITS_STD_FMT::format("{}", v) == os.str()); + } #endif -TEST_CASE("constexpr basics", "[vector][constexpr]") -{ - constexpr cartesian_vector a{1, 2, 3}; - constexpr cartesian_vector b{4, 5, 6}; - constexpr auto c = a + b; - static_assert(c._coordinates_[0] == 5 && c._coordinates_[1] == 7 && c._coordinates_[2] == 9); - (void)c; + SECTION("constexpr basics") + { + constexpr cartesian_vector a{1, 2, 3}; + constexpr cartesian_vector b{4, 5, 6}; + constexpr auto c = a + b; + static_assert(c._coordinates_[0] == 5 && c._coordinates_[1] == 7 && c._coordinates_[2] == 9); + (void)c; + } } From 26c88fe8efa02d15570a8e848eb71d0062c3f960 Mon Sep 17 00:00:00 2001 From: Julia Smerdel <97158770+Jullija@users.noreply.github.com> Date: Sun, 2 Nov 2025 14:57:23 +0000 Subject: [PATCH 08/18] fix tensor and vector after review --- src/core/include/mp-units/cartesian_tensor.h | 164 ++++++------- src/core/include/mp-units/cartesian_vector.h | 234 +++++++++---------- test/runtime/cartesian_tensor_test.cpp | 10 - test/runtime/cartesian_vector_test.cpp | 3 +- test/static/quantity_test.cpp | 3 +- 5 files changed, 180 insertions(+), 234 deletions(-) diff --git a/src/core/include/mp-units/cartesian_tensor.h b/src/core/include/mp-units/cartesian_tensor.h index 7a657836ac..550812de73 100644 --- a/src/core/include/mp-units/cartesian_tensor.h +++ b/src/core/include/mp-units/cartesian_tensor.h @@ -24,7 +24,7 @@ #include #include -#include // for matvec/outer_numeric +#include #include #include @@ -48,104 +48,22 @@ import std; namespace mp_units { -// Forward declaration (exported) MP_UNITS_EXPORT template class cartesian_tensor; -// ================================ tensor rep ================================ - MP_UNITS_EXPORT template class cartesian_tensor { static_assert(R >= 1 && R <= 3 && C >= 1 && C <= 3, "cartesian_tensor supports sizes up to 3x3"); public: + T _data_[R * C]; using value_type = T; static constexpr std::size_t rows_v = R; static constexpr std::size_t cols_v = C; - T _data_[R * C]{}; - - cartesian_tensor() = default; - cartesian_tensor(const cartesian_tensor&) = default; - cartesian_tensor(cartesian_tensor&&) = default; - cartesian_tensor& operator=(const cartesian_tensor&) = default; - cartesian_tensor& operator=(cartesian_tensor&&) = default; - - // fill ctor (row-major R*C) - template - requires(sizeof...(Args) == R * C) && (... && std::constructible_from) - constexpr explicit(!(... && std::convertible_to)) cartesian_tensor(Args&&... args) : - _data_{static_cast(std::forward(args))...} - { - } - - // element access [[nodiscard]] constexpr T& operator()(std::size_t r, std::size_t c) { return _data_[r * C + c]; } [[nodiscard]] constexpr const T& operator()(std::size_t r, std::size_t c) const { return _data_[r * C + c]; } - // elementwise +, - - template - requires requires(const T& t, const U& u) { t + u; } - [[nodiscard]] friend constexpr auto operator+(const cartesian_tensor& A, const cartesian_tensor& B) - { - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(A._data_[i]) + static_cast(B._data_[i]); - return Rm; - } - - template - requires requires(const T& t, const U& u) { t - u; } - [[nodiscard]] friend constexpr auto operator-(const cartesian_tensor& A, const cartesian_tensor& B) - { - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(A._data_[i]) - static_cast(B._data_[i]); - return Rm; - } - - // elementwise % (integral uses %, floating uses fmod) - template - requires(requires(const T& t, const U& u) { t % u; }) || (std::floating_point && std::floating_point) - [[nodiscard]] friend constexpr auto operator%(const cartesian_tensor& A, const cartesian_tensor& B) - { - using CT = std::common_type_t; - cartesian_tensor Rm{}; - if constexpr (std::floating_point || std::floating_point) { - using std::fmod; - for (std::size_t i = 0; i < R * C; ++i) - Rm._data_[i] = - static_cast(fmod(static_cast(A._data_[i]), static_cast(B._data_[i]))); - } else { - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(A._data_[i] % B._data_[i]); - } - return Rm; - } - - // scalar *, / (constrained to numeric scalars to avoid recursive constraints) - template - [[nodiscard]] friend constexpr auto operator*(const cartesian_tensor& tensor, const S& scalar) - { - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(tensor._data_[i]) * static_cast(scalar); - return Rm; - } - - template - [[nodiscard]] friend constexpr auto operator*(const S& scalar, const cartesian_tensor& tensor) - { - return tensor * scalar; - } - - template - [[nodiscard]] friend constexpr auto operator/(const cartesian_tensor& tensor, const S& scalar) - { - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(tensor._data_[i]) / static_cast(scalar); - return Rm; - } #if MP_UNITS_HOSTED friend std::ostream& operator<<(std::ostream& os, const cartesian_tensor& A) @@ -163,13 +81,7 @@ class cartesian_tensor { #endif }; -// Register as tensor rep -template -inline constexpr bool is_tensor> = true; - -// ======================== numeric helpers (no units) ======================== -// Matrix × Matrix template [[nodiscard]] constexpr auto matmul(const cartesian_tensor& A, const cartesian_tensor& B) { @@ -184,45 +96,99 @@ template return Rm; } -// Matrix × Vector (3×3) template -[[nodiscard]] constexpr auto matvec(const cartesian_tensor& M, const cartesian_vector& x) +[[nodiscard]] constexpr auto matvec(const cartesian_tensor& tensor, const cartesian_vector& vector) { using CT = std::common_type_t; cartesian_vector y{}; for (std::size_t r = 0; r < 3; ++r) { CT acc{}; - for (std::size_t c = 0; c < 3; ++c) acc += static_cast(M(r, c)) * static_cast(x[c]); + for (std::size_t c = 0; c < 3; ++c) acc += static_cast(tensor(r, c)) * static_cast(vector[c]); y[r] = acc; } return y; } -// Double contraction: A : B template [[nodiscard]] constexpr auto double_contraction(const cartesian_tensor& A, const cartesian_tensor& B) { using CT = std::common_type_t; CT acc{}; for (std::size_t i = 0; i < R * C; ++i) acc += static_cast(A._data_[i]) * static_cast(B._data_[i]); - return acc; // numeric scalar + return acc; } -// Outer product: vector ⊗ vector -> 3x3 matrix + template -[[nodiscard]] constexpr auto outer_numeric(const cartesian_vector& a, const cartesian_vector& b) +[[nodiscard]] constexpr auto outer_numeric(const cartesian_vector& lhs, const cartesian_vector& rhs) { using CT = std::common_type_t; cartesian_tensor Rm{}; for (std::size_t i = 0; i < 3; ++i) - for (std::size_t j = 0; j < 3; ++j) Rm(i, j) = static_cast(a[i]) * static_cast(b[j]); + for (std::size_t j = 0; j < 3; ++j) Rm(i, j) = static_cast(lhs[i]) * static_cast(rhs[j]); + return Rm; +} + +template + requires requires(const T& t, const U& u) { t + u; } +[[nodiscard]] constexpr auto operator+(const cartesian_tensor& A, const cartesian_tensor& B) +{ + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(A._data_[i]) + static_cast(B._data_[i]); + return Rm; +} + +template + requires requires(const T& t, const U& u) { t - u; } +[[nodiscard]] constexpr auto operator-(const cartesian_tensor& A, const cartesian_tensor& B) +{ + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(A._data_[i]) - static_cast(B._data_[i]); + return Rm; +} + +template + requires(!treat_as_floating_point && !treat_as_floating_point && requires(const T& t, const U& u) { t % u; }) +[[nodiscard]] constexpr auto operator%(const cartesian_tensor& A, const cartesian_tensor& B) +{ + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(A._data_[i] % B._data_[i]); + return Rm; +} + +template + requires requires(const T& t, const S& s) { t * s; } +[[nodiscard]] constexpr auto operator*(const cartesian_tensor& tensor, const S& scalar) +{ + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(tensor._data_[i]) * static_cast(scalar); + return Rm; +} + +template + requires requires(const S& s, const U& u) { s * u; } +[[nodiscard]] constexpr auto operator*(const S& scalar, const cartesian_tensor& tensor) +{ + return tensor * scalar; +} + +template + requires requires(const T& t, const S& s) { t / s; } +[[nodiscard]] constexpr auto operator/(const cartesian_tensor& tensor, const S& scalar) +{ + using CT = std::common_type_t; + cartesian_tensor Rm{}; + for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(tensor._data_[i]) / static_cast(scalar); return Rm; } } // namespace mp_units #if MP_UNITS_HOSTED -// fmt/format (or std::format) support template struct MP_UNITS_STD_FMT::formatter, Char> : formatter, Char> { diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index 85f5541886..fa0b0b85ea 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -26,6 +26,7 @@ #include #include #include +#include #if MP_UNITS_HOSTED #include @@ -39,6 +40,7 @@ import std; #include #include #include +#include #if MP_UNITS_HOSTED #include #endif @@ -50,130 +52,13 @@ namespace mp_units { MP_UNITS_EXPORT template class cartesian_vector; -namespace detail { - -struct cartesian_vector_iface { - template - requires requires(const T& t, const U& u) { t + u; } - [[nodiscard]] friend constexpr auto operator+(const cartesian_vector& lhs, const cartesian_vector& rhs) - { - return ::mp_units::cartesian_vector{lhs._coordinates_[0] + rhs._coordinates_[0], - lhs._coordinates_[1] + rhs._coordinates_[1], - lhs._coordinates_[2] + rhs._coordinates_[2]}; - } - - template - requires requires(const T& t, const U& u) { t - u; } - [[nodiscard]] friend constexpr auto operator-(const cartesian_vector& lhs, const cartesian_vector& rhs) - { - return ::mp_units::cartesian_vector{lhs._coordinates_[0] - rhs._coordinates_[0], - lhs._coordinates_[1] - rhs._coordinates_[1], - lhs._coordinates_[2] - rhs._coordinates_[2]}; - } - - template - requires(!treat_as_floating_point && !treat_as_floating_point && requires(const T& t, const U& u) { t % u; }) - [[nodiscard]] friend constexpr auto operator%(const cartesian_vector& lhs, const cartesian_vector& rhs) - { - using CT = std::common_type_t; - return ::mp_units::cartesian_vector{static_cast(lhs._coordinates_[0] % rhs._coordinates_[0]), - static_cast(lhs._coordinates_[1] % rhs._coordinates_[1]), - static_cast(lhs._coordinates_[2] % rhs._coordinates_[2])}; - } - - template - requires requires(const T& t, const S& s) { t * s; } - [[nodiscard]] friend constexpr auto operator*(const cartesian_vector& vector, const S& scalar) - { - return ::mp_units::cartesian_vector{vector._coordinates_[0] * scalar, vector._coordinates_[1] * scalar, - vector._coordinates_[2] * scalar}; - } - - template - requires requires(const S& s, const U& u) { s * u; } - [[nodiscard]] friend constexpr auto operator*(const S& scalar, const cartesian_vector& vector) - { - return vector * scalar; - } - - template - requires requires(const T& t, const S& s) { t / s; } - [[nodiscard]] friend constexpr auto operator/(const cartesian_vector& vector, const S& scalar) - { - return ::mp_units::cartesian_vector{vector._coordinates_[0] / scalar, vector._coordinates_[1] / scalar, - vector._coordinates_[2] / scalar}; - } - - template U> - [[nodiscard]] friend constexpr bool operator==(const cartesian_vector& lhs, const cartesian_vector& rhs) - { - return lhs._coordinates_[0] == rhs._coordinates_[0] && lhs._coordinates_[1] == rhs._coordinates_[1] && - lhs._coordinates_[2] == rhs._coordinates_[2]; - } - - template - requires requires(const T& t, const U& u, decltype(t * u) v) { - t * u; - v + v; - } - [[nodiscard]] friend constexpr auto scalar_product(const cartesian_vector& lhs, const cartesian_vector& rhs) - { - return lhs._coordinates_[0] * rhs._coordinates_[0] + lhs._coordinates_[1] * rhs._coordinates_[1] + - lhs._coordinates_[2] * rhs._coordinates_[2]; - } - - template - requires requires(const T& t, const U& u, decltype(t * u) v) { - t * u; - v - v; - } - [[nodiscard]] friend constexpr auto vector_product(const cartesian_vector& lhs, const cartesian_vector& rhs) - { - return ::mp_units::cartesian_vector{ - lhs._coordinates_[1] * rhs._coordinates_[2] - lhs._coordinates_[2] * rhs._coordinates_[1], - lhs._coordinates_[2] * rhs._coordinates_[0] - lhs._coordinates_[0] * rhs._coordinates_[2], - lhs._coordinates_[0] * rhs._coordinates_[1] - lhs._coordinates_[1] * rhs._coordinates_[0]}; - } -}; - -} // namespace detail - MP_UNITS_EXPORT template -class cartesian_vector : public detail::cartesian_vector_iface { +class cartesian_vector { public: - // public members required to satisfy structural type requirements :-( + // NOTE: This type is intentionally an aggregate. T _coordinates_[3]; using value_type = T; - - cartesian_vector() = default; - cartesian_vector(const cartesian_vector&) = default; - cartesian_vector(cartesian_vector&&) = default; - cartesian_vector& operator=(const cartesian_vector&) = default; - cartesian_vector& operator=(cartesian_vector&&) = default; - - template - requires(sizeof...(Args) <= 3) && (... && std::constructible_from) - constexpr explicit(!(... && std::convertible_to)) cartesian_vector(Args&&... args) : - _coordinates_{static_cast(std::forward(args))...} - { - } - - template - requires std::constructible_from - constexpr explicit(!std::convertible_to) cartesian_vector(const cartesian_vector& other) : - _coordinates_{static_cast(other[0]), static_cast(other[1]), static_cast(other[2])} - { - } - - template - requires std::constructible_from - constexpr explicit(!std::convertible_to) cartesian_vector(cartesian_vector&& other) : - _coordinates_{static_cast(std::move(other[0])), static_cast(std::move(other[1])), - static_cast(std::move(other[2]))} - { - } - template U> constexpr cartesian_vector& operator=(const cartesian_vector& other) { @@ -231,7 +116,9 @@ class cartesian_vector : public detail::cartesian_vector_iface { } template - requires requires(T& t, const U& u) { t -= u; } + requires requires(T& t, const U& u) { + { t -= u } -> std::same_as; + } constexpr cartesian_vector& operator-=(const cartesian_vector& other) { _coordinates_[0] -= other[0]; @@ -284,11 +171,112 @@ template requires(sizeof...(Args) <= 2) && requires { typename std::common_type_t; } cartesian_vector(Arg, Args...) -> cartesian_vector>; -template -inline constexpr bool is_vector> = true; - +template + requires requires(const T& t, const U& u) { t + u; } +[[nodiscard]] constexpr auto operator+(const cartesian_vector& lhs, const cartesian_vector& rhs) +{ + return ::mp_units::cartesian_vector{lhs._coordinates_[0] + rhs._coordinates_[0], + lhs._coordinates_[1] + rhs._coordinates_[1], + lhs._coordinates_[2] + rhs._coordinates_[2]}; +} + +template + requires requires(const T& t, const U& u) { t - u; } +[[nodiscard]] constexpr auto operator-(const cartesian_vector& lhs, const cartesian_vector& rhs) +{ + return ::mp_units::cartesian_vector{lhs._coordinates_[0] - rhs._coordinates_[0], + lhs._coordinates_[1] - rhs._coordinates_[1], + lhs._coordinates_[2] - rhs._coordinates_[2]}; +} + +template + requires(!treat_as_floating_point && !treat_as_floating_point && requires(const T& t, const U& u) { t % u; }) +[[nodiscard]] constexpr auto operator%(const cartesian_vector& lhs, const cartesian_vector& rhs) +{ + using CT = std::common_type_t; + return ::mp_units::cartesian_vector{static_cast(lhs._coordinates_[0] % rhs._coordinates_[0]), + static_cast(lhs._coordinates_[1] % rhs._coordinates_[1]), + static_cast(lhs._coordinates_[2] % rhs._coordinates_[2])}; +} + +template + requires requires(const T& t, const S& s) { t * s; } +[[nodiscard]] constexpr auto operator*(const cartesian_vector& vector, const S& scalar) +{ + return ::mp_units::cartesian_vector{vector._coordinates_[0] * scalar, vector._coordinates_[1] * scalar, + vector._coordinates_[2] * scalar}; +} + +template + requires requires(const S& s, const U& u) { s * u; } +[[nodiscard]] constexpr auto operator*(const S& scalar, const cartesian_vector& vector) +{ + return vector * scalar; +} + +template + requires requires(const T& t, const S& s) { t / s; } +[[nodiscard]] constexpr auto operator/(const cartesian_vector& vector, const S& scalar) +{ + return ::mp_units::cartesian_vector{vector._coordinates_[0] / scalar, vector._coordinates_[1] / scalar, + vector._coordinates_[2] / scalar}; +} + +template U> +[[nodiscard]] constexpr bool operator==(const cartesian_vector& lhs, const cartesian_vector& rhs) +{ + return lhs._coordinates_[0] == rhs._coordinates_[0] && lhs._coordinates_[1] == rhs._coordinates_[1] && + lhs._coordinates_[2] == rhs._coordinates_[2]; +} + +template + requires requires(const T& t, const U& u, decltype(t * u) v) { + t * u; + v + v; + } +[[nodiscard]] constexpr auto scalar_product(const cartesian_vector& lhs, const cartesian_vector& rhs) +{ + return lhs._coordinates_[0] * rhs._coordinates_[0] + lhs._coordinates_[1] * rhs._coordinates_[1] + + lhs._coordinates_[2] * rhs._coordinates_[2]; +} + +template + requires requires(const T& t, const U& u, decltype(t * u) v) { + t * u; + v - v; + } +[[nodiscard]] constexpr auto vector_product(const cartesian_vector& lhs, const cartesian_vector& rhs) +{ + return ::mp_units::cartesian_vector{ + lhs._coordinates_[1] * rhs._coordinates_[2] - lhs._coordinates_[2] * rhs._coordinates_[1], + lhs._coordinates_[2] * rhs._coordinates_[0] - lhs._coordinates_[0] * rhs._coordinates_[2], + lhs._coordinates_[0] * rhs._coordinates_[1] - lhs._coordinates_[1] * rhs._coordinates_[0]}; +} + +template +inline constexpr bool treat_as_floating_point> = treat_as_floating_point; + +template +inline constexpr bool is_value_preserving> = is_value_preserving; } // namespace mp_units +namespace std { +template +struct common_type, mp_units::cartesian_vector> { + using type = mp_units::cartesian_vector>; +}; + +template +struct common_type, U> { + using type = mp_units::cartesian_vector>; +}; + +template +struct common_type> { + using type = mp_units::cartesian_vector>; +}; +} // namespace std + #if MP_UNITS_HOSTED // TODO use parse and use formatter for the underlying type template diff --git a/test/runtime/cartesian_tensor_test.cpp b/test/runtime/cartesian_tensor_test.cpp index 798e0e8c10..ac1c8075ea 100644 --- a/test/runtime/cartesian_tensor_test.cpp +++ b/test/runtime/cartesian_tensor_test.cpp @@ -83,16 +83,6 @@ TEST_CASE("elementwise modulo", "[tensor]") REQUIRE(R(1, 1) == 4); REQUIRE(R(1, 2) == 1); } - - SECTION("floating uses fmod, result in common_type") - { - cartesian_tensor A{5.5, 7.25, 9.0}; - cartesian_tensor B{2.0, 2.50, 4.0}; - auto R = A % B; - REQUIRE_THAT(R(0, 0), WithinAbs(1.5, 1e-12)); - REQUIRE_THAT(R(0, 1), WithinAbs(2.25, 1e-12)); - REQUIRE_THAT(R(0, 2), WithinAbs(1.0, 1e-12)); - } } TEST_CASE("scalar multiply/divide", "[tensor]") diff --git a/test/runtime/cartesian_vector_test.cpp b/test/runtime/cartesian_vector_test.cpp index e826ba5fb9..d80ed6d612 100644 --- a/test/runtime/cartesian_vector_test.cpp +++ b/test/runtime/cartesian_vector_test.cpp @@ -81,7 +81,8 @@ TEST_CASE("cartesian_vector", "[vector]") SECTION("construction from other rep") { - cartesian_vector v2 = v1; + cartesian_vector v2{}; + v2 = v1; REQUIRE(v2[0] == 1.0); REQUIRE(v2[1] == 2.0); REQUIRE(v2[2] == 3.0); diff --git a/test/static/quantity_test.cpp b/test/static/quantity_test.cpp index 49e9082cd9..3773662c4a 100644 --- a/test/static/quantity_test.cpp +++ b/test/static/quantity_test.cpp @@ -303,7 +303,8 @@ static_assert(std::constructible_from, quantity>, cartesian_vector>); static_assert(std::constructible_from, quantity>>); static_assert(!std::convertible_to, cartesian_vector>); -static_assert(std::constructible_from, quantity>); +static_assert(std::is_aggregate_v>); + #endif static_assert(!std::convertible_to, double>); From 00b2e74bd6945f5bba2921b0b99857d756f89a53 Mon Sep 17 00:00:00 2001 From: Julia Smerdel <97158770+Jullija@users.noreply.github.com> Date: Sun, 9 Nov 2025 15:10:19 +0000 Subject: [PATCH 09/18] fix tensor test --- test/runtime/cartesian_tensor_test.cpp | 226 ++++++++++++------------- 1 file changed, 111 insertions(+), 115 deletions(-) diff --git a/test/runtime/cartesian_tensor_test.cpp b/test/runtime/cartesian_tensor_test.cpp index ac1c8075ea..5d93b3ed62 100644 --- a/test/runtime/cartesian_tensor_test.cpp +++ b/test/runtime/cartesian_tensor_test.cpp @@ -38,40 +38,39 @@ import mp_units; using namespace mp_units; using Catch::Matchers::WithinAbs; -TEST_CASE("cartesian_tensor construction and access", "[tensor]") +TEST_CASE("cartesian_tensor — core", "[tensor]") { - // 2x3 fill-ctor order is row-major - cartesian_tensor A{1, 2, 3, 4, 5, 6}; - REQUIRE(A(0, 0) == 1); - REQUIRE(A(0, 1) == 2); - REQUIRE(A(0, 2) == 3); - REQUIRE(A(1, 0) == 4); - REQUIRE(A(1, 1) == 5); - REQUIRE(A(1, 2) == 6); -} + SECTION("construction and access (row-major fill)") + { + cartesian_tensor A{1, 2, 3, 4, 5, 6}; + REQUIRE(A(0, 0) == 1); + REQUIRE(A(0, 1) == 2); + REQUIRE(A(0, 2) == 3); + REQUIRE(A(1, 0) == 4); + REQUIRE(A(1, 1) == 5); + REQUIRE(A(1, 2) == 6); + } -TEST_CASE("elementwise + and - with common_type", "[tensor]") -{ - cartesian_tensor A{1, 2, 3, 4}; - cartesian_tensor B{0.5, 1.5, 2.5, 3.5}; - - auto S = A + B; - static_assert(std::is_same_v>); - REQUIRE(S(0, 0) == 1.5); - REQUIRE(S(0, 1) == 3.5); - REQUIRE(S(1, 0) == 5.5); - REQUIRE(S(1, 1) == 7.5); - - auto D = B - A; - REQUIRE_THAT(D(0, 0), WithinAbs(-0.5, 1e-12)); - REQUIRE_THAT(D(0, 1), WithinAbs(-0.5, 1e-12)); - REQUIRE_THAT(D(1, 0), WithinAbs(-0.5, 1e-12)); - REQUIRE_THAT(D(1, 1), WithinAbs(-0.5, 1e-12)); -} + SECTION("elementwise + and - with common_type") + { + cartesian_tensor A{1, 2, 3, 4}; + cartesian_tensor B{0.5, 1.5, 2.5, 3.5}; + + auto S = A + B; + static_assert(std::is_same_v>); + REQUIRE(S(0, 0) == 1.5); + REQUIRE(S(0, 1) == 3.5); + REQUIRE(S(1, 0) == 5.5); + REQUIRE(S(1, 1) == 7.5); + + auto D = B - A; + REQUIRE_THAT(D(0, 0), WithinAbs(-0.5, 1e-12)); + REQUIRE_THAT(D(0, 1), WithinAbs(-0.5, 1e-12)); + REQUIRE_THAT(D(1, 0), WithinAbs(-0.5, 1e-12)); + REQUIRE_THAT(D(1, 1), WithinAbs(-0.5, 1e-12)); + } -TEST_CASE("elementwise modulo", "[tensor]") -{ - SECTION("integral % integral") + SECTION("elementwise modulo (integral)") { cartesian_tensor A{10, 11, 12, 13, 14, 15}; cartesian_tensor B{4, 5, 7, 4, 5, 7}; @@ -83,97 +82,94 @@ TEST_CASE("elementwise modulo", "[tensor]") REQUIRE(R(1, 1) == 4); REQUIRE(R(1, 2) == 1); } -} -TEST_CASE("scalar multiply/divide", "[tensor]") -{ - cartesian_tensor A{1, 2, 3, 4}; - - auto T1 = A * 2.0; - REQUIRE(T1(0, 0) == 2.0); - REQUIRE(T1(0, 1) == 4.0); - REQUIRE(T1(1, 0) == 6.0); - REQUIRE(T1(1, 1) == 8.0); - - auto T2 = 2.0 * A; - REQUIRE(T2(0, 0) == 2.0); - REQUIRE(T2(0, 1) == 4.0); - REQUIRE(T2(1, 0) == 6.0); - REQUIRE(T2(1, 1) == 8.0); - - auto T3 = A / 2.0; - REQUIRE(T3(0, 0) == 0.5); - REQUIRE(T3(0, 1) == 1.0); - REQUIRE(T3(1, 0) == 1.5); - REQUIRE(T3(1, 1) == 2.0); -} + SECTION("scalar multiply/divide") + { + cartesian_tensor A{1, 2, 3, 4}; + + auto T1 = A * 2.0; + REQUIRE(T1(0, 0) == 2.0); + REQUIRE(T1(0, 1) == 4.0); + REQUIRE(T1(1, 0) == 6.0); + REQUIRE(T1(1, 1) == 8.0); + + auto T2 = 2.0 * A; + REQUIRE(T2(0, 0) == 2.0); + REQUIRE(T2(0, 1) == 4.0); + REQUIRE(T2(1, 0) == 6.0); + REQUIRE(T2(1, 1) == 8.0); + + auto T3 = A / 2.0; + REQUIRE(T3(0, 0) == 0.5); + REQUIRE(T3(0, 1) == 1.0); + REQUIRE(T3(1, 0) == 1.5); + REQUIRE(T3(1, 1) == 2.0); + } -TEST_CASE("matmul (R×K) * (K×C) -> (R×C)", "[tensor]") -{ - cartesian_tensor A{1, 2, 3, 4, 5, 6}; - cartesian_tensor B{7, 8, 9, 10, 11, 12}; - auto C = matmul(A, B); // 2x2 - - static_assert(std::is_same_v>); - REQUIRE(C(0, 0) == 58); - REQUIRE(C(0, 1) == 64); - REQUIRE(C(1, 0) == 139); - REQUIRE(C(1, 1) == 154); -} + SECTION("matmul (R×K) * (K×C) -> (R×C)") + { + cartesian_tensor A{1, 2, 3, 4, 5, 6}; + cartesian_tensor B{7, 8, 9, 10, 11, 12}; + auto C = matmul(A, B); + + static_assert(std::is_same_v>); + REQUIRE(C(0, 0) == 58); + REQUIRE(C(0, 1) == 64); + REQUIRE(C(1, 0) == 139); + REQUIRE(C(1, 1) == 154); + } -TEST_CASE("matvec (3x3) * vector -> vector", "[tensor][vector]") -{ - cartesian_tensor M{1, 2, 3, 0, 1, 4, 5, 6, 0}; - cartesian_vector x{1, 2, 3}; - auto y = matvec(M, x); - REQUIRE_THAT(y[0], WithinAbs(14.0, 1e-12)); // 1*1 + 2*2 + 3*3 - REQUIRE_THAT(y[1], WithinAbs(14.0, 1e-12)); // 0*1 + 1*2 + 4*3 - REQUIRE_THAT(y[2], WithinAbs(17.0, 1e-12)); // 5*1 + 6*2 + 0*3 -} + SECTION("matvec (3x3) * vector -> vector") + { + cartesian_tensor M{1, 2, 3, 0, 1, 4, 5, 6, 0}; + cartesian_vector x{1, 2, 3}; + auto y = matvec(M, x); + REQUIRE_THAT(y[0], WithinAbs(14.0, 1e-12)); // 1*1 + 2*2 + 3*3 + REQUIRE_THAT(y[1], WithinAbs(14.0, 1e-12)); // 0*1 + 1*2 + 4*3 + REQUIRE_THAT(y[2], WithinAbs(17.0, 1e-12)); // 5*1 + 6*2 + 0*3 + } -TEST_CASE("double contraction A : B", "[tensor]") -{ - cartesian_tensor A{1, 2, 3, 4}; - cartesian_tensor B{5, 6, 7, 8}; - // 1*5 + 2*6 + 3*7 + 4*8 = 70 - auto s = double_contraction(A, B); - REQUIRE(s == 70); -} + SECTION("double contraction A : B") + { + cartesian_tensor A{1, 2, 3, 4}; + cartesian_tensor B{5, 6, 7, 8}; + auto s = double_contraction(A, B); // 1*5 + 2*6 + 3*7 + 4*8 + REQUIRE(s == 70); + } -TEST_CASE("outer_numeric (vector ⊗ vector)", "[tensor][vector]") -{ - cartesian_vector a{1, 2, 3}; - cartesian_vector b{4, 5, 6}; - auto T = outer_numeric(a, b); // 3x3 - REQUIRE(T(0, 0) == 4); - REQUIRE(T(0, 1) == 5); - REQUIRE(T(0, 2) == 6); - REQUIRE(T(1, 0) == 8); - REQUIRE(T(1, 1) == 10); - REQUIRE(T(1, 2) == 12); - REQUIRE(T(2, 0) == 12); - REQUIRE(T(2, 1) == 15); - REQUIRE(T(2, 2) == 18); -} + SECTION("outer_numeric (vector ⊗ vector)") + { + cartesian_vector a{1, 2, 3}; + cartesian_vector b{4, 5, 6}; + auto T = outer_numeric(a, b); + REQUIRE(T(0, 0) == 4); + REQUIRE(T(0, 1) == 5); + REQUIRE(T(0, 2) == 6); + REQUIRE(T(1, 0) == 8); + REQUIRE(T(1, 1) == 10); + REQUIRE(T(1, 2) == 12); + REQUIRE(T(2, 0) == 12); + REQUIRE(T(2, 1) == 15); + REQUIRE(T(2, 2) == 18); + } #if MP_UNITS_HOSTED -TEST_CASE("text output (ostream + fmt)", "[tensor][fmt][ostream]") -{ - cartesian_tensor A{1, 2, 3, 4}; - - std::ostringstream os; - os << A; - CHECK(os.str() == "[[1, 2]\n [3, 4]]"); - - CHECK(MP_UNITS_STD_FMT::format("{}", A) == os.str()); -} + SECTION("text output (ostream + fmt)") + { + cartesian_tensor A{1, 2, 3, 4}; + std::ostringstream os; + os << A; + CHECK(os.str() == "[[1, 2]\n [3, 4]]"); + CHECK(MP_UNITS_STD_FMT::format("{}", A) == os.str()); + } #endif -TEST_CASE("constexpr basics", "[tensor][constexpr]") -{ - constexpr cartesian_tensor A{1, 2, 3}; - constexpr cartesian_tensor B{4, 5, 6}; - constexpr auto C = A + B; - static_assert(C(0, 0) == 5 && C(0, 1) == 7 && C(0, 2) == 9); - (void)C; + SECTION("constexpr basics") + { + constexpr cartesian_tensor A{1, 2, 3}; + constexpr cartesian_tensor B{4, 5, 6}; + constexpr auto C = A + B; + static_assert(C(0, 0) == 5 && C(0, 1) == 7 && C(0, 2) == 9); + (void)C; + } } From 59f9298706905441dd4bbe9a9c43c2532599e2a0 Mon Sep 17 00:00:00 2001 From: Julia Smerdel Date: Thu, 20 Nov 2025 18:36:53 +0100 Subject: [PATCH 10/18] fix pipeline by adding export --- src/core/CMakeLists.txt | 1 + src/core/include/mp-units/cartesian_tensor.h | 30 +++++++------ src/core/include/mp-units/cartesian_vector.h | 47 +++++++------------- test/runtime/cartesian_tensor_test.cpp | 8 ++-- test/runtime/cartesian_vector_test.cpp | 26 ++++++----- 5 files changed, 49 insertions(+), 63 deletions(-) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index abec497a60..aaffd7c7bf 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -102,6 +102,7 @@ if(NOT ${projectPrefix}API_FREESTANDING) include/mp-units/bits/requires_hosted.h include/mp-units/ext/format.h include/mp-units/cartesian_vector.h + include/mp-units/cartesian_tensor.h include/mp-units/format.h include/mp-units/math.h include/mp-units/ostream.h diff --git a/src/core/include/mp-units/cartesian_tensor.h b/src/core/include/mp-units/cartesian_tensor.h index 550812de73..473dd950fa 100644 --- a/src/core/include/mp-units/cartesian_tensor.h +++ b/src/core/include/mp-units/cartesian_tensor.h @@ -22,8 +22,8 @@ #pragma once -#include #include +#include #include #include #include @@ -56,6 +56,8 @@ class cartesian_tensor { static_assert(R >= 1 && R <= 3 && C >= 1 && C <= 3, "cartesian_tensor supports sizes up to 3x3"); public: + // NOTE: This type is intentionally an aggregate (like std::array). + // All special member functions are implicitly defined. T _data_[R * C]; using value_type = T; static constexpr std::size_t rows_v = R; @@ -82,7 +84,7 @@ class cartesian_tensor { }; -template +MP_UNITS_EXPORT template [[nodiscard]] constexpr auto matmul(const cartesian_tensor& A, const cartesian_tensor& B) { using CT = std::common_type_t; @@ -96,7 +98,7 @@ template return Rm; } -template +MP_UNITS_EXPORT template [[nodiscard]] constexpr auto matvec(const cartesian_tensor& tensor, const cartesian_vector& vector) { using CT = std::common_type_t; @@ -109,7 +111,7 @@ template return y; } -template +MP_UNITS_EXPORT template [[nodiscard]] constexpr auto double_contraction(const cartesian_tensor& A, const cartesian_tensor& B) { using CT = std::common_type_t; @@ -119,7 +121,7 @@ template } -template +MP_UNITS_EXPORT template [[nodiscard]] constexpr auto outer_numeric(const cartesian_vector& lhs, const cartesian_vector& rhs) { using CT = std::common_type_t; @@ -129,7 +131,7 @@ template return Rm; } -template +MP_UNITS_EXPORT template requires requires(const T& t, const U& u) { t + u; } [[nodiscard]] constexpr auto operator+(const cartesian_tensor& A, const cartesian_tensor& B) { @@ -139,7 +141,7 @@ template return Rm; } -template +MP_UNITS_EXPORT template requires requires(const T& t, const U& u) { t - u; } [[nodiscard]] constexpr auto operator-(const cartesian_tensor& A, const cartesian_tensor& B) { @@ -149,7 +151,7 @@ template return Rm; } -template +MP_UNITS_EXPORT template requires(!treat_as_floating_point && !treat_as_floating_point && requires(const T& t, const U& u) { t % u; }) [[nodiscard]] constexpr auto operator%(const cartesian_tensor& A, const cartesian_tensor& B) { @@ -159,7 +161,7 @@ template return Rm; } -template +MP_UNITS_EXPORT template requires requires(const T& t, const S& s) { t * s; } [[nodiscard]] constexpr auto operator*(const cartesian_tensor& tensor, const S& scalar) { @@ -169,14 +171,14 @@ template return Rm; } -template +MP_UNITS_EXPORT template requires requires(const S& s, const U& u) { s * u; } [[nodiscard]] constexpr auto operator*(const S& scalar, const cartesian_tensor& tensor) { return tensor * scalar; } -template +MP_UNITS_EXPORT template requires requires(const T& t, const S& s) { t / s; } [[nodiscard]] constexpr auto operator/(const cartesian_tensor& tensor, const S& scalar) { @@ -192,8 +194,8 @@ template template struct MP_UNITS_STD_FMT::formatter, Char> : formatter, Char> { - template - auto format(const mp_units::cartesian_tensor& A, Ctx& ctx) const + template + auto format(const mp_units::cartesian_tensor& A, FormatContext& ctx) const { auto out = ctx.out(); for (std::size_t r = 0; r < R; ++r) { @@ -207,4 +209,4 @@ struct MP_UNITS_STD_FMT::formatter, Char> : return out; } }; -#endif +#endif \ No newline at end of file diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index fa0b0b85ea..a9919ad3f3 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -22,8 +22,8 @@ #pragma once -#include #include +#include #include #include #include @@ -55,28 +55,11 @@ class cartesian_vector; MP_UNITS_EXPORT template class cartesian_vector { public: - // NOTE: This type is intentionally an aggregate. + // NOTE: This type is intentionally an aggregate (like std::array). + // All special member functions are implicitly defined. T _coordinates_[3]; using value_type = T; - template U> - constexpr cartesian_vector& operator=(const cartesian_vector& other) - { - _coordinates_[0] = other[0]; - _coordinates_[1] = other[1]; - _coordinates_[2] = other[2]; - return *this; - } - - template U> - constexpr cartesian_vector& operator=(cartesian_vector&& other) - { - _coordinates_[0] = std::move(other[0]); - _coordinates_[1] = std::move(other[1]); - _coordinates_[2] = std::move(other[2]); - return *this; - } - [[nodiscard]] constexpr T magnitude() const requires treat_as_floating_point { @@ -171,7 +154,7 @@ template requires(sizeof...(Args) <= 2) && requires { typename std::common_type_t; } cartesian_vector(Arg, Args...) -> cartesian_vector>; -template +MP_UNITS_EXPORT template requires requires(const T& t, const U& u) { t + u; } [[nodiscard]] constexpr auto operator+(const cartesian_vector& lhs, const cartesian_vector& rhs) { @@ -180,7 +163,7 @@ template lhs._coordinates_[2] + rhs._coordinates_[2]}; } -template +MP_UNITS_EXPORT template requires requires(const T& t, const U& u) { t - u; } [[nodiscard]] constexpr auto operator-(const cartesian_vector& lhs, const cartesian_vector& rhs) { @@ -189,7 +172,7 @@ template lhs._coordinates_[2] - rhs._coordinates_[2]}; } -template +MP_UNITS_EXPORT template requires(!treat_as_floating_point && !treat_as_floating_point && requires(const T& t, const U& u) { t % u; }) [[nodiscard]] constexpr auto operator%(const cartesian_vector& lhs, const cartesian_vector& rhs) { @@ -199,7 +182,7 @@ template static_cast(lhs._coordinates_[2] % rhs._coordinates_[2])}; } -template +MP_UNITS_EXPORT template requires requires(const T& t, const S& s) { t * s; } [[nodiscard]] constexpr auto operator*(const cartesian_vector& vector, const S& scalar) { @@ -207,14 +190,14 @@ template vector._coordinates_[2] * scalar}; } -template +MP_UNITS_EXPORT template requires requires(const S& s, const U& u) { s * u; } [[nodiscard]] constexpr auto operator*(const S& scalar, const cartesian_vector& vector) { return vector * scalar; } -template +MP_UNITS_EXPORT template requires requires(const T& t, const S& s) { t / s; } [[nodiscard]] constexpr auto operator/(const cartesian_vector& vector, const S& scalar) { @@ -222,14 +205,14 @@ template vector._coordinates_[2] / scalar}; } -template U> +MP_UNITS_EXPORT template U> [[nodiscard]] constexpr bool operator==(const cartesian_vector& lhs, const cartesian_vector& rhs) { return lhs._coordinates_[0] == rhs._coordinates_[0] && lhs._coordinates_[1] == rhs._coordinates_[1] && lhs._coordinates_[2] == rhs._coordinates_[2]; } -template +MP_UNITS_EXPORT template requires requires(const T& t, const U& u, decltype(t * u) v) { t * u; v + v; @@ -240,7 +223,7 @@ template lhs._coordinates_[2] * rhs._coordinates_[2]; } -template +MP_UNITS_EXPORT template requires requires(const T& t, const U& u, decltype(t * u) v) { t * u; v - v; @@ -283,9 +266,9 @@ template struct MP_UNITS_STD_FMT::formatter, Char> : formatter, Char> { template - auto format(const mp_units::cartesian_vector& vector, FormatContext& ctx) const + auto format(const mp_units::cartesian_vector& vec, FormatContext& ctx) const { - return format_to(ctx.out(), "[{}, {}, {}]", vector[0], vector[1], vector[2]); + return format_to(ctx.out(), "[{}, {}, {}]", vec[0], vec[1], vec[2]); } }; -#endif +#endif \ No newline at end of file diff --git a/test/runtime/cartesian_tensor_test.cpp b/test/runtime/cartesian_tensor_test.cpp index 5d93b3ed62..bf2408d8b1 100644 --- a/test/runtime/cartesian_tensor_test.cpp +++ b/test/runtime/cartesian_tensor_test.cpp @@ -29,11 +29,9 @@ import mp_units; #include #include #include -#if MP_UNITS_HOSTED #include #include #endif -#endif using namespace mp_units; using Catch::Matchers::WithinAbs; @@ -153,16 +151,16 @@ TEST_CASE("cartesian_tensor — core", "[tensor]") REQUIRE(T(2, 2) == 18); } -#if MP_UNITS_HOSTED SECTION("text output (ostream + fmt)") { cartesian_tensor A{1, 2, 3, 4}; std::ostringstream os; os << A; CHECK(os.str() == "[[1, 2]\n [3, 4]]"); +#ifndef MP_UNITS_MODULES CHECK(MP_UNITS_STD_FMT::format("{}", A) == os.str()); - } #endif + } SECTION("constexpr basics") { @@ -172,4 +170,4 @@ TEST_CASE("cartesian_tensor — core", "[tensor]") static_assert(C(0, 0) == 5 && C(0, 1) == 7 && C(0, 2) == 9); (void)C; } -} +} \ No newline at end of file diff --git a/test/runtime/cartesian_vector_test.cpp b/test/runtime/cartesian_vector_test.cpp index d80ed6d612..eefdd1c5d3 100644 --- a/test/runtime/cartesian_vector_test.cpp +++ b/test/runtime/cartesian_vector_test.cpp @@ -31,6 +31,10 @@ import mp_units; #include #endif +#ifndef MP_UNITS_MODULES +#include +#endif + using namespace mp_units; using Catch::Matchers::WithinAbs; @@ -75,22 +79,20 @@ TEST_CASE("cartesian_vector", "[vector]") } } - SECTION("convertibility from another vector") + SECTION("aggregate assignment") { - cartesian_vector v1{1, 2, 3}; - - SECTION("construction from other rep") + SECTION("aggregate copy assignment") { - cartesian_vector v2{}; - v2 = v1; + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2 = v1; // Use initialization instead of assignment REQUIRE(v2[0] == 1.0); REQUIRE(v2[1] == 2.0); REQUIRE(v2[2] == 3.0); } - SECTION("assignment from other rep") + SECTION("aggregate assignment from different rep") { - cartesian_vector v2{3.0, 2.0, 1.0}; - v2 = v1; + cartesian_vector v1{1, 2, 3}; + cartesian_vector v2{static_cast(v1[0]), static_cast(v1[1]), static_cast(v1[2])}; REQUIRE(v2[0] == 1.0); REQUIRE(v2[1] == 2.0); REQUIRE(v2[2] == 3.0); @@ -224,16 +226,16 @@ TEST_CASE("cartesian_vector", "[vector]") REQUIRE_THAT(u[2], WithinAbs(0.0, 1e-12)); } -#if MP_UNITS_HOSTED SECTION("text output (ostream + fmt)") { cartesian_vector v{1, 2, 3}; std::ostringstream os; os << v; CHECK(os.str() == "[1, 2, 3]"); +#ifndef MP_UNITS_MODULES CHECK(MP_UNITS_STD_FMT::format("{}", v) == os.str()); - } #endif + } SECTION("constexpr basics") { @@ -243,4 +245,4 @@ TEST_CASE("cartesian_vector", "[vector]") static_assert(c._coordinates_[0] == 5 && c._coordinates_[1] == 7 && c._coordinates_[2] == 9); (void)c; } -} +} \ No newline at end of file From 1ecb38f6f90376b16283e9490f95b687fd6ff848 Mon Sep 17 00:00:00 2001 From: Julia Smerdel Date: Thu, 20 Nov 2025 20:13:28 +0100 Subject: [PATCH 11/18] format change --- src/core/include/mp-units/cartesian_tensor.h | 6 +++--- src/core/include/mp-units/cartesian_vector.h | 6 +++--- test/runtime/cartesian_tensor_test.cpp | 2 +- test/runtime/cartesian_vector_test.cpp | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/include/mp-units/cartesian_tensor.h b/src/core/include/mp-units/cartesian_tensor.h index 473dd950fa..cedbb73ecb 100644 --- a/src/core/include/mp-units/cartesian_tensor.h +++ b/src/core/include/mp-units/cartesian_tensor.h @@ -22,8 +22,8 @@ #pragma once -#include #include +#include #include #include #include @@ -56,8 +56,8 @@ class cartesian_tensor { static_assert(R >= 1 && R <= 3 && C >= 1 && C <= 3, "cartesian_tensor supports sizes up to 3x3"); public: + // public members required to satisfy structural type requirements :-( // NOTE: This type is intentionally an aggregate (like std::array). - // All special member functions are implicitly defined. T _data_[R * C]; using value_type = T; static constexpr std::size_t rows_v = R; @@ -209,4 +209,4 @@ struct MP_UNITS_STD_FMT::formatter, Char> : return out; } }; -#endif \ No newline at end of file +#endif diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index a9919ad3f3..45c882edc4 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -22,8 +22,8 @@ #pragma once -#include #include +#include #include #include #include @@ -55,8 +55,8 @@ class cartesian_vector; MP_UNITS_EXPORT template class cartesian_vector { public: + // public members required to satisfy structural type requirements :-( // NOTE: This type is intentionally an aggregate (like std::array). - // All special member functions are implicitly defined. T _coordinates_[3]; using value_type = T; @@ -271,4 +271,4 @@ struct MP_UNITS_STD_FMT::formatter, Char> : return format_to(ctx.out(), "[{}, {}, {}]", vec[0], vec[1], vec[2]); } }; -#endif \ No newline at end of file +#endif diff --git a/test/runtime/cartesian_tensor_test.cpp b/test/runtime/cartesian_tensor_test.cpp index bf2408d8b1..0acc5064a2 100644 --- a/test/runtime/cartesian_tensor_test.cpp +++ b/test/runtime/cartesian_tensor_test.cpp @@ -170,4 +170,4 @@ TEST_CASE("cartesian_tensor — core", "[tensor]") static_assert(C(0, 0) == 5 && C(0, 1) == 7 && C(0, 2) == 9); (void)C; } -} \ No newline at end of file +} diff --git a/test/runtime/cartesian_vector_test.cpp b/test/runtime/cartesian_vector_test.cpp index eefdd1c5d3..25f5cdfb45 100644 --- a/test/runtime/cartesian_vector_test.cpp +++ b/test/runtime/cartesian_vector_test.cpp @@ -245,4 +245,4 @@ TEST_CASE("cartesian_vector", "[vector]") static_assert(c._coordinates_[0] == 5 && c._coordinates_[1] == 7 && c._coordinates_[2] == 9); (void)c; } -} \ No newline at end of file +} From 8cfa4c8843fa91a1495e6ef1680fd3be263c2806 Mon Sep 17 00:00:00 2001 From: Julia Smerdel <97158770+Jullija@users.noreply.github.com> Date: Sun, 28 Dec 2025 19:56:57 +0000 Subject: [PATCH 12/18] fixes after review --- src/core/include/mp-units/cartesian_vector.h | 178 ++++++++++--------- test/runtime/cartesian_vector_test.cpp | 59 +++--- test/static/quantity_test.cpp | 2 +- 3 files changed, 131 insertions(+), 108 deletions(-) diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index 45c882edc4..72f58416c3 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -56,10 +56,22 @@ MP_UNITS_EXPORT template class cartesian_vector { public: // public members required to satisfy structural type requirements :-( - // NOTE: This type is intentionally an aggregate (like std::array). T _coordinates_[3]; using value_type = T; + constexpr cartesian_vector() = default; + + explicit constexpr cartesian_vector(T x) : _coordinates_{x, T{}, T{}} {} + constexpr cartesian_vector(T x, T y) : _coordinates_{x, y, T{}} {} + constexpr cartesian_vector(T x, T y, T z) : _coordinates_{x, y, z} {} + + template + requires std::constructible_from && (!std::same_as, T>) && + (!std::same_as, cartesian_vector>) + explicit constexpr cartesian_vector(U&& val) : _coordinates_{T(val), T(val), T(val)} + { + } + [[nodiscard]] constexpr T magnitude() const requires treat_as_floating_point { @@ -142,6 +154,88 @@ class cartesian_vector { return vec.unit(); } + template + requires requires(const T& t, const U& u) { t + u; } + [[nodiscard]] friend constexpr auto operator+(const cartesian_vector& lhs, const cartesian_vector& rhs) + { + return ::mp_units::cartesian_vector{lhs._coordinates_[0] + rhs._coordinates_[0], + lhs._coordinates_[1] + rhs._coordinates_[1], + lhs._coordinates_[2] + rhs._coordinates_[2]}; + } + + template + requires requires(const T& t, const U& u) { t - u; } + [[nodiscard]] friend constexpr auto operator-(const cartesian_vector& lhs, const cartesian_vector& rhs) + { + return ::mp_units::cartesian_vector{lhs._coordinates_[0] - rhs._coordinates_[0], + lhs._coordinates_[1] - rhs._coordinates_[1], + lhs._coordinates_[2] - rhs._coordinates_[2]}; + } + + template + requires(!treat_as_floating_point && !treat_as_floating_point && requires(const T& t, const U& u) { t % u; }) + [[nodiscard]] friend constexpr auto operator%(const cartesian_vector& lhs, const cartesian_vector& rhs) + { + using CT = std::common_type_t; + return ::mp_units::cartesian_vector{static_cast(lhs._coordinates_[0] % rhs._coordinates_[0]), + static_cast(lhs._coordinates_[1] % rhs._coordinates_[1]), + static_cast(lhs._coordinates_[2] % rhs._coordinates_[2])}; + } + + template + requires(!std::same_as, cartesian_vector>) && detail::NotQuantity + [[nodiscard]] friend constexpr auto operator*(const cartesian_vector& vector, const S& scalar) + { + return ::mp_units::cartesian_vector{vector._coordinates_[0] * scalar, vector._coordinates_[1] * scalar, + vector._coordinates_[2] * scalar}; + } + + template + requires(!std::same_as, cartesian_vector>) && detail::NotQuantity + [[nodiscard]] friend constexpr auto operator*(const S& scalar, const cartesian_vector& vector) + { + return vector * scalar; + } + + template + requires(!std::same_as, cartesian_vector>) && detail::NotQuantity + [[nodiscard]] friend constexpr auto operator/(const cartesian_vector& vector, const S& scalar) + { + return ::mp_units::cartesian_vector{vector._coordinates_[0] / scalar, vector._coordinates_[1] / scalar, + vector._coordinates_[2] / scalar}; + } + + template U> + [[nodiscard]] friend constexpr bool operator==(const cartesian_vector& lhs, const cartesian_vector& rhs) + { + return lhs._coordinates_[0] == rhs._coordinates_[0] && lhs._coordinates_[1] == rhs._coordinates_[1] && + lhs._coordinates_[2] == rhs._coordinates_[2]; + } + + template + requires requires(const T& t, const U& u, decltype(t * u) v) { + t * u; + v + v; + } + [[nodiscard]] friend constexpr auto scalar_product(const cartesian_vector& lhs, const cartesian_vector& rhs) + { + return lhs._coordinates_[0] * rhs._coordinates_[0] + lhs._coordinates_[1] * rhs._coordinates_[1] + + lhs._coordinates_[2] * rhs._coordinates_[2]; + } + + template + requires requires(const T& t, const U& u, decltype(t * u) v) { + t * u; + v - v; + } + [[nodiscard]] friend constexpr auto vector_product(const cartesian_vector& lhs, const cartesian_vector& rhs) + { + return ::mp_units::cartesian_vector{ + lhs._coordinates_[1] * rhs._coordinates_[2] - lhs._coordinates_[2] * rhs._coordinates_[1], + lhs._coordinates_[2] * rhs._coordinates_[0] - lhs._coordinates_[0] * rhs._coordinates_[2], + lhs._coordinates_[0] * rhs._coordinates_[1] - lhs._coordinates_[1] * rhs._coordinates_[0]}; + } + #if MP_UNITS_HOSTED friend std::ostream& operator<<(std::ostream& os, const cartesian_vector& vec) { @@ -154,88 +248,6 @@ template requires(sizeof...(Args) <= 2) && requires { typename std::common_type_t; } cartesian_vector(Arg, Args...) -> cartesian_vector>; -MP_UNITS_EXPORT template - requires requires(const T& t, const U& u) { t + u; } -[[nodiscard]] constexpr auto operator+(const cartesian_vector& lhs, const cartesian_vector& rhs) -{ - return ::mp_units::cartesian_vector{lhs._coordinates_[0] + rhs._coordinates_[0], - lhs._coordinates_[1] + rhs._coordinates_[1], - lhs._coordinates_[2] + rhs._coordinates_[2]}; -} - -MP_UNITS_EXPORT template - requires requires(const T& t, const U& u) { t - u; } -[[nodiscard]] constexpr auto operator-(const cartesian_vector& lhs, const cartesian_vector& rhs) -{ - return ::mp_units::cartesian_vector{lhs._coordinates_[0] - rhs._coordinates_[0], - lhs._coordinates_[1] - rhs._coordinates_[1], - lhs._coordinates_[2] - rhs._coordinates_[2]}; -} - -MP_UNITS_EXPORT template - requires(!treat_as_floating_point && !treat_as_floating_point && requires(const T& t, const U& u) { t % u; }) -[[nodiscard]] constexpr auto operator%(const cartesian_vector& lhs, const cartesian_vector& rhs) -{ - using CT = std::common_type_t; - return ::mp_units::cartesian_vector{static_cast(lhs._coordinates_[0] % rhs._coordinates_[0]), - static_cast(lhs._coordinates_[1] % rhs._coordinates_[1]), - static_cast(lhs._coordinates_[2] % rhs._coordinates_[2])}; -} - -MP_UNITS_EXPORT template - requires requires(const T& t, const S& s) { t * s; } -[[nodiscard]] constexpr auto operator*(const cartesian_vector& vector, const S& scalar) -{ - return ::mp_units::cartesian_vector{vector._coordinates_[0] * scalar, vector._coordinates_[1] * scalar, - vector._coordinates_[2] * scalar}; -} - -MP_UNITS_EXPORT template - requires requires(const S& s, const U& u) { s * u; } -[[nodiscard]] constexpr auto operator*(const S& scalar, const cartesian_vector& vector) -{ - return vector * scalar; -} - -MP_UNITS_EXPORT template - requires requires(const T& t, const S& s) { t / s; } -[[nodiscard]] constexpr auto operator/(const cartesian_vector& vector, const S& scalar) -{ - return ::mp_units::cartesian_vector{vector._coordinates_[0] / scalar, vector._coordinates_[1] / scalar, - vector._coordinates_[2] / scalar}; -} - -MP_UNITS_EXPORT template U> -[[nodiscard]] constexpr bool operator==(const cartesian_vector& lhs, const cartesian_vector& rhs) -{ - return lhs._coordinates_[0] == rhs._coordinates_[0] && lhs._coordinates_[1] == rhs._coordinates_[1] && - lhs._coordinates_[2] == rhs._coordinates_[2]; -} - -MP_UNITS_EXPORT template - requires requires(const T& t, const U& u, decltype(t * u) v) { - t * u; - v + v; - } -[[nodiscard]] constexpr auto scalar_product(const cartesian_vector& lhs, const cartesian_vector& rhs) -{ - return lhs._coordinates_[0] * rhs._coordinates_[0] + lhs._coordinates_[1] * rhs._coordinates_[1] + - lhs._coordinates_[2] * rhs._coordinates_[2]; -} - -MP_UNITS_EXPORT template - requires requires(const T& t, const U& u, decltype(t * u) v) { - t * u; - v - v; - } -[[nodiscard]] constexpr auto vector_product(const cartesian_vector& lhs, const cartesian_vector& rhs) -{ - return ::mp_units::cartesian_vector{ - lhs._coordinates_[1] * rhs._coordinates_[2] - lhs._coordinates_[2] * rhs._coordinates_[1], - lhs._coordinates_[2] * rhs._coordinates_[0] - lhs._coordinates_[0] * rhs._coordinates_[2], - lhs._coordinates_[0] * rhs._coordinates_[1] - lhs._coordinates_[1] * rhs._coordinates_[0]}; -} - template inline constexpr bool treat_as_floating_point> = treat_as_floating_point; diff --git a/test/runtime/cartesian_vector_test.cpp b/test/runtime/cartesian_vector_test.cpp index 25f5cdfb45..d2d9d05d56 100644 --- a/test/runtime/cartesian_vector_test.cpp +++ b/test/runtime/cartesian_vector_test.cpp @@ -22,21 +22,20 @@ #include #include +#include +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#endif #ifdef MP_UNITS_MODULES import mp_units; #else #include -#include -#include -#endif - -#ifndef MP_UNITS_MODULES -#include #endif using namespace mp_units; -using Catch::Matchers::WithinAbs; TEST_CASE("cartesian_vector", "[vector]") { @@ -84,7 +83,7 @@ TEST_CASE("cartesian_vector", "[vector]") SECTION("aggregate copy assignment") { cartesian_vector v1{1.0, 2.0, 3.0}; - cartesian_vector v2 = v1; // Use initialization instead of assignment + cartesian_vector v2 = v1; REQUIRE(v2[0] == 1.0); REQUIRE(v2[1] == 2.0); REQUIRE(v2[2] == 3.0); @@ -218,23 +217,12 @@ TEST_CASE("cartesian_vector", "[vector]") SECTION("magnitude and unit (floating types)") { cartesian_vector v{3.0, 4.0, 0.0}; - REQUIRE_THAT(v.magnitude(), WithinAbs(5.0, 1e-12)); + REQUIRE_THAT(v.magnitude(), Catch::Matchers::WithinULP(5.0, 2)); auto u = v.unit(); - REQUIRE_THAT(u.magnitude(), WithinAbs(1.0, 1e-12)); - REQUIRE_THAT(u[0], WithinAbs(3.0 / 5.0, 1e-12)); - REQUIRE_THAT(u[1], WithinAbs(4.0 / 5.0, 1e-12)); - REQUIRE_THAT(u[2], WithinAbs(0.0, 1e-12)); - } - - SECTION("text output (ostream + fmt)") - { - cartesian_vector v{1, 2, 3}; - std::ostringstream os; - os << v; - CHECK(os.str() == "[1, 2, 3]"); -#ifndef MP_UNITS_MODULES - CHECK(MP_UNITS_STD_FMT::format("{}", v) == os.str()); -#endif + REQUIRE_THAT(u.magnitude(), Catch::Matchers::WithinULP(1.0, 2)); + REQUIRE_THAT(u[0], Catch::Matchers::WithinULP(3.0 / 5.0, 2)); + REQUIRE_THAT(u[1], Catch::Matchers::WithinULP(4.0 / 5.0, 2)); + REQUIRE_THAT(u[2], Catch::Matchers::WithinULP(0.0, 2)); } SECTION("constexpr basics") @@ -246,3 +234,26 @@ TEST_CASE("cartesian_vector", "[vector]") (void)c; } } + +TEST_CASE("cartesian_vector text output", "[vector][fmt][ostream]") +{ + std::ostringstream os; + + SECTION("integral representation") + { + cartesian_vector v{1, 2, 3}; + os << v; + + SECTION("iostream") { CHECK(os.str() == "[1, 2, 3]"); } + SECTION("fmt with default format {}") { CHECK(MP_UNITS_STD_FMT::format("{}", v) == os.str()); } + } + + SECTION("floating-point representation") + { + cartesian_vector v{1.2, 2.3, 3.4}; + os << v; + + SECTION("iostream") { CHECK(os.str() == "[1.2, 2.3, 3.4]"); } + SECTION("fmt with default format {}") { CHECK(MP_UNITS_STD_FMT::format("{}", v) == os.str()); } + } +} diff --git a/test/static/quantity_test.cpp b/test/static/quantity_test.cpp index 3773662c4a..570bd71824 100644 --- a/test/static/quantity_test.cpp +++ b/test/static/quantity_test.cpp @@ -303,7 +303,7 @@ static_assert(std::constructible_from, quantity>, cartesian_vector>); static_assert(std::constructible_from, quantity>>); static_assert(!std::convertible_to, cartesian_vector>); -static_assert(std::is_aggregate_v>); +static_assert(std::constructible_from, quantity>); #endif From 7313258216109681d3a73137b61e38dcc95fff4e Mon Sep 17 00:00:00 2001 From: Julia Smerdel Date: Wed, 31 Dec 2025 19:23:06 +0100 Subject: [PATCH 13/18] little order fix --- src/core/include/mp-units/cartesian_tensor.h | 2 +- src/core/include/mp-units/cartesian_vector.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/include/mp-units/cartesian_tensor.h b/src/core/include/mp-units/cartesian_tensor.h index cedbb73ecb..55b0977e03 100644 --- a/src/core/include/mp-units/cartesian_tensor.h +++ b/src/core/include/mp-units/cartesian_tensor.h @@ -22,8 +22,8 @@ #pragma once -#include #include +#include #include #include #include diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index 72f58416c3..32326fc2ec 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -22,8 +22,8 @@ #pragma once -#include #include +#include #include #include #include From dedc03967bc16697475ad93bd25f2837739a1d30 Mon Sep 17 00:00:00 2001 From: Julia Smerdel <97158770+Jullija@users.noreply.github.com> Date: Wed, 21 Jan 2026 07:45:51 +0000 Subject: [PATCH 14/18] fix style and cast --- src/core/include/mp-units/cartesian_tensor.h | 1 + src/core/include/mp-units/cartesian_vector.h | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/include/mp-units/cartesian_tensor.h b/src/core/include/mp-units/cartesian_tensor.h index 55b0977e03..f1c3a1e39b 100644 --- a/src/core/include/mp-units/cartesian_tensor.h +++ b/src/core/include/mp-units/cartesian_tensor.h @@ -23,6 +23,7 @@ #pragma once #include +// #include #include #include diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index 32326fc2ec..c4396eca1d 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -23,6 +23,7 @@ #pragma once #include +// #include #include #include @@ -66,9 +67,10 @@ class cartesian_vector { constexpr cartesian_vector(T x, T y, T z) : _coordinates_{x, y, z} {} template - requires std::constructible_from && (!std::same_as, T>) && - (!std::same_as, cartesian_vector>) - explicit constexpr cartesian_vector(U&& val) : _coordinates_{T(val), T(val), T(val)} + requires std::constructible_from + constexpr explicit(!std::convertible_to) cartesian_vector(cartesian_vector&& other) : + _coordinates_{static_cast(std::move(other[0])), static_cast(std::move(other[1])), + static_cast(std::move(other[2]))} { } From bfde546c5767bef5cb8989e813e60dc71b0422b0 Mon Sep 17 00:00:00 2001 From: Julia Smerdel <97158770+Jullija@users.noreply.github.com> Date: Wed, 21 Jan 2026 07:57:12 +0000 Subject: [PATCH 15/18] add tensor --- src/core/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 93e37627c1..f7143d6983 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -101,7 +101,7 @@ if(NOT MP_UNITS_API_FREESTANDING) include/mp-units/ext/format.h include/mp-units/cartesian_vector.h include/mp-units/cartesian_tensor.h - include/mp-units/format.h + include/mp-units/format.h # deprecated include/mp-units/math.h include/mp-units/ostream.h include/mp-units/random.h From b516aa7fa65234ec67baec19d3c8b4850f609552 Mon Sep 17 00:00:00 2001 From: Julia Smerdel <97158770+Jullija@users.noreply.github.com> Date: Thu, 22 Jan 2026 20:01:44 +0000 Subject: [PATCH 16/18] remove tensors --- src/core/CMakeLists.txt | 1 - src/core/include/mp-units/cartesian_tensor.h | 213 ------------------- src/core/include/mp-units/core.h | 1 - test/runtime/CMakeLists.txt | 1 - test/runtime/cartesian_tensor_test.cpp | 173 --------------- 5 files changed, 389 deletions(-) delete mode 100644 src/core/include/mp-units/cartesian_tensor.h delete mode 100644 test/runtime/cartesian_tensor_test.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f7143d6983..88900b6621 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -100,7 +100,6 @@ if(NOT MP_UNITS_API_FREESTANDING) include/mp-units/bits/requires_hosted.h include/mp-units/ext/format.h include/mp-units/cartesian_vector.h - include/mp-units/cartesian_tensor.h include/mp-units/format.h # deprecated include/mp-units/math.h include/mp-units/ostream.h diff --git a/src/core/include/mp-units/cartesian_tensor.h b/src/core/include/mp-units/cartesian_tensor.h deleted file mode 100644 index f1c3a1e39b..0000000000 --- a/src/core/include/mp-units/cartesian_tensor.h +++ /dev/null @@ -1,213 +0,0 @@ -// 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 - -#if MP_UNITS_HOSTED -#include -#endif - -#ifndef MP_UNITS_IN_MODULE_INTERFACE -#ifdef MP_UNITS_IMPORT_STD -import std; -#else -#include -#include -#include -#include -#if MP_UNITS_HOSTED -#include -#endif -#endif -#endif - -namespace mp_units { - -MP_UNITS_EXPORT template -class cartesian_tensor; - -MP_UNITS_EXPORT template -class cartesian_tensor { - static_assert(R >= 1 && R <= 3 && C >= 1 && C <= 3, "cartesian_tensor supports sizes up to 3x3"); - -public: - // public members required to satisfy structural type requirements :-( - // NOTE: This type is intentionally an aggregate (like std::array). - T _data_[R * C]; - using value_type = T; - static constexpr std::size_t rows_v = R; - static constexpr std::size_t cols_v = C; - - [[nodiscard]] constexpr T& operator()(std::size_t r, std::size_t c) { return _data_[r * C + c]; } - [[nodiscard]] constexpr const T& operator()(std::size_t r, std::size_t c) const { return _data_[r * C + c]; } - - -#if MP_UNITS_HOSTED - friend std::ostream& operator<<(std::ostream& os, const cartesian_tensor& A) - { - for (std::size_t r = 0; r < R; ++r) { - os << (r == 0 ? "[[" : " ["); - for (std::size_t c = 0; c < C; ++c) { - os << A(r, c); - if (c + 1 != C) os << ", "; - } - os << (r + 1 == R ? "]]" : "]\n"); - } - return os; - } -#endif -}; - - -MP_UNITS_EXPORT template -[[nodiscard]] constexpr auto matmul(const cartesian_tensor& A, const cartesian_tensor& B) -{ - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t r = 0; r < R; ++r) - for (std::size_t c = 0; c < C; ++c) { - CT acc{}; - for (std::size_t k = 0; k < K; ++k) acc += static_cast(A(r, k)) * static_cast(B(k, c)); - Rm(r, c) = acc; - } - return Rm; -} - -MP_UNITS_EXPORT template -[[nodiscard]] constexpr auto matvec(const cartesian_tensor& tensor, const cartesian_vector& vector) -{ - using CT = std::common_type_t; - cartesian_vector y{}; - for (std::size_t r = 0; r < 3; ++r) { - CT acc{}; - for (std::size_t c = 0; c < 3; ++c) acc += static_cast(tensor(r, c)) * static_cast(vector[c]); - y[r] = acc; - } - return y; -} - -MP_UNITS_EXPORT template -[[nodiscard]] constexpr auto double_contraction(const cartesian_tensor& A, const cartesian_tensor& B) -{ - using CT = std::common_type_t; - CT acc{}; - for (std::size_t i = 0; i < R * C; ++i) acc += static_cast(A._data_[i]) * static_cast(B._data_[i]); - return acc; -} - - -MP_UNITS_EXPORT template -[[nodiscard]] constexpr auto outer_numeric(const cartesian_vector& lhs, const cartesian_vector& rhs) -{ - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < 3; ++i) - for (std::size_t j = 0; j < 3; ++j) Rm(i, j) = static_cast(lhs[i]) * static_cast(rhs[j]); - return Rm; -} - -MP_UNITS_EXPORT template - requires requires(const T& t, const U& u) { t + u; } -[[nodiscard]] constexpr auto operator+(const cartesian_tensor& A, const cartesian_tensor& B) -{ - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(A._data_[i]) + static_cast(B._data_[i]); - return Rm; -} - -MP_UNITS_EXPORT template - requires requires(const T& t, const U& u) { t - u; } -[[nodiscard]] constexpr auto operator-(const cartesian_tensor& A, const cartesian_tensor& B) -{ - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(A._data_[i]) - static_cast(B._data_[i]); - return Rm; -} - -MP_UNITS_EXPORT template - requires(!treat_as_floating_point && !treat_as_floating_point && requires(const T& t, const U& u) { t % u; }) -[[nodiscard]] constexpr auto operator%(const cartesian_tensor& A, const cartesian_tensor& B) -{ - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(A._data_[i] % B._data_[i]); - return Rm; -} - -MP_UNITS_EXPORT template - requires requires(const T& t, const S& s) { t * s; } -[[nodiscard]] constexpr auto operator*(const cartesian_tensor& tensor, const S& scalar) -{ - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(tensor._data_[i]) * static_cast(scalar); - return Rm; -} - -MP_UNITS_EXPORT template - requires requires(const S& s, const U& u) { s * u; } -[[nodiscard]] constexpr auto operator*(const S& scalar, const cartesian_tensor& tensor) -{ - return tensor * scalar; -} - -MP_UNITS_EXPORT template - requires requires(const T& t, const S& s) { t / s; } -[[nodiscard]] constexpr auto operator/(const cartesian_tensor& tensor, const S& scalar) -{ - using CT = std::common_type_t; - cartesian_tensor Rm{}; - for (std::size_t i = 0; i < R * C; ++i) Rm._data_[i] = static_cast(tensor._data_[i]) / static_cast(scalar); - return Rm; -} - -} // namespace mp_units - -#if MP_UNITS_HOSTED -template -struct MP_UNITS_STD_FMT::formatter, Char> : - formatter, Char> { - template - auto format(const mp_units::cartesian_tensor& A, FormatContext& ctx) const - { - auto out = ctx.out(); - for (std::size_t r = 0; r < R; ++r) { - out = format_to(out, "{}", (r == 0 ? "[[" : " [")); - for (std::size_t c = 0; c < C; ++c) { - out = format_to(out, "{}", A(r, c)); - if (c + 1 != C) out = format_to(out, "{}", ", "); - } - out = format_to(out, "{}", (r + 1 == R ? "]]" : "]\n")); - } - return out; - } -}; -#endif diff --git a/src/core/include/mp-units/core.h b/src/core/include/mp-units/core.h index fa36bd3661..269507a794 100644 --- a/src/core/include/mp-units/core.h +++ b/src/core/include/mp-units/core.h @@ -28,7 +28,6 @@ #include #if MP_UNITS_HOSTED -#include #include #include #include diff --git a/test/runtime/CMakeLists.txt b/test/runtime/CMakeLists.txt index 36446634cc..6ad12760c7 100644 --- a/test/runtime/CMakeLists.txt +++ b/test/runtime/CMakeLists.txt @@ -26,7 +26,6 @@ add_executable( unit_tests_runtime atomic_test.cpp cartesian_vector_test.cpp - cartesian_tensor_test.cpp distribution_test.cpp fixed_string_test.cpp fmt_test.cpp diff --git a/test/runtime/cartesian_tensor_test.cpp b/test/runtime/cartesian_tensor_test.cpp deleted file mode 100644 index 0acc5064a2..0000000000 --- a/test/runtime/cartesian_tensor_test.cpp +++ /dev/null @@ -1,173 +0,0 @@ -// 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. - -#include -#include - -#ifdef MP_UNITS_MODULES -import mp_units; -#else -#include -#include -#include -#include -#include -#endif - -using namespace mp_units; -using Catch::Matchers::WithinAbs; - -TEST_CASE("cartesian_tensor — core", "[tensor]") -{ - SECTION("construction and access (row-major fill)") - { - cartesian_tensor A{1, 2, 3, 4, 5, 6}; - REQUIRE(A(0, 0) == 1); - REQUIRE(A(0, 1) == 2); - REQUIRE(A(0, 2) == 3); - REQUIRE(A(1, 0) == 4); - REQUIRE(A(1, 1) == 5); - REQUIRE(A(1, 2) == 6); - } - - SECTION("elementwise + and - with common_type") - { - cartesian_tensor A{1, 2, 3, 4}; - cartesian_tensor B{0.5, 1.5, 2.5, 3.5}; - - auto S = A + B; - static_assert(std::is_same_v>); - REQUIRE(S(0, 0) == 1.5); - REQUIRE(S(0, 1) == 3.5); - REQUIRE(S(1, 0) == 5.5); - REQUIRE(S(1, 1) == 7.5); - - auto D = B - A; - REQUIRE_THAT(D(0, 0), WithinAbs(-0.5, 1e-12)); - REQUIRE_THAT(D(0, 1), WithinAbs(-0.5, 1e-12)); - REQUIRE_THAT(D(1, 0), WithinAbs(-0.5, 1e-12)); - REQUIRE_THAT(D(1, 1), WithinAbs(-0.5, 1e-12)); - } - - SECTION("elementwise modulo (integral)") - { - cartesian_tensor A{10, 11, 12, 13, 14, 15}; - cartesian_tensor B{4, 5, 7, 4, 5, 7}; - auto R = A % B; - REQUIRE(R(0, 0) == 2); - REQUIRE(R(0, 1) == 1); - REQUIRE(R(0, 2) == 5); - REQUIRE(R(1, 0) == 1); - REQUIRE(R(1, 1) == 4); - REQUIRE(R(1, 2) == 1); - } - - SECTION("scalar multiply/divide") - { - cartesian_tensor A{1, 2, 3, 4}; - - auto T1 = A * 2.0; - REQUIRE(T1(0, 0) == 2.0); - REQUIRE(T1(0, 1) == 4.0); - REQUIRE(T1(1, 0) == 6.0); - REQUIRE(T1(1, 1) == 8.0); - - auto T2 = 2.0 * A; - REQUIRE(T2(0, 0) == 2.0); - REQUIRE(T2(0, 1) == 4.0); - REQUIRE(T2(1, 0) == 6.0); - REQUIRE(T2(1, 1) == 8.0); - - auto T3 = A / 2.0; - REQUIRE(T3(0, 0) == 0.5); - REQUIRE(T3(0, 1) == 1.0); - REQUIRE(T3(1, 0) == 1.5); - REQUIRE(T3(1, 1) == 2.0); - } - - SECTION("matmul (R×K) * (K×C) -> (R×C)") - { - cartesian_tensor A{1, 2, 3, 4, 5, 6}; - cartesian_tensor B{7, 8, 9, 10, 11, 12}; - auto C = matmul(A, B); - - static_assert(std::is_same_v>); - REQUIRE(C(0, 0) == 58); - REQUIRE(C(0, 1) == 64); - REQUIRE(C(1, 0) == 139); - REQUIRE(C(1, 1) == 154); - } - - SECTION("matvec (3x3) * vector -> vector") - { - cartesian_tensor M{1, 2, 3, 0, 1, 4, 5, 6, 0}; - cartesian_vector x{1, 2, 3}; - auto y = matvec(M, x); - REQUIRE_THAT(y[0], WithinAbs(14.0, 1e-12)); // 1*1 + 2*2 + 3*3 - REQUIRE_THAT(y[1], WithinAbs(14.0, 1e-12)); // 0*1 + 1*2 + 4*3 - REQUIRE_THAT(y[2], WithinAbs(17.0, 1e-12)); // 5*1 + 6*2 + 0*3 - } - - SECTION("double contraction A : B") - { - cartesian_tensor A{1, 2, 3, 4}; - cartesian_tensor B{5, 6, 7, 8}; - auto s = double_contraction(A, B); // 1*5 + 2*6 + 3*7 + 4*8 - REQUIRE(s == 70); - } - - SECTION("outer_numeric (vector ⊗ vector)") - { - cartesian_vector a{1, 2, 3}; - cartesian_vector b{4, 5, 6}; - auto T = outer_numeric(a, b); - REQUIRE(T(0, 0) == 4); - REQUIRE(T(0, 1) == 5); - REQUIRE(T(0, 2) == 6); - REQUIRE(T(1, 0) == 8); - REQUIRE(T(1, 1) == 10); - REQUIRE(T(1, 2) == 12); - REQUIRE(T(2, 0) == 12); - REQUIRE(T(2, 1) == 15); - REQUIRE(T(2, 2) == 18); - } - - SECTION("text output (ostream + fmt)") - { - cartesian_tensor A{1, 2, 3, 4}; - std::ostringstream os; - os << A; - CHECK(os.str() == "[[1, 2]\n [3, 4]]"); -#ifndef MP_UNITS_MODULES - CHECK(MP_UNITS_STD_FMT::format("{}", A) == os.str()); -#endif - } - - SECTION("constexpr basics") - { - constexpr cartesian_tensor A{1, 2, 3}; - constexpr cartesian_tensor B{4, 5, 6}; - constexpr auto C = A + B; - static_assert(C(0, 0) == 5 && C(0, 1) == 7 && C(0, 2) == 9); - (void)C; - } -} From 9ffc3749961908109aff810eaf93d63b6b22ded1 Mon Sep 17 00:00:00 2001 From: Julia Smerdel <97158770+Jullija@users.noreply.github.com> Date: Fri, 23 Jan 2026 12:38:07 +0000 Subject: [PATCH 17/18] fix tests --- test/runtime/cartesian_vector_test.cpp | 313 +++++++++++++++++++------ 1 file changed, 242 insertions(+), 71 deletions(-) diff --git a/test/runtime/cartesian_vector_test.cpp b/test/runtime/cartesian_vector_test.cpp index d2d9d05d56..dadacf3892 100644 --- a/test/runtime/cartesian_vector_test.cpp +++ b/test/runtime/cartesian_vector_test.cpp @@ -36,12 +36,13 @@ import mp_units; #endif using namespace mp_units; +using namespace Catch::Matchers; -TEST_CASE("cartesian_vector", "[vector]") +TEST_CASE("cartesian_vector operations", "[vector]") { - SECTION("construction & access") + SECTION("cartesian_vector initialization and access") { - SECTION("value-initialization yields zeros") + SECTION("zero arguments") { cartesian_vector v{}; REQUIRE(v[0] == 0); @@ -78,27 +79,29 @@ TEST_CASE("cartesian_vector", "[vector]") } } - SECTION("aggregate assignment") + SECTION("convertibility from another vector") { - SECTION("aggregate copy assignment") + cartesian_vector v1{1.0, 2.0, 3.0}; + + SECTION("construction") { - cartesian_vector v1{1.0, 2.0, 3.0}; cartesian_vector v2 = v1; REQUIRE(v2[0] == 1.0); REQUIRE(v2[1] == 2.0); REQUIRE(v2[2] == 3.0); } - SECTION("aggregate assignment from different rep") + + SECTION("assignment") { - cartesian_vector v1{1, 2, 3}; - cartesian_vector v2{static_cast(v1[0]), static_cast(v1[1]), static_cast(v1[2])}; + cartesian_vector v2{3.0, 2.0, 1.0}; + v2 = v1; REQUIRE(v2[0] == 1.0); REQUIRE(v2[1] == 2.0); REQUIRE(v2[2] == 3.0); } } - SECTION("compound assignments") + SECTION("cartesian_vector compound assignment addition") { SECTION("operator+=") { @@ -109,7 +112,7 @@ TEST_CASE("cartesian_vector", "[vector]") REQUIRE(v1[1] == 7.0); REQUIRE(v1[2] == 9.0); } - SECTION("operator-=") + SECTION("cartesian_vector compound assignment subtraction") { cartesian_vector v1{4.0, 5.0, 6.0}; cartesian_vector v2{1.0, 2.0, 3.0}; @@ -118,7 +121,7 @@ TEST_CASE("cartesian_vector", "[vector]") REQUIRE(v1[1] == 3.0); REQUIRE(v1[2] == 3.0); } - SECTION("operator*=") + SECTION("cartesian_vector compound assignment scalar multiplication") { cartesian_vector v{1.0, 2.0, 3.0}; v *= 2.0; @@ -126,7 +129,7 @@ TEST_CASE("cartesian_vector", "[vector]") REQUIRE(v[1] == 4.0); REQUIRE(v[2] == 6.0); } - SECTION("operator/=") + SECTION("cartesian_vector compound assignment scalar division") { cartesian_vector v{2.0, 4.0, 6.0}; v /= 2.0; @@ -136,102 +139,270 @@ TEST_CASE("cartesian_vector", "[vector]") } } - SECTION("binary + and -") + SECTION("cartesian_vector addition") { SECTION("double + double") { cartesian_vector v1{1.0, 2.0, 3.0}; cartesian_vector v2{4.0, 5.0, 6.0}; - auto r = v1 + v2; - REQUIRE(r[0] == 5.0); - REQUIRE(r[1] == 7.0); - REQUIRE(r[2] == 9.0); + cartesian_vector result = v1 + v2; + REQUIRE(result[0] == 5.0); + REQUIRE(result[1] == 7.0); + REQUIRE(result[2] == 9.0); + } + + SECTION("double + int") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4, 5, 6}; + cartesian_vector result = v1 + v2; + REQUIRE(result[0] == 5.0); + REQUIRE(result[1] == 7.0); + REQUIRE(result[2] == 9.0); + } + + SECTION("int + double") + { + cartesian_vector v1{1, 2, 3}; + cartesian_vector v2{4.0, 5.0, 6.0}; + cartesian_vector result = v1 + v2; + REQUIRE(result[0] == 5.0); + REQUIRE(result[1] == 7.0); + REQUIRE(result[2] == 9.0); + } + + SECTION("int + int") + { + cartesian_vector v1{1, 2, 3}; + cartesian_vector v2{4, 5, 6}; + cartesian_vector result = v1 + v2; + REQUIRE(result[0] == 5); + REQUIRE(result[1] == 7); + REQUIRE(result[2] == 9); + } + } + + SECTION("cartesian_vector substraction") + { + SECTION("double - double") + { + cartesian_vector v1{4.0, 5.0, 6.0}; + cartesian_vector v2{1.0, 2.0, 3.0}; + cartesian_vector result = v1 - v2; + REQUIRE(result[0] == 3.0); + REQUIRE(result[1] == 3.0); + REQUIRE(result[2] == 3.0); } + + SECTION("double - int") + { + cartesian_vector v1{4.0, 5.0, 6.0}; + cartesian_vector v2{1, 2, 3}; + cartesian_vector result = v1 - v2; + REQUIRE(result[0] == 3.0); + REQUIRE(result[1] == 3.0); + REQUIRE(result[2] == 3.0); + } + + SECTION("int - double") + { + cartesian_vector v1{4, 5, 6}; + cartesian_vector v2{1.0, 2.0, 3.0}; + cartesian_vector result = v1 - v2; + REQUIRE(result[0] == 3.0); + REQUIRE(result[1] == 3.0); + REQUIRE(result[2] == 3.0); + } + SECTION("int - int") { cartesian_vector v1{4, 5, 6}; cartesian_vector v2{1, 2, 3}; - auto r = v1 - v2; - REQUIRE(r[0] == 3); - REQUIRE(r[1] == 3); - REQUIRE(r[2] == 3); + cartesian_vector result = v1 - v2; + REQUIRE(result[0] == 3); + REQUIRE(result[1] == 3); + REQUIRE(result[2] == 3); } } - SECTION("elementwise modulo (integral only)") + SECTION("cartesian_vector scalar multiplication") { - cartesian_vector a{10, 11, 12}; - cartesian_vector b{4, 5, 7}; - auto r = a % b; - REQUIRE(r[0] == 2); - REQUIRE(r[1] == 1); - REQUIRE(r[2] == 5); + SECTION("double * double") + { + cartesian_vector v{1.0, 2.0, 3.0}; + cartesian_vector result = v * 2.0; + REQUIRE(result[0] == 2.0); + REQUIRE(result[1] == 4.0); + REQUIRE(result[2] == 6.0); + } + + SECTION("double * int") + { + cartesian_vector v{1.0, 2.0, 3.0}; + cartesian_vector result = v * 2; + REQUIRE(result[0] == 2.0); + REQUIRE(result[1] == 4.0); + REQUIRE(result[2] == 6.0); + } + + SECTION("int * double") + { + cartesian_vector v{1, 2, 3}; + cartesian_vector result = v * 2.0; + REQUIRE(result[0] == 2.0); + REQUIRE(result[1] == 4.0); + REQUIRE(result[2] == 6.0); + } + + SECTION("int * int") + { + cartesian_vector v{1, 2, 3}; + cartesian_vector result = v * 2; + REQUIRE(result[0] == 2); + REQUIRE(result[1] == 4); + REQUIRE(result[2] == 6); + } } - SECTION("scalar multiply/divide") + SECTION("cartesian_vector scalar division") { - cartesian_vector v{1, 2, 3}; - SECTION("v * s") + SECTION("double / double") { - auto r = v * 2.0; - REQUIRE(r[0] == 2.0); - REQUIRE(r[1] == 4.0); - REQUIRE(r[2] == 6.0); + cartesian_vector v{2.0, 4.0, 6.0}; + cartesian_vector result = v / 2.0; + REQUIRE(result[0] == 1.0); + REQUIRE(result[1] == 2.0); + REQUIRE(result[2] == 3.0); + } + + SECTION("double / int") + { + cartesian_vector v{2.0, 4.0, 6.0}; + cartesian_vector result = v / 2; + REQUIRE(result[0] == 1.0); + REQUIRE(result[1] == 2.0); + REQUIRE(result[2] == 3.0); } - SECTION("s * v") + + SECTION("int / double") { - auto r = 2.0 * v; - REQUIRE(r[0] == 2.0); - REQUIRE(r[1] == 4.0); - REQUIRE(r[2] == 6.0); + cartesian_vector v{2, 4, 6}; + cartesian_vector result = v / 2.0; + REQUIRE(result[0] == 1.0); + REQUIRE(result[1] == 2.0); + REQUIRE(result[2] == 3.0); } - SECTION("v / s") + + SECTION("int / int") { - auto r = v / 2.0; - REQUIRE(r[0] == 0.5); - REQUIRE(r[1] == 1.0); - REQUIRE(r[2] == 1.5); + cartesian_vector v{2, 4, 6}; + cartesian_vector result = v / 2; + REQUIRE(result[0] == 1); + REQUIRE(result[1] == 2); + REQUIRE(result[2] == 3); } } - SECTION("equality and inequality") + SECTION("cartesian_vector magnitude") + { + cartesian_vector v1{3.0, 4.0, 0.0}; + cartesian_vector v2{2.0, 3.0, 6.0}; + REQUIRE(v1.magnitude() == 5.0); + REQUIRE(v2.magnitude() == 7.0); + } + + SECTION("cartesian_vector unit vector") + { + cartesian_vector v{3.0, 4.0, 0.0}; + cartesian_vector unit_v = v.unit(); + REQUIRE_THAT(unit_v.magnitude(), WithinULP(1.0, 1)); + } + + SECTION("cartesian_vector equality") { cartesian_vector v1{1.0, 2.0, 3.0}; cartesian_vector v2{1, 2, 3}; cartesian_vector v3{1.1, 2.0, 3.0}; + cartesian_vector v4{1.0, 2.1, 3.0}; + cartesian_vector v5{1.0, 2.0, 3.1}; REQUIRE(v1 == v2); REQUIRE(v1 != v3); + REQUIRE(v1 != v4); + REQUIRE(v1 != v5); } - SECTION("scalar_product and vector_product") + SECTION("cartesian_vector scalar product") { - cartesian_vector a{1, 2, 3}; - cartesian_vector b{4, 5, 6}; - REQUIRE(scalar_product(a, b) == 32); - auto r = vector_product(a, b); - REQUIRE(r[0] == -3); - REQUIRE(r[1] == 6); - REQUIRE(r[2] == -3); - } + SECTION("double * double") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4.0, 5.0, 6.0}; + REQUIRE(scalar_product(v1, v2) == 32.0); + } - SECTION("magnitude and unit (floating types)") - { - cartesian_vector v{3.0, 4.0, 0.0}; - REQUIRE_THAT(v.magnitude(), Catch::Matchers::WithinULP(5.0, 2)); - auto u = v.unit(); - REQUIRE_THAT(u.magnitude(), Catch::Matchers::WithinULP(1.0, 2)); - REQUIRE_THAT(u[0], Catch::Matchers::WithinULP(3.0 / 5.0, 2)); - REQUIRE_THAT(u[1], Catch::Matchers::WithinULP(4.0 / 5.0, 2)); - REQUIRE_THAT(u[2], Catch::Matchers::WithinULP(0.0, 2)); + SECTION("double * int") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4, 5, 6}; + REQUIRE(scalar_product(v1, v2) == 32.0); + } + + SECTION("int * double") + { + cartesian_vector v1{1, 2, 3}; + cartesian_vector v2{4.0, 5.0, 6.0}; + REQUIRE(scalar_product(v1, v2) == 32.0); + } + + SECTION("int * int") + { + cartesian_vector v1{1, 2, 3}; + cartesian_vector v2{4, 5, 6}; + REQUIRE(scalar_product(v1, v2) == 32); + } } - SECTION("constexpr basics") + SECTION("cartesian_vector vector product") { - constexpr cartesian_vector a{1, 2, 3}; - constexpr cartesian_vector b{4, 5, 6}; - constexpr auto c = a + b; - static_assert(c._coordinates_[0] == 5 && c._coordinates_[1] == 7 && c._coordinates_[2] == 9); - (void)c; + SECTION("double * double") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4.0, 5.0, 6.0}; + cartesian_vector result = vector_product(v1, v2); + REQUIRE(result[0] == -3.0); + REQUIRE(result[1] == 6.0); + REQUIRE(result[2] == -3.0); + } + + SECTION("double * int") + { + cartesian_vector v1{1.0, 2.0, 3.0}; + cartesian_vector v2{4, 5, 6}; + cartesian_vector result = vector_product(v1, v2); + REQUIRE(result[0] == -3.0); + REQUIRE(result[1] == 6.0); + REQUIRE(result[2] == -3.0); + } + + SECTION("int * double") + { + cartesian_vector v1{1, 2, 3}; + cartesian_vector v2{4.0, 5.0, 6.0}; + cartesian_vector result = vector_product(v1, v2); + REQUIRE(result[0] == -3.0); + REQUIRE(result[1] == 6.0); + REQUIRE(result[2] == -3.0); + } + + SECTION("int * int") + { + cartesian_vector v1{1, 2, 3}; + cartesian_vector v2{4, 5, 6}; + cartesian_vector result = vector_product(v1, v2); + REQUIRE(result[0] == -3); + REQUIRE(result[1] == 6); + REQUIRE(result[2] == -3); + } } } From 1f6e9f0d4962873255cbfd768b306788a4fdd78c Mon Sep 17 00:00:00 2001 From: Julia Smerdel <97158770+Jullija@users.noreply.github.com> Date: Sun, 25 Jan 2026 14:55:12 +0000 Subject: [PATCH 18/18] add concepts --- src/core/include/mp-units/cartesian_vector.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h index c4396eca1d..900b2f7094 100644 --- a/src/core/include/mp-units/cartesian_vector.h +++ b/src/core/include/mp-units/cartesian_vector.h @@ -74,6 +74,7 @@ class cartesian_vector { { } + [[nodiscard]] constexpr T magnitude() const requires treat_as_floating_point { @@ -125,7 +126,9 @@ class cartesian_vector { } template - requires requires(T& t, const S& s) { t *= s; } + requires requires(T& t, const S& s) { + { t *= s } -> std::same_as; + } constexpr cartesian_vector& operator*=(const S& scalar) { _coordinates_[0] *= scalar; @@ -135,7 +138,9 @@ class cartesian_vector { } template - requires requires(T& t, const S& s) { t /= s; } + requires requires(T& t, const S& s) { + { t /= s } -> std::same_as; + } constexpr cartesian_vector& operator/=(const S& scalar) { _coordinates_[0] /= scalar;