dogleg_strategy_test.cc 11 KB

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