blob: a8e699b04c2eb211c9bac81a09ac7de188c9ca44 [file] [log] [blame]
Keir Mierle7928ca02017-10-10 02:15:25 -07001// Ceres Solver - A fast non-linear least squares minimizer
Sameer Agarwal88411192021-09-14 09:54:50 -07002// Copyright 2021 Google Inc. All rights reserved.
Keir Mierle7928ca02017-10-10 02:15:25 -07003// 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//
Kuang Fangjun0d3a84f2018-09-22 11:53:42 +080041// Note: This code has no dependencies beyond Eigen, including on other parts of
Keir Mierle7928ca02017-10-10 02:15:25 -070042// 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
Sameer Agarwalcc0bd492017-10-14 09:41:20 -070057#include "Eigen/Dense"
Keir Mierle7928ca02017-10-10 02:15:25 -070058
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
Kuang Fangjun0d3a84f2018-09-22 11:53:42 +080063// different to enable statically allocating all memory for the solver
Keir Mierle7928ca02017-10-10 02:15:25 -070064// (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 {
Keir Mierle7928ca02017-10-10 02:15:25 -070071// NUM_RESIDUALS = <int> OR Eigen::Dynamic,
Sameer Agarwal4d88f502017-10-11 16:03:15 -070072// NUM_PARAMETERS = <int> OR Eigen::Dynamic,
Keir Mierle7928ca02017-10-10 02:15:25 -070073// };
74// bool operator()(const double* parameters,
75// double* residuals,
76// double* jacobian) const;
77//
Kuang Fangjun0d3a84f2018-09-22 11:53:42 +080078// int NumResiduals() const; -- Needed if NUM_RESIDUALS == Eigen::Dynamic.
79// int NumParameters() const; -- Needed if NUM_PARAMETERS == Eigen::Dynamic.
80// };
Keir Mierle7928ca02017-10-10 02:15:25 -070081//
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
Sameer Agarwal88411192021-09-14 09:54:50 -070087// (Eigen's default); or nullptr if no jacobian
88// requested.
Keir Mierle7928ca02017-10-10 02:15:25 -070089//
90// An example (fully statically sized):
91//
92// struct MyCostFunctionExample {
93// typedef double Scalar;
94// enum {
Keir Mierle7928ca02017-10-10 02:15:25 -070095// NUM_RESIDUALS = 2,
Sameer Agarwal4d88f502017-10-11 16:03:15 -070096// NUM_PARAMETERS = 3,
Keir Mierle7928ca02017-10-10 02:15:25 -070097// };
98// bool operator()(const double* parameters,
99// double* residuals,
100// double* jacobian) const {
101// residuals[0] = x + 2*y + 4*z;
102// residuals[1] = y * z;
103// if (jacobian) {
104// jacobian[0 * 2 + 0] = 1; // First column (x).
105// jacobian[0 * 2 + 1] = 0;
106//
107// jacobian[1 * 2 + 0] = 2; // Second column (y).
108// jacobian[1 * 2 + 1] = z;
109//
110// jacobian[2 * 2 + 0] = 4; // Third column (z).
111// jacobian[2 * 2 + 1] = y;
112// }
Sameer Agarwal4d88f502017-10-11 16:03:15 -0700113// return true;
Keir Mierle7928ca02017-10-10 02:15:25 -0700114// }
115// };
116//
117// The solver supports either statically or dynamically sized cost
Sameer Agarwal4d88f502017-10-11 16:03:15 -0700118// functions. If the number of residuals is dynamic then the Function
Keir Mierle7928ca02017-10-10 02:15:25 -0700119// must define:
120//
Keir Mierle7928ca02017-10-10 02:15:25 -0700121// int NumResiduals() const;
122//
Sameer Agarwal4d88f502017-10-11 16:03:15 -0700123// If the number of parameters is dynamic then the Function must
124// define:
125//
126// int NumParameters() const;
127//
Sameer Agarwal4362a212019-12-02 13:52:31 -0800128template <typename Function,
129 typename LinearSolver =
Sameer Agarwal88411192021-09-14 09:54:50 -0700130 Eigen::LDLT<Eigen::Matrix<typename Function::Scalar, //
131 Function::NUM_PARAMETERS, //
Sameer Agarwal4362a212019-12-02 13:52:31 -0800132 Function::NUM_PARAMETERS>>>
Keir Mierle7928ca02017-10-10 02:15:25 -0700133class TinySolver {
134 public:
Johannes Beck939253c2019-04-14 20:31:47 +0200135 // This class needs to have an Eigen aligned operator new as it contains
136 // fixed-size Eigen types.
137 EIGEN_MAKE_ALIGNED_OPERATOR_NEW
138
Keir Mierle7928ca02017-10-10 02:15:25 -0700139 enum {
140 NUM_RESIDUALS = Function::NUM_RESIDUALS,
141 NUM_PARAMETERS = Function::NUM_PARAMETERS
142 };
143 typedef typename Function::Scalar Scalar;
144 typedef typename Eigen::Matrix<Scalar, NUM_PARAMETERS, 1> Parameters;
145
Keir Mierle7928ca02017-10-10 02:15:25 -0700146 enum Status {
Sameer Agarwal88411192021-09-14 09:54:50 -0700147 // max_norm |J'(x) * f(x)| < gradient_tolerance
148 GRADIENT_TOO_SMALL,
149 // ||dx|| <= parameter_tolerance * (||x|| + parameter_tolerance)
150 RELATIVE_STEP_SIZE_TOO_SMALL,
151 // cost_threshold > ||f(x)||^2 / 2
152 COST_TOO_SMALL,
153 // num_iterations >= max_num_iterations
Keir Mierle7928ca02017-10-10 02:15:25 -0700154 HIT_MAX_ITERATIONS,
Sameer Agarwal88411192021-09-14 09:54:50 -0700155 // (new_cost - old_cost) < function_tolerance * old_cost
156 COST_CHANGE_TOO_SMALL,
Keir Mierle7928ca02017-10-10 02:15:25 -0700157
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700158 // TODO(sameeragarwal): Deal with numerical failures.
Keir Mierle7928ca02017-10-10 02:15:25 -0700159 };
160
Sameer Agarwalcc0bd492017-10-14 09:41:20 -0700161 struct Options {
Sameer Agarwal9814a912018-04-06 08:38:11 -0700162 int max_num_iterations = 50;
Sameer Agarwal88411192021-09-14 09:54:50 -0700163
164 // max_norm |J'(x) * f(x)| < gradient_tolerance
165 Scalar gradient_tolerance = 1e-10;
166
167 // ||dx|| <= parameter_tolerance * (||x|| + parameter_tolerance)
168 Scalar parameter_tolerance = 1e-8;
169
170 // (new_cost - old_cost) < function_tolerance * old_cost
171 Scalar function_tolerance = 1e-6;
172
173 // cost_threshold > ||f(x)||^2 / 2
174 Scalar cost_threshold = std::numeric_limits<Scalar>::epsilon();
175
176 Scalar initial_trust_region_radius = 1e4;
Keir Mierle7928ca02017-10-10 02:15:25 -0700177 };
178
Sameer Agarwalcc0bd492017-10-14 09:41:20 -0700179 struct Summary {
Sameer Agarwal88411192021-09-14 09:54:50 -0700180 // 1/2 ||f(x_0)||^2
181 Scalar initial_cost = -1;
182 // 1/2 ||f(x)||^2
183 Scalar final_cost = -1;
184 // max_norm(J'f(x))
185 Scalar gradient_max_norm = -1;
Sameer Agarwal9814a912018-04-06 08:38:11 -0700186 int iterations = -1;
187 Status status = HIT_MAX_ITERATIONS;
Keir Mierle7928ca02017-10-10 02:15:25 -0700188 };
189
Sameer Agarwal4362a212019-12-02 13:52:31 -0800190 bool Update(const Function& function, const Parameters& x) {
Sameer Agarwal88411192021-09-14 09:54:50 -0700191 if (!function(x.data(), residuals_.data(), jacobian_.data())) {
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700192 return false;
193 }
194
Sameer Agarwal88411192021-09-14 09:54:50 -0700195 residuals_ = -residuals_;
Keir Mierle7928ca02017-10-10 02:15:25 -0700196
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700197 // On the first iteration, compute a diagonal (Jacobi) scaling
198 // matrix, which we store as a vector.
199 if (summary.iterations == 0) {
200 // jacobi_scaling = 1 / (1 + diagonal(J'J))
201 //
202 // 1 is added to the denominator to regularize small diagonal
203 // entries.
204 jacobi_scaling_ = 1.0 / (1.0 + jacobian_.colwise().norm().array());
205 }
206
Keir Mierle7928ca02017-10-10 02:15:25 -0700207 // This explicitly computes the normal equations, which is numerically
208 // unstable. Nevertheless, it is often good enough and is fast.
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700209 //
210 // TODO(sameeragarwal): Refactor this to allow for DenseQR
211 // factorization.
212 jacobian_ = jacobian_ * jacobi_scaling_.asDiagonal();
Keir Mierle7928ca02017-10-10 02:15:25 -0700213 jtj_ = jacobian_.transpose() * jacobian_;
Sameer Agarwal88411192021-09-14 09:54:50 -0700214 g_ = jacobian_.transpose() * residuals_;
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700215 summary.gradient_max_norm = g_.array().abs().maxCoeff();
Sameer Agarwal88411192021-09-14 09:54:50 -0700216 cost_ = residuals_.squaredNorm() / 2;
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700217 return true;
Keir Mierle7928ca02017-10-10 02:15:25 -0700218 }
219
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700220 const Summary& Solve(const Function& function, Parameters* x_and_min) {
Sameer Agarwal4d88f502017-10-11 16:03:15 -0700221 Initialize<NUM_RESIDUALS, NUM_PARAMETERS>(function);
Keir Mierle7928ca02017-10-10 02:15:25 -0700222 assert(x_and_min);
223 Parameters& x = *x_and_min;
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700224 summary = Summary();
Sameer Agarwal9814a912018-04-06 08:38:11 -0700225 summary.iterations = 0;
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700226
227 // TODO(sameeragarwal): Deal with failure here.
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700228 Update(function, x);
229 summary.initial_cost = cost_;
230 summary.final_cost = cost_;
Keir Mierle7928ca02017-10-10 02:15:25 -0700231
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700232 if (summary.gradient_max_norm < options.gradient_tolerance) {
233 summary.status = GRADIENT_TOO_SMALL;
234 return summary;
235 }
236
237 if (cost_ < options.cost_threshold) {
238 summary.status = COST_TOO_SMALL;
239 return summary;
240 }
241
242 Scalar u = 1.0 / options.initial_trust_region_radius;
Keir Mierle7928ca02017-10-10 02:15:25 -0700243 Scalar v = 2;
244
Keir Mierledc9bf012017-10-19 23:46:49 -0700245 for (summary.iterations = 1;
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700246 summary.iterations < options.max_num_iterations;
Keir Mierledc9bf012017-10-19 23:46:49 -0700247 summary.iterations++) {
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700248 jtj_regularized_ = jtj_;
249 const Scalar min_diagonal = 1e-6;
250 const Scalar max_diagonal = 1e32;
Keir Mierledc9bf012017-10-19 23:46:49 -0700251 for (int i = 0; i < lm_diagonal_.rows(); ++i) {
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700252 lm_diagonal_[i] = std::sqrt(
253 u * std::min(std::max(jtj_(i, i), min_diagonal), max_diagonal));
254 jtj_regularized_(i, i) += lm_diagonal_[i] * lm_diagonal_[i];
Keir Mierle7928ca02017-10-10 02:15:25 -0700255 }
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700256
257 // TODO(sameeragarwal): Check for failure and deal with it.
258 linear_solver_.compute(jtj_regularized_);
259 lm_step_ = linear_solver_.solve(g_);
260 dx_ = jacobi_scaling_.asDiagonal() * lm_step_;
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700261
262 // Adding parameter_tolerance to x.norm() ensures that this
263 // works if x is near zero.
264 const Scalar parameter_tolerance =
265 options.parameter_tolerance *
266 (x.norm() + options.parameter_tolerance);
267 if (dx_.norm() < parameter_tolerance) {
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700268 summary.status = RELATIVE_STEP_SIZE_TOO_SMALL;
269 break;
270 }
271 x_new_ = x + dx_;
272
273 // TODO(keir): Add proper handling of errors from user eval of cost
274 // functions.
Sameer Agarwal88411192021-09-14 09:54:50 -0700275 function(&x_new_[0], &f_x_new_[0], nullptr);
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700276
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700277 const Scalar cost_change = (2 * cost_ - f_x_new_.squaredNorm());
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700278 // TODO(sameeragarwal): Better more numerically stable evaluation.
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700279 const Scalar model_cost_change = lm_step_.dot(2 * g_ - jtj_ * lm_step_);
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700280
281 // rho is the ratio of the actual reduction in error to the reduction
282 // in error that would be obtained if the problem was linear. See [1]
283 // for details.
284 Scalar rho(cost_change / model_cost_change);
285 if (rho > 0) {
286 // Accept the Levenberg-Marquardt step because the linear
287 // model fits well.
288 x = x_new_;
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700289
Sameer Agarwal88411192021-09-14 09:54:50 -0700290 if (std::abs(cost_change) < options.function_tolerance) {
291 cost_ = f_x_new_.squaredNorm() / 2;
292 summary.status = COST_CHANGE_TOO_SMALL;
293 break;
294 }
295
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700296 // TODO(sameeragarwal): Deal with failure.
297 Update(function, x);
298 if (summary.gradient_max_norm < options.gradient_tolerance) {
299 summary.status = GRADIENT_TOO_SMALL;
300 break;
301 }
302
303 if (cost_ < options.cost_threshold) {
304 summary.status = COST_TOO_SMALL;
305 break;
306 }
307
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700308 Scalar tmp = Scalar(2 * rho - 1);
Sameer Agarwale84cf102020-12-14 08:55:05 -0800309 u = u * std::max(Scalar(1 / 3.), 1 - tmp * tmp * tmp);
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700310 v = 2;
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700311
Sameer Agarwal88411192021-09-14 09:54:50 -0700312 } else {
313 // Reject the update because either the normal equations failed to solve
314 // or the local linear model was not good (rho < 0).
315
316 // Additionally if the cost change is too small, then terminate.
317 if (std::abs(cost_change) < options.function_tolerance) {
318 // Terminate
319 summary.status = COST_CHANGE_TOO_SMALL;
320 break;
321 }
322
323 // Reduce the size of the trust region.
324 u *= v;
325 v *= 2;
326 }
Keir Mierle7928ca02017-10-10 02:15:25 -0700327 }
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700328
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700329 summary.final_cost = cost_;
Sameer Agarwalcc0bd492017-10-14 09:41:20 -0700330 return summary;
Keir Mierle7928ca02017-10-10 02:15:25 -0700331 }
332
Sameer Agarwalcc0bd492017-10-14 09:41:20 -0700333 Options options;
334 Summary summary;
Keir Mierle7928ca02017-10-10 02:15:25 -0700335
336 private:
337 // Preallocate everything, including temporary storage needed for solving the
338 // linear system. This allows reusing the intermediate storage across solves.
339 LinearSolver linear_solver_;
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700340 Scalar cost_;
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700341 Parameters dx_, x_new_, g_, jacobi_scaling_, lm_diagonal_, lm_step_;
Sameer Agarwal88411192021-09-14 09:54:50 -0700342 Eigen::Matrix<Scalar, NUM_RESIDUALS, 1> residuals_, f_x_new_;
Keir Mierle7928ca02017-10-10 02:15:25 -0700343 Eigen::Matrix<Scalar, NUM_RESIDUALS, NUM_PARAMETERS> jacobian_;
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700344 Eigen::Matrix<Scalar, NUM_PARAMETERS, NUM_PARAMETERS> jtj_, jtj_regularized_;
Keir Mierle7928ca02017-10-10 02:15:25 -0700345
346 // The following definitions are needed for template metaprogramming.
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700347 template <bool Condition, typename T>
348 struct enable_if;
Keir Mierle7928ca02017-10-10 02:15:25 -0700349
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700350 template <typename T>
351 struct enable_if<true, T> {
Keir Mierle7928ca02017-10-10 02:15:25 -0700352 typedef T type;
353 };
354
355 // The number of parameters and residuals are dynamically sized.
Sameer Agarwal4d88f502017-10-11 16:03:15 -0700356 template <int R, int P>
357 typename enable_if<(R == Eigen::Dynamic && P == Eigen::Dynamic), void>::type
Keir Mierle7928ca02017-10-10 02:15:25 -0700358 Initialize(const Function& function) {
Sameer Agarwal4d88f502017-10-11 16:03:15 -0700359 Initialize(function.NumResiduals(), function.NumParameters());
Keir Mierle7928ca02017-10-10 02:15:25 -0700360 }
361
362 // The number of parameters is dynamically sized and the number of
363 // residuals is statically sized.
Sameer Agarwal4d88f502017-10-11 16:03:15 -0700364 template <int R, int P>
365 typename enable_if<(R == Eigen::Dynamic && P != Eigen::Dynamic), void>::type
Keir Mierle7928ca02017-10-10 02:15:25 -0700366 Initialize(const Function& function) {
Sameer Agarwal4d88f502017-10-11 16:03:15 -0700367 Initialize(function.NumResiduals(), P);
Keir Mierle7928ca02017-10-10 02:15:25 -0700368 }
369
370 // The number of parameters is statically sized and the number of
371 // residuals is dynamically sized.
Sameer Agarwal4d88f502017-10-11 16:03:15 -0700372 template <int R, int P>
373 typename enable_if<(R != Eigen::Dynamic && P == Eigen::Dynamic), void>::type
Keir Mierle7928ca02017-10-10 02:15:25 -0700374 Initialize(const Function& function) {
Sameer Agarwal4d88f502017-10-11 16:03:15 -0700375 Initialize(R, function.NumParameters());
Keir Mierle7928ca02017-10-10 02:15:25 -0700376 }
377
378 // The number of parameters and residuals are statically sized.
Sameer Agarwal4d88f502017-10-11 16:03:15 -0700379 template <int R, int P>
380 typename enable_if<(R != Eigen::Dynamic && P != Eigen::Dynamic), void>::type
Sameer Agarwal030b41d2017-10-21 22:33:55 -0700381 Initialize(const Function& /* function */) {}
Keir Mierle7928ca02017-10-10 02:15:25 -0700382
Sameer Agarwal4d88f502017-10-11 16:03:15 -0700383 void Initialize(int num_residuals, int num_parameters) {
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700384 dx_.resize(num_parameters);
385 x_new_.resize(num_parameters);
386 g_.resize(num_parameters);
387 jacobi_scaling_.resize(num_parameters);
388 lm_diagonal_.resize(num_parameters);
389 lm_step_.resize(num_parameters);
Sameer Agarwal88411192021-09-14 09:54:50 -0700390 residuals_.resize(num_residuals);
Keir Mierle7928ca02017-10-10 02:15:25 -0700391 f_x_new_.resize(num_residuals);
392 jacobian_.resize(num_residuals, num_parameters);
393 jtj_.resize(num_parameters, num_parameters);
Sameer Agarwalba73ce12017-10-18 20:48:12 -0700394 jtj_regularized_.resize(num_parameters, num_parameters);
Keir Mierle7928ca02017-10-10 02:15:25 -0700395 }
396};
397
398} // namespace ceres
399
Sameer Agarwal4d88f502017-10-11 16:03:15 -0700400#endif // CERES_PUBLIC_TINY_SOLVER_H_