From 234fe48aa952dee790ae55b9be89a8bcff1c7125 Mon Sep 17 00:00:00 2001 From: DennisGlaeser <dennis.glaeser@iws.uni-stuttgart.de> Date: Wed, 6 Dec 2017 18:19:05 +0100 Subject: [PATCH] [2p] introduce twop adaptive grid framework and test --- .../2p/implicit/adaptionhelper.hh | 454 ------------------ .../2p/implicit/gridadaptindicator.hh | 319 ++++++------ .../2p/implicit/griddatatransfer.hh | 419 ++++++++++++++++ .../2p/implicit/CMakeLists.txt | 1 + .../2p/implicit/adaptive/CMakeLists.txt | 36 ++ .../implicit/adaptive/test_2p_adaptive.input | 25 + .../implicit/adaptive/test_2p_adaptive_fv.cc | 265 ++++++++++ 7 files changed, 912 insertions(+), 607 deletions(-) delete mode 100644 dumux/porousmediumflow/2p/implicit/adaptionhelper.hh create mode 100644 dumux/porousmediumflow/2p/implicit/griddatatransfer.hh create mode 100644 test/porousmediumflow/2p/implicit/adaptive/CMakeLists.txt create mode 100644 test/porousmediumflow/2p/implicit/adaptive/test_2p_adaptive.input create mode 100644 test/porousmediumflow/2p/implicit/adaptive/test_2p_adaptive_fv.cc diff --git a/dumux/porousmediumflow/2p/implicit/adaptionhelper.hh b/dumux/porousmediumflow/2p/implicit/adaptionhelper.hh deleted file mode 100644 index 181ba0af5d..0000000000 --- a/dumux/porousmediumflow/2p/implicit/adaptionhelper.hh +++ /dev/null @@ -1,454 +0,0 @@ -// -*- 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/>. * - *****************************************************************************/ -#ifndef DUMUX_TWOP_ADAPTIONHELPER_HH -#define DUMUX_TWOP_ADAPTIONHELPER_HH - -#include <dumux/common/properties.hh> -#include <dumux/implicit/adaptive/adaptionhelper.hh> - -namespace Dumux { - -/*! - * \brief Base class holding the variables for implicit models. - */ -template<class TypeTag> -class TwoPAdaptionHelper : public ImplicitAdaptionHelper<TypeTag> -{ -private: - using ParentType = ImplicitAdaptionHelper<TypeTag>; - using Problem = typename GET_PROP_TYPE(TypeTag, Problem); - using GridView = typename GET_PROP_TYPE(TypeTag, GridView); - using Indices = typename GET_PROP_TYPE(TypeTag, Indices); - using PrimaryVariables = typename GET_PROP_TYPE(TypeTag, PrimaryVariables); - using ElementSolutionVector = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector); - using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables); - using FVElementGeometry = typename GET_PROP_TYPE(TypeTag, FVElementGeometry); - using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume); - using FluidSystem = typename GET_PROP_TYPE(TypeTag, FluidSystem); - using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar); - - enum { - // Grid and world dimension - dim = GridView::dimension, - dimWorld = GridView::dimensionworld, - - //indices - pressureIdx = Indices::pressureIdx, - saturationIdx = Indices::saturationIdx, - pwsn = Indices::pwsn, - pnsw = Indices::pnsw, - formulation = GET_PROP_VALUE(TypeTag, Formulation), - wPhaseIdx = Indices::wPhaseIdx, - nPhaseIdx = Indices::nPhaseIdx - }; - - enum { isBox = GET_PROP_VALUE(TypeTag, ImplicitIsBox) }; - enum { dofCodim = isBox ? dim : 0 }; - - using Grid = typename GridView::Grid; - using Element = typename GridView::Traits::template Codim<0>::Entity; - - using GlobalPosition = Dune::FieldVector<Scalar, dimWorld>; - using CoordScalar = typename GridView::ctype; - using LocalPosition = Dune::FieldVector<CoordScalar, dim>; - - using LocalFiniteElementCache = Dune::PQkLocalFiniteElementCache<CoordScalar, Scalar, dim, 1>; - using LocalFiniteElement = typename LocalFiniteElementCache::FiniteElementType; - - struct AdaptedValues - { - ElementSolutionVector u; - int count; - PrimaryVariables associatedMass; - AdaptedValues(): count(0), associatedMass(0.0) {} - }; - - using PersistentContainer = Dune::PersistentContainer<Grid, AdaptedValues>; - PersistentContainer adaptionMap_; - -public: - //! Constructs an adaption helper object - /** - * @param gridView a DUNE gridview object - */ - TwoPAdaptionHelper(Problem& problem) : ParentType(problem), adaptionMap_(problem.grid(), 0) - { - if(FluidSystem::isCompressible(wPhaseIdx) || FluidSystem::isCompressible(nPhaseIdx)) - DUNE_THROW(Dune::InvalidStateException, "This adaption helper is only mass conservative for incompressible fluids!"); - } - - /*! - * Store primary variables - * - * To reconstruct the solution in father elements, problem properties might - * need to be accessed. - * From upper level on downwards, the old solution is stored into an container - * object, before the grid is adapted. Father elements hold averaged information - * from the son cells for the case of the sons being coarsened. - * - * @param problem The current problem - */ - void storePrimVars(Problem& problem) - { - adaptionMap_.resize(); - - // loop over all levels of the grid - for (int level = problem.grid().maxLevel(); level >= 0; level--) - { - // get grid view on level grid - auto levelView = problem.grid().levelGridView(level); - - for (const auto& element : elements(levelView)) - { - //get your map entry - auto& adaptedValues = adaptionMap_[element]; - - // put values in the map - if (element.isLeaf()) - { - auto fvGeometry = localView(problem.model().globalFvGeometry()); - fvGeometry.bindElement(element); - - auto elemVolVars = localView(problem.model().curGlobalVolVars()); - elemVolVars.bindElement(element, fvGeometry, problem.model().curSol()); - - for (auto&& scv : scvs(fvGeometry)) - { - adaptedValues.u = problem.model().elementSolution(element, problem.model().curSol()); - - VolumeVariables volVars; - volVars.update(adaptedValues.u, problem, element, scv); - - adaptedValues.associatedMass[nPhaseIdx] += scv.volume() * volVars.density(nPhaseIdx) - * volVars.porosity() * volVars.saturation(nPhaseIdx); - adaptedValues.associatedMass[wPhaseIdx] += scv.volume() * volVars.density(wPhaseIdx) - * volVars.porosity() * volVars.saturation(wPhaseIdx); - } - adaptedValues.count = 1; - } - // Average in father - if (element.level() > 0) - { - auto& adaptedValuesFather = adaptionMap_[element.father()]; - // For some grids the father element is identical to the son element. - // For that case averaging is not necessary. - if(&adaptedValues != &adaptedValuesFather) - { - adaptedValuesFather.count += 1; - storeAdaptionValues(adaptedValues, adaptedValuesFather); - } - } - - if(isBox && !element.isLeaf()) - { - adaptedValues.u = problem.model().elementSolution(element, problem.model().curSol()); - } - } - } - } - - /*! - * Reconstruct missing primary variables (where elements are created/deleted) - * - * To reconstruct the solution in father elements, problem properties might - * need to be accessed. - * Starting from the lowest level, the old solution is mapped on the new grid: - * Where coarsened, new cells get information from old father element. - * Where refined, a new solution is reconstructed from the old father cell, - * and then a new son is created. That is then stored into the general data - * structure (CellData). - * - * @param problem The current problem - */ - void reconstructPrimVars(Problem& problem) - { - adaptionMap_.resize(); - // vectors storing the mass associated with each vertex, when using the box method - std::vector<Scalar> massCoeff; - std::vector<Scalar> associatedMass; - - if(isBox) - { - massCoeff.resize(problem.model().numDofs(), 0.0); - associatedMass.resize(problem.model().numDofs(), 0.0); - } - - for (int level = 0; level <= problem.grid().maxLevel(); level++) - { - auto levelView = problem.grid().levelGridView(level); - - for (const auto& element : elements(levelView)) - { - // only treat non-ghosts, ghost data is communicated afterwards - if (element.partitionType() == Dune::GhostEntity) - continue; - - if (!element.isNew() || element.level() == 0) - { - // entry is in map, write in leaf - if (element.isLeaf()) - { - auto& adaptedValues = adaptionMap_[element]; - - auto fvGeometry = localView(problem.model().globalFvGeometry()); - fvGeometry.bindElement(element); - - auto elementVolume = element.geometry().volume(); - - for (auto&& scv : scvs(fvGeometry)) - { - VolumeVariables volVars; - volVars.update(adaptedValues.u, problem, element, scv); - - problem.model().curSol()[scv.dofIndex()] = getPriVars(adaptedValues, scv); - - if (formulation == pwsn) - { - problem.model().curSol()[scv.dofIndex()][saturationIdx] = adaptedValues.associatedMass[nPhaseIdx]; - problem.model().curSol()[scv.dofIndex()][saturationIdx] /= elementVolume * volVars.density(nPhaseIdx) * volVars.porosity(); - } - else if (formulation == pnsw) - { - problem.model().curSol()[scv.dofIndex()][saturationIdx] = adaptedValues.associatedMass[wPhaseIdx]; - problem.model().curSol()[scv.dofIndex()][saturationIdx] /= elementVolume * volVars.density(wPhaseIdx) * volVars.porosity(); - } - - if(isBox) - { - if (int(formulation) == pwsn) - { - massCoeff[scv.dofIndex()] += scv.volume() * volVars.density(nPhaseIdx) * volVars.porosity(); - associatedMass[scv.dofIndex()] += scv.volume() / elementVolume * adaptedValues.associatedMass[nPhaseIdx]; - } - else if (int(formulation) == pnsw) - { - massCoeff[scv.dofIndex()] += scv.volume() * volVars.density(wPhaseIdx) * volVars.porosity(); - associatedMass[scv.dofIndex()] += scv.volume() / elementVolume * adaptedValues.associatedMass[wPhaseIdx]; - } - } - - } - - } - } - else - { - // value is not in map, interpolate from father element - if (element.hasFather()) - { - auto fatherElement = element.father(); - while(fatherElement.isNew() && fatherElement.level() > 0) - fatherElement = fatherElement.father(); - - Scalar massFather = 0.0; - - if(!isBox) - { - auto& adaptedValuesFather = adaptionMap_[fatherElement]; - - if (formulation == pwsn) - massFather = adaptedValuesFather.associatedMass[nPhaseIdx]; - - else if (formulation == pnsw) - massFather = adaptedValuesFather.associatedMass[wPhaseIdx]; - - // access new son - auto& adaptedValues = adaptionMap_[element]; - adaptedValues.count = 1; - - auto fvGeometry = localView(problem.model().globalFvGeometry()); - fvGeometry.bindElement(element); - - adaptedValues.u = adaptedValuesFather.u; - - for (auto&& scv : scvs(fvGeometry)) - { - VolumeVariables volVars; - volVars.update(adaptedValues.u, problem, element, scv); - - Scalar massCoeffSon = 0.0; - if (int(formulation) == pwsn) - massCoeffSon = scv.volume() * volVars.density(nPhaseIdx) * volVars.porosity(); - - else if (int(formulation) == pnsw) - massCoeffSon = scv.volume() * volVars.density(wPhaseIdx) * volVars.porosity(); - - auto fatherElementVolume = fatherElement.geometry().volume(); - adaptedValues.u[0][saturationIdx] = (scv.volume() / fatherElementVolume * massFather)/massCoeffSon; - } - - // if we are on leaf, store reconstructed values of son in CellData object - if (element.isLeaf()) - { - // access new CellData object - auto newIdxI = problem.elementMapper().index(element); - problem.model().curSol()[newIdxI] = adaptedValues.u[0]; - } - } - else - { - // auto& adaptedValuesFather = adaptionMap_[fatherElement]; - // // access new son - // auto& adaptedValues = adaptionMap_[element]; - // adaptedValues.u.clear(); - // adaptedValues.count = 1; - // - // const auto geometryI = element.geometry(); - // - // FVElementGeometry fvGeometry; - // fvGeometry.update(problem.gridView(), element); - // - // for (int scvIdx = 0; scvIdx < fvGeometry.numScv; ++scvIdx) - // { - // auto subEntity = element.template subEntity <dofCodim>(scvIdx); - // - // LocalPosition dofCenterPos = geometryI.local(subEntity.geometry().center()); - // const LocalFiniteElementCache feCache; - // Dune::GeometryType geomType = fatherElement.geometry().type(); - // - // // Interpolate values from father element by using ansatz functions - // const LocalFiniteElement &localFiniteElement = feCache.get(geomType); - // std::vector<Dune::FieldVector<Scalar, 1> > shapeVal; - // localFiniteElement.localBasis().evaluateFunction(dofCenterPos, shapeVal); - // PrimaryVariables u(0); - // for (int j = 0; j < shapeVal.size(); ++j) - // { - // u.axpy(shapeVal[j], adaptedValuesFather.u[j]); - // } - // - // adaptedValues.u.push_back(u); - // adaptedValues.count = 1; - // - // if (element.isLeaf()) - // { - // VolumeVariables volVars; - // volVars.update(adaptedValues.u[scvIdx], - // problem, - // element, - // fvGeometry, - // scvIdx, - // false); - // - // Scalar volume = fvGeometry.subContVol[scvIdx].volume; - // Scalar fatherElementVolume = fatherElement.geometry().volume(); - // - // int dofIdxGlobal = this->dofIndex(problem, element, scvIdx); - // if (int(formulation) == pwsn) - // { - // massCoeff[dofIdxGlobal] += volume * volVars.density(nPhaseIdx) * volVars.porosity(); - // associatedMass[dofIdxGlobal] += volume/fatherElementVolume*adaptedValuesFather.associatedMass[nPhaseIdx]; - // } - // else if (int(formulation) == pnsw) - // { - // massCoeff[dofIdxGlobal] += volume * volVars.density(wPhaseIdx) * volVars.porosity(); - // associatedMass[dofIdxGlobal] += volume/fatherElementVolume*adaptedValuesFather.associatedMass[wPhaseIdx]; - // } - // - // this->setAdaptionValues(adaptedValues, problem.model().curSol()[dofIdxGlobal],scvIdx); - // } - // - // } - } - } - else - { - DUNE_THROW(Dune::InvalidStateException, "Element is new but has no father element!"); - } - - } - } - - } - - if(isBox) - { - for(int dofIdxGlobal = 0; dofIdxGlobal < problem.model().numDofs(); dofIdxGlobal++) - problem.model().curSol()[dofIdxGlobal][saturationIdx] = associatedMass[dofIdxGlobal] / massCoeff[dofIdxGlobal]; - } - - // reset entries in restrictionmap - adaptionMap_.resize( typename PersistentContainer::Value() ); - adaptionMap_.shrinkToFit(); - adaptionMap_.fill( typename PersistentContainer::Value() ); - -//#if HAVE_MPI -// // communicate ghost data -// typedef typename GET_PROP(TypeTag, SolutionTypes) SolutionTypes; -// typedef typename SolutionTypes::ElementMapper ElementMapper; -// typedef VectorExchange<ElementMapper, std::vector<CellData> > DataHandle; -// DataHandle dataHandle(problem.elementMapper(), this->cellDataGlobal()); -// problem.gridView().template communicate<DataHandle>(dataHandle, -// Dune::InteriorBorder_All_Interface, -// Dune::ForwardCommunication); -//#endif - } - - //! Stores sons entries into father element for averaging - /** - * Sum up the adaptedValues (sons values) into father element. We store from leaf - * upwards, so sons are stored first, then cells on the next leaf (=fathers) - * can be averaged. - * - * \param adaptedValues Container for model-specific values to be adapted - * \param adaptedValuesFather Values to be adapted of father cell - */ - static void storeAdaptionValues(AdaptedValues& adaptedValues, - AdaptedValues& adaptedValuesFather) - { - if(!isBox) - { - if(adaptedValuesFather.u.size() == 0) - adaptedValuesFather.u.resize(1); - - adaptedValuesFather.u[0] += adaptedValues.u[0]; - adaptedValuesFather.u[0] /= adaptedValues.count; - adaptedValuesFather.associatedMass += adaptedValues.associatedMass; - } - else - { - adaptedValuesFather.associatedMass += adaptedValues.associatedMass; - } - } - //! Set adapted values in CellData - /** - * This methods stores reconstructed values into the cellData object, by - * this setting a newly mapped solution to the storage container of the - * sequential models. - * - * \param adaptedValues Container for model-specific values to be adapted - * \param u The variables to be stored - */ - static PrimaryVariables getPriVars(const AdaptedValues& adaptedValues, const SubControlVolume& scv) - { - if(!isBox) - { - auto uNew = adaptedValues.u[0]; - uNew /= adaptedValues.count; - return uNew; - } - else - { - return adaptedValues.u[scv.index()]; - } - } -}; - -} // end namespace Dumux - -#endif diff --git a/dumux/porousmediumflow/2p/implicit/gridadaptindicator.hh b/dumux/porousmediumflow/2p/implicit/gridadaptindicator.hh index 5d7ac8b703..23e970d71d 100644 --- a/dumux/porousmediumflow/2p/implicit/gridadaptindicator.hh +++ b/dumux/porousmediumflow/2p/implicit/gridadaptindicator.hh @@ -16,170 +16,161 @@ * You should have received a copy of the GNU General Public License * * along with this program. If not, see <http://www.gnu.org/licenses/>. * *****************************************************************************/ -#ifndef DUMUX_IMPLICIT_GRIDADAPTINDICATOR2P_HH -#define DUMUX_IMPLICIT_GRIDADAPTINDICATOR2P_HH +#ifndef DUMUX_TWOP_ADAPTION_INDICATOR_HH +#define DUMUX_TWOP_ADAPTION_INDICATOR_HH + +#include <dune/common/exceptions.hh> #include <dumux/common/properties.hh> -#include <dune/localfunctions/lagrange/pqkfactory.hh> -//#include <dumux/linear/vectorexchange.hh> +#include <dumux/discretization/evalsolution.hh> /** - * @file - * @brief Class defining a standard, saturation dependent indicator for grid adaptation + * \file + * \brief Class defining a standard, saturation dependent indicator for grid adaptation */ namespace Dumux { -/*!\ingroup IMPES - * @brief Class defining a standard, saturation dependent indicator for grid adaptation - * - * \tparam TypeTag The problem TypeTag + +/*!\ingroup TwoPModel + * \brief Class defining a standard, saturation dependent indicator for grid adaptation */ template<class TypeTag> -class TwoPImplicitGridAdaptIndicator +class TwoPGridAdaptIndicator { -private: - typedef typename GET_PROP_TYPE(TypeTag, Problem) Problem; - typedef typename GET_PROP_TYPE(TypeTag, GridView) GridView; - typedef typename GET_PROP_TYPE(TypeTag, Scalar) Scalar; - typedef typename GridView::Traits::template Codim<0>::Entity Element; + using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry); + using GridView = typename GET_PROP_TYPE(TypeTag, GridView); + using Element = typename GridView::template Codim<0>::Entity; + using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar); + using Indices = typename GET_PROP_TYPE(TypeTag, Indices); + using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector); + using ElementSolution = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector); - typedef typename GET_PROP_TYPE(TypeTag, Indices) Indices; + enum { saturationIdx = Indices::saturationIdx }; - enum - { - saturationIdx = Indices::saturationIdx, - pressureIdx = Indices::pressureIdx - }; - enum +public: + /*! \brief The Constructor + * + * \param fvGridGeometry The finite volume grid geometry + * + * Note: refineBound_, coarsenBound_ & maxSaturationDelta_ are chosen + * in a way such that the indicator returns false for all elements + * before having been calculated. + */ + TwoPGridAdaptIndicator(std::shared_ptr<const FVGridGeometry> fvGridGeometry) + : fvGridGeometry_(fvGridGeometry) + , refineBound_(std::numeric_limits<Scalar>::max()) + , coarsenBound_(std::numeric_limits<Scalar>::lowest()) + , maxSaturationDelta_(fvGridGeometry_->gridView().size(0), 0.0) + , minLevel_(getParamFromGroup<std::size_t>(GET_PROP_VALUE(TypeTag, ModelParameterGroup), "Adaptive.MinLevel", 0)) + , maxLevel_(getParamFromGroup<std::size_t>(GET_PROP_VALUE(TypeTag, ModelParameterGroup), "Adaptive.MaxLevel", 0)) + {} + + /*! \brief Function to set the minumum allowed levels. + *. + */ + void setMinLevel(std::size_t minLevel) { - wPhaseIdx = Indices::wPhaseIdx, - nPhaseIdx = Indices::nPhaseIdx - }; - - enum { - // Grid and world dimension - dim = GridView::dimension, - dimWorld = GridView::dimensionworld - }; - - enum { isBox = GET_PROP_VALUE(TypeTag, ImplicitIsBox) }; - enum { dofCodim = isBox ? dim : 0 }; + minLevel_ = minLevel; + } - typedef Dune::FieldVector<Scalar, dimWorld> GlobalPosition; - typedef typename GridView::ctype CoordScalar; + /*! \brief Function to set the maximum allowed levels. + *. + */ + void setMaxLevel(std::size_t maxLevel) + { + maxLevel_ = maxLevel; + } - typedef Dune::PQkLocalFiniteElementCache<CoordScalar, Scalar, dim, 1> LocalFiniteElementCache; - typedef typename LocalFiniteElementCache::FiniteElementType LocalFiniteElement; + /*! \brief Function to set the minumum/maximum allowed levels. + *. + */ + void setLevels(std::size_t minLevel, std::size_t maxLevel) + { + minLevel_ = minLevel; + maxLevel_ = maxLevel; + } -public: /*! \brief Calculates the indicator used for refinement/coarsening for each grid cell. * - * This standard indicator is based on the saturation gradient. + * This standard two-phase indicator is based on the saturation gradient. */ - void calculateIndicator() + void calculate(const SolutionVector& sol, + Scalar refineTol = 0.05, + Scalar coarsenTol = 0.001) { - // prepare an indicator for refinement - if(indicatorVector_.size() != problem_.gridView().size(0)) - { - indicatorVector_.resize(problem_.gridView().size(0)); - } - indicatorVector_ = -1e100; + //! reset the indicator to a state that returns false for all elements + refineBound_ = std::numeric_limits<Scalar>::max(); + coarsenBound_ = std::numeric_limits<Scalar>::lowest(); + maxSaturationDelta_.assign(fvGridGeometry_->gridView().size(0), 0.0); - Scalar globalMax = -1e100; - Scalar globalMin = 1e100; + //! maxLevel_ must be higher than minLevel_ to allow for refinement + if (minLevel_ >= maxLevel_) + return; - // 1) calculate Indicator -> min, maxvalues - // loop over all leaf-elements - for (const auto& element : elements(problem_.gridView())) - { - // calculate minimum and maximum saturation - // index of the current leaf-elements - int globalIdxI = problem_.elementMapper().index(element); + //! check for inadmissible tolerance combination + if (coarsenTol > refineTol) + DUNE_THROW(Dune::InvalidStateException, "Refine tolerance must be higher than coarsen tolerance"); - Scalar satI = 0.0; + //! variables to hold the max/mon saturation values on the leaf + Scalar globalMax = std::numeric_limits<Scalar>::lowest(); + Scalar globalMin = std::numeric_limits<Scalar>::max(); - if(!isBox) - satI = problem_.model().curSol()[globalIdxI][saturationIdx]; - else - { - const LocalFiniteElementCache feCache; - const auto geometryI = element.geometry(); - Dune::GeometryType geomType = geometryI.type(); - - GlobalPosition centerI = geometryI.local(geometryI.center()); - const LocalFiniteElement &localFiniteElement = feCache.get(geomType); - std::vector<Dune::FieldVector<Scalar, 1> > shapeVal; - localFiniteElement.localBasis().evaluateFunction(centerI, shapeVal); - - for (int i = 0; i < shapeVal.size(); ++i) - { - int dofIdxGlobal = problem_.model().dofMapper().subIndex(element, i, dofCodim); - satI += shapeVal[i]*problem_.model().curSol()[dofIdxGlobal][saturationIdx]; - } - } + //! Calculate minimum and maximum saturation + for (const auto& element : elements(fvGridGeometry_->gridView())) + { + //! index of the current leaf-element + const auto globalIdxI = fvGridGeometry_->elementMapper().index(element); + + //! obtain the saturation at the center of the element + const auto geometry = element.geometry(); + const ElementSolution elemSol(element, sol, *fvGridGeometry_); + const Scalar satI = evalSolution(element, geometry, *fvGridGeometry_, elemSol, geometry.center())[saturationIdx]; + //! maybe update the global minimum/maximum using std::min; using std::max; globalMin = min(satI, globalMin); globalMax = max(satI, globalMax); - // calculate refinement indicator in all cells - for (const auto& intersection : intersections(problem_.gridView(), element)) + //! calculate maximum delta in saturation for this cell + for (const auto& intersection : intersections(fvGridGeometry_->gridView(), element)) { - // Only consider internal intersections + //! Only consider internal intersections if (intersection.neighbor()) { - // Access neighbor - auto outside = intersection.outside(); - int globalIdxJ = problem_.elementMapper().index(outside); + //! Access neighbor + const auto outside = intersection.outside(); + const auto globalIdxJ = fvGridGeometry_->elementMapper().index(outside); - // Visit intersection only once + //! Visit intersection only once if (element.level() > outside.level() || (element.level() == outside.level() && globalIdxI < globalIdxJ)) { - Scalar satJ = 0.0; - - if(!isBox) - satJ = problem_.model().curSol()[globalIdxJ][saturationIdx]; - else - { - const LocalFiniteElementCache feCache; - const auto geometryJ = outside.geometry(); - Dune::GeometryType geomType = geometryJ.type(); - - GlobalPosition centerJ = geometryJ.local(geometryJ.center()); - const LocalFiniteElement &localFiniteElement = feCache.get(geomType); - std::vector<Dune::FieldVector<Scalar, 1> > shapeVal; - localFiniteElement.localBasis().evaluateFunction(centerJ, shapeVal); - - for (int i = 0; i < shapeVal.size(); ++i) - { - int dofIdxGlobal = problem_.model().dofMapper().subIndex(outside, i, dofCodim); - - satJ += shapeVal[i]*problem_.model().curSol()[dofIdxGlobal][saturationIdx]; - } - } - - + //! obtain saturation in the neighbor + const auto outsideGeometry = outside.geometry(); + const ElementSolution elemSolJ(outside, sol, *fvGridGeometry_); + const Scalar satJ = evalSolution(outside, outsideGeometry, *fvGridGeometry_, elemSolJ, outsideGeometry.center())[saturationIdx]; using std::abs; Scalar localdelta = abs(satI - satJ); - using std::max; - indicatorVector_[globalIdxI][0] = max(indicatorVector_[globalIdxI][0], localdelta); - indicatorVector_[globalIdxJ][0] = max(indicatorVector_[globalIdxJ][0], localdelta); + maxSaturationDelta_[globalIdxI] = max(maxSaturationDelta_[globalIdxI], localdelta); + maxSaturationDelta_[globalIdxJ] = max(maxSaturationDelta_[globalIdxJ], localdelta); } } } } - Scalar globaldelta = globalMax - globalMin; + //! compute the maximum delta in saturation + const auto globalDelta = globalMax - globalMin; - refineBound_ = refinetol_*globaldelta; - coarsenBound_ = coarsentol_*globaldelta; + //! compute the refinement/coarsening bounds + refineBound_ = refineTol*globalDelta; + coarsenBound_ = coarsenTol*globalDelta; +// TODO: fix adaptive simulations in parallel //#if HAVE_MPI // // communicate updated values // typedef VectorExchange<ElementMapper, ScalarSolutionType> DataHandle; -// DataHandle dataHandle(problem_.elementMapper(), indicatorVector_); +// DataHandle dataHandle(problem_.elementMapper(), maxSaturationDelta_); // problem_.gridView().template communicate<DataHandle>(dataHandle, // Dune::InteriorBorder_All_Interface, // Dune::ForwardCommunication); @@ -189,62 +180,84 @@ public: // coarsenBound_ = problem_.gridView().comm().max(coarsenBound_); // //#endif + + //! check if neighbors have to be refined too + for (const auto& element : elements(fvGridGeometry_->gridView(), Dune::Partitions::interior)) + if (this->operator()(element) > 0) + checkNeighborsRefine_(element); } - /*! \brief Indicator function for marking of grid cells for refinement + /*! \brief function call operator to return mark * - * Returns true if an element should be refined. + * \return 1 if an element should be refined + * -1 if an element should be coarsened + * 0 otherwise * * \param element A grid element */ - bool refine(const Element& element) + int operator() (const Element& element) const { - return (indicatorVector_[problem_.elementMapper().index(element)] > refineBound_); + if (element.hasFather() + && maxSaturationDelta_[fvGridGeometry_->elementMapper().index(element)] < coarsenBound_) + { + return -1; + } + else if (element.level() < maxLevel_ + && maxSaturationDelta_[fvGridGeometry_->elementMapper().index(element)] > refineBound_) + { + return 1; + } + else + return 0; } - /*! \brief Indicator function for marking of grid cells for coarsening +private: + /*! + * \brief Method ensuring the refinement ratio of 2:1 * - * Returns true if an element should be coarsened. + * For any given element, a loop over the neighbors checks if the + * entities refinement would require that any of the neighbors has + * to be refined, too. This is done recursively over all levels of the grid. * - * \param element A grid element + * \param element Element of interest that is to be refined + * \param level level of the refined element: it is at least 1 + * \return true if everything was successful */ - bool coarsen(const Element& element) + bool checkNeighborsRefine_(const Element &element, std::size_t level = 1) { - return (indicatorVector_[problem_.elementMapper().index(element)] < coarsenBound_); - } + for(const auto& intersection : intersections(fvGridGeometry_->gridView(), element)) + { + if(!intersection.neighbor()) + continue; - /*! \brief Initializes the adaptation indicator class*/ - void init() - { - refineBound_ = 0.; - coarsenBound_ = 0.; - indicatorVector_.resize(problem_.gridView().size(0)); - }; + // obtain outside element + const auto outside = intersection.outside(); - /*! @brief Constructs a GridAdaptIndicator instance - * - * This standard indicator is based on the saturation gradient. - * It checks the local gradient compared to the maximum global gradient. - * The indicator is compared locally to a refinement/coarsening threshold to decide whether - * a cell should be marked for refinement or coarsening or should not be adapted. - * - * \param problem The problem object - */ - TwoPImplicitGridAdaptIndicator(Problem& problem): - problem_(problem) - { - refinetol_ = GET_PARAM_FROM_GROUP(TypeTag, Scalar, GridAdapt, RefineTolerance); - coarsentol_ = GET_PARAM_FROM_GROUP(TypeTag, Scalar, GridAdapt, CoarsenTolerance); + // only mark non-ghost elements + if (outside.partitionType() == Dune::GhostEntity) + continue; + + if (outside.level() < maxLevel_ && outside.level() < element.level()) + { + // ensure refinement for outside element + maxSaturationDelta_[fvGridGeometry_->elementMapper().index(outside)] = std::numeric_limits<Scalar>::max(); + if(level < maxLevel_) + checkNeighborsRefine_(outside, ++level); + } + } + + return true; } -protected: - Problem& problem_; - Scalar refinetol_; - Scalar coarsentol_; + std::shared_ptr<const FVGridGeometry> fvGridGeometry_; + Scalar refineBound_; Scalar coarsenBound_; - Dune::BlockVector<Dune::FieldVector<Scalar, 1> > indicatorVector_; + std::vector< Scalar > maxSaturationDelta_; + std::size_t minLevel_; + std::size_t maxLevel_; }; -} -#endif +} // end namespace Dumux + +#endif /* DUMUX_TWOP_ADAPTION_INDICATOR_HH */ diff --git a/dumux/porousmediumflow/2p/implicit/griddatatransfer.hh b/dumux/porousmediumflow/2p/implicit/griddatatransfer.hh new file mode 100644 index 0000000000..0d253be92a --- /dev/null +++ b/dumux/porousmediumflow/2p/implicit/griddatatransfer.hh @@ -0,0 +1,419 @@ +// -*- 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/>. * + *****************************************************************************/ +#ifndef DUMUX_TWOP_GRIDDATA_TRANSFER_HH +#define DUMUX_TWOP_GRIDDATA_TRANSFER_HH + +#include <dumux/common/properties.hh> +#include <dumux/discretization/methods.hh> +#include <dumux/adaptive/griddatatransfer.hh> + +namespace Dumux { + +/*! + * \brief Class performing the transfer of data on a grid from before to after adaptation. + */ +template<class TypeTag> +class TwoPGridDataTransfer : public GridDataTransfer +{ + using Grid = typename GET_PROP_TYPE(TypeTag, Grid); + using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar); + using Problem = typename GET_PROP_TYPE(TypeTag, Problem); + using Indices = typename GET_PROP_TYPE(TypeTag, Indices); + using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry); + using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume); + using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables); + using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables); + using PrimaryVariables = typename GET_PROP_TYPE(TypeTag, PrimaryVariables); + using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector); + using ElementSolution = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector); + using FluidSystem = typename GET_PROP_TYPE(TypeTag, FluidSystem); + + struct AdaptedValues + { + ElementSolution u; + int count = 0; + PrimaryVariables associatedMass{0.0}; + bool wasLeaf = false; + }; + + using PersistentContainer = Dune::PersistentContainer<Grid, AdaptedValues>; + + static constexpr int dim = Grid::dimension; + static constexpr int dimWorld = Grid::dimensionworld; + static constexpr bool isBox = GET_PROP_VALUE(TypeTag, DiscretizationMethod) == DiscretizationMethods::Box; + + //! export some indices + enum { + // index of saturation in primary variables + saturationIdx = Indices::saturationIdx, + + // phase indices + wPhaseIdx = Indices::wPhaseIdx, + nPhaseIdx = Indices::nPhaseIdx, + + // formulations + pwsn = Indices::pwsn, + pnsw = Indices::pnsw, + + // the formulation that is actually used + formulation = GET_PROP_VALUE(TypeTag, Formulation) + }; + + //! This won't work (mass conservative) for compressible fluids + static_assert(!FluidSystem::isCompressible(wPhaseIdx) + && !FluidSystem::isCompressible(nPhaseIdx), + "This adaption helper is only mass conservative for incompressible fluids!"); + + //! check if the used formulation is implemented here + static_assert(formulation == pwsn || formulation == pnsw, "Chosen formulation not known to the TwoPGridDataTransfer"); + +public: + /*! \brief Constructor + * + * \param problem The DuMuX problem to be solved + * \param fvGridGeometry The finite volume grid geometry + * \param gridVariables The secondary variables on the grid + * \param sol The solution (primary variables) on the grid + */ + TwoPGridDataTransfer(std::shared_ptr<const Problem> problem, + std::shared_ptr<FVGridGeometry> fvGridGeometry, + std::shared_ptr<const GridVariables> gridVariables, + SolutionVector& sol) + : GridDataTransfer() + , problem_(problem) + , fvGridGeometry_(fvGridGeometry) + , gridVariables_(gridVariables) + , sol_(sol) + , adaptionMap_(fvGridGeometry->gridView().grid(), 0) + {} + + /*! \brief Stores primary variables and additional data + * + * To reconstruct the solution in father elements, problem properties might + * need to be accessed. From upper level on downwards, the old solution is stored + * into a container object, before the grid is adapted. Father elements hold averaged + * information from the son cells for the case of the sons being coarsened. + */ + void store() override + { + adaptionMap_.resize(); + + const auto& grid = fvGridGeometry_->gridView().grid(); + for (auto level = grid.maxLevel(); level >= 0; level--) + { + for (const auto& element : elements(grid.levelGridView(level))) + { + //! get map entry + auto& adaptedValues = adaptionMap_[element]; + + //! put values in the map for leaf elements + if (element.isLeaf()) + { + auto fvGeometry = localView(*fvGridGeometry_); + fvGeometry.bindElement(element); + + //! store current element solution + adaptedValues.u = ElementSolution(element, sol_, *fvGridGeometry_); + + //! compute mass in the scvs + for (const auto& scv : scvs(fvGeometry)) + { + VolumeVariables volVars; + volVars.update(adaptedValues.u, *problem_, element, scv); + + const auto poreVolume = scv.volume()*volVars.porosity(); + adaptedValues.associatedMass[nPhaseIdx] += poreVolume * volVars.density(nPhaseIdx) * volVars.saturation(nPhaseIdx); + adaptedValues.associatedMass[wPhaseIdx] += poreVolume * volVars.density(wPhaseIdx) * volVars.saturation(wPhaseIdx); + } + + //! leaf elements always start with count = 1 + adaptedValues.count = 1; + adaptedValues.wasLeaf = true; + } + //! Average in father elements + if (element.level() > 0) + { + auto& adaptedValuesFather = adaptionMap_[element.father()]; + // For some grids the father element is identical to the son element. + // In that case averaging is not necessary. + if(&adaptedValues != &adaptedValuesFather) + storeAdaptionValues(adaptedValues, adaptedValuesFather); + } + + //! The vertices of the non-leaf elements exist on the leaf as well + //! This element solution constructor uses the vertex mapper to obtain + //! the privars at the vertices, thus, this works for non-leaf elements! + if(isBox && !element.isLeaf()) + adaptedValues.u = ElementSolution(element, sol_, *fvGridGeometry_); + } + } + } + + /*! \brief Reconstruct missing primary variables (where elements are created/deleted) + * + * To reconstruct the solution in father elements, problem properties might + * need to be accessed. + * Starting from the lowest level, the old solution is mapped on the new grid: + * Where coarsened, new cells get information from old father element. + * Where refined, a new solution is reconstructed from the old father cell, + * and then a new son is created. That is then stored into the general data + * structure (AdaptedValues). + */ + void reconstruct() override + { + //! resize stuff (grid might have changed) + adaptionMap_.resize(); + fvGridGeometry_->update(); + sol_.resize(fvGridGeometry_->numDofs()); + + //! vectors storing the mass associated with each vertex, when using the box method + std::vector<Scalar> massCoeff; + std::vector<Scalar> associatedMass; + + if(isBox) + { + massCoeff.resize(fvGridGeometry_->numDofs(), 0.0); + associatedMass.resize(fvGridGeometry_->numDofs(), 0.0); + } + + //! iterate over leaf and reconstruct the solution + for (const auto& element : elements(fvGridGeometry_->gridView().grid().leafGridView(), Dune::Partitions::interior)) + { + if (!element.isNew()) + { + const auto& adaptedValues = adaptionMap_[element]; + + auto fvGeometry = localView(*fvGridGeometry_); + fvGeometry.bindElement(element); + + //! obtain element solution from map (divide by count!) + auto elemSol = adaptedValues.u; + elemSol /= adaptedValues.count; + + const auto elementVolume = element.geometry().volume(); + for (const auto& scv : scvs(fvGeometry)) + { + VolumeVariables volVars; + volVars.update(elemSol, *problem_, element, scv); + + //! write solution at dof in current solution vector + sol_[scv.dofIndex()] = elemSol[scv.indexInElement()]; + + const auto dofIdxGlobal = scv.dofIndex(); + //! For cc schemes, overwrite the saturation by a mass conservative one here + if (!isBox) + { + //! only recalculate the saturations if element hasn't been leaf before adaptation + if (!adaptedValues.wasLeaf) + { + if (formulation == pwsn) + { + sol_[dofIdxGlobal][saturationIdx] = adaptedValues.associatedMass[nPhaseIdx]; + sol_[dofIdxGlobal][saturationIdx] /= elementVolume * volVars.density(nPhaseIdx) * volVars.porosity(); + } + else if (formulation == pnsw) + { + sol_[dofIdxGlobal][saturationIdx] = adaptedValues.associatedMass[wPhaseIdx]; + sol_[dofIdxGlobal][saturationIdx] /= elementVolume * volVars.density(wPhaseIdx) * volVars.porosity(); + } + } + } + + //! For the box scheme, add mass & mass coefficient to container (saturations are recalculated at the end) + else + { + const auto scvVolume = scv.volume(); + if (formulation == pwsn) + { + massCoeff[dofIdxGlobal] += scvVolume * volVars.density(nPhaseIdx) * volVars.porosity(); + associatedMass[dofIdxGlobal] += scvVolume / elementVolume * adaptedValues.associatedMass[nPhaseIdx]; + } + else if (formulation == pnsw) + { + massCoeff[dofIdxGlobal] += scvVolume * volVars.density(wPhaseIdx) * volVars.porosity(); + associatedMass[dofIdxGlobal] += scvVolume / elementVolume * adaptedValues.associatedMass[wPhaseIdx]; + } + } + } + } + else + { + //! value is not in map, interpolate from father element + assert(element.hasFather() && "new element does not have a father element!"); + + //! find the ancestor element that existed on the old grid already + auto fatherElement = element.father(); + while(fatherElement.isNew() && fatherElement.level() > 0) + fatherElement = fatherElement.father(); + + if(!isBox) + { + const auto& adaptedValuesFather = adaptionMap_[fatherElement]; + + //! obtain the mass contained in father + Scalar massFather = 0.0; + if (formulation == pwsn) + massFather = adaptedValuesFather.associatedMass[nPhaseIdx]; + else if (formulation == pnsw) + massFather = adaptedValuesFather.associatedMass[wPhaseIdx]; + + // obtain the element solution through the father + auto elemSolSon = adaptedValuesFather.u; + elemSolSon /= adaptedValuesFather.count; + + auto fvGeometry = localView(*fvGridGeometry_); + fvGeometry.bindElement(element); + + for (const auto& scv : scvs(fvGeometry)) + { + VolumeVariables volVars; + volVars.update(elemSolSon, *problem_, element, scv); + + //! store constructed values of son in the current solution + sol_[scv.dofIndex()] = elemSolSon[0]; + + //! overwrite the saturation by a mass conservative one here + Scalar massCoeffSon = 0.0; + if (formulation == pwsn) + massCoeffSon = scv.volume() * volVars.density(nPhaseIdx) * volVars.porosity(); + else if (formulation == pnsw) + massCoeffSon = scv.volume() * volVars.density(wPhaseIdx) * volVars.porosity(); + sol_[scv.dofIndex()][saturationIdx] = (scv.volume() / fatherElement.geometry().volume() * massFather)/massCoeffSon; + } + } + else + { + auto& adaptedValuesFather = adaptionMap_[fatherElement]; + + auto fvGeometry = localView(*fvGridGeometry_); + fvGeometry.bindElement(element); + + //! interpolate solution in the father to the vertices of the new son + ElementSolution elemSolSon(element, sol_, *fvGridGeometry_); + const auto fatherGeometry = fatherElement.geometry(); + for (const auto& scv : scvs(fvGeometry)) + elemSolSon[scv.indexInElement()] = evalSolution(fatherElement, + fatherGeometry, + adaptedValuesFather.u, + scv.dofPosition()); + + //! compute mass & mass coeffients for the scvs (saturations are recalculated at the end) + const auto fatherElementVolume = fatherGeometry.volume(); + for (const auto& scv : scvs(fvGeometry)) + { + VolumeVariables volVars; + volVars.update(elemSolSon, *problem_, element, scv); + + const auto dofIdxGlobal = scv.dofIndex(); + const auto scvVolume = scv.volume(); //std::cout << "ratio = " << scvVolume / fatherElementVolume << std::endl; + if (int(formulation) == pwsn) + { + massCoeff[dofIdxGlobal] += scvVolume * volVars.density(nPhaseIdx) * volVars.porosity(); + associatedMass[dofIdxGlobal] += scvVolume / fatherElementVolume * adaptedValuesFather.associatedMass[nPhaseIdx]; + } + else if (int(formulation) == pnsw) + { + massCoeff[dofIdxGlobal] += scvVolume * volVars.density(wPhaseIdx) * volVars.porosity(); + associatedMass[dofIdxGlobal] += scvVolume / fatherElementVolume * adaptedValuesFather.associatedMass[wPhaseIdx]; + } + + //! store constructed (pressure) values of son in the current solution (saturation comes later) + sol_[dofIdxGlobal] = elemSolSon[scv.indexInElement()]; + } + } + } + } + + if(isBox) + { + for(std::size_t dofIdxGlobal = 0; dofIdxGlobal < fvGridGeometry_->numDofs(); dofIdxGlobal++) + sol_[dofIdxGlobal][saturationIdx] = associatedMass[dofIdxGlobal] / massCoeff[dofIdxGlobal]; + } + + //! reset entries in adaptation map + adaptionMap_.resize( typename PersistentContainer::Value() ); + adaptionMap_.shrinkToFit(); + adaptionMap_.fill( typename PersistentContainer::Value() ); + +//! TODO: fix adaptive simulations in parallel +//#if HAVE_MPI +// // communicate ghost data +// typedef typename GET_PROP(TypeTag, SolutionTypes) SolutionTypes; +// typedef typename SolutionTypes::ElementMapper ElementMapper; +// typedef VectorExchange<ElementMapper, std::vector<CellData> > DataHandle; +// DataHandle dataHandle(problem.elementMapper(), this->cellDataGlobal()); +// problem.gridView().template communicate<DataHandle>(dataHandle, +// Dune::InteriorBorder_All_Interface, +// Dune::ForwardCommunication); +//#endif + } + + private: + + /*! \brief Stores sons entries into father element for averaging + * + * Sum up the adaptedValues (sons values) into father element. We store from leaf + * upwards, so sons are stored first, then cells on the next leaf (=fathers) + * can be averaged. + * + * \param adaptedValues Container for model-specific values to be adapted + * \param adaptedValuesFather Values to be adapted of father cell + */ + static void storeAdaptionValues(AdaptedValues& adaptedValues, + AdaptedValues& adaptedValuesFather) + { + //! Add associated mass of the child to the one of the father + adaptedValuesFather.associatedMass += adaptedValues.associatedMass; + + if(!isBox) + { + //! add the child's primary variables to the ones of father + //! we have to divide the child's ones in case it was composed + //! of several children as well! + auto values = adaptedValues.u[0]; + values /= adaptedValues.count; + adaptedValuesFather.u[0] += values; + + //! keep track of the number of children that composed this father + adaptedValuesFather.count += 1; + + //! A father element is never leaf + adaptedValuesFather.wasLeaf = false; + } + else + { + //! For the box scheme, scaling of primary variables by count is obsolete + //! Thus, we always want count = 1 + adaptedValuesFather.count = 1; + + //! A father element is never leaf + adaptedValuesFather.wasLeaf = false; + } + } + + std::shared_ptr<const Problem> problem_; + std::shared_ptr<FVGridGeometry> fvGridGeometry_; + std::shared_ptr<const GridVariables> gridVariables_; + SolutionVector& sol_; + PersistentContainer adaptionMap_; +}; + +} // end namespace Dumux + +#endif /* DUMUX_TWOP_GRIDDATA_TRANSFER_HH */ diff --git a/test/porousmediumflow/2p/implicit/CMakeLists.txt b/test/porousmediumflow/2p/implicit/CMakeLists.txt index 1bb24dedd4..aafe2e5b07 100644 --- a/test/porousmediumflow/2p/implicit/CMakeLists.txt +++ b/test/porousmediumflow/2p/implicit/CMakeLists.txt @@ -1,4 +1,5 @@ #add_subdirectory(pointsources) +add_subdirectory(adaptive) add_subdirectory(fracture) add_subdirectory(incompressible) add_subdirectory(nonisothermal) diff --git a/test/porousmediumflow/2p/implicit/adaptive/CMakeLists.txt b/test/porousmediumflow/2p/implicit/adaptive/CMakeLists.txt new file mode 100644 index 0000000000..2fc2d6438d --- /dev/null +++ b/test/porousmediumflow/2p/implicit/adaptive/CMakeLists.txt @@ -0,0 +1,36 @@ +dune_symlink_to_source_files(FILES "test_2p_adaptive.input") + +# using tpfa +dune_add_test(NAME test_2p_adaptive_tpfa + SOURCES test_2p_adaptive_fv.cc + COMPILE_DEFINITIONS TYPETAG=TwoPIncompressibleAdaptiveTpfa + CMAKE_GUARD dune-alugrid_FOUND + COMMAND ${CMAKE_SOURCE_DIR}/bin/testing/runtest.py + CMD_ARGS --script fuzzy + --files ${CMAKE_SOURCE_DIR}/test/references/lensccadaptive-reference.vtu + ${CMAKE_CURRENT_BINARY_DIR}/2p_adaptive_tpfa-00008.vtu + --command "${CMAKE_CURRENT_BINARY_DIR}/test_2p_adaptive_tpfa test_2p_adaptive.input -Problem.Name 2p_adaptive_tpfa") + +# using mpfa +dune_add_test(NAME test_2p_adaptive_mpfa + SOURCES test_2p_adaptive_fv.cc + COMPILE_DEFINITIONS TYPETAG=TwoPIncompressibleAdaptiveMpfa + CMAKE_GUARD dune-alugrid_FOUND + COMMAND ${CMAKE_SOURCE_DIR}/bin/testing/runtest.py + CMD_ARGS --script fuzzy + --files ${CMAKE_SOURCE_DIR}/test/references/lensccadaptive-reference.vtu + ${CMAKE_CURRENT_BINARY_DIR}/2p_adaptive_mpfa-00008.vtu + --command "${CMAKE_CURRENT_BINARY_DIR}/test_2p_adaptive_mpfa test_2p_adaptive.input -Problem.Name 2p_adaptive_mpfa") + +# using box +dune_add_test(NAME test_2p_adaptive_box + SOURCES test_2p_adaptive_fv.cc + COMPILE_DEFINITIONS TYPETAG=TwoPIncompressibleAdaptiveBox + CMAKE_GUARD dune-uggrid_FOUND + COMMAND ${CMAKE_SOURCE_DIR}/bin/testing/runtest.py + CMD_ARGS --script fuzzy + --files ${CMAKE_SOURCE_DIR}/test/references/lensboxadaptive-reference.vtu + ${CMAKE_CURRENT_BINARY_DIR}/2p_adaptive_box-00008.vtu + --command "${CMAKE_CURRENT_BINARY_DIR}/test_2p_adaptive_box test_2p_adaptive.input -Problem.Name 2p_adaptive_box") + +set(CMAKE_BUILD_TYPE Release) diff --git a/test/porousmediumflow/2p/implicit/adaptive/test_2p_adaptive.input b/test/porousmediumflow/2p/implicit/adaptive/test_2p_adaptive.input new file mode 100644 index 0000000000..5e9aa92d80 --- /dev/null +++ b/test/porousmediumflow/2p/implicit/adaptive/test_2p_adaptive.input @@ -0,0 +1,25 @@ +[TimeLoop] +DtInitial = 250 # [s] +TEnd = 3000 # [s] + +[Grid] +UpperRight = 6 4 +Cells = 48 32 +CellType = Cube +ClosureType = Green + +[SpatialParams] +LensLowerLeft = 1.0 2.0 # [m] coordinates of the lower left lens corner +LensUpperRight = 4.0 3.0 # [m] coordinates of the upper right lens corner + +[Problem] +Name = 2padaptive # name passed to the output routines + +[Adaptive] +EnableInitializationIndicator = 1 +RefineAtDirichletBC = 1 +RefineAtFluxBC = 1 +MinLevel = 0 +MaxLevel = 2 +CoarsenTolerance = 1e-4 +RefineTolerance = 1e-4 diff --git a/test/porousmediumflow/2p/implicit/adaptive/test_2p_adaptive_fv.cc b/test/porousmediumflow/2p/implicit/adaptive/test_2p_adaptive_fv.cc new file mode 100644 index 0000000000..8c62d57878 --- /dev/null +++ b/test/porousmediumflow/2p/implicit/adaptive/test_2p_adaptive_fv.cc @@ -0,0 +1,265 @@ +// -*- 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 test for the two-phase porousmedium flow model + */ +#include <config.h> + +#include <ctime> +#include <iostream> + +#include <dune/common/parallel/mpihelper.hh> +#include <dune/common/timer.hh> +#include <dune/grid/io/file/dgfparser/dgfexception.hh> +#include <dune/grid/io/file/vtk.hh> +#include <dune/istl/io.hh> + +//! Use the incompressible problem for this adaptive test +#include <test/porousmediumflow/2p/implicit/incompressible/problem.hh> + +#include <dumux/common/propertysystem.hh> +#include <dumux/common/parameters.hh> +#include <dumux/common/valgrind.hh> +#include <dumux/common/dumuxmessage.hh> +#include <dumux/common/defaultusagemessage.hh> + +#include <dumux/linear/amgbackend.hh> +#include <dumux/nonlinear/newtonmethod.hh> +#include <dumux/nonlinear/newtoncontroller.hh> +#include <dumux/nonlinear/newtonconvergencewriter.hh> + +#include <dumux/assembly/fvassembler.hh> +#include <dumux/assembly/diffmethod.hh> + +#include <dumux/discretization/methods.hh> + +#include <dumux/io/vtkoutputmodule.hh> + +#include <dumux/adaptive/adapt.hh> +#include <dumux/adaptive/markelements.hh> +#include <dumux/porousmediumflow/2p/implicit/griddatatransfer.hh> +#include <dumux/porousmediumflow/2p/implicit/gridadaptindicator.hh> + +//! type tags for the adaptive versions of the two-phase incompressible problem +namespace Dumux { + namespace Properties { + //! Type Tags for the adaptive tests + NEW_TYPE_TAG(TwoPIncompressibleAdaptiveTpfa, INHERITS_FROM(TwoPIncompressibleTpfa)); + NEW_TYPE_TAG(TwoPIncompressibleAdaptiveMpfa, INHERITS_FROM(TwoPIncompressibleMpfa)); + NEW_TYPE_TAG(TwoPIncompressibleAdaptiveBox, INHERITS_FROM(TwoPIncompressibleBox)); + + //! Use non-conforming refinement in the cell-centered tests, conforming for box + SET_TYPE_PROP(TwoPIncompressibleAdaptiveTpfa, Grid, Dune::ALUGrid<2, 2, Dune::cube, Dune::nonconforming>); + SET_TYPE_PROP(TwoPIncompressibleAdaptiveMpfa, Grid, Dune::ALUGrid<2, 2, Dune::cube, Dune::nonconforming>); + SET_TYPE_PROP(TwoPIncompressibleAdaptiveBox, Grid, Dune::UGGrid<2>); + } +} + +int main(int argc, char** argv) try +{ + using namespace Dumux; + + // define the type tag for this problem + using TypeTag = TTAG(TYPETAG); + + // initialize MPI, finalize is done automatically on exit + const auto& mpiHelper = Dune::MPIHelper::instance(argc, argv); + + // print dumux start message + if (mpiHelper.rank() == 0) + DumuxMessage::print(/*firstCall=*/true); + + // parse command line arguments and input file + Parameters::init(argc, argv); + + // try to create a grid (from the given grid file or the input file) + using GridCreator = typename GET_PROP_TYPE(TypeTag, GridCreator); + GridCreator::makeGrid(); + GridCreator::loadBalance(); + + //////////////////////////////////////////////////////////// + // run instationary non-linear problem on this grid + //////////////////////////////////////////////////////////// + + // we compute on the leaf grid view + const auto& leafGridView = GridCreator::grid().leafGridView(); + + // create the finite volume grid geometry + using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry); + auto fvGridGeometry = std::make_shared<FVGridGeometry>(leafGridView); + fvGridGeometry->update(); + + // the problem (initial and boundary conditions) + using Problem = typename GET_PROP_TYPE(TypeTag, Problem); + auto problem = std::make_shared<Problem>(fvGridGeometry); + + // the solution vector + using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector); + SolutionVector x(fvGridGeometry->numDofs()); + problem->applyInitialSolution(x); + auto xOld = x; + + // the grid variables + using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables); + auto gridVariables = std::make_shared<GridVariables>(problem, fvGridGeometry); + gridVariables->init(x, xOld); + + // instantiate indicator & data transfer, read parameters for indicator + using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar); + const Scalar refineTol = getParam<Scalar>("Adaptive.RefineTolerance"); + const Scalar coarsenTol = getParam<Scalar>("Adaptive.CoarsenTolerance"); + TwoPGridAdaptIndicator<TypeTag> indicator(fvGridGeometry); + TwoPGridDataTransfer<TypeTag> dataTransfer(problem, fvGridGeometry, gridVariables, x); + + // get some time loop parameters + using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar); + const auto tEnd = getParam<Scalar>("TimeLoop.TEnd"); + const auto maxDivisions = getParam<int>("TimeLoop.MaxTimeStepDivisions"); + const auto maxDt = getParam<Scalar>("TimeLoop.MaxTimeStepSize"); + auto dt = getParam<Scalar>("TimeLoop.DtInitial"); + + // check if we are about to restart a previously interrupted simulation + Scalar restartTime = 0; + if (haveParam("Restart") || haveParam("TimeLoop.Restart")) + restartTime = getParam<Scalar>("TimeLoop.Restart"); + + // intialize the vtk output module + using VtkOutputFields = typename GET_PROP_TYPE(TypeTag, VtkOutputFields); + VtkOutputModule<TypeTag> vtkWriter(*problem, *fvGridGeometry, *gridVariables, x, problem->name()); + VtkOutputFields::init(vtkWriter); //! Add model specific output fields + vtkWriter.write(0.0); + + // instantiate time loop + auto timeLoop = std::make_shared<TimeLoop<Scalar>>(restartTime, dt, tEnd); + timeLoop->setMaxTimeStepSize(maxDt); + + // the assembler with time loop for instationary problem + using Assembler = FVAssembler<TypeTag, DiffMethod::numeric>; + auto assembler = std::make_shared<Assembler>(problem, fvGridGeometry, gridVariables, timeLoop); + + // the linear solver + using LinearSolver = AMGBackend<TypeTag>; + auto linearSolver = std::make_shared<LinearSolver>(leafGridView, fvGridGeometry->dofMapper()); + + // the non-linear solver + using NewtonController = Dumux::NewtonController<TypeTag>; + using NewtonMethod = Dumux::NewtonMethod<NewtonController, Assembler, LinearSolver>; + auto newtonController = std::make_shared<NewtonController>(leafGridView.comm(), timeLoop); + NewtonMethod nonLinearSolver(newtonController, assembler, linearSolver); + + // time loop + timeLoop->start(); do + { + // compute refinement indicator + indicator.calculate(x, refineTol, coarsenTol); + + // mark elements and maybe adapt grid + bool wasAdapted = false; + if (markElements(GridCreator::grid(), indicator)) + wasAdapted = adapt(GridCreator::grid(), dataTransfer); + + if (wasAdapted) + { + //! Note that if we were using point sources, we would have to update the map here as well + xOld = x; //! Overwrite the old solution with the new (resized & interpolated) one + assembler->setJacobianPattern(); //! Tell the assembler to resize the matrix and set pattern + assembler->setResidualSize(); //! Tell the assembler to resize the residual + gridVariables->init(x, xOld); //! Initialize the secondary variables to the new (and "new old") solution + } + + // set previous solution for storage evaluations + assembler->setPreviousSolution(xOld); + + for (int i = 0; i < maxDivisions; ++i) + { + // linearize & solve + auto converged = nonLinearSolver.solve(x); + + if (converged) + break; + + if (!converged && i == maxDivisions-1) + DUNE_THROW(Dune::MathError, + "Newton solver didn't converge after " + << maxDivisions + << " time-step divisions. dt=" + << timeLoop->timeStepSize() + << ".\nThe solutions of the current and the previous time steps " + << "have been saved to restart files."); + } + + // make the new solution the old solution + xOld = x; + gridVariables->advanceTimeStep(); + + // advance to the time loop to the next step + timeLoop->advanceTimeStep(); + + // write vtk output + vtkWriter.write(timeLoop->time()); + + // report statistics of this time step + timeLoop->reportTimeStep(); + + // set new dt as suggested by newton controller + timeLoop->setTimeStepSize(newtonController->suggestTimeStepSize(timeLoop->timeStepSize())); + + } while (!timeLoop->finished()); + + timeLoop->finalize(leafGridView.comm()); + + //////////////////////////////////////////////////////////// + // finalize, print dumux message to say goodbye + //////////////////////////////////////////////////////////// + + // print dumux end message + if (mpiHelper.rank() == 0) + { + Parameters::print(); + DumuxMessage::print(/*firstCall=*/false); + } + + return 0; +} // end main +catch (Dumux::ParameterException &e) +{ + std::cerr << std::endl << e << " ---> Abort!" << std::endl; + return 1; +} +catch (Dune::DGFException & e) +{ + std::cerr << "DGF exception thrown (" << e << + "). Most likely, the DGF file name is wrong " + "or the DGF file is corrupted, " + "e.g. missing hash at end of file or wrong number (dimensions) of entries." + << " ---> Abort!" << std::endl; + return 2; +} +catch (Dune::Exception &e) +{ + std::cerr << "Dune reported error: " << e << " ---> Abort!" << std::endl; + return 3; +} +catch (...) +{ + std::cerr << "Unknown exception thrown! ---> Abort!" << std::endl; + return 4; +} -- GitLab