Skip to content
Snippets Groups Projects
dg.hh 20.06 KiB
#ifndef DUNE_MMDG_DG_HH
#define DUNE_MMDG_DG_HH

#include <cmath>

#include <dune/common/dynmatrix.hh>
#include <dune/common/dynvector.hh>

#include <dune/grid/common/mcmgmapper.hh>
#include <dune/grid/io/file/vtk.hh>

#include <dune/geometry/quadraturerules.hh>

#include <dune/istl/bcrsmatrix.hh>
#include <dune/istl/solver.hh>
#include <dune/istl/umfpack.hh>

#include <dune/mmdg/nonconformingp1vtkfunction.hh>

template<class GridView, class Mapper, class Problem>
class DG
{
public:
  static constexpr int dim = GridView::dimension;
  static constexpr auto simplex = Dune::GeometryTypes::simplex(dim);
  static constexpr auto edge = Dune::GeometryTypes::simplex(dim-1);

  using Scalar = typename GridView::ctype;
  // using Matrix = Dune::BCRSMatrix<Dune::FieldMatrix<Scalar,1,1>>;
  // using Vector = Dune::BlockVector<Dune::FieldVector<Scalar,1>>;

  using Matrix = Dune::DynamicMatrix<Scalar>;
  using Vector = Dune::DynamicVector<Scalar>;

  using VTKFunction = Dune::VTKFunction<GridView>;
  using P1Function = Dune::NonconformingP1VTKFunction<GridView,
    Dune::DynamicVector<Scalar>>;
  using QRuleElem = Dune::QuadratureRule<Scalar, dim>;
  using QRulesElem = Dune::QuadratureRules<Scalar, dim>;
  using QRuleEdge = Dune::QuadratureRule<Scalar, dim-1>;
  using QRulesEdge = Dune::QuadratureRules<Scalar, dim-1>;

  //constructor
  DG (const GridView& gridView, const Mapper& mapper, const Problem& problem) :
    gridView_(gridView), mapper_(mapper), problem_(problem),
    dof_((1 + dim) * gridView.size(0))
    {

      A = std::make_shared<Matrix>(dof_, dof_, 0.0); //stiffness matrix
      b = Vector(dof_, 0.0); //load vector
      d = Vector(dof_, 0.0); //solution vector

      //initialize stiffness matrix A, load vector b and solution vector d
      // A = std::make_shared<Matrix>(dof_, dof_, 10, 1, Matrix::implicit); //TODO
      // b = Vector(dof_);
      // d = Vector(dof_);
    }

  void operator() (const Scalar mu)
  {
    //assemble stiffness matrix A and load vector b
    assembleSLE(mu);
    //A->compress();

    (*A).solve(d, b);

    for (int i = 0; i < dof_; i++)
      for (int j = 0; j < i; j++)
        /*assert*/if(std::abs((*A)[i][j] - (*A)[j][i]) >
          std::numeric_limits<Scalar>::epsilon())
            std::cout << i << ", " << j << std::endl;

    // Dune::InverseOperatorResult result;
    // Dune::UMFPack<Matrix> solver(*A);
    // solver.apply(d, b, result);

    //write solution to a vtk file
    writeVTKoutput();
  }

  //compute L2 error using a quadrature rule
  Scalar computeL2error(const int quadratureOrder = 5) const
  {
    if (!problem_.hasExactSolution())
    {
      DUNE_THROW(Dune::Exception, "ERROR: Problem has no exact solution or "
        "exact solution is not implemented");
    }

    Scalar error(0.0);

    for (const auto& elem : elements(gridView_))
    {
      const int elemIdxSLE = (dim + 1) * mapper_.index(elem);
      const auto& geo = elem.geometry();
      const QRuleElem& qrule = QRulesElem::rule(simplex, quadratureOrder);

      //determine L2-error on element elem using a quadrature rule
      for (const auto& ip : qrule)
      {
        const auto& qpLocal = ip.position();
        const auto& qpGlobal = geo.global(qpLocal);

        const Scalar quadratureFactor =
          ip.weight() * geo.integrationElement(qpLocal);

        Scalar numericalSolutionAtQP = d[elemIdxSLE];

        for (int i = 0; i < dim; i++)
        {
          numericalSolutionAtQP += d[elemIdxSLE + i + 1] * qpGlobal[i];
        }

        const Scalar errorEvaluation =
          numericalSolutionAtQP - problem_.exactSolution(qpGlobal);

        error += quadratureFactor * errorEvaluation * errorEvaluation;
      }
    }

    return std::sqrt(error);
  }

  const GridView& gridView_;
  const Mapper& mapper_; //element mapper for gridView_
  const Problem& problem_; //the DG problem to be solved

  const int dof_; //degrees of freedom

protected:
  DG (const GridView& gridView, const Mapper& mapper, const Problem& problem,
    const int dof) :
    gridView_(gridView), mapper_(mapper), problem_(problem), dof_(dof)
    {
      A = std::make_shared<Matrix>(dof_, dof_, 0.0); //stiffness matrix
      b = Vector(dof_, 0.0); //load vector
      d = Vector(dof_, 0.0); //solution vector

      //initialize stiffness matrix A, load vector b and solution vector d
      // A = std::make_shared<Matrix>(dof_, dof_, 10, 1, Matrix::implicit); //TODO
      // b = Vector(dof_);
      // d = Vector(dof_);
    }

  //assemble stiffness matrix A and load vector b
  void assembleSLE (const Scalar mu)
  {
    //quadrature rules
    const QRuleElem& rule_q =
      QRulesElem::rule(simplex, problem_.quadratureOrder_q());
    const QRuleElem& rule_K_Elem =
      QRulesElem::rule(simplex, problem_.quadratureOrder_K());
    const QRuleEdge& rule_K_Edge =
      QRulesEdge::rule(edge, problem_.quadratureOrder_K());
    const QRuleEdge& rule_Kplus1_Edge =
      QRulesEdge::rule(edge, problem_.quadratureOrder_K() + 1);
    const QRuleEdge& rule_2ndOrder = QRulesEdge::rule(edge, 2);
    const QRuleEdge& rule_boundary =
      QRulesEdge::rule(edge, problem_.quadratureOrderBoundary());

    //we use the basis
    // phi_elem,0 (x) = indicator(elem);
    // phi_elem,i (x) = x[i]*indicator(elem);
    //for elem in elements(gridView) and i = 1,...,dim
    for (const auto& elem : elements(gridView_))
    {
      const int elemIdx = mapper_.index(elem);
      const auto& geo = elem.geometry();

      //in the system of linear equations (SLE) Ad = b,
      //the index elemIdxSLE refers to the basis function phi_elem,0
      //and the indices elemIdxSLE + i + 1, i = 1,...,dim, refer to the
      //basis function phi_elem,i
      const int elemIdxSLE = (dim + 1)*elemIdx;

      //fill load vector b using a quadrature rule
      for (const auto& ip : rule_q)
      {
        //quadrature point in local and global coordinates
        const auto& qpLocal = ip.position();
        const auto& qpGlobal = geo.global(qpLocal);

        const Scalar quadratureFactor =
          ip.weight() * geo.integrationElement(qpLocal);
        const Scalar qEvaluation(problem_.q(qpGlobal));

        //quadrature for int_elem q*phi_elem,0 dV
        b[elemIdxSLE] += quadratureFactor * qEvaluation;

        //quadrature for int_elem q*phi_elem,i dV
        for (int i = 0; i < dim; i++)
        {
          b[elemIdxSLE + i + 1] += quadratureFactor * qpGlobal[i] * qEvaluation;
        }
      }

      //evaluate
      // int_elem K*grad(phi_elem,i)*grad(phi_elem,j) dV
      // = int_elem K[j][i] dV
      //using a quadrature rule
      for (const auto& ip : rule_K_Elem)
      {
        //quadrature point in local and global coordinates
        const auto& qpLocal = ip.position();
        const auto& qpGlobal = geo.global(qpLocal);

        const Scalar quadratureFactor =
          ip.weight() * geo.integrationElement(qpLocal);

        for (int i = 0; i < dim; i++)
        {
          for (int j = 0; j <= i; j++)
          {
            (*A)[elemIdxSLE + i + 1][elemIdxSLE + j + 1] +=
              quadratureFactor * problem_.K(qpGlobal)[j][i];
          }
        }
      }

      //iterate over all intersection with the boundary of elem
      for (const auto& intersct : intersections(gridView_, elem))
      {
        if constexpr (dim != 1)
        {
          if (gridView_.grid().isInterface(intersct))
          {
            continue;
          }
        }

        const auto& normal = intersct.centerUnitOuterNormal();
        const auto& intersctGeo = intersct.geometry();
        const double intersctVol = intersctGeo.volume();
        const auto& intersctCenter = intersctGeo.center();

        //create storage for multiply used integral values
        //linearIntegrals[i] = int_intersct x[i] ds
        Dune::FieldVector<Scalar, dim> linearIntegrals(0.0);

        //quadraticIntregrals[i][j] = int_intersct x[i]*x[j] ds
        //NOTE: is there a better type for a symmetric matrix?
        //note that only the lower diagonal and diagonal entries of
        //quadraticIntregrals are used
        Dune::FieldMatrix<Scalar, dim, dim> quadraticIntregrals(0.0);

        //KIntegrals[i] = int_intersct K[:,i]*normal ds
        Dune::FieldVector<Scalar, dim> KIntegrals(0.0);

        //KIntegralsX[i][j] = int_intersct x[j]*(K[:,i]*normal) ds
        Dune::FieldMatrix<Scalar, dim, dim> KIntegralsX(0.0);

        //fill vector linearIntegrals and matrix quadraticIntregrals
        for (const auto& ip : rule_2ndOrder)
        {
          //quadrature point in local and global coordinates
          const auto& qpLocal = ip.position();
          const auto& qpGlobal = intersctGeo.global(qpLocal);

          const Scalar quadratureFactor =
            ip.weight() * intersctGeo.integrationElement(qpLocal);

          for (int i = 0; i < dim; i++)
          {
            //use midpoint rule for exact evaluation of
            // int_intersct x[i] ds //TODO
            linearIntegrals[i] = intersctVol * intersctCenter[i];

            for (int j = 0; j <= i; j++)
            {
              quadraticIntregrals[i][j] +=
                quadratureFactor * qpGlobal[i] * qpGlobal[j];
              }
            }
          }

          //quadraticIntregrals is symmetric
          for (int i = 0; i < dim; i++)
          {
            for (int j = 0; j < i; j++)
            {
              quadraticIntregrals[j][i] = quadraticIntregrals[i][j];
            }
          }

        //fill vector KIntegrals using a quadrature rule
        for (const auto& ip : rule_K_Edge)
        {
          //quadrature point in local and global coordinates
          const auto& qpLocal = ip.position();
          const auto& qpGlobal = intersctGeo.global(qpLocal);

          const Scalar quadratureFactor =
            ip.weight() * intersctGeo.integrationElement(qpLocal);
          const Dune::FieldMatrix<Scalar, dim, dim> KEvaluation =
            problem_.K(qpGlobal);

          for (int i = 0; i < dim; i++)
          {
            KIntegrals[i] += quadratureFactor * (KEvaluation[i] * normal);
          }
        }

        //fill vector KIntegralsX using a quadrature rule
        for (const auto& ip : rule_Kplus1_Edge)
        {
          //quadrature point in local and global coordinates
          const auto& qpLocal = ip.position();
          const auto& qpGlobal = intersctGeo.global(qpLocal);

          const Scalar quadratureFactor =
            ip.weight() * intersctGeo.integrationElement(qpLocal);
          const Dune::FieldMatrix<Scalar, dim, dim> KEvaluation =
            problem_.K(qpGlobal);

          for (int i = 0; i < dim; i++)
          {
            for (int j = 0; j < dim; j++)
            {
              KIntegralsX[i][j] += quadratureFactor *
                qpGlobal[j] * (KEvaluation[i] * normal);
            }
          }
        }

        //exact evaluation of
        // int_intersct mu*jump(phi_elem,0)*jump(phi_elem,0) ds
        (*A)[elemIdxSLE][elemIdxSLE] += mu * intersctVol;

        if (intersct.neighbor()) //intersct has neighboring element
        {
          //index of the neighboring element
          const int neighborIdx = mapper_.index(intersct.outside());
          const int neighborIdxSLE = (dim + 1) * neighborIdx;

          for (int i = 0; i < dim; i++)
          { //we use the relations
            // int_intersct mu*jump(phi_elem,0)*jump(phi_elem,i) ds
            // = mu * int_intersct x[i] ds
            //and
            // int_intersct avg(K*grad(phi_elem,i))*jump(phi_elem,0) ds
            // = 0.5 * int_intersct K[:,i] * normal ds
            (*A)[elemIdxSLE + i + 1][elemIdxSLE] +=
              mu * linearIntegrals[i] - 0.5 * KIntegrals[i];

            for (int j = 0; j <= i; j++)
            {
              //we use the relations
              // int_intersct mu*jump(phi_elem,i)*jump(phi_elem,j) ds
              // = mu * int_intersct x[i] * x[j] ds
              //and
              // int_intersct avg(K*grad(phi_elem,i))*jump(phi_elem,j) ds
              // = 0.5 * int_intersct x[j] * ( K[:,i] * normal ) ds
              (*A)[elemIdxSLE + i + 1][elemIdxSLE + j + 1]
                += mu * quadraticIntregrals[i][j]
                  - 0.5 * ( KIntegralsX[i][j] + KIntegralsX[j][i] );
            }
          }

          if (neighborIdx > elemIdx)
          { //make sure that each facet is considered only once
            continue;
          }

          //exact evaluation of
          // int_intersct mu * jump(phi_elem,0) * jump(phi_neighbor,0) ds
          (*A)[elemIdxSLE][neighborIdxSLE] += -mu * intersctVol;

          //stiffness matrix A is symmetric
          (*A)[neighborIdxSLE][elemIdxSLE] +=
            (*A)[elemIdxSLE][neighborIdxSLE];

          for (int i = 0; i < dim; i++)
          {
            //we use the relations
            // int_intersct mu * jump(phi_elem,i) * jump(phi_neighbor,0) ds
            // = -mu * int_intersct x[i] ds
            //and
            // int_intersct avg(K*grad(phi_neighbor,i)) * jump(phi_elem,0) ds
            // = -0.5 * int_intersct K[:,i] * normal ds
            (*A)[elemIdxSLE + i + 1][neighborIdxSLE] +=
              -mu * linearIntegrals[i] + 0.5 * KIntegrals[i];

            //we use the relations
            // int_intersct mu * jump(phi_elem,0) * jump(phi_neighbor,i) ds
            // = -mu * int_intersct x[i] ds
            //and
            // int_intersct avg(K*grad(phi_neighbor,i)) * jump(phi_elem,0) ds
            // = 0.5 * int_intersct K[:,i] * normal ds
            (*A)[elemIdxSLE][neighborIdxSLE + i + 1] +=
              -mu * linearIntegrals[i] - 0.5 * KIntegrals[i];

            //stiffness matrix A is symmetric
            (*A)[neighborIdxSLE][elemIdxSLE + i + 1] +=
              (*A)[elemIdxSLE + i + 1][neighborIdxSLE];
            (*A)[neighborIdxSLE + i + 1][elemIdxSLE] +=
              (*A)[elemIdxSLE][neighborIdxSLE + i + 1];

            for (int j = 0; j <= i; j++)
            {
              //we use the relations
              // int_intersct mu * jump(phi_elem,i) * jump(phi_neighbor,j) ds
              // = -mu * int_intersct x[i] * x[j] ds
              //and
              // int_intersct avg(K*grad(phi_neighbor,j)) * jump(phi_elem,i) ds
              // = 0.5 * int_intersct x[i] * ( K[:,j] * normal ) ds
              //as well as
              // int_intersct avg(K*grad(phi_elem,i)) * jump(phi_neighbor,j) ds
              // = -0.5 * int_intersct x[j] * ( K[:,i] * normal ) ds
              (*A)[elemIdxSLE + i + 1][neighborIdxSLE + j + 1] +=
                -mu * quadraticIntregrals[i][j]
                - 0.5 * ( KIntegralsX[j][i] - KIntegralsX[i][j] );

              //stiffness matrix A is symmetric
              (*A)[neighborIdxSLE + j + 1][elemIdxSLE + i + 1] +=
                (*A)[elemIdxSLE + i + 1][neighborIdxSLE + j + 1];

              if (i != j)
              {
                //we use the relations
                // int_intersct mu * jump(phi_elem,j) * jump(phi_neighbor,i) ds
                // = -mu * int_intersct x[i] * x[j] ds
                //and
                // int_intersct avg(K*grad(phi_neighbor,i))*jump(phi_elem,j) ds
                // = 0.5 * int_intersct x[j] * ( K[:,i] * normal ) ds
                //as well as
                // int_intersct avg(K*grad(phi_elem,j))*jump(phi_neighbor,i) ds
                // = -0.5 * int_intersct x[i] * ( K[:,j] * normal ) ds
                (*A)[elemIdxSLE + j + 1][neighborIdxSLE + i + 1] +=
                  -mu * quadraticIntregrals[i][j]
                  - 0.5 * ( KIntegralsX[i][j] - KIntegralsX[j][i] );

                //stiffness matrix A is symmetric
                (*A)[neighborIdxSLE + i + 1][elemIdxSLE + j + 1] +=
                  (*A)[elemIdxSLE + j + 1][neighborIdxSLE + i + 1];
              }
            }
          }
        }
        else //boundary facet
        {
          //fill load vector b using a quadrature rule (boundary terms)
          for (const auto& ip : rule_boundary)
          {
            //quadrature point in local and global coordinates
            const auto& qpLocal = ip.position();
            const auto& qpGlobal = intersctGeo.global(qpLocal);

            const Scalar quadratureFactor =
              ip.weight() * intersctGeo.integrationElement(qpLocal);

            const Scalar boundaryEvaluation(problem_.boundary(qpGlobal));
            const Dune::FieldMatrix<Scalar, dim, dim> KEvaluation =
              problem_.K(qpGlobal);

            //quadrature for int_intersct mu * g * phi_elem,0 ds
            b[elemIdxSLE] += quadratureFactor * mu * boundaryEvaluation;

            //quadrature for
            // int_intersct (mu * phi_elem,i - K[:,i] * normal) * g ds
            //with K*grad(phi_elem,i) = K[:,i]
            for (int i = 0; i < dim; i++)
            {
              b[elemIdxSLE + i + 1] +=
                quadratureFactor * (mu * qpGlobal[i] -
                  (KEvaluation[i] * normal)) * boundaryEvaluation;
            }
          }

          for (int i = 0; i < dim; i++)
          {
            //we evaluate the integrals
            // int_intersct mu * phi_elem,0 * phi_elem,i ds
            //and
            // int_intersct phi_elem,0 * K*grad(phi_elem,i) * normal ds
            //with K*grad(phi_elem,i) = K[:,i]
            (*A)[elemIdxSLE + i + 1][elemIdxSLE] +=
              mu * linearIntegrals[i] - KIntegrals[i];

            for (int j = 0; j <= i; j++)
            {
              //we evaluate the integrals
                // int_intersct mu * phi_elem,i * phi_elem,j ds
                //and
                // int_intersct (phi_elem,i * K[:,j]
                //    + phi_elem,j * K[:,i]) * normal ds
                //with K*grad(phi_elem,i) = K[:,i]
              (*A)[elemIdxSLE + i + 1][elemIdxSLE + j + 1] +=
                mu * quadraticIntregrals[i][j]
                - ( KIntegralsX[i][j] + KIntegralsX[j][i] );
            }
          }
        }
      }

      //stiffness matrix A is symmetric
      for (int i = 0; i < dim; i++)
      {
        (*A)[elemIdxSLE][elemIdxSLE + i + 1] =
          (*A)[elemIdxSLE + i + 1][elemIdxSLE];

        for(int j = 0; j < i; j++)
        {
          (*A)[elemIdxSLE + j + 1][elemIdxSLE + i + 1] =
            (*A)[elemIdxSLE + i + 1][elemIdxSLE + j + 1];
        }
      }
    }
  }

  //writes the solution to a vtk file
  void writeVTKoutput (const int bulkDOF) const
  {
    //storage for pressure data, for each element we store the
    //pressure at the corners of the element, the VTKFunction will
    //create the output using a linear interpolation for each element
    Dune::DynamicVector<Scalar> pressure(bulkDOF, 0.0);
    Dune::DynamicVector<Scalar> exactPressure(bulkDOF, 0.0);

    for (const auto& elem : elements(gridView_))
    {
      const int elemIdxSLE = (dim + 1)*mapper_.index(elem);
      const auto& geo = elem.geometry();

      for (int k = 0; k < geo.corners(); k++)
      {
        if (problem_.hasExactSolution())
        { //evaluation at the corners is slightly shifted towards the center of
          //the element to correctly picture discontinuities of the solution
          exactPressure[elemIdxSLE + k] = problem_.exactSolution(
            0.9999 * geo.corner(k) + 0.0001 * geo.center());
        }

        //contribution of the basis function
        // phi_elem,0 (x) = indicator(elem);
        //at the kth corner of elem
        pressure[elemIdxSLE + k] = d[elemIdxSLE];

        for (int i = 0; i < dim; i++)
        {
          //contribution of the basis function
          // phi_elem,i (x) = x[i]*indicator(elem);
          //at the kth corner of elem
          pressure[elemIdxSLE + k] += d[elemIdxSLE + i + 1] * geo.corner(k)[i];
        }
      }
    }

    //create output directory if necessary
    mkdir("data", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);

    Dune::VTKWriter<GridView> vtkWriter(gridView_, Dune::VTK::nonconforming);
    vtkWriter.addVertexData( std::shared_ptr<const VTKFunction>(
      new P1Function(gridView_, pressure, "pressure")));

    if (problem_.hasExactSolution())
    {
      vtkWriter.addVertexData( std::shared_ptr<const VTKFunction>(
        new P1Function(gridView_, exactPressure, "exactPressure")));
    }
    vtkWriter.pwrite("pressureData", "", "data");
  }

  std::shared_ptr<Matrix> A; //stiffness matrix
  Vector b; //load vector
  Vector d; //solution vector

private:
  //writes the solution to a vtk file
  void writeVTKoutput () const
  {
    writeVTKoutput(dof_);
  }

};

#endif