diff --git a/CHANGELOG.md b/CHANGELOG.md index 6943ee9948b886c70588ea5c051ebf4dfa5b854d..0b129b301af1ed4d01ded369749f506fbe7ca08f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ - CholmodSolver: `CholmodSolver` can be used with any blocked matrix/vector type supported by `flatMatrixForEach` and `flatVectorForEach` in dune-istl. +- CholmodSolver: There are now various new methods that allow to factorize the matrix once, + and use the factorization to solve linear systems with several right hand sides. + ## Deprecations and removals - The file `blockgsstep.hh` has been removed after four years of deprecation. Use the replacement diff --git a/dune/solvers/solvers/cholmodsolver.hh b/dune/solvers/solvers/cholmodsolver.hh index 553f528dc687f8d20d824faa1160c91c0de07e6e..b25e78cbe2759abacc50aceaa21fefcda2fa6ec1 100644 --- a/dune/solvers/solvers/cholmodsolver.hh +++ b/dune/solvers/solvers/cholmodsolver.hh @@ -46,7 +46,14 @@ public: /** \brief Default constructor */ CholmodSolver () : LinearSolver<MatrixType,VectorType>(NumProc::FULL) - {} + { + // Bug in LibScotchMetis: + // metis_dswitch: see cholmod.h: LibScotchMetis throws segmentation faults for large problems. + // This caused a hard to understand CI problems and therefore, METIS is disabled for problems + // larger than defined in metis_nswitch for now (default 3000). + // Bug report: https://gitlab.inria.fr/scotch/scotch/issues/8 + cholmodCommonObject().metis_dswitch = 0.0; + } /** \brief Constructor for a linear problem */ CholmodSolver (const MatrixType& matrix, @@ -57,56 +64,117 @@ public: matrix_(&matrix), x_(&x), rhs_(&rhs) - {} + { + // Bug in LibScotchMetis: + // metis_dswitch: see cholmod.h: LibScotchMetis throws segmentation faults for large problems. + // This caused a hard to understand CI problems and therefore, METIS is disabled for problems + // larger than defined in metis_nswitch for now (default 3000). + // Bug report: https://gitlab.inria.fr/scotch/scotch/issues/8 + cholmodCommonObject().metis_dswitch = 0.0; + } /** \brief Set the linear problem to solve * - * Warning! The matrix is assumed to be symmetric! - * CHOLMOD uses only the upper part of the matrix, the lower part is ignored and can be empty for optimal memory usage. + * The matrix is assumed to be symmetric! CHOLMOD uses only the + * upper part of the matrix, the lower part is ignored and can be + * empty for optimal memory use. */ void setProblem(const MatrixType& matrix, VectorType& x, const VectorType& rhs) override + { + setMatrix(matrix); + setSolutionVector(x); + setRhs(rhs); + } + + /** \brief Set the matrix for the linear linear problem to solve + * + * The matrix is assumed to be symmetric! CHOLMOD uses only the + * upper part of the matrix, the lower part is ignored and can be + * empty for optimal memory usage. + * + * \warning Unlike the setMatrix method of the dune-istl Cholmod class, + * the method here does not factorize the matrix! Call the factorize + * method for that. + */ + void setMatrix(const MatrixType& matrix) { matrix_ = &matrix; - x_ = &x; + isFactorized_ = false; + } + + /** \brief Set the rhs vector for the linear problem + */ + void setRhs(const VectorType& rhs) + { rhs_ = &rhs; } - virtual void solve() + /** \brief Set the vector where to write the solution of the linear problem + */ + void setSolutionVector(VectorType& x) + { + x_ = &x; + } + + /** \brief Compute the Cholesky decomposition of the matrix + * + * You must have set a matrix to be able to call this, + * either by calling setProblem or the appropriate constructor. + * + * The degrees of freedom to ignore must have been set before + * calling the `factorize` method. + */ + void factorize() + { + if (not this->hasIgnore()) + { + // setMatrix will decompose the matrix + istlCholmodSolver_.setMatrix(*matrix_); + + // get the error code from Cholmod in case something happened + errorCode_ = cholmodCommonObject().status; + } + else + { + const auto& ignore = this->ignore(); + + // The setMatrix method stores only the selected entries of the matrix (determined by the ignore field) + istlCholmodSolver_.setMatrix(*matrix_,&ignore); + // get the error code from Cholmod in case something happened + errorCode_ = cholmodCommonObject().status; + } + + if (errorCode_ >= 0) // okay or warning, but no error + isFactorized_ = true; + } + + /** \brief Solve the linear system + * + * This factorizes the matrix if it hasn't been factorized earlier + */ + virtual void solve() override { // prepare the solver Dune::InverseOperatorResult statistics; - Dune::Cholmod<VectorType> solver; - // Bug in LibScotchMetis: - // metis_dswitch: see cholmod.h: LibScotchMetis throws segmentation faults for large problems. - // This caused a hard to understand CI problems and therefore, METIS is disabled for problems - // larger than defined in metis_nswitch for now (default 3000). - // Bug report: https://gitlab.inria.fr/scotch/scotch/issues/8 - solver.cholmodCommonObject().metis_dswitch = 0.0; + // Factorize the matrix now, if the caller hasn't done so explicitly earlier. + if (!isFactorized_) + factorize(); if (not this->hasIgnore()) { // the apply() method doesn't like constant values auto mutableRhs = *rhs_; - // setMatrix will decompose the matrix - solver.setMatrix(*matrix_); - // get the error code from Cholmod in case something happened - errorCode_ = solver.cholmodCommonObject().status; - - solver.apply(*x_, mutableRhs, statistics); + // The back-substitution + istlCholmodSolver_.apply(*x_, mutableRhs, statistics); } else { const auto& ignore = this->ignore(); - // The setMatrix method stores only the selected entries of the matrix (determined by the ignore field) - solver.setMatrix(*matrix_,&ignore); - // get the error code from Cholmod in case something happened - errorCode_ = solver.cholmodCommonObject().status; - // create flat vectors using T = typename VectorType::field_type; std::size_t flatSize = flatVectorForEach(ignore, [](...){}); @@ -148,10 +216,15 @@ public: }); // Solve the modified system - solver.apply(*x_, modifiedRhs, statistics); + istlCholmodSolver_.apply(*x_, modifiedRhs, statistics); } } + cholmod_common& cholmodCommonObject() + { + return istlCholmodSolver_.cholmodCommonObject(); + } + /** \brief return the error code of the Cholmod factorize call * * In setMatrix() the matrix factorization takes place and Cholmod is @@ -165,6 +238,9 @@ public: return errorCode_; } + //! dune-istl Cholmod solver object + Dune::Cholmod<VectorType> istlCholmodSolver_; + //! Pointer to the system matrix const MatrixType* matrix_; @@ -176,6 +252,13 @@ public: //! error code of Cholmod factorize call int errorCode_ = 0; + + /** \brief Whether istlCholmodSolver_ currently holds a factorized matrix. + * + * isFactorized==true is equivalent to istlCholmodSolver_->L_ != nullptr, + * but the latter information is private to the dune-istl Cholmod class. + */ + bool isFactorized_ = false; }; } // namespace Solvers diff --git a/dune/solvers/test/cholmodsolvertest.cc b/dune/solvers/test/cholmodsolvertest.cc index a1183dbb8eaecce203dcdcd46235279014df3a88..61239010e169573073ecad1690a794fa1ce7d36d 100644 --- a/dune/solvers/test/cholmodsolvertest.cc +++ b/dune/solvers/test/cholmodsolvertest.cc @@ -59,7 +59,80 @@ struct CHOLMODSolverTestSuite } }; +// Check whether we can reuse a factorization for several right hand sides +bool checkMultipleSolves() +{ + bool passed = true; + + using Matrix = BCRSMatrix<double>; + using Vector = BlockVector<double>; + + // Construct an example matrix + Matrix matrix(4, 4, + 3, // Expected average number of nonzeros + // per row + 0.2, // Size of the compression buffer, + // as a fraction of the expected total number of nonzeros + BCRSMatrix<double>::implicit); + + matrix.entry(0,0) = 2; + matrix.entry(0,1) = 1; + matrix.entry(1,0) = 1; + matrix.entry(1,1) = 2; + matrix.entry(1,2) = 1; + matrix.entry(2,1) = 1; + matrix.entry(2,2) = 2; + matrix.entry(2,3) = 1; + matrix.entry(3,2) = 1; + matrix.entry(3,3) = 2; + + matrix.compress(); + + // Set up the solver + Solvers::CholmodSolver<Matrix,Vector> cholmodSolver; + + // Where to write the solution + Vector x(4); + cholmodSolver.setSolutionVector(x); + + // Factorize the matrix + cholmodSolver.setMatrix(matrix); + cholmodSolver.factorize(); + + // Test with a particular right-hand side + Vector rhs = {3,4,4,3}; + + // The corresponding solution + Vector reference = {1,1,1,1}; + + cholmodSolver.setRhs(rhs); + cholmodSolver.solve(); + + // Check whether result is correct + auto diff = reference; + diff -= x; + if (diff.infinity_norm() > 1e-8) + { + std::cerr << "CholmodSolver didn't produce the correct result!" << std::endl; + passed = false; + } + // Test with a different right-hand side + // This reuses the original factorization. + rhs = {4,6,6,5}; + reference = {1,2,1,2}; + cholmodSolver.solve(); + + diff = reference; + diff -= x; + if (diff.infinity_norm() > 1e-8) + { + std::cerr << "CholmodSolver didn't produce the correct result!" << std::endl; + passed = false; + } + + return passed; +} int main(int argc, char** argv) { @@ -72,5 +145,7 @@ int main(int argc, char** argv) passed &= checkWithStandardGrids(testsuite1); passed &= checkWithStandardGrids(testsuite2); + passed &= checkMultipleSolves(); + return passed ? 0 : 1; }