blob: 65421fa752487558a162feba4ead6364229e3346 [file] [log] [blame]
Keir Mierle8ebb0732012-04-30 23:09:08 -07001// Ceres Solver - A fast non-linear least squares minimizer
2// Copyright 2010, 2011, 2012 Google Inc. All rights reserved.
3// http://code.google.com/p/ceres-solver/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are met:
7//
8// * Redistributions of source code must retain the above copyright notice,
9// this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13// * Neither the name of Google Inc. nor the names of its contributors may be
14// used to endorse or promote products derived from this software without
15// specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27// POSSIBILITY OF SUCH DAMAGE.
28//
29// Author: sameeragarwal@google.com (Sameer Agarwal)
30
31#include <cmath>
32#include <limits>
33#include <string>
Keir Mierle8ebb0732012-04-30 23:09:08 -070034#include "ceres/internal/eigen.h"
35#include "ceres/internal/port.h"
36#include "ceres/jet.h"
37#include "ceres/rotation.h"
Sameer Agarwal0beab862012-08-13 15:12:01 -070038#include "ceres/stringprintf.h"
39#include "ceres/test_util.h"
40#include "glog/logging.h"
41#include "gmock/gmock.h"
42#include "gtest/gtest.h"
Keir Mierle8ebb0732012-04-30 23:09:08 -070043
44namespace ceres {
45namespace internal {
46
Keir Mierleefe7ac62012-06-24 22:25:28 -070047const double kPi = 3.14159265358979323846;
48const double kHalfSqrt2 = 0.707106781186547524401;
49
Keir Mierle8ebb0732012-04-30 23:09:08 -070050double RandDouble() {
51 double r = rand();
52 return r / RAND_MAX;
53}
54
55// A tolerance value for floating-point comparisons.
56static double const kTolerance = numeric_limits<double>::epsilon() * 10;
57
58// Looser tolerance used for for numerically unstable conversions.
Keir Mierleefe7ac62012-06-24 22:25:28 -070059static double const kLooseTolerance = 1e-9;
Keir Mierle8ebb0732012-04-30 23:09:08 -070060
61// Use as:
62// double quaternion[4];
63// EXPECT_THAT(quaternion, IsNormalizedQuaternion());
64MATCHER(IsNormalizedQuaternion, "") {
65 if (arg == NULL) {
66 *result_listener << "Null quaternion";
67 return false;
68 }
69
70 double norm2 = arg[0] * arg[0] + arg[1] * arg[1] +
71 arg[2] * arg[2] + arg[3] * arg[3];
72 if (fabs(norm2 - 1.0) > kTolerance) {
73 *result_listener << "squared norm is " << norm2;
74 return false;
75 }
76
77 return true;
78}
79
80// Use as:
81// double expected_quaternion[4];
82// double actual_quaternion[4];
83// EXPECT_THAT(actual_quaternion, IsNearQuaternion(expected_quaternion));
84MATCHER_P(IsNearQuaternion, expected, "") {
85 if (arg == NULL) {
86 *result_listener << "Null quaternion";
87 return false;
88 }
89
90 for (int i = 0; i < 4; i++) {
91 if (fabs(arg[i] - expected[i]) > kTolerance) {
92 *result_listener << "component " << i << " should be " << expected[i];
93 return false;
94 }
95 }
96
97 return true;
98}
99
100// Use as:
101// double expected_axis_angle[4];
102// double actual_axis_angle[4];
103// EXPECT_THAT(actual_axis_angle, IsNearAngleAxis(expected_axis_angle));
104MATCHER_P(IsNearAngleAxis, expected, "") {
105 if (arg == NULL) {
106 *result_listener << "Null axis/angle";
107 return false;
108 }
109
110 for (int i = 0; i < 3; i++) {
111 if (fabs(arg[i] - expected[i]) > kTolerance) {
112 *result_listener << "component " << i << " should be " << expected[i];
113 return false;
114 }
115 }
116
117 return true;
118}
119
120// Use as:
121// double matrix[9];
122// EXPECT_THAT(matrix, IsOrthonormal());
123MATCHER(IsOrthonormal, "") {
124 if (arg == NULL) {
125 *result_listener << "Null matrix";
126 return false;
127 }
128
129 for (int c1 = 0; c1 < 3; c1++) {
130 for (int c2 = 0; c2 < 3; c2++) {
131 double v = 0;
132 for (int i = 0; i < 3; i++) {
133 v += arg[i + 3 * c1] * arg[i + 3 * c2];
134 }
135 double expected = (c1 == c2) ? 1 : 0;
136 if (fabs(expected - v) > kTolerance) {
137 *result_listener << "Columns " << c1 << " and " << c2
138 << " should have dot product " << expected
139 << " but have " << v;
140 return false;
141 }
142 }
143 }
144
145 return true;
146}
147
148// Use as:
149// double matrix1[9];
150// double matrix2[9];
151// EXPECT_THAT(matrix1, IsNear3x3Matrix(matrix2));
152MATCHER_P(IsNear3x3Matrix, expected, "") {
153 if (arg == NULL) {
154 *result_listener << "Null matrix";
155 return false;
156 }
157
158 for (int i = 0; i < 9; i++) {
159 if (fabs(arg[i] - expected[i]) > kTolerance) {
160 *result_listener << "component " << i << " should be " << expected[i];
161 return false;
162 }
163 }
164
165 return true;
166}
167
168// Transforms a zero axis/angle to a quaternion.
169TEST(Rotation, ZeroAngleAxisToQuaternion) {
170 double axis_angle[3] = { 0, 0, 0 };
171 double quaternion[4];
172 double expected[4] = { 1, 0, 0, 0 };
173 AngleAxisToQuaternion(axis_angle, quaternion);
174 EXPECT_THAT(quaternion, IsNormalizedQuaternion());
175 EXPECT_THAT(quaternion, IsNearQuaternion(expected));
176}
177
178// Test that exact conversion works for small angles.
179TEST(Rotation, SmallAngleAxisToQuaternion) {
180 // Small, finite value to test.
181 double theta = 1.0e-2;
182 double axis_angle[3] = { theta, 0, 0 };
183 double quaternion[4];
184 double expected[4] = { cos(theta/2), sin(theta/2.0), 0, 0 };
185 AngleAxisToQuaternion(axis_angle, quaternion);
186 EXPECT_THAT(quaternion, IsNormalizedQuaternion());
187 EXPECT_THAT(quaternion, IsNearQuaternion(expected));
188}
189
190// Test that approximate conversion works for very small angles.
191TEST(Rotation, TinyAngleAxisToQuaternion) {
192 // Very small value that could potentially cause underflow.
193 double theta = pow(numeric_limits<double>::min(), 0.75);
194 double axis_angle[3] = { theta, 0, 0 };
195 double quaternion[4];
196 double expected[4] = { cos(theta/2), sin(theta/2.0), 0, 0 };
197 AngleAxisToQuaternion(axis_angle, quaternion);
198 EXPECT_THAT(quaternion, IsNormalizedQuaternion());
199 EXPECT_THAT(quaternion, IsNearQuaternion(expected));
200}
201
202// Transforms a rotation by pi/2 around X to a quaternion.
203TEST(Rotation, XRotationToQuaternion) {
Keir Mierleefe7ac62012-06-24 22:25:28 -0700204 double axis_angle[3] = { kPi / 2, 0, 0 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700205 double quaternion[4];
Keir Mierleefe7ac62012-06-24 22:25:28 -0700206 double expected[4] = { kHalfSqrt2, kHalfSqrt2, 0, 0 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700207 AngleAxisToQuaternion(axis_angle, quaternion);
208 EXPECT_THAT(quaternion, IsNormalizedQuaternion());
209 EXPECT_THAT(quaternion, IsNearQuaternion(expected));
210}
211
212// Transforms a unit quaternion to an axis angle.
213TEST(Rotation, UnitQuaternionToAngleAxis) {
214 double quaternion[4] = { 1, 0, 0, 0 };
215 double axis_angle[3];
216 double expected[3] = { 0, 0, 0 };
217 QuaternionToAngleAxis(quaternion, axis_angle);
218 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
219}
220
221// Transforms a quaternion that rotates by pi about the Y axis to an axis angle.
222TEST(Rotation, YRotationQuaternionToAngleAxis) {
223 double quaternion[4] = { 0, 0, 1, 0 };
224 double axis_angle[3];
Keir Mierleefe7ac62012-06-24 22:25:28 -0700225 double expected[3] = { 0, kPi, 0 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700226 QuaternionToAngleAxis(quaternion, axis_angle);
227 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
228}
229
230// Transforms a quaternion that rotates by pi/3 about the Z axis to an axis
231// angle.
232TEST(Rotation, ZRotationQuaternionToAngleAxis) {
233 double quaternion[4] = { sqrt(3) / 2, 0, 0, 0.5 };
234 double axis_angle[3];
Keir Mierleefe7ac62012-06-24 22:25:28 -0700235 double expected[3] = { 0, 0, kPi / 3 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700236 QuaternionToAngleAxis(quaternion, axis_angle);
237 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
238}
239
240// Test that exact conversion works for small angles.
241TEST(Rotation, SmallQuaternionToAngleAxis) {
242 // Small, finite value to test.
243 double theta = 1.0e-2;
244 double quaternion[4] = { cos(theta/2), sin(theta/2.0), 0, 0 };
245 double axis_angle[3];
246 double expected[3] = { theta, 0, 0 };
247 QuaternionToAngleAxis(quaternion, axis_angle);
248 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
249}
250
251// Test that approximate conversion works for very small angles.
252TEST(Rotation, TinyQuaternionToAngleAxis) {
253 // Very small value that could potentially cause underflow.
254 double theta = pow(numeric_limits<double>::min(), 0.75);
255 double quaternion[4] = { cos(theta/2), sin(theta/2.0), 0, 0 };
256 double axis_angle[3];
257 double expected[3] = { theta, 0, 0 };
258 QuaternionToAngleAxis(quaternion, axis_angle);
259 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
260}
261
262static const int kNumTrials = 10000;
263
264// Takes a bunch of random axis/angle values, converts them to quaternions,
265// and back again.
266TEST(Rotation, AngleAxisToQuaterionAndBack) {
267 srand(5);
268 for (int i = 0; i < kNumTrials; i++) {
269 double axis_angle[3];
270 // Make an axis by choosing three random numbers in [-1, 1) and
271 // normalizing.
272 double norm = 0;
273 for (int i = 0; i < 3; i++) {
274 axis_angle[i] = RandDouble() * 2 - 1;
275 norm += axis_angle[i] * axis_angle[i];
276 }
277 norm = sqrt(norm);
278
279 // Angle in [-pi, pi).
Keir Mierleefe7ac62012-06-24 22:25:28 -0700280 double theta = kPi * 2 * RandDouble() - kPi;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700281 for (int i = 0; i < 3; i++) {
282 axis_angle[i] = axis_angle[i] * theta / norm;
283 }
284
285 double quaternion[4];
286 double round_trip[3];
287 // We use ASSERTs here because if there's one failure, there are
288 // probably many and spewing a million failures doesn't make anyone's
289 // day.
290 AngleAxisToQuaternion(axis_angle, quaternion);
291 ASSERT_THAT(quaternion, IsNormalizedQuaternion());
292 QuaternionToAngleAxis(quaternion, round_trip);
293 ASSERT_THAT(round_trip, IsNearAngleAxis(axis_angle));
294 }
295}
296
297// Takes a bunch of random quaternions, converts them to axis/angle,
298// and back again.
299TEST(Rotation, QuaterionToAngleAxisAndBack) {
300 srand(5);
301 for (int i = 0; i < kNumTrials; i++) {
302 double quaternion[4];
303 // Choose four random numbers in [-1, 1) and normalize.
304 double norm = 0;
305 for (int i = 0; i < 4; i++) {
306 quaternion[i] = RandDouble() * 2 - 1;
307 norm += quaternion[i] * quaternion[i];
308 }
309 norm = sqrt(norm);
310
311 for (int i = 0; i < 4; i++) {
312 quaternion[i] = quaternion[i] / norm;
313 }
314
315 double axis_angle[3];
316 double round_trip[4];
317 QuaternionToAngleAxis(quaternion, axis_angle);
318 AngleAxisToQuaternion(axis_angle, round_trip);
319 ASSERT_THAT(round_trip, IsNormalizedQuaternion());
320 ASSERT_THAT(round_trip, IsNearQuaternion(quaternion));
321 }
322}
323
324// Transforms a zero axis/angle to a rotation matrix.
325TEST(Rotation, ZeroAngleAxisToRotationMatrix) {
326 double axis_angle[3] = { 0, 0, 0 };
327 double matrix[9];
328 double expected[9] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
329 AngleAxisToRotationMatrix(axis_angle, matrix);
330 EXPECT_THAT(matrix, IsOrthonormal());
331 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
332}
333
334TEST(Rotation, NearZeroAngleAxisToRotationMatrix) {
335 double axis_angle[3] = { 1e-24, 2e-24, 3e-24 };
336 double matrix[9];
337 double expected[9] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
338 AngleAxisToRotationMatrix(axis_angle, matrix);
339 EXPECT_THAT(matrix, IsOrthonormal());
340 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
341}
342
343// Transforms a rotation by pi/2 around X to a rotation matrix and back.
344TEST(Rotation, XRotationToRotationMatrix) {
Keir Mierleefe7ac62012-06-24 22:25:28 -0700345 double axis_angle[3] = { kPi / 2, 0, 0 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700346 double matrix[9];
347 // The rotation matrices are stored column-major.
348 double expected[9] = { 1, 0, 0, 0, 0, 1, 0, -1, 0 };
349 AngleAxisToRotationMatrix(axis_angle, matrix);
350 EXPECT_THAT(matrix, IsOrthonormal());
351 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
352 double round_trip[3];
353 RotationMatrixToAngleAxis(matrix, round_trip);
354 EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle));
355}
356
357// Transforms an axis angle that rotates by pi about the Y axis to a
358// rotation matrix and back.
359TEST(Rotation, YRotationToRotationMatrix) {
Keir Mierleefe7ac62012-06-24 22:25:28 -0700360 double axis_angle[3] = { 0, kPi, 0 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700361 double matrix[9];
362 double expected[9] = { -1, 0, 0, 0, 1, 0, 0, 0, -1 };
363 AngleAxisToRotationMatrix(axis_angle, matrix);
364 EXPECT_THAT(matrix, IsOrthonormal());
365 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
366
367 double round_trip[3];
368 RotationMatrixToAngleAxis(matrix, round_trip);
369 EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle));
370}
371
372TEST(Rotation, NearPiAngleAxisRoundTrip) {
373 double in_axis_angle[3];
374 double matrix[9];
375 double out_axis_angle[3];
376
377 srand(5);
378 for (int i = 0; i < kNumTrials; i++) {
379 // Make an axis by choosing three random numbers in [-1, 1) and
380 // normalizing.
381 double norm = 0;
382 for (int i = 0; i < 3; i++) {
383 in_axis_angle[i] = RandDouble() * 2 - 1;
384 norm += in_axis_angle[i] * in_axis_angle[i];
385 }
386 norm = sqrt(norm);
387
388 // Angle in [pi - kMaxSmallAngle, pi).
389 const double kMaxSmallAngle = 1e-2;
Keir Mierleefe7ac62012-06-24 22:25:28 -0700390 double theta = kPi - kMaxSmallAngle * RandDouble();
Keir Mierle8ebb0732012-04-30 23:09:08 -0700391
392 for (int i = 0; i < 3; i++) {
393 in_axis_angle[i] *= (theta / norm);
394 }
395 AngleAxisToRotationMatrix(in_axis_angle, matrix);
396 RotationMatrixToAngleAxis(matrix, out_axis_angle);
397
398 for (int i = 0; i < 3; ++i) {
399 EXPECT_NEAR(out_axis_angle[i], in_axis_angle[i], kLooseTolerance);
400 }
401 }
402}
403
404TEST(Rotation, AtPiAngleAxisRoundTrip) {
Keir Mierleefe7ac62012-06-24 22:25:28 -0700405 // A rotation of kPi about the X axis;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700406 static const double kMatrix[3][3] = {
407 {1.0, 0.0, 0.0},
408 {0.0, -1.0, 0.0},
409 {0.0, 0.0, -1.0}
410 };
411
412 double in_matrix[9];
413 // Fill it from kMatrix in col-major order.
414 for (int j = 0, k = 0; j < 3; ++j) {
415 for (int i = 0; i < 3; ++i, ++k) {
416 in_matrix[k] = kMatrix[i][j];
417 }
418 }
419
Keir Mierleefe7ac62012-06-24 22:25:28 -0700420 const double expected_axis_angle[3] = { kPi, 0, 0 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700421
422 double out_matrix[9];
423 double axis_angle[3];
424 RotationMatrixToAngleAxis(in_matrix, axis_angle);
425 AngleAxisToRotationMatrix(axis_angle, out_matrix);
426
427 LOG(INFO) << "AngleAxis = " << axis_angle[0] << " " << axis_angle[1]
428 << " " << axis_angle[2];
Keir Mierleefe7ac62012-06-24 22:25:28 -0700429 LOG(INFO) << "Expected AngleAxis = " << kPi << " 0 0";
Keir Mierle8ebb0732012-04-30 23:09:08 -0700430 double out_rowmajor[3][3];
431 for (int j = 0, k = 0; j < 3; ++j) {
432 for (int i = 0; i < 3; ++i, ++k) {
433 out_rowmajor[i][j] = out_matrix[k];
434 }
435 }
436 LOG(INFO) << "Rotation:";
437 LOG(INFO) << "EXPECTED | ACTUAL";
438 for (int i = 0; i < 3; ++i) {
439 string line;
440 for (int j = 0; j < 3; ++j) {
441 StringAppendF(&line, "%g ", kMatrix[i][j]);
442 }
443 line += " | ";
444 for (int j = 0; j < 3; ++j) {
445 StringAppendF(&line, "%g ", out_rowmajor[i][j]);
446 }
447 LOG(INFO) << line;
448 }
449
450 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected_axis_angle));
451 EXPECT_THAT(out_matrix, IsNear3x3Matrix(in_matrix));
452}
453
454// Transforms an axis angle that rotates by pi/3 about the Z axis to a
455// rotation matrix.
456TEST(Rotation, ZRotationToRotationMatrix) {
Keir Mierleefe7ac62012-06-24 22:25:28 -0700457 double axis_angle[3] = { 0, 0, kPi / 3 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700458 double matrix[9];
459 // This is laid-out row-major on the screen but is actually stored
460 // column-major.
461 double expected[9] = { 0.5, sqrt(3) / 2, 0, // Column 1
462 -sqrt(3) / 2, 0.5, 0, // Column 2
463 0, 0, 1 }; // Column 3
464 AngleAxisToRotationMatrix(axis_angle, matrix);
465 EXPECT_THAT(matrix, IsOrthonormal());
466 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
467 double round_trip[3];
468 RotationMatrixToAngleAxis(matrix, round_trip);
469 EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle));
470}
471
472// Takes a bunch of random axis/angle values, converts them to rotation
473// matrices, and back again.
474TEST(Rotation, AngleAxisToRotationMatrixAndBack) {
475 srand(5);
476 for (int i = 0; i < kNumTrials; i++) {
477 double axis_angle[3];
478 // Make an axis by choosing three random numbers in [-1, 1) and
479 // normalizing.
480 double norm = 0;
481 for (int i = 0; i < 3; i++) {
482 axis_angle[i] = RandDouble() * 2 - 1;
483 norm += axis_angle[i] * axis_angle[i];
484 }
485 norm = sqrt(norm);
486
487 // Angle in [-pi, pi).
Keir Mierleefe7ac62012-06-24 22:25:28 -0700488 double theta = kPi * 2 * RandDouble() - kPi;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700489 for (int i = 0; i < 3; i++) {
490 axis_angle[i] = axis_angle[i] * theta / norm;
491 }
492
493 double matrix[9];
494 double round_trip[3];
495 AngleAxisToRotationMatrix(axis_angle, matrix);
496 ASSERT_THAT(matrix, IsOrthonormal());
497 RotationMatrixToAngleAxis(matrix, round_trip);
498
499 for (int i = 0; i < 3; ++i) {
500 EXPECT_NEAR(round_trip[i], axis_angle[i], kLooseTolerance);
501 }
502 }
503}
504
505// Transposes a 3x3 matrix.
506static void Transpose3x3(double m[9]) {
507 std::swap(m[1], m[3]);
508 std::swap(m[2], m[6]);
509 std::swap(m[5], m[7]);
510}
511
512// Convert Euler angles from radians to degrees.
513static void ToDegrees(double ea[3]) {
514 for (int i = 0; i < 3; ++i)
Keir Mierleefe7ac62012-06-24 22:25:28 -0700515 ea[i] *= 180.0 / kPi;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700516}
517
518// Compare the 3x3 rotation matrices produced by the axis-angle
519// rotation 'aa' and the Euler angle rotation 'ea' (in radians).
520static void CompareEulerToAngleAxis(double aa[3], double ea[3]) {
521 double aa_matrix[9];
522 AngleAxisToRotationMatrix(aa, aa_matrix);
523 Transpose3x3(aa_matrix); // Column to row major order.
524
525 double ea_matrix[9];
526 ToDegrees(ea); // Radians to degrees.
527 const int kRowStride = 3;
528 EulerAnglesToRotationMatrix(ea, kRowStride, ea_matrix);
529
530 EXPECT_THAT(aa_matrix, IsOrthonormal());
531 EXPECT_THAT(ea_matrix, IsOrthonormal());
532 EXPECT_THAT(ea_matrix, IsNear3x3Matrix(aa_matrix));
533}
534
535// Test with rotation axis along the x/y/z axes.
536// Also test zero rotation.
537TEST(EulerAnglesToRotationMatrix, OnAxis) {
538 int n_tests = 0;
539 for (double x = -1.0; x <= 1.0; x += 1.0) {
540 for (double y = -1.0; y <= 1.0; y += 1.0) {
541 for (double z = -1.0; z <= 1.0; z += 1.0) {
542 if ((x != 0) + (y != 0) + (z != 0) > 1)
543 continue;
544 double axis_angle[3] = {x, y, z};
545 double euler_angles[3] = {x, y, z};
546 CompareEulerToAngleAxis(axis_angle, euler_angles);
547 ++n_tests;
548 }
549 }
550 }
551 CHECK_EQ(7, n_tests);
552}
553
554// Test that a random rotation produces an orthonormal rotation
555// matrix.
556TEST(EulerAnglesToRotationMatrix, IsOrthonormal) {
557 srand(5);
558 for (int trial = 0; trial < kNumTrials; ++trial) {
559 double ea[3];
560 for (int i = 0; i < 3; ++i)
561 ea[i] = 360.0 * (RandDouble() * 2.0 - 1.0);
562 double ea_matrix[9];
563 ToDegrees(ea); // Radians to degrees.
564 EulerAnglesToRotationMatrix(ea, 3, ea_matrix);
565 EXPECT_THAT(ea_matrix, IsOrthonormal());
566 }
567}
568
569// Tests using Jets for specific behavior involving auto differentiation
570// near singularity points.
571
572typedef Jet<double, 3> J3;
573typedef Jet<double, 4> J4;
574
575J3 MakeJ3(double a, double v0, double v1, double v2) {
576 J3 j;
577 j.a = a;
578 j.v[0] = v0;
579 j.v[1] = v1;
580 j.v[2] = v2;
581 return j;
582}
583
584J4 MakeJ4(double a, double v0, double v1, double v2, double v3) {
585 J4 j;
586 j.a = a;
587 j.v[0] = v0;
588 j.v[1] = v1;
589 j.v[2] = v2;
590 j.v[3] = v3;
591 return j;
592}
593
594
595bool IsClose(double x, double y) {
Keir Mierle58ede272012-06-24 17:23:57 -0700596 EXPECT_FALSE(IsNaN(x));
597 EXPECT_FALSE(IsNaN(y));
Keir Mierle8ebb0732012-04-30 23:09:08 -0700598 double absdiff = fabs(x - y);
599 if (x == 0 || y == 0) {
600 return absdiff <= kTolerance;
601 }
602 double reldiff = absdiff / max(fabs(x), fabs(y));
603 return reldiff <= kTolerance;
604}
605
606template <int N>
607bool IsClose(const Jet<double, N> &x, const Jet<double, N> &y) {
608 if (IsClose(x.a, y.a)) {
609 for (int i = 0; i < N; i++) {
610 if (!IsClose(x.v[i], y.v[i])) {
611 return false;
612 }
613 }
614 }
615 return true;
616}
617
618template <int M, int N>
619void ExpectJetArraysClose(const Jet<double, N> *x, const Jet<double, N> *y) {
620 for (int i = 0; i < M; i++) {
621 if (!IsClose(x[i], y[i])) {
622 LOG(ERROR) << "Jet " << i << "/" << M << " not equal";
623 LOG(ERROR) << "x[" << i << "]: " << x[i];
624 LOG(ERROR) << "y[" << i << "]: " << y[i];
625 Jet<double, N> d, zero;
626 d.a = y[i].a - x[i].a;
627 for (int j = 0; j < N; j++) {
628 d.v[j] = y[i].v[j] - x[i].v[j];
629 }
630 LOG(ERROR) << "diff: " << d;
631 EXPECT_TRUE(IsClose(x[i], y[i]));
632 }
633 }
634}
635
636// Log-10 of a value well below machine precision.
637static const int kSmallTinyCutoff =
638 static_cast<int>(2 * log(numeric_limits<double>::epsilon())/log(10.0));
639
640// Log-10 of a value just below values representable by double.
641static const int kTinyZeroLimit =
642 static_cast<int>(1 + log(numeric_limits<double>::min())/log(10.0));
643
644// Test that exact conversion works for small angles when jets are used.
645TEST(Rotation, SmallAngleAxisToQuaternionForJets) {
646 // Examine small x rotations that are still large enough
647 // to be well within the range represented by doubles.
648 for (int i = -2; i >= kSmallTinyCutoff; i--) {
649 double theta = pow(10.0, i);
650 J3 axis_angle[3] = { J3(theta, 0), J3(0, 1), J3(0, 2) };
651 J3 quaternion[4];
652 J3 expected[4] = {
653 MakeJ3(cos(theta/2), -sin(theta/2)/2, 0, 0),
654 MakeJ3(sin(theta/2), cos(theta/2)/2, 0, 0),
655 MakeJ3(0, 0, sin(theta/2)/theta, 0),
656 MakeJ3(0, 0, 0, sin(theta/2)/theta),
657 };
658 AngleAxisToQuaternion(axis_angle, quaternion);
659 ExpectJetArraysClose<4, 3>(quaternion, expected);
660 }
661}
662
663
664// Test that conversion works for very small angles when jets are used.
665TEST(Rotation, TinyAngleAxisToQuaternionForJets) {
666 // Examine tiny x rotations that extend all the way to where
667 // underflow occurs.
668 for (int i = kSmallTinyCutoff; i >= kTinyZeroLimit; i--) {
669 double theta = pow(10.0, i);
670 J3 axis_angle[3] = { J3(theta, 0), J3(0, 1), J3(0, 2) };
671 J3 quaternion[4];
672 // To avoid loss of precision in the test itself,
673 // a finite expansion is used here, which will
674 // be exact up to machine precision for the test values used.
675 J3 expected[4] = {
676 MakeJ3(1.0, 0, 0, 0),
677 MakeJ3(0, 0.5, 0, 0),
678 MakeJ3(0, 0, 0.5, 0),
679 MakeJ3(0, 0, 0, 0.5),
680 };
681 AngleAxisToQuaternion(axis_angle, quaternion);
682 ExpectJetArraysClose<4, 3>(quaternion, expected);
683 }
684}
685
686// Test that derivatives are correct for zero rotation.
687TEST(Rotation, ZeroAngleAxisToQuaternionForJets) {
688 J3 axis_angle[3] = { J3(0, 0), J3(0, 1), J3(0, 2) };
689 J3 quaternion[4];
690 J3 expected[4] = {
691 MakeJ3(1.0, 0, 0, 0),
692 MakeJ3(0, 0.5, 0, 0),
693 MakeJ3(0, 0, 0.5, 0),
694 MakeJ3(0, 0, 0, 0.5),
695 };
696 AngleAxisToQuaternion(axis_angle, quaternion);
697 ExpectJetArraysClose<4, 3>(quaternion, expected);
698}
699
700// Test that exact conversion works for small angles.
701TEST(Rotation, SmallQuaternionToAngleAxisForJets) {
702 // Examine small x rotations that are still large enough
703 // to be well within the range represented by doubles.
704 for (int i = -2; i >= kSmallTinyCutoff; i--) {
705 double theta = pow(10.0, i);
706 double s = sin(theta);
707 double c = cos(theta);
708 J4 quaternion[4] = { J4(c, 0), J4(s, 1), J4(0, 2), J4(0, 3) };
709 J4 axis_angle[3];
710 J4 expected[3] = {
711 MakeJ4(s, -2*theta, 2*theta*c, 0, 0),
712 MakeJ4(0, 0, 0, 2*theta/s, 0),
713 MakeJ4(0, 0, 0, 0, 2*theta/s),
714 };
715 QuaternionToAngleAxis(quaternion, axis_angle);
716 ExpectJetArraysClose<3, 4>(axis_angle, expected);
717 }
718}
719
720// Test that conversion works for very small angles.
721TEST(Rotation, TinyQuaternionToAngleAxisForJets) {
722 // Examine tiny x rotations that extend all the way to where
723 // underflow occurs.
724 for (int i = kSmallTinyCutoff; i >= kTinyZeroLimit; i--) {
725 double theta = pow(10.0, i);
726 double s = sin(theta);
727 double c = cos(theta);
728 J4 quaternion[4] = { J4(c, 0), J4(s, 1), J4(0, 2), J4(0, 3) };
729 J4 axis_angle[3];
730 // To avoid loss of precision in the test itself,
731 // a finite expansion is used here, which will
732 // be exact up to machine precision for the test values used.
733 J4 expected[3] = {
734 MakeJ4(theta, -2*theta, 2.0, 0, 0),
735 MakeJ4(0, 0, 0, 2.0, 0),
736 MakeJ4(0, 0, 0, 0, 2.0),
737 };
738 QuaternionToAngleAxis(quaternion, axis_angle);
739 ExpectJetArraysClose<3, 4>(axis_angle, expected);
740 }
741}
742
743// Test that conversion works for no rotation.
744TEST(Rotation, ZeroQuaternionToAngleAxisForJets) {
745 J4 quaternion[4] = { J4(1, 0), J4(0, 1), J4(0, 2), J4(0, 3) };
746 J4 axis_angle[3];
747 J4 expected[3] = {
748 MakeJ4(0, 0, 2.0, 0, 0),
749 MakeJ4(0, 0, 0, 2.0, 0),
750 MakeJ4(0, 0, 0, 0, 2.0),
751 };
752 QuaternionToAngleAxis(quaternion, axis_angle);
753 ExpectJetArraysClose<3, 4>(axis_angle, expected);
754}
755
756TEST(Quaternion, RotatePointGivesSameAnswerAsRotationByMatrixCanned) {
757 // Canned data generated in octave.
758 double const q[4] = {
759 +0.1956830471754074,
760 -0.0150618562474847,
761 +0.7634572982788086,
762 -0.3019454777240753,
763 };
764 double const Q[3][3] = { // Scaled rotation matrix.
765 { -0.6355194033477252, 0.0951730541682254, 0.3078870197911186 },
766 { -0.1411693904792992, 0.5297609702153905, -0.4551502574482019 },
767 { -0.2896955822708862, -0.4669396571547050, -0.4536309793389248 },
768 };
769 double const R[3][3] = { // With unit rows and columns.
770 { -0.8918859164053080, 0.1335655625725649, 0.4320876677394745 },
771 { -0.1981166751680096, 0.7434648665444399, -0.6387564287225856 },
772 { -0.4065578619806013, -0.6553016349046693, -0.6366242786393164 },
773 };
774
775 // Compute R from q and compare to known answer.
776 double Rq[3][3];
777 QuaternionToScaledRotation<double>(q, Rq[0]);
778 ExpectArraysClose(9, Q[0], Rq[0], kTolerance);
779
780 // Now do the same but compute R with normalization.
781 QuaternionToRotation<double>(q, Rq[0]);
782 ExpectArraysClose(9, R[0], Rq[0], kTolerance);
783}
784
785
786TEST(Quaternion, RotatePointGivesSameAnswerAsRotationByMatrix) {
787 // Rotation defined by a unit quaternion.
788 double const q[4] = {
789 0.2318160216097109,
790 -0.0178430356832060,
791 0.9044300776717159,
792 -0.3576998641394597,
793 };
794 double const p[3] = {
795 +0.11,
796 -13.15,
797 1.17,
798 };
799
800 double R[3 * 3];
801 QuaternionToRotation(q, R);
802
803 double result1[3];
804 UnitQuaternionRotatePoint(q, p, result1);
805
806 double result2[3];
807 VectorRef(result2, 3) = ConstMatrixRef(R, 3, 3)* ConstVectorRef(p, 3);
808 ExpectArraysClose(3, result1, result2, kTolerance);
809}
810
811
812// Verify that (a * b) * c == a * (b * c).
813TEST(Quaternion, MultiplicationIsAssociative) {
814 double a[4];
815 double b[4];
816 double c[4];
817 for (int i = 0; i < 4; ++i) {
818 a[i] = 2 * RandDouble() - 1;
819 b[i] = 2 * RandDouble() - 1;
820 c[i] = 2 * RandDouble() - 1;
821 }
822
823 double ab[4];
824 double ab_c[4];
825 QuaternionProduct(a, b, ab);
826 QuaternionProduct(ab, c, ab_c);
827
828 double bc[4];
829 double a_bc[4];
830 QuaternionProduct(b, c, bc);
831 QuaternionProduct(a, bc, a_bc);
832
833 ASSERT_NEAR(ab_c[0], a_bc[0], kTolerance);
834 ASSERT_NEAR(ab_c[1], a_bc[1], kTolerance);
835 ASSERT_NEAR(ab_c[2], a_bc[2], kTolerance);
836 ASSERT_NEAR(ab_c[3], a_bc[3], kTolerance);
837}
838
839
840TEST(AngleAxis, RotatePointGivesSameAnswerAsRotationMatrix) {
841 double angle_axis[3];
842 double R[9];
843 double p[3];
844 double angle_axis_rotated_p[3];
845 double rotation_matrix_rotated_p[3];
846
847 for (int i = 0; i < 10000; ++i) {
Keir Mierleefe7ac62012-06-24 22:25:28 -0700848 double theta = (2.0 * i * 0.0011 - 1.0) * kPi;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700849 for (int j = 0; j < 50; ++j) {
850 double norm2 = 0.0;
851 for (int k = 0; k < 3; ++k) {
852 angle_axis[k] = 2.0 * RandDouble() - 1.0;
853 p[k] = 2.0 * RandDouble() - 1.0;
854 norm2 = angle_axis[k] * angle_axis[k];
855 }
856
857 const double inv_norm = theta / sqrt(norm2);
858 for (int k = 0; k < 3; ++k) {
859 angle_axis[k] *= inv_norm;
860 }
861
862 AngleAxisToRotationMatrix(angle_axis, R);
863 rotation_matrix_rotated_p[0] = R[0] * p[0] + R[3] * p[1] + R[6] * p[2];
864 rotation_matrix_rotated_p[1] = R[1] * p[0] + R[4] * p[1] + R[7] * p[2];
865 rotation_matrix_rotated_p[2] = R[2] * p[0] + R[5] * p[1] + R[8] * p[2];
866
867 AngleAxisRotatePoint(angle_axis, p, angle_axis_rotated_p);
868 for (int k = 0; k < 3; ++k) {
869 EXPECT_NEAR(rotation_matrix_rotated_p[k],
870 angle_axis_rotated_p[k],
871 kTolerance) << "p: " << p[0]
872 << " " << p[1]
873 << " " << p[2]
874 << " angle_axis: " << angle_axis[0]
875 << " " << angle_axis[1]
876 << " " << angle_axis[2];
877 }
878 }
879 }
880}
881
882TEST(AngleAxis, NearZeroRotatePointGivesSameAnswerAsRotationMatrix) {
883 double angle_axis[3];
884 double R[9];
885 double p[3];
886 double angle_axis_rotated_p[3];
887 double rotation_matrix_rotated_p[3];
888
889 for (int i = 0; i < 10000; ++i) {
890 double norm2 = 0.0;
891 for (int k = 0; k < 3; ++k) {
892 angle_axis[k] = 2.0 * RandDouble() - 1.0;
893 p[k] = 2.0 * RandDouble() - 1.0;
894 norm2 = angle_axis[k] * angle_axis[k];
895 }
896
897 double theta = (2.0 * i * 0.0001 - 1.0) * 1e-16;
898 const double inv_norm = theta / sqrt(norm2);
899 for (int k = 0; k < 3; ++k) {
900 angle_axis[k] *= inv_norm;
901 }
902
903 AngleAxisToRotationMatrix(angle_axis, R);
904 rotation_matrix_rotated_p[0] = R[0] * p[0] + R[3] * p[1] + R[6] * p[2];
905 rotation_matrix_rotated_p[1] = R[1] * p[0] + R[4] * p[1] + R[7] * p[2];
906 rotation_matrix_rotated_p[2] = R[2] * p[0] + R[5] * p[1] + R[8] * p[2];
907
908 AngleAxisRotatePoint(angle_axis, p, angle_axis_rotated_p);
909 for (int k = 0; k < 3; ++k) {
910 EXPECT_NEAR(rotation_matrix_rotated_p[k],
911 angle_axis_rotated_p[k],
912 kTolerance) << "p: " << p[0]
913 << " " << p[1]
914 << " " << p[2]
915 << " angle_axis: " << angle_axis[0]
916 << " " << angle_axis[1]
917 << " " << angle_axis[2];
918 }
919 }
920}
921
922
923} // namespace internal
924} // namespace ceres