diff --git a/dune/istl/istlexception.hh b/dune/istl/istlexception.hh index d72f6feb770b6c56455bafc15024c1f4d830a89c..4a1da2d3f4473f6cdc9c8ff1d487f3cd4592992f 100644 --- a/dune/istl/istlexception.hh +++ b/dune/istl/istlexception.hh @@ -33,6 +33,15 @@ namespace Dune { : public BCRSMatrixError {}; + //! Thrown when a solver aborts due to some problem. + /** + * Problems that may cause the solver to abort include a NaN detected during + * the convergence check (which may be caused by invalid input data), or + * breakdown conditions (which can happen e.g. in BiCGSTABSolver or + * RestartedGMResSolver). + */ + class SolverAbort : public ISTLError {}; + /** @} end documentation */ } // end namespace diff --git a/dune/istl/solver.hh b/dune/istl/solver.hh index 8898d9adfc26acc7958d6eb208a5ba3b2a502267..d010fe09b679c85d0607e7eaaba713aadb6f87be 100644 --- a/dune/istl/solver.hh +++ b/dune/istl/solver.hh @@ -95,6 +95,9 @@ namespace Dune \param x The left hand side to store the result in. \param b The right hand side \param res Object to store the statistics about applying the operator. + + \throw SolverAbort When the solver detects a problem and cannot + continue */ virtual void apply (X& x, Y& b, InverseOperatorResult& res) = 0; @@ -107,6 +110,9 @@ namespace Dune \param b The right hand side \param reduction The minimum defect reduction to achieve. \param res Object to store the statistics about applying the operator. + + \throw SolverAbort When the solver detects a problem and cannot + continue */ virtual void apply (X& x, Y& b, double reduction, InverseOperatorResult& res) = 0; diff --git a/dune/istl/solvers.hh b/dune/istl/solvers.hh index 7f9e1c1b787735d18d738eab582e678e12d15e3b..320354f0187e9e6e8c928caf403cac7636d97c6c 100644 --- a/dune/istl/solvers.hh +++ b/dune/istl/solvers.hh @@ -19,6 +19,7 @@ #include "solver.hh" #include "preconditioner.hh" #include <dune/common/deprecated.hh> +#include <dune/common/exceptions.hh> #include <dune/common/timer.hh> #include <dune/common/ftraits.hh> #include <dune/common/typetraits.hh> @@ -410,9 +411,17 @@ namespace Dune { \brief Apply inverse operator. \copydoc InverseOperator::apply(X&,Y&,InverseOperatorResult&) + + \note Currently, the CGSolver aborts when a NaN or infinite defect is + detected. However, -ffinite-math-only (implied by -ffast-math) + can inhibit a result from becoming NaN that really should be NaN. + E.g. numeric_limits<double>::quiet_NaN()*0.0==0.0 with gcc-5.3 + -ffast-math. */ virtual void apply (X& x, X& b, InverseOperatorResult& res) { + using std::isfinite; + res.clear(); // clear solver statistics Timer watch; // start a timer _prec.pre(x,b); // prepare preconditioner @@ -422,6 +431,16 @@ namespace Dune { X q(x); // a temporary vector real_type def0 = _sp.norm(b); // compute norm + + if (!isfinite(def0)) // check for inf or NaN + { + if (_verbose>0) + std::cout << "=== CGSolver: abort due to infinite or NaN initial defect" + << std::endl; + DUNE_THROW(SolverAbort, "CGSolver: initial defect=" << def0 + << " is infinite or NaN"); + } + if (def0<1E-30) // convergence check { res.converged = true; @@ -472,6 +491,15 @@ namespace Dune { this->printOutput(std::cout,real_type(i),defnew,def); def = defnew; // update norm + if (!isfinite(def)) // check for inf or NaN + { + if (_verbose>0) + std::cout << "=== CGSolver: abort due to infinite or NaN defect" + << std::endl; + DUNE_THROW(SolverAbort, + "CGSolver: defect=" << def << " is infinite or NaN"); + } + if (def<def0*_reduction || def<1E-30) // convergence check { res.converged = true; @@ -513,6 +541,12 @@ namespace Dune { \brief Apply inverse operator with given reduction factor. \copydoc InverseOperator::apply(X&,Y&,double,InverseOperatorResult&) + + \note Currently, the CGSolver aborts when a NaN or infinite defect is + detected. However, -ffinite-math-only (implied by -ffast-math) + can inhibit a result from becoming NaN that really should be NaN. + E.g. numeric_limits<double>::quiet_NaN()*0.0==0.0 with gcc-5.3 + -ffast-math. */ virtual void apply (X& x, X& b, double reduction, InverseOperatorResult& res) @@ -583,6 +617,8 @@ namespace Dune { \brief Apply inverse operator. \copydoc InverseOperator::apply(X&,Y&,InverseOperatorResult&) + + \note Currently, the BiCGSTABSolver aborts when it detects a breakdown. */ virtual void apply (X& x, X& b, InverseOperatorResult& res) { @@ -661,11 +697,11 @@ namespace Dune { // look if breakdown occurred if (abs(rho) <= EPSILON) - DUNE_THROW(ISTLError,"breakdown in BiCGSTAB - rho " + DUNE_THROW(SolverAbort,"breakdown in BiCGSTAB - rho " << rho << " <= EPSILON " << EPSILON << " after " << it << " iterations"); if (abs(omega) <= EPSILON) - DUNE_THROW(ISTLError,"breakdown in BiCGSTAB - omega " + DUNE_THROW(SolverAbort,"breakdown in BiCGSTAB - omega " << omega << " <= EPSILON " << EPSILON << " after " << it << " iterations"); @@ -691,7 +727,7 @@ namespace Dune { h = _sp.dot(rt,v); if (abs(h) < EPSILON) - DUNE_THROW(ISTLError,"abs(h) < EPSILON in BiCGSTAB - abs(h) " + DUNE_THROW(SolverAbort,"abs(h) < EPSILON in BiCGSTAB - abs(h) " << abs(h) << " < EPSILON " << EPSILON << " after " << it << " iterations"); @@ -785,6 +821,8 @@ namespace Dune { \brief Apply inverse operator with given reduction factor. \copydoc InverseOperator::apply(X&,Y&,double,InverseOperatorResult&) + + \note Currently, the BiCGSTABSolver aborts when it detects a breakdown. */ virtual void apply (X& x, X& b, double reduction, InverseOperatorResult& res) { @@ -1191,7 +1229,14 @@ namespace Dune { "P and S must have the same category!"); } - //! \copydoc InverseOperator::apply(X&,Y&,InverseOperatorResult&) + /*! + \brief Apply inverse operator. + + \copydoc InverseOperator::apply(X&,Y&,InverseOperatorResult&) + + \note Currently, the RestartedGMResSolver aborts when it detects a + breakdown. + */ virtual void apply (X& x, Y& b, InverseOperatorResult& res) { apply(x,b,_reduction,res); @@ -1201,6 +1246,9 @@ namespace Dune { \brief Apply inverse operator. \copydoc InverseOperator::apply(X&,Y&,double,InverseOperatorResult&) + + \note Currently, the RestartedGMResSolver aborts when it detects a + breakdown. */ virtual void apply (X& x, Y& b, double reduction, InverseOperatorResult& res) { @@ -1277,7 +1325,7 @@ namespace Dune { } H[i+1][i] = _sp.norm(w); if(abs(H[i+1][i]) < EPSILON) - DUNE_THROW(ISTLError, + DUNE_THROW(SolverAbort, "breakdown in GMRes - |w| == 0.0 after " << j << " iterations"); // normalize new vector diff --git a/dune/istl/test/CMakeLists.txt b/dune/istl/test/CMakeLists.txt index 0a998ea23bd7bcd00f55bf6b1897237235b0fc59..33f7d295b0b6e46082c9634ca8d4e62068bbd38a 100644 --- a/dune/istl/test/CMakeLists.txt +++ b/dune/istl/test/CMakeLists.txt @@ -45,6 +45,9 @@ dune_add_test(SOURCES inverseoperator2prectest.cc) dune_add_test(SOURCES scaledidmatrixtest.cc) +dune_add_test(SOURCES solveraborttest.cc + SKIP_ON_77) + # Pardiso tests dune_add_test(SOURCES test_pardiso.cc SKIP_ON_77) diff --git a/dune/istl/test/solveraborttest.cc b/dune/istl/test/solveraborttest.cc new file mode 100644 index 0000000000000000000000000000000000000000..57ae8f9eb4f5d39191fbd3ed83c59fd288c36620 --- /dev/null +++ b/dune/istl/test/solveraborttest.cc @@ -0,0 +1,113 @@ +// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- +// vi: set et ts=4 sw=2 sts=2: + +#include "config.h" + +#include <iostream> +#include <limits> +#include <string> + +#include <dune/common/fmatrix.hh> +#include <dune/common/fvector.hh> + +#include <dune/istl/operators.hh> +#include <dune/istl/preconditioners.hh> +#include <dune/istl/solver.hh> +#include <dune/istl/solvers.hh> + +template<class Solver, class Vector> +void checkSolverAbort(int &status, const std::string &name, + Solver &solver, Vector &x, Vector &b) +{ + try { + Dune::InverseOperatorResult res; + solver.apply(x, b, res); + std::cout << "Error: " << name << "::apply() did not abort, which is " + << "unexpected.\n" + << "converged = " << std::boolalpha << res.converged << "\n" + << "iterations = " << res.iterations << "\n" + << "reduction = " << res.reduction << "\n" + << "conv_rate = " << res.conv_rate << "\n" + << "elapsed = " << res.elapsed << "\n" + << "solution = {" << x << "}" << std::endl; + status = 1; // FAIL + } + catch(const Dune::SolverAbort &e) + { + std::cout << name << "::apply() aborted, as expected" << std::endl + << "Abort message was: " << e << std::endl; + if(status == 77) status = 0; // PASS + } + catch(const std::exception &e) + { + std::cout << "Error: " << name << "::apply() aborted with an exception " + << "not derived from Dune::SolverAbort, which is unexpected.\n" + << "e.what(): " << e.what() << std::endl; + status = 1; // FAIL + } + catch(...) + { + std::cout << "Error: " << name << "::apply() aborted with an exception " + << "not derived from Dune::SolverAbort, which is unexpected.\n" + << "In addition, the exception is not derived from " + << "std::exception, so there is no further information, sorry." + << std::endl; + status = 1; // FAIL + } +} + +int main() +{ + + int status = 77; + + // How verbose the solvers should be. Use 2 (maximum verbosity) by default, + // this will include all information in the logs, and for the casual user of + // the unit tests ctest will hide the output anyway. + int verbose = 2; + + { // CGSolver + std::cout << "Checking CGSolver with an unsolvable system...\n" + << "Expecting SolverAbort with a NaN defect" << std::endl; + + using Matrix = Dune::FieldMatrix<double, 2, 2>; + using Vector = Dune::FieldVector<double, 2>; + + Matrix matrix = { { 1, 1 }, + { 1, 1 } }; + Vector b = { 1, 2 }; + Vector x = { 0, 0 }; + + Dune::MatrixAdapter<Matrix, Vector, Vector> op(matrix); + Dune::Richardson<Vector, Vector> richardson; + Dune::CGSolver<Vector> solver(op, richardson, 1e-10, 5000, verbose); + + checkSolverAbort(status, "CGSolver", solver, x, b); + } + + { // BiCGSTABSolver + std::cout << "Checking BiCGSTABSolver with an unsolvable system...\n" + << "Expecting abs(h) < EPSILON" << std::endl; + + using Matrix = Dune::FieldMatrix<double, 2, 2>; + using Vector = Dune::FieldVector<double, 2>; + + Matrix matrix = { { 1, 1 }, + { 1, 1 } }; + Vector b = { 1, 2 }; + Vector x = { 0, 0 }; + + Dune::MatrixAdapter<Matrix, Vector, Vector> op(matrix); + Dune::Richardson<Vector, Vector> richardson; + Dune::BiCGSTABSolver<Vector> solver(op, richardson, 1e-10, 5000, verbose); + + checkSolverAbort(status, "BiCGSTABSolver", solver, x, b); + } + + // TODO: + // - trigger "breakdown in BiCGSTAB - rho" + // - trigger "breakdown in BiCGSTAB - omega" + // - trigger "breakdown in GMRes" + + return status; +}