1 // Ceres Solver - A fast non-linear least squares minimizer 2 // Copyright 2014 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: keir (at) google.com (Keir Mierle) 30 // sameeragarwal (at) google.com (Sameer Agarwal) 31 32 #include "ceres/internal/port.h" 33 #include "ceres/solver.h" 34 35 #include <sstream> // NOLINT 36 #include <vector> 37 38 #include "ceres/problem.h" 39 #include "ceres/problem_impl.h" 40 #include "ceres/program.h" 41 #include "ceres/solver_impl.h" 42 #include "ceres/stringprintf.h" 43 #include "ceres/types.h" 44 #include "ceres/version.h" 45 #include "ceres/wall_time.h" 46 47 namespace ceres { 48 namespace { 49 50 #define OPTION_OP(x, y, OP) \ 51 if (!(options.x OP y)) { \ 52 std::stringstream ss; \ 53 ss << "Invalid configuration. "; \ 54 ss << string("Solver::Options::" #x " = ") << options.x << ". "; \ 55 ss << "Violated constraint: "; \ 56 ss << string("Solver::Options::" #x " " #OP " "#y); \ 57 *error = ss.str(); \ 58 return false; \ 59 } 60 61 #define OPTION_OP_OPTION(x, y, OP) \ 62 if (!(options.x OP options.y)) { \ 63 std::stringstream ss; \ 64 ss << "Invalid configuration. "; \ 65 ss << string("Solver::Options::" #x " = ") << options.x << ". "; \ 66 ss << string("Solver::Options::" #y " = ") << options.y << ". "; \ 67 ss << "Violated constraint: "; \ 68 ss << string("Solver::Options::" #x ); \ 69 ss << string(#OP " Solver::Options::" #y "."); \ 70 *error = ss.str(); \ 71 return false; \ 72 } 73 74 #define OPTION_GE(x, y) OPTION_OP(x, y, >=); 75 #define OPTION_GT(x, y) OPTION_OP(x, y, >); 76 #define OPTION_LE(x, y) OPTION_OP(x, y, <=); 77 #define OPTION_LT(x, y) OPTION_OP(x, y, <); 78 #define OPTION_LE_OPTION(x, y) OPTION_OP_OPTION(x ,y, <=) 79 #define OPTION_LT_OPTION(x, y) OPTION_OP_OPTION(x ,y, <) 80 81 bool CommonOptionsAreValid(const Solver::Options& options, string* error) { 82 OPTION_GE(max_num_iterations, 0); 83 OPTION_GE(max_solver_time_in_seconds, 0.0); 84 OPTION_GE(function_tolerance, 0.0); 85 OPTION_GE(gradient_tolerance, 0.0); 86 OPTION_GE(parameter_tolerance, 0.0); 87 OPTION_GT(num_threads, 0); 88 OPTION_GT(num_linear_solver_threads, 0); 89 if (options.check_gradients) { 90 OPTION_GT(gradient_check_relative_precision, 0.0); 91 OPTION_GT(numeric_derivative_relative_step_size, 0.0); 92 } 93 return true; 94 } 95 96 bool TrustRegionOptionsAreValid(const Solver::Options& options, string* error) { 97 OPTION_GT(initial_trust_region_radius, 0.0); 98 OPTION_GT(min_trust_region_radius, 0.0); 99 OPTION_GT(max_trust_region_radius, 0.0); 100 OPTION_LE_OPTION(min_trust_region_radius, max_trust_region_radius); 101 OPTION_LE_OPTION(min_trust_region_radius, initial_trust_region_radius); 102 OPTION_LE_OPTION(initial_trust_region_radius, max_trust_region_radius); 103 OPTION_GE(min_relative_decrease, 0.0); 104 OPTION_GE(min_lm_diagonal, 0.0); 105 OPTION_GE(max_lm_diagonal, 0.0); 106 OPTION_LE_OPTION(min_lm_diagonal, max_lm_diagonal); 107 OPTION_GE(max_num_consecutive_invalid_steps, 0); 108 OPTION_GT(eta, 0.0); 109 OPTION_GE(min_linear_solver_iterations, 1); 110 OPTION_GE(max_linear_solver_iterations, 1); 111 OPTION_LE_OPTION(min_linear_solver_iterations, max_linear_solver_iterations); 112 113 if (options.use_inner_iterations) { 114 OPTION_GE(inner_iteration_tolerance, 0.0); 115 } 116 117 if (options.use_nonmonotonic_steps) { 118 OPTION_GT(max_consecutive_nonmonotonic_steps, 0); 119 } 120 121 if (options.preconditioner_type == CLUSTER_JACOBI && 122 options.sparse_linear_algebra_library_type != SUITE_SPARSE) { 123 *error = "CLUSTER_JACOBI requires " 124 "Solver::Options::sparse_linear_algebra_library_type to be " 125 "SUITE_SPARSE"; 126 return false; 127 } 128 129 if (options.preconditioner_type == CLUSTER_TRIDIAGONAL && 130 options.sparse_linear_algebra_library_type != SUITE_SPARSE) { 131 *error = "CLUSTER_TRIDIAGONAL requires " 132 "Solver::Options::sparse_linear_algebra_library_type to be " 133 "SUITE_SPARSE"; 134 return false; 135 } 136 137 #ifdef CERES_NO_LAPACK 138 if (options.dense_linear_algebra_library_type == LAPACK) { 139 if (options.linear_solver_type == DENSE_NORMAL_CHOLESKY) { 140 *error = "Can't use DENSE_NORMAL_CHOLESKY with LAPACK because " 141 "LAPACK was not enabled when Ceres was built."; 142 return false; 143 } 144 145 if (options.linear_solver_type == DENSE_QR) { 146 *error = "Can't use DENSE_QR with LAPACK because " 147 "LAPACK was not enabled when Ceres was built."; 148 return false; 149 } 150 151 if (options.linear_solver_type == DENSE_SCHUR) { 152 *error = "Can't use DENSE_SCHUR with LAPACK because " 153 "LAPACK was not enabled when Ceres was built."; 154 return false; 155 } 156 } 157 #endif 158 159 #ifdef CERES_NO_SUITESPARSE 160 if (options.sparse_linear_algebra_library_type == SUITE_SPARSE) { 161 if (options.linear_solver_type == SPARSE_NORMAL_CHOLESKY) { 162 *error = "Can't use SPARSE_NORMAL_CHOLESKY with SUITESPARSE because " 163 "SuiteSparse was not enabled when Ceres was built."; 164 return false; 165 } 166 167 if (options.linear_solver_type == SPARSE_SCHUR) { 168 *error = "Can't use SPARSE_SCHUR with SUITESPARSE because " 169 "SuiteSparse was not enabled when Ceres was built."; 170 return false; 171 } 172 173 if (options.preconditioner_type == CLUSTER_JACOBI) { 174 *error = "CLUSTER_JACOBI preconditioner not supported. " 175 "SuiteSparse was not enabled when Ceres was built."; 176 return false; 177 } 178 179 if (options.preconditioner_type == CLUSTER_TRIDIAGONAL) { 180 *error = "CLUSTER_TRIDIAGONAL preconditioner not supported. " 181 "SuiteSparse was not enabled when Ceres was built."; 182 return false; 183 } 184 } 185 #endif 186 187 #ifdef CERES_NO_CXSPARSE 188 if (options.sparse_linear_algebra_library_type == CX_SPARSE) { 189 if (options.linear_solver_type == SPARSE_NORMAL_CHOLESKY) { 190 *error = "Can't use SPARSE_NORMAL_CHOLESKY with CX_SPARSE because " 191 "CXSparse was not enabled when Ceres was built."; 192 return false; 193 } 194 195 if (options.linear_solver_type == SPARSE_SCHUR) { 196 *error = "Can't use SPARSE_SCHUR with CX_SPARSE because " 197 "CXSparse was not enabled when Ceres was built."; 198 return false; 199 } 200 } 201 #endif 202 203 if (options.trust_region_strategy_type == DOGLEG) { 204 if (options.linear_solver_type == ITERATIVE_SCHUR || 205 options.linear_solver_type == CGNR) { 206 *error = "DOGLEG only supports exact factorization based linear " 207 "solvers. If you want to use an iterative solver please " 208 "use LEVENBERG_MARQUARDT as the trust_region_strategy_type"; 209 return false; 210 } 211 } 212 213 if (options.trust_region_minimizer_iterations_to_dump.size() > 0 && 214 options.trust_region_problem_dump_format_type != CONSOLE && 215 options.trust_region_problem_dump_directory.empty()) { 216 *error = "Solver::Options::trust_region_problem_dump_directory is empty."; 217 return false; 218 } 219 220 if (options.dynamic_sparsity && 221 options.linear_solver_type != SPARSE_NORMAL_CHOLESKY) { 222 *error = "Dynamic sparsity is only supported with SPARSE_NORMAL_CHOLESKY."; 223 return false; 224 } 225 226 return true; 227 } 228 229 bool LineSearchOptionsAreValid(const Solver::Options& options, string* error) { 230 OPTION_GT(max_lbfgs_rank, 0); 231 OPTION_GT(min_line_search_step_size, 0.0); 232 OPTION_GT(max_line_search_step_contraction, 0.0); 233 OPTION_LT(max_line_search_step_contraction, 1.0); 234 OPTION_LT_OPTION(max_line_search_step_contraction, 235 min_line_search_step_contraction); 236 OPTION_LE(min_line_search_step_contraction, 1.0); 237 OPTION_GT(max_num_line_search_step_size_iterations, 0); 238 OPTION_GT(line_search_sufficient_function_decrease, 0.0); 239 OPTION_LT_OPTION(line_search_sufficient_function_decrease, 240 line_search_sufficient_curvature_decrease); 241 OPTION_LT(line_search_sufficient_curvature_decrease, 1.0); 242 OPTION_GT(max_line_search_step_expansion, 1.0); 243 244 if ((options.line_search_direction_type == ceres::BFGS || 245 options.line_search_direction_type == ceres::LBFGS) && 246 options.line_search_type != ceres::WOLFE) { 247 248 *error = 249 string("Invalid configuration: Solver::Options::line_search_type = ") 250 + string(LineSearchTypeToString(options.line_search_type)) 251 + string(". When using (L)BFGS, " 252 "Solver::Options::line_search_type must be set to WOLFE."); 253 return false; 254 } 255 256 // Warn user if they have requested BISECTION interpolation, but constraints 257 // on max/min step size change during line search prevent bisection scaling 258 // from occurring. Warn only, as this is likely a user mistake, but one which 259 // does not prevent us from continuing. 260 LOG_IF(WARNING, 261 (options.line_search_interpolation_type == ceres::BISECTION && 262 (options.max_line_search_step_contraction > 0.5 || 263 options.min_line_search_step_contraction < 0.5))) 264 << "Line search interpolation type is BISECTION, but specified " 265 << "max_line_search_step_contraction: " 266 << options.max_line_search_step_contraction << ", and " 267 << "min_line_search_step_contraction: " 268 << options.min_line_search_step_contraction 269 << ", prevent bisection (0.5) scaling, continuing with solve regardless."; 270 271 return true; 272 } 273 274 #undef OPTION_OP 275 #undef OPTION_OP_OPTION 276 #undef OPTION_GT 277 #undef OPTION_GE 278 #undef OPTION_LE 279 #undef OPTION_LT 280 #undef OPTION_LE_OPTION 281 #undef OPTION_LT_OPTION 282 283 void StringifyOrdering(const vector<int>& ordering, string* report) { 284 if (ordering.size() == 0) { 285 internal::StringAppendF(report, "AUTOMATIC"); 286 return; 287 } 288 289 for (int i = 0; i < ordering.size() - 1; ++i) { 290 internal::StringAppendF(report, "%d, ", ordering[i]); 291 } 292 internal::StringAppendF(report, "%d", ordering.back()); 293 } 294 295 } // namespace 296 297 bool Solver::Options::IsValid(string* error) const { 298 if (!CommonOptionsAreValid(*this, error)) { 299 return false; 300 } 301 302 if (minimizer_type == TRUST_REGION) { 303 return TrustRegionOptionsAreValid(*this, error); 304 } 305 306 CHECK_EQ(minimizer_type, LINE_SEARCH); 307 return LineSearchOptionsAreValid(*this, error); 308 } 309 310 Solver::~Solver() {} 311 312 void Solver::Solve(const Solver::Options& options, 313 Problem* problem, 314 Solver::Summary* summary) { 315 double start_time_seconds = internal::WallTimeInSeconds(); 316 CHECK_NOTNULL(problem); 317 CHECK_NOTNULL(summary); 318 319 *summary = Summary(); 320 if (!options.IsValid(&summary->message)) { 321 LOG(ERROR) << "Terminating: " << summary->message; 322 return; 323 } 324 325 internal::ProblemImpl* problem_impl = problem->problem_impl_.get(); 326 internal::SolverImpl::Solve(options, problem_impl, summary); 327 summary->total_time_in_seconds = 328 internal::WallTimeInSeconds() - start_time_seconds; 329 } 330 331 void Solve(const Solver::Options& options, 332 Problem* problem, 333 Solver::Summary* summary) { 334 Solver solver; 335 solver.Solve(options, problem, summary); 336 } 337 338 Solver::Summary::Summary() 339 // Invalid values for most fields, to ensure that we are not 340 // accidentally reporting default values. 341 : minimizer_type(TRUST_REGION), 342 termination_type(FAILURE), 343 message("ceres::Solve was not called."), 344 initial_cost(-1.0), 345 final_cost(-1.0), 346 fixed_cost(-1.0), 347 num_successful_steps(-1), 348 num_unsuccessful_steps(-1), 349 num_inner_iteration_steps(-1), 350 preprocessor_time_in_seconds(-1.0), 351 minimizer_time_in_seconds(-1.0), 352 postprocessor_time_in_seconds(-1.0), 353 total_time_in_seconds(-1.0), 354 linear_solver_time_in_seconds(-1.0), 355 residual_evaluation_time_in_seconds(-1.0), 356 jacobian_evaluation_time_in_seconds(-1.0), 357 inner_iteration_time_in_seconds(-1.0), 358 num_parameter_blocks(-1), 359 num_parameters(-1), 360 num_effective_parameters(-1), 361 num_residual_blocks(-1), 362 num_residuals(-1), 363 num_parameter_blocks_reduced(-1), 364 num_parameters_reduced(-1), 365 num_effective_parameters_reduced(-1), 366 num_residual_blocks_reduced(-1), 367 num_residuals_reduced(-1), 368 num_threads_given(-1), 369 num_threads_used(-1), 370 num_linear_solver_threads_given(-1), 371 num_linear_solver_threads_used(-1), 372 linear_solver_type_given(SPARSE_NORMAL_CHOLESKY), 373 linear_solver_type_used(SPARSE_NORMAL_CHOLESKY), 374 inner_iterations_given(false), 375 inner_iterations_used(false), 376 preconditioner_type(IDENTITY), 377 visibility_clustering_type(CANONICAL_VIEWS), 378 trust_region_strategy_type(LEVENBERG_MARQUARDT), 379 dense_linear_algebra_library_type(EIGEN), 380 sparse_linear_algebra_library_type(SUITE_SPARSE), 381 line_search_direction_type(LBFGS), 382 line_search_type(ARMIJO), 383 line_search_interpolation_type(BISECTION), 384 nonlinear_conjugate_gradient_type(FLETCHER_REEVES), 385 max_lbfgs_rank(-1) { 386 } 387 388 using internal::StringAppendF; 389 using internal::StringPrintf; 390 391 string Solver::Summary::BriefReport() const { 392 return StringPrintf("Ceres Solver Report: " 393 "Iterations: %d, " 394 "Initial cost: %e, " 395 "Final cost: %e, " 396 "Termination: %s", 397 num_successful_steps + num_unsuccessful_steps, 398 initial_cost, 399 final_cost, 400 TerminationTypeToString(termination_type)); 401 }; 402 403 string Solver::Summary::FullReport() const { 404 string report = 405 "\n" 406 "Ceres Solver v" CERES_VERSION_STRING " Solve Report\n" 407 "----------------------------------\n"; 408 409 StringAppendF(&report, "%45s %21s\n", "Original", "Reduced"); 410 StringAppendF(&report, "Parameter blocks % 25d% 25d\n", 411 num_parameter_blocks, num_parameter_blocks_reduced); 412 StringAppendF(&report, "Parameters % 25d% 25d\n", 413 num_parameters, num_parameters_reduced); 414 if (num_effective_parameters_reduced != num_parameters_reduced) { 415 StringAppendF(&report, "Effective parameters% 25d% 25d\n", 416 num_effective_parameters, num_effective_parameters_reduced); 417 } 418 StringAppendF(&report, "Residual blocks % 25d% 25d\n", 419 num_residual_blocks, num_residual_blocks_reduced); 420 StringAppendF(&report, "Residual % 25d% 25d\n", 421 num_residuals, num_residuals_reduced); 422 423 if (minimizer_type == TRUST_REGION) { 424 // TRUST_SEARCH HEADER 425 StringAppendF(&report, "\nMinimizer %19s\n", 426 "TRUST_REGION"); 427 428 if (linear_solver_type_used == DENSE_NORMAL_CHOLESKY || 429 linear_solver_type_used == DENSE_SCHUR || 430 linear_solver_type_used == DENSE_QR) { 431 StringAppendF(&report, "\nDense linear algebra library %15s\n", 432 DenseLinearAlgebraLibraryTypeToString( 433 dense_linear_algebra_library_type)); 434 } 435 436 if (linear_solver_type_used == SPARSE_NORMAL_CHOLESKY || 437 linear_solver_type_used == SPARSE_SCHUR || 438 (linear_solver_type_used == ITERATIVE_SCHUR && 439 (preconditioner_type == CLUSTER_JACOBI || 440 preconditioner_type == CLUSTER_TRIDIAGONAL))) { 441 StringAppendF(&report, "\nSparse linear algebra library %15s\n", 442 SparseLinearAlgebraLibraryTypeToString( 443 sparse_linear_algebra_library_type)); 444 } 445 446 StringAppendF(&report, "Trust region strategy %19s", 447 TrustRegionStrategyTypeToString( 448 trust_region_strategy_type)); 449 if (trust_region_strategy_type == DOGLEG) { 450 if (dogleg_type == TRADITIONAL_DOGLEG) { 451 StringAppendF(&report, " (TRADITIONAL)"); 452 } else { 453 StringAppendF(&report, " (SUBSPACE)"); 454 } 455 } 456 StringAppendF(&report, "\n"); 457 StringAppendF(&report, "\n"); 458 459 StringAppendF(&report, "%45s %21s\n", "Given", "Used"); 460 StringAppendF(&report, "Linear solver %25s%25s\n", 461 LinearSolverTypeToString(linear_solver_type_given), 462 LinearSolverTypeToString(linear_solver_type_used)); 463 464 if (linear_solver_type_given == CGNR || 465 linear_solver_type_given == ITERATIVE_SCHUR) { 466 StringAppendF(&report, "Preconditioner %25s%25s\n", 467 PreconditionerTypeToString(preconditioner_type), 468 PreconditionerTypeToString(preconditioner_type)); 469 } 470 471 if (preconditioner_type == CLUSTER_JACOBI || 472 preconditioner_type == CLUSTER_TRIDIAGONAL) { 473 StringAppendF(&report, "Visibility clustering%24s%25s\n", 474 VisibilityClusteringTypeToString( 475 visibility_clustering_type), 476 VisibilityClusteringTypeToString( 477 visibility_clustering_type)); 478 } 479 StringAppendF(&report, "Threads % 25d% 25d\n", 480 num_threads_given, num_threads_used); 481 StringAppendF(&report, "Linear solver threads % 23d% 25d\n", 482 num_linear_solver_threads_given, 483 num_linear_solver_threads_used); 484 485 if (IsSchurType(linear_solver_type_used)) { 486 string given; 487 StringifyOrdering(linear_solver_ordering_given, &given); 488 string used; 489 StringifyOrdering(linear_solver_ordering_used, &used); 490 StringAppendF(&report, 491 "Linear solver ordering %22s %24s\n", 492 given.c_str(), 493 used.c_str()); 494 } 495 496 if (inner_iterations_given) { 497 StringAppendF(&report, 498 "Use inner iterations %20s %20s\n", 499 inner_iterations_given ? "True" : "False", 500 inner_iterations_used ? "True" : "False"); 501 } 502 503 if (inner_iterations_used) { 504 string given; 505 StringifyOrdering(inner_iteration_ordering_given, &given); 506 string used; 507 StringifyOrdering(inner_iteration_ordering_used, &used); 508 StringAppendF(&report, 509 "Inner iteration ordering %20s %24s\n", 510 given.c_str(), 511 used.c_str()); 512 } 513 } else { 514 // LINE_SEARCH HEADER 515 StringAppendF(&report, "\nMinimizer %19s\n", "LINE_SEARCH"); 516 517 518 string line_search_direction_string; 519 if (line_search_direction_type == LBFGS) { 520 line_search_direction_string = StringPrintf("LBFGS (%d)", max_lbfgs_rank); 521 } else if (line_search_direction_type == NONLINEAR_CONJUGATE_GRADIENT) { 522 line_search_direction_string = 523 NonlinearConjugateGradientTypeToString( 524 nonlinear_conjugate_gradient_type); 525 } else { 526 line_search_direction_string = 527 LineSearchDirectionTypeToString(line_search_direction_type); 528 } 529 530 StringAppendF(&report, "Line search direction %19s\n", 531 line_search_direction_string.c_str()); 532 533 const string line_search_type_string = 534 StringPrintf("%s %s", 535 LineSearchInterpolationTypeToString( 536 line_search_interpolation_type), 537 LineSearchTypeToString(line_search_type)); 538 StringAppendF(&report, "Line search type %19s\n", 539 line_search_type_string.c_str()); 540 StringAppendF(&report, "\n"); 541 542 StringAppendF(&report, "%45s %21s\n", "Given", "Used"); 543 StringAppendF(&report, "Threads % 25d% 25d\n", 544 num_threads_given, num_threads_used); 545 } 546 547 StringAppendF(&report, "\nCost:\n"); 548 StringAppendF(&report, "Initial % 30e\n", initial_cost); 549 if (termination_type != FAILURE && 550 termination_type != USER_FAILURE) { 551 StringAppendF(&report, "Final % 30e\n", final_cost); 552 StringAppendF(&report, "Change % 30e\n", 553 initial_cost - final_cost); 554 } 555 556 StringAppendF(&report, "\nMinimizer iterations % 16d\n", 557 num_successful_steps + num_unsuccessful_steps); 558 559 // Successful/Unsuccessful steps only matter in the case of the 560 // trust region solver. Line search terminates when it encounters 561 // the first unsuccessful step. 562 if (minimizer_type == TRUST_REGION) { 563 StringAppendF(&report, "Successful steps % 14d\n", 564 num_successful_steps); 565 StringAppendF(&report, "Unsuccessful steps % 14d\n", 566 num_unsuccessful_steps); 567 } 568 if (inner_iterations_used) { 569 StringAppendF(&report, "Steps with inner iterations % 14d\n", 570 num_inner_iteration_steps); 571 } 572 573 StringAppendF(&report, "\nTime (in seconds):\n"); 574 StringAppendF(&report, "Preprocessor %25.3f\n", 575 preprocessor_time_in_seconds); 576 577 StringAppendF(&report, "\n Residual evaluation %23.3f\n", 578 residual_evaluation_time_in_seconds); 579 StringAppendF(&report, " Jacobian evaluation %23.3f\n", 580 jacobian_evaluation_time_in_seconds); 581 582 if (minimizer_type == TRUST_REGION) { 583 StringAppendF(&report, " Linear solver %23.3f\n", 584 linear_solver_time_in_seconds); 585 } 586 587 if (inner_iterations_used) { 588 StringAppendF(&report, " Inner iterations %23.3f\n", 589 inner_iteration_time_in_seconds); 590 } 591 592 StringAppendF(&report, "Minimizer %25.3f\n\n", 593 minimizer_time_in_seconds); 594 595 StringAppendF(&report, "Postprocessor %24.3f\n", 596 postprocessor_time_in_seconds); 597 598 StringAppendF(&report, "Total %25.3f\n\n", 599 total_time_in_seconds); 600 601 StringAppendF(&report, "Termination: %25s (%s)\n", 602 TerminationTypeToString(termination_type), message.c_str()); 603 return report; 604 }; 605 606 bool Solver::Summary::IsSolutionUsable() const { 607 return (termination_type == CONVERGENCE || 608 termination_type == NO_CONVERGENCE || 609 termination_type == USER_SUCCESS); 610 } 611 612 } // namespace ceres 613