// Copyright (c) 2010-2024, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.

// Implementation of Surface and Cutcell IntegrationRule(s) classes

#include "fem.hpp"
#include <cmath>

using namespace std;

namespace mfem
{

void CutIntegrationRules::SetOrder(int order)
{
   MFEM_VERIFY(order > 0, "Invalid input");
   Order = order;
}

void CutIntegrationRules::SetLevelSetProjectionOrder(int order)
{
   MFEM_VERIFY(order > 0, "Invalid input");
   lsOrder = order;
}

#ifdef MFEM_USE_LAPACK

void MomentFittingIntRules::InitSurface(int order, Coefficient& levelset,
                                        int lsO, ElementTransformation& Tr)
{
   Init(order, levelset, lsO);
   dim = Tr.GetDimension();
   if (Tr.GetDimension() == 1)
   {
      nBasis = -1;
      IntegrationRules irs(0, Quadrature1D::GaussLegendre);
      ir = irs.Get(Geometry::SEGMENT, 0);
   }
   else
   {
      if (Tr.GetDimension() == 2)
      {
         nBasis = 2 * (Order + 1) + static_cast<int>(Order * (Order + 1) / 2);
      }
      else if (Tr.GetDimension() == 3)
      {
         if (Order== 0) { nBasis = 3; }
         else if (Order== 1) { nBasis = 11; }
         else if (Order== 2) { nBasis = 26; }
         else if (Order== 3) { nBasis = 50; }
         else if (Order== 4) { nBasis = 85; }
         else if (Order== 5) { nBasis = 133; }
         else if (Order== 6) { nBasis = 196; }
         else if (Order>= 7) { nBasis = 276; Order = 7; }
      }

      // compute the quadrature points
      int qorder = 0;
      IntegrationRules irs(0, Quadrature1D::GaussLegendre);
      ir = irs.Get(Tr.GetGeometryType(), qorder);
      for (; ir.GetNPoints() <= nBasis; qorder++)
      {
         ir = irs.Get(Tr.GetGeometryType(), qorder);
      }
   }
}

void MomentFittingIntRules::InitVolume(int order, Coefficient& levelset,
                                       int lsO, ElementTransformation& Tr)
{
   order++;
   InitSurface(order, levelset, lsO, Tr);
   Order--;

   nBasisVolume = 0;
   if (Tr.GetDimension() == 1)
   {
      nBasisVolume = -1;
      IntegrationRules irs(0, Quadrature1D::GaussLegendre);
      ir = irs.Get(Geometry::SEGMENT, Order);
   }
   else
   {
      if (Tr.GetDimension() == 2)
      {
         nBasisVolume = (int)((Order + 1) * (Order + 2) / 2);
      }
      else if (Tr.GetDimension() == 3)
      {
         for (int p = 0; p <= Order; p++)
         {
            nBasisVolume +=(int)((p + 1) * (p + 2) / 2);
         }
      }

      // assemble the matrix
      DenseMatrix Mat(nBasisVolume, ir.GetNPoints());
      for (int ip = 0; ip < ir.GetNPoints(); ip++)
      {
         Vector shape;
         if (Tr.GetDimension() == 2)
         {
            Basis2D(ir.IntPoint(ip), shape);
         }
         else if (Tr.GetDimension() == 3)
         {
            Basis3D(ir.IntPoint(ip), shape);
         }

         Mat.SetCol(ip, shape);
      }

      // compute the SVD for the matrix
      VolumeSVD = new DenseMatrixSVD(Mat, 'A', 'A');
      VolumeSVD->Eval(Mat);
   }

}

void MomentFittingIntRules::ComputeFaceWeights(ElementTransformation& Tr)
{
   int elem = Tr.ElementNo;
   const Mesh *mesh = Tr.mesh;

   if (FaceIP.Size() == 0)
   {
      FaceWeightsComp.SetSize(mesh->GetNFaces());
      FaceWeightsComp = 0.;
   }

   const Element* me = mesh->GetElement(elem);
   IsoparametricTransformation Trafo;
   mesh->GetElementTransformation(elem, &Trafo);

   Array<int> faces;
   Array<int> cor;
   mesh->GetElementFaces(elem, faces, cor);

   for (int face = 0; face < me->GetNFaces(); face++)
   {
      if (FaceWeightsComp(faces[face]) == 0.)
      {
         FaceWeightsComp(faces[face]) = 1.;
         Array<int> verts;
         mesh->GetFaceVertices(faces[face], verts);
         Vector pointA(mesh->SpaceDimension());
         Vector pointB(mesh->SpaceDimension());
         Vector pointC(mesh->SpaceDimension());
         Vector pointD(mesh->SpaceDimension());
         for (int d = 0; d < mesh->SpaceDimension(); d++)
         {
            pointA(d) = (mesh->GetVertex(verts[0]))[d];
            pointB(d) = (mesh->GetVertex(verts[1]))[d];
            pointC(d) = (mesh->GetVertex(verts[2]))[d];
            pointD(d) = (mesh->GetVertex(verts[3]))[d];
         }

         // TODO - don't we lose the curvature with this local mesh setup?
         Mesh local_mesh(2,4,1,0,3);
         local_mesh.AddVertex(pointA);
         local_mesh.AddVertex(pointB);
         local_mesh.AddVertex(pointC);
         local_mesh.AddVertex(pointD);
         local_mesh.AddQuad(0,1,2,3);
         local_mesh.FinalizeQuadMesh(1);
         IsoparametricTransformation faceTrafo;
         local_mesh.GetElementTransformation(0, &faceTrafo);

         // The 3D face integrals are computed as 2D volumetric integrals.
         MomentFittingIntRules FaceRules(Order, *LvlSet, lsOrder);
         IntegrationRule FaceRule;
         FaceRules.GetVolumeIntegrationRule(faceTrafo, FaceRule);
         if (FaceIP.Size() != FaceRule.Size())
         {
            FaceIP.SetSize(FaceRule.Size());
            for (int ip = 0; ip < FaceRule.GetNPoints(); ip++)
            {
               FaceIP[ip].index = ip;
               IntegrationPoint &intp = FaceIP[ip];
               intp.x = FaceRule.IntPoint(ip).x;
               intp.y = FaceRule.IntPoint(ip).y;
               intp.weight = 0.;
            }

            FaceWeights.SetSize(FaceRule.GetNPoints(), mesh->GetNFaces());
            FaceWeights = 0.;

            FaceWeightsComp = 0.;
            FaceWeightsComp(faces[face]) = 1.;
         }

         for (int ip = 0; ip < FaceRule.GetNPoints(); ip++)
         {
            FaceWeights(ip, faces[face]) = FaceRule.IntPoint(ip).weight;
         }
      }
   }

   mesh->GetElementTransformation(elem, &Trafo);
}

void MomentFittingIntRules::ComputeSurfaceWeights1D(ElementTransformation& Tr)
{
   IntegrationPoint& intp = ir.IntPoint(0);

   IntegrationPoint ip0;
   ip0.x = 0.;
   IntegrationPoint ip1;
   ip1.x = 1.;
   Tr.SetIntPoint(&ip0);
   if (LvlSet->Eval(Tr, ip0) * LvlSet->Eval(Tr, ip1) < 0.)
   {
      IntegrationPoint ip2;
      ip2.x = .5;
      while (LvlSet->Eval(Tr, ip2) > 1e-12
             || LvlSet->Eval(Tr, ip2) < -1e-12)
      {
         if (LvlSet->Eval(Tr, ip0) * LvlSet->Eval(Tr, ip2) < 0.)
         {
            ip1.x = ip2.x;
         }
         else
         {
            ip0.x = ip2.x;
         }

         ip2.x = (ip1.x + ip0.x) / 2.;
      }
      intp.x = ip2.x;
      intp.weight = 1. / Tr.Weight();
   }
   else if (LvlSet->Eval(Tr, ip0) > 0. && LvlSet->Eval(Tr, ip1) <= 1e-12)
   {
      intp.x = 1.;
      intp.weight = 1. / Tr.Weight();
   }
   else if (LvlSet->Eval(Tr, ip1) > 0. && LvlSet->Eval(Tr, ip0) <= 1e-12)
   {
      intp.x = 0.;
      intp.weight = 1. / Tr.Weight();
   }
   else
   {
      intp.x = .5;
      intp.weight = 0.;
   }
}

void MomentFittingIntRules::ComputeVolumeWeights1D(ElementTransformation& Tr,
                                                   const IntegrationRule* sir)
{
   IntegrationRules irs(0, Quadrature1D::GaussLegendre);
   IntegrationRule ir2 = irs.Get(Geometry::SEGMENT, ir.GetOrder());

   IntegrationPoint ip0;
   ip0.x = 0.;
   IntegrationPoint ip1;
   ip1.x = 1.;
   Tr.SetIntPoint(&ip0);
   if (LvlSet->Eval(Tr, ip0) * LvlSet->Eval(Tr, ip1) < 0.)
   {
      Vector tempX(ir.GetNPoints());
      real_t length;
      if (LvlSet->Eval(Tr, ip0) > 0.)
      {
         length = sir->IntPoint(0).x;
         for (int ip = 0; ip < ir.GetNPoints(); ip++)
         {
            IntegrationPoint &intp = ir.IntPoint(ip);
            intp.x = ir2.IntPoint(ip).x * length;
            intp.weight = ir2.IntPoint(ip).weight * length;
         }
      }
      else
      {
         length = 1. - sir->IntPoint(0).x;
         for (int ip = 0; ip < ir.GetNPoints(); ip++)
         {
            IntegrationPoint &intp = ir.IntPoint(ip);
            intp.x = sir->IntPoint(ip).x + ir2.IntPoint(ip).x * length;
            intp.weight = ir2.IntPoint(ip).weight * length;
         }
      }
   }
   else if (LvlSet->Eval(Tr, ip0) <= -1e-12
            || LvlSet->Eval(Tr, ip1) <= -1e-12)
   {
      for (int ip = 0; ip < ir.GetNPoints(); ip++)
      {
         IntegrationPoint &intp = ir.IntPoint(ip);
         intp.x = ir2.IntPoint(ip).x;
         intp.weight = 0.;
      }
   }
   else
   {
      ir = ir2;
   }
}

void MomentFittingIntRules::ComputeSurfaceWeights2D(ElementTransformation& Tr)
{
   int elem = Tr.ElementNo;
   const Mesh* mesh = Tr.mesh;

   const Element* me = mesh->GetElement(elem);
   IsoparametricTransformation Trafo;
   mesh->GetElementTransformation(elem, &Trafo);

   DenseMatrix Mat(nBasis, ir.GetNPoints());
   Mat = 0.;
   Vector RHS(nBasis);
   RHS = 0.;
   Vector ElemWeights(ir.GetNPoints());
   ElemWeights = 0.;

   bool element_int = false;
   bool interior = true;
   Array<bool> edge_int;

   DenseMatrix PointA(me->GetNEdges(), Trafo.GetSpaceDim());
   DenseMatrix PointB(me->GetNEdges(), Trafo.GetSpaceDim());
   Vector edgelength(me->GetNEdges());

   Array<int> verts;
   mesh->GetElementVertices(elem, verts);

   // find the edges that are intersected by the surface and inside the area
   for (int edge = 0; edge < me->GetNEdges(); edge++)
   {
      enum class Layout {inside, intersected, outside};
      Layout layout;

      const int* vert = me->GetEdgeVertices(edge);
      Vector pointA(Trafo.GetSpaceDim());
      Vector pointB(Trafo.GetSpaceDim());
      for (int d = 0; d < Trafo.GetSpaceDim(); d++)
      {
         pointA(d) = (Trafo.mesh->GetVertex(verts[vert[0]]))[d];
         pointB(d) = (Trafo.mesh->GetVertex(verts[vert[1]]))[d];
      }
      Vector edgevec(Trafo.GetSpaceDim());
      subtract(pointA, pointB, edgevec);
      edgelength(edge) = edgevec.Norml2();

      IntegrationPoint ipA;
      Trafo.TransformBack(pointA, ipA);
      IntegrationPoint ipB;
      Trafo.TransformBack(pointB, ipB);

      if (LvlSet->Eval(Trafo, ipA) < -1e-12
          || LvlSet->Eval(Trafo, ipB) < -1e-12)
      {
         interior = false;
      }

      if (LvlSet->Eval(Trafo, ipA) > -1e-12
          && LvlSet->Eval(Trafo, ipB) > -1e-12)
      {
         layout = Layout::inside;
      }
      else if (LvlSet->Eval(Trafo, ipA) > 1e-15
               && LvlSet->Eval(Trafo, ipB) <= 0.)
      {
         layout = Layout::intersected;
      }
      else if (LvlSet->Eval(Trafo, ipA) <= 0.
               && LvlSet->Eval(Trafo, ipB) > 1e-15)
      {
         layout = Layout::intersected;
         Vector temp(pointA.Size());
         temp = pointA;
         pointA = pointB;
         pointB = temp;
      }
      else
      {
         layout = Layout::outside;
      }

      // Store the end points of the (1D) intersected edge.
      if (layout == Layout::intersected)
      {
         Vector pointC(pointA.Size());
         Vector mid(pointA.Size());
         pointC = pointA;
         mid = pointC;
         mid += pointB;
         mid /= 2.;

         IntegrationPoint ip;
         Trafo.TransformBack(mid, ip);

         while (LvlSet->Eval(Trafo, ip) > 1e-12
                || LvlSet->Eval(Trafo, ip) < -1e-12)
         {
            if (LvlSet->Eval(Trafo, ip) > 1e-12)
            {
               pointC = mid;
            }
            else
            {
               pointB = mid;
            }

            mid = pointC;
            mid += pointB;
            mid /= 2.;
            Trafo.TransformBack(mid, ip);
         }
         pointB = mid;
      }
      PointA.SetRow(edge, pointA);
      PointB.SetRow(edge, pointB);

      if ((layout == Layout::inside || layout == Layout::intersected))
      {
         edge_int.Append(true);
      }
      else
      {
         edge_int.Append(false);
      }
   }

   // Integrate over the 1D edges.
   for (int edge = 0; edge < me->GetNEdges(); edge++)
   {
      if (edge_int[edge] && !interior)
      {
         Vector point0(Trafo.GetSpaceDim());
         Vector point1(Trafo.GetSpaceDim());
         PointA.GetRow(edge, point0);
         PointB.GetRow(edge, point1);

         element_int = true;

         const IntegrationRule *ir2 = &IntRules.Get(Geometry::SEGMENT,
                                                    2*Order+1);

         Vector normal(Trafo.GetDimension());
         normal = 0.;
         if (edge == 0 || edge == 2)
         {
            normal(1) = 1.;
         }
         if (edge == 1 || edge == 3)
         {
            normal(0) = 1.;
         }
         if (edge == 0 || edge == 3)
         {
            normal *= -1.;
         }

         for (int ip = 0; ip < ir2->GetNPoints(); ip++)
         {
            Vector dist(Trafo.GetSpaceDim());
            dist = point1;
            dist -= point0;

            Vector point(Trafo.GetSpaceDim());
            point = dist;
            point *= ir2->IntPoint(ip).x;
            point += point0;

            IntegrationPoint intpoint;
            Trafo.TransformBack(point, intpoint);
            Trafo.SetIntPoint(&intpoint);
            DenseMatrix shapes;
            OrthoBasis2D(intpoint, shapes);
            Vector grad(Trafo.GetDimension());

            for (int dof = 0; dof < nBasis; dof++)
            {
               shapes.GetRow(dof, grad);
               RHS(dof) -= (grad * normal) * ir2->IntPoint(ip).weight
                           * dist.Norml2() / edgelength(edge);
            }
         }
      }
   }

   // do integration over the area for integral over interface
   if (element_int && !interior)
   {
      H1_FECollection fec(lsOrder, 2);
      FiniteElementSpace fes(const_cast<Mesh*>(Tr.mesh), &fec);
      GridFunction LevelSet(&fes);
      LevelSet.ProjectCoefficient(*LvlSet);
      mesh->GetElementTransformation(elem, &Trafo);

      const FiniteElement* fe = fes.GetFE(elem);
      Vector normal(Trafo.GetDimension());
      Vector gradi(Trafo.GetDimension());
      DenseMatrix dshape(fe->GetDof(), Trafo.GetDimension());
      Array<int> dofs;
      fes.GetElementDofs(elem, dofs);

      for (int ip = 0; ip < ir.GetNPoints(); ip++)
      {
         Trafo.SetIntPoint(&(ir.IntPoint(ip)));

         normal = 0.;
         fe->CalcDShape(ir.IntPoint(ip), dshape);
         for (int dof = 0; dof < fe->GetDof(); dof++)
         {
            dshape.GetRow(dof, gradi);
            gradi *= LevelSet(dofs[dof]);
            normal += gradi;
         }
         normal *= (-1. / normal.Norml2());

         DenseMatrix shapes;
         OrthoBasis2D(ir.IntPoint(ip), shapes);

         for (int dof = 0; dof < nBasis; dof++)
         {
            Vector grad(Trafo.GetSpaceDim());
            shapes.GetRow(dof, grad);
            Mat(dof, ip) =  (grad * normal);
         }
      }

      // solve the underdetermined linear system
      Vector temp(nBasis);
      Vector temp2(ir.GetNPoints());
      DenseMatrixSVD SVD(Mat, 'A', 'A');
      SVD.Eval(Mat);
      SVD.LeftSingularvectors().MultTranspose(RHS, temp);
      temp2 = 0.;
      for (int i = 0; i < nBasis; i++)
      {
         if (SVD.Singularvalue(i) > 1e-12)
         {
            temp2(i) = temp(i) / SVD.Singularvalue(i);
         }
      }
      SVD.RightSingularvectors().MultTranspose(temp2, ElemWeights);
   }

   // save the weights
   for (int ip = 0; ip < ir.GetNPoints(); ip++)
   {
      IntegrationPoint& intp = ir.IntPoint(ip);
      intp.weight = ElemWeights(ip);
   }

   mesh->GetElementTransformation(elem, &Trafo);
}

void MomentFittingIntRules::ComputeVolumeWeights2D(ElementTransformation& Tr,
                                                   const IntegrationRule* sir)
{
   int elem = Tr.ElementNo;
   const Mesh* mesh = Tr.mesh;

   const Element* me = mesh->GetElement(elem);
   IsoparametricTransformation Trafo;
   mesh->GetElementTransformation(elem, &Trafo);

   Vector RHS(nBasisVolume);
   RHS = 0.;
   Vector ElemWeights(ir.GetNPoints());
   ElemWeights = 0.;

   bool element_int = false;
   bool interior = true;
   Array<bool> edge_int;

   DenseMatrix PointA(me->GetNEdges(), Trafo.GetSpaceDim());
   DenseMatrix PointB(me->GetNEdges(), Trafo.GetSpaceDim());
   Vector edgelength(me->GetNEdges());

   Array<int> verts;
   mesh->GetElementVertices(elem, verts);

   // find the edges that are intersected by he surface and inside the area
   for (int edge = 0; edge < me->GetNEdges(); edge++)
   {
      enum class Layout {inside, intersected, outside};
      Layout layout;

      const int* vert = me->GetEdgeVertices(edge);
      Vector pointA(Trafo.GetSpaceDim());
      Vector pointB(Trafo.GetSpaceDim());
      for (int d = 0; d < Trafo.GetSpaceDim(); d++)
      {
         pointA(d) = (Trafo.mesh->GetVertex(verts[vert[0]]))[d];
         pointB(d) = (Trafo.mesh->GetVertex(verts[vert[1]]))[d];
      }
      Vector edgevec(Trafo.GetSpaceDim());
      subtract(pointA, pointB, edgevec);
      edgelength(edge) = edgevec.Norml2();

      IntegrationPoint ipA;
      Trafo.TransformBack(pointA, ipA);
      IntegrationPoint ipB;
      Trafo.TransformBack(pointB, ipB);

      if (LvlSet->Eval(Trafo, ipA) < -1e-12
          || LvlSet->Eval(Trafo, ipB) < -1e-12)
      {
         interior = false;
      }

      if (LvlSet->Eval(Trafo, ipA) > -1e-12
          && LvlSet->Eval(Trafo, ipB) > -1e-12)
      {
         layout = Layout::inside;
      }
      else if (LvlSet->Eval(Trafo, ipA) > 1e-15
               && LvlSet->Eval(Trafo, ipB) <= 0.)
      {
         layout = Layout::intersected;
      }
      else if (LvlSet->Eval(Trafo, ipA) <= 0.
               && LvlSet->Eval(Trafo, ipB) > 1e-15)
      {
         layout = Layout::intersected;
         Vector temp(pointA.Size());
         temp = pointA;
         pointA = pointB;
         pointB = temp;
      }
      else
      {
         layout = Layout::outside;
      }

      if (layout == Layout::intersected)
      {
         Vector pointC(pointA.Size());
         Vector mid(pointA.Size());
         pointC = pointA;
         mid = pointC;
         mid += pointB;
         mid /= 2.;

         IntegrationPoint ip;
         Trafo.TransformBack(mid, ip);

         while (LvlSet->Eval(Trafo, ip) > 1e-12
                || LvlSet->Eval(Trafo, ip) < -1e-12)
         {
            if (LvlSet->Eval(Trafo, ip) > 1e-12)
            {
               pointC = mid;
            }
            else
            {
               pointB = mid;
            }

            mid = pointC;
            mid += pointB;
            mid /= 2.;
            Trafo.TransformBack(mid, ip);
         }
         pointB = mid;
      }

      PointA.SetRow(edge, pointA);
      PointB.SetRow(edge, pointB);

      if ((layout == Layout::inside || layout == Layout::intersected))
      {
         edge_int.Append(true);
      }
      else
      {
         edge_int.Append(false);
      }
   }

   // do the integration over the edges
   for (int edge = 0; edge < me->GetNEdges(); edge++)
   {
      if (edge_int[edge] && !interior)
      {
         Vector point0(Trafo.GetSpaceDim());
         Vector point1(Trafo.GetSpaceDim());
         PointA.GetRow(edge, point0);
         PointB.GetRow(edge, point1);

         element_int = true;

         const IntegrationRule *ir2 = &IntRules.Get(Geometry::SEGMENT,
                                                    2*Order+1);
         Vector normal(Trafo.GetDimension());
         normal = 0.;
         if (edge == 0 || edge == 2)
         {
            normal(1) = 1.;
         }
         if (edge == 1 || edge == 3)
         {
            normal(0) = 1.;
         }
         if (edge == 0 || edge == 3)
         {
            normal *= -1.;
         }

         for (int ip = 0; ip < ir2->GetNPoints(); ip++)
         {
            Vector dist(Trafo.GetSpaceDim());
            dist = point1;
            dist -= point0;

            Vector point(Trafo.GetSpaceDim());
            point = dist;
            point *= ir2->IntPoint(ip).x;
            point += point0;

            IntegrationPoint intpoint;
            Trafo.TransformBack(point, intpoint);
            DenseMatrix shapes;
            BasisAD2D(intpoint, shapes);
            Vector adiv(Trafo.GetDimension());

            for (int dof = 0; dof < nBasisVolume; dof++)
            {
               shapes.GetRow(dof, adiv);
               RHS(dof) += (adiv * normal) * ir2->IntPoint(ip).weight
                           * dist.Norml2() / edgelength(edge);
            }
         }
      }
   }

   // Integrate over the interface using the already computed surface rule, and
   // solve the linear system for the weights.
   if (element_int && !interior)
   {
      H1_FECollection fec(lsOrder, 2);
      FiniteElementSpace fes(const_cast<Mesh*>(Tr.mesh), &fec);
      GridFunction LevelSet(&fes);
      LevelSet.ProjectCoefficient(*LvlSet);
      mesh->GetElementTransformation(elem, &Trafo);

      const FiniteElement* fe = fes.GetFE(elem);
      Vector normal(Trafo.GetDimension());
      Vector gradi(Trafo.GetDimension());
      DenseMatrix dshape(fe->GetDof(), Trafo.GetDimension());
      Array<int> dofs;
      fes.GetElementDofs(elem, dofs);

      for (int ip = 0; ip < sir->GetNPoints(); ip++)
      {
         Trafo.SetIntPoint(&(sir->IntPoint(ip)));

         normal = 0.;
         fe->CalcDShape(sir->IntPoint(ip), dshape);
         for (int dof = 0; dof < fe->GetDof(); dof++)
         {
            dshape.GetRow(dof, gradi);
            gradi *= LevelSet(dofs[dof]);
            normal += gradi;
         }
         normal *= (-1. / normal.Norml2());

         DenseMatrix shapes;
         BasisAD2D(sir->IntPoint(ip), shapes);

         for (int dof = 0; dof < nBasisVolume; dof++)
         {
            Vector adiv(2);
            shapes.GetRow(dof, adiv);
            RHS(dof) += (adiv * normal) * sir->IntPoint(ip).weight;
         }
      }

      // solve the underdetermined linear system
      Vector temp(nBasisVolume);
      Vector temp2(ir.GetNPoints());
      temp2 = 0.;
      VolumeSVD->LeftSingularvectors().MultTranspose(RHS, temp);
      for (int i = 0; i < nBasisVolume; i++)
      {
         if (VolumeSVD->Singularvalue(i) > 1e-12)
         {
            temp2(i) = temp(i) / VolumeSVD->Singularvalue(i);
         }
      }
      VolumeSVD->RightSingularvectors().MultTranspose(temp2, ElemWeights);
   }

   for (int ip = 0; ip < ir.GetNPoints(); ip++)
   {
      IntegrationPoint& intp = ir.IntPoint(ip);
      intp.weight = ElemWeights(ip);
   }

   if (interior)
   {
      int qorder = 0;
      IntegrationRules irs(0, Quadrature1D::GaussLegendre);
      IntegrationRule ir2 = irs.Get(Trafo.GetGeometryType(), qorder);
      for (; ir2.GetNPoints() < ir.GetNPoints(); qorder++)
      {
         ir2 = irs.Get(Trafo.GetGeometryType(), qorder);
      }
      ir = ir2;
   }

   mesh->GetElementTransformation(elem, &Trafo);
}

void MomentFittingIntRules::ComputeSurfaceWeights3D(ElementTransformation& Tr)
{
   ComputeFaceWeights(Tr);

   int elem = Tr.ElementNo;
   const Mesh* mesh = Tr.mesh;

   const Element* me = mesh->GetElement(elem);
   IsoparametricTransformation Trafo;
   mesh->GetElementTransformation(elem, &Trafo);

   DenseMatrix Mat(nBasis, ir.GetNPoints());
   Mat = 0.;
   Vector RHS(nBasis);
   RHS = 0.;
   Vector ElemWeights(ir.GetNPoints());
   ElemWeights = 0.;

   // Does the element have a positive vertex?
   bool element_int = false;
   // Are all element vertices positive?
   bool interior = true;

   Array<int> verts;
   mesh->GetElementVertices(elem, verts);

   for (int face = 0; face < me->GetNFaces(); face++)
   {
      const int* vert = me->GetFaceVertices(face);
      Vector pointA(Trafo.GetSpaceDim());
      Vector pointB(Trafo.GetSpaceDim());
      Vector pointC(Trafo.GetSpaceDim());
      Vector pointD(Trafo.GetSpaceDim());
      for (int d = 0; d < Trafo.GetSpaceDim(); d++)
      {
         pointA(d) = (Trafo.mesh->GetVertex(verts[vert[0]]))[d];
         pointB(d) = (Trafo.mesh->GetVertex(verts[vert[1]]))[d];
         pointC(d) = (Trafo.mesh->GetVertex(verts[vert[2]]))[d];
         pointD(d) = (Trafo.mesh->GetVertex(verts[vert[3]]))[d];
      }

      IntegrationPoint ipA;
      Trafo.TransformBack(pointA, ipA);
      IntegrationPoint ipB;
      Trafo.TransformBack(pointB, ipB);
      IntegrationPoint ipC;
      Trafo.TransformBack(pointC, ipC);
      IntegrationPoint ipD;
      Trafo.TransformBack(pointD, ipD);

      if (LvlSet->Eval(Trafo, ipA) < -1e-12
          || LvlSet->Eval(Trafo, ipB) < -1e-12
          || LvlSet->Eval(Trafo, ipC) < -1e-12
          || LvlSet->Eval(Trafo, ipD) < -1e-12)
      {
         interior = false;
      }

      if (LvlSet->Eval(Trafo, ipA) > -1e-12
          || LvlSet->Eval(Trafo, ipB) > -1e-12
          || LvlSet->Eval(Trafo, ipC) > -1e-12
          || LvlSet->Eval(Trafo, ipD) > -1e-12)
      {
         element_int = true;
      }

      Array<int> faces;
      Array<int> cor;
      mesh->GetElementFaces(elem, faces, cor);

      IsoparametricTransformation Tr1, Tr2;
      FaceElementTransformations FTrans;
      Trafo.mesh->GetFaceElementTransformations(faces[face], FTrans, Tr1, Tr2);
      FTrans.SetIntPoint(&(FaceIP[0]));

      Vector normal(Trafo.GetDimension());
      normal = 0.;
      if (face == 0 || face == 5)
      {
         normal(2) = 1.;
      }
      if (face == 1 || face == 3)
      {
         normal(1) = 1.;
      }
      if (face == 2 || face == 4)
      {
         normal(0) = 1.;
      }
      if (face == 0 || face == 1 || face == 4)
      {
         normal *= -1.;
      }

      for (int ip = 0; ip < FaceIP.Size(); ip++)
      {
         DenseMatrix shape;
         Vector point(3);
         IntegrationPoint ipoint;
         FTrans.Transform(FaceIP[ip], point);
         Trafo.TransformBack(point, ipoint);
         OrthoBasis3D(ipoint, shape);

         for (int dof = 0; dof < nBasis; dof++)
         {
            Vector grad(Trafo.GetSpaceDim());
            shape.GetRow(dof, grad);
            RHS(dof) -= (grad * normal) * FaceWeights(ip, faces[face]);
         }
      }
   }

   // If the element is intersected, form the matrix and solve for the weights.
   if (element_int && !interior)
   {
      H1_FECollection fec(lsOrder, 3);
      FiniteElementSpace fes(const_cast<Mesh*>(Tr.mesh), &fec);
      GridFunction LevelSet(&fes);
      LevelSet.ProjectCoefficient(*LvlSet);
      mesh->GetElementTransformation(elem, &Trafo);

      const FiniteElement* fe = fes.GetFE(elem);
      Vector normal(Trafo.GetDimension());
      Vector gradi(Trafo.GetDimension());
      DenseMatrix dshape(fe->GetDof(), Trafo.GetDimension());
      Array<int> dofs;
      fes.GetElementDofs(elem, dofs);

      // Form the matrix.
      for (int ip = 0; ip < ir.GetNPoints(); ip++)
      {
         Trafo.SetIntPoint(&(ir.IntPoint(ip)));

         normal = 0.;
         fe->CalcDShape(ir.IntPoint(ip), dshape);
         for (int dof = 0; dof < fe->GetDof(); dof++)
         {
            dshape.GetRow(dof, gradi);
            gradi *= LevelSet(dofs[dof]);
            normal += gradi;
         }
         normal *= (-1. / normal.Norml2());

         DenseMatrix shapes;
         OrthoBasis3D(ir.IntPoint(ip), shapes);

         for (int dof = 0; dof < nBasis; dof++)
         {
            Vector grad(Trafo.GetSpaceDim());
            shapes.GetRow(dof, grad);
            Mat(dof, ip) =  (grad * normal);
         }
      }

      // solve the underdetermined linear system
      Vector temp(nBasis);
      Vector temp2(ir.GetNPoints());
      DenseMatrixSVD SVD(Mat, 'A', 'A');
      SVD.Eval(Mat);
      SVD.LeftSingularvectors().MultTranspose(RHS, temp);
      temp2 = 0.;
      for (int i = 0; i < nBasis; i++)
      {
         if (SVD.Singularvalue(i) > 1e-12)
         {
            temp2(i) = temp(i) / SVD.Singularvalue(i);
         }
      }
      SVD.RightSingularvectors().MultTranspose(temp2, ElemWeights);
   }

   // scale the weights
   for (int ip = 0; ip < ir.GetNPoints(); ip++)
   {
      IntegrationPoint& intp = ir.IntPoint(ip);
      intp.weight = ElemWeights(ip);
   }

   mesh->GetElementTransformation(elem, &Trafo);
}

void MomentFittingIntRules::ComputeVolumeWeights3D(ElementTransformation& Tr,
                                                   const IntegrationRule* sir)
{
   Order++;
   ComputeFaceWeights(Tr);
   Order--;

   int elem = Tr.ElementNo;
   const Mesh* mesh = Tr.mesh;

   const Element* me = mesh->GetElement(elem);
   IsoparametricTransformation Trafo;
   mesh->GetElementTransformation(elem, &Trafo);

   Vector RHS(nBasisVolume);
   RHS = 0.;
   Vector ElemWeights(ir.GetNPoints());
   ElemWeights = 0.;

   // Does the element have a positive vertex?
   bool element_int = false;
   // Are all element vertices positive?
   bool interior = true;

   Array<int> verts;
   mesh->GetElementVertices(elem, verts);

   for (int face = 0; face < me->GetNFaces(); face++)
   {
      const int* vert = me->GetFaceVertices(face);
      Vector pointA(Trafo.GetSpaceDim());
      Vector pointB(Trafo.GetSpaceDim());
      Vector pointC(Trafo.GetSpaceDim());
      Vector pointD(Trafo.GetSpaceDim());
      for (int d = 0; d < Trafo.GetSpaceDim(); d++)
      {
         pointA(d) = (Trafo.mesh->GetVertex(verts[vert[0]]))[d];
         pointB(d) = (Trafo.mesh->GetVertex(verts[vert[1]]))[d];
         pointC(d) = (Trafo.mesh->GetVertex(verts[vert[2]]))[d];
         pointD(d) = (Trafo.mesh->GetVertex(verts[vert[3]]))[d];
      }

      IntegrationPoint ipA;
      Trafo.TransformBack(pointA, ipA);
      IntegrationPoint ipB;
      Trafo.TransformBack(pointB, ipB);
      IntegrationPoint ipC;
      Trafo.TransformBack(pointC, ipC);
      IntegrationPoint ipD;
      Trafo.TransformBack(pointD, ipD);

      if (LvlSet->Eval(Trafo, ipA) < -1e-12
          || LvlSet->Eval(Trafo, ipB) < -1e-12
          || LvlSet->Eval(Trafo, ipC) < -1e-12
          || LvlSet->Eval(Trafo, ipD) < -1e-12)
      {
         interior = false;
      }

      if (LvlSet->Eval(Trafo, ipA) > -1e-12
          || LvlSet->Eval(Trafo, ipB) > -1e-12
          || LvlSet->Eval(Trafo, ipC) > -1e-12
          || LvlSet->Eval(Trafo, ipD) > -1e-12)
      {
         element_int = true;
      }

      Array<int> faces;
      Array<int> cor;
      mesh->GetElementFaces(elem, faces, cor);

      IsoparametricTransformation Tr1, Tr2;
      FaceElementTransformations FTrans;
      Trafo.mesh->GetFaceElementTransformations(faces[face], FTrans, Tr1, Tr2);

      FTrans.SetIntPoint(&(FaceIP[0]));

      Vector normal(Trafo.GetDimension());
      normal = 0.;
      if (face == 0 || face == 5)
      {
         normal(2) = 1.;
      }
      if (face == 1 || face == 3)
      {
         normal(1) = 1.;
      }
      if (face == 2 || face == 4)
      {
         normal(0) = 1.;
      }
      if (face == 0 || face == 1 || face == 4)
      {
         normal *= -1.;
      }

      for (int ip = 0; ip < FaceIP.Size(); ip++)
      {
         DenseMatrix shape;
         Vector point(3);
         IntegrationPoint ipoint;
         FTrans.Transform(FaceIP[ip], point);
         Trafo.TransformBack(point, ipoint);
         BasisAD3D(ipoint, shape);

         for (int dof = 0; dof < nBasisVolume; dof++)
         {
            Vector adiv(Trafo.GetSpaceDim());
            shape.GetRow(dof, adiv);
            RHS(dof) += (adiv * normal) * FaceWeights(ip, faces[face]);
         }
      }
   }

   // If the element is intersected, integrate over the cut surface (using the
   // already computed rule) and solve the matrix for the weights.
   if (element_int && !interior)
   {
      H1_FECollection fec(lsOrder, 3);
      FiniteElementSpace fes(const_cast<Mesh*>(Tr.mesh), &fec);
      GridFunction LevelSet(&fes);
      LevelSet.ProjectCoefficient(*LvlSet);
      mesh->GetElementTransformation(elem, &Trafo);

      const FiniteElement* fe = fes.GetFE(elem);
      Vector normal(Trafo.GetDimension());
      Vector gradi(Trafo.GetDimension());
      DenseMatrix dshape(fe->GetDof(), Trafo.GetDimension());
      Array<int> dofs;
      fes.GetElementDofs(elem, dofs);

      // Integrate over the cut surface using the already computed rule.
      for (int ip = 0; ip < sir->GetNPoints(); ip++)
      {
         Trafo.SetIntPoint(&(sir->IntPoint(ip)));

         normal = 0.;
         fe->CalcDShape(sir->IntPoint(ip), dshape);
         for (int dof = 0; dof < fe->GetDof(); dof++)
         {
            dshape.GetRow(dof, gradi);
            gradi *= LevelSet(dofs[dof]);
            normal += gradi;
         }
         normal *= (-1. / normal.Norml2());

         DenseMatrix shapes;
         BasisAD3D(sir->IntPoint(ip), shapes);

         for (int dof = 0; dof < nBasisVolume; dof++)
         {
            Vector adiv(Trafo.GetSpaceDim());
            shapes.GetRow(dof, adiv);
            RHS(dof) +=  (adiv * normal) * sir->IntPoint(ip).weight;
         }
      }

      // solve the underdetermined linear system
      Vector temp(nBasisVolume);
      Vector temp2(ir.GetNPoints());
      VolumeSVD->LeftSingularvectors().MultTranspose(RHS, temp);
      temp2 = 0.;
      for (int i = 0; i < nBasisVolume; i++)
         if (VolumeSVD->Singularvalue(i) > 1e-12)
         {
            temp2(i) = temp(i) / VolumeSVD->Singularvalue(i);
         }
      VolumeSVD->RightSingularvectors().MultTranspose(temp2, ElemWeights);
   }

   // scale the weights
   for (int ip = 0; ip < ir.GetNPoints(); ip++)
   {
      IntegrationPoint& intp = ir.IntPoint(ip);
      intp.weight = ElemWeights(ip);
   }

   // Fully inside the subdomain -> standard integration.
   if (interior)
   {
      int qorder = 0;
      IntegrationRules irs(0, Quadrature1D::GaussLegendre);
      IntegrationRule ir2 = irs.Get(Trafo.GetGeometryType(), qorder);
      for (; ir2.GetNPoints() < ir.GetNPoints(); qorder++)
      {
         ir2 = irs.Get(Trafo.GetGeometryType(), qorder);
      }
      ir = ir2;
   }

   mesh->GetElementTransformation(elem, &Trafo);
}

void MomentFittingIntRules::DivFreeBasis2D(const IntegrationPoint& ip,
                                           DenseMatrix& shape)
{
   shape.SetSize(nBasis, 2);

   Vector X(2);
   X(0) = -1. + 2. * ip.x;
   X(1) = -1. + 2. * ip.y;

   for (int c = 0; c <= Order; c++)
   {
      Vector a(2);
      a = 0.;
      a(1) = pow(X(0), (real_t)(c));

      Vector b(2);
      b = 0.;
      b(0) = pow(X(1), (real_t)(c));

      shape.SetRow(2 * c, a);
      shape.SetRow(2 * c + 1, b);
   }

   Poly_1D poly;
   int count = 2 * Order+ 2;
   for (int c = 1; c <= Order; c++)
   {
      const int* factorial = poly.Binom(c);
      for (int expo = c; expo > 0; expo--)
      {
         Vector a(2);
         a(0) = (real_t)(factorial[expo]) * pow(X(0), (real_t)(expo))
                *  pow(X(1), (real_t)(c - expo));
         a(1) = -1. * (real_t)(factorial[expo - 1])
                * pow(X(0), (real_t)(expo - 1))
                * pow(X(1), (real_t)(c - expo + 1));

         shape.SetRow(count, a);
         count++;
      }
   }
}

void MomentFittingIntRules::OrthoBasis2D(const IntegrationPoint& ip,
                                         DenseMatrix& shape)
{
   const IntegrationRule *ir_ = &IntRules.Get(Geometry::SQUARE, 2*Order+1);

   shape.SetSize(nBasis, 2);

   // evaluate basis in the point
   DenseMatrix preshape(nBasis, 2);
   DivFreeBasis2D(ip, shape);

   // evaluate basis for quadrature points
   DenseTensor shapeMFN(nBasis, 2, ir_->GetNPoints());
   for (int p = 0; p < ir_->GetNPoints(); p++)
   {
      DenseMatrix shapeN(nBasis, 2);
      DivFreeBasis2D(ir_->IntPoint(p), shapeN);
      for (int i = 0; i < nBasis; i++)
         for (int j = 0; j < 2; j++)
         {
            shapeMFN(i, j, p) = shapeN(i, j);
         }
   }

   // do modified Gram-Schmidt orthogonalization
   for (int count = 1; count < nBasis; count++)
   {
      mGSStep(shape, shapeMFN, count);
   }
}

void MomentFittingIntRules::OrthoBasis3D(const IntegrationPoint& ip,
                                         DenseMatrix& shape)
{
   Vector X(3);
   X(0) = -1. + 2. * ip.x;
   X(1) = -1. + 2. * ip.y;
   X(2) = -1. + 2. * ip.z;

   DivFreeBasis::GetDivFree3DBasis(X, shape, Order);
}

void MomentFittingIntRules::mGSStep(DenseMatrix& shape, DenseTensor& shapeMFN,
                                    int step)
{
   const IntegrationRule *ir_ = &IntRules.Get(Geometry::SQUARE, 2*Order+1);

   for (int count = step; count < shape.Height(); count++)
   {
      real_t den = 0.;
      real_t num = 0.;

      for (int ip = 0; ip < ir_->GetNPoints(); ip++)
      {
         Vector u(2);
         Vector v(2);

         shapeMFN(ip).GetRow(count, u);
         shapeMFN(ip).GetRow(step - 1, v);

         den += v * v * ir_->IntPoint(ip).weight;
         num += u * v * ir_->IntPoint(ip).weight;
      }

      real_t coeff = num / den;

      Vector s(2);
      Vector t(2);
      shape.GetRow(step - 1, s);
      shape.GetRow(count, t);
      s *= coeff;
      t += s;
      shape.SetRow(count, t);

      for (int ip = 0; ip < ir_->GetNPoints(); ip++)
      {
         shapeMFN(ip).GetRow(step - 1, s);
         shapeMFN(ip).GetRow(count, t);
         s *= coeff;
         t += s;
         shapeMFN(ip).SetRow(count, t);
      }
   }
}

void MomentFittingIntRules::Basis2D(const IntegrationPoint& ip, Vector& shape)
{
   shape.SetSize(nBasisVolume);

   Vector X(2);
   X(0) = -1. + 2. * ip.x;
   X(1) = -1. + 2. * ip.y;

   int count = 0;
   for (int c = 0; c <= Order; c++)
   {
      for (int expo = 0; expo <= c; expo++)
      {
         shape(count) = pow(X(0), (real_t)(expo))
                        * pow(X(1), (real_t)(c - expo));
         count++;
      }
   }
}

void MomentFittingIntRules::BasisAD2D(const IntegrationPoint& ip,
                                      DenseMatrix& shape)
{
   shape.SetSize(nBasisVolume, 2);

   Vector X(2);
   X(0) = -1. + 2. * ip.x;
   X(1) = -1. + 2. * ip.y;

   int count = 0;
   for (int c = 0; c <= Order; c++)
   {
      for (int expo = 0; expo <= c; expo++)
      {
         shape(count, 0) = .25 * pow(X(0), (real_t)(expo + 1))
                           * pow(X(1), (real_t)(c - expo))
                           / (real_t)(expo + 1);
         shape(count, 1) = .25 * pow(X(0), (real_t)(expo))
                           * pow(X(1), (real_t)(c - expo + 1))
                           / (real_t)(c - expo + 1);
         count++;
      }
   }
}

void MomentFittingIntRules::Basis3D(const IntegrationPoint& ip, Vector& shape)
{
   shape.SetSize(nBasisVolume);

   Vector X(3);
   X(0) = -1. + 2. * ip.x;
   X(1) = -1. + 2. * ip.y;
   X(2) = -1. + 2. * ip.z;

   int count = 0;
   for (int c = 0; c <= Order; c++)
      for (int expo = 0; expo <= c; expo++)
         for (int expo2 = 0; expo2 <= c - expo; expo2++)
         {
            shape(count) = pow(X(0), (real_t)(expo))
                           * pow(X(1), (real_t)(expo2))
                           * pow(X(2), (real_t)(c - expo - expo2));
            count++;
         }
}

void MomentFittingIntRules::BasisAD3D(const IntegrationPoint& ip,
                                      DenseMatrix& shape)
{
   shape.SetSize(nBasisVolume, 3);

   Vector X(3);
   X(0) = -1. + 2. * ip.x;
   X(1) = -1. + 2. * ip.y;
   X(2) = -1. + 2. * ip.z;

   int count = 0;
   for (int c = 0; c <= Order; c++)
      for (int expo = 0; expo <= c; expo++)
         for (int expo2 = 0; expo2 <= c - expo; expo2++)
         {
            shape(count, 0) = pow(X(0), (real_t)(expo + 1))
                              * pow(X(1), (real_t)(expo2))
                              * pow(X(2), (real_t)(c - expo - expo2))
                              / (6. * (real_t)(expo + 1));
            shape(count, 1) = pow(X(0), (real_t)(expo))
                              * pow(X(1), (real_t)(expo2 + 1))
                              * pow(X(2), (real_t)(c - expo - expo2))
                              / (6. * (real_t)(expo2 + 1));;
            shape(count, 2) = pow(X(0), (real_t)(expo))
                              * pow(X(1), (real_t)(expo2))
                              * pow(X(2), (real_t)(c - expo - expo2 + 1))
                              / (6. * (real_t)(c - expo + expo2 + 1));;
            count++;
         }
}

void MomentFittingIntRules::Clear()
{
   dim = -1;
   nBasis = -1;
   nBasisVolume = -1;
   delete VolumeSVD;
   VolumeSVD = NULL;
   FaceIP.DeleteAll();
   FaceWeights = 0.;
   FaceWeightsComp = 0.;
}

void MomentFittingIntRules::SetOrder(int order)
{
   if (order != Order) { Clear(); }
   Order = order;
}

void MomentFittingIntRules::GetSurfaceIntegrationRule(ElementTransformation& Tr,
                                                      IntegrationRule& result)
{
   if (nBasis == -1 || dim != Tr.GetDimension())
   {
      Clear();
      InitSurface(Order, *LvlSet, lsOrder, Tr);
   }

   if (Tr.GetDimension() == 3)
   {
      FaceIP.DeleteAll();
      FaceWeights = 0.;
      FaceWeightsComp = 0.;
   }

   if (Tr.GetDimension() == 1)
   {
      ComputeSurfaceWeights1D(Tr);
   }
   else if (Tr.GetDimension() == 2)
   {
      ComputeSurfaceWeights2D(Tr);
   }
   else if (Tr.GetDimension() == 3)
   {
      ComputeSurfaceWeights3D(Tr);
   }

   result.SetSize(ir.GetNPoints());
   for (int ip = 0; ip < ir.GetNPoints(); ip++)
   {
      result.IntPoint(ip).index = ip;
      IntegrationPoint &intp = result.IntPoint(ip);
      intp.x = ir.IntPoint(ip).x;
      intp.y = ir.IntPoint(ip).y;
      intp.z = ir.IntPoint(ip).z;
      intp.weight = ir.IntPoint(ip).weight;
   }
}

void MomentFittingIntRules::GetVolumeIntegrationRule(ElementTransformation& Tr,
                                                     IntegrationRule& result,
                                                     const IntegrationRule* sir)
{
   if (nBasis == -1 || nBasisVolume == -1 || dim != Tr.GetDimension())
   {
      Clear();
      InitVolume(Order, *LvlSet, lsOrder, Tr);
   }

   if (Tr.GetDimension() == 3)
   {
      FaceIP.DeleteAll();
      FaceWeights = 0.;
      FaceWeightsComp = 0.;
   }

   IntegrationRule SIR;
   if (sir == NULL)
   {
      Order++;
      GetSurfaceIntegrationRule(Tr, SIR);
      Order--;
   }
   else if ((sir->GetOrder() - 1) != ir.GetOrder())
   {
      Order++;
      GetSurfaceIntegrationRule(Tr, SIR);
      Order--;
   }
   else
   {
      SIR = *sir;
   }

   if (Tr.GetDimension() == 1)
   {
      ComputeVolumeWeights1D(Tr, &SIR);
   }
   else if (Tr.GetDimension() == 2)
   {
      ComputeVolumeWeights2D(Tr, &SIR);
   }
   else if (Tr.GetDimension() == 3)
   {
      ComputeVolumeWeights3D(Tr, &SIR);
   }

   result.SetSize(ir.GetNPoints());
   for (int ip = 0; ip < ir.GetNPoints(); ip++)
   {
      result.IntPoint(ip).index = ip;
      IntegrationPoint &intp = result.IntPoint(ip);
      intp.x = ir.IntPoint(ip).x;
      intp.y = ir.IntPoint(ip).y;
      intp.z = ir.IntPoint(ip).z;
      intp.weight = ir.IntPoint(ip).weight;
   }
}

void MomentFittingIntRules::GetSurfaceWeights(ElementTransformation& Tr,
                                              const IntegrationRule &sir,
                                              Vector &weights)
{
   if (nBasis == -1 || dim != Tr.GetDimension())
   {
      Clear();
      InitSurface(Order, *LvlSet, lsOrder, Tr);
   }

   weights.SetSize(sir.GetNPoints());
   weights = 0.0;

   bool computeweights = false;
   for (int ip = 0; ip < sir.GetNPoints(); ip++)
   {
      if (sir.IntPoint(ip).weight != 0.)
      {
         computeweights = true;
      }
   }

   if (Tr.GetDimension() > 1 && computeweights)
   {
      int elem = Tr.ElementNo;
      const Mesh* mesh = Tr.mesh;
      H1_FECollection fec(lsOrder, Tr.GetDimension());
      FiniteElementSpace fes(const_cast<Mesh*>(Tr.mesh), &fec);
      GridFunction LevelSet(&fes);
      LevelSet.ProjectCoefficient(*LvlSet);

      IsoparametricTransformation Trafo;
      mesh->GetElementTransformation(elem, &Trafo);

      const FiniteElement* fe = fes.GetFE(elem);
      Vector normal(Tr.GetDimension());
      Vector normal2(Tr.GetSpaceDim());
      Vector gradi(Tr.GetDimension());
      DenseMatrix dshape(fe->GetDof(), Tr.GetDimension());
      Array<int> dofs;
      fes.GetElementDofs(elem, dofs);

      for (int ip = 0; ip < sir.GetNPoints(); ip++)
      {
         Trafo.SetIntPoint(&(sir.IntPoint(ip)));
         LevelSet.GetGradient(Trafo, normal2);
         real_t normphys = normal2.Norml2();

         normal = 0.;
         fe->CalcDShape(sir.IntPoint(ip), dshape);
         for (int dof = 0; dof < fe->GetDof(); dof++)
         {
            dshape.GetRow(dof, gradi);
            gradi *= LevelSet(dofs[dof]);
            normal += gradi;
         }
         real_t normref = normal.Norml2();
         normal *= (-1. / normal.Norml2());

         weights(ip) = normphys / normref;
      }
   }
}

#endif // MFEM_USE_LAPACK

}
