1 // Ceres Solver - A fast non-linear least squares minimizer 2 // Copyright 2010, 2011, 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: keir (at) google.com (Keir Mierle) 30 31 #include "ceres/solver_impl.h" 32 33 #include <cstdio> 34 #include <iostream> // NOLINT 35 #include <numeric> 36 #include <string> 37 #include "ceres/coordinate_descent_minimizer.h" 38 #include "ceres/cxsparse.h" 39 #include "ceres/evaluator.h" 40 #include "ceres/gradient_checking_cost_function.h" 41 #include "ceres/iteration_callback.h" 42 #include "ceres/levenberg_marquardt_strategy.h" 43 #include "ceres/line_search_minimizer.h" 44 #include "ceres/linear_solver.h" 45 #include "ceres/map_util.h" 46 #include "ceres/minimizer.h" 47 #include "ceres/ordered_groups.h" 48 #include "ceres/parameter_block.h" 49 #include "ceres/parameter_block_ordering.h" 50 #include "ceres/problem.h" 51 #include "ceres/problem_impl.h" 52 #include "ceres/program.h" 53 #include "ceres/residual_block.h" 54 #include "ceres/stringprintf.h" 55 #include "ceres/suitesparse.h" 56 #include "ceres/trust_region_minimizer.h" 57 #include "ceres/wall_time.h" 58 59 namespace ceres { 60 namespace internal { 61 namespace { 62 63 // Callback for updating the user's parameter blocks. Updates are only 64 // done if the step is successful. 65 class StateUpdatingCallback : public IterationCallback { 66 public: 67 StateUpdatingCallback(Program* program, double* parameters) 68 : program_(program), parameters_(parameters) {} 69 70 CallbackReturnType operator()(const IterationSummary& summary) { 71 if (summary.step_is_successful) { 72 program_->StateVectorToParameterBlocks(parameters_); 73 program_->CopyParameterBlockStateToUserState(); 74 } 75 return SOLVER_CONTINUE; 76 } 77 78 private: 79 Program* program_; 80 double* parameters_; 81 }; 82 83 void SetSummaryFinalCost(Solver::Summary* summary) { 84 summary->final_cost = summary->initial_cost; 85 // We need the loop here, instead of just looking at the last 86 // iteration because the minimizer maybe making non-monotonic steps. 87 for (int i = 0; i < summary->iterations.size(); ++i) { 88 const IterationSummary& iteration_summary = summary->iterations[i]; 89 summary->final_cost = min(iteration_summary.cost, summary->final_cost); 90 } 91 } 92 93 // Callback for logging the state of the minimizer to STDERR or STDOUT 94 // depending on the user's preferences and logging level. 95 class TrustRegionLoggingCallback : public IterationCallback { 96 public: 97 explicit TrustRegionLoggingCallback(bool log_to_stdout) 98 : log_to_stdout_(log_to_stdout) {} 99 100 ~TrustRegionLoggingCallback() {} 101 102 CallbackReturnType operator()(const IterationSummary& summary) { 103 const char* kReportRowFormat = 104 "% 4d: f:% 8e d:% 3.2e g:% 3.2e h:% 3.2e " 105 "rho:% 3.2e mu:% 3.2e li:% 3d it:% 3.2e tt:% 3.2e"; 106 string output = StringPrintf(kReportRowFormat, 107 summary.iteration, 108 summary.cost, 109 summary.cost_change, 110 summary.gradient_max_norm, 111 summary.step_norm, 112 summary.relative_decrease, 113 summary.trust_region_radius, 114 summary.linear_solver_iterations, 115 summary.iteration_time_in_seconds, 116 summary.cumulative_time_in_seconds); 117 if (log_to_stdout_) { 118 cout << output << endl; 119 } else { 120 VLOG(1) << output; 121 } 122 return SOLVER_CONTINUE; 123 } 124 125 private: 126 const bool log_to_stdout_; 127 }; 128 129 // Callback for logging the state of the minimizer to STDERR or STDOUT 130 // depending on the user's preferences and logging level. 131 class LineSearchLoggingCallback : public IterationCallback { 132 public: 133 explicit LineSearchLoggingCallback(bool log_to_stdout) 134 : log_to_stdout_(log_to_stdout) {} 135 136 ~LineSearchLoggingCallback() {} 137 138 CallbackReturnType operator()(const IterationSummary& summary) { 139 const char* kReportRowFormat = 140 "% 4d: f:% 8e d:% 3.2e g:% 3.2e h:% 3.2e " 141 "s:% 3.2e e:% 3d it:% 3.2e tt:% 3.2e"; 142 string output = StringPrintf(kReportRowFormat, 143 summary.iteration, 144 summary.cost, 145 summary.cost_change, 146 summary.gradient_max_norm, 147 summary.step_norm, 148 summary.step_size, 149 summary.line_search_function_evaluations, 150 summary.iteration_time_in_seconds, 151 summary.cumulative_time_in_seconds); 152 if (log_to_stdout_) { 153 cout << output << endl; 154 } else { 155 VLOG(1) << output; 156 } 157 return SOLVER_CONTINUE; 158 } 159 160 private: 161 const bool log_to_stdout_; 162 }; 163 164 165 // Basic callback to record the execution of the solver to a file for 166 // offline analysis. 167 class FileLoggingCallback : public IterationCallback { 168 public: 169 explicit FileLoggingCallback(const string& filename) 170 : fptr_(NULL) { 171 fptr_ = fopen(filename.c_str(), "w"); 172 CHECK_NOTNULL(fptr_); 173 } 174 175 virtual ~FileLoggingCallback() { 176 if (fptr_ != NULL) { 177 fclose(fptr_); 178 } 179 } 180 181 virtual CallbackReturnType operator()(const IterationSummary& summary) { 182 fprintf(fptr_, 183 "%4d %e %e\n", 184 summary.iteration, 185 summary.cost, 186 summary.cumulative_time_in_seconds); 187 return SOLVER_CONTINUE; 188 } 189 private: 190 FILE* fptr_; 191 }; 192 193 // Iterate over each of the groups in order of their priority and fill 194 // summary with their sizes. 195 void SummarizeOrdering(ParameterBlockOrdering* ordering, 196 vector<int>* summary) { 197 CHECK_NOTNULL(summary)->clear(); 198 if (ordering == NULL) { 199 return; 200 } 201 202 const map<int, set<double*> >& group_to_elements = 203 ordering->group_to_elements(); 204 for (map<int, set<double*> >::const_iterator it = group_to_elements.begin(); 205 it != group_to_elements.end(); 206 ++it) { 207 summary->push_back(it->second.size()); 208 } 209 } 210 211 } // namespace 212 213 void SolverImpl::TrustRegionMinimize( 214 const Solver::Options& options, 215 Program* program, 216 CoordinateDescentMinimizer* inner_iteration_minimizer, 217 Evaluator* evaluator, 218 LinearSolver* linear_solver, 219 double* parameters, 220 Solver::Summary* summary) { 221 Minimizer::Options minimizer_options(options); 222 223 // TODO(sameeragarwal): Add support for logging the configuration 224 // and more detailed stats. 225 scoped_ptr<IterationCallback> file_logging_callback; 226 if (!options.solver_log.empty()) { 227 file_logging_callback.reset(new FileLoggingCallback(options.solver_log)); 228 minimizer_options.callbacks.insert(minimizer_options.callbacks.begin(), 229 file_logging_callback.get()); 230 } 231 232 TrustRegionLoggingCallback logging_callback( 233 options.minimizer_progress_to_stdout); 234 if (options.logging_type != SILENT) { 235 minimizer_options.callbacks.insert(minimizer_options.callbacks.begin(), 236 &logging_callback); 237 } 238 239 StateUpdatingCallback updating_callback(program, parameters); 240 if (options.update_state_every_iteration) { 241 // This must get pushed to the front of the callbacks so that it is run 242 // before any of the user callbacks. 243 minimizer_options.callbacks.insert(minimizer_options.callbacks.begin(), 244 &updating_callback); 245 } 246 247 minimizer_options.evaluator = evaluator; 248 scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian()); 249 250 minimizer_options.jacobian = jacobian.get(); 251 minimizer_options.inner_iteration_minimizer = inner_iteration_minimizer; 252 253 TrustRegionStrategy::Options trust_region_strategy_options; 254 trust_region_strategy_options.linear_solver = linear_solver; 255 trust_region_strategy_options.initial_radius = 256 options.initial_trust_region_radius; 257 trust_region_strategy_options.max_radius = options.max_trust_region_radius; 258 trust_region_strategy_options.min_lm_diagonal = options.min_lm_diagonal; 259 trust_region_strategy_options.max_lm_diagonal = options.max_lm_diagonal; 260 trust_region_strategy_options.trust_region_strategy_type = 261 options.trust_region_strategy_type; 262 trust_region_strategy_options.dogleg_type = options.dogleg_type; 263 scoped_ptr<TrustRegionStrategy> strategy( 264 TrustRegionStrategy::Create(trust_region_strategy_options)); 265 minimizer_options.trust_region_strategy = strategy.get(); 266 267 TrustRegionMinimizer minimizer; 268 double minimizer_start_time = WallTimeInSeconds(); 269 minimizer.Minimize(minimizer_options, parameters, summary); 270 summary->minimizer_time_in_seconds = 271 WallTimeInSeconds() - minimizer_start_time; 272 } 273 274 #ifndef CERES_NO_LINE_SEARCH_MINIMIZER 275 void SolverImpl::LineSearchMinimize( 276 const Solver::Options& options, 277 Program* program, 278 Evaluator* evaluator, 279 double* parameters, 280 Solver::Summary* summary) { 281 Minimizer::Options minimizer_options(options); 282 283 // TODO(sameeragarwal): Add support for logging the configuration 284 // and more detailed stats. 285 scoped_ptr<IterationCallback> file_logging_callback; 286 if (!options.solver_log.empty()) { 287 file_logging_callback.reset(new FileLoggingCallback(options.solver_log)); 288 minimizer_options.callbacks.insert(minimizer_options.callbacks.begin(), 289 file_logging_callback.get()); 290 } 291 292 LineSearchLoggingCallback logging_callback( 293 options.minimizer_progress_to_stdout); 294 if (options.logging_type != SILENT) { 295 minimizer_options.callbacks.insert(minimizer_options.callbacks.begin(), 296 &logging_callback); 297 } 298 299 StateUpdatingCallback updating_callback(program, parameters); 300 if (options.update_state_every_iteration) { 301 // This must get pushed to the front of the callbacks so that it is run 302 // before any of the user callbacks. 303 minimizer_options.callbacks.insert(minimizer_options.callbacks.begin(), 304 &updating_callback); 305 } 306 307 minimizer_options.evaluator = evaluator; 308 309 LineSearchMinimizer minimizer; 310 double minimizer_start_time = WallTimeInSeconds(); 311 minimizer.Minimize(minimizer_options, parameters, summary); 312 summary->minimizer_time_in_seconds = 313 WallTimeInSeconds() - minimizer_start_time; 314 } 315 #endif // CERES_NO_LINE_SEARCH_MINIMIZER 316 317 void SolverImpl::Solve(const Solver::Options& options, 318 ProblemImpl* problem_impl, 319 Solver::Summary* summary) { 320 VLOG(2) << "Initial problem: " 321 << problem_impl->NumParameterBlocks() 322 << " parameter blocks, " 323 << problem_impl->NumParameters() 324 << " parameters, " 325 << problem_impl->NumResidualBlocks() 326 << " residual blocks, " 327 << problem_impl->NumResiduals() 328 << " residuals."; 329 330 if (options.minimizer_type == TRUST_REGION) { 331 TrustRegionSolve(options, problem_impl, summary); 332 } else { 333 #ifndef CERES_NO_LINE_SEARCH_MINIMIZER 334 LineSearchSolve(options, problem_impl, summary); 335 #else 336 LOG(FATAL) << "Ceres Solver was compiled with -DLINE_SEARCH_MINIMIZER=OFF"; 337 #endif 338 } 339 } 340 341 void SolverImpl::TrustRegionSolve(const Solver::Options& original_options, 342 ProblemImpl* original_problem_impl, 343 Solver::Summary* summary) { 344 EventLogger event_logger("TrustRegionSolve"); 345 double solver_start_time = WallTimeInSeconds(); 346 347 Program* original_program = original_problem_impl->mutable_program(); 348 ProblemImpl* problem_impl = original_problem_impl; 349 350 // Reset the summary object to its default values. 351 *CHECK_NOTNULL(summary) = Solver::Summary(); 352 353 summary->minimizer_type = TRUST_REGION; 354 summary->num_parameter_blocks = problem_impl->NumParameterBlocks(); 355 summary->num_parameters = problem_impl->NumParameters(); 356 summary->num_effective_parameters = 357 original_program->NumEffectiveParameters(); 358 summary->num_residual_blocks = problem_impl->NumResidualBlocks(); 359 summary->num_residuals = problem_impl->NumResiduals(); 360 361 // Empty programs are usually a user error. 362 if (summary->num_parameter_blocks == 0) { 363 summary->error = "Problem contains no parameter blocks."; 364 LOG(ERROR) << summary->error; 365 return; 366 } 367 368 if (summary->num_residual_blocks == 0) { 369 summary->error = "Problem contains no residual blocks."; 370 LOG(ERROR) << summary->error; 371 return; 372 } 373 374 SummarizeOrdering(original_options.linear_solver_ordering, 375 &(summary->linear_solver_ordering_given)); 376 377 SummarizeOrdering(original_options.inner_iteration_ordering, 378 &(summary->inner_iteration_ordering_given)); 379 380 Solver::Options options(original_options); 381 options.linear_solver_ordering = NULL; 382 options.inner_iteration_ordering = NULL; 383 384 #ifndef CERES_USE_OPENMP 385 if (options.num_threads > 1) { 386 LOG(WARNING) 387 << "OpenMP support is not compiled into this binary; " 388 << "only options.num_threads=1 is supported. Switching " 389 << "to single threaded mode."; 390 options.num_threads = 1; 391 } 392 if (options.num_linear_solver_threads > 1) { 393 LOG(WARNING) 394 << "OpenMP support is not compiled into this binary; " 395 << "only options.num_linear_solver_threads=1 is supported. Switching " 396 << "to single threaded mode."; 397 options.num_linear_solver_threads = 1; 398 } 399 #endif 400 401 summary->num_threads_given = original_options.num_threads; 402 summary->num_threads_used = options.num_threads; 403 404 if (options.trust_region_minimizer_iterations_to_dump.size() > 0 && 405 options.trust_region_problem_dump_format_type != CONSOLE && 406 options.trust_region_problem_dump_directory.empty()) { 407 summary->error = 408 "Solver::Options::trust_region_problem_dump_directory is empty."; 409 LOG(ERROR) << summary->error; 410 return; 411 } 412 413 event_logger.AddEvent("Init"); 414 415 original_program->SetParameterBlockStatePtrsToUserStatePtrs(); 416 event_logger.AddEvent("SetParameterBlockPtrs"); 417 418 // If the user requests gradient checking, construct a new 419 // ProblemImpl by wrapping the CostFunctions of problem_impl inside 420 // GradientCheckingCostFunction and replacing problem_impl with 421 // gradient_checking_problem_impl. 422 scoped_ptr<ProblemImpl> gradient_checking_problem_impl; 423 if (options.check_gradients) { 424 VLOG(1) << "Checking Gradients"; 425 gradient_checking_problem_impl.reset( 426 CreateGradientCheckingProblemImpl( 427 problem_impl, 428 options.numeric_derivative_relative_step_size, 429 options.gradient_check_relative_precision)); 430 431 // From here on, problem_impl will point to the gradient checking 432 // version. 433 problem_impl = gradient_checking_problem_impl.get(); 434 } 435 436 if (original_options.linear_solver_ordering != NULL) { 437 if (!IsOrderingValid(original_options, problem_impl, &summary->error)) { 438 LOG(ERROR) << summary->error; 439 return; 440 } 441 event_logger.AddEvent("CheckOrdering"); 442 options.linear_solver_ordering = 443 new ParameterBlockOrdering(*original_options.linear_solver_ordering); 444 event_logger.AddEvent("CopyOrdering"); 445 } else { 446 options.linear_solver_ordering = new ParameterBlockOrdering; 447 const ProblemImpl::ParameterMap& parameter_map = 448 problem_impl->parameter_map(); 449 for (ProblemImpl::ParameterMap::const_iterator it = parameter_map.begin(); 450 it != parameter_map.end(); 451 ++it) { 452 options.linear_solver_ordering->AddElementToGroup(it->first, 0); 453 } 454 event_logger.AddEvent("ConstructOrdering"); 455 } 456 457 // Create the three objects needed to minimize: the transformed program, the 458 // evaluator, and the linear solver. 459 scoped_ptr<Program> reduced_program(CreateReducedProgram(&options, 460 problem_impl, 461 &summary->fixed_cost, 462 &summary->error)); 463 464 event_logger.AddEvent("CreateReducedProgram"); 465 if (reduced_program == NULL) { 466 return; 467 } 468 469 SummarizeOrdering(options.linear_solver_ordering, 470 &(summary->linear_solver_ordering_used)); 471 472 summary->num_parameter_blocks_reduced = reduced_program->NumParameterBlocks(); 473 summary->num_parameters_reduced = reduced_program->NumParameters(); 474 summary->num_effective_parameters_reduced = 475 reduced_program->NumEffectiveParameters(); 476 summary->num_residual_blocks_reduced = reduced_program->NumResidualBlocks(); 477 summary->num_residuals_reduced = reduced_program->NumResiduals(); 478 479 if (summary->num_parameter_blocks_reduced == 0) { 480 summary->preprocessor_time_in_seconds = 481 WallTimeInSeconds() - solver_start_time; 482 483 double post_process_start_time = WallTimeInSeconds(); 484 LOG(INFO) << "Terminating: FUNCTION_TOLERANCE reached. " 485 << "No non-constant parameter blocks found."; 486 487 summary->initial_cost = summary->fixed_cost; 488 summary->final_cost = summary->fixed_cost; 489 490 // FUNCTION_TOLERANCE is the right convergence here, as we know 491 // that the objective function is constant and cannot be changed 492 // any further. 493 summary->termination_type = FUNCTION_TOLERANCE; 494 495 // Ensure the program state is set to the user parameters on the way out. 496 original_program->SetParameterBlockStatePtrsToUserStatePtrs(); 497 498 summary->postprocessor_time_in_seconds = 499 WallTimeInSeconds() - post_process_start_time; 500 return; 501 } 502 503 scoped_ptr<LinearSolver> 504 linear_solver(CreateLinearSolver(&options, &summary->error)); 505 event_logger.AddEvent("CreateLinearSolver"); 506 if (linear_solver == NULL) { 507 return; 508 } 509 510 summary->linear_solver_type_given = original_options.linear_solver_type; 511 summary->linear_solver_type_used = options.linear_solver_type; 512 513 summary->preconditioner_type = options.preconditioner_type; 514 515 summary->num_linear_solver_threads_given = 516 original_options.num_linear_solver_threads; 517 summary->num_linear_solver_threads_used = options.num_linear_solver_threads; 518 519 summary->dense_linear_algebra_library_type = 520 options.dense_linear_algebra_library_type; 521 summary->sparse_linear_algebra_library_type = 522 options.sparse_linear_algebra_library_type; 523 524 summary->trust_region_strategy_type = options.trust_region_strategy_type; 525 summary->dogleg_type = options.dogleg_type; 526 527 scoped_ptr<Evaluator> evaluator(CreateEvaluator(options, 528 problem_impl->parameter_map(), 529 reduced_program.get(), 530 &summary->error)); 531 532 event_logger.AddEvent("CreateEvaluator"); 533 534 if (evaluator == NULL) { 535 return; 536 } 537 538 scoped_ptr<CoordinateDescentMinimizer> inner_iteration_minimizer; 539 if (options.use_inner_iterations) { 540 if (reduced_program->parameter_blocks().size() < 2) { 541 LOG(WARNING) << "Reduced problem only contains one parameter block." 542 << "Disabling inner iterations."; 543 } else { 544 inner_iteration_minimizer.reset( 545 CreateInnerIterationMinimizer(original_options, 546 *reduced_program, 547 problem_impl->parameter_map(), 548 summary)); 549 if (inner_iteration_minimizer == NULL) { 550 LOG(ERROR) << summary->error; 551 return; 552 } 553 } 554 } 555 event_logger.AddEvent("CreateInnerIterationMinimizer"); 556 557 // The optimizer works on contiguous parameter vectors; allocate some. 558 Vector parameters(reduced_program->NumParameters()); 559 560 // Collect the discontiguous parameters into a contiguous state vector. 561 reduced_program->ParameterBlocksToStateVector(parameters.data()); 562 563 Vector original_parameters = parameters; 564 565 double minimizer_start_time = WallTimeInSeconds(); 566 summary->preprocessor_time_in_seconds = 567 minimizer_start_time - solver_start_time; 568 569 // Run the optimization. 570 TrustRegionMinimize(options, 571 reduced_program.get(), 572 inner_iteration_minimizer.get(), 573 evaluator.get(), 574 linear_solver.get(), 575 parameters.data(), 576 summary); 577 event_logger.AddEvent("Minimize"); 578 579 SetSummaryFinalCost(summary); 580 581 // If the user aborted mid-optimization or the optimization 582 // terminated because of a numerical failure, then return without 583 // updating user state. 584 if (summary->termination_type == USER_ABORT || 585 summary->termination_type == NUMERICAL_FAILURE) { 586 return; 587 } 588 589 double post_process_start_time = WallTimeInSeconds(); 590 591 // Push the contiguous optimized parameters back to the user's 592 // parameters. 593 reduced_program->StateVectorToParameterBlocks(parameters.data()); 594 reduced_program->CopyParameterBlockStateToUserState(); 595 596 // Ensure the program state is set to the user parameters on the way 597 // out. 598 original_program->SetParameterBlockStatePtrsToUserStatePtrs(); 599 600 const map<string, double>& linear_solver_time_statistics = 601 linear_solver->TimeStatistics(); 602 summary->linear_solver_time_in_seconds = 603 FindWithDefault(linear_solver_time_statistics, 604 "LinearSolver::Solve", 605 0.0); 606 607 const map<string, double>& evaluator_time_statistics = 608 evaluator->TimeStatistics(); 609 610 summary->residual_evaluation_time_in_seconds = 611 FindWithDefault(evaluator_time_statistics, "Evaluator::Residual", 0.0); 612 summary->jacobian_evaluation_time_in_seconds = 613 FindWithDefault(evaluator_time_statistics, "Evaluator::Jacobian", 0.0); 614 615 // Stick a fork in it, we're done. 616 summary->postprocessor_time_in_seconds = 617 WallTimeInSeconds() - post_process_start_time; 618 event_logger.AddEvent("PostProcess"); 619 } 620 621 622 #ifndef CERES_NO_LINE_SEARCH_MINIMIZER 623 void SolverImpl::LineSearchSolve(const Solver::Options& original_options, 624 ProblemImpl* original_problem_impl, 625 Solver::Summary* summary) { 626 double solver_start_time = WallTimeInSeconds(); 627 628 Program* original_program = original_problem_impl->mutable_program(); 629 ProblemImpl* problem_impl = original_problem_impl; 630 631 // Reset the summary object to its default values. 632 *CHECK_NOTNULL(summary) = Solver::Summary(); 633 634 summary->minimizer_type = LINE_SEARCH; 635 summary->line_search_direction_type = 636 original_options.line_search_direction_type; 637 summary->max_lbfgs_rank = original_options.max_lbfgs_rank; 638 summary->line_search_type = original_options.line_search_type; 639 summary->line_search_interpolation_type = 640 original_options.line_search_interpolation_type; 641 summary->nonlinear_conjugate_gradient_type = 642 original_options.nonlinear_conjugate_gradient_type; 643 644 summary->num_parameter_blocks = original_program->NumParameterBlocks(); 645 summary->num_parameters = original_program->NumParameters(); 646 summary->num_residual_blocks = original_program->NumResidualBlocks(); 647 summary->num_residuals = original_program->NumResiduals(); 648 summary->num_effective_parameters = 649 original_program->NumEffectiveParameters(); 650 651 // Validate values for configuration parameters supplied by user. 652 if ((original_options.line_search_direction_type == ceres::BFGS || 653 original_options.line_search_direction_type == ceres::LBFGS) && 654 original_options.line_search_type != ceres::WOLFE) { 655 summary->error = 656 string("Invalid configuration: require line_search_type == " 657 "ceres::WOLFE when using (L)BFGS to ensure that underlying " 658 "assumptions are guaranteed to be satisfied."); 659 LOG(ERROR) << summary->error; 660 return; 661 } 662 if (original_options.max_lbfgs_rank <= 0) { 663 summary->error = 664 string("Invalid configuration: require max_lbfgs_rank > 0"); 665 LOG(ERROR) << summary->error; 666 return; 667 } 668 if (original_options.min_line_search_step_size <= 0.0) { 669 summary->error = "Invalid configuration: min_line_search_step_size <= 0.0."; 670 LOG(ERROR) << summary->error; 671 return; 672 } 673 if (original_options.line_search_sufficient_function_decrease <= 0.0) { 674 summary->error = 675 string("Invalid configuration: require ") + 676 string("line_search_sufficient_function_decrease <= 0.0."); 677 LOG(ERROR) << summary->error; 678 return; 679 } 680 if (original_options.max_line_search_step_contraction <= 0.0 || 681 original_options.max_line_search_step_contraction >= 1.0) { 682 summary->error = string("Invalid configuration: require ") + 683 string("0.0 < max_line_search_step_contraction < 1.0."); 684 LOG(ERROR) << summary->error; 685 return; 686 } 687 if (original_options.min_line_search_step_contraction <= 688 original_options.max_line_search_step_contraction || 689 original_options.min_line_search_step_contraction > 1.0) { 690 summary->error = string("Invalid configuration: require ") + 691 string("max_line_search_step_contraction < ") + 692 string("min_line_search_step_contraction <= 1.0."); 693 LOG(ERROR) << summary->error; 694 return; 695 } 696 // Warn user if they have requested BISECTION interpolation, but constraints 697 // on max/min step size change during line search prevent bisection scaling 698 // from occurring. Warn only, as this is likely a user mistake, but one which 699 // does not prevent us from continuing. 700 LOG_IF(WARNING, 701 (original_options.line_search_interpolation_type == ceres::BISECTION && 702 (original_options.max_line_search_step_contraction > 0.5 || 703 original_options.min_line_search_step_contraction < 0.5))) 704 << "Line search interpolation type is BISECTION, but specified " 705 << "max_line_search_step_contraction: " 706 << original_options.max_line_search_step_contraction << ", and " 707 << "min_line_search_step_contraction: " 708 << original_options.min_line_search_step_contraction 709 << ", prevent bisection (0.5) scaling, continuing with solve regardless."; 710 if (original_options.max_num_line_search_step_size_iterations <= 0) { 711 summary->error = string("Invalid configuration: require ") + 712 string("max_num_line_search_step_size_iterations > 0."); 713 LOG(ERROR) << summary->error; 714 return; 715 } 716 if (original_options.line_search_sufficient_curvature_decrease <= 717 original_options.line_search_sufficient_function_decrease || 718 original_options.line_search_sufficient_curvature_decrease > 1.0) { 719 summary->error = string("Invalid configuration: require ") + 720 string("line_search_sufficient_function_decrease < ") + 721 string("line_search_sufficient_curvature_decrease < 1.0."); 722 LOG(ERROR) << summary->error; 723 return; 724 } 725 if (original_options.max_line_search_step_expansion <= 1.0) { 726 summary->error = string("Invalid configuration: require ") + 727 string("max_line_search_step_expansion > 1.0."); 728 LOG(ERROR) << summary->error; 729 return; 730 } 731 732 // Empty programs are usually a user error. 733 if (summary->num_parameter_blocks == 0) { 734 summary->error = "Problem contains no parameter blocks."; 735 LOG(ERROR) << summary->error; 736 return; 737 } 738 739 if (summary->num_residual_blocks == 0) { 740 summary->error = "Problem contains no residual blocks."; 741 LOG(ERROR) << summary->error; 742 return; 743 } 744 745 Solver::Options options(original_options); 746 747 // This ensures that we get a Block Jacobian Evaluator along with 748 // none of the Schur nonsense. This file will have to be extensively 749 // refactored to deal with the various bits of cleanups related to 750 // line search. 751 options.linear_solver_type = CGNR; 752 753 options.linear_solver_ordering = NULL; 754 options.inner_iteration_ordering = NULL; 755 756 #ifndef CERES_USE_OPENMP 757 if (options.num_threads > 1) { 758 LOG(WARNING) 759 << "OpenMP support is not compiled into this binary; " 760 << "only options.num_threads=1 is supported. Switching " 761 << "to single threaded mode."; 762 options.num_threads = 1; 763 } 764 #endif // CERES_USE_OPENMP 765 766 summary->num_threads_given = original_options.num_threads; 767 summary->num_threads_used = options.num_threads; 768 769 if (original_options.linear_solver_ordering != NULL) { 770 if (!IsOrderingValid(original_options, problem_impl, &summary->error)) { 771 LOG(ERROR) << summary->error; 772 return; 773 } 774 options.linear_solver_ordering = 775 new ParameterBlockOrdering(*original_options.linear_solver_ordering); 776 } else { 777 options.linear_solver_ordering = new ParameterBlockOrdering; 778 const ProblemImpl::ParameterMap& parameter_map = 779 problem_impl->parameter_map(); 780 for (ProblemImpl::ParameterMap::const_iterator it = parameter_map.begin(); 781 it != parameter_map.end(); 782 ++it) { 783 options.linear_solver_ordering->AddElementToGroup(it->first, 0); 784 } 785 } 786 787 original_program->SetParameterBlockStatePtrsToUserStatePtrs(); 788 789 // If the user requests gradient checking, construct a new 790 // ProblemImpl by wrapping the CostFunctions of problem_impl inside 791 // GradientCheckingCostFunction and replacing problem_impl with 792 // gradient_checking_problem_impl. 793 scoped_ptr<ProblemImpl> gradient_checking_problem_impl; 794 if (options.check_gradients) { 795 VLOG(1) << "Checking Gradients"; 796 gradient_checking_problem_impl.reset( 797 CreateGradientCheckingProblemImpl( 798 problem_impl, 799 options.numeric_derivative_relative_step_size, 800 options.gradient_check_relative_precision)); 801 802 // From here on, problem_impl will point to the gradient checking 803 // version. 804 problem_impl = gradient_checking_problem_impl.get(); 805 } 806 807 // Create the three objects needed to minimize: the transformed program, the 808 // evaluator, and the linear solver. 809 scoped_ptr<Program> reduced_program(CreateReducedProgram(&options, 810 problem_impl, 811 &summary->fixed_cost, 812 &summary->error)); 813 if (reduced_program == NULL) { 814 return; 815 } 816 817 summary->num_parameter_blocks_reduced = reduced_program->NumParameterBlocks(); 818 summary->num_parameters_reduced = reduced_program->NumParameters(); 819 summary->num_residual_blocks_reduced = reduced_program->NumResidualBlocks(); 820 summary->num_effective_parameters_reduced = 821 reduced_program->NumEffectiveParameters(); 822 summary->num_residuals_reduced = reduced_program->NumResiduals(); 823 824 if (summary->num_parameter_blocks_reduced == 0) { 825 summary->preprocessor_time_in_seconds = 826 WallTimeInSeconds() - solver_start_time; 827 828 LOG(INFO) << "Terminating: FUNCTION_TOLERANCE reached. " 829 << "No non-constant parameter blocks found."; 830 831 // FUNCTION_TOLERANCE is the right convergence here, as we know 832 // that the objective function is constant and cannot be changed 833 // any further. 834 summary->termination_type = FUNCTION_TOLERANCE; 835 836 const double post_process_start_time = WallTimeInSeconds(); 837 838 SetSummaryFinalCost(summary); 839 840 // Ensure the program state is set to the user parameters on the way out. 841 original_program->SetParameterBlockStatePtrsToUserStatePtrs(); 842 summary->postprocessor_time_in_seconds = 843 WallTimeInSeconds() - post_process_start_time; 844 return; 845 } 846 847 scoped_ptr<Evaluator> evaluator(CreateEvaluator(options, 848 problem_impl->parameter_map(), 849 reduced_program.get(), 850 &summary->error)); 851 if (evaluator == NULL) { 852 return; 853 } 854 855 // The optimizer works on contiguous parameter vectors; allocate some. 856 Vector parameters(reduced_program->NumParameters()); 857 858 // Collect the discontiguous parameters into a contiguous state vector. 859 reduced_program->ParameterBlocksToStateVector(parameters.data()); 860 861 Vector original_parameters = parameters; 862 863 const double minimizer_start_time = WallTimeInSeconds(); 864 summary->preprocessor_time_in_seconds = 865 minimizer_start_time - solver_start_time; 866 867 // Run the optimization. 868 LineSearchMinimize(options, 869 reduced_program.get(), 870 evaluator.get(), 871 parameters.data(), 872 summary); 873 874 // If the user aborted mid-optimization or the optimization 875 // terminated because of a numerical failure, then return without 876 // updating user state. 877 if (summary->termination_type == USER_ABORT || 878 summary->termination_type == NUMERICAL_FAILURE) { 879 return; 880 } 881 882 const double post_process_start_time = WallTimeInSeconds(); 883 884 // Push the contiguous optimized parameters back to the user's parameters. 885 reduced_program->StateVectorToParameterBlocks(parameters.data()); 886 reduced_program->CopyParameterBlockStateToUserState(); 887 888 SetSummaryFinalCost(summary); 889 890 // Ensure the program state is set to the user parameters on the way out. 891 original_program->SetParameterBlockStatePtrsToUserStatePtrs(); 892 893 const map<string, double>& evaluator_time_statistics = 894 evaluator->TimeStatistics(); 895 896 summary->residual_evaluation_time_in_seconds = 897 FindWithDefault(evaluator_time_statistics, "Evaluator::Residual", 0.0); 898 summary->jacobian_evaluation_time_in_seconds = 899 FindWithDefault(evaluator_time_statistics, "Evaluator::Jacobian", 0.0); 900 901 // Stick a fork in it, we're done. 902 summary->postprocessor_time_in_seconds = 903 WallTimeInSeconds() - post_process_start_time; 904 } 905 #endif // CERES_NO_LINE_SEARCH_MINIMIZER 906 907 bool SolverImpl::IsOrderingValid(const Solver::Options& options, 908 const ProblemImpl* problem_impl, 909 string* error) { 910 if (options.linear_solver_ordering->NumElements() != 911 problem_impl->NumParameterBlocks()) { 912 *error = "Number of parameter blocks in user supplied ordering " 913 "does not match the number of parameter blocks in the problem"; 914 return false; 915 } 916 917 const Program& program = problem_impl->program(); 918 const vector<ParameterBlock*>& parameter_blocks = program.parameter_blocks(); 919 for (vector<ParameterBlock*>::const_iterator it = parameter_blocks.begin(); 920 it != parameter_blocks.end(); 921 ++it) { 922 if (!options.linear_solver_ordering 923 ->IsMember(const_cast<double*>((*it)->user_state()))) { 924 *error = "Problem contains a parameter block that is not in " 925 "the user specified ordering."; 926 return false; 927 } 928 } 929 930 if (IsSchurType(options.linear_solver_type) && 931 options.linear_solver_ordering->NumGroups() > 1) { 932 const vector<ResidualBlock*>& residual_blocks = program.residual_blocks(); 933 const set<double*>& e_blocks = 934 options.linear_solver_ordering->group_to_elements().begin()->second; 935 if (!IsParameterBlockSetIndependent(e_blocks, residual_blocks)) { 936 *error = "The user requested the use of a Schur type solver. " 937 "But the first elimination group in the ordering is not an " 938 "independent set."; 939 return false; 940 } 941 } 942 return true; 943 } 944 945 bool SolverImpl::IsParameterBlockSetIndependent( 946 const set<double*>& parameter_block_ptrs, 947 const vector<ResidualBlock*>& residual_blocks) { 948 // Loop over each residual block and ensure that no two parameter 949 // blocks in the same residual block are part of 950 // parameter_block_ptrs as that would violate the assumption that it 951 // is an independent set in the Hessian matrix. 952 for (vector<ResidualBlock*>::const_iterator it = residual_blocks.begin(); 953 it != residual_blocks.end(); 954 ++it) { 955 ParameterBlock* const* parameter_blocks = (*it)->parameter_blocks(); 956 const int num_parameter_blocks = (*it)->NumParameterBlocks(); 957 int count = 0; 958 for (int i = 0; i < num_parameter_blocks; ++i) { 959 count += parameter_block_ptrs.count( 960 parameter_blocks[i]->mutable_user_state()); 961 } 962 if (count > 1) { 963 return false; 964 } 965 } 966 return true; 967 } 968 969 970 // Strips varying parameters and residuals, maintaining order, and updating 971 // num_eliminate_blocks. 972 bool SolverImpl::RemoveFixedBlocksFromProgram(Program* program, 973 ParameterBlockOrdering* ordering, 974 double* fixed_cost, 975 string* error) { 976 vector<ParameterBlock*>* parameter_blocks = 977 program->mutable_parameter_blocks(); 978 979 scoped_array<double> residual_block_evaluate_scratch; 980 if (fixed_cost != NULL) { 981 residual_block_evaluate_scratch.reset( 982 new double[program->MaxScratchDoublesNeededForEvaluate()]); 983 *fixed_cost = 0.0; 984 } 985 986 // Mark all the parameters as unused. Abuse the index member of the parameter 987 // blocks for the marking. 988 for (int i = 0; i < parameter_blocks->size(); ++i) { 989 (*parameter_blocks)[i]->set_index(-1); 990 } 991 992 // Filter out residual that have all-constant parameters, and mark all the 993 // parameter blocks that appear in residuals. 994 { 995 vector<ResidualBlock*>* residual_blocks = 996 program->mutable_residual_blocks(); 997 int j = 0; 998 for (int i = 0; i < residual_blocks->size(); ++i) { 999 ResidualBlock* residual_block = (*residual_blocks)[i]; 1000 int num_parameter_blocks = residual_block->NumParameterBlocks(); 1001 1002 // Determine if the residual block is fixed, and also mark varying 1003 // parameters that appear in the residual block. 1004 bool all_constant = true; 1005 for (int k = 0; k < num_parameter_blocks; k++) { 1006 ParameterBlock* parameter_block = residual_block->parameter_blocks()[k]; 1007 if (!parameter_block->IsConstant()) { 1008 all_constant = false; 1009 parameter_block->set_index(1); 1010 } 1011 } 1012 1013 if (!all_constant) { 1014 (*residual_blocks)[j++] = (*residual_blocks)[i]; 1015 } else if (fixed_cost != NULL) { 1016 // The residual is constant and will be removed, so its cost is 1017 // added to the variable fixed_cost. 1018 double cost = 0.0; 1019 if (!residual_block->Evaluate(true, 1020 &cost, 1021 NULL, 1022 NULL, 1023 residual_block_evaluate_scratch.get())) { 1024 *error = StringPrintf("Evaluation of the residual %d failed during " 1025 "removal of fixed residual blocks.", i); 1026 return false; 1027 } 1028 *fixed_cost += cost; 1029 } 1030 } 1031 residual_blocks->resize(j); 1032 } 1033 1034 // Filter out unused or fixed parameter blocks, and update 1035 // the ordering. 1036 { 1037 vector<ParameterBlock*>* parameter_blocks = 1038 program->mutable_parameter_blocks(); 1039 int j = 0; 1040 for (int i = 0; i < parameter_blocks->size(); ++i) { 1041 ParameterBlock* parameter_block = (*parameter_blocks)[i]; 1042 if (parameter_block->index() == 1) { 1043 (*parameter_blocks)[j++] = parameter_block; 1044 } else { 1045 ordering->Remove(parameter_block->mutable_user_state()); 1046 } 1047 } 1048 parameter_blocks->resize(j); 1049 } 1050 1051 if (!(((program->NumResidualBlocks() == 0) && 1052 (program->NumParameterBlocks() == 0)) || 1053 ((program->NumResidualBlocks() != 0) && 1054 (program->NumParameterBlocks() != 0)))) { 1055 *error = "Congratulations, you found a bug in Ceres. Please report it."; 1056 return false; 1057 } 1058 1059 return true; 1060 } 1061 1062 Program* SolverImpl::CreateReducedProgram(Solver::Options* options, 1063 ProblemImpl* problem_impl, 1064 double* fixed_cost, 1065 string* error) { 1066 CHECK_NOTNULL(options->linear_solver_ordering); 1067 Program* original_program = problem_impl->mutable_program(); 1068 scoped_ptr<Program> transformed_program(new Program(*original_program)); 1069 1070 ParameterBlockOrdering* linear_solver_ordering = 1071 options->linear_solver_ordering; 1072 const int min_group_id = 1073 linear_solver_ordering->group_to_elements().begin()->first; 1074 1075 if (!RemoveFixedBlocksFromProgram(transformed_program.get(), 1076 linear_solver_ordering, 1077 fixed_cost, 1078 error)) { 1079 return NULL; 1080 } 1081 1082 VLOG(2) << "Reduced problem: " 1083 << transformed_program->NumParameterBlocks() 1084 << " parameter blocks, " 1085 << transformed_program->NumParameters() 1086 << " parameters, " 1087 << transformed_program->NumResidualBlocks() 1088 << " residual blocks, " 1089 << transformed_program->NumResiduals() 1090 << " residuals."; 1091 1092 if (transformed_program->NumParameterBlocks() == 0) { 1093 LOG(WARNING) << "No varying parameter blocks to optimize; " 1094 << "bailing early."; 1095 return transformed_program.release(); 1096 } 1097 1098 if (IsSchurType(options->linear_solver_type) && 1099 linear_solver_ordering->GroupSize(min_group_id) == 0) { 1100 // If the user requested the use of a Schur type solver, and 1101 // supplied a non-NULL linear_solver_ordering object with more than 1102 // one elimination group, then it can happen that after all the 1103 // parameter blocks which are fixed or unused have been removed from 1104 // the program and the ordering, there are no more parameter blocks 1105 // in the first elimination group. 1106 // 1107 // In such a case, the use of a Schur type solver is not possible, 1108 // as they assume there is at least one e_block. Thus, we 1109 // automatically switch to the closest solver to the one indicated 1110 // by the user. 1111 AlternateLinearSolverForSchurTypeLinearSolver(options); 1112 } 1113 1114 if (IsSchurType(options->linear_solver_type)) { 1115 if (!ReorderProgramForSchurTypeLinearSolver( 1116 options->linear_solver_type, 1117 options->sparse_linear_algebra_library_type, 1118 problem_impl->parameter_map(), 1119 linear_solver_ordering, 1120 transformed_program.get(), 1121 error)) { 1122 return NULL; 1123 } 1124 return transformed_program.release(); 1125 } 1126 1127 if (options->linear_solver_type == SPARSE_NORMAL_CHOLESKY) { 1128 if (!ReorderProgramForSparseNormalCholesky( 1129 options->sparse_linear_algebra_library_type, 1130 linear_solver_ordering, 1131 transformed_program.get(), 1132 error)) { 1133 return NULL; 1134 } 1135 1136 return transformed_program.release(); 1137 } 1138 1139 transformed_program->SetParameterOffsetsAndIndex(); 1140 return transformed_program.release(); 1141 } 1142 1143 LinearSolver* SolverImpl::CreateLinearSolver(Solver::Options* options, 1144 string* error) { 1145 CHECK_NOTNULL(options); 1146 CHECK_NOTNULL(options->linear_solver_ordering); 1147 CHECK_NOTNULL(error); 1148 1149 if (options->trust_region_strategy_type == DOGLEG) { 1150 if (options->linear_solver_type == ITERATIVE_SCHUR || 1151 options->linear_solver_type == CGNR) { 1152 *error = "DOGLEG only supports exact factorization based linear " 1153 "solvers. If you want to use an iterative solver please " 1154 "use LEVENBERG_MARQUARDT as the trust_region_strategy_type"; 1155 return NULL; 1156 } 1157 } 1158 1159 #ifdef CERES_NO_LAPACK 1160 if (options->linear_solver_type == DENSE_NORMAL_CHOLESKY && 1161 options->dense_linear_algebra_library_type == LAPACK) { 1162 *error = "Can't use DENSE_NORMAL_CHOLESKY with LAPACK because " 1163 "LAPACK was not enabled when Ceres was built."; 1164 return NULL; 1165 } 1166 1167 if (options->linear_solver_type == DENSE_QR && 1168 options->dense_linear_algebra_library_type == LAPACK) { 1169 *error = "Can't use DENSE_QR with LAPACK because " 1170 "LAPACK was not enabled when Ceres was built."; 1171 return NULL; 1172 } 1173 1174 if (options->linear_solver_type == DENSE_SCHUR && 1175 options->dense_linear_algebra_library_type == LAPACK) { 1176 *error = "Can't use DENSE_SCHUR with LAPACK because " 1177 "LAPACK was not enabled when Ceres was built."; 1178 return NULL; 1179 } 1180 #endif 1181 1182 #ifdef CERES_NO_SUITESPARSE 1183 if (options->linear_solver_type == SPARSE_NORMAL_CHOLESKY && 1184 options->sparse_linear_algebra_library_type == SUITE_SPARSE) { 1185 *error = "Can't use SPARSE_NORMAL_CHOLESKY with SUITESPARSE because " 1186 "SuiteSparse was not enabled when Ceres was built."; 1187 return NULL; 1188 } 1189 1190 if (options->preconditioner_type == CLUSTER_JACOBI) { 1191 *error = "CLUSTER_JACOBI preconditioner not suppored. Please build Ceres " 1192 "with SuiteSparse support."; 1193 return NULL; 1194 } 1195 1196 if (options->preconditioner_type == CLUSTER_TRIDIAGONAL) { 1197 *error = "CLUSTER_TRIDIAGONAL preconditioner not suppored. Please build " 1198 "Ceres with SuiteSparse support."; 1199 return NULL; 1200 } 1201 #endif 1202 1203 #ifdef CERES_NO_CXSPARSE 1204 if (options->linear_solver_type == SPARSE_NORMAL_CHOLESKY && 1205 options->sparse_linear_algebra_library_type == CX_SPARSE) { 1206 *error = "Can't use SPARSE_NORMAL_CHOLESKY with CXSPARSE because " 1207 "CXSparse was not enabled when Ceres was built."; 1208 return NULL; 1209 } 1210 #endif 1211 1212 #if defined(CERES_NO_SUITESPARSE) && defined(CERES_NO_CXSPARSE) 1213 if (options->linear_solver_type == SPARSE_SCHUR) { 1214 *error = "Can't use SPARSE_SCHUR because neither SuiteSparse nor" 1215 "CXSparse was enabled when Ceres was compiled."; 1216 return NULL; 1217 } 1218 #endif 1219 1220 if (options->max_linear_solver_iterations <= 0) { 1221 *error = "Solver::Options::max_linear_solver_iterations is not positive."; 1222 return NULL; 1223 } 1224 if (options->min_linear_solver_iterations <= 0) { 1225 *error = "Solver::Options::min_linear_solver_iterations is not positive."; 1226 return NULL; 1227 } 1228 if (options->min_linear_solver_iterations > 1229 options->max_linear_solver_iterations) { 1230 *error = "Solver::Options::min_linear_solver_iterations > " 1231 "Solver::Options::max_linear_solver_iterations."; 1232 return NULL; 1233 } 1234 1235 LinearSolver::Options linear_solver_options; 1236 linear_solver_options.min_num_iterations = 1237 options->min_linear_solver_iterations; 1238 linear_solver_options.max_num_iterations = 1239 options->max_linear_solver_iterations; 1240 linear_solver_options.type = options->linear_solver_type; 1241 linear_solver_options.preconditioner_type = options->preconditioner_type; 1242 linear_solver_options.sparse_linear_algebra_library_type = 1243 options->sparse_linear_algebra_library_type; 1244 linear_solver_options.dense_linear_algebra_library_type = 1245 options->dense_linear_algebra_library_type; 1246 linear_solver_options.use_postordering = options->use_postordering; 1247 1248 // Ignore user's postordering preferences and force it to be true if 1249 // cholmod_camd is not available. This ensures that the linear 1250 // solver does not assume that a fill-reducing pre-ordering has been 1251 // done. 1252 #if !defined(CERES_NO_SUITESPARSE) && defined(CERES_NO_CAMD) 1253 if (IsSchurType(linear_solver_options.type) && 1254 options->sparse_linear_algebra_library_type == SUITE_SPARSE) { 1255 linear_solver_options.use_postordering = true; 1256 } 1257 #endif 1258 1259 linear_solver_options.num_threads = options->num_linear_solver_threads; 1260 options->num_linear_solver_threads = linear_solver_options.num_threads; 1261 1262 const map<int, set<double*> >& groups = 1263 options->linear_solver_ordering->group_to_elements(); 1264 for (map<int, set<double*> >::const_iterator it = groups.begin(); 1265 it != groups.end(); 1266 ++it) { 1267 linear_solver_options.elimination_groups.push_back(it->second.size()); 1268 } 1269 // Schur type solvers, expect at least two elimination groups. If 1270 // there is only one elimination group, then CreateReducedProgram 1271 // guarantees that this group only contains e_blocks. Thus we add a 1272 // dummy elimination group with zero blocks in it. 1273 if (IsSchurType(linear_solver_options.type) && 1274 linear_solver_options.elimination_groups.size() == 1) { 1275 linear_solver_options.elimination_groups.push_back(0); 1276 } 1277 1278 return LinearSolver::Create(linear_solver_options); 1279 } 1280 1281 1282 // Find the minimum index of any parameter block to the given residual. 1283 // Parameter blocks that have indices greater than num_eliminate_blocks are 1284 // considered to have an index equal to num_eliminate_blocks. 1285 static int MinParameterBlock(const ResidualBlock* residual_block, 1286 int num_eliminate_blocks) { 1287 int min_parameter_block_position = num_eliminate_blocks; 1288 for (int i = 0; i < residual_block->NumParameterBlocks(); ++i) { 1289 ParameterBlock* parameter_block = residual_block->parameter_blocks()[i]; 1290 if (!parameter_block->IsConstant()) { 1291 CHECK_NE(parameter_block->index(), -1) 1292 << "Did you forget to call Program::SetParameterOffsetsAndIndex()? " 1293 << "This is a Ceres bug; please contact the developers!"; 1294 min_parameter_block_position = std::min(parameter_block->index(), 1295 min_parameter_block_position); 1296 } 1297 } 1298 return min_parameter_block_position; 1299 } 1300 1301 // Reorder the residuals for program, if necessary, so that the residuals 1302 // involving each E block occur together. This is a necessary condition for the 1303 // Schur eliminator, which works on these "row blocks" in the jacobian. 1304 bool SolverImpl::LexicographicallyOrderResidualBlocks( 1305 const int num_eliminate_blocks, 1306 Program* program, 1307 string* error) { 1308 CHECK_GE(num_eliminate_blocks, 1) 1309 << "Congratulations, you found a Ceres bug! Please report this error " 1310 << "to the developers."; 1311 1312 // Create a histogram of the number of residuals for each E block. There is an 1313 // extra bucket at the end to catch all non-eliminated F blocks. 1314 vector<int> residual_blocks_per_e_block(num_eliminate_blocks + 1); 1315 vector<ResidualBlock*>* residual_blocks = program->mutable_residual_blocks(); 1316 vector<int> min_position_per_residual(residual_blocks->size()); 1317 for (int i = 0; i < residual_blocks->size(); ++i) { 1318 ResidualBlock* residual_block = (*residual_blocks)[i]; 1319 int position = MinParameterBlock(residual_block, num_eliminate_blocks); 1320 min_position_per_residual[i] = position; 1321 DCHECK_LE(position, num_eliminate_blocks); 1322 residual_blocks_per_e_block[position]++; 1323 } 1324 1325 // Run a cumulative sum on the histogram, to obtain offsets to the start of 1326 // each histogram bucket (where each bucket is for the residuals for that 1327 // E-block). 1328 vector<int> offsets(num_eliminate_blocks + 1); 1329 std::partial_sum(residual_blocks_per_e_block.begin(), 1330 residual_blocks_per_e_block.end(), 1331 offsets.begin()); 1332 CHECK_EQ(offsets.back(), residual_blocks->size()) 1333 << "Congratulations, you found a Ceres bug! Please report this error " 1334 << "to the developers."; 1335 1336 CHECK(find(residual_blocks_per_e_block.begin(), 1337 residual_blocks_per_e_block.end() - 1, 0) != 1338 residual_blocks_per_e_block.end()) 1339 << "Congratulations, you found a Ceres bug! Please report this error " 1340 << "to the developers."; 1341 1342 // Fill in each bucket with the residual blocks for its corresponding E block. 1343 // Each bucket is individually filled from the back of the bucket to the front 1344 // of the bucket. The filling order among the buckets is dictated by the 1345 // residual blocks. This loop uses the offsets as counters; subtracting one 1346 // from each offset as a residual block is placed in the bucket. When the 1347 // filling is finished, the offset pointerts should have shifted down one 1348 // entry (this is verified below). 1349 vector<ResidualBlock*> reordered_residual_blocks( 1350 (*residual_blocks).size(), static_cast<ResidualBlock*>(NULL)); 1351 for (int i = 0; i < residual_blocks->size(); ++i) { 1352 int bucket = min_position_per_residual[i]; 1353 1354 // Decrement the cursor, which should now point at the next empty position. 1355 offsets[bucket]--; 1356 1357 // Sanity. 1358 CHECK(reordered_residual_blocks[offsets[bucket]] == NULL) 1359 << "Congratulations, you found a Ceres bug! Please report this error " 1360 << "to the developers."; 1361 1362 reordered_residual_blocks[offsets[bucket]] = (*residual_blocks)[i]; 1363 } 1364 1365 // Sanity check #1: The difference in bucket offsets should match the 1366 // histogram sizes. 1367 for (int i = 0; i < num_eliminate_blocks; ++i) { 1368 CHECK_EQ(residual_blocks_per_e_block[i], offsets[i + 1] - offsets[i]) 1369 << "Congratulations, you found a Ceres bug! Please report this error " 1370 << "to the developers."; 1371 } 1372 // Sanity check #2: No NULL's left behind. 1373 for (int i = 0; i < reordered_residual_blocks.size(); ++i) { 1374 CHECK(reordered_residual_blocks[i] != NULL) 1375 << "Congratulations, you found a Ceres bug! Please report this error " 1376 << "to the developers."; 1377 } 1378 1379 // Now that the residuals are collected by E block, swap them in place. 1380 swap(*program->mutable_residual_blocks(), reordered_residual_blocks); 1381 return true; 1382 } 1383 1384 Evaluator* SolverImpl::CreateEvaluator( 1385 const Solver::Options& options, 1386 const ProblemImpl::ParameterMap& parameter_map, 1387 Program* program, 1388 string* error) { 1389 Evaluator::Options evaluator_options; 1390 evaluator_options.linear_solver_type = options.linear_solver_type; 1391 evaluator_options.num_eliminate_blocks = 1392 (options.linear_solver_ordering->NumGroups() > 0 && 1393 IsSchurType(options.linear_solver_type)) 1394 ? (options.linear_solver_ordering 1395 ->group_to_elements().begin() 1396 ->second.size()) 1397 : 0; 1398 evaluator_options.num_threads = options.num_threads; 1399 return Evaluator::Create(evaluator_options, program, error); 1400 } 1401 1402 CoordinateDescentMinimizer* SolverImpl::CreateInnerIterationMinimizer( 1403 const Solver::Options& options, 1404 const Program& program, 1405 const ProblemImpl::ParameterMap& parameter_map, 1406 Solver::Summary* summary) { 1407 summary->inner_iterations_given = true; 1408 1409 scoped_ptr<CoordinateDescentMinimizer> inner_iteration_minimizer( 1410 new CoordinateDescentMinimizer); 1411 scoped_ptr<ParameterBlockOrdering> inner_iteration_ordering; 1412 ParameterBlockOrdering* ordering_ptr = NULL; 1413 1414 if (options.inner_iteration_ordering == NULL) { 1415 // Find a recursive decomposition of the Hessian matrix as a set 1416 // of independent sets of decreasing size and invert it. This 1417 // seems to work better in practice, i.e., Cameras before 1418 // points. 1419 inner_iteration_ordering.reset(new ParameterBlockOrdering); 1420 ComputeRecursiveIndependentSetOrdering(program, 1421 inner_iteration_ordering.get()); 1422 inner_iteration_ordering->Reverse(); 1423 ordering_ptr = inner_iteration_ordering.get(); 1424 } else { 1425 const map<int, set<double*> >& group_to_elements = 1426 options.inner_iteration_ordering->group_to_elements(); 1427 1428 // Iterate over each group and verify that it is an independent 1429 // set. 1430 map<int, set<double*> >::const_iterator it = group_to_elements.begin(); 1431 for ( ; it != group_to_elements.end(); ++it) { 1432 if (!IsParameterBlockSetIndependent(it->second, 1433 program.residual_blocks())) { 1434 summary->error = 1435 StringPrintf("The user-provided " 1436 "parameter_blocks_for_inner_iterations does not " 1437 "form an independent set. Group Id: %d", it->first); 1438 return NULL; 1439 } 1440 } 1441 ordering_ptr = options.inner_iteration_ordering; 1442 } 1443 1444 if (!inner_iteration_minimizer->Init(program, 1445 parameter_map, 1446 *ordering_ptr, 1447 &summary->error)) { 1448 return NULL; 1449 } 1450 1451 summary->inner_iterations_used = true; 1452 summary->inner_iteration_time_in_seconds = 0.0; 1453 SummarizeOrdering(ordering_ptr, &(summary->inner_iteration_ordering_used)); 1454 return inner_iteration_minimizer.release(); 1455 } 1456 1457 void SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver( 1458 Solver::Options* options) { 1459 if (!IsSchurType(options->linear_solver_type)) { 1460 return; 1461 } 1462 1463 string msg = "No e_blocks remaining. Switching from "; 1464 if (options->linear_solver_type == SPARSE_SCHUR) { 1465 options->linear_solver_type = SPARSE_NORMAL_CHOLESKY; 1466 msg += "SPARSE_SCHUR to SPARSE_NORMAL_CHOLESKY."; 1467 } else if (options->linear_solver_type == DENSE_SCHUR) { 1468 // TODO(sameeragarwal): This is probably not a great choice. 1469 // Ideally, we should have a DENSE_NORMAL_CHOLESKY, that can 1470 // take a BlockSparseMatrix as input. 1471 options->linear_solver_type = DENSE_QR; 1472 msg += "DENSE_SCHUR to DENSE_QR."; 1473 } else if (options->linear_solver_type == ITERATIVE_SCHUR) { 1474 options->linear_solver_type = CGNR; 1475 if (options->preconditioner_type != IDENTITY) { 1476 msg += StringPrintf("ITERATIVE_SCHUR with %s preconditioner " 1477 "to CGNR with JACOBI preconditioner.", 1478 PreconditionerTypeToString( 1479 options->preconditioner_type)); 1480 // CGNR currently only supports the JACOBI preconditioner. 1481 options->preconditioner_type = JACOBI; 1482 } else { 1483 msg += "ITERATIVE_SCHUR with IDENTITY preconditioner" 1484 "to CGNR with IDENTITY preconditioner."; 1485 } 1486 } 1487 LOG(WARNING) << msg; 1488 } 1489 1490 bool SolverImpl::ApplyUserOrdering( 1491 const ProblemImpl::ParameterMap& parameter_map, 1492 const ParameterBlockOrdering* parameter_block_ordering, 1493 Program* program, 1494 string* error) { 1495 const int num_parameter_blocks = program->NumParameterBlocks(); 1496 if (parameter_block_ordering->NumElements() != num_parameter_blocks) { 1497 *error = StringPrintf("User specified ordering does not have the same " 1498 "number of parameters as the problem. The problem" 1499 "has %d blocks while the ordering has %d blocks.", 1500 num_parameter_blocks, 1501 parameter_block_ordering->NumElements()); 1502 return false; 1503 } 1504 1505 vector<ParameterBlock*>* parameter_blocks = 1506 program->mutable_parameter_blocks(); 1507 parameter_blocks->clear(); 1508 1509 const map<int, set<double*> >& groups = 1510 parameter_block_ordering->group_to_elements(); 1511 1512 for (map<int, set<double*> >::const_iterator group_it = groups.begin(); 1513 group_it != groups.end(); 1514 ++group_it) { 1515 const set<double*>& group = group_it->second; 1516 for (set<double*>::const_iterator parameter_block_ptr_it = group.begin(); 1517 parameter_block_ptr_it != group.end(); 1518 ++parameter_block_ptr_it) { 1519 ProblemImpl::ParameterMap::const_iterator parameter_block_it = 1520 parameter_map.find(*parameter_block_ptr_it); 1521 if (parameter_block_it == parameter_map.end()) { 1522 *error = StringPrintf("User specified ordering contains a pointer " 1523 "to a double that is not a parameter block in " 1524 "the problem. The invalid double is in group: %d", 1525 group_it->first); 1526 return false; 1527 } 1528 parameter_blocks->push_back(parameter_block_it->second); 1529 } 1530 } 1531 return true; 1532 } 1533 1534 1535 TripletSparseMatrix* SolverImpl::CreateJacobianBlockSparsityTranspose( 1536 const Program* program) { 1537 1538 // Matrix to store the block sparsity structure of the Jacobian. 1539 TripletSparseMatrix* tsm = 1540 new TripletSparseMatrix(program->NumParameterBlocks(), 1541 program->NumResidualBlocks(), 1542 10 * program->NumResidualBlocks()); 1543 int num_nonzeros = 0; 1544 int* rows = tsm->mutable_rows(); 1545 int* cols = tsm->mutable_cols(); 1546 double* values = tsm->mutable_values(); 1547 1548 const vector<ResidualBlock*>& residual_blocks = program->residual_blocks(); 1549 for (int c = 0; c < residual_blocks.size(); ++c) { 1550 const ResidualBlock* residual_block = residual_blocks[c]; 1551 const int num_parameter_blocks = residual_block->NumParameterBlocks(); 1552 ParameterBlock* const* parameter_blocks = 1553 residual_block->parameter_blocks(); 1554 1555 for (int j = 0; j < num_parameter_blocks; ++j) { 1556 if (parameter_blocks[j]->IsConstant()) { 1557 continue; 1558 } 1559 1560 // Re-size the matrix if needed. 1561 if (num_nonzeros >= tsm->max_num_nonzeros()) { 1562 tsm->set_num_nonzeros(num_nonzeros); 1563 tsm->Reserve(2 * num_nonzeros); 1564 rows = tsm->mutable_rows(); 1565 cols = tsm->mutable_cols(); 1566 values = tsm->mutable_values(); 1567 } 1568 CHECK_LT(num_nonzeros, tsm->max_num_nonzeros()); 1569 1570 const int r = parameter_blocks[j]->index(); 1571 rows[num_nonzeros] = r; 1572 cols[num_nonzeros] = c; 1573 values[num_nonzeros] = 1.0; 1574 ++num_nonzeros; 1575 } 1576 } 1577 1578 tsm->set_num_nonzeros(num_nonzeros); 1579 return tsm; 1580 } 1581 1582 bool SolverImpl::ReorderProgramForSchurTypeLinearSolver( 1583 const LinearSolverType linear_solver_type, 1584 const SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type, 1585 const ProblemImpl::ParameterMap& parameter_map, 1586 ParameterBlockOrdering* parameter_block_ordering, 1587 Program* program, 1588 string* error) { 1589 if (parameter_block_ordering->NumGroups() == 1) { 1590 // If the user supplied an parameter_block_ordering with just one 1591 // group, it is equivalent to the user supplying NULL as an 1592 // parameter_block_ordering. Ceres is completely free to choose the 1593 // parameter block ordering as it sees fit. For Schur type solvers, 1594 // this means that the user wishes for Ceres to identify the 1595 // e_blocks, which we do by computing a maximal independent set. 1596 vector<ParameterBlock*> schur_ordering; 1597 const int num_eliminate_blocks = 1598 ComputeStableSchurOrdering(*program, &schur_ordering); 1599 1600 CHECK_EQ(schur_ordering.size(), program->NumParameterBlocks()) 1601 << "Congratulations, you found a Ceres bug! Please report this error " 1602 << "to the developers."; 1603 1604 // Update the parameter_block_ordering object. 1605 for (int i = 0; i < schur_ordering.size(); ++i) { 1606 double* parameter_block = schur_ordering[i]->mutable_user_state(); 1607 const int group_id = (i < num_eliminate_blocks) ? 0 : 1; 1608 parameter_block_ordering->AddElementToGroup(parameter_block, group_id); 1609 } 1610 1611 // We could call ApplyUserOrdering but this is cheaper and 1612 // simpler. 1613 swap(*program->mutable_parameter_blocks(), schur_ordering); 1614 } else { 1615 // The user provided an ordering with more than one elimination 1616 // group. Trust the user and apply the ordering. 1617 if (!ApplyUserOrdering(parameter_map, 1618 parameter_block_ordering, 1619 program, 1620 error)) { 1621 return false; 1622 } 1623 } 1624 1625 // Pre-order the columns corresponding to the schur complement if 1626 // possible. 1627 #if !defined(CERES_NO_SUITESPARSE) && !defined(CERES_NO_CAMD) 1628 if (linear_solver_type == SPARSE_SCHUR && 1629 sparse_linear_algebra_library_type == SUITE_SPARSE) { 1630 vector<int> constraints; 1631 vector<ParameterBlock*>& parameter_blocks = 1632 *(program->mutable_parameter_blocks()); 1633 1634 for (int i = 0; i < parameter_blocks.size(); ++i) { 1635 constraints.push_back( 1636 parameter_block_ordering->GroupId( 1637 parameter_blocks[i]->mutable_user_state())); 1638 } 1639 1640 // Renumber the entries of constraints to be contiguous integers 1641 // as camd requires that the group ids be in the range [0, 1642 // parameter_blocks.size() - 1]. 1643 SolverImpl::CompactifyArray(&constraints); 1644 1645 // Set the offsets and index for CreateJacobianSparsityTranspose. 1646 program->SetParameterOffsetsAndIndex(); 1647 // Compute a block sparse presentation of J'. 1648 scoped_ptr<TripletSparseMatrix> tsm_block_jacobian_transpose( 1649 SolverImpl::CreateJacobianBlockSparsityTranspose(program)); 1650 1651 SuiteSparse ss; 1652 cholmod_sparse* block_jacobian_transpose = 1653 ss.CreateSparseMatrix(tsm_block_jacobian_transpose.get()); 1654 1655 vector<int> ordering(parameter_blocks.size(), 0); 1656 ss.ConstrainedApproximateMinimumDegreeOrdering(block_jacobian_transpose, 1657 &constraints[0], 1658 &ordering[0]); 1659 ss.Free(block_jacobian_transpose); 1660 1661 const vector<ParameterBlock*> parameter_blocks_copy(parameter_blocks); 1662 for (int i = 0; i < program->NumParameterBlocks(); ++i) { 1663 parameter_blocks[i] = parameter_blocks_copy[ordering[i]]; 1664 } 1665 } 1666 #endif 1667 1668 program->SetParameterOffsetsAndIndex(); 1669 // Schur type solvers also require that their residual blocks be 1670 // lexicographically ordered. 1671 const int num_eliminate_blocks = 1672 parameter_block_ordering->group_to_elements().begin()->second.size(); 1673 return LexicographicallyOrderResidualBlocks(num_eliminate_blocks, 1674 program, 1675 error); 1676 } 1677 1678 bool SolverImpl::ReorderProgramForSparseNormalCholesky( 1679 const SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type, 1680 const ParameterBlockOrdering* parameter_block_ordering, 1681 Program* program, 1682 string* error) { 1683 // Set the offsets and index for CreateJacobianSparsityTranspose. 1684 program->SetParameterOffsetsAndIndex(); 1685 // Compute a block sparse presentation of J'. 1686 scoped_ptr<TripletSparseMatrix> tsm_block_jacobian_transpose( 1687 SolverImpl::CreateJacobianBlockSparsityTranspose(program)); 1688 1689 vector<int> ordering(program->NumParameterBlocks(), 0); 1690 vector<ParameterBlock*>& parameter_blocks = 1691 *(program->mutable_parameter_blocks()); 1692 1693 if (sparse_linear_algebra_library_type == SUITE_SPARSE) { 1694 #ifdef CERES_NO_SUITESPARSE 1695 *error = "Can't use SPARSE_NORMAL_CHOLESKY with SUITE_SPARSE because " 1696 "SuiteSparse was not enabled when Ceres was built."; 1697 return false; 1698 #else 1699 SuiteSparse ss; 1700 cholmod_sparse* block_jacobian_transpose = 1701 ss.CreateSparseMatrix(tsm_block_jacobian_transpose.get()); 1702 1703 # ifdef CERES_NO_CAMD 1704 // No cholmod_camd, so ignore user's parameter_block_ordering and 1705 // use plain old AMD. 1706 ss.ApproximateMinimumDegreeOrdering(block_jacobian_transpose, &ordering[0]); 1707 # else 1708 if (parameter_block_ordering->NumGroups() > 1) { 1709 // If the user specified more than one elimination groups use them 1710 // to constrain the ordering. 1711 vector<int> constraints; 1712 for (int i = 0; i < parameter_blocks.size(); ++i) { 1713 constraints.push_back( 1714 parameter_block_ordering->GroupId( 1715 parameter_blocks[i]->mutable_user_state())); 1716 } 1717 ss.ConstrainedApproximateMinimumDegreeOrdering( 1718 block_jacobian_transpose, 1719 &constraints[0], 1720 &ordering[0]); 1721 } else { 1722 ss.ApproximateMinimumDegreeOrdering(block_jacobian_transpose, 1723 &ordering[0]); 1724 } 1725 # endif // CERES_NO_CAMD 1726 1727 ss.Free(block_jacobian_transpose); 1728 #endif // CERES_NO_SUITESPARSE 1729 1730 } else if (sparse_linear_algebra_library_type == CX_SPARSE) { 1731 #ifndef CERES_NO_CXSPARSE 1732 1733 // CXSparse works with J'J instead of J'. So compute the block 1734 // sparsity for J'J and compute an approximate minimum degree 1735 // ordering. 1736 CXSparse cxsparse; 1737 cs_di* block_jacobian_transpose; 1738 block_jacobian_transpose = 1739 cxsparse.CreateSparseMatrix(tsm_block_jacobian_transpose.get()); 1740 cs_di* block_jacobian = cxsparse.TransposeMatrix(block_jacobian_transpose); 1741 cs_di* block_hessian = 1742 cxsparse.MatrixMatrixMultiply(block_jacobian_transpose, block_jacobian); 1743 cxsparse.Free(block_jacobian); 1744 cxsparse.Free(block_jacobian_transpose); 1745 1746 cxsparse.ApproximateMinimumDegreeOrdering(block_hessian, &ordering[0]); 1747 cxsparse.Free(block_hessian); 1748 #else // CERES_NO_CXSPARSE 1749 *error = "Can't use SPARSE_NORMAL_CHOLESKY with CX_SPARSE because " 1750 "CXSparse was not enabled when Ceres was built."; 1751 return false; 1752 #endif // CERES_NO_CXSPARSE 1753 } else { 1754 *error = "Unknown sparse linear algebra library."; 1755 return false; 1756 } 1757 1758 // Apply ordering. 1759 const vector<ParameterBlock*> parameter_blocks_copy(parameter_blocks); 1760 for (int i = 0; i < program->NumParameterBlocks(); ++i) { 1761 parameter_blocks[i] = parameter_blocks_copy[ordering[i]]; 1762 } 1763 1764 program->SetParameterOffsetsAndIndex(); 1765 return true; 1766 } 1767 1768 void SolverImpl::CompactifyArray(vector<int>* array_ptr) { 1769 vector<int>& array = *array_ptr; 1770 const set<int> unique_group_ids(array.begin(), array.end()); 1771 map<int, int> group_id_map; 1772 for (set<int>::const_iterator it = unique_group_ids.begin(); 1773 it != unique_group_ids.end(); 1774 ++it) { 1775 InsertOrDie(&group_id_map, *it, group_id_map.size()); 1776 } 1777 1778 for (int i = 0; i < array.size(); ++i) { 1779 array[i] = group_id_map[array[i]]; 1780 } 1781 } 1782 1783 } // namespace internal 1784 } // namespace ceres 1785