From 048eafc638a0b8b11a5326eb82b86f2f14c22cc2 Mon Sep 17 00:00:00 2001
From: Kilian Weishaupt <kilian.weishaupt@iws.uni-stuttgart.de>
Date: Mon, 9 Jul 2018 18:13:45 +0200
Subject: [PATCH] [localassembler] Add multidomain box subdomain assembler
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Collaboration with
Timo Koch <timo.koch@iws.uni-stuttgart.de>
Dennis Gläser <dennis.glaeser@iws.uni-stuttgart.de>
---
 dumux/multidomain/CMakeLists.txt              |   1 +
 .../multidomain/subdomainboxlocalassembler.hh | 536 ++++++++++++++++++
 2 files changed, 537 insertions(+)
 create mode 100644 dumux/multidomain/subdomainboxlocalassembler.hh

diff --git a/dumux/multidomain/CMakeLists.txt b/dumux/multidomain/CMakeLists.txt
index 705c536e73..2a80fbedd0 100644
--- a/dumux/multidomain/CMakeLists.txt
+++ b/dumux/multidomain/CMakeLists.txt
@@ -2,5 +2,6 @@ install(FILES
 couplingjacobianpattern.hh
 couplingmanager.hh
 newtonsolver.hh
+subdomainboxlocalassembler.hh
 traits.hh
 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dumux/multidomain)
diff --git a/dumux/multidomain/subdomainboxlocalassembler.hh b/dumux/multidomain/subdomainboxlocalassembler.hh
new file mode 100644
index 0000000000..59b6e8df5b
--- /dev/null
+++ b/dumux/multidomain/subdomainboxlocalassembler.hh
@@ -0,0 +1,536 @@
+// -*- 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 Assembly
+ * \ingroup BoxDiscretization
+ * \brief An assembler for Jacobian and residual contribution per element (box methods)
+ * \tparam TypeTag the TypeTag
+ * \tparam DM the differentiation method to residual compute derivatives
+ * \tparam implicit if to use an implicit or explicit time discretization
+ */
+#ifndef DUMUX_MULTIDOMAIN_BOX_LOCAL_ASSEMBLER_HH
+#define DUMUX_MULTIDOMAIN_BOX_LOCAL_ASSEMBLER_HH
+
+#include <dune/common/reservedvector.hh>
+#include <dune/grid/common/gridenums.hh> // for GhostEntity
+#include <dune/istl/matrixindexset.hh>
+
+#include <dumux/common/reservedblockvector.hh>
+#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/fvlocalassemblerbase.hh>
+
+namespace Dumux {
+
+/*!
+ * \ingroup Assembly
+ * \ingroup CCDiscretization
+ * \brief A base class for all local assemblers
+ * \tparam id the id of the sub domain
+ * \tparam TypeTag the TypeTag
+ * \tparam Assembler the assembler type
+ */
+template<std::size_t id, class TypeTag, class Assembler, class Implementation>
+class SubDomainBoxLocalAssemblerBase : public FVLocalAssemblerBase<TypeTag, Assembler,Implementation, true>
+{
+    using ParentType = FVLocalAssemblerBase<TypeTag, Assembler,Implementation, true>;
+
+    using Problem = typename GET_PROP_TYPE(TypeTag, Problem);
+    using LocalResidualValues = typename GET_PROP_TYPE(TypeTag, NumEqVector);
+    using ElementResidualVector = typename ParentType::LocalResidual::ElementResidualVector;
+    using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix);
+    using SolutionVector = typename Assembler::SolutionVector;
+    using SubSolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector);
+    using ElementBoundaryTypes = typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes);
+
+    using GridVariables = typename GET_PROP_TYPE(TypeTag, GridVariables);
+    using GridVolumeVariables = typename GridVariables::GridVolumeVariables;
+    using ElementVolumeVariables = typename GridVolumeVariables::LocalView;
+    using ElementFluxVariablesCache = typename GridVariables::GridFluxVariablesCache::LocalView;
+    using Scalar = typename GridVariables::Scalar;
+
+    using FVGridGeometry = typename GridVariables::GridGeometry;
+    using FVElementGeometry = typename FVGridGeometry::LocalView;
+    using SubControlVolume = typename FVGridGeometry::SubControlVolume;
+    using SubControlVolumeFace = typename FVGridGeometry::SubControlVolumeFace;
+    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
+    using Element = typename GridView::template Codim<0>::Entity;
+
+    using CouplingManager = typename Assembler::CouplingManager;
+
+    static constexpr auto numEq = GET_PROP_TYPE(TypeTag, 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;
+
+    // the constructor
+    explicit SubDomainBoxLocalAssemblerBase(const Assembler& assembler,
+                                            const Element& element,
+                                            const SolutionVector& curSol,
+                                            CouplingManager& couplingManager)
+    : ParentType(assembler,
+                 element,
+                 curSol,
+                 localView(assembler.fvGridGeometry(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)
+    {
+        this->asImp_().bindLocalViews();
+        this->elemBcTypes().update(problem(), this->element(), this->fvGeometry());
+
+        // for the diagonal jacobian block
+        // forward to the internal implementation
+        const auto residual = this->asImp_().assembleJacobianAndResidualImpl(jacRow[domainId], *std::get<domainId>(gridVariables));
+
+        for (const auto& scv : scvs(this->fvGeometry()))
+            res[scv.dofIndex()] += residual[scv.localDofIndex()];
+
+        auto applyDirichlet = [&] (const auto& scvI,
+                                   const auto& dirichletValues,
+                                   const auto eqIdx,
+                                   const auto pvIdx)
+        {
+            res[scvI.dofIndex()][eqIdx] = this->curElemVolVars()[scvI].priVars()[pvIdx] - dirichletValues[pvIdx];
+
+            for (const auto& scvJ : scvs(this->fvGeometry()))
+                jacRow[domainId][scvI.dofIndex()][scvJ.dofIndex()][eqIdx] = 0.0;
+
+            jacRow[domainId][scvI.dofIndex()][scvI.dofIndex()][eqIdx][pvIdx] = 1.0;
+        };
+
+        // for the coupling blocks
+        using namespace Dune::Hybrid;
+        forEach(integralRange(Dune::Hybrid::size(jacRow)), [&](auto&& i)
+        {
+            if (i != id)
+                this->assembleJacobianCoupling(i, jacRow, residual, gridVariables);
+        });
+
+        this->asImp_().evalDirichletBoundaries(applyDirichlet);
+    }
+
+    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)
+    {}
+
+    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 Assemble the residual only
+     */
+    void assembleResidual(SubSolutionVector& res)
+    {
+        this->asImp_().bindLocalViews();
+        this->elemBcTypes().update(problem(), this->element(), this->fvGeometry());
+
+        const auto residual = this->asImp_().assembleResidualImpl();
+
+        for (const auto& scv : scvs(this->fvGeometry()))
+            res[scv.dofIndex()] += residual[scv.localDofIndex()];
+    }
+
+    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;
+    }
+
+    const Problem& problem() const
+    { return this->assembler().problem(domainId); }
+
+    CouplingManager& couplingManager()
+    { return couplingManager_; }
+
+    /*!
+     * \brief Evaluates Dirichlet boundaries
+     */
+    template< typename ApplyDirichletFunctionType >
+    void evalDirichletBoundaries(ApplyDirichletFunctionType applyDirichlet)
+    {
+        // enforce Dirichlet boundaries by overwriting partial derivatives with 1 or 0
+        // and set the residual to (privar - dirichletvalue)
+        if (this->elemBcTypes().hasDirichlet())
+        {
+            for (const auto& scvI : scvs(this->fvGeometry()))
+            {
+                const auto bcTypes = this->elemBcTypes()[scvI.localDofIndex()];
+                if (bcTypes.hasDirichlet())
+                {
+                    const auto dirichletValues = this->problem().dirichlet(this->element(), scvI);
+
+                    // set the dirichlet conditions in residual and jacobian
+                    for (int eqIdx = 0; eqIdx < numEq; ++eqIdx)
+                    {
+                        if (bcTypes.isDirichlet(eqIdx))
+                        {
+                            const auto pvIdx = bcTypes.eqToDirichletIndex(eqIdx);
+                            assert(0 <= pvIdx && pvIdx < numEq);
+                            applyDirichlet(scvI, dirichletValues, eqIdx, pvIdx);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+private:
+    CouplingManager& couplingManager_; //!< the coupling manager
+};
+
+/*!
+ * \ingroup Assembly
+ * \ingroup CCDiscretization
+ * \brief A base class for all implicit local assemblers
+ * \tparam TypeTag the TypeTag
+ * \tparam Assembler the assembler type
+ */
+template<std::size_t id, class TypeTag, class Assembler, class Implementation>
+class SubDomainBoxLocalAssemblerImplicitBase : public SubDomainBoxLocalAssemblerBase<id, TypeTag, Assembler, Implementation>
+{
+    using ParentType = SubDomainBoxLocalAssemblerBase<id, TypeTag, Assembler, Implementation>;
+    using ElementResidualVector = typename ParentType::LocalResidual::ElementResidualVector;
+    using ResidualVector = typename GET_PROP_TYPE(TypeTag, NumEqVector);
+    using GridView = typename GET_PROP_TYPE(TypeTag, GridView);
+    using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
+    using SubControlVolume = typename FVGridGeometry::SubControlVolume;
+    using Element = typename GridView::template Codim<0>::Entity;
+    static constexpr auto domainId = Dune::index_constant<id>();
+public:
+    using ParentType::ParentType;
+
+    void bindLocalViews()
+    {
+        // get some references for convenience
+        auto& couplingManager = this->couplingManager();
+        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);
+        curElemVolVars.bind(element, fvGeometry, curSol);
+        elemFluxVarsCache.bind(element, fvGeometry, curElemVolVars);
+        if (!this->assembler().isStationaryProblem())
+            this->prevElemVolVars().bindElement(element, fvGeometry, this->assembler().prevSol()[domainId]);
+    }
+
+    using ParentType::evalLocalSourceResidual;
+    ElementResidualVector evalLocalSourceResidual(const Element& neighbor) const
+    { return this->evalLocalSourceResidual(neighbor, this->curElemVolVars()); }
+
+    /*!
+     * \brief Computes the residual
+     * \return The element residual at the current solution.
+     */
+    ElementResidualVector assembleResidualImpl()
+    { return this->evalLocalResidual(); }
+};
+
+/*!
+ * \ingroup Assembly
+ * \ingroup CCDiscretization
+ * \brief An assembler for Jacobian and residual contribution per element (cell-centered methods)
+ * \tparam TypeTag the TypeTag
+ * \tparam DM the differentiation method to residual compute derivatives
+ * \tparam implicit if to use an implicit or explicit time discretization
+ */
+template<std::size_t id, class TypeTag, class Assembler, DiffMethod DM = DiffMethod::numeric, bool implicit = true>
+class SubDomainBoxLocalAssembler;
+
+/*!
+ * \ingroup Assembly
+ * \ingroup CCDiscretization
+ * \brief Cell-centered scheme local assembler using numeric differentiation and implicit time discretization
+ */
+template<std::size_t id, class TypeTag, class Assembler>
+class SubDomainBoxLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/true>
+: public SubDomainBoxLocalAssemblerImplicitBase<id, TypeTag, Assembler,
+            SubDomainBoxLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, true> >
+{
+    using ThisType = SubDomainBoxLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/true>;
+    using ParentType = SubDomainBoxLocalAssemblerImplicitBase<id, TypeTag, Assembler, ThisType>;
+    using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar);
+    using VolumeVariables = typename GET_PROP_TYPE(TypeTag, VolumeVariables);
+    using ElementResidualVector = typename ParentType::LocalResidual::ElementResidualVector;
+
+    using ElementVolumeVariables = typename GET_PROP_TYPE(TypeTag, GridVolumeVariables)::LocalView;
+    using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry);
+    using FVElementGeometry = typename FVGridGeometry::LocalView;
+    using GridView = typename FVGridGeometry::GridView;
+    using Element = typename GridView::template Codim<0>::Entity;
+
+    enum { numEq = GET_PROP_TYPE(TypeTag, ModelTraits)::numEq() };
+    enum { dim = GET_PROP_TYPE(TypeTag, GridView)::dimension };
+
+    static constexpr bool enableGridFluxVarsCache = GET_PROP_VALUE(TypeTag, EnableGridFluxVariablesCache);
+    static constexpr bool enableGridVolVarsCache = GET_PROP_VALUE(TypeTag, EnableGridVolumeVariablesCache);
+    static constexpr int maxNeighbors = 4*(2*dim);
+    static constexpr auto domainI = Dune::index_constant<id>();
+
+public:
+    using ParentType::ParentType;
+
+    /*!
+     * \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<class JacobianMatrixDiagBlock, class GridVariables>
+    ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrixDiagBlock& A, GridVariables& gridVariables)
+    {
+        //////////////////////////////////////////////////////////////////////////////////////////////////
+        // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the //
+        // neighboring elements we do so by computing the derivatives of the fluxes which depend on the //
+        // actual element. In the actual element we evaluate the derivative of the entire residual.     //
+        //////////////////////////////////////////////////////////////////////////////////////////////////
+
+        // get the vecor of the acutal element residuals
+        const auto origResiduals = this->evalLocalResidual();
+
+        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+        // compute the derivatives of this element with respect to all of the element's dofs and add them to the Jacobian //
+        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+        const auto& element = this->element();
+        const auto& fvGeometry = this->fvGeometry();
+        const auto& curSol = this->curSol()[domainI];
+        auto&& curElemVolVars = this->curElemVolVars();
+
+        // create the element solution
+        auto elemSol = elementSolution(element, curSol, fvGeometry.fvGridGeometry());
+
+        auto partialDerivs = origResiduals;
+        partialDerivs = 0.0;
+
+        for (auto&& scv : scvs(fvGeometry))
+        {
+            // dof index and corresponding actual pri vars
+            const auto dofIdx = scv.dofIndex();
+            auto& curVolVars = this->getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv);
+            const VolumeVariables origVolVars(curVolVars);
+
+            // calculate derivatives w.r.t to the privars at the dof at hand
+            for (int pvIdx = 0; pvIdx < numEq; pvIdx++)
+            {
+                partialDerivs = 0.0;
+
+                auto evalResiduals = [&](Scalar priVar)
+                {
+                    // update the volume variables and compute element residual
+                    const auto localDofIndex = scv.localDofIndex();
+                    elemSol[localDofIndex][pvIdx] = priVar;
+                    curVolVars.update(elemSol, this->problem(), element, scv);
+                    this->couplingManager().updateCouplingContext(domainI, *this, domainI, scv.dofIndex(), elemSol[localDofIndex], pvIdx);
+                    return this->evalLocalResidual();
+                };
+
+                // derive the residuals numerically
+                static const int numDiffMethod = getParamFromGroup<int>(this->problem().paramGroup(), "Assembly.NumericDifferenceMethod");
+                static const NumericEpsilon<Scalar, numEq> eps_{this->problem().paramGroup()};
+                NumericDifferentiation::partialDerivative(evalResiduals, elemSol[scv.localDofIndex()][pvIdx], partialDerivs, origResiduals,
+                                                          eps_(elemSol[scv.localDofIndex()][pvIdx], pvIdx), numDiffMethod);
+
+                // update the global stiffness matrix with the current partial derivatives
+                for (auto&& scvJ : scvs(fvGeometry))
+                {
+                      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[scvJ.dofIndex()][dofIdx][eqIdx][pvIdx] += partialDerivs[scvJ.localDofIndex()][eqIdx];
+                      }
+                }
+
+                // restore the original state of the scv's volume variables
+                curVolVars = origVolVars;
+
+                // restore the original element solution and coupling context
+                elemSol[scv.localDofIndex()][pvIdx] = curSol[scv.dofIndex()][pvIdx];
+                this->couplingManager().updateCouplingContext(domainI, *this, domainI, scv.dofIndex(), elemSol[scv.localDofIndex()], pvIdx);
+            }
+        }
+
+        // evaluate additional derivatives that might arise from the coupling
+        this->couplingManager().evalAdditionalDomainDerivatives(domainI, *this, origResiduals, A, gridVariables);
+
+        return origResiduals;
+    }
+
+    /*!
+     * \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();
+
+        // get element stencil informations
+        const auto& stencil = this->couplingManager().couplingStencil(domainI, element, domainJ);
+
+        // convenience lambda for call to update self
+        auto updateSelf = [&] ()
+        {
+            // 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 (enableGridFluxVarsCache)
+            {
+                if (enableGridVolVarsCache)
+                    this->couplingManager().updateCoupledVariables(domainI, *this, gridVariables.curGridVolVars(), gridVariables.gridFluxVarsCache());
+                else
+                    this->couplingManager().updateCoupledVariables(domainI, *this, curElemVolVars, gridVariables.gridFluxVarsCache());
+            }
+            else
+            {
+                if (enableGridVolVarsCache)
+                    this->couplingManager().updateCoupledVariables(domainI, *this, gridVariables.curGridVolVars(), elemFluxVarsCache);
+                else
+                    this->couplingManager().updateCoupledVariables(domainI, *this, curElemVolVars, elemFluxVarsCache);
+            }
+        };
+
+        const auto& curSolJ = this->curSol()[domainJ];
+        for (const auto globalJ : stencil)
+        {
+            // undeflected privars and privars to be deflected
+            const auto origPriVarsJ = curSolJ[globalJ];
+            auto priVarsJ = origPriVarsJ;
+
+            // the undeflected coupling residual
+            const auto origResidual = this->couplingManager().evalCouplingResidual(domainI, *this, domainJ, globalJ);
+
+            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);
+                    updateSelf();
+                    return this->couplingManager().evalCouplingResidual(domainI, *this, domainJ, globalJ);
+                };
+
+                // derive the residuals numerically
+                ElementResidualVector partialDerivs(element.subEntities(dim));
+
+                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 (auto&& scv : scvs(fvGeometry))
+                {
+                    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'.
+
+                        // 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.
+                        const auto bcTypes = this->elemBcTypes()[scv.localDofIndex()];
+                        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;
+                        else
+                            A[scv.dofIndex()][globalJ][eqIdx][pvIdx] += partialDerivs[scv.localDofIndex()][eqIdx];
+                    }
+                }
+
+                // 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 in case of global caching.
+        // This has to be done in order to guarantee that everything is in an undeflected
+        // state before the assembly of another element is called. In the case of local caching
+        // this is obsolete because the local views used here go out of scope after this.
+        // We only have to do this for the last primary variable, for all others the flux var cache
+        // is updated with the correct element volume variables before residual evaluations
+        updateSelf();
+    }
+};
+
+} // end namespace Dumux
+
+#endif
-- 
GitLab