diff --git a/test/mixeddimension/embedded/1p2c_richards2c/richardstestproblem.hh b/test/mixeddimension/embedded/1p2c_richards2c/richardstestproblem.hh index f1fdc125c7645f59ee00d8d1068a8046354a665d..b8787e408dea74494316654874aa4731e78507d6 100644 --- a/test/mixeddimension/embedded/1p2c_richards2c/richardstestproblem.hh +++ b/test/mixeddimension/embedded/1p2c_richards2c/richardstestproblem.hh @@ -149,7 +149,7 @@ public: contaminantMoleFraction_ = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, Problem, ContaminantMoleFraction); // for initial conditions - const Scalar sw = 0.2; // start with 20% saturation on top + const Scalar sw = 0.3; // start with 30% saturation on top pcTop_ = MaterialLaw::pc(this->spatialParams().materialLawParamsAtPos(this->bBoxMax()), sw); } @@ -338,12 +338,13 @@ public: const auto xTracer = [&,this]() { auto contaminationPos = this->bBoxMax()-this->bBoxMin(); - contaminationPos[0] *= 0.2; - contaminationPos[1] *= 0.5; - contaminationPos[2] *= 0.2; + contaminationPos[0] *= 0.25; + contaminationPos[1] *= 0.55; + contaminationPos[2] *= 0.25; contaminationPos += this->bBoxMin(); - if ((globalPos - contaminationPos).two_norm() < 0.1*(this->bBoxMax()-this->bBoxMin()).two_norm() + eps_) + static const Scalar extend = 0.15*(this->bBoxMax()[0]-this->bBoxMin()[0]); + if ((globalPos - contaminationPos).infinity_norm() < extend + eps_) return contaminantMoleFraction_; else return 0.0; diff --git a/test/mixeddimension/embedded/1p2c_richards2c/rootsystemtestproblem.hh b/test/mixeddimension/embedded/1p2c_richards2c/rootsystemtestproblem.hh index 6809d892aa2e710c4cc95fc800d5d9577cea5cb6..40ee2464d658585ee587604111083e18ccffb3ae 100644 --- a/test/mixeddimension/embedded/1p2c_richards2c/rootsystemtestproblem.hh +++ b/test/mixeddimension/embedded/1p2c_richards2c/rootsystemtestproblem.hh @@ -26,6 +26,8 @@ #include <cmath> +#include <dune/grid/common/scsgmapper.hh> + #include <dumux/implicit/cellcentered/tpfa/properties.hh> #include <dumux/porousmediumflow/implicit/problem.hh> #include <dumux/porousmediumflow/1p2c/implicit/model.hh> diff --git a/test/mixeddimension/embedded/1p2c_richards2c/rositestproblem.hh b/test/mixeddimension/embedded/1p2c_richards2c/rositestproblem.hh index 0b449ac614e191c29ddf7d62020d5769e57063d7..8859c6b927086cc8a707426cdafb773e9667432e 100644 --- a/test/mixeddimension/embedded/1p2c_richards2c/rositestproblem.hh +++ b/test/mixeddimension/embedded/1p2c_richards2c/rositestproblem.hh @@ -32,6 +32,8 @@ #include <dumux/mixeddimension/embedded/cellcentered/bboxtreecouplingmanagersimple.hh> #include <dumux/mixeddimension/integrationpointsource.hh> +#include "schursolver.hh" + namespace Dumux { template <class TypeTag> @@ -66,7 +68,7 @@ SET_TYPE_PROP(RootsystemTestCCProblem, PointSourceHelper, Dumux::IntegrationPoin SET_TYPE_PROP(RichardsTestCCProblem, PointSource, Dumux::IntegrationPointSource<TTAG(RichardsTestCCProblem), unsigned int>); SET_TYPE_PROP(RichardsTestCCProblem, PointSourceHelper, Dumux::IntegrationPointSourceHelper<TTAG(RichardsTestCCProblem)>); -SET_TYPE_PROP(RosiTestProblem, LinearSolver, ILU0BiCGSTABBackend<TypeTag>); +SET_TYPE_PROP(RosiTestProblem, LinearSolver, BlockILU0BiCGSTABSolver<TypeTag>); }//end namespace properties diff --git a/test/mixeddimension/embedded/1p2c_richards2c/schursolver.hh b/test/mixeddimension/embedded/1p2c_richards2c/schursolver.hh new file mode 100644 index 0000000000000000000000000000000000000000..a30cd54d62dea640febe17f6eef378816b781d8d --- /dev/null +++ b/test/mixeddimension/embedded/1p2c_richards2c/schursolver.hh @@ -0,0 +1,376 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/***************************************************************************** + * See the file COPYING for full copying permissions. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + *****************************************************************************/ +/*! + * \file + * \brief Schur complement solver for the embedded problem, the idea is to solve + * only linear systems of the size of the much smaller embedded domain by + * forming a Schur complement preconditioner. If the embedded system is much + * smaller it might be feasible to just use a direct solver. + */ +#ifndef DUMUX_EMBEDDED_SCHUR_COMPLEMENT_SOLVER_BACKEND_HH +#define DUMUX_EMBEDDED_SCHUR_COMPLEMENT_SOLVER_BACKEND_HH + +#include <dune/istl/preconditioners.hh> +#include <dune/istl/solvers.hh> +#include <dune/istl/superlu.hh> +#include <dune/istl/umfpack.hh> + +#include <dumux/common/parameters.hh> +#include <dumux/common/basicproperties.hh> +#include <dumux/linear/linearsolverproperties.hh> + +#include <dune/istl/paamg/amg.hh> +#include <dune/istl/paamg/pinfo.hh> +#include <dumux/linear/amgproperties.hh> + +namespace Dumux { + +/*! + \brief Schur complement linear operator + this applies the Schur complement to a vector and can be used in an inverse operator +*/ +template<class AType, class BType, class CType, class DType, class InvOpDType, class X, class Y> +class SchurComplement : public Dune::LinearOperator<X,Y> +{ +public: + // export types + // typedef DType matrix_type; + typedef X domain_type; + typedef Y range_type; + typedef typename X::field_type field_type; + + //! constructor: just store a reference to a matrix + explicit SchurComplement (const AType& A, const BType& B, + const CType& C, const DType& D, + const std::shared_ptr<InvOpDType>& invD) + : A_(A), B_(B), C_(C), D_(D), invD_(invD) {} + + //! apply operator to x: \f$ y = A(x) \f$ + virtual void apply (const X& x, Y& y) const + { + // apply C, note x and aTmp1 have different size (Cx) + X aTmp1(D_.N()); + aTmp1 = 0.0; + C_.mv(x, aTmp1); + + // apply D^-1 (D^-1Cx) + auto aTmp2 = aTmp1; + Dune::InverseOperatorResult result; + invD_->apply(aTmp2, aTmp1, result); + + // apply B (BD^-1Cx) + B_.mv(aTmp2, y); + + y *= -1.0; + + // add Ax (Ax - BD^-1Cx) -> the full Schur complement of S_D := D/M + A_.umv(x, y); + } + + //! apply operator to x, scale and add: \f$ y = y + \alpha A(x) \f$ + virtual void applyscaleadd (field_type alpha, const X& x, Y& y) const + { + auto tmp = y; + this->apply(x, tmp); + y.axpy(alpha, tmp); + } + + //! Category of the preconditioner (see SolverCategory::Category) + virtual Dune::SolverCategory::Category category() const + { + return Dune::SolverCategory::sequential; + } + + private: + const AType& A_; + const BType& B_; + const CType& C_; + const DType& D_; + std::shared_ptr<InvOpDType> invD_; +}; + +/*! \brief The schur complement preconditioner + + \tparam M The matrix type to operate on + \tparam X Type of the update + \tparam Y Type of the defect + \tparam l The block level to invert. Default is 1 +*/ +template<class TypeTag, class AType, class BType, class CType, class DType, class InvOpDType, class X, class Y> +class SchurComplementPreconditioner : public Dune::Preconditioner<X, Y> +{ +public: + //! \brief The matrix type the preconditioner is for. + // typedef M matrix_type; + //! \brief The domain type of the preconditioner. + typedef X domain_type; + //! \brief The range type of the preconditioner. + typedef Y range_type; + //! \brief The field type of the preconditioner. + typedef typename X::field_type field_type; + + using BulkVector = typename GET_PROP(TypeTag, SolutionVector)::SolutionVectorBulk; + using EmbeddedVector = typename GET_PROP(TypeTag, SolutionVector)::SolutionVectorLowDim; + + /*! \brief Approximates the Schur complement + */ + SchurComplementPreconditioner (const AType& A, const BType& B, + const CType& C, const DType& D, + const std::shared_ptr<InvOpDType>& invD) + : A_(A), B_(B), C_(C), D_(D), invD_(invD) {} + + + virtual void pre (X& x, Y& b) + { + DUNE_UNUSED_PARAMETER(x); + DUNE_UNUSED_PARAMETER(b); + } + + /*! + \brief Apply the preconditioner. + + \copydoc Preconditioner::apply(X&,const Y&) + */ + virtual void apply (X& x, const Y& b) + { + using namespace Dune::Indices; + const BulkVector& f = b[_0]; + const EmbeddedVector& g = b[_1]; + + static const int verbosity = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, int, LinearSolver, PreconditionerVerbosity); + static const int schurMaxIter = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, int, LinearSolver, SchurIterations); + static const double schurReduction = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, double, LinearSolver, SchurResidualReduction); + + Dune::Richardson<BulkVector, BulkVector> identity(1.0); + SchurComplement<AType, BType, CType, DType, InvOpDType, BulkVector, BulkVector> schurOp(A_, B_, C_, D_, invD_); + Dune::BiCGSTABSolver<BulkVector> invOpS(schurOp, identity, schurReduction, schurMaxIter, verbosity); + + // apply S^-1 to solve the bulk block + BulkVector fTmp(f); + Dune::InverseOperatorResult res; + invOpS.apply(x[_0], fTmp, res); + + // prepare rhs for application of D^-1 (g - Ca) + EmbeddedVector gTmp(g); + C_.mmv(x[_0], gTmp); + + // then solve for embedded block b using the action of D^-1 + // use direct solver + invD_->apply(x[_1], gTmp, res); + } + + virtual void post (X& x) + { DUNE_UNUSED_PARAMETER(x); } + + //! Category of the preconditioner (see SolverCategory::Category) + virtual Dune::SolverCategory::Category category() const + { return Dune::SolverCategory::sequential; } + +private: + const AType& A_; + const BType& B_; + const CType& C_; + const DType& D_; + std::shared_ptr<InvOpDType> invD_; +}; + +template <class TypeTag> +class SchurComplementSolver +{ + using Problem = typename GET_PROP_TYPE(TypeTag, Problem); + +public: + SchurComplementSolver() = default; + SchurComplementSolver(const Problem& problem) {} + + // expects a system as a multi-type matrix + // | A B | + // | C D | + + // Solve saddle-point problem using a Schur complement based preconditioner + template<class Matrix, class Vector> + bool solve(const Matrix& M, Vector& x, const Vector& b) + { + int verbosity = GET_PARAM_FROM_GROUP(TypeTag, int, LinearSolver, Verbosity); + static const double reduction = GET_PARAM_FROM_GROUP(TypeTag, double, LinearSolver, ResidualReduction); + static const double maxIter = GET_PARAM_FROM_GROUP(TypeTag, double, LinearSolver, MaxIterations); + static const int restartGMRes = GET_PARAM_FROM_GROUP(TypeTag, double, LinearSolver, GMResRestart); + + using AType = typename GET_PROP(TypeTag, JacobianMatrix)::MatrixBlockBulk; + using BType = typename GET_PROP(TypeTag, JacobianMatrix)::MatrixBlockBulkCoupling; + using CType = typename GET_PROP(TypeTag, JacobianMatrix)::MatrixBlockLowDimCoupling; + using DType = typename GET_PROP(TypeTag, JacobianMatrix)::MatrixBlockLowDim; + + using namespace Dune::Indices; + const AType& A = M[_0][_0]; + const BType& B = M[_0][_1]; + const CType& C = M[_1][_0]; + const DType& D = M[_1][_1]; + + // Solver to compute applications of D^-1 used in the preconditioner + // We construct it here so e.g. LU decomposition is only done once + using InnerSolver = Dune::UMFPack<DType>; + auto invD = std::make_shared<InnerSolver>(D, false); + + SchurComplementPreconditioner<TypeTag, AType, BType, CType, DType, InnerSolver, Vector, Vector> preconditioner(A, B, C, D, invD); + + Dune::MatrixAdapter<Matrix, Vector, Vector> op(M); + Dune::RestartedfGMResSolver<Vector> solver(op, preconditioner, reduction, restartGMRes, maxIter, verbosity); + auto bTmp(b); + solver.apply(x, bTmp, result_); + + return true; + } + + const Dune::InverseOperatorResult& result() const + { + return result_; + } + +private: + Dune::InverseOperatorResult result_; +}; + + +template<class FirstBlock, class FirstX, class FirstY, + class SecondBlock, class SecondX, class SecondY, + class X, class Y> +class BlockILU0Preconditioner : public Dune::Preconditioner<X, Y> +{ +public: + //! \brief The matrix type the preconditioner is for. + // typedef typename std::remove_const<M>::type matrix_type; + //! \brief The domain type of the preconditioner. + typedef X domain_type; + //! \brief The range type of the preconditioner. + typedef Y range_type; + //! \brief The field type of the preconditioner. + typedef typename X::field_type field_type; + + /*! \brief Constructor. + + Constructor gets all parameters to operate the prec. + \param A The matrix to operate on. + \param w The relaxation factor. + */ + BlockILU0Preconditioner (const FirstBlock& A, const SecondBlock& B) + : firstILU_(A, 1.0), secondILU_(B, 1.0) // does ILU decomposition + {} + + /*! + \brief Prepare the preconditioner. + + \copydoc Preconditioner::pre(X&,Y&) + */ + virtual void pre (X& x, Y& b) + { + DUNE_UNUSED_PARAMETER(x); + DUNE_UNUSED_PARAMETER(b); + } + + /*! + \brief Apply the preconditoner. + + \copydoc Preconditioner::apply(X&,const Y&) + */ + virtual void apply (X& v, const Y& d) + { + using namespace Dune::Indices; + firstILU_.apply(v[_0], d[_0]); + secondILU_.apply(v[_1], d[_1]); + } + + /*! + \brief Clean up. + + \copydoc Preconditioner::post(X&) + */ + virtual void post (X& x) + { + DUNE_UNUSED_PARAMETER(x); + } + + //! Category of the preconditioner (see SolverCategory::Category) + virtual Dune::SolverCategory::Category category() const + { + return Dune::SolverCategory::sequential; + } + + private: + Dune::SeqILU0<FirstBlock, FirstX, FirstY> firstILU_; + Dune::SeqILU0<SecondBlock, SecondX, SecondY> secondILU_; +}; + +template <class TypeTag> +class BlockILU0BiCGSTABSolver +{ + using Problem = typename GET_PROP_TYPE(TypeTag, Problem); + +public: + BlockILU0BiCGSTABSolver() = default; + BlockILU0BiCGSTABSolver(const Problem& problem) {} + + // expects a system as a multi-type matrix + // | A B | + // | C D | + + // Solve saddle-point problem using a Schur complement based preconditioner + template<class Matrix, class Vector> + bool solve(const Matrix& M, Vector& x, const Vector& b) + { + int verbosity = GET_PARAM_FROM_GROUP(TypeTag, int, LinearSolver, Verbosity); + static const double reduction = GET_PARAM_FROM_GROUP(TypeTag, double, LinearSolver, ResidualReduction); + static const double maxIter = GET_PARAM_FROM_GROUP(TypeTag, double, LinearSolver, MaxIterations); + + using AType = typename GET_PROP(TypeTag, JacobianMatrix)::MatrixBlockBulk; + using DType = typename GET_PROP(TypeTag, JacobianMatrix)::MatrixBlockLowDim; + + using BulkVector = typename GET_PROP(TypeTag, SolutionVector)::SolutionVectorBulk; + using EmbeddedVector = typename GET_PROP(TypeTag, SolutionVector)::SolutionVectorLowDim; + + using namespace Dune::Indices; + const AType& A = M[_0][_0]; + const DType& D = M[_1][_1]; + + // Solver to compute applications of D^-1 used in the preconditioner + // We construct it here so e.g. LU decomposition is only done once + using InnerSolver = Dune::UMFPack<DType>; + auto invD = std::make_shared<InnerSolver>(D, false); + + BlockILU0Preconditioner<AType, BulkVector, BulkVector, DType, EmbeddedVector, EmbeddedVector, Vector, Vector> preconditioner(A, D); + Dune::MatrixAdapter<Matrix, Vector, Vector> op(M); + Dune::BiCGSTABSolver<Vector> solver(op, preconditioner, reduction, maxIter, verbosity); + auto bTmp(b); + solver.apply(x, bTmp, result_); + + return result_.converged; + } + + const Dune::InverseOperatorResult& result() const + { + return result_; + } + +private: + Dune::InverseOperatorResult result_; +}; + +} // end namespace Dumux + +#endif diff --git a/test/mixeddimension/embedded/1p2c_richards2c/start.hh b/test/mixeddimension/embedded/1p2c_richards2c/start.hh index 3bebc455759c21f0b55ce3ebff3a20ab87eb3b16..8ed49d6955440fef0b653cdd7d8d600e80e3e0b9 100644 --- a/test/mixeddimension/embedded/1p2c_richards2c/start.hh +++ b/test/mixeddimension/embedded/1p2c_richards2c/start.hh @@ -150,8 +150,8 @@ int start_(int argc, TimeManager timeManager; Problem problem(timeManager, bulkGridView, lowDimGridView); - // locally refine levels deep around the embedded grid - int levels = 2; + //locally refine levels deep around the embedded grid + int levels = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, int, SoilGrid, LocalRefinement); for (int i = 0; i < levels; ++i) { CCMultiDimensionGlue<TypeTag> glue(problem.bulkProblem(), problem.lowDimProblem()); @@ -159,8 +159,49 @@ int start_(int argc, // refine all 3D cells intersected for (const auto& is : intersections(glue)) + { for (unsigned int outsideIdx = 0; outsideIdx < is.neighbor(0); ++outsideIdx) - BulkGridCreator::grid().mark(1, is.outside(outsideIdx)); + { + const auto cutElement = is.outside(outsideIdx); + + // mark the cut element and all it's neighbors + BulkGridCreator::grid().mark(1, cutElement); + for (const auto& intersection : intersections(bulkGridView, cutElement)) + if (intersection.neighbor()) + BulkGridCreator::grid().mark(1, intersection.outside()); + } + + } + + // refine all 3D cells that are where the contamination is + for (const auto& element : elements(bulkGridView)) + { + const auto globalPos = element.geometry().center(); + auto contaminationPos = problem.bulkProblem().bBoxMax()-problem.bulkProblem().bBoxMin(); + contaminationPos[0] *= 0.25; + contaminationPos[1] *= 0.55; + contaminationPos[2] *= 0.25; + contaminationPos += problem.bulkProblem().bBoxMin(); + + static const Scalar extend = 0.15*(problem.bulkProblem().bBoxMax()[0]-problem.bulkProblem().bBoxMin()[0]); + if ((globalPos - contaminationPos).infinity_norm() < extend + 1e-7) + BulkGridCreator::grid().mark(1, element); + } + + BulkGridCreator::grid().preAdapt(); + BulkGridCreator::grid().adapt(); + BulkGridCreator::grid().postAdapt(); + + // make sure there is only one level difference + for (const auto& element : elements(bulkGridView)) + { + for (const auto& intersection : intersections(bulkGridView, element)) + { + if (intersection.neighbor()) + if (intersection.outside().level()-1 > element.level()) + BulkGridCreator::grid().mark(1, element); + } + } BulkGridCreator::grid().preAdapt(); BulkGridCreator::grid().adapt(); diff --git a/test/mixeddimension/embedded/1p2c_richards2c/test_rosi2c.input b/test/mixeddimension/embedded/1p2c_richards2c/test_rosi2c.input index 07c683890fccd812d81cc9a086bdd8e4f599c539..a2016460e23ac89ef95148009a1a8e87cdde9618 100644 --- a/test/mixeddimension/embedded/1p2c_richards2c/test_rosi2c.input +++ b/test/mixeddimension/embedded/1p2c_richards2c/test_rosi2c.input @@ -8,21 +8,23 @@ EpisodeTime = 8640 # [s] [Grid] File = ./grids/lupine.dgf -Refinement = 0 +Refinement = 1 [SoilGrid] LowerLeft = -0.05 -0.05 -0.1 UpperRight = 0.05 0.05 0 -Cells = 15 15 15 +Cells = 10 10 10 ClosureType = None +Refinement = 0 +LocalRefinement = 0 [Problem] Name = rosi2c -ContaminantMoleFraction = 1e-6 +ContaminantMoleFraction = 3e-7 MolarMass = 20e-3 # in kg/mol, molar mass heavy water D2O [Component] -LiquidDiffusionCoefficient = 1e-9 +LiquidDiffusionCoefficient = 2.3e-9 Name = D2O [SpatialParams]