diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 2132d71a..e6251cf2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -91,6 +91,7 @@ if(NOT ${projectPrefix}API_FREESTANDING) include/mp-units/bits/fmt.h include/mp-units/bits/requires_hosted.h include/mp-units/ext/format.h + include/mp-units/cartesian_vector.h include/mp-units/complex.h include/mp-units/format.h include/mp-units/math.h diff --git a/src/core/include/mp-units/cartesian_vector.h b/src/core/include/mp-units/cartesian_vector.h new file mode 100644 index 00000000..7227f193 --- /dev/null +++ b/src/core/include/mp-units/cartesian_vector.h @@ -0,0 +1,230 @@ +// 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 + +#if MP_UNITS_HOSTED +#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 +#endif + +namespace mp_units { + +MP_UNITS_EXPORT template +class cartesian_vector { +public: + // public members required to satisfy structural type requirements :-( + 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 Arg1, std::convertible_to... Args> + constexpr cartesian_vector(Arg1&& arg1, Args&&... args) : + _coordinates_(std::forward(arg1), std::forward(args)...) + { + } + + template U> + constexpr cartesian_vector(const cartesian_vector& other) : _coordinates_{other[0], other[1], other[2]} + { + } + + template U> + constexpr cartesian_vector(cartesian_vector&& other) : + _coordinates_{std::move(other[0]), std::move(other[1]), std::move(other[2])} + { + } + + 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 + { + return std::hypot(_coordinates_[0], _coordinates_[1], _coordinates_[2]); + } + + [[nodiscard]] constexpr cartesian_vector unit() const + requires treat_as_floating_point + { + 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]}; + } + + template U, typename V> + requires requires(U u, V v) { u + v; } + [[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 U, typename V> + requires requires(U u, V v) { u - v; } + [[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 U> + requires requires(U u, T t) { u* t; } + [[nodiscard]] friend constexpr auto operator*(const cartesian_vector& lhs, const T& rhs) + { + return ::mp_units::cartesian_vector{lhs._coordinates_[0] * rhs, lhs._coordinates_[1] * rhs, + lhs._coordinates_[2] * rhs}; + } + + template U> + requires requires(T t, U u) { t* u; } + [[nodiscard]] friend constexpr auto operator*(const T& lhs, const cartesian_vector& rhs) + { + return rhs * lhs; + } + + template U> + requires requires(U u, T t) { u / t; } + [[nodiscard]] friend constexpr auto operator/(const cartesian_vector& lhs, const T& rhs) + { + return ::mp_units::cartesian_vector{lhs._coordinates_[0] / rhs, lhs._coordinates_[1] / rhs, + lhs._coordinates_[2] / rhs}; + } + + template U, std::equality_comparable_with V> + [[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]; + } + + [[nodiscard]] friend constexpr T norm(const cartesian_vector& v) + requires treat_as_floating_point + { + return v.magnitude(); + } + + [[nodiscard]] friend constexpr cartesian_vector unit_vector(const cartesian_vector& v) + requires treat_as_floating_point + { + return v.unit(); + } + + template U, typename V> + requires requires(U u, V v, decltype(u * v) t) { + u* v; + t + t; + } + [[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 U, typename V> + requires requires(U u, V v, decltype(u * v) t) { + u* v; + t - t; + } + [[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 constexpr std::ostream& operator<<(std::ostream& os, const cartesian_vector& v) + { + return os << '[' << v[0] << ", " << v[1] << ", " << v[2] << ']'; + } +#endif +}; + +template + requires(sizeof...(Args) <= 2) && requires { typename std::common_type_t; } +cartesian_vector(Arg, Args...) -> cartesian_vector>; + +template +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, FormatContext& ctx) const + { + return format_to(ctx.out(), "[{}, {}, {}]", v[0], v[1], v[2]); + } +}; +#endif diff --git a/src/core/include/mp-units/core.h b/src/core/include/mp-units/core.h index a99978f8..702f3b6b 100644 --- a/src/core/include/mp-units/core.h +++ b/src/core/include/mp-units/core.h @@ -28,6 +28,7 @@ #include #if MP_UNITS_HOSTED +#include #include #include #include diff --git a/test/runtime/CMakeLists.txt b/test/runtime/CMakeLists.txt index d79ed54b..c1d990ba 100644 --- a/test/runtime/CMakeLists.txt +++ b/test/runtime/CMakeLists.txt @@ -24,13 +24,14 @@ find_package(Catch2 3 REQUIRED) add_executable( unit_tests_runtime + atomic_test.cpp + cartesian_vector_test.cpp distribution_test.cpp fixed_string_test.cpp fmt_test.cpp math_test.cpp - atomic_test.cpp - truncation_test.cpp quantity_test.cpp + truncation_test.cpp ) if(${projectPrefix}BUILD_CXX_MODULES) target_compile_definitions(unit_tests_runtime PUBLIC ${projectPrefix}MODULES) diff --git a/test/runtime/cartesian_vector_test.cpp b/test/runtime/cartesian_vector_test.cpp new file mode 100644 index 00000000..04dfe28b --- /dev/null +++ b/test/runtime/cartesian_vector_test.cpp @@ -0,0 +1,374 @@ +// 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" +#include +#include +#ifdef MP_UNITS_MODULES +import mp_units; +#else +#include +#endif + +using namespace mp_units; +using namespace Catch::Matchers; + +TEST_CASE("cartesian_vector operations", "[vector]") +{ + SECTION("cartesian_vector initialization and access") + { + 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("convertibility") + { + 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("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); + } + } + + 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("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); + } + } + + 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("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("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("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); + } + } +} + +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()); } + } +}