sparse_cholesky_test.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. // Ceres Solver - A fast non-linear least squares minimizer
  2. // Copyright 2017 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/sparse_cholesky.h"
  31. #include <memory>
  32. #include <numeric>
  33. #include <random>
  34. #include <vector>
  35. #include "Eigen/Dense"
  36. #include "Eigen/SparseCore"
  37. #include "ceres/block_sparse_matrix.h"
  38. #include "ceres/compressed_row_sparse_matrix.h"
  39. #include "ceres/inner_product_computer.h"
  40. #include "ceres/internal/config.h"
  41. #include "ceres/internal/eigen.h"
  42. #include "ceres/iterative_refiner.h"
  43. #include "glog/logging.h"
  44. #include "gmock/gmock.h"
  45. #include "gtest/gtest.h"
  46. namespace ceres::internal {
  47. namespace {
  48. std::unique_ptr<BlockSparseMatrix> CreateRandomFullRankMatrix(
  49. const int num_col_blocks,
  50. const int min_col_block_size,
  51. const int max_col_block_size,
  52. const double block_density,
  53. std::mt19937& prng) {
  54. // Create a random matrix
  55. BlockSparseMatrix::RandomMatrixOptions options;
  56. options.num_col_blocks = num_col_blocks;
  57. options.min_col_block_size = min_col_block_size;
  58. options.max_col_block_size = max_col_block_size;
  59. options.num_row_blocks = 2 * num_col_blocks;
  60. options.min_row_block_size = 1;
  61. options.max_row_block_size = max_col_block_size;
  62. options.block_density = block_density;
  63. auto random_matrix = BlockSparseMatrix::CreateRandomMatrix(options, prng);
  64. // Add a diagonal block sparse matrix to make it full rank.
  65. Vector diagonal = Vector::Ones(random_matrix->num_cols());
  66. auto block_diagonal = BlockSparseMatrix::CreateDiagonalMatrix(
  67. diagonal.data(), random_matrix->block_structure()->cols);
  68. random_matrix->AppendRows(*block_diagonal);
  69. return random_matrix;
  70. }
  71. bool ComputeExpectedSolution(const CompressedRowSparseMatrix& lhs,
  72. const Vector& rhs,
  73. Vector* solution) {
  74. Matrix eigen_lhs;
  75. lhs.ToDenseMatrix(&eigen_lhs);
  76. if (lhs.storage_type() ==
  77. CompressedRowSparseMatrix::StorageType::UPPER_TRIANGULAR) {
  78. Matrix full_lhs = eigen_lhs.selfadjointView<Eigen::Upper>();
  79. Eigen::LLT<Matrix, Eigen::Upper> llt =
  80. eigen_lhs.selfadjointView<Eigen::Upper>().llt();
  81. if (llt.info() != Eigen::Success) {
  82. return false;
  83. }
  84. *solution = llt.solve(rhs);
  85. return (llt.info() == Eigen::Success);
  86. }
  87. Matrix full_lhs = eigen_lhs.selfadjointView<Eigen::Lower>();
  88. Eigen::LLT<Matrix, Eigen::Lower> llt =
  89. eigen_lhs.selfadjointView<Eigen::Lower>().llt();
  90. if (llt.info() != Eigen::Success) {
  91. return false;
  92. }
  93. *solution = llt.solve(rhs);
  94. return (llt.info() == Eigen::Success);
  95. }
  96. void SparseCholeskySolverUnitTest(
  97. const SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type,
  98. const OrderingType ordering_type,
  99. const bool use_block_structure,
  100. const int num_blocks,
  101. const int min_block_size,
  102. const int max_block_size,
  103. const double block_density,
  104. std::mt19937& prng) {
  105. LinearSolver::Options sparse_cholesky_options;
  106. sparse_cholesky_options.sparse_linear_algebra_library_type =
  107. sparse_linear_algebra_library_type;
  108. sparse_cholesky_options.ordering_type = ordering_type;
  109. auto sparse_cholesky = SparseCholesky::Create(sparse_cholesky_options);
  110. const CompressedRowSparseMatrix::StorageType storage_type =
  111. sparse_cholesky->StorageType();
  112. auto m = CreateRandomFullRankMatrix(
  113. num_blocks, min_block_size, max_block_size, block_density, prng);
  114. auto inner_product_computer = InnerProductComputer::Create(*m, storage_type);
  115. inner_product_computer->Compute();
  116. CompressedRowSparseMatrix* lhs = inner_product_computer->mutable_result();
  117. if (!use_block_structure) {
  118. lhs->mutable_row_blocks()->clear();
  119. lhs->mutable_col_blocks()->clear();
  120. }
  121. Vector rhs = Vector::Random(lhs->num_rows());
  122. Vector expected(lhs->num_rows());
  123. Vector actual(lhs->num_rows());
  124. EXPECT_TRUE(ComputeExpectedSolution(*lhs, rhs, &expected));
  125. std::string message;
  126. EXPECT_EQ(
  127. sparse_cholesky->FactorAndSolve(lhs, rhs.data(), actual.data(), &message),
  128. LinearSolverTerminationType::SUCCESS);
  129. Matrix eigen_lhs;
  130. lhs->ToDenseMatrix(&eigen_lhs);
  131. EXPECT_NEAR((actual - expected).norm() / actual.norm(),
  132. 0.0,
  133. std::numeric_limits<double>::epsilon() * 20)
  134. << "\n"
  135. << eigen_lhs;
  136. }
  137. using Param =
  138. ::testing::tuple<SparseLinearAlgebraLibraryType, OrderingType, bool>;
  139. std::string ParamInfoToString(testing::TestParamInfo<Param> info) {
  140. Param param = info.param;
  141. std::stringstream ss;
  142. ss << SparseLinearAlgebraLibraryTypeToString(::testing::get<0>(param)) << "_"
  143. << ::testing::get<1>(param) << "_"
  144. << (::testing::get<2>(param) ? "UseBlockStructure" : "NoBlockStructure");
  145. return ss.str();
  146. }
  147. } // namespace
  148. class SparseCholeskyTest : public ::testing::TestWithParam<Param> {};
  149. TEST_P(SparseCholeskyTest, FactorAndSolve) {
  150. constexpr int kMinNumBlocks = 1;
  151. constexpr int kMaxNumBlocks = 10;
  152. constexpr int kNumTrials = 10;
  153. constexpr int kMinBlockSize = 1;
  154. constexpr int kMaxBlockSize = 5;
  155. Param param = GetParam();
  156. std::mt19937 prng;
  157. std::uniform_real_distribution<double> distribution(0.1, 1.0);
  158. for (int num_blocks = kMinNumBlocks; num_blocks < kMaxNumBlocks;
  159. ++num_blocks) {
  160. for (int trial = 0; trial < kNumTrials; ++trial) {
  161. const double block_density = distribution(prng);
  162. SparseCholeskySolverUnitTest(::testing::get<0>(param),
  163. ::testing::get<1>(param),
  164. ::testing::get<2>(param),
  165. num_blocks,
  166. kMinBlockSize,
  167. kMaxBlockSize,
  168. block_density,
  169. prng);
  170. }
  171. }
  172. }
  173. namespace {
  174. #ifndef CERES_NO_SUITESPARSE
  175. INSTANTIATE_TEST_SUITE_P(
  176. SuiteSparseCholesky,
  177. SparseCholeskyTest,
  178. ::testing::Combine(::testing::Values(SUITE_SPARSE),
  179. ::testing::Values(OrderingType::AMD,
  180. OrderingType::NATURAL),
  181. ::testing::Values(true, false)),
  182. ParamInfoToString);
  183. #endif
  184. #if !defined(CERES_NO_SUITESPARSE) && !defined(CERES_NO_CHOLMOD_PARTITION)
  185. INSTANTIATE_TEST_SUITE_P(
  186. SuiteSparseCholeskyMETIS,
  187. SparseCholeskyTest,
  188. ::testing::Combine(::testing::Values(SUITE_SPARSE),
  189. ::testing::Values(OrderingType::NESDIS),
  190. ::testing::Values(true, false)),
  191. ParamInfoToString);
  192. #endif // !defined(CERES_NO_SUITESPARSE) &&
  193. // !defined(CERES_NO_CHOLMOD_PARTITION)
  194. #ifndef CERES_NO_ACCELERATE_SPARSE
  195. INSTANTIATE_TEST_SUITE_P(
  196. AccelerateSparseCholesky,
  197. SparseCholeskyTest,
  198. ::testing::Combine(::testing::Values(ACCELERATE_SPARSE),
  199. ::testing::Values(OrderingType::AMD,
  200. OrderingType::NESDIS,
  201. OrderingType::NATURAL),
  202. ::testing::Values(true, false)),
  203. ParamInfoToString);
  204. INSTANTIATE_TEST_SUITE_P(
  205. AccelerateSparseCholeskySingle,
  206. SparseCholeskyTest,
  207. ::testing::Combine(::testing::Values(ACCELERATE_SPARSE),
  208. ::testing::Values(OrderingType::AMD,
  209. OrderingType::NESDIS,
  210. OrderingType::NATURAL),
  211. ::testing::Values(true, false)),
  212. ParamInfoToString);
  213. #endif
  214. #ifdef CERES_USE_EIGEN_SPARSE
  215. INSTANTIATE_TEST_SUITE_P(
  216. EigenSparseCholesky,
  217. SparseCholeskyTest,
  218. ::testing::Combine(::testing::Values(EIGEN_SPARSE),
  219. ::testing::Values(OrderingType::AMD,
  220. OrderingType::NATURAL),
  221. ::testing::Values(true, false)),
  222. ParamInfoToString);
  223. INSTANTIATE_TEST_SUITE_P(
  224. EigenSparseCholeskySingle,
  225. SparseCholeskyTest,
  226. ::testing::Combine(::testing::Values(EIGEN_SPARSE),
  227. ::testing::Values(OrderingType::AMD,
  228. OrderingType::NATURAL),
  229. ::testing::Values(true, false)),
  230. ParamInfoToString);
  231. #endif // CERES_USE_EIGEN_SPARSE
  232. #if defined(CERES_USE_EIGEN_SPARSE) && !defined(CERES_NO_EIGEN_METIS)
  233. INSTANTIATE_TEST_SUITE_P(
  234. EigenSparseCholeskyMETIS,
  235. SparseCholeskyTest,
  236. ::testing::Combine(::testing::Values(EIGEN_SPARSE),
  237. ::testing::Values(OrderingType::NESDIS),
  238. ::testing::Values(true, false)),
  239. ParamInfoToString);
  240. INSTANTIATE_TEST_SUITE_P(
  241. EigenSparseCholeskySingleMETIS,
  242. SparseCholeskyTest,
  243. ::testing::Combine(::testing::Values(EIGEN_SPARSE),
  244. ::testing::Values(OrderingType::NESDIS),
  245. ::testing::Values(true, false)),
  246. ParamInfoToString);
  247. #endif // defined(CERES_USE_EIGEN_SPARSE) && !defined(CERES_NO_EIGEN_METIS)
  248. class MockSparseCholesky : public SparseCholesky {
  249. public:
  250. MOCK_CONST_METHOD0(StorageType, CompressedRowSparseMatrix::StorageType());
  251. MOCK_METHOD2(Factorize,
  252. LinearSolverTerminationType(CompressedRowSparseMatrix* lhs,
  253. std::string* message));
  254. MOCK_METHOD3(Solve,
  255. LinearSolverTerminationType(const double* rhs,
  256. double* solution,
  257. std::string* message));
  258. };
  259. class MockSparseIterativeRefiner : public SparseIterativeRefiner {
  260. public:
  261. MockSparseIterativeRefiner() : SparseIterativeRefiner(1) {}
  262. MOCK_METHOD4(Refine,
  263. void(const SparseMatrix& lhs,
  264. const double* rhs,
  265. SparseCholesky* sparse_cholesky,
  266. double* solution));
  267. };
  268. using testing::_;
  269. using testing::Return;
  270. TEST(RefinedSparseCholesky, StorageType) {
  271. auto sparse_cholesky = std::make_unique<MockSparseCholesky>();
  272. auto iterative_refiner = std::make_unique<MockSparseIterativeRefiner>();
  273. EXPECT_CALL(*sparse_cholesky, StorageType())
  274. .Times(1)
  275. .WillRepeatedly(
  276. Return(CompressedRowSparseMatrix::StorageType::UPPER_TRIANGULAR));
  277. EXPECT_CALL(*iterative_refiner, Refine(_, _, _, _)).Times(0);
  278. RefinedSparseCholesky refined_sparse_cholesky(std::move(sparse_cholesky),
  279. std::move(iterative_refiner));
  280. EXPECT_EQ(refined_sparse_cholesky.StorageType(),
  281. CompressedRowSparseMatrix::StorageType::UPPER_TRIANGULAR);
  282. };
  283. TEST(RefinedSparseCholesky, Factorize) {
  284. auto* mock_sparse_cholesky = new MockSparseCholesky;
  285. auto* mock_iterative_refiner = new MockSparseIterativeRefiner;
  286. EXPECT_CALL(*mock_sparse_cholesky, Factorize(_, _))
  287. .Times(1)
  288. .WillRepeatedly(Return(LinearSolverTerminationType::SUCCESS));
  289. EXPECT_CALL(*mock_iterative_refiner, Refine(_, _, _, _)).Times(0);
  290. std::unique_ptr<SparseCholesky> sparse_cholesky(mock_sparse_cholesky);
  291. std::unique_ptr<SparseIterativeRefiner> iterative_refiner(
  292. mock_iterative_refiner);
  293. RefinedSparseCholesky refined_sparse_cholesky(std::move(sparse_cholesky),
  294. std::move(iterative_refiner));
  295. CompressedRowSparseMatrix m(1, 1, 1);
  296. std::string message;
  297. EXPECT_EQ(refined_sparse_cholesky.Factorize(&m, &message),
  298. LinearSolverTerminationType::SUCCESS);
  299. };
  300. TEST(RefinedSparseCholesky, FactorAndSolveWithUnsuccessfulFactorization) {
  301. auto* mock_sparse_cholesky = new MockSparseCholesky;
  302. auto* mock_iterative_refiner = new MockSparseIterativeRefiner;
  303. EXPECT_CALL(*mock_sparse_cholesky, Factorize(_, _))
  304. .Times(1)
  305. .WillRepeatedly(Return(LinearSolverTerminationType::FAILURE));
  306. EXPECT_CALL(*mock_sparse_cholesky, Solve(_, _, _)).Times(0);
  307. EXPECT_CALL(*mock_iterative_refiner, Refine(_, _, _, _)).Times(0);
  308. std::unique_ptr<SparseCholesky> sparse_cholesky(mock_sparse_cholesky);
  309. std::unique_ptr<SparseIterativeRefiner> iterative_refiner(
  310. mock_iterative_refiner);
  311. RefinedSparseCholesky refined_sparse_cholesky(std::move(sparse_cholesky),
  312. std::move(iterative_refiner));
  313. CompressedRowSparseMatrix m(1, 1, 1);
  314. std::string message;
  315. double rhs;
  316. double solution;
  317. EXPECT_EQ(
  318. refined_sparse_cholesky.FactorAndSolve(&m, &rhs, &solution, &message),
  319. LinearSolverTerminationType::FAILURE);
  320. };
  321. TEST(RefinedSparseCholesky, FactorAndSolveWithSuccess) {
  322. auto* mock_sparse_cholesky = new MockSparseCholesky;
  323. std::unique_ptr<MockSparseIterativeRefiner> mock_iterative_refiner(
  324. new MockSparseIterativeRefiner);
  325. EXPECT_CALL(*mock_sparse_cholesky, Factorize(_, _))
  326. .Times(1)
  327. .WillRepeatedly(Return(LinearSolverTerminationType::SUCCESS));
  328. EXPECT_CALL(*mock_sparse_cholesky, Solve(_, _, _))
  329. .Times(1)
  330. .WillRepeatedly(Return(LinearSolverTerminationType::SUCCESS));
  331. EXPECT_CALL(*mock_iterative_refiner, Refine(_, _, _, _)).Times(1);
  332. std::unique_ptr<SparseCholesky> sparse_cholesky(mock_sparse_cholesky);
  333. std::unique_ptr<SparseIterativeRefiner> iterative_refiner(
  334. std::move(mock_iterative_refiner));
  335. RefinedSparseCholesky refined_sparse_cholesky(std::move(sparse_cholesky),
  336. std::move(iterative_refiner));
  337. CompressedRowSparseMatrix m(1, 1, 1);
  338. std::string message;
  339. double rhs;
  340. double solution;
  341. EXPECT_EQ(
  342. refined_sparse_cholesky.FactorAndSolve(&m, &rhs, &solution, &message),
  343. LinearSolverTerminationType::SUCCESS);
  344. };
  345. } // namespace
  346. } // namespace ceres::internal