// Ceres Solver - A fast non-linear least squares minimizer // Copyright 2015 Google Inc. All rights reserved. // http://ceres-solver.org/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors may be // used to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // Author: sameeragarwal@google.com (Sameer Agarwal) // mierle@gmail.com (Keir Mierle) // tbennun@gmail.com (Tal Ben-Nun) // // Finite differencing routines used by NumericDiffCostFunction. #ifndef CERES_PUBLIC_INTERNAL_NUMERIC_DIFF_H_ #define CERES_PUBLIC_INTERNAL_NUMERIC_DIFF_H_ #include #include #include "Eigen/Dense" #include "Eigen/StdVector" #include "ceres/cost_function.h" #include "ceres/internal/fixed_array.h" #include "ceres/internal/variadic_evaluate.h" #include "ceres/numeric_diff_options.h" #include "ceres/types.h" #include "glog/logging.h" namespace ceres { namespace internal { // This is split from the main class because C++ doesn't allow partial template // specializations for member functions. The alternative is to repeat the main // class for differing numbers of parameters, which is also unfortunate. template struct NumericDiff { // Mutates parameters but must restore them before return. static bool EvaluateJacobianForParameterBlock( const CostFunctor* functor, const double* residuals_at_eval_point, const NumericDiffOptions& options, int num_residuals, int parameter_block_index, int parameter_block_size, double** parameters, double* jacobian) { using Eigen::ColMajor; using Eigen::Map; using Eigen::Matrix; using Eigen::RowMajor; DCHECK(jacobian); const int num_residuals_internal = (kNumResiduals != ceres::DYNAMIC ? kNumResiduals : num_residuals); const int parameter_block_index_internal = (kParameterBlock != ceres::DYNAMIC ? kParameterBlock : parameter_block_index); const int parameter_block_size_internal = (kParameterBlockSize != ceres::DYNAMIC ? kParameterBlockSize : parameter_block_size); using ResidualVector = Matrix; using ParameterVector = Matrix; // The convoluted reasoning for choosing the Row/Column major // ordering of the matrix is an artifact of the restrictions in // Eigen that prevent it from creating RowMajor matrices with a // single column. In these cases, we ask for a ColMajor matrix. using JacobianMatrix = Matrix; Map parameter_jacobian( jacobian, num_residuals_internal, parameter_block_size_internal); Map x_plus_delta( parameters[parameter_block_index_internal], parameter_block_size_internal); ParameterVector x(x_plus_delta); ParameterVector step_size = x.array().abs() * ((kMethod == RIDDERS) ? options.ridders_relative_initial_step_size : options.relative_step_size); // It is not a good idea to make the step size arbitrarily // small. This will lead to problems with round off and numerical // instability when dividing by the step size. The general // recommendation is to not go down below sqrt(epsilon). double min_step_size = std::sqrt(std::numeric_limits::epsilon()); // For Ridders' method, the initial step size is required to be large, // thus ridders_relative_initial_step_size is used. if (kMethod == RIDDERS) { min_step_size = (std::max)(min_step_size, options.ridders_relative_initial_step_size); } // For each parameter in the parameter block, use finite differences to // compute the derivative for that parameter. FixedArray temp_residual_array(num_residuals_internal); FixedArray residual_array(num_residuals_internal); Map residuals(residual_array.data(), num_residuals_internal); for (int j = 0; j < parameter_block_size_internal; ++j) { const double delta = (std::max)(min_step_size, step_size(j)); if (kMethod == RIDDERS) { if (!EvaluateRiddersJacobianColumn(functor, j, delta, options, num_residuals_internal, parameter_block_size_internal, x.data(), residuals_at_eval_point, parameters, x_plus_delta.data(), temp_residual_array.data(), residual_array.data())) { return false; } } else { if (!EvaluateJacobianColumn(functor, j, delta, num_residuals_internal, parameter_block_size_internal, x.data(), residuals_at_eval_point, parameters, x_plus_delta.data(), temp_residual_array.data(), residual_array.data())) { return false; } } parameter_jacobian.col(j).matrix() = residuals; } return true; } static bool EvaluateJacobianColumn(const CostFunctor* functor, int parameter_index, double delta, int num_residuals, int parameter_block_size, const double* x_ptr, const double* residuals_at_eval_point, double** parameters, double* x_plus_delta_ptr, double* temp_residuals_ptr, double* residuals_ptr) { using Eigen::Map; using Eigen::Matrix; using ResidualVector = Matrix; using ParameterVector = Matrix; Map x(x_ptr, parameter_block_size); Map x_plus_delta(x_plus_delta_ptr, parameter_block_size); Map residuals(residuals_ptr, num_residuals); Map temp_residuals(temp_residuals_ptr, num_residuals); // Mutate 1 element at a time and then restore. x_plus_delta(parameter_index) = x(parameter_index) + delta; if (!VariadicEvaluate( *functor, parameters, residuals.data())) { return false; } // Compute this column of the jacobian in 3 steps: // 1. Store residuals for the forward part. // 2. Subtract residuals for the backward (or 0) part. // 3. Divide out the run. double one_over_delta = 1.0 / delta; if (kMethod == CENTRAL || kMethod == RIDDERS) { // Compute the function on the other side of x(parameter_index). x_plus_delta(parameter_index) = x(parameter_index) - delta; if (!VariadicEvaluate( *functor, parameters, temp_residuals.data())) { return false; } residuals -= temp_residuals; one_over_delta /= 2; } else { // Forward difference only; reuse existing residuals evaluation. residuals -= Map(residuals_at_eval_point, num_residuals); } // Restore x_plus_delta. x_plus_delta(parameter_index) = x(parameter_index); // Divide out the run to get slope. residuals *= one_over_delta; return true; } // This numeric difference implementation uses adaptive differentiation // on the parameters to obtain the Jacobian matrix. The adaptive algorithm // is based on Ridders' method for adaptive differentiation, which creates // a Romberg tableau from varying step sizes and extrapolates the // intermediate results to obtain the current computational error. // // References: // C.J.F. Ridders, Accurate computation of F'(x) and F'(x) F"(x), Advances // in Engineering Software (1978), Volume 4, Issue 2, April 1982, // Pages 75-76, ISSN 0141-1195, // http://dx.doi.org/10.1016/S0141-1195(82)80057-0. static bool EvaluateRiddersJacobianColumn( const CostFunctor* functor, int parameter_index, double delta, const NumericDiffOptions& options, int num_residuals, int parameter_block_size, const double* x_ptr, const double* residuals_at_eval_point, double** parameters, double* x_plus_delta_ptr, double* temp_residuals_ptr, double* residuals_ptr) { using Eigen::aligned_allocator; using Eigen::Map; using Eigen::Matrix; using ResidualVector = Matrix; using ResidualCandidateMatrix = Matrix; using ParameterVector = Matrix; Map x(x_ptr, parameter_block_size); Map x_plus_delta(x_plus_delta_ptr, parameter_block_size); Map residuals(residuals_ptr, num_residuals); Map temp_residuals(temp_residuals_ptr, num_residuals); // In order for the algorithm to converge, the step size should be // initialized to a value that is large enough to produce a significant // change in the function. // As the derivative is estimated, the step size decreases. // By default, the step sizes are chosen so that the middle column // of the Romberg tableau uses the input delta. double current_step_size = delta * pow(options.ridders_step_shrink_factor, options.max_num_ridders_extrapolations / 2); // Double-buffering temporary differential candidate vectors // from previous step size. ResidualCandidateMatrix stepsize_candidates_a( num_residuals, options.max_num_ridders_extrapolations); ResidualCandidateMatrix stepsize_candidates_b( num_residuals, options.max_num_ridders_extrapolations); ResidualCandidateMatrix* current_candidates = &stepsize_candidates_a; ResidualCandidateMatrix* previous_candidates = &stepsize_candidates_b; // Represents the computational error of the derivative. This variable is // initially set to a large value, and is set to the difference between // current and previous finite difference extrapolations. // norm_error is supposed to decrease as the finite difference tableau // generation progresses, serving both as an estimate for differentiation // error and as a measure of differentiation numerical stability. double norm_error = (std::numeric_limits::max)(); // Loop over decreasing step sizes until: // 1. Error is smaller than a given value (ridders_epsilon), // 2. Maximal order of extrapolation reached, or // 3. Extrapolation becomes numerically unstable. for (int i = 0; i < options.max_num_ridders_extrapolations; ++i) { // Compute the numerical derivative at this step size. if (!EvaluateJacobianColumn(functor, parameter_index, current_step_size, num_residuals, parameter_block_size, x.data(), residuals_at_eval_point, parameters, x_plus_delta.data(), temp_residuals.data(), current_candidates->col(0).data())) { // Something went wrong; bail. return false; } // Store initial results. if (i == 0) { residuals = current_candidates->col(0); } // Shrink differentiation step size. current_step_size /= options.ridders_step_shrink_factor; // Extrapolation factor for Richardson acceleration method (see below). double richardson_factor = options.ridders_step_shrink_factor * options.ridders_step_shrink_factor; for (int k = 1; k <= i; ++k) { // Extrapolate the various orders of finite differences using // the Richardson acceleration method. current_candidates->col(k) = (richardson_factor * current_candidates->col(k - 1) - previous_candidates->col(k - 1)) / (richardson_factor - 1.0); richardson_factor *= options.ridders_step_shrink_factor * options.ridders_step_shrink_factor; // Compute the difference between the previous value and the current. double candidate_error = (std::max)( (current_candidates->col(k) - current_candidates->col(k - 1)) .norm(), (current_candidates->col(k) - previous_candidates->col(k - 1)) .norm()); // If the error has decreased, update results. if (candidate_error <= norm_error) { norm_error = candidate_error; residuals = current_candidates->col(k); // If the error is small enough, stop. if (norm_error < options.ridders_epsilon) { break; } } } // After breaking out of the inner loop, declare convergence. if (norm_error < options.ridders_epsilon) { break; } // Check to see if the current gradient estimate is numerically unstable. // If so, bail out and return the last stable result. if (i > 0) { double tableau_error = (current_candidates->col(i) - previous_candidates->col(i - 1)) .norm(); // Compare current error to the chosen candidate's error. if (tableau_error >= 2 * norm_error) { break; } } std::swap(current_candidates, previous_candidates); } return true; } }; // This function calls NumericDiff<...>::EvaluateJacobianForParameterBlock for // each parameter block. // // Example: // A call to // EvaluateJacobianForParameterBlocks>( // functor, // residuals_at_eval_point, // options, // num_residuals, // parameters, // jacobians); // will result in the following calls to // NumericDiff<...>::EvaluateJacobianForParameterBlock: // // if (jacobians[0] != nullptr) { // if (!NumericDiff< // CostFunctor, // method, // kNumResiduals, // StaticParameterDims<2, 3>, // 0, // 2>::EvaluateJacobianForParameterBlock(functor, // residuals_at_eval_point, // options, // num_residuals, // 0, // 2, // parameters, // jacobians[0])) { // return false; // } // } // if (jacobians[1] != nullptr) { // if (!NumericDiff< // CostFunctor, // method, // kNumResiduals, // StaticParameterDims<2, 3>, // 1, // 3>::EvaluateJacobianForParameterBlock(functor, // residuals_at_eval_point, // options, // num_residuals, // 1, // 3, // parameters, // jacobians[1])) { // return false; // } // } template struct EvaluateJacobianForParameterBlocks; template struct EvaluateJacobianForParameterBlocks, ParameterIdx> { template static bool Apply(const CostFunctor* functor, const double* residuals_at_eval_point, const NumericDiffOptions& options, int num_residuals, double** parameters, double** jacobians) { if (jacobians[ParameterIdx] != nullptr) { if (!NumericDiff< CostFunctor, method, kNumResiduals, ParameterDims, ParameterIdx, N>::EvaluateJacobianForParameterBlock(functor, residuals_at_eval_point, options, num_residuals, ParameterIdx, N, parameters, jacobians[ParameterIdx])) { return false; } } return EvaluateJacobianForParameterBlocks, ParameterIdx + 1>:: template Apply(functor, residuals_at_eval_point, options, num_residuals, parameters, jacobians); } }; // End of 'recursion'. Nothing more to do. template struct EvaluateJacobianForParameterBlocks, ParameterIdx> { template static bool Apply(const CostFunctor* /* NOT USED*/, const double* /* NOT USED*/, const NumericDiffOptions& /* NOT USED*/, int /* NOT USED*/, double** /* NOT USED*/, double** /* NOT USED*/) { return true; } }; } // namespace internal } // namespace ceres #endif // CERES_PUBLIC_INTERNAL_NUMERIC_DIFF_H_