From 03915e579a8b456d2e7afeba5bc3762a73585f04 Mon Sep 17 00:00:00 2001 From: jtlap Date: Sun, 10 Sep 2023 15:22:40 +0200 Subject: [PATCH] Implements a bunch of quaternion-specific functions --- include/kyosu/cayley.hpp | 80 ---- include/kyosu/functions.hpp | 42 ++- include/kyosu/functions/dot.hpp | 81 ++++ include/kyosu/functions/from_angle_axis.hpp | 90 +++++ include/kyosu/functions/from_cylindrical.hpp | 96 +++++ .../functions/from_cylindricospherical.hpp | 99 +++++ include/kyosu/functions/from_euler.hpp | 151 ++++++++ include/kyosu/functions/from_multipolar.hpp | 97 +++++ include/kyosu/functions/from_polar.hpp | 91 +++++ .../kyosu/functions/from_rotation_matrix.hpp | 115 ++++++ include/kyosu/functions/from_semipolar.hpp | 97 +++++ include/kyosu/functions/from_spherical.hpp | 101 +++++ include/kyosu/functions/is_unitary.hpp | 75 ++++ include/kyosu/functions/multipolar.hpp | 97 +++++ include/kyosu/functions/pure.hpp | 2 +- include/kyosu/functions/rot_angle.hpp | 83 +++++ include/kyosu/functions/rot_axis.hpp | 84 +++++ include/kyosu/functions/rotate_vec | 104 ++++++ include/kyosu/functions/rotate_vec.hpp | 105 ++++++ include/kyosu/functions/semipolar.hpp | 100 +++++ include/kyosu/functions/slerp.hpp | 83 +++++ include/kyosu/functions/sqrt.hpp | 7 +- include/kyosu/functions/to_angle_axis.hpp | 85 +++++ include/kyosu/functions/to_cylindrical.hpp | 86 +++++ include/kyosu/functions/to_euler.hpp | 124 +++++++ include/kyosu/functions/to_multipolar.hpp | 86 +++++ include/kyosu/functions/to_polar.hpp | 86 +++++ .../kyosu/functions/to_rotation_matrix.hpp | 116 ++++++ include/kyosu/functions/to_semipolar.hpp | 86 +++++ include/kyosu/functions/to_spherical.hpp | 86 +++++ include/kyosu/types/cayley_dickson.hpp | 3 +- include/kyosu/types/impl/arithmetic.hpp | 27 +- .../kyosu/types/impl/complex/arithmetic.hpp | 9 +- include/kyosu/types/impl/math.hpp | 4 +- include/kyosu/types/impl/predicates.hpp | 20 +- include/kyosu/types/impl/quaternion/axis.hpp | 28 ++ .../kyosu/types/impl/quaternion/specific.hpp | 349 ++++++++++++++++++ test/doc/conversions.cpp | 32 ++ test/doc/dot.cpp | 45 +++ test/doc/from_cylindrical.cpp | 22 ++ test/doc/from_euler.cpp | 23 ++ test/doc/is_unitary.cpp | 48 +++ test/unit/complex/arg.cpp | 36 ++ test/unit/complex/to_polar.cpp | 32 ++ test/unit/function/average.cpp | 49 +++ test/unit/function/csch.cpp | 49 +++ test/unit/function/dot.cpp | 53 +++ test/unit/function/exp_ipi.cpp | 29 ++ test/unit/function/ipart.cpp | 52 +++ test/unit/function/is_unitary.cpp | 62 ++++ test/unit/function/jpart.cpp | 52 +++ test/unit/function/kpart.cpp | 52 +++ test/unit/function/log_abs.cpp | 32 ++ test/unit/function/pure.cpp | 52 +++ test/unit/function/real.cpp | 52 +++ test/unit/function/sech.cpp | 49 +++ test/unit/function/sign.cpp | 32 ++ test/unit/function/sqrt.cpp | 44 +++ test/unit/quaternion/rotate_vec.cpp | 36 ++ test/unit/quaternion/slerp.cpp | 42 +++ test/unit/quaternion/to_angle_axis.cpp | 44 +++ test/unit/quaternion/to_cylindrical.cpp | 41 ++ test/unit/quaternion/to_euler.cpp | 36 ++ test/unit/quaternion/to_multipolar.cpp | 41 ++ test/unit/quaternion/to_rotation_matrix.cpp | 52 +++ test/unit/quaternion/to_semipolar.cpp | 41 ++ test/unit/quaternion/to_spherical.cpp | 41 ++ 67 files changed, 4241 insertions(+), 105 deletions(-) delete mode 100644 include/kyosu/cayley.hpp create mode 100644 include/kyosu/functions/dot.hpp create mode 100644 include/kyosu/functions/from_angle_axis.hpp create mode 100644 include/kyosu/functions/from_cylindrical.hpp create mode 100644 include/kyosu/functions/from_cylindricospherical.hpp create mode 100644 include/kyosu/functions/from_euler.hpp create mode 100644 include/kyosu/functions/from_multipolar.hpp create mode 100644 include/kyosu/functions/from_polar.hpp create mode 100644 include/kyosu/functions/from_rotation_matrix.hpp create mode 100644 include/kyosu/functions/from_semipolar.hpp create mode 100644 include/kyosu/functions/from_spherical.hpp create mode 100644 include/kyosu/functions/is_unitary.hpp create mode 100644 include/kyosu/functions/multipolar.hpp create mode 100644 include/kyosu/functions/rot_angle.hpp create mode 100644 include/kyosu/functions/rot_axis.hpp create mode 100644 include/kyosu/functions/rotate_vec create mode 100644 include/kyosu/functions/rotate_vec.hpp create mode 100644 include/kyosu/functions/semipolar.hpp create mode 100644 include/kyosu/functions/slerp.hpp create mode 100644 include/kyosu/functions/to_angle_axis.hpp create mode 100644 include/kyosu/functions/to_cylindrical.hpp create mode 100644 include/kyosu/functions/to_euler.hpp create mode 100644 include/kyosu/functions/to_multipolar.hpp create mode 100644 include/kyosu/functions/to_polar.hpp create mode 100644 include/kyosu/functions/to_rotation_matrix.hpp create mode 100644 include/kyosu/functions/to_semipolar.hpp create mode 100644 include/kyosu/functions/to_spherical.hpp create mode 100644 include/kyosu/types/impl/quaternion/axis.hpp create mode 100644 include/kyosu/types/impl/quaternion/specific.hpp create mode 100644 test/doc/conversions.cpp create mode 100644 test/doc/dot.cpp create mode 100644 test/doc/from_cylindrical.cpp create mode 100644 test/doc/from_euler.cpp create mode 100644 test/doc/is_unitary.cpp create mode 100644 test/unit/complex/arg.cpp create mode 100644 test/unit/complex/to_polar.cpp create mode 100644 test/unit/function/average.cpp create mode 100644 test/unit/function/csch.cpp create mode 100644 test/unit/function/dot.cpp create mode 100644 test/unit/function/exp_ipi.cpp create mode 100644 test/unit/function/ipart.cpp create mode 100644 test/unit/function/is_unitary.cpp create mode 100644 test/unit/function/jpart.cpp create mode 100644 test/unit/function/kpart.cpp create mode 100644 test/unit/function/log_abs.cpp create mode 100644 test/unit/function/pure.cpp create mode 100644 test/unit/function/real.cpp create mode 100644 test/unit/function/sech.cpp create mode 100644 test/unit/function/sign.cpp create mode 100644 test/unit/function/sqrt.cpp create mode 100644 test/unit/quaternion/rotate_vec.cpp create mode 100644 test/unit/quaternion/slerp.cpp create mode 100644 test/unit/quaternion/to_angle_axis.cpp create mode 100644 test/unit/quaternion/to_cylindrical.cpp create mode 100644 test/unit/quaternion/to_euler.cpp create mode 100644 test/unit/quaternion/to_multipolar.cpp create mode 100644 test/unit/quaternion/to_rotation_matrix.cpp create mode 100644 test/unit/quaternion/to_semipolar.cpp create mode 100644 test/unit/quaternion/to_spherical.cpp diff --git a/include/kyosu/cayley.hpp b/include/kyosu/cayley.hpp deleted file mode 100644 index c4ea1332..00000000 --- a/include/kyosu/cayley.hpp +++ /dev/null @@ -1,80 +0,0 @@ -//====================================================================================================================== -/* - Kyosu - Complex Without Complexes - Copyright : KYOSU Contributors & Maintainers - SPDX-License-Identifier: BSL-1.0 -*/ -//====================================================================================================================== -#pragma once - -//====================================================================================================================== -//! @defgroup functions cayley Functions -//! @brief Functions performing computations over all cayley-dicson types, complex, quaternions and octonions... -//====================================================================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/include/kyosu/functions.hpp b/include/kyosu/functions.hpp index a1e4dc70..0d6a7849 100644 --- a/include/kyosu/functions.hpp +++ b/include/kyosu/functions.hpp @@ -27,14 +27,15 @@ #include #include #include +#include #include -#include #include +#include +#include +#include #include #include #include -#include -#include #include #include #include @@ -55,13 +56,14 @@ #include #include #include +#include #include #include #include #include -#include -#include #include +#include +#include #include #include #include @@ -69,11 +71,10 @@ #include #include #include -#include #include #include +#include #include -#include #include #include #include @@ -87,8 +88,9 @@ #include #include #include -#include +#include #include +#include #include #include #include @@ -102,3 +104,27 @@ #include #include #include +#include +#include + +//====================================================================================================================== +//! @brief Functions performing computations over quaternion complex or real elements only. +//====================================================================================================================== + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/include/kyosu/functions/dot.hpp b/include/kyosu/functions/dot.hpp new file mode 100644 index 00000000..40d57f83 --- /dev/null +++ b/include/kyosu/functions/dot.hpp @@ -0,0 +1,81 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#pragma once + +#include + +namespace kyosu::tags +{ + struct callable_dot : eve::elementwise + { + using callable_tag_type = callable_dot; + + KYOSU_DEFERS_CALLABLE(dot_); + + static KYOSU_FORCEINLINE auto deferred_call(auto + , eve::ordered_value auto const& v0 + , eve::ordered_value auto const& v1) noexcept + { + return eve::dot(v0, v1); + } + + KYOSU_FORCEINLINE auto operator()(auto const& target0, auto const& target1 ) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1)) + { + return eve::tag_invoke(*this, target0, target1); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ +//====================================================================================================================== +//! @addtogroup functions +//! @{ +//! @var dot +//! @brief Computes elementwise the dot product of the coordinates of the corresponding element. +//! +//! **Defined in Header** +//! +//! @code +//! #include +//! @endcode +//! +//! @groupheader{Callable Signatures} +//! +//! @code +//! namespace kyosu +//! { +//! template constexpr auto dot(T0 z0, T1, z1) noexcept; +//! template > constexpr auto dot(T0 z0, T1, z1) noexcept; +//! template constexpr auto dot(T0 z0, T1, z1) noexcept; +//! template > constexpr auto dot(T0 z0, T1, z1) noexcept; +///! } +//! @endcode +//! +//! **Parameters** +//! +//! * `z0, z1` : Value to process. +//! +//! **Return value** +//! +//! Returns the dot product of z0 and z1. If z0 and z1 are floating point this is equivalent to z0*z1. +//! +//! `dot(z0, z0)` is always semantically equivalent to `sqr_abs(z0)`. +//! +//! @groupheader{Example} +//! +//! @godbolt{doc/dot.cpp} +//! @} +//====================================================================================================================== +inline constexpr tags::callable_dot dot = {}; +} diff --git a/include/kyosu/functions/from_angle_axis.hpp b/include/kyosu/functions/from_angle_axis.hpp new file mode 100644 index 00000000..b015676c --- /dev/null +++ b/include/kyosu/functions/from_angle_axis.hpp @@ -0,0 +1,90 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include +#include + +namespace kyosu::tags +{ + struct callable_from_angle_axis : eve::elementwise + { + using callable_tag_type = callable_from_angle_axis; + + KYOSU_DEFERS_CALLABLE(from_angle_axis_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & angle + , std::span axis) noexcept + { + auto q = to_quaternion(U(0), axis[0], axis[1], axis[2]); + auto [s, c] = eve::sincos(angle*eve::half(eve::as(angle))); + return c+s*q; + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0, + T1 const& target1 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1)) + { + return eve::tag_invoke(*this, target0, target1); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var from_angle_axis + //! + //! @brief Callable object computing a quaternion from its angle_axis representation. + //! + //! This function build an unitary quaternion from an angle value and a 3 dimensionnal axis vector + //! + //! **Defined in header** + //! + //! @code + //! #include kyosu/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace kyosu + //! { + //! auto from_angle_axis(auto angle, auto axis, auto norming = normalize) const noexcept; + //! } + //! @endcode + //! + //! **Parameters** + //! + //! * `angle` : rotation angle in radian + //! * `axis`` : rotation axis given by an std::span of dimension 3. + //! * normalize : can be assume_normalized or normalize in the second case axis is normalized. + //! if axis is already normalized use assume_normalized. + //! + //! **Return value** + //! + //! the quaternion value representing the rotation. + //! + //! @groupheader{Example} + //! + //! @godbolt{doc/quaternion/regular/conversions.cpp} + //! @} + //================================================================================================ + inline constexpr tags::callable_from_angle_axis from_angle_axis = {}; +} diff --git a/include/kyosu/functions/from_cylindrical.hpp b/include/kyosu/functions/from_cylindrical.hpp new file mode 100644 index 00000000..46b90abc --- /dev/null +++ b/include/kyosu/functions/from_cylindrical.hpp @@ -0,0 +1,96 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_from_cylindrical : eve::elementwise + { + using callable_tag_type = callable_from_cylindrical; + + KYOSU_DEFERS_CALLABLE(from_cylindrical_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & r + , U const & angle + , W const & h1 + , T const & h2) noexcept + { + using e_t = decltype(r+angle+h1+h2); + auto [sa, ca] = eve::sincos(angle); + return kyosu::to_quaternion(r*ca, r*sa, h1, h2); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0, + T1 const& target1, + T2 const& target2, + T3 const& target3 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1, target2, target3)) + { + return eve::tag_invoke(*this, target0, target1, target2, target3); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var from_cylindrical + //! + //! @brief Callable object computing a quaternion from its cylindrical representation. + //! + //! This function build quaternions in a way similar to the way polar builds complex numbers + //! from a cylindrical representation of an \f$\mathbb{R}^2\f$ element. + //! + //! from_cylindrical first two inputs are the polar coordinates of the first \f$\mathbb{C}\f$ + //! component of the quaternion. + //! The third and fourth inputs are placed into the third and fourth \f$\mathbb{R}\f$ + //! components of the quaternion, respectively. + //! + //! **Defined in header** + //! + //! @code + //! #include kyosu/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace kyosu + //! { + //! auto from_cylindrical(auto r, auto angle, auto h1, auto h2) const noexcept; + //! } + //! @endcode + //! + //! **Parameters** + //! + //! * `r`, angle`, `h1`, `h2` + //! + //! **Return value** + //! + //! the quaternion value. + //! + //! @groupheader{Example} + //! + //! @godbolt{doc/quaternion/regular/conversions.cpp} + //! @} + //================================================================================================ + inline constexpr tags::callable_from_cylindrical from_cylindrical = {}; +} diff --git a/include/kyosu/functions/from_cylindricospherical.hpp b/include/kyosu/functions/from_cylindricospherical.hpp new file mode 100644 index 00000000..9c319a96 --- /dev/null +++ b/include/kyosu/functions/from_cylindricospherical.hpp @@ -0,0 +1,99 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_from_cylindrospherical : eve::elementwise + { + using callable_tag_type = callable_from_cylindrospherical; + + KYOSU_DEFERS_CALLABLE(from_cylindrospherical_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & r + , U const & angle + , W const & h1 + , T const & h2) noexcept + { + auto [slat, clat] = eve::sincos(latitude); + auto [slon, clon] = eve::sincos(longitude); + auto f = r*clat; + return kyosu::to_quaternion(t, f*clon, f*slon, r*slat); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0, + T1 const& target1, + T2 const& target2, + T3 const& target3 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1, target2, target3)) + { + return eve::tag_invoke(*this, target0, target1, target2, target3); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var from_cylindrospherical + //! + //! @brief Callable object computing a quaternion from its cylindrospherical representation. + //! + //! cylindrospherical is specific to quaternions. It is often interesting to consider + //! \f$\mathbb{H}\f$ as the cartesian product of \f$\mathbb{R}\f$ by \f$\mathbb{R3}\f$ + //! (the quaternionic multiplication has then a special form, as given here). + //! This function therefore builds a quaternion from this representation, with the \f$\mathbb{R3}\f$ component given + //! in usual \f$\mathbb{R3}\f$ spherical coordinates. + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto from_cylindrospherical(auto t, auto radius, auto longitude, auto latitude) const noexcept; + //! } + //! @endcode + //! + //! **Parameters** + //! + //! * `t`, `radius`: the moduli + //! * `longitude`, `latitude`: angles in radian + //! + //! **Return value** + //! + //! the quaternion value + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/conversions.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_from_cylindrospherical from_cylindrospherical = {}; +} diff --git a/include/kyosu/functions/from_euler.hpp b/include/kyosu/functions/from_euler.hpp new file mode 100644 index 00000000..897d77a4 --- /dev/null +++ b/include/kyosu/functions/from_euler.hpp @@ -0,0 +1,151 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include +#include + +namespace kyosu::tags +{ + struct callable_from_euler : eve::elementwise + { + using callable_tag_type = callable_from_euler; + + KYOSU_DEFERS_CALLABLE(from_euler_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , U const & v1 + , V const & v2 + , W const & v3 + , _::axis + , _::axis + , _::axis + , _::ext + ) noexcept + requires(I != J && J != K) + { + using e_t = decltype(v1+v2+v3); + using q_t = decltype(to_quaternion(e_t{})); + auto h = eve::half(eve::as()); + std::array qs; + auto [sa, ca] = eve::sincos(v3*h); + auto [sb, cb] = eve::sincos(v2*h); + auto [sc, cc] = eve::sincos(v1*h); + get<0>(qs[0]) = ca; + get<0>(qs[1]) = cb; + get(qs[1]) = sb; + get<0>(qs[2]) = cc; + if constexpr(!Extrinsic) + { + get(qs[0]) = sa; + get(qs[2]) = sc; + q_t q = qs[2]*qs[1]*qs[0]; + return q; + } + else + { + get(qs[0]) = sa; + get(qs[2]) = sc; + q_t q = qs[0]*qs[1]*qs[2]; + return q; + } + } + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0, + T1 const& target1, + T2 const& target2 + , _::axis + , _::axis + , _::axis + , _::ext + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1, target2 + , _::axis{} + , _::axis{} + , _::axis{} + , _::ext{} + )) + { + return eve::tag_invoke(*this, target0, target1, target2 + , _::axis{} + , _::axis{} + , _::axis{} + , _::ext{} ); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var from_euler + //! + //! @brief Callable object computing a quaternion from its euler representation. + //! + //! This function build euler angles from 3 euler angles in radian. Template parameters I, J, K of type int + //! are used to choose the euler axis order. + //! + //! for instance I = 3, J = 2, K = 3 choose the ZYZ sequence. + //! the values of I, J, and K must be in {1, 2, 3} ans satisfy I != J && J != K. + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! template < int I, int J, int K > auto from_euler(auto a, auto b, auto c + //! , axis a1, axis a2, axis a3 + //! , ext e + //! ) const noexcept; + //! requires(I != J && J != K) + //! } + //! @endcode + //! + //! **Parameters** + //! + //! * `a`, `b`, `c` : the angles in radian + //! * `a1`, `a2`, `a3` the axis parameters to be chosen between X_, Y_, Z_ (two consecutive axis cannot be the same) + //! * `e' : allows to choose between Extrinsic or Intrinsic representations. + //! + //! **Template parameters** + //! + //! * I, J, K : are on call deduced from the axis parameters + //! + //! + //! The computation method is taken from the article : "Quaternion to Euler angles conversion: A + //! direct, general and computationally efficient method". PLoS ONE + //! 17(11): e0276302. https://doi.org/10.1371/journal pone 0276302. + //! Evandro Bernardes, and Stephane Viollet + //! + //! **Return value** + //! + //! quaternion representing the rotation + //! + //! @groupheader{Example} + //! + //! @godbolt{doc/quaternion/regular/from_euler.cpp} + //! @} + //================================================================================================ + inline constexpr tags::callable_from_euler from_euler = {}; +} diff --git a/include/kyosu/functions/from_multipolar.hpp b/include/kyosu/functions/from_multipolar.hpp new file mode 100644 index 00000000..d68bd425 --- /dev/null +++ b/include/kyosu/functions/from_multipolar.hpp @@ -0,0 +1,97 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include +#include + +namespace kyosu::tags +{ + struct callable_from_multipolar : eve::elementwise + { + using callable_tag_type = callable_from_multipolar; + + KYOSU_DEFERS_CALLABLE(from_multipolar_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & rho1 + , U const & theta1 + , W const & rho2 + , T const & theta2) noexcept + { + auto [a0, a1] = kyosu::from_polar(rho1, theta1); + auto [a2, a3] = kyosu::from_polar(rho2, theta2); + return kyosu::to_quaternion(a0, a1, a2, a3); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0, + T1 const& target1, + T2 const& target2, + T3 const& target3 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1, target2, target3)) + { + return eve::tag_invoke(*this, target0, target1, target2, target3); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var from_multipolar + //! + //! @brief Callable object computing a quaternion from its multipolar representation. + //! + //! This function build quaternions in a way similar to the way polar builds complex numbers + //! from a multipolar representation of an \f$\mathbb{R}^4\f$ element. + //! + //! from_multipolar the two \f$\mathbb{C}\f$ components of the quaternion are given in polar coordinates + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto from_multipolar( auto rho1, auto theta1 auto rho2, auto theta2) const noexcept; + //! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //! + //! **Parameters** + //! + //! `rho1`, `rho2`: the moduli + //! 'theta1', 'theta2': the angles in radian + //! + //! **Return value** + //! + //! the quaternion value + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/quaternion/regular/conversions.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_from_multipolar from_multipolar = {}; +} diff --git a/include/kyosu/functions/from_polar.hpp b/include/kyosu/functions/from_polar.hpp new file mode 100644 index 00000000..6e96d996 --- /dev/null +++ b/include/kyosu/functions/from_polar.hpp @@ -0,0 +1,91 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_from_polar : eve::elementwise + { + using callable_tag_type = callable_from_polar; + + KYOSU_DEFERS_CALLABLE(from_polar_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & v + , U const & u) noexcept + { + auto r = eve::abs(v); + auto a = eve::if_else(eve::is_positive(v), u, eve::pi(eve::as(u))+u); + auto [s, c] = eve::sincos(a); + return to_complex(r*c, r*s); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0, T1 const& target1 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1)) + { + return eve::tag_invoke(*this, target0, target1); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup complex + //! @{ + //! @var from_polar + //! + //! @brief Callable object computing a complex from its polar coordinates. + //! + //! This function is the reciprocal of from_polar + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto from_polar( auto rho, auto theta) const noexcept; + //! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //! + //! **Parameters** + //! + //! `rho` : modulus + //! `rho` : argument. + //! + //! **Return value** + //! + //! the complex number associated. + //! + //! @note : a negative rho is not an error but is treated as {-rho, theta+pi}. + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/quaternion/regular/from_polar.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_from_polar from_polar = {}; +} diff --git a/include/kyosu/functions/from_rotation_matrix.hpp b/include/kyosu/functions/from_rotation_matrix.hpp new file mode 100644 index 00000000..b653dd2a --- /dev/null +++ b/include/kyosu/functions/from_rotation_matrix.hpp @@ -0,0 +1,115 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_from_rotation_matrix : eve::elementwise + { + using callable_tag_type = callable_from_rotation_matrix; + + KYOSU_DEFERS_CALLABLE(from_rotation_matrix_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , M const & r) noexcept + { + auto r11pr22 = r[1][1] + r[2][2]; + auto qq0m1 = r[0][0] + r11pr22; + auto qq1m1 = r[0][0] - r11pr22; + auto r11mr22 = r[1][1] - r[2][2]; + auto qq2m1 = -r[0][0] + r11mr22; + auto qq3m1 = -r[0][0] - r11mr22; + + auto r21mr12 = r[2][1] - r[1][2]; + auto r02mr20 = r[0][2] - r[2][0]; + auto r10mr01 = r[1][0] - r[0][1]; + auto r01pr10 = r[1][0] + r[0][1]; + auto r20pr02 = r[2][0] + r[0][2]; + auto r12pr21 = r[1][2] + r[2][1]; + + auto h = half(as(r11pr22)); + auto q0 = eve::sqrt(eve::if_else(is_gtz(qq0m1), eve::inc(qq0m1), (eve::sqr(r21mr12)+sqr(r02mr20)+eve::sqr(r10mr01))/(3-qq0m1)))*h; + auto q1 = eve::sqrt(eve::if_else(is_gtz(qq1m1), eve::inc(qq1m1), (eve::sqr(r21mr12)+sqr(r01pr10)+eve::sqr(r20pr02))/(3-qq1m1)))*h; + auto q2 = eve::sqrt(eve::if_else(is_gtz(qq2m1), eve::inc(qq2m1), (eve::sqr(r02mr20)+sqr(r01pr10)+eve::sqr(r12pr21))/(3-qq2m1)))*h; + auto q3 = eve::sqrt(eve::if_else(is_gtz(qq3m1), eve::inc(qq3m1), (eve::sqr(r10mr01)+sqr(r20pr02)+eve::sqr(r12pr21))/(3-qq3m1)))*h; + using e_t = decltype(r11pr22); + return to_quaternion(q0, q1, q2, q3); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0, + T1 const& target1, + T2 const& target2, + T3 const& target3 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1, target2, target3)) + { + return eve::tag_invoke(*this, target0, target1, target2, target3); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var from_rotation_matrix + //! + //! @brief Callable object computing a quaternion from its rotation_matrix representation. + //! + //! This function returns a quaternion associated to the input rotation matrix m. + //! If m is not a proper rotation 3x3 rotation matrix (i.e an orthogonal matrix with determinant 1) + //! the result is undefined. + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! template < typename M > + //! auto from_rotation_matrix(auto m) const noexcept + //! } + //! @endcode + //! + //! **Parameters** + //! + //! * `m` the rotation matrix. The actual implementation assumes that m[i][j] will return + //! the ith line and jth column element of the matrix (indices starting from 0). + //! + //! + //! The computation method is inspired from the article : "Accurate Computation of + //! Quaternions from Rotation Matrices", by Soheil Sarabandi and Federico Thomas + //! Institut de Robotica i Informatica Industrial (CSIC-UPC) + //! Llorens Artigas 4-6, 08028 Barcelona, Spain. + //! + //! **Return value** + //! + //! an unitary quaternion representing the rotation + //! + //! @groupheader{Example} + //! + //! @godbolt{doc/from_rotation_matrix.cpp} + //! @} + //================================================================================================ + inline constexpr tags::callable_from_rotation_matrix from_rotation_matrix = {}; +} diff --git a/include/kyosu/functions/from_semipolar.hpp b/include/kyosu/functions/from_semipolar.hpp new file mode 100644 index 00000000..eddc4f95 --- /dev/null +++ b/include/kyosu/functions/from_semipolar.hpp @@ -0,0 +1,97 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_from_semipolar : eve::elementwise + { + using callable_tag_type = callable_from_semipolar; + + KYOSU_DEFERS_CALLABLE(from_semipolar_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & rho + , U const & alpha + , W const & theta1 + , T const & theta2) noexcept + { + auto [st1, ct1] = eve::sincos(theta1); + auto [st2, ct2] = eve::sincos(theta2); + auto [sa, ca] = eve::sincos(alpha); + return rho*kyosu::to_quaternion(ca*ct1, ca*st1, sa*ct2, sa*st2); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0, + T1 const& target1, + T2 const& target2, + T3 const& target3 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1, target2, target3)) + { + return eve::tag_invoke(*this, target0, target1, target2, target3); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var from_semipolar + //! + //! @brief Callable object computing a quaternion from its semipolar representation. + //! + //! This function build quaternions in a way similar to the way polar builds complex numbers + //! from a semipolar representation of an \f$\mathbb{R}^2\f$ element. + //! + //! from_semipolar first two inputs are the polar coordinates of the first \f$\mathbb{C}\f$ + //! component of the quaternion. + //! The third and fourth inputs are placed into the third and fourth \f$\mathbb{R}\f$ + //! components of the quaternion, respectively. + //! + //! **Defined in header** + //! + //! @code + //! #include kyosu/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace kyosu + //! { + //! auto from_semipolar(auto r, auto angle, auto h1, auto h2) const noexcept; + //! } + //! @endcode + //! + //! **Parameters** + //! + //! * `r`, angle`, `h1`, `h2` + //! + //! **Return value** + //! + //! the quaternion value. + //! + //! @groupheader{Example} + //! + //! @godbolt{doc/quaternion/regular/conversions.cpp} + //! @} + //================================================================================================ + inline constexpr tags::callable_from_semipolar from_semipolar = {}; +} diff --git a/include/kyosu/functions/from_spherical.hpp b/include/kyosu/functions/from_spherical.hpp new file mode 100644 index 00000000..0711e531 --- /dev/null +++ b/include/kyosu/functions/from_spherical.hpp @@ -0,0 +1,101 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_from_spherical : eve::elementwise + { + using callable_tag_type = callable_from_spherical; + + KYOSU_DEFERS_CALLABLE(from_spherical_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & rho + , U const & theta + , W const & phi1 + , T const & phi2) noexcept + { + auto [st, ct] = eve::sincos(theta); + auto [sp1, cp1] = eve::sincos(phi1); + auto [sp2, cp2] = eve::sincos(phi2); + auto f = cp1*cp2; + return rho*to_quaternion(ct*f, st*f, sp1*cp2, sp2); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0, + T1 const& target1, + T2 const& target2, + T3 const& target3 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1, target2, target3)) + { + return eve::tag_invoke(*this, target0, target1, target2, target3); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var from_spherical + //! + //! @brief Callable object computing a quaternion from its spherical representation. + //! + //! This function build quaternions in a way similar to the way polar builds complex numbers + //! from a spherical representation of an \f$\mathbb{R}^4\f$ element. + //! + //! from_spherical takes as inputs a (positive) magnitude and a point on the hypersphere, given by three angles. + //! The first of these, theta has a natural range of \f$-\pi\f$ to \f$+\pi\f$, and the other two have natural + //! ranges of \f$-\pi/2\f$ to \f$+\pi/2\f$ (as is the case with the usual spherical coordinates in \f$\mathbb{R}^3\f$). + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto from_spherical(auto rho, auto theta, auto phi1, auto phi2) const noexcept; + //! } + //! @endcode + //! + //! **Parameters** + //! + //! * `rho`: the modulus + //! * `theta`, 'phi1`, 'phi2`: angles in radian + //! + //! **Return value** + //! + //! the quaternion value + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/conversions.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_from_spherical from_spherical = {}; +} diff --git a/include/kyosu/functions/is_unitary.hpp b/include/kyosu/functions/is_unitary.hpp new file mode 100644 index 00000000..3c8b26b3 --- /dev/null +++ b/include/kyosu/functions/is_unitary.hpp @@ -0,0 +1,75 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#pragma once + +#include + +namespace kyosu::tags +{ + struct callable_is_unitary : eve::elementwise + { + using callable_tag_type = callable_is_unitary; + + KYOSU_DEFERS_CALLABLE(is_unitary_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto, T const& v) noexcept { return eve::abs(v) == eve::one(eve::as(v)); } + + template + KYOSU_FORCEINLINE auto operator()(T const& target) const noexcept -> decltype(eve::tag_invoke(*this, target)) + { + return eve::tag_invoke(*this, target); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ +//====================================================================================================================== +//! @addtogroup functions +//! @{ +//! @var is_unitary +//! @brief test if the parameter is unitary (absolute value one). +//! +//! **Defined in Header** +//! +//! @code +//! #include +//! @endcode +//! +//! @groupheader{Callable Signatures} +//! +//! @code +//! namespace kyosu +//! { +//! template constexpr auto is_unitary(T z) noexcept; +//! template constexpr auto is_unitary(T z) noexcept; +//! } +//! @endcode +//! +//! **Parameters** +//! +//! * `z` : Value to process. +//! +//! **Return value** +//! +//! Returns elementwise true if an element is of absolute value one. +//! +//! @note As for now is_unitary accepts almost equality (will change when decorators will be at hand in kyosu) +//! +//! @groupheader{Example} +//! +//! @godbolt{doc/is_unitary.cpp} +//! @} +//====================================================================================================================== +inline constexpr tags::callable_is_unitary is_unitary = {}; +} diff --git a/include/kyosu/functions/multipolar.hpp b/include/kyosu/functions/multipolar.hpp new file mode 100644 index 00000000..e1be237d --- /dev/null +++ b/include/kyosu/functions/multipolar.hpp @@ -0,0 +1,97 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_from_multipolar : eve::elementwise + { + using callable_tag_type = callable_from_multipolar; + + KYOSU_DEFERS_CALLABLE(from_multipolar_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & rho1 + , U const & theta1 + , W const & rho2 + , T const & theta2) noexcept + { + auto [slat, clat] = eve::sincos(latitude); + auto [slon, clon] = eve::sincos(longitude); + auto f = r*clat; + return kyosu::to_quaternion(t, f*clon, f*slon, r*slat); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0, + T1 const& target1, + T2 const& target2, + T3 const& target3 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1, target2, target3)) + { + return eve::tag_invoke(*this, target0, target1, target2, target3); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var from_multipolar + //! + //! @brief Callable object computing a quaternion from its multipolar representation. + //! + //! This function build quaternions in a way similar to the way polar builds complex numbers + //! from a multipolar representation of an \f$\mathbb{R}^4\f$ element. + //! + //! from_multipolar the two \f$\mathbb{C}\f$ components of the quaternion are given in polar coordinates + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto from_multipolar( auto rho1, auto theta1 auto rho2, auto theta2) const noexcept; + //! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //! + //! **Parameters** + //! + //!`rho1`, `rho2`: the moduli + //! 'theta1', 'theta2': the angles in radian + //! + //! **Return value** + //! + //! the quaternion value + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/quaternion/regular/conversions.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_from_multipolar from_multipolar = {}; +} diff --git a/include/kyosu/functions/pure.hpp b/include/kyosu/functions/pure.hpp index 1c254fa9..5afe2ceb 100644 --- a/include/kyosu/functions/pure.hpp +++ b/include/kyosu/functions/pure.hpp @@ -18,7 +18,7 @@ namespace kyosu::tags KYOSU_DEFERS_CALLABLE(pure_); template - static KYOSU_FORCEINLINE auto deferred_call(auto, T const& v) noexcept { return eve::zero(as(v)); } + static KYOSU_FORCEINLINE auto deferred_call(auto, T const& v) noexcept { return eve::zero(eve::as(v)); } template KYOSU_FORCEINLINE auto operator()(T const& target) const noexcept -> decltype(eve::tag_invoke(*this, target)) diff --git a/include/kyosu/functions/rot_angle.hpp b/include/kyosu/functions/rot_angle.hpp new file mode 100644 index 00000000..b03a8b21 --- /dev/null +++ b/include/kyosu/functions/rot_angle.hpp @@ -0,0 +1,83 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include + +namespace kyosu::tags +{ + struct callable_rot_angle : eve::elementwise + { + using callable_tag_type = callable_rot_angle; + + KYOSU_DEFERS_CALLABLE(rot_angle_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & v) noexcept + { + return eve::zero(as(v)); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0)) + { + return eve::tag_invoke(*this, target0); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var rot_angle + //! + //! @brief Callable object computing the normalized angle of rotation defined by a quaternion. + //! + //! This function is the reciprocal of from_polar + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto rot_angle( auto q) const noexcept; + //! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //! + //! **Parameters** + //! + //! `q` : quaternion + //! + //! **Return value** + //! + //! the rotation angle in radian + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/quaternion/regular/rot_angle.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_rot_angle rot_angle = {}; +} diff --git a/include/kyosu/functions/rot_axis.hpp b/include/kyosu/functions/rot_axis.hpp new file mode 100644 index 00000000..f6e897e8 --- /dev/null +++ b/include/kyosu/functions/rot_axis.hpp @@ -0,0 +1,84 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include + +namespace kyosu::tags +{ + struct callable_rot_axis : eve::elementwise + { + using callable_tag_type = callable_rot_axis; + + KYOSU_DEFERS_CALLABLE(rot_axis_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & v) noexcept + { + return std::array{1, 0, 0}; + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0)) + { + return eve::tag_invoke(*this, target0); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var rot_axis + //! + //! @brief Callable object computing the normalized axis of rotation defined by a quaternion. + //! + //! This function is the reciprocal of from_polar + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto rot_axis( auto q) const noexcept; + //! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //! + //! **Parameters** + //! + //! `q` : quaternion + //! + //! **Return value** + //! + //! a span containing the three coordinates of the axis + //! if the quaternion is zero {1, 0, 0} is returned + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/quaternion/regular/rot_axis.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_rot_axis rot_axis = {}; +} diff --git a/include/kyosu/functions/rotate_vec b/include/kyosu/functions/rotate_vec new file mode 100644 index 00000000..2e6ab4e8 --- /dev/null +++ b/include/kyosu/functions/rotate_vec @@ -0,0 +1,104 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_rot_vec : eve::elementwise + { + using callable_tag_type = callable_rot_vec; + + KYOSU_DEFERS_CALLABLE(rot_vec_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & v + , nor) noexcept + { + return v; + } + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & + , std::span const & v + ) noexcept + { + return v; + } + + template + KYOSU_FORCEINLINE auto operator()( T0 const& target0 + , T1 const& target1 + , nor) const noexcept + -> decltype(eve::tag_invoke(*this, target0, nor())) + { + return eve::tag_invoke(*this, target0, nor()); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0, + T1 const& target1) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1)) + { + return eve::tag_invoke(*this, target0, target1, Normalize); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var rot_vec + //! + //! @brief Callable object rotatating an \f$\mathbb{R}\f$ using a quaternion. + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto rot_vec( auto q) const noexcept; + //! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //! + //! **Parameters** + //! + //! `q`: [quaternion](@ref eve::value) defining the rotation. + //! `x`: span of 3 elements to rotate + //! + //! **Return value** + //! + //! the span rotated by q + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/quaternion/regular/rot_vec.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_rot_vec rot_vec = {}; +} diff --git a/include/kyosu/functions/rotate_vec.hpp b/include/kyosu/functions/rotate_vec.hpp new file mode 100644 index 00000000..1399e2c5 --- /dev/null +++ b/include/kyosu/functions/rotate_vec.hpp @@ -0,0 +1,105 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include +#include + +namespace kyosu::tags +{ + struct callable_rotate_vec : eve::elementwise + { + using callable_tag_type = callable_rotate_vec; + + KYOSU_DEFERS_CALLABLE(rotate_vec_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & + , std::span const & v + , _::norming) noexcept + { + return v; + } + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & + , std::span const & v + ) noexcept + { + return v; + } + + template + KYOSU_FORCEINLINE auto operator()( T0 const& target0 + , T1 const& target1 + , _::norming) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1, _::norming())) + { + return eve::tag_invoke(*this, target0, target1, _::norming()); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0, + T1 const& target1) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1)) + { + return eve::tag_invoke(*this, target0, target1, normalize); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var rotate_vec + //! + //! @brief Callable object rotating an \f$\mathbb{R}^3\f$ vector using a quaternion. + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto rotate_vec( auto q) const noexcept; + //! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //! + //! **Parameters** + //! + //! `q`: [quaternion](@ref eve::value) defining the rotation. + //! `x`: span of 3 elements to rotate + //! + //! **Return value** + //! + //! the span rotated by q + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/quaternion/regular/rotate_vec.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_rotate_vec rotate_vec = {}; +} diff --git a/include/kyosu/functions/semipolar.hpp b/include/kyosu/functions/semipolar.hpp new file mode 100644 index 00000000..b3fe9ee9 --- /dev/null +++ b/include/kyosu/functions/semipolar.hpp @@ -0,0 +1,100 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_from_semipolar : eve::elementwise + { + using callable_tag_type = callable_from_semipolar; + + KYOSU_DEFERS_CALLABLE(from_semipolar_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & rho + , U const & alpha + , W const & theta1 + , T const & theta2) noexcept + { + using z_t = eve::as_quaternion_t; + auto [st1, ct1] = eve::sincos(theta1); + auto [st2, ct2] = eve::sincos(theta2); + auto [sa, ca] = eve::sincos(alpha); + return rho*to_quaternion(ca*ct1, ca*st1, sa*ct2, sa*st2); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0, + T1 const& target1, + T2 const& target2, + T3 const& target3 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1, target2, target3)) + { + return eve::tag_invoke(*this, target0, target1, target2, target3); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var from_semipolar + //! + //! @brief Callable object computing a quaternion from its semipolar representation. + //! + //! This function build quaternions in a way similar to the way polar builds complex numbers + //! from a semipolar representation of an \f$\mathbb{R}^4\f$ element. + //! + //! from_semipolar takes as a first input the magnitude of the quaternion, + //! as a second input an angle in the range 0 to \f$\pi/2\f$ such that magnitudes of the first + //! two \f$\mathbb{C}\f$ components of the quaternion are the product of the first input and the sine and cosine + //! of this angle, respectively, and finally as third and fourth inputs angles in the range \f$-\pi/2\f$ to \f$+\pi/2\f$ + //! which represent the arguments of the first and second \f$\mathbb{C}\f$ components of the quaternion, respectively. + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto from_semipolar(auto rho, auto alpha auto theta1, auto theta2) const noexcept; + //! } + //! @endcode + //! + //! **Parameters** + //! + //! * `rho`: the modulus + //! * `alpha`, `theta1`, `theta2`: angles in radian + //! + //! **Return value** + //! + //! the quaternion value representing the \f$\R^4\f$ element. + //! + //! @groupheader{Example} + //! + //! @godbolt{doc/conversions.cpp} + //! @} + //================================================================================================ + inline constexpr tags::callable_from_semipolar from_semipolar = {}; +} diff --git a/include/kyosu/functions/slerp.hpp b/include/kyosu/functions/slerp.hpp new file mode 100644 index 00000000..59562934 --- /dev/null +++ b/include/kyosu/functions/slerp.hpp @@ -0,0 +1,83 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#pragma once + +#include + +namespace kyosu::tags +{ + struct callable_slerp : eve::elementwise + { + using callable_tag_type = callable_slerp; + + KYOSU_DEFERS_CALLABLE(slerp_); + + static KYOSU_FORCEINLINE auto deferred_call(auto + , eve::ordered_value auto const& v0 + , eve::ordered_value auto const& v1 + , eve::ordered_value auto const& t) noexcept + { + EVE_ASSERT(eve::all(is_unitary(v0) && is_unitary(v1)), "quaternion parameters must be unitary"); + return v0; + } + + KYOSU_FORCEINLINE auto operator()(auto const& target0, auto const& target1, auto const & target2) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1, target2)) + { + return eve::tag_invoke(*this, target0, target1, target2); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ +//====================================================================================================================== +//! @addtogroup functions +//! @{ +//! @var slerp +//! @brief Computes the spherical interpolation between unitary quaternions. +//! +//! **Defined in Header** +//! +//! @code +//! #include +//! @endcode +//! +//! @groupheader{Callable Signatures} +//! +//! @code +//! namespace kyosu +//! { +//! template constexpr auto slerp(T0 z0, T1, z1, auto eve::ordered_value t) noexcept; +//! template > constexpr auto slerp(T0 z0, T1, z1, auto eve::ordered_value t) noexcept; +//! template constexpr auto slerp(T0 z0, T1, z1, auto eve::ordered_value t) noexcept; +//! template > constexpr auto slerp(T0 z0, T1, z1, auto eve::ordered_value t) noexcept; +///! } +//! @endcode +//! +//! **Parameters** +//! +//! * `z0, z1` : unitary quaternions to process. +//! * `t` : floating value interpolation coefficient. +//! +//! **Return value** +//! +//! The value of the spherical interpolation (or extrapolation) between `z0` and `z1` is returned. +//! The functions asserts if the quaternions are not unitary. +//! +//! @groupheader{Example} +//! +//! @godbolt{doc/slerp.cpp} +//! @} +//====================================================================================================================== +inline constexpr tags::callable_slerp slerp = {}; +} diff --git a/include/kyosu/functions/sqrt.hpp b/include/kyosu/functions/sqrt.hpp index 129ec8f2..10b49e96 100644 --- a/include/kyosu/functions/sqrt.hpp +++ b/include/kyosu/functions/sqrt.hpp @@ -19,12 +19,11 @@ namespace kyosu::tags KYOSU_DEFERS_CALLABLE(sqrt_); - template + template static KYOSU_FORCEINLINE auto deferred_call(auto, T const& v) noexcept { - auto fn = callable_log{}; - return fn(kyosu::to_complex(v, T(0))); - + auto asq = eve::sqrt(eve::abs(v)); + return if_else(eve::is_gez(v), to_complex(asq, T(0)), to_complex(T(0), asq)); } template diff --git a/include/kyosu/functions/to_angle_axis.hpp b/include/kyosu/functions/to_angle_axis.hpp new file mode 100644 index 00000000..e6eaaa6d --- /dev/null +++ b/include/kyosu/functions/to_angle_axis.hpp @@ -0,0 +1,85 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_to_angle_axis : eve::elementwise + { + using callable_tag_type = callable_to_angle_axis; + + KYOSU_DEFERS_CALLABLE(to_angle_axis_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & v) noexcept + { + return kumi::tuple{V(0), std::array{V(1), V(0), V(0)}}; + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0)) + { + return eve::tag_invoke(*this, target0); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var to_angle_axis + //! + //! @brief Callable object computing the angle_axis coordinates from a quaternion. + //! + //! This function is the reciprocal of from_angle_axis + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto to_angle_axis( auto q) const noexcept; + //! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //! + //! **Parameters** + //! + //! `q` : quaternion + //! + //! **Return value** + //! + //! a tuple containing in this order `rho1`, 'theta1', `rho2` 'theta2': the moduli + //! and the angles in radian of the angle_axis \f$\mathbb{R}^4\f$ coordinates + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/quaternion/regular/to_angle_axis.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_to_angle_axis to_angle_axis = {}; +} diff --git a/include/kyosu/functions/to_cylindrical.hpp b/include/kyosu/functions/to_cylindrical.hpp new file mode 100644 index 00000000..5883bfcf --- /dev/null +++ b/include/kyosu/functions/to_cylindrical.hpp @@ -0,0 +1,86 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_to_cylindrical : eve::elementwise + { + using callable_tag_type = callable_to_cylindrical; + + KYOSU_DEFERS_CALLABLE(to_cylindrical_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & v) noexcept + { + auto z = eve::zero(eve::as(v)); + return kumi::tuple{eve::abs(v), eve::arg(v), z, z}; + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0)) + { + return eve::tag_invoke(*this, target0); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var to_cylindrical + //! + //! @brief Callable object computing the cylindrical coordinates from a quaternion. + //! + //! This function is the reciprocal of from_cylindrical + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto to_cylindrical( auto q) const noexcept; + //! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //! + //! **Parameters** + //! + //! `q` : quaternion + //! + //! **Return value** + //! + //! a tuple containing in this order `rho1`, 'theta1', `h1` 'h2': the components + //! of the cylindrical parametrisation of \f$\mathbb{R}^4\f$ coordinates + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/quaternion/regular/to_cylindrical.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_to_cylindrical to_cylindrical = {}; +} diff --git a/include/kyosu/functions/to_euler.hpp b/include/kyosu/functions/to_euler.hpp new file mode 100644 index 00000000..ed8830ee --- /dev/null +++ b/include/kyosu/functions/to_euler.hpp @@ -0,0 +1,124 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include +#include + +namespace kyosu::tags +{ + struct callable_to_euler : eve::elementwise + { + using callable_tag_type = callable_to_euler; + + KYOSU_DEFERS_CALLABLE(to_euler_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & v + , _::axis + , _::axis + , _::axis + , _::ext + ) noexcept + requires(I != J && J != K) + { + return kumi::tuple{eve::zero(eve::as()), eve::zero(eve::as()), eve::zero(eve::as())}; + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0 + , _::axis + , _::axis + , _::axis + , _::ext + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0 + , _::axis{} + , _::axis{} + , _::axis{} + , _::ext{} + )) + { + return eve::tag_invoke(*this, target0 + , _::axis{} + , _::axis{} + , _::axis{} + , _::ext{} ); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var to_euler + //! + //! @brief Callable object computing euler angles from a quaternion. + //! + //! This function build euler angles from a quaternion. Template parameters I, J, K of type int + //! are used to choose the euler order. + //! + //! for instance I = 3, J = 2, K = 3 choose the ZYZ sequence. + //! the values of I, J, and K must be in {1, 2, 3} ans satisfy I != J && J != K. + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! template < int I, int J, int K > + //! auto to_euler(auto q + //! , axis const & a1, axis const & a2, axis const & a3) const noexcept + //! requires(I != J && J != K) + //! } + //! @endcode + //! + //! **Parameters** + //! + //! * `q` the rotation quaternion (not necesseraly normalized) + //! * `a1`, `a2`, `a3` : the axis parameters to be chosen between X_, Y_, Z_ (two consecutive axis cannot be the same) + //! * depending of the euler order + //! + //! **Template parameters** + //! + //! * I, J, K : actual parameters can be chosen between axis values X_, Y_, Z_ from + //! which I, J and K are deduced + //! + //! + //! The computation method is taken from the article : "Quaternion to Euler angles conversion: A + //! direct, general and computationally efficient method". PLoS ONE + //! 17(11): e0276302. https://doi.org/10.1371/journal pone 0276302. + //! Evandro Bernardes, and Stephane Viollet + //! + //! **Return value** + //! + //! kumi tuple of the three euler angles in radian. + //! In case of singularity the first angle is 0. + //! + //! @groupheader{Example} + //! + //! @godbolt{doc/to_euler.cpp} + //! @} + //================================================================================================ + inline constexpr tags::callable_to_euler to_euler = {}; +} diff --git a/include/kyosu/functions/to_multipolar.hpp b/include/kyosu/functions/to_multipolar.hpp new file mode 100644 index 00000000..325d70c4 --- /dev/null +++ b/include/kyosu/functions/to_multipolar.hpp @@ -0,0 +1,86 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_to_multipolar : eve::elementwise + { + using callable_tag_type = callable_to_multipolar; + + KYOSU_DEFERS_CALLABLE(to_multipolar_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & v) noexcept + { + auto z = eve::zero(eve::as(v)); + return kumi::tuple{eve::abs(v), eve::arg(v), z, z}; + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0)) + { + return eve::tag_invoke(*this, target0); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var to_multipolar + //! + //! @brief Callable object computing the multipolar coordinates from a quaternion. + //! + //! This function is the reciprocal of from_multipolar + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto to_multipolar( auto q) const noexcept; + //! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //! + //! **Parameters** + //! + //! `q` : quaternion + //! + //! **Return value** + //! + //! a tuple containing in this order `rho1`, 'theta1', `rho2` 'theta2': the moduli + //! and the angles in radian of the multipolar \f$\mathbb{R}^4\f$ coordinates + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/quaternion/regular/to_multipolar.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_to_multipolar to_multipolar = {}; +} diff --git a/include/kyosu/functions/to_polar.hpp b/include/kyosu/functions/to_polar.hpp new file mode 100644 index 00000000..a28a9fcd --- /dev/null +++ b/include/kyosu/functions/to_polar.hpp @@ -0,0 +1,86 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_to_polar : eve::elementwise + { + using callable_tag_type = callable_to_polar; + + KYOSU_DEFERS_CALLABLE(to_polar_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & v) noexcept + { + auto z = eve::zero(eve::as(v)); + return kumi::tuple{eve::abs(v), eve::arg(v)}; + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0)) + { + return eve::tag_invoke(*this, target0); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup complex + //! @{ + //! @var to_polar + //! + //! @brief Callable object computing the polar coordinates from a complex. + //! + //! This function is the reciprocal of from_polar + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto to_polar( auto q) const noexcept; + //! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //! + //! **Parameters** + //! + //! `q` : quaternion + //! + //! **Return value** + //! + //! a tuple containing in this order `rho`, 'theta': the modulus + //! and the argument in radian of the complex input + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/quaternion/regular/to_polar.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_to_polar to_polar = {}; +} diff --git a/include/kyosu/functions/to_rotation_matrix.hpp b/include/kyosu/functions/to_rotation_matrix.hpp new file mode 100644 index 00000000..a59aea68 --- /dev/null +++ b/include/kyosu/functions/to_rotation_matrix.hpp @@ -0,0 +1,116 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include +#include + +namespace kyosu::tags +{ + struct callable_to_rotation_matrix : eve::elementwise + { + using callable_tag_type = callable_to_rotation_matrix; + + KYOSU_DEFERS_CALLABLE(to_rotation_matrix_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , [[maybe_unused]] V const & q + , _::norming) noexcept + { + if constexpr (!normalize) EVE_ASSERT(eve::all(kyosu::is_unitary(q)), "some quaternions are not unitary"); + using m_t = std::array< std::array, 3>; + return m_t{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}} ; + + } + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & q) noexcept + { + return to_rotation_matrix(q, assume_normalized); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 target0, + _::norming target1 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0, target1)) + { + return eve::tag_invoke(*this, target0, target1); + } + + template + KYOSU_FORCEINLINE auto operator()(T0 target0) const noexcept + -> decltype(eve::tag_invoke(*this, target0, normalize)) + { + return eve::tag_invoke(*this, target0, normalize); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var to_rotation_matrix + //! + //! @brief Callable object computing a quaternion from its to_rotation_matrix representation. + //! + //! This function build rotation_matrix angles from a quaternion. Template parameters I, J, K of type int + //! are used to choose the rotation_matrix order. + //! + //! for instance I = 3, J = 2, K = 3 choose the ZYZ sequence. + //! the values of I, J, and K must be in {1, 2, 3} ans satisfy I != J && J != K. + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto to_rotation_matrix(auto q) const noexcept; + //! auto to_rotation_matrix(auto q, assume_normalized) const noexcept; + //! } + //! @endcode + //! + //! **Parameters** + //! + //! * `q` quaternion representing the rotation + //! * `assume_normalized``: suppose that q is already normalized + //! + //! + //! + //! **Return value** + //! + //! compute the rotation matrix associated to the quaternion. + //! + //! if T is the element type of q, returns an std::array, 3> containing + //! the 9 coefficients of the rotation matrix + //! + //! @note use this function if you really need the rotation matrix, but to rotate vectors + //! prefer the function rot_vec that directly uses the quaternion. + //! + //! @groupheader{Example} + //! + //! @godbolt{doc/quaternion/regular/to_rotation_matrix.cpp} + //! @} + //================================================================================================ + inline constexpr tags::callable_to_rotation_matrix to_rotation_matrix = {}; +} diff --git a/include/kyosu/functions/to_semipolar.hpp b/include/kyosu/functions/to_semipolar.hpp new file mode 100644 index 00000000..68894924 --- /dev/null +++ b/include/kyosu/functions/to_semipolar.hpp @@ -0,0 +1,86 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_to_semipolar : eve::elementwise + { + using callable_tag_type = callable_to_semipolar; + + KYOSU_DEFERS_CALLABLE(to_semipolar_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & v) noexcept + { + auto z = eve::zero(eve::as(v)); + return kumi::tuple{eve::abs(v), eve::arg(v), z, z}; + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0)) + { + return eve::tag_invoke(*this, target0); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var to_semipolar + //! + //! @brief Callable object computing the semipolar coordinates from a quaternion. + //! + //! This function is the reciprocal of from_semipolar + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto to_semipolar( auto q) const noexcept; + //! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //! + //! **Parameters** + //! + //! `q` : quaternion + //! + //! **Return value** + //! + //! a tuple containing in this order `rho1`, 'theta1', `h1` 'h2': the components + //! of the semipolar parametrisation of \f$\mathbb{R}^4\f$ coordinates + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/quaternion/regular/to_semipolar.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_to_semipolar to_semipolar = {}; +} diff --git a/include/kyosu/functions/to_spherical.hpp b/include/kyosu/functions/to_spherical.hpp new file mode 100644 index 00000000..29bb478e --- /dev/null +++ b/include/kyosu/functions/to_spherical.hpp @@ -0,0 +1,86 @@ +//================================================================================================== +/* + KYOSU - Expressive Vector Engine + Copyright : KYOSU Project Contributors + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#pragma once + +#include +#include + +namespace kyosu::tags +{ + struct callable_to_spherical : eve::elementwise + { + using callable_tag_type = callable_to_spherical; + + KYOSU_DEFERS_CALLABLE(to_spherical_); + + template + static KYOSU_FORCEINLINE auto deferred_call(auto + , V const & v) noexcept + { + auto z = eve::zero(eve::as(v)); + return kumi::tuple{eve::abs(v), eve::arg(v), z, z}; + } + + template + KYOSU_FORCEINLINE auto operator()(T0 const& target0 + ) const noexcept + -> decltype(eve::tag_invoke(*this, target0)) + { + return eve::tag_invoke(*this, target0); + } + + template + eve::unsupported_call operator()(T&&... x) const + requires(!requires { eve::tag_invoke(*this, KYOSU_FWD(x)...); }) = delete; + }; +} + +namespace kyosu +{ + //================================================================================================ + //! @addtogroup quaternion + //! @{ + //! @var to_spherical + //! + //! @brief Callable object computing the spherical coordinates from a quaternion. + //! + //! This function is the reciprocal of from_spherical + //! + //! **Defined in header** + //! + //! @code + //! #include eve/module/quaternion.hpp>` + //! @endcode + //! + //! @groupheader{Callable Signatures} + //! + //! @code + //! namespace eve + //! { + //! auto to_spherical( auto q) const noexcept; + //! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //! + //! **Parameters** + //! + //! `q` : quaternion + //! + //! **Return value** + //! + //! a tuple containing in this order `rho`, 'theta', `ph1` 'ph2': the components + //! of the spherical parametrisation of \f$\mathbb{R}^4\f$ coordinates + //! + //! --- + //! + //! #### Example + //! + //! @godbolt{doc/quaternion/regular/to_spherical.cpp} + //! + //! @} + //================================================================================================ + inline constexpr tags::callable_to_spherical to_spherical = {}; +} diff --git a/include/kyosu/types/cayley_dickson.hpp b/include/kyosu/types/cayley_dickson.hpp index 7d18185a..780e5f6d 100644 --- a/include/kyosu/types/cayley_dickson.hpp +++ b/include/kyosu/types/cayley_dickson.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -165,7 +166,7 @@ namespace kyosu if constexpr(T::minimum_valid_index == T::maximum_valid_index) { if constexpr(sz > T::minimum_valid_index) return get(EVE_FWD(c)); - else return eve::underlying_type_t>{0}; + else return as_real_t>{0}; } else { diff --git a/include/kyosu/types/impl/arithmetic.hpp b/include/kyosu/types/impl/arithmetic.hpp index 72d6cf7e..dd44931c 100644 --- a/include/kyosu/types/impl/arithmetic.hpp +++ b/include/kyosu/types/impl/arithmetic.hpp @@ -130,10 +130,6 @@ namespace kyosu::_ KYOSU_FORCEINLINE constexpr auto dispatch(eve::tag_of const&, C0 const & c0, C1 const & c1) noexcept { -// using r_t = kyosu::as_cayley_dickson_t; -// using er_t = eve::element_type_t; -// auto cc0 = kyosu::convert(c0, eve::as()); -// auto cc1 = kyosu::convert(c1, eve::as()); return kyosu::dist(c0, c1)/eve::max(kyosu::abs(c0), kyosu::abs(c1), eve::one(eve::as(abs(c0)))); } @@ -219,5 +215,28 @@ namespace kyosu::_ }; } + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const&, C0 const & c0, C1 const & c1) noexcept + { + using r_t = kyosu::as_cayley_dickson_t; + using er_t = decltype(kyosu::abs(r_t{})); + if constexpr(eve::floating_value || eve::floating_value) + { + return kyosu::real(c0)*kyosu::real(c1); + } + else + { + constexpr auto P = kyosu::dimension_v < kyosu::dimension_v ? kyosu::dimension_v : kyosu::dimension_v; + er_t res(0.0f); + auto sum = [&](auto i, auto x){ + auto [e, f] = x; + res = eve::fam(res, e, f); + }; + kumi::for_each_index(sum, kumi::zip( kumi::extract(c0, kumi::index_t<0>{}, kumi::index_t

{}) + , kumi::extract(c1, kumi::index_t<0>{}, kumi::index_t

{}))); + return res; + } + } } diff --git a/include/kyosu/types/impl/complex/arithmetic.hpp b/include/kyosu/types/impl/complex/arithmetic.hpp index 78348889..ab4b829a 100644 --- a/include/kyosu/types/impl/complex/arithmetic.hpp +++ b/include/kyosu/types/impl/complex/arithmetic.hpp @@ -15,7 +15,14 @@ namespace kyosu::_ KYOSU_FORCEINLINE constexpr auto dispatch(eve::tag_of const&, C const& c) noexcept { - return /*eve::pedantic*/(eve::atan2(kyosu::imag(c), kyosu::real(c))); + return eve::pedantic(eve::atan2)(kyosu::imag(c), kyosu::real(c)); + } + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const&, C const& c) noexcept + { + return kumi::tuple{kyosu::abs(c), kyosu::arg(c)}; } } diff --git a/include/kyosu/types/impl/math.hpp b/include/kyosu/types/impl/math.hpp index 38d2127a..6673e4e7 100644 --- a/include/kyosu/types/impl/math.hpp +++ b/include/kyosu/types/impl/math.hpp @@ -122,8 +122,8 @@ namespace kyosu::_ } else { - using u_t = as_cayley_dickson_n_t<2,eve::underlying_type_t>; - const auto ipi = c_t(u_t(0), eve::pi()); + using u_t = eve::underlying_type_t; + const auto ipi = to_complex(u_t(0), eve::pi(eve::as())); return kyosu::exp(ipi*z); } } diff --git a/include/kyosu/types/impl/predicates.hpp b/include/kyosu/types/impl/predicates.hpp index 28edc0f8..f339e682 100644 --- a/include/kyosu/types/impl/predicates.hpp +++ b/include/kyosu/types/impl/predicates.hpp @@ -14,16 +14,18 @@ namespace kyosu::_ { template KYOSU_FORCEINLINE constexpr - auto dispatch(eve::tag_of const&, C const& c) noexcept + auto dispatch(eve::tag_of const&, C c) noexcept { - return kumi::any_of(purepart(c), [](auto const& e) { return eve::is_nez(e); }); + get<0>(c) = eve::zero(eve::as(get<0>(c))); + return kumi::any_of(c, [](auto const& e) { return eve::is_nez(e); }); } template KYOSU_FORCEINLINE constexpr - auto dispatch(eve::tag_of const&, C const& c) noexcept + auto dispatch(eve::tag_of const&, C c) noexcept { - return kumi::all_of(purepart(c), [](auto const& e) { return eve::is_eqz(e); }); + get<0>(c) = eve::zero(eve::as(get<0>(c))); + return kumi::all_of(c, [](auto const& e) { return eve::is_eqz(e); }); } template @@ -82,4 +84,14 @@ namespace kyosu::_ return kumi::any_of(c, [](auto const& e) { return eve::is_not_finite(e); }); } + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const&, C const& c) noexcept + { + // almost is used to encompass the fact that normalization of cayley_dickson can suffer + // rounding errors. Whhen available use pedantic if you don't accept this behavior. + using ar_t = decltype(kyosu::sqr_abs(c)); + return eve::almost(eve::is_equal)(kyosu::sqr_abs(c), eve::one(eve::as())); + } + } diff --git a/include/kyosu/types/impl/quaternion/axis.hpp b/include/kyosu/types/impl/quaternion/axis.hpp new file mode 100644 index 00000000..d8d3a753 --- /dev/null +++ b/include/kyosu/types/impl/quaternion/axis.hpp @@ -0,0 +1,28 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#pragma once + +namespace kyosu::_ +{ + template < int N > struct axis:std::integral_constant{}; + template < bool E > struct ext:std::integral_constant{}; + template < bool E > struct norming:std::integral_constant{}; +} + +namespace kyosu +{ + + inline constexpr auto X_(kyosu::_::axis<1>{}); + inline constexpr auto Y_(kyosu::_::axis<2>{}); + inline constexpr auto Z_(kyosu::_::axis<3>{}); + + inline constexpr kyosu::_::ext extrinsic = {}; + inline constexpr kyosu::_::ext intrinsic = {}; + inline constexpr kyosu::_::norming assume_normalized = {}; + inline constexpr kyosu::_::norming normalize = {}; +} diff --git a/include/kyosu/types/impl/quaternion/specific.hpp b/include/kyosu/types/impl/quaternion/specific.hpp new file mode 100644 index 00000000..01a9ee32 --- /dev/null +++ b/include/kyosu/types/impl/quaternion/specific.hpp @@ -0,0 +1,349 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#pragma once + +#include +#include +namespace kyosu::_ +{ + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const& + , Z const& q0 + , axis + , axis + , axis + , ext + ) noexcept + requires(II != JJ && JJ != KK) + { + using e_t = std::remove_reference_t; + auto q = to_quaternion(q0); + std::array aq{get<0>(q), get<1>(q), get<2>(q), get<3>(q)}; + EVE_ASSERT(eve::all(is_nez(q)), "some quaternion are null"); + constexpr bool is_proper = II == KK; //Proper Euler angles else Tait-Bryan + + auto prepare = [&](){ + if constexpr(Extrinsic) + { + constexpr int K = 6-II-JJ; + constexpr int I = II; + constexpr int J = JJ; + int sign = (I-J)*(J-K)*(K-I)/2; // even (+1) permutation or odd (-1); + + auto a = aq[0]; + auto b = aq[I]; + auto c = aq[J]; + auto d = aq[K]*sign; + if constexpr(!is_proper) + { + a -= aq[J]; + b += aq[K]*sign; + c += aq[0]; + d -= aq[I]; + } + return kumi::tuple{a, b, c, d, sign}; + } + else + { + constexpr int I = KK; + constexpr int J = JJ; + constexpr int K = 6-I-J; + int sign = (I-J)*(J-K)*(K-I)/2; // even (+1) permutation or odd (-1); + + auto a = aq[0]; + auto b = aq[I]; + auto c = aq[J]; + auto d = aq[K]*sign; + if constexpr(!is_proper) + { + a -= aq[J]; + b += aq[K]*sign; + c += aq[0]; + d -= aq[I]; + } + return kumi::tuple{a, b, c, d, sign}; + } + }; + auto [a, b, c, d, sign] = prepare(); + auto a2pb2 = eve::sqr(a)+eve::sqr(b); + auto n2 = a2pb2+eve::sqr(c)+eve::sqr(d); + auto theta1 = eve::acos(eve::dec(2*a2pb2/n2)); + auto eps = 1e-7; + auto pi = eve::pi(eve::as()); + auto twopi = eve::two_pi(eve::as()); + auto mpi = -pi; + auto is_safe1 = eve::abs(theta1) >= eps; + auto is_safe2 = eve::abs(theta1 - pi) >= eps; + auto is_safe = is_safe1 && is_safe2; + + auto hp = eve::atan2(b, a); + auto hm = eve::atan2(-d, c); + + auto theta0 = hp + hm; + auto theta2 = hp - hm; + + if constexpr(!Extrinsic) + { + theta0 = eve::if_else(!is_safe, eve::zero, theta0); + theta2 = eve::if_else(!is_safe1, 2*hp, theta2); + theta2 = eve::if_else(!is_safe2, -2*hm, theta2); + } + else + { + theta2 = eve::if_else(!is_safe, eve::zero, theta2); + theta0 = eve::if_else(!is_safe1, 2*hp, theta0); + theta0 = eve::if_else(!is_safe2, 2*hm, theta0); + } + theta0 += eve::if_else(theta0 < mpi, twopi, eve::zero); + theta0 -= eve::if_else(theta0 > pi, twopi, eve::zero); + theta1 += eve::if_else(theta1 < mpi, twopi, eve::zero); + theta1 -= eve::if_else(theta1 > pi, twopi, eve::zero); + theta2 += eve::if_else(theta2 < mpi, twopi, eve::zero); + theta2 -= eve::if_else(theta2 > pi, twopi, eve::zero); + + // for Tait-Bryan thetas + if(!is_proper) + { + theta2 *= sign; + theta1 -= pi / 2; + } + if constexpr(!Extrinsic) std::swap(theta0, theta2); + + return kumi::tuple{theta0, theta1, theta2}; + } + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const& + , Z q + , _::norming) noexcept + { + if constexpr (!normalize) + { + EVE_ASSERT(eve::all(kyosu::is_unitary(q)), "some quaternions are not unitary"); + } + else + { + q = sign(q); + } + if constexpr(kyosu::concepts::complex) + { + auto q0 = real(q); + auto q1 = imag(q); + auto q02 = 2*sqr(q0)-1; + auto q0q1= 2*q0*q1; + using m_t = std::array< std::array, 3>; + return m_t{{1, 0, 0}, {0, q02, -q0q1}, {0, q0q1, q02}}; + } + else + { + auto q0 = real(q); + auto q1 = ipart(q); + auto q2 = jpart(q); + auto q3 = kpart(q); + + // First row of the rotation matrix + auto r00 = 2 * (sqr(q0) + sqr(q1)) - 1; + auto r01 = 2 * (q1 * q2 - q0 * q3); + auto r02 = 2 * (q1 * q3 + q0 * q2); + + // Second row of the rotation matrix + auto r10 = 2 * (q1 * q2 + q0 * q3); + auto r11 = 2 * (sqr(q0) + sqr(q2)) - 1; + auto r12 = 2 * (q2 * q3 - q0 * q1); + + // Third row of the rotation matrix + auto r20 = 2 * (q1 * q3 - q0 * q2); + auto r21 = 2 * (q2 * q3 + q0 * q1); + auto r22 = 2 * (sqr(q0) + sqr(q3)) - 1; + + // 3x3 rotation matrix + using e_t = std::decay_t; + using l_t = std::array; + using m_t = std::array; + std::array l1{r00, r01, r02}; + std::array l2{r10, r11, r12}; + std::array l3{r20, r21, r22}; + return m_t{l1, l2, l3}; + } + } + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const& + , Z const& q) noexcept + { + return to_rotation_matrix(q, _::norming{}); + } + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const& + , Z const& q) noexcept + { + auto c0 = to_complex(get<0>(q), get<1>(q)); + if constexpr(kyosu::concepts::complex) + { + auto z = eve::zero(eve::as(abs(c0))); + return kumi::tuple{abs(c0), arg(c0), z, z}; + } + else + { + auto c1 = to_complex(get<2>(q), get<3>(q)); + return kumi::tuple{abs(c0), arg(c0), abs(c1), arg(c1)}; + } + } + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const& + , Z const& q) noexcept + { + auto c0 = to_complex(get<0>(q), get<1>(q)); + if constexpr(kyosu::concepts::complex) + { + auto z = eve::zero(eve::as(abs(c0))); + return kumi::tuple{abs(c0), arg(c0), z, z}; + } + else + { + return kumi::tuple{abs(c0), arg(c0), get<2>(q), get<3>(q) }; + } + } + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const& + , Z const& q) noexcept + { + auto c0 = to_complex(get<0>(q), get<1>(q)); + if constexpr(kyosu::concepts::complex) + { + auto z = eve::zero(eve::as(abs(c0))); + return kumi::tuple{abs(c0), z, arg(c0), z}; + } + else + { + auto rho = kyosu::abs(q); + auto c0 = to_complex(get<0>(q), get<1>(q)); + auto c1 = to_complex(get<2>(q), get<3>(q)); + auto alpha = eve::pedantic(eve::atan2)(abs(c1), abs(c0)); + auto theta1 = arg(c0); + auto theta2 = arg(c1); + return kumi::tuple{rho, alpha, theta1, theta2}; + } + } + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const& + , Z q) noexcept + { + if constexpr(kyosu::concepts::complex) + { + auto c0 = to_complex(get<0>(q), get<1>(q)); + auto z = eve::zero(eve::as(abs(c0))); + return kumi::tuple{abs(c0), arg(c0), z, z}; + } + else + { + auto rho = kyosu::abs(q); + auto phi2 = eve::asin(get<3>(q)/rho); + get<3>(q) = 0; + auto rho1 = kyosu::abs(q); + auto phi1 = eve::asin(get<2>(q)/rho1); + get<2>(q) = 0; + auto rho2 = kyosu::abs(q); + auto theta= eve::asin(get<1>(q)/rho2); + return kumi::tuple{rho, theta, phi1, phi2}; + } + } + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const& + , Z const & q + , std::span const & v + , _::norming) noexcept + { + using e_t = std::decay_t; + using v_t = decltype(T()+e_t()); + if constexpr (!normalize) EVE_ASSERT(eve::all(eve::pedantic(kyosu::is_unitary)(q)), "some quaternions are not unitary"); + std::array w, wp; + using a_t = decltype(kyosu::abs(q)); + a_t fac(2); + if constexpr(normalize) fac *= kyosu::rec(kyosu::sqr_abs(q)); + auto [r, i, j, k] = q; + w[0] = eve::fma(r, v[0], eve::diff_of_prod(j, v[2], k, v[1])); + w[1] = eve::fma(r, v[1], eve::diff_of_prod(k, v[0], i, v[2])); + w[2] = eve::fma(r, v[2], eve::diff_of_prod(i, v[1], j, v[0])); + + wp[0] = eve::fam(v[0], fac, eve::diff_of_prod(j, w[2], k, w[1])); + wp[1] = eve::fam(v[1], fac, eve::diff_of_prod(k, w[0], i, w[2])); + wp[2] = eve::fam(v[2], fac, eve::diff_of_prod(i, w[1], j, w[0])); + return wp; + } + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const& + , Z const & q + , std::span const & v) noexcept + { + return rot_vec(q, v, _::norming{}); + } + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const& + , Z const& q) noexcept + { + using e_t = std::decay_t; + auto invn = eve::rec(kyosu::abs(kyosu::pure(q))); + invn = eve::if_else(eve::is_nan(invn), eve::zero, invn); + std::array v{kyosu::ipart(q)*invn, kyosu::jpart(q)*invn, kyosu::kpart(q)*invn}; + return v; + } + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const& + , const Z &q) noexcept + { + return 2*eve::pedantic(eve::atan2)(kyosu::abs(kyosu::pure(q)), kyosu::real(q)); + } + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const& + , Z q) noexcept + { + q = sign(q); + using e_t = std::decay_t; + auto ap = kyosu::abs(pure(q)); + auto invn = eve::rec(ap); + invn = eve::if_else(eve::is_infinite(invn), eve::zero, invn); + std::array v{kyosu::ipart(q)*invn, kyosu::jpart(q)*invn, kyosu::kpart(q)*invn}; + auto a = 2*eve::pedantic(eve::atan2)(ap, kyosu::real(q)); + return kumi::tuple{a, v}; + } + + template + KYOSU_FORCEINLINE constexpr + auto dispatch(eve::tag_of const& + , Z1 const& z1, Z2 z2, T const & t + ) noexcept + { + EVE_ASSERT(eve::all(is_unitary(z1) && is_unitary(z2)), "quaternion parameters must be unitary"); + z2 = kyosu::if_else(eve::is_gez(kyosu::dot(z1, z2)), z2, -z2); + return z1*kyosu::pow(kyosu::conj(z1)*z2, t); + } + +} diff --git a/test/doc/conversions.cpp b/test/doc/conversions.cpp new file mode 100644 index 00000000..08143412 --- /dev/null +++ b/test/doc/conversions.cpp @@ -0,0 +1,32 @@ +#include +#include +#include + +using wide_ft = eve::wide >; + +int main() +{ + wide_ft a = { 0.0f, 1.0f, -1.0f, 0.5f}; + wide_ft b = { 2.0f , -1.0, -5.0, 0.0}; + wide_ft c = { 1.0f, -3.0f, -4.0f, 1.5f}; + wide_ft d = { -2.0, 1.5f, 2.3f, 6.7f}; + wide_ft r1= { 1.0f, 2.0f, 3.0f, 0.0f}; + wide_ft r2= { 0.0f, 3.0f, 4.0f, 10.0f}; + + std::cout + << "---- simd" << std::endl + << "<- a = " << a << std::endl + << "<- b = " << b << std::endl + << "<- c = " << c << std::endl + << "<- d = " << d << std::endl + << "<- r1 = " << c << std::endl + << "<- r2 = " << d << std::endl + << "-> cylindrical(r1, b, c, d) = " << eve::from_cylindrical(r1, b, c, d) << std::endl + << "-> cylindrospherical(r1, r2, c, d) = " << eve::from_cylindrospherical(r1, r2, c, d) << std::endl + << "-> multipolar(r1, c, r2, d) = " << eve::from_multipolar(r1, c, r2, d) << std::endl + << "-> semipolar(r1, b, c, d) = " << eve::from_semipolar(r1, b, c, d) << std::endl + << "-> spherical(r1, b, c, d) = " << eve::from_spherical(r1, b, c, d) << std::endl + ; + + return 0; +} diff --git a/test/doc/dot.cpp b/test/doc/dot.cpp new file mode 100644 index 00000000..c8cdf259 --- /dev/null +++ b/test/doc/dot.cpp @@ -0,0 +1,45 @@ +#include +#include +#include + +int main() +{ + using kyosu::dot; + using kyosu::complex; + using kyosu::quaternion; + using e_t = float; + using c_t = complex; + using q_t = quaternion; + using we_t = eve::wide>; + using wc_t = eve::wide, eve::fixed<2>>; + using wq_t = eve::wide, eve::fixed<2>>; + + std::cout << "Real: "<< "\n"; + e_t e0(1); + e_t e1(2); + std::cout << e0 << ", " << e1 << " -> " << dot(e0, e1) << "\n"; + we_t we0(e0); + we_t we1(e1); + std::cout << we0 << ", " << we1 << " -> " << dot(we0, we1) << "\n"; + + std::cout << "Complex: "<< "\n"; + c_t c0(1, 5); + c_t c1(5, 9); + std::cout << c0 << ", " << c1 << " -> " << dot(c0, c1) << "\n"; + wc_t wc0(c0); + wc_t wc1(c1); + std::cout << wc0 << ", " << wc1 << " -> " << dot(wc0, wc1) << "\n"; + + std::cout << "Quaternion: "<< "\n"; + q_t q0(1, 5, 2, 3); + q_t q1(5, 9, 6, 7); + std::cout << q0 << ", " << q1 << " -> " << dot(q0, q1) << "\n"; + wq_t wq0(q0); + wq_t wq1(q1); + std::cout << wq0 << ", " << wq1 << " -> " << dot(wq0, wq1) << "\n"; + std::cout << wq0 << ", " << q1 << " -> " << dot(wq0, q1) << "\n"; + std::cout << wq0 << ", " << c1 << " -> " << dot(wq0, c1) << "\n"; + std::cout << wq0 << ", " << e1 << " -> " << dot(wq0, e1) << "\n"; + + return 0; +} diff --git a/test/doc/from_cylindrical.cpp b/test/doc/from_cylindrical.cpp new file mode 100644 index 00000000..9f183dd1 --- /dev/null +++ b/test/doc/from_cylindrical.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +int main() +{ + using kyosu::from_cylindrical; + using kyosu::complex; + using kyosu::quaternion; + + auto r = 2.0; + auto a = eve::pio_3(eve::as(r)); + auto h1 = 3.0; + auto h2 = 4.0; + std::cout << " <- modulus " << r << std::endl; + std::cout << " <- angle " << a << std::endl; + std::cout << " <- h1 " << h1<< std::endl; + std::cout << " <- h2 " << h2<< std::endl; + std::cout << " -> " << from_cylindrical(r, a, h1, h2) << "\n"; + + return 0; +} diff --git a/test/doc/from_euler.cpp b/test/doc/from_euler.cpp new file mode 100644 index 00000000..2d0a8eb8 --- /dev/null +++ b/test/doc/from_euler.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +int main() +{ + using kyosu::from_euler; + using kyosu::X_; + using kyosu::Z_; + using kyosu::extrinsic; + + auto psi = eve::pio_3(eve::as(0.0)); + auto theta= eve::pio_4(eve::as(0.0)); + auto phi = eve::pio_6(eve::as(0.0)); + + std::cout << " <- psi " << psi << std::endl; + std::cout << " <- theta " << theta << std::endl; + std::cout << " <- phi " << phi<< std::endl; + std::cout << " -> from_euler(psi, theta, phi, X_, Z_, X_, extrinsic) " + << kyosu::from_euler(psi, theta, phi, X_, Z_, X_, extrinsic) << "\n"; + + return 0; +} diff --git a/test/doc/is_unitary.cpp b/test/doc/is_unitary.cpp new file mode 100644 index 00000000..724bc645 --- /dev/null +++ b/test/doc/is_unitary.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +int main() +{ + using kyosu::is_unitary; + using kyosu::complex; + using kyosu::quaternion; + using e_t = float; + using c_t = complex; + using q_t = quaternion; + using we_t = eve::wide>; + using wc_t = eve::wide>; + using wq_t = eve::wide>; + + std::cout << "Real: \n"; + e_t e(72.9f); + we_t we = we_t(e); + e_t ue = kyosu::sign(e); + we_t uwe = kyosu::sign(we); + std::cout << e << " -> " << is_unitary(e) << "\n"; + std::cout << we << " -> " << is_unitary(we) << "\n"; + std::cout << ue << " -> " << is_unitary(ue) << "\n"; + std::cout << uwe << " -> " << is_unitary(uwe) << "\n"; + + std::cout << "Complex: \n"; + c_t c(3.5f,-2.9f); + wc_t wc = wc_t(c); + c_t uc = kyosu::sign(c); + wc_t uwc = kyosu::sign(wc); + std::cout << c << " -> " << is_unitary(c) << "\n"; + std::cout << wc << " -> " << is_unitary(wc) << "\n"; + std::cout << uc << " -> " << is_unitary(uc) << "\n"; + std::cout << uwc << " -> " << is_unitary(uwc) << "\n"; + + std::cout << "Quaternion: \n"; + q_t q(3.5f,-2.9f, 2.1f, 3.2f); + wq_t wq = wq_t(q); + q_t uq = kyosu::sign(q); + wq_t uwq = kyosu::sign(wq); + std::cout << q << " -> " << is_unitary(q) << "\n"; + std::cout << wq << " -> " << is_unitary(wq) << "\n"; + std::cout << uq << " -> " << is_unitary(uq) << "\n"; + std::cout << uwq << " -> " << is_unitary(uwq) << "\n"; + + return 0; +} diff --git a/test/unit/complex/arg.cpp b/test/unit/complex/arg.cpp new file mode 100644 index 00000000..9effb4d5 --- /dev/null +++ b/test/unit/complex/arg.cpp @@ -0,0 +1,36 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include +#include + + +TTS_CASE_WITH( "Check behavior of arg on scalar" + , tts::bunch + , tts::generate( tts::randoms(-10, 10), tts::randoms(-10, 10)) + ) + (T const& a0, T const& a1 ) +{ + for(size_t i = 0; i < a0.size(); ++i) + { + auto e = a0[i]; + auto f = a1[i]; + + TTS_RELATIVE_EQUAL(kyosu::arg(kyosu::to_complex(e, f)), eve::atan2(f, e), 1.0e-6); + } +}; + +TTS_CASE_WITH( "Check behavior of arg on wide" + , kyosu::simd_real_types + , tts::generate( tts::between(-10, 10) + , tts::between(-10, 10)) + ) + (T const& a0, T const& a1 ) +{ + TTS_RELATIVE_EQUAL(kyosu::arg(kyosu::to_complex(a0,a1)), eve::atan2(a1, a0), 1.0e-6); +}; diff --git a/test/unit/complex/to_polar.cpp b/test/unit/complex/to_polar.cpp new file mode 100644 index 00000000..a6f8d7f3 --- /dev/null +++ b/test/unit/complex/to_polar.cpp @@ -0,0 +1,32 @@ +//================================================================================================== +/** + EVE - Expressive Vector Engine + Copyright : EVE Project Contributors + SPDX-License-Identifier: BSL-1.0 +**/ +//================================================================================================== +#include "test.hpp" +#include + +TTS_CASE_WITH ( "Check behavior of from_polar on wide" + , kyosu::real_types + , tts::generate( tts::randoms(-1.0, +1.0) + , tts::randoms(-1.0, +1.0) + ) + ) + (T const& a0, T const& a1) +{ + { + auto c = kyosu::to_complex(a0, a1); + auto [r1, t1] = kyosu::to_polar(c); + auto c1 = kyosu::from_polar(r1, t1); + TTS_RELATIVE_EQUAL(c, c1, 1.0e-5); + } + { + auto [r1, t1] = kyosu::to_polar(a0); + auto c1 = kyosu::from_polar(r1, t1); + TTS_RELATIVE_EQUAL(kyosu::to_complex(a0), c1, 1.0e-5); + } + + +}; diff --git a/test/unit/function/average.cpp b/test/unit/function/average.cpp new file mode 100644 index 00000000..fc0a07a8 --- /dev/null +++ b/test/unit/function/average.cpp @@ -0,0 +1,49 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +TTS_CASE_WITH ( "Check kyosu::average over real" + , kyosu::real_types + , tts::generate(tts::between(-10,10) + ,tts::between(-10,10) + ) + ) +(auto r0, auto r1) +{ + TTS_EQUAL(kyosu::average(r0, r1), eve::average(r0, r1)); +}; + +TTS_CASE_WITH ( "Check kyosu::average over complex" + , kyosu::real_types + , tts::generate(tts::between(-10,10), tts::between(-10,10) + ,tts::between(-10,10), tts::between(-10,10) + ) + ) +(T r0, T i0, T r1, T i1) +{ + auto c0 = kyosu::to_complex(r0,i0); + auto c1 = kyosu::to_complex(r1,i1); + TTS_RELATIVE_EQUAL(kyosu::average(c0, c1), (c0+c1)*T(0.5), 1e-7); +}; + +TTS_CASE_WITH ( "Check kyosu::average over quaternion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + ) + ) +(T r0, T i0, T j0, T k0, T r1, T i1, T j1, T k1) +{ + using type = kyosu::as_quaternion_t; + auto q0 = type(r0,i0,j0,k0); + auto q1 = type(r1,i1,j1,k1); + TTS_RELATIVE_EQUAL(kyosu::average(q0, q1), (q0+q1)*T(0.5) , 1e-7); +}; diff --git a/test/unit/function/csch.cpp b/test/unit/function/csch.cpp new file mode 100644 index 00000000..2ed2ecf6 --- /dev/null +++ b/test/unit/function/csch.cpp @@ -0,0 +1,49 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +# if __has_include () +# include +# define HAS_BOOST +# endif + +TTS_CASE_WITH ( "Check kyosu::csch over real" + , kyosu::real_types + , tts::generate(tts::between(-10,10)) + ) +(auto data) +{ + TTS_ULP_EQUAL(kyosu::csch(data), eve::csch(data), 0.5); +}; + +#ifdef HAS_BOOST + +template < typename T > +auto cv(boost::math::quaternion const &bq) +{ + return kyosu::quaternion(bq.R_component_1(), bq.R_component_2(), + bq.R_component_3(), bq.R_component_4()); +} + +TTS_CASE_WITH ( "Check kyosu::csch over quaternion" + , kyosu::simd_real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + ) + ) +(T r, T i, T j, T k) +{ + using ke_t = kyosu::as_quaternion_t; + using bq_t = boost::math::quaternion>; + auto boost_csch = [](auto x, auto y, auto z, auto t){return kyosu::rec(cv(boost::math::sinh(bq_t(x, y, z, t)))); }; + ke_t e([&](auto n, auto){return boost_csch(r.get(n), i.get(n), j.get(n), k.get(n)); }); + auto q = ke_t(r,i,j,k); + TTS_RELATIVE_EQUAL(kyosu::csch(q), e, 1e-5); +}; +# endif diff --git a/test/unit/function/dot.cpp b/test/unit/function/dot.cpp new file mode 100644 index 00000000..31aedbc9 --- /dev/null +++ b/test/unit/function/dot.cpp @@ -0,0 +1,53 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +TTS_CASE_WITH ( "Check kyosu::dot over real" + , kyosu::real_types + , tts::generate(tts::between(-10,10) + ,tts::between(-10,10) + ) + ) +(auto r0, auto r1) +{ + TTS_EQUAL(kyosu::dot(r0, r1), r0*r1); +}; + +TTS_CASE_WITH ( "Check kyosu::dot over complex" + , kyosu::real_types + , tts::generate(tts::between(-10,10), tts::between(-10,10) + ,tts::between(-10,10), tts::between(-10,10) + ) + ) +(T r0, T i0, T r1, T i1) +{ + auto c0 = kyosu::to_complex(r0,i0); + auto c1 = kyosu::to_complex(r1,i1); + TTS_RELATIVE_EQUAL(kyosu::dot(c0, c1) + , kyosu::real(c0)*kyosu::real(c1)+kyosu::imag(c0)*kyosu::imag(c1), 1e-7); +}; + +TTS_CASE_WITH ( "Check kyosu::dot over quaternion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + ) + ) +(T r0, T i0, T j0, T k0, T r1, T i1, T j1, T k1) +{ + using type = kyosu::as_quaternion_t; + auto q0 = type(r0,i0,j0,k0); + auto q1 = type(r1,i1,j1,k1); + TTS_RELATIVE_EQUAL(kyosu::dot(q0, q1), kyosu::real(q0)*kyosu::real(q1)+ + kyosu::ipart(q0)*kyosu::ipart(q1)+ + kyosu::jpart(q0)*kyosu::jpart(q1)+ + kyosu::kpart(q0)*kyosu::kpart(q1), 1e-7); +}; diff --git a/test/unit/function/exp_ipi.cpp b/test/unit/function/exp_ipi.cpp new file mode 100644 index 00000000..f04a1da5 --- /dev/null +++ b/test/unit/function/exp_ipi.cpp @@ -0,0 +1,29 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +TTS_CASE_WITH ( "Check kyosu::exp_ipi over quaternion" + , kyosu::real_types + , tts::generate ( tts::between(0.5,2.0), tts::between(0.5,2.0) + , tts::between(0.5,2.0), tts::between(0.5,2.0) + ) + ) +(T r, T i, T j, T k) +{ + using u_t = eve::underlying_type_t; + auto ipi = kyosu::as_complex_t(u_t(0), eve::pi(eve::as())); + + TTS_RELATIVE_EQUAL(kyosu::exp_ipi(r), kyosu::exp(ipi*r), 1e-5); + using ce_t = kyosu::as_complex_t; + auto c = ce_t(r,i); + TTS_RELATIVE_EQUAL(kyosu::exp_ipi(c), kyosu::exp(ipi*c), 1e-5); + using qe_t = kyosu::as_quaternion_t; + auto q = qe_t(r,i,j,k); + TTS_RELATIVE_EQUAL(kyosu::exp_ipi(q), kyosu::exp(ipi*q), 1e-5); +}; diff --git a/test/unit/function/ipart.cpp b/test/unit/function/ipart.cpp new file mode 100644 index 00000000..bb400e73 --- /dev/null +++ b/test/unit/function/ipart.cpp @@ -0,0 +1,52 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +TTS_CASE_WITH ( "Check kyosu::ipart over real" + , kyosu::real_types + , tts::generate(tts::between(-10,10)) + ) +(auto data) +{ + TTS_EQUAL(kyosu::ipart(data), eve::zero(eve::as(data))); +}; + +TTS_CASE_WITH ( "Check kyosu::ipart over complex" + , kyosu::real_types + , tts::generate(tts::between(-10,10), tts::between(-10,10)) + ) +(auto r, auto i) +{ + TTS_EQUAL(kyosu::ipart(kyosu::to_complex(r,i)), i); +}; + +TTS_CASE_WITH ( "Check kyosu::ipart over quaternion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + ) + ) + (T r, T i, T j, T k) +{ + using type = kyosu::as_quaternion_t; + TTS_EQUAL(kyosu::ipart(type(r,i,j,k)), i); +}; + +TTS_CASE_WITH ( "Check kyosu::ipart over octonion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) ) + ) + (T r, T i, T j, T k, T l, T li, T lj, T lk) +{ + using type = kyosu::as_octonion_t; + TTS_EQUAL(kyosu::ipart(type(r,i,j,k,l,li,lj,lk)), i ); +}; diff --git a/test/unit/function/is_unitary.cpp b/test/unit/function/is_unitary.cpp new file mode 100644 index 00000000..f4e014c6 --- /dev/null +++ b/test/unit/function/is_unitary.cpp @@ -0,0 +1,62 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +TTS_CASE_WITH ( "Check kyosu::is_unitary over real" + , kyosu::real_types + , tts::generate(tts::between(-10,10)) + ) +(auto data) +{ + auto o = eve::one(eve::as(data)); + TTS_EQUAL(kyosu::is_unitary(data), data == o); +}; + +TTS_CASE_WITH ( "Check kyosu::is_unitary over complex" + , kyosu::real_types + , tts::generate(tts::between(-10,10), tts::between(-10,10)) + ) +(auto r, auto i) +{ + auto c = kyosu::to_complex(r,i); + c /= kyosu::abs(c); + auto o = eve::one(eve::as(kyosu::abs(c))); + TTS_EQUAL(kyosu::is_unitary(c), eve::almost(eve::is_equal)(kyosu::abs(c), o)); +}; + +TTS_CASE_WITH ( "Check kyosu::is_unitary over quaternion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + ) + ) + (T r, T i, T j, T k) +{ + using type = kyosu::as_quaternion_t; + auto z(type(r,i,j,k)); + z /= kyosu::abs(z); + auto o = eve::one(eve::as(kyosu::abs(z))); + TTS_EQUAL(kyosu::is_unitary(z), eve::almost(eve::is_equal)(kyosu::abs(z), o)); +}; + +TTS_CASE_WITH ( "Check kyosu::is_unitary over octonion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) ) + ) + (T r, T i, T j, T k, T l, T li, T lj, T lk) +{ + using type = kyosu::as_octonion_t; + auto z(type(r,i,j,k,l,li,lj,lk)); + z /= kyosu::abs(z); + auto o = eve::one(eve::as(kyosu::abs(z))); + TTS_EQUAL(kyosu::is_unitary(z), eve::almost(eve::is_equal)(kyosu::abs(z), o)); +}; diff --git a/test/unit/function/jpart.cpp b/test/unit/function/jpart.cpp new file mode 100644 index 00000000..99a01587 --- /dev/null +++ b/test/unit/function/jpart.cpp @@ -0,0 +1,52 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +TTS_CASE_WITH ( "Check kyosu::jpart over real" + , kyosu::real_types + , tts::generate(tts::between(-10,10)) + ) +(auto data) +{ + TTS_EQUAL(kyosu::jpart(data), eve::zero(eve::as(data))); +}; + +TTS_CASE_WITH ( "Check kyosu::jpart over complex" + , kyosu::real_types + , tts::generate(tts::between(-10,10), tts::between(-10,10)) + ) +(auto r, auto i) +{ + TTS_EQUAL(kyosu::jpart(kyosu::to_complex(r,i)), eve::zero(eve::as(r))); +}; + +TTS_CASE_WITH ( "Check kyosu::jpart over quaternion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + ) + ) + (T r, T i, T j, T k) +{ + using type = kyosu::as_quaternion_t; + TTS_EQUAL(kyosu::jpart(type(r,i,j,k)), j); +}; + +TTS_CASE_WITH ( "Check kyosu::jpart over octonion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) ) + ) + (T r, T i, T j, T k, T l, T li, T lj, T lk) +{ + using type = kyosu::as_octonion_t; + TTS_EQUAL(kyosu::jpart(type(r,i,j,k,l,li,lj,lk)), j ); +}; diff --git a/test/unit/function/kpart.cpp b/test/unit/function/kpart.cpp new file mode 100644 index 00000000..8c732de0 --- /dev/null +++ b/test/unit/function/kpart.cpp @@ -0,0 +1,52 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +TTS_CASE_WITH ( "Check kyosu::kpart over real" + , kyosu::real_types + , tts::generate(tts::between(-10,10)) + ) +(auto data) +{ + TTS_EQUAL(kyosu::kpart(data), eve::zero(eve::as(data))); +}; + +TTS_CASE_WITH ( "Check kyosu::kpart over complex" + , kyosu::real_types + , tts::generate(tts::between(-10,10), tts::between(-10,10)) + ) +(auto r, auto i) +{ + TTS_EQUAL(kyosu::kpart(kyosu::to_complex(r,i)), eve::zero(eve::as(r))); +}; + +TTS_CASE_WITH ( "Check kyosu::kpart over quaternion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + ) + ) + (T r, T i, T j, T k) +{ + using type = kyosu::as_quaternion_t; + TTS_EQUAL(kyosu::kpart(type(r,i,j,k)), k); +}; + +TTS_CASE_WITH ( "Check kyosu::kpart over octonion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) ) + ) + (T r, T i, T j, T k, T l, T li, T lj, T lk) +{ + using type = kyosu::as_octonion_t; + TTS_EQUAL(kyosu::kpart(type(r,i,j,k,l,li,lj,lk)), k); +}; diff --git a/test/unit/function/log_abs.cpp b/test/unit/function/log_abs.cpp new file mode 100644 index 00000000..2210ac04 --- /dev/null +++ b/test/unit/function/log_abs.cpp @@ -0,0 +1,32 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +TTS_CASE_WITH ( "Check kyosu::exp over quaternion" + , kyosu::simd_real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + ) + ) +(T a0, T a1, T a2, T a3) +{ + using ce_t = kyosu::as_complex_t; + using qe_t = kyosu::as_quaternion_t; + + auto r = T(a0); + auto c = ce_t(a0,a1); + auto q = qe_t(a0,a1,a2,a3); + + auto lr = kyosu::log_abs(r); + auto lc = kyosu::log_abs(c); + auto lq = kyosu::log_abs(q); + TTS_RELATIVE_EQUAL(lr, eve::log(kyosu::abs(r)), 1e-5); + TTS_RELATIVE_EQUAL(lc, eve::log(kyosu::abs(c)), 1e-5); + TTS_RELATIVE_EQUAL(lq, eve::log(kyosu::abs(q)), 1e-5); +}; diff --git a/test/unit/function/pure.cpp b/test/unit/function/pure.cpp new file mode 100644 index 00000000..9605bafe --- /dev/null +++ b/test/unit/function/pure.cpp @@ -0,0 +1,52 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +TTS_CASE_WITH ( "Check kyosu::pure over real" + , kyosu::real_types + , tts::generate(tts::between(-10,10)) + ) +(auto data) +{ + TTS_EQUAL(kyosu::pure(data), eve::zero(eve::as(data))); +}; + +TTS_CASE_WITH ( "Check kyosu::pure over complex" + , kyosu::real_types + , tts::generate(tts::between(-10,10), tts::between(-10,10)) + ) + (T r, T i) +{ + TTS_EQUAL(kyosu::pure(kyosu::to_complex(r,i)), kyosu::to_complex(T(0),i)); +}; + +TTS_CASE_WITH ( "Check kyosu::pure over quaternion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + ) + ) + (T r, T i, T j, T k) +{ + using type = kyosu::as_quaternion_t; + TTS_EQUAL(kyosu::pure(type(r,i,j,k)), type(T(0), i, j, k)); +}; + +TTS_CASE_WITH ( "Check kyosu::pure over octonion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) ) + ) + (T r, T i, T j, T k, T l, T li, T lj, T lk) +{ + using type = kyosu::as_octonion_t; + TTS_EQUAL(kyosu::pure(type(r,i,j,k,l,li,lj,lk)), type(T(0),i,j,k,l,li,lj,lk) ); +}; diff --git a/test/unit/function/real.cpp b/test/unit/function/real.cpp new file mode 100644 index 00000000..6e97b050 --- /dev/null +++ b/test/unit/function/real.cpp @@ -0,0 +1,52 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +TTS_CASE_WITH ( "Check kyosu::real over real" + , kyosu::real_types + , tts::generate(tts::between(-10,10)) + ) +(auto data) +{ + TTS_EQUAL(kyosu::real(data), data); +}; + +TTS_CASE_WITH ( "Check kyosu::real over complex" + , kyosu::real_types + , tts::generate(tts::between(-10,10), tts::between(-10,10)) + ) +(auto r, auto i) +{ + TTS_EQUAL(kyosu::real(kyosu::to_complex(r,i)), r); +}; + +TTS_CASE_WITH ( "Check kyosu::real over quaternion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + ) + ) + (T r, T i, T j, T k) +{ + using type = kyosu::as_quaternion_t; + TTS_EQUAL(kyosu::real(type(r,i,j,k)), r); +}; + +TTS_CASE_WITH ( "Check kyosu::real over octonion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) ) + ) + (T r, T i, T j, T k, T l, T li, T lj, T lk) +{ + using type = kyosu::as_octonion_t; + TTS_EQUAL(kyosu::real(type(r,i,j,k,l,li,lj,lk)), r ); +}; diff --git a/test/unit/function/sech.cpp b/test/unit/function/sech.cpp new file mode 100644 index 00000000..dc782e66 --- /dev/null +++ b/test/unit/function/sech.cpp @@ -0,0 +1,49 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +# if __has_include () +# include +# define HAS_BOOST +# endif + +TTS_CASE_WITH ( "Check kyosu::sech over real" + , kyosu::real_types + , tts::generate(tts::between(-10,10)) + ) +(auto data) +{ + TTS_ULP_EQUAL(kyosu::sech(data), eve::sech(data), 0.5); +}; + +#ifdef HAS_BOOST + +template < typename T > +auto cv(boost::math::quaternion const &bq) +{ + return kyosu::quaternion(bq.R_component_1(), bq.R_component_2(), + bq.R_component_3(), bq.R_component_4()); +} + +TTS_CASE_WITH ( "Check kyosu::sech over quaternion" + , kyosu::simd_real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + ) + ) +(T r, T i, T j, T k) +{ + using ke_t = kyosu::as_quaternion_t; + using bq_t = boost::math::quaternion>; + auto boost_sech = [](auto x, auto y, auto z, auto t){return kyosu::rec(cv(boost::math::cosh(bq_t(x, y, z, t)))); }; + ke_t e([&](auto n, auto){return boost_sech(r.get(n), i.get(n), j.get(n), k.get(n)); }); + auto q = ke_t(r,i,j,k); + TTS_RELATIVE_EQUAL(kyosu::sech(q), e, 1e-5); +}; +# endif diff --git a/test/unit/function/sign.cpp b/test/unit/function/sign.cpp new file mode 100644 index 00000000..c676cd1c --- /dev/null +++ b/test/unit/function/sign.cpp @@ -0,0 +1,32 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +TTS_CASE_WITH ( "Check kyosu::sign over real" + , kyosu::real_types + , tts::generate(tts::between(-10,10)) + ) +(auto data) +{ + TTS_ULP_EQUAL(kyosu::sign(data), eve::sign(data), 0.5); +}; + + +TTS_CASE_WITH ( "Check kyosu::sign over quaternion" + , kyosu::simd_real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + ) + ) +(T r, T i, T j, T k) +{ + using ke_t = kyosu::as_quaternion_t; + auto q = ke_t(r,i,j,k); + TTS_RELATIVE_EQUAL(kyosu::sign(q), q/kyosu::abs(q), 1e-5); +}; diff --git a/test/unit/function/sqrt.cpp b/test/unit/function/sqrt.cpp new file mode 100644 index 00000000..865140ac --- /dev/null +++ b/test/unit/function/sqrt.cpp @@ -0,0 +1,44 @@ +//====================================================================================================================== +/* + Kyosu - Complex Without Complexes + Copyright : KYOSU Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +*/ +//====================================================================================================================== +#include +#include + +TTS_CASE_WITH ( "Check kyosu::sqrt over real" + , kyosu::real_types + , tts::generate(tts::between(-10,10)) + ) + (T v) +{ + auto asq = eve::sqrt(eve::abs(v)); + TTS_RELATIVE_EQUAL(kyosu::sqrt(v), kyosu::if_else(eve::is_gez(v) + , kyosu::to_complex(asq, T(0)) + , kyosu::to_complex(T(0), asq)), 1.0e-5); +}; + +TTS_CASE_WITH ( "Check kyosu::sqrt over complex" + , kyosu::real_types + , tts::generate(tts::between(-10,10), tts::between(-10,10)) + ) +(auto r, auto i) +{ + auto c = kyosu::to_complex(r,i); + TTS_RELATIVE_EQUAL(kyosu::sqr(kyosu::sqrt(c)), c, 1e-5); +}; + +TTS_CASE_WITH ( "Check kyosu::sqrt over quaternion" + , kyosu::real_types + , tts::generate ( tts::between(-10,10), tts::between(-10,10) + , tts::between(-10,10), tts::between(-10,10) + ) + ) + (T r, T i, T j, T k) +{ + using type = kyosu::as_quaternion_t; + auto q = type(r,i,j,k); + TTS_RELATIVE_EQUAL(kyosu::sqr((kyosu::sqrt(q))), q, 1e-5); +}; diff --git a/test/unit/quaternion/rotate_vec.cpp b/test/unit/quaternion/rotate_vec.cpp new file mode 100644 index 00000000..1150df53 --- /dev/null +++ b/test/unit/quaternion/rotate_vec.cpp @@ -0,0 +1,36 @@ +//================================================================================================== +/** + EVE - Expressive Vector Engine + Copyright : EVE Project Contributors + SPDX-License-Identifier: BSL-1.0 +**/ +//================================================================================================== +#include "test.hpp" +#include + + +TTS_CASE_WITH ( "Check behavior of rotate_vec on wide" + , kyosu::real_types + , tts::generate( tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + ) + ) + (T const& a0, T const& a1, T const& a2, T const& a3 ) +{ + auto pr = [](auto name, auto v){std::cout << name << v[0] << ", " << v[1] << ", " << v[2] << std::endl; }; + { + std::array v{a0, a1, a2}; + auto rsq3 = eve::rec(eve::sqrt_3(eve::as(a3))); + std::array axis{rsq3, rsq3, rsq3}; + pr("axis = ", axis); + auto q = kyosu::from_angle_axis(eve::pi(eve::as(a0)), std::span(axis)); + auto vr = kyosu::rotate_vec(q, std::span(v), kyosu::normalize); + auto vr2= kyosu::rotate_vec(q, std::span(vr), kyosu::normalize); + pr("v = ", v); + pr("vr = ", vr); + pr("vr2 = ", vr2); + TTS_RELATIVE_EQUAL(v[0], vr2[0], 1.0e-4); + } +}; diff --git a/test/unit/quaternion/slerp.cpp b/test/unit/quaternion/slerp.cpp new file mode 100644 index 00000000..a5aa3fe5 --- /dev/null +++ b/test/unit/quaternion/slerp.cpp @@ -0,0 +1,42 @@ +//================================================================================================== +/** + EVE - Expressive Vector Engine + Copyright : EVE Project Contributors + SPDX-License-Identifier: BSL-1.0 +**/ +//================================================================================================== +#include "test.hpp" +#include + + +TTS_CASE_WITH ( "Check behavior of slerp on" + , kyosu::real_types + , tts::generate( tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + ) + ) + (T const& a0, T const& a1, T const& a2, T const& a3 + , T const& a4, T const& a5, T const& a6, T const& a7) +{ +// auto pr = [](auto name, auto v){std::cout << name << v[0] << ", " << v[1] << ", " << v[2] << std::endl; }; + using e_t = eve::element_type_t; + using q_t = kyosu::as_quaternion_t; + if constexpr(sizeof(e_t) == 8) + { + auto z1 = kyosu::sign(q_t(a0, a1, a2, a3)); + auto z2 = kyosu::sign(q_t(a4, a5, a6, a7)); + auto dz1z2 = kyosu::dot(z1, z2); + z2 = kyosu::if_else(eve::is_gez(dz1z2), z2, -z2); + auto r1 = kyosu::slerp(z1, z2, e_t(0.0)); + auto t1 = kyosu::conj(z1)*z2; + auto t2 = kyosu::pow(t1, e_t(0.0)); + auto t3 = z1*t2; + TTS_RELATIVE_EQUAL(r1, t3, 0.0001); + } +}; diff --git a/test/unit/quaternion/to_angle_axis.cpp b/test/unit/quaternion/to_angle_axis.cpp new file mode 100644 index 00000000..e8cb9b06 --- /dev/null +++ b/test/unit/quaternion/to_angle_axis.cpp @@ -0,0 +1,44 @@ +//================================================================================================== +/** + EVE - Expressive Vector Engine + Copyright : EVE Project Contributors + SPDX-License-Identifier: BSL-1.0 +**/ +//================================================================================================== +#include "test.hpp" +#include + +TTS_CASE_WITH ( "Check behavior of from_angle_axis on wide" + , kyosu::real_types + , tts::generate( tts::randoms(0.5, +1.0) + , tts::randoms(0.5, +1.0) + , tts::randoms(0.5, +1.0) + , tts::randoms(0.5, +1.0) + ) + ) + (T const& a0, T const& a1, T const& a2, T const& a3 ) +{ + { + auto q = kyosu::sign(kyosu::to_quaternion(a0, a1, a2, a3)); + auto [a, v] = kyosu::to_angle_axis(q); + std::span vv(v); + auto q1 = kyosu::from_angle_axis(a, vv); + TTS_RELATIVE_EQUAL(q, q1, 1.0e-5); + } + { + auto c = kyosu::sign(kyosu::to_complex(a0, a1)); + auto [a, v] = kyosu::to_angle_axis(c); + std::span vv(v); + auto c1 = kyosu::from_angle_axis(a, vv); + TTS_RELATIVE_EQUAL(kyosu::to_quaternion(c), c1, 1.0e-5); + } + + { + auto [a, v] = kyosu::to_angle_axis(kyosu::sign(a0)); + std::span vv(v); + auto q1 = kyosu::from_angle_axis(a, vv); + TTS_RELATIVE_EQUAL(kyosu::to_quaternion(eve::sign(a0)), q1, 1.0e-5); + } + + +}; diff --git a/test/unit/quaternion/to_cylindrical.cpp b/test/unit/quaternion/to_cylindrical.cpp new file mode 100644 index 00000000..ffaaf966 --- /dev/null +++ b/test/unit/quaternion/to_cylindrical.cpp @@ -0,0 +1,41 @@ +//================================================================================================== +/** + EVE - Expressive Vector Engine + Copyright : EVE Project Contributors + SPDX-License-Identifier: BSL-1.0 +**/ +//================================================================================================== +#include "test.hpp" +#include + +TTS_CASE_WITH ( "Check behavior of to_cylindrical on wide" + , kyosu::real_types + , tts::generate( tts::randoms(0.5, +1.0) + , tts::randoms(0.5, +1.0) + , tts::randoms(0.5, +1.0) + , tts::randoms(0.5, +1.0) + ) + ) + (T const& a0, T const& a1, T const& a2, T const& a3 ) +{ + { + auto q = kyosu::to_quaternion(a0, a1, a2, a3); + auto [r1, t1, r2, t2] = kyosu::to_cylindrical(q); + auto q1 = kyosu::from_cylindrical(r1, t1, r2, t2); + TTS_RELATIVE_EQUAL(q, q1, 1.0e-5); + } + { + auto c = kyosu::to_complex(a0, a1); + auto [r1, t1, r2, t2] = kyosu::to_cylindrical(c); + auto c1 = kyosu::from_cylindrical(r1, t1, r2, t2); + TTS_RELATIVE_EQUAL(kyosu::to_quaternion(c), c1, 1.0e-5); + } + + { + auto [r1, t1, r2, t2] = kyosu::to_cylindrical(a0); + auto q1 = kyosu::from_cylindrical(r1, t1, r2, t2); + TTS_RELATIVE_EQUAL(kyosu::to_quaternion(a0), q1, 1.0e-5); + } + + +}; diff --git a/test/unit/quaternion/to_euler.cpp b/test/unit/quaternion/to_euler.cpp new file mode 100644 index 00000000..3d3b7d58 --- /dev/null +++ b/test/unit/quaternion/to_euler.cpp @@ -0,0 +1,36 @@ +//================================================================================================== +/** + EVE - Expressive Vector Engine + Copyright : EVE Project Contributors + SPDX-License-Identifier: BSL-1.0 +**/ +//================================================================================================== +#include "test.hpp" +#include + + +TTS_CASE_WITH ( "Check behavior of rotate_vec on wide" + , kyosu::real_types + , tts::generate( tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + ) + ) + (T const& a0, T const& a1, T const& a2) +{ + { + auto z = kyosu::from_euler(a0, a1, a2, kyosu::Z_, kyosu::X_, kyosu::Z_, kyosu::extrinsic); + auto [t1, t2, t3] = kyosu::to_euler(z, kyosu::Z_, kyosu::X_, kyosu::Z_, kyosu::extrinsic); + TTS_RELATIVE_EQUAL(t1, a0, 1.0e-4); + TTS_RELATIVE_EQUAL(t2, a1, 1.0e-4); + TTS_RELATIVE_EQUAL(t3, a2, 1.0e-4); + } + { + auto z = kyosu::from_euler(a0, a1, a2, kyosu::Z_, kyosu::X_, kyosu::Z_, kyosu::intrinsic); + auto [t1, t2, t3] = kyosu::to_euler(z, kyosu::Z_, kyosu::X_, kyosu::Z_, kyosu::intrinsic); + TTS_RELATIVE_EQUAL(t1, a0, 1.0e-4); + TTS_RELATIVE_EQUAL(t2, a1, 1.0e-4); + TTS_RELATIVE_EQUAL(t3, a2, 1.0e-4); + } + +}; diff --git a/test/unit/quaternion/to_multipolar.cpp b/test/unit/quaternion/to_multipolar.cpp new file mode 100644 index 00000000..3e89ec8d --- /dev/null +++ b/test/unit/quaternion/to_multipolar.cpp @@ -0,0 +1,41 @@ +//================================================================================================== +/** + EVE - Expressive Vector Engine + Copyright : EVE Project Contributors + SPDX-License-Identifier: BSL-1.0 +**/ +//================================================================================================== +#include "test.hpp" +#include + +TTS_CASE_WITH ( "Check behavior of from_multipolar on wide" + , kyosu::real_types + , tts::generate( tts::randoms(0.5, +1.0) + , tts::randoms(0.5, +1.0) + , tts::randoms(0.5, +1.0) + , tts::randoms(0.5, +1.0) + ) + ) + (T const& a0, T const& a1, T const& a2, T const& a3 ) +{ + { + auto q = kyosu::to_quaternion(a0, a1, a2, a3); + auto [r1, t1, r2, t2] = kyosu::to_multipolar(q); + auto q1 = kyosu::from_multipolar(r1, t1, r2, t2); + TTS_RELATIVE_EQUAL(q, q1, 1.0e-5); + } + { + auto c = kyosu::to_complex(a0, a1); + auto [r1, t1, r2, t2] = kyosu::to_multipolar(c); + auto c1 = kyosu::from_multipolar(r1, t1, r2, t2); + TTS_RELATIVE_EQUAL(kyosu::to_quaternion(c), c1, 1.0e-5); + } + + { + auto [r1, t1, r2, t2] = kyosu::to_multipolar(a0); + auto q1 = kyosu::from_multipolar(r1, t1, r2, t2); + TTS_RELATIVE_EQUAL(kyosu::to_quaternion(a0), q1, 1.0e-5); + } + + +}; diff --git a/test/unit/quaternion/to_rotation_matrix.cpp b/test/unit/quaternion/to_rotation_matrix.cpp new file mode 100644 index 00000000..3d15152a --- /dev/null +++ b/test/unit/quaternion/to_rotation_matrix.cpp @@ -0,0 +1,52 @@ +//================================================================================================== +/** + EVE - Expressive Vector Engine + Copyright : EVE Project Contributors + SPDX-License-Identifier: BSL-1.0 +**/ +//================================================================================================== +#include "test.hpp" +#include + +template +auto prod(auto m, std::array const & v) +{ + std::array r; + for(size_t i=0; i < v.size(); ++i) + { + r[i] = m[i][0]*v[0]+m[i][1]*v[1]+m[i][2]*v[2]; + } + return r; +} + +TTS_CASE_WITH ( "Check behavior of to_rotation_matrix on wide" + , kyosu::simd_real_types + , tts::generate( tts::randoms(0.5, +1.0) + , tts::randoms(0.5, +1.0) + , tts::randoms(0.5, +1.0) + , tts::randoms(0.5, +1.0) + ) + ) + (T const& a0, T const& a1, T const& a2, T const& a3 ) +{ + using e_t = eve::element_type_t ; + using wq_t = eve::wide, eve::cardinal_t>; + std::array v{T(1), T(2), T(3)}; + auto q = kyosu::sign(wq_t(a0, a1, a2, a3)); + wq_t qv(0, v[0], v[1], v[2]); + auto m = kyosu::to_rotation_matrix(q, kyosu::assume_normalized); + auto refq = q*qv*kyosu::conj(q); + auto res = prod(m, v); + std::array ref{kyosu::ipart(refq), kyosu::jpart(refq), kyosu::kpart(refq)}; + for(int j=0; j <3 ; ++j) + { + TTS_RELATIVE_EQUAL(res[j], ref[j], 0.0002); + } + auto q1 = wq_t(a0, a1, a2, a3); + auto m1 = kyosu::to_rotation_matrix(q1); + auto res1 = prod(m1, v); + for(int j=0; j <3 ; ++j) + { + TTS_RELATIVE_EQUAL(res1[j], ref[j], 0.0002); + } +}; diff --git a/test/unit/quaternion/to_semipolar.cpp b/test/unit/quaternion/to_semipolar.cpp new file mode 100644 index 00000000..98875367 --- /dev/null +++ b/test/unit/quaternion/to_semipolar.cpp @@ -0,0 +1,41 @@ +//================================================================================================== +/** + EVE - Expressive Vector Engine + Copyright : EVE Project Contributors + SPDX-License-Identifier: BSL-1.0 +**/ +//================================================================================================== +#include "test.hpp" +#include + +TTS_CASE_WITH ( "Check behavior of to_semipolar on wide" + , kyosu::real_types + , tts::generate( tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + ) + ) + (T const& a0, T const& a1, T const& a2, T const& a3 ) +{ + { + auto q = kyosu::to_quaternion(a0, a1, a2, a3); + auto [r1, t1, r2, t2] = kyosu::to_semipolar(q); + auto q1 = kyosu::from_semipolar(r1, t1, r2, t2); + TTS_RELATIVE_EQUAL(q, q1, 1.0e-5); + } + { + auto c = kyosu::to_complex(a0, a1); + auto [r1, t1, r2, t2] = kyosu::to_semipolar(c); + auto c1 = kyosu::from_semipolar(r1, t1, r2, t2); + TTS_RELATIVE_EQUAL(kyosu::to_quaternion(c), c1, 1.0e-5); + } + + { + auto [r1, t1, r2, t2] = kyosu::to_semipolar(a0); + auto q1 = kyosu::from_semipolar(r1, t1, r2, t2); + TTS_RELATIVE_EQUAL(kyosu::to_quaternion(a0), q1, 1.0e-5); + } + + +}; diff --git a/test/unit/quaternion/to_spherical.cpp b/test/unit/quaternion/to_spherical.cpp new file mode 100644 index 00000000..6c507f3c --- /dev/null +++ b/test/unit/quaternion/to_spherical.cpp @@ -0,0 +1,41 @@ +//================================================================================================== +/** + EVE - Expressive Vector Engine + Copyright : EVE Project Contributors + SPDX-License-Identifier: BSL-1.0 +**/ +//================================================================================================== +#include "test.hpp" +#include + +TTS_CASE_WITH ( "Check behavior of to_spherical on wide" + , kyosu::scalar_real_types + , tts::generate( tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + , tts::randoms(0.25, +0.75) + ) + ) + (T const& a0, T const& a1, T const& a2, T const& a3 ) +{ + { + auto q = kyosu::to_quaternion(a0, a1, a2, a3); + auto [rho, theta, phi1, phi2] = kyosu::to_spherical(q); + auto q1 = kyosu::from_spherical(rho, theta, phi1, phi2); + TTS_RELATIVE_EQUAL(q, q1, 1.0e-5); + } + { + auto c = kyosu::to_complex(a0, a1); + auto [rho, theta, ph11, phi2] = kyosu::to_spherical(c); + auto c1 = kyosu::from_spherical(rho, theta, ph11, phi2); + TTS_RELATIVE_EQUAL(kyosu::to_quaternion(c), c1, 1.0e-5); + } + + { + auto [rho, theta, ph11, phi2] = kyosu::to_spherical(a0); + auto q1 = kyosu::from_spherical(rho, theta, ph11, phi2); + TTS_RELATIVE_EQUAL(kyosu::to_quaternion(a0), q1, 1.0e-5); + } + + +};