From aad87590e25c0411fd4098c4c4cb401bbcac2d79 Mon Sep 17 00:00:00 2001 From: DennisGlaeser <dennis.glaeser@iws.uni-stuttgart.de> Date: Fri, 10 Feb 2017 18:04:23 +0100 Subject: [PATCH] [mixeddimension] add facet coupling framework --- dumux/mixeddimension/CMakeLists.txt | 1 + dumux/mixeddimension/facet/CMakeLists.txt | 6 + .../facet/gmshdualfacetgridcreator.hh | 463 ++++++++++++++++++ .../mixeddimension/facet/mpfa/CMakeLists.txt | 10 + .../facet/mpfa/couplingmanager.hh | 304 ++++++++++++ .../facet/mpfa/couplingmapper.hh | 257 ++++++++++ dumux/mixeddimension/facet/mpfa/darcyslaw.hh | 289 +++++++++++ dumux/mixeddimension/facet/mpfa/fickslaw.hh | 334 +++++++++++++ .../facet/mpfa/interactionvolume.hh | 149 ++++++ .../facet/mpfa/interiorboundarydata.hh | 201 ++++++++ dumux/mixeddimension/facet/mpfa/properties.hh | 72 +++ dumux/mixeddimension/facet/start.hh | 203 ++++++++ 12 files changed, 2289 insertions(+) create mode 100644 dumux/mixeddimension/facet/CMakeLists.txt create mode 100644 dumux/mixeddimension/facet/gmshdualfacetgridcreator.hh create mode 100644 dumux/mixeddimension/facet/mpfa/CMakeLists.txt create mode 100644 dumux/mixeddimension/facet/mpfa/couplingmanager.hh create mode 100644 dumux/mixeddimension/facet/mpfa/couplingmapper.hh create mode 100644 dumux/mixeddimension/facet/mpfa/darcyslaw.hh create mode 100644 dumux/mixeddimension/facet/mpfa/fickslaw.hh create mode 100644 dumux/mixeddimension/facet/mpfa/interactionvolume.hh create mode 100644 dumux/mixeddimension/facet/mpfa/interiorboundarydata.hh create mode 100644 dumux/mixeddimension/facet/mpfa/properties.hh create mode 100644 dumux/mixeddimension/facet/start.hh diff --git a/dumux/mixeddimension/CMakeLists.txt b/dumux/mixeddimension/CMakeLists.txt index a592084f9a..0eca574b98 100644 --- a/dumux/mixeddimension/CMakeLists.txt +++ b/dumux/mixeddimension/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory("embedded") +add_subdirectory("facet") add_subdirectory("glue") install(FILES diff --git a/dumux/mixeddimension/facet/CMakeLists.txt b/dumux/mixeddimension/facet/CMakeLists.txt new file mode 100644 index 0000000000..a272c20bcc --- /dev/null +++ b/dumux/mixeddimension/facet/CMakeLists.txt @@ -0,0 +1,6 @@ +add_subdirectory("mpfa") + +install(FILES +gmshdualfacetgridcreator.hh +start.hh +DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dumux/mixeddimension/facet) \ No newline at end of file diff --git a/dumux/mixeddimension/facet/gmshdualfacetgridcreator.hh b/dumux/mixeddimension/facet/gmshdualfacetgridcreator.hh new file mode 100644 index 0000000000..28d1433781 --- /dev/null +++ b/dumux/mixeddimension/facet/gmshdualfacetgridcreator.hh @@ -0,0 +1,463 @@ +/***************************************************************************** + * 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 Reads gmsh files where fractures are defined as lines in surfaces + * or surfaces in volumes and constructs a grid for the bulk part and a + * lower-dimensional grid for the lines/surfaces. + */ + +#ifndef DUMUX_GMSHDUALFACETGRIDCREATOR_HH +#define DUMUX_GMSHDUALFACETGRIDCREATOR_HH + +#include <algorithm> +#include <iostream> +#include <fstream> +#include <sstream> +#include <iomanip> + +#include <dune/common/version.hh> +#include <dune/grid/common/mcmgmapper.hh> +#include <dune/grid/common/gridfactory.hh> + +#include <dumux/common/math.hh> +#include <dumux/common/propertysystem.hh> +#include <dumux/common/parameters.hh> +#include <dumux/common/valgrind.hh> + +namespace Dumux +{ + +namespace Properties +{ +NEW_PROP_TAG(Scalar); +} + +/*! + * \brief Reads gmsh files where fractures are defined as lines in surfaces + * or surfaces in volumes and constructs a grid for the bulk part and a + * lower-dimensional grid for the lines/surfaces. + */ +template <class TypeTag> +class GmshDualFacetGridCreator +{ + using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar); + + // obtain the type tags of the sub problems + using BulkProblemTypeTag = typename GET_PROP_TYPE(TypeTag, BulkProblemTypeTag); + using LowDimProblemTypeTag = typename GET_PROP_TYPE(TypeTag, LowDimProblemTypeTag); + + // obtain the grid types of the sub problems + using BulkGrid = typename GET_PROP_TYPE(BulkProblemTypeTag, Grid); + using LowDimGrid = typename GET_PROP_TYPE(LowDimProblemTypeTag, Grid); + using LowDimElement = typename LowDimGrid::template Codim<0>::Entity; + + // The Bulk Element Mapper + using BulkElementMapper = typename GET_PROP_TYPE(BulkProblemTypeTag, ElementMapper); + + static constexpr int bulkDim = BulkGrid::dimension; + static constexpr int lowDimDim = LowDimGrid::dimension; + static constexpr int dimWorld = BulkGrid::dimensionworld; + static constexpr int lowDimDimWorld = LowDimGrid::dimensionworld; + static_assert(dimWorld == lowDimDimWorld, "dimWorld cannot be different for the two sub-domains!"); + + static constexpr int invalidIndex = -5; + + using GlobalPosition = Dune::FieldVector<double, dimWorld>; + using BulkIndexType = typename BulkGrid::LeafGridView::IndexSet::IndexType; + using LowDimIndexType = typename LowDimGrid::LeafGridView::IndexSet::IndexType; + +public: + /*! + * \brief Create the Grid. + * Specialization for bulk dim = 2 and lowDim dim = 1 + */ + template<class T = TypeTag> + static typename std::enable_if<bulkDim == 2 && lowDimDim == 1>::type + makeGrid() + { + const std::string fileName = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, std::string, Grid, File); + + // set up the grid factories + auto& bulkFactory = bulkFactory_(); + auto& lowDimFactory = lowDimFactory_(); + + // open the mesh file + std::cout << "Opening " << fileName << std::endl; + std::ifstream mshFile(fileName.c_str()); + if (mshFile.fail()) + DUNE_THROW(Dune::InvalidStateException, "Could not open the given .msh file. Make sure it exists"); + if (getFileExtension_(fileName) != "msh") + DUNE_THROW(Dune::InvalidStateException, "Please provide a .msh file for this grid creator"); + + // read file until we get to the list of nodes + std::string line; + std::getline(mshFile, line); + while (line.compare(0, 6, "$Nodes") != 0) + std::getline(mshFile, line); + + // get total number of nodes + std::getline(mshFile, line); + const auto numVertices = stringToNumber_(line); + + // read vertices line by line + std::getline(mshFile, line); + std::vector<GlobalPosition> bulkVertices(numVertices); + unsigned int vertexCounter = 0; + while (line.compare(0, 9, "$EndNodes") != 0) + { + // read the coordinates of the vertex + std::string buf; + std::stringstream stream(line); + + // drop first entry in line + stream >> buf; + std::vector<std::string> coords; + while (stream >> buf) + coords.push_back(buf); + + // "create" a vertex + GlobalPosition v; + for (int i = 0; i < dimWorld; ++i) + v[i] = stringToNumber_(coords[i]); + + // insert vertex into container and the bulk grid factory + bulkVertices[vertexCounter] = v; + bulkFactory.insertVertex(v); + std::getline(mshFile, line); + vertexCounter++; + } + + // we should always find as many vertices as the mesh file states + assert(vertexCounter == numVertices); + + // read file until we get to the list of elements + while(line.compare(0, 9, "$Elements") != 0) + std::getline(mshFile, line); + + // get total number of elements + std::getline(mshFile, line); + const auto numElements = stringToNumber_(line); + + // while inserting the low dim vertices, keep track of indices that have + // been inserted already and element vertex indices in "original" indexing + std::vector<GlobalPosition> lowDimVertices; + std::vector<LowDimIndexType> lowDimVertexIndices; + std::vector<std::vector<BulkIndexType>> lowDimElemVertices; + + lowDimVertices.reserve(numVertices); + lowDimVertexIndices.reserve(numVertices); + lowDimElemVertices.reserve(numElements); + + // the coupling data container lowDim -> bulk + auto& lowDimCouplingMap = lowDimCouplingElements_(); + + // read in the elements line by line + std::getline(mshFile, line); + BulkIndexType bulkElementCounter = 0; + LowDimIndexType lowDimElementCounter = 0; + while (line.compare(0, 12, "$EndElements") != 0) + { + // read the data of this element + std::string buf; + std::stringstream stream(line); + std::vector<unsigned int> elemData; + while (stream >> buf) + elemData.push_back(stringToNumber_(buf)); + + // get number of gmsh tags for this element + const auto noOfTags = elemData[2]; + + // get element type, make container of indexvertices and insert element + const auto elemType = elemData[1]; + + switch (elemType) + { + case 1: // 2-node line + { + // the geometry type for lines + static const Dune::GeometryType gt = [] () { Dune::GeometryType t; t.makeLine(); return t; } (); + + // get the connected vertex indices + std::vector<LowDimIndexType> elemVertexIndices(2); + + const auto vIdx1 = elemData[3 + noOfTags]; + const auto vIdx2 = elemData[3 + noOfTags + 1]; + + elemVertexIndices[0] = findIndexInVector_(lowDimVertexIndices, vIdx1); + elemVertexIndices[1] = findIndexInVector_(lowDimVertexIndices, vIdx2); + + if (elemVertexIndices[0] == invalidIndex) + { + const auto v1 = bulkVertices[vIdx1-1]; + + lowDimVertices.push_back(v1); + lowDimVertexIndices.push_back(vIdx1); + lowDimFactory.insertVertex(v1); + + elemVertexIndices[0] = lowDimVertices.size()-1; + } + if (elemVertexIndices[1] == invalidIndex) + { + const auto v2 = bulkVertices[vIdx2-1]; + + lowDimVertices.push_back(v2); + lowDimVertexIndices.push_back(vIdx2); + lowDimFactory.insertVertex(v2); + + elemVertexIndices[1] = lowDimVertices.size()-1; + } + + // store the low dim elements' vertices + lowDimElemVertices.push_back(std::vector<BulkIndexType>({vIdx1, vIdx2})); + + // insert element into the factory + lowDimFactory.insertElement(gt, elemVertexIndices); + + // increase low dim element counter + lowDimElementCounter++; + break; + } + + case 2: // 3-node triangle + { + // the geometry type for triangles + static const Dune::GeometryType gt = Dune::GeometryType(Dune::GeometryType::simplex,2); + + // we know that the low dim elements have been read already + if (lowDimCouplingMap.empty()) + lowDimCouplingMap.resize(lowDimElementCounter); + + // get the vertex indices of this bulk element + std::vector<BulkIndexType> elemVertexIndices(3); + elemVertexIndices[0] = elemData[3 + noOfTags]; + elemVertexIndices[1] = elemData[3 + noOfTags + 1]; + elemVertexIndices[2] = elemData[3 + noOfTags + 2]; + + // get the insertion indices of the low dim elements connected to this element + const auto lowDimElemIndices = obtainLowDimConnections_(lowDimElemVertices, elemVertexIndices); + + // if we found some, insert the connections in the map + for (auto lowDimElemIdx : lowDimElemIndices) + lowDimCouplingMap[lowDimElemIdx].push_back(bulkElementCounter); + + // subtract 1 from the element indices (gmsh starts at index 1) + elemVertexIndices[0] -= 1; + elemVertexIndices[1] -= 1; + elemVertexIndices[2] -= 1; + + // insert bulk element into the factory + bulkFactory.insertElement(gt, elemVertexIndices); + + // increase bulk element counter + bulkElementCounter++; + break; + } + + default: + DUNE_THROW(Dune::NotImplemented, "GmshDualFacetGridCreator for given element type"); + } + + // get next line + std::getline(mshFile, line); + } + + std::cout << bulkElementCounter << " bulk elements and " + << lowDimElementCounter << " low dim elements have been created." << std::endl; + + + bulkGridPtr_() = std::shared_ptr<BulkGrid>(bulkFactory.createGrid()); + lowDimGridPtr_() = std::shared_ptr<LowDimGrid>(lowDimFactory.createGrid()); + + // set up the map from insertion to actual global indices for bulk elements + BulkElementMapper elementMapper(bulkGrid().leafGridView()); + auto& map = bulkInsertionToGlobalIdxMap_(); + map.resize(bulkElementCounter); + for (const auto& element : elements(bulkGrid().leafGridView())) + map[bulkFactory.insertionIndex(element)] = elementMapper.index(element); + } + + /*! + * \brief Returns a reference to the bulk grid. + */ + static BulkGrid& bulkGrid() + { return *bulkGridPtr_(); } + + /*! + * \brief Returns a reference to the lower dimensional facet grid. + */ + static LowDimGrid& lowDimGrid() + { return *lowDimGridPtr_(); } + + /*! + * \brief Distributes the grid on all processes of a parallel + * computation. + */ + static void loadBalance() + { + bulkGrid().loadBalance(); + lowDimGrid().loadBalance(); + } + + static std::vector<BulkIndexType> getCoupledBulkElementIndices(const LowDimElement& lowDimElement) + { + const auto& insertionIndices = lowDimCouplingElements_()[lowDimFactory_().insertionIndex(lowDimElement)]; + const auto& bulkInsertionToGlobalMap = bulkInsertionToGlobalIdxMap_(); + + const auto n = insertionIndices.size(); + std::vector<BulkIndexType> globalIndices(n); + for (unsigned int i = 0; i < n; ++i) + globalIndices[i] = bulkInsertionToGlobalMap[insertionIndices[i]]; + + return globalIndices; + } + +private: + + /*! + * \brief Returns a reference to the vector containing lists of bulk element indices + * that are connected to the low dim elements. Note that the access occurs + * with the insertion index of a low dim element. + */ + static std::vector<std::vector<BulkIndexType>>& lowDimCouplingElements_() + { + static std::vector<std::vector<BulkIndexType>> couplingMap_; + return couplingMap_; + } + + /*! + * \brief Returns a reference to the map mapping bulk element insertion indices + * to actual global indices + */ + static std::vector<BulkIndexType>& bulkInsertionToGlobalIdxMap_() + { + static std::vector<BulkIndexType> insertionIdxMap_; + return insertionIdxMap_; + } + + /*! + * \brief Returns a reference to the bulk grid pointer (std::shared_ptr<BulkGrid>) + */ + static std::shared_ptr<BulkGrid>& bulkGridPtr_() + { + static std::shared_ptr<BulkGrid> bulkGridPtr_; + return bulkGridPtr_; + } + + /*! + * \brief Returns a reference to the lowDim grid pointer (std::shared_ptr<LowDimGrid>) + */ + static std::shared_ptr<LowDimGrid>& lowDimGridPtr_() + { + static std::shared_ptr<LowDimGrid> lowDimGridPtr_; + return lowDimGridPtr_; + } + + static Dune::GridFactory<BulkGrid>& bulkFactory_() + { + static Dune::GridFactory<BulkGrid> bulkFactory_; + return bulkFactory_; + } + + static Dune::GridFactory<LowDimGrid>& lowDimFactory_() + { + static Dune::GridFactory<LowDimGrid> lowDimFactory_; + return lowDimFactory_; + } + + /*! + * \brief Returns a list of low dim elements that are connected to this bulk element + */ + static std::vector<LowDimIndexType> obtainLowDimConnections_(const std::vector<std::vector<LowDimIndexType>>& lowDimElemData, + const std::vector<BulkIndexType>& bulkElemVertices) + { + std::vector<LowDimIndexType> lowDimElemIndices = std::vector<LowDimIndexType>(); + + // check for each low dim element if it is contained + unsigned int lowDimElementIdx = 0; + for (const auto& lowDimElem : lowDimElemData) + { + const bool isContained = [&lowDimElem, &bulkElemVertices] () + { + for (auto idx : lowDimElem) + if (findIndexInVector_(bulkElemVertices, idx) == invalidIndex) + return false; + return true; + } (); + + // insert in list if low dim element is in bulk element + if (isContained) + lowDimElemIndices.push_back(lowDimElementIdx); + + // keep track of the low dim element insertion index + lowDimElementIdx++; + } + + return lowDimElemIndices; + } + + /*! + * \brief Returns the local index of a given value in a given vector + */ + template<typename IdxType1, typename IdxType2> + static int findIndexInVector_(const std::vector<IdxType1>& vector, const IdxType2 globalIdx) + { + auto it = std::find(vector.begin(), vector.end(), globalIdx); + + // we use the -5 here to detect whether or not an index exists already + if (it != vector.end()) + return std::distance(vector.begin(), it); + else + return invalidIndex; + } + + /*! + * \brief Returns the filename extension of a given filename + */ + static std::string getFileExtension_(const std::string& fileName) + { + std::size_t i = fileName.rfind('.', fileName.length()); + if (i != std::string::npos) + return(fileName.substr(i+1, fileName.length() - i)); + else + DUNE_THROW(Dune::IOError, "Please provide and extension for your grid file ('"<< fileName << "')!"); + } + + /*! + * \brief Turns a number in form of a string into a Scalar + */ + static Scalar stringToNumber_(const std::string& numberAsString) + { + Scalar value; + + std::stringstream stream(numberAsString); + stream >> value; + + if (stream.fail()) + { + std::runtime_error e(numberAsString); + throw e; + } + + return value; + } +}; + +} // end namespace + +#endif // DUMUX_GMSHDUALFACETGRIDCREATOR_HH diff --git a/dumux/mixeddimension/facet/mpfa/CMakeLists.txt b/dumux/mixeddimension/facet/mpfa/CMakeLists.txt new file mode 100644 index 0000000000..22c0ab9868 --- /dev/null +++ b/dumux/mixeddimension/facet/mpfa/CMakeLists.txt @@ -0,0 +1,10 @@ +install(FILES +couplingmanager.hh +couplingmapper.hh +darcyslaw.hh +fickslaw.hh +fourierslaw.hh +interactionvolume.hh +interiorboundarydata.hh +properties.hh +DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dumux/mixeddimension/facet) \ No newline at end of file diff --git a/dumux/mixeddimension/facet/mpfa/couplingmanager.hh b/dumux/mixeddimension/facet/mpfa/couplingmanager.hh new file mode 100644 index 0000000000..6b6d65b0b0 --- /dev/null +++ b/dumux/mixeddimension/facet/mpfa/couplingmanager.hh @@ -0,0 +1,304 @@ +/***************************************************************************** + * 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 + * \ingroup FacetCoupling + * \brief Manages the coupling between bulk elements and lower dimensional elements on the element facets + */ + +#ifndef DUMUX_MIXEDDIMENSION_CCMPFA_FACET_COUPLINGMANAGER_HH +#define DUMUX_MIXEDDIMENSION_CCMPFA_FACET_COUPLINGMANAGER_HH + +#include <dumux/implicit/properties.hh> +#include <dumux/mixeddimension/facet/mpfa/couplingmapper.hh> + +namespace Dumux +{ + +/*! + * \ingroup FacetCoupling + * \brief Manages the interaction between the bulk and lower dimensional (facet) elements + */ +template<class TypeTag> +class CCMpfaFacetCouplingManager +{ + using Implementation = typename GET_PROP_TYPE(TypeTag, CouplingManager); + using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar); + using GlobalProblem = typename GET_PROP_TYPE(TypeTag, Problem); + using GridCreator = typename GET_PROP_TYPE(TypeTag, GridCreator); + + using BulkProblemTypeTag = typename GET_PROP_TYPE(TypeTag, BulkProblemTypeTag); + using LowDimProblemTypeTag = typename GET_PROP_TYPE(TypeTag, LowDimProblemTypeTag); + + using BulkGridView = typename GET_PROP_TYPE(BulkProblemTypeTag, GridView); + using BulkElement = typename BulkGridView::template Codim<0>::Entity; + using BulkIntersection = typename BulkGridView::Intersection; + using BulkIndexType = typename BulkGridView::IndexSet::IndexType; + using BulkProblem = typename GET_PROP_TYPE(BulkProblemTypeTag, Problem); + using BulkMpfaHelper = typename GET_PROP_TYPE(BulkProblemTypeTag, MpfaHelper); + using BulkLocalResidual = typename GET_PROP_TYPE(BulkProblemTypeTag, LocalResidual); + using BulkPrimaryVariables = typename GET_PROP_TYPE(BulkProblemTypeTag, PrimaryVariables); + using BulkFVElementGeometry = typename GET_PROP_TYPE(BulkProblemTypeTag, FVElementGeometry); + using BulkSubControlVolumeFace = typename GET_PROP_TYPE(BulkProblemTypeTag, SubControlVolumeFace); + using BulkElementBoundaryTypes = typename GET_PROP_TYPE(BulkProblemTypeTag, ElementBoundaryTypes); + using BulkElementVolumeVariables = typename GET_PROP_TYPE(BulkProblemTypeTag, ElementVolumeVariables); + using BulkElementFluxVariablesCache = typename GET_PROP_TYPE(BulkProblemTypeTag, ElementFluxVariablesCache); + + using LowDimGridView = typename GET_PROP_TYPE(LowDimProblemTypeTag, GridView); + using LowDimElement = typename LowDimGridView::template Codim<0>::Entity; + using LowDimIndexType = typename LowDimGridView::IndexSet::IndexType; + using LowDimProblem = typename GET_PROP_TYPE(LowDimProblemTypeTag, Problem); + using LowDimLocalResidual = typename GET_PROP_TYPE(LowDimProblemTypeTag, LocalResidual); + using LowDimPrimaryVariables = typename GET_PROP_TYPE(LowDimProblemTypeTag, PrimaryVariables); + using LowDimSubControlVolume = typename GET_PROP_TYPE(LowDimProblemTypeTag, SubControlVolume); + using LowDimFVElementGeometry = typename GET_PROP_TYPE(LowDimProblemTypeTag, FVElementGeometry); + using LowDimElementBoundaryTypes = typename GET_PROP_TYPE(LowDimProblemTypeTag, ElementBoundaryTypes); + using LowDimVolumeVariables = typename GET_PROP_TYPE(LowDimProblemTypeTag, VolumeVariables); + using LowDimElementVolumeVariables = typename GET_PROP_TYPE(LowDimProblemTypeTag, ElementVolumeVariables); + using LowDimElementFluxVariablesCache = typename GET_PROP_TYPE(LowDimProblemTypeTag, ElementFluxVariablesCache); + + static constexpr int dimWorld = BulkGridView::dimensionworld; + static constexpr int lowDimDimWorld = LowDimGridView::dimensionworld; + static_assert(dimWorld == lowDimDimWorld, "dimWorld cannot be different for the two sub-domains!"); + + using GlobalPosition = Dune::FieldVector<Scalar, dimWorld>; + +public: + + /*! + * \brief Constructor + */ + CCMpfaFacetCouplingManager(BulkProblem& bulkProblem, LowDimProblem& lowDimProblem) + : bulkProblemPtr_(&bulkProblem), + lowDimProblemPtr_(&lowDimProblem), + couplingMapper_(bulkProblem, lowDimProblem) + { + // initialize the local residuals + bulkLocalResidual_.init(bulkProblem); + lowDimLocalResidual_.init(lowDimProblem); + } + + void preInit() {} + + void postInit() + { + // abfrage ob no flow tip?? + couplingMapper_.init(); + } + + //! evaluates if an intersection is on an interior boundary + bool isInteriorBoundary(const BulkElement& element, const BulkIntersection& is) const + { + // TODO: Facet elements that are on the bulk domain boundary + if (is.boundary()) + return false; + + const auto insideIdx = bulkProblem().elementMapper().index(element); + const auto outsideIdx = bulkProblem().elementMapper().index(is.outside()); + return isInteriorBoundary_(insideIdx, outsideIdx); + } + + //! evaluates if an scvf is on an interior boundary + bool isInteriorBoundary(const BulkElement& element, const BulkSubControlVolumeFace& scvf) const + { + // TODO: Facet elements that are on the bulk domain boundary + if (scvf.boundary()) + return false; + + if (couplingMapper_.isInitialized()) + { + const auto& couplingData = couplingMapper_.getBulkCouplingData(element); + + // if the element is coupled, check if the scvf is so as well + if (!couplingData.isCoupled) + return false; + return couplingData.getScvfCouplingData(scvf).first; + } + + // If the mapper has not been initialized yet, use the private function + return isInteriorBoundary_(scvf.insideScvIdx(), scvf.outsideScvIdx()); + } + + const std::vector<LowDimIndexType>& couplingStencil(const BulkElement& element) + { return couplingMapper_.getBulkCouplingData(element).couplingStencil; } + + const std::vector<BulkIndexType>& couplingStencil(const LowDimElement& element) + { return couplingMapper_.getLowDimCouplingData(element).couplingStencil; } + + //! evaluate coupling residual for the derivative bulk DOF with respect to low dim DOF + //! we only need to evaluate the part of the residual that will be influence by the low dim DOF + BulkPrimaryVariables evalCouplingResidual(const BulkElement& element, + const BulkFVElementGeometry& fvGeometry, + const BulkElementVolumeVariables& curElemVolVars, + const BulkElementBoundaryTypes& elemBcTypes, + const BulkElementFluxVariablesCache& elemFluxVarsCache, + const LowDimElement& lowDimElement) + { + const auto& couplingData = couplingMapper_.getBulkCouplingData(element); + + if (!couplingData.isCoupled) + return BulkPrimaryVariables(0.0); + + // calculate the local residual of the bulk element + auto prevElemVolVars = localView(bulkProblem().model().prevGlobalVolVars()); + prevElemVolVars.bindElement(element, fvGeometry, bulkProblem().model().prevSol()); + bulkLocalResidual_.eval(element, fvGeometry, prevElemVolVars, curElemVolVars, elemBcTypes, elemFluxVarsCache); + return bulkLocalResidual_.residual(0); + } + + //! evaluate coupling residual for the derivative low dim DOF with respect to bulk DOF + //! we only need to evaluate the part of the residual that will be influence by the bulk DOF + LowDimPrimaryVariables evalCouplingResidual(const LowDimElement& element, + const LowDimFVElementGeometry& fvGeometry, + const LowDimElementVolumeVariables& curElemVolVars, + const LowDimElementBoundaryTypes& elemBcTypes, + const LowDimElementFluxVariablesCache& elemFluxVarsCache, + const BulkElement& bulkElement) + { + const auto& couplingData = couplingMapper_.getLowDimCouplingData(element); + + if (!couplingData.isCoupled) + return LowDimPrimaryVariables(0.0); + + // calculate the source term of the low dim element + const auto eIdx = lowDimProblem().elementMapper().index(element); + const auto& scv = fvGeometry.scv(eIdx); + + auto source = lowDimLocalResidual_.computeSource(element, fvGeometry, curElemVolVars, scv); + source *= -scv.volume()*curElemVolVars[scv].extrusionFactor(); + return source; + } + + //! evaluates the sources in the low dim domain coming from the bulk domain + //! i.e. the fluxes from the bulk domain into the given low dim element. We return bulk priVars here, + //! the low dim problem itself needs to transform that into suitable information + BulkPrimaryVariables evalSourcesFromBulk(const LowDimElement& element, + const LowDimFVElementGeometry& fvGeometry, + const LowDimElementVolumeVariables& curElemVolVars, + const LowDimSubControlVolume& scv) const + { + const auto& couplingData = couplingMapper_.getLowDimCouplingData(element); + + if (!couplingData.isCoupled) + return BulkPrimaryVariables(0.0); + + // evaluate the fluxes over each scvf in each bulk neighbor. Prepare the local views + // of the first element, the necessary quantities for all the scvfs will be in there, + // as the scvfs in the other elements are the "flipped" scvfs of the ones in the first element + const auto& firstPair = couplingData.elementScvfList[0]; + const auto firstBulkElement = bulkProblem().model().globalFvGeometry().element(firstPair.first); + + auto bulkFvGeometry = localView(bulkProblem().model().globalFvGeometry()); + bulkFvGeometry.bind(firstBulkElement); + + auto bulkElemVolVars = localView(bulkProblem().model().curGlobalVolVars()); + bulkElemVolVars.bind(firstBulkElement, bulkFvGeometry, bulkProblem().model().curSol()); + + auto bulkElemFluxVarsCache = localView(bulkProblem().model().globalFluxVarsCache()); + bulkElemFluxVarsCache.bind(firstBulkElement, bulkFvGeometry, bulkElemVolVars); + + BulkPrimaryVariables flux(0.0); + for (const auto& pair : couplingData.elementScvfList) + { + const auto bulkElement = bulkProblem().model().globalFvGeometry().element(pair.first); + for (auto scvfIdx : pair.second) + flux += bulkLocalResidual_.computeFlux(bulkElement, + bulkFvGeometry, + bulkElemVolVars, + bulkFvGeometry.scvf(scvfIdx), + bulkElemFluxVarsCache); + } + + // The source has to be formulated per volume + flux /= scv.volume()*curElemVolVars[scv].extrusionFactor(); + return flux; + } + + //! Compute lowDim volume variables for given bulk element and scvf + LowDimVolumeVariables lowDimVolVars(const BulkElement& element, + const BulkFVElementGeometry& fvGeometry, + const BulkSubControlVolumeFace& scvf) const + { + const auto& scvfCouplingData = couplingMapper_.getBulkCouplingData(element).getScvfCouplingData(scvf); + + assert(scvfCouplingData.first && "no coupled facet element found for given scvf!"); + const auto lowDimElement = lowDimProblem().model().globalFvGeometry().element(scvfCouplingData.second); + auto lowDimFvGeometry = localView(lowDimProblem().model().globalFvGeometry()); + lowDimFvGeometry.bindElement(lowDimElement); + + const auto& lowDimCurSol = lowDimProblem().model().curSol(); + LowDimVolumeVariables lowDimVolVars; + lowDimVolVars.update(lowDimProblem().model().elementSolution(lowDimElement, lowDimCurSol), + lowDimProblem(), + lowDimElement, + lowDimFvGeometry.scv(scvfCouplingData.second)); + return lowDimVolVars; + } + + const BulkProblem& bulkProblem() const + { return *bulkProblemPtr_; } + + const LowDimProblem& lowDimProblem() const + { return *lowDimProblemPtr_; } + + const CCMpfaFacetCouplingMapper<TypeTag>& couplingMapper() const + { return couplingMapper_; } + +private: + + //! evaluates if there is an interior boundary between two bulk elements + bool isInteriorBoundary_(BulkIndexType eIdxInside, BulkIndexType eIdxOutside) const + { + for (const auto& lowDimElement : elements(lowDimProblem().gridView())) + { + // The indices of bulk elements in which the actual low dim element is a facet + const auto& bulkElementIndices = GridCreator::getCoupledBulkElementIndices(lowDimElement); + + // if inside & outside element are contained in them, this is a "coupling" intersection + if (BulkMpfaHelper::contains(bulkElementIndices, eIdxInside) && BulkMpfaHelper::contains(bulkElementIndices, eIdxOutside)) + return true; + } + + return false; + } + + BulkProblem& bulkProblem() + { return *bulkProblemPtr_; } + + LowDimProblem& lowDimProblem() + { return *lowDimProblemPtr_; } + + //! Returns the implementation of the problem (i.e. static polymorphism) + Implementation &asImp_() + { return *static_cast<Implementation *>(this); } + + //! \copydoc asImp_() + const Implementation &asImp_() const + { return *static_cast<const Implementation *>(this); } + + BulkProblem *bulkProblemPtr_; + LowDimProblem *lowDimProblemPtr_; + + mutable BulkLocalResidual bulkLocalResidual_; + mutable LowDimLocalResidual lowDimLocalResidual_; + + CCMpfaFacetCouplingMapper<TypeTag> couplingMapper_; +}; + +} // end namespace + +#endif // DUMUX_FACETCOUPLINGMANAGER_HH diff --git a/dumux/mixeddimension/facet/mpfa/couplingmapper.hh b/dumux/mixeddimension/facet/mpfa/couplingmapper.hh new file mode 100644 index 0000000000..83168983a7 --- /dev/null +++ b/dumux/mixeddimension/facet/mpfa/couplingmapper.hh @@ -0,0 +1,257 @@ +/***************************************************************************** + * 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 + * \ingroup Facet + * \brief Creates mappings of the entities from the bulk and the facet subdomain + */ + +#ifndef DUMUX_MIXEDDIMENSION_CCMPFA_FACET_COUPLINGMAPPER_HH +#define DUMUX_MIXEDDIMENSION_CCMPFA_FACET_COUPLINGMAPPER_HH + +#include <dune/common/version.hh> +#include <dune/grid/common/mcmgmapper.hh> +#include <dune/grid/common/gridfactory.hh> + +#include <dumux/common/math.hh> +#include <dumux/common/propertysystem.hh> +#include <dumux/common/parameters.hh> +#include <dumux/common/valgrind.hh> + +namespace Dumux +{ + +namespace Properties +{ +NEW_PROP_TAG(Scalar); +NEW_PROP_TAG(BulkProblemTypeTag); +NEW_PROP_TAG(LowDimProblemTypeTag); +} + +/*! + * \ingroup FacetCoupling + * \brief Creates mappings of the entities from the bulk and the facet subdomain + */ +template<class TypeTag> +class CCMpfaFacetCouplingMapper +{ + using GridCreator = typename GET_PROP_TYPE(TypeTag, GridCreator); + + using BulkProblemTypeTag = typename GET_PROP_TYPE(TypeTag, BulkProblemTypeTag); + using LowDimProblemTypeTag = typename GET_PROP_TYPE(TypeTag, LowDimProblemTypeTag); + + using BulkProblem = typename GET_PROP_TYPE(BulkProblemTypeTag, Problem); + using LowDimProblem = typename GET_PROP_TYPE(LowDimProblemTypeTag, Problem); + + using BulkGridView = typename GET_PROP_TYPE(BulkProblemTypeTag, GridView); + using LowDimGridView = typename GET_PROP_TYPE(LowDimProblemTypeTag, GridView); + + using BulkIndexType = typename BulkGridView::IndexSet::IndexType; + using LowDimIndexType = typename LowDimGridView::IndexSet::IndexType; + + using BulkElement = typename BulkGridView::template Codim<0>::Entity; + using LowDimElement = typename LowDimGridView::template Codim<0>::Entity; + + using BulkSubControlVolumeFace = typename GET_PROP_TYPE(BulkProblemTypeTag, SubControlVolumeFace); + using BulkMpfaHelper = typename GET_PROP_TYPE(BulkProblemTypeTag, MpfaHelper); + + static constexpr int lowDimDim = LowDimGridView::dimension; + +public: + + struct BulkDataInLowDim + { + // indicates whether or not this element is coupled + bool isCoupled; + + // The list of bulk element indices connected to a low dim element + std::vector<BulkIndexType> couplingStencil; + + // We store for each directly connected element a list of + // global scvf indices over which the two domains are coupled + std::vector<std::pair<BulkIndexType, std::vector<BulkIndexType>>> elementScvfList; + + // The constructor + BulkDataInLowDim() : isCoupled(false) {} + }; + + struct LowDimDataInBulk + { + // indicates whether or not this element is coupled + bool isCoupled; + + // The list of low dim elements with influence on this bulk element + std::vector<LowDimIndexType> couplingStencil; + + // to each scvf of the bulk element we store + // the low dim element index that it is connected to + std::vector<std::pair<BulkIndexType, LowDimIndexType>> scvfToLowDimData; + + // The constructor + LowDimDataInBulk() : isCoupled(false) {} + + // For a given scvf of the element this method returns a pair of a bool and an index. The boolean + // indicates if this scvf does couple to a low dim element, the index is the low dim element index + std::pair<bool, LowDimIndexType> getScvfCouplingData(const BulkSubControlVolumeFace& scvf) const + { return getScvfCouplingData(scvf.index()); } + + std::pair<bool, LowDimIndexType> getScvfCouplingData(BulkIndexType scvfIdx) const + { + for (const auto& pair : scvfToLowDimData) + if (pair.first == scvfIdx) + return std::make_pair(true, pair.second); + return std::make_pair(false, 0); + } + }; + + //! The constructor + CCMpfaFacetCouplingMapper(BulkProblem &bulkProblem, LowDimProblem &lowDimProblem) + : isInitialized_(false), + bulkProblemPtr_(&bulkProblem), + lowDimProblemPtr_(&lowDimProblem) + {} + + /*! + * \brief initializes the maps + * + * \param bulkProblem The bulk sub problem + * \param lowDimProblem The lower-dimensional sub problem + */ + void init() + { + const auto& bulkGridView = bulkProblem_().gridView(); + const auto& lowDimGridView = lowDimProblem_().gridView(); + + lowDimCouplingData_.resize(lowDimGridView.size(0)); + bulkCouplingData_.resize(bulkGridView.size(0)); + + for (const auto& lowDimElement : elements(lowDimGridView)) + { + const auto lowDimElementGlobalIdx = lowDimProblem_().elementMapper().index(lowDimElement); + + // The indices of bulk elements in which the actual low dim element is a facet + const auto& bulkElementIndices = GridCreator::getCoupledBulkElementIndices(lowDimElement); + if (bulkElementIndices.size() > 1) + { + lowDimCouplingData_[lowDimElementGlobalIdx].isCoupled = true; + + for (auto bulkElementIdx : bulkElementIndices) + { + bulkCouplingData_[bulkElementIdx].isCoupled = true; + + const auto bulkElement = bulkProblem_().model().globalFvGeometry().element(bulkElementIdx); + auto bulkFvGeometry = localView(bulkProblem_().model().globalFvGeometry()); + bulkFvGeometry.bindElement(bulkElement); + + // find the scvfs in the bulk element that "touch" the facet element + std::vector<BulkIndexType> scvfIndices; + for (const auto& scvf : scvfs(bulkFvGeometry)) + { + if (scvf.boundary()) + continue; + + const auto anyOtherIdx = bulkElementIndices[0] == bulkElementIdx ? + bulkElementIndices[1] : + bulkElementIndices[0]; + + // check if any of the other bulk indices is in the outside indices of the scvf + if (BulkMpfaHelper::contains(scvf.outsideScvIndices(), anyOtherIdx)) + { + scvfIndices.push_back(scvf.index()); + + // add data to the bulk data map + bulkCouplingData_[bulkElementIdx].scvfToLowDimData.emplace_back(std::make_pair(scvf.index(), lowDimElementGlobalIdx)); + + // also, insert scvf stencil to the coupling stencil of the low dim element + const auto scvfStencil = [&] () + { + if (bulkProblem_().model().globalFvGeometry().isInBoundaryInteractionVolume(scvf)) + return bulkProblem_().model().globalFvGeometry().boundaryInteractionVolumeSeed(scvf).globalScvIndices(); + else + return bulkProblem_().model().globalFvGeometry().interactionVolumeSeed(scvf).globalScvIndices(); + } (); + + auto& lowDimCouplingStencil = lowDimCouplingData_[lowDimElementGlobalIdx].couplingStencil; + lowDimCouplingStencil.insert(lowDimCouplingStencil.end(), scvfStencil.begin(), scvfStencil.end()); + + // also, all the bulk elements in the scvf stencil will be coupled to the actual low dim element + for (auto bulkIdx : scvfStencil) + { + bulkCouplingData_[bulkIdx].isCoupled = true; + bulkCouplingData_[bulkIdx].couplingStencil.push_back(lowDimElementGlobalIdx); + } + } + } + + // insert data into the low dim map + lowDimCouplingData_[lowDimElementGlobalIdx].elementScvfList.emplace_back(std::make_pair(bulkElementIdx, std::move(scvfIndices))); + } + } + else if (bulkElementIndices.size() == 1) + DUNE_THROW(Dune::NotImplemented, "Coupled facet elements on the bulk boundary"); + + // make the coupling stencil of the low dim element unique + auto& stencil = lowDimCouplingData_[lowDimElementGlobalIdx].couplingStencil; + std::sort(stencil.begin(), stencil.end()); + stencil.erase(std::unique(stencil.begin(), stencil.end()), stencil.end()); + } + + // make the coupling stencils in the bulk map unique + for (auto& data : bulkCouplingData_) + { + auto& stencil = data.couplingStencil; + std::sort(stencil.begin(), stencil.end()); + stencil.erase(std::unique(stencil.begin(), stencil.end()), stencil.end()); + } + + // state the initialization status + isInitialized_ = true; + } + + const LowDimDataInBulk& getBulkCouplingData(const BulkElement& bulkElement) const + { return getBulkCouplingData(bulkProblem_().elementMapper().index(bulkElement)); } + + const LowDimDataInBulk& getBulkCouplingData(BulkIndexType bulkElementIdx) const + { return bulkCouplingData_[bulkElementIdx]; } + + const BulkDataInLowDim& getLowDimCouplingData(const LowDimElement& lowDimElement) const + { return getLowDimCouplingData(lowDimProblem_().elementMapper().index(lowDimElement)); } + + const BulkDataInLowDim& getLowDimCouplingData(LowDimIndexType lowDimElementIdx) const + { return lowDimCouplingData_[lowDimElementIdx]; } + + bool isInitialized() const + { return isInitialized_; } + +private: + const BulkProblem& bulkProblem_() const + { return *bulkProblemPtr_; } + + const LowDimProblem& lowDimProblem_() const + { return *lowDimProblemPtr_; } + + bool isInitialized_; + const BulkProblem* bulkProblemPtr_; + const LowDimProblem* lowDimProblemPtr_; + + std::vector<LowDimDataInBulk> bulkCouplingData_; + std::vector<BulkDataInLowDim> lowDimCouplingData_; +}; + +} // end namespace + +#endif // DUMUX_FACETCOUPLINGMAPPER_HH diff --git a/dumux/mixeddimension/facet/mpfa/darcyslaw.hh b/dumux/mixeddimension/facet/mpfa/darcyslaw.hh new file mode 100644 index 0000000000..d1357c57de --- /dev/null +++ b/dumux/mixeddimension/facet/mpfa/darcyslaw.hh @@ -0,0 +1,289 @@ +// -*- 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 This file contains the data which is required to calculate volume and mass fluxes of + * fluid phases over a face of a finite volume by means of the Darcy approximation in the + * presence of lower dimensional (coupled) elements living on the this domain's element facets. + */ +#ifndef DUMUX_DISCRETIZATION_CC_MPFA_FACET_DARCYS_LAW_HH +#define DUMUX_DISCRETIZATION_CC_MPFA_FACET_DARCYS_LAW_HH + +#include <dumux/discretization/cellcentered/mpfa/darcyslaw.hh> +#include <dumux/discretization/cellcentered/mpfa/facetypes.hh> + +namespace Dumux +{ + +/*! + * \ingroup DarcysLaw + * \brief Specialization of Darcy's Law for the CCMpfa method with lower dimensional + * elements living on the bulk elements' facets. + */ +template <class TypeTag> +class CCMpfaFacetCouplingDarcysLaw : public DarcysLawImplementation<TypeTag, DiscretizationMethods::CCMpfa> +{ + using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar); + using Problem = typename GET_PROP_TYPE(TypeTag, Problem); + using GridView = typename GET_PROP_TYPE(TypeTag, GridView); + using Element = typename GridView::template Codim<0>::Entity; + using MpfaHelper = typename GET_PROP_TYPE(TypeTag, MpfaHelper); + using FVElementGeometry = typename GET_PROP_TYPE(TypeTag, FVElementGeometry); + using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume); + using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace); + using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables); + using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables); + using ElementFluxVariablesCache = typename GET_PROP_TYPE(TypeTag, ElementFluxVariablesCache); + using FluxVariablesCache = typename GET_PROP_TYPE(TypeTag, FluxVariablesCache); + using ElementSolutionVector = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector); + + // Always use the dynamic type for vectors (compatibility with the boundary) + using BoundaryInteractionVolume = typename GET_PROP_TYPE(TypeTag, BoundaryInteractionVolume); + using CoefficientVector = typename BoundaryInteractionVolume::Vector; + + static constexpr int numPhases = GET_PROP_VALUE(TypeTag, NumPhases); + static constexpr bool facetCoupling = GET_PROP_VALUE(TypeTag, MpfaFacetCoupling); + static constexpr bool useTpfaBoundary = GET_PROP_VALUE(TypeTag, UseTpfaBoundary); + static constexpr bool enableInteriorBoundaries = GET_PROP_VALUE(TypeTag, EnableInteriorBoundaries); + + //! The cache used in conjunction with the mpfa Darcy's Law + class MpfaFacetCouplingDarcysLawCache + { + // We always use the dynamic types here to be compatible on the boundary + using Stencil = typename BoundaryInteractionVolume::GlobalIndexSet; + using PositionVector = typename BoundaryInteractionVolume::PositionVector; + + public: + //! update cached objects + template<class InteractionVolume> + void updateAdvection(const InteractionVolume& iv, const SubControlVolumeFace &scvf) + { + const auto& localFaceData = iv.getLocalFaceData(scvf); + // update the quantities that are equal for all phases + advectionVolVarsStencil_ = iv.volVarsStencil(); + advectionVolVarsPositions_ = iv.volVarsPositions(); + advectionTij_ = iv.getTransmissibilities(localFaceData); + + // we will need the neumann flux transformation on interior Neumann boundaries + advectionCij_ = iv.getNeumannFluxTransformationCoefficients(localFaceData); + + // The neumann fluxes always have to be set per phase + for (unsigned int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) + phaseNeumannFluxes_[phaseIdx] = iv.getNeumannFlux(localFaceData, phaseIdx); + } + + //! Returns the volume variables indices necessary for flux computation + //! This includes all participating boundary volume variables. Since we + //! do not allow mixed BC for the mpfa this is the same for all phases. + const Stencil& advectionVolVarsStencil() const + { return advectionVolVarsStencil_; } + + //! Returns the position on which the volume variables live. This is + //! necessary as we need to evaluate gravity also for the boundary volvars + const PositionVector& advectionVolVarsPositions() const + { return advectionVolVarsPositions_; } + + //! Returns the transmissibilities associated with the volume variables + //! All phases flow through the same rock, thus, tij are equal for all phases + const CoefficientVector& advectionTij() const + { return advectionTij_; } + + //! Returns the vector of coefficients with which the vector of neumann boundary conditions + //! has to be multiplied in order to transform them on the scvf this cache belongs to + const CoefficientVector& advectionCij() const + { return advectionCij_; } + + //! If the useTpfaBoundary property is set to false, the boundary conditions + //! are put into the local systems leading to possible contributions on all faces + Scalar advectionNeumannFlux(unsigned int phaseIdx) const + { return phaseNeumannFluxes_[phaseIdx]; } + + private: + // Quantities associated with advection + Stencil advectionVolVarsStencil_; + PositionVector advectionVolVarsPositions_; + CoefficientVector advectionTij_; + CoefficientVector advectionCij_; + std::array<Scalar, numPhases> phaseNeumannFluxes_; + }; + +public: + // state the discretization method this implementation belongs to + static const DiscretizationMethods myDiscretizationMethod = DiscretizationMethods::CCMpfa; + + // state the new type for the corresponding cache + using Cache = MpfaFacetCouplingDarcysLawCache; + + static Scalar flux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const unsigned int phaseIdx, + const ElementFluxVariablesCache& elemFluxVarsCache) + { + static const bool gravity = GET_PARAM_FROM_GROUP(TypeTag, bool, Problem, EnableGravity); + + const auto& fluxVarsCache = elemFluxVarsCache[scvf]; + const auto& volVarsStencil = fluxVarsCache.advectionVolVarsStencil(); + const auto& volVarsPositions = fluxVarsCache.advectionVolVarsPositions(); + const auto& tij = fluxVarsCache.advectionTij(); + + const bool isInteriorBoundary = enableInteriorBoundaries && fluxVarsCache.isInteriorBoundary(); + + // Calculate the interface density for gravity evaluation + const auto rho = interpolateDensity(fvGeometry, elemVolVars, scvf, fluxVarsCache, phaseIdx, isInteriorBoundary); + + // calculate Tij*pj + Scalar flux(0.0); + unsigned int localIdx = 0; + for (const auto volVarIdx : volVarsStencil) + { + const auto& volVars = elemVolVars[volVarIdx]; + Scalar h = volVars.pressure(phaseIdx); + + // if gravity is enabled, add gravitational acceleration + if (gravity) + { + // gravitational acceleration in the center of the actual element + const auto x = volVarsPositions[localIdx]; + const auto g = problem.gravityAtPos(x); + + h -= rho*(g*x); + } + + flux += tij[localIdx++]*h; + } + + // if no interior boundaries are present, return the flux + if (!enableInteriorBoundaries) + return useTpfaBoundary ? flux : flux + fluxVarsCache.advectionNeumannFlux(phaseIdx); + + // Handle interior boundaries + flux += computeInteriorBoundaryContribution(problem, fvGeometry, elemVolVars, fluxVarsCache, phaseIdx, rho); + + // return overall resulting flux + return useTpfaBoundary ? flux : flux + fluxVarsCache.advectionNeumannFlux(phaseIdx); + } + + static Scalar interpolateDensity(const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const FluxVariablesCache& fluxVarsCache, + const unsigned int phaseIdx, + const bool isInteriorBoundary) + { + static const bool gravity = GET_PARAM_FROM_GROUP(TypeTag, bool, Problem, EnableGravity); + + if (!gravity) + return Scalar(0.0); + else + { + // Return facet density on interior boundaries + if (isInteriorBoundary) + return fluxVarsCache.interiorBoundaryDataSelf().facetVolVars(fvGeometry).density(phaseIdx); + + // use arithmetic mean of the densities around the scvf + if (!scvf.boundary()) + { + Scalar rho = elemVolVars[scvf.insideScvIdx()].density(phaseIdx); + for (auto outsideIdx : scvf.outsideScvIndices()) + rho += elemVolVars[outsideIdx].density(phaseIdx); + return rho/(scvf.outsideScvIndices().size()+1); + } + else + return elemVolVars[scvf.outsideScvIdx()].density(phaseIdx); + } + } + + static Scalar computeInteriorBoundaryContribution(const Problem& problem, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const FluxVariablesCache& fluxVarsCache, + unsigned int phaseIdx, Scalar rho) + { + static const bool gravity = GET_PARAM_FROM_GROUP(TypeTag, bool, Problem, EnableGravity); + + // obtain the transmissibilites associated with all pressures + const auto& tij = fluxVarsCache.advectionTij(); + + // the interior dirichlet boundaries local indices start after + // the cell and the domain Dirichlet boundary pressures + const auto startIdx = fluxVarsCache.advectionVolVarsStencil().size(); + + // The vector of interior neumann fluxes + const auto& cij = fluxVarsCache.advectionCij(); + CoefficientVector facetCouplingFluxes(cij.size(), 0.0); + + // add interior Dirichlet boundary contributions + Scalar flux = 0.0; + for (auto&& data : fluxVarsCache.interiorBoundaryData()) + { + // Add additional Dirichlet fluxes for interior Dirichlet faces + if (data.faceType() == MpfaFaceTypes::interiorDirichlet) + { + Scalar h = data.facetVolVars(fvGeometry).pressure(phaseIdx); + + if (gravity) + { + const auto x = fvGeometry.scvf(data.scvfIndex()).ipGlobal(); + const auto g = problem.gravityAtPos(x); + + h -= rho*(g*x); + } + + flux += tij[startIdx + data.localIndexInInteractionVolume()]*h; + } + + // add neumann contributions + if (data.faceType() == MpfaFaceTypes::interiorNeumann) + { + // get the scvf corresponding to actual interior neumann face + const auto& curScvf = fvGeometry.scvf(data.scvfIndex()); + + // get the volvars of the actual interior neumann face + const auto facetVolVars = data.facetVolVars(fvGeometry, curScvf); + + // calculate "leakage factor" + const auto n = curScvf.unitOuterNormal(); + const auto v = [&] () + { + auto res = n; + res *= -0.5*facetVolVars.extrusionFactor(); + res += curScvf.ipGlobal(); + res -= curScvf.facetCorner(); + res /= res.two_norm2(); + return res; + } (); + + // add value to vector of interior neumann fluxes + facetCouplingFluxes[data.localIndexInInteractionVolume()] += facetVolVars.pressure(phaseIdx)* + curScvf.area()* + elemVolVars[curScvf.insideScvIdx()].extrusionFactor()* + MpfaHelper::nT_M_v(n, facetVolVars.permeability(), v); + } + } + + return flux + cij*facetCouplingFluxes; + } +}; + +} // end namespace + +#endif diff --git a/dumux/mixeddimension/facet/mpfa/fickslaw.hh b/dumux/mixeddimension/facet/mpfa/fickslaw.hh new file mode 100644 index 0000000000..d97477569e --- /dev/null +++ b/dumux/mixeddimension/facet/mpfa/fickslaw.hh @@ -0,0 +1,334 @@ +// -*- 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 This file contains the data which is required to calculate + * molar and mass fluxes of a component in a fluid phase over a face of a finite volume by means + * of Fick's Law for cell-centered MPFA models in the presence of lower dimensional (coupled) elements + * living on the this domain's element facets. + */ +#ifndef DUMUX_DISCRETIZATION_CC_MPFA_FACET_FICKS_LAW_HH +#define DUMUX_DISCRETIZATION_CC_MPFA_FACET_FICKS_LAW_HH + +#include <dumux/discretization/cellcentered/mpfa/fickslaw.hh> +#include <dumux/discretization/cellcentered/mpfa/facetypes.hh> + +namespace Dumux +{ +/*! + * \ingroup CCMpfaFicksLaw + * \brief Specialization of Fick's Law for the CCMpfa method with lower dimensional + * elements living on the bulk elements' facets. + */ +template <class TypeTag> +class CCMpfaFacetCouplingFicksLaw : public FicksLawImplementation<TypeTag, DiscretizationMethods::CCMpfa> +{ + using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar); + using Problem = typename GET_PROP_TYPE(TypeTag, Problem); + using GridView = typename GET_PROP_TYPE(TypeTag, GridView); + using MpfaHelper = typename GET_PROP_TYPE(TypeTag, MpfaHelper); + using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables); + using SubControlVolume = typename GET_PROP_TYPE(TypeTag, SubControlVolume); + using FVElementGeometry = typename GET_PROP_TYPE(TypeTag, FVElementGeometry); + using EffDiffModel = typename GET_PROP_TYPE(TypeTag, EffectiveDiffusivityModel); + using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace); + using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables); + using ElementFluxVariablesCache = typename GET_PROP_TYPE(TypeTag, ElementFluxVariablesCache); + using FluxVariablesCache = typename GET_PROP_TYPE(TypeTag, FluxVariablesCache); + + // Always use the dynamic type for vectors (compatibility with the boundary) + using BoundaryInteractionVolume = typename GET_PROP_TYPE(TypeTag, BoundaryInteractionVolume); + using CoefficientVector = typename BoundaryInteractionVolume::Vector; + + using Element = typename GridView::template Codim<0>::Entity; + using IndexType = typename GridView::IndexSet::IndexType; + + static constexpr int numPhases = GET_PROP_VALUE(TypeTag, NumPhases); + static constexpr bool useTpfaBoundary = GET_PROP_VALUE(TypeTag, UseTpfaBoundary); + static constexpr bool facetCoupling = GET_PROP_VALUE(TypeTag, MpfaFacetCoupling); + static constexpr bool enableInteriorBoundaries = GET_PROP_VALUE(TypeTag, EnableInteriorBoundaries); + + //! The cache used in conjunction with the mpfa Fick's Law + class MpfaFicksLawCache + { + static const int numComponents = GET_PROP_VALUE(TypeTag, NumComponents); + + // We always use the dynamic types here to be compatible on the boundary + using Stencil = typename BoundaryInteractionVolume::GlobalIndexSet; + using PositionVector = typename BoundaryInteractionVolume::PositionVector; + + public: + //! The constructor. Initializes the Neumann flux to zero + MpfaFicksLawCache() { componentNeumannFluxes_.fill(0.0); } + + // update cached objects for the diffusive fluxes + template<typename InteractionVolume> + void updateDiffusion(const InteractionVolume& iv, const SubControlVolumeFace &scvf, + unsigned int phaseIdx, unsigned int compIdx) + { + const auto& localFaceData = iv.getLocalFaceData(scvf); + diffusionTij_[phaseIdx][compIdx] = iv.getTransmissibilities(localFaceData); + // get the stencil only for the first call + if (phaseIdx == 0 && compIdx == 0) + diffusionVolVarsStencil_ = iv.volVarsStencil(); + + // we will need the neumann flux transformation on interior Neumann boundaries + diffusionCij_[phaseIdx][compIdx] = iv.getNeumannFluxTransformationCoefficients(localFaceData); + + //! For compositional models, we associate neumann fluxes with the phases (main components) + //! This is done in the AdvectionCache. However, in single-phasic models we solve the phase AND + //! the component mass balance equations. Thus, in this case we have diffusive neumann contributions. + //! we assume compIdx = eqIdx + if (numPhases == 1 && phaseIdx != compIdx) + componentNeumannFluxes_[compIdx] = iv.getNeumannFlux(localFaceData, compIdx); + + } + + //! Returns the volume variables indices necessary for diffusive flux + //! computation. This includes all participating boundary volume variables + //! and it can be different for the phases & components. + const Stencil& diffusionVolVarsStencil(unsigned int phaseIdx, unsigned int compIdx) const + { return diffusionVolVarsStencil_; } + + //! Returns the transmissibilities associated with the volume variables + //! This can be different for the phases & components. + const CoefficientVector& diffusionTij(unsigned int phaseIdx, unsigned int compIdx) const + { return diffusionTij_[phaseIdx][compIdx]; } + + //! Returns the vector of coefficients with which the vector of neumann boundary conditions + //! has to be multiplied in order to transform them on the scvf this cache belongs to + const CoefficientVector& diffusionCij(unsigned int phaseIdx, unsigned int compIdx) const + { return diffusionCij_[phaseIdx][compIdx]; } + + //! If the useTpfaBoundary property is set to false, the boundary conditions + //! are put into the local systems leading to possible contributions on all faces + Scalar componentNeumannFlux(unsigned int compIdx) const + { + assert(numPhases == 1); + return componentNeumannFluxes_[compIdx]; + } + + private: + // Quantities associated with molecular diffusion + Stencil diffusionVolVarsStencil_; + std::array< std::array<CoefficientVector, numComponents>, numPhases> diffusionTij_; + std::array< std::array<CoefficientVector, numComponents>, numPhases> diffusionCij_; + + // diffusive neumann flux for single-phasic models + std::array<Scalar, numComponents> componentNeumannFluxes_; + }; + +public: + // state the discretization method this implementation belongs to + static const DiscretizationMethods myDiscretizationMethod = DiscretizationMethods::CCMpfa; + + // state the new type for the corresponding cache + using Cache = MpfaFicksLawCache; + + static Scalar flux(const Problem& problem, + const Element& element, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + int phaseIdx, int compIdx, + const ElementFluxVariablesCache& elemFluxVarsCache, + bool useMoles = true) + { + const auto& fluxVarsCache = elemFluxVarsCache[scvf]; + const auto& volVarsStencil = fluxVarsCache.diffusionVolVarsStencil(phaseIdx, compIdx); + const auto& tij = fluxVarsCache.diffusionTij(phaseIdx, compIdx); + + const bool isInteriorBoundary = enableInteriorBoundaries && fluxVarsCache.isInteriorBoundary(); + + // get the scaling factor for the effective diffusive fluxes + const auto effFactor = computeEffectivityFactor(fvGeometry, elemVolVars, scvf, fluxVarsCache, phaseIdx, isInteriorBoundary); + + // if factor is zero, the flux will end up zero anyway + if (effFactor == 0.0) + return 0.0; + + // lambda functions depending on if we use mole or mass fractions + auto getX = [useMoles, phaseIdx, compIdx] (const auto& volVars) + { return useMoles ? volVars.moleFraction(phaseIdx, compIdx) : volVars.massFraction(phaseIdx, compIdx); }; + + auto getRho = [useMoles, phaseIdx] (const auto& volVars) + { return useMoles ? volVars.molarDensity(phaseIdx) : volVars.density(phaseIdx); }; + + // calculate the density at the interface + const auto rho = interpolateDensity(fvGeometry, elemVolVars, scvf, fluxVarsCache, getRho, isInteriorBoundary); + + // calculate Tij*xj + Scalar flux(0.0); + unsigned int localIdx = 0; + for (const auto volVarIdx : volVarsStencil) + flux += tij[localIdx++]*getX(elemVolVars[volVarIdx]); + + // if no interior boundaries are present, return effective mass flux + if (!enableInteriorBoundaries) + return useTpfaBoundary ? flux*rho*effFactor : flux*rho*effFactor + fluxVarsCache.componentNeumannFlux(compIdx); + + // Handle interior boundaries + flux += computeInteriorBoundaryContribution(fvGeometry, elemVolVars, fluxVarsCache, getX, phaseIdx, compIdx); + + // return overall resulting flux + return useTpfaBoundary ? flux*rho*effFactor : flux*rho*effFactor + fluxVarsCache.componentNeumannFlux(compIdx); + } + + template<typename GetRhoFunction> + static Scalar interpolateDensity(const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const FluxVariablesCache& fluxVarsCache, + const GetRhoFunction& getRho, + const bool isInteriorBoundary) + { + + // maybe use the density of the interior BC on the facet + if (isInteriorBoundary) + return getRho(fluxVarsCache.interiorBoundaryDataSelf().facetVolVars(fvGeometry)); + + // use arithmetic mean of the densities around the scvf + if (!scvf.boundary()) + { + Scalar rho = getRho(elemVolVars[scvf.insideScvIdx()]); + for (auto outsideIdx : scvf.outsideScvIndices()) + rho += getRho(elemVolVars[outsideIdx]); + return rho/(scvf.outsideScvIndices().size()+1); + } + else + return getRho(elemVolVars[scvf.outsideScvIdx()]); + } + + //! Here we want to calculate the factors with which the diffusion coefficient has to be + //! scaled to get the effective diffusivity. For this we use the effective diffusivity with + //! a diffusion coefficient of 1.0 as input. Then we scale the transmissibilites during flux + //! calculation (above) with the harmonic average of the two factors + static Scalar computeEffectivityFactor(const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const SubControlVolumeFace& scvf, + const FluxVariablesCache& fluxVarsCache, + const unsigned int phaseIdx, + const bool isInteriorBoundary) + { + if (isInteriorBoundary) + { + // use harmonic mean between the interior and the facet volvars + const auto& data = fluxVarsCache.interiorBoundaryDataSelf(); + + const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()]; + const auto factor = EffDiffModel::effectiveDiffusivity(insideVolVars.porosity(), + insideVolVars.saturation(phaseIdx), + /*Diffusion coefficient*/ 1.0); + + const auto facetVolVars = data.facetVolVars(fvGeometry); + const auto outsideFactor = EffDiffModel::effectiveDiffusivity(facetVolVars.porosity(), + facetVolVars.saturation(phaseIdx), + /*Diffusion coefficient*/ 1.0); + + return harmonicMean(factor, outsideFactor); + } + + // use the harmonic mean between inside and outside + const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()]; + const auto factor = EffDiffModel::effectiveDiffusivity(insideVolVars.porosity(), + insideVolVars.saturation(phaseIdx), + /*Diffusion coefficient*/ 1.0); + + if (!scvf.boundary()) + { + // interpret outside factor as arithmetic mean + Scalar outsideFactor = 0.0; + for (auto outsideIdx : scvf.outsideScvIndices()) + { + const auto& outsideVolVars = elemVolVars[outsideIdx]; + outsideFactor += EffDiffModel::effectiveDiffusivity(outsideVolVars.porosity(), + outsideVolVars.saturation(phaseIdx), + /*Diffusion coefficient*/ 1.0); + } + outsideFactor /= scvf.outsideScvIndices().size(); + + // use the harmonic mean of the two + return harmonicMean(factor, outsideFactor); + } + + return factor; + } + + template<typename GetXFunction> + static Scalar computeInteriorBoundaryContribution(const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars, + const FluxVariablesCache& fluxVarsCache, + const GetXFunction& getX, + unsigned int phaseIdx, unsigned int compIdx) + { + // obtain the transmissibilites associated with all pressures + const auto& tij = fluxVarsCache.diffusionTij(phaseIdx, compIdx); + + // the interior dirichlet boundaries local indices start after + // the cell and the domain Dirichlet boundary pressures + const auto startIdx = fluxVarsCache.diffusionVolVarsStencil(phaseIdx, compIdx).size(); + + // The vector of interior neumann fluxes + const auto& cij = fluxVarsCache.advectionCij(); + CoefficientVector facetCouplingFluxes(cij.size(), 0.0); + + // add interior Dirichlet boundary contributions + Scalar flux = 0.0; + for (auto&& data : fluxVarsCache.interiorBoundaryData()) + { + // Add additional Dirichlet fluxes for interior Dirichlet faces + if (data.faceType() == MpfaFaceTypes::interiorDirichlet) + flux += tij[startIdx + data.localIndexInInteractionVolume()]*getX(data.facetVolVars(fvGeometry)); + + // add neumann contributions + if (data.faceType() == MpfaFaceTypes::interiorNeumann) + { + // get the scvf corresponding to actual interior neumann face + const auto& curScvf = fvGeometry.scvf(data.scvfIndex()); + + // get the volvars of the actual interior neumann face + const auto facetVolVars = data.facetVolVars(fvGeometry, curScvf); + + // calculate "leakage factor" + const auto n = curScvf.unitOuterNormal(); + const auto v = [&] () + { + auto res = n; + res *= -0.5*facetVolVars.extrusionFactor(); + res += curScvf.ipGlobal(); + res -= curScvf.facetCorner(); + res /= res.two_norm2(); + return res; + } (); + + // add value to vector of interior neumann fluxes + facetCouplingFluxes[data.localIndexInInteractionVolume()] += getX(facetVolVars)* + curScvf.area()* + elemVolVars[curScvf.insideScvIdx()].extrusionFactor()* + MpfaHelper::nT_M_v(n, facetVolVars.diffusionCoefficient(phaseIdx, compIdx), v); + } + } + + return flux + cij*facetCouplingFluxes; + } +}; + +} // end namespace + +#endif diff --git a/dumux/mixeddimension/facet/mpfa/interactionvolume.hh b/dumux/mixeddimension/facet/mpfa/interactionvolume.hh new file mode 100644 index 0000000000..92410e3bf1 --- /dev/null +++ b/dumux/mixeddimension/facet/mpfa/interactionvolume.hh @@ -0,0 +1,149 @@ +// -*- 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 Base classes for interaction volumes of mpfa models with active coupling over the element facets. + */ +#ifndef DUMUX_MIXEDDIMENSION_FACET_INTERACTIONVOLUME_HH +#define DUMUX_MIXEDDIMENSION_FACET_INTERACTIONVOLUME_HH + +#include <dumux/discretization/cellcentered/mpfa/interactionvolume.hh> +#include <dumux/discretization/cellcentered/mpfa/facetypes.hh> +#include <dumux/discretization/cellcentered/mpfa/methods.hh> + +namespace Dumux +{ +// forward declaration of the implementation +template<class TypeTag, MpfaMethods Method> +class CCMpfaFacetCouplingInteractionVolumeImplementation; + +/*! + * \ingroup MixedDimension + * \brief Base class for the interaction volumes of the mpfa method with active coupling over the element facets. + */ +template<class TypeTag> +using CCMpfaFacetCouplingInteractionVolume = CCMpfaFacetCouplingInteractionVolumeImplementation<TypeTag, GET_PROP_VALUE(TypeTag, MpfaMethod)>; + +// Per default, we inherit from the standard interaction volumes +template<class TypeTag, MpfaMethods Method> +class CCMpfaFacetCouplingInteractionVolumeImplementation : public CCMpfaInteractionVolumeImplementation<TypeTag, Method> {}; + +// the o-method interaction volume is substituted by the one including data on the facet element's +// tensorial quantities into the local system to be solved. This has to be used as boundary interaction volume +template<class TypeTag> +class CCMpfaFacetCouplingInteractionVolumeImplementation<TypeTag, MpfaMethods::oMethod> + : public CCMpfaInteractionVolumeImplementation<TypeTag, MpfaMethods::oMethod> +{ + using ParentType = CCMpfaInteractionVolumeImplementation<TypeTag, MpfaMethods::oMethod>; + + using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar); + using GridView = typename GET_PROP_TYPE(TypeTag, GridView); + using Element = typename GridView::template Codim<0>::Entity; + using Problem = typename GET_PROP_TYPE(TypeTag, Problem); + using MpfaHelper = typename GET_PROP_TYPE(TypeTag, MpfaHelper); + using PrimaryVariables = typename GET_PROP_TYPE(TypeTag, PrimaryVariables); + using FVElementGeometry = typename GET_PROP_TYPE(TypeTag, FVElementGeometry); + using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace); + using InteriorBoundaryData = typename GET_PROP_TYPE(TypeTag, InteriorBoundaryData); + using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables); + + using LocalScvfType = typename ParentType::Traits::LocalScvfType; + + static constexpr bool useTpfaBoundary = GET_PROP_VALUE(TypeTag, UseTpfaBoundary); + +public: + using typename ParentType::LocalIndexType; + using typename ParentType::Seed; + + CCMpfaFacetCouplingInteractionVolumeImplementation(const Seed& seed, + const Problem& problem, + const FVElementGeometry& fvGeometry, + const ElementVolumeVariables& elemVolVars) + : ParentType(seed, problem, fvGeometry, elemVolVars) + {} + +public: + // We include data on the tensorial quantities of the facet elements here + template<typename GetTensorFunction> + Scalar interiorNeumannTerm(const GetTensorFunction& getTensor, + const Element& element, + const LocalScvfType& localScvf, + const InteriorBoundaryData& data) const + { + // obtain the complete data on the facet element + const auto completeFacetData = data.completeCoupledFacetData(this->fvGeometry_()); + + // calculate "leakage factor" + const auto n = localScvf.unitOuterNormal(); + const auto v = [&] () + { + auto res = n; + res *= -0.5*completeFacetData.volVars().extrusionFactor(); + res += localScvf.ip(); + res -= localScvf.globalScvf().facetCorner(); + res /= res.two_norm2(); + return res; + } (); + + // substract (n*T*v)*Area from diagonal matrix entry + const auto facetTensor = getTensor(completeFacetData.problem(), + completeFacetData.element(), + completeFacetData.volVars(), + completeFacetData.fvGeometry(), + completeFacetData.scv()); + + return localScvf.area()* + this->elemVolVars_()[localScvf.insideGlobalScvIndex()].extrusionFactor()* + MpfaHelper::nT_M_v(n, facetTensor, v); + } + + void assembleNeumannFluxVector_() + { + // initialize the neumann fluxes vector to zero + this->neumannFluxes_.resize(this->fluxFaceIndexSet_.size(), PrimaryVariables(0.0)); + + if (!this->onDomainOrInteriorBoundary() || useTpfaBoundary) + return; + + LocalIndexType fluxFaceIdx = 0; + for (auto localFluxFaceIdx : this->fluxFaceIndexSet_) + { + const auto& localScvf = this->localScvf_(localFluxFaceIdx); + const auto faceType = localScvf.faceType(); + + if (faceType == MpfaFaceTypes::neumann) + { + const auto& element = this->localElement_(localScvf.insideLocalScvIndex()); + const auto& globalScvf = this->fvGeometry_().scvf(localScvf.insideGlobalScvfIndex()); + auto neumannFlux = this->problem_().neumann(element, this->fvGeometry_(), this->elemVolVars_(), globalScvf); + neumannFlux *= globalScvf.area(); + neumannFlux *= this->elemVolVars_()[globalScvf.insideScvIdx()].extrusionFactor(); + + // The flux is assumed to be prescribed in the form of -D*gradU + this->neumannFluxes_[fluxFaceIdx] = neumannFlux; + } + + fluxFaceIdx++; + } + } +}; + +} // end namespace + +#endif diff --git a/dumux/mixeddimension/facet/mpfa/interiorboundarydata.hh b/dumux/mixeddimension/facet/mpfa/interiorboundarydata.hh new file mode 100644 index 0000000000..6a0fe64c23 --- /dev/null +++ b/dumux/mixeddimension/facet/mpfa/interiorboundarydata.hh @@ -0,0 +1,201 @@ +// -*- 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 A class to store info on interior boundaries + */ +#ifndef DUMUX_DISCRETIZATION_CC_MPFA_FACET_INTERIORBOUNDARYDATA_HH +#define DUMUX_DISCRETIZATION_CC_MPFA_FACET_INTERIORBOUNDARYDATA_HH + +#include <dumux/discretization/cellcentered/mpfa/facetypes.hh> +#include <dumux/mixeddimension/properties.hh> + +namespace Dumux +{ + +template<class TypeTag> +class CCMpfaFacetCouplingInteriorBoundaryData +{ + // types associated with the low dim domain + using GlobalProblemTypeTag = typename GET_PROP_TYPE(TypeTag, GlobalProblemTypeTag); + using LowDimProblemTypeTag = typename GET_PROP_TYPE(GlobalProblemTypeTag, LowDimProblemTypeTag); + + using Problem = typename GET_PROP_TYPE(TypeTag, Problem); + using GridView = typename GET_PROP_TYPE(TypeTag, GridView); + using Element = typename GridView::template Codim<0>::Entity; + using SpatialParams = typename GET_PROP_TYPE(TypeTag, SpatialParams); + using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables); + using FVElementGeometry = typename GET_PROP_TYPE(TypeTag, FVElementGeometry); + using SubControlVolumeFace = typename GET_PROP_TYPE(TypeTag, SubControlVolumeFace); + using ElementSolutionVector = typename GET_PROP_TYPE(TypeTag, ElementSolutionVector); + using BoundaryInteractionVolume = typename GET_PROP_TYPE(TypeTag, BoundaryInteractionVolume); + + using IndexType = typename GridView::IndexSet::IndexType; + using LocalIndexType = typename BoundaryInteractionVolume::LocalIndexType; + + using LowDimProblem = typename GET_PROP_TYPE(LowDimProblemTypeTag, Problem); + using LowDimSpatialParams = typename GET_PROP_TYPE(LowDimProblemTypeTag, SpatialParams); + using LowDimVolumeVariables = typename GET_PROP_TYPE(LowDimProblemTypeTag, VolumeVariables); + using LowDimGridView = typename GET_PROP_TYPE(LowDimProblemTypeTag, GridView); + using LowDimElement = typename LowDimGridView::template Codim<0>::Entity; + using LowDimFVElementGeometry = typename GET_PROP_TYPE(LowDimProblemTypeTag, FVElementGeometry); + using LowDimSubControlVolume = typename GET_PROP_TYPE(LowDimProblemTypeTag, SubControlVolume); + + //! Dummy type for the CompleteCoupledFacetData struct. + //! Implementations need to have at least the provided interfaces. + //! Note that the return types are also "wrong" (Here just to satisfy the compiler) + struct CompleteCoupledFacetData + { + const LowDimProblem& problem() const + { return *lowDimProblemPtr_; } + + const LowDimSpatialParams& spatialParams() const + { return *lowDimSpatialParamsPtr_; } + + const LowDimVolumeVariables& volVars() const + { return lowDimVolVars_; } + + const LowDimElement& element() const + { return lowDimElement_;} + + const LowDimFVElementGeometry& fvGeometry() const + { return lowDimFvGeometry_; } + + const LowDimSubControlVolume& scv() const + { return lowDimScv_; } + + CompleteCoupledFacetData(const LowDimProblem& problem, + const IndexType elementIndex, + LowDimElement&& element, + LowDimFVElementGeometry&& fvGeometry, + LowDimVolumeVariables&& volVars) + : lowDimProblemPtr_(&problem), + lowDimSpatialParamsPtr_(&problem.spatialParams()), + lowDimElement_(std::move(element)), + lowDimFvGeometry_(std::move(fvGeometry)), + lowDimVolVars_(std::move(volVars)), + lowDimScv_(lowDimFvGeometry_.scv(elementIndex)) + {} + + private: + const LowDimProblem* lowDimProblemPtr_; + const LowDimSpatialParams* lowDimSpatialParamsPtr_; + + LowDimElement lowDimElement_; + LowDimFVElementGeometry lowDimFvGeometry_; + LowDimVolumeVariables lowDimVolVars_; + const LowDimSubControlVolume& lowDimScv_; + }; + +public: + //! the constructor + CCMpfaFacetCouplingInteriorBoundaryData(const Problem& problem, + IndexType elementIndex, + IndexType scvfIndex, + LocalIndexType localIndex, + MpfaFaceTypes faceType) + : problemPtr_(&problem), + elementIndex_(elementIndex), + scvfIndex_(scvfIndex), + localIndex_(localIndex), + faceType_(faceType) + {} + + //! returns the global index of the element/scv connected to the interior boundary + IndexType elementIndex() const + { return elementIndex_; } + + //! returns the global index of the scvf connected to the interior boundary + IndexType scvfIndex() const + { return scvfIndex_; } + + //! returns the local index i of the scvf within the interaction volume. + //! This is either: + //! - the i-th flux face index (interior neumann boundaries) + //! - the i-th interior dirichlet face (interior dirichlet boundaries) + LocalIndexType localIndexInInteractionVolume() const + { return localIndex_; } + + //! returns the face type of this scvf + MpfaFaceTypes faceType() const + { return faceType_; } + + //! returns the volume variables for interior dirichlet boundaries + LowDimVolumeVariables facetVolVars(const FVElementGeometry& fvGeometry) const + { + return problem_().couplingManager().lowDimVolVars(problem_().model().globalFvGeometry().element(elementIndex()), + fvGeometry, + fvGeometry.scvf(scvfIndex())); + } + + //! returns the volume variables for interior dirichlet boundaries + LowDimVolumeVariables facetVolVars(const FVElementGeometry& fvGeometry, const SubControlVolumeFace& scvf) const + { + assert(scvf.index() == scvfIndex() && "calling facet volume variables for an scvf other than the bound one"); + return problem_().couplingManager().lowDimVolVars(problem_().model().globalFvGeometry().element(elementIndex()), + fvGeometry, + scvf); + } + + //! The following interface is here for compatibility reasonsto be overloaded for problems using facet coupling. + //! prepares all the necessary variables of the other domain. + //! Note that also an implementation of the CompleteFacetData structure has to be provided. + CompleteCoupledFacetData completeCoupledFacetData(const FVElementGeometry& fvGeometry) const + { + const auto& couplingMapper = problem_().couplingManager().couplingMapper(); + + // get coupling data for this scvf + const auto element = problem_().model().globalFvGeometry().element(elementIndex()); + const auto& scvfCouplingData = couplingMapper.getBulkCouplingData(element).getScvfCouplingData(fvGeometry.scvf(scvfIndex())); + + // obtain data necessary to fully instantiate the complete coupled facet data + assert(!scvfCouplingData.first && "no coupled facet element found for given scvf!"); + const auto& lowDimProblem = problem_().couplingManager().lowDimProblem(); + + auto lowDimElement = lowDimProblem.model().globalFvGeometry().element(scvfCouplingData.second); + auto lowDimFvGeometry = localView(lowDimProblem.model().globalFvGeometry()); + lowDimFvGeometry.bindElement(lowDimElement); + + LowDimVolumeVariables lowDimVolVars; + lowDimVolVars.update(lowDimProblem.model().elementSolution(lowDimElement, lowDimProblem.model().curSol()), + lowDimProblem, + lowDimElement, + lowDimFvGeometry.scv(scvfCouplingData.second)); + + return CompleteCoupledFacetData(lowDimProblem, + scvfCouplingData.second, + std::move(lowDimElement), + std::move(lowDimFvGeometry), + std::move(lowDimVolVars)); + } + +private: + const Problem& problem_() const + { return *problemPtr_; } + + const Problem* problemPtr_; + + IndexType elementIndex_; + IndexType scvfIndex_; + LocalIndexType localIndex_; + MpfaFaceTypes faceType_; +}; +} // end namespace + +#endif diff --git a/dumux/mixeddimension/facet/mpfa/properties.hh b/dumux/mixeddimension/facet/mpfa/properties.hh new file mode 100644 index 0000000000..32e11462ac --- /dev/null +++ b/dumux/mixeddimension/facet/mpfa/properties.hh @@ -0,0 +1,72 @@ +// -*- 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 + * \ingroup MixedDimension + * \brief Base properties for the bulk problems in mixed dimensional models + * with a lower dimensional model living on the element facets. + */ + +#ifndef DUMUX_FACET_MIXEDDIMENSION_PROPERTIES_HH +#define DUMUX_FACET_MIXEDDIMENSION_PROPERTIES_HH + +#include <dumux/implicit/cellcentered/mpfa/properties.hh> + +#include <dumux/mixeddimension/subproblemproperties.hh> +#include <dumux/mixeddimension/facet/mpfa/interactionvolume.hh> +#include <dumux/mixeddimension/facet/mpfa/interiorboundarydata.hh> +#include <dumux/mixeddimension/facet/mpfa/darcyslaw.hh> +#include <dumux/mixeddimension/facet/mpfa/fickslaw.hh> + +namespace Dumux +{ + +namespace Properties +{ +NEW_TYPE_TAG(FacetCouplingBulkMpfaModel, INHERITS_FROM(CCMpfaModel)); + +//! The interaction volume class +SET_TYPE_PROP(FacetCouplingBulkMpfaModel, InteractionVolume, CCMpfaFacetCouplingInteractionVolume<TypeTag>); + +//! The boundary interaction volume class (for methods other than the omethod) +SET_TYPE_PROP(FacetCouplingBulkMpfaModel, BoundaryInteractionVolume, CCMpfaFacetCouplingInteractionVolumeImplementation<TypeTag, MpfaMethods::oMethod>); + +//! The interior boundary data class +SET_TYPE_PROP(FacetCouplingBulkMpfaModel, InteriorBoundaryData, CCMpfaFacetCouplingInteriorBoundaryData<TypeTag>); + +//! Ẃe always enable interior boundaries +SET_BOOL_PROP(FacetCouplingBulkMpfaModel, EnableInteriorBoundaries, true); + +//! Facet coupling is always true here +SET_BOOL_PROP(FacetCouplingBulkMpfaModel, MpfaFacetCoupling, true); + +//! Darcy's Law +SET_TYPE_PROP(FacetCouplingBulkMpfaModel, AdvectionType, CCMpfaFacetCouplingDarcysLaw<TypeTag>); + +//! Ficks's Law +SET_TYPE_PROP(FacetCouplingBulkMpfaModel, MolecularDiffusionType, CCMpfaFacetCouplingFicksLaw<TypeTag>); + +//! Darcy's Law +// SET_TYPE_PROP(FacetCouplingBulkMpfaModel, AdvectionType, CCMpfaFacetCouplingFouriersLaw<TypeTag>); + +}//end namespace Properties + +}//end namespace Dumux + +#endif diff --git a/dumux/mixeddimension/facet/start.hh b/dumux/mixeddimension/facet/start.hh new file mode 100644 index 0000000000..bd59fe1673 --- /dev/null +++ b/dumux/mixeddimension/facet/start.hh @@ -0,0 +1,203 @@ +// -*- 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 Provides a few default main functions for convenience. + */ +#ifndef DUMUX_MIXEDDIMENSION_FACET_START_HH +#define DUMUX_MIXEDDIMENSION_FACET_START_HH + +#include <ctime> +#include <iostream> + +#include <dune/common/parallel/mpihelper.hh> +#include <dune/grid/io/file/dgfparser/dgfexception.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/common/parameterparser.hh> + +namespace Dumux +{ +// forward declaration of property tags +namespace Properties +{ +NEW_PROP_TAG(Scalar); +NEW_PROP_TAG(GridCreator); +NEW_PROP_TAG(Problem); +NEW_PROP_TAG(TimeManager); +} + +/*! + * \ingroup Start + * + * \brief Provides a main function which reads in parameters from the + * command line and a parameter file. + * + * \tparam TypeTag The type tag of the problem which needs to be solved + * + * \param argc The 'argc' argument of the main function: count of arguments (1 if there are no arguments) + * \param argv The 'argv' argument of the main function: array of pointers to the argument strings + * \param usage Callback function for printing the usage message + */ +template <class TypeTag> +int start_(int argc, + char **argv, + void (*usage)(const char *, const std::string &)) +{ + // some aliases for better readability + using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar); + using GridCreator = typename GET_PROP_TYPE(TypeTag, GridCreator); + using Problem = typename GET_PROP_TYPE(TypeTag, Problem); + using TimeManager = typename GET_PROP_TYPE(TypeTag, TimeManager); + using ParameterTree = typename GET_PROP(TypeTag, ParameterTree); + + // 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 the command line arguments and input file + //////////////////////////////////////////////////////////// + + // if the user just wanted to see the help / usage message show usage and stop program + if(!ParameterParser::parseCommandLineArguments(argc, argv, ParameterTree::tree(), usage)) + { + usage(argv[0], defaultUsageMessage(argv[0])); + return 0; + } + // parse the input file into the parameter tree + // check first if the user provided an input file through the command line, if not use the default + const auto parameterFileName = ParameterTree::tree().hasKey("ParameterFile") ? GET_RUNTIME_PARAM(TypeTag, std::string, ParameterFile) : ""; + ParameterParser::parseInputFile(argc, argv, ParameterTree::tree(), parameterFileName, usage); + + //////////////////////////////////////////////////////////// + // check for some user debugging parameters + //////////////////////////////////////////////////////////// + + bool printProps = false; // per default don't print all properties + if (ParameterTree::tree().hasKey("PrintProperties") || ParameterTree::tree().hasKey("TimeManager.PrintProperties")) + printProps = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, bool, TimeManager, PrintProperties); + + if (printProps && mpiHelper.rank() == 0) + Properties::print<TypeTag>(); + + bool printParams = true; // per default print all properties + if (ParameterTree::tree().hasKey("PrintParameters") || ParameterTree::tree().hasKey("TimeManager.PrintParameters")) + printParams = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, bool, TimeManager, PrintParameters); + + ////////////////////////////////////////////////////////////////////// + // try to create a grid (from the given grid file or the input file) + ///////////////////////////////////////////////////////////////////// + + try { GridCreator::makeGrid(); } + catch (...) { + std::string usageMessage = "\n\t -> Creation of the grid failed! <- \n\n"; + usageMessage += defaultUsageMessage(argv[0]); + usage(argv[0], usageMessage); + throw; + } + GridCreator::loadBalance(); + + ////////////////////////////////////////////////////////////////////// + // run the simulation + ///////////////////////////////////////////////////////////////////// + + // read the initial time step and the end time (mandatory parameters) + auto tEnd = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, TimeManager, TEnd); + auto dt = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, TimeManager, DtInitial); + + // check if we are about to restart a previously interrupted simulation + bool restart = false; + Scalar restartTime = 0; + if (ParameterTree::tree().hasKey("Restart") || ParameterTree::tree().hasKey("TimeManager.Restart")) + { + restart = true; + restartTime = GET_RUNTIME_PARAM_FROM_GROUP(TypeTag, Scalar, TimeManager, Restart); + } + + // instantiate and run the problem + TimeManager timeManager; + Problem problem(timeManager, GridCreator::bulkGrid().leafGridView(), GridCreator::lowDimGrid().leafGridView()); + timeManager.init(problem, restartTime, dt, tEnd, restart); + timeManager.run(); + + // print dumux end message and maybe the parameters for debugging + if (mpiHelper.rank() == 0) + { + DumuxMessage::print(/*firstCall=*/false); + + if (printParams) + Parameters::print<TypeTag>(); + } + + return 0; +} + +/*! + * \ingroup Start + * + * \brief Provides a main function with error handling + * + * \tparam TypeTag The type tag of the problem which needs to be solved + * + * \param argc The number of command line arguments of the program + * \param argv The contents of the command line arguments of the program + * \param usage Callback function for printing the usage message + */ +template <class TypeTag> +int start(int argc, + char **argv, + void (*usage)(const char *, const std::string &)) +{ + try { + return start_<TypeTag>(argc, argv, usage); + } + catch (ParameterException &e) { + Parameters::print<TypeTag>(); + 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." + << std::endl; + return 2; + } + catch (Dune::Exception &e) { + std::cerr << "Dune reported error: " << e << std::endl; + return 3; + } + catch (...) { + std::cerr << "Unknown exception thrown!\n"; + return 4; + } +} + +} // end namespace Dumux + +#endif -- GitLab