Add AutoDiffManifold

AutoDiffManifold allows the user to define a templated
functor that implements the Plus and Minus operations
on the Manifold and will compute the Jacobians needed
to define the Manifold object using automatic differentiation.

Change-Id: Ibd073c25847389308ca1ab66e6f5fe78aae77205
diff --git a/include/ceres/autodiff_manifold.h b/include/ceres/autodiff_manifold.h
new file mode 100644
index 0000000..fb5f459
--- /dev/null
+++ b/include/ceres/autodiff_manifold.h
@@ -0,0 +1,259 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2022 Google Inc. All rights reserved.
+// http://ceres-solver.org/
+//
+// 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)
+
+#ifndef CERES_PUBLIC_AUTODIFF_MANIFOLD_H_
+#define CERES_PUBLIC_AUTODIFF_MANIFOLD_H_
+
+#include <memory>
+
+#include "ceres/internal/autodiff.h"
+#include "ceres/manifold.h"
+
+namespace ceres {
+
+// Create a Manifold with Jacobians computed via automatic differentiation. For
+// more information on manifolds, see include/ceres/manifold.h
+//
+// To get an auto differentiated manifold, you must define a class/struct with
+// templated Plus and Minus functions that computes
+//
+//   x_plus_delta = Plus(x, delta);
+//   y_minus_x    = Minus(y, x);
+//
+// Where, x, y and x_plus_y are vectors on the manifold in the ambient space (so
+// they are kAmbientSize vectors) and and delta, y_minus_x are vectors in the
+// tangent space (so they are kTangentSize vectors).
+//
+// The Functor should have the signatures:
+//
+// struct Functor {
+//   template <typename T>
+//   bool Plus(const T* x, const T* delta, T* x_plus_delta) const;
+//
+//   template <typename T>
+//   bool Minus(const T* y, const T* x, T* y_minus_x) const;
+// };
+//
+// Observe that the Plus and Minus operations are templated on the parameter T.
+// The autodiff framework substitutes appropriate "Jet" objects for T in order
+// to compute the derivative when necessary. This is the same mechanism that is
+// used to compute derivatives when using AutoDiffCostFunction.
+//
+// Plus and Minus should return true if the computation is successful and false
+// otherwise, in which case the result will not be used.
+//
+// Given this Functor, the corresponding Manifold can be constructed as:
+//
+// AutoDiffManifold<Functor, kAmbientSize, kTangentSize> manifold;
+//
+// As a concrete example consider the case of Quaternions. Quaternions form a
+// three dimensional manifold embedded in R^4, i.e. they have an ambient
+// dimension of 4 and their tangent space has dimension 3. The following Functor
+// (taken from autodiff_manifold_test.cc) defines the Plus and Minus operations
+// on the Quaternion manifold:
+//
+// NOTE: The following is only used for illustration purposes. Ceres Solver
+// ships with optimized production grade QuaternionManifold implementation. See
+// manifold.h.
+//
+// This functor assumes that the quaternions are laid out as [w,x,y,z] in
+// memory, i.e. the real or scalar part is the first coordinate.
+//
+// struct QuaternionFunctor {
+//   template <typename T>
+//   bool Plus(const T* x, const T* delta, T* x_plus_delta) const {
+//     const T squared_norm_delta =
+//         delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2];
+//
+//     T q_delta[4];
+//     if (squared_norm_delta > T(0.0)) {
+//       T norm_delta = sqrt(squared_norm_delta);
+//       const T sin_delta_by_delta = sin(norm_delta) / norm_delta;
+//       q_delta[0] = cos(norm_delta);
+//       q_delta[1] = sin_delta_by_delta * delta[0];
+//       q_delta[2] = sin_delta_by_delta * delta[1];
+//       q_delta[3] = sin_delta_by_delta * delta[2];
+//     } else {
+//       // We do not just use q_delta = [1,0,0,0] here because that is a
+//       // constant and when used for automatic differentiation will
+//       // lead to a zero derivative. Instead we take a first order
+//       // approximation and evaluate it at zero.
+//       q_delta[0] = T(1.0);
+//       q_delta[1] = delta[0];
+//       q_delta[2] = delta[1];
+//       q_delta[3] = delta[2];
+//     }
+//
+//     QuaternionProduct(q_delta, x, x_plus_delta);
+//     return true;
+//   }
+//
+//   template <typename T>
+//   bool Minus(const T* y, const T* x, T* y_minus_x) const {
+//     T minus_x[4] = {x[0], -x[1], -x[2], -x[3]};
+//     T ambient_y_minus_x[4];
+//     QuaternionProduct(y, minus_x, ambient_y_minus_x);
+//     T u_norm = sqrt(ambient_y_minus_x[1] * ambient_y_minus_x[1] +
+//                     ambient_y_minus_x[2] * ambient_y_minus_x[2] +
+//                     ambient_y_minus_x[3] * ambient_y_minus_x[3]);
+//     if (u_norm > 0.0) {
+//       T theta = atan2(u_norm, ambient_y_minus_x[0]);
+//       y_minus_x[0] = theta * ambient_y_minus_x[1] / u_norm;
+//       y_minus_x[1] = theta * ambient_y_minus_x[2] / u_norm;
+//       y_minus_x[2] = theta * ambient_y_minus_x[3] / u_norm;
+//     } else {
+//       // We do not use [0,0,0] here because even though the value part is
+//       // a constant, the derivative part is not.
+//       y_minus_x[0] = ambient_y_minus_x[1];
+//       y_minus_x[1] = ambient_y_minus_x[2];
+//       y_minus_x[2] = ambient_y_minus_x[3];
+//     }
+//     return true;
+//   }
+// };
+//
+// Then given this struct, the auto differentiated Quaternion Manifold can now
+// be constructed as
+//
+//   Manifold* manifold = new AutoDiffManifold<QuaternionFunctor, 4, 3>;
+
+template <typename Functor, int kAmbientSize, int kTangentSize>
+class AutoDiffManifold : public Manifold {
+ public:
+  AutoDiffManifold() : functor_(std::make_unique<Functor>()) {}
+
+  // Takes ownership of functor.
+  explicit AutoDiffManifold(Functor* functor) : functor_(functor) {}
+
+  ~AutoDiffManifold() override {}
+
+  int AmbientSize() const override { return kAmbientSize; }
+  int TangentSize() const override { return kTangentSize; }
+
+  bool Plus(const double* x,
+            const double* delta,
+            double* x_plus_delta) const override {
+    return functor_->Plus(x, delta, x_plus_delta);
+  }
+
+  bool PlusJacobian(const double* x, double* jacobian) const override;
+
+  bool Minus(const double* y,
+             const double* x,
+             double* y_minus_x) const override {
+    return functor_->Minus(y, x, y_minus_x);
+  }
+
+  bool MinusJacobian(const double* x, double* jacobian) const override;
+
+ private:
+  std::unique_ptr<Functor> functor_;
+};
+
+namespace internal {
+
+// The following two helper structs are needed to interface the Plus and Minus
+// methods of the ManifoldFunctor with the automatic differentiation which
+// expects a Functor with operator().
+template <typename Functor>
+struct PlusWrapper {
+  PlusWrapper(const Functor& functor) : functor(functor) {}
+  template <typename T>
+  bool operator()(const T* x, const T* delta, T* x_plus_delta) const {
+    return functor.Plus(x, delta, x_plus_delta);
+  }
+  const Functor& functor;
+};
+
+template <typename Functor>
+struct MinusWrapper {
+  MinusWrapper(const Functor& functor) : functor(functor) {}
+  template <typename T>
+  bool operator()(const T* y, const T* x, T* y_minus_x) const {
+    return functor.Minus(y, x, y_minus_x);
+  }
+  const Functor& functor;
+};
+}  // namespace internal
+
+template <typename Functor, int kAmbientSize, int kTangentSize>
+bool AutoDiffManifold<Functor, kAmbientSize, kTangentSize>::PlusJacobian(
+    const double* x, double* jacobian) const {
+  double zero_delta[kTangentSize];
+  for (int i = 0; i < kTangentSize; ++i) {
+    zero_delta[i] = 0.0;
+  }
+
+  double x_plus_delta[kAmbientSize];
+  for (int i = 0; i < kAmbientSize; ++i) {
+    x_plus_delta[i] = 0.0;
+  }
+
+  const double* parameter_ptrs[2] = {x, zero_delta};
+
+  // PlusJacobian is D_2 Plus(x,0) so we only need to compute the Jacobian
+  // w.r.t. the second argument.
+  double* jacobian_ptrs[2] = {nullptr, jacobian};
+  return internal::AutoDifferentiate<
+      kAmbientSize,
+      internal::StaticParameterDims<kAmbientSize, kTangentSize>>(
+      internal::PlusWrapper(*functor_),
+      parameter_ptrs,
+      kAmbientSize,
+      x_plus_delta,
+      jacobian_ptrs);
+}
+
+template <typename Functor, int kAmbientSize, int kTangentSize>
+bool AutoDiffManifold<Functor, kAmbientSize, kTangentSize>::MinusJacobian(
+    const double* x, double* jacobian) const {
+  double y_minus_x[kTangentSize];
+  for (int i = 0; i < kTangentSize; ++i) {
+    y_minus_x[i] = 0.0;
+  }
+
+  const double* parameter_ptrs[2] = {x, x};
+
+  // MinusJacobian is D_1 Minus(x,x), so we only need to compute the Jacobian
+  // w.r.t. the first argument.
+  double* jacobian_ptrs[2] = {jacobian, nullptr};
+  return internal::AutoDifferentiate<
+      kTangentSize,
+      internal::StaticParameterDims<kAmbientSize, kAmbientSize>>(
+      internal::MinusWrapper(*functor_),
+      parameter_ptrs,
+      kAmbientSize,
+      y_minus_x,
+      jacobian_ptrs);
+}
+
+}  // namespace ceres
+
+#endif  // CERES_PUBLIC_AUTODIFF_MANIFOLD_H_
diff --git a/include/ceres/ceres.h b/include/ceres/ceres.h
index 86a84ab..a070c96 100644
--- a/include/ceres/ceres.h
+++ b/include/ceres/ceres.h
@@ -37,6 +37,7 @@
 #include "ceres/autodiff_cost_function.h"
 #include "ceres/autodiff_first_order_function.h"
 #include "ceres/autodiff_local_parameterization.h"
+#include "ceres/autodiff_manifold.h"
 #include "ceres/conditioned_cost_function.h"
 #include "ceres/context.h"
 #include "ceres/cost_function.h"
diff --git a/internal/ceres/CMakeLists.txt b/internal/ceres/CMakeLists.txt
index 3d39458..56e88cb 100644
--- a/internal/ceres/CMakeLists.txt
+++ b/internal/ceres/CMakeLists.txt
@@ -435,6 +435,7 @@
   ceres_test(autodiff_first_order_function)
   ceres_test(autodiff_cost_function)
   ceres_test(autodiff_local_parameterization)
+  ceres_test(autodiff_manifold)
   ceres_test(block_jacobi_preconditioner)
   ceres_test(block_random_access_dense_matrix)
   ceres_test(block_random_access_diagonal_matrix)
diff --git a/internal/ceres/autodiff_manifold_test.cc b/internal/ceres/autodiff_manifold_test.cc
new file mode 100644
index 0000000..f204d5a
--- /dev/null
+++ b/internal/ceres/autodiff_manifold_test.cc
@@ -0,0 +1,292 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2022 Google Inc. All rights reserved.
+// http://ceres-solver.org/
+//
+// 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 "ceres/autodiff_manifold.h"
+
+#include <cmath>
+
+#include "ceres/manifold.h"
+#include "ceres/manifold_test_utils.h"
+#include "ceres/rotation.h"
+#include "gtest/gtest.h"
+
+namespace ceres {
+namespace internal {
+
+constexpr int kNumTrials = 1000;
+constexpr double kTolerance = 1e-9;
+
+struct EuclideanFunctor {
+  template <typename T>
+  bool Plus(const T* x, const T* delta, T* x_plus_delta) const {
+    for (int i = 0; i < 3; ++i) {
+      x_plus_delta[i] = x[i] + delta[i];
+    }
+    return true;
+  }
+
+  template <typename T>
+  bool Minus(const T* y, const T* x, T* y_minus_x) const {
+    for (int i = 0; i < 3; ++i) {
+      y_minus_x[i] = y[i] - x[i];
+    }
+    return true;
+  }
+};
+
+TEST(AutoDiffLManifoldTest, EuclideanManifold) {
+  AutoDiffManifold<EuclideanFunctor, 3, 3> manifold;
+  EXPECT_EQ(manifold.AmbientSize(), 3);
+  EXPECT_EQ(manifold.TangentSize(), 3);
+
+  for (int trial = 0; trial < kNumTrials; ++trial) {
+    const Vector x = Vector::Random(manifold.AmbientSize());
+    const Vector y = Vector::Random(manifold.AmbientSize());
+    Vector delta = Vector::Random(manifold.TangentSize());
+    Vector x_plus_delta = Vector::Zero(manifold.AmbientSize());
+
+    manifold.Plus(x.data(), delta.data(), x_plus_delta.data());
+    EXPECT_NEAR((x_plus_delta - x - delta).norm() / (x + delta).norm(),
+                0.0,
+                kTolerance);
+
+    EXPECT_THAT_MANIFOLD_INVARIANTS_HOLD(manifold, x, delta, y, kTolerance);
+  }
+}
+
+struct ScaledFunctor {
+  explicit ScaledFunctor(const double s) : s_(s) {}
+
+  template <typename T>
+  bool Plus(const T* x, const T* delta, T* x_plus_delta) const {
+    for (int i = 0; i < 3; ++i) {
+      x_plus_delta[i] = x[i] + s_ * delta[i];
+    }
+    return true;
+  }
+
+  template <typename T>
+  bool Minus(const T* y, const T* x, T* y_minus_x) const {
+    for (int i = 0; i < 3; ++i) {
+      y_minus_x[i] = (y[i] - x[i]) / s_;
+    }
+    return true;
+  }
+
+  double s_;
+};
+
+TEST(AutoDiffManifoldTest, ScaledManifold) {
+  constexpr double kScale = 1.2342;
+  AutoDiffManifold<ScaledFunctor, 3, 3> manifold(new ScaledFunctor(kScale));
+  EXPECT_EQ(manifold.AmbientSize(), 3);
+  EXPECT_EQ(manifold.TangentSize(), 3);
+
+  for (int trial = 0; trial < kNumTrials; ++trial) {
+    const Vector x = Vector::Random(manifold.AmbientSize());
+    const Vector y = Vector::Random(manifold.AmbientSize());
+    Vector delta = Vector::Random(manifold.TangentSize());
+    Vector x_plus_delta = Vector::Zero(manifold.AmbientSize());
+
+    manifold.Plus(x.data(), delta.data(), x_plus_delta.data());
+    EXPECT_NEAR((x_plus_delta - x - delta * kScale).norm() /
+                    (x + delta * kScale).norm(),
+                0.0,
+                kTolerance);
+
+    EXPECT_THAT_MANIFOLD_INVARIANTS_HOLD(manifold, x, delta, y, kTolerance);
+  }
+}
+
+// Templated functor that implements the Plus and Minus operations on the
+// Quaternion manifold.
+struct QuaternionFunctor {
+  template <typename T>
+  bool Plus(const T* x, const T* delta, T* x_plus_delta) const {
+    const T squared_norm_delta =
+        delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2];
+
+    T q_delta[4];
+    if (squared_norm_delta > T(0.0)) {
+      T norm_delta = sqrt(squared_norm_delta);
+      const T sin_delta_by_delta = sin(norm_delta) / norm_delta;
+      q_delta[0] = cos(norm_delta);
+      q_delta[1] = sin_delta_by_delta * delta[0];
+      q_delta[2] = sin_delta_by_delta * delta[1];
+      q_delta[3] = sin_delta_by_delta * delta[2];
+    } else {
+      // We do not just use q_delta = [1,0,0,0] here because that is a
+      // constant and when used for automatic differentiation will
+      // lead to a zero derivative. Instead we take a first order
+      // approximation and evaluate it at zero.
+      q_delta[0] = T(1.0);
+      q_delta[1] = delta[0];
+      q_delta[2] = delta[1];
+      q_delta[3] = delta[2];
+    }
+
+    QuaternionProduct(q_delta, x, x_plus_delta);
+    return true;
+  }
+
+  template <typename T>
+  bool Minus(const T* y, const T* x, T* y_minus_x) const {
+    T minus_x[4] = {x[0], -x[1], -x[2], -x[3]};
+    T ambient_y_minus_x[4];
+    QuaternionProduct(y, minus_x, ambient_y_minus_x);
+    T u_norm = sqrt(ambient_y_minus_x[1] * ambient_y_minus_x[1] +
+                    ambient_y_minus_x[2] * ambient_y_minus_x[2] +
+                    ambient_y_minus_x[3] * ambient_y_minus_x[3]);
+    if (u_norm > 0.0) {
+      T theta = atan2(u_norm, ambient_y_minus_x[0]);
+      y_minus_x[0] = theta * ambient_y_minus_x[1] / u_norm;
+      y_minus_x[1] = theta * ambient_y_minus_x[2] / u_norm;
+      y_minus_x[2] = theta * ambient_y_minus_x[3] / u_norm;
+    } else {
+      // We do not use [0,0,0] here because even though the value part is
+      // a constant, the derivative part is not.
+      y_minus_x[0] = ambient_y_minus_x[1];
+      y_minus_x[1] = ambient_y_minus_x[2];
+      y_minus_x[2] = ambient_y_minus_x[3];
+    }
+    return true;
+  }
+};
+
+TEST(AutoDiffManifoldTest, QuaternionPlusPiBy2) {
+  AutoDiffManifold<QuaternionFunctor, 4, 3> manifold;
+
+  Vector x = Vector::Zero(4);
+  x[0] = 1.0;
+
+  for (int i = 0; i < 3; ++i) {
+    Vector delta = Vector::Zero(3);
+    delta[i] = M_PI / 2;
+    Vector x_plus_delta = Vector::Zero(4);
+    EXPECT_TRUE(manifold.Plus(x.data(), delta.data(), x_plus_delta.data()));
+
+    // Expect that the element corresponding to pi/2 is +/- 1. All other
+    // elements should be zero.
+    for (int j = 0; j < 4; ++j) {
+      if (i == (j - 1)) {
+        EXPECT_LT(std::abs(x_plus_delta[j]) - 1,
+                  std::numeric_limits<double>::epsilon())
+            << "\ndelta = " << delta.transpose()
+            << "\nx_plus_delta = " << x_plus_delta.transpose()
+            << "\n expected the " << j
+            << "th element of x_plus_delta to be +/- 1.";
+      } else {
+        EXPECT_LT(std::abs(x_plus_delta[j]),
+                  std::numeric_limits<double>::epsilon())
+            << "\ndelta = " << delta.transpose()
+            << "\nx_plus_delta = " << x_plus_delta.transpose()
+            << "\n expected the " << j << "th element of x_plus_delta to be 0.";
+      }
+    }
+    EXPECT_THAT_MANIFOLD_INVARIANTS_HOLD(
+        manifold, x, delta, x_plus_delta, kTolerance);
+  }
+}
+
+// Compute the expected value of Quaternion::Plus via functions in rotation.h
+// and compares it to the one computed by Quaternion::Plus.
+MATCHER_P2(QuaternionPlusIsCorrectAt, x, delta, "") {
+  // This multiplication by 2 is needed because AngleAxisToQuaternion uses
+  // |delta|/2 as the angle of rotation where as in the implementation of
+  // Quaternion for historical reasons we use |delta|.
+  const Vector two_delta = delta * 2;
+  Vector delta_q(4);
+  AngleAxisToQuaternion(two_delta.data(), delta_q.data());
+
+  Vector expected(4);
+  QuaternionProduct(delta_q.data(), x.data(), expected.data());
+  Vector actual(4);
+  EXPECT_TRUE(arg.Plus(x.data(), delta.data(), actual.data()));
+
+  const double n = (actual - expected).norm();
+  const double d = expected.norm();
+  const double diffnorm = n / d;
+  if (diffnorm > kTolerance) {
+    *result_listener << "\nx: " << x.transpose()
+                     << "\ndelta: " << delta.transpose()
+                     << "\nexpected: " << expected.transpose()
+                     << "\nactual: " << actual.transpose()
+                     << "\ndiff: " << (expected - actual).transpose()
+                     << "\ndiffnorm : " << diffnorm;
+    return false;
+  }
+  return true;
+}
+
+Vector RandomQuaternion() {
+  Vector x = Vector::Random(4);
+  x.normalize();
+  return x;
+}
+
+TEST(AutoDiffManifoldTest, QuaternionGenericDelta) {
+  AutoDiffManifold<QuaternionFunctor, 4, 3> manifold;
+  for (int trial = 0; trial < kNumTrials; ++trial) {
+    const Vector x = RandomQuaternion();
+    const Vector y = RandomQuaternion();
+    Vector delta = Vector::Random(3);
+    EXPECT_THAT(manifold, QuaternionPlusIsCorrectAt(x, delta));
+    EXPECT_THAT_MANIFOLD_INVARIANTS_HOLD(manifold, x, delta, y, kTolerance);
+  }
+}
+
+TEST(AutoDiffManifoldTest, QuaternionSmallDelta) {
+  AutoDiffManifold<QuaternionFunctor, 4, 3> manifold;
+  for (int trial = 0; trial < kNumTrials; ++trial) {
+    const Vector x = RandomQuaternion();
+    const Vector y = RandomQuaternion();
+    Vector delta = Vector::Random(3);
+    delta.normalize();
+    delta *= 1e-6;
+    EXPECT_THAT(manifold, QuaternionPlusIsCorrectAt(x, delta));
+    EXPECT_THAT_MANIFOLD_INVARIANTS_HOLD(manifold, x, delta, y, kTolerance);
+  }
+}
+
+TEST(AutoDiffManifold, QuaternionDeltaJustBelowPi) {
+  AutoDiffManifold<QuaternionFunctor, 4, 3> manifold;
+  for (int trial = 0; trial < kNumTrials; ++trial) {
+    const Vector x = RandomQuaternion();
+    const Vector y = RandomQuaternion();
+    Vector delta = Vector::Random(3);
+    delta.normalize();
+    delta *= (M_PI - 1e-6);
+    EXPECT_THAT(manifold, QuaternionPlusIsCorrectAt(x, delta));
+    EXPECT_THAT_MANIFOLD_INVARIANTS_HOLD(manifold, x, delta, y, kTolerance);
+  }
+}
+
+}  // namespace internal
+}  // namespace ceres