blob: c47569043c5ce5ec194c6c11d379e9caa140ee89 [file] [log] [blame]
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -07001// Ceres Solver - A fast non-linear least squares minimizer
2// Copyright 2012 Google Inc. All rights reserved.
3// http://code.google.com/p/ceres-solver/
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: sameeragarwal@google.com (Sameer Agarwal)
30
31#include "ceres/trust_region_minimizer.h"
32
33#include <algorithm>
34#include <cstdlib>
35#include <cmath>
36#include <cstring>
37#include <string>
38#include <vector>
39#include <glog/logging.h>
40
41#include "Eigen/Core"
42#include "ceres/array_utils.h"
43#include "ceres/evaluator.h"
44#include "ceres/linear_least_squares_problems.h"
45#include "ceres/internal/eigen.h"
46#include "ceres/internal/scoped_ptr.h"
47#include "ceres/sparse_matrix.h"
48#include "ceres/trust_region_strategy.h"
49#include "ceres/types.h"
50
51namespace ceres {
52namespace internal {
53namespace {
54// Small constant for various floating point issues.
55const double kEpsilon = 1e-12;
56} // namespace
57
58// Execute the list of IterationCallbacks sequentially. If any one of
59// the callbacks does not return SOLVER_CONTINUE, then stop and return
60// its status.
61CallbackReturnType TrustRegionMinimizer::RunCallbacks(
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -070062 const IterationSummary& iteration_summary) {
63 for (int i = 0; i < options_.callbacks.size(); ++i) {
64 const CallbackReturnType status =
65 (*options_.callbacks[i])(iteration_summary);
66 if (status != SOLVER_CONTINUE) {
67 return status;
68 }
69 }
70 return SOLVER_CONTINUE;
71}
72
73// Compute a scaling vector that is used to improve the conditioning
74// of the Jacobian.
75void TrustRegionMinimizer::EstimateScale(const SparseMatrix& jacobian,
76 double* scale) const {
77 jacobian.SquaredColumnNorm(scale);
78 for (int i = 0; i < jacobian.num_cols(); ++i) {
79 scale[i] = 1.0 / (kEpsilon + sqrt(scale[i]));
80 }
81}
82
83void TrustRegionMinimizer::Init(const Minimizer::Options& options) {
84 options_ = options;
85 sort(options_.lsqp_iterations_to_dump.begin(),
86 options_.lsqp_iterations_to_dump.end());
87}
88
89bool TrustRegionMinimizer::MaybeDumpLinearLeastSquaresProblem(
90 const int iteration,
91 const SparseMatrix* jacobian,
92 const double* residuals,
93 const double* step) const {
94 // TODO(sameeragarwal): Since the use of trust_region_radius has
95 // moved inside TrustRegionStrategy, its not clear how we dump the
96 // regularization vector/matrix anymore.
97 //
98 // Doing this right requires either an API change to the
99 // TrustRegionStrategy and/or how LinearLeastSquares problems are
100 // stored on disk.
101 //
102 // For now, we will just not dump the regularizer.
103 return (!binary_search(options_.lsqp_iterations_to_dump.begin(),
104 options_.lsqp_iterations_to_dump.end(),
105 iteration) ||
106 DumpLinearLeastSquaresProblem(options_.lsqp_dump_directory,
107 iteration,
108 options_.lsqp_dump_format_type,
109 jacobian,
110 NULL,
111 residuals,
112 step,
113 options_.num_eliminate_blocks));
114}
115
116void TrustRegionMinimizer::Minimize(const Minimizer::Options& options,
117 double* parameters,
118 Solver::Summary* summary) {
119 time_t start_time = time(NULL);
120 time_t iteration_start_time = start_time;
121 Init(options);
122
123 summary->termination_type = NO_CONVERGENCE;
124 summary->num_successful_steps = 0;
125 summary->num_unsuccessful_steps = 0;
126
127 Evaluator* evaluator = CHECK_NOTNULL(options_.evaluator);
128 SparseMatrix* jacobian = CHECK_NOTNULL(options_.jacobian);
129 TrustRegionStrategy* strategy = CHECK_NOTNULL(options_.trust_region_strategy);
130
131 const int num_parameters = evaluator->NumParameters();
132 const int num_effective_parameters = evaluator->NumEffectiveParameters();
133 const int num_residuals = evaluator->NumResiduals();
134
135 VectorRef x(parameters, num_parameters);
136 double x_norm = x.norm();
137
138 Vector residuals(num_residuals);
139 Vector trust_region_step(num_effective_parameters);
140 Vector delta(num_effective_parameters);
141 Vector x_plus_delta(num_parameters);
142 Vector gradient(num_effective_parameters);
143 Vector model_residuals(num_residuals);
144 Vector scale(num_effective_parameters);
145
146 IterationSummary iteration_summary;
147 iteration_summary.iteration = 0;
148 iteration_summary.step_is_valid=false;
149 iteration_summary.step_is_successful=false;
150 iteration_summary.cost = summary->initial_cost;
151 iteration_summary.cost_change = 0.0;
152 iteration_summary.gradient_max_norm = 0.0;
153 iteration_summary.step_norm = 0.0;
154 iteration_summary.relative_decrease = 0.0;
155 iteration_summary.trust_region_radius = strategy->Radius();
156 // TODO(sameeragarwal): Rename eta to linear_solver_accuracy or
157 // something similar across the board.
158 iteration_summary.eta = options_.eta;
159 iteration_summary.linear_solver_iterations = 0;
Sameer Agarwalfa015192012-06-11 14:21:42 -0700160 iteration_summary.step_solver_time_in_seconds = 0;
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700161
162 // Do initial cost and Jacobian evaluation.
163 double cost = 0.0;
164 if (!evaluator->Evaluate(x.data(), &cost, residuals.data(), jacobian)) {
165 LOG(WARNING) << "Terminating: Residual and Jacobian evaluation failed.";
166 summary->termination_type = NUMERICAL_FAILURE;
167 return;
168 }
169
170 // Compute the fixed part of the cost.
171 //
172 // This is a poor way to do this computation. Even if fixed_cost is
173 // zero, because we are subtracting two possibly large numbers, we
174 // are depending on exact cancellation to give us a zero here. But
175 // initial_cost and cost have been computed by two different
176 // evaluators. One which runs on the whole problem (in
177 // solver_impl.cc) in single threaded mode and another which runs
178 // here on the reduced problem, so fixed_cost can (and does) contain
179 // some numerical garbage with a relative magnitude of 1e-14.
180 //
181 // The right way to do this, would be to compute the fixed cost on
182 // just the set of residual blocks which are held constant and were
183 // removed from the original problem when the reduced problem was
184 // constructed.
185 summary->fixed_cost = summary->initial_cost - cost;
186
187 gradient.setZero();
188 jacobian->LeftMultiply(residuals.data(), gradient.data());
189 iteration_summary.gradient_max_norm = gradient.lpNorm<Eigen::Infinity>();
190
191 if (options_.jacobi_scaling) {
192 EstimateScale(*jacobian, scale.data());
193 jacobian->ScaleColumns(scale.data());
194 } else {
195 scale.setOnes();
196 }
197
Sameer Agarwal4441b5b2012-06-12 18:01:11 -0700198 // The initial gradient max_norm is bounded from below so that we do
199 // not divide by zero.
200 const double gradient_max_norm_0 =
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700201 max(iteration_summary.gradient_max_norm, kEpsilon);
Sameer Agarwal4441b5b2012-06-12 18:01:11 -0700202 const double absolute_gradient_tolerance =
203 options_.gradient_tolerance * gradient_max_norm_0;
204
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700205 if (iteration_summary.gradient_max_norm <= absolute_gradient_tolerance) {
206 summary->termination_type = GRADIENT_TOLERANCE;
207 VLOG(1) << "Terminating: Gradient tolerance reached."
208 << "Relative gradient max norm: "
Sameer Agarwal4441b5b2012-06-12 18:01:11 -0700209 << iteration_summary.gradient_max_norm / gradient_max_norm_0
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700210 << " <= " << options_.gradient_tolerance;
211 return;
212 }
213
Sameer Agarwalfa015192012-06-11 14:21:42 -0700214 iteration_summary.iteration_time_in_seconds =
215 time(NULL) - iteration_start_time;
216 iteration_summary.cumulative_time_in_seconds = time(NULL) - start_time +
217 summary->preprocessor_time_in_seconds;
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700218 summary->iterations.push_back(iteration_summary);
Sameer Agarwalfa015192012-06-11 14:21:42 -0700219
220 // Call the various callbacks.
Keir Mierlef7471832012-06-14 11:31:53 -0700221 switch (RunCallbacks(iteration_summary)) {
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700222 case SOLVER_TERMINATE_SUCCESSFULLY:
223 summary->termination_type = USER_SUCCESS;
224 VLOG(1) << "Terminating: User callback returned USER_SUCCESS.";
225 return;
226 case SOLVER_ABORT:
227 summary->termination_type = USER_ABORT;
228 VLOG(1) << "Terminating: User callback returned USER_ABORT.";
229 return;
230 case SOLVER_CONTINUE:
231 break;
232 default:
233 LOG(FATAL) << "Unknown type of user callback status";
234 }
235
236 int num_consecutive_invalid_steps = 0;
237 while (true) {
238 iteration_start_time = time(NULL);
239 if (iteration_summary.iteration >= options_.max_num_iterations) {
240 summary->termination_type = NO_CONVERGENCE;
241 VLOG(1) << "Terminating: Maximum number of iterations reached.";
242 break;
243 }
244
Sameer Agarwalfa015192012-06-11 14:21:42 -0700245 const double total_solver_time = iteration_start_time - start_time +
246 summary->preprocessor_time_in_seconds;
247 if (total_solver_time >= options_.max_solver_time_in_seconds) {
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700248 summary->termination_type = NO_CONVERGENCE;
249 VLOG(1) << "Terminating: Maximum solver time reached.";
250 break;
251 }
252
253 iteration_summary = IterationSummary();
254 iteration_summary = summary->iterations.back();
255 iteration_summary.iteration = summary->iterations.back().iteration + 1;
256 iteration_summary.step_is_valid = false;
257 iteration_summary.step_is_successful = false;
258
259 const time_t strategy_start_time = time(NULL);
260 TrustRegionStrategy::PerSolveOptions per_solve_options;
261 per_solve_options.eta = options_.eta;
262 LinearSolver::Summary strategy_summary =
263 strategy->ComputeStep(per_solve_options,
264 jacobian,
265 residuals.data(),
266 trust_region_step.data());
267
Sameer Agarwalfa015192012-06-11 14:21:42 -0700268 iteration_summary.step_solver_time_in_seconds =
269 time(NULL) - strategy_start_time;
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700270 iteration_summary.linear_solver_iterations =
271 strategy_summary.num_iterations;
272
273 if (!MaybeDumpLinearLeastSquaresProblem(iteration_summary.iteration,
274 jacobian,
275 residuals.data(),
276 trust_region_step.data())) {
277 LOG(FATAL) << "Tried writing linear least squares problem: "
278 << options.lsqp_dump_directory << "but failed.";
279 }
280
281 double new_model_cost = 0.0;
282 if (strategy_summary.termination_type != FAILURE) {
283 // new_model_cost = 1/2 |J * step - f|^2
284 model_residuals = -residuals;
285 jacobian->RightMultiply(trust_region_step.data(), model_residuals.data());
286 new_model_cost = model_residuals.squaredNorm() / 2.0;
287
288 // In exact arithmetic, this would never be the case. But poorly
289 // conditioned matrices can give rise to situations where the
290 // new_model_cost can actually be larger than half the squared
291 // norm of the residual vector. We allow for small tolerance
292 // around cost and beyond that declare the step to be invalid.
293 if (cost < (new_model_cost - kEpsilon)) {
294 VLOG(1) << "Invalid step: current_cost: " << cost
295 << " new_model_cost " << new_model_cost;
296 } else {
297 iteration_summary.step_is_valid = true;
298 }
299 }
300
301 if (!iteration_summary.step_is_valid) {
302 // Invalid steps can happen due to a number of reasons, and we
303 // allow a limited number of successive failures, and return with
304 // NUMERICAL_FAILURE if this limit is exceeded.
305 if (++num_consecutive_invalid_steps >=
306 options_.max_num_consecutive_invalid_steps) {
307 summary->termination_type = NUMERICAL_FAILURE;
308 LOG(WARNING) << "Terminating. Number of successive invalid steps more "
309 << "than "
310 << "Solver::Options::max_num_consecutive_invalid_steps: "
311 << options_.max_num_consecutive_invalid_steps;
312 return;
313 }
314
315 // We are going to try and reduce the trust region radius and
316 // solve again. To do this, we are going to treat this iteration
317 // as an unsuccessful iteration. Since the various callbacks are
318 // still executed, we are going to fill the iteration summary
319 // with data that assumes a step of length zero and no progress.
320 iteration_summary.cost = cost;
321 iteration_summary.cost_change = 0.0;
322 iteration_summary.gradient_max_norm =
323 summary->iterations.back().gradient_max_norm;
324 iteration_summary.step_norm = 0.0;
325 iteration_summary.relative_decrease = 0.0;
326 iteration_summary.eta = options_.eta;
327 } else {
328 // The step is numerically valid, so now we can judge its quality.
329 num_consecutive_invalid_steps = 0;
330
331 // We allow some slop around 0, and clamp the model_cost_change
332 // at kEpsilon from below.
333 //
334 // There is probably a better way to do this, as it is going to
335 // create problems for problems where the objective function is
336 // kEpsilon close to zero.
337 const double model_cost_change = max(kEpsilon, cost - new_model_cost);
338
339 // Undo the Jacobian column scaling.
340 delta = -(trust_region_step.array() * scale.array()).matrix();
341 iteration_summary.step_norm = delta.norm();
342
343 // Convergence based on parameter_tolerance.
344 const double step_size_tolerance = options_.parameter_tolerance *
345 (x_norm + options_.parameter_tolerance);
346 if (iteration_summary.step_norm <= step_size_tolerance) {
347 VLOG(1) << "Terminating. Parameter tolerance reached. "
348 << "relative step_norm: "
349 << iteration_summary.step_norm /
350 (x_norm + options_.parameter_tolerance)
351 << " <= " << options_.parameter_tolerance;
352 summary->termination_type = PARAMETER_TOLERANCE;
353 return;
354 }
355
356 if (!evaluator->Plus(x.data(), delta.data(), x_plus_delta.data())) {
357 summary->termination_type = NUMERICAL_FAILURE;
358 LOG(WARNING) << "Terminating. Failed to compute "
359 << "Plus(x, delta, x_plus_delta).";
360 return;
361 }
362
363 // Try this step.
364 double new_cost;
365 if (!evaluator->Evaluate(x_plus_delta.data(), &new_cost, NULL, NULL)) {
366 summary->termination_type = NUMERICAL_FAILURE;
367 LOG(WARNING) << "Terminating: Cost evaluation failed.";
368 return;
369 }
370
Sameer Agarwald28b3c82012-06-05 21:50:31 -0700371 VLOG(2) << "old cost: " << cost << " new cost: " << new_cost;
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700372 iteration_summary.cost_change = cost - new_cost;
373 const double absolute_function_tolerance =
374 options_.function_tolerance * cost;
375 if (fabs(iteration_summary.cost_change) < absolute_function_tolerance) {
376 VLOG(1) << "Terminating. Function tolerance reached. "
Sameer Agarwalfa015192012-06-11 14:21:42 -0700377 << "|cost_change|/cost: "
378 << fabs(iteration_summary.cost_change) / cost
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700379 << " <= " << options_.function_tolerance;
380 summary->termination_type = FUNCTION_TOLERANCE;
381 return;
382 }
383
384 iteration_summary.relative_decrease =
385 iteration_summary.cost_change / model_cost_change;
386 iteration_summary.step_is_successful =
387 iteration_summary.relative_decrease > options_.min_relative_decrease;
388 }
389
390 if (iteration_summary.step_is_successful) {
391 ++summary->num_successful_steps;
392 strategy->StepAccepted(iteration_summary.relative_decrease);
393 x = x_plus_delta;
394 x_norm = x.norm();
395 // Step looks good, evaluate the residuals and Jacobian at this
396 // point.
397 if (!evaluator->Evaluate(x.data(), &cost, residuals.data(), jacobian)) {
398 summary->termination_type = NUMERICAL_FAILURE;
399 LOG(WARNING) << "Terminating: Residual and Jacobian evaluation failed.";
400 return;
401 }
402
403 gradient.setZero();
404 jacobian->LeftMultiply(residuals.data(), gradient.data());
405 iteration_summary.gradient_max_norm = gradient.lpNorm<Eigen::Infinity>();
406
407 if (iteration_summary.gradient_max_norm <= absolute_gradient_tolerance) {
408 summary->termination_type = GRADIENT_TOLERANCE;
409 VLOG(1) << "Terminating: Gradient tolerance reached."
410 << "Relative gradient max norm: "
Sameer Agarwal4441b5b2012-06-12 18:01:11 -0700411 << iteration_summary.gradient_max_norm / gradient_max_norm_0
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700412 << " <= " << options_.gradient_tolerance;
413 return;
414 }
415
416 if (options_.jacobi_scaling) {
417 jacobian->ScaleColumns(scale.data());
418 }
419 } else {
420 ++summary->num_unsuccessful_steps;
Sameer Agarwalfa015192012-06-11 14:21:42 -0700421 if (iteration_summary.step_is_valid) {
422 strategy->StepRejected(iteration_summary.relative_decrease);
423 } else {
424 strategy->StepIsInvalid();
425 }
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700426 }
427
428 iteration_summary.cost = cost + summary->fixed_cost;
429 iteration_summary.trust_region_radius = strategy->Radius();
430 if (iteration_summary.trust_region_radius <
431 options_.min_trust_region_radius) {
432 summary->termination_type = PARAMETER_TOLERANCE;
433 VLOG(1) << "Termination. Minimum trust region radius reached.";
434 return;
435 }
436
Sameer Agarwalfa015192012-06-11 14:21:42 -0700437 iteration_summary.iteration_time_in_seconds =
438 time(NULL) - iteration_start_time;
439 iteration_summary.cumulative_time_in_seconds = time(NULL) - start_time +
440 summary->preprocessor_time_in_seconds;
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700441 summary->iterations.push_back(iteration_summary);
Sameer Agarwalfa015192012-06-11 14:21:42 -0700442
Keir Mierlef7471832012-06-14 11:31:53 -0700443 switch (RunCallbacks(iteration_summary)) {
Sameer Agarwalaa9a83c2012-05-29 17:40:17 -0700444 case SOLVER_TERMINATE_SUCCESSFULLY:
445 summary->termination_type = USER_SUCCESS;
446 VLOG(1) << "Terminating: User callback returned USER_SUCCESS.";
447 return;
448 case SOLVER_ABORT:
449 summary->termination_type = USER_ABORT;
450 VLOG(1) << "Terminating: User callback returned USER_ABORT.";
451 return;
452 case SOLVER_CONTINUE:
453 break;
454 default:
455 LOG(FATAL) << "Unknown type of user callback status";
456 }
457 }
458}
459
460
461} // namespace internal
462} // namespace ceres