blob: 47db5824dc5dbf5a46b44df609d4ac9bbcf3cf88 [file] [log] [blame]
Austin Schuh70cc9552019-01-21 19:46:48 -08001// Ceres Solver - A fast non-linear least squares minimizer
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08002// Copyright 2019 Google Inc. All rights reserved.
Austin Schuh70cc9552019-01-21 19:46:48 -08003// 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
59namespace 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 Schuh1d1e6ea2020-12-23 21:56:30 -0800127template <typename Function,
128 typename LinearSolver =
129 Eigen::LDLT<Eigen::Matrix<typename Function::Scalar,
130 Function::NUM_PARAMETERS,
131 Function::NUM_PARAMETERS>>>
Austin Schuh70cc9552019-01-21 19:46:48 -0800132class TinySolver {
133 public:
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800134 // 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 Schuh70cc9552019-01-21 19:46:48 -0800138 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 Schuh1d1e6ea2020-12-23 21:56:30 -0800171 bool Update(const Function& function, const Parameters& x) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800172 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_