From a807c110eb9939e351306fbf0024f727f00c16e6 Mon Sep 17 00:00:00 2001
From: Oliver Sander <oliver.sander@tu-dresden.de>
Date: Mon, 18 Oct 2021 21:58:20 +0200
Subject: [PATCH] CholmodSolver: Implement rowAdd method

This method allows to add new matrix rows to the Cholesky factor.
See the CHOLMOD manual for more details.
---
 dune/solvers/solvers/cholmodsolver.hh  | 57 ++++++++++++++++++++++++++
 dune/solvers/test/cholmodsolvertest.cc | 13 ++++++
 2 files changed, 70 insertions(+)

diff --git a/dune/solvers/solvers/cholmodsolver.hh b/dune/solvers/solvers/cholmodsolver.hh
index 99d9bd72..87b65225 100644
--- a/dune/solvers/solvers/cholmodsolver.hh
+++ b/dune/solvers/solvers/cholmodsolver.hh
@@ -268,6 +268,63 @@ public:
     errorCode_ = cholmodCommonObject().status;
   }
 
+  /** \brief Adds row and column to an LDL^T factorization
+   *
+   * \param k The scalar(!) row and column to add
+   *
+   * The pattern and values are taken from the original matrix given to CholmodSolver
+   *
+   * \todo Currently only works if the matrix entries are scalars or 1x1 FieldMatrix
+   */
+  void rowAdd(size_t k)
+  {
+    // Length of a row
+    auto n = istlCholmodSolver_.cholmodFactor()->n;
+
+    size_t nonZeros = 0;
+    for ([[maybe_unused]] auto&& entry : sparseRange((*this->matrix_)[k]))
+      nonZeros++;
+
+    // Create a Cholmod data structure with the row to be added
+    const auto deleter = [c = &cholmodCommonObject()](auto* p) {
+      cholmod_free_sparse(&p, c);
+    };
+
+    auto R = std::unique_ptr<cholmod_sparse, decltype(deleter)>(
+      cholmod_allocate_sparse(n,             // # rows
+                              1,             // # cols
+                              nonZeros,      // # of nonzeroes
+                              1,             // indices are sorted ( 1 = true)
+                              1,             // matrix is "packed" ( 1 = true)
+                              0,             // stype of matrix ( 0 = consider the entire matrix )
+                              CHOLMOD_REAL,  // xtype of matrix ( CHOLMOD_REAL = single array, no complex numbers)
+                              &cholmodCommonObject()
+                             ), deleter);
+
+    // copy the data of BCRS matrix to Cholmod Sparse matrix
+    int* Ap = static_cast<int*>(R->p);
+    int* Ai = static_cast<int*>(R->i);
+    double* Ax = static_cast<double*>(R->x);
+
+    Ap[0] = 0;  // We have a single column, which starts at 0
+    Ap[1] = nonZeros;
+    for (auto [entry, col] : sparseRange((*this->matrix_)[k]))
+    {
+      *Ax++ = entry;
+      *Ai++ = invPermutation_[col];
+    }
+
+    cholmod_factor *L = istlCholmodSolver_.cholmodFactor();
+
+    cholmod_rowadd(invPermutation_[k],   // row/column index to add
+                   R.get(),              // row/column of matrix to factorize (n x 1)
+                   L,                    // Factor to modify
+                   &cholmodCommonObject());
+
+    // get the error code from Cholmod in case something happened
+    errorCode_ = cholmodCommonObject().status;
+  }
+
   //! dune-istl Cholmod solver object
   Dune::Cholmod<VectorType> istlCholmodSolver_;
 
diff --git a/dune/solvers/test/cholmodsolvertest.cc b/dune/solvers/test/cholmodsolvertest.cc
index faf1cb94..a0d7733c 100644
--- a/dune/solvers/test/cholmodsolvertest.cc
+++ b/dune/solvers/test/cholmodsolvertest.cc
@@ -144,6 +144,19 @@ bool checkMultipleSolves()
     passed = false;
   }
 
+  // Put the deleted row/column back in
+  cholmodSolver.rowAdd(1);
+  reference = {1,2,1,2};
+  cholmodSolver.solve();
+
+  diff = reference;
+  diff -= x;
+  if (std::isnan(diff.infinity_norm()) || diff.infinity_norm() > 1e-8)
+  {
+    std::cerr << "CholmodSolver didn't produce the correct result!" << std::endl;
+    passed = false;
+  }
+
   return passed;
 }
 
-- 
GitLab