/home/runner/work/kynema/kynema/kynema/src/math/quaternion_operations.hpp Source File

Kynema API: /home/runner/work/kynema/kynema/kynema/src/math/quaternion_operations.hpp Source File
Kynema API
A flexible multibody structural dynamics code for wind turbines
Loading...
Searching...
No Matches
quaternion_operations.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <array>
4#include <cmath>
5#include <iterator>
6#include <numbers>
7#include <ranges>
8#include <span>
9
10#include <Kokkos_Core.hpp>
11
12#include "vector_operations.hpp"
13
14namespace kynema::math {
15
19template <typename Quaternion, typename RotationMatrix>
20KOKKOS_INLINE_FUNCTION void QuaternionToRotationMatrix(
21 const Quaternion& q, const RotationMatrix& R
22) {
23 R(0, 0) = q(0) * q(0) + q(1) * q(1) - q(2) * q(2) - q(3) * q(3);
24 R(0, 1) = 2. * (q(1) * q(2) - q(0) * q(3));
25 R(0, 2) = 2. * (q(1) * q(3) + q(0) * q(2));
26 R(1, 0) = 2. * (q(1) * q(2) + q(0) * q(3));
27 R(1, 1) = q(0) * q(0) - q(1) * q(1) + q(2) * q(2) - q(3) * q(3);
28 R(1, 2) = 2. * (q(2) * q(3) - q(0) * q(1));
29 R(2, 0) = 2. * (q(1) * q(3) - q(0) * q(2));
30 R(2, 1) = 2. * (q(2) * q(3) + q(0) * q(1));
31 R(2, 2) = q(0) * q(0) - q(1) * q(1) - q(2) * q(2) + q(3) * q(3);
32}
33
37inline std::array<std::array<double, 3>, 3> QuaternionToRotationMatrix(const std::array<double, 4>& q
38) {
39 return std::array<std::array<double, 3>, 3>{{
40 {
41 q[0] * q[0] + q[1] * q[1] - q[2] * q[2] - q[3] * q[3],
42 2. * (q[1] * q[2] - q[0] * q[3]),
43 2. * (q[1] * q[3] + q[0] * q[2]),
44 },
45 {
46 2. * (q[1] * q[2] + q[0] * q[3]),
47 q[0] * q[0] - q[1] * q[1] + q[2] * q[2] - q[3] * q[3],
48 2. * (q[2] * q[3] - q[0] * q[1]),
49 },
50 {
51 2. * (q[1] * q[3] - q[0] * q[2]),
52 2. * (q[2] * q[3] + q[0] * q[1]),
53 q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3],
54 },
55 }};
56}
57
63inline std::array<double, 4> RotationMatrixToQuaternion(const std::array<std::array<double, 3>, 3>& m
64) {
65 auto m22_p_m33 = m[1][1] + m[2][2];
66 auto m22_m_m33 = m[1][1] - m[2][2];
67 std::array<double, 4> vals{
68 m[0][0] + m22_p_m33,
69 m[0][0] - m22_p_m33,
70 -m[0][0] + m22_m_m33,
71 -m[0][0] - m22_m_m33,
72 };
73
74 // Get maximum value and index of maximum value
75 const auto* max_num = std::max_element(vals.begin(), vals.end());
76 auto max_idx = std::distance(vals.cbegin(), max_num);
77
78 auto tmp = sqrt(*max_num + 1.);
79 auto c = 0.5 / tmp;
80
81 if (max_idx == 0) {
82 return std::array<double, 4>{
83 0.5 * tmp,
84 (m[2][1] - m[1][2]) * c,
85 (m[0][2] - m[2][0]) * c,
86 (m[1][0] - m[0][1]) * c,
87 };
88 }
89 if (max_idx == 1) {
90 return std::array<double, 4>{
91 (m[2][1] - m[1][2]) * c,
92 0.5 * tmp,
93 (m[0][1] + m[1][0]) * c,
94 (m[0][2] + m[2][0]) * c,
95 };
96 }
97 if (max_idx == 2) {
98 return std::array<double, 4>{
99 (m[0][2] - m[2][0]) * c,
100 (m[0][1] + m[1][0]) * c,
101 0.5 * tmp,
102 (m[1][2] + m[2][1]) * c,
103 };
104 }
105 if (max_idx == 3) {
106 return std::array<double, 4>{
107 (m[1][0] - m[0][1]) * c,
108 (m[0][2] + m[2][0]) * c,
109 (m[1][2] + m[2][1]) * c,
110 0.5 * tmp,
111 };
112 }
113 return std::array<double, 4>{1., 0., 0., 0};
114}
115
119template <typename Quaternion, typename View1, typename View2>
120KOKKOS_INLINE_FUNCTION void RotateVectorByQuaternion(
121 const Quaternion& q, const View1& v, const View2& v_rot
122) {
123 v_rot(0) = (q(0) * q(0) + q(1) * q(1) - q(2) * q(2) - q(3) * q(3)) * v(0) +
124 2. * (q(1) * q(2) - q(0) * q(3)) * v(1) + 2. * (q(1) * q(3) + q(0) * q(2)) * v(2);
125 v_rot(1) = 2. * (q(1) * q(2) + q(0) * q(3)) * v(0) +
126 (q(0) * q(0) - q(1) * q(1) + q(2) * q(2) - q(3) * q(3)) * v(1) +
127 2. * (q(2) * q(3) - q(0) * q(1)) * v(2);
128 v_rot(2) = 2. * (q(1) * q(3) - q(0) * q(2)) * v(0) + 2. * (q(2) * q(3) + q(0) * q(1)) * v(1) +
129 (q(0) * q(0) - q(1) * q(1) - q(2) * q(2) + q(3) * q(3)) * v(2);
130}
131
135inline std::array<double, 3> RotateVectorByQuaternion(
136 std::span<const double, 4> q, std::span<const double, 3> v
137) {
138 auto v_rot = std::array<double, 3>{};
139 v_rot[0] = (q[0] * q[0] + q[1] * q[1] - q[2] * q[2] - q[3] * q[3]) * v[0] +
140 2. * (q[1] * q[2] - q[0] * q[3]) * v[1] + 2. * (q[1] * q[3] + q[0] * q[2]) * v[2];
141 v_rot[1] = 2. * (q[1] * q[2] + q[0] * q[3]) * v[0] +
142 (q[0] * q[0] - q[1] * q[1] + q[2] * q[2] - q[3] * q[3]) * v[1] +
143 2. * (q[2] * q[3] - q[0] * q[1]) * v[2];
144 v_rot[2] = 2. * (q[1] * q[3] - q[0] * q[2]) * v[0] + 2. * (q[2] * q[3] + q[0] * q[1]) * v[1] +
145 (q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3]) * v[2];
146 return v_rot;
147}
148
152template <typename Quaternion, typename Matrix>
153KOKKOS_INLINE_FUNCTION void QuaternionDerivative(const Quaternion& q, const Matrix& m) {
154 m(0, 0) = -q(1);
155 m(0, 1) = q(0);
156 m(0, 2) = -q(3);
157 m(0, 3) = q(2);
158 m(1, 0) = -q(2);
159 m(1, 1) = q(3);
160 m(1, 2) = q(0);
161 m(1, 3) = -q(1);
162 m(2, 0) = -q(3);
163 m(2, 1) = -q(2);
164 m(2, 2) = q(1);
165 m(2, 3) = q(0);
166}
167
171template <typename QuaternionInput, typename QuaternionOutput>
172KOKKOS_INLINE_FUNCTION void QuaternionInverse(
173 const QuaternionInput& q_in, const QuaternionOutput& q_out
174) {
175 auto length =
176 Kokkos::sqrt(q_in(0) * q_in(0) + q_in(1) * q_in(1) + q_in(2) * q_in(2) + q_in(3) * q_in(3));
177
178 // Inverse of a quaternion is the conjugate divided by the length
179 q_out(0) = q_in(0) / length;
180 for (auto i = 1; i < 4; ++i) {
181 q_out(i) = -q_in(i) / length;
182 }
183}
184
188inline std::array<double, 4> QuaternionInverse(std::span<const double, 4> quaternion) {
189 const auto length = std::sqrt(
190 quaternion[0] * quaternion[0] + quaternion[1] * quaternion[1] +
191 quaternion[2] * quaternion[2] + quaternion[3] * quaternion[3]
192 );
193
194 return {
195 quaternion[0] / length, -quaternion[1] / length, -quaternion[2] / length,
196 -quaternion[3] / length
197 };
198}
199
203template <typename Quaternion1, typename Quaternion2, typename QuaternionN>
204KOKKOS_INLINE_FUNCTION void QuaternionCompose(
205 const Quaternion1& q1, const Quaternion2& q2, QuaternionN& qn
206) {
207 qn(0) = q1(0) * q2(0) - q1(1) * q2(1) - q1(2) * q2(2) - q1(3) * q2(3);
208 qn(1) = q1(0) * q2(1) + q1(1) * q2(0) + q1(2) * q2(3) - q1(3) * q2(2);
209 qn(2) = q1(0) * q2(2) - q1(1) * q2(3) + q1(2) * q2(0) + q1(3) * q2(1);
210 qn(3) = q1(0) * q2(3) + q1(1) * q2(2) - q1(2) * q2(1) + q1(3) * q2(0);
211}
212
216inline std::array<double, 4> QuaternionCompose(
217 const std::array<double, 4>& q1, const std::array<double, 4>& q2
218) {
219 auto qn = std::array<double, 4>{};
220 qn[0] = q1[0] * q2[0] - q1[1] * q2[1] - q1[2] * q2[2] - q1[3] * q2[3];
221 qn[1] = q1[0] * q2[1] + q1[1] * q2[0] + q1[2] * q2[3] - q1[3] * q2[2];
222 qn[2] = q1[0] * q2[2] - q1[1] * q2[3] + q1[2] * q2[0] + q1[3] * q2[1];
223 qn[3] = q1[0] * q2[3] + q1[1] * q2[2] - q1[2] * q2[1] + q1[3] * q2[0];
224 return qn;
225}
226
230template <typename Vector, typename Quaternion>
231KOKKOS_INLINE_FUNCTION void RotationVectorToQuaternion(
232 const Vector& phi, const Quaternion& quaternion
233) {
234 const auto angle = Kokkos::sqrt(phi(0) * phi(0) + phi(1) * phi(1) + phi(2) * phi(2));
235 const auto cos_angle = Kokkos::cos(angle / 2.);
236 const auto factor = (Kokkos::abs(angle) < 1.e-12) ? 0. : Kokkos::sin(angle / 2.) / angle;
237
238 quaternion(0) = cos_angle;
239 for (auto i = 1; i < 4; ++i) {
240 quaternion(i) = phi(i - 1) * factor;
241 }
242}
243
247template <typename Quaternion, typename Vector>
248KOKKOS_INLINE_FUNCTION void QuaternionToRotationVector(
249 const Quaternion& quaternion, const Vector& phi
250) {
251 auto theta = 2. * Kokkos::acos(quaternion(0));
252 const auto sin_half_theta = std::sqrt(1. - quaternion(0) * quaternion(0));
253 if (sin_half_theta > 1e-12) {
254 phi(0) = theta * quaternion(1) / sin_half_theta;
255 phi(1) = theta * quaternion(2) / sin_half_theta;
256 phi(2) = theta * quaternion(3) / sin_half_theta;
257 } else {
258 phi(0) = 0.;
259 phi(1) = 0.;
260 phi(2) = 0.;
261 }
262}
263
267inline std::array<double, 3> QuaternionToRotationVector(const std::array<double, 4>& quaternion) {
268 auto theta = 2. * Kokkos::acos(quaternion[0]);
269 const auto sin_half_theta = std::sqrt(1. - quaternion[0] * quaternion[0]);
270 std::array<double, 3> phi{};
271 if (sin_half_theta > 1e-12) {
272 phi[0] = theta * quaternion[1] / sin_half_theta;
273 phi[1] = theta * quaternion[2] / sin_half_theta;
274 phi[2] = theta * quaternion[3] / sin_half_theta;
275 } else {
276 phi[0] = 0.;
277 phi[1] = 0.;
278 phi[2] = 0.;
279 }
280 return phi;
281}
282
286inline std::array<double, 4> RotationVectorToQuaternion(const std::array<double, 3>& phi) {
287 const auto angle = std::sqrt(phi[0] * phi[0] + phi[1] * phi[1] + phi[2] * phi[2]);
288
289 if (std::abs(angle) < 1e-12) {
290 return std::array<double, 4>{1., 0., 0., 0.};
291 }
292
293 const auto sin_angle = std::sin(angle / 2.);
294 const auto cos_angle = std::cos(angle / 2.);
295 const auto factor = sin_angle / angle;
296 return std::array<double, 4>{cos_angle, phi[0] * factor, phi[1] * factor, phi[2] * factor};
297}
298
308KOKKOS_INLINE_FUNCTION
309Kokkos::Array<double, 4> NormalizeQuaternion(const Kokkos::Array<double, 4>& q) {
310 const auto length_squared = q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3];
311
312 // If the length is 1, our work is done
313 if (std::abs(length_squared - 1.) < 1.e-16) {
314 return q;
315 }
316
317 // If the length of the quaternion is zero, return a default unit quaternion
318 if (std::abs(length_squared) < 1.e-16) {
319 return Kokkos::Array<double, 4>{1., 0., 0., 0.};
320 }
321
322 // Normalize the quaternion
323 const auto length = std::sqrt(length_squared);
324 auto normalized_quaternion = Kokkos::Array<double, 4>{};
325 for (auto k = 0U; k < 4; ++k) {
326 normalized_quaternion[k] = q[k] / length;
327 }
328 return normalized_quaternion;
329}
330
334inline std::array<double, 4> TangentTwistToQuaternion(
335 const std::array<double, 3>& tangent, const double twist
336) {
337 const auto e1 = UnitVector(tangent);
338 std::array<double, 3> temp{0., 1., 0.};
339 if (std::abs(DotProduct(e1, temp)) > 0.9) {
340 temp = {1., 0., 0.};
341 }
342 const auto a = DotProduct(e1, temp);
343
344 // Construct e2 orthogonal to e1 and lying in the y-z plane
345 std::array<double, 3> e2 =
346 UnitVector({temp[0] - e1[0] * a, temp[1] - e1[1] * a, temp[2] - e1[2] * a});
347
348 // Construct e3 as cross product
349 const auto e3 = CrossProduct(e1, e2);
350
351 auto q_tan = RotationMatrixToQuaternion({{
352 {e1[0], e2[0], e3[0]},
353 {e1[1], e2[1], e3[1]},
354 {e1[2], e2[2], e3[2]},
355 }});
356
357 const auto twist_rad = twist * std::numbers::pi / 180.;
358 auto q_twist =
359 RotationVectorToQuaternion({e1[0] * twist_rad, e1[1] * twist_rad, e1[2] * twist_rad});
360
361 return QuaternionCompose(q_twist, q_tan);
362}
363
367inline std::array<double, 4> AxisAngleToQuaternion(const std::array<double, 3>& axis, double angle) {
368 if (std::abs(angle) < 1.e-16) {
369 return {1., 0., 0., 0.};
370 }
371 const auto sin = std::sin(angle / 2.);
372 const auto cos = std::cos(angle / 2.);
373 return {cos, sin * axis[0], sin * axis[1], sin * axis[2]};
374}
375
383inline bool IsIdentityQuaternion(const std::array<double, 4>& q, double tolerance = 1e-12) {
384 return std::abs(q[0] - 1.) <= tolerance && std::abs(q[1]) <= tolerance &&
385 std::abs(q[2]) <= tolerance && std::abs(q[3]) <= tolerance;
386}
387
388} // namespace kynema::math
Definition gll_quadrature.hpp:7
KOKKOS_INLINE_FUNCTION void QuaternionToRotationVector(const Quaternion &quaternion, const Vector &phi)
Returns a 3-D rotation vector from provided 4-D quaternion, i.e. the logarithmic map.
Definition quaternion_operations.hpp:248
KOKKOS_INLINE_FUNCTION void CrossProduct(const VectorType &a, const VectorType &b, const VectorType &c)
Calculate the cross product between two vector views.
Definition vector_operations.hpp:40
std::array< double, 4 > TangentTwistToQuaternion(const std::array< double, 3 > &tangent, const double twist)
Returns a 4-D quaternion from provided tangent vector and twist (degrees) about tangent.
Definition quaternion_operations.hpp:334
KOKKOS_INLINE_FUNCTION void QuaternionCompose(const Quaternion1 &q1, const Quaternion2 &q2, QuaternionN &qn)
Composes (i.e. multiplies) two quaternions and stores the result in a third quaternion.
Definition quaternion_operations.hpp:204
std::array< double, 4 > RotationMatrixToQuaternion(const std::array< std::array< double, 3 >, 3 > &m)
Converts a 3x3 rotation matrix to a 4x1 quaternion and returns the result, see https://www....
Definition quaternion_operations.hpp:63
std::array< double, 4 > AxisAngleToQuaternion(const std::array< double, 3 > &axis, double angle)
Returns a quaternion from the axis vector and angle (radians)
Definition quaternion_operations.hpp:367
KOKKOS_INLINE_FUNCTION void RotationVectorToQuaternion(const Vector &phi, const Quaternion &quaternion)
Returns a 4-D quaternion from provided 3-D rotation vector, i.e. the exponential map.
Definition quaternion_operations.hpp:231
KOKKOS_INLINE_FUNCTION double DotProduct(const AVectorType &a, const BVectorType &b)
Calculate the dot product between two vector views.
Definition vector_operations.hpp:25
bool IsIdentityQuaternion(const std::array< double, 4 > &q, double tolerance=1e-12)
Checks if a quaternion is approximately the identity quaternion [1, 0, 0, 0].
Definition quaternion_operations.hpp:383
KOKKOS_INLINE_FUNCTION Kokkos::Array< double, 4 > NormalizeQuaternion(const Kokkos::Array< double, 4 > &q)
Normalizes a quaternion to ensure it is a unit quaternion.
Definition quaternion_operations.hpp:309
KOKKOS_INLINE_FUNCTION void RotateVectorByQuaternion(const Quaternion &q, const View1 &v, const View2 &v_rot)
Rotates provided vector by provided unit quaternion and returns the result.
Definition quaternion_operations.hpp:120
constexpr std::array< double, 3 > UnitVector(const std::array< double, 3 > &v)
UnitVector returns the unit vector of the given vector.
Definition vector_operations.hpp:65
KOKKOS_INLINE_FUNCTION void QuaternionToRotationMatrix(const Quaternion &q, const RotationMatrix &R)
Converts a 4x1 quaternion to a 3x3 rotation matrix and returns the result.
Definition quaternion_operations.hpp:20
KOKKOS_INLINE_FUNCTION void QuaternionDerivative(const Quaternion &q, const Matrix &m)
Computes the derivative of a quaternion and stores the result in a 3x4 matrix.
Definition quaternion_operations.hpp:153
KOKKOS_INLINE_FUNCTION void QuaternionInverse(const QuaternionInput &q_in, const QuaternionOutput &q_out)
Computes the inverse of a quaternion.
Definition quaternion_operations.hpp:172