// -*- 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 3 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 . * *****************************************************************************/ /*! * \file * \ingroup EmbeddedCoupling * \brief Coupling manager for low-dimensional domains embedded in the bulk * domain. Point sources on each integration point are computed by an AABB tree. */ #ifndef DUMUX_MULTIDOMAIN_EMBEDDED_COUPLINGMANAGERBASE_HH #define DUMUX_MULTIDOMAIN_EMBEDDED_COUPLINGMANAGERBASE_HH #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Dumux { //! the default point source traits template struct DefaultPointSourceTraits { private: template using SubDomainTypeTag = typename MDTraits::template SubDomain::TypeTag; template using GridGeometry = GetPropType, Properties::GridGeometry>; template using NumEqVector = Dumux::NumEqVector, Properties::PrimaryVariables>>; public: //! export the point source type for domain i template using PointSource = IntegrationPointSource::GlobalCoordinate, NumEqVector>; //! export the point source helper type for domain i template using PointSourceHelper = IntegrationPointSourceHelper; //! export the point source data type using PointSourceData = Dumux::PointSourceData; }; /*! * \ingroup EmbeddedCoupling * \brief Manages the coupling between bulk elements and lower dimensional elements * Point sources on each integration point are computed by an AABB tree. */ template> class EmbeddedCouplingManagerBase : public CouplingManager { using ParentType = CouplingManager; using Scalar = typename MDTraits::Scalar; static constexpr auto bulkIdx = typename MDTraits::template SubDomain<0>::Index(); static constexpr auto lowDimIdx = typename MDTraits::template SubDomain<1>::Index(); using SolutionVector = typename MDTraits::SolutionVector; using PointSourceData = typename PSTraits::PointSourceData; // the sub domain type tags template using PointSource = typename PSTraits::template PointSource; template using SubDomainTypeTag = typename MDTraits::template SubDomain::TypeTag; template using Problem = GetPropType, Properties::Problem>; template using PrimaryVariables = GetPropType, Properties::PrimaryVariables>; template using GridGeometry = GetPropType, Properties::GridGeometry>; template using GridView = typename GridGeometry::GridView; template using ElementMapper = typename GridGeometry::ElementMapper; template using Element = typename GridView::template Codim<0>::Entity; template using GridIndex = typename IndexTraits>::GridIndex; template using CouplingStencil = std::vector>; static constexpr int bulkDim = GridView::dimension; static constexpr int lowDimDim = GridView::dimension; static constexpr int dimWorld = GridView::dimensionworld; template static constexpr bool isBox() { return GridGeometry::discMethod == DiscretizationMethod::box; } using GlobalPosition = typename Element::Geometry::GlobalCoordinate; using GlueType = MultiDomainGlue, GridView, ElementMapper, ElementMapper>; public: //! export traits using MultiDomainTraits = MDTraits; //! export the point source traits using PointSourceTraits = PSTraits; //! export stencil types template using CouplingStencils = std::unordered_map, CouplingStencil>; /*! * \brief call this after grid adaption */ void updateAfterGridAdaption(std::shared_ptr> bulkGridGeometry, std::shared_ptr> lowDimGridGeometry) { glue_ = std::make_shared(); } /*! * \brief Constructor */ EmbeddedCouplingManagerBase(std::shared_ptr> bulkGridGeometry, std::shared_ptr> lowDimGridGeometry) { updateAfterGridAdaption(bulkGridGeometry, lowDimGridGeometry); } /*! * \brief Methods to be accessed by main */ // \{ void init(std::shared_ptr> bulkProblem, std::shared_ptr> lowDimProblem, const SolutionVector& curSol) { this->updateSolution(curSol); this->setSubProblems(std::make_tuple(bulkProblem, lowDimProblem)); integrationOrder_ = getParam("MixedDimension.IntegrationOrder", 1); asImp_().computePointSourceData(integrationOrder_); } // \} /*! * \brief Methods to be accessed by the assembly */ // \{ /*! * \brief returns an iteratable container of all indices of degrees of freedom of domain j * that couple with / influence the element residual of the given element of domain i * * \param domainI the domain index of domain i * \param element the coupled element of domain í * \param domainJ the domain index of domain j * * \note The element residual definition depends on the discretization scheme of domain i * box: a container of the residuals of all sub control volumes * cc : the residual of the (sub) control volume * fem: the residual of the element * \note This function has to be implemented by all coupling managers for all combinations of i and j */ template const CouplingStencil& couplingStencil(Dune::index_constant domainI, const Element& element, Dune::index_constant domainJ) const { static_assert(i != j, "A domain cannot be coupled to itself!"); const auto eIdx = this->problem(domainI).gridGeometry().elementMapper().index(element); if (couplingStencils(domainI).count(eIdx)) return couplingStencils(domainI).at(eIdx); else return emptyStencil(domainI); } /*! * \brief evaluates the element residual of a coupled element of domain i which depends on the variables * at the degree of freedom with index dofIdxGlobalJ of domain j * * \param domainI the domain index of domain i * \param localAssemblerI the local assembler assembling the element residual of an element of domain i * \param domainJ the domain index of domain j * \param dofIdxGlobalJ the index of the degree of freedom of domain j which has an influence on the element residual of domain i * * \note we only need to evaluate the source contribution to the residual here as the coupling term is the source * \return the element residual */ template decltype(auto) evalCouplingResidual(Dune::index_constant domainI, const LocalAssemblerI& localAssemblerI, Dune::index_constant domainJ, std::size_t dofIdxGlobalJ) { static_assert(i != j, "A domain cannot be coupled to itself!"); typename LocalAssemblerI::LocalResidual::ElementResidualVector residual; const auto& element = localAssemblerI.element(); const auto& fvGeometry = localAssemblerI.fvGeometry(); const auto& curElemVolVars = localAssemblerI.curElemVolVars(); residual.resize(fvGeometry.numScv()); for (const auto& scv : scvs(fvGeometry)) { auto couplingSource = this->problem(domainI).scvPointSources(element, fvGeometry, curElemVolVars, scv); couplingSource += this->problem(domainI).source(element, fvGeometry, curElemVolVars, scv); couplingSource *= -GridGeometry::Extrusion::volume(scv)*curElemVolVars[scv].extrusionFactor(); residual[scv.indexInElement()] = couplingSource; } return residual; } // \} /* \brief Compute integration point point sources and associated data * * This method uses grid glue to intersect the given grids. Over each intersection * we later need to integrate a source term. This method places point sources * at each quadrature point and provides the point source with the necessary * information to compute integrals (quadrature weight and integration element) * \param order The order of the quadrature rule for integration of sources over an intersection * \param verbose If the point source computation is verbose */ void computePointSourceData(std::size_t order = 1, bool verbose = false) { // initilize the maps // do some logging and profiling Dune::Timer watch; std::cout << "Initializing the point sources..." << std::endl; // clear all internal members like pointsource vectors and stencils // initializes the point source id counter clear(); // precompute the vertex indices for efficiency for the box method this->precomputeVertexIndices(bulkIdx); this->precomputeVertexIndices(lowDimIdx); const auto& bulkGridGeometry = this->problem(bulkIdx).gridGeometry(); const auto& lowDimGridGeometry = this->problem(lowDimIdx).gridGeometry(); // intersect the bounding box trees glueGrids(); pointSourceData_.reserve(this->glue().size()); averageDistanceToBulkCell_.reserve(this->glue().size()); for (const auto& is : intersections(this->glue())) { // all inside elements are identical... const auto& inside = is.targetEntity(0); // get the intersection geometry for integrating over it const auto intersectionGeometry = is.geometry(); // get the Gaussian quadrature rule for the local intersection const auto& quad = Dune::QuadratureRules::rule(intersectionGeometry.type(), order); const std::size_t lowDimElementIdx = lowDimGridGeometry.elementMapper().index(inside); // iterate over all quadrature points for (auto&& qp : quad) { // compute the coupling stencils for (std::size_t outsideIdx = 0; outsideIdx < is.numDomainNeighbors(); ++outsideIdx) { const auto& outside = is.domainEntity(outsideIdx); const std::size_t bulkElementIdx = bulkGridGeometry.elementMapper().index(outside); // each quadrature point will be a point source for the sub problem const auto globalPos = intersectionGeometry.global(qp.position()); const auto id = idCounter_++; const auto qpweight = qp.weight(); const auto ie = intersectionGeometry.integrationElement(qp.position()); pointSources(bulkIdx).emplace_back(globalPos, id, qpweight, ie, bulkElementIdx); pointSources(bulkIdx).back().setEmbeddings(is.numDomainNeighbors()); pointSources(lowDimIdx).emplace_back(globalPos, id, qpweight, ie, lowDimElementIdx); pointSources(lowDimIdx).back().setEmbeddings(is.numDomainNeighbors()); // pre compute additional data used for the evaluation of // the actual solution dependent source term PointSourceData psData; if constexpr (isBox()) { using ShapeValues = std::vector >; const auto lowDimGeometry = this->problem(lowDimIdx).gridGeometry().element(lowDimElementIdx).geometry(); ShapeValues shapeValues; this->getShapeValues(lowDimIdx, this->problem(lowDimIdx).gridGeometry(), lowDimGeometry, globalPos, shapeValues); psData.addLowDimInterpolation(shapeValues, this->vertexIndices(lowDimIdx, lowDimElementIdx), lowDimElementIdx); } else { psData.addLowDimInterpolation(lowDimElementIdx); } // add data needed to compute integral over the circle if constexpr (isBox()) { using ShapeValues = std::vector >; const auto bulkGeometry = this->problem(bulkIdx).gridGeometry().element(bulkElementIdx).geometry(); ShapeValues shapeValues; this->getShapeValues(bulkIdx, this->problem(bulkIdx).gridGeometry(), bulkGeometry, globalPos, shapeValues); psData.addBulkInterpolation(shapeValues, this->vertexIndices(bulkIdx, bulkElementIdx), bulkElementIdx); } else { psData.addBulkInterpolation(bulkElementIdx); } // publish point source data in the global vector this->pointSourceData().emplace_back(std::move(psData)); // compute average distance to bulk cell averageDistanceToBulkCell_.push_back(averageDistancePointGeometry(globalPos, outside.geometry())); // export the lowdim coupling stencil // we insert all vertices / elements and make it unique later if (isBox()) { const auto& vertices = this->vertexIndices(bulkIdx, bulkElementIdx); this->couplingStencils(lowDimIdx)[lowDimElementIdx].insert(this->couplingStencils(lowDimIdx)[lowDimElementIdx].end(), vertices.begin(), vertices.end()); } else { this->couplingStencils(lowDimIdx)[lowDimElementIdx].push_back(bulkElementIdx); } // export the bulk coupling stencil // we insert all vertices / elements and make it unique later if (isBox()) { const auto& vertices = this->vertexIndices(lowDimIdx, lowDimElementIdx); this->couplingStencils(bulkIdx)[bulkElementIdx].insert(this->couplingStencils(bulkIdx)[bulkElementIdx].end(), vertices.begin(), vertices.end()); } else { this->couplingStencils(bulkIdx)[bulkElementIdx].push_back(lowDimElementIdx); } } } } // make stencils unique using namespace Dune::Hybrid; forEach(integralRange(std::integral_constant{}), [&](const auto domainIdx) { for (auto&& stencil : this->couplingStencils(domainIdx)) { std::sort(stencil.second.begin(), stencil.second.end()); stencil.second.erase(std::unique(stencil.second.begin(), stencil.second.end()), stencil.second.end()); } }); std::cout << "took " << watch.elapsed() << " seconds." << std::endl; } /*! * \brief Methods to be accessed by the subproblems */ // \{ //! Return a reference to the pointSource data const PointSourceData& pointSourceData(std::size_t id) const { return pointSourceData_[id]; } //! Return a reference to the bulk problem template const GridView& gridView(Dune::index_constant domainIdx) const { return this->problem(domainIdx).gridGeometry().gridView(); } //! Return data for a bulk point source with the identifier id PrimaryVariables bulkPriVars(std::size_t id) const { return pointSourceData_[id].interpolateBulk(this->curSol()[bulkIdx]); } //! Return data for a low dim point source with the identifier id PrimaryVariables lowDimPriVars(std::size_t id) const { return pointSourceData_[id].interpolateLowDim(this->curSol()[lowDimIdx]); } //! return the average distance to the coupled bulk cell center Scalar averageDistance(std::size_t id) const { return averageDistanceToBulkCell_[id]; } //! Return reference to bulk point sources const std::vector>& bulkPointSources() const { return std::get(pointSources_); } //! Return reference to low dim point sources const std::vector>& lowDimPointSources() const { return std::get(pointSources_); } //! Return the point source if domain i template const std::vector>& pointSources(Dune::index_constant dom) const { return std::get(pointSources_); } //! Return reference to bulk coupling stencil member of domain i template const CouplingStencils& couplingStencils(Dune::index_constant dom) const { return std::get(couplingStencils_); } //! Return reference to point source data vector member const std::vector& pointSourceData() const { return pointSourceData_; } //! Return a reference to an empty stencil template const CouplingStencil& emptyStencil(Dune::index_constant dom) const { return std::get(emptyStencil_); } protected: //! computes the vertex indices per element for the box method template void precomputeVertexIndices(Dune::index_constant domainIdx) { // fill helper structure for box discretization if constexpr (isBox()) { this->vertexIndices(domainIdx).resize(gridView(domainIdx).size(0)); for (const auto& element : elements(gridView(domainIdx))) { constexpr int dim = GridView::dimension; const auto eIdx = this->problem(domainIdx).gridGeometry().elementMapper().index(element); this->vertexIndices(domainIdx, eIdx).resize(element.subEntities(dim)); for (int i = 0; i < element.subEntities(dim); ++i) this->vertexIndices(domainIdx, eIdx)[i] = this->problem(domainIdx).gridGeometry().vertexMapper().subIndex(element, i, dim); } } } //! compute the shape function for a given point and geometry template void getShapeValues(Dune::index_constant domainI, const FVGG& gridGeometry, const Geometry& geo, const GlobalPosition& globalPos, ShapeValues& shapeValues) { if constexpr (FVGG::discMethod == DiscretizationMethod::box) { const auto ipLocal = geo.local(globalPos); const auto& localBasis = this->problem(domainI).gridGeometry().feCache().get(geo.type()).localBasis(); localBasis.evaluateFunction(ipLocal, shapeValues); } else DUNE_THROW(Dune::InvalidStateException, "Shape values requested for other discretization than box!"); } //! Clear all internal data members void clear() { pointSources(bulkIdx).clear(); pointSources(lowDimIdx).clear(); couplingStencils(bulkIdx).clear(); couplingStencils(lowDimIdx).clear(); vertexIndices(bulkIdx).clear(); vertexIndices(lowDimIdx).clear(); pointSourceData_.clear(); averageDistanceToBulkCell_.clear(); idCounter_ = 0; } //! compute the intersections between the two grids void glueGrids() { const auto& bulkGridGeometry = this->problem(bulkIdx).gridGeometry(); const auto& lowDimGridGeometry = this->problem(lowDimIdx).gridGeometry(); // intersect the bounding box trees glue_->build(bulkGridGeometry.boundingBoxTree(), lowDimGridGeometry.boundingBoxTree()); } //! Return reference to point source data vector member std::vector& pointSourceData() { return pointSourceData_; } //! Return reference to average distances to bulk cell std::vector& averageDistanceToBulkCell() { return averageDistanceToBulkCell_; } //! Return the point source if domain i template std::vector>& pointSources(Dune::index_constant dom) { return std::get(pointSources_); } //! Return reference to bulk coupling stencil member of domain i template CouplingStencils& couplingStencils(Dune::index_constant dom) { return std::get(couplingStencils_); } //! Return a reference to the vertex indices template std::vector>& vertexIndices(Dune::index_constant dom, GridIndex eIdx) { return std::get(vertexIndices_)[eIdx]; } //! Return a reference to the vertex indices container template std::vector>>& vertexIndices(Dune::index_constant dom) { return std::get(vertexIndices_); } const GlueType& glue() const { return *glue_; } //! Returns the implementation of the problem (i.e. static polymorphism) Implementation &asImp_() { return *static_cast(this); } //! \copydoc asImp_() const Implementation &asImp_() const { return *static_cast(this); } //! id generator for point sources std::size_t idCounter_ = 0; private: //! the point source in both domains std::tuple>, std::vector>> pointSources_; std::vector pointSourceData_; std::vector averageDistanceToBulkCell_; //! Stencil data std::tuple>>, std::vector>>> vertexIndices_; std::tuple, CouplingStencils> couplingStencils_; std::tuple, CouplingStencil> emptyStencil_; //! The glue object std::shared_ptr glue_; //! integration order for coupling source int integrationOrder_ = 1; }; } // end namespace Dumux #endif