iterative_refiner_test.cc 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. // Ceres Solver - A fast non-linear least squares minimizer
  2. // Copyright 2023 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: sameeragarwal@google.com (Sameer Agarwal)
  30. #include "ceres/iterative_refiner.h"
  31. #include <utility>
  32. #include "Eigen/Dense"
  33. #include "ceres/dense_cholesky.h"
  34. #include "ceres/internal/eigen.h"
  35. #include "ceres/sparse_cholesky.h"
  36. #include "ceres/sparse_matrix.h"
  37. #include "glog/logging.h"
  38. #include "gtest/gtest.h"
  39. namespace ceres::internal {
  40. // Macros to help us define virtual methods which we do not expect to
  41. // use/call in this test.
  42. #define DO_NOT_CALL \
  43. { LOG(FATAL) << "DO NOT CALL"; }
  44. #define DO_NOT_CALL_WITH_RETURN(x) \
  45. { \
  46. LOG(FATAL) << "DO NOT CALL"; \
  47. return x; \
  48. }
  49. // A fake SparseMatrix, which uses an Eigen matrix to do the real work.
  50. class FakeSparseMatrix : public SparseMatrix {
  51. public:
  52. explicit FakeSparseMatrix(Matrix m) : m_(std::move(m)) {}
  53. // y += Ax
  54. void RightMultiplyAndAccumulate(const double* x, double* y) const final {
  55. VectorRef(y, m_.cols()) += m_ * ConstVectorRef(x, m_.cols());
  56. }
  57. // y += A'x
  58. void LeftMultiplyAndAccumulate(const double* x, double* y) const final {
  59. // We will assume that this is a symmetric matrix.
  60. RightMultiplyAndAccumulate(x, y);
  61. }
  62. double* mutable_values() final { return m_.data(); }
  63. const double* values() const final { return m_.data(); }
  64. int num_rows() const final { return m_.cols(); }
  65. int num_cols() const final { return m_.cols(); }
  66. int num_nonzeros() const final { return m_.cols() * m_.cols(); }
  67. // The following methods are not needed for tests in this file.
  68. void SquaredColumnNorm(double* x) const final DO_NOT_CALL;
  69. void ScaleColumns(const double* scale) final DO_NOT_CALL;
  70. void SetZero() final DO_NOT_CALL;
  71. void ToDenseMatrix(Matrix* dense_matrix) const final DO_NOT_CALL;
  72. void ToTextFile(FILE* file) const final DO_NOT_CALL;
  73. private:
  74. Matrix m_;
  75. };
  76. // A fake SparseCholesky which uses Eigen's Cholesky factorization to
  77. // do the real work. The template parameter allows us to work in
  78. // doubles or floats, even though the source matrix is double.
  79. template <typename Scalar>
  80. class FakeSparseCholesky : public SparseCholesky {
  81. public:
  82. explicit FakeSparseCholesky(const Matrix& lhs) { lhs_ = lhs.cast<Scalar>(); }
  83. LinearSolverTerminationType Solve(const double* rhs_ptr,
  84. double* solution_ptr,
  85. std::string* message) final {
  86. const int num_cols = lhs_.cols();
  87. VectorRef solution(solution_ptr, num_cols);
  88. ConstVectorRef rhs(rhs_ptr, num_cols);
  89. auto llt = lhs_.llt();
  90. CHECK_EQ(llt.info(), Eigen::Success);
  91. solution = llt.solve(rhs.cast<Scalar>()).template cast<double>();
  92. return LinearSolverTerminationType::SUCCESS;
  93. }
  94. // The following methods are not needed for tests in this file.
  95. CompressedRowSparseMatrix::StorageType StorageType() const final
  96. DO_NOT_CALL_WITH_RETURN(
  97. CompressedRowSparseMatrix::StorageType::UPPER_TRIANGULAR);
  98. LinearSolverTerminationType Factorize(CompressedRowSparseMatrix* lhs,
  99. std::string* message) final
  100. DO_NOT_CALL_WITH_RETURN(LinearSolverTerminationType::FAILURE);
  101. private:
  102. Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> lhs_;
  103. };
  104. // A fake DenseCholesky which uses Eigen's Cholesky factorization to
  105. // do the real work. The template parameter allows us to work in
  106. // doubles or floats, even though the source matrix is double.
  107. template <typename Scalar>
  108. class FakeDenseCholesky : public DenseCholesky {
  109. public:
  110. explicit FakeDenseCholesky(const Matrix& lhs) { lhs_ = lhs.cast<Scalar>(); }
  111. LinearSolverTerminationType Solve(const double* rhs_ptr,
  112. double* solution_ptr,
  113. std::string* message) final {
  114. const int num_cols = lhs_.cols();
  115. VectorRef solution(solution_ptr, num_cols);
  116. ConstVectorRef rhs(rhs_ptr, num_cols);
  117. solution = lhs_.llt().solve(rhs.cast<Scalar>()).template cast<double>();
  118. return LinearSolverTerminationType::SUCCESS;
  119. }
  120. LinearSolverTerminationType Factorize(int num_cols,
  121. double* lhs,
  122. std::string* message) final
  123. DO_NOT_CALL_WITH_RETURN(LinearSolverTerminationType::FAILURE);
  124. private:
  125. Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> lhs_;
  126. };
  127. #undef DO_NOT_CALL
  128. #undef DO_NOT_CALL_WITH_RETURN
  129. class SparseIterativeRefinerTest : public ::testing::Test {
  130. public:
  131. void SetUp() override {
  132. num_cols_ = 5;
  133. max_num_iterations_ = 30;
  134. Matrix m(num_cols_, num_cols_);
  135. m.setRandom();
  136. lhs_ = m * m.transpose();
  137. solution_.resize(num_cols_);
  138. solution_.setRandom();
  139. rhs_ = lhs_ * solution_;
  140. };
  141. protected:
  142. int num_cols_;
  143. int max_num_iterations_;
  144. Matrix lhs_;
  145. Vector rhs_, solution_;
  146. };
  147. TEST_F(SparseIterativeRefinerTest,
  148. RandomSolutionWithExactFactorizationConverges) {
  149. FakeSparseMatrix lhs(lhs_);
  150. FakeSparseCholesky<double> sparse_cholesky(lhs_);
  151. SparseIterativeRefiner refiner(max_num_iterations_);
  152. Vector refined_solution(num_cols_);
  153. refined_solution.setRandom();
  154. refiner.Refine(lhs, rhs_.data(), &sparse_cholesky, refined_solution.data());
  155. EXPECT_NEAR((lhs_ * refined_solution - rhs_).norm(),
  156. 0.0,
  157. std::numeric_limits<double>::epsilon() * 10);
  158. }
  159. TEST_F(SparseIterativeRefinerTest,
  160. RandomSolutionWithApproximationFactorizationConverges) {
  161. FakeSparseMatrix lhs(lhs_);
  162. // Use a single precision Cholesky factorization of the double
  163. // precision matrix. This will give us an approximate factorization.
  164. FakeSparseCholesky<float> sparse_cholesky(lhs_);
  165. SparseIterativeRefiner refiner(max_num_iterations_);
  166. Vector refined_solution(num_cols_);
  167. refined_solution.setRandom();
  168. refiner.Refine(lhs, rhs_.data(), &sparse_cholesky, refined_solution.data());
  169. EXPECT_NEAR((lhs_ * refined_solution - rhs_).norm(),
  170. 0.0,
  171. std::numeric_limits<double>::epsilon() * 10);
  172. }
  173. class DenseIterativeRefinerTest : public ::testing::Test {
  174. public:
  175. void SetUp() override {
  176. num_cols_ = 5;
  177. max_num_iterations_ = 30;
  178. Matrix m(num_cols_, num_cols_);
  179. m.setRandom();
  180. lhs_ = m * m.transpose();
  181. solution_.resize(num_cols_);
  182. solution_.setRandom();
  183. rhs_ = lhs_ * solution_;
  184. };
  185. protected:
  186. int num_cols_;
  187. int max_num_iterations_;
  188. Matrix lhs_;
  189. Vector rhs_, solution_;
  190. };
  191. TEST_F(DenseIterativeRefinerTest,
  192. RandomSolutionWithExactFactorizationConverges) {
  193. Matrix lhs = lhs_;
  194. FakeDenseCholesky<double> dense_cholesky(lhs);
  195. DenseIterativeRefiner refiner(max_num_iterations_);
  196. Vector refined_solution(num_cols_);
  197. refined_solution.setRandom();
  198. refiner.Refine(lhs.cols(),
  199. lhs.data(),
  200. rhs_.data(),
  201. &dense_cholesky,
  202. refined_solution.data());
  203. EXPECT_NEAR((lhs_ * refined_solution - rhs_).norm(),
  204. 0.0,
  205. std::numeric_limits<double>::epsilon() * 10);
  206. }
  207. TEST_F(DenseIterativeRefinerTest,
  208. RandomSolutionWithApproximationFactorizationConverges) {
  209. Matrix lhs = lhs_;
  210. // Use a single precision Cholesky factorization of the double
  211. // precision matrix. This will give us an approximate factorization.
  212. FakeDenseCholesky<float> dense_cholesky(lhs_);
  213. DenseIterativeRefiner refiner(max_num_iterations_);
  214. Vector refined_solution(num_cols_);
  215. refined_solution.setRandom();
  216. refiner.Refine(lhs.cols(),
  217. lhs.data(),
  218. rhs_.data(),
  219. &dense_cholesky,
  220. refined_solution.data());
  221. EXPECT_NEAR((lhs_ * refined_solution - rhs_).norm(),
  222. 0.0,
  223. std::numeric_limits<double>::epsilon() * 10);
  224. }
  225. } // namespace ceres::internal