blob: ea1c5072a14853a40e08b1e0a4c96e62388d72f8 [file] [log] [blame]
Austin Schuh70cc9552019-01-21 19:46:48 -08001// Ceres Solver - A fast non-linear least squares minimizer
2// Copyright 2015 Google Inc. All rights reserved.
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: sameeragarwal@google.com (Sameer Agarwal)
30//
31// Generic loop for line search based optimization algorithms.
32//
33// This is primarily inpsired by the minFunc packaged written by Mark
34// Schmidt.
35//
36// http://www.di.ens.fr/~mschmidt/Software/minFunc.html
37//
38// For details on the theory and implementation see "Numerical
39// Optimization" by Nocedal & Wright.
40
41#include "ceres/line_search_minimizer.h"
42
43#include <algorithm>
Austin Schuh70cc9552019-01-21 19:46:48 -080044#include <cmath>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080045#include <cstdlib>
Austin Schuh70cc9552019-01-21 19:46:48 -080046#include <memory>
47#include <string>
48#include <vector>
49
50#include "Eigen/Dense"
51#include "ceres/array_utils.h"
52#include "ceres/evaluator.h"
53#include "ceres/internal/eigen.h"
54#include "ceres/internal/port.h"
55#include "ceres/line_search.h"
56#include "ceres/line_search_direction.h"
57#include "ceres/stringprintf.h"
58#include "ceres/types.h"
59#include "ceres/wall_time.h"
60#include "glog/logging.h"
61
62namespace ceres {
63namespace internal {
64namespace {
65
66bool EvaluateGradientNorms(Evaluator* evaluator,
67 const Vector& x,
68 LineSearchMinimizer::State* state,
69 std::string* message) {
70 Vector negative_gradient = -state->gradient;
71 Vector projected_gradient_step(x.size());
72 if (!evaluator->Plus(
73 x.data(), negative_gradient.data(), projected_gradient_step.data())) {
74 *message = "projected_gradient_step = Plus(x, -gradient) failed.";
75 return false;
76 }
77
78 state->gradient_squared_norm = (x - projected_gradient_step).squaredNorm();
79 state->gradient_max_norm =
80 (x - projected_gradient_step).lpNorm<Eigen::Infinity>();
81 return true;
82}
83
84} // namespace
85
86void LineSearchMinimizer::Minimize(const Minimizer::Options& options,
87 double* parameters,
88 Solver::Summary* summary) {
89 const bool is_not_silent = !options.is_silent;
90 double start_time = WallTimeInSeconds();
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080091 double iteration_start_time = start_time;
Austin Schuh70cc9552019-01-21 19:46:48 -080092
93 CHECK(options.evaluator != nullptr);
94 Evaluator* evaluator = options.evaluator.get();
95 const int num_parameters = evaluator->NumParameters();
96 const int num_effective_parameters = evaluator->NumEffectiveParameters();
97
98 summary->termination_type = NO_CONVERGENCE;
99 summary->num_successful_steps = 0;
100 summary->num_unsuccessful_steps = 0;
101
102 VectorRef x(parameters, num_parameters);
103
104 State current_state(num_parameters, num_effective_parameters);
105 State previous_state(num_parameters, num_effective_parameters);
106
107 IterationSummary iteration_summary;
108 iteration_summary.iteration = 0;
109 iteration_summary.step_is_valid = false;
110 iteration_summary.step_is_successful = false;
111 iteration_summary.cost_change = 0.0;
112 iteration_summary.gradient_max_norm = 0.0;
113 iteration_summary.gradient_norm = 0.0;
114 iteration_summary.step_norm = 0.0;
115 iteration_summary.linear_solver_iterations = 0;
116 iteration_summary.step_solver_time_in_seconds = 0;
117
118 // Do initial cost and gradient evaluation.
119 if (!evaluator->Evaluate(x.data(),
120 &(current_state.cost),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800121 nullptr,
Austin Schuh70cc9552019-01-21 19:46:48 -0800122 current_state.gradient.data(),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800123 nullptr)) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800124 summary->termination_type = FAILURE;
125 summary->message = "Initial cost and jacobian evaluation failed.";
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800126 if (is_not_silent) {
127 LOG(WARNING) << "Terminating: " << summary->message;
128 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800129 return;
130 }
131
132 if (!EvaluateGradientNorms(evaluator, x, &current_state, &summary->message)) {
133 summary->termination_type = FAILURE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800134 summary->message =
135 "Initial cost and jacobian evaluation failed. More details: " +
136 summary->message;
137 if (is_not_silent) {
138 LOG(WARNING) << "Terminating: " << summary->message;
139 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800140 return;
141 }
142
143 summary->initial_cost = current_state.cost + summary->fixed_cost;
144 iteration_summary.cost = current_state.cost + summary->fixed_cost;
145
146 iteration_summary.gradient_norm = sqrt(current_state.gradient_squared_norm);
147 iteration_summary.gradient_max_norm = current_state.gradient_max_norm;
148 if (iteration_summary.gradient_max_norm <= options.gradient_tolerance) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800149 summary->message =
150 StringPrintf("Gradient tolerance reached. Gradient max norm: %e <= %e",
151 iteration_summary.gradient_max_norm,
152 options.gradient_tolerance);
Austin Schuh70cc9552019-01-21 19:46:48 -0800153 summary->termination_type = CONVERGENCE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800154 if (is_not_silent) {
155 VLOG(1) << "Terminating: " << summary->message;
156 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800157 return;
158 }
159
160 iteration_summary.iteration_time_in_seconds =
161 WallTimeInSeconds() - iteration_start_time;
162 iteration_summary.cumulative_time_in_seconds =
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800163 WallTimeInSeconds() - start_time + summary->preprocessor_time_in_seconds;
Austin Schuh70cc9552019-01-21 19:46:48 -0800164 summary->iterations.push_back(iteration_summary);
165
166 LineSearchDirection::Options line_search_direction_options;
167 line_search_direction_options.num_parameters = num_effective_parameters;
168 line_search_direction_options.type = options.line_search_direction_type;
169 line_search_direction_options.nonlinear_conjugate_gradient_type =
170 options.nonlinear_conjugate_gradient_type;
171 line_search_direction_options.max_lbfgs_rank = options.max_lbfgs_rank;
172 line_search_direction_options.use_approximate_eigenvalue_bfgs_scaling =
173 options.use_approximate_eigenvalue_bfgs_scaling;
174 std::unique_ptr<LineSearchDirection> line_search_direction(
175 LineSearchDirection::Create(line_search_direction_options));
176
177 LineSearchFunction line_search_function(evaluator);
178
179 LineSearch::Options line_search_options;
180 line_search_options.interpolation_type =
181 options.line_search_interpolation_type;
182 line_search_options.min_step_size = options.min_line_search_step_size;
183 line_search_options.sufficient_decrease =
184 options.line_search_sufficient_function_decrease;
185 line_search_options.max_step_contraction =
186 options.max_line_search_step_contraction;
187 line_search_options.min_step_contraction =
188 options.min_line_search_step_contraction;
189 line_search_options.max_num_iterations =
190 options.max_num_line_search_step_size_iterations;
191 line_search_options.sufficient_curvature_decrease =
192 options.line_search_sufficient_curvature_decrease;
193 line_search_options.max_step_expansion =
194 options.max_line_search_step_expansion;
195 line_search_options.is_silent = options.is_silent;
196 line_search_options.function = &line_search_function;
197
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800198 std::unique_ptr<LineSearch> line_search(LineSearch::Create(
199 options.line_search_type, line_search_options, &summary->message));
200 if (line_search.get() == nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800201 summary->termination_type = FAILURE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800202 if (is_not_silent) {
203 LOG(ERROR) << "Terminating: " << summary->message;
204 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800205 return;
206 }
207
208 LineSearch::Summary line_search_summary;
209 int num_line_search_direction_restarts = 0;
210
211 while (true) {
212 if (!RunCallbacks(options, iteration_summary, summary)) {
213 break;
214 }
215
216 iteration_start_time = WallTimeInSeconds();
217 if (iteration_summary.iteration >= options.max_num_iterations) {
218 summary->message = "Maximum number of iterations reached.";
219 summary->termination_type = NO_CONVERGENCE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800220 if (is_not_silent) {
221 VLOG(1) << "Terminating: " << summary->message;
222 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800223 break;
224 }
225
226 const double total_solver_time = iteration_start_time - start_time +
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800227 summary->preprocessor_time_in_seconds;
Austin Schuh70cc9552019-01-21 19:46:48 -0800228 if (total_solver_time >= options.max_solver_time_in_seconds) {
229 summary->message = "Maximum solver time reached.";
230 summary->termination_type = NO_CONVERGENCE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800231 if (is_not_silent) {
232 VLOG(1) << "Terminating: " << summary->message;
233 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800234 break;
235 }
236
237 iteration_summary = IterationSummary();
238 iteration_summary.iteration = summary->iterations.back().iteration + 1;
239 iteration_summary.step_is_valid = false;
240 iteration_summary.step_is_successful = false;
241
242 bool line_search_status = true;
243 if (iteration_summary.iteration == 1) {
244 current_state.search_direction = -current_state.gradient;
245 } else {
246 line_search_status = line_search_direction->NextDirection(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800247 previous_state, current_state, &current_state.search_direction);
Austin Schuh70cc9552019-01-21 19:46:48 -0800248 }
249
250 if (!line_search_status &&
251 num_line_search_direction_restarts >=
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800252 options.max_num_line_search_direction_restarts) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800253 // Line search direction failed to generate a new direction, and we
254 // have already reached our specified maximum number of restarts,
255 // terminate optimization.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800256 summary->message = StringPrintf(
257 "Line search direction failure: specified "
258 "max_num_line_search_direction_restarts: %d reached.",
259 options.max_num_line_search_direction_restarts);
Austin Schuh70cc9552019-01-21 19:46:48 -0800260 summary->termination_type = FAILURE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800261 if (is_not_silent) {
262 LOG(WARNING) << "Terminating: " << summary->message;
263 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800264 break;
265 } else if (!line_search_status) {
266 // Restart line search direction with gradient descent on first iteration
267 // as we have not yet reached our maximum number of restarts.
268 CHECK_LT(num_line_search_direction_restarts,
269 options.max_num_line_search_direction_restarts);
270
271 ++num_line_search_direction_restarts;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800272 if (is_not_silent) {
273 LOG(WARNING) << "Line search direction algorithm: "
274 << LineSearchDirectionTypeToString(
275 options.line_search_direction_type)
276 << ", failed to produce a valid new direction at "
277 << "iteration: " << iteration_summary.iteration
278 << ". Restarting, number of restarts: "
279 << num_line_search_direction_restarts << " / "
280 << options.max_num_line_search_direction_restarts
281 << " [max].";
282 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800283 line_search_direction.reset(
284 LineSearchDirection::Create(line_search_direction_options));
285 current_state.search_direction = -current_state.gradient;
286 }
287
288 line_search_function.Init(x, current_state.search_direction);
289 current_state.directional_derivative =
290 current_state.gradient.dot(current_state.search_direction);
291
292 // TODO(sameeragarwal): Refactor this into its own object and add
293 // explanations for the various choices.
294 //
295 // Note that we use !line_search_status to ensure that we treat cases when
296 // we restarted the line search direction equivalently to the first
297 // iteration.
298 const double initial_step_size =
299 (iteration_summary.iteration == 1 || !line_search_status)
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800300 ? std::min(1.0, 1.0 / current_state.gradient_max_norm)
301 : std::min(1.0,
302 2.0 * (current_state.cost - previous_state.cost) /
303 current_state.directional_derivative);
Austin Schuh70cc9552019-01-21 19:46:48 -0800304 // By definition, we should only ever go forwards along the specified search
305 // direction in a line search, most likely cause for this being violated
306 // would be a numerical failure in the line search direction calculation.
307 if (initial_step_size < 0.0) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800308 summary->message = StringPrintf(
309 "Numerical failure in line search, initial_step_size is "
310 "negative: %.5e, directional_derivative: %.5e, "
311 "(current_cost - previous_cost): %.5e",
312 initial_step_size,
313 current_state.directional_derivative,
314 (current_state.cost - previous_state.cost));
Austin Schuh70cc9552019-01-21 19:46:48 -0800315 summary->termination_type = FAILURE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800316 if (is_not_silent) {
317 LOG(WARNING) << "Terminating: " << summary->message;
318 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800319 break;
320 }
321
322 line_search->Search(initial_step_size,
323 current_state.cost,
324 current_state.directional_derivative,
325 &line_search_summary);
326 if (!line_search_summary.success) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800327 summary->message = StringPrintf(
328 "Numerical failure in line search, failed to find "
329 "a valid step size, (did not run out of iterations) "
330 "using initial_step_size: %.5e, initial_cost: %.5e, "
331 "initial_gradient: %.5e.",
332 initial_step_size,
333 current_state.cost,
334 current_state.directional_derivative);
335 if (is_not_silent) {
336 LOG(WARNING) << "Terminating: " << summary->message;
337 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800338 summary->termination_type = FAILURE;
339 break;
340 }
341
342 const FunctionSample& optimal_point = line_search_summary.optimal_point;
343 CHECK(optimal_point.vector_x_is_valid)
344 << "Congratulations, you found a bug in Ceres. Please report it.";
345 current_state.step_size = optimal_point.x;
346 previous_state = current_state;
347 iteration_summary.step_solver_time_in_seconds =
348 WallTimeInSeconds() - iteration_start_time;
349
350 if (optimal_point.vector_gradient_is_valid) {
351 current_state.cost = optimal_point.value;
352 current_state.gradient = optimal_point.vector_gradient;
353 } else {
354 Evaluator::EvaluateOptions evaluate_options;
355 evaluate_options.new_evaluation_point = false;
356 if (!evaluator->Evaluate(evaluate_options,
357 optimal_point.vector_x.data(),
358 &(current_state.cost),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800359 nullptr,
Austin Schuh70cc9552019-01-21 19:46:48 -0800360 current_state.gradient.data(),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800361 nullptr)) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800362 summary->termination_type = FAILURE;
363 summary->message = "Cost and jacobian evaluation failed.";
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800364 if (is_not_silent) {
365 LOG(WARNING) << "Terminating: " << summary->message;
366 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800367 return;
368 }
369 }
370
371 if (!EvaluateGradientNorms(evaluator,
372 optimal_point.vector_x,
373 &current_state,
374 &summary->message)) {
375 summary->termination_type = FAILURE;
376 summary->message =
377 "Step failed to evaluate. This should not happen as the step was "
378 "valid when it was selected by the line search. More details: " +
379 summary->message;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800380 if (is_not_silent) {
381 LOG(WARNING) << "Terminating: " << summary->message;
382 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800383 break;
384 }
385
386 // Compute the norm of the step in the ambient space.
387 iteration_summary.step_norm = (optimal_point.vector_x - x).norm();
388 const double x_norm = x.norm();
389 x = optimal_point.vector_x;
390
391 iteration_summary.gradient_max_norm = current_state.gradient_max_norm;
392 iteration_summary.gradient_norm = sqrt(current_state.gradient_squared_norm);
393 iteration_summary.cost_change = previous_state.cost - current_state.cost;
394 iteration_summary.cost = current_state.cost + summary->fixed_cost;
395
396 iteration_summary.step_is_valid = true;
397 iteration_summary.step_is_successful = true;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800398 iteration_summary.step_size = current_state.step_size;
Austin Schuh70cc9552019-01-21 19:46:48 -0800399 iteration_summary.line_search_function_evaluations =
400 line_search_summary.num_function_evaluations;
401 iteration_summary.line_search_gradient_evaluations =
402 line_search_summary.num_gradient_evaluations;
403 iteration_summary.line_search_iterations =
404 line_search_summary.num_iterations;
405 iteration_summary.iteration_time_in_seconds =
406 WallTimeInSeconds() - iteration_start_time;
407 iteration_summary.cumulative_time_in_seconds =
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800408 WallTimeInSeconds() - start_time +
409 summary->preprocessor_time_in_seconds;
Austin Schuh70cc9552019-01-21 19:46:48 -0800410 summary->iterations.push_back(iteration_summary);
411
412 // Iterations inside the line search algorithm are considered
413 // 'steps' in the broader context, to distinguish these inner
414 // iterations from from the outer iterations of the line search
415 // minimizer. The number of line search steps is the total number
416 // of inner line search iterations (or steps) across the entire
417 // minimization.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800418 summary->num_line_search_steps += line_search_summary.num_iterations;
Austin Schuh70cc9552019-01-21 19:46:48 -0800419 summary->line_search_cost_evaluation_time_in_seconds +=
420 line_search_summary.cost_evaluation_time_in_seconds;
421 summary->line_search_gradient_evaluation_time_in_seconds +=
422 line_search_summary.gradient_evaluation_time_in_seconds;
423 summary->line_search_polynomial_minimization_time_in_seconds +=
424 line_search_summary.polynomial_minimization_time_in_seconds;
425 summary->line_search_total_time_in_seconds +=
426 line_search_summary.total_time_in_seconds;
427 ++summary->num_successful_steps;
428
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800429 const double step_size_tolerance =
430 options.parameter_tolerance * (x_norm + options.parameter_tolerance);
Austin Schuh70cc9552019-01-21 19:46:48 -0800431 if (iteration_summary.step_norm <= step_size_tolerance) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800432 summary->message = StringPrintf(
433 "Parameter tolerance reached. "
434 "Relative step_norm: %e <= %e.",
435 (iteration_summary.step_norm /
436 (x_norm + options.parameter_tolerance)),
437 options.parameter_tolerance);
Austin Schuh70cc9552019-01-21 19:46:48 -0800438 summary->termination_type = CONVERGENCE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800439 if (is_not_silent) {
440 VLOG(1) << "Terminating: " << summary->message;
441 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800442 return;
443 }
444
445 if (iteration_summary.gradient_max_norm <= options.gradient_tolerance) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800446 summary->message = StringPrintf(
447 "Gradient tolerance reached. "
448 "Gradient max norm: %e <= %e",
449 iteration_summary.gradient_max_norm,
450 options.gradient_tolerance);
Austin Schuh70cc9552019-01-21 19:46:48 -0800451 summary->termination_type = CONVERGENCE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800452 if (is_not_silent) {
453 VLOG(1) << "Terminating: " << summary->message;
454 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800455 break;
456 }
457
458 const double absolute_function_tolerance =
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800459 options.function_tolerance * std::abs(previous_state.cost);
460 if (std::abs(iteration_summary.cost_change) <=
461 absolute_function_tolerance) {
462 summary->message = StringPrintf(
463 "Function tolerance reached. "
464 "|cost_change|/cost: %e <= %e",
465 std::abs(iteration_summary.cost_change) / previous_state.cost,
466 options.function_tolerance);
Austin Schuh70cc9552019-01-21 19:46:48 -0800467 summary->termination_type = CONVERGENCE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800468 if (is_not_silent) {
469 VLOG(1) << "Terminating: " << summary->message;
470 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800471 break;
472 }
473 }
474}
475
476} // namespace internal
477} // namespace ceres