OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
TestQuaternion.cpp
Go to the documentation of this file.
1#include "gtest/gtest.h"
2
3#include "Algorithms/Matrix.h"
5#include "Ippl.h"
7
8#include <cmath>
9
10class QuaternionTest : public ::testing::Test {
11protected:
12 static void SetUpTestSuite() {
13 int argc = 0;
14 char** argv = nullptr;
15
16 ippl::initialize(argc, argv);
17 }
18
19 static void TearDownTestSuite() { ippl::finalize(); }
20
21 void SetUp() override {
22 // nothing special
23 }
24
25 void TearDown() override {
26 // nothing special
27 }
28};
29
30TEST_F(QuaternionTest, DefaultConstructor) {
31 Quaternion q;
32 EXPECT_DOUBLE_EQ(q.real(), 1.0);
33 EXPECT_DOUBLE_EQ(q(0), 1.0);
34 EXPECT_DOUBLE_EQ(q(1), 0.0);
35 EXPECT_DOUBLE_EQ(q(2), 0.0);
36 EXPECT_DOUBLE_EQ(q(3), 0.0);
37}
38
39TEST_F(QuaternionTest, ComponentConstructor) {
40 Quaternion q(1.0, 2.0, 3.0, 4.0);
41 EXPECT_DOUBLE_EQ(q.real(), 1.0);
42 EXPECT_DOUBLE_EQ(q(0), 1.0);
43 EXPECT_DOUBLE_EQ(q(1), 2.0);
44 EXPECT_DOUBLE_EQ(q(2), 3.0);
45 EXPECT_DOUBLE_EQ(q(3), 4.0);
46}
47
48TEST_F(QuaternionTest, VectorConstructor) {
49 ippl::Vector<double, 3> vec(1.0, 2.0, 3.0);
50 Quaternion q(vec);
51 EXPECT_DOUBLE_EQ(q.real(), 0.0);
52 EXPECT_DOUBLE_EQ(q(1), 1.0);
53 EXPECT_DOUBLE_EQ(q(2), 2.0);
54 EXPECT_DOUBLE_EQ(q(3), 3.0);
55 EXPECT_TRUE(q.isPure());
56}
57
58TEST_F(QuaternionTest, RealPlusVectorConstructor) {
59 ippl::Vector<double, 3> vec(1.0, 2.0, 3.0);
60 Quaternion q(0.5, vec);
61 EXPECT_DOUBLE_EQ(q.real(), 0.5);
62 EXPECT_DOUBLE_EQ(q(1), 1.0);
63 EXPECT_DOUBLE_EQ(q(2), 2.0);
64 EXPECT_DOUBLE_EQ(q(3), 3.0);
65}
66
67TEST_F(QuaternionTest, CopyConstructor) {
68 Quaternion q1(1.0, 2.0, 3.0, 4.0);
69 Quaternion q2(q1);
70 EXPECT_DOUBLE_EQ(q2.real(), 1.0);
71 EXPECT_DOUBLE_EQ(q2(1), 2.0);
72 EXPECT_DOUBLE_EQ(q2(2), 3.0);
73 EXPECT_DOUBLE_EQ(q2(3), 4.0);
74}
75
76TEST_F(QuaternionTest, RealAndImagParts) {
77 Quaternion q(1.0, 2.0, 3.0, 4.0);
78 EXPECT_DOUBLE_EQ(q.real(), 1.0);
79
80 ippl::Vector<double, 3> imag = q.imag();
81 EXPECT_DOUBLE_EQ(imag(0), 2.0);
82 EXPECT_DOUBLE_EQ(imag(1), 3.0);
83 EXPECT_DOUBLE_EQ(imag(2), 4.0);
84}
85
86TEST_F(QuaternionTest, NormAndLength) {
87 Quaternion q(1.0, 2.0, 3.0, 4.0);
88 // Norm = w^2 + x^2 + y^2 + z^2 = 1 + 4 + 9 + 16 = 30
89 EXPECT_DOUBLE_EQ(q.Norm(), 30.0);
90 EXPECT_DOUBLE_EQ(q.length(), std::sqrt(30.0));
91}
92
94 Quaternion q1(1.0, 0.0, 0.0, 0.0);
95 EXPECT_TRUE(q1.isUnit());
96 Quaternion q2(0.5, 0.5, 0.5, 0.5);
97 EXPECT_TRUE(q2.isUnit());
98 Quaternion q3(1.0, 1.0, 0.0, 0.0);
99 EXPECT_FALSE(q3.isUnit());
100}
101
103 Quaternion q1(0.0, 1.0, 2.0, 3.0);
104 EXPECT_TRUE(q1.isPure());
105 Quaternion q2(1.0, 1.0, 2.0, 3.0);
106 EXPECT_FALSE(q2.isPure());
107}
108
109TEST_F(QuaternionTest, IsPureUnit) {
110 Quaternion q1(0.0, 1.0, 0.0, 0.0);
111 EXPECT_TRUE(q1.isPureUnit());
112 Quaternion q2(0.0, 0.5, 0.5, 0.5);
113 EXPECT_FALSE(q2.isPureUnit());
114 Quaternion q3(0.5, 0.5, 0.5, 0.5);
115 EXPECT_FALSE(q3.isPureUnit());
116}
117
119 Quaternion q(1.0, 2.0, 3.0, 4.0);
120 Quaternion qConj = q.conjugate();
121
122 EXPECT_DOUBLE_EQ(qConj.real(), 1.0);
123 EXPECT_DOUBLE_EQ(qConj(1), -2.0);
124 EXPECT_DOUBLE_EQ(qConj(2), -3.0);
125 EXPECT_DOUBLE_EQ(qConj(3), -4.0);
126}
127
129 Quaternion q(1.0, 2.0, 3.0, 4.0);
130 q.normalize();
131
132 EXPECT_TRUE(q.isUnit());
133 EXPECT_NEAR(q.Norm(), 1.0, 1e-12);
134}
135
137 Quaternion q(1.0, 2.0, 3.0, 4.0);
138 Quaternion qInv = q.inverse();
139
140 // q * q^-1 should be identity (1, 0, 0, 0)
141 Quaternion identity = q * qInv;
142
143 EXPECT_NEAR(identity.real(), 1.0, 1e-12);
144 EXPECT_NEAR(identity(1), 0.0, 1e-12);
145 EXPECT_NEAR(identity(2), 0.0, 1e-12);
146 EXPECT_NEAR(identity(3), 0.0, 1e-12);
147}
148
149TEST_F(QuaternionTest, ScalarMultiplication) {
150 Quaternion q(1.0, 2.0, 3.0, 4.0);
151 Quaternion q2 = q * 2.0;
152
153 EXPECT_DOUBLE_EQ(q2.real(), 2.0);
154 EXPECT_DOUBLE_EQ(q2(1), 4.0);
155 EXPECT_DOUBLE_EQ(q2(2), 6.0);
156 EXPECT_DOUBLE_EQ(q2(3), 8.0);
157}
158
159TEST_F(QuaternionTest, ScalarDivision) {
160 Quaternion q(2.0, 4.0, 6.0, 8.0);
161 Quaternion q2 = q / 2.0;
162
163 EXPECT_DOUBLE_EQ(q2.real(), 1.0);
164 EXPECT_DOUBLE_EQ(q2(1), 2.0);
165 EXPECT_DOUBLE_EQ(q2(2), 3.0);
166 EXPECT_DOUBLE_EQ(q2(3), 4.0);
167}
168
169TEST_F(QuaternionTest, QuaternionMultiplication) {
170 Quaternion q1(1.0, 2.0, 3.0, 4.0);
171 Quaternion q2(4.0, 3.0, 2.0, 1.0);
172
173 Quaternion result = q1 * q2;
174 EXPECT_DOUBLE_EQ(result.real(), -12.0);
175 EXPECT_DOUBLE_EQ(result(1), 6.0);
176 EXPECT_DOUBLE_EQ(result(2), 24.0);
177 EXPECT_DOUBLE_EQ(result(3), 12.0);
178}
179
180TEST_F(QuaternionTest, QuaternionMultiplicationAssociativity) {
181 Quaternion q1(1.0, 2.0, 3.0, 4.0);
182 Quaternion q2(5.0, 6.0, 7.0, 8.0);
183 Quaternion q3(9.0, 10.0, 11.0, 12.0);
184
185 Quaternion result1 = (q1 * q2) * q3;
186 Quaternion result2 = q1 * (q2 * q3);
187
188 EXPECT_NEAR(result1.real(), result2.real(), 1e-10);
189 EXPECT_NEAR(result1(1), result2(1), 1e-10);
190 EXPECT_NEAR(result1(2), result2(2), 1e-10);
191 EXPECT_NEAR(result1(3), result2(3), 1e-10);
192}
193
194TEST_F(QuaternionTest, RotateVector90DegreesAroundZ) {
195 // 90 degree rotation around z-axis
196 double angle = M_PI / 2.0;
197 ippl::Vector<double, 3> axis(0.0, 0.0, 1.0);
198 Quaternion q(std::cos(angle / 2.0), std::sin(angle / 2.0) * axis);
199
200 ippl::Vector<double, 3> vec(1.0, 0.0, 0.0);
201 ippl::Vector<double, 3> rotated = q.rotate(vec);
202
203 EXPECT_NEAR(rotated(0), 0.0, 1e-12);
204 EXPECT_NEAR(rotated(1), 1.0, 1e-12);
205 EXPECT_NEAR(rotated(2), 0.0, 1e-12);
206}
207
208TEST_F(QuaternionTest, RotateVector180DegreesAroundX) {
209 // 180 degree rotation around x-axis
210 double angle = M_PI;
211 ippl::Vector<double, 3> axis(1.0, 0.0, 0.0);
212 Quaternion q(std::cos(angle / 2.0), std::sin(angle / 2.0) * axis);
213
214 ippl::Vector<double, 3> vec(0.0, 1.0, 0.0);
215 ippl::Vector<double, 3> rotated = q.rotate(vec);
216
217 EXPECT_NEAR(rotated(0), 0.0, 1e-12);
218 EXPECT_NEAR(rotated(1), -1.0, 1e-12);
219 EXPECT_NEAR(rotated(2), 0.0, 1e-12);
220}
221
222TEST_F(QuaternionTest, RotateVectorIdentity) {
223 // Identity quaternion (no rotation)
224 Quaternion q(1.0, 0.0, 0.0, 0.0);
225
226 ippl::Vector<double, 3> vec(1.0, 2.0, 3.0);
227 ippl::Vector<double, 3> rotated = q.rotate(vec);
228
229 EXPECT_NEAR(rotated(0), vec(0), 1e-12);
230 EXPECT_NEAR(rotated(1), vec(1), 1e-12);
231 EXPECT_NEAR(rotated(2), vec(2), 1e-12);
232}
233
234TEST_F(QuaternionTest, GetRotationMatrix90DegreesZ) {
235 // 90 degree rotation around z-axis
236 double angle = M_PI / 2.0;
237 ippl::Vector<double, 3> axis(0.0, 0.0, 1.0);
238 Quaternion q(std::cos(angle / 2.0), std::sin(angle / 2.0) * axis);
239
241
242 // Expected rotation matrix for 90 degrees around z
243 // [0 -1 0]
244 // [1 0 0]
245 // [0 0 1]
246 EXPECT_NEAR(R(0, 0), 0.0, 1e-12);
247 EXPECT_NEAR(R(0, 1), -1.0, 1e-12);
248 EXPECT_NEAR(R(0, 2), 0.0, 1e-12);
249 EXPECT_NEAR(R(1, 0), 1.0, 1e-12);
250 EXPECT_NEAR(R(1, 1), 0.0, 1e-12);
251 EXPECT_NEAR(R(1, 2), 0.0, 1e-12);
252 EXPECT_NEAR(R(2, 0), 0.0, 1e-12);
253 EXPECT_NEAR(R(2, 1), 0.0, 1e-12);
254 EXPECT_NEAR(R(2, 2), 1.0, 1e-12);
255}
256
257TEST_F(QuaternionTest, GetRotationMatrixIdentity) {
258 Quaternion q(1.0, 0.0, 0.0, 0.0);
260
261 // Should be identity matrix
262 EXPECT_NEAR(R(0, 0), 1.0, 1e-12);
263 EXPECT_NEAR(R(0, 1), 0.0, 1e-12);
264 EXPECT_NEAR(R(0, 2), 0.0, 1e-12);
265 EXPECT_NEAR(R(1, 0), 0.0, 1e-12);
266 EXPECT_NEAR(R(1, 1), 1.0, 1e-12);
267 EXPECT_NEAR(R(1, 2), 0.0, 1e-12);
268 EXPECT_NEAR(R(2, 0), 0.0, 1e-12);
269 EXPECT_NEAR(R(2, 1), 0.0, 1e-12);
270 EXPECT_NEAR(R(2, 2), 1.0, 1e-12);
271}
272
273TEST_F(QuaternionTest, MatrixConstructorRoundTrip) {
274 // Create a rotation matrix for 45 degrees around x-axis
275 double angle = M_PI / 4.0;
276 double c = std::cos(angle);
277 double s = std::sin(angle);
278
279 matrix3x3_t R(0.0);
280 R(0, 0) = 1.0;
281 R(0, 1) = 0.0;
282 R(0, 2) = 0.0;
283 R(1, 0) = 0.0;
284 R(1, 1) = c;
285 R(1, 2) = -s;
286 R(2, 0) = 0.0;
287 R(2, 1) = s;
288 R(2, 2) = c;
289
290 // Convert to quaternion and back to matrix
291 Quaternion q(R);
293
294 // Check all elements match
295 for (int i = 0; i < 3; ++i) {
296 for (int j = 0; j < 3; ++j) {
297 EXPECT_NEAR(R(i, j), R2(i, j), 1e-10);
298 }
299 }
300}
301
302TEST_F(QuaternionTest, GetQuaternionSameVectors) {
303 ippl::Vector<double, 3> u(1.0, 0.0, 0.0);
304 ippl::Vector<double, 3> ref(1.0, 0.0, 0.0);
305
306 Quaternion q = getQuaternion(u, ref);
307
308 // Should be identity quaternion
309 EXPECT_NEAR(q.real(), 1.0, 1e-12);
310 EXPECT_TRUE(q.isUnit());
311}
312
313TEST_F(QuaternionTest, GetQuaternionOrthogonalVectors) {
314 ippl::Vector<double, 3> u(1.0, 0.0, 0.0);
315 ippl::Vector<double, 3> ref(0.0, 1.0, 0.0);
316
317 Quaternion q = getQuaternion(u, ref);
318
319 // Should be 90 degree rotation
320 EXPECT_TRUE(q.isUnit());
321
322 // Rotate u by q should give ref
323 ippl::Vector<double, 3> rotated = q.rotate(u);
324 EXPECT_NEAR(rotated(0), ref(0), 1e-12);
325 EXPECT_NEAR(rotated(1), ref(1), 1e-12);
326 EXPECT_NEAR(rotated(2), ref(2), 1e-12);
327}
328
329TEST_F(QuaternionTest, GetQuaternionOppositeVectors) {
330 ippl::Vector<double, 3> u(1.0, 0.0, 0.0);
331 ippl::Vector<double, 3> ref(-1.0, 0.0, 0.0);
332
333 Quaternion q = getQuaternion(u, ref);
334
335 // Should be 180 degree rotation
336 EXPECT_TRUE(q.isUnit());
337 EXPECT_NEAR(q.real(), 0.0, 1e-12);
338
339 // Rotate u by q should give ref
340 ippl::Vector<double, 3> rotated = q.rotate(u);
341 EXPECT_NEAR(rotated(0), ref(0), 1e-10);
342 EXPECT_NEAR(rotated(1), ref(1), 1e-10);
343 EXPECT_NEAR(rotated(2), ref(2), 1e-10);
344}
345
346TEST_F(QuaternionTest, ConjugatePreservesNorm) {
347 Quaternion q(1.0, 2.0, 3.0, 4.0);
348 Quaternion qConj = q.conjugate();
349
350 EXPECT_DOUBLE_EQ(q.Norm(), qConj.Norm());
351}
352
353TEST_F(QuaternionTest, MultiplicationByConjugateGivesNorm) {
354 Quaternion q(1.0, 2.0, 3.0, 4.0);
355 Quaternion result = q * q.conjugate();
356
357 EXPECT_NEAR(result.real(), q.Norm(), 1e-12);
358 EXPECT_NEAR(result(1), 0.0, 1e-12);
359 EXPECT_NEAR(result(2), 0.0, 1e-12);
360 EXPECT_NEAR(result(3), 0.0, 1e-12);
361}
362
363TEST_F(QuaternionTest, NormalizeZeroQuaternionThrows) {
364 Quaternion q(0.0, 0.0, 0.0, 0.0);
365
366 EXPECT_THROW(q.normalize(), GeneralOpalException);
367}
368
369TEST_F(QuaternionTest, InverseZeroQuaternionThrows) {
370 Quaternion q(0.0, 0.0, 0.0, 0.0);
371
372 EXPECT_THROW(q.inverse(), GeneralOpalException);
373}
374
375TEST_F(QuaternionTest, RotateRequiresUnitQuaternion) {
376 Quaternion q(2.0, 0.0, 0.0, 0.0);
377 ippl::Vector<double, 3> vec(1.0, 0.0, 0.0);
378
379 EXPECT_THROW(q.rotate(vec), GeneralOpalException);
380}
381
382TEST_F(QuaternionTest, GetQuaternionRejectsZeroVector) {
383 ippl::Vector<double, 3> zero(0.0, 0.0, 0.0);
384 ippl::Vector<double, 3> ref(1.0, 0.0, 0.0);
385
386 EXPECT_THROW(getQuaternion(zero, ref), GeneralOpalException);
387}
Euclid3D Inverse(const Euclid3D &t)
Euclidean inverse.
Definition Euclid3D.h:199
Quaternion getQuaternion(ippl::Vector< double, 3 > u, ippl::Vector< double, 3 > ref)
Return a unit quaternion that rotates vec onto reference.
TEST_F(QuaternionTest, DefaultConstructor)
void SetUp() override
void TearDown() override
static void SetUpTestSuite()
static void TearDownTestSuite()
Quaternion storage and rotation algebra used by OPALX geometry code.
bool isPure() const
Return true if the scalar part is approximately zero.
Quaternion & normalize()
Normalize the quaternion in place.
double real() const
Return the scalar part .
Quaternion conjugate() const
Return the quaternion conjugate .
matrix3x3_t getRotationMatrix() const
Convert the quaternion to a 3x3 rotation matrix.
Quaternion inverse() const
Return the multiplicative inverse.
ippl::Vector< double, 3 > rotate(const ippl::Vector< double, 3 > &) const
Rotate a spatial vector by a unit quaternion.
bool isUnit() const
Return true if within a fixed tolerance.
bool isPureUnit() const
Return true if the quaternion is both pure and unit length.
double length() const
Return the Euclidean norm .
ippl::Vector< double, 3 > imag() const
Return the vector part .
double Norm() const
Return .