diff --git a/dune/solvers/solvers/cholmodsolver.hh b/dune/solvers/solvers/cholmodsolver.hh new file mode 100644 index 0000000000000000000000000000000000000000..50114c64e15cc2daa39e1540c5a2f9211822c9cf --- /dev/null +++ b/dune/solvers/solvers/cholmodsolver.hh @@ -0,0 +1,165 @@ +// -*- tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=8 sw=4 sts=4: +#ifndef DUNE_SOLVERS_SOLVERS_CHOLMODSOLVER_HH +#define DUNE_SOLVERS_SOLVERS_CHOLMODSOLVER_HH + +/** \file + \brief A wrapper for the CHOLMOD sparse direct solver +*/ + +#include <dune/common/exceptions.hh> +#include <dune/common/bitsetvector.hh> +#include <dune/common/fmatrix.hh> + +#include <dune/istl/solver.hh> +#include <dune/istl/cholmod.hh> +#include <dune/istl/matrixindexset.hh> +#include <dune/istl/io.hh> + +#include <dune/solvers/common/canignore.hh> +#include <dune/solvers/solvers/linearsolver.hh> + +namespace Dune +{ + +namespace Solvers +{ + +/** \brief Wraps the CHOLMOD sparse symmetric direct solver +* +* CHOLMOD uses the Cholesky decomposition A = LL^T for sparse +* symmetric matrices to solve linear systems Ax = b efficiently. +* +* This class wraps the dune-istl CHOLMOD solver for dune-solvers to provide +* a Solvers::LinearSolver interface. +*/ +template <class MatrixType, class VectorType> +class CholmodSolver +: public LinearSolver<MatrixType,VectorType>, public CanIgnore<Dune::BitSetVector<VectorType::block_type::dimension> > +{ + enum {blocksize = VectorType::block_type::dimension}; + + using field_type = typename VectorType::field_type; + + using MatrixBlock = Dune::FieldMatrix<field_type, blocksize, blocksize> ; + using VectorBlock = Dune::FieldVector<field_type, blocksize>; + +public: + + /** \brief Default constructor */ + CholmodSolver () + : LinearSolver<MatrixType,VectorType>(NumProc::FULL) + {} + + /** \brief Constructor for a linear problem */ + CholmodSolver (const MatrixType& matrix, + VectorType& x, + const VectorType& rhs, + NumProc::VerbosityMode verbosity=NumProc::FULL) + : LinearSolver<MatrixType,VectorType>(verbosity), + matrix_(&matrix), + x_(&x), + rhs_(&rhs) + {} + + /** \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. + */ + void setProblem(const MatrixType& matrix, + VectorType& x, + const VectorType& rhs) override + { + matrix_ = &matrix; + x_ = &x; + rhs_ = &rhs; + } + + virtual void solve() + { + // 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; + + if (not this->hasIgnore()) + { + // the apply() method doesn't like constant values + auto mutableRhs = *rhs_; + solver.setMatrix(*matrix_); + solver.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); + + auto modifiedRhs = *rhs_; + + // pull all ignored columns to the rhs + for(auto rowIt = matrix_->begin(); rowIt != matrix_->end(); rowIt++) + { + const auto row = rowIt.index(); + + for(auto ii=0; ii<blocksize; ii++) + { + if( ignore[row][ii] ) + continue; + + for(auto colIt = rowIt->begin(); colIt != rowIt->end(); colIt++) + { + const auto col = colIt.index(); + for(auto jj=0; jj<blocksize; jj++) + { + if( ignore[col][jj] ) + { + // we assume values only in the upper part + if ( row < col ) + { + // upper part -> use canonical indices + modifiedRhs[row][ii] -= (*matrix_)[row][col][ii][jj] * (*x_)[col][jj]; + } + else if (row == col and ii > jj ) + { + // diagonal block but there in the lower part -> swap ii <-> jj + modifiedRhs[row][ii] -= (*matrix_)[row][col][jj][ii] * (*x_)[col][jj]; + } + else + { + // lower part -> swap col <-> row and ii <-> jj + modifiedRhs[row][ii] -= (*matrix_)[col][row][jj][ii] * (*x_)[col][jj]; + } + } + } + } + } + } + + // Solve the modified system + solver.apply(*x_, modifiedRhs, statistics); + } + } + + //! Pointer to the system matrix + const MatrixType* matrix_; + + //! Pointer to the solution vector + VectorType* x_; + + //! Pointer to the right hand side + const VectorType* rhs_; +}; + +} // namespace Solvers +} // namespace Dune + +#endif diff --git a/dune/solvers/test/CMakeLists.txt b/dune/solvers/test/CMakeLists.txt index 30d1946e9479d5f34d42864aec42d70f63f1e945..314ad6ed239fa0103fca5211e0d5fc39fa4f70de 100644 --- a/dune/solvers/test/CMakeLists.txt +++ b/dune/solvers/test/CMakeLists.txt @@ -22,3 +22,8 @@ if(SuiteSparse_UMFPACK_FOUND) dune_add_test(SOURCES umfpacksolvertest.cc) add_dune_suitesparse_flags(umfpacksolvertest) endif() + +if(SuiteSparse_CHOLMOD_FOUND) + dune_add_test(SOURCES cholmodsolvertest.cc) + add_dune_suitesparse_flags(cholmodsolvertest) +endif() diff --git a/dune/solvers/test/cholmodsolvertest.cc b/dune/solvers/test/cholmodsolvertest.cc new file mode 100644 index 0000000000000000000000000000000000000000..3411fa91ea6475724ba9bce4d38a88935f6e4d43 --- /dev/null +++ b/dune/solvers/test/cholmodsolvertest.cc @@ -0,0 +1,76 @@ +// -*- tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=8 sw=4 sts=4: + +#include <config.h> + +#include <cmath> +#include <iostream> +#include <sstream> + +#include <dune/common/bitsetvector.hh> +#include <dune/common/parallel/mpihelper.hh> + +#include <dune/istl/bcrsmatrix.hh> + +#include <dune/solvers/norms/energynorm.hh> +#include <dune/solvers/solvers/cholmodsolver.hh> + + +#include "common.hh" + +using namespace Dune; + +/** \brief test for the CholmodSolver class + * + * This test tests whether the Dirichlet problem for a Laplace operator is solved correctly for a "random" rhs by an CholmodSolver. + * Implementation is more or less stolen from UMFPackSolverTest + */ +template <size_t blocksize> +struct CHOLMODSolverTestSuite +{ + template <class GridType> + bool check(const GridType& grid) + { + double tol = 1e-12; + + bool passed = true; + + using Problem = + SymmetricSampleProblem<blocksize, typename GridType::LeafGridView>; + Problem p(grid.leafGridView()); + + typedef Solvers::CholmodSolver<typename Problem::Matrix, + typename Problem::Vector> Solver; + Solver solver(p.A,p.u,p.rhs); + solver.setIgnore(p.ignore); + + // solve problem + solver.preprocess(); + solver.solve(); + + if (p.energyNorm.diff(p.u,p.u_ex)>tol*10) + { + std::cout << "The CholmodSolver did not produce a satisfactory result. ||u-u_ex||=" << p.energyNorm.diff(p.u,p.u_ex) << std::endl; + std::cout << "||u_ex||=" << p.energyNorm(p.u_ex) << std::endl; + passed = false; + } + + return passed; + } +}; + + + +int main(int argc, char** argv) +{ + Dune::MPIHelper::instance(argc, argv); + + bool passed = true; + + CHOLMODSolverTestSuite<1> testsuite1; + CHOLMODSolverTestSuite<2> testsuite2; + passed = checkWithStandardGrids(testsuite1); + passed = checkWithStandardGrids(testsuite2); + + return passed ? 0 : 1; +}