|  | // Ceres Solver - A fast non-linear least squares minimizer | 
|  | // Copyright 2021 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: mierle@gmail.com (Keir Mierle) | 
|  | // | 
|  | // WARNING WARNING WARNING | 
|  | // WARNING WARNING WARNING  Tiny solver is experimental and will change. | 
|  | // WARNING WARNING WARNING | 
|  | // | 
|  | // A tiny least squares solver using Levenberg-Marquardt, intended for solving | 
|  | // small dense problems with low latency and low overhead. The implementation | 
|  | // takes care to do all allocation up front, so that no memory is allocated | 
|  | // during solving. This is especially useful when solving many similar problems; | 
|  | // for example, inverse pixel distortion for every pixel on a grid. | 
|  | // | 
|  | // Note: This code has no dependencies beyond Eigen, including on other parts of | 
|  | // Ceres, so it is possible to take this file alone and put it in another | 
|  | // project without the rest of Ceres. | 
|  | // | 
|  | // Algorithm based off of: | 
|  | // | 
|  | // [1] K. Madsen, H. Nielsen, O. Tingleoff. | 
|  | //     Methods for Non-linear Least Squares Problems. | 
|  | //     http://www2.imm.dtu.dk/pubdb/views/edoc_download.php/3215/pdf/imm3215.pdf | 
|  |  | 
|  | #ifndef CERES_PUBLIC_TINY_SOLVER_H_ | 
|  | #define CERES_PUBLIC_TINY_SOLVER_H_ | 
|  |  | 
|  | #include <cassert> | 
|  | #include <cmath> | 
|  |  | 
|  | #include "Eigen/Dense" | 
|  |  | 
|  | namespace ceres { | 
|  |  | 
|  | // To use tiny solver, create a class or struct that allows computing the cost | 
|  | // function (described below). This is similar to a ceres::CostFunction, but is | 
|  | // different to enable statically allocating all memory for the solver | 
|  | // (specifically, enum sizes). Key parts are the Scalar typedef, the enums to | 
|  | // describe problem sizes (needed to remove all heap allocations), and the | 
|  | // operator() overload to evaluate the cost and (optionally) jacobians. | 
|  | // | 
|  | //   struct TinySolverCostFunctionTraits { | 
|  | //     typedef double Scalar; | 
|  | //     enum { | 
|  | //       NUM_RESIDUALS = <int> OR Eigen::Dynamic, | 
|  | //       NUM_PARAMETERS = <int> OR Eigen::Dynamic, | 
|  | //     }; | 
|  | //     bool operator()(const double* parameters, | 
|  | //                     double* residuals, | 
|  | //                     double* jacobian) const; | 
|  | // | 
|  | //     int NumResiduals() const;  -- Needed if NUM_RESIDUALS == Eigen::Dynamic. | 
|  | //     int NumParameters() const; -- Needed if NUM_PARAMETERS == Eigen::Dynamic. | 
|  | //   }; | 
|  | // | 
|  | // For operator(), the size of the objects is: | 
|  | // | 
|  | //   double* parameters -- NUM_PARAMETERS or NumParameters() | 
|  | //   double* residuals  -- NUM_RESIDUALS or NumResiduals() | 
|  | //   double* jacobian   -- NUM_RESIDUALS * NUM_PARAMETERS in column-major format | 
|  | //                         (Eigen's default); or nullptr if no jacobian | 
|  | //                         requested. | 
|  | // | 
|  | // An example (fully statically sized): | 
|  | // | 
|  | //   struct MyCostFunctionExample { | 
|  | //     typedef double Scalar; | 
|  | //     enum { | 
|  | //       NUM_RESIDUALS = 2, | 
|  | //       NUM_PARAMETERS = 3, | 
|  | //     }; | 
|  | //     bool operator()(const double* parameters, | 
|  | //                     double* residuals, | 
|  | //                     double* jacobian) const { | 
|  | //       residuals[0] = x + 2*y + 4*z; | 
|  | //       residuals[1] = y * z; | 
|  | //       if (jacobian) { | 
|  | //         jacobian[0 * 2 + 0] = 1;   // First column (x). | 
|  | //         jacobian[0 * 2 + 1] = 0; | 
|  | // | 
|  | //         jacobian[1 * 2 + 0] = 2;   // Second column (y). | 
|  | //         jacobian[1 * 2 + 1] = z; | 
|  | // | 
|  | //         jacobian[2 * 2 + 0] = 4;   // Third column (z). | 
|  | //         jacobian[2 * 2 + 1] = y; | 
|  | //       } | 
|  | //       return true; | 
|  | //     } | 
|  | //   }; | 
|  | // | 
|  | // The solver supports either statically or dynamically sized cost | 
|  | // functions. If the number of residuals is dynamic then the Function | 
|  | // must define: | 
|  | // | 
|  | //   int NumResiduals() const; | 
|  | // | 
|  | // If the number of parameters is dynamic then the Function must | 
|  | // define: | 
|  | // | 
|  | //   int NumParameters() const; | 
|  | // | 
|  | template <typename Function, | 
|  | typename LinearSolver = | 
|  | Eigen::LDLT<Eigen::Matrix<typename Function::Scalar,  // | 
|  | Function::NUM_PARAMETERS,   // | 
|  | Function::NUM_PARAMETERS>>> | 
|  | class TinySolver { | 
|  | public: | 
|  | // This class needs to have an Eigen aligned operator new as it contains | 
|  | // fixed-size Eigen types. | 
|  | EIGEN_MAKE_ALIGNED_OPERATOR_NEW | 
|  |  | 
|  | enum { | 
|  | NUM_RESIDUALS = Function::NUM_RESIDUALS, | 
|  | NUM_PARAMETERS = Function::NUM_PARAMETERS | 
|  | }; | 
|  | using Scalar = typename Function::Scalar; | 
|  | using Parameters = typename Eigen::Matrix<Scalar, NUM_PARAMETERS, 1>; | 
|  |  | 
|  | enum Status { | 
|  | // max_norm |J'(x) * f(x)| < gradient_tolerance | 
|  | GRADIENT_TOO_SMALL, | 
|  | //  ||dx|| <= parameter_tolerance * (||x|| + parameter_tolerance) | 
|  | RELATIVE_STEP_SIZE_TOO_SMALL, | 
|  | // cost_threshold > ||f(x)||^2 / 2 | 
|  | COST_TOO_SMALL, | 
|  | // num_iterations >= max_num_iterations | 
|  | HIT_MAX_ITERATIONS, | 
|  | // (new_cost - old_cost) < function_tolerance * old_cost | 
|  | COST_CHANGE_TOO_SMALL, | 
|  |  | 
|  | // TODO(sameeragarwal): Deal with numerical failures. | 
|  | }; | 
|  |  | 
|  | struct Options { | 
|  | int max_num_iterations = 50; | 
|  |  | 
|  | // max_norm |J'(x) * f(x)| < gradient_tolerance | 
|  | Scalar gradient_tolerance = 1e-10; | 
|  |  | 
|  | //  ||dx|| <= parameter_tolerance * (||x|| + parameter_tolerance) | 
|  | Scalar parameter_tolerance = 1e-8; | 
|  |  | 
|  | // (new_cost - old_cost) < function_tolerance * old_cost | 
|  | Scalar function_tolerance = 1e-6; | 
|  |  | 
|  | // cost_threshold > ||f(x)||^2 / 2 | 
|  | Scalar cost_threshold = std::numeric_limits<Scalar>::epsilon(); | 
|  |  | 
|  | Scalar initial_trust_region_radius = 1e4; | 
|  | }; | 
|  |  | 
|  | struct Summary { | 
|  | // 1/2 ||f(x_0)||^2 | 
|  | Scalar initial_cost = -1; | 
|  | // 1/2 ||f(x)||^2 | 
|  | Scalar final_cost = -1; | 
|  | // max_norm(J'f(x)) | 
|  | Scalar gradient_max_norm = -1; | 
|  | int iterations = -1; | 
|  | Status status = HIT_MAX_ITERATIONS; | 
|  | }; | 
|  |  | 
|  | bool Update(const Function& function, const Parameters& x) { | 
|  | if (!function(x.data(), residuals_.data(), jacobian_.data())) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | residuals_ = -residuals_; | 
|  |  | 
|  | // On the first iteration, compute a diagonal (Jacobi) scaling | 
|  | // matrix, which we store as a vector. | 
|  | if (summary.iterations == 0) { | 
|  | // jacobi_scaling = 1 / (1 + diagonal(J'J)) | 
|  | // | 
|  | // 1 is added to the denominator to regularize small diagonal | 
|  | // entries. | 
|  | jacobi_scaling_ = 1.0 / (1.0 + jacobian_.colwise().norm().array()); | 
|  | } | 
|  |  | 
|  | // This explicitly computes the normal equations, which is numerically | 
|  | // unstable. Nevertheless, it is often good enough and is fast. | 
|  | // | 
|  | // TODO(sameeragarwal): Refactor this to allow for DenseQR | 
|  | // factorization. | 
|  | jacobian_ = jacobian_ * jacobi_scaling_.asDiagonal(); | 
|  | jtj_ = jacobian_.transpose() * jacobian_; | 
|  | g_ = jacobian_.transpose() * residuals_; | 
|  | summary.gradient_max_norm = g_.array().abs().maxCoeff(); | 
|  | cost_ = residuals_.squaredNorm() / 2; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | const Summary& Solve(const Function& function, Parameters* x_and_min) { | 
|  | Initialize<NUM_RESIDUALS, NUM_PARAMETERS>(function); | 
|  | assert(x_and_min); | 
|  | Parameters& x = *x_and_min; | 
|  | summary = Summary(); | 
|  | summary.iterations = 0; | 
|  |  | 
|  | // TODO(sameeragarwal): Deal with failure here. | 
|  | Update(function, x); | 
|  | summary.initial_cost = cost_; | 
|  | summary.final_cost = cost_; | 
|  |  | 
|  | if (summary.gradient_max_norm < options.gradient_tolerance) { | 
|  | summary.status = GRADIENT_TOO_SMALL; | 
|  | return summary; | 
|  | } | 
|  |  | 
|  | if (cost_ < options.cost_threshold) { | 
|  | summary.status = COST_TOO_SMALL; | 
|  | return summary; | 
|  | } | 
|  |  | 
|  | Scalar u = 1.0 / options.initial_trust_region_radius; | 
|  | Scalar v = 2; | 
|  |  | 
|  | for (summary.iterations = 1; | 
|  | summary.iterations < options.max_num_iterations; | 
|  | summary.iterations++) { | 
|  | jtj_regularized_ = jtj_; | 
|  | const Scalar min_diagonal = 1e-6; | 
|  | const Scalar max_diagonal = 1e32; | 
|  | for (int i = 0; i < lm_diagonal_.rows(); ++i) { | 
|  | lm_diagonal_[i] = std::sqrt( | 
|  | u * (std::min)((std::max)(jtj_(i, i), min_diagonal), max_diagonal)); | 
|  | jtj_regularized_(i, i) += lm_diagonal_[i] * lm_diagonal_[i]; | 
|  | } | 
|  |  | 
|  | // TODO(sameeragarwal): Check for failure and deal with it. | 
|  | linear_solver_.compute(jtj_regularized_); | 
|  | lm_step_ = linear_solver_.solve(g_); | 
|  | dx_ = jacobi_scaling_.asDiagonal() * lm_step_; | 
|  |  | 
|  | // Adding parameter_tolerance to x.norm() ensures that this | 
|  | // works if x is near zero. | 
|  | const Scalar parameter_tolerance = | 
|  | options.parameter_tolerance * | 
|  | (x.norm() + options.parameter_tolerance); | 
|  | if (dx_.norm() < parameter_tolerance) { | 
|  | summary.status = RELATIVE_STEP_SIZE_TOO_SMALL; | 
|  | break; | 
|  | } | 
|  | x_new_ = x + dx_; | 
|  |  | 
|  | // TODO(keir): Add proper handling of errors from user eval of cost | 
|  | // functions. | 
|  | function(&x_new_[0], &f_x_new_[0], nullptr); | 
|  |  | 
|  | const Scalar cost_change = (2 * cost_ - f_x_new_.squaredNorm()); | 
|  | // TODO(sameeragarwal): Better more numerically stable evaluation. | 
|  | const Scalar model_cost_change = lm_step_.dot(2 * g_ - jtj_ * lm_step_); | 
|  |  | 
|  | // rho is the ratio of the actual reduction in error to the reduction | 
|  | // in error that would be obtained if the problem was linear. See [1] | 
|  | // for details. | 
|  | Scalar rho(cost_change / model_cost_change); | 
|  | if (rho > 0) { | 
|  | // Accept the Levenberg-Marquardt step because the linear | 
|  | // model fits well. | 
|  | x = x_new_; | 
|  |  | 
|  | if (std::abs(cost_change) < options.function_tolerance) { | 
|  | cost_ = f_x_new_.squaredNorm() / 2; | 
|  | summary.status = COST_CHANGE_TOO_SMALL; | 
|  | break; | 
|  | } | 
|  |  | 
|  | // TODO(sameeragarwal): Deal with failure. | 
|  | Update(function, x); | 
|  | if (summary.gradient_max_norm < options.gradient_tolerance) { | 
|  | summary.status = GRADIENT_TOO_SMALL; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (cost_ < options.cost_threshold) { | 
|  | summary.status = COST_TOO_SMALL; | 
|  | break; | 
|  | } | 
|  |  | 
|  | Scalar tmp = Scalar(2 * rho - 1); | 
|  | u = u * (std::max)(Scalar(1 / 3.), Scalar(1) - tmp * tmp * tmp); | 
|  | v = 2; | 
|  |  | 
|  | } else { | 
|  | // Reject the update because either the normal equations failed to solve | 
|  | // or the local linear model was not good (rho < 0). | 
|  |  | 
|  | // Additionally if the cost change is too small, then terminate. | 
|  | if (std::abs(cost_change) < options.function_tolerance) { | 
|  | // Terminate | 
|  | summary.status = COST_CHANGE_TOO_SMALL; | 
|  | break; | 
|  | } | 
|  |  | 
|  | // Reduce the size of the trust region. | 
|  | u *= v; | 
|  | v *= 2; | 
|  | } | 
|  | } | 
|  |  | 
|  | summary.final_cost = cost_; | 
|  | return summary; | 
|  | } | 
|  |  | 
|  | Options options; | 
|  | Summary summary; | 
|  |  | 
|  | private: | 
|  | // Preallocate everything, including temporary storage needed for solving the | 
|  | // linear system. This allows reusing the intermediate storage across solves. | 
|  | LinearSolver linear_solver_; | 
|  | Scalar cost_; | 
|  | Parameters dx_, x_new_, g_, jacobi_scaling_, lm_diagonal_, lm_step_; | 
|  | Eigen::Matrix<Scalar, NUM_RESIDUALS, 1> residuals_, f_x_new_; | 
|  | Eigen::Matrix<Scalar, NUM_RESIDUALS, NUM_PARAMETERS> jacobian_; | 
|  | Eigen::Matrix<Scalar, NUM_PARAMETERS, NUM_PARAMETERS> jtj_, jtj_regularized_; | 
|  |  | 
|  | // The following definitions are needed for template metaprogramming. | 
|  | template <bool Condition, typename T> | 
|  | struct enable_if; | 
|  |  | 
|  | template <typename T> | 
|  | struct enable_if<true, T> { | 
|  | using type = T; | 
|  | }; | 
|  |  | 
|  | // The number of parameters and residuals are dynamically sized. | 
|  | template <int R, int P> | 
|  | typename enable_if<(R == Eigen::Dynamic && P == Eigen::Dynamic), void>::type | 
|  | Initialize(const Function& function) { | 
|  | Initialize(function.NumResiduals(), function.NumParameters()); | 
|  | } | 
|  |  | 
|  | // The number of parameters is dynamically sized and the number of | 
|  | // residuals is statically sized. | 
|  | template <int R, int P> | 
|  | typename enable_if<(R == Eigen::Dynamic && P != Eigen::Dynamic), void>::type | 
|  | Initialize(const Function& function) { | 
|  | Initialize(function.NumResiduals(), P); | 
|  | } | 
|  |  | 
|  | // The number of parameters is statically sized and the number of | 
|  | // residuals is dynamically sized. | 
|  | template <int R, int P> | 
|  | typename enable_if<(R != Eigen::Dynamic && P == Eigen::Dynamic), void>::type | 
|  | Initialize(const Function& function) { | 
|  | Initialize(R, function.NumParameters()); | 
|  | } | 
|  |  | 
|  | // The number of parameters and residuals are statically sized. | 
|  | template <int R, int P> | 
|  | typename enable_if<(R != Eigen::Dynamic && P != Eigen::Dynamic), void>::type | 
|  | Initialize(const Function& /* function */) {} | 
|  |  | 
|  | void Initialize(int num_residuals, int num_parameters) { | 
|  | dx_.resize(num_parameters); | 
|  | x_new_.resize(num_parameters); | 
|  | g_.resize(num_parameters); | 
|  | jacobi_scaling_.resize(num_parameters); | 
|  | lm_diagonal_.resize(num_parameters); | 
|  | lm_step_.resize(num_parameters); | 
|  | residuals_.resize(num_residuals); | 
|  | f_x_new_.resize(num_residuals); | 
|  | jacobian_.resize(num_residuals, num_parameters); | 
|  | jtj_.resize(num_parameters, num_parameters); | 
|  | jtj_regularized_.resize(num_parameters, num_parameters); | 
|  | } | 
|  | }; | 
|  |  | 
|  | }  // namespace ceres | 
|  |  | 
|  | #endif  // CERES_PUBLIC_TINY_SOLVER_H_ |