| // Ceres Solver - A fast non-linear least squares minimizer | 
 | // Copyright 2023 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/corrector.h" | 
 |  | 
 | #include <algorithm> | 
 | #include <cmath> | 
 | #include <cstring> | 
 | #include <random> | 
 |  | 
 | #include "ceres/internal/eigen.h" | 
 | #include "gtest/gtest.h" | 
 |  | 
 | namespace ceres { | 
 | namespace internal { | 
 |  | 
 | // If rho[1] is zero, the Corrector constructor should crash. | 
 | TEST(Corrector, ZeroGradientDeathTest) { | 
 |   const double kRho[] = {0.0, 0.0, 1.0}; | 
 |   EXPECT_DEATH_IF_SUPPORTED({ Corrector c(1.0, kRho); }, ".*"); | 
 | } | 
 |  | 
 | // If rho[1] is negative, the Corrector constructor should crash. | 
 | TEST(Corrector, NegativeGradientDeathTest) { | 
 |   const double kRho[] = {0.0, -0.1, 1.0}; | 
 |   EXPECT_DEATH_IF_SUPPORTED({ Corrector c(1.0, kRho); }, ".*"); | 
 | } | 
 |  | 
 | TEST(Corrector, ScalarCorrection) { | 
 |   double residuals = sqrt(3.0); | 
 |   double jacobian = 10.0; | 
 |   double sq_norm = residuals * residuals; | 
 |  | 
 |   const double kRho[] = {sq_norm, 0.1, -0.01}; | 
 |  | 
 |   // In light of the rho'' < 0 clamping now implemented in | 
 |   // corrector.cc, alpha = 0 whenever rho'' < 0. | 
 |   const double kAlpha = 0.0; | 
 |  | 
 |   // Thus the expected value of the residual is | 
 |   // residual[i] * sqrt(kRho[1]) / (1.0 - kAlpha). | 
 |   const double kExpectedResidual = residuals * sqrt(kRho[1]) / (1 - kAlpha); | 
 |  | 
 |   // The jacobian in this case will be | 
 |   // sqrt(kRho[1]) * (1 - kAlpha) * jacobian. | 
 |   const double kExpectedJacobian = sqrt(kRho[1]) * (1 - kAlpha) * jacobian; | 
 |  | 
 |   Corrector c(sq_norm, kRho); | 
 |   c.CorrectJacobian(1.0, 1.0, &residuals, &jacobian); | 
 |   c.CorrectResiduals(1.0, &residuals); | 
 |  | 
 |   ASSERT_NEAR(residuals, kExpectedResidual, 1e-6); | 
 |   ASSERT_NEAR(kExpectedJacobian, jacobian, 1e-6); | 
 | } | 
 |  | 
 | TEST(Corrector, ScalarCorrectionZeroResidual) { | 
 |   double residuals = 0.0; | 
 |   double jacobian = 10.0; | 
 |   double sq_norm = residuals * residuals; | 
 |  | 
 |   const double kRho[] = {0.0, 0.1, -0.01}; | 
 |   Corrector c(sq_norm, kRho); | 
 |  | 
 |   // The alpha equation is | 
 |   // 1/2 alpha^2 - alpha + 0.0 = 0. | 
 |   // i.e. alpha = 1.0 - sqrt(1.0). | 
 |   //      alpha = 0.0. | 
 |   // Thus the expected value of the residual is | 
 |   // residual[i] * sqrt(kRho[1]) | 
 |   const double kExpectedResidual = residuals * sqrt(kRho[1]); | 
 |  | 
 |   // The jacobian in this case will be | 
 |   // sqrt(kRho[1]) * jacobian. | 
 |   const double kExpectedJacobian = sqrt(kRho[1]) * jacobian; | 
 |  | 
 |   c.CorrectJacobian(1, 1, &residuals, &jacobian); | 
 |   c.CorrectResiduals(1, &residuals); | 
 |  | 
 |   ASSERT_NEAR(residuals, kExpectedResidual, 1e-6); | 
 |   ASSERT_NEAR(kExpectedJacobian, jacobian, 1e-6); | 
 | } | 
 |  | 
 | // Scaling behaviour for one dimensional functions. | 
 | TEST(Corrector, ScalarCorrectionAlphaClamped) { | 
 |   double residuals = sqrt(3.0); | 
 |   double jacobian = 10.0; | 
 |   double sq_norm = residuals * residuals; | 
 |  | 
 |   const double kRho[] = {3, 0.1, -0.1}; | 
 |  | 
 |   // rho[2] < 0 -> alpha = 0.0 | 
 |   const double kAlpha = 0.0; | 
 |  | 
 |   // Thus the expected value of the residual is | 
 |   // residual[i] * sqrt(kRho[1]) / (1.0 - kAlpha). | 
 |   const double kExpectedResidual = residuals * sqrt(kRho[1]) / (1.0 - kAlpha); | 
 |  | 
 |   // The jacobian in this case will be scaled by | 
 |   // sqrt(rho[1]) * (1 - alpha) * J. | 
 |   const double kExpectedJacobian = sqrt(kRho[1]) * (1.0 - kAlpha) * jacobian; | 
 |  | 
 |   Corrector c(sq_norm, kRho); | 
 |   c.CorrectJacobian(1, 1, &residuals, &jacobian); | 
 |   c.CorrectResiduals(1, &residuals); | 
 |  | 
 |   ASSERT_NEAR(residuals, kExpectedResidual, 1e-6); | 
 |   ASSERT_NEAR(kExpectedJacobian, jacobian, 1e-6); | 
 | } | 
 |  | 
 | // Test that the corrected multidimensional residual and jacobians | 
 | // match the expected values and the resulting modified normal | 
 | // equations match the robustified gauss newton approximation. | 
 | TEST(Corrector, MultidimensionalGaussNewtonApproximation) { | 
 |   double residuals[3]; | 
 |   double jacobian[2 * 3]; | 
 |   double rho[3]; | 
 |  | 
 |   // Eigen matrix references for linear algebra. | 
 |   MatrixRef jac(jacobian, 3, 2); | 
 |   VectorRef res(residuals, 3); | 
 |  | 
 |   // Ground truth values of the modified jacobian and residuals. | 
 |   Matrix g_jac(3, 2); | 
 |   Vector g_res(3); | 
 |  | 
 |   // Ground truth values of the robustified Gauss-Newton | 
 |   // approximation. | 
 |   Matrix g_hess(2, 2); | 
 |   Vector g_grad(2); | 
 |  | 
 |   // Corrected hessian and gradient implied by the modified jacobian | 
 |   // and hessians. | 
 |   Matrix c_hess(2, 2); | 
 |   Vector c_grad(2); | 
 |   std::mt19937 prng; | 
 |   std::uniform_real_distribution<double> uniform01(0.0, 1.0); | 
 |   for (int iter = 0; iter < 10000; ++iter) { | 
 |     // Initialize the jacobian and residual. | 
 |     for (double& jacobian_entry : jacobian) jacobian_entry = uniform01(prng); | 
 |     for (double& residual : residuals) residual = uniform01(prng); | 
 |  | 
 |     const double sq_norm = res.dot(res); | 
 |  | 
 |     rho[0] = sq_norm; | 
 |     rho[1] = uniform01(prng); | 
 |     rho[2] = uniform01( | 
 |         prng, std::uniform_real_distribution<double>::param_type(-1, 1)); | 
 |  | 
 |     // If rho[2] > 0, then the curvature correction to the correction | 
 |     // and the gauss newton approximation will match. Otherwise, we | 
 |     // will clamp alpha to 0. | 
 |  | 
 |     const double kD = 1 + 2 * rho[2] / rho[1] * sq_norm; | 
 |     const double kAlpha = (rho[2] > 0.0) ? 1 - sqrt(kD) : 0.0; | 
 |  | 
 |     // Ground truth values. | 
 |     g_res = sqrt(rho[1]) / (1.0 - kAlpha) * res; | 
 |     g_jac = | 
 |         sqrt(rho[1]) * (jac - kAlpha / sq_norm * res * res.transpose() * jac); | 
 |  | 
 |     g_grad = rho[1] * jac.transpose() * res; | 
 |     g_hess = rho[1] * jac.transpose() * jac + | 
 |              2.0 * rho[2] * jac.transpose() * res * res.transpose() * jac; | 
 |  | 
 |     Corrector c(sq_norm, rho); | 
 |     c.CorrectJacobian(3, 2, residuals, jacobian); | 
 |     c.CorrectResiduals(3, residuals); | 
 |  | 
 |     // Corrected gradient and hessian. | 
 |     c_grad = jac.transpose() * res; | 
 |     c_hess = jac.transpose() * jac; | 
 |  | 
 |     ASSERT_NEAR((g_res - res).norm(), 0.0, 1e-10); | 
 |     ASSERT_NEAR((g_jac - jac).norm(), 0.0, 1e-10); | 
 |  | 
 |     ASSERT_NEAR((g_grad - c_grad).norm(), 0.0, 1e-10); | 
 |   } | 
 | } | 
 |  | 
 | TEST(Corrector, MultidimensionalGaussNewtonApproximationZeroResidual) { | 
 |   double residuals[3]; | 
 |   double jacobian[2 * 3]; | 
 |   double rho[3]; | 
 |  | 
 |   // Eigen matrix references for linear algebra. | 
 |   MatrixRef jac(jacobian, 3, 2); | 
 |   VectorRef res(residuals, 3); | 
 |  | 
 |   // Ground truth values of the modified jacobian and residuals. | 
 |   Matrix g_jac(3, 2); | 
 |   Vector g_res(3); | 
 |  | 
 |   // Ground truth values of the robustified Gauss-Newton | 
 |   // approximation. | 
 |   Matrix g_hess(2, 2); | 
 |   Vector g_grad(2); | 
 |  | 
 |   // Corrected hessian and gradient implied by the modified jacobian | 
 |   // and hessians. | 
 |   Matrix c_hess(2, 2); | 
 |   Vector c_grad(2); | 
 |  | 
 |   std::mt19937 prng; | 
 |   std::uniform_real_distribution<double> uniform01(0.0, 1.0); | 
 |   for (int iter = 0; iter < 10000; ++iter) { | 
 |     // Initialize the jacobian. | 
 |     for (double& jacobian_entry : jacobian) jacobian_entry = uniform01(prng); | 
 |  | 
 |     // Zero residuals | 
 |     res.setZero(); | 
 |  | 
 |     const double sq_norm = res.dot(res); | 
 |  | 
 |     rho[0] = sq_norm; | 
 |     rho[1] = uniform01(prng); | 
 |     rho[2] = uniform01( | 
 |         prng, std::uniform_real_distribution<double>::param_type(-1, 1)); | 
 |  | 
 |     // Ground truth values. | 
 |     g_res = sqrt(rho[1]) * res; | 
 |     g_jac = sqrt(rho[1]) * jac; | 
 |  | 
 |     g_grad = rho[1] * jac.transpose() * res; | 
 |     g_hess = rho[1] * jac.transpose() * jac + | 
 |              2.0 * rho[2] * jac.transpose() * res * res.transpose() * jac; | 
 |  | 
 |     Corrector c(sq_norm, rho); | 
 |     c.CorrectJacobian(3, 2, residuals, jacobian); | 
 |     c.CorrectResiduals(3, residuals); | 
 |  | 
 |     // Corrected gradient and hessian. | 
 |     c_grad = jac.transpose() * res; | 
 |     c_hess = jac.transpose() * jac; | 
 |  | 
 |     ASSERT_NEAR((g_res - res).norm(), 0.0, 1e-10); | 
 |     ASSERT_NEAR((g_jac - jac).norm(), 0.0, 1e-10); | 
 |  | 
 |     ASSERT_NEAR((g_grad - c_grad).norm(), 0.0, 1e-10); | 
 |     ASSERT_NEAR((g_hess - c_hess).norm(), 0.0, 1e-10); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace internal | 
 | }  // namespace ceres |