blob: 58a4bf9a871bb9a25313e09da54890fa7ff89879 [file] [log] [blame]
Austin Schuh70cc9552019-01-21 19:46:48 -08001// Ceres Solver - A fast non-linear least squares minimizer
Austin Schuh3de38b02024-06-25 18:25:10 -07002// Copyright 2023 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: sameeragarwal@google.com (Sameer Agarwal)
30//
31// Generic loop for line search based optimization algorithms.
32//
Austin Schuh3de38b02024-06-25 18:25:10 -070033// This is primarily inspired by the minFunc packaged written by Mark
Austin Schuh70cc9552019-01-21 19:46:48 -080034// 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"
Austin Schuh3de38b02024-06-25 18:25:10 -070054#include "ceres/internal/export.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080055#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
Austin Schuh3de38b02024-06-25 18:25:10 -070062namespace ceres::internal {
Austin Schuh70cc9552019-01-21 19:46:48 -080063namespace {
64
65bool EvaluateGradientNorms(Evaluator* evaluator,
66 const Vector& x,
67 LineSearchMinimizer::State* state,
68 std::string* message) {
69 Vector negative_gradient = -state->gradient;
70 Vector projected_gradient_step(x.size());
71 if (!evaluator->Plus(
72 x.data(), negative_gradient.data(), projected_gradient_step.data())) {
73 *message = "projected_gradient_step = Plus(x, -gradient) failed.";
74 return false;
75 }
76
77 state->gradient_squared_norm = (x - projected_gradient_step).squaredNorm();
78 state->gradient_max_norm =
79 (x - projected_gradient_step).lpNorm<Eigen::Infinity>();
80 return true;
81}
82
83} // namespace
84
85void LineSearchMinimizer::Minimize(const Minimizer::Options& options,
86 double* parameters,
87 Solver::Summary* summary) {
88 const bool is_not_silent = !options.is_silent;
89 double start_time = WallTimeInSeconds();
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080090 double iteration_start_time = start_time;
Austin Schuh70cc9552019-01-21 19:46:48 -080091
92 CHECK(options.evaluator != nullptr);
93 Evaluator* evaluator = options.evaluator.get();
94 const int num_parameters = evaluator->NumParameters();
95 const int num_effective_parameters = evaluator->NumEffectiveParameters();
96
97 summary->termination_type = NO_CONVERGENCE;
98 summary->num_successful_steps = 0;
99 summary->num_unsuccessful_steps = 0;
100
101 VectorRef x(parameters, num_parameters);
102
103 State current_state(num_parameters, num_effective_parameters);
104 State previous_state(num_parameters, num_effective_parameters);
105
106 IterationSummary iteration_summary;
107 iteration_summary.iteration = 0;
108 iteration_summary.step_is_valid = false;
109 iteration_summary.step_is_successful = false;
110 iteration_summary.cost_change = 0.0;
111 iteration_summary.gradient_max_norm = 0.0;
112 iteration_summary.gradient_norm = 0.0;
113 iteration_summary.step_norm = 0.0;
114 iteration_summary.linear_solver_iterations = 0;
115 iteration_summary.step_solver_time_in_seconds = 0;
116
117 // Do initial cost and gradient evaluation.
118 if (!evaluator->Evaluate(x.data(),
119 &(current_state.cost),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800120 nullptr,
Austin Schuh70cc9552019-01-21 19:46:48 -0800121 current_state.gradient.data(),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800122 nullptr)) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800123 summary->termination_type = FAILURE;
124 summary->message = "Initial cost and jacobian evaluation failed.";
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800125 if (is_not_silent) {
126 LOG(WARNING) << "Terminating: " << summary->message;
127 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800128 return;
129 }
130
131 if (!EvaluateGradientNorms(evaluator, x, &current_state, &summary->message)) {
132 summary->termination_type = FAILURE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800133 summary->message =
134 "Initial cost and jacobian evaluation failed. More details: " +
135 summary->message;
136 if (is_not_silent) {
137 LOG(WARNING) << "Terminating: " << summary->message;
138 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800139 return;
140 }
141
142 summary->initial_cost = current_state.cost + summary->fixed_cost;
143 iteration_summary.cost = current_state.cost + summary->fixed_cost;
144
145 iteration_summary.gradient_norm = sqrt(current_state.gradient_squared_norm);
146 iteration_summary.gradient_max_norm = current_state.gradient_max_norm;
147 if (iteration_summary.gradient_max_norm <= options.gradient_tolerance) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800148 summary->message =
149 StringPrintf("Gradient tolerance reached. Gradient max norm: %e <= %e",
150 iteration_summary.gradient_max_norm,
151 options.gradient_tolerance);
Austin Schuh70cc9552019-01-21 19:46:48 -0800152 summary->termination_type = CONVERGENCE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800153 if (is_not_silent) {
154 VLOG(1) << "Terminating: " << summary->message;
155 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800156 return;
157 }
158
159 iteration_summary.iteration_time_in_seconds =
160 WallTimeInSeconds() - iteration_start_time;
161 iteration_summary.cumulative_time_in_seconds =
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800162 WallTimeInSeconds() - start_time + summary->preprocessor_time_in_seconds;
Austin Schuh70cc9552019-01-21 19:46:48 -0800163 summary->iterations.push_back(iteration_summary);
164
165 LineSearchDirection::Options line_search_direction_options;
166 line_search_direction_options.num_parameters = num_effective_parameters;
167 line_search_direction_options.type = options.line_search_direction_type;
168 line_search_direction_options.nonlinear_conjugate_gradient_type =
169 options.nonlinear_conjugate_gradient_type;
170 line_search_direction_options.max_lbfgs_rank = options.max_lbfgs_rank;
171 line_search_direction_options.use_approximate_eigenvalue_bfgs_scaling =
172 options.use_approximate_eigenvalue_bfgs_scaling;
Austin Schuh3de38b02024-06-25 18:25:10 -0700173 std::unique_ptr<LineSearchDirection> line_search_direction =
174 LineSearchDirection::Create(line_search_direction_options);
Austin Schuh70cc9552019-01-21 19:46:48 -0800175
176 LineSearchFunction line_search_function(evaluator);
177
178 LineSearch::Options line_search_options;
179 line_search_options.interpolation_type =
180 options.line_search_interpolation_type;
181 line_search_options.min_step_size = options.min_line_search_step_size;
182 line_search_options.sufficient_decrease =
183 options.line_search_sufficient_function_decrease;
184 line_search_options.max_step_contraction =
185 options.max_line_search_step_contraction;
186 line_search_options.min_step_contraction =
187 options.min_line_search_step_contraction;
188 line_search_options.max_num_iterations =
189 options.max_num_line_search_step_size_iterations;
190 line_search_options.sufficient_curvature_decrease =
191 options.line_search_sufficient_curvature_decrease;
192 line_search_options.max_step_expansion =
193 options.max_line_search_step_expansion;
194 line_search_options.is_silent = options.is_silent;
195 line_search_options.function = &line_search_function;
196
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800197 std::unique_ptr<LineSearch> line_search(LineSearch::Create(
198 options.line_search_type, line_search_options, &summary->message));
199 if (line_search.get() == nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800200 summary->termination_type = FAILURE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800201 if (is_not_silent) {
202 LOG(ERROR) << "Terminating: " << summary->message;
203 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800204 return;
205 }
206
207 LineSearch::Summary line_search_summary;
208 int num_line_search_direction_restarts = 0;
209
210 while (true) {
211 if (!RunCallbacks(options, iteration_summary, summary)) {
212 break;
213 }
214
215 iteration_start_time = WallTimeInSeconds();
216 if (iteration_summary.iteration >= options.max_num_iterations) {
217 summary->message = "Maximum number of iterations reached.";
218 summary->termination_type = NO_CONVERGENCE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800219 if (is_not_silent) {
220 VLOG(1) << "Terminating: " << summary->message;
221 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800222 break;
223 }
224
225 const double total_solver_time = iteration_start_time - start_time +
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800226 summary->preprocessor_time_in_seconds;
Austin Schuh70cc9552019-01-21 19:46:48 -0800227 if (total_solver_time >= options.max_solver_time_in_seconds) {
228 summary->message = "Maximum solver time reached.";
229 summary->termination_type = NO_CONVERGENCE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800230 if (is_not_silent) {
231 VLOG(1) << "Terminating: " << summary->message;
232 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800233 break;
234 }
235
236 iteration_summary = IterationSummary();
237 iteration_summary.iteration = summary->iterations.back().iteration + 1;
238 iteration_summary.step_is_valid = false;
239 iteration_summary.step_is_successful = false;
240
241 bool line_search_status = true;
242 if (iteration_summary.iteration == 1) {
243 current_state.search_direction = -current_state.gradient;
244 } else {
245 line_search_status = line_search_direction->NextDirection(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800246 previous_state, current_state, &current_state.search_direction);
Austin Schuh70cc9552019-01-21 19:46:48 -0800247 }
248
249 if (!line_search_status &&
250 num_line_search_direction_restarts >=
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800251 options.max_num_line_search_direction_restarts) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800252 // Line search direction failed to generate a new direction, and we
253 // have already reached our specified maximum number of restarts,
254 // terminate optimization.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800255 summary->message = StringPrintf(
256 "Line search direction failure: specified "
257 "max_num_line_search_direction_restarts: %d reached.",
258 options.max_num_line_search_direction_restarts);
Austin Schuh70cc9552019-01-21 19:46:48 -0800259 summary->termination_type = FAILURE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800260 if (is_not_silent) {
261 LOG(WARNING) << "Terminating: " << summary->message;
262 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800263 break;
264 } else if (!line_search_status) {
265 // Restart line search direction with gradient descent on first iteration
266 // as we have not yet reached our maximum number of restarts.
267 CHECK_LT(num_line_search_direction_restarts,
268 options.max_num_line_search_direction_restarts);
269
270 ++num_line_search_direction_restarts;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800271 if (is_not_silent) {
272 LOG(WARNING) << "Line search direction algorithm: "
273 << LineSearchDirectionTypeToString(
274 options.line_search_direction_type)
275 << ", failed to produce a valid new direction at "
276 << "iteration: " << iteration_summary.iteration
277 << ". Restarting, number of restarts: "
278 << num_line_search_direction_restarts << " / "
279 << options.max_num_line_search_direction_restarts
280 << " [max].";
281 }
Austin Schuh3de38b02024-06-25 18:25:10 -0700282 line_search_direction =
283 LineSearchDirection::Create(line_search_direction_options);
Austin Schuh70cc9552019-01-21 19:46:48 -0800284 current_state.search_direction = -current_state.gradient;
285 }
286
287 line_search_function.Init(x, current_state.search_direction);
288 current_state.directional_derivative =
289 current_state.gradient.dot(current_state.search_direction);
290
291 // TODO(sameeragarwal): Refactor this into its own object and add
292 // explanations for the various choices.
293 //
294 // Note that we use !line_search_status to ensure that we treat cases when
295 // we restarted the line search direction equivalently to the first
296 // iteration.
297 const double initial_step_size =
298 (iteration_summary.iteration == 1 || !line_search_status)
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800299 ? std::min(1.0, 1.0 / current_state.gradient_max_norm)
300 : std::min(1.0,
301 2.0 * (current_state.cost - previous_state.cost) /
302 current_state.directional_derivative);
Austin Schuh70cc9552019-01-21 19:46:48 -0800303 // By definition, we should only ever go forwards along the specified search
304 // direction in a line search, most likely cause for this being violated
305 // would be a numerical failure in the line search direction calculation.
306 if (initial_step_size < 0.0) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800307 summary->message = StringPrintf(
308 "Numerical failure in line search, initial_step_size is "
309 "negative: %.5e, directional_derivative: %.5e, "
310 "(current_cost - previous_cost): %.5e",
311 initial_step_size,
312 current_state.directional_derivative,
313 (current_state.cost - previous_state.cost));
Austin Schuh70cc9552019-01-21 19:46:48 -0800314 summary->termination_type = FAILURE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800315 if (is_not_silent) {
316 LOG(WARNING) << "Terminating: " << summary->message;
317 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800318 break;
319 }
320
321 line_search->Search(initial_step_size,
322 current_state.cost,
323 current_state.directional_derivative,
324 &line_search_summary);
325 if (!line_search_summary.success) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800326 summary->message = StringPrintf(
327 "Numerical failure in line search, failed to find "
328 "a valid step size, (did not run out of iterations) "
329 "using initial_step_size: %.5e, initial_cost: %.5e, "
330 "initial_gradient: %.5e.",
331 initial_step_size,
332 current_state.cost,
333 current_state.directional_derivative);
334 if (is_not_silent) {
335 LOG(WARNING) << "Terminating: " << summary->message;
336 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800337 summary->termination_type = FAILURE;
338 break;
339 }
340
341 const FunctionSample& optimal_point = line_search_summary.optimal_point;
342 CHECK(optimal_point.vector_x_is_valid)
343 << "Congratulations, you found a bug in Ceres. Please report it.";
344 current_state.step_size = optimal_point.x;
345 previous_state = current_state;
346 iteration_summary.step_solver_time_in_seconds =
347 WallTimeInSeconds() - iteration_start_time;
348
349 if (optimal_point.vector_gradient_is_valid) {
350 current_state.cost = optimal_point.value;
351 current_state.gradient = optimal_point.vector_gradient;
352 } else {
353 Evaluator::EvaluateOptions evaluate_options;
354 evaluate_options.new_evaluation_point = false;
355 if (!evaluator->Evaluate(evaluate_options,
356 optimal_point.vector_x.data(),
357 &(current_state.cost),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800358 nullptr,
Austin Schuh70cc9552019-01-21 19:46:48 -0800359 current_state.gradient.data(),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800360 nullptr)) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800361 summary->termination_type = FAILURE;
362 summary->message = "Cost and jacobian evaluation failed.";
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800363 if (is_not_silent) {
364 LOG(WARNING) << "Terminating: " << summary->message;
365 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800366 return;
367 }
368 }
369
370 if (!EvaluateGradientNorms(evaluator,
371 optimal_point.vector_x,
372 &current_state,
373 &summary->message)) {
374 summary->termination_type = FAILURE;
375 summary->message =
376 "Step failed to evaluate. This should not happen as the step was "
377 "valid when it was selected by the line search. More details: " +
378 summary->message;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800379 if (is_not_silent) {
380 LOG(WARNING) << "Terminating: " << summary->message;
381 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800382 break;
383 }
384
385 // Compute the norm of the step in the ambient space.
386 iteration_summary.step_norm = (optimal_point.vector_x - x).norm();
387 const double x_norm = x.norm();
388 x = optimal_point.vector_x;
389
390 iteration_summary.gradient_max_norm = current_state.gradient_max_norm;
391 iteration_summary.gradient_norm = sqrt(current_state.gradient_squared_norm);
392 iteration_summary.cost_change = previous_state.cost - current_state.cost;
393 iteration_summary.cost = current_state.cost + summary->fixed_cost;
394
395 iteration_summary.step_is_valid = true;
396 iteration_summary.step_is_successful = true;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800397 iteration_summary.step_size = current_state.step_size;
Austin Schuh70cc9552019-01-21 19:46:48 -0800398 iteration_summary.line_search_function_evaluations =
399 line_search_summary.num_function_evaluations;
400 iteration_summary.line_search_gradient_evaluations =
401 line_search_summary.num_gradient_evaluations;
402 iteration_summary.line_search_iterations =
403 line_search_summary.num_iterations;
404 iteration_summary.iteration_time_in_seconds =
405 WallTimeInSeconds() - iteration_start_time;
406 iteration_summary.cumulative_time_in_seconds =
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800407 WallTimeInSeconds() - start_time +
408 summary->preprocessor_time_in_seconds;
Austin Schuh70cc9552019-01-21 19:46:48 -0800409 summary->iterations.push_back(iteration_summary);
410
411 // Iterations inside the line search algorithm are considered
412 // 'steps' in the broader context, to distinguish these inner
413 // iterations from from the outer iterations of the line search
414 // minimizer. The number of line search steps is the total number
415 // of inner line search iterations (or steps) across the entire
416 // minimization.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800417 summary->num_line_search_steps += line_search_summary.num_iterations;
Austin Schuh70cc9552019-01-21 19:46:48 -0800418 summary->line_search_cost_evaluation_time_in_seconds +=
419 line_search_summary.cost_evaluation_time_in_seconds;
420 summary->line_search_gradient_evaluation_time_in_seconds +=
421 line_search_summary.gradient_evaluation_time_in_seconds;
422 summary->line_search_polynomial_minimization_time_in_seconds +=
423 line_search_summary.polynomial_minimization_time_in_seconds;
424 summary->line_search_total_time_in_seconds +=
425 line_search_summary.total_time_in_seconds;
426 ++summary->num_successful_steps;
427
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800428 const double step_size_tolerance =
429 options.parameter_tolerance * (x_norm + options.parameter_tolerance);
Austin Schuh70cc9552019-01-21 19:46:48 -0800430 if (iteration_summary.step_norm <= step_size_tolerance) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800431 summary->message = StringPrintf(
432 "Parameter tolerance reached. "
433 "Relative step_norm: %e <= %e.",
434 (iteration_summary.step_norm /
435 (x_norm + options.parameter_tolerance)),
436 options.parameter_tolerance);
Austin Schuh70cc9552019-01-21 19:46:48 -0800437 summary->termination_type = CONVERGENCE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800438 if (is_not_silent) {
439 VLOG(1) << "Terminating: " << summary->message;
440 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800441 return;
442 }
443
444 if (iteration_summary.gradient_max_norm <= options.gradient_tolerance) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800445 summary->message = StringPrintf(
446 "Gradient tolerance reached. "
447 "Gradient max norm: %e <= %e",
448 iteration_summary.gradient_max_norm,
449 options.gradient_tolerance);
Austin Schuh70cc9552019-01-21 19:46:48 -0800450 summary->termination_type = CONVERGENCE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800451 if (is_not_silent) {
452 VLOG(1) << "Terminating: " << summary->message;
453 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800454 break;
455 }
456
457 const double absolute_function_tolerance =
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800458 options.function_tolerance * std::abs(previous_state.cost);
459 if (std::abs(iteration_summary.cost_change) <=
460 absolute_function_tolerance) {
461 summary->message = StringPrintf(
462 "Function tolerance reached. "
463 "|cost_change|/cost: %e <= %e",
464 std::abs(iteration_summary.cost_change) / previous_state.cost,
465 options.function_tolerance);
Austin Schuh70cc9552019-01-21 19:46:48 -0800466 summary->termination_type = CONVERGENCE;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800467 if (is_not_silent) {
468 VLOG(1) << "Terminating: " << summary->message;
469 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800470 break;
471 }
472 }
473}
474
Austin Schuh3de38b02024-06-25 18:25:10 -0700475} // namespace ceres::internal