Refactor BlockJacobiPreconditioner

1. Rename BlockJacobiPreconditionet to BlockSparseJacobiPreconditioner.
2. Add CompressedRowSparseJacobiPreconditioner which is a
   block Jacobi preconditioner for CompressedRowSparseMatrix objects.
3. Re-write the tests to be more comprehensive.

Change-Id: Icbc91f9ad2cefaad593c11397f8cdcf805d7e118
diff --git a/internal/ceres/block_jacobi_preconditioner.cc b/internal/ceres/block_jacobi_preconditioner.cc
index fdba2b8..779693c 100644
--- a/internal/ceres/block_jacobi_preconditioner.cc
+++ b/internal/ceres/block_jacobi_preconditioner.cc
@@ -30,6 +30,7 @@
 
 #include "ceres/block_jacobi_preconditioner.h"
 
+#include "Eigen/Dense"
 #include "ceres/block_random_access_diagonal_matrix.h"
 #include "ceres/block_sparse_matrix.h"
 #include "ceres/block_structure.h"
@@ -38,7 +39,7 @@
 
 namespace ceres::internal {
 
-BlockJacobiPreconditioner::BlockJacobiPreconditioner(
+BlockSparseJacobiPreconditioner::BlockSparseJacobiPreconditioner(
     const BlockSparseMatrix& A) {
   const CompressedRowBlockStructure* bs = A.block_structure();
   std::vector<int> blocks(bs->cols.size());
@@ -49,10 +50,10 @@
   m_ = std::make_unique<BlockRandomAccessDiagonalMatrix>(blocks);
 }
 
-BlockJacobiPreconditioner::~BlockJacobiPreconditioner() = default;
+BlockSparseJacobiPreconditioner::~BlockSparseJacobiPreconditioner() = default;
 
-bool BlockJacobiPreconditioner::UpdateImpl(const BlockSparseMatrix& A,
-                                           const double* D) {
+bool BlockSparseJacobiPreconditioner::UpdateImpl(const BlockSparseMatrix& A,
+                                                 const double* D) {
   const CompressedRowBlockStructure* bs = A.block_structure();
   const double* values = A.values();
   m_->SetZero();
@@ -90,9 +91,91 @@
   return true;
 }
 
-void BlockJacobiPreconditioner::RightMultiplyAndAccumulate(const double* x,
-                                                           double* y) const {
-  m_->RightMultiplyAndAccumulate(x, y);
+BlockCRSJacobiPreconditioner::BlockCRSJacobiPreconditioner(
+    const CompressedRowSparseMatrix& A) {
+  auto& col_blocks = A.col_blocks();
+
+  // Compute the number of non-zeros in the preconditioner. This is needed so
+  // that we can construct the CompressedRowSparseMatrix.
+  int m_nnz = 0;
+  for (int col_block_size : col_blocks) {
+    m_nnz += col_block_size * col_block_size;
+  }
+
+  m_ = std::make_unique<CompressedRowSparseMatrix>(
+      A.num_cols(), A.num_cols(), m_nnz);
+
+  const int num_col_blocks = col_blocks.size();
+
+  // Populate the sparsity structure of the preconditioner matrix.
+  int* m_cols = m_->mutable_cols();
+  int* m_rows = m_->mutable_rows();
+  m_rows[0] = 0;
+  for (int i = 0, col = 0, idx = 0; i < num_col_blocks; ++i) {
+    // For each column block populate a diagonal block in the preconditioner.
+    // Not that the because of the way the CompressedRowSparseMatrix format
+    // works, the entire diagonal block is laid out contiguously in memory as a
+    // row-major matrix. We will use this when updating the block.
+    const int col_block_size = col_blocks[i];
+    for (int j = 0; j < col_block_size; ++j) {
+      for (int k = 0; k < col_block_size; ++k, ++idx) {
+        m_cols[idx] = col + k;
+      }
+      m_rows[col + j + 1] = idx;
+    }
+    col += col_block_size;
+  }
+
+  CHECK_EQ(m_rows[A.num_cols()], m_nnz);
+}
+
+BlockCRSJacobiPreconditioner::~BlockCRSJacobiPreconditioner() = default;
+
+bool BlockCRSJacobiPreconditioner::UpdateImpl(
+    const CompressedRowSparseMatrix& A, const double* D) {
+  const auto& col_blocks = A.col_blocks();
+  const int num_col_blocks = col_blocks.size();
+
+  const int* a_rows = A.rows();
+  const int* a_cols = A.cols();
+  const double* a_values = A.values();
+
+  m_->SetZero();
+  double* m_values = m_->mutable_values();
+  const int* m_rows = m_->rows();
+
+  const int num_rows = A.num_rows();
+  // The following loop can likely be optimized by exploiting the fact that each
+  // row block has exactly the same sparsity structure.
+  for (int r = 0; r < num_rows; ++r) {
+    int idx = a_rows[r];
+    while (idx < a_rows[r + 1]) {
+      const int col = a_cols[idx];
+      const int col_block_size = m_rows[col + 1] - m_rows[col];
+      // We make use of the fact that the entire diagonal block is stored
+      // contiguously in memory as a row-major matrix.
+      MatrixRef m(m_values + m_rows[col], col_block_size, col_block_size);
+      ConstVectorRef b(a_values + idx, col_block_size);
+      m.selfadjointView<Eigen::Upper>().rankUpdate(b);
+      idx += col_block_size;
+    }
+  }
+
+  for (int i = 0, col = 0; i < num_col_blocks; ++i) {
+    const int col_block_size = m_rows[col + 1] - m_rows[col];
+    MatrixRef m(m_values + m_rows[col], col_block_size, col_block_size);
+
+    if (D != nullptr) {
+      m.diagonal() +=
+          ConstVectorRef(D + col, col_block_size).array().square().matrix();
+    }
+
+    m = m.selfadjointView<Eigen::Upper>().llt().solve(
+        Matrix::Identity(col_block_size, col_block_size));
+    col += col_block_size;
+  }
+
+  return true;
 }
 
 }  // namespace ceres::internal
diff --git a/internal/ceres/block_jacobi_preconditioner.h b/internal/ceres/block_jacobi_preconditioner.h
index 7728eb9..265b7d6 100644
--- a/internal/ceres/block_jacobi_preconditioner.h
+++ b/internal/ceres/block_jacobi_preconditioner.h
@@ -41,30 +41,22 @@
 namespace ceres::internal {
 
 class BlockSparseMatrix;
-struct CompressedRowBlockStructure;
+class CompressedRowSparseMatrix;
 
 // A block Jacobi preconditioner. This is intended for use with
-// conjugate gradients, or other iterative symmetric solvers. To use
-// the preconditioner, create one by passing a BlockSparseMatrix "A"
-// to the constructor. This fixes the sparsity pattern to the pattern
-// of the matrix A^TA.
-//
-// Before each use of the preconditioner in a solve with conjugate gradients,
-// update the matrix by running Update(A, D). The values of the matrix A are
-// inspected to construct the preconditioner. The vector D is applied as the
-// D^TD diagonal term.
-class CERES_NO_EXPORT BlockJacobiPreconditioner
+// conjugate gradients, or other iterative symmetric solvers.
+
+// This version of the preconditioner is for use with BlockSparseMatrix
+// Jacobians.
+class CERES_NO_EXPORT BlockSparseJacobiPreconditioner
     : public BlockSparseMatrixPreconditioner {
  public:
   // A must remain valid while the BlockJacobiPreconditioner is.
-  explicit BlockJacobiPreconditioner(const BlockSparseMatrix& A);
-  BlockJacobiPreconditioner(const BlockJacobiPreconditioner&) = delete;
-  void operator=(const BlockJacobiPreconditioner&) = delete;
-
-  ~BlockJacobiPreconditioner() override;
-
-  // Preconditioner interface
-  void RightMultiplyAndAccumulate(const double* x, double* y) const final;
+  explicit BlockSparseJacobiPreconditioner(const BlockSparseMatrix& A);
+  ~BlockSparseJacobiPreconditioner() override;
+  void RightMultiplyAndAccumulate(const double* x, double* y) const final {
+    return m_->RightMultiplyAndAccumulate(x, y);
+  }
   int num_rows() const final { return m_->num_rows(); }
   int num_cols() const final { return m_->num_rows(); }
   const BlockRandomAccessDiagonalMatrix& matrix() const { return *m_; }
@@ -75,6 +67,27 @@
   std::unique_ptr<BlockRandomAccessDiagonalMatrix> m_;
 };
 
+// This version of the preconditioner is for use with CompressedRowSparseMatrix
+// Jacobians.
+class CERES_NO_EXPORT BlockCRSJacobiPreconditioner
+    : public CompressedRowSparseMatrixPreconditioner {
+ public:
+  // A must remain valid while the BlockJacobiPreconditioner is.
+  explicit BlockCRSJacobiPreconditioner(const CompressedRowSparseMatrix& A);
+  ~BlockCRSJacobiPreconditioner() override;
+  void RightMultiplyAndAccumulate(const double* x, double* y) const final {
+    m_->RightMultiplyAndAccumulate(x, y);
+  }
+  int num_rows() const final { return m_->num_rows(); }
+  int num_cols() const final { return m_->num_rows(); }
+  const CompressedRowSparseMatrix& matrix() const { return *m_; }
+
+ private:
+  bool UpdateImpl(const CompressedRowSparseMatrix& A, const double* D) final;
+
+  std::unique_ptr<CompressedRowSparseMatrix> m_;
+};
+
 }  // namespace ceres::internal
 
 #include "ceres/internal/reenable_warnings.h"
diff --git a/internal/ceres/block_jacobi_preconditioner_test.cc b/internal/ceres/block_jacobi_preconditioner_test.cc
index ab69148..6e35387 100644
--- a/internal/ceres/block_jacobi_preconditioner_test.cc
+++ b/internal/ceres/block_jacobi_preconditioner_test.cc
@@ -41,58 +41,108 @@
 
 namespace ceres::internal {
 
-class BlockJacobiPreconditionerTest : public ::testing::Test {
- protected:
-  void SetUpFromProblemId(int problem_id) {
-    std::unique_ptr<LinearLeastSquaresProblem> problem =
-        CreateLinearLeastSquaresProblemFromId(problem_id);
+TEST(BlockSparseJacobiPreconditioner, _) {
+  constexpr int kNumtrials = 10;
+  BlockSparseMatrix::RandomMatrixOptions options;
+  options.num_col_blocks = 3;
+  options.min_col_block_size = 1;
+  options.max_col_block_size = 3;
 
-    CHECK(problem != nullptr);
-    A.reset(down_cast<BlockSparseMatrix*>(problem->A.release()));
-    D = std::move(problem->D);
+  options.num_row_blocks = 5;
+  options.min_row_block_size = 1;
+  options.max_row_block_size = 4;
+  options.block_density = 0.25;
+  std::mt19937 prng;
 
-    Matrix dense_a;
-    A->ToDenseMatrix(&dense_a);
-    dense_ata = dense_a.transpose() * dense_a;
-    dense_ata += VectorRef(D.get(), A->num_cols())
-                     .array()
-                     .square()
-                     .matrix()
-                     .asDiagonal();
-  }
+  for (int trial = 0; trial < kNumtrials; ++trial) {
+    auto jacobian = BlockSparseMatrix::CreateRandomMatrix(options, prng);
+    Vector diagonal = Vector::Ones(jacobian->num_cols());
+    Matrix dense_jacobian;
+    jacobian->ToDenseMatrix(&dense_jacobian);
+    Matrix hessian = dense_jacobian.transpose() * dense_jacobian;
+    hessian.diagonal() += diagonal.array().square().matrix();
 
-  void VerifyDiagonalBlocks(const int problem_id) {
-    SetUpFromProblemId(problem_id);
+    BlockSparseJacobiPreconditioner pre(*jacobian);
+    pre.Update(*jacobian, diagonal.data());
 
-    BlockJacobiPreconditioner pre(*A);
-    pre.Update(*A, D.get());
+    // The const_cast is needed to be able to call GetCell.
     auto* m = const_cast<BlockRandomAccessDiagonalMatrix*>(&pre.matrix());
-    EXPECT_EQ(m->num_rows(), A->num_cols());
-    EXPECT_EQ(m->num_cols(), A->num_cols());
+    EXPECT_EQ(m->num_rows(), jacobian->num_cols());
+    EXPECT_EQ(m->num_cols(), jacobian->num_cols());
 
-    const CompressedRowBlockStructure* bs = A->block_structure();
+    const CompressedRowBlockStructure* bs = jacobian->block_structure();
     for (int i = 0; i < bs->cols.size(); ++i) {
       const int block_size = bs->cols[i].size;
       int r, c, row_stride, col_stride;
       CellInfo* cell_info = m->GetCell(i, i, &r, &c, &row_stride, &col_stride);
-      MatrixRef m(cell_info->values, row_stride, col_stride);
-      Matrix actual_block_inverse = m.block(r, c, block_size, block_size);
-      Matrix expected_block = dense_ata.block(
+      Matrix actual_block_inverse =
+          MatrixRef(cell_info->values, row_stride, col_stride)
+              .block(r, c, block_size, block_size);
+      Matrix expected_block = hessian.block(
           bs->cols[i].position, bs->cols[i].position, block_size, block_size);
       const double residual = (actual_block_inverse * expected_block -
                                Matrix::Identity(block_size, block_size))
                                   .norm();
       EXPECT_NEAR(residual, 0.0, 1e-12) << "Block: " << i;
     }
+    options.num_col_blocks++;
+    options.num_row_blocks++;
   }
+}
 
-  std::unique_ptr<BlockSparseMatrix> A;
-  std::unique_ptr<double[]> D;
-  Matrix dense_ata;
-};
+TEST(CompressedRowSparseJacobiPreconditioner, _) {
+  constexpr int kNumtrials = 10;
+  CompressedRowSparseMatrix::RandomMatrixOptions options;
+  options.num_col_blocks = 3;
+  options.min_col_block_size = 1;
+  options.max_col_block_size = 3;
 
-TEST_F(BlockJacobiPreconditionerTest, SmallProblem) { VerifyDiagonalBlocks(2); }
+  options.num_row_blocks = 5;
+  options.min_row_block_size = 1;
+  options.max_row_block_size = 4;
+  options.block_density = 0.25;
+  std::mt19937 prng;
 
-TEST_F(BlockJacobiPreconditionerTest, LargeProblem) { VerifyDiagonalBlocks(3); }
+  for (int trial = 0; trial < kNumtrials; ++trial) {
+    auto jacobian =
+        CompressedRowSparseMatrix::CreateRandomMatrix(options, prng);
+    Vector diagonal = Vector::Ones(jacobian->num_cols());
+
+    Matrix dense_jacobian;
+    jacobian->ToDenseMatrix(&dense_jacobian);
+    Matrix hessian = dense_jacobian.transpose() * dense_jacobian;
+    hessian.diagonal() += diagonal.array().square().matrix();
+
+    BlockCRSJacobiPreconditioner pre(*jacobian);
+    pre.Update(*jacobian, diagonal.data());
+    auto& m = pre.matrix();
+
+    EXPECT_EQ(m.num_rows(), jacobian->num_cols());
+    EXPECT_EQ(m.num_cols(), jacobian->num_cols());
+
+    const auto& col_blocks = jacobian->col_blocks();
+    for (int i = 0, col = 0; i < col_blocks.size(); ++i) {
+      const int block_size = col_blocks[i];
+      int idx = m.rows()[col];
+      for (int j = 0; j < block_size; ++j) {
+        EXPECT_EQ(m.rows()[col + j + 1] - m.rows()[col + j], block_size);
+        for (int k = 0; k < block_size; ++k, ++idx) {
+          EXPECT_EQ(m.cols()[idx], col + k);
+        }
+      }
+
+      ConstMatrixRef actual_block_inverse(
+          m.values() + m.rows()[col], block_size, block_size);
+      Matrix expected_block = hessian.block(col, col, block_size, block_size);
+      const double residual = (actual_block_inverse * expected_block -
+                               Matrix::Identity(block_size, block_size))
+                                  .norm();
+      EXPECT_NEAR(residual, 0.0, 1e-12) << "Block: " << i;
+      col += block_size;
+    }
+    options.num_col_blocks++;
+    options.num_row_blocks++;
+  }
+}
 
 }  // namespace ceres::internal
diff --git a/internal/ceres/cgnr_solver.cc b/internal/ceres/cgnr_solver.cc
index f4f9e0f..3452e64 100644
--- a/internal/ceres/cgnr_solver.cc
+++ b/internal/ceres/cgnr_solver.cc
@@ -137,7 +137,7 @@
   EventLogger event_logger("CgnrSolver::Solve");
   if (!preconditioner_) {
     if (options_.preconditioner_type == JACOBI) {
-      preconditioner_ = std::make_unique<BlockJacobiPreconditioner>(*A);
+      preconditioner_ = std::make_unique<BlockSparseJacobiPreconditioner>(*A);
     } else if (options_.preconditioner_type == SUBSET) {
       Preconditioner::Options preconditioner_options;
       preconditioner_options.type = SUBSET;