partitioned_matrix_view_test.cc 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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/partitioned_matrix_view.h"
  31. #include <memory>
  32. #include <random>
  33. #include <sstream>
  34. #include <string>
  35. #include <vector>
  36. #include "ceres/block_structure.h"
  37. #include "ceres/casts.h"
  38. #include "ceres/internal/eigen.h"
  39. #include "ceres/linear_least_squares_problems.h"
  40. #include "ceres/sparse_matrix.h"
  41. #include "glog/logging.h"
  42. #include "gtest/gtest.h"
  43. namespace ceres {
  44. namespace internal {
  45. const double kEpsilon = 1e-14;
  46. // Param = <problem_id, num_threads>
  47. using Param = ::testing::tuple<int, int>;
  48. static std::string ParamInfoToString(testing::TestParamInfo<Param> info) {
  49. Param param = info.param;
  50. std::stringstream ss;
  51. ss << ::testing::get<0>(param) << "_" << ::testing::get<1>(param);
  52. return ss.str();
  53. }
  54. class PartitionedMatrixViewTest : public ::testing::TestWithParam<Param> {
  55. protected:
  56. void SetUp() final {
  57. const int problem_id = ::testing::get<0>(GetParam());
  58. const int num_threads = ::testing::get<1>(GetParam());
  59. auto problem = CreateLinearLeastSquaresProblemFromId(problem_id);
  60. CHECK(problem != nullptr);
  61. A_ = std::move(problem->A);
  62. auto block_sparse = down_cast<BlockSparseMatrix*>(A_.get());
  63. options_.num_threads = num_threads;
  64. options_.context = &context_;
  65. options_.elimination_groups.push_back(problem->num_eliminate_blocks);
  66. pmv_ = PartitionedMatrixViewBase::Create(options_, *block_sparse);
  67. LinearSolver::Options options_single_threaded = options_;
  68. options_single_threaded.num_threads = 1;
  69. pmv_single_threaded_ =
  70. PartitionedMatrixViewBase::Create(options_, *block_sparse);
  71. EXPECT_EQ(pmv_->num_col_blocks_e(), problem->num_eliminate_blocks);
  72. EXPECT_EQ(pmv_->num_col_blocks_f(),
  73. block_sparse->block_structure()->cols.size() -
  74. problem->num_eliminate_blocks);
  75. EXPECT_EQ(pmv_->num_cols(), A_->num_cols());
  76. EXPECT_EQ(pmv_->num_rows(), A_->num_rows());
  77. }
  78. double RandDouble() { return distribution_(prng_); }
  79. LinearSolver::Options options_;
  80. ContextImpl context_;
  81. std::unique_ptr<LinearLeastSquaresProblem> problem_;
  82. std::unique_ptr<SparseMatrix> A_;
  83. std::unique_ptr<PartitionedMatrixViewBase> pmv_;
  84. std::unique_ptr<PartitionedMatrixViewBase> pmv_single_threaded_;
  85. std::mt19937 prng_;
  86. std::uniform_real_distribution<double> distribution_ =
  87. std::uniform_real_distribution<double>(0.0, 1.0);
  88. };
  89. TEST_P(PartitionedMatrixViewTest, RightMultiplyAndAccumulateE) {
  90. Vector x1(pmv_->num_cols_e());
  91. Vector x2(pmv_->num_cols());
  92. x2.setZero();
  93. for (int i = 0; i < pmv_->num_cols_e(); ++i) {
  94. x1(i) = x2(i) = RandDouble();
  95. }
  96. Vector expected = Vector::Zero(pmv_->num_rows());
  97. A_->RightMultiplyAndAccumulate(x2.data(), expected.data());
  98. Vector actual = Vector::Zero(pmv_->num_rows());
  99. pmv_->RightMultiplyAndAccumulateE(x1.data(), actual.data());
  100. for (int i = 0; i < pmv_->num_rows(); ++i) {
  101. EXPECT_NEAR(actual(i), expected(i), kEpsilon);
  102. }
  103. }
  104. TEST_P(PartitionedMatrixViewTest, RightMultiplyAndAccumulateF) {
  105. Vector x1(pmv_->num_cols_f());
  106. Vector x2(pmv_->num_cols());
  107. x2.setZero();
  108. for (int i = 0; i < pmv_->num_cols_f(); ++i) {
  109. x1(i) = x2(i + pmv_->num_cols_e()) = RandDouble();
  110. }
  111. Vector actual = Vector::Zero(pmv_->num_rows());
  112. pmv_->RightMultiplyAndAccumulateF(x1.data(), actual.data());
  113. Vector expected = Vector::Zero(pmv_->num_rows());
  114. A_->RightMultiplyAndAccumulate(x2.data(), expected.data());
  115. for (int i = 0; i < pmv_->num_rows(); ++i) {
  116. EXPECT_NEAR(actual(i), expected(i), kEpsilon);
  117. }
  118. }
  119. TEST_P(PartitionedMatrixViewTest, LeftMultiplyAndAccumulate) {
  120. Vector x = Vector::Zero(pmv_->num_rows());
  121. for (int i = 0; i < pmv_->num_rows(); ++i) {
  122. x(i) = RandDouble();
  123. }
  124. Vector x_pre = x;
  125. Vector expected = Vector::Zero(pmv_->num_cols());
  126. Vector e_actual = Vector::Zero(pmv_->num_cols_e());
  127. Vector f_actual = Vector::Zero(pmv_->num_cols_f());
  128. A_->LeftMultiplyAndAccumulate(x.data(), expected.data());
  129. pmv_->LeftMultiplyAndAccumulateE(x.data(), e_actual.data());
  130. pmv_->LeftMultiplyAndAccumulateF(x.data(), f_actual.data());
  131. for (int i = 0; i < pmv_->num_cols(); ++i) {
  132. EXPECT_NEAR(expected(i),
  133. (i < pmv_->num_cols_e()) ? e_actual(i)
  134. : f_actual(i - pmv_->num_cols_e()),
  135. kEpsilon);
  136. }
  137. }
  138. TEST_P(PartitionedMatrixViewTest, BlockDiagonalFtF) {
  139. std::unique_ptr<BlockSparseMatrix> block_diagonal_ff(
  140. pmv_->CreateBlockDiagonalFtF());
  141. const auto bs_diagonal = block_diagonal_ff->block_structure();
  142. const int num_rows = pmv_->num_rows();
  143. const int num_cols_f = pmv_->num_cols_f();
  144. const int num_cols_e = pmv_->num_cols_e();
  145. const int num_col_blocks_f = pmv_->num_col_blocks_f();
  146. const int num_col_blocks_e = pmv_->num_col_blocks_e();
  147. CHECK_EQ(block_diagonal_ff->num_rows(), num_cols_f);
  148. CHECK_EQ(block_diagonal_ff->num_cols(), num_cols_f);
  149. EXPECT_EQ(bs_diagonal->cols.size(), num_col_blocks_f);
  150. EXPECT_EQ(bs_diagonal->rows.size(), num_col_blocks_f);
  151. Matrix EF;
  152. A_->ToDenseMatrix(&EF);
  153. const auto F = EF.topRightCorner(num_rows, num_cols_f);
  154. Matrix expected_FtF = F.transpose() * F;
  155. Matrix actual_FtF;
  156. block_diagonal_ff->ToDenseMatrix(&actual_FtF);
  157. // FtF might be not block-diagonal
  158. auto bs = down_cast<BlockSparseMatrix*>(A_.get())->block_structure();
  159. for (int i = 0; i < num_col_blocks_f; ++i) {
  160. const auto col_block_f = bs->cols[num_col_blocks_e + i];
  161. const int block_size = col_block_f.size;
  162. const int block_pos = col_block_f.position - num_cols_e;
  163. const auto cell_expected =
  164. expected_FtF.block(block_pos, block_pos, block_size, block_size);
  165. auto cell_actual =
  166. actual_FtF.block(block_pos, block_pos, block_size, block_size);
  167. cell_actual -= cell_expected;
  168. EXPECT_NEAR(cell_actual.norm(), 0., kEpsilon);
  169. }
  170. // There should be nothing remaining outside block-diagonal
  171. EXPECT_NEAR(actual_FtF.norm(), 0., kEpsilon);
  172. }
  173. TEST_P(PartitionedMatrixViewTest, BlockDiagonalEtE) {
  174. std::unique_ptr<BlockSparseMatrix> block_diagonal_ee(
  175. pmv_->CreateBlockDiagonalEtE());
  176. const CompressedRowBlockStructure* bs = block_diagonal_ee->block_structure();
  177. const int num_rows = pmv_->num_rows();
  178. const int num_cols_e = pmv_->num_cols_e();
  179. const int num_col_blocks_e = pmv_->num_col_blocks_e();
  180. CHECK_EQ(block_diagonal_ee->num_rows(), num_cols_e);
  181. CHECK_EQ(block_diagonal_ee->num_cols(), num_cols_e);
  182. EXPECT_EQ(bs->cols.size(), num_col_blocks_e);
  183. EXPECT_EQ(bs->rows.size(), num_col_blocks_e);
  184. Matrix EF;
  185. A_->ToDenseMatrix(&EF);
  186. const auto E = EF.topLeftCorner(num_rows, num_cols_e);
  187. Matrix expected_EtE = E.transpose() * E;
  188. Matrix actual_EtE;
  189. block_diagonal_ee->ToDenseMatrix(&actual_EtE);
  190. EXPECT_NEAR((expected_EtE - actual_EtE).norm(), 0., kEpsilon);
  191. }
  192. TEST_P(PartitionedMatrixViewTest, UpdateBlockDiagonalEtE) {
  193. std::unique_ptr<BlockSparseMatrix> block_diagonal_ete(
  194. pmv_->CreateBlockDiagonalEtE());
  195. const int num_cols = pmv_->num_cols_e();
  196. Matrix multi_threaded(num_cols, num_cols);
  197. pmv_->UpdateBlockDiagonalEtE(block_diagonal_ete.get());
  198. block_diagonal_ete->ToDenseMatrix(&multi_threaded);
  199. Matrix single_threaded(num_cols, num_cols);
  200. pmv_single_threaded_->UpdateBlockDiagonalEtE(block_diagonal_ete.get());
  201. block_diagonal_ete->ToDenseMatrix(&single_threaded);
  202. EXPECT_NEAR((multi_threaded - single_threaded).norm(), 0., kEpsilon);
  203. }
  204. TEST_P(PartitionedMatrixViewTest, UpdateBlockDiagonalFtF) {
  205. std::unique_ptr<BlockSparseMatrix> block_diagonal_ftf(
  206. pmv_->CreateBlockDiagonalFtF());
  207. const int num_cols = pmv_->num_cols_f();
  208. Matrix multi_threaded(num_cols, num_cols);
  209. pmv_->UpdateBlockDiagonalFtF(block_diagonal_ftf.get());
  210. block_diagonal_ftf->ToDenseMatrix(&multi_threaded);
  211. Matrix single_threaded(num_cols, num_cols);
  212. pmv_single_threaded_->UpdateBlockDiagonalFtF(block_diagonal_ftf.get());
  213. block_diagonal_ftf->ToDenseMatrix(&single_threaded);
  214. EXPECT_NEAR((multi_threaded - single_threaded).norm(), 0., kEpsilon);
  215. }
  216. INSTANTIATE_TEST_SUITE_P(
  217. ParallelProducts,
  218. PartitionedMatrixViewTest,
  219. ::testing::Combine(::testing::Values(2, 4, 6),
  220. ::testing::Values(1, 2, 3, 4, 5, 6, 7, 8)),
  221. ParamInfoToString);
  222. } // namespace internal
  223. } // namespace ceres