// -*- 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