123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- // Ceres Solver - A fast non-linear least squares minimizer
- // Copyright 2023 Google Inc. All rights reserved.
- // http://ceres-solver.org/
- //
- // Redistribution and use in source and binary forms, with or without
- // modification, are permitted provided that the following conditions are met:
- //
- // * Redistributions of source code must retain the above copyright notice,
- // this list of conditions and the following disclaimer.
- // * Redistributions in binary form must reproduce the above copyright notice,
- // this list of conditions and the following disclaimer in the documentation
- // and/or other materials provided with the distribution.
- // * Neither the name of Google Inc. nor the names of its contributors may be
- // used to endorse or promote products derived from this software without
- // specific prior written permission.
- //
- // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- // POSSIBILITY OF SUCH DAMAGE.
- //
- // Author: sameeragarwal@google.com (Sameer Agarwal)
- #include "ceres/inner_product_computer.h"
- #include <algorithm>
- #include <memory>
- #include "ceres/small_blas.h"
- namespace ceres::internal {
- // Create the CompressedRowSparseMatrix matrix that will contain the
- // inner product.
- //
- // storage_type controls whether the result matrix contains the upper
- // or the lower triangular part of the product.
- //
- // num_nonzeros is the number of non-zeros in the result matrix.
- std::unique_ptr<CompressedRowSparseMatrix>
- InnerProductComputer::CreateResultMatrix(
- const CompressedRowSparseMatrix::StorageType storage_type,
- const int num_nonzeros) {
- auto matrix = std::make_unique<CompressedRowSparseMatrix>(
- m_.num_cols(), m_.num_cols(), num_nonzeros);
- matrix->set_storage_type(storage_type);
- const CompressedRowBlockStructure* bs = m_.block_structure();
- *matrix->mutable_row_blocks() = bs->cols;
- *matrix->mutable_col_blocks() = bs->cols;
- return matrix;
- }
- // Given the set of product terms in the inner product, return the
- // total number of non-zeros in the result and for each row block of
- // the result matrix, compute the number of non-zeros in any one row
- // of the row block.
- int InnerProductComputer::ComputeNonzeros(
- const std::vector<InnerProductComputer::ProductTerm>& product_terms,
- std::vector<int>* row_nnz) {
- const CompressedRowBlockStructure* bs = m_.block_structure();
- const std::vector<Block>& blocks = bs->cols;
- row_nnz->resize(blocks.size());
- std::fill(row_nnz->begin(), row_nnz->end(), 0);
- if (product_terms.empty()) {
- return 0;
- }
- // First product term.
- (*row_nnz)[product_terms[0].row] = blocks[product_terms[0].col].size;
- int num_nonzeros =
- blocks[product_terms[0].row].size * blocks[product_terms[0].col].size;
- // Remaining product terms.
- for (int i = 1; i < product_terms.size(); ++i) {
- const ProductTerm& previous = product_terms[i - 1];
- const ProductTerm& current = product_terms[i];
- // Each (row, col) block counts only once.
- // This check depends on product sorted on (row, col).
- if (current.row != previous.row || current.col != previous.col) {
- (*row_nnz)[current.row] += blocks[current.col].size;
- num_nonzeros += blocks[current.row].size * blocks[current.col].size;
- }
- }
- return num_nonzeros;
- }
- InnerProductComputer::InnerProductComputer(const BlockSparseMatrix& m,
- const int start_row_block,
- const int end_row_block)
- : m_(m), start_row_block_(start_row_block), end_row_block_(end_row_block) {}
- // Compute the sparsity structure of the product m.transpose() * m
- // and create a CompressedRowSparseMatrix corresponding to it.
- //
- // Also compute the "program" vector, which for every term in the
- // block outer product provides the information for the entry in the
- // values array of the result matrix where it should be accumulated.
- //
- // Since the entries of the program are the same for rows with the
- // same sparsity structure, the program only stores the result for one
- // row per row block. The Compute function reuses this information for
- // each row in the row block.
- //
- // product_storage_type controls the form of the output matrix. It
- // can be LOWER_TRIANGULAR or UPPER_TRIANGULAR.
- std::unique_ptr<InnerProductComputer> InnerProductComputer::Create(
- const BlockSparseMatrix& m,
- CompressedRowSparseMatrix::StorageType product_storage_type) {
- return InnerProductComputer::Create(
- m, 0, m.block_structure()->rows.size(), product_storage_type);
- }
- std::unique_ptr<InnerProductComputer> InnerProductComputer::Create(
- const BlockSparseMatrix& m,
- const int start_row_block,
- const int end_row_block,
- CompressedRowSparseMatrix::StorageType product_storage_type) {
- CHECK(product_storage_type ==
- CompressedRowSparseMatrix::StorageType::LOWER_TRIANGULAR ||
- product_storage_type ==
- CompressedRowSparseMatrix::StorageType::UPPER_TRIANGULAR);
- CHECK_GT(m.num_nonzeros(), 0)
- << "Congratulations, you found a bug in Ceres. Please report it.";
- std::unique_ptr<InnerProductComputer> inner_product_computer(
- new InnerProductComputer(m, start_row_block, end_row_block));
- inner_product_computer->Init(product_storage_type);
- return inner_product_computer;
- }
- void InnerProductComputer::Init(
- const CompressedRowSparseMatrix::StorageType product_storage_type) {
- std::vector<InnerProductComputer::ProductTerm> product_terms;
- const CompressedRowBlockStructure* bs = m_.block_structure();
- // Give input matrix m in Block Sparse format
- // (row_block, col_block)
- // represent each block multiplication
- // (row_block, col_block1)' X (row_block, col_block2)
- // by its product term:
- // (col_block1, col_block2, index)
- for (int row_block = start_row_block_; row_block < end_row_block_;
- ++row_block) {
- const CompressedRow& row = bs->rows[row_block];
- for (int c1 = 0; c1 < row.cells.size(); ++c1) {
- const Cell& cell1 = row.cells[c1];
- int c2_begin, c2_end;
- if (product_storage_type ==
- CompressedRowSparseMatrix::StorageType::LOWER_TRIANGULAR) {
- c2_begin = 0;
- c2_end = c1 + 1;
- } else {
- c2_begin = c1;
- c2_end = row.cells.size();
- }
- for (int c2 = c2_begin; c2 < c2_end; ++c2) {
- const Cell& cell2 = row.cells[c2];
- product_terms.emplace_back(
- cell1.block_id, cell2.block_id, product_terms.size());
- }
- }
- }
- std::sort(product_terms.begin(), product_terms.end());
- ComputeOffsetsAndCreateResultMatrix(product_storage_type, product_terms);
- }
- void InnerProductComputer::ComputeOffsetsAndCreateResultMatrix(
- const CompressedRowSparseMatrix::StorageType product_storage_type,
- const std::vector<InnerProductComputer::ProductTerm>& product_terms) {
- const std::vector<Block>& col_blocks = m_.block_structure()->cols;
- std::vector<int> row_block_nnz;
- const int num_nonzeros = ComputeNonzeros(product_terms, &row_block_nnz);
- result_ = CreateResultMatrix(product_storage_type, num_nonzeros);
- // Populate the row non-zero counts in the result matrix.
- int* crsm_rows = result_->mutable_rows();
- crsm_rows[0] = 0;
- for (int i = 0; i < col_blocks.size(); ++i) {
- for (int j = 0; j < col_blocks[i].size; ++j, ++crsm_rows) {
- *(crsm_rows + 1) = *crsm_rows + row_block_nnz[i];
- }
- }
- result_offsets_.resize(product_terms.size());
- if (num_nonzeros == 0) {
- return;
- }
- // The following macro FILL_CRSM_COL_BLOCK is key to understanding
- // how this class works.
- //
- // It does two things.
- //
- // Sets the value for the current term in the result_offsets_ array
- // and populates the cols array of the result matrix.
- //
- // row_block and col_block as the names imply, refer to the row and
- // column blocks of the current term.
- //
- // row_nnz is the number of nonzeros in the result_matrix at the
- // beginning of the first row of row_block.
- //
- // col_nnz is the number of nonzeros in the first row of the row
- // block that occur before the current column block, i.e. this is
- // sum of the sizes of all the column blocks in this row block that
- // came before this column block.
- //
- // Given these two numbers and the total number of nonzeros in this
- // row (nnz_in_row), we can now populate the cols array as follows:
- //
- // nnz + j * nnz_in_row is the beginning of the j^th row.
- //
- // nnz + j * nnz_in_row + col_nnz is the beginning of the column
- // block in the j^th row.
- //
- // nnz + j * nnz_in_row + col_nnz + k is then the j^th row and the
- // k^th column of the product block, whose value is
- //
- // col_blocks[col_block].position + k, which is the column number of
- // the k^th column of the current column block.
- #define FILL_CRSM_COL_BLOCK \
- const int row_block = current->row; \
- const int col_block = current->col; \
- const int nnz_in_row = row_block_nnz[row_block]; \
- int* crsm_cols = result_->mutable_cols(); \
- result_offsets_[current->index] = nnz + col_nnz; \
- for (int j = 0; j < col_blocks[row_block].size; ++j) { \
- for (int k = 0; k < col_blocks[col_block].size; ++k) { \
- crsm_cols[nnz + j * nnz_in_row + col_nnz + k] = \
- col_blocks[col_block].position + k; \
- } \
- }
- int col_nnz = 0;
- int nnz = 0;
- // Process the first term.
- const InnerProductComputer::ProductTerm* current = product_terms.data();
- FILL_CRSM_COL_BLOCK;
- // Process the rest of the terms.
- for (int i = 1; i < product_terms.size(); ++i) {
- current = &product_terms[i];
- const InnerProductComputer::ProductTerm* previous = &product_terms[i - 1];
- // If the current term is the same as the previous term, then it
- // stores its product at the same location as the previous term.
- if (previous->row == current->row && previous->col == current->col) {
- result_offsets_[current->index] = result_offsets_[previous->index];
- continue;
- }
- if (previous->row == current->row) {
- // if the current and previous terms are in the same row block,
- // then they differ in the column block, in which case advance
- // col_nnz by the column size of the previous term.
- col_nnz += col_blocks[previous->col].size;
- } else {
- // If we have moved to a new row-block , then col_nnz is zero,
- // and nnz is set to the beginning of the row block.
- col_nnz = 0;
- nnz += row_block_nnz[previous->row] * col_blocks[previous->row].size;
- }
- FILL_CRSM_COL_BLOCK;
- }
- }
- // Use the results_offsets_ array to numerically compute the product
- // m' * m and store it in result_.
- //
- // TODO(sameeragarwal): Multithreading support.
- void InnerProductComputer::Compute() {
- const double* m_values = m_.values();
- const CompressedRowBlockStructure* bs = m_.block_structure();
- const CompressedRowSparseMatrix::StorageType storage_type =
- result_->storage_type();
- result_->SetZero();
- double* values = result_->mutable_values();
- const int* rows = result_->rows();
- int cursor = 0;
- // Iterate row blocks.
- for (int r = start_row_block_; r < end_row_block_; ++r) {
- const CompressedRow& m_row = bs->rows[r];
- for (int c1 = 0; c1 < m_row.cells.size(); ++c1) {
- const Cell& cell1 = m_row.cells[c1];
- const int c1_size = bs->cols[cell1.block_id].size;
- const int row_nnz = rows[bs->cols[cell1.block_id].position + 1] -
- rows[bs->cols[cell1.block_id].position];
- int c2_begin, c2_end;
- if (storage_type ==
- CompressedRowSparseMatrix::StorageType::LOWER_TRIANGULAR) {
- c2_begin = 0;
- c2_end = c1 + 1;
- } else {
- c2_begin = c1;
- c2_end = m_row.cells.size();
- }
- for (int c2 = c2_begin; c2 < c2_end; ++c2, ++cursor) {
- const Cell& cell2 = m_row.cells[c2];
- const int c2_size = bs->cols[cell2.block_id].size;
- // clang-format off
- MatrixTransposeMatrixMultiply<Eigen::Dynamic, Eigen::Dynamic,
- Eigen::Dynamic, Eigen::Dynamic, 1>(
- m_values + cell1.position,
- m_row.block.size, c1_size,
- m_values + cell2.position,
- m_row.block.size, c2_size,
- values + result_offsets_[cursor],
- 0, 0, c1_size, row_nnz);
- // clang-format on
- }
- }
- }
- CHECK_EQ(cursor, result_offsets_.size());
- }
- } // namespace ceres::internal
|