From 806e07a89f80ba40cbe98a75a8e763f5bde0a906 Mon Sep 17 00:00:00 2001
From: Timo Koch <timokoch@math.uio.no>
Date: Sat, 18 Mar 2023 16:20:04 +0100
Subject: [PATCH] [assembly][md] Reuse CVFELocalAssembler in subdomain
 assemblers

---
 .../multidomain/subdomainboxlocalassembler.hh | 403 +++---------------
 .../subdomainfcdiamondlocalassembler.hh       |   8 +-
 .../subdomainpq1bubblelocalassembler.hh       |   8 +-
 3 files changed, 69 insertions(+), 350 deletions(-)

diff --git a/dumux/multidomain/subdomainboxlocalassembler.hh b/dumux/multidomain/subdomainboxlocalassembler.hh
index 66b1725e37..bd33d1c237 100644
--- a/dumux/multidomain/subdomainboxlocalassembler.hh
+++ b/dumux/multidomain/subdomainboxlocalassembler.hh
@@ -39,7 +39,7 @@
 #include <dumux/common/numeqvector.hh>
 #include <dumux/assembly/numericepsilon.hh>
 #include <dumux/assembly/diffmethod.hh>
-#include <dumux/assembly/fvlocalassemblerbase.hh>
+#include <dumux/assembly/cvfelocalassembler.hh>
 #include <dumux/discretization/extrusion.hh>
 
 namespace Dumux {
@@ -55,14 +55,13 @@ namespace Dumux {
  * \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, bool implicit>
-class SubDomainBoxLocalAssemblerBase : public FVLocalAssemblerBase<TypeTag, Assembler, Implementation, implicit>
+template<std::size_t id, class TypeTag, class Assembler, class Implementation, DiffMethod dm, bool implicit>
+class SubDomainBoxLocalAssemblerBase : public CVFELocalAssembler<TypeTag, Assembler, dm, implicit, Implementation>
 {
-    using ParentType = FVLocalAssemblerBase<TypeTag, Assembler,Implementation, implicit>;
+    using ParentType = CVFELocalAssembler<TypeTag, Assembler, dm, implicit, Implementation>;
 
     using Problem = GetPropType<TypeTag, Properties::Problem>;
     using LocalResidualValues = Dumux::NumEqVector<GetPropType<TypeTag, Properties::PrimaryVariables>>;
-    using ElementResidualVector = typename ParentType::LocalResidual::ElementResidualVector;
     using JacobianMatrix = GetPropType<TypeTag, Properties::JacobianMatrix>;
     using SolutionVector = typename Assembler::SolutionVector;
     using ElementBoundaryTypes = GetPropType<TypeTag, Properties::ElementBoundaryTypes>;
@@ -90,12 +89,16 @@ public:
     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 SubDomainBoxLocalAssemblerBase(const Assembler& assembler,
-                                            const Element& element,
-                                            const SolutionVector& curSol,
-                                            CouplingManager& couplingManager)
+    explicit SubDomainBoxLocalAssemblerBase(
+        const Assembler& assembler,
+        const Element& element,
+        const SolutionVector& curSol,
+        CouplingManager& couplingManager
+    )
     : ParentType(assembler,
                  element,
                  curSol,
@@ -115,19 +118,8 @@ public:
     template<class JacobianMatrixRow, class SubResidualVector, class GridVariablesTuple>
     void assembleJacobianAndResidual(JacobianMatrixRow& jacRow, SubResidualVector& res, GridVariablesTuple& gridVariables)
     {
-        this->asImp_().bindLocalViews();
-        this->elemBcTypes().update(problem(), this->element(), this->fvGeometry());
-
-        if (!this->elementIsGhost())
+        auto assembleCouplingBlocks = [&](const auto& residual)
         {
-            // for the diagonal jacobian block
-            // forward to the internal implementation
-            const auto residual = this->asImp_().assembleJacobianAndResidualImpl(jacRow[domainId], *std::get<domainId>(gridVariables));
-
-            // update the residual vector
-            for (const auto& scv : scvs(this->fvGeometry()))
-                res[scv.dofIndex()] += residual[scv.localDofIndex()];
-
             // assemble the coupling blocks
             using namespace Dune::Hybrid;
             forEach(integralRange(Dune::Hybrid::size(jacRow)), [&](auto&& i)
@@ -135,60 +127,11 @@ public:
                 if (i != id)
                     this->assembleJacobianCoupling(i, jacRow, residual, gridVariables);
             });
-        }
-        else
-        {
-            using GridGeometry = typename GridVariables::GridGeometry;
-            using GridView = typename GridGeometry::GridView;
-            static constexpr auto dim = GridView::dimension;
-
-            int numVerticesLocal = this->element().subEntities(dim);
-
-            for (int i = 0; i < numVerticesLocal; ++i)
-            {
-                const auto vertex = this->element().template subEntity<dim>(i);
-
-                if (vertex.partitionType() == Dune::InteriorEntity ||
-                    vertex.partitionType() == Dune::BorderEntity)
-                {
-                    // do not change the non-ghost vertices
-                    continue;
-                }
-
-                // set main diagonal entries for the vertex
-                const auto vIdx = this->assembler().gridGeometry(domainId).vertexMapper().index(vertex);
-
-                typedef typename JacobianMatrix::block_type BlockType;
-                BlockType &J = jacRow[domainId][vIdx][vIdx];
-                for (int j = 0; j < BlockType::rows; ++j)
-                    J[j][j] = 1.0;
-
-                // set residual for the vertex
-                res[vIdx] = 0;
-            }
-        }
-
-        // lambda for the incorporation of Dirichlet Bcs
-        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];
-
-            // in explicit schemes we only have entries on the diagonal
-            // and thus don't have to do anything with off-diagonal entries
-            if (implicit)
-            {
-                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;
         };
 
-        // incorporate Dirichlet BCs
-        this->asImp_().enforceDirichletConstraints(applyDirichlet);
+        // the coupled model does not support partial reassembly yet
+        const DefaultPartialReassembler* noReassembler = nullptr;
+        ParentType::assembleJacobianAndResidual(jacRow[domainId], res, *std::get<domainId>(gridVariables), noReassembler, assembleCouplingBlocks);
     }
 
     /*!
@@ -212,30 +155,6 @@ public:
         this->asImp_().assembleJacobianCoupling(domainJ, jacRow[domainJ], res, *std::get<domainId>(gridVariables));
     }
 
-    /*!
-     * \brief Assemble the residual vector entries only
-     */
-    template<class SubResidualVector>
-    void assembleResidual(SubResidualVector& res)
-    {
-        this->asImp_().bindLocalViews();
-        this->elemBcTypes().update(problem(), this->element(), this->fvGeometry());
-
-        const auto residual = this->evalLocalResidual();
-        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];
-        };
-
-        this->asImp_().enforceDirichletConstraints(applyDirichlet);
-    }
-
     /*!
      * \brief Evaluates the local source term for an element and given element volume variables
      */
@@ -246,7 +165,7 @@ public:
 
         // evaluate the volume terms (storage + source terms)
         // forward to the local residual specialized for the discretization methods
-        for (auto&& scv : scvs(this->fvGeometry()))
+        for (const auto& scv : scvs(this->fvGeometry()))
         {
             const auto& curVolVars = elemVolVars[scv];
             auto source = this->localResidual().computeSource(problem(), element, this->fvGeometry(), elemVolVars, scv);
@@ -263,49 +182,6 @@ public:
     ElementResidualVector evalLocalSourceResidual(const Element& neighbor) const
     { return this->evalLocalSourceResidual(neighbor, implicit ? this->curElemVolVars() : this->prevElemVolVars()); }
 
-    //! Enforce Dirichlet constraints
-    template<typename ApplyFunction>
-    void enforceDirichletConstraints(const ApplyFunction& applyDirichlet)
-    {
-        // enforce Dirichlet boundary conditions
-        this->asImp_().evalDirichletBoundaries(applyDirichlet);
-        // take care of internal Dirichlet constraints (if enabled)
-        this->asImp_().enforceInternalDirichletConstraints(applyDirichlet);
-    }
-
-    /*!
-     * \brief Incorporate Dirichlet boundary conditions
-     * \param applyDirichlet Lambda function for the BC incorporation on an scv
-     */
-    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().get(this->fvGeometry(), scvI);
-                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);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
     /*!
      * \brief Prepares all local views necessary for local assembly.
      */
@@ -338,12 +214,20 @@ public:
             prevElemVolVars.bind(element, fvGeometry, prevSol);
             elemFluxVarsCache.bind(element, fvGeometry, prevElemVolVars);
         }
+
+        this->elemBcTypes().update(problem(), this->element(), this->fvGeometry());
     }
 
     //! return reference to the underlying problem
-    const Problem& problem() const
+    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_; }
@@ -375,23 +259,23 @@ class SubDomainBoxLocalAssembler;
 template<std::size_t id, class TypeTag, class Assembler>
 class SubDomainBoxLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/true>
 : public SubDomainBoxLocalAssemblerBase<id, TypeTag, Assembler,
-             SubDomainBoxLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, true>, true >
+             SubDomainBoxLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, true>, DiffMethod::numeric, true >
 {
     using ThisType = SubDomainBoxLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/true>;
-    using ParentType = SubDomainBoxLocalAssemblerBase<id, TypeTag, Assembler, ThisType, /*implicit=*/true>;
+    using ParentType = SubDomainBoxLocalAssemblerBase<id, TypeTag, Assembler, ThisType, DiffMethod::numeric, /*implicit=*/true>;
     using Scalar = GetPropType<TypeTag, Properties::Scalar>;
     using VolumeVariables = GetPropType<TypeTag, Properties::VolumeVariables>;
-    using ElementResidualVector = typename ParentType::LocalResidual::ElementResidualVector;
 
     using ElementVolumeVariables = typename GetPropType<TypeTag, Properties::GridVolumeVariables>::LocalView;
     using GridGeometry = GetPropType<TypeTag, Properties::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 Problem = GetPropType<TypeTag, Properties::Problem>;
 
-    enum { numEq = GetPropType<TypeTag, Properties::ModelTraits>::numEq() };
-    enum { dim = GridView::dimension };
+    static constexpr int numEq = GetPropType<TypeTag, Properties::ModelTraits>::numEq();
+    static constexpr int dim = GridView::dimension;
 
     static constexpr bool enableGridFluxVarsCache = GetPropType<TypeTag, Properties::GridVariables>::GridFluxVariablesCache::cachingEnabled;
     static constexpr bool enableGridVolVarsCache = GetPropType<TypeTag, Properties::GridVariables>::GridVolumeVariables::cachingEnabled;
@@ -399,93 +283,25 @@ class SubDomainBoxLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, /*
 
 public:
     using ParentType::ParentType;
+    //! export element residual vector type
+    using ElementResidualVector = typename ParentType::LocalResidual::ElementResidualVector;
 
     /*!
-     * \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.
+     * \brief Update the coupling context for coupled models.
      */
-    template<class JacobianMatrixDiagBlock, class GridVariables>
-    ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrixDiagBlock& A, GridVariables& gridVariables)
+    template<class ElemSol>
+    void maybeUpdateCouplingContext(const SubControlVolume& scv, ElemSol& elemSol, const int pvIdx)
     {
-        //////////////////////////////////////////////////////////////////////////////////////////////////
-        // 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 actual 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.gridGeometry());
-
-        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);
-            }
-        }
+        this->couplingManager().updateCouplingContext(domainI, *this, domainI, scv.dofIndex(), elemSol[scv.localDofIndex()], pvIdx);
+    }
 
-        // evaluate additional derivatives that might arise from the coupling
+    /*!
+     * \brief Update the additional domain derivatives for coupled models.
+     */
+    template<class JacobianMatrixDiagBlock, class GridVariables>
+    void maybeEvalAdditionalDomainDerivatives(const ElementResidualVector& origResiduals, JacobianMatrixDiagBlock& A, GridVariables& gridVariables)
+    {
         this->couplingManager().evalAdditionalDomainDerivatives(domainI, *this, origResiduals, A, gridVariables);
-
-        return origResiduals;
     }
 
     /*!
@@ -506,9 +322,6 @@ public:
         auto&& curElemVolVars = this->curElemVolVars();
         auto&& elemFluxVarsCache = this->elemFluxVarsCache();
 
-        // get element stencil information
-        const auto& stencil = this->couplingManager().couplingStencil(domainI, element, domainJ);
-
         // convenience lambda for call to update self
         auto updateCoupledVariables = [&] ()
         {
@@ -530,7 +343,9 @@ public:
             }
         };
 
-        const auto& curSolJ = this->curSol()[domainJ];
+        // get element stencil information
+        const auto& stencil = this->couplingManager().couplingStencil(domainI, element, domainJ);
+        const auto& curSolJ = this->curSol(domainJ);
         for (const auto globalJ : stencil)
         {
             // undeflected privars and privars to be deflected
@@ -561,7 +376,7 @@ public:
                                                           epsCoupl(origPriVarsJ[pvIdx], pvIdx), numDiffMethod);
 
                 // update the global stiffness matrix with the current partial derivatives
-                for (auto&& scv : scvs(fvGeometry))
+                for (const auto& scv : scvs(fvGeometry))
                 {
                     for (int eqIdx = 0; eqIdx < numEq; eqIdx++)
                     {
@@ -569,17 +384,18 @@ public:
                         // 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];
 
                         // 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().get(fvGeometry, scv);
-                        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];
+                        if (this->elemBcTypes().hasDirichlet())
+                        {
+                            const auto bcTypes = this->elemBcTypes().get(fvGeometry, scv);
+                            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;
+                        }
 
                         // enforce internal Dirichlet constraints
                         if constexpr (Problem::enableInternalDirichletConstraints())
@@ -617,112 +433,15 @@ public:
 template<std::size_t id, class TypeTag, class Assembler>
 class SubDomainBoxLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/false>
 : public SubDomainBoxLocalAssemblerBase<id, TypeTag, Assembler,
-             SubDomainBoxLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, false>, false >
+             SubDomainBoxLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, false>, DiffMethod::numeric, false >
 {
     using ThisType = SubDomainBoxLocalAssembler<id, TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/false>;
-    using ParentType = SubDomainBoxLocalAssemblerBase<id, TypeTag, Assembler, ThisType, /*implicit=*/false>;
-    using Scalar = GetPropType<TypeTag, Properties::Scalar>;
-    using VolumeVariables = GetPropType<TypeTag, Properties::VolumeVariables>;
-    using ElementResidualVector = typename ParentType::LocalResidual::ElementResidualVector;
-    using ElementVolumeVariables = typename GetPropType<TypeTag, Properties::GridVolumeVariables>::LocalView;
-    using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
-    using FVElementGeometry = typename GridGeometry::LocalView;
-    using GridView = typename GridGeometry::GridView;
-    using Element = typename GridView::template Codim<0>::Entity;
-
-    enum { numEq = GetPropType<TypeTag, Properties::ModelTraits>::numEq() };
-    enum { dim = GridView::dimension };
-
-    static constexpr bool enableGridFluxVarsCache = GetPropType<TypeTag, Properties::GridVariables>::GridFluxVariablesCache::cachingEnabled;
-    static constexpr bool enableGridVolVarsCache = GetPropType<TypeTag, Properties::GridVariables>::GridVolumeVariables::cachingEnabled;
-    static constexpr auto domainI = Dune::index_constant<id>();
+    using ParentType = SubDomainBoxLocalAssemblerBase<id, TypeTag, Assembler, ThisType, DiffMethod::numeric, /*implicit=*/false>;
 
 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)
-    {
-        if (this->assembler().isStationaryProblem())
-            DUNE_THROW(Dune::InvalidStateException, "Using explicit jacobian assembler with stationary local residual");
-
-        // get some aliases for convenience
-        const auto& element = this->element();
-        const auto& fvGeometry = this->fvGeometry();
-        const auto& curSol = this->curSol()[domainI];
-        auto&& curElemVolVars = this->curElemVolVars();
-
-        // get the vector of the actual (undeflected) element residuals
-        const auto origResiduals = this->evalLocalResidual();
-        const auto origStorageResiduals = this->evalLocalStorageResidual();
-
-        //////////////////////////////////////////////////////////////////////////////////////////////////
-        // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the //
-        // neighboring elements all derivatives are zero. For the assembled element only the storage    //
-        // derivatives are non-zero.                                                                    //
-        //////////////////////////////////////////////////////////////////////////////////////////////////
-
-        // create the element solution
-        auto elemSol = elementSolution(element, curSol, fvGeometry.gridGeometry());
-
-        // create the vector storing the partial derivatives
-        ElementResidualVector partialDerivs(element.subEntities(dim));
-
-        // calculation of the derivatives
-        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 evalStorage = [&](Scalar priVar)
-                {
-                    // auto partialDerivsTmp = partialDerivs;
-                    elemSol[scv.localDofIndex()][pvIdx] = priVar;
-                    curVolVars.update(elemSol, this->problem(), element, scv);
-                    return this->evalLocalStorageResidual();
-                };
-
-                // derive the residuals numerically
-                static const NumericEpsilon<Scalar, numEq> eps_{this->problem().paramGroup()};
-                static const int numDiffMethod = getParamFromGroup<int>(this->problem().paramGroup(), "Assembly.NumericDifferenceMethod");
-                NumericDifferentiation::partialDerivative(evalStorage, elemSol[scv.localDofIndex()][pvIdx], partialDerivs, origStorageResiduals,
-                                                          eps_(elemSol[scv.localDofIndex()][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[dofIdx][dofIdx][eqIdx][pvIdx] += partialDerivs[scv.localDofIndex()][eqIdx];
-                }
-
-                // restore the original state of the scv's volume variables
-                curVolVars = origVolVars;
-
-                // restore the original element solution
-                elemSol[scv.localDofIndex()][pvIdx] = curSol[scv.dofIndex()][pvIdx];
-                // TODO additional dof dependencies
-            }
-        }
-
-        // return the undeflected residual
-        return origResiduals;
-    }
+    //! export element residual vector type
+    using ElementResidualVector = typename ParentType::LocalResidual::ElementResidualVector;
 
     /*!
      * \brief Computes the coupling derivatives with respect to the given element and adds them
diff --git a/dumux/multidomain/subdomainfcdiamondlocalassembler.hh b/dumux/multidomain/subdomainfcdiamondlocalassembler.hh
index fbcb3c2486..bd5c3ef7d6 100644
--- a/dumux/multidomain/subdomainfcdiamondlocalassembler.hh
+++ b/dumux/multidomain/subdomainfcdiamondlocalassembler.hh
@@ -35,7 +35,7 @@
 #include <dumux/common/numericdifferentiation.hh>
 #include <dumux/assembly/numericepsilon.hh>
 #include <dumux/assembly/diffmethod.hh>
-#include <dumux/assembly/fcdiamondlocalassembler.hh>
+#include <dumux/assembly/cvfelocalassembler.hh>
 
 namespace Dumux {
 
@@ -51,9 +51,9 @@ namespace Dumux {
  * \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 SubDomainFaceCenteredDiamondLocalAssemblerBase : public FaceCenteredDiamondLocalAssembler<TypeTag, Assembler, dm, implicit, Implementation>
+class SubDomainFaceCenteredDiamondLocalAssemblerBase : public CVFELocalAssembler<TypeTag, Assembler, dm, implicit, Implementation>
 {
-    using ParentType = FaceCenteredDiamondLocalAssembler<TypeTag, Assembler, dm, implicit, Implementation>;
+    using ParentType = CVFELocalAssembler<TypeTag, Assembler, dm, implicit, Implementation>;
 
     using Problem = GetPropType<TypeTag, Properties::Problem>;
     using SolutionVector = typename Assembler::SolutionVector;
@@ -288,7 +288,7 @@ public:
      * \brief Update the additional domain derivatives for coupled models.
      */
     template<class JacobianMatrixDiagBlock, class GridVariables>
-    void maybeEvalAdditionalDomainDerivatives(const ElementResidualVector& origResiduals, const JacobianMatrixDiagBlock& A, GridVariables& gridVariables)
+    void maybeEvalAdditionalDomainDerivatives(const ElementResidualVector& origResiduals, JacobianMatrixDiagBlock& A, GridVariables& gridVariables)
     {
         this->couplingManager().evalAdditionalDomainDerivatives(domainI, *this, origResiduals, A, gridVariables);
     }
diff --git a/dumux/multidomain/subdomainpq1bubblelocalassembler.hh b/dumux/multidomain/subdomainpq1bubblelocalassembler.hh
index 4fe99e9575..f76f5730a1 100644
--- a/dumux/multidomain/subdomainpq1bubblelocalassembler.hh
+++ b/dumux/multidomain/subdomainpq1bubblelocalassembler.hh
@@ -35,7 +35,7 @@
 #include <dumux/common/numericdifferentiation.hh>
 #include <dumux/assembly/numericepsilon.hh>
 #include <dumux/assembly/diffmethod.hh>
-#include <dumux/assembly/pq1bubblelocalassembler.hh>
+#include <dumux/assembly/cvfelocalassembler.hh>
 
 namespace Dumux {
 
@@ -51,9 +51,9 @@ namespace Dumux {
  * \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 SubDomainPQ1BubbleLocalAssemblerBase : public PQ1BubbleLocalAssembler<TypeTag, Assembler, dm, implicit, Implementation>
+class SubDomainPQ1BubbleLocalAssemblerBase : public CVFELocalAssembler<TypeTag, Assembler, dm, implicit, Implementation>
 {
-    using ParentType = PQ1BubbleLocalAssembler<TypeTag, Assembler, dm, implicit, Implementation>;
+    using ParentType = CVFELocalAssembler<TypeTag, Assembler, dm, implicit, Implementation>;
 
     using Problem = GetPropType<TypeTag, Properties::Problem>;
     using SolutionVector = typename Assembler::SolutionVector;
@@ -288,7 +288,7 @@ public:
      * \brief Update the additional domain derivatives for coupled models.
      */
     template<class JacobianMatrixDiagBlock, class GridVariables>
-    void maybeEvalAdditionalDomainDerivatives(const ElementResidualVector& origResiduals, const JacobianMatrixDiagBlock& A, GridVariables& gridVariables)
+    void maybeEvalAdditionalDomainDerivatives(const ElementResidualVector& origResiduals, JacobianMatrixDiagBlock& A, GridVariables& gridVariables)
     {
         this->couplingManager().evalAdditionalDomainDerivatives(domainI, *this, origResiduals, A, gridVariables);
     }
-- 
GitLab