1 // 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 "ceres/corrector.h"
32 
33 #include <algorithm>
34 #include <cmath>
35 #include <cstring>
36 #include <cstdlib>
37 #include "gtest/gtest.h"
38 #include "ceres/random.h"
39 #include "ceres/internal/eigen.h"
40 
41 namespace ceres {
42 namespace internal {
43 
44 // If rho[1] is zero, the Corrector constructor should crash.
TEST(Corrector,ZeroGradientDeathTest)45 TEST(Corrector, ZeroGradientDeathTest) {
46   const double kRho[] = {0.0, 0.0, 1.0};
47   EXPECT_DEATH_IF_SUPPORTED({Corrector c(1.0, kRho);},
48                ".*");
49 }
50 
51 // If rho[1] is negative, the Corrector constructor should crash.
TEST(Corrector,NegativeGradientDeathTest)52 TEST(Corrector, NegativeGradientDeathTest) {
53   const double kRho[] = {0.0, -0.1, 1.0};
54   EXPECT_DEATH_IF_SUPPORTED({Corrector c(1.0, kRho);},
55                ".*");
56 }
57 
TEST(Corrector,ScalarCorrection)58 TEST(Corrector, ScalarCorrection) {
59   double residuals = sqrt(3.0);
60   double jacobian = 10.0;
61   double sq_norm = residuals * residuals;
62 
63   const double kRho[] = {sq_norm, 0.1, -0.01};
64 
65   // In light of the rho'' < 0 clamping now implemented in
66   // corrector.cc, alpha = 0 whenever rho'' < 0.
67   const double kAlpha = 0.0;
68 
69   // Thus the expected value of the residual is
70   // residual[i] * sqrt(kRho[1]) / (1.0 - kAlpha).
71   const double kExpectedResidual =
72       residuals * sqrt(kRho[1]) / (1 - kAlpha);
73 
74   // The jacobian in this case will be
75   // sqrt(kRho[1]) * (1 - kAlpha) * jacobian.
76   const double kExpectedJacobian = sqrt(kRho[1]) * (1 - kAlpha) * jacobian;
77 
78   Corrector c(sq_norm, kRho);
79   c.CorrectJacobian(1.0, 1.0, &residuals, &jacobian);
80   c.CorrectResiduals(1.0, &residuals);
81 
82   ASSERT_NEAR(residuals, kExpectedResidual, 1e-6);
83   ASSERT_NEAR(kExpectedJacobian, jacobian, 1e-6);
84 }
85 
TEST(Corrector,ScalarCorrectionZeroResidual)86 TEST(Corrector, ScalarCorrectionZeroResidual) {
87   double residuals = 0.0;
88   double jacobian = 10.0;
89   double sq_norm = residuals * residuals;
90 
91   const double kRho[] = {0.0, 0.1, -0.01};
92   Corrector c(sq_norm, kRho);
93 
94   // The alpha equation is
95   // 1/2 alpha^2 - alpha + 0.0 = 0.
96   // i.e. alpha = 1.0 - sqrt(1.0).
97   //      alpha = 0.0.
98   // Thus the expected value of the residual is
99   // residual[i] * sqrt(kRho[1])
100   const double kExpectedResidual = residuals * sqrt(kRho[1]);
101 
102   // The jacobian in this case will be
103   // sqrt(kRho[1]) * jacobian.
104   const double kExpectedJacobian = sqrt(kRho[1]) * jacobian;
105 
106   c.CorrectJacobian(1, 1, &residuals, &jacobian);
107   c.CorrectResiduals(1, &residuals);
108 
109   ASSERT_NEAR(residuals, kExpectedResidual, 1e-6);
110   ASSERT_NEAR(kExpectedJacobian, jacobian, 1e-6);
111 }
112 
113 // Scaling behaviour for one dimensional functions.
TEST(Corrector,ScalarCorrectionAlphaClamped)114 TEST(Corrector, ScalarCorrectionAlphaClamped) {
115   double residuals = sqrt(3.0);
116   double jacobian = 10.0;
117   double sq_norm = residuals * residuals;
118 
119   const double kRho[] = {3, 0.1, -0.1};
120 
121   // rho[2] < 0 -> alpha = 0.0
122   const double kAlpha = 0.0;
123 
124   // Thus the expected value of the residual is
125   // residual[i] * sqrt(kRho[1]) / (1.0 - kAlpha).
126   const double kExpectedResidual =
127       residuals * sqrt(kRho[1]) / (1.0 - kAlpha);
128 
129   // The jacobian in this case will be scaled by
130   // sqrt(rho[1]) * (1 - alpha) * J.
131   const double kExpectedJacobian = sqrt(kRho[1]) *
132       (1.0 - kAlpha) * jacobian;
133 
134   Corrector c(sq_norm, kRho);
135   c.CorrectJacobian(1, 1, &residuals, &jacobian);
136   c.CorrectResiduals(1, &residuals);
137 
138   ASSERT_NEAR(residuals, kExpectedResidual, 1e-6);
139   ASSERT_NEAR(kExpectedJacobian, jacobian, 1e-6);
140 }
141 
142 // Test that the corrected multidimensional residual and jacobians
143 // match the expected values and the resulting modified normal
144 // equations match the robustified gauss newton approximation.
TEST(Corrector,MultidimensionalGaussNewtonApproximation)145 TEST(Corrector, MultidimensionalGaussNewtonApproximation) {
146   double residuals[3];
147   double jacobian[2 * 3];
148   double rho[3];
149 
150   // Eigen matrix references for linear algebra.
151   MatrixRef jac(jacobian, 3, 2);
152   VectorRef res(residuals, 3);
153 
154   // Ground truth values of the modified jacobian and residuals.
155   Matrix g_jac(3, 2);
156   Vector g_res(3);
157 
158   // Ground truth values of the robustified Gauss-Newton
159   // approximation.
160   Matrix g_hess(2, 2);
161   Vector g_grad(2);
162 
163   // Corrected hessian and gradient implied by the modified jacobian
164   // and hessians.
165   Matrix c_hess(2, 2);
166   Vector c_grad(2);
167 
168   srand(5);
169   for (int iter = 0; iter < 10000; ++iter) {
170     // Initialize the jacobian and residual.
171     for (int i = 0; i < 2 * 3; ++i)
172       jacobian[i] = RandDouble();
173     for (int i = 0; i < 3; ++i)
174       residuals[i] = RandDouble();
175 
176     const double sq_norm = res.dot(res);
177 
178     rho[0] = sq_norm;
179     rho[1] = RandDouble();
180     rho[2] = 2.0 * RandDouble() - 1.0;
181 
182     // If rho[2] > 0, then the curvature correction to the correction
183     // and the gauss newton approximation will match. Otherwise, we
184     // will clamp alpha to 0.
185 
186     const double kD = 1 + 2 * rho[2] / rho[1] * sq_norm;
187     const double kAlpha = (rho[2] > 0.0) ? 1 - sqrt(kD) : 0.0;
188 
189     // Ground truth values.
190     g_res = sqrt(rho[1]) / (1.0 - kAlpha) * res;
191     g_jac = sqrt(rho[1]) * (jac - kAlpha / sq_norm *
192                             res * res.transpose() * jac);
193 
194     g_grad = rho[1] * jac.transpose() * res;
195     g_hess = rho[1] * jac.transpose() * jac +
196         2.0 * rho[2] * jac.transpose() * res * res.transpose() * jac;
197 
198     Corrector c(sq_norm, rho);
199     c.CorrectJacobian(3, 2, residuals, jacobian);
200     c.CorrectResiduals(3, residuals);
201 
202     // Corrected gradient and hessian.
203     c_grad  = jac.transpose() * res;
204     c_hess = jac.transpose() * jac;
205 
206     ASSERT_NEAR((g_res - res).norm(), 0.0, 1e-10);
207     ASSERT_NEAR((g_jac - jac).norm(), 0.0, 1e-10);
208 
209     ASSERT_NEAR((g_grad - c_grad).norm(), 0.0, 1e-10);
210   }
211 }
212 
TEST(Corrector,MultidimensionalGaussNewtonApproximationZeroResidual)213 TEST(Corrector, MultidimensionalGaussNewtonApproximationZeroResidual) {
214   double residuals[3];
215   double jacobian[2 * 3];
216   double rho[3];
217 
218   // Eigen matrix references for linear algebra.
219   MatrixRef jac(jacobian, 3, 2);
220   VectorRef res(residuals, 3);
221 
222   // Ground truth values of the modified jacobian and residuals.
223   Matrix g_jac(3, 2);
224   Vector g_res(3);
225 
226   // Ground truth values of the robustified Gauss-Newton
227   // approximation.
228   Matrix g_hess(2, 2);
229   Vector g_grad(2);
230 
231   // Corrected hessian and gradient implied by the modified jacobian
232   // and hessians.
233   Matrix c_hess(2, 2);
234   Vector c_grad(2);
235 
236   srand(5);
237   for (int iter = 0; iter < 10000; ++iter) {
238     // Initialize the jacobian.
239     for (int i = 0; i < 2 * 3; ++i)
240       jacobian[i] = RandDouble();
241 
242     // Zero residuals
243     res.setZero();
244 
245     const double sq_norm = res.dot(res);
246 
247     rho[0] = sq_norm;
248     rho[1] = RandDouble();
249     rho[2] = 2 * RandDouble() - 1.0;
250 
251     // Ground truth values.
252     g_res = sqrt(rho[1]) * res;
253     g_jac = sqrt(rho[1]) * jac;
254 
255     g_grad = rho[1] * jac.transpose() * res;
256     g_hess = rho[1] * jac.transpose() * jac +
257         2.0 * rho[2] * jac.transpose() * res * res.transpose() * jac;
258 
259     Corrector c(sq_norm, rho);
260     c.CorrectJacobian(3, 2, residuals, jacobian);
261     c.CorrectResiduals(3, residuals);
262 
263     // Corrected gradient and hessian.
264     c_grad = jac.transpose() * res;
265     c_hess = jac.transpose() * jac;
266 
267     ASSERT_NEAR((g_res - res).norm(), 0.0, 1e-10);
268     ASSERT_NEAR((g_jac - jac).norm(), 0.0, 1e-10);
269 
270     ASSERT_NEAR((g_grad - c_grad).norm(), 0.0, 1e-10);
271     ASSERT_NEAR((g_hess - c_hess).norm(), 0.0, 1e-10);
272   }
273 }
274 
275 }  // namespace internal
276 }  // namespace ceres
277