Skip to content
Snippets Groups Projects
Select Git revision
  • 5880f105e9b69d7a3d4b2012c54534ece2c5b0fe
  • master default protected
  • release/1.0
3 results

mmdg.hh

Blame
  • mmdg.hh 27.17 KiB
    #ifndef DUNE_MMDG_HH
    #define DUNE_MMDG_HH
    
    #include <dune/mmdg/dg.hh>
    
    template<class GridView, class Mapper, class InterfaceGridView,
      class InterfaceMapper, class Problem>
    class MMDG : public DG<GridView, Mapper, Problem>
    {
    public:
      static constexpr int dim = GridView::dimension;
      static constexpr auto iSimplex = Dune::GeometryTypes::simplex(dim-1);
    
      using Base = DG<GridView, Mapper, Problem>;
      using Scalar = typename Base::Scalar; //NOTE using Base::Scalar
      using Matrix = typename Base::Matrix;
      using Vector = typename Base::Vector;
      using Coordinate = Dune::FieldVector<Scalar, dim>;
      using InterfaceBasis = std::array<Coordinate, dim-1>;
      using QRuleInterface = typename Base::QRuleEdge;
      using QRulesInterface = typename Base::QRulesEdge;
      using VTKFunction = Dune::VTKFunction<InterfaceGridView>;
      using P1Function = Dune::NonconformingP1VTKFunction<InterfaceGridView,
        Dune::DynamicVector<Scalar>>;
    
      //constructor
      MMDG (const GridView& gridView, const Mapper& mapper,
        const InterfaceGridView& iGridView, const InterfaceMapper& iMapper,
        const Problem& problem) :
        Base(gridView, mapper, problem,
          (1 + dim) * gridView.size(0) + dim * iGridView.size(0)),
        iGridView_(iGridView), iMapper_(iMapper) {}
    
      const void operator() (const Scalar mu, const Scalar xi)
      {
        assembleSLE(mu, xi);
        Base::A->compress();
    
        Dune::InverseOperatorResult result;
        Dune::UMFPack<Matrix> solver(*Base::A);
        solver.apply(Base::d, Base::b, result);
    
        //write solution to a vtk file
        writeVTKoutput();
      }
    
      //compute L2 error using quadrature rules
      // norm = sqrt( ||.||^2_L2(bulk) + ||.||^2_L2(interface) )
      Scalar computeL2error(const int quadratureOrder = 5) const
      {
        const Scalar bulkError = Base::computeL2error(quadratureOrder);
        Scalar interfaceError(0.0);
    
        for (const auto& iElem : elements(iGridView_))
        {
          const auto& iGeo = iElem.geometry();
          const int iElemIdxSLE =
            (dim + 1) * Base::gridView_.size(0) + dim * iMapper_.index(iElem);
          const QRuleInterface& qrule =
            QRulesInterface::rule(iSimplex, quadratureOrder);
          const InterfaceBasis iFrame = interfaceFrame(
            Base::gridView_.grid().asIntersection(iElem).centerUnitOuterNormal());
    
          //determine L2-error on the interface element iElem using a
          //quadrature rule
          for (const auto& ip : qrule)
          {
            const auto& qpLocal = ip.position();
            const auto& qpGlobal = iGeo.global(qpLocal);
    
            const Scalar quadratureFactor =
              ip.weight() * iGeo.integrationElement(qpLocal);
    
            Scalar numericalSolutionAtQP = Base::d[iElemIdxSLE];
    
            for (int i = 0; i < dim - 1; i++)
            {
              numericalSolutionAtQP +=
                Base::d[iElemIdxSLE + i + 1] * (qpGlobal * iFrame[i]);
            }
    
            const Scalar errorEvaluation = numericalSolutionAtQP
              - Base::problem_.exactInterfaceSolution(qpGlobal);
    
            interfaceError += quadratureFactor * errorEvaluation * errorEvaluation;
          }
        }
    
        return sqrt(bulkError * bulkError + interfaceError);
      }
    
      const InterfaceGridView& iGridView_;
      const InterfaceMapper& iMapper_;
    
    private:
      //assemble stiffness matrix A and load vector b
      void assembleSLE (const Scalar mu, const Scalar xi)
      {
        //modell parameters //NOTE: what should be the capture of alpha?
        const auto alpha = [this](const Coordinate& pos) -> Scalar
        {
          return Base::problem_.Kperp(pos) / Base::problem_.aperture(pos);
        };
    
        const auto beta = [&alpha, &xi](const Coordinate& pos) -> Scalar
        {
          return 4 * alpha(pos) / (2 * xi - 1);
        };
    
        //quadrature rules
        const QRuleInterface& rule_qInterface = QRulesInterface::rule(iSimplex,
          Base::problem_.quadratureOrder_qInterface());
        const QRuleInterface& rule_Kpar = QRulesInterface::rule(iSimplex,
          Base::problem_.quadratureOrder_Kparallel());
        const QRuleInterface& rule_Kperp = QRulesInterface::rule(iSimplex,
          Base::problem_.quadratureOrder_Kperp());
        const QRuleInterface& rule_KperpPlus1 = QRulesInterface::rule(iSimplex,
          Base::problem_.quadratureOrder_Kperp() + 1);
    
        //we use the bulk basis
        // phi_elem,0 (x) = indicator(elem);
        // phi_elem,i (x) = x[i]*indicator(elem);
        //for elem in elements(gridView) and i = 1,...,dim
    
        //we use the interface basis
        // phi_iElem,0 (x) = indicator(elem);
        // phi_iElem,i (x) = (x * tau_i)*indicator(elem);
        //for iElem in elements(iGridView) and i = 1,...,dim-1
        //where {tau_i | i=1,...,dim-1} is the interface coordinate system given
        //by the method interfaceFrame(normal)
    
        //in total we use the basis given by the union of
        // { (phi_elem,i , 0) | elem in elements(gridView), i = 0,...,dim }
        //and
        // { (0 , phi_iElem,i ) | iElem in elements(iGridView), i = 0,...,dim-1 }
    
        //thus the stiffness matrix A and the load vector b exhibit the following
        //block structure
        // A = [ bulk,     coupling                b = [ bulk,
        //       coupling, interface],                   interface ]
    
        //call base class method for the assignment to fill stiffness matrix A
        //and load vector b with bulk entries
        Base::assembleSLE(mu);
    
        for (const auto& iElem : elements(iGridView_))
        {
          const int iElemIdx = iMapper_.index(iElem);
          const auto& iGeo = iElem.geometry();
          const auto& intersct = Base::gridView_.grid().asIntersection(iElem);
    
          //get basis of the interface coordinate system for evaluation of
          //basis function phi_iElem,i with i=1,...,dim-1
          const InterfaceBasis iFrame =
            interfaceFrame(intersct.centerUnitOuterNormal());
    
          //in the total system of linear equations (SLE) Ad = b,
          //the index iElemIdxSLE refers to the basis function (0, phi_iElem,0)
          //and the indices iElemIdxSLE + i + 1, i = 1,...,dim-1, refer to the
          //basis functions (0, phi_iElem,i)
          const int iElemIdxSLE = (dim + 1)*Base::gridView_.size(0) + dim*iElemIdx;
    
          // === coupling entries ===
    
          //evaluate the coupling term
          // int_iElem beta * phi_iElem,i * phi_iElem,j ds
          //using a quadrature rule for i = 0 and j = 0,...,dim-1 or vice versa //TODO symmetrize more efficiently
          for (const auto& ip : rule_Kperp)
          {
            const auto& qpLocal = ip.position();
            const auto& qpGlobal = iGeo.global(qpLocal);
    
            const Scalar quadratureFactor =
              ip.weight() * iGeo.integrationElement(qpLocal);
            const Scalar betaEvaluation = beta(qpGlobal);
    
            Base::A->entry(iElemIdxSLE, iElemIdxSLE) +=
              quadratureFactor * betaEvaluation;
    
            for (int i = 0; i < dim - 1; i++)
            {
              Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE) +=
                quadratureFactor * betaEvaluation * (iFrame[i] * qpGlobal);
              Base::A->entry(iElemIdxSLE, iElemIdxSLE + i + 1) +=
                quadratureFactor * betaEvaluation * (iFrame[i] * qpGlobal);
            }
          }
    
          //evaluate the coupling term
          // int_iElem beta * phi_iElem,i * phi_iElem,j ds
          //using a quadrature rule for i,j = 1,...,dim-1 //TODO symmetrize more efficiently
          for (const auto& ip : rule_KperpPlus1)
          {
            const auto& qpLocal = ip.position();
            const auto& qpGlobal = iGeo.global(qpLocal);
    
            const Scalar quadratureFactor =
              ip.weight() * iGeo.integrationElement(qpLocal);
            const Scalar betaEvaluation = beta(qpGlobal);
    
            for (int i = 0; i < dim - 1; i++)
            {
              for (int j = 0; j <= i; j++)
              {
                Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + j + 1) +=
                  quadratureFactor * betaEvaluation * (iFrame[i] * qpGlobal)
                  * (iFrame[j] * qpGlobal);
    
                if (i != j)
                {
                  Base::A->entry(iElemIdxSLE + j + 1, iElemIdxSLE + i + 1) +=
                    quadratureFactor * betaEvaluation * (iFrame[i] * qpGlobal)
                    * (iFrame[j] * qpGlobal);
                }
              }
            }
          }
    
          //get neighboring bulk grid elements of iELem
          const auto elemIn = intersct.inside();
          const auto elemOut = intersct.outside();
    
          const int elemInIdxSLE = (dim + 1) * Base::mapper_.index(elemIn);
          const int elemOutIdxSLE = (dim + 1) * Base::mapper_.index(elemOut);
    
          //evalute the coupling terms
          // int_iElem alpha * jump(phi_elem1,0) * jump(phi_elem2,i) ds
          //and
          // int_iElem beta * avg(phi_elem1,0) * avg(phi_elem2,i) ds
          //for elem1, elem2 in {elemIn, elemOut} and i = 0,...,dim
          //using a quadrature rule
          for (const auto& ip : rule_Kperp)
          {
            const auto& qpLocal = ip.position();
            const auto& qpGlobal = iGeo.global(qpLocal);
    
            const Scalar quadratureFactor =
              ip.weight() * iGeo.integrationElement(qpLocal);
            const Scalar alphaEvaluation = alpha(qpGlobal);
            const Scalar betaEvaluation4 = 0.25 * beta(qpGlobal);
    
            const Scalar bulkUpdate1 =
              quadratureFactor * (alphaEvaluation + betaEvaluation4);
            const Scalar bulkUpdate2 =
              quadratureFactor * (-alphaEvaluation + betaEvaluation4);
    
            Base::A->entry(elemInIdxSLE, elemInIdxSLE) += bulkUpdate1;
            Base::A->entry(elemOutIdxSLE, elemOutIdxSLE) += bulkUpdate1;
    
            Base::A->entry(elemInIdxSLE, elemOutIdxSLE) += bulkUpdate2;
            Base::A->entry(elemOutIdxSLE, elemInIdxSLE) += bulkUpdate2;
    
            for (int i = 0; i < dim; i++)
            {
              const Scalar bulkUpdate3 = qpGlobal[i] * bulkUpdate1;
              const Scalar bulkUpdate4 = qpGlobal[i] * bulkUpdate2;
    
              Base::A->entry(elemInIdxSLE + i + 1, elemInIdxSLE) += bulkUpdate3;
              Base::A->entry(elemInIdxSLE, elemInIdxSLE + i + 1) += bulkUpdate3;
              Base::A->entry(elemOutIdxSLE + i + 1, elemOutIdxSLE) += bulkUpdate3;
              Base::A->entry(elemOutIdxSLE, elemOutIdxSLE + i + 1) += bulkUpdate3;
    
              Base::A->entry(elemInIdxSLE + i + 1, elemOutIdxSLE) += bulkUpdate4;
              Base::A->entry(elemOutIdxSLE, elemInIdxSLE + i + 1) += bulkUpdate4;
              Base::A->entry(elemOutIdxSLE + i + 1, elemInIdxSLE) += bulkUpdate4;
              Base::A->entry(elemInIdxSLE, elemOutIdxSLE + i + 1) += bulkUpdate4;
            }
          }
    
          //evalute the coupling terms
          // int_iElem alpha * jump(phi_elem1,i) * jump(phi_elem2,j) ds
          //and
          // int_iElem beta * avg(phi_elem1,i) * avg(phi_elem2,j) ds
          //for elem1, elem2 in {elemIn, elemOut} and i,j = 1,...,dim
          //using a quadrature rule
          for (const auto& ip : rule_KperpPlus1)
          {
            const auto& qpLocal = ip.position();
            const auto& qpGlobal = iGeo.global(qpLocal);
    
            const Scalar quadratureFactor =
              ip.weight() * iGeo.integrationElement(qpLocal);
            const Scalar alphaEvaluation = alpha(qpGlobal);
            const Scalar betaEvaluation4 = 0.25 * beta(qpGlobal);
    
            const Scalar bulkUpdate1 =
              quadratureFactor * (alphaEvaluation + betaEvaluation4);
            const Scalar bulkUpdate2 =
              quadratureFactor * (-alphaEvaluation + betaEvaluation4);
    
            for (int i = 0; i < dim; i++)
            {
              const Scalar bulkUpdate3 = qpGlobal[i] * qpGlobal[i] * bulkUpdate1;
              const Scalar bulkUpdate4 = qpGlobal[i] * qpGlobal[i] * bulkUpdate2;
    
              Base::A->entry(elemInIdxSLE + i + 1, elemInIdxSLE + i + 1) +=
                bulkUpdate3;
              Base::A->entry(elemOutIdxSLE + i + 1, elemOutIdxSLE + i + 1) +=
                bulkUpdate3;
    
              Base::A->entry(elemInIdxSLE + i + 1, elemOutIdxSLE + i + 1) +=
                bulkUpdate4;
              Base::A->entry(elemOutIdxSLE + i + 1, elemInIdxSLE + i + 1) +=
                bulkUpdate4;
    
              for (int j = 0; j < i; j++)
              {
                const Scalar bulkUpdate5 = qpGlobal[i] * qpGlobal[j] * bulkUpdate1;
                const Scalar bulkUpdate6 = qpGlobal[i] * qpGlobal[j] * bulkUpdate2;
    
                Base::A->entry(elemInIdxSLE + i + 1, elemInIdxSLE + j + 1) +=
                  bulkUpdate5;
                Base::A->entry(elemInIdxSLE + j + 1, elemInIdxSLE + i + 1) +=
                  bulkUpdate5;
                Base::A->entry(elemOutIdxSLE + i + 1, elemOutIdxSLE + j + 1) +=
                  bulkUpdate5;
                Base::A->entry(elemOutIdxSLE + j + 1, elemOutIdxSLE + i + 1) +=
                  bulkUpdate5;
    
                Base::A->entry(elemInIdxSLE + i + 1, elemOutIdxSLE + j + 1) +=
                  bulkUpdate6;
                Base::A->entry(elemOutIdxSLE + j + 1, elemInIdxSLE + i + 1) +=
                  bulkUpdate6;
                Base::A->entry(elemOutIdxSLE + i + 1, elemInIdxSLE + j + 1) +=
                  bulkUpdate6;
                Base::A->entry(elemInIdxSLE + j + 1, elemOutIdxSLE + i + 1) +=
                  bulkUpdate6;
              }
            }
          }
    
          //evalute the coupling terms
          // -int_iElem beta * phi_iElem,i * avg(phi_elem,j) ds
          //for (i = 0 and j = 0,...,dim-1) or (i = 0,...,dim and j = 0)
          //and for elem in {elemIn, elemOut} using a quadrature rule
          for (const auto& ip : rule_Kperp) //TODO: symmetrize more efficiently
          //TODO: apply quadrature rule only once
          {
            const auto& qpLocal = ip.position();
            const auto& qpGlobal = iGeo.global(qpLocal);
    
            const Scalar quadratureFactor =
              ip.weight() * iGeo.integrationElement(qpLocal);
            const Scalar betaEvaluation2 = 0.5 * beta(qpGlobal);
    
            const Scalar couplingUpdate1 = quadratureFactor * betaEvaluation2;
    
            Base::A->entry(elemInIdxSLE, iElemIdx) -= couplingUpdate1;
            Base::A->entry(iElemIdx, elemInIdxSLE) -= couplingUpdate1;
            Base::A->entry(elemOutIdxSLE, iElemIdx) -= couplingUpdate1;
            Base::A->entry(iElemIdx, elemOutIdxSLE) -= couplingUpdate1;
    
            for (int i = 0; i < dim - 1; i++)
            {
              const Scalar couplingUpdate2 = qpGlobal[i] * couplingUpdate1;
              const Scalar couplingUpdate3 =
                (iFrame[i] * qpGlobal) * couplingUpdate1;
    
              Base::A->entry(elemInIdxSLE + i + 1, iElemIdx) -= couplingUpdate2;
              Base::A->entry(iElemIdx, elemInIdxSLE + i + 1) -= couplingUpdate2;
              Base::A->entry(elemOutIdxSLE + i + 1, iElemIdx) -= couplingUpdate2;
              Base::A->entry(iElemIdx, elemOutIdxSLE + i + 1) -= couplingUpdate2;
    
              Base::A->entry(elemInIdxSLE, iElemIdx + i + 1) -= couplingUpdate3;
              Base::A->entry(iElemIdx + i + 1, elemInIdxSLE) -= couplingUpdate3;
              Base::A->entry(elemOutIdxSLE, iElemIdx + i + 1) -= couplingUpdate3;
              Base::A->entry(iElemIdx + i + 1, elemOutIdxSLE) -= couplingUpdate3;
            }
    
            //i = dim - 1
            const Scalar couplingUpdate4 = qpGlobal[dim] * couplingUpdate1;
            Base::A->entry(elemInIdxSLE + dim + 1, iElemIdx) -= couplingUpdate4;
            Base::A->entry(iElemIdx, elemInIdxSLE + dim + 1) -= couplingUpdate4;
            Base::A->entry(elemOutIdxSLE + dim + 1, iElemIdx) -= couplingUpdate4;
            Base::A->entry(iElemIdx, elemOutIdxSLE + dim + 1) -= couplingUpdate4;
          }
    
          // === interface entries ===
    
          //fill load vector b with interface entries using a quadrature rule
          for (const auto& ip : rule_qInterface)
          {
            //quadrature point in local and global coordinates
            const auto& qpLocal = ip.position();
            const auto& qpGlobal = iGeo.global(qpLocal);
    
            const Scalar quadratureFactor =
              ip.weight() * iGeo.integrationElement(qpLocal);
            const Scalar qInterfaceEvaluation(Base::problem_.qInterface(qpGlobal));
    
            //quadrature for int_elem q*phi_elem,0 dV
            Base::b[iElemIdxSLE] += quadratureFactor * qInterfaceEvaluation;
    
            //quadrature for int_elem q*phi_elem,i dV
            for (int i = 0; i < dim - 1; i++)
            {
              Base::b[iElemIdxSLE + i + 1] += quadratureFactor *
                (qpGlobal * iFrame[i]) * qInterfaceEvaluation;
            }
          }
    
          //evaluate
          // int_iElem (Kparallel * grad(phi_iElem,i)) * grad(phi_iElem,j) ds
          // = int_iElem (Kpar * tau_i) * tau_j ds
          //using a quadrature rule for i,j = 1,...,dim-1
          for (const auto& ip : rule_Kpar)
          {
            const auto& qpLocal = ip.position();
            const auto& qpGlobal = iGeo.global(qpLocal);
    
            const Scalar quadratureFactor =
              ip.weight() * iGeo.integrationElement(qpLocal);
    
            for (int i = 0; i < dim-1 ; i++)
            {
              Coordinate KparDotTau_i;
              Base::problem_.Kparallel(qpGlobal).mv(iFrame[i], KparDotTau_i);
    
              for (int j = 0; j <= i; j++)
              {
                Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + j + 1) +=
                  quadratureFactor * ( KparDotTau_i * iFrame[j] );
    
                if (i != j)
                {
                  Base::A->entry(iElemIdxSLE + j + 1, iElemIdxSLE + i + 1) +=
                    quadratureFactor * ( KparDotTau_i * iFrame[j] );
                }
              }
            }
          }
    
          //iterate over all intersection with the boundary of iElem
          //NOTE: only works for 2d!, otherwise quadrature required
          for (const auto& intersct : intersections(iGridView_, iElem))
          {
            const auto& center = intersct.geometry().center();
            const auto& normal = intersct.centerUnitOuterNormal();
            Coordinate KparDotTau_i;
    
            //evaluate the interface term
            // int_intersct mu^Gamma * jump(phi_iElem,0) * jump(phi_iElem,0) dr
            Base::A->entry(iElemIdxSLE, iElemIdxSLE) += mu;
    
            if (intersct.neighbor()) //inner interface edge
            {
              //evaluate the interface terms
              // int_intersct mu^Gamma * jump(phi_iElem,i) * jump(phi_iElem,j) dr
              //and
              // -int_intersct avg(Kparallel * grad(phi_iElem,i))
              //    * jump(phi_iElem,j) dr
              //for i,j = 0,...,dim-1 and (i != 0 or j != 0)
              for (int i = 0; i < dim - 1; i++)
              {
                Base::problem_.Kparallel(center).mv(iFrame[i], KparDotTau_i);
                const Scalar centerI = center * iFrame[i];
                const Scalar interfaceUpdate1 = mu * centerI;
                const Scalar interfaceUpdate2 = KparDotTau_i * normal;
                const Scalar interfaceUpdate3 =
                  interfaceUpdate1 - 0.5 * interfaceUpdate2;
    
                Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE) +=
                  interfaceUpdate3;
                Base::A->entry(iElemIdxSLE, iElemIdxSLE + i + 1) +=
                  interfaceUpdate3;
                Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + i + 1) +=
                  centerI * (interfaceUpdate1 - interfaceUpdate2);
    
                for (int j = 0; j < i; i++)
                { //NOTE: only relevant for n=3, requires quadrature rule for n=3
                  Coordinate KparDotTau_j;
                  Base::problem_.Kparallel(center).mv(iFrame[j], KparDotTau_j);
                  const Scalar interfaceUpdate4 =
                    (center * iFrame[j]) * interfaceUpdate1
                    - 0.5 * (interfaceUpdate2 * (center * iFrame[j])
                    + (KparDotTau_j * normal) * centerI);
    
                  Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + j + 1) +=
                    interfaceUpdate4;
                  Base::A->entry(iElemIdxSLE + j + 1, iElemIdxSLE + i + 1) +=
                    interfaceUpdate4;
                }
              }
    
              //index of the neighboring element
              const int neighborIdx = iMapper_.index(intersct.outside());
    
              if (neighborIdx > iElemIdx)
              { //make sure that each interface edge is considered only once
                continue;
              }
    
              const int neighborIdxSLE =
                (dim + 1) * Base::gridView_.size(0) + dim * neighborIdx;
    
              //evaluate the interface terms
              // int_intersct mu^Gamma * jump(phi_iElem,i) * jump(phi_neighbor,j) dr
              //and
              // -int_intersct avg(Kparallel * grad(phi_iElem,i))
              //    * jump(phi_neighbor,j) dr
              //resp.
              // -int_intersct avg(Kparallel * grad(phi_neighbor,i))
              //    * jump(phi_iElem,j) dr
              //for i,j = 0,...,dim-1
              Base::A->entry(iElemIdxSLE, neighborIdxSLE) += -mu;
              Base::A->entry(neighborIdxSLE, iElemIdxSLE) += -mu;
    
              for (int i = 0; i < dim - 1; i++)
              {
                Base::problem_.Kparallel(center).mv(iFrame[i], KparDotTau_i);
                const Scalar centerI = center * iFrame[i];
                const Scalar interfaceUpdate1 = mu * centerI;
                const Scalar interfaceUpdate2 = KparDotTau_i * normal;
                const Scalar interfaceUpdate3 =
                  -interfaceUpdate1 + 0.5 * interfaceUpdate2;
                const Scalar interfaceUpdate4 =
                  -interfaceUpdate1 - 0.5 * interfaceUpdate2;
    
                Base::A->entry(iElemIdxSLE + i + 1, neighborIdxSLE) +=
                  interfaceUpdate3;
                Base::A->entry(neighborIdxSLE, iElemIdxSLE + i + 1) +=
                  interfaceUpdate3;
                Base::A->entry(neighborIdxSLE + i + 1, iElemIdxSLE) +=
                  -interfaceUpdate4;
                Base::A->entry(iElemIdxSLE, neighborIdxSLE + i + 1) +=
                  -interfaceUpdate4;
    
                Base::A->entry(iElemIdxSLE + i + 1, neighborIdxSLE + i + 1) +=
                  -centerI * interfaceUpdate1;
    
                for (int j = 0; j < i; j++)
                { //NOTE: only relevant for n=3, requires quadrature rule
                  Coordinate KparDotTau_j;
                  Base::problem_.Kparallel(center).mv(iFrame[j], KparDotTau_j);
                  const Scalar interfaceUpdate5 =
                    (center * iFrame[j]) * interfaceUpdate1;
                  const Scalar interfaceUpdate6 = interfaceUpdate2 *
                    (center * iFrame[j]) - (KparDotTau_j * normal) * centerI;
                  const Scalar interfaceUpdate7 =
                    -interfaceUpdate5 - 0.5 * interfaceUpdate6;
                  const Scalar interfaceUpdate8 =
                    -interfaceUpdate5 + 0.5 * interfaceUpdate6;
    
                  Base::A->entry(iElemIdxSLE + i + 1, neighborIdxSLE + j + 1) +=
                    interfaceUpdate7;
                  Base::A->entry(neighborIdxSLE + j + 1, iElemIdxSLE + i + 1) +=
                    interfaceUpdate7;
                  Base::A->entry(iElemIdxSLE + j + 1, neighborIdxSLE + i + 1) +=
                    interfaceUpdate8;
                  Base::A->entry(neighborIdxSLE + i + 1, iElemIdxSLE + j + 1) +=
                    interfaceUpdate8;
                }
              }
            }
            else //boundary interface edge
            {
              //evaluate the interface terms
              // int_intersct mu^Gamma * phi_iElem,i * phi_iElem,j dr
              //and
              // -int_intersct phi_iElem,j * (Kparallel * grad(phi_iElem,i))
              //    * normal dr
              //for i,j = 0,...,dim-1 and (i != 0 or j != 0)
              //and add interface boundary terms to the load vector b:
              // int_intersct d * g * (mu * phi_iElem,i +
              //      (Kparallel * grad(phi_i,iElem)) * normal) dr
              //for i = 0,...,dim-1
              const Scalar dgGamma = Base::problem_.aperture(center)
                * Base::problem_.boundary(center);
              Base::b[iElemIdxSLE] += mu * dgGamma;
    
              for (int i = 0; i < dim - 1; i++)
              {
                Base::problem_.Kparallel(center).mv(iFrame[i], KparDotTau_i);
                const Scalar centerI = center * iFrame[i];
                const Scalar interfaceUpdate1 = mu * centerI;
                const Scalar interfaceUpdate2 = KparDotTau_i * normal;
                const Scalar interfaceUpdate3 =
                  interfaceUpdate1 - interfaceUpdate2;
    
                Base::b[iElemIdxSLE] += dgGamma * (mu * centerI - interfaceUpdate2);
    
                Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE) +=
                  interfaceUpdate3;
                Base::A->entry(iElemIdxSLE, iElemIdxSLE + i + 1) +=
                  interfaceUpdate3;
                Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + i + 1) +=
                  centerI * (interfaceUpdate1 - 2.0 * interfaceUpdate2);
    
                for (int j = 0; j < i; i++)
                { //NOTE: only relevant for n=3, requires quadrature rule for n=3
                  Coordinate KparDotTau_j;
                  Base::problem_.Kparallel(center).mv(iFrame[j], KparDotTau_j);
                  const Scalar interfaceUpdate4 =
                    (center * iFrame[j]) * interfaceUpdate1
                    - (interfaceUpdate2 * (center * iFrame[j])
                      + (KparDotTau_j * normal) * centerI);
    
                  Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + j + 1) +=
                    interfaceUpdate4;
                  Base::A->entry(iElemIdxSLE + j + 1, iElemIdxSLE + i + 1) +=
                    interfaceUpdate4;
                }
              }
            }
          }
        }
      }
    
    //NOTE: MMesh ensures unique intersection, i.e. unique normal, add assert index(inside) < index(outside)
      //returns a basis of the interface coordinate system
      //2d: tau = [ normal[1],
      //           -normal[0] ]
      //3d: tau_1 ~ [ normal[1] + normal[2],     tau_2 ~ [ -normal[1],
      //             -normal[0],                            normal[0] + normal[2]
      //             -normal[0]             ],             -normal[1]            ]
      InterfaceBasis interfaceFrame(const Coordinate& normal) const
      {
        InterfaceBasis iBasis;
    
        for (int i = 0; i < dim - 1; i++)
        {
          Coordinate tau(0.0);
          for (int j = 0; j < dim - 1; j++)
          {
            if (j == i)
            {
              continue;
            }
    
            tau[i] += normal[j];
            tau[j] = -normal[i];
          }
    
          if constexpr (dim != 2)
          {
            tau /= tau.two_norm();
          }
    
          iBasis[i] = tau;
        }
    
        return iBasis;
      }
    
      void writeVTKoutput () const
      {
        //degrees of freedom of bulk and interface grids
        const int bulkDOF = (dim + 1) * Base::gridView_.size(0);
        const int interfaceDOF = dim * iGridView_.size(0);
    
        //=== write bulk solution to file ===
        Base::writeVTKoutput(bulkDOF);
    
        //=== write interface solution to file ===
    
        //storage for pressure data, for each interface element we store the
        //pressure at the corners of the interface element, the VTKFunction will
        //create the output using a linear interpolation for each interface element
        Dune::DynamicVector<Scalar> iPressure(interfaceDOF, 0.0);
        Dune::DynamicVector<Scalar> exactIPressure(interfaceDOF, 0.0);
    
        for (const auto& iElem : elements(iGridView_))
        {
          const int iElemIdxSLE = bulkDOF + dim * iMapper_.index(iElem);
          const auto& iGeo = iElem.geometry();
    
          //get basis of the interface coordinate system for evaluation of
          //basis function phi_iElem,i with i=1,...,dim-1
          const InterfaceBasis iFrame = interfaceFrame(
            Base::gridView_.grid().asIntersection(iElem).centerUnitOuterNormal());
    
          for (int k = 0; k < iGeo.corners(); k++)
          {
            if (Base::problem_.hasExactSolution())
            {
              exactIPressure[iElemIdxSLE + k] =
                Base::problem_.exactInterfaceSolution(iGeo.corner(k));
            }
    
            //contribution of the basis function
            // phi_iElem,0 (x) = indicator(iElem);
            //at the kth corner of iElem
            iPressure[iElemIdxSLE + k] = Base::d[iElemIdxSLE];
    
            for (int i = 0; i < dim - 1; i++)
            {
              //contribution of the basis function
              // phi_iElem,i (x) = (x * tau[i]) * indicator(iElem);
              //at the kth corner of iElem
              iPressure[iElemIdxSLE + k] +=
                Base::d[iElemIdxSLE + i + 1] * (iGeo.corner(k) * iFrame[i]);
            }
          }
        }
    
        Dune::VTKWriter<InterfaceGridView>
          vtkWriter(iGridView_, Dune::VTK::nonconforming);
        vtkWriter.addVertexData( std::shared_ptr<const VTKFunction>(
          new P1Function(iGridView_, iPressure, "interfacePressure")));
    
        if (Base::problem_.hasExactSolution())
        {
          vtkWriter.addVertexData( std::shared_ptr<const VTKFunction>(
            new P1Function(iGridView_, exactIPressure, "exactInterfacePressure")));
        }
    
        vtkWriter.pwrite("interfacePressureData", "", "data");
      }
    
    };
    
    #endif