Updates to sparse block matrix structures to support new
sparse linear solvers.
* Add methods to convert TripletSparseMatrix and BlockSparseMatrix to
CRSMatrix structure.
* Added tests for conversion of TripletSparseMatrix and BlockSparseMatrix
to CRSMatrix structure.
* Added documentation on the BlockSparseMatrix structure.
Change-Id: I020cfa91c301567ceeb39ff2064183c5d88c9ed5
diff --git a/internal/ceres/block_sparse_matrix.cc b/internal/ceres/block_sparse_matrix.cc
index 9ba054f..22224a3 100644
--- a/internal/ceres/block_sparse_matrix.cc
+++ b/internal/ceres/block_sparse_matrix.cc
@@ -36,6 +36,7 @@
#include <vector>
#include "ceres/block_structure.h"
+#include "ceres/crs_matrix.h"
#include "ceres/internal/eigen.h"
#include "ceres/random.h"
#include "ceres/small_blas.h"
@@ -165,6 +166,42 @@
}
}
+void BlockSparseMatrix::ToCRSMatrix(CRSMatrix* crs_matrix) const {
+ CHECK(crs_matrix != nullptr);
+ crs_matrix->num_rows = num_rows_;
+ crs_matrix->num_cols = num_cols_;
+ vector<int>& rows = crs_matrix->rows;
+ vector<int>& cols = crs_matrix->cols;
+ vector<double>& values = crs_matrix->values;
+ rows.clear();
+ cols.clear();
+ values.clear();
+ rows.reserve(num_nonzeros_ + 1);
+ cols.reserve(num_nonzeros_);
+ values.reserve(num_nonzeros_);
+
+ for (const auto& row_block : block_structure_->rows) {
+ int row_block_size = row_block.block.size;
+ const vector<Cell>& cells = row_block.cells;
+ const int row_start = row_block.block.position;
+ for (int r = 0; r < row_block_size; ++r) {
+ rows.push_back(values.size());
+ for (const auto& cell : cells) {
+ int col_block_id = cell.block_id;
+ int col_block_size = block_structure_->cols[col_block_id].size;
+ int col_start = block_structure_->cols[col_block_id].position;
+ const MatrixRef m(
+ values_.get() + cell.position, row_block_size, col_block_size);
+ for (int c = 0; c < col_block_size; ++c) {
+ cols.push_back(c + col_start);
+ values.push_back(m(r, c));
+ }
+ }
+ }
+ }
+ rows.push_back(values.size());
+}
+
void BlockSparseMatrix::ToDenseMatrix(Matrix* dense_matrix) const {
CHECK(dense_matrix != nullptr);
diff --git a/internal/ceres/block_sparse_matrix.h b/internal/ceres/block_sparse_matrix.h
index 987a435..da6b641 100644
--- a/internal/ceres/block_sparse_matrix.h
+++ b/internal/ceres/block_sparse_matrix.h
@@ -37,6 +37,7 @@
#include <memory>
#include "ceres/block_structure.h"
+#include "ceres/crs_matrix.h"
#include "ceres/internal/disable_warnings.h"
#include "ceres/internal/eigen.h"
#include "ceres/internal/export.h"
@@ -74,6 +75,7 @@
void LeftMultiply(const double* x, double* y) const final;
void SquaredColumnNorm(double* x) const final;
void ScaleColumns(const double* scale) final;
+ void ToCRSMatrix(CRSMatrix* matrix) const;
void ToDenseMatrix(Matrix* dense_matrix) const final;
void ToTextFile(FILE* file) const final;
diff --git a/internal/ceres/block_sparse_matrix_test.cc b/internal/ceres/block_sparse_matrix_test.cc
index d8cf987..7b64a7c 100644
--- a/internal/ceres/block_sparse_matrix_test.cc
+++ b/internal/ceres/block_sparse_matrix_test.cc
@@ -34,6 +34,7 @@
#include <string>
#include "ceres/casts.h"
+#include "ceres/crs_matrix.h"
#include "ceres/internal/eigen.h"
#include "ceres/linear_least_squares_problems.h"
#include "ceres/triplet_sparse_matrix.h"
@@ -43,6 +44,92 @@
namespace ceres {
namespace internal {
+namespace {
+template<typename T>
+void CheckVectorEq(const std::vector<T>& a, const std::vector<T>& b) {
+ EXPECT_EQ(a.size(), b.size());
+ for (int i = 0; i < a.size(); ++i) {
+ EXPECT_EQ(a[i], b[i]);
+ }
+}
+
+std::unique_ptr<BlockSparseMatrix> CreateTestMatrixFromId(int id) {
+ if (id == 0) {
+ // Create the following block sparse matrix:
+ // [ 1 2 0 0 0 0 ]
+ // [ 3 4 0 0 0 0 ]
+ // [ 0 0 5 6 7 0 ]
+ // [ 0 0 8 9 10 0 ]
+ CompressedRowBlockStructure* bs = new CompressedRowBlockStructure;
+ bs->cols = {
+ // Block size 2, position 0.
+ Block(2, 0),
+ // Block size 3, position 2.
+ Block(3, 2),
+ // Block size 1, position 5.
+ Block(1, 5),
+ };
+ bs->rows = {
+ CompressedRow(1),
+ CompressedRow(1)
+ };
+ bs->rows[0].block = Block(2, 0);
+ bs->rows[0].cells = { Cell(0, 0) };
+
+ bs->rows[1].block = Block(2, 2);
+ bs->rows[1].cells = { Cell(1, 4) };
+ std::unique_ptr<BlockSparseMatrix> m =
+ std::make_unique<BlockSparseMatrix>(bs);
+ EXPECT_NE(m, nullptr);
+ EXPECT_EQ(m->num_rows(), 4);
+ EXPECT_EQ(m->num_cols(), 6);
+ EXPECT_EQ(m->num_nonzeros(), 10);
+ double* values = m->mutable_values();
+ for (int i = 0; i < 10; ++i) {
+ values[i] = i + 1;
+ }
+ return m;
+ } else if (id == 1) {
+ // Create the following block sparse matrix:
+ // [ 1 2 0 5 6 0 ]
+ // [ 3 4 0 7 8 0 ]
+ // [ 0 0 9 0 0 0 ]
+ CompressedRowBlockStructure* bs = new CompressedRowBlockStructure;
+ bs->cols = {
+ // Block size 2, position 0.
+ Block(2, 0),
+ // Block size 1, position 2.
+ Block(1, 2),
+ // Block size 2, position 3.
+ Block(2, 3),
+ // Block size 1, position 5.
+ Block(1, 5),
+ };
+ bs->rows = {
+ CompressedRow(2),
+ CompressedRow(1)
+ };
+ bs->rows[0].block = Block(2, 0);
+ bs->rows[0].cells = { Cell(0, 0), Cell(2, 4) };
+
+ bs->rows[1].block = Block(1, 2);
+ bs->rows[1].cells = { Cell(1, 8) };
+ std::unique_ptr<BlockSparseMatrix> m =
+ std::make_unique<BlockSparseMatrix>(bs);
+ EXPECT_NE(m, nullptr);
+ EXPECT_EQ(m->num_rows(), 3);
+ EXPECT_EQ(m->num_cols(), 6);
+ EXPECT_EQ(m->num_nonzeros(), 9);
+ double* values = m->mutable_values();
+ for (int i = 0; i < 9; ++i) {
+ values[i] = i + 1;
+ }
+ return m;
+ }
+ return nullptr;
+}
+} // namespace
+
class BlockSparseMatrixTest : public ::testing::Test {
protected:
void SetUp() final {
@@ -214,5 +301,59 @@
}
}
+TEST(BlockSparseMatrix, ToDenseMatrix) {
+ {
+ std::unique_ptr<BlockSparseMatrix> m = CreateTestMatrixFromId(0);
+ Matrix m_dense;
+ m->ToDenseMatrix(&m_dense);
+ EXPECT_EQ(m_dense.rows(), 4);
+ EXPECT_EQ(m_dense.cols(), 6);
+ Matrix m_expected(4, 6);
+ m_expected << 1, 2, 0, 0, 0, 0,
+ 3, 4, 0, 0, 0, 0,
+ 0, 0, 5, 6, 7, 0,
+ 0, 0, 8, 9, 10, 0;
+ EXPECT_EQ(m_dense, m_expected);
+ }
+
+ {
+ std::unique_ptr<BlockSparseMatrix> m = CreateTestMatrixFromId(1);
+ Matrix m_dense;
+ m->ToDenseMatrix(&m_dense);
+ EXPECT_EQ(m_dense.rows(), 3);
+ EXPECT_EQ(m_dense.cols(), 6);
+ Matrix m_expected(3, 6);
+ m_expected << 1, 2, 0, 5, 6, 0,
+ 3, 4, 0, 7, 8, 0,
+ 0, 0, 9, 0, 0, 0;
+ EXPECT_EQ(m_dense, m_expected);
+ }
+}
+
+TEST(BlockSparseMatrix, ToCRSMatrix) {
+ {
+ std::unique_ptr<BlockSparseMatrix> m = CreateTestMatrixFromId(0);
+ CRSMatrix m_crs;
+ m->ToCRSMatrix(&m_crs);
+ std::vector<int> rows_expected = {0, 2, 4, 7, 10};
+ std::vector<int> cols_expected = {0, 1, 0, 1, 2, 3, 4, 2, 3, 4};
+ std::vector<double> values_expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ CheckVectorEq(rows_expected, m_crs.rows);
+ CheckVectorEq(cols_expected, m_crs.cols);
+ CheckVectorEq(values_expected, m_crs.values);
+ }
+ {
+ std::unique_ptr<BlockSparseMatrix> m = CreateTestMatrixFromId(1);
+ CRSMatrix m_crs;
+ m->ToCRSMatrix(&m_crs);
+ std::vector<int> rows_expected = {0, 4, 8, 9};
+ std::vector<int> cols_expected = {0, 1, 3, 4, 0, 1, 3, 4, 2};
+ std::vector<double> values_expected = {1, 2, 5, 6, 3, 4, 7, 8, 9};
+ CheckVectorEq(rows_expected, m_crs.rows);
+ CheckVectorEq(cols_expected, m_crs.cols);
+ CheckVectorEq(values_expected, m_crs.values);
+ }
+}
+
} // namespace internal
} // namespace ceres
diff --git a/internal/ceres/block_structure.h b/internal/ceres/block_structure.h
index 52b7b2f..2ba6b32 100644
--- a/internal/ceres/block_structure.h
+++ b/internal/ceres/block_structure.h
@@ -82,6 +82,82 @@
using CompressedRow = CompressedList;
using CompressedColumn = CompressedList;
+// CompressedRowBlockStructure specifies the storage structure of a row block
+// sparse matrix.
+//
+// Consider the following matrix A:
+// A = [A_11 A_12 ...
+// A_21 A_22 ...
+// ...
+// A_m1 A_m2 ... ]
+//
+// A row block sparse matrix is a matrix where the following properties hold:
+// 1. The number of rows in every block A_ij and A_ik are the same.
+// 2. The number of columns in every block A_ij and A_kj are the same.
+// 3. The number of rows in A_ij and A_kj may be different (i != k).
+// 4. The number of columns in A_ij and A_ik may be different (j != k).
+// 5. Any block A_ij may be all 0s, in which case the block is not stored.
+//
+// The structure of the matrix is stored as follows:
+//
+// The `rows' array contains the following information for each row block:
+// - rows[i].block.size: The number of rows in each block A_ij in the row block.
+// - rows[i].block.position: The starting row in the full matrix A of the
+// row block i.
+// - rows[i].cells[j].block_id: The index into the `cols' array corresponding to
+// the non-zero blocks A_ij.
+// - rows[i].cells[j].position: The index in the `values' array for the contents
+// of block A_ij.
+//
+// The `cols' array contains the following information for block:
+// - cols[.].size: The number of columns spanned by the block.
+// - cols[.].position: The starting column in the full matrix A of the block.
+//
+//
+// Example of a row block sparse matrix:
+// block_id: | 0 |1|2 |3 |
+// rows[0]: [ 1 2 0 3 4 0 ]
+// [ 5 6 0 7 8 0 ]
+// rows[1]: [ 0 0 9 0 0 0 ]
+//
+// This matrix is stored as follows:
+//
+// There are four column blocks:
+// cols[0].size = 2
+// cols[0].position = 0
+// cols[1].size = 1
+// cols[1].position = 2
+// cols[2].size = 2
+// cols[2].position = 3
+// cols[3].size = 1
+// cols[3].position = 5
+
+// The first row block spans two rows, starting at row 0:
+// rows[0].block.size = 2 // This row block spans two rows.
+// rows[0].block.position = 0 // It starts at row 0.
+// rows[0] has two cells, at column blocks 0 and 2:
+// rows[0].cells[0].block_id = 0 // This cell is in column block 0.
+// rows[0].cells[0].position = 0 // See below for an explanation of this.
+// rows[0].cells[1].block_id = 2 // This cell is in column block 2.
+// rows[0].cells[1].position = 4 // See below for an explanation of this.
+//
+// The second row block spans two rows, starting at row 2:
+// rows[1].block.size = 1 // This row block spans one row.
+// rows[1].block.position = 2 // It starts at row 2.
+// rows[1] has one cell at column block 1:
+// rows[1].cells[0].block_id = 1 // This cell is in column block 1.
+// rows[1].cells[0].position = 8 // See below for an explanation of this.
+//
+// The values in each blocks are stored contiguously in row major order.
+// However, there is no unique way to order the blocks -- it is usually
+// optimized to promote cache coherent access, e.g. ordering it so that
+// Jacobian blocks of parameters of the same type are stored nearby.
+// This is one possible way to store the values of the blocks in a values array:
+// values = { 1, 2, 5, 6, 3, 4, 7, 8, 9 }
+// | | | | // The three blocks.
+// ^ rows[0].cells[0].position = 0
+// ^ rows[0].cells[1].position = 4
+// ^ rows[1].cells[0].position = 8
struct CERES_NO_EXPORT CompressedRowBlockStructure {
std::vector<Block> cols;
std::vector<CompressedRow> rows;
diff --git a/internal/ceres/triplet_sparse_matrix.cc b/internal/ceres/triplet_sparse_matrix.cc
index c1c7ddc..86a444e 100644
--- a/internal/ceres/triplet_sparse_matrix.cc
+++ b/internal/ceres/triplet_sparse_matrix.cc
@@ -33,6 +33,8 @@
#include <algorithm>
#include <memory>
+#include "ceres/compressed_row_sparse_matrix.h"
+#include "ceres/crs_matrix.h"
#include "ceres/internal/eigen.h"
#include "ceres/internal/export.h"
#include "ceres/random.h"
@@ -194,6 +196,11 @@
}
}
+void TripletSparseMatrix::ToCRSMatrix(CRSMatrix* crs_matrix) const {
+ CompressedRowSparseMatrix::FromTripletSparseMatrix(*this)->ToCRSMatrix(
+ crs_matrix);
+}
+
void TripletSparseMatrix::ToDenseMatrix(Matrix* dense_matrix) const {
dense_matrix->resize(num_rows_, num_cols_);
dense_matrix->setZero();
@@ -275,6 +282,31 @@
}
}
+std::unique_ptr<TripletSparseMatrix>
+TripletSparseMatrix::CreateFromTextFile(FILE* file) {
+ CHECK(file != nullptr);
+ int num_rows = 0;
+ int num_cols = 0;
+ std::vector<int> rows;
+ std::vector<int> cols;
+ std::vector<double> values;
+ while (true) {
+ int row, col;
+ double value;
+ if (fscanf(file, "%d %d %lf", &row, &col, &value) != 3) {
+ break;
+ }
+ rows.push_back(row);
+ cols.push_back(col);
+ values.push_back(value);
+ num_rows = std::max(num_rows, row + 1);
+ num_cols = std::max(num_cols, col + 1);
+ }
+ VLOG(1) << "Read " << rows.size() << " nonzeros from file.";
+ return std::make_unique<TripletSparseMatrix>(
+ num_rows, num_cols, rows, cols, values);
+}
+
std::unique_ptr<TripletSparseMatrix> TripletSparseMatrix::CreateRandomMatrix(
const TripletSparseMatrix::RandomMatrixOptions& options) {
CHECK_GT(options.num_rows, 0);
diff --git a/internal/ceres/triplet_sparse_matrix.h b/internal/ceres/triplet_sparse_matrix.h
index 2cf4290..a2ba1f1 100644
--- a/internal/ceres/triplet_sparse_matrix.h
+++ b/internal/ceres/triplet_sparse_matrix.h
@@ -34,6 +34,7 @@
#include <memory>
#include <vector>
+#include "ceres/crs_matrix.h"
#include "ceres/internal/disable_warnings.h"
#include "ceres/internal/eigen.h"
#include "ceres/internal/export.h"
@@ -68,6 +69,7 @@
void LeftMultiply(const double* x, double* y) const final;
void SquaredColumnNorm(double* x) const final;
void ScaleColumns(const double* scale) final;
+ void ToCRSMatrix(CRSMatrix* matrix) const;
void ToDenseMatrix(Matrix* dense_matrix) const final;
void ToTextFile(FILE* file) const final;
// clang-format off
@@ -135,6 +137,8 @@
static std::unique_ptr<TripletSparseMatrix> CreateRandomMatrix(
const TripletSparseMatrix::RandomMatrixOptions& options);
+ // Load a triplet sparse matrix from a text file.
+ static std::unique_ptr<TripletSparseMatrix> CreateFromTextFile(FILE* file);
private:
void AllocateMemory();
void CopyData(const TripletSparseMatrix& orig);
diff --git a/internal/ceres/triplet_sparse_matrix_test.cc b/internal/ceres/triplet_sparse_matrix_test.cc
index e96c994..577d959 100644
--- a/internal/ceres/triplet_sparse_matrix_test.cc
+++ b/internal/ceres/triplet_sparse_matrix_test.cc
@@ -28,6 +28,7 @@
//
// Author: sameeragarwal@google.com (Sameer Agarwal)
+#include "ceres/crs_matrix.h"
#include "ceres/triplet_sparse_matrix.h"
#include <memory>
@@ -343,4 +344,42 @@
}
}
+TEST(TripletSparseMatrix, ToCRSMatrix) {
+ // Test matrix:
+ // [1, 2, 0, 5, 6, 0,
+ // 3, 4, 0, 7, 8, 0,
+ // 0, 0, 9, 0, 0, 0]
+ TripletSparseMatrix m(3,
+ 6,
+ {0, 0, 0, 0, 1, 1, 1, 1, 2},
+ {0, 1, 3, 4, 0, 1, 3, 4, 2},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9});
+ CRSMatrix m_crs;
+ m.ToCRSMatrix(&m_crs);
+ EXPECT_EQ(m_crs.num_rows, 3);
+ EXPECT_EQ(m_crs.num_cols, 6);
+
+ EXPECT_EQ(m_crs.rows.size(), 4);
+ EXPECT_EQ(m_crs.rows[0], 0);
+ EXPECT_EQ(m_crs.rows[1], 4);
+ EXPECT_EQ(m_crs.rows[2], 8);
+ EXPECT_EQ(m_crs.rows[3], 9);
+
+ EXPECT_EQ(m_crs.cols.size(), 9);
+ EXPECT_EQ(m_crs.cols[0], 0);
+ EXPECT_EQ(m_crs.cols[1], 1);
+ EXPECT_EQ(m_crs.cols[2], 3);
+ EXPECT_EQ(m_crs.cols[3], 4);
+ EXPECT_EQ(m_crs.cols[4], 0);
+ EXPECT_EQ(m_crs.cols[5], 1);
+ EXPECT_EQ(m_crs.cols[6], 3);
+ EXPECT_EQ(m_crs.cols[7], 4);
+ EXPECT_EQ(m_crs.cols[8], 2);
+
+ EXPECT_EQ(m_crs.values.size(), 9);
+ for (int i = 0; i < 9; ++i) {
+ EXPECT_EQ(m_crs.values[i], i + 1);
+ }
+}
+
} // namespace ceres::internal