Austin Schuh | 70cc955 | 2019-01-21 19:46:48 -0800 | [diff] [blame] | 1 | // Ceres Solver - A fast non-linear least squares minimizer |
Austin Schuh | 1d1e6ea | 2020-12-23 21:56:30 -0800 | [diff] [blame^] | 2 | // Copyright 2019 Google Inc. All rights reserved. |
Austin Schuh | 70cc955 | 2019-01-21 19:46:48 -0800 | [diff] [blame] | 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: mierle@gmail.com (Keir Mierle) |
| 30 | // |
| 31 | // WARNING WARNING WARNING |
| 32 | // WARNING WARNING WARNING Tiny solver is experimental and will change. |
| 33 | // WARNING WARNING WARNING |
| 34 | // |
| 35 | // A tiny least squares solver using Levenberg-Marquardt, intended for solving |
| 36 | // small dense problems with low latency and low overhead. The implementation |
| 37 | // takes care to do all allocation up front, so that no memory is allocated |
| 38 | // during solving. This is especially useful when solving many similar problems; |
| 39 | // for example, inverse pixel distortion for every pixel on a grid. |
| 40 | // |
| 41 | // Note: This code has no dependencies beyond Eigen, including on other parts of |
| 42 | // Ceres, so it is possible to take this file alone and put it in another |
| 43 | // project without the rest of Ceres. |
| 44 | // |
| 45 | // Algorithm based off of: |
| 46 | // |
| 47 | // [1] K. Madsen, H. Nielsen, O. Tingleoff. |
| 48 | // Methods for Non-linear Least Squares Problems. |
| 49 | // http://www2.imm.dtu.dk/pubdb/views/edoc_download.php/3215/pdf/imm3215.pdf |
| 50 | |
| 51 | #ifndef CERES_PUBLIC_TINY_SOLVER_H_ |
| 52 | #define CERES_PUBLIC_TINY_SOLVER_H_ |
| 53 | |
| 54 | #include <cassert> |
| 55 | #include <cmath> |
| 56 | |
| 57 | #include "Eigen/Dense" |
| 58 | |
| 59 | namespace ceres { |
| 60 | |
| 61 | // To use tiny solver, create a class or struct that allows computing the cost |
| 62 | // function (described below). This is similar to a ceres::CostFunction, but is |
| 63 | // different to enable statically allocating all memory for the solver |
| 64 | // (specifically, enum sizes). Key parts are the Scalar typedef, the enums to |
| 65 | // describe problem sizes (needed to remove all heap allocations), and the |
| 66 | // operator() overload to evaluate the cost and (optionally) jacobians. |
| 67 | // |
| 68 | // struct TinySolverCostFunctionTraits { |
| 69 | // typedef double Scalar; |
| 70 | // enum { |
| 71 | // NUM_RESIDUALS = <int> OR Eigen::Dynamic, |
| 72 | // NUM_PARAMETERS = <int> OR Eigen::Dynamic, |
| 73 | // }; |
| 74 | // bool operator()(const double* parameters, |
| 75 | // double* residuals, |
| 76 | // double* jacobian) const; |
| 77 | // |
| 78 | // int NumResiduals() const; -- Needed if NUM_RESIDUALS == Eigen::Dynamic. |
| 79 | // int NumParameters() const; -- Needed if NUM_PARAMETERS == Eigen::Dynamic. |
| 80 | // }; |
| 81 | // |
| 82 | // For operator(), the size of the objects is: |
| 83 | // |
| 84 | // double* parameters -- NUM_PARAMETERS or NumParameters() |
| 85 | // double* residuals -- NUM_RESIDUALS or NumResiduals() |
| 86 | // double* jacobian -- NUM_RESIDUALS * NUM_PARAMETERS in column-major format |
| 87 | // (Eigen's default); or NULL if no jacobian requested. |
| 88 | // |
| 89 | // An example (fully statically sized): |
| 90 | // |
| 91 | // struct MyCostFunctionExample { |
| 92 | // typedef double Scalar; |
| 93 | // enum { |
| 94 | // NUM_RESIDUALS = 2, |
| 95 | // NUM_PARAMETERS = 3, |
| 96 | // }; |
| 97 | // bool operator()(const double* parameters, |
| 98 | // double* residuals, |
| 99 | // double* jacobian) const { |
| 100 | // residuals[0] = x + 2*y + 4*z; |
| 101 | // residuals[1] = y * z; |
| 102 | // if (jacobian) { |
| 103 | // jacobian[0 * 2 + 0] = 1; // First column (x). |
| 104 | // jacobian[0 * 2 + 1] = 0; |
| 105 | // |
| 106 | // jacobian[1 * 2 + 0] = 2; // Second column (y). |
| 107 | // jacobian[1 * 2 + 1] = z; |
| 108 | // |
| 109 | // jacobian[2 * 2 + 0] = 4; // Third column (z). |
| 110 | // jacobian[2 * 2 + 1] = y; |
| 111 | // } |
| 112 | // return true; |
| 113 | // } |
| 114 | // }; |
| 115 | // |
| 116 | // The solver supports either statically or dynamically sized cost |
| 117 | // functions. If the number of residuals is dynamic then the Function |
| 118 | // must define: |
| 119 | // |
| 120 | // int NumResiduals() const; |
| 121 | // |
| 122 | // If the number of parameters is dynamic then the Function must |
| 123 | // define: |
| 124 | // |
| 125 | // int NumParameters() const; |
| 126 | // |
Austin Schuh | 1d1e6ea | 2020-12-23 21:56:30 -0800 | [diff] [blame^] | 127 | template <typename Function, |
| 128 | typename LinearSolver = |
| 129 | Eigen::LDLT<Eigen::Matrix<typename Function::Scalar, |
| 130 | Function::NUM_PARAMETERS, |
| 131 | Function::NUM_PARAMETERS>>> |
Austin Schuh | 70cc955 | 2019-01-21 19:46:48 -0800 | [diff] [blame] | 132 | class TinySolver { |
| 133 | public: |
Austin Schuh | 1d1e6ea | 2020-12-23 21:56:30 -0800 | [diff] [blame^] | 134 | // This class needs to have an Eigen aligned operator new as it contains |
| 135 | // fixed-size Eigen types. |
| 136 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW |
| 137 | |
Austin Schuh | 70cc955 | 2019-01-21 19:46:48 -0800 | [diff] [blame] | 138 | enum { |
| 139 | NUM_RESIDUALS = Function::NUM_RESIDUALS, |
| 140 | NUM_PARAMETERS = Function::NUM_PARAMETERS |
| 141 | }; |
| 142 | typedef typename Function::Scalar Scalar; |
| 143 | typedef typename Eigen::Matrix<Scalar, NUM_PARAMETERS, 1> Parameters; |
| 144 | |
| 145 | enum Status { |
| 146 | GRADIENT_TOO_SMALL, // eps > max(J'*f(x)) |
| 147 | RELATIVE_STEP_SIZE_TOO_SMALL, // eps > ||dx|| / (||x|| + eps) |
| 148 | COST_TOO_SMALL, // eps > ||f(x)||^2 / 2 |
| 149 | HIT_MAX_ITERATIONS, |
| 150 | |
| 151 | // TODO(sameeragarwal): Deal with numerical failures. |
| 152 | }; |
| 153 | |
| 154 | struct Options { |
| 155 | Scalar gradient_tolerance = 1e-10; // eps > max(J'*f(x)) |
| 156 | Scalar parameter_tolerance = 1e-8; // eps > ||dx|| / ||x|| |
| 157 | Scalar cost_threshold = // eps > ||f(x)|| |
| 158 | std::numeric_limits<Scalar>::epsilon(); |
| 159 | Scalar initial_trust_region_radius = 1e4; |
| 160 | int max_num_iterations = 50; |
| 161 | }; |
| 162 | |
| 163 | struct Summary { |
| 164 | Scalar initial_cost = -1; // 1/2 ||f(x)||^2 |
| 165 | Scalar final_cost = -1; // 1/2 ||f(x)||^2 |
| 166 | Scalar gradient_max_norm = -1; // max(J'f(x)) |
| 167 | int iterations = -1; |
| 168 | Status status = HIT_MAX_ITERATIONS; |
| 169 | }; |
| 170 | |
Austin Schuh | 1d1e6ea | 2020-12-23 21:56:30 -0800 | [diff] [blame^] | 171 | bool Update(const Function& function, const Parameters& x) { |
Austin Schuh | 70cc955 | 2019-01-21 19:46:48 -0800 | [diff] [blame] | 172 | if (!function(x.data(), error_.data(), jacobian_.data())) { |
| 173 | return false; |
| 174 | } |
| 175 | |
| 176 | error_ = -error_; |
| 177 | |
| 178 | // On the first iteration, compute a diagonal (Jacobi) scaling |
| 179 | // matrix, which we store as a vector. |
| 180 | if (summary.iterations == 0) { |
| 181 | // jacobi_scaling = 1 / (1 + diagonal(J'J)) |
| 182 | // |
| 183 | // 1 is added to the denominator to regularize small diagonal |
| 184 | // entries. |
| 185 | jacobi_scaling_ = 1.0 / (1.0 + jacobian_.colwise().norm().array()); |
| 186 | } |
| 187 | |
| 188 | // This explicitly computes the normal equations, which is numerically |
| 189 | // unstable. Nevertheless, it is often good enough and is fast. |
| 190 | // |
| 191 | // TODO(sameeragarwal): Refactor this to allow for DenseQR |
| 192 | // factorization. |
| 193 | jacobian_ = jacobian_ * jacobi_scaling_.asDiagonal(); |
| 194 | jtj_ = jacobian_.transpose() * jacobian_; |
| 195 | g_ = jacobian_.transpose() * error_; |
| 196 | summary.gradient_max_norm = g_.array().abs().maxCoeff(); |
| 197 | cost_ = error_.squaredNorm() / 2; |
| 198 | return true; |
| 199 | } |
| 200 | |
| 201 | const Summary& Solve(const Function& function, Parameters* x_and_min) { |
| 202 | Initialize<NUM_RESIDUALS, NUM_PARAMETERS>(function); |
| 203 | assert(x_and_min); |
| 204 | Parameters& x = *x_and_min; |
| 205 | summary = Summary(); |
| 206 | summary.iterations = 0; |
| 207 | |
| 208 | // TODO(sameeragarwal): Deal with failure here. |
| 209 | Update(function, x); |
| 210 | summary.initial_cost = cost_; |
| 211 | summary.final_cost = cost_; |
| 212 | |
| 213 | if (summary.gradient_max_norm < options.gradient_tolerance) { |
| 214 | summary.status = GRADIENT_TOO_SMALL; |
| 215 | return summary; |
| 216 | } |
| 217 | |
| 218 | if (cost_ < options.cost_threshold) { |
| 219 | summary.status = COST_TOO_SMALL; |
| 220 | return summary; |
| 221 | } |
| 222 | |
| 223 | Scalar u = 1.0 / options.initial_trust_region_radius; |
| 224 | Scalar v = 2; |
| 225 | |
| 226 | for (summary.iterations = 1; |
| 227 | summary.iterations < options.max_num_iterations; |
| 228 | summary.iterations++) { |
| 229 | jtj_regularized_ = jtj_; |
| 230 | const Scalar min_diagonal = 1e-6; |
| 231 | const Scalar max_diagonal = 1e32; |
| 232 | for (int i = 0; i < lm_diagonal_.rows(); ++i) { |
| 233 | lm_diagonal_[i] = std::sqrt( |
| 234 | u * std::min(std::max(jtj_(i, i), min_diagonal), max_diagonal)); |
| 235 | jtj_regularized_(i, i) += lm_diagonal_[i] * lm_diagonal_[i]; |
| 236 | } |
| 237 | |
| 238 | // TODO(sameeragarwal): Check for failure and deal with it. |
| 239 | linear_solver_.compute(jtj_regularized_); |
| 240 | lm_step_ = linear_solver_.solve(g_); |
| 241 | dx_ = jacobi_scaling_.asDiagonal() * lm_step_; |
| 242 | |
| 243 | // Adding parameter_tolerance to x.norm() ensures that this |
| 244 | // works if x is near zero. |
| 245 | const Scalar parameter_tolerance = |
| 246 | options.parameter_tolerance * |
| 247 | (x.norm() + options.parameter_tolerance); |
| 248 | if (dx_.norm() < parameter_tolerance) { |
| 249 | summary.status = RELATIVE_STEP_SIZE_TOO_SMALL; |
| 250 | break; |
| 251 | } |
| 252 | x_new_ = x + dx_; |
| 253 | |
| 254 | // TODO(keir): Add proper handling of errors from user eval of cost |
| 255 | // functions. |
| 256 | function(&x_new_[0], &f_x_new_[0], NULL); |
| 257 | |
| 258 | const Scalar cost_change = (2 * cost_ - f_x_new_.squaredNorm()); |
| 259 | |
| 260 | // TODO(sameeragarwal): Better more numerically stable evaluation. |
| 261 | const Scalar model_cost_change = lm_step_.dot(2 * g_ - jtj_ * lm_step_); |
| 262 | |
| 263 | // rho is the ratio of the actual reduction in error to the reduction |
| 264 | // in error that would be obtained if the problem was linear. See [1] |
| 265 | // for details. |
| 266 | Scalar rho(cost_change / model_cost_change); |
| 267 | if (rho > 0) { |
| 268 | // Accept the Levenberg-Marquardt step because the linear |
| 269 | // model fits well. |
| 270 | x = x_new_; |
| 271 | |
| 272 | // TODO(sameeragarwal): Deal with failure. |
| 273 | Update(function, x); |
| 274 | if (summary.gradient_max_norm < options.gradient_tolerance) { |
| 275 | summary.status = GRADIENT_TOO_SMALL; |
| 276 | break; |
| 277 | } |
| 278 | |
| 279 | if (cost_ < options.cost_threshold) { |
| 280 | summary.status = COST_TOO_SMALL; |
| 281 | break; |
| 282 | } |
| 283 | |
| 284 | Scalar tmp = Scalar(2 * rho - 1); |
| 285 | u = u * std::max(1 / 3., 1 - tmp * tmp * tmp); |
| 286 | v = 2; |
| 287 | continue; |
| 288 | } |
| 289 | |
| 290 | // Reject the update because either the normal equations failed to solve |
| 291 | // or the local linear model was not good (rho < 0). Instead, increase u |
| 292 | // to move closer to gradient descent. |
| 293 | u *= v; |
| 294 | v *= 2; |
| 295 | } |
| 296 | |
| 297 | summary.final_cost = cost_; |
| 298 | return summary; |
| 299 | } |
| 300 | |
| 301 | Options options; |
| 302 | Summary summary; |
| 303 | |
| 304 | private: |
| 305 | // Preallocate everything, including temporary storage needed for solving the |
| 306 | // linear system. This allows reusing the intermediate storage across solves. |
| 307 | LinearSolver linear_solver_; |
| 308 | Scalar cost_; |
| 309 | Parameters dx_, x_new_, g_, jacobi_scaling_, lm_diagonal_, lm_step_; |
| 310 | Eigen::Matrix<Scalar, NUM_RESIDUALS, 1> error_, f_x_new_; |
| 311 | Eigen::Matrix<Scalar, NUM_RESIDUALS, NUM_PARAMETERS> jacobian_; |
| 312 | Eigen::Matrix<Scalar, NUM_PARAMETERS, NUM_PARAMETERS> jtj_, jtj_regularized_; |
| 313 | |
| 314 | // The following definitions are needed for template metaprogramming. |
| 315 | template <bool Condition, typename T> |
| 316 | struct enable_if; |
| 317 | |
| 318 | template <typename T> |
| 319 | struct enable_if<true, T> { |
| 320 | typedef T type; |
| 321 | }; |
| 322 | |
| 323 | // The number of parameters and residuals are dynamically sized. |
| 324 | template <int R, int P> |
| 325 | typename enable_if<(R == Eigen::Dynamic && P == Eigen::Dynamic), void>::type |
| 326 | Initialize(const Function& function) { |
| 327 | Initialize(function.NumResiduals(), function.NumParameters()); |
| 328 | } |
| 329 | |
| 330 | // The number of parameters is dynamically sized and the number of |
| 331 | // residuals is statically sized. |
| 332 | template <int R, int P> |
| 333 | typename enable_if<(R == Eigen::Dynamic && P != Eigen::Dynamic), void>::type |
| 334 | Initialize(const Function& function) { |
| 335 | Initialize(function.NumResiduals(), P); |
| 336 | } |
| 337 | |
| 338 | // The number of parameters is statically sized and the number of |
| 339 | // residuals is dynamically sized. |
| 340 | template <int R, int P> |
| 341 | typename enable_if<(R != Eigen::Dynamic && P == Eigen::Dynamic), void>::type |
| 342 | Initialize(const Function& function) { |
| 343 | Initialize(R, function.NumParameters()); |
| 344 | } |
| 345 | |
| 346 | // The number of parameters and residuals are statically sized. |
| 347 | template <int R, int P> |
| 348 | typename enable_if<(R != Eigen::Dynamic && P != Eigen::Dynamic), void>::type |
| 349 | Initialize(const Function& /* function */) {} |
| 350 | |
| 351 | void Initialize(int num_residuals, int num_parameters) { |
| 352 | dx_.resize(num_parameters); |
| 353 | x_new_.resize(num_parameters); |
| 354 | g_.resize(num_parameters); |
| 355 | jacobi_scaling_.resize(num_parameters); |
| 356 | lm_diagonal_.resize(num_parameters); |
| 357 | lm_step_.resize(num_parameters); |
| 358 | error_.resize(num_residuals); |
| 359 | f_x_new_.resize(num_residuals); |
| 360 | jacobian_.resize(num_residuals, num_parameters); |
| 361 | jtj_.resize(num_parameters, num_parameters); |
| 362 | jtj_regularized_.resize(num_parameters, num_parameters); |
| 363 | } |
| 364 | }; |
| 365 | |
| 366 | } // namespace ceres |
| 367 | |
| 368 | #endif // CERES_PUBLIC_TINY_SOLVER_H_ |