blob: 0950c0e24e2fd158f899c7772c553c90c02d9776 [file] [log] [blame]
// Ceres Solver - A fast non-linear least squares minimizer
// Copyright 2012 Google Inc. All rights reserved.
// http://code.google.com/p/ceres-solver/
//
// 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)
#include "ceres/dogleg_strategy.h"
#include <cmath>
#include "Eigen/Core"
#include <glog/logging.h>
#include "ceres/array_utils.h"
#include "ceres/internal/eigen.h"
#include "ceres/linear_solver.h"
#include "ceres/sparse_matrix.h"
#include "ceres/trust_region_strategy.h"
#include "ceres/types.h"
namespace ceres {
namespace internal {
namespace {
const double kMaxMu = 1.0;
const double kMinMu = 1e-8;
}
DoglegStrategy::DoglegStrategy(const TrustRegionStrategy::Options& options)
: linear_solver_(options.linear_solver),
radius_(options.initial_radius),
max_radius_(options.max_radius),
min_diagonal_(options.lm_min_diagonal),
max_diagonal_(options.lm_max_diagonal),
mu_(kMinMu),
min_mu_(kMinMu),
max_mu_(kMaxMu),
mu_increase_factor_(10.0),
increase_threshold_(0.75),
decrease_threshold_(0.25),
dogleg_step_norm_(0.0),
reuse_(false) {
CHECK_NOTNULL(linear_solver_);
CHECK_GT(min_diagonal_, 0.0);
CHECK_LT(min_diagonal_, max_diagonal_);
CHECK_GT(max_radius_, 0.0);
}
// If the reuse_ flag is not set, then the Cauchy point (scaled
// gradient) and the new Gauss-Newton step are computed from
// scratch. The Dogleg step is then computed as interpolation of these
// two vectors.
LinearSolver::Summary DoglegStrategy::ComputeStep(
const TrustRegionStrategy::PerSolveOptions& per_solve_options,
SparseMatrix* jacobian,
const double* residuals,
double* step) {
CHECK_NOTNULL(jacobian);
CHECK_NOTNULL(residuals);
CHECK_NOTNULL(step);
const int n = jacobian->num_cols();
if (reuse_) {
// Gauss-Newton and gradient vectors are always available, only a
// new interpolant need to be computed.
ComputeDoglegStep(step);
LinearSolver::Summary linear_solver_summary;
linear_solver_summary.num_iterations = 0;
linear_solver_summary.termination_type = TOLERANCE;
return linear_solver_summary;
}
reuse_ = true;
// Check that we have the storage needed to hold the various
// temporary vectors.
if (diagonal_.rows() != n) {
diagonal_.resize(n, 1);
gradient_.resize(n, 1);
gauss_newton_step_.resize(n, 1);
}
// Vector used to form the diagonal matrix that is used to
// regularize the Gauss-Newton solve.
jacobian->SquaredColumnNorm(diagonal_.data());
for (int i = 0; i < n; ++i) {
diagonal_[i] = min(max(diagonal_[i], min_diagonal_), max_diagonal_);
}
gradient_.setZero();
jacobian->LeftMultiply(residuals, gradient_.data());
// alpha * gradient is the Cauchy point.
Vector Jg(jacobian->num_rows());
Jg.setZero();
jacobian->RightMultiply(gradient_.data(), Jg.data());
alpha_ = gradient_.squaredNorm() / Jg.squaredNorm();
LinearSolver::Summary linear_solver_summary =
ComputeGaussNewtonStep(jacobian, residuals);
// Interpolate the Cauchy point and the Gauss-Newton step.
if (linear_solver_summary.termination_type != FAILURE) {
ComputeDoglegStep(step);
}
return linear_solver_summary;
}
void DoglegStrategy::ComputeDoglegStep(double* dogleg) {
VectorRef dogleg_step(dogleg, gradient_.rows());
// Case 1. The Gauss-Newton step lies inside the trust region, and
// is therefore the optimal solution to the trust-region problem.
const double gradient_norm = gradient_.norm();
const double gauss_newton_norm = gauss_newton_step_.norm();
if (gauss_newton_norm <= radius_) {
dogleg_step = gauss_newton_step_;
dogleg_step_norm_ = gauss_newton_norm;
VLOG(3) << "GaussNewton step size: " << dogleg_step_norm_
<< " radius: " << radius_;
return;
}
// Case 2. The Cauchy point and the Gauss-Newton steps lie outside
// the trust region. Rescale the Cauchy point to the trust region
// and return.
if (gradient_norm * alpha_ >= radius_) {
dogleg_step = (radius_ / gradient_norm) * gradient_;
dogleg_step_norm_ = radius_;
VLOG(3) << "Cauchy step size: " << dogleg_step_norm_
<< " radius: " << radius_;
return;
}
// Case 3. The Cauchy point is inside the trust region and the
// Gauss-Newton step is outside. Compute the line joining the two
// points and the point on it which intersects the trust region
// boundary.
// a = alpha * gradient
// b = gauss_newton_step
const double b_dot_a = alpha_ * gradient_.dot(gauss_newton_step_);
const double a_squared_norm = pow(alpha_ * gradient_norm, 2.0);
const double b_minus_a_squared_norm =
a_squared_norm - 2 * b_dot_a + pow(gauss_newton_norm, 2);
// c = a' (b - a)
// = alpha * gradient' gauss_newton_step - alpha^2 |gradient|^2
const double c = b_dot_a - a_squared_norm;
const double d = sqrt(c * c + b_minus_a_squared_norm *
(pow(radius_, 2.0) - a_squared_norm));
double beta =
(c <= 0)
? (d - c) / b_minus_a_squared_norm
: (radius_ * radius_ - a_squared_norm) / (d + c);
dogleg_step = (alpha_ * (1.0 - beta)) * gradient_ + beta * gauss_newton_step_;
dogleg_step_norm_ = dogleg_step.norm();
VLOG(3) << "Dogleg step size: " << dogleg_step_norm_
<< " radius: " << radius_;
}
LinearSolver::Summary DoglegStrategy::ComputeGaussNewtonStep(
SparseMatrix* jacobian,
const double* residuals) {
const int n = jacobian->num_cols();
LinearSolver::Summary linear_solver_summary;
linear_solver_summary.termination_type = FAILURE;
// The Jacobian matrix is often quite poorly conditioned. Thus it is
// necessary to add a diagonal matrix at the bottom to prevent the
// linear solver from failing.
//
// We do this by computing the same diagonal matrix as the one used
// by Levenberg-Marquardt (other choices are possible), and scaling
// it by a small constant (independent of the trust region radius).
//
// If the solve fails, the multiplier to the diagonal is increased
// up to max_mu_ by a factor of mu_increase_factor_ every time. If
// the linear solver is still not successful, the strategy returns
// with FAILURE.
//
// Next time when a new Gauss-Newton step is requested, the
// multiplier starts out from the last successful solve.
//
// When a step is declared successful, the multiplier is decreased
// by half of mu_increase_factor_.
while (mu_ < max_mu_) {
// Dogleg, as far as I (sameeragarwal) understand it, requires a
// reasonably good estimate of the Gauss-Newton step. This means
// that we need to solve the normal equations more or less
// exactly. This is reflected in the values of the tolerances set
// below.
//
// For now, this strategy should only be used with exact
// factorization based solvers, for which these tolerances are
// automatically satisfied.
//
// The right way to combine inexact solves with trust region
// methods is to use Stiehaug's method.
LinearSolver::PerSolveOptions solve_options;
solve_options.q_tolerance = 0.0;
solve_options.r_tolerance = 0.0;
lm_diagonal_ = (diagonal_ * mu_).array().sqrt();
solve_options.D = lm_diagonal_.data();
InvalidateArray(n, gauss_newton_step_.data());
linear_solver_summary = linear_solver_->Solve(jacobian,
residuals,
solve_options,
gauss_newton_step_.data());
if (linear_solver_summary.termination_type == FAILURE ||
!IsArrayValid(n, gauss_newton_step_.data())) {
mu_ *= mu_increase_factor_;
VLOG(2) << "Increasing mu " << mu_;
linear_solver_summary.termination_type = FAILURE;
continue;
}
break;
}
return linear_solver_summary;
}
void DoglegStrategy::StepAccepted(double step_quality) {
CHECK_GT(step_quality, 0.0);
if (step_quality < decrease_threshold_) {
radius_ *= 0.5;
return;
}
if (step_quality > increase_threshold_) {
radius_ = max(radius_, 3.0 * dogleg_step_norm_);
}
// Reduce the regularization multiplier, in the hope that whatever
// was causing the rank deficiency has gone away and we can return
// to doing a pure Gauss-Newton solve.
mu_ = max(min_mu_, 2.0 * mu_ / mu_increase_factor_ );
reuse_ = false;
}
void DoglegStrategy::StepRejected(double step_quality) {
radius_ *= 0.5;
reuse_ = true;
}
void DoglegStrategy::StepIsInvalid() {
mu_ *= mu_increase_factor_;
reuse_ = false;
}
double DoglegStrategy::Radius() const {
return radius_;
}
} // namespace internal
} // namespace ceres