普通文本  |  971行  |  29.76 KB

// Ceres Solver - A fast non-linear least squares minimizer
// Copyright 2010, 2011, 2012 Google Inc. All rights reserved.
// http://code.google.com/p/ceres-solver/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// * Neither the name of Google Inc. nor the names of its contributors may be
//   used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Author: sameeragarwal@google.com (Sameer Agarwal)

#include <cmath>
#include <limits>
#include <string>
#include "ceres/internal/eigen.h"
#include "ceres/internal/port.h"
#include "ceres/jet.h"
#include "ceres/rotation.h"
#include "ceres/stringprintf.h"
#include "ceres/test_util.h"
#include "glog/logging.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace ceres {
namespace internal {

const double kPi = 3.14159265358979323846;
const double kHalfSqrt2 = 0.707106781186547524401;

double RandDouble() {
  double r = rand();
  return r / RAND_MAX;
}

// A tolerance value for floating-point comparisons.
static double const kTolerance = numeric_limits<double>::epsilon() * 10;

// Looser tolerance used for for numerically unstable conversions.
static double const kLooseTolerance = 1e-9;

// Use as:
// double quaternion[4];
// EXPECT_THAT(quaternion, IsNormalizedQuaternion());
MATCHER(IsNormalizedQuaternion, "") {
  if (arg == NULL) {
    *result_listener << "Null quaternion";
    return false;
  }

  double norm2 = arg[0] * arg[0] + arg[1] * arg[1] +
      arg[2] * arg[2] + arg[3] * arg[3];
  if (fabs(norm2 - 1.0) > kTolerance) {
    *result_listener << "squared norm is " << norm2;
    return false;
  }

  return true;
}

// Use as:
// double expected_quaternion[4];
// double actual_quaternion[4];
// EXPECT_THAT(actual_quaternion, IsNearQuaternion(expected_quaternion));
MATCHER_P(IsNearQuaternion, expected, "") {
  if (arg == NULL) {
    *result_listener << "Null quaternion";
    return false;
  }

  // Quaternions are equivalent upto a sign change. So we will compare
  // both signs before declaring failure.
  bool near = true;
  for (int i = 0; i < 4; i++) {
    if (fabs(arg[i] - expected[i]) > kTolerance) {
      near = false;
      break;
    }
  }

  if (near) {
    return true;
  }

  near = true;
  for (int i = 0; i < 4; i++) {
    if (fabs(arg[i] + expected[i]) > kTolerance) {
      near = false;
      break;
    }
  }

  if (near) {
    return true;
  }

  *result_listener << "expected : "
                   << expected[0] << " "
                   << expected[1] << " "
                   << expected[2] << " "
                   << expected[3] << " "
                   << "actual : "
                   << arg[0] << " "
                   << arg[1] << " "
                   << arg[2] << " "
                   << arg[3];
  return false;
}

// Use as:
// double expected_axis_angle[4];
// double actual_axis_angle[4];
// EXPECT_THAT(actual_axis_angle, IsNearAngleAxis(expected_axis_angle));
MATCHER_P(IsNearAngleAxis, expected, "") {
  if (arg == NULL) {
    *result_listener << "Null axis/angle";
    return false;
  }

  for (int i = 0; i < 3; i++) {
    if (fabs(arg[i] - expected[i]) > kTolerance) {
      *result_listener << "component " << i << " should be " << expected[i];
      return false;
    }
  }

  return true;
}

// Use as:
// double matrix[9];
// EXPECT_THAT(matrix, IsOrthonormal());
MATCHER(IsOrthonormal, "") {
  if (arg == NULL) {
    *result_listener << "Null matrix";
    return false;
  }

  for (int c1 = 0; c1 < 3; c1++) {
    for (int c2 = 0; c2 < 3; c2++) {
      double v = 0;
      for (int i = 0; i < 3; i++) {
        v += arg[i + 3 * c1] * arg[i + 3 * c2];
      }
      double expected = (c1 == c2) ? 1 : 0;
      if (fabs(expected - v) > kTolerance) {
        *result_listener << "Columns " << c1 << " and " << c2
                         << " should have dot product " << expected
                         << " but have " << v;
        return false;
      }
    }
  }

  return true;
}

// Use as:
// double matrix1[9];
// double matrix2[9];
// EXPECT_THAT(matrix1, IsNear3x3Matrix(matrix2));
MATCHER_P(IsNear3x3Matrix, expected, "") {
  if (arg == NULL) {
    *result_listener << "Null matrix";
    return false;
  }

  for (int i = 0; i < 9; i++) {
    if (fabs(arg[i] - expected[i]) > kTolerance) {
      *result_listener << "component " << i << " should be " << expected[i];
      return false;
    }
  }

  return true;
}

// Transforms a zero axis/angle to a quaternion.
TEST(Rotation, ZeroAngleAxisToQuaternion) {
  double axis_angle[3] = { 0, 0, 0 };
  double quaternion[4];
  double expected[4] = { 1, 0, 0, 0 };
  AngleAxisToQuaternion(axis_angle, quaternion);
  EXPECT_THAT(quaternion, IsNormalizedQuaternion());
  EXPECT_THAT(quaternion, IsNearQuaternion(expected));
}

// Test that exact conversion works for small angles.
TEST(Rotation, SmallAngleAxisToQuaternion) {
  // Small, finite value to test.
  double theta = 1.0e-2;
  double axis_angle[3] = { theta, 0, 0 };
  double quaternion[4];
  double expected[4] = { cos(theta/2), sin(theta/2.0), 0, 0 };
  AngleAxisToQuaternion(axis_angle, quaternion);
  EXPECT_THAT(quaternion, IsNormalizedQuaternion());
  EXPECT_THAT(quaternion, IsNearQuaternion(expected));
}

// Test that approximate conversion works for very small angles.
TEST(Rotation, TinyAngleAxisToQuaternion) {
  // Very small value that could potentially cause underflow.
  double theta = pow(numeric_limits<double>::min(), 0.75);
  double axis_angle[3] = { theta, 0, 0 };
  double quaternion[4];
  double expected[4] = { cos(theta/2), sin(theta/2.0), 0, 0 };
  AngleAxisToQuaternion(axis_angle, quaternion);
  EXPECT_THAT(quaternion, IsNormalizedQuaternion());
  EXPECT_THAT(quaternion, IsNearQuaternion(expected));
}

// Transforms a rotation by pi/2 around X to a quaternion.
TEST(Rotation, XRotationToQuaternion) {
  double axis_angle[3] = { kPi / 2, 0, 0 };
  double quaternion[4];
  double expected[4] = { kHalfSqrt2, kHalfSqrt2, 0, 0 };
  AngleAxisToQuaternion(axis_angle, quaternion);
  EXPECT_THAT(quaternion, IsNormalizedQuaternion());
  EXPECT_THAT(quaternion, IsNearQuaternion(expected));
}

// Transforms a unit quaternion to an axis angle.
TEST(Rotation, UnitQuaternionToAngleAxis) {
  double quaternion[4] = { 1, 0, 0, 0 };
  double axis_angle[3];
  double expected[3] = { 0, 0, 0 };
  QuaternionToAngleAxis(quaternion, axis_angle);
  EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
}

// Transforms a quaternion that rotates by pi about the Y axis to an axis angle.
TEST(Rotation, YRotationQuaternionToAngleAxis) {
  double quaternion[4] = { 0, 0, 1, 0 };
  double axis_angle[3];
  double expected[3] = { 0, kPi, 0 };
  QuaternionToAngleAxis(quaternion, axis_angle);
  EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
}

// Transforms a quaternion that rotates by pi/3 about the Z axis to an axis
// angle.
TEST(Rotation, ZRotationQuaternionToAngleAxis) {
  double quaternion[4] = { sqrt(3) / 2, 0, 0, 0.5 };
  double axis_angle[3];
  double expected[3] = { 0, 0, kPi / 3 };
  QuaternionToAngleAxis(quaternion, axis_angle);
  EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
}

// Test that exact conversion works for small angles.
TEST(Rotation, SmallQuaternionToAngleAxis) {
  // Small, finite value to test.
  double theta = 1.0e-2;
  double quaternion[4] = { cos(theta/2), sin(theta/2.0), 0, 0 };
  double axis_angle[3];
  double expected[3] = { theta, 0, 0 };
  QuaternionToAngleAxis(quaternion, axis_angle);
  EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
}

// Test that approximate conversion works for very small angles.
TEST(Rotation, TinyQuaternionToAngleAxis) {
  // Very small value that could potentially cause underflow.
  double theta = pow(numeric_limits<double>::min(), 0.75);
  double quaternion[4] = { cos(theta/2), sin(theta/2.0), 0, 0 };
  double axis_angle[3];
  double expected[3] = { theta, 0, 0 };
  QuaternionToAngleAxis(quaternion, axis_angle);
  EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
}

TEST(Rotation, QuaternionToAngleAxisAngleIsLessThanPi) {
  double quaternion[4];
  double angle_axis[3];

  const double half_theta = 0.75 * kPi;

  quaternion[0] = cos(half_theta);
  quaternion[1] = 1.0 * sin(half_theta);
  quaternion[2] = 0.0;
  quaternion[3] = 0.0;
  QuaternionToAngleAxis(quaternion, angle_axis);
  const double angle = sqrt(angle_axis[0] * angle_axis[0] +
                            angle_axis[1] * angle_axis[1] +
                            angle_axis[2] * angle_axis[2]);
  EXPECT_LE(angle, kPi);
}

static const int kNumTrials = 10000;

// Takes a bunch of random axis/angle values, converts them to quaternions,
// and back again.
TEST(Rotation, AngleAxisToQuaterionAndBack) {
  srand(5);
  for (int i = 0; i < kNumTrials; i++) {
    double axis_angle[3];
    // Make an axis by choosing three random numbers in [-1, 1) and
    // normalizing.
    double norm = 0;
    for (int i = 0; i < 3; i++) {
      axis_angle[i] = RandDouble() * 2 - 1;
      norm += axis_angle[i] * axis_angle[i];
    }
    norm = sqrt(norm);

    // Angle in [-pi, pi).
    double theta = kPi * 2 * RandDouble() - kPi;
    for (int i = 0; i < 3; i++) {
      axis_angle[i] = axis_angle[i] * theta / norm;
    }

    double quaternion[4];
    double round_trip[3];
    // We use ASSERTs here because if there's one failure, there are
    // probably many and spewing a million failures doesn't make anyone's
    // day.
    AngleAxisToQuaternion(axis_angle, quaternion);
    ASSERT_THAT(quaternion, IsNormalizedQuaternion());
    QuaternionToAngleAxis(quaternion, round_trip);
    ASSERT_THAT(round_trip, IsNearAngleAxis(axis_angle));
  }
}

// Takes a bunch of random quaternions, converts them to axis/angle,
// and back again.
TEST(Rotation, QuaterionToAngleAxisAndBack) {
  srand(5);
  for (int i = 0; i < kNumTrials; i++) {
    double quaternion[4];
    // Choose four random numbers in [-1, 1) and normalize.
    double norm = 0;
    for (int i = 0; i < 4; i++) {
      quaternion[i] = RandDouble() * 2 - 1;
      norm += quaternion[i] * quaternion[i];
    }
    norm = sqrt(norm);

    for (int i = 0; i < 4; i++) {
      quaternion[i] = quaternion[i] / norm;
    }

    double axis_angle[3];
    double round_trip[4];
    QuaternionToAngleAxis(quaternion, axis_angle);
    AngleAxisToQuaternion(axis_angle, round_trip);
    ASSERT_THAT(round_trip, IsNormalizedQuaternion());
    ASSERT_THAT(round_trip, IsNearQuaternion(quaternion));
  }
}

// Transforms a zero axis/angle to a rotation matrix.
TEST(Rotation, ZeroAngleAxisToRotationMatrix) {
  double axis_angle[3] = { 0, 0, 0 };
  double matrix[9];
  double expected[9] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
  AngleAxisToRotationMatrix(axis_angle, matrix);
  EXPECT_THAT(matrix, IsOrthonormal());
  EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
}

TEST(Rotation, NearZeroAngleAxisToRotationMatrix) {
  double axis_angle[3] = { 1e-24, 2e-24, 3e-24 };
  double matrix[9];
  double expected[9] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
  AngleAxisToRotationMatrix(axis_angle, matrix);
  EXPECT_THAT(matrix, IsOrthonormal());
  EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
}

// Transforms a rotation by pi/2 around X to a rotation matrix and back.
TEST(Rotation, XRotationToRotationMatrix) {
  double axis_angle[3] = { kPi / 2, 0, 0 };
  double matrix[9];
  // The rotation matrices are stored column-major.
  double expected[9] = { 1, 0, 0, 0, 0, 1, 0, -1, 0 };
  AngleAxisToRotationMatrix(axis_angle, matrix);
  EXPECT_THAT(matrix, IsOrthonormal());
  EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
  double round_trip[3];
  RotationMatrixToAngleAxis(matrix, round_trip);
  EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle));
}

// Transforms an axis angle that rotates by pi about the Y axis to a
// rotation matrix and back.
TEST(Rotation, YRotationToRotationMatrix) {
  double axis_angle[3] = { 0, kPi, 0 };
  double matrix[9];
  double expected[9] = { -1, 0, 0, 0, 1, 0, 0, 0, -1 };
  AngleAxisToRotationMatrix(axis_angle, matrix);
  EXPECT_THAT(matrix, IsOrthonormal());
  EXPECT_THAT(matrix, IsNear3x3Matrix(expected));

  double round_trip[3];
  RotationMatrixToAngleAxis(matrix, round_trip);
  EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle));
}

TEST(Rotation, NearPiAngleAxisRoundTrip) {
  double in_axis_angle[3];
  double matrix[9];
  double out_axis_angle[3];

  srand(5);
  for (int i = 0; i < kNumTrials; i++) {
    // Make an axis by choosing three random numbers in [-1, 1) and
    // normalizing.
    double norm = 0;
    for (int i = 0; i < 3; i++) {
      in_axis_angle[i] = RandDouble() * 2 - 1;
      norm += in_axis_angle[i] * in_axis_angle[i];
    }
    norm = sqrt(norm);

    // Angle in [pi - kMaxSmallAngle, pi).
    const double kMaxSmallAngle = 1e-2;
    double theta = kPi - kMaxSmallAngle * RandDouble();

    for (int i = 0; i < 3; i++) {
      in_axis_angle[i] *= (theta / norm);
    }
    AngleAxisToRotationMatrix(in_axis_angle, matrix);
    RotationMatrixToAngleAxis(matrix, out_axis_angle);

    for (int i = 0; i < 3; ++i) {
      EXPECT_NEAR(out_axis_angle[i], in_axis_angle[i], kLooseTolerance);
    }
  }
}

TEST(Rotation, AtPiAngleAxisRoundTrip) {
  // A rotation of kPi about the X axis;
  static const double kMatrix[3][3] = {
    {1.0,  0.0,  0.0},
    {0.0,  -1.0,  0.0},
    {0.0,  0.0,  -1.0}
  };

  double in_matrix[9];
  // Fill it from kMatrix in col-major order.
  for (int j = 0, k = 0; j < 3; ++j) {
     for (int i = 0; i < 3; ++i, ++k) {
       in_matrix[k] = kMatrix[i][j];
     }
  }

  const double expected_axis_angle[3] = { kPi, 0, 0 };

  double out_matrix[9];
  double axis_angle[3];
  RotationMatrixToAngleAxis(in_matrix, axis_angle);
  AngleAxisToRotationMatrix(axis_angle, out_matrix);

  LOG(INFO) << "AngleAxis = " << axis_angle[0] << " " << axis_angle[1]
            << " " << axis_angle[2];
  LOG(INFO) << "Expected AngleAxis = " << kPi << " 0 0";
  double out_rowmajor[3][3];
  for (int j = 0, k = 0; j < 3; ++j) {
    for (int i = 0; i < 3; ++i, ++k) {
      out_rowmajor[i][j] = out_matrix[k];
    }
  }
  LOG(INFO) << "Rotation:";
  LOG(INFO) << "EXPECTED      |        ACTUAL";
  for (int i = 0; i < 3; ++i) {
    string line;
    for (int j = 0; j < 3; ++j) {
      StringAppendF(&line, "%g ", kMatrix[i][j]);
    }
    line += "         |        ";
    for (int j = 0; j < 3; ++j) {
      StringAppendF(&line, "%g ", out_rowmajor[i][j]);
    }
    LOG(INFO) << line;
  }

  EXPECT_THAT(axis_angle, IsNearAngleAxis(expected_axis_angle));
  EXPECT_THAT(out_matrix, IsNear3x3Matrix(in_matrix));
}

// Transforms an axis angle that rotates by pi/3 about the Z axis to a
// rotation matrix.
TEST(Rotation, ZRotationToRotationMatrix) {
  double axis_angle[3] =  { 0, 0, kPi / 3 };
  double matrix[9];
  // This is laid-out row-major on the screen but is actually stored
  // column-major.
  double expected[9] = { 0.5, sqrt(3) / 2, 0,   // Column 1
                         -sqrt(3) / 2, 0.5, 0,  // Column 2
                         0, 0, 1 };             // Column 3
  AngleAxisToRotationMatrix(axis_angle, matrix);
  EXPECT_THAT(matrix, IsOrthonormal());
  EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
  double round_trip[3];
  RotationMatrixToAngleAxis(matrix, round_trip);
  EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle));
}

// Takes a bunch of random axis/angle values, converts them to rotation
// matrices, and back again.
TEST(Rotation, AngleAxisToRotationMatrixAndBack) {
  srand(5);
  for (int i = 0; i < kNumTrials; i++) {
    double axis_angle[3];
    // Make an axis by choosing three random numbers in [-1, 1) and
    // normalizing.
    double norm = 0;
    for (int i = 0; i < 3; i++) {
      axis_angle[i] = RandDouble() * 2 - 1;
      norm += axis_angle[i] * axis_angle[i];
    }
    norm = sqrt(norm);

    // Angle in [-pi, pi).
    double theta = kPi * 2 * RandDouble() - kPi;
    for (int i = 0; i < 3; i++) {
      axis_angle[i] = axis_angle[i] * theta / norm;
    }

    double matrix[9];
    double round_trip[3];
    AngleAxisToRotationMatrix(axis_angle, matrix);
    ASSERT_THAT(matrix, IsOrthonormal());
    RotationMatrixToAngleAxis(matrix, round_trip);

    for (int i = 0; i < 3; ++i) {
      EXPECT_NEAR(round_trip[i], axis_angle[i], kLooseTolerance);
    }
  }
}

// Transposes a 3x3 matrix.
static void Transpose3x3(double m[9]) {
  std::swap(m[1], m[3]);
  std::swap(m[2], m[6]);
  std::swap(m[5], m[7]);
}

// Convert Euler angles from radians to degrees.
static void ToDegrees(double ea[3]) {
  for (int i = 0; i < 3; ++i)
    ea[i] *= 180.0 / kPi;
}

// Compare the 3x3 rotation matrices produced by the axis-angle
// rotation 'aa' and the Euler angle rotation 'ea' (in radians).
static void CompareEulerToAngleAxis(double aa[3], double ea[3]) {
  double aa_matrix[9];
  AngleAxisToRotationMatrix(aa, aa_matrix);
  Transpose3x3(aa_matrix);  // Column to row major order.

  double ea_matrix[9];
  ToDegrees(ea);  // Radians to degrees.
  const int kRowStride = 3;
  EulerAnglesToRotationMatrix(ea, kRowStride, ea_matrix);

  EXPECT_THAT(aa_matrix, IsOrthonormal());
  EXPECT_THAT(ea_matrix, IsOrthonormal());
  EXPECT_THAT(ea_matrix, IsNear3x3Matrix(aa_matrix));
}

// Test with rotation axis along the x/y/z axes.
// Also test zero rotation.
TEST(EulerAnglesToRotationMatrix, OnAxis) {
  int n_tests = 0;
  for (double x = -1.0; x <= 1.0; x += 1.0) {
    for (double y = -1.0; y <= 1.0; y += 1.0) {
      for (double z = -1.0; z <= 1.0; z += 1.0) {
        if ((x != 0) + (y != 0) + (z != 0) > 1)
          continue;
        double axis_angle[3] = {x, y, z};
        double euler_angles[3] = {x, y, z};
        CompareEulerToAngleAxis(axis_angle, euler_angles);
        ++n_tests;
      }
    }
  }
  CHECK_EQ(7, n_tests);
}

// Test that a random rotation produces an orthonormal rotation
// matrix.
TEST(EulerAnglesToRotationMatrix, IsOrthonormal) {
  srand(5);
  for (int trial = 0; trial < kNumTrials; ++trial) {
    double ea[3];
    for (int i = 0; i < 3; ++i)
      ea[i] = 360.0 * (RandDouble() * 2.0 - 1.0);
    double ea_matrix[9];
    ToDegrees(ea);  // Radians to degrees.
    EulerAnglesToRotationMatrix(ea, 3, ea_matrix);
    EXPECT_THAT(ea_matrix, IsOrthonormal());
  }
}

// Tests using Jets for specific behavior involving auto differentiation
// near singularity points.

typedef Jet<double, 3> J3;
typedef Jet<double, 4> J4;

J3 MakeJ3(double a, double v0, double v1, double v2) {
  J3 j;
  j.a = a;
  j.v[0] = v0;
  j.v[1] = v1;
  j.v[2] = v2;
  return j;
}

J4 MakeJ4(double a, double v0, double v1, double v2, double v3) {
  J4 j;
  j.a = a;
  j.v[0] = v0;
  j.v[1] = v1;
  j.v[2] = v2;
  j.v[3] = v3;
  return j;
}


bool IsClose(double x, double y) {
  EXPECT_FALSE(IsNaN(x));
  EXPECT_FALSE(IsNaN(y));
  double absdiff = fabs(x - y);
  if (x == 0 || y == 0) {
    return absdiff <= kTolerance;
  }
  double reldiff = absdiff / max(fabs(x), fabs(y));
  return reldiff <= kTolerance;
}

template <int N>
bool IsClose(const Jet<double, N> &x, const Jet<double, N> &y) {
  if (IsClose(x.a, y.a)) {
    for (int i = 0; i < N; i++) {
      if (!IsClose(x.v[i], y.v[i])) {
        return false;
      }
    }
  }
  return true;
}

template <int M, int N>
void ExpectJetArraysClose(const Jet<double, N> *x, const Jet<double, N> *y) {
  for (int i = 0; i < M; i++) {
    if (!IsClose(x[i], y[i])) {
      LOG(ERROR) << "Jet " << i << "/" << M << " not equal";
      LOG(ERROR) << "x[" << i << "]: " << x[i];
      LOG(ERROR) << "y[" << i << "]: " << y[i];
      Jet<double, N> d, zero;
      d.a = y[i].a - x[i].a;
      for (int j = 0; j < N; j++) {
        d.v[j] = y[i].v[j] - x[i].v[j];
      }
      LOG(ERROR) << "diff: " << d;
      EXPECT_TRUE(IsClose(x[i], y[i]));
    }
  }
}

// Log-10 of a value well below machine precision.
static const int kSmallTinyCutoff =
    static_cast<int>(2 * log(numeric_limits<double>::epsilon())/log(10.0));

// Log-10 of a value just below values representable by double.
static const int kTinyZeroLimit   =
    static_cast<int>(1 + log(numeric_limits<double>::min())/log(10.0));

// Test that exact conversion works for small angles when jets are used.
TEST(Rotation, SmallAngleAxisToQuaternionForJets) {
  // Examine small x rotations that are still large enough
  // to be well within the range represented by doubles.
  for (int i = -2; i >= kSmallTinyCutoff; i--) {
    double theta = pow(10.0, i);
    J3 axis_angle[3] = { J3(theta, 0), J3(0, 1), J3(0, 2) };
    J3 quaternion[4];
    J3 expected[4] = {
        MakeJ3(cos(theta/2), -sin(theta/2)/2, 0, 0),
        MakeJ3(sin(theta/2), cos(theta/2)/2, 0, 0),
        MakeJ3(0, 0, sin(theta/2)/theta, 0),
        MakeJ3(0, 0, 0, sin(theta/2)/theta),
    };
    AngleAxisToQuaternion(axis_angle, quaternion);
    ExpectJetArraysClose<4, 3>(quaternion, expected);
  }
}


// Test that conversion works for very small angles when jets are used.
TEST(Rotation, TinyAngleAxisToQuaternionForJets) {
  // Examine tiny x rotations that extend all the way to where
  // underflow occurs.
  for (int i = kSmallTinyCutoff; i >= kTinyZeroLimit; i--) {
    double theta = pow(10.0, i);
    J3 axis_angle[3] = { J3(theta, 0), J3(0, 1), J3(0, 2) };
    J3 quaternion[4];
    // To avoid loss of precision in the test itself,
    // a finite expansion is used here, which will
    // be exact up to machine precision for the test values used.
    J3 expected[4] = {
        MakeJ3(1.0, 0, 0, 0),
        MakeJ3(0, 0.5, 0, 0),
        MakeJ3(0, 0, 0.5, 0),
        MakeJ3(0, 0, 0, 0.5),
    };
    AngleAxisToQuaternion(axis_angle, quaternion);
    ExpectJetArraysClose<4, 3>(quaternion, expected);
  }
}

// Test that derivatives are correct for zero rotation.
TEST(Rotation, ZeroAngleAxisToQuaternionForJets) {
  J3 axis_angle[3] = { J3(0, 0), J3(0, 1), J3(0, 2) };
  J3 quaternion[4];
  J3 expected[4] = {
      MakeJ3(1.0, 0, 0, 0),
      MakeJ3(0, 0.5, 0, 0),
      MakeJ3(0, 0, 0.5, 0),
      MakeJ3(0, 0, 0, 0.5),
  };
  AngleAxisToQuaternion(axis_angle, quaternion);
  ExpectJetArraysClose<4, 3>(quaternion, expected);
}

// Test that exact conversion works for small angles.
TEST(Rotation, SmallQuaternionToAngleAxisForJets) {
  // Examine small x rotations that are still large enough
  // to be well within the range represented by doubles.
  for (int i = -2; i >= kSmallTinyCutoff; i--) {
    double theta = pow(10.0, i);
    double s = sin(theta);
    double c = cos(theta);
    J4 quaternion[4] = { J4(c, 0), J4(s, 1), J4(0, 2), J4(0, 3) };
    J4 axis_angle[3];
    J4 expected[3] = {
        MakeJ4(s, -2*theta, 2*theta*c, 0, 0),
        MakeJ4(0, 0, 0, 2*theta/s, 0),
        MakeJ4(0, 0, 0, 0, 2*theta/s),
    };
    QuaternionToAngleAxis(quaternion, axis_angle);
    ExpectJetArraysClose<3, 4>(axis_angle, expected);
  }
}

// Test that conversion works for very small angles.
TEST(Rotation, TinyQuaternionToAngleAxisForJets) {
  // Examine tiny x rotations that extend all the way to where
  // underflow occurs.
  for (int i = kSmallTinyCutoff; i >= kTinyZeroLimit; i--) {
    double theta = pow(10.0, i);
    double s = sin(theta);
    double c = cos(theta);
    J4 quaternion[4] = { J4(c, 0), J4(s, 1), J4(0, 2), J4(0, 3) };
    J4 axis_angle[3];
    // To avoid loss of precision in the test itself,
    // a finite expansion is used here, which will
    // be exact up to machine precision for the test values used.
    J4 expected[3] = {
        MakeJ4(theta, -2*theta, 2.0, 0, 0),
        MakeJ4(0, 0, 0, 2.0, 0),
        MakeJ4(0, 0, 0, 0, 2.0),
    };
    QuaternionToAngleAxis(quaternion, axis_angle);
    ExpectJetArraysClose<3, 4>(axis_angle, expected);
  }
}

// Test that conversion works for no rotation.
TEST(Rotation, ZeroQuaternionToAngleAxisForJets) {
  J4 quaternion[4] = { J4(1, 0), J4(0, 1), J4(0, 2), J4(0, 3) };
  J4 axis_angle[3];
  J4 expected[3] = {
      MakeJ4(0, 0, 2.0, 0, 0),
      MakeJ4(0, 0, 0, 2.0, 0),
      MakeJ4(0, 0, 0, 0, 2.0),
  };
  QuaternionToAngleAxis(quaternion, axis_angle);
  ExpectJetArraysClose<3, 4>(axis_angle, expected);
}

TEST(Quaternion, RotatePointGivesSameAnswerAsRotationByMatrixCanned) {
  // Canned data generated in octave.
  double const q[4] = {
    +0.1956830471754074,
    -0.0150618562474847,
    +0.7634572982788086,
    -0.3019454777240753,
  };
  double const Q[3][3] = {  // Scaled rotation matrix.
    { -0.6355194033477252,  0.0951730541682254,  0.3078870197911186 },
    { -0.1411693904792992,  0.5297609702153905, -0.4551502574482019 },
    { -0.2896955822708862, -0.4669396571547050, -0.4536309793389248 },
  };
  double const R[3][3] = {  // With unit rows and columns.
    { -0.8918859164053080,  0.1335655625725649,  0.4320876677394745 },
    { -0.1981166751680096,  0.7434648665444399, -0.6387564287225856 },
    { -0.4065578619806013, -0.6553016349046693, -0.6366242786393164 },
  };

  // Compute R from q and compare to known answer.
  double Rq[3][3];
  QuaternionToScaledRotation<double>(q, Rq[0]);
  ExpectArraysClose(9, Q[0], Rq[0], kTolerance);

  // Now do the same but compute R with normalization.
  QuaternionToRotation<double>(q, Rq[0]);
  ExpectArraysClose(9, R[0], Rq[0], kTolerance);
}


TEST(Quaternion, RotatePointGivesSameAnswerAsRotationByMatrix) {
  // Rotation defined by a unit quaternion.
  double const q[4] = {
    0.2318160216097109,
    -0.0178430356832060,
    0.9044300776717159,
    -0.3576998641394597,
  };
  double const p[3] = {
    +0.11,
    -13.15,
    1.17,
  };

  double R[3 * 3];
  QuaternionToRotation(q, R);

  double result1[3];
  UnitQuaternionRotatePoint(q, p, result1);

  double result2[3];
  VectorRef(result2, 3) = ConstMatrixRef(R, 3, 3)* ConstVectorRef(p, 3);
  ExpectArraysClose(3, result1, result2, kTolerance);
}


// Verify that (a * b) * c == a * (b * c).
TEST(Quaternion, MultiplicationIsAssociative) {
  double a[4];
  double b[4];
  double c[4];
  for (int i = 0; i < 4; ++i) {
    a[i] = 2 * RandDouble() - 1;
    b[i] = 2 * RandDouble() - 1;
    c[i] = 2 * RandDouble() - 1;
  }

  double ab[4];
  double ab_c[4];
  QuaternionProduct(a, b, ab);
  QuaternionProduct(ab, c, ab_c);

  double bc[4];
  double a_bc[4];
  QuaternionProduct(b, c, bc);
  QuaternionProduct(a, bc, a_bc);

  ASSERT_NEAR(ab_c[0], a_bc[0], kTolerance);
  ASSERT_NEAR(ab_c[1], a_bc[1], kTolerance);
  ASSERT_NEAR(ab_c[2], a_bc[2], kTolerance);
  ASSERT_NEAR(ab_c[3], a_bc[3], kTolerance);
}


TEST(AngleAxis, RotatePointGivesSameAnswerAsRotationMatrix) {
  double angle_axis[3];
  double R[9];
  double p[3];
  double angle_axis_rotated_p[3];
  double rotation_matrix_rotated_p[3];

  for (int i = 0; i < 10000; ++i) {
    double theta = (2.0 * i * 0.0011 - 1.0) * kPi;
    for (int j = 0; j < 50; ++j) {
      double norm2 = 0.0;
      for (int k = 0; k < 3; ++k) {
        angle_axis[k] = 2.0 * RandDouble() - 1.0;
        p[k] = 2.0 * RandDouble() - 1.0;
        norm2 = angle_axis[k] * angle_axis[k];
      }

      const double inv_norm = theta / sqrt(norm2);
      for (int k = 0; k < 3; ++k) {
        angle_axis[k] *= inv_norm;
      }

      AngleAxisToRotationMatrix(angle_axis, R);
      rotation_matrix_rotated_p[0] = R[0] * p[0] + R[3] * p[1] + R[6] * p[2];
      rotation_matrix_rotated_p[1] = R[1] * p[0] + R[4] * p[1] + R[7] * p[2];
      rotation_matrix_rotated_p[2] = R[2] * p[0] + R[5] * p[1] + R[8] * p[2];

      AngleAxisRotatePoint(angle_axis, p, angle_axis_rotated_p);
      for (int k = 0; k < 3; ++k) {
        EXPECT_NEAR(rotation_matrix_rotated_p[k],
                    angle_axis_rotated_p[k],
                    kTolerance) << "p: " << p[0]
                                << " " << p[1]
                                << " " << p[2]
                                << " angle_axis: " << angle_axis[0]
                                << " " << angle_axis[1]
                                << " " << angle_axis[2];
      }
    }
  }
}

TEST(AngleAxis, NearZeroRotatePointGivesSameAnswerAsRotationMatrix) {
  double angle_axis[3];
  double R[9];
  double p[3];
  double angle_axis_rotated_p[3];
  double rotation_matrix_rotated_p[3];

  for (int i = 0; i < 10000; ++i) {
    double norm2 = 0.0;
    for (int k = 0; k < 3; ++k) {
      angle_axis[k] = 2.0 * RandDouble() - 1.0;
      p[k] = 2.0 * RandDouble() - 1.0;
      norm2 = angle_axis[k] * angle_axis[k];
    }

    double theta = (2.0 * i * 0.0001  - 1.0) * 1e-16;
    const double inv_norm = theta / sqrt(norm2);
    for (int k = 0; k < 3; ++k) {
      angle_axis[k] *= inv_norm;
    }

    AngleAxisToRotationMatrix(angle_axis, R);
    rotation_matrix_rotated_p[0] = R[0] * p[0] + R[3] * p[1] + R[6] * p[2];
    rotation_matrix_rotated_p[1] = R[1] * p[0] + R[4] * p[1] + R[7] * p[2];
    rotation_matrix_rotated_p[2] = R[2] * p[0] + R[5] * p[1] + R[8] * p[2];

    AngleAxisRotatePoint(angle_axis, p, angle_axis_rotated_p);
    for (int k = 0; k < 3; ++k) {
      EXPECT_NEAR(rotation_matrix_rotated_p[k],
                  angle_axis_rotated_p[k],
                  kTolerance) << "p: " << p[0]
                              << " " << p[1]
                              << " " << p[2]
                              << " angle_axis: " << angle_axis[0]
                              << " " << angle_axis[1]
                              << " " << angle_axis[2];
    }
  }
}


}  // namespace internal
}  // namespace ceres