autodiff_manifold_test.cc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. // Ceres Solver - A fast non-linear least squares minimizer
  2. // Copyright 2023 Google Inc. All rights reserved.
  3. // http://ceres-solver.org/
  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. #include "ceres/autodiff_manifold.h"
  31. #include <cmath>
  32. #include "ceres/constants.h"
  33. #include "ceres/manifold.h"
  34. #include "ceres/manifold_test_utils.h"
  35. #include "ceres/rotation.h"
  36. #include "gtest/gtest.h"
  37. namespace ceres::internal {
  38. namespace {
  39. constexpr int kNumTrials = 1000;
  40. constexpr double kTolerance = 1e-9;
  41. Vector RandomQuaternion() {
  42. Vector x = Vector::Random(4);
  43. x.normalize();
  44. return x;
  45. }
  46. } // namespace
  47. struct EuclideanFunctor {
  48. template <typename T>
  49. bool Plus(const T* x, const T* delta, T* x_plus_delta) const {
  50. for (int i = 0; i < 3; ++i) {
  51. x_plus_delta[i] = x[i] + delta[i];
  52. }
  53. return true;
  54. }
  55. template <typename T>
  56. bool Minus(const T* y, const T* x, T* y_minus_x) const {
  57. for (int i = 0; i < 3; ++i) {
  58. y_minus_x[i] = y[i] - x[i];
  59. }
  60. return true;
  61. }
  62. };
  63. TEST(AutoDiffLManifoldTest, EuclideanManifold) {
  64. AutoDiffManifold<EuclideanFunctor, 3, 3> manifold;
  65. EXPECT_EQ(manifold.AmbientSize(), 3);
  66. EXPECT_EQ(manifold.TangentSize(), 3);
  67. for (int trial = 0; trial < kNumTrials; ++trial) {
  68. const Vector x = Vector::Random(manifold.AmbientSize());
  69. const Vector y = Vector::Random(manifold.AmbientSize());
  70. Vector delta = Vector::Random(manifold.TangentSize());
  71. Vector x_plus_delta = Vector::Zero(manifold.AmbientSize());
  72. manifold.Plus(x.data(), delta.data(), x_plus_delta.data());
  73. EXPECT_NEAR((x_plus_delta - x - delta).norm() / (x + delta).norm(),
  74. 0.0,
  75. kTolerance);
  76. EXPECT_THAT_MANIFOLD_INVARIANTS_HOLD(manifold, x, delta, y, kTolerance);
  77. }
  78. }
  79. struct ScaledFunctor {
  80. explicit ScaledFunctor(const double s) : s(s) {}
  81. template <typename T>
  82. bool Plus(const T* x, const T* delta, T* x_plus_delta) const {
  83. for (int i = 0; i < 3; ++i) {
  84. x_plus_delta[i] = x[i] + s * delta[i];
  85. }
  86. return true;
  87. }
  88. template <typename T>
  89. bool Minus(const T* y, const T* x, T* y_minus_x) const {
  90. for (int i = 0; i < 3; ++i) {
  91. y_minus_x[i] = (y[i] - x[i]) / s;
  92. }
  93. return true;
  94. }
  95. const double s;
  96. };
  97. TEST(AutoDiffManifoldTest, ScaledManifold) {
  98. constexpr double kScale = 1.2342;
  99. AutoDiffManifold<ScaledFunctor, 3, 3> manifold(new ScaledFunctor(kScale));
  100. EXPECT_EQ(manifold.AmbientSize(), 3);
  101. EXPECT_EQ(manifold.TangentSize(), 3);
  102. for (int trial = 0; trial < kNumTrials; ++trial) {
  103. const Vector x = Vector::Random(manifold.AmbientSize());
  104. const Vector y = Vector::Random(manifold.AmbientSize());
  105. Vector delta = Vector::Random(manifold.TangentSize());
  106. Vector x_plus_delta = Vector::Zero(manifold.AmbientSize());
  107. manifold.Plus(x.data(), delta.data(), x_plus_delta.data());
  108. EXPECT_NEAR((x_plus_delta - x - delta * kScale).norm() /
  109. (x + delta * kScale).norm(),
  110. 0.0,
  111. kTolerance);
  112. EXPECT_THAT_MANIFOLD_INVARIANTS_HOLD(manifold, x, delta, y, kTolerance);
  113. }
  114. }
  115. // Templated functor that implements the Plus and Minus operations on the
  116. // Quaternion manifold.
  117. struct QuaternionFunctor {
  118. template <typename T>
  119. bool Plus(const T* x, const T* delta, T* x_plus_delta) const {
  120. const T squared_norm_delta =
  121. delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2];
  122. T q_delta[4];
  123. if (squared_norm_delta > T(0.0)) {
  124. T norm_delta = sqrt(squared_norm_delta);
  125. const T sin_delta_by_delta = sin(norm_delta) / norm_delta;
  126. q_delta[0] = cos(norm_delta);
  127. q_delta[1] = sin_delta_by_delta * delta[0];
  128. q_delta[2] = sin_delta_by_delta * delta[1];
  129. q_delta[3] = sin_delta_by_delta * delta[2];
  130. } else {
  131. // We do not just use q_delta = [1,0,0,0] here because that is a
  132. // constant and when used for automatic differentiation will
  133. // lead to a zero derivative. Instead we take a first order
  134. // approximation and evaluate it at zero.
  135. q_delta[0] = T(1.0);
  136. q_delta[1] = delta[0];
  137. q_delta[2] = delta[1];
  138. q_delta[3] = delta[2];
  139. }
  140. QuaternionProduct(q_delta, x, x_plus_delta);
  141. return true;
  142. }
  143. template <typename T>
  144. bool Minus(const T* y, const T* x, T* y_minus_x) const {
  145. T minus_x[4] = {x[0], -x[1], -x[2], -x[3]};
  146. T ambient_y_minus_x[4];
  147. QuaternionProduct(y, minus_x, ambient_y_minus_x);
  148. T u_norm = sqrt(ambient_y_minus_x[1] * ambient_y_minus_x[1] +
  149. ambient_y_minus_x[2] * ambient_y_minus_x[2] +
  150. ambient_y_minus_x[3] * ambient_y_minus_x[3]);
  151. if (u_norm > 0.0) {
  152. T theta = atan2(u_norm, ambient_y_minus_x[0]);
  153. y_minus_x[0] = theta * ambient_y_minus_x[1] / u_norm;
  154. y_minus_x[1] = theta * ambient_y_minus_x[2] / u_norm;
  155. y_minus_x[2] = theta * ambient_y_minus_x[3] / u_norm;
  156. } else {
  157. // We do not use [0,0,0] here because even though the value part is
  158. // a constant, the derivative part is not.
  159. y_minus_x[0] = ambient_y_minus_x[1];
  160. y_minus_x[1] = ambient_y_minus_x[2];
  161. y_minus_x[2] = ambient_y_minus_x[3];
  162. }
  163. return true;
  164. }
  165. };
  166. TEST(AutoDiffManifoldTest, QuaternionPlusPiBy2) {
  167. AutoDiffManifold<QuaternionFunctor, 4, 3> manifold;
  168. Vector x = Vector::Zero(4);
  169. x[0] = 1.0;
  170. for (int i = 0; i < 3; ++i) {
  171. Vector delta = Vector::Zero(3);
  172. delta[i] = constants::pi / 2;
  173. Vector x_plus_delta = Vector::Zero(4);
  174. EXPECT_TRUE(manifold.Plus(x.data(), delta.data(), x_plus_delta.data()));
  175. // Expect that the element corresponding to pi/2 is +/- 1. All other
  176. // elements should be zero.
  177. for (int j = 0; j < 4; ++j) {
  178. if (i == (j - 1)) {
  179. EXPECT_LT(std::abs(x_plus_delta[j]) - 1,
  180. std::numeric_limits<double>::epsilon())
  181. << "\ndelta = " << delta.transpose()
  182. << "\nx_plus_delta = " << x_plus_delta.transpose()
  183. << "\n expected the " << j
  184. << "th element of x_plus_delta to be +/- 1.";
  185. } else {
  186. EXPECT_LT(std::abs(x_plus_delta[j]),
  187. std::numeric_limits<double>::epsilon())
  188. << "\ndelta = " << delta.transpose()
  189. << "\nx_plus_delta = " << x_plus_delta.transpose()
  190. << "\n expected the " << j << "th element of x_plus_delta to be 0.";
  191. }
  192. }
  193. EXPECT_THAT_MANIFOLD_INVARIANTS_HOLD(
  194. manifold, x, delta, x_plus_delta, kTolerance);
  195. }
  196. }
  197. // Compute the expected value of Quaternion::Plus via functions in rotation.h
  198. // and compares it to the one computed by Quaternion::Plus.
  199. MATCHER_P2(QuaternionPlusIsCorrectAt, x, delta, "") {
  200. // This multiplication by 2 is needed because AngleAxisToQuaternion uses
  201. // |delta|/2 as the angle of rotation where as in the implementation of
  202. // Quaternion for historical reasons we use |delta|.
  203. const Vector two_delta = delta * 2;
  204. Vector delta_q(4);
  205. AngleAxisToQuaternion(two_delta.data(), delta_q.data());
  206. Vector expected(4);
  207. QuaternionProduct(delta_q.data(), x.data(), expected.data());
  208. Vector actual(4);
  209. EXPECT_TRUE(arg.Plus(x.data(), delta.data(), actual.data()));
  210. const double n = (actual - expected).norm();
  211. const double d = expected.norm();
  212. const double diffnorm = n / d;
  213. if (diffnorm > kTolerance) {
  214. *result_listener << "\nx: " << x.transpose()
  215. << "\ndelta: " << delta.transpose()
  216. << "\nexpected: " << expected.transpose()
  217. << "\nactual: " << actual.transpose()
  218. << "\ndiff: " << (expected - actual).transpose()
  219. << "\ndiffnorm : " << diffnorm;
  220. return false;
  221. }
  222. return true;
  223. }
  224. TEST(AutoDiffManifoldTest, QuaternionGenericDelta) {
  225. AutoDiffManifold<QuaternionFunctor, 4, 3> manifold;
  226. for (int trial = 0; trial < kNumTrials; ++trial) {
  227. const Vector x = RandomQuaternion();
  228. const Vector y = RandomQuaternion();
  229. Vector delta = Vector::Random(3);
  230. EXPECT_THAT(manifold, QuaternionPlusIsCorrectAt(x, delta));
  231. EXPECT_THAT_MANIFOLD_INVARIANTS_HOLD(manifold, x, delta, y, kTolerance);
  232. }
  233. }
  234. TEST(AutoDiffManifoldTest, QuaternionSmallDelta) {
  235. AutoDiffManifold<QuaternionFunctor, 4, 3> manifold;
  236. for (int trial = 0; trial < kNumTrials; ++trial) {
  237. const Vector x = RandomQuaternion();
  238. const Vector y = RandomQuaternion();
  239. Vector delta = Vector::Random(3);
  240. delta.normalize();
  241. delta *= 1e-6;
  242. EXPECT_THAT(manifold, QuaternionPlusIsCorrectAt(x, delta));
  243. EXPECT_THAT_MANIFOLD_INVARIANTS_HOLD(manifold, x, delta, y, kTolerance);
  244. }
  245. }
  246. TEST(AutoDiffManifold, QuaternionDeltaJustBelowPi) {
  247. AutoDiffManifold<QuaternionFunctor, 4, 3> manifold;
  248. for (int trial = 0; trial < kNumTrials; ++trial) {
  249. const Vector x = RandomQuaternion();
  250. const Vector y = RandomQuaternion();
  251. Vector delta = Vector::Random(3);
  252. delta.normalize();
  253. delta *= (constants::pi - 1e-6);
  254. EXPECT_THAT(manifold, QuaternionPlusIsCorrectAt(x, delta));
  255. EXPECT_THAT_MANIFOLD_INVARIANTS_HOLD(manifold, x, delta, y, kTolerance);
  256. }
  257. }
  258. } // namespace ceres::internal