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: moll.markus (at) arcor.de (Markus Moll)
     30 
     31 #include <limits>
     32 #include "ceres/internal/eigen.h"
     33 #include "ceres/internal/scoped_ptr.h"
     34 #include "ceres/dense_qr_solver.h"
     35 #include "ceres/dogleg_strategy.h"
     36 #include "ceres/linear_solver.h"
     37 #include "ceres/trust_region_strategy.h"
     38 #include "glog/logging.h"
     39 #include "gtest/gtest.h"
     40 
     41 namespace ceres {
     42 namespace internal {
     43 namespace {
     44 
     45 class Fixture : public testing::Test {
     46  protected:
     47   scoped_ptr<DenseSparseMatrix> jacobian_;
     48   Vector residual_;
     49   Vector x_;
     50   TrustRegionStrategy::Options options_;
     51 };
     52 
     53 // A test problem where
     54 //
     55 //   J^T J = Q diag([1 2 4 8 16 32]) Q^T
     56 //
     57 // where Q is a randomly chosen orthonormal basis of R^6.
     58 // The residual is chosen so that the minimum of the quadratic function is
     59 // at (1, 1, 1, 1, 1, 1). It is therefore at a distance of sqrt(6) ~ 2.45
     60 // from the origin.
     61 class DoglegStrategyFixtureEllipse : public Fixture {
     62  protected:
     63   virtual void SetUp() {
     64     Matrix basis(6, 6);
     65     // The following lines exceed 80 characters for better readability.
     66     basis << -0.1046920933796121, -0.7449367449921986, -0.4190744502875876, -0.4480450716142566,  0.2375351607929440, -0.0363053418882862,
     67               0.4064975684355914,  0.2681113508511354, -0.7463625494601520, -0.0803264850508117, -0.4463149623021321,  0.0130224954867195,
     68              -0.5514387729089798,  0.1026621026168657, -0.5008316122125011,  0.5738122212666414,  0.2974664724007106,  0.1296020877535158,
     69               0.5037835370947156,  0.2668479925183712, -0.1051754618492798, -0.0272739396578799,  0.7947481647088278, -0.1776623363955670,
     70              -0.4005458426625444,  0.2939330589634109, -0.0682629380550051, -0.2895448882503687, -0.0457239396341685, -0.8139899477847840,
     71              -0.3247764582762654,  0.4528151365941945, -0.0276683863102816, -0.6155994592510784,  0.1489240599972848,  0.5362574892189350;
     72 
     73     Vector Ddiag(6);
     74     Ddiag << 1.0, 2.0, 4.0, 8.0, 16.0, 32.0;
     75 
     76     Matrix sqrtD = Ddiag.array().sqrt().matrix().asDiagonal();
     77     Matrix jacobian = sqrtD * basis;
     78     jacobian_.reset(new DenseSparseMatrix(jacobian));
     79 
     80     Vector minimum(6);
     81     minimum << 1.0, 1.0, 1.0, 1.0, 1.0, 1.0;
     82     residual_ = -jacobian * minimum;
     83 
     84     x_.resize(6);
     85     x_.setZero();
     86 
     87     options_.lm_min_diagonal = 1.0;
     88     options_.lm_max_diagonal = 1.0;
     89   }
     90 };
     91 
     92 // A test problem where
     93 //
     94 //   J^T J = diag([1 2 4 8 16 32]) .
     95 //
     96 // The residual is chosen so that the minimum of the quadratic function is
     97 // at (0, 0, 1, 0, 0, 0). It is therefore at a distance of 1 from the origin.
     98 // The gradient at the origin points towards the global minimum.
     99 class DoglegStrategyFixtureValley : public Fixture {
    100  protected:
    101   virtual void SetUp() {
    102     Vector Ddiag(6);
    103     Ddiag << 1.0, 2.0, 4.0, 8.0, 16.0, 32.0;
    104 
    105     Matrix jacobian = Ddiag.asDiagonal();
    106     jacobian_.reset(new DenseSparseMatrix(jacobian));
    107 
    108     Vector minimum(6);
    109     minimum << 0.0, 0.0, 1.0, 0.0, 0.0, 0.0;
    110     residual_ = -jacobian * minimum;
    111 
    112     x_.resize(6);
    113     x_.setZero();
    114 
    115     options_.lm_min_diagonal = 1.0;
    116     options_.lm_max_diagonal = 1.0;
    117   }
    118 };
    119 
    120 const double kTolerance = 1e-14;
    121 const double kToleranceLoose = 1e-5;
    122 const double kEpsilon = std::numeric_limits<double>::epsilon();
    123 
    124 }  // namespace
    125 
    126 // The DoglegStrategy must never return a step that is longer than the current
    127 // trust region radius.
    128 TEST_F(DoglegStrategyFixtureEllipse, TrustRegionObeyedTraditional) {
    129   scoped_ptr<LinearSolver> linear_solver(
    130       new DenseQRSolver(LinearSolver::Options()));
    131   options_.linear_solver = linear_solver.get();
    132   // The global minimum is at (1, 1, ..., 1), so the distance to it is sqrt(6.0).
    133   // By restricting the trust region to a radius of 2.0, we test if the trust
    134   // region is actually obeyed.
    135   options_.dogleg_type = TRADITIONAL_DOGLEG;
    136   options_.initial_radius = 2.0;
    137   options_.max_radius = 2.0;
    138 
    139   DoglegStrategy strategy(options_);
    140   TrustRegionStrategy::PerSolveOptions pso;
    141 
    142   TrustRegionStrategy::Summary summary = strategy.ComputeStep(pso,
    143                                                               jacobian_.get(),
    144                                                               residual_.data(),
    145                                                               x_.data());
    146 
    147   EXPECT_NE(summary.termination_type, FAILURE);
    148   EXPECT_LE(x_.norm(), options_.initial_radius * (1.0 + 4.0 * kEpsilon));
    149 }
    150 
    151 TEST_F(DoglegStrategyFixtureEllipse, TrustRegionObeyedSubspace) {
    152   scoped_ptr<LinearSolver> linear_solver(
    153       new DenseQRSolver(LinearSolver::Options()));
    154   options_.linear_solver = linear_solver.get();
    155   options_.dogleg_type = SUBSPACE_DOGLEG;
    156   options_.initial_radius = 2.0;
    157   options_.max_radius = 2.0;
    158 
    159   DoglegStrategy strategy(options_);
    160   TrustRegionStrategy::PerSolveOptions pso;
    161 
    162   TrustRegionStrategy::Summary summary = strategy.ComputeStep(pso,
    163                                                               jacobian_.get(),
    164                                                               residual_.data(),
    165                                                               x_.data());
    166 
    167   EXPECT_NE(summary.termination_type, FAILURE);
    168   EXPECT_LE(x_.norm(), options_.initial_radius * (1.0 + 4.0 * kEpsilon));
    169 }
    170 
    171 TEST_F(DoglegStrategyFixtureEllipse, CorrectGaussNewtonStep) {
    172   scoped_ptr<LinearSolver> linear_solver(
    173       new DenseQRSolver(LinearSolver::Options()));
    174   options_.linear_solver = linear_solver.get();
    175   options_.dogleg_type = SUBSPACE_DOGLEG;
    176   options_.initial_radius = 10.0;
    177   options_.max_radius = 10.0;
    178 
    179   DoglegStrategy strategy(options_);
    180   TrustRegionStrategy::PerSolveOptions pso;
    181 
    182   TrustRegionStrategy::Summary summary = strategy.ComputeStep(pso,
    183                                                               jacobian_.get(),
    184                                                               residual_.data(),
    185                                                               x_.data());
    186 
    187   EXPECT_NE(summary.termination_type, FAILURE);
    188   EXPECT_NEAR(x_(0), 1.0, kToleranceLoose);
    189   EXPECT_NEAR(x_(1), 1.0, kToleranceLoose);
    190   EXPECT_NEAR(x_(2), 1.0, kToleranceLoose);
    191   EXPECT_NEAR(x_(3), 1.0, kToleranceLoose);
    192   EXPECT_NEAR(x_(4), 1.0, kToleranceLoose);
    193   EXPECT_NEAR(x_(5), 1.0, kToleranceLoose);
    194 }
    195 
    196 // Test if the subspace basis is a valid orthonormal basis of the space spanned
    197 // by the gradient and the Gauss-Newton point.
    198 TEST_F(DoglegStrategyFixtureEllipse, ValidSubspaceBasis) {
    199   scoped_ptr<LinearSolver> linear_solver(
    200       new DenseQRSolver(LinearSolver::Options()));
    201   options_.linear_solver = linear_solver.get();
    202   options_.dogleg_type = SUBSPACE_DOGLEG;
    203   options_.initial_radius = 2.0;
    204   options_.max_radius = 2.0;
    205 
    206   DoglegStrategy strategy(options_);
    207   TrustRegionStrategy::PerSolveOptions pso;
    208 
    209   TrustRegionStrategy::Summary summary = strategy.ComputeStep(pso,
    210                                                               jacobian_.get(),
    211                                                               residual_.data(),
    212                                                               x_.data());
    213 
    214   // Check if the basis is orthonormal.
    215   const Matrix basis = strategy.subspace_basis();
    216   EXPECT_NEAR(basis.col(0).norm(), 1.0, kTolerance);
    217   EXPECT_NEAR(basis.col(1).norm(), 1.0, kTolerance);
    218   EXPECT_NEAR(basis.col(0).dot(basis.col(1)), 0.0, kTolerance);
    219 
    220   // Check if the gradient projects onto itself.
    221   const Vector gradient = strategy.gradient();
    222   EXPECT_NEAR((gradient - basis*(basis.transpose()*gradient)).norm(),
    223               0.0,
    224               kTolerance);
    225 
    226   // Check if the Gauss-Newton point projects onto itself.
    227   const Vector gn = strategy.gauss_newton_step();
    228   EXPECT_NEAR((gn - basis*(basis.transpose()*gn)).norm(),
    229               0.0,
    230               kTolerance);
    231 }
    232 
    233 // Test if the step is correct if the gradient and the Gauss-Newton step point
    234 // in the same direction and the Gauss-Newton step is outside the trust region,
    235 // i.e. the trust region is active.
    236 TEST_F(DoglegStrategyFixtureValley, CorrectStepLocalOptimumAlongGradient) {
    237   scoped_ptr<LinearSolver> linear_solver(
    238       new DenseQRSolver(LinearSolver::Options()));
    239   options_.linear_solver = linear_solver.get();
    240   options_.dogleg_type = SUBSPACE_DOGLEG;
    241   options_.initial_radius = 0.25;
    242   options_.max_radius = 0.25;
    243 
    244   DoglegStrategy strategy(options_);
    245   TrustRegionStrategy::PerSolveOptions pso;
    246 
    247   TrustRegionStrategy::Summary summary = strategy.ComputeStep(pso,
    248                                                               jacobian_.get(),
    249                                                               residual_.data(),
    250                                                               x_.data());
    251 
    252   EXPECT_NE(summary.termination_type, FAILURE);
    253   EXPECT_NEAR(x_(0), 0.0, kToleranceLoose);
    254   EXPECT_NEAR(x_(1), 0.0, kToleranceLoose);
    255   EXPECT_NEAR(x_(2), options_.initial_radius, kToleranceLoose);
    256   EXPECT_NEAR(x_(3), 0.0, kToleranceLoose);
    257   EXPECT_NEAR(x_(4), 0.0, kToleranceLoose);
    258   EXPECT_NEAR(x_(5), 0.0, kToleranceLoose);
    259 }
    260 
    261 // Test if the step is correct if the gradient and the Gauss-Newton step point
    262 // in the same direction and the Gauss-Newton step is inside the trust region,
    263 // i.e. the trust region is inactive.
    264 TEST_F(DoglegStrategyFixtureValley, CorrectStepGlobalOptimumAlongGradient) {
    265   scoped_ptr<LinearSolver> linear_solver(
    266       new DenseQRSolver(LinearSolver::Options()));
    267   options_.linear_solver = linear_solver.get();
    268   options_.dogleg_type = SUBSPACE_DOGLEG;
    269   options_.initial_radius = 2.0;
    270   options_.max_radius = 2.0;
    271 
    272   DoglegStrategy strategy(options_);
    273   TrustRegionStrategy::PerSolveOptions pso;
    274 
    275   TrustRegionStrategy::Summary summary = strategy.ComputeStep(pso,
    276                                                               jacobian_.get(),
    277                                                               residual_.data(),
    278                                                               x_.data());
    279 
    280   EXPECT_NE(summary.termination_type, FAILURE);
    281   EXPECT_NEAR(x_(0), 0.0, kToleranceLoose);
    282   EXPECT_NEAR(x_(1), 0.0, kToleranceLoose);
    283   EXPECT_NEAR(x_(2), 1.0, kToleranceLoose);
    284   EXPECT_NEAR(x_(3), 0.0, kToleranceLoose);
    285   EXPECT_NEAR(x_(4), 0.0, kToleranceLoose);
    286   EXPECT_NEAR(x_(5), 0.0, kToleranceLoose);
    287 }
    288 
    289 }  // namespace internal
    290 }  // namespace ceres
    291 
    292