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