Home | History | Annotate | Download | only in ceres
      1 // Ceres Solver - A fast non-linear least squares minimizer
      2 // Copyright 2012 Google Inc. All rights reserved.
      3 // http://code.google.com/p/ceres-solver/
      4 //
      5 // Redistribution and use in source and binary forms, with or without
      6 // modification, are permitted provided that the following conditions are met:
      7 //
      8 // * Redistributions of source code must retain the above copyright notice,
      9 //   this list of conditions and the following disclaimer.
     10 // * Redistributions in binary form must reproduce the above copyright notice,
     11 //   this list of conditions and the following disclaimer in the documentation
     12 //   and/or other materials provided with the distribution.
     13 // * Neither the name of Google Inc. nor the names of its contributors may be
     14 //   used to endorse or promote products derived from this software without
     15 //   specific prior written permission.
     16 //
     17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     20 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
     21 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     22 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     23 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     25 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     26 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     27 // POSSIBILITY OF SUCH DAMAGE.
     28 //
     29 // Author: sameeragarwal (at) google.com (Sameer Agarwal)
     30 
     31 #include "ceres/trust_region_minimizer.h"
     32 
     33 #include <algorithm>
     34 #include <cstdlib>
     35 #include <cmath>
     36 #include <cstring>
     37 #include <limits>
     38 #include <string>
     39 #include <vector>
     40 
     41 #include "Eigen/Core"
     42 #include "ceres/array_utils.h"
     43 #include "ceres/evaluator.h"
     44 #include "ceres/internal/eigen.h"
     45 #include "ceres/internal/scoped_ptr.h"
     46 #include "ceres/linear_least_squares_problems.h"
     47 #include "ceres/sparse_matrix.h"
     48 #include "ceres/stringprintf.h"
     49 #include "ceres/trust_region_strategy.h"
     50 #include "ceres/types.h"
     51 #include "ceres/wall_time.h"
     52 #include "glog/logging.h"
     53 
     54 namespace ceres {
     55 namespace internal {
     56 namespace {
     57 // Small constant for various floating point issues.
     58 const double kEpsilon = 1e-12;
     59 }  // namespace
     60 
     61 // Execute the list of IterationCallbacks sequentially. If any one of
     62 // the callbacks does not return SOLVER_CONTINUE, then stop and return
     63 // its status.
     64 CallbackReturnType TrustRegionMinimizer::RunCallbacks(
     65     const IterationSummary& iteration_summary) {
     66   for (int i = 0; i < options_.callbacks.size(); ++i) {
     67     const CallbackReturnType status =
     68         (*options_.callbacks[i])(iteration_summary);
     69     if (status != SOLVER_CONTINUE) {
     70       return status;
     71     }
     72   }
     73   return SOLVER_CONTINUE;
     74 }
     75 
     76 // Compute a scaling vector that is used to improve the conditioning
     77 // of the Jacobian.
     78 void TrustRegionMinimizer::EstimateScale(const SparseMatrix& jacobian,
     79                                          double* scale) const {
     80   jacobian.SquaredColumnNorm(scale);
     81   for (int i = 0; i < jacobian.num_cols(); ++i) {
     82     scale[i] = 1.0 / (1.0 + sqrt(scale[i]));
     83   }
     84 }
     85 
     86 void TrustRegionMinimizer::Init(const Minimizer::Options& options) {
     87   options_ = options;
     88   sort(options_.lsqp_iterations_to_dump.begin(),
     89        options_.lsqp_iterations_to_dump.end());
     90 }
     91 
     92 bool TrustRegionMinimizer::MaybeDumpLinearLeastSquaresProblem(
     93     const int iteration,
     94     const SparseMatrix* jacobian,
     95     const double* residuals,
     96     const double* step) const  {
     97   // TODO(sameeragarwal): Since the use of trust_region_radius has
     98   // moved inside TrustRegionStrategy, its not clear how we dump the
     99   // regularization vector/matrix anymore.
    100   //
    101   // Also num_eliminate_blocks is not visible to the trust region
    102   // minimizer either.
    103   //
    104   // Both of these indicate that this is the wrong place for this
    105   // code, and going forward this should needs fixing/refactoring.
    106   return true;
    107 }
    108 
    109 void TrustRegionMinimizer::Minimize(const Minimizer::Options& options,
    110                                     double* parameters,
    111                                     Solver::Summary* summary) {
    112   double start_time = WallTimeInSeconds();
    113   double iteration_start_time =  start_time;
    114   Init(options);
    115 
    116   summary->termination_type = NO_CONVERGENCE;
    117   summary->num_successful_steps = 0;
    118   summary->num_unsuccessful_steps = 0;
    119 
    120   Evaluator* evaluator = CHECK_NOTNULL(options_.evaluator);
    121   SparseMatrix* jacobian = CHECK_NOTNULL(options_.jacobian);
    122   TrustRegionStrategy* strategy = CHECK_NOTNULL(options_.trust_region_strategy);
    123 
    124   const int num_parameters = evaluator->NumParameters();
    125   const int num_effective_parameters = evaluator->NumEffectiveParameters();
    126   const int num_residuals = evaluator->NumResiduals();
    127 
    128   VectorRef x_min(parameters, num_parameters);
    129   Vector x = x_min;
    130   double x_norm = x.norm();
    131 
    132   Vector residuals(num_residuals);
    133   Vector trust_region_step(num_effective_parameters);
    134   Vector delta(num_effective_parameters);
    135   Vector x_plus_delta(num_parameters);
    136   Vector gradient(num_effective_parameters);
    137   Vector model_residuals(num_residuals);
    138   Vector scale(num_effective_parameters);
    139 
    140   IterationSummary iteration_summary;
    141   iteration_summary.iteration = 0;
    142   iteration_summary.step_is_valid = false;
    143   iteration_summary.step_is_successful = false;
    144   iteration_summary.cost_change = 0.0;
    145   iteration_summary.gradient_max_norm = 0.0;
    146   iteration_summary.step_norm = 0.0;
    147   iteration_summary.relative_decrease = 0.0;
    148   iteration_summary.trust_region_radius = strategy->Radius();
    149   // TODO(sameeragarwal): Rename eta to linear_solver_accuracy or
    150   // something similar across the board.
    151   iteration_summary.eta = options_.eta;
    152   iteration_summary.linear_solver_iterations = 0;
    153   iteration_summary.step_solver_time_in_seconds = 0;
    154 
    155   // Do initial cost and Jacobian evaluation.
    156   double cost = 0.0;
    157   if (!evaluator->Evaluate(x.data(), &cost, residuals.data(), NULL, jacobian)) {
    158     LOG(WARNING) << "Terminating: Residual and Jacobian evaluation failed.";
    159     summary->termination_type = NUMERICAL_FAILURE;
    160     return;
    161   }
    162 
    163   iteration_summary.cost = cost + summary->fixed_cost;
    164 
    165   int num_consecutive_nonmonotonic_steps = 0;
    166   double minimum_cost = cost;
    167   double reference_cost = cost;
    168   double accumulated_reference_model_cost_change = 0.0;
    169   double candidate_cost = cost;
    170   double accumulated_candidate_model_cost_change = 0.0;
    171 
    172   gradient.setZero();
    173   jacobian->LeftMultiply(residuals.data(), gradient.data());
    174   iteration_summary.gradient_max_norm = gradient.lpNorm<Eigen::Infinity>();
    175 
    176   if (options_.jacobi_scaling) {
    177     EstimateScale(*jacobian, scale.data());
    178     jacobian->ScaleColumns(scale.data());
    179   } else {
    180     scale.setOnes();
    181   }
    182 
    183   // The initial gradient max_norm is bounded from below so that we do
    184   // not divide by zero.
    185   const double gradient_max_norm_0 =
    186       max(iteration_summary.gradient_max_norm, kEpsilon);
    187   const double absolute_gradient_tolerance =
    188       options_.gradient_tolerance * gradient_max_norm_0;
    189 
    190   if (iteration_summary.gradient_max_norm <= absolute_gradient_tolerance) {
    191     summary->termination_type = GRADIENT_TOLERANCE;
    192     VLOG(1) << "Terminating: Gradient tolerance reached."
    193             << "Relative gradient max norm: "
    194             << iteration_summary.gradient_max_norm / gradient_max_norm_0
    195             << " <= " << options_.gradient_tolerance;
    196     return;
    197   }
    198 
    199   iteration_summary.iteration_time_in_seconds =
    200       WallTimeInSeconds() - iteration_start_time;
    201   iteration_summary.cumulative_time_in_seconds =
    202       WallTimeInSeconds() - start_time
    203       + summary->preprocessor_time_in_seconds;
    204   summary->iterations.push_back(iteration_summary);
    205 
    206   // Call the various callbacks.
    207   switch (RunCallbacks(iteration_summary)) {
    208     case SOLVER_TERMINATE_SUCCESSFULLY:
    209       summary->termination_type = USER_SUCCESS;
    210       VLOG(1) << "Terminating: User callback returned USER_SUCCESS.";
    211       return;
    212     case SOLVER_ABORT:
    213       summary->termination_type = USER_ABORT;
    214       VLOG(1) << "Terminating: User callback returned  USER_ABORT.";
    215       return;
    216     case SOLVER_CONTINUE:
    217       break;
    218     default:
    219       LOG(FATAL) << "Unknown type of user callback status";
    220   }
    221 
    222   int num_consecutive_invalid_steps = 0;
    223   while (true) {
    224     iteration_start_time = WallTimeInSeconds();
    225     if (iteration_summary.iteration >= options_.max_num_iterations) {
    226       summary->termination_type = NO_CONVERGENCE;
    227       VLOG(1) << "Terminating: Maximum number of iterations reached.";
    228       break;
    229     }
    230 
    231     const double total_solver_time = iteration_start_time - start_time +
    232         summary->preprocessor_time_in_seconds;
    233     if (total_solver_time >= options_.max_solver_time_in_seconds) {
    234       summary->termination_type = NO_CONVERGENCE;
    235       VLOG(1) << "Terminating: Maximum solver time reached.";
    236       break;
    237     }
    238 
    239     iteration_summary = IterationSummary();
    240     iteration_summary = summary->iterations.back();
    241     iteration_summary.iteration = summary->iterations.back().iteration + 1;
    242     iteration_summary.step_is_valid = false;
    243     iteration_summary.step_is_successful = false;
    244 
    245     const double strategy_start_time = WallTimeInSeconds();
    246     TrustRegionStrategy::PerSolveOptions per_solve_options;
    247     per_solve_options.eta = options_.eta;
    248     TrustRegionStrategy::Summary strategy_summary =
    249         strategy->ComputeStep(per_solve_options,
    250                               jacobian,
    251                               residuals.data(),
    252                               trust_region_step.data());
    253 
    254     iteration_summary.step_solver_time_in_seconds =
    255         WallTimeInSeconds() - strategy_start_time;
    256     iteration_summary.linear_solver_iterations =
    257         strategy_summary.num_iterations;
    258 
    259     if (!MaybeDumpLinearLeastSquaresProblem(iteration_summary.iteration,
    260                                             jacobian,
    261                                             residuals.data(),
    262                                             trust_region_step.data())) {
    263       LOG(FATAL) << "Tried writing linear least squares problem: "
    264                  << options.lsqp_dump_directory << "but failed.";
    265     }
    266 
    267     double model_cost_change = 0.0;
    268     if (strategy_summary.termination_type != FAILURE) {
    269       // new_model_cost
    270       //  = 1/2 [f + J * step]^2
    271       //  = 1/2 [ f'f + 2f'J * step + step' * J' * J * step ]
    272       // model_cost_change
    273       //  = cost - new_model_cost
    274       //  = f'f/2  - 1/2 [ f'f + 2f'J * step + step' * J' * J * step]
    275       //  = -f'J * step - step' * J' * J * step / 2
    276       model_residuals.setZero();
    277       jacobian->RightMultiply(trust_region_step.data(), model_residuals.data());
    278       model_cost_change = -(residuals.dot(model_residuals) +
    279                             model_residuals.squaredNorm() / 2.0);
    280 
    281       if (model_cost_change < 0.0) {
    282         VLOG(1) << "Invalid step: current_cost: " << cost
    283                 << " absolute difference " << model_cost_change
    284                 << " relative difference " << (model_cost_change / cost);
    285       } else {
    286         iteration_summary.step_is_valid = true;
    287       }
    288     }
    289 
    290     if (!iteration_summary.step_is_valid) {
    291       // Invalid steps can happen due to a number of reasons, and we
    292       // allow a limited number of successive failures, and return with
    293       // NUMERICAL_FAILURE if this limit is exceeded.
    294       if (++num_consecutive_invalid_steps >=
    295           options_.max_num_consecutive_invalid_steps) {
    296         summary->termination_type = NUMERICAL_FAILURE;
    297         summary->error = StringPrintf(
    298             "Terminating. Number of successive invalid steps more "
    299             "than Solver::Options::max_num_consecutive_invalid_steps: %d",
    300             options_.max_num_consecutive_invalid_steps);
    301 
    302         LOG(WARNING) << summary->error;
    303         return;
    304       }
    305 
    306       // We are going to try and reduce the trust region radius and
    307       // solve again. To do this, we are going to treat this iteration
    308       // as an unsuccessful iteration. Since the various callbacks are
    309       // still executed, we are going to fill the iteration summary
    310       // with data that assumes a step of length zero and no progress.
    311       iteration_summary.cost = cost + summary->fixed_cost;
    312       iteration_summary.cost_change = 0.0;
    313       iteration_summary.gradient_max_norm =
    314           summary->iterations.back().gradient_max_norm;
    315       iteration_summary.step_norm = 0.0;
    316       iteration_summary.relative_decrease = 0.0;
    317       iteration_summary.eta = options_.eta;
    318     } else {
    319       // The step is numerically valid, so now we can judge its quality.
    320       num_consecutive_invalid_steps = 0;
    321 
    322       // Undo the Jacobian column scaling.
    323       delta = (trust_region_step.array() * scale.array()).matrix();
    324       if (!evaluator->Plus(x.data(), delta.data(), x_plus_delta.data())) {
    325         summary->termination_type = NUMERICAL_FAILURE;
    326         summary->error =
    327             "Terminating. Failed to compute Plus(x, delta, x_plus_delta).";
    328 
    329         LOG(WARNING) << summary->error;
    330         return;
    331       }
    332 
    333       // Try this step.
    334       double new_cost = numeric_limits<double>::max();
    335       if (!evaluator->Evaluate(x_plus_delta.data(),
    336                                &new_cost,
    337                                NULL, NULL, NULL)) {
    338         // If the evaluation of the new cost fails, treat it as a step
    339         // with high cost.
    340         LOG(WARNING) << "Step failed to evaluate. "
    341                      << "Treating it as step with infinite cost";
    342         new_cost = numeric_limits<double>::max();
    343       } else {
    344         // Check if performing an inner iteration will make it better.
    345         if (options.inner_iteration_minimizer != NULL) {
    346           const double x_plus_delta_cost = new_cost;
    347           Vector inner_iteration_x = x_plus_delta;
    348           Solver::Summary inner_iteration_summary;
    349           options.inner_iteration_minimizer->Minimize(options,
    350                                                       inner_iteration_x.data(),
    351                                                       &inner_iteration_summary);
    352           if(!evaluator->Evaluate(inner_iteration_x.data(),
    353                                   &new_cost,
    354                                   NULL, NULL, NULL)) {
    355             VLOG(2) << "Inner iteration failed.";
    356             new_cost = x_plus_delta_cost;
    357           } else {
    358             x_plus_delta = inner_iteration_x;
    359             // Bost the model_cost_change, since the inner iteration
    360             // improvements are not accounted for by the trust region.
    361             model_cost_change +=  x_plus_delta_cost - new_cost;
    362             VLOG(2) << "Inner iteration succeeded; current cost: " << cost
    363                     << " x_plus_delta_cost: " << x_plus_delta_cost
    364                     << " new_cost: " << new_cost;
    365           }
    366         }
    367       }
    368 
    369       iteration_summary.step_norm = (x - x_plus_delta).norm();
    370 
    371       // Convergence based on parameter_tolerance.
    372       const double step_size_tolerance =  options_.parameter_tolerance *
    373           (x_norm + options_.parameter_tolerance);
    374       if (iteration_summary.step_norm <= step_size_tolerance) {
    375         VLOG(1) << "Terminating. Parameter tolerance reached. "
    376                 << "relative step_norm: "
    377                 << iteration_summary.step_norm /
    378             (x_norm + options_.parameter_tolerance)
    379                 << " <= " << options_.parameter_tolerance;
    380         summary->termination_type = PARAMETER_TOLERANCE;
    381         return;
    382       }
    383 
    384       VLOG(2) << "old cost: " << cost << " new cost: " << new_cost;
    385       iteration_summary.cost_change =  cost - new_cost;
    386       const double absolute_function_tolerance =
    387           options_.function_tolerance * cost;
    388       if (fabs(iteration_summary.cost_change) < absolute_function_tolerance) {
    389         VLOG(1) << "Terminating. Function tolerance reached. "
    390                 << "|cost_change|/cost: "
    391                 << fabs(iteration_summary.cost_change) / cost
    392                 << " <= " << options_.function_tolerance;
    393         summary->termination_type = FUNCTION_TOLERANCE;
    394         return;
    395       }
    396 
    397       const double relative_decrease =
    398           iteration_summary.cost_change / model_cost_change;
    399 
    400       const double historical_relative_decrease =
    401           (reference_cost - new_cost) /
    402           (accumulated_reference_model_cost_change + model_cost_change);
    403 
    404       // If monotonic steps are being used, then the relative_decrease
    405       // is the usual ratio of the change in objective function value
    406       // divided by the change in model cost.
    407       //
    408       // If non-monotonic steps are allowed, then we take the maximum
    409       // of the relative_decrease and the
    410       // historical_relative_decrease, which measures the increase
    411       // from a reference iteration. The model cost change is
    412       // estimated by accumulating the model cost changes since the
    413       // reference iteration. The historical relative_decrease offers
    414       // a boost to a step which is not too bad compared to the
    415       // reference iteration, allowing for non-monotonic steps.
    416       iteration_summary.relative_decrease =
    417           options.use_nonmonotonic_steps
    418           ? max(relative_decrease, historical_relative_decrease)
    419           : relative_decrease;
    420 
    421       iteration_summary.step_is_successful =
    422           iteration_summary.relative_decrease > options_.min_relative_decrease;
    423 
    424       if (iteration_summary.step_is_successful) {
    425         accumulated_candidate_model_cost_change += model_cost_change;
    426         accumulated_reference_model_cost_change += model_cost_change;
    427         if (relative_decrease <= options_.min_relative_decrease) {
    428           iteration_summary.step_is_nonmonotonic = true;
    429           VLOG(2) << "Non-monotonic step! "
    430                   << " relative_decrease: " << relative_decrease
    431                   << " historical_relative_decrease: "
    432                   << historical_relative_decrease;
    433         }
    434       }
    435     }
    436 
    437     if (iteration_summary.step_is_successful) {
    438       ++summary->num_successful_steps;
    439       strategy->StepAccepted(iteration_summary.relative_decrease);
    440       x = x_plus_delta;
    441       x_norm = x.norm();
    442 
    443       // Step looks good, evaluate the residuals and Jacobian at this
    444       // point.
    445       if (!evaluator->Evaluate(x.data(),
    446                                &cost,
    447                                residuals.data(),
    448                                NULL,
    449                                jacobian)) {
    450         summary->termination_type = NUMERICAL_FAILURE;
    451         summary->error = "Terminating: Residual and Jacobian evaluation failed.";
    452         LOG(WARNING) << summary->error;
    453         return;
    454       }
    455 
    456       gradient.setZero();
    457       jacobian->LeftMultiply(residuals.data(), gradient.data());
    458       iteration_summary.gradient_max_norm = gradient.lpNorm<Eigen::Infinity>();
    459 
    460       if (iteration_summary.gradient_max_norm <= absolute_gradient_tolerance) {
    461         summary->termination_type = GRADIENT_TOLERANCE;
    462         VLOG(1) << "Terminating: Gradient tolerance reached."
    463                 << "Relative gradient max norm: "
    464                 << iteration_summary.gradient_max_norm / gradient_max_norm_0
    465                 << " <= " << options_.gradient_tolerance;
    466         return;
    467       }
    468 
    469       if (options_.jacobi_scaling) {
    470         jacobian->ScaleColumns(scale.data());
    471       }
    472 
    473       // Update the best, reference and candidate iterates.
    474       //
    475       // Based on algorithm 10.1.2 (page 357) of "Trust Region
    476       // Methods" by Conn Gould & Toint, or equations 33-40 of
    477       // "Non-monotone trust-region algorithms for nonlinear
    478       // optimization subject to convex constraints" by Phil Toint,
    479       // Mathematical Programming, 77, 1997.
    480       if (cost < minimum_cost) {
    481         // A step that improves solution quality was found.
    482         x_min = x;
    483         minimum_cost = cost;
    484         // Set the candidate iterate to the current point.
    485         candidate_cost = cost;
    486         num_consecutive_nonmonotonic_steps = 0;
    487         accumulated_candidate_model_cost_change = 0.0;
    488       } else {
    489         ++num_consecutive_nonmonotonic_steps;
    490         if (cost > candidate_cost) {
    491           // The current iterate is has a higher cost than the
    492           // candidate iterate. Set the candidate to this point.
    493           VLOG(2) << "Updating the candidate iterate to the current point.";
    494           candidate_cost = cost;
    495           accumulated_candidate_model_cost_change = 0.0;
    496         }
    497 
    498         // At this point we have made too many non-monotonic steps and
    499         // we are going to reset the value of the reference iterate so
    500         // as to force the algorithm to descend.
    501         //
    502         // This is the case because the candidate iterate has a value
    503         // greater than minimum_cost but smaller than the reference
    504         // iterate.
    505         if (num_consecutive_nonmonotonic_steps ==
    506             options.max_consecutive_nonmonotonic_steps) {
    507           VLOG(2) << "Resetting the reference point to the candidate point";
    508           reference_cost = candidate_cost;
    509           accumulated_reference_model_cost_change =
    510               accumulated_candidate_model_cost_change;
    511         }
    512       }
    513     } else {
    514       ++summary->num_unsuccessful_steps;
    515       if (iteration_summary.step_is_valid) {
    516         strategy->StepRejected(iteration_summary.relative_decrease);
    517       } else {
    518         strategy->StepIsInvalid();
    519       }
    520     }
    521 
    522     iteration_summary.cost = cost + summary->fixed_cost;
    523     iteration_summary.trust_region_radius = strategy->Radius();
    524     if (iteration_summary.trust_region_radius <
    525         options_.min_trust_region_radius) {
    526       summary->termination_type = PARAMETER_TOLERANCE;
    527       VLOG(1) << "Termination. Minimum trust region radius reached.";
    528       return;
    529     }
    530 
    531     iteration_summary.iteration_time_in_seconds =
    532         WallTimeInSeconds() - iteration_start_time;
    533     iteration_summary.cumulative_time_in_seconds =
    534         WallTimeInSeconds() - start_time
    535         + summary->preprocessor_time_in_seconds;
    536     summary->iterations.push_back(iteration_summary);
    537 
    538     switch (RunCallbacks(iteration_summary)) {
    539       case SOLVER_TERMINATE_SUCCESSFULLY:
    540         summary->termination_type = USER_SUCCESS;
    541         VLOG(1) << "Terminating: User callback returned USER_SUCCESS.";
    542         return;
    543       case SOLVER_ABORT:
    544         summary->termination_type = USER_ABORT;
    545         VLOG(1) << "Terminating: User callback returned  USER_ABORT.";
    546         return;
    547       case SOLVER_CONTINUE:
    548         break;
    549       default:
    550         LOG(FATAL) << "Unknown type of user callback status";
    551     }
    552   }
    553 }
    554 
    555 
    556 }  // namespace internal
    557 }  // namespace ceres
    558