inner_product_computer.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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/inner_product_computer.h"
  31. #include <algorithm>
  32. #include <memory>
  33. #include "ceres/small_blas.h"
  34. namespace ceres::internal {
  35. // Create the CompressedRowSparseMatrix matrix that will contain the
  36. // inner product.
  37. //
  38. // storage_type controls whether the result matrix contains the upper
  39. // or the lower triangular part of the product.
  40. //
  41. // num_nonzeros is the number of non-zeros in the result matrix.
  42. std::unique_ptr<CompressedRowSparseMatrix>
  43. InnerProductComputer::CreateResultMatrix(
  44. const CompressedRowSparseMatrix::StorageType storage_type,
  45. const int num_nonzeros) {
  46. auto matrix = std::make_unique<CompressedRowSparseMatrix>(
  47. m_.num_cols(), m_.num_cols(), num_nonzeros);
  48. matrix->set_storage_type(storage_type);
  49. const CompressedRowBlockStructure* bs = m_.block_structure();
  50. *matrix->mutable_row_blocks() = bs->cols;
  51. *matrix->mutable_col_blocks() = bs->cols;
  52. return matrix;
  53. }
  54. // Given the set of product terms in the inner product, return the
  55. // total number of non-zeros in the result and for each row block of
  56. // the result matrix, compute the number of non-zeros in any one row
  57. // of the row block.
  58. int InnerProductComputer::ComputeNonzeros(
  59. const std::vector<InnerProductComputer::ProductTerm>& product_terms,
  60. std::vector<int>* row_nnz) {
  61. const CompressedRowBlockStructure* bs = m_.block_structure();
  62. const std::vector<Block>& blocks = bs->cols;
  63. row_nnz->resize(blocks.size());
  64. std::fill(row_nnz->begin(), row_nnz->end(), 0);
  65. if (product_terms.empty()) {
  66. return 0;
  67. }
  68. // First product term.
  69. (*row_nnz)[product_terms[0].row] = blocks[product_terms[0].col].size;
  70. int num_nonzeros =
  71. blocks[product_terms[0].row].size * blocks[product_terms[0].col].size;
  72. // Remaining product terms.
  73. for (int i = 1; i < product_terms.size(); ++i) {
  74. const ProductTerm& previous = product_terms[i - 1];
  75. const ProductTerm& current = product_terms[i];
  76. // Each (row, col) block counts only once.
  77. // This check depends on product sorted on (row, col).
  78. if (current.row != previous.row || current.col != previous.col) {
  79. (*row_nnz)[current.row] += blocks[current.col].size;
  80. num_nonzeros += blocks[current.row].size * blocks[current.col].size;
  81. }
  82. }
  83. return num_nonzeros;
  84. }
  85. InnerProductComputer::InnerProductComputer(const BlockSparseMatrix& m,
  86. const int start_row_block,
  87. const int end_row_block)
  88. : m_(m), start_row_block_(start_row_block), end_row_block_(end_row_block) {}
  89. // Compute the sparsity structure of the product m.transpose() * m
  90. // and create a CompressedRowSparseMatrix corresponding to it.
  91. //
  92. // Also compute the "program" vector, which for every term in the
  93. // block outer product provides the information for the entry in the
  94. // values array of the result matrix where it should be accumulated.
  95. //
  96. // Since the entries of the program are the same for rows with the
  97. // same sparsity structure, the program only stores the result for one
  98. // row per row block. The Compute function reuses this information for
  99. // each row in the row block.
  100. //
  101. // product_storage_type controls the form of the output matrix. It
  102. // can be LOWER_TRIANGULAR or UPPER_TRIANGULAR.
  103. std::unique_ptr<InnerProductComputer> InnerProductComputer::Create(
  104. const BlockSparseMatrix& m,
  105. CompressedRowSparseMatrix::StorageType product_storage_type) {
  106. return InnerProductComputer::Create(
  107. m, 0, m.block_structure()->rows.size(), product_storage_type);
  108. }
  109. std::unique_ptr<InnerProductComputer> InnerProductComputer::Create(
  110. const BlockSparseMatrix& m,
  111. const int start_row_block,
  112. const int end_row_block,
  113. CompressedRowSparseMatrix::StorageType product_storage_type) {
  114. CHECK(product_storage_type ==
  115. CompressedRowSparseMatrix::StorageType::LOWER_TRIANGULAR ||
  116. product_storage_type ==
  117. CompressedRowSparseMatrix::StorageType::UPPER_TRIANGULAR);
  118. CHECK_GT(m.num_nonzeros(), 0)
  119. << "Congratulations, you found a bug in Ceres. Please report it.";
  120. std::unique_ptr<InnerProductComputer> inner_product_computer(
  121. new InnerProductComputer(m, start_row_block, end_row_block));
  122. inner_product_computer->Init(product_storage_type);
  123. return inner_product_computer;
  124. }
  125. void InnerProductComputer::Init(
  126. const CompressedRowSparseMatrix::StorageType product_storage_type) {
  127. std::vector<InnerProductComputer::ProductTerm> product_terms;
  128. const CompressedRowBlockStructure* bs = m_.block_structure();
  129. // Give input matrix m in Block Sparse format
  130. // (row_block, col_block)
  131. // represent each block multiplication
  132. // (row_block, col_block1)' X (row_block, col_block2)
  133. // by its product term:
  134. // (col_block1, col_block2, index)
  135. for (int row_block = start_row_block_; row_block < end_row_block_;
  136. ++row_block) {
  137. const CompressedRow& row = bs->rows[row_block];
  138. for (int c1 = 0; c1 < row.cells.size(); ++c1) {
  139. const Cell& cell1 = row.cells[c1];
  140. int c2_begin, c2_end;
  141. if (product_storage_type ==
  142. CompressedRowSparseMatrix::StorageType::LOWER_TRIANGULAR) {
  143. c2_begin = 0;
  144. c2_end = c1 + 1;
  145. } else {
  146. c2_begin = c1;
  147. c2_end = row.cells.size();
  148. }
  149. for (int c2 = c2_begin; c2 < c2_end; ++c2) {
  150. const Cell& cell2 = row.cells[c2];
  151. product_terms.emplace_back(
  152. cell1.block_id, cell2.block_id, product_terms.size());
  153. }
  154. }
  155. }
  156. std::sort(product_terms.begin(), product_terms.end());
  157. ComputeOffsetsAndCreateResultMatrix(product_storage_type, product_terms);
  158. }
  159. void InnerProductComputer::ComputeOffsetsAndCreateResultMatrix(
  160. const CompressedRowSparseMatrix::StorageType product_storage_type,
  161. const std::vector<InnerProductComputer::ProductTerm>& product_terms) {
  162. const std::vector<Block>& col_blocks = m_.block_structure()->cols;
  163. std::vector<int> row_block_nnz;
  164. const int num_nonzeros = ComputeNonzeros(product_terms, &row_block_nnz);
  165. result_ = CreateResultMatrix(product_storage_type, num_nonzeros);
  166. // Populate the row non-zero counts in the result matrix.
  167. int* crsm_rows = result_->mutable_rows();
  168. crsm_rows[0] = 0;
  169. for (int i = 0; i < col_blocks.size(); ++i) {
  170. for (int j = 0; j < col_blocks[i].size; ++j, ++crsm_rows) {
  171. *(crsm_rows + 1) = *crsm_rows + row_block_nnz[i];
  172. }
  173. }
  174. result_offsets_.resize(product_terms.size());
  175. if (num_nonzeros == 0) {
  176. return;
  177. }
  178. // The following macro FILL_CRSM_COL_BLOCK is key to understanding
  179. // how this class works.
  180. //
  181. // It does two things.
  182. //
  183. // Sets the value for the current term in the result_offsets_ array
  184. // and populates the cols array of the result matrix.
  185. //
  186. // row_block and col_block as the names imply, refer to the row and
  187. // column blocks of the current term.
  188. //
  189. // row_nnz is the number of nonzeros in the result_matrix at the
  190. // beginning of the first row of row_block.
  191. //
  192. // col_nnz is the number of nonzeros in the first row of the row
  193. // block that occur before the current column block, i.e. this is
  194. // sum of the sizes of all the column blocks in this row block that
  195. // came before this column block.
  196. //
  197. // Given these two numbers and the total number of nonzeros in this
  198. // row (nnz_in_row), we can now populate the cols array as follows:
  199. //
  200. // nnz + j * nnz_in_row is the beginning of the j^th row.
  201. //
  202. // nnz + j * nnz_in_row + col_nnz is the beginning of the column
  203. // block in the j^th row.
  204. //
  205. // nnz + j * nnz_in_row + col_nnz + k is then the j^th row and the
  206. // k^th column of the product block, whose value is
  207. //
  208. // col_blocks[col_block].position + k, which is the column number of
  209. // the k^th column of the current column block.
  210. #define FILL_CRSM_COL_BLOCK \
  211. const int row_block = current->row; \
  212. const int col_block = current->col; \
  213. const int nnz_in_row = row_block_nnz[row_block]; \
  214. int* crsm_cols = result_->mutable_cols(); \
  215. result_offsets_[current->index] = nnz + col_nnz; \
  216. for (int j = 0; j < col_blocks[row_block].size; ++j) { \
  217. for (int k = 0; k < col_blocks[col_block].size; ++k) { \
  218. crsm_cols[nnz + j * nnz_in_row + col_nnz + k] = \
  219. col_blocks[col_block].position + k; \
  220. } \
  221. }
  222. int col_nnz = 0;
  223. int nnz = 0;
  224. // Process the first term.
  225. const InnerProductComputer::ProductTerm* current = product_terms.data();
  226. FILL_CRSM_COL_BLOCK;
  227. // Process the rest of the terms.
  228. for (int i = 1; i < product_terms.size(); ++i) {
  229. current = &product_terms[i];
  230. const InnerProductComputer::ProductTerm* previous = &product_terms[i - 1];
  231. // If the current term is the same as the previous term, then it
  232. // stores its product at the same location as the previous term.
  233. if (previous->row == current->row && previous->col == current->col) {
  234. result_offsets_[current->index] = result_offsets_[previous->index];
  235. continue;
  236. }
  237. if (previous->row == current->row) {
  238. // if the current and previous terms are in the same row block,
  239. // then they differ in the column block, in which case advance
  240. // col_nnz by the column size of the previous term.
  241. col_nnz += col_blocks[previous->col].size;
  242. } else {
  243. // If we have moved to a new row-block , then col_nnz is zero,
  244. // and nnz is set to the beginning of the row block.
  245. col_nnz = 0;
  246. nnz += row_block_nnz[previous->row] * col_blocks[previous->row].size;
  247. }
  248. FILL_CRSM_COL_BLOCK;
  249. }
  250. }
  251. // Use the results_offsets_ array to numerically compute the product
  252. // m' * m and store it in result_.
  253. //
  254. // TODO(sameeragarwal): Multithreading support.
  255. void InnerProductComputer::Compute() {
  256. const double* m_values = m_.values();
  257. const CompressedRowBlockStructure* bs = m_.block_structure();
  258. const CompressedRowSparseMatrix::StorageType storage_type =
  259. result_->storage_type();
  260. result_->SetZero();
  261. double* values = result_->mutable_values();
  262. const int* rows = result_->rows();
  263. int cursor = 0;
  264. // Iterate row blocks.
  265. for (int r = start_row_block_; r < end_row_block_; ++r) {
  266. const CompressedRow& m_row = bs->rows[r];
  267. for (int c1 = 0; c1 < m_row.cells.size(); ++c1) {
  268. const Cell& cell1 = m_row.cells[c1];
  269. const int c1_size = bs->cols[cell1.block_id].size;
  270. const int row_nnz = rows[bs->cols[cell1.block_id].position + 1] -
  271. rows[bs->cols[cell1.block_id].position];
  272. int c2_begin, c2_end;
  273. if (storage_type ==
  274. CompressedRowSparseMatrix::StorageType::LOWER_TRIANGULAR) {
  275. c2_begin = 0;
  276. c2_end = c1 + 1;
  277. } else {
  278. c2_begin = c1;
  279. c2_end = m_row.cells.size();
  280. }
  281. for (int c2 = c2_begin; c2 < c2_end; ++c2, ++cursor) {
  282. const Cell& cell2 = m_row.cells[c2];
  283. const int c2_size = bs->cols[cell2.block_id].size;
  284. // clang-format off
  285. MatrixTransposeMatrixMultiply<Eigen::Dynamic, Eigen::Dynamic,
  286. Eigen::Dynamic, Eigen::Dynamic, 1>(
  287. m_values + cell1.position,
  288. m_row.block.size, c1_size,
  289. m_values + cell2.position,
  290. m_row.block.size, c2_size,
  291. values + result_offsets_[cursor],
  292. 0, 0, c1_size, row_nnz);
  293. // clang-format on
  294. }
  295. }
  296. }
  297. CHECK_EQ(cursor, result_offsets_.size());
  298. }
  299. } // namespace ceres::internal