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

mmdg.hh

Blame
  • mmdg.hh 38.86 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);
      static constexpr auto iFacet = Dune::GeometryTypes::simplex(dim-2);
    
      using Base = DG<GridView, Mapper, Problem>;
      using Scalar = typename Base::Scalar;
      using Matrix = typename Base::Matrix;
      using Vector = typename Base::Vector;
      using Coordinate = typename Base::Coordinate;
      using CoordinateMatrix = typename Base::CoordinateMatrix;
      using InterfaceBasis = std::array<Coordinate, dim-1>;
      using QRuleInterface = typename Base::QRuleEdge;
      using QRulesInterface = typename Base::QRulesEdge;
      using QRuleIFacet = Dune::QuadratureRule<Scalar, dim-2>;
      using QRulesIFacet = Dune::QuadratureRules<Scalar, dim-2>;
      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 mu0, const Scalar xi)
      {
        assembleSLE(mu0, 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 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
          const auto qlambda =
            [&](const Coordinate& qpGlobal, const Scalar weight)
            {
              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 += weight * errorEvaluation * errorEvaluation;
            };
    
          Base::applyQuadratureRule(iGeo, qrule, qlambda);
        }
    
        //print bulk and interface error
        std::cout << "ERROR bulk: " << bulkError << "\t ERROR interface: " <<
          sqrt(interfaceError) << "\n";
    
        return sqrt(bulkError * bulkError + interfaceError);
      }
    
      const InterfaceGridView& iGridView_;
      const InterfaceMapper& iMapper_;
    
    private:
      //assemble stiffness matrix A and load vector b
      void assembleSLE (const Scalar mu0, const Scalar xi)
      {
        //modell parameter
        const auto beta = [this, &xi](const Coordinate& pos) -> Scalar
        {
          return 4.0 * Base::problem_.Kperp(pos) / (2.0 * xi - 1.0);
        };
    
        //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);
        const QRuleIFacet& rule_Kpar_iFacet =
          QRulesIFacet::rule(iFacet, Base::problem_.quadratureOrder_Kparallel());
        const QRuleIFacet& rule_interfaceBoundary = QRulesIFacet::rule(iFacet,
          Base::problem_.quadratureOrderInterfaceBoundary());
    
        //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(mu0);
    
        for (const auto& iElem : elements(iGridView_))
        {
          const int iElemIdx = iMapper_.index(iElem);
          const auto& iGeo = iElem.geometry();
          const auto& iGeoCenter = iGeo.center();
          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;
    
          //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);
    
          // === coupling entries ===
    
          //lambda to evaluate the coupling terms
          // int_iElem beta * phi_iElem,0 * phi_iElem,j ds
          //and
          // int_iElem K_perp * jump(phi_elem1,0) * jump(phi_elem2,i) ds
          //and
          // int_iElem beta * avg(phi_elem1,0) * avg(phi_elem2,i) ds
          //and
          // -int_iElem beta * phi_iElem,0 * avg(phi_elem,i) ds
          //and
          // -int_iElem beta * phi_iElem,j * avg(phi_elem,0) ds
          //for i = 0,...,dim and j = 0,...,dim-1
          //and for elem in {elemIn, elemOut} using a quadrature rule
          //TODO symmetrize more efficiently
          const auto lambda_Kperp =
            [&](const Coordinate& qpGlobal, const Scalar weight)
            {
              const Scalar betaEvaluation = beta(qpGlobal);
              const Scalar KEvaluation = Base::problem_.Kperp(qpGlobal);
    
              const Scalar bulkUpdate1 =
                weight * (KEvaluation + 0.25 * betaEvaluation);
              const Scalar bulkUpdate2 =
                weight * (-KEvaluation + 0.25 * betaEvaluation);
              const Scalar couplingUpdate1 = 0.5 * weight * betaEvaluation;
    
              //quadrature for
              // int_iElem beta * phi_iElem,0 * phi_iElem,0 ds
              Base::A->entry(iElemIdxSLE, iElemIdxSLE) += weight * betaEvaluation;
    
              //quadrature for
              // int_iElem K_perp * jump(phi_elem1,0) * jump(phi_elem2,i) ds
              //and
              // int_iElem beta * avg(phi_elem1,0) * avg(phi_elem2,0) ds
              //for elem1, elem2 in {elemIn, elemOut}
              Base::A->entry(elemInIdxSLE, elemInIdxSLE) += bulkUpdate1;
              Base::A->entry(elemOutIdxSLE, elemOutIdxSLE) += bulkUpdate1;
              Base::A->entry(elemInIdxSLE, elemOutIdxSLE) += bulkUpdate2;
              Base::A->entry(elemOutIdxSLE, elemInIdxSLE) += bulkUpdate2;
    
              //quadrature for
              // -int_iElem beta * phi_iElem,0 * avg(phi_elem,j) ds
              //for elem in {elemIn, elemOut}
              Base::A->entry(elemInIdxSLE, iElemIdxSLE) -= couplingUpdate1;
              Base::A->entry(iElemIdxSLE, elemInIdxSLE) -= couplingUpdate1;
              Base::A->entry(elemOutIdxSLE, iElemIdxSLE) -= couplingUpdate1;
              Base::A->entry(iElemIdxSLE, elemOutIdxSLE) -= couplingUpdate1;
    
              for (int i = 0; i < dim; i++)
              {
                const Scalar bulkUpdate3 = qpGlobal[i] * bulkUpdate1;
                const Scalar bulkUpdate4 = qpGlobal[i] * bulkUpdate2;
                const Scalar couplingUpdate2 = qpGlobal[i] * couplingUpdate1;
    
                if (i != dim - 1)
                {
                  const Scalar interfaceUpdate =
                    weight * betaEvaluation * (iFrame[i] * qpGlobal);
                  const Scalar couplingUpdate3 =
                    (iFrame[i] * qpGlobal) * couplingUpdate1;
    
                  //quadrature for
                  // int_iElem beta * phi_iElem,0 * phi_iElem,i ds
                  Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE) += interfaceUpdate;
                  Base::A->entry(iElemIdxSLE, iElemIdxSLE + i + 1) += interfaceUpdate;
    
                  //quadrature for
                  // -int_iElem beta * phi_iElem,i * avg(phi_elem,0) ds
                  //for elem in {elemIn, elemOut} using a quadrature rule
                  Base::A->entry(elemInIdxSLE, iElemIdxSLE + i + 1) -= couplingUpdate3;
                  Base::A->entry(iElemIdxSLE + i + 1, elemInIdxSLE) -= couplingUpdate3;
                  Base::A->entry(elemOutIdxSLE, iElemIdxSLE + i + 1) -= couplingUpdate3;
                  Base::A->entry(iElemIdxSLE + i + 1, elemOutIdxSLE) -= couplingUpdate3;
                }
    
                //quadrature for
                // int_iElem K_perp * 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}
                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;
    
                //quadrature for
                // -int_iElem beta * phi_iElem,0 * avg(phi_elem,i) ds
                //for elem in {elemIn, elemOut}
                Base::A->entry(elemInIdxSLE + i + 1, iElemIdxSLE) -= couplingUpdate2;
                Base::A->entry(iElemIdxSLE, elemInIdxSLE + i + 1) -= couplingUpdate2;
                Base::A->entry(elemOutIdxSLE + i + 1, iElemIdxSLE) -= couplingUpdate2;
                Base::A->entry(iElemIdxSLE, elemOutIdxSLE + i + 1) -= couplingUpdate2;
              }
            };
    
          //lambda to evaluate the coupling terms
          // int_iElem beta * phi_iElem,i * phi_iElem,j ds
          //and
          // int_iElem K_perp * jump(phi_elem1,k) * jump(phi_elem2,l) ds
          //and
          // int_iElem beta * avg(phi_elem1,k) * avg(phi_elem2,l) ds
          //and
          // -int_iElem beta * phi_iElem,i * avg(phi_elem1,k) ds
          //for i,j = 1,...,dim-1 and k,l = 1,...,dim
          //and for elem1,elem2 in {elemIn, elemOut} using a quadrature rule
          //TODO symmetrize more efficiently
          const auto lambda_KperpPlus1 =
            [&](const Coordinate& qpGlobal, const Scalar weight)
            {
              const Scalar betaEvaluation = beta(qpGlobal);
              const Scalar KEvaluation = Base::problem_.Kperp(qpGlobal);
    
              const Scalar bulkUpdate1 =
                weight * (KEvaluation + 0.25 * betaEvaluation);
              const Scalar bulkUpdate2 =
                weight * (-KEvaluation + 0.25 * betaEvaluation);
    
              for (int i = 0; i < dim; i++)
              {
                const Scalar qpI = iFrame[i] * qpGlobal;
                const Scalar interfaceUpdate1 = weight * betaEvaluation * qpI;
                const Scalar couplingUpdate1 =
                  0.5 * weight * betaEvaluation * qpGlobal[i];
                const Scalar couplingUpdate2 =
                  (iFrame[i] * qpGlobal) * couplingUpdate1;
                const Scalar bulkUpdate3 = qpGlobal[i] * qpGlobal[i] * bulkUpdate1;
                const Scalar bulkUpdate4 = qpGlobal[i] * qpGlobal[i] * bulkUpdate2;
    
                if (i != dim - 1)
                {
                  //quadrature for
                  // int_iElem beta * (phi_iElem,i)^2 ds
                  Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + i + 1) +=
                    interfaceUpdate1 * qpI;
    
                  //quadrature for
                  // -int_iElem beta * phi_iElem,i * avg(phi_elem,i) ds
                  //for elem in {elemIn, elemOut}
                  Base::A->entry(elemInIdxSLE + i + 1, iElemIdxSLE + i + 1) -=
                    couplingUpdate2;
                  Base::A->entry(iElemIdxSLE + i + 1, elemInIdxSLE + i + 1) -=
                    couplingUpdate2;
                  Base::A->entry(elemOutIdxSLE + i + 1, iElemIdxSLE + i + 1) -=
                    couplingUpdate2;
                  Base::A->entry(iElemIdxSLE + i + 1, elemOutIdxSLE + i + 1) -=
                    couplingUpdate2;
                }
    
                //quadrature for
                // int_iElem K_perp * jump(phi_elem1,i) * jump(phi_elem2,i) ds
                //and
                // int_iElem beta * avg(phi_elem1,i) * avg(phi_elem2,i) ds
                //for elem1, elem2 in {elemIn, elemOut}
                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 couplingUpdate3 =
                    (iFrame[j] * qpGlobal) * couplingUpdate1;
                  const Scalar bulkUpdate5 =
                    qpGlobal[i] * qpGlobal[j] * bulkUpdate1;
                  const Scalar bulkUpdate6 =
                    qpGlobal[i] * qpGlobal[j] * bulkUpdate2;
    
                  if (i != dim - 1)
                  {
                    const Scalar interfaceUpdate2 =
                      interfaceUpdate1 * (iFrame[j] * qpGlobal);
                    const Scalar couplingUpdate4 =
                      0.5 * weight * betaEvaluation * qpGlobal[j] * qpI;
    
                    //quadrature for
                    // int_iElem beta * phi_iElem,i * phi_iElem,j ds
                    Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + j + 1) +=
                      interfaceUpdate2;
                    Base::A->entry(iElemIdxSLE + j + 1, iElemIdxSLE + i + 1) +=
                      interfaceUpdate2;
    
                    //quadrature for
                    // -int_iElem beta * phi_iElem,i * avg(phi_elem,j) ds
                    //for elem in {elemIn, elemOut}
                    Base::A->entry(elemInIdxSLE + j + 1, iElemIdxSLE + i + 1) -=
                      couplingUpdate4;
                    Base::A->entry(iElemIdxSLE + i + 1, elemInIdxSLE + j + 1) -=
                      couplingUpdate4;
                    Base::A->entry(elemOutIdxSLE + j + 1, iElemIdxSLE + i + 1) -=
                      couplingUpdate4;
                    Base::A->entry(iElemIdxSLE + i + 1, elemOutIdxSLE + j + 1) -=
                      couplingUpdate4;
                  }
    
                  //quadrature for
                  // int_iElem K_perp * 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}
                  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;
    
                  //quadrature for
                  // -int_iElem beta * phi_iElem,j * avg(phi_elem,i) ds
                  //for elem in {elemIn, elemOut}
                  Base::A->entry(elemInIdxSLE + i + 1, iElemIdxSLE + j + 1) -=
                    couplingUpdate3;
                  Base::A->entry(iElemIdxSLE + j + 1, elemInIdxSLE + i + 1) -=
                    couplingUpdate3;
                  Base::A->entry(elemOutIdxSLE + i + 1, iElemIdxSLE + j + 1) -=
                    couplingUpdate3;
                  Base::A->entry(iElemIdxSLE + j + 1, elemOutIdxSLE + i + 1) -=
                    couplingUpdate3;
                }
              }
            };
    
          // === interface entries ===
    
          //lambda to fill the load vector b with interface entries using a
          //quadrature rule
          const auto lambda_qInterface =
            [&](const Coordinate& qpGlobal, const Scalar weight)
            {
              const Scalar
                qInterfaceEvaluation(Base::problem_.qInterface(qpGlobal));
    
              //quadrature for int_elem q*phi_elem,0 dV
              Base::b[iElemIdxSLE] += weight * qInterfaceEvaluation;
    
              //quadrature for int_elem q*phi_elem,i dV
              for (int i = 0; i < dim - 1; i++)
              {
                Base::b[iElemIdxSLE + i + 1] +=
                  weight * (qpGlobal * iFrame[i]) * qInterfaceEvaluation;
              }
            };
    
          //lambda to evaluate
          // int_iElem (Kparallel * grad(d phi_iElem,i)) * grad(d phi_iElem,j) ds
          //using a quadrature rule for i,j = 0,...,dim-1
          const auto lambda_Kpar =
            [&](const Coordinate& qpGlobal, const Scalar weight)
            {
              const CoordinateMatrix KEvaluation =
                Base::problem_.Kparallel(qpGlobal);
              const Scalar dEvaluation = Base::problem_.aperture(qpGlobal);
              const Scalar dEvaluation2 = dEvaluation * dEvaluation;
              const Coordinate gradDEvaluation =
                Base::problem_.gradAperture(qpGlobal);
    
              Coordinate KparDotTau_i; //storage for Kparallel * iFrame[i]
              Coordinate KparDotGradD; //storage for Kparallel * grad(d)
    
              KEvaluation.mv(gradDEvaluation, KparDotGradD);
    
              const Scalar interfaceUpdate1 = KparDotGradD * gradDEvaluation;
    
              //quadrature for
              // int_iElem (Kpar * grad(d * phi_iElem,0)) * grad(d * phi,iElem,0) ds
              Base::A->entry(iElemIdxSLE, iElemIdxSLE) += weight * interfaceUpdate1;
    
              for (int i = 0; i < dim - 1 ; i++)
              {
                KEvaluation.mv(iFrame[i], KparDotTau_i);
    
                const Scalar qpI = qpGlobal * iFrame[i];
                const Scalar interfaceUpdate2 =
                  dEvaluation * (KparDotGradD * iFrame[i]);
                const Scalar interfaceUpdate3 =
                  weight * (qpI * interfaceUpdate1 + interfaceUpdate2);
    
                //quadrature for
                // int_iElem (Kpar * grad(d * phi_iElem,i))
                //    * grad(d * phi,iElem,0) ds
                Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE) += interfaceUpdate3;
                Base::A->entry(iElemIdxSLE, iElemIdxSLE + i + 1) += interfaceUpdate3;
    
                //quadrature for
                // int_iElem (Kpar * grad(d * phi_iElem,i))
                //    * grad(d * phi,iElem,i) ds
                Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + i + 1) +=
                  weight * ( dEvaluation2 * (KparDotTau_i * iFrame[i])
                  + qpI * (qpI * interfaceUpdate1 + 2.0 * interfaceUpdate2) );
    
                for (int j = 0; j < i; j++)
                {
                  const Scalar interfaceUpdate4 =
                    weight * ( dEvaluation2 * (KparDotTau_i * iFrame[j])
                    + dEvaluation * qpI * (KparDotGradD * iFrame[j])
                    + (qpGlobal * iFrame[j])
                    * (interfaceUpdate2 + qpI * interfaceUpdate1) );
    
                  //quadrature for
                  // int_iElem (Kpar * grad(d * phi_iElem,i))
                  //    * grad(d * phi,iElem,j) ds
                  Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + j + 1) +=
                    interfaceUpdate4;
                  Base::A->entry(iElemIdxSLE + j + 1, iElemIdxSLE + i + 1) +=
                    interfaceUpdate4;
                }
              }
            };
    
          //apply quadrature rules on iElem
          Base::applyQuadratureRule(iGeo, rule_Kperp, lambda_Kperp);
          Base::applyQuadratureRule(iGeo, rule_KperpPlus1, lambda_KperpPlus1);
          Base::applyQuadratureRule(iGeo, rule_qInterface, lambda_qInterface);
          Base::applyQuadratureRule(iGeo, rule_Kpar, lambda_Kpar);
    
          //iterate over all intersection with the boundary of iElem
          for (const auto& intersct : intersections(iGridView_, iElem))
          {
            const auto& normal = intersct.centerUnitOuterNormal();
            const auto& intersctGeo = intersct.geometry();
    
            //determine penalty parameter
            const Scalar mu = getPenaltyParameter(mu0, intersct, iGeoCenter);
    
            if (intersct.neighbor()) //interior interface edge
            {
              //index of the neighboring element
              const int neighborIdx = iMapper_.index(intersct.outside());
    
              //lambda to evaluate the interface terms
              // int_intersct mu * d^2 * jump(phi_iElem1,i) * jump(phi_iElem2,j) dr
              //and
              // -int_intersct d * avg(Kparallel * grad(d * phi_iElem1,i))
              //    * jump(phi_iElem2,j) dr
              //for iElem1, iElem2 in {iElem, neighbor} and for i,j = 0,...,dim-1
              //using a quadrature rule
              const auto lambda_Kpar_iFacet =
                [&](const Coordinate& qpGlobal, const Scalar weight)
                {
                  const Scalar dEvaluation = Base::problem_.aperture(qpGlobal);
                  const Scalar dEvaluation2 = dEvaluation * dEvaluation;
                  const CoordinateMatrix KEvaluation =
                    Base::problem_.Kparallel(qpGlobal);
    
                  Coordinate KparDotTau_i; //storage for Kparallel * iFrame[i]
                  Coordinate KparDotTau_j; //storage for Kparallel * iFrame[j]
                  Coordinate KparDotGradD; //storage for Kparallel * grad(d)
    
                  KEvaluation.mv(Base::problem_.gradAperture(qpGlobal),
                    KparDotGradD);
    
                  const Scalar interfaceUpdate1 = KparDotGradD * normal;
    
                  //quadrature for
                  // int_intersct mu * d^2 * jump(phi_iElem,0)
                  //    * jump(phi_iElem,0) dr
                  Base::A->entry(iElemIdxSLE, iElemIdxSLE) +=
                    weight * dEvaluation * (mu * dEvaluation - interfaceUpdate1);
    
                  for (int i = 0; i < dim - 1; i++)
                  {
                    KEvaluation.mv(iFrame[i], KparDotTau_i);
    
                    const Scalar qpI = qpGlobal * iFrame[i];
                    const Scalar interfaceUpdate2 = mu * dEvaluation * qpI;
                    const Scalar interfaceUpdate3 =
                      dEvaluation * (KparDotTau_i * normal);
                    const Scalar interfaceUpdate4 = weight * dEvaluation
                      * (interfaceUpdate2 - 0.5 * interfaceUpdate3
                      - qpI * interfaceUpdate1);
    
                    //quadrature for
                    // int_intersct mu * jump(phi_iElem,0) * jump(phi_iElem,i) dr
                    //and
                    // -int_intersct avg(Kparallel * grad(d * phi_iElem,i))
                    //    * jump(phi_iElem,0) dr
                    Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE) +=
                      interfaceUpdate4;
                    Base::A->entry(iElemIdxSLE, iElemIdxSLE + i + 1) +=
                      interfaceUpdate4;
    
                    //quadrature for
                    // int_intersct mu * jump(phi_iElem,i)^2 dr
                    //and
                    // -int_intersct avg(Kparallel * grad(d * phi_iElem,i))
                    //    * jump(phi_iElem,i) dr
                    Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + i + 1) +=
                      weight * dEvaluation * qpI * (interfaceUpdate2
                      - interfaceUpdate3 - qpI * interfaceUpdate1);
    
                    for (int j = 0; j < i; j++)
                    {
                      KEvaluation.mv(iFrame[j], KparDotTau_j);
    
                      const Scalar interfaceUpdate5 = weight * dEvaluation *
                        ( (qpGlobal * iFrame[j]) * ( interfaceUpdate2
                        - 0.5 * interfaceUpdate3 - qpI * interfaceUpdate1 )
                        - 0.5 * (KparDotTau_j * normal) * qpI * dEvaluation );
    
                      //quadrature for
                      // int_intersct mu * jump(phi_iElem,i) * jump(phi_iElem,j) dr
                      //and
                      // -int_intersct avg(Kparallel * grad(d * phi_iElem,i))
                      //    * jump(phi_iElem,j) dr
                      Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + j + 1) +=
                        interfaceUpdate5;
                      Base::A->entry(iElemIdxSLE + j + 1, iElemIdxSLE + i + 1) +=
                        interfaceUpdate5;
                    }
                  }
    
                  if (neighborIdx > iElemIdx)
                  { //make sure that each interface edge is considered only once
                    return;
                  }
    
                  const int neighborIdxSLE =
                    (dim + 1) * Base::gridView_.size(0) + dim * neighborIdx;
                  const InterfaceBasis neighborFrame =
                    interfaceFrame(Base::gridView_.grid().asIntersection(
                      intersct.outside()).centerUnitOuterNormal());
    
                  //storage for K_par * neighborFrame[i]
                  Coordinate KparDotNeighborTau_i;
    
                  //quadrature for
                  // int_intersct mu * d^2 * jump(phi_iElem,0)
                  //    * jump(phi_neighbor,0) dr
                  Base::A->entry(iElemIdxSLE, neighborIdxSLE) -=
                    weight * mu * dEvaluation2;
                  Base::A->entry(neighborIdxSLE, iElemIdxSLE) -=
                    weight * mu * dEvaluation2;
    
                  for (int i = 0; i < dim - 1; i++)
                  {
                    KEvaluation.mv(iFrame[i], KparDotTau_i);
                    KEvaluation.mv(neighborFrame[i], KparDotNeighborTau_i);
    
                    const Scalar qpI = qpGlobal * iFrame[i];
                    const Scalar neighborQpI = qpGlobal * neighborFrame[i];
                    const Scalar interfaceUpdate1a = mu * qpI;
                    const Scalar interfaceUpdate1b = mu * neighborQpI;
                    const Scalar interfaceUpdate2a = KparDotTau_i * normal;
                    const Scalar interfaceUpdate2b = KparDotNeighborTau_i * normal;
                    const Scalar interfaceUpdate3a = weight * dEvaluation2 *
                      (-interfaceUpdate1a + 0.5 * interfaceUpdate2a);
                    const Scalar interfaceUpdate3b = weight * dEvaluation2 *
                      (-interfaceUpdate1b - 0.5 * interfaceUpdate2b);
    
                    //quadrature for
                    // int_intersct mu * d^2 * jump(phi_iElem,i)
                    //    * jump(phi_neighbor,0) dr
                    //and
                    // -int_intersct d * avg(Kparallel * grad(d * phi_iElem,i))
                    //    * jump(phi_neighbor,0) dr
                    Base::A->entry(iElemIdxSLE + i + 1, neighborIdxSLE) +=
                      interfaceUpdate3a;
                    Base::A->entry(neighborIdxSLE, iElemIdxSLE + i + 1) +=
                      interfaceUpdate3a;
    
                    //quadrature for
                    // int_intersct mu * d^2 * jump(phi_iElem,0)
                    //    * jump(phi_neighbor,i) dr
                    //and.
                    // -int_intersct d * avg(Kparallel * grad(d * phi_neighbor,i))
                    //    * jump(phi_iElem,0) dr
                    Base::A->entry(neighborIdxSLE + i + 1, iElemIdxSLE) +=
                      interfaceUpdate3b;
                    Base::A->entry(iElemIdxSLE, neighborIdxSLE + i + 1) +=
                      interfaceUpdate3b;
    
                    for (int j = 0; j < dim - 1; j++)
                    {
                      Coordinate KparDotNeighborTau_j;
    
                      KEvaluation.mv(neighborFrame[j], KparDotNeighborTau_j);
    
                      const Scalar interfaceUpdate5 = -weight * dEvaluation2
                        * ((qpGlobal * neighborFrame[j]) * interfaceUpdate1a
                        + 0.5 * (-interfaceUpdate2a * (qpGlobal * neighborFrame[j])
                        + (KparDotNeighborTau_j * normal) * qpI));
    
                      //quadrature for
                      // int_intersct mu * d^2 * jump(phi_iElem,i)
                      //    * jump(phi_neighbor,j) dr
                      //and
                      // -int_intersct d * avg(Kparallel * grad(d * phi_iElem,i))
                      //    * jump(phi_neighbor,j) dr
                      //resp.
                      // -int_intersct d * avg(Kparallel * grad(d * phi_neighbor,i))
                      //    * jump(phi_iElem,j) dr
                      Base::A->entry(iElemIdxSLE + i + 1, neighborIdxSLE + j + 1) +=
                        interfaceUpdate5;
                      Base::A->entry(neighborIdxSLE + j + 1, iElemIdxSLE + i + 1) +=
                        interfaceUpdate5;
                    }
                  }
                };
    
              Base::applyQuadratureRule(intersctGeo, rule_Kpar_iFacet,
                lambda_Kpar_iFacet);
            }
            else //boundary interface edge
            {
              //lambda to evaluate the interface terms on the boundary
              // int_intersct mu * d^2 * phi_iElem,i * phi_iElem,j dr
              //and
              // -int_intersct d * (Kparallel * grad(d * phi_iElem,i))
              //    * phi_iElem,j * normal dr
              //for i,j = 0,...,dim-1 using a quadrature rule
              const auto lambda_Kpar_iFacet_boundary =
                [&](const Coordinate& qpGlobal, const Scalar weight)
                {
                  const Scalar dEvaluation = Base::problem_.aperture(qpGlobal);
                  const CoordinateMatrix KEvaluation =
                    Base::problem_.Kparallel(qpGlobal);
    
                  Coordinate KparDotTau_i; //storage for K_parallel * iFrame[i]
                  Coordinate KparDotTau_j; //storage for K_parallel * iFrame[j]
                  Coordinate KparDotGradD; //storage for K_parallel * grad(d)
    
                  KEvaluation.mv(Base::problem_.gradAperture(qpGlobal),
                    KparDotGradD);
    
                  const Scalar interfaceUpdate1 = 2.0 * (KparDotGradD * normal);
    
                  //quadrature for
                  // int_intersct mu * d^2 * jump(phi_iElem,0)
                  //    * jump(phi_iElem,0) dr
                  Base::A->entry(iElemIdxSLE, iElemIdxSLE) +=
                    weight * dEvaluation * (mu * dEvaluation - interfaceUpdate1);
    
                  for (int i = 0; i < dim - 1; i++)
                  {
                    KEvaluation.mv(iFrame[i], KparDotTau_i);
    
                    const Scalar qpI = qpGlobal * iFrame[i];
                    const Scalar interfaceUpdate2 = mu * dEvaluation * qpI;
                    const Scalar interfaceUpdate3 =
                      dEvaluation * (KparDotTau_i * normal);
                    const Scalar interfaceUpdate4 = weight * dEvaluation *
                      (interfaceUpdate2 - interfaceUpdate3
                      - qpI * interfaceUpdate1);
    
                    //quadrature for
                    // int_intersct mu * d^2 * phi_iElem,0 * phi_iElem,i dr
                    //and
                    // -int_intersct d * (Kparallel * grad(d * phi_iElem,i))
                    //    * phi_iElem,0 * normal dr
                    Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE) +=
                      interfaceUpdate4;
                    Base::A->entry(iElemIdxSLE, iElemIdxSLE + i + 1) +=
                      interfaceUpdate4;
    
                    //quadrature for
                    // int_intersct mu * d^2 * (phi_iElem,i)^2 dr
                    //and
                    // -int_intersct d * (Kparallel * grad(d * phi_iElem,i))
                    //    * phi_iElem,i * normal dr
                    Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + i + 1) +=
                      weight * qpI * dEvaluation *
                      (interfaceUpdate2 - 2.0 * interfaceUpdate3
                      - qpI * interfaceUpdate1);
    
                    for (int j = 0; j < i; j++)
                    {
                      KEvaluation.mv(iFrame[j], KparDotTau_j);
    
                      const Scalar qpJ = qpGlobal * iFrame[j];
                      const Scalar interfaceUpdate5 =
                        weight * dEvaluation * (qpJ * (interfaceUpdate2
                        - interfaceUpdate3 - qpI * interfaceUpdate1)
                        - (KparDotTau_j * normal) * qpI * dEvaluation);
    
                      //quadrature for
                      // int_intersct mu * d^2 * phi_iElem,i * phi_iElem,j dr
                      //and
                      // -int_intersct d * (Kparallel * grad(d * phi_iElem,i))
                      //    * phi_iElem,j * normal dr
                      Base::A->entry(iElemIdxSLE + i + 1, iElemIdxSLE + j + 1) +=
                        interfaceUpdate5;
                      Base::A->entry(iElemIdxSLE + j + 1, iElemIdxSLE + i + 1) +=
                        interfaceUpdate5;
                    }
                  }
                };
    
              //lambda to evaluate the interface boundary terms of the load vector b
              // int_intersct mu * d^2 * g^Gamma * phi_iElem,i dr
              //and
              // int_intersct d * g^Gamma * K_parallel * grad(d * phi_iElem,i) dr
              //for i = 0,...,dim-1 using a quadrature rule
              const auto lambda_interfaceBoundary =
                [&](const Coordinate& qpGlobal, const Scalar weight)
                {
                  const Scalar dEvaluation = Base::problem_.aperture(qpGlobal);
                  const CoordinateMatrix KEvaluation =
                    Base::problem_.Kparallel(qpGlobal);
    
                  Coordinate KparDotTau_i; //storage for K_parallel * iFrame[i]
                  Coordinate KparDotGradD; //storage for K_parallel * grad(d)
    
                  KEvaluation.mv(Base::problem_.gradAperture(qpGlobal),
                    KparDotGradD);
    
                  const Scalar interfaceUpdate1 = weight * dEvaluation *
                    Base::problem_.interfaceBoundary(qpGlobal);
                  const Scalar interfaceUpdate2 =
                    mu * dEvaluation - KparDotGradD * normal;
    
                  Base::b[iElemIdxSLE] += interfaceUpdate1 * interfaceUpdate2;
    
                  for (int i = 0; i < dim - 1; i++)
                  {
                    KEvaluation.mv(iFrame[i], KparDotTau_i);
    
                    Base::b[iElemIdxSLE + i + 1] +=
                      interfaceUpdate1 * ((qpGlobal * iFrame[i]) * interfaceUpdate2
                      - dEvaluation * (KparDotTau_i * normal));
                  }
                };
    
              //apply quadrature rules on the boundary interface facet intersct
              Base::applyQuadratureRule(intersctGeo, rule_Kpar_iFacet,
                lambda_Kpar_iFacet_boundary);
              Base::applyQuadratureRule(intersctGeo, rule_interfaceBoundary,
                lambda_interfaceBoundary);
            }
          }
        }
      }
    
    //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; 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 iElemIdxOutput = dim * iMapper_.index(iElem);
          const int iElemIdxSLE = bulkDOF + iElemIdxOutput;
          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[iElemIdxOutput + 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[iElemIdxOutput + 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[iElemIdxOutput + 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");
      }
    
      //returns the interface penalty parameter mu^Gamma for an interface facet
      //given as intersection of the interface grid where center is the center of
      //intersection.inside()
      const Scalar getPenaltyParameter (const Scalar mu0, const auto& intersection,
        const auto& center) const
      {
        Scalar mu = Base::getLargestEigenvalue(Base::problem_.Kparallel(center))
          / Base::getDiameter(intersection.inside());
    
        if (intersection.neighbor())
        {
          const auto& neighbor = intersection.outside();
          mu = std::max(mu, Base::getLargestEigenvalue(
            Base::problem_.Kparallel(neighbor.geometry().center()))
            / Base::getDiameter(neighbor));
        }
    
        return mu * mu0 * 2.0 * (1.0 + dim);
      }
    
    };
    
    #endif