diff --git a/dumux/multidomain/fvassembler.hh b/dumux/multidomain/fvassembler.hh index 93815e3dc0797b86b06c9df786a6b605d61c3898..8039452e7983a10b02fe659987a125f225529d2d 100644 --- a/dumux/multidomain/fvassembler.hh +++ b/dumux/multidomain/fvassembler.hh @@ -44,6 +44,7 @@ #include "subdomaincclocalassembler.hh" #include "subdomainboxlocalassembler.hh" #include "subdomainstaggeredlocalassembler.hh" +#include "subdomainfclocalassembler.hh" #include <dumux/discretization/method.hh> @@ -130,6 +131,12 @@ private: using type = SubDomainStaggeredLocalAssembler<id, SubDomainTypeTag<id>, ThisType, diffMethod, isImplicit()>; }; + template<std::size_t id> + struct SubDomainAssemblerType<DiscretizationMethod::fcstaggered, id> + { + using type = SubDomainFaceCenteredLocalAssembler<id, SubDomainTypeTag<id>, ThisType, diffMethod, isImplicit()>; + }; + template<std::size_t id> using SubDomainAssembler = typename SubDomainAssemblerType<GridGeometry<id>::discMethod, id>::type; diff --git a/dumux/multidomain/subdomainfclocalassembler.hh b/dumux/multidomain/subdomainfclocalassembler.hh new file mode 100644 index 0000000000000000000000000000000000000000..c7648a1b953b7c28b4c74d504cea4a2b2cf53ed6 --- /dev/null +++ b/dumux/multidomain/subdomainfclocalassembler.hh @@ -0,0 +1,418 @@ +// -*- 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 <http://www.gnu.org/licenses/>. * + *****************************************************************************/ +/*! + * \file + * \ingroup Assembly + * \ingroup StaggeredDiscretization + * \ingroup MultiDomain + * \brief An assembler for Jacobian and residual contribution per element (face-centered staggered methods) for multidomain problems + */ +#ifndef DUMUX_MULTIDOMAIN_FACECENTERED_LOCAL_ASSEMBLER_HH +#define DUMUX_MULTIDOMAIN_FACECENTERED_LOCAL_ASSEMBLER_HH + +#include <dune/common/indices.hh> +#include <dune/common/hybridutilities.hh> +#include <dune/grid/common/gridenums.hh> // for GhostEntity + +#include <dumux/common/properties.hh> +#include <dumux/common/parameters.hh> +#include <dumux/common/numericdifferentiation.hh> +#include <dumux/assembly/numericepsilon.hh> +#include <dumux/assembly/diffmethod.hh> +#include <dumux/assembly/fclocalassembler.hh> + +namespace Dumux { + +/*! + * \ingroup Assembly + * \ingroup StaggeredDiscretization + * \ingroup MultiDomain + * \brief A base class for all face-centered staggered local assemblers + * \tparam id the id of the sub domain + * \tparam TypeTag the TypeTag + * \tparam Assembler the assembler type + * \tparam Implementation the actual implementation type + * \tparam implicit Specifies whether the time discretization is implicit or not not (i.e. explicit) + */ +template<std::size_t id, class TypeTag, class Assembler, class Implementation, DiffMethod dm, bool implicit> +class SubDomainFaceCenteredLocalAssemblerBase : public FaceCenteredLocalAssembler<TypeTag, Assembler, dm, implicit, Implementation> +{ + using ParentType = FaceCenteredLocalAssembler<TypeTag, Assembler, dm, implicit, Implementation>; + + using Problem = GetPropType<TypeTag, Properties::Problem>; + using SolutionVector = typename Assembler::SolutionVector; + using SubSolutionVector = GetPropType<TypeTag, Properties::SolutionVector>; + + using GridVariables = GetPropType<TypeTag, Properties::GridVariables>; + using GridVolumeVariables = typename GridVariables::GridVolumeVariables; + using ElementVolumeVariables = typename GridVolumeVariables::LocalView; + using ElementFluxVariablesCache = typename GridVariables::GridFluxVariablesCache::LocalView; + using Scalar = typename GridVariables::Scalar; + + using GridGeometry = typename GridVariables::GridGeometry; + using FVElementGeometry = typename GridGeometry::LocalView; + using SubControlVolume = typename GridGeometry::SubControlVolume; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + + using CouplingManager = typename Assembler::CouplingManager; + + static constexpr auto numEq = GetPropType<TypeTag, Properties::ModelTraits>::numEq(); + +public: + //! export the domain id of this sub-domain + static constexpr auto domainId = typename Dune::index_constant<id>(); + //! pull up constructor of parent class + using ParentType::ParentType; + //! export element residual vector type + using ElementResidualVector = typename ParentType::LocalResidual::ElementResidualVector; + + // the constructor + explicit SubDomainFaceCenteredLocalAssemblerBase(const Assembler& assembler, + const Element& element, + const SolutionVector& curSol, + CouplingManager& couplingManager) + : ParentType(assembler, + element, + curSol, + localView(assembler.gridGeometry(domainId)), + localView(assembler.gridVariables(domainId).curGridVolVars()), + localView(assembler.gridVariables(domainId).prevGridVolVars()), + localView(assembler.gridVariables(domainId).gridFluxVarsCache()), + assembler.localResidual(domainId), + (element.partitionType() == Dune::GhostEntity)) + , couplingManager_(couplingManager) + {} + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. The element residual is written into the right hand side. + */ + template<class JacobianMatrixRow, class GridVariablesTuple> + void assembleJacobianAndResidual(JacobianMatrixRow& jacRow, SubSolutionVector& res, GridVariablesTuple& gridVariables) + { + auto assembleCouplingBlocks = [&](const auto& residual) + { + // assemble the coupling blocks + using namespace Dune::Hybrid; + forEach(integralRange(Dune::Hybrid::size(jacRow)), [&](auto&& i) + { + if (i != id) + this->assembleJacobianCoupling(i, jacRow, residual, gridVariables); + }); + }; + + // the coupled model does not support partial reassembly yet + const DefaultPartialReassembler* noReassembler = nullptr; + ParentType::assembleJacobianAndResidual(jacRow[domainId], res, *std::get<domainId>(gridVariables), noReassembler, assembleCouplingBlocks); + } + + /*! + * \brief Assemble the entries in a coupling block of the jacobian. + * There is no coupling block between a domain and itself. + */ + template<std::size_t otherId, class JacRow, class GridVariables, + typename std::enable_if_t<(otherId == id), int> = 0> + void assembleJacobianCoupling(Dune::index_constant<otherId> domainJ, JacRow& jacRow, + const ElementResidualVector& res, GridVariables& gridVariables) + {} + + /*! + * \brief Assemble the entries in a coupling block of the jacobian. + */ + template<std::size_t otherId, class JacRow, class GridVariables, + typename std::enable_if_t<(otherId != id), int> = 0> + void assembleJacobianCoupling(Dune::index_constant<otherId> domainJ, JacRow& jacRow, + const ElementResidualVector& res, GridVariables& gridVariables) + { + this->asImp_().assembleJacobianCoupling(domainJ, jacRow[domainJ], res, *std::get<domainId>(gridVariables)); + } + + /*! + * \brief Evaluates the local source term for an element and given element volume variables + */ + ElementResidualVector evalLocalSourceResidual(const Element& element, const ElementVolumeVariables& elemVolVars) const + { + // initialize the residual vector for all scvs in this element + ElementResidualVector residual(this->fvGeometry().numScv()); + + // evaluate the volume terms (storage + source terms) + // forward to the local residual specialized for the discretization methods + for (auto&& scv : scvs(this->fvGeometry())) + { + const auto& curVolVars = elemVolVars[scv]; + auto source = this->localResidual().computeSource(problem(), element, this->fvGeometry(), elemVolVars, scv); + source *= -scv.volume()*curVolVars.extrusionFactor(); + residual[scv.localDofIndex()] = std::move(source); + } + + return residual; + } + + /*! + * \brief Evaluates the local source term depending on time discretization scheme + */ + ElementResidualVector evalLocalSourceResidual(const Element& neighbor) const + { return this->evalLocalSourceResidual(neighbor, implicit ? this->curElemVolVars() : this->prevElemVolVars()); } + + /*! + * \brief Prepares all local views necessary for local assembly. + */ + void bindLocalViews() + { + // get some references for convenience + const auto& element = this->element(); + const auto& curSol = this->curSol(domainId); + auto&& fvGeometry = this->fvGeometry(); + auto&& curElemVolVars = this->curElemVolVars(); + auto&& elemFluxVarsCache = this->elemFluxVarsCache(); + + // bind the caches + couplingManager_.bindCouplingContext(domainId, element, this->assembler()); + fvGeometry.bind(element); + + if (implicit) + { + curElemVolVars.bind(element, fvGeometry, curSol); + elemFluxVarsCache.bind(element, fvGeometry, curElemVolVars); + if (!this->assembler().isStationaryProblem()) + this->prevElemVolVars().bindElement(element, fvGeometry, this->assembler().prevSol()[domainId]); + } + else + { + auto& prevElemVolVars = this->prevElemVolVars(); + const auto& prevSol = this->assembler().prevSol()[domainId]; + + curElemVolVars.bindElement(element, fvGeometry, curSol); + prevElemVolVars.bind(element, fvGeometry, prevSol); + elemFluxVarsCache.bind(element, fvGeometry, prevElemVolVars); + } + + this->elemBcTypes().update(problem(), this->element(), this->fvGeometry()); + } + + //! return reference to the underlying problem + template<std::size_t i = domainId> + const Problem& problem(Dune::index_constant<i> dId = domainId) const + { return this->assembler().problem(domainId); } + + //! return reference to the underlying problem + template<std::size_t i = domainId> + const auto& curSol(Dune::index_constant<i> dId = domainId) const + { return ParentType::curSol()[dId]; } + + //! return reference to the coupling manager + CouplingManager& couplingManager() + { return couplingManager_; } + +private: + CouplingManager& couplingManager_; //!< the coupling manager +}; + +/*! + * \ingroup Assembly + * \ingroup StaggeredDiscretization + * \ingroup MultiDomain + * \brief The face-centered staggered scheme multidomain local assembler + * \tparam id the id of the sub domain + * \tparam TypeTag the TypeTag + * \tparam Assembler the assembler type + * \tparam DM the numeric differentiation method + * \tparam implicit whether the assembler is explicit or implicit in time + */ +template<std::size_t id, class TypeTag, class Assembler, DiffMethod DM = DiffMethod::numeric, bool implicit = true> +class SubDomainFaceCenteredLocalAssembler; + +/*! + * \ingroup Assembly + * \ingroup StaggeredDiscretization + * \ingroup MultiDomain + * \brief Face-centered staggered scheme multi domain local assembler using numeric differentiation and implicit time discretization + */ +template<std::size_t id, class TypeTag, class Assembler> +class SubDomainFaceCenteredLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/true> +: public SubDomainFaceCenteredLocalAssemblerBase<id, TypeTag, Assembler, + SubDomainFaceCenteredLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, true>, DiffMethod::numeric, /*implicit=*/true> +{ + using ThisType = SubDomainFaceCenteredLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/true>; + using ParentType = SubDomainFaceCenteredLocalAssemblerBase<id, TypeTag, Assembler, ThisType, DiffMethod::numeric, /*implicit=*/true>; + using Scalar = GetPropType<TypeTag, Properties::Scalar>; + using VolumeVariables = GetPropType<TypeTag, Properties::VolumeVariables>; + + using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>; + using FVElementGeometry = typename GridGeometry::LocalView; + using GridView = typename GridGeometry::GridView; + using Element = typename GridView::template Codim<0>::Entity; + using SubControlVolume = typename FVElementGeometry::SubControlVolume; + + enum { numEq = GetPropType<TypeTag, Properties::ModelTraits>::numEq() }; + enum { dim = GridView::dimension }; + + static constexpr bool enableGridFluxVarsCache = getPropValue<TypeTag, Properties::EnableGridFluxVariablesCache>(); + static constexpr bool enableGridVolVarsCache = getPropValue<TypeTag, Properties::EnableGridVolumeVariablesCache>(); + static constexpr auto domainI = Dune::index_constant<id>(); + +public: + using ParentType::ParentType; + //! export element residual vector type + using ElementResidualVector = typename ParentType::LocalResidual::ElementResidualVector; + + /*! + * \brief Update the coupling context for coupled models. + */ + template<class ElemSol> + void maybeUpdateCouplingContext(const SubControlVolume& scv, ElemSol& elemSol, const int pvIdx) + { + this->couplingManager().updateCouplingContext(domainI, *this, domainI, scv.dofIndex(), elemSol[scv.localDofIndex()], pvIdx); + } + + /*! + * \brief Update the additional domain derivatives for coupled models. + */ + template<class JacobianMatrixDiagBlock, class GridVariables> + void maybeEvalAdditionalDomainDerivatives(const ElementResidualVector& origResiduals, const JacobianMatrixDiagBlock& A, GridVariables& gridVariables) + { + this->couplingManager().evalAdditionalDomainDerivatives(domainI, *this, origResiduals, A, gridVariables); + } + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + * + * \return The element residual at the current solution. + */ + template<std::size_t otherId, class JacobianBlock, class GridVariables> + void assembleJacobianCoupling(Dune::index_constant<otherId> domainJ, JacobianBlock& A, + const ElementResidualVector& res, GridVariables& gridVariables) + { + //////////////////////////////////////////////////////////////////////////////////////////////////////// + // Calculate derivatives of all dofs in the element with respect to all dofs in the coupling stencil. // + //////////////////////////////////////////////////////////////////////////////////////////////////////// + + // get some aliases for convenience + const auto& element = this->element(); + const auto& fvGeometry = this->fvGeometry(); + auto&& curElemVolVars = this->curElemVolVars(); + auto&& elemFluxVarsCache = this->elemFluxVarsCache(); + + // the solution vector of the other domain + const auto& curSolJ = this->curSol(domainJ); + + // convenience lambda for call to update self + auto updateCoupledVariables = [&] () + { + // Update ourself after the context has been modified. Depending on the + // type of caching, other objects might have to be updated. All ifs can be optimized away. + if constexpr (enableGridFluxVarsCache) + { + if constexpr (enableGridVolVarsCache) + this->couplingManager().updateCoupledVariables(domainI, *this, gridVariables.curGridVolVars(), gridVariables.gridFluxVarsCache()); + else + this->couplingManager().updateCoupledVariables(domainI, *this, curElemVolVars, gridVariables.gridFluxVarsCache()); + } + else + { + if constexpr (enableGridVolVarsCache) + this->couplingManager().updateCoupledVariables(domainI, *this, gridVariables.curGridVolVars(), elemFluxVarsCache); + else + this->couplingManager().updateCoupledVariables(domainI, *this, curElemVolVars, elemFluxVarsCache); + } + }; + + for (const auto& scv : scvs(fvGeometry)) + { + const auto& stencil = this->couplingManager().couplingStencil(domainI, element, scv, domainJ); + + for (const auto globalJ : stencil) + { + const auto origResidual = this->couplingManager().evalCouplingResidual(domainI, *this, scv, domainJ, globalJ); // TODO is this necessary? + // undeflected privars and privars to be deflected + const auto origPriVarsJ = curSolJ[globalJ]; + auto priVarsJ = origPriVarsJ; + + for (int pvIdx = 0; pvIdx < JacobianBlock::block_type::cols; ++pvIdx) + { + auto evalCouplingResidual = [&](Scalar priVar) + { + priVarsJ[pvIdx] = priVar; + this->couplingManager().updateCouplingContext(domainI, *this, domainJ, globalJ, priVarsJ, pvIdx); + updateCoupledVariables(); + return this->couplingManager().evalCouplingResidual(domainI, *this, scv, domainJ, globalJ); + }; + + // derive the residuals numerically + ElementResidualVector partialDerivs(element.subEntities(1)); + + const auto& paramGroup = this->assembler().problem(domainJ).paramGroup(); + static const int numDiffMethod = getParamFromGroup<int>(paramGroup, "Assembly.NumericDifferenceMethod"); + static const auto epsCoupl = this->couplingManager().numericEpsilon(domainJ, paramGroup); + + NumericDifferentiation::partialDerivative(evalCouplingResidual, origPriVarsJ[pvIdx], partialDerivs, origResidual, + epsCoupl(origPriVarsJ[pvIdx], pvIdx), numDiffMethod); + + // update the global stiffness matrix with the current partial derivatives + for (int eqIdx = 0; eqIdx < numEq; eqIdx++) + { + // A[i][col][eqIdx][pvIdx] is the rate of change of + // the residual of equation 'eqIdx' at dof 'i' + // depending on the primary variable 'pvIdx' at dof + // 'col'. + A[scv.dofIndex()][globalJ][eqIdx][pvIdx] += partialDerivs[scv.localDofIndex()][eqIdx]; + } + + // handle Dirichlet boundary conditions + // TODO internal constraints + if (scv.boundary() && this->elemBcTypes().hasDirichlet()) + { + const auto bcTypes = this->elemBcTypes()[fvGeometry.frontalScvfOnBoundary(scv).localIndex()]; + if (bcTypes.hasDirichlet()) + { + // If the dof is coupled by a Dirichlet condition, + // set the derived value only once (i.e. overwrite existing values). + // For other dofs, add the contribution of the partial derivative. + for (int eqIdx = 0; eqIdx < numEq; ++eqIdx) + { + if (bcTypes.isCouplingDirichlet(eqIdx)) + A[scv.dofIndex()][globalJ][eqIdx][pvIdx] = partialDerivs[scv.localDofIndex()][eqIdx]; + else if (bcTypes.isDirichlet(eqIdx)) + A[scv.dofIndex()][globalJ][eqIdx][pvIdx] = 0.0; + } + } + } + + // restore the current element solution + priVarsJ[pvIdx] = origPriVarsJ[pvIdx]; + + // restore the undeflected state of the coupling context + this->couplingManager().updateCouplingContext(domainI, *this, domainJ, globalJ, priVarsJ, pvIdx); + } + + // Restore original state of the flux vars cache and/or vol vars. + // This has to be done in case they depend on variables of domainJ before + // we continue with the numeric derivative w.r.t the next globalJ. Otherwise, + // the next "origResidual" will be incorrect. + updateCoupledVariables(); + } + } + } +}; + +} // end namespace Dumux + +#endif