| // 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 |