// This is a user-supplied routine that sets the the boundary conditions
//
void SetBndValues_Unst(const mesh& Mesh, dTensor3* q, dTensor3* aux)
{   
    int meqn = q->getsize(2);
    int kmax = q->getsize(3);
    int maux = aux->getsize(2);
    int      NumElems = Mesh.get_NumElems();
    int  NumPhysElems = Mesh.get_NumPhysElems();
    int NumGhostElems = Mesh.get_NumGhostElems();
    int      NumNodes = Mesh.get_NumNodes();
    int  NumPhysNodes = Mesh.get_NumPhysNodes();
    int      NumEdges = Mesh.get_NumEdges();

    // ----------------------------------------
    // Loop over each ghost cell element and
    // place the correct information into
    // these elements
    // ----------------------------------------
    for (int i=1; i<=NumGhostElems; i++)
    {
        int j = Mesh.get_ghost_link(i);

        for (int m=1; m<=meqn; m++)
            for (int k=1; k<=kmax; k++)
            {
                q->set(i+NumPhysElems,m,k, 0.0 );
            }
    }

}
// This is a user-supplied routine that sets the the boundary conditions
//
// The default routine for the 4D Vlasov code is to apply zero boundary
// conditions in configuration space.  This routine is identical to the one
// found in the unst branch of the 2D DoGPack code, and *should* be setting
// periodic boundary conditions.
//
void SetBndValues_Unst(const mesh& Mesh, dTensor3* q, dTensor3* aux)
{   

    // problem information (If this were to be pulled from DogParams, these
    // numbers would NOT be correct!  The reason is that each quadrature point
    // was actually saved as a separate "equation")
    const int meqn = q->getsize(2);
    const int kmax = q->getsize(3);
    const int maux = aux->getsize(2);

    // Mesh information
    const int      NumElems = Mesh.get_NumElems();
    const int  NumPhysElems = Mesh.get_NumPhysElems();
    const int NumGhostElems = Mesh.get_NumGhostElems();
    const int      NumNodes = Mesh.get_NumNodes();
    const int  NumPhysNodes = Mesh.get_NumPhysNodes();
    const int      NumEdges = Mesh.get_NumEdges();

    // ----------------------------------------
    // Loop over each ghost cell element and
    // place the correct information into
    // these elements
    // ----------------------------------------
    for (int i=1; i<=NumGhostElems; i++)
    {

        int j = Mesh.get_ghost_link(i);

        for (int m=1; m<=meqn; m++)
        for (int k=1; k<=kmax; k++)
        {
            q->set(i+NumPhysElems, m, k,  q->get(j, m, k) );
        }

    }

}
//
// Output basic mesh information to screen
//
void ScreenOutput(const mesh& Mesh)
{
  // Compute mesh quality parameters
  double totalarea = Mesh.get_area_prim(1);
  double maxarea = Mesh.get_area_prim(1);
  double minarea = Mesh.get_area_prim(1);
  for (int i=2; i<=Mesh.get_NumPhysElems(); i++)
    {
      double tmp = Mesh.get_area_prim(i);
      totalarea = totalarea + tmp;
      if (tmp < minarea)
	{ minarea = tmp; }
      if (tmp > maxarea)
	{ maxarea = tmp; }
    }

  double minAngle = 180.0;

  for (int i=1; i<=Mesh.get_NumPhysElems(); i++)
    {
      const int i1 = Mesh.get_tnode(i,1);
      const int i2 = Mesh.get_tnode(i,2);
      const int i3 = Mesh.get_tnode(i,3);

      point v12, v23, v31;
      v12.x = Mesh.get_node(i2,1) - Mesh.get_node(i1,1);
      v12.y = Mesh.get_node(i2,2) - Mesh.get_node(i1,2);
      v23.x = Mesh.get_node(i3,1) - Mesh.get_node(i2,1);
      v23.y = Mesh.get_node(i3,2) - Mesh.get_node(i2,2);
      v31.x = Mesh.get_node(i1,1) - Mesh.get_node(i3,1);
      v31.y = Mesh.get_node(i1,2) - Mesh.get_node(i3,2);
      
      double angle1 = acos((v12.x*-v31.x+v12.y*-v31.y)
			   /(sqrt(v12.x*v12.x+v12.y*v12.y)*sqrt(v31.x*v31.x+v31.y*v31.y)));
      double angle2 = acos((v23.x*-v12.x+v23.y*-v12.y)
			   /(sqrt(v23.x*v23.x+v23.y*v23.y)*sqrt(v12.x*v12.x+v12.y*v12.y)));
      double angle3 = acos((v31.x*-v23.x+v31.y*-v23.y)
			   /(sqrt(v31.x*v31.x+v31.y*v31.y)*sqrt(v23.x*v23.x+v23.y*v23.y)));
      if ((angle1*180/pi) < minAngle)
        {  minAngle = angle1*180/pi;  }
      if ((angle2*180/pi) < minAngle)
        {  minAngle = angle2*180/pi;  }
      if ((angle3*180/pi) < minAngle)
        {  minAngle = angle3*180/pi;  }
    }

  // Output summary of results to screen
  printf("\n");
  printf("  SUMMARY OF RESULTS:\n");
  printf("  -------------------\n");
  printf("          Number of Elements:  %8i\n",Mesh.get_NumElems());
  printf(" Number of Physical Elements:  %8i\n",Mesh.get_NumPhysElems());
  printf("    Number of Ghost Elements:  %8i\n",Mesh.get_NumGhostElems());
  printf("             Number of Nodes:  %8i\n",Mesh.get_NumNodes());
  printf("    Number of Physical Nodes:  %8i\n",Mesh.get_NumPhysNodes());
  printf("    Number of Boundary Nodes:  %8i\n",Mesh.get_NumBndNodes());
  printf("             Number of Edges:  %8i\n",Mesh.get_NumEdges());
  printf("    Number of Boundary Edges:  %8i\n",Mesh.get_NumBndEdges());
  printf("\n");
  printf("          Total Area Covered:  %24.16e\n",totalarea);
  printf("     Area Ratio: small/large:  %24.16e\n",minarea/maxarea);
  printf("    Angle Ratio: minAngle/60:  %24.16e\n",minAngle/60.0);
  printf("\n");
}
Example #4
0
void ComputeError(const int space_order,
		  const mesh& Mesh,
		  const dTensor1& phi,
		  const dTensor2& E1,
		  const dTensor2& E2,
		  void (*PhiFunc)(const dTensor2& xpts,dTensor2& phi_ex),
		  void (*EfieldFunc)(const dTensor2& xpts,dTensor2& Efield_ex))
{
  
  // Potential
  const int NumPhysNodes = phi.getsize();//Mesh.get_NumPhysNodes();
  dTensor2 xpts(NumPhysNodes,2);
  dTensor2 phi_ex(NumPhysNodes,1);
  double phi_err;
  double phi_rel;
  
  switch(space_order)
    {
    case 2:
      for (int i=1; i<=NumPhysNodes; i++)
	{  
	  xpts.set(i,1, Mesh.get_node(i,1) );
	  xpts.set(i,2, Mesh.get_node(i,2) );
	}
      PhiFunc(xpts,phi_ex);
      
      phi_err = 0.0;
      phi_rel = 0.0;
      for (int i=1; i<=NumPhysNodes; i++)
	{ 
	  phi_rel = phi_rel + pow(phi_ex.get(i,1),2);
	  phi_err = phi_err + pow(phi_ex.get(i,1)-phi.get(i),2);
	}
      phi_err = sqrt(phi_err/phi_rel);
      break;

    case 3:
      for (int i=1; i<=NumPhysNodes; i++)
	{  
	  xpts.set(i,1, Mesh.get_sub_node(i,1) );
	  xpts.set(i,2, Mesh.get_sub_node(i,2) );
	}
      PhiFunc(xpts,phi_ex);
      
      phi_err = 0.0;
      phi_rel = 0.0;
      for (int i=1; i<=NumPhysNodes; i++)
	{ 
	  phi_rel = phi_rel + pow(phi_ex.get(i,1),2);
	  phi_err = phi_err + pow(phi_ex.get(i,1)-phi.get(i),2);
	}
      phi_err = sqrt(phi_err/phi_rel);
      break;
    }
  
  // Electric field components
  void L2Project_Unst(const int istart, 
		      const int iend, 
		      const int QuadOrder,		    
		      const int BasisOrder_fout,
		      const mesh& Mesh, 
		      dTensor3* fout, 
		      void (*Func)(const dTensor2&,dTensor2&));
  
  const int NumElems     = Mesh.get_NumElems();
  const int NumPhysElems = Mesh.get_NumPhysElems();
  const int kmax = E1.getsize(2);

  dTensor3 Efield_ex(NumElems,2,kmax);
  L2Project_Unst(1,NumElems,space_order,space_order,
		 Mesh,&Efield_ex,EfieldFunc);
  
  double E1_err = 0.0;
  double E1_rel = 0.0;
  double E2_err = 0.0;
  double E2_rel = 0.0;

  for (int i=1; i<=NumPhysElems; i++)
    {
      double Area = Mesh.get_area_prim(i);
      double tmp1 = 0.0;
      double tmp2 = 0.0;
      double tmp1_rel = 0.0;
      double tmp2_rel = 0.0;
      for (int k=1; k<=kmax; k++)
	{
	  tmp1 = tmp1 + pow((E1.get(i,k)-Efield_ex.get(i,1,k)),2);
	  tmp2 = tmp2 + pow((E2.get(i,k)-Efield_ex.get(i,2,k)),2);
	  tmp1_rel = tmp1_rel + pow((Efield_ex.get(i,1,k)),2);
	  tmp2_rel = tmp2_rel + pow((Efield_ex.get(i,2,k)),2);
	}
      E1_err = E1_err + Area*tmp1;
      E2_err = E2_err + Area*tmp2;
      E1_rel = E1_rel + Area*tmp1_rel;
      E2_rel = E2_rel + Area*tmp2_rel;
    }
  E1_err = sqrt(E1_err/E1_rel);
  E2_err = sqrt(E2_err/E2_rel);

  // Summary
  printf("  |----------------------------\n");
  printf("  | Errors:\n");
  printf("  |----------------------------\n");
  printf("  |  phi_err = %e\n",phi_err);
  printf("  |   E1_err = %e\n",E1_err);
  printf("  |   E2_err = %e\n",E2_err);
  printf("  |----------------------------\n");
  printf("\n");
}
// All-purpose routine for computing the L2-projection
// of various functions onto the gradient of the Legendre basis
//     (Unstructured grid version)
//
void L2ProjectGrad_Unst(
    const dTensor2* vel_vec,
    const int istart, 
    const int iend, 
    const int QuadOrder, 
    const int BasisOrder_qin,
    const int BasisOrder_auxin,
    const int BasisOrder_fout,
    const mesh& Mesh, 
    const dTensor3* qin, 
    const dTensor3* auxin, 
    dTensor3* fout, 
    void (*Func)(const dTensor2* vel_vec,
        const dTensor2&,const dTensor2&,
        const dTensor2&,dTensor3&))
{
    // starting and ending indeces
    const int   NumElems = Mesh.get_NumElems();
    assert_ge(istart,1);
    assert_le(iend,NumElems);

    // qin variable
    assert_eq(NumElems,qin->getsize(1));
    const int     meqn = qin->getsize(2);
    const int kmax_qin = qin->getsize(3);
    assert_eq(kmax_qin,(BasisOrder_qin*(BasisOrder_qin+1))/2);

    // auxin variable
    assert_eq(NumElems,auxin->getsize(1));
    const int       maux = auxin->getsize(2);
    const int kmax_auxin = auxin->getsize(3);
    assert_eq(kmax_auxin,(BasisOrder_auxin*(BasisOrder_auxin+1))/2);

    // fout variables
    assert_eq(NumElems,fout->getsize(1));
    const int mcomps_out = fout->getsize(2);
    const int  kmax_fout = fout->getsize(3);
    assert_eq(kmax_fout,(BasisOrder_fout*(BasisOrder_fout+1))/2);

    // number of quadrature points
    assert_ge(QuadOrder,1);
    assert_le(QuadOrder,5);
    int mpoints;
    switch ( QuadOrder )
    {
        case 1:
            mpoints = 0;
            break;

        case 2:
            mpoints = 1;
            break;

        case 3:
            mpoints = 6;
            break;

        case 4:
            mpoints = 7;
            break;

        case 5:	     
            mpoints = 16;
            break;
    }

    // trivial case
    if ( QuadOrder==1 )
    {
        for (int i=istart; i<=iend; i++)
        for (int m=1; m<=mcomps_out; m++) 
        for (int k=1; k<=kmax_fout; k++) 
        {  fout->set(i,m,k, 0.0 );  }
    }
    else
    {
        const int kmax = iMax(iMax(kmax_qin,kmax_auxin),kmax_fout);
        dTensor2    spts(mpoints,2);
        dTensor1    wgts(mpoints);
        dTensor2    xpts(mpoints,2);
        dTensor2   qvals(mpoints,meqn);
        dTensor2 auxvals(mpoints,maux);
        dTensor3   fvals(mpoints,mcomps_out,2);
        dTensor2      mu(mpoints,kmax); // monomial basis (non-orthogonal)
        dTensor2     phi(mpoints,kmax); // Legendre basis (orthogonal)
        dTensor2   mu_xi(mpoints,kmax_fout);   //  xi-derivative of monomial basis (non-orthogonal)
        dTensor2  mu_eta(mpoints,kmax_fout);   // eta-derivative of monomial basis (non-orthogonal)
        dTensor2  phi_xi(mpoints,kmax_fout);   //  xi-derivative of Legendre basis (orthogonal)
        dTensor2 phi_eta(mpoints,kmax_fout);   // eta-derivative of Legendre basis (orthogonal)
        dTensor2   phi_x(mpoints,kmax_fout);   //   x-derivative of Legendre basis (orthogonal)
        dTensor2   phi_y(mpoints,kmax_fout);   //   y-derivative of Legendre basis (orthogonal)

        switch ( QuadOrder )
        {
            case 2:
                spts.set(1,1, 0.0 );
                spts.set(1,2, 0.0 );

                wgts.set(1, 0.5 );
                break;

            case 3:
                spts.set(1,1,  0.112615157582632 );
                spts.set(1,2,  0.112615157582632 );

                spts.set(2,1, -0.225230315165263 );
                spts.set(2,2,  0.112615157582632 );

                spts.set(3,1,  0.112615157582632 );
                spts.set(3,2, -0.225230315165263 );

                spts.set(4,1, -0.241757119823562 );
                spts.set(4,2, -0.241757119823562 );

                spts.set(5,1,  0.483514239647126 );
                spts.set(5,2, -0.241757119823562 );

                spts.set(6,1, -0.241757119823562 );
                spts.set(6,2,  0.483514239647126 );

                wgts.set(1, 0.1116907948390055 );
                wgts.set(2, 0.1116907948390055 );
                wgts.set(3, 0.1116907948390055 );
                wgts.set(4, 0.0549758718276610 );
                wgts.set(5, 0.0549758718276610 );
                wgts.set(6, 0.0549758718276610 );
                break;

            case 4:
                spts.set(1,1,   0.000000000000000 );
                spts.set(1,2,   0.000000000000000 );

                spts.set(2,1,   0.136808730771782 );
                spts.set(2,2,   0.136808730771782 );

                spts.set(3,1,  -0.273617461543563 );
                spts.set(3,2,   0.136808730771782 );

                spts.set(4,1,   0.136808730771782 );
                spts.set(4,2,  -0.273617461543563 );

                spts.set(5,1,  -0.232046826009877 );
                spts.set(5,2,  -0.232046826009877 );

                spts.set(6,1,   0.464093652019754 );
                spts.set(6,2,  -0.232046826009877 );

                spts.set(7,1,  -0.232046826009877 );
                spts.set(7,2,   0.464093652019754 );	 

                wgts.set(1,  0.1125000000000000 );
                wgts.set(2,  0.0661970763942530 );
                wgts.set(3,  0.0661970763942530 );
                wgts.set(4,  0.0661970763942530 );
                wgts.set(5,  0.0629695902724135 );
                wgts.set(6,  0.0629695902724135 );
                wgts.set(7,  0.0629695902724135 );
                break;

            case 5:
                spts.set(1,1,   0.000000000000000 );
                spts.set(1,2,   0.000000000000000 );

                spts.set(2,1,   0.125959254959390 );
                spts.set(2,2,   0.125959254959390 );

                spts.set(3,1,  -0.251918509918779 );
                spts.set(3,2,   0.125959254959390 );

                spts.set(4,1,   0.125959254959390 );
                spts.set(4,2,  -0.251918509918779 );

                spts.set(5,1,  -0.162764025581573 );
                spts.set(5,2,  -0.162764025581573 );

                spts.set(6,1,   0.325528051163147 );
                spts.set(6,2,  -0.162764025581573 );

                spts.set(7,1,  -0.162764025581573 );
                spts.set(7,2,   0.325528051163147 );

                spts.set(8,1,  -0.282786105016302 );
                spts.set(8,2,  -0.282786105016302 );

                spts.set(9,1,   0.565572210032605 );
                spts.set(9,2,  -0.282786105016302 );

                spts.set(10,1, -0.282786105016302 );
                spts.set(10,2,  0.565572210032605 );

                spts.set(11,1, -0.324938555923375 );
                spts.set(11,2, -0.070220503698695 );

                spts.set(12,1, -0.324938555923375 );
                spts.set(12,2,  0.395159059622071 );

                spts.set(13,1, -0.070220503698695 );
                spts.set(13,2, -0.324938555923375 );

                spts.set(14,1, -0.070220503698695 );
                spts.set(14,2,  0.395159059622071 );

                spts.set(15,1,  0.395159059622071 );
                spts.set(15,2, -0.324938555923375 );

                spts.set(16,1,  0.395159059622071 );
                spts.set(16,2, -0.070220503698695 );

                wgts.set(1,  0.0721578038388935 );
                wgts.set(2,  0.0475458171336425 );
                wgts.set(3,  0.0475458171336425 );
                wgts.set(4,  0.0475458171336425 );
                wgts.set(5,  0.0516086852673590 );
                wgts.set(6,  0.0516086852673590 );
                wgts.set(7,  0.0516086852673590 );
                wgts.set(8,  0.0162292488115990 );
                wgts.set(9,  0.0162292488115990 );
                wgts.set(10, 0.0162292488115990 );
                wgts.set(11, 0.0136151570872175 );
                wgts.set(12, 0.0136151570872175 );
                wgts.set(13, 0.0136151570872175 );
                wgts.set(14, 0.0136151570872175 );
                wgts.set(15, 0.0136151570872175 );
                wgts.set(16, 0.0136151570872175 );
                break;
        }

        // Loop over each quadrature point and construct monomial polys
        for (int m=1; m<=mpoints; m++)
        {
            // coordinates
            const double xi   = spts.get(m,1);      
            const double xi2  = xi*xi;
            const double xi3  = xi2*xi;
            const double xi4  = xi3*xi;
            const double eta  = spts.get(m,2);
            const double eta2 = eta*eta;
            const double eta3 = eta2*eta;
            const double eta4 = eta3*eta;      

            // monomial basis functions at each gaussian quadrature point
            switch( kmax )
            {
                case 15:  // fifth order		    		    
                    mu.set(m, 15, eta4     );
                    mu.set(m, 14, xi4      );
                    mu.set(m, 13, xi2*eta2 );
                    mu.set(m, 12, eta3*xi  );
                    mu.set(m, 11, xi3*eta  );

                case 10:  // fourth order
                    mu.set(m, 10, eta3     );
                    mu.set(m, 9,  xi3      );
                    mu.set(m, 8,  xi*eta2  );
                    mu.set(m, 7,  eta*xi2  );

                case 6:  // third order
                    mu.set(m, 6,  eta2     );
                    mu.set(m, 5,  xi2      );
                    mu.set(m, 4,  xi*eta   );		    

                case 3:  // second order		    
                    mu.set(m, 3, eta       );
                    mu.set(m, 2, xi        );

                case 1:  // first order
                    mu.set(m, 1, 1.0       );

                    break;		    
            }

            // Loop over each quadrature point and construct Legendre polys
            for (int i=1; i<=kmax; i++)
            {
                double tmp = 0.0;
                for (int j=1; j<=i; j++)
                {  tmp = tmp + Mmat[i-1][j-1]*mu.get(m,j);  }

                phi.set(m,i, tmp );
            }	

            // Gradient of monomial basis functions at each gaussian quadrature point
            switch( kmax_fout )
            {
                case 15:  // fifth order
                    mu_xi.set( m,15,  0.0         );
                    mu_xi.set( m,14,  4.0*xi3     );
                    mu_xi.set( m,13,  2.0*xi*eta2 );
                    mu_xi.set( m,12,  eta3        );
                    mu_xi.set( m,11,  3.0*xi2*eta );

                    mu_eta.set( m,15, 4.0*eta3    );
                    mu_eta.set( m,14, 0.0         );
                    mu_eta.set( m,13, 2.0*xi2*eta );
                    mu_eta.set( m,12, 3.0*eta2*xi );
                    mu_eta.set( m,11, xi3 );

                case 10:  // fourth order
                    mu_xi.set( m,10,  0.0        );
                    mu_xi.set( m,9,   3.0*xi2    );			
                    mu_xi.set( m,8,   eta2       );
                    mu_xi.set( m,7,   2.0*eta*xi );

                    mu_eta.set( m,10, 3.0*eta2   );
                    mu_eta.set( m,9,  0.0        );
                    mu_eta.set( m,8,  2.0*eta*xi );
                    mu_eta.set( m,7,  xi2        );

                case 6:  // third order
                    mu_xi.set( m,6,  0.0      );
                    mu_xi.set( m,5,  2.0*xi   );			
                    mu_xi.set( m,4,  eta      );

                    mu_eta.set( m,6,  2.0*eta );			
                    mu_eta.set( m,5,  0.0     );
                    mu_eta.set( m,4,  xi      );

                case 3:  // second order
                    mu_xi.set( m,3,  0.0 );
                    mu_xi.set( m,2,  1.0 );

                    mu_eta.set( m,3, 1.0 );
                    mu_eta.set( m,2, 0.0 );

                case 1:  // first order
                    mu_xi.set( m,1,  0.0 );

                    mu_eta.set( m,1, 0.0 );
                    break;
            }

            // Loop over each quadrature point and construct Legendre polys
            for (int i=1; i<=kmax_fout; i++)
            {
                double tmp1 = 0.0;
                double tmp2 = 0.0;
                for (int j=1; j<=i; j++)
                {  
                    tmp1 = tmp1 + Mmat[i-1][j-1]*mu_xi.get(m,j);  
                    tmp2 = tmp2 + Mmat[i-1][j-1]*mu_eta.get(m,j);
                }

                phi_xi.set(m,i,  tmp1 );
                phi_eta.set(m,i, tmp2 );
            }
        }

        // -------------------------------------------------------------
        // Loop over every grid cell indexed by user supplied parameters
        // described by istart...iend
        // -------------------------------------------------------------
#pragma omp parallel for
        for (int i=istart; i<=iend; i++)
        {	  
            // Find center of current cell
            const int i1 = Mesh.get_tnode(i,1);
            const int i2 = Mesh.get_tnode(i,2);
            const int i3 = Mesh.get_tnode(i,3);
            const double x1 = Mesh.get_node(i1,1);
            const double y1 = Mesh.get_node(i1,2);
            const double x2 = Mesh.get_node(i2,1);
            const double y2 = Mesh.get_node(i2,2);
            const double x3 = Mesh.get_node(i3,1);
            const double y3 = Mesh.get_node(i3,2);

            const double xc = (x1+x2+x3)/3.0;
            const double yc = (y1+y2+y3)/3.0;

            // Compute q, aux and fvals at each Gaussian Quadrature point
            // for this current cell indexed by (i,j)
            // Save results into dTensor2 qvals, auxvals and fvals.
            for (int m=1; m<=mpoints; m++)
            {
                // convert phi_xi and phi_eta derivatives
                // to phi_x and phi_y derivatives through Jacobian
                for (int k=1; k<=kmax_fout; k++)
                {
                    phi_x.set(m,k, Mesh.get_jmat(i,1,1)*phi_xi.get(m,k)
                            + Mesh.get_jmat(i,1,2)*phi_eta.get(m,k) );
                    phi_y.set(m,k, Mesh.get_jmat(i,2,1)*phi_xi.get(m,k)
                            + Mesh.get_jmat(i,2,2)*phi_eta.get(m,k) );
                }

                // point on the unit triangle
                const double s = spts.get(m,1);
                const double t = spts.get(m,2);

                // point on the physical triangle
                xpts.set(m,1, xc + (x2-x1)*s + (x3-x1)*t );
                xpts.set(m,2, yc + (y2-y1)*s + (y3-y1)*t );

                // Solution values (q) at each grid point
                for (int me=1; me<=meqn; me++)
                {
                    qvals.set(m,me, 0.0 );

                    for (int k=1; k<=kmax_qin; k++)
                    {
                        qvals.set(m,me, qvals.get(m,me) 
                                + phi.get(m,k) * qin->get(i,me,k) );
                    }
                }

                // Auxiliary values (aux) at each grid point
                for (int ma=1; ma<=maux; ma++)
                {
                    auxvals.set(m,ma, 0.0 );

                    for (int k=1; k<=kmax_auxin; k++)
                    {
                        auxvals.set(m,ma, auxvals.get(m,ma) 
                                + phi.get(m,k) * auxin->get(i,ma,k) );
                    }
                } 
            }

            // Call user-supplied function to set fvals
            Func(vel_vec, xpts, qvals, auxvals, fvals);

            // Evaluate integral on current cell (project onto Legendre basis) 
            // using Gaussian Quadrature for the integration
            for (int m1=1; m1<=mcomps_out; m1++)		
            for (int m2=1; m2<=kmax_fout; m2++)
            {
                double tmp = 0.0;
                for (int k=1; k<=mpoints; k++)
                {
                    tmp = tmp + wgts.get(k)*
                        ( fvals.get(k,m1,1)*phi_x.get(k,m2) +
                          fvals.get(k,m1,2)*phi_y.get(k,m2) );
                }
                fout->set(i, m1, m2,  2.0*tmp );
            }

        }
    }
}
// Right-hand side for hyperbolic PDE in divergence form
//
//       q_t = -( f(q,x,y,t)_x + g(q,x,y,t)_y ) + Psi(q,x,y,t)
//
void LaxWendroff_Unst(double dt,
    const mesh& Mesh, const edge_data_Unst& EdgeData,
    dTensor3& aux,                  // SetBndValues modifies ghost cells
    dTensor3& q,                    // SetBndValues modifies ghost cells
    dTensor3& Lstar, dTensor1& smax)
{

    const int NumElems      = Mesh.get_NumElems();
    const int NumPhysElems  = Mesh.get_NumPhysElems();
    const int NumEdges      = Mesh.get_NumEdges();
    const int meqn          = q.getsize(2);
    const int kmax          = q.getsize(3);
    const int maux          = aux.getsize(2);
    const int space_order   = dogParams.get_space_order();
    dTensor3 EdgeFluxIntegral(NumElems,meqn,kmax);
    dTensor3 ElemFluxIntegral(NumElems,meqn,kmax);
    dTensor3              Psi(NumElems,meqn,kmax);

    // ---------------------------------------------------------
    // Boundary Conditions
    SetBndValues_Unst(Mesh, &q, &aux);
    // ---------------------------------------------------------

    // --------------------------------------------------------------------- //
    // Part 0: Compute the Lax-Wendroff "flux" function:
    //
    // Here, we include the extra information about time derivatives.
    // --------------------------------------------------------------------- //
    dTensor3 F(NumElems, meqn, kmax );  F.setall(0.);
    dTensor3 G(NumElems, meqn, kmax );  G.setall(0.);
    L2ProjectLxW_Unst( dogParams.get_time_order(), 1.0, 0.5*dt, dt*dt/6.0, 1, NumElems,
        space_order, space_order, space_order, space_order, Mesh,
        &q, &aux, &F, &G, &FluxFunc, &DFluxFunc, &D2FluxFunc );

    // ---------------------------------------------------------
    // Part I: compute source term
    // --------------------------------------------------------- 
    if ( dogParams.get_source_term()>0 )
    {        
        // eprintf("error: have not implemented source term for LxW solver.");
        printf("Source term has not been implemented for LxW solver.  Terminating program.");
        exit(1);
    }
    Lstar.setall(0.);
    // ---------------------------------------------------------


    // ---------------------------------------------------------
    // Part II: compute flux integral on element edges
    // ---------------------------------------------------------

    // Loop over all interior edges
    EdgeFluxIntegral.setall(0.);
    ElemFluxIntegral.setall(0.);

#pragma omp parallel for
    // Loop over all interior edges
    for (int i=1; i<=NumEdges; i++)
    {
        // Edge coordinates
        double x1 = Mesh.get_edge(i,1);
        double y1 = Mesh.get_edge(i,2);
        double x2 = Mesh.get_edge(i,3);
        double y2 = Mesh.get_edge(i,4);

        // Elements on either side of edge
        int ileft  = Mesh.get_eelem(i,1);
        int iright = Mesh.get_eelem(i,2);  
        double Areal = Mesh.get_area_prim(ileft);
        double Arear = Mesh.get_area_prim(iright);

        // Scaled normal to edge
        dTensor1 nhat(2);      
        nhat.set(1, (y2-y1) );
        nhat.set(2, (x1-x2) );

        // Variables to store flux integrals along edge
        dTensor2 Fr_tmp(meqn,dogParams.get_space_order());
        dTensor2 Fl_tmp(meqn,dogParams.get_space_order());

        // Loop over number of quadrature points along each edge
        for (int ell=1; ell<=dogParams.get_space_order(); ell++)
        {
            dTensor1   Ql(meqn),   Qr(meqn);
            dTensor1  ffl(meqn),  ffr(meqn);  // << -- NEW PART -- >>
            dTensor1 Auxl(maux), Auxr(maux);

            // Riemann data - q
            for (int m=1; m<=meqn; m++)
            {
                Ql.set(m, 0.0 );
                Qr.set(m, 0.0 );

                // << -- NEW PART, ffl and ffr -- >> //
                ffl.set(m, 0.0 );
                ffr.set(m, 0.0 );

                for (int k=1; k<=kmax; k++)
                {
                    Ql.set(m, Ql.get(m) + EdgeData.phi_left->get(i,ell,k) 
                            *q.get(ileft, m,k) );
                    Qr.set(m, Qr.get(m) + EdgeData.phi_right->get(i,ell,k)
                            *q.get(iright,m,k) );

                    // << -- NEW PART, ffl and ffr -- >> //
                    // Is this the correct way to use the normal vector?
                    ffl.set(m, ffl.get(m) + EdgeData.phi_left->get (i, ell, k) * ( 
                        nhat.get(1)*F.get( ileft, m, k) + nhat.get(2)*G.get( ileft, m, k) ) );

                    ffr.set(m, ffr.get(m) + EdgeData.phi_right->get(i, ell, k) * (
                        nhat.get(1)*F.get(iright, m, k) + nhat.get(2)*G.get(iright, m, k) ) );

                }
            }

            // Riemann data - aux
            for (int m=1; m<=maux; m++)
            {
                Auxl.set(m, 0.0 );
                Auxr.set(m, 0.0 );

                for (int k=1; k<=kmax; k++)
                {
                    Auxl.set(m, Auxl.get(m) + EdgeData.phi_left->get(i,ell,k)  * aux.get(ileft, m,k) );
                    Auxr.set(m, Auxr.get(m) + EdgeData.phi_right->get(i,ell,k) * aux.get(iright,m,k) );
                }
            }

            // Solve Riemann problem
            dTensor1 xedge(2);
            double s = EdgeData.xpts1d->get(ell);
            xedge.set(1, x1 + 0.5*(s+1.0)*(x2-x1) );
            xedge.set(2, y1 + 0.5*(s+1.0)*(y2-y1) );

            // Solve the Riemann problem for this edge
            dTensor1 Fl(meqn), Fr(meqn);

            // Use the time-averaged fluxes to define left and right values for
            // the Riemann solver.
            const double smax_edge = RiemannSolveLxW(
                    nhat, xedge, Ql, Qr, Auxl, Auxr, ffl, ffr, Fl, Fr);
            smax.set(i, Max(smax_edge,smax.get(i)) );

            // Construct fluxes
            for (int m=1; m<=meqn; m++)
            {
                Fr_tmp.set(m,ell, Fr.get(m) );
                Fl_tmp.set(m,ell, Fl.get(m) );
            }
        }

        // Add edge integral to line integral around the full element
        for (int m=1; m<=meqn; m++)
        for (int k=1; k<=kmax; k++)
        {
            double Fl_sum = 0.0;
            double Fr_sum = 0.0;
            for (int ell=1; ell<=dogParams.get_space_order(); ell++)
            {
                Fl_sum = Fl_sum + 0.5*EdgeData.wgts1d->get(ell)
                    *EdgeData.phi_left->get(i,ell,k) *Fl_tmp.get(m,ell);
                Fr_sum = Fr_sum + 0.5*EdgeData.wgts1d->get(ell)
                    *EdgeData.phi_right->get(i,ell,k)*Fr_tmp.get(m,ell);
            }
            EdgeFluxIntegral.set(ileft, m,k, EdgeFluxIntegral.get(ileft, m,k) + Fl_sum/Areal );
            EdgeFluxIntegral.set(iright,m,k, EdgeFluxIntegral.get(iright,m,k) - Fr_sum/Arear );
        }
    }
    // ---------------------------------------------------------

    // ---------------------------------------------------------
    // Part III: compute intra-element contributions
    // ---------------------------------------------------------
    if( dogParams.get_space_order() > 1 )
    {
        L2ProjectGradAddLegendre_Unst(1, NumPhysElems, space_order, 
            Mesh, &F, &G, &ElemFluxIntegral );
    }
    // ---------------------------------------------------------

    // ---------------------------------------------------------
    // Part IV: construct Lstar
    // ---------------------------------------------------------
    if (dogParams.get_source_term()==0)  // Without Source Term
    { 
#pragma omp parallel for
        for (int i=1; i<=NumPhysElems; i++)	
        for (int m=1; m<=meqn; m++)
        for (int k=1; k<=kmax; k++)
        {
            double tmp = ElemFluxIntegral.get(i,m,k) - EdgeFluxIntegral.get(i,m,k);
            Lstar.set(i,m,k, tmp );	      
        }
    }
    else  // With Source Term
    {
#pragma omp parallel for
        for (int i=1; i<=NumPhysElems; i++)
        for (int m=1; m<=meqn; m++)
        for (int k=1; k<=kmax; k++)
        {
//          double tmp = ElemFluxIntegral.get(i,m,k) 
//              - EdgeFluxIntegral.get(i,m,k)
//              + Psi.get(i,m,k);

//          Lstar.set(i,m,k, tmp );

            printf("Source term has not been implemented for LxW solver.  Terminating program.");
            exit(1);
        }
    }
    // ---------------------------------------------------------

    // ---------------------------------------------------------
    // Part V: add extra contributions to Lstar
    // ---------------------------------------------------------
    // Call LstarExtra
    LstarExtra_Unst(Mesh, &q, &aux, &Lstar);
    // ---------------------------------------------------------

    // ---------------------------------------------------------
    // Part VI: artificial viscosity limiter
    // ---------------------------------------------------------  
//  if (dogParams.get_space_order()>1  &&
//          dogParams.using_viscosity_limiter())
//  {  ArtificialViscosity(&aux,&q,&Lstar);  }
    // ---------------------------------------------------------

}
// This file should be identical to DogSolveRK_Unst, with the exception that all
// output printing statements are silenced.
//
// Advance the solution qold to qnew over time interval tstart to tend.
//
// All local information is allocated within this function.  The only part
// that gets shared are time values passed through dogStateUnst2.  This class
// should be modified to accept the state variable, q and aux in place of only
// containing time information as is currently the case.  (-DS)
double DogSolveRK_Unst_Quiet(
    const dTensor2* vel_vec,
    const mesh& Mesh, const edge_data_Unst& EdgeData,
    dTensor3& aux, dTensor3& qold, dTensor3& qnew, 
    const double tstart, const double tend, DogStateUnst2& dogStateUnst2)
{

    const int mx   = qnew.getsize(1);
    const int meqn = qnew.getsize(2);
    const int kmax = qnew.getsize(3);
    const int maux = aux.getsize(2);
    const double* cflv = dogParams.get_cflv();
    const int nv   = dogParams.get_nv();

    RKinfo rk;
    SetRKinfo(dogParams.get_time_order(),rk);

    // define local variables
    int n_step = 0;
    double t  = tstart;
    double dt = dogStateUnst2.get_initial_dt();

    const double CFL_max    = cflv[1];
    const double CFL_target = cflv[2];
    double cfl   = 0.0;
    double dtmin = dt;
    double dtmax = dt;

    const int NumElems = Mesh.get_NumElems(); // Number of total elements in mesh
    const int NumNodes = Mesh.get_NumNodes(); // Number of nodes in mesh
    const int NumEdges = Mesh.get_NumEdges(); // Number of edges in mesh 

    dTensor3   qstar(NumElems,meqn,kmax);
    dTensor3      q1(NumElems,meqn,kmax);
    dTensor3      q2(NumElems,meqn,kmax);
    dTensor3 auxstar(NumElems,maux,kmax);
    dTensor3   Lstar(NumElems,meqn,kmax);
    dTensor3    Lold(NumElems,meqn,kmax);
    dTensor3  auxold(NumElems,maux,kmax);
    dTensor1    smax(NumEdges);

    void L2Project_Unst( 
        const dTensor2* vel_vec, 
        const int istart, const int iend,
        const int QuadOrder, const int BasisOrder_qin, const int BasisOrder_auxin, const
        int BasisOrder_fout, const mesh& Mesh, const dTensor3* qin, const dTensor3*
        auxin, dTensor3* fout, void (*Func)(const dTensor2* vel_vec, const
        dTensor2&,const dTensor2&, const dTensor2&,dTensor2&));

    // JUNK here:
    void AuxFuncWrapper(
        const dTensor2* vel_vec,
        const dTensor2& xpts,
        const dTensor2& NOT_USED_1,
        const dTensor2& NOT_USED_2,
        dTensor2& auxvals);
    const int space_order = dogParams.get_space_order();
    if( maux > 0 )
    { 
        printf("WARNING: maux = %d should be zero for Vlasov routines.", maux);
        printf("    Modify parameters.ini to remove this warning\n" );
        L2Project_Unst(vel_vec,1,NumElems,
                space_order,space_order,space_order,space_order,		       
                Mesh,&qnew,&aux,&aux,&AuxFuncWrapper);  
    }

    // Set initialize qstar and auxstar values
    qstar.copyfrom(qold);
    auxstar.copyfrom(aux);

    // Runge-Kutta time stepping
    while (t<tend)
    {
        // initialize time step
        int m_accept = 0;      
        n_step = n_step + 1;

        // check if max number of time steps exceeded
        if( n_step > nv )
        {
            eprintf(" Error in DogSolveRK_Unst.cpp: "
                    " Exceeded allowed # of time steps \n"
                    "    n_step = %d\n"
                    "        nv = %d\n\n",
                    n_step, nv);
        }

        // copy qnew into qold
        qold.copyfrom(qnew);
        auxold.copyfrom(aux);

        // keep trying until we get a dt that does not violate CFL condition
        while (m_accept==0)
        {

            // set current time
            double told = t;
            if (told+dt > tend)
            { dt = tend - told; }
            t = told + dt;

            // TODO - this needs to be performed at the 'local' level
            dogStateUnst2.set_time ( told );
            dogStateUnst2.set_dt   ( dt   );

            // Set initial maximum wave speed to zero
            smax.setall(0.);

            // Take a full time step of size dt
            switch ( dogParams.get_time_order() )
            {

                case 1:  // First order in time (1-stage)


                    // -----------------------------------------------
                    // Stage #1 (the only one in this case)  
                    rk.mstage = 1;
                    BeforeStep_Unst(dt,Mesh,aux,qnew);
                    ConstructL_Unst(told, vel_vec,Mesh,EdgeData,aux,qnew,Lstar,smax);
                    UpdateSoln_Unst(rk.alpha1->get(rk.mstage),rk.alpha2->get(rk.mstage),
                            rk.beta->get(rk.mstage),dt,Mesh,aux, qnew, Lstar, qnew);
                    AfterStep_Unst(dt,Mesh,aux,qnew);
                    // -----------------------------------------------
                    break;
  
                case 2:  // Second order in time (2-stages)

                    // -----------------------------------------------
                    // Stage #1  	        
                    rk.mstage = 1;
                    dogStateUnst2.set_time(told);
                    BeforeStep_Unst(dt,Mesh,aux,qnew);
                    ConstructL_Unst(told,vel_vec,Mesh,EdgeData,aux,qnew,Lstar,smax);
                    UpdateSoln_Unst(
                        rk.alpha1->get(rk.mstage),rk.alpha2->get(rk.mstage),
                        rk.beta->get(rk.mstage), dt, Mesh, aux, qnew, Lstar, qstar);
                    AfterStep_Unst(dt, Mesh, auxstar, qstar);

                    // ------------------------------------------------
                    // Stage #2
                    rk.mstage = 2;
                    dogStateUnst2.set_time(told+dt);
                    BeforeStep_Unst(dt, Mesh, auxstar, qstar);
                    ConstructL_Unst(told+1.0*dt, vel_vec, Mesh, EdgeData, aux, qstar, Lstar, smax);
                    UpdateSoln_Unst(rk.alpha1->get(rk.mstage), rk.alpha2->get(rk.mstage), 
                            rk.beta->get(rk.mstage), dt, Mesh, auxstar, qstar, Lstar, qnew);
                    AfterStep_Unst(dt, Mesh, aux, qnew);
                    // ------------------------------------------------
                    break;

                case 3:  // Third order in time (3-stages)

//     qnew = alpha1 * qstar + alpha2 * qnew + beta * dt * L( qstar )

// alpha1 = 1.0
// alpha2 = 0.0
// beta   = 1.0

                    // ------------------------------------------------
                    // Stage #1
                    rk.mstage = 1;
                    dogStateUnst2.set_time(told);
                    BeforeStep_Unst(dt,Mesh,aux,qnew);	      
                    ConstructL_Unst(told, vel_vec,Mesh,EdgeData,aux,qnew,Lstar,smax);
                    Lold.copyfrom(Lstar);
                    UpdateSoln_Unst(rk.alpha1->get(rk.mstage),rk.alpha2->get(rk.mstage),
                            rk.beta->get(rk.mstage),dt,Mesh,aux,qnew,Lstar,qstar);
                    AfterStep_Unst(dt,Mesh,aux,qstar);
                    // -------------------------------------------------

// alpha1 = 0.75
// alpha2 = 0.25
// beta   = 0.25

                    // Stage #2
                    rk.mstage = 2;
                    dogStateUnst2.set_time(told+0.5*dt);
                    BeforeStep_Unst(dt,Mesh,aux,qstar);
                    ConstructL_Unst(told+dt,  vel_vec,Mesh,EdgeData,aux,qstar,Lstar,smax);
                    UpdateSoln_Unst(rk.alpha1->get(rk.mstage),rk.alpha2->get(rk.mstage),
                            rk.beta->get(rk.mstage),dt,Mesh,aux,qnew,Lstar,qstar);   
                    AfterStep_Unst(dt,Mesh,aux,qstar);
                    // --------------------------------------------------

// alpha1 = 2/3
// alpha2 = 1/3
// beta   = 2/3

                    // Stage #3
                    rk.mstage = 3;
                    dogStateUnst2.set_time(told+dt);
                    BeforeStep_Unst(dt,Mesh,auxstar,qstar);
                    ConstructL_Unst(told+0.5*dt,vel_vec,Mesh,EdgeData,auxstar,qstar,Lstar,smax);
                    UpdateSoln_Unst(rk.alpha1->get(rk.mstage),rk.alpha2->get(rk.mstage),
                            rk.beta->get(rk.mstage),dt,Mesh,aux,qstar,Lstar,qnew);   
                    AfterStep_Unst(dt,Mesh,aux,qnew);
                    // --------------------------------------------------   
                    break;


                default: unsupported_value_error(dogParams.get_time_order());

            }

            // compute cfl number
            cfl = GetCFL_Unst(dt,Mesh,aux,smax);

            // output time step information
//          if (dogParams.get_verbosity()>0) 
//          {
//              printf("    In DogSolveRK_Quiet: DogSolve2D ... Step %5d"
//                      "   CFL =%6.3f"
//                      "   dt =%11.3e"
//                      "   t =%11.3e\n",
//                      n_step, cfl, dt, t);
//          }

            // choose new time step
            if (cfl>0.0)
            {   
                dt = Min(dogParams.get_max_dt(), dt*CFL_target/cfl);
                dtmin = Min(dt,dtmin);
                dtmax = Max(dt,dtmax);
            }
            else
            {
                dt = dogParams.get_max_dt();
            }

            // see whether to accept or reject this step
            if (cfl<=CFL_max)
                // accept
            { 
                m_accept = 1; 
                dogStateUnst2.set_time(t);

                // do any extra work
//              AfterFullTimeStep_Unst(dogStateUnst2.get_dt(),Mesh,
//                      auxold,qold,Lold,aux,qnew);
            }
            else 
                //reject
            {   
                t = told;
                dogStateUnst2.set_time(told);
//              if( dogParams.get_verbosity() > 0 )
//              {
//                  printf("DogSolve2D rejecting step..."
//                          "CFL number too large\n");
//              }

                // copy qold into qnew
                qnew.copyfrom(qold);
                aux.copyfrom(auxold);

                // after reject function	      
//              AfterReject_Unst(Mesh,dt,aux,qnew);
            }      
        }

    }

//  printf("    Finished!  t = %2.3e and nsteps = %d\n", t, n_step );

    // set initial time step for next call to DogSolveRK
    dogStateUnst2.set_initial_dt(dt);

    void DeleteRKInfo(RKinfo& rk);
    DeleteRKInfo(rk);

    return cfl;

}
// This routine simply glues together many of the routines that are already
// written in the Poisson solver library
//
// phi( 1:SubNumPhysNodes       ) is a scalar quantity.  
//
// E1 ( 1:NumElems, 1:kmax2d ) is a vector quantity.
// E2 ( 1:NumElems, 1:kmax2d ) is a vector quantity.
//
// See also: ConvertEfieldOntoDGbasis
void ComputeElectricField( const double t, const mesh& Mesh, const dTensorBC5& q,
    dTensor2& E1, dTensor2& E2)
{

    //
    const int       mx = q.getsize(1);   assert_eq(mx,dogParamsCart2.get_mx());
    const int       my = q.getsize(2);   assert_eq(my,dogParamsCart2.get_my());
    const int NumElems = q.getsize(3);
    const int     meqn = q.getsize(4);
    const int     kmax = q.getsize(5);

    const int space_order = dogParams.get_space_order();

    // unstructured parameters:
    const int kmax2d    = E2.getsize(2);
    const int NumBndNodes  = Mesh.get_NumBndNodes();
    const int NumPhysNodes = Mesh.get_NumPhysNodes();

    // Quick error check
    if( !Mesh.get_is_submesh() )
    {
        printf("ERROR: mesh needs to have subfactor set to %d\n", space_order);
        printf("Go to Unstructured mesh and remesh the problem\n");
        exit(-1);
    }
    const int SubFactor    = Mesh.get_SubFactor();

    assert_eq( NumElems, Mesh.get_NumElems() );

    // -- Step 1: Compute rho -- //
    dTensor3 rho(NumElems, 1, kmax2d );
    void ComputeDensity( const mesh& Mesh, const dTensorBC5& q, dTensor3& rho );
    ComputeDensity( Mesh, q, rho );

    // -- Step 2: Figure out how large phi needs to be
    int SubNumPhysNodes = 0;
    int SubNumBndNodes  = 0;
    switch( dogParams.get_space_order() )
    {
        case 1:
            SubNumPhysNodes = NumPhysNodes;
            SubNumBndNodes  = NumBndNodes;
            break;

        case 2:
            SubNumPhysNodes = Mesh.get_SubNumPhysNodes();
            SubNumBndNodes  = Mesh.get_SubNumBndNodes();
            if(SubFactor!=2)
            {
                printf("\n");
                printf(" Error: for space_order = %i, need SubFactor = %i\n",space_order,2);
                printf("      SubFactor = %i\n",SubFactor);
                printf("\n");
                exit(1);
            }
            break;

        case 3:
            SubNumPhysNodes = Mesh.get_SubNumPhysNodes();
            SubNumBndNodes  = Mesh.get_SubNumBndNodes();
            if(SubFactor!=3)
            {
                printf("\n");
                printf(" Error: for space_order = %i, need SubFactor = %i\n",space_order,3);
                printf("      SubFactor = %i\n",SubFactor);
                printf("\n");
                exit(1);
            }
            break;

        default:
            printf("\n");
            printf(" ERROR in RunDogpack_unst.cpp: space_order value not supported.\n");
            printf("       space_order = %i\n",space_order);
            printf("\n");
            exit(1);
    }

    // local storage:
    dTensor1 rhs(SubNumPhysNodes);
    dTensor1 phi(SubNumPhysNodes);

    // Get Cholesky factorization matrix R
    //
    // TODO - this should be saved earlier in the code rather than reading
    // from file every time we with to run a Poisson solve!
    //
    SparseCholesky R(SubNumPhysNodes);
    string outputdir = dogParams.get_outputdir();
    R.init(outputdir);
    R.read(outputdir);

    // Create right-hand side vector
    void Rhs2D_unst(const int space_order,
            const mesh& Mesh, const dTensor3& rhs_dg,
            dTensor1& rhs);
    Rhs2D_unst(space_order, Mesh, rho, rhs);

    // Call Poisson solver  
    void PoissonSolver2D_unst(const int space_order,
            const mesh& Mesh,
            const SparseCholesky& R,
            const dTensor1& rhs,
            dTensor1& phi,
            dTensor2& E1,
            dTensor2& E2);
    PoissonSolver2D_unst(space_order, Mesh, R, rhs, phi, E1, E2);

    // Compare errors with the exact Electric field:
    //
    void L2Project_Unst(
        const double time,
        const dTensor2* vel_vec,
        const int istart, 
        const int iend, 
        const int QuadOrder, 
        const int BasisOrder_qin,
        const int BasisOrder_auxin,
        const int BasisOrder_fout,
        const mesh& Mesh, 
        const dTensor3* qin, 
        const dTensor3* auxin, 
        dTensor3* fout, 
        void (*Func)(const double t, const dTensor2* vel_vec, const dTensor2&,const dTensor2&,
            const dTensor2&,dTensor2&));

    const int sorder = dogParams.get_space_order();
    dTensor3 qtmp   (NumElems, 2, kmax2d );  qtmp.setall(0.);
    dTensor3 auxtmp (NumElems, 0, kmax2d );
    dTensor3 ExactE (NumElems, 2, kmax2d );
    L2Project_Unst( t, NULL, 1, NumElems, 
        sorder, sorder, sorder, sorder, Mesh, 
        &qtmp, &auxtmp, &ExactE, 
        &ExactElectricField );

    // Compute errors on these two:
    //
    double err = 0.;
    for( int n=1; n <= NumElems; n++ )
    for( int k=1; k <= kmax2d;   k++ )
    {
        err += Mesh.get_area_prim(n)*pow( ExactE.get(n,1,k) - E1.get(n,k), 2 );
        err += Mesh.get_area_prim(n)*pow( ExactE.get(n,2,k) - E2.get(n,k), 2 );
    }
    printf("error = %2.15e\n", err );

}
// This is the positivity preserving limiter proposed in 
// "Maximum-Principle-Satisfying and Positivity-Preserving
// High Order Discontinuous Galerkin Schemes
// for Conservation Laws on Triangular Meshes", Zhang, Xia and Shu
// J. Sci. Comput. (2012).
//
// THIS METHOD ASSUMES THAT EVERY COMPONENT OF CONSERVED VARIABLES SHOULD STAY
// POSITIVE.
//
// In order to implement this for a different scheme, one should rewrite, or
// redefine what components should remain positiive.  This will require
// reworking the control flow logic for how time step lengths are chosen.
void ApplyPosLimiter_Unst(const mesh& Mesh, const dTensor3& aux, dTensor3& q)
{

    const int NumElems      = Mesh.get_NumElems();
    const int NumPhysElems  = Mesh.get_NumPhysElems();
    const int NumEdges      = Mesh.get_NumEdges();
    const int meqn          = q.getsize(2);
    const int kmax          = q.getsize(3);
    const int maux          = aux.getsize(2);
    const int space_order   = dogParams.get_space_order();

    // Do nothing in the case of piecewise constants
    if( space_order == 1 )
    { return; }

    // ------------------------------------------------ //
    // number of points where we want to check solution //
    // ------------------------------------------------ //
    const int space_order_sq = space_order*space_order;
    const int mpts_vec[] = {0, 3*space_order_sq, 18, 3*space_order_sq, 3*space_order_sq };  // TODO - FILL IN 2ND-ORDER CASE
    const int mpoints    = mpts_vec[space_order-1];

    // ---------------------------------------------------------- //
    // sample basis at all points where we want to check solution //
    // ---------------------------------------------------------- //
    dTensor2 spts(mpoints, 2);
    void SetPositivePoints_Unst(const int& space_order, dTensor2& spts);
    SetPositivePoints_Unst(space_order, spts);

    void SamplePhiAtPositivePoints_Unst(const int& space_order, 
            const dTensor2& spts, dTensor2& phi);
    dTensor2 phi(mpoints, kmax);
    SamplePhiAtPositivePoints_Unst(space_order, spts, phi);

    // -------------------------------------------------------------- //
    // q_limited = Q1 + \theta ( q(xi,eta) - Q1 )                     //
    // where theta = min(1, |Q1| / |Q1-m|; m = min_{i} q(xi_i, eta_i) //
    // -------------------------------------------------------------- //
#pragma omp parallel for
    for(int  i=1;  i <= NumPhysElems; i++)
    for(int me=1; me <= meqn; me++)
    {

        double m = 0.0;
        for(int mp=1; mp <= mpoints; mp++)
        {
            // evaluate q at spts(mp) //
            double qnow = 0.0;
            for( int k=1; k <= kmax; k++ )
            {
                qnow += q.get(i,me,k) * phi.get(mp,k);
            }
            m = Min(m, qnow);
        }

        double theta = 0.0;
        double Q1 = q.get(i,me,1);  assert_ge( Q1, -1e-13 );
        if( fabs( Q1 - m ) < 1.0e-14 ){ theta = 1.0; }
        else{ theta = Min( 1.0, fabs( Q1 / (Q1 - m) ) ); }

        // limit q //
        for( int k=2; k <= kmax; k++ )
        {
            q.set(i,me,k, q.get(i,me,k) * theta );
        }

    }

}
// Modified version of the all purpose routine L2Project specifically written
// for projecting the "time-averaged" flux function onto the basis function.
//
// This routine also returns the coefficients of the Lax Wendroff Flux
// Function when expanded with legendre basis functions, and therefore the
// basis expansions produced by this routine can be used for all of the
// Riemann solves.
//
// ---------------------------------------------------------------------
// Inputs should have the following sizes:   
//           TODO - document the inputs here
// ---------------------------------------------------------------------
void L2ProjectLxW_Unst( const int mterms,
        const double alpha, const double beta_dt, const double charlie_dt,
        const int istart, const int iend,               // Start-stop indices
        const int QuadOrder,
        const int BasisOrder_qin,
        const int BasisOrder_auxin,
        const int BasisOrder_fout,
        const mesh& Mesh, 
        const dTensor3* qin, const dTensor3* auxin,     // state vector
        dTensor3* F, dTensor3* G,                       // time-averaged Flux function
        void FluxFunc (const dTensor2& xpts, 
            const dTensor2& Q, const dTensor2& Aux, dTensor3& flux),
        void DFluxFunc (const dTensor2& xpts, 
            const dTensor2& Q, const dTensor2& aux, dTensor4& Dflux),
        void D2FluxFunc (const dTensor2& xpts, 
            const dTensor2& Q, const dTensor2& aux, dTensor5& D2flux) )
{    

    if( fabs( alpha ) < 1e-14 && fabs( beta_dt ) < 1e-14 && fabs( charlie_dt ) < 1e-14 )
    {
        F->setall(0.);
        G->setall(0.);
        return;
    }

    // starting and ending indices 
    const int   NumElems = Mesh.get_NumElems();
    assert_ge(istart,1);
    assert_le(iend,NumElems);

    // qin variable
    assert_eq(NumElems,qin->getsize(1));
    const int     meqn = qin->getsize(2);
    const int kmax_qin = qin->getsize(3);
    assert_eq(kmax_qin,(BasisOrder_qin*(BasisOrder_qin+1))/2);

    // auxin variable
    assert_eq(NumElems,auxin->getsize(1));
    const int       maux = auxin->getsize(2);
    const int kmax_auxin = auxin->getsize(3);
    assert_eq(kmax_auxin,(BasisOrder_auxin*(BasisOrder_auxin+1))/2);

    // fout variables
    assert_eq(NumElems,    F->getsize(1));
    const int mcomps_out = F->getsize(2);
    const int  kmax_fout = F->getsize(3);
    assert_eq(kmax_fout, (BasisOrder_fout*(BasisOrder_fout+1))/2 );

    // number of quadrature points
    assert_ge(QuadOrder, 1);
    assert_le(QuadOrder, 5);

    // Number of quadrature points
    int mpoints;
    switch( QuadOrder )
    {
        case 1:
            mpoints = 1;
            break;

        case 2:
            mpoints = 3;
            break;

        case 3:
            mpoints = 6;
            break;

        case 4:
            mpoints = 12;
            break;

        case 5:	     
            mpoints = 16;
            break;
    }

    const int kmax = iMax(iMax(kmax_qin, kmax_auxin), kmax_fout);
    dTensor2  phi(mpoints, kmax); // Legendre basis (orthogonal)
    dTensor2 spts(mpoints, 2);    // List of quadrature points
    dTensor1 wgts(mpoints);       // List of quadrature weights

    setQuadPoints_Unst( QuadOrder, wgts, spts );

    // ---------------------------------------------------------------------- //
    // Evaluate the basis functions at each point
    SetLegendreAtPoints_Unst(spts, phi);
    // ---------------------------------------------------------------------- //

    // ---------------------------------------------------------------------- //
    // First-order derivatives
    dTensor2 phi_xi (mpoints, kmax );
    dTensor2 phi_eta(mpoints, kmax );
    SetLegendreGrad_Unst( spts, phi_xi, phi_eta );
    // ---------------------------------------------------------------------- //

    // ---------------------------------------------------------------------- //
    // Second-order derivatives
    dTensor2 phi_xi2  (mpoints, kmax );
    dTensor2 phi_xieta(mpoints, kmax );
    dTensor2 phi_eta2 (mpoints, kmax );
    LegendreDiff2_Unst(spts, &phi_xi2, &phi_xieta, &phi_eta2 );
    // ---------------------------------------------------------------------- //

    // ------------------------------------------------------------- //
    // Loop over every grid cell indexed by user supplied parameters //
    // described by istart...iend, jstart...jend                     // 
    // ------------------------------------------------------------- //
#pragma omp parallel for
    for (int i=istart; i<=iend; i++)
    {

        // These need to be defined locally.  Each mesh element carries its
        // own change of basis matrix, so these need to be recomputed for
        // each element.  The canonical derivatives, phi_xi, and phi_eta can
        // be computed and shared for each element.

        // First-order derivatives
        dTensor2   phi_x(mpoints, kmax_fout);   //   x-derivative of Legendre basis (orthogonal)
        dTensor2   phi_y(mpoints, kmax_fout);   //   y-derivative of Legendre basis (orthogonal)

        // Second-order derivatives
        dTensor2   phi_xx(mpoints, kmax_fout);   //   xx-derivative of Legendre basis (orthogonal)
        dTensor2   phi_xy(mpoints, kmax_fout);   //   xy-derivative of Legendre basis (orthogonal)
        dTensor2   phi_yy(mpoints, kmax_fout);   //   yy-derivative of Legendre basis (orthogonal)

        //find center of current cell
        const int    i1 = Mesh.get_tnode(i,1);
        const int    i2 = Mesh.get_tnode(i,2);
        const int    i3 = Mesh.get_tnode(i,3);

        // Corners:
        const double x1 = Mesh.get_node(i1,1);
        const double y1 = Mesh.get_node(i1,2);
        const double x2 = Mesh.get_node(i2,1);
        const double y2 = Mesh.get_node(i2,2);
        const double x3 = Mesh.get_node(i3,1);
        const double y3 = Mesh.get_node(i3,2);

        // Center of current cell:
        const double xc = (x1+x2+x3)/3.0;
        const double yc = (y1+y2+y3)/3.0;

        // Variables that need to be written to, and therefore are 
        // created for each thread
        dTensor2 xpts   (mpoints, 2);
        dTensor2 qvals  (mpoints, meqn);
        dTensor2 auxvals(mpoints, maux);

        // local storage for Flux function its Jacobian, and the Hessian:
        dTensor3    fvals(mpoints,             meqn, 2);  // flux function (vector)
        dTensor4        A(mpoints,       meqn, meqn, 2);  // Jacobian of flux
        dTensor5        H(mpoints, meqn, meqn, meqn, 2);  // Hessian of flux

        // Compute q, aux and fvals at each Gaussian Quadrature point
        // for this current cell indexed by (i,j)
        // Save results into dTensor2 qvals, auxvals and fvals.
        for (int m=1; m<= mpoints; m++)
        {

            // convert phi_xi and phi_eta derivatives
            // to phi_x and phi_y derivatives through Jacobian
            //
            // Note that: 
            //
            //     pd_x = J11 pd_xi + J12 pd_eta and
            //     pd_y = J21 pd_xi + J22 pd_eta.
            //
            // Squaring these operators yields the second derivatives.
            for (int k=1; k<=kmax_fout; k++)
            {
                phi_x.set(m,k, Mesh.get_jmat(i,1,1)*phi_xi.get(m,k)
                             + Mesh.get_jmat(i,1,2)*phi_eta.get(m,k) );
                phi_y.set(m,k, Mesh.get_jmat(i,2,1)*phi_xi.get(m,k)
                             + Mesh.get_jmat(i,2,2)*phi_eta.get(m,k) );

                phi_xx.set(m,k, Mesh.get_jmat(i,1,1)*Mesh.get_jmat(i,1,1)*phi_xi2.get(m,k)
                              + Mesh.get_jmat(i,1,1)*Mesh.get_jmat(i,1,2)*phi_xieta.get(m,k)
                              + Mesh.get_jmat(i,1,2)*Mesh.get_jmat(i,1,2)*phi_eta2.get(m,k)
                           );

                phi_xy.set(m,k, Mesh.get_jmat(i,1,1)*Mesh.get_jmat(i,2,1)*phi_xi2.get(m,k)
                             +(Mesh.get_jmat(i,1,2)*Mesh.get_jmat(i,2,1)
                             + Mesh.get_jmat(i,1,1)*Mesh.get_jmat(i,2,2))*phi_xieta.get(m,k)
                             + Mesh.get_jmat(i,1,2)*Mesh.get_jmat(i,2,2)*phi_eta2.get(m,k)
                           );

                phi_yy.set(m,k, Mesh.get_jmat(i,2,1)*Mesh.get_jmat(i,2,1)*phi_xi2.get(m,k)
                              + Mesh.get_jmat(i,2,1)*Mesh.get_jmat(i,2,2)*phi_xieta.get(m,k)
                              + Mesh.get_jmat(i,2,2)*Mesh.get_jmat(i,2,2)*phi_eta2.get(m,k)
                           );
            }

            // point on the unit triangle
            const double s = spts.get(m,1);
            const double t = spts.get(m,2);

            // point on the physical triangle
            xpts.set(m,1, xc + (x2-x1)*s + (x3-x1)*t );
            xpts.set(m,2, yc + (y2-y1)*s + (y3-y1)*t );

            // Solution values (q) at each grid point
            for (int me=1; me<=meqn; me++)
            {
                qvals.set(m,me, 0.0 );
                for (int k=1; k<=kmax_qin; k++)
                {
                    qvals.set(m,me, qvals.get(m,me) 
                            + phi.get(m,k) * qin->get(i,me,k) );
                }
            }

            // Auxiliary values (aux) at each grid point
            for (int ma=1; ma<=maux; ma++)
            {
                auxvals.set(m,ma, 0.0 );
                for (int k=1; k<=kmax_auxin; k++)
                {
                    auxvals.set(m,ma, auxvals.get(m,ma) 
                            + phi.get(m,k) * auxin->get(i,ma,k) );
                }
            } 
        }

        // ----------------------------------------------------------------- //
        //
        // Part I:
        //
        // Project the flux function onto the basis 
        // functions.  This is the term of order O( 1 ) in the
        // "time-averaged" Taylor expansion of f and g.
        //
        // ----------------------------------------------------------------- //

        // Call user-supplied function to set fvals
        FluxFunc(xpts, qvals, auxvals, fvals);

        // Evaluate integral on current cell (project onto Legendre basis) 
        // using Gaussian Quadrature for the integration
        //
        // TODO - do we want to optimize this by looking into using transposes,
        // as has been done in the 2d/cart code? (5/14/2014) -DS
        for (int me=1; me<=mcomps_out; me++)		
        for (int k=1; k<=kmax; k++)
        {
            double tmp1 = 0.0;
            double tmp2 = 0.0;
            for (int mp=1; mp <= mpoints; mp++)
            {
                tmp1 += wgts.get(mp)*fvals.get(mp, me, 1)*phi.get(mp, k);
                tmp2 += wgts.get(mp)*fvals.get(mp, me, 2)*phi.get(mp, k);
            }
            F->set(i, me, k,  2.0*tmp1 );
            G->set(i, me, k,  2.0*tmp2 );
        }

        // ----------------------------------------------------------------- //
        //
        // Part II:
        //
        // Project the derivative of the flux function onto the basis 
        // functions.  This is the term of order O( \dt ) in the
        // "time-averaged" Taylor expansion of f and g.
        //
        // ----------------------------------------------------------------- //

        // ----------------------------------------------------------------- //
        // Compute pointwise values for fx+gy:
        //
        // We can't multiply fvals of f, and g,
        // by alpha, otherwise we compute the wrong derivative here!
        //
        dTensor2 fx_plus_gy( mpoints, meqn ); fx_plus_gy.setall(0.);
        for( int mp=1; mp <= mpoints; mp++ )
        for( int me=1; me <= meqn; me++ )
        {
            double tmp = 0.;
            for( int k=2; k <= kmax; k++ )                
            {
                tmp += F->get( i, me, k ) * phi_x.get( mp, k );
                tmp += G->get( i, me, k ) * phi_y.get( mp, k );
            }
            fx_plus_gy.set( mp, me, tmp );
        }

        // Call user-supplied Jacobian to set f'(q) and g'(q):
        DFluxFunc( xpts, qvals, auxvals, A );

        // place-holders for point values of
        // f'(q)( fx + gy ) and g'(q)( fx + gy ):
        dTensor2 dt_times_fdot( mpoints, meqn );
        dTensor2 dt_times_gdot( mpoints, meqn );

        // Compute point values for f'(q) * (fx+gy) and g'(q) * (fx+gy):
        for( int mp=1; mp <= mpoints; mp++ )
        for( int m1=1; m1 <= meqn; m1++ )
        {
            double tmp1 = 0.;
            double tmp2 = 0.;
            for( int m2=1; m2 <= meqn; m2++ )
            {
                tmp1 += A.get(mp, m1, m2, 1 ) * fx_plus_gy.get(mp, m2);
                tmp2 += A.get(mp, m1, m2, 2 ) * fx_plus_gy.get(mp, m2);
            }
            dt_times_fdot.set( mp, m1, -beta_dt*tmp1 );
            dt_times_gdot.set( mp, m1, -beta_dt*tmp2 );
        }

        // ---  Third-order terms --- //
        //
        // These are the terms that are O( \dt^2 ) in the "time-averaged"
        // flux function.
        dTensor2 f_tt( mpoints, meqn );   f_tt.setall(0.);
        dTensor2 g_tt( mpoints, meqn );   g_tt.setall(0.);
        if( mterms > 2 )
        {

            // Construct the Hessian at each (quadrature) point
            D2FluxFunc( xpts, qvals, auxvals, H );

            // Second-order derivative terms
            dTensor2 qx_vals (mpoints, meqn);   qx_vals.setall(0.);
            dTensor2 qy_vals (mpoints, meqn);   qy_vals.setall(0.);

            dTensor2 fxx_vals(mpoints, meqn);   fxx_vals.setall(0.);
            dTensor2 gxx_vals(mpoints, meqn);   gxx_vals.setall(0.);

            dTensor2 fxy_vals(mpoints, meqn);   fxy_vals.setall(0.);
            dTensor2 gxy_vals(mpoints, meqn);   gxy_vals.setall(0.);

            dTensor2 fyy_vals(mpoints, meqn);   fyy_vals.setall(0.);
            dTensor2 gyy_vals(mpoints, meqn);   gyy_vals.setall(0.);

            for( int m=1; m <= mpoints; m++ )
            for( int me=1; me <= meqn; me++ )
            {
                // Can start at k=1, because derivative of a constant is
                // zero.
                double tmp_qx = 0.;
                double tmp_qy = 0.;
                for( int  k=2; k <= kmax; k++   )
                {
                    tmp_qx += phi_x.get(m,k) * qin->get(i,me,k);
                    tmp_qy += phi_y.get(m,k) * qin->get(i,me,k);
                }
                qx_vals.set(m,me, tmp_qx );
                qy_vals.set(m,me, tmp_qy );

                // First non-zero terms start at third-order.
                for( int  k=4; k <= kmax; k++   )
                {
                    fxx_vals.set(m,me, fxx_vals.get(m,me) + phi_xx.get(m,k)*F->get(i,me,k) );
                    gxx_vals.set(m,me, gxx_vals.get(m,me) + phi_xx.get(m,k)*G->get(i,me,k) );

                    fxy_vals.set(m,me, fxy_vals.get(m,me) + phi_xy.get(m,k)*F->get(i,me,k) );
                    gxy_vals.set(m,me, gxy_vals.get(m,me) + phi_xy.get(m,k)*G->get(i,me,k) );

                    fyy_vals.set(m,me, fyy_vals.get(m,me) + phi_yy.get(m,k)*F->get(i,me,k) );
                    gyy_vals.set(m,me, gyy_vals.get(m,me) + phi_yy.get(m,k)*G->get(i,me,k) );
                }

            }

            // ----------------------------------- //
            // Part I: Compute (f_x + g_y)_{,t}
            // ----------------------------------- //

            // Compute terms that get multiplied by \pd2{ f }{ q } and \pd2{ g }{ q }.
            dTensor2 fx_plus_gy_t( mpoints, meqn );
            for( int  m = 1;  m <= mpoints; m++ )
            for( int me = 1; me <= meqn; me++   )
            {

                double tmp = 0.;

                // Terms that get multiplied by the Hessian:
                for( int m1=1; m1 <= meqn; m1++ )
                for( int m2=1; m2 <= meqn; m2++ )
                {

                    tmp += H.get(m,me,m1,m2,1)*qx_vals.get(m,m1)*fx_plus_gy.get(m,m2);
                    tmp += H.get(m,me,m1,m2,2)*qy_vals.get(m,m1)*fx_plus_gy.get(m,m2);
                }

                // Terms that get multiplied by f'(q) and g'(q):
                for( int m1=1; m1 <= meqn; m1++ )
                {

                    tmp += A.get(m,me,m1,1)*( fxx_vals.get(m,m1)+gxy_vals.get(m,m1) );
                    tmp += A.get(m,me,m1,2)*( fxy_vals.get(m,m1)+gyy_vals.get(m,m1) );
                }

                fx_plus_gy_t.set( m, me, tmp );
            }

            // ----------------------------------- //
            // Part II: Compute 
            //      f'(q) * fx_plus_gy_t and 
            //      g'(q) * fx_plus_gy_t
            // ----------------------------------- //

            // Add in the third term that gets multiplied by A:
            for( int m=1; m <= mpoints; m++ )
            for( int m1=1; m1 <= meqn; m1++ )
            {
                double tmp1 = 0.;
                double tmp2 = 0.;
                for( int m2=1; m2 <= meqn; m2++ )
                {
                    tmp1 += A.get(m,m1,m2,1)*fx_plus_gy_t.get(m,m2);
                    tmp2 += A.get(m,m1,m2,2)*fx_plus_gy_t.get(m,m2);
                }
                f_tt.set( m, m1, tmp1 );
                g_tt.set( m, m1, tmp2 );
            }

            // ----------------------------------------------- //
            // Part III: Add in contributions from
            //      f''(q) * (fx_plus_gy, fx_plus_gy ) and 
            //      g''(q) * (fx_plus_gy, fx_plus_gy ).
            // ----------------------------------------------- //
            for( int m =1; m <= mpoints; m++ )
            for( int me =1; me <= meqn; me++ )
            {
                double tmp1 = 0.;
                double tmp2 = 0.;

                // Terms that get multiplied by the Hessian:
                for( int m1=1; m1 <= meqn; m1++ )
                for( int m2=1; m2 <= meqn; m2++ )
                {
                    tmp1 += H.get(m,me,m1,m2,1)*fx_plus_gy.get(m,m1)*fx_plus_gy.get(m,m2);
                    tmp2 += H.get(m,me,m1,m2,2)*fx_plus_gy.get(m,m1)*fx_plus_gy.get(m,m2);
                }

                f_tt.set( m, me, f_tt.get(m,me) + tmp1 );
                g_tt.set( m, me, g_tt.get(m,me) + tmp2 );
            }

        } // End of computing "third"-order terms

        // ---------------------------------------------------------- //
        // 
        // Construct basis coefficients (integrate_on_current_cell)
        //
        // ---------------------------------------------------------- //
        for (int me=1; me<=mcomps_out; me++)		
        for (int k=1; k<=kmax; k++)
        {

            double tmp1 = 0.0;
            double tmp2 = 0.0;
            for (int mp=1; mp<=mpoints; mp++)
            {
                tmp1 += wgts.get(mp)*phi.get(mp,k)*(
                    dt_times_fdot.get(mp, me) + charlie_dt*f_tt.get(mp, me) );
                tmp2 += wgts.get(mp)*phi.get(mp,k)*(
                    dt_times_gdot.get(mp, me) + charlie_dt*g_tt.get(mp, me) );
            }
            F->set(i,me,k,  F->get(i,me,k) + 2.0*tmp1 );
            G->set(i,me,k,  G->get(i,me,k) + 2.0*tmp2 );

        }

    }

}
void ConstructL_Unst(
    const double t,
    const dTensor2* vel_vec,
    const mesh& Mesh,
    const edge_data_Unst& EdgeData,
    dTensor3& aux, // SetBndValues_Unst modifies ghost cells
    dTensor3& q,   // SetBndValues_Unst modifies ghost cells
    dTensor3& Lstar, 
    dTensor1& smax)
{

    const int NumElems      = Mesh.get_NumElems();
    const int NumPhysElems  = Mesh.get_NumPhysElems();
    const int NumEdges      = Mesh.get_NumEdges();
    const int meqn          = q.getsize(2);
    const int kmax          = q.getsize(3);
    const int maux          = aux.getsize(2);
    const int space_order   = dogParams.get_space_order();

    dTensor3 EdgeFluxIntegral(NumElems,meqn,kmax);
    dTensor3 ElemFluxIntegral(NumElems,meqn,kmax);
    dTensor3              Psi(NumElems,meqn,kmax);


    // ---------------------------------------------------------
    // Boundary Conditions
    SetBndValues_Unst(Mesh,&q,&aux);  
    
    // Positivity limiter
    void ApplyPosLimiter_Unst(const mesh& Mesh, const dTensor3& aux, dTensor3& q);
    if( dogParams.using_moment_limiter() )
    { ApplyPosLimiter_Unst(Mesh, aux, q); }
    // ---------------------------------------------------------

    // ---------------------------------------------------------
    // Part I: compute flux integral on element edges
    // ---------------------------------------------------------

    // Loop over all interior edges and solve Riemann problems
    // dTensor1 nvec(2);

    // Loop over all interior edges
    EdgeFluxIntegral.setall(0.);
    ElemFluxIntegral.setall(0.);

    // Loop over all interior edges
#pragma omp parallel for
    for (int i=1; i<=NumEdges; i++)
    {
        // Edge coordinates
        double x1 = Mesh.get_edge(i,1);
        double y1 = Mesh.get_edge(i,2);
        double x2 = Mesh.get_edge(i,3);
        double y2 = Mesh.get_edge(i,4);

        // Elements on either side of edge
        int ileft  = Mesh.get_eelem(i,1);
        int iright = Mesh.get_eelem(i,2);  
        double Areal = Mesh.get_area_prim(ileft);
        double Arear = Mesh.get_area_prim(iright);

        // Scaled normal to edge
        dTensor1 nhat(2);      
        nhat.set(1, (y2-y1) );
        nhat.set(2, (x1-x2) );

        // Variables to store flux integrals along edge
        dTensor2 Fr_tmp(meqn,dogParams.get_space_order());
        dTensor2 Fl_tmp(meqn,dogParams.get_space_order());

        // Loop over number of quadrature points along each edge
        for (int ell=1; ell<=dogParams.get_space_order(); ell++)
        {
            dTensor1 Ql(meqn),Qr(meqn);
            dTensor1 Auxl(maux),Auxr(maux);	  

            // Riemann data - q
            for (int m=1; m<=meqn; m++)
            {
                Ql.set(m, 0.0 );
                Qr.set(m, 0.0 );

                for (int k=1; k<=kmax; k++)
                {
                    Ql.set(m, Ql.get(m) + EdgeData.phi_left->get(i,ell,k) 
                            *q.get(ileft, m,k) );
                    Qr.set(m, Qr.get(m) + EdgeData.phi_right->get(i,ell,k)
                            *q.get(iright,m,k) );
                }

            }


            // Riemann data - aux
            for (int m=1; m<=maux; m++)
            {
                Auxl.set(m, 0.0 );
                Auxr.set(m, 0.0 );

                for (int k=1; k<=kmax; k++)
                {
                    Auxl.set(m, Auxl.get(m) + EdgeData.phi_left->get(i,ell,k)
                            *aux.get(ileft, m,k) );
                    Auxr.set(m, Auxr.get(m) + EdgeData.phi_right->get(i,ell,k)
                            *aux.get(iright,m,k) );
                }
            }

            // Solve Riemann problem
            dTensor1 xedge(2);
            double s = EdgeData.xpts1d->get(ell);
            xedge.set(1, x1 + 0.5*(s+1.0)*(x2-x1) );
            xedge.set(2, y1 + 0.5*(s+1.0)*(y2-y1) );
            dTensor1 Fl(meqn),Fr(meqn);
            const double smax_edge = RiemannSolve(vel_vec, nhat, xedge, Ql, Qr, Auxl, Auxr, Fl, Fr);
            smax.set(i, Max(smax_edge,smax.get(i)) );

            // Construct fluxes
            for (int m=1; m<=meqn; m++)
            {
                Fr_tmp.set(m,ell, Fr.get(m) );
                Fl_tmp.set(m,ell, Fl.get(m) );
            }
        }

        // Add edge integral to line integral around the full element
        for (int m=1; m<=meqn; m++)
        for (int k=1; k<=kmax; k++)
        {
            double Fl_sum = 0.0;
            double Fr_sum = 0.0;
            for (int ell=1; ell<=dogParams.get_space_order(); ell++)
            {
                Fl_sum = Fl_sum + 0.5*EdgeData.wgts1d->get(ell)
                    *EdgeData.phi_left->get(i,ell,k) *Fl_tmp.get(m,ell);
                Fr_sum = Fr_sum + 0.5*EdgeData.wgts1d->get(ell)
                    *EdgeData.phi_right->get(i,ell,k)*Fr_tmp.get(m,ell);
            }
            EdgeFluxIntegral.set(ileft, m,k, EdgeFluxIntegral.get(ileft, m,k) + Fl_sum/Areal );
            EdgeFluxIntegral.set(iright,m,k, EdgeFluxIntegral.get(iright,m,k) - Fr_sum/Arear );
        }
    }
    // ---------------------------------------------------------

    // ---------------------------------------------------------
    // Part II: compute intra-element contributions
    // ---------------------------------------------------------
    L2ProjectGrad_Unst(vel_vec, 1,NumPhysElems,
            space_order,space_order,space_order,space_order,
            Mesh,&q,&aux,&ElemFluxIntegral,&FluxFunc);
    // ---------------------------------------------------------

    // ---------------------------------------------------------
    // Part III: compute source term
    // --------------------------------------------------------- 
    if ( dogParams.get_source_term()>0 )
    {        
        // Set source term on computational grid
        // Set values and apply L2-projection
        L2Project_Unst(t, vel_vec, 1,NumPhysElems,
                space_order,space_order,space_order,space_order,
                Mesh,&q,&aux,&Psi,&SourceTermFunc);
    }
    // ---------------------------------------------------------

    // ---------------------------------------------------------
    // Part IV: construct Lstar
    // ---------------------------------------------------------
    if (dogParams.get_source_term()==0)  // Without Source Term
    { 
#pragma omp parallel for
        for (int i=1; i<=NumPhysElems; i++)	
        for (int m=1; m<=meqn; m++)
        for (int k=1; k<=kmax; k++)
        {
            double tmp = ElemFluxIntegral.get(i,m,k) - EdgeFluxIntegral.get(i,m,k);
            Lstar.set(i,m,k, tmp );	      
        }
    }
    else  // With Source Term
    {
#pragma omp parallel for
        for (int i=1; i<=NumPhysElems; i++)
        for (int m=1; m<=meqn; m++)
        for (int k=1; k<=kmax; k++)
        {
            double tmp = ElemFluxIntegral.get(i,m,k) 
                - EdgeFluxIntegral.get(i,m,k)
                + Psi.get(i,m,k);

            Lstar.set(i,m,k, tmp );
        }
    }
    // ---------------------------------------------------------

    // ---------------------------------------------------------
    // Part V: add extra contributions to Lstar
    // ---------------------------------------------------------
    // Call LstarExtra
    LstarExtra_Unst(Mesh,&q,&aux,&Lstar);
    // ---------------------------------------------------------

}