///////////////////////////////////////////////////////////////////////////////
//
// File: DiffusionLFR.cpp
//
// For more information, please see: http://www.nektar.info
//
// The MIT License
//
// Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
// Department of Aeronautics, Imperial College London (UK), and Scientific
// Computing and Imaging Institute, University of Utah (USA).
//
// License for the specific language governing rights and limitations under
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
// Description: LFR diffusion class.
//
///////////////////////////////////////////////////////////////////////////////

#include <LocalRegions/Expansion1D.h>
#include <LocalRegions/Expansion2D.h>
#include <SolverUtils/Diffusion/DiffusionLFR.h>
#include <LibUtilities/Polylib/Polylib.h>
#include <MultiRegions/DisContField1D.h>
#include <boost/math/special_functions/gamma.hpp>
#include <iostream>
#include <iomanip>

using namespace std;

namespace Nektar
{
    namespace SolverUtils
    {
        std::string DiffusionLFR::type[] = {
            GetDiffusionFactory().RegisterCreatorFunction(
                                        "LFRDG", DiffusionLFR::create), 
            GetDiffusionFactory().RegisterCreatorFunction(
                                        "LFRSD", DiffusionLFR::create), 
            GetDiffusionFactory().RegisterCreatorFunction(
                                        "LFRHU", DiffusionLFR::create),
            GetDiffusionFactory().RegisterCreatorFunction(
                                        "LFRcmin", DiffusionLFR::create),
            GetDiffusionFactory().RegisterCreatorFunction(
                                        "LFRcinf", DiffusionLFR::create)};
        
        /**
         * @brief DiffusionLFR uses the Flux Reconstruction (FR) approach to 
         * compute the diffusion term. The implementation is only for segments,
         * quadrilaterals and hexahedra at the moment.
         * 
         * \todo Extension to triangles, tetrahedra and other shapes. 
         * (Long term objective) 
         */
        DiffusionLFR::DiffusionLFR(std::string diffType):m_diffType(diffType)
        {
        }
        
        /**
         * @brief Initiliase DiffusionLFR objects and store them before starting 
         * the time-stepping.
         * 
         * This routine calls the virtual functions #v_SetupMetrics and
         * #v_SetupCFunctions to initialise the objects needed by DiffusionLFR. 
         * 
         * @param pSession  Pointer to session reader.
         * @param pFields   Pointer to fields.
         */
        void DiffusionLFR::v_InitObject(
            LibUtilities::SessionReaderSharedPtr        pSession,
            Array<OneD, MultiRegions::ExpListSharedPtr> pFields)
        {
            m_session = pSession;
            v_SetupMetrics(pSession, pFields);
            v_SetupCFunctions(pSession, pFields);
            
            // Initialising arrays
            int i, j;
            int nConvectiveFields = pFields.num_elements();
            int nDim         = pFields[0]->GetCoordim(0);
            int nSolutionPts = pFields[0]->GetTotPoints();
            int nTracePts    = pFields[0]->GetTrace()->GetTotPoints();
            
            m_IF1 = Array<OneD, Array<OneD, Array<OneD, NekDouble> > > (
                                                            nConvectiveFields);
            m_DU1 = Array<OneD, Array<OneD, Array<OneD, NekDouble> > > (
                                                            nConvectiveFields);
            m_DFC1 = Array<OneD, Array<OneD, Array<OneD, NekDouble> > > (
                                                            nConvectiveFields);
            m_BD1 = Array<OneD, Array<OneD, Array<OneD, NekDouble> > > (
                                                            nConvectiveFields);
            m_D1 = Array<OneD, Array<OneD, Array<OneD, NekDouble> > > (
                                                            nConvectiveFields);
            m_DD1 = Array<OneD, Array<OneD, Array<OneD, NekDouble> > > (
                                                            nConvectiveFields);
            m_IF2 = Array<OneD, Array<OneD, NekDouble> >   (nConvectiveFields);
            m_DFC2 = Array<OneD, Array<OneD, Array<OneD, NekDouble> > > (
                                                            nConvectiveFields);
            m_divFD = Array<OneD, Array<OneD, NekDouble> > (nConvectiveFields);
            m_divFC = Array<OneD, Array<OneD, NekDouble> > (nConvectiveFields);
            
            m_tmp1 = Array<OneD, Array<OneD, Array<OneD, NekDouble> > > (
                                                            nConvectiveFields);
            m_tmp2 = Array<OneD, Array<OneD, Array<OneD, NekDouble> > > (
                                                            nConvectiveFields);
            

            
            for (i = 0; i < nConvectiveFields; ++i)
            {
                m_IF1[i]  = Array<OneD, Array<OneD, NekDouble> >(nDim);
                m_DU1[i]  = Array<OneD, Array<OneD, NekDouble> >(nDim);                
                m_DFC1[i] = Array<OneD, Array<OneD, NekDouble> >(nDim);
                m_BD1[i]  = Array<OneD, Array<OneD, NekDouble> >(nDim);
                m_D1[i]   = Array<OneD, Array<OneD, NekDouble> >(nDim);
                m_DD1[i]  = Array<OneD, Array<OneD, NekDouble> >(nDim);
                m_IF2[i]  = Array<OneD, NekDouble>(nTracePts, 0.0);
                m_DFC2[i] = Array<OneD, Array<OneD, NekDouble> >(nDim);
                m_divFD[i]  = Array<OneD, NekDouble>(nSolutionPts, 0.0);
                m_divFC[i]  = Array<OneD, NekDouble>(nSolutionPts, 0.0);

                m_tmp1[i] = Array<OneD, Array<OneD, NekDouble> >(nDim);
                m_tmp2[i] = Array<OneD, Array<OneD, NekDouble> >(nDim);

                for (j = 0; j < nDim; ++j)
                {
                    m_IF1[i][j]  = Array<OneD, NekDouble>(nTracePts, 0.0);
                    m_DU1[i][j]  = Array<OneD, NekDouble>(nSolutionPts, 0.0);
                    m_DFC1[i][j] = Array<OneD, NekDouble>(nSolutionPts, 0.0);
                    m_BD1[i][j] = Array<OneD, NekDouble>(nSolutionPts, 0.0);
                    m_D1[i][j]   = Array<OneD, NekDouble>(nSolutionPts, 0.0);
                    m_DD1[i][j]  = Array<OneD, NekDouble>(nSolutionPts, 0.0);
                    m_DFC2[i][j] = Array<OneD, NekDouble>(nSolutionPts, 0.0);
                    
                    m_tmp1[i][j]  = Array<OneD, NekDouble>(nSolutionPts, 0.0);
                    m_tmp2[i][j]  = Array<OneD, NekDouble>(nSolutionPts, 0.0);
                }
            }

        }
        
        /**
         * @brief Setup the metric terms to compute the contravariant 
         * fluxes. (i.e. this special metric terms transform the fluxes
         * at the interfaces of each element from the physical space to 
         * the standard space).
         * 
         * This routine calls the function #GetEdgeQFactors to compute and 
         * store the metric factors following an anticlockwise conventions
         * along the edges/faces of each element. Note: for 1D problem 
         * the transformation is not needed.
         * 
         * @param pSession  Pointer to session reader.
         * @param pFields   Pointer to fields.
         *
         * \todo Add the metric terms for 3D Hexahedra.
         */
        void DiffusionLFR::v_SetupMetrics(
            LibUtilities::SessionReaderSharedPtr        pSession,
            Array<OneD, MultiRegions::ExpListSharedPtr> pFields)
        {
            int i, n;
            int nquad0, nquad1;
            int phys_offset;
            int nLocalSolutionPts;
            int nElements    = pFields[0]->GetExpSize();            
            int nDimensions  = pFields[0]->GetCoordim(0);
            int nSolutionPts = pFields[0]->GetTotPoints();
            int nTracePts    = pFields[0]->GetTrace()->GetTotPoints();
            
            m_traceNormals = Array<OneD, Array<OneD, NekDouble> >(nDimensions);
            for (i = 0; i < nDimensions; ++i)
            {
                m_traceNormals[i] = Array<OneD, NekDouble> (nTracePts);
            }
            pFields[0]->GetTrace()->GetNormals(m_traceNormals);
            
            Array<TwoD, const NekDouble> gmat;
            Array<OneD, const NekDouble> jac;
            
            m_jac  = Array<OneD, NekDouble>(nSolutionPts);
            
            Array<OneD, NekDouble> auxArray1;
            Array<OneD, LibUtilities::BasisSharedPtr> base;
            LibUtilities::PointsKeyVector ptsKeys;
            
            switch (nDimensions)
            {
                case 1:
                {
                    for (n = 0; n < nElements; ++n) 
                    {
                        ptsKeys = pFields[0]->GetExp(n)->GetPointsKeys();
                        nLocalSolutionPts = pFields[0]->GetExp(n)->GetTotPoints();
                        phys_offset = pFields[0]->GetPhys_Offset(n);
                        jac = pFields[0]->GetExp(n)
                                ->as<LocalRegions::Expansion1D>()->GetGeom1D()
                                ->GetMetricInfo()->GetJac(ptsKeys);
                        for (i = 0; i < nLocalSolutionPts; ++i)
                        {
                            m_jac[i+phys_offset] = jac[0];
                        }
                    }
                    break;
                }
                case 2:
                {
                    m_gmat = Array<OneD, Array<OneD, NekDouble> >(4);
                    m_gmat[0] = Array<OneD, NekDouble>(nSolutionPts);
                    m_gmat[1] = Array<OneD, NekDouble>(nSolutionPts);
                    m_gmat[2] = Array<OneD, NekDouble>(nSolutionPts);
                    m_gmat[3] = Array<OneD, NekDouble>(nSolutionPts);
                    
                    m_Q2D_e0 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    m_Q2D_e1 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    m_Q2D_e2 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    m_Q2D_e3 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    
                    for (n = 0; n < nElements; ++n)
                    {
                        base        = pFields[0]->GetExp(n)->GetBase();
                        nquad0      = base[0]->GetNumPoints();
                        nquad1      = base[1]->GetNumPoints();
                        
                        m_Q2D_e0[n] = Array<OneD, NekDouble>(nquad0);
                        m_Q2D_e1[n] = Array<OneD, NekDouble>(nquad1);
                        m_Q2D_e2[n] = Array<OneD, NekDouble>(nquad0);
                        m_Q2D_e3[n] = Array<OneD, NekDouble>(nquad1);
                        
                        // Extract the Q factors at each edge point
                        pFields[0]->GetExp(n)->GetEdgeQFactors(
                            0, auxArray1 = m_Q2D_e0[n]);
                        pFields[0]->GetExp(n)->GetEdgeQFactors(
                            1, auxArray1 = m_Q2D_e1[n]);
                        pFields[0]->GetExp(n)->GetEdgeQFactors(
                            2, auxArray1 = m_Q2D_e2[n]);
                        pFields[0]->GetExp(n)->GetEdgeQFactors(
                            3, auxArray1 = m_Q2D_e3[n]);
                        
                        ptsKeys = pFields[0]->GetExp(n)->GetPointsKeys();
                        nLocalSolutionPts = pFields[0]->GetExp(n)->GetTotPoints();
                        phys_offset = pFields[0]->GetPhys_Offset(n);
                        
                        jac  = pFields[0]->GetExp(n)
                                ->as<LocalRegions::Expansion2D>()->GetGeom2D()
                                ->GetMetricInfo()->GetJac(ptsKeys);
                        gmat = pFields[0]->GetExp(n)
                                ->as<LocalRegions::Expansion2D>()->GetGeom2D()
                                ->GetMetricInfo()->GetDerivFactors(ptsKeys);
                        
                        if (pFields[0]->GetExp(n)
                                ->as<LocalRegions::Expansion2D>()->GetGeom2D()
                                ->GetMetricInfo()->GetGtype()
                            == SpatialDomains::eDeformed)
                        {
                            for (i = 0; i < nLocalSolutionPts; ++i)
                            {
                                m_jac[i+phys_offset]     = jac[i];
                                m_gmat[0][i+phys_offset] = gmat[0][i];
                                m_gmat[1][i+phys_offset] = gmat[1][i];
                                m_gmat[2][i+phys_offset] = gmat[2][i];
                                m_gmat[3][i+phys_offset] = gmat[3][i];
                            }
                        }
                        else
                        {
                            for (i = 0; i < nLocalSolutionPts; ++i)
                            {
                                m_jac[i+phys_offset]     = jac[0];
                                m_gmat[0][i+phys_offset] = gmat[0][0];
                                m_gmat[1][i+phys_offset] = gmat[1][0];
                                m_gmat[2][i+phys_offset] = gmat[2][0];
                                m_gmat[3][i+phys_offset] = gmat[3][0];
                            }  
                        }
                    }
                    break;
                }
                case 3:
                {
                    ASSERTL0(false,"3DFR Metric terms not implemented yet");
                    break;
                }      
                default:
                {
                    ASSERTL0(false, "Expansion dimension not recognised");
                    break;
                }
            }
        }
        
        /**
         * @brief Setup the derivatives of the correction functions. For more 
         * details see J Sci Comput (2011) 47: 50–72
         * 
         * This routine calls 3 different bases: 
         *      #eDG_DG_Left - #eDG_DG_Left which recovers a nodal DG scheme,
         *      #eDG_SD_Left - #eDG_SD_Left which recovers the SD scheme,
         *      #eDG_HU_Left - #eDG_HU_Left which recovers the Huynh scheme.
         * The values of the derivatives of the correction function are then 
         * stored into global variables and reused into the virtual functions 
         * #v_DivCFlux_1D, #v_DivCFlux_2D, #v_DivCFlux_3D to compute the
         * the divergence of the correction flux for 1D, 2D or 3D problems. 
         *
         * @param pSession  Pointer to session reader.
         * @param pFields   Pointer to fields.
         */
        void DiffusionLFR::v_SetupCFunctions(
            LibUtilities::SessionReaderSharedPtr        pSession,
            Array<OneD, MultiRegions::ExpListSharedPtr> pFields)
        {        
            int i, n;
            NekDouble c0 = 0.0;
            NekDouble c1 = 0.0;
            NekDouble c2 = 0.0;
            int nquad0, nquad1, nquad2;
            int nmodes0, nmodes1, nmodes2;
            Array<OneD, LibUtilities::BasisSharedPtr> base;
            
            int nElements = pFields[0]->GetExpSize();            
            int nDim      = pFields[0]->GetCoordim(0);
            
            switch (nDim)
            {
                case 1:
                {
                    m_dGL_xi1 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    m_dGR_xi1 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    
                    for (n = 0; n < nElements; ++n)
                    {
                        base    = pFields[0]->GetExp(n)->GetBase();
                        nquad0  = base[0]->GetNumPoints();
                        nmodes0 = base[0]->GetNumModes();
                        Array<OneD, const NekDouble> z0;
                        Array<OneD, const NekDouble> w0;
                        
                        base[0]->GetZW(z0, w0);
                        
                        m_dGL_xi1[n] = Array<OneD, NekDouble>(nquad0);
                        m_dGR_xi1[n] = Array<OneD, NekDouble>(nquad0);
                        
                        // Auxiliary vectors to build up the auxiliary 
                        // derivatives of the Legendre polynomials
                        Array<OneD,NekDouble> dLp0   (nquad0, 0.0);
                        Array<OneD,NekDouble> dLpp0  (nquad0, 0.0);
                        Array<OneD,NekDouble> dLpm0  (nquad0, 0.0);
                        
                        // Degree of the correction functions
                        int p0 = nmodes0 - 1;
                        
                        // Function sign to compute  left correction function
                        NekDouble sign0 = pow(-1.0, p0);
                        
                        // Factorial factor to build the scheme
                        NekDouble ap0 = boost::math::tgamma(2 * p0 + 1) 
                        / (pow(2.0, p0) 
                           * boost::math::tgamma(p0 + 1) 
                           * boost::math::tgamma(p0 + 1));
                        
                        // Scalar parameter which recovers the FR schemes
                        if (m_diffType == "LFRDG")
                        {
                            c0 = 0.0;
                        }
                        else if (m_diffType == "LFRSD")
                        {
                            c0 = 2.0 * p0 / ((2.0 * p0 + 1.0) * (p0 + 1.0) 
                                        * (ap0 * boost::math::tgamma(p0 + 1)) 
                                        * (ap0 * boost::math::tgamma(p0 + 1)));
                        }
                        else if (m_diffType == "LFRHU")
                        {
                            c0 = 2.0 * (p0 + 1.0) / ((2.0 * p0 + 1.0) * p0 
                                        * (ap0 * boost::math::tgamma(p0 + 1)) 
                                        * (ap0 * boost::math::tgamma(p0 + 1)));
                        }
                        else if (m_diffType == "LFRcmin")
                        {
                            c0 = -2.0 / ((2.0 * p0 + 1.0) 
                                        * (ap0 * boost::math::tgamma(p0 + 1))
                                        * (ap0 * boost::math::tgamma(p0 + 1)));
                        }
                        else if (m_diffType == "LFRinf")
                        {
                            c0 = 10000000000000000.0;
                        }
                        
                        NekDouble etap0 = 0.5 * c0 * (2.0 * p0 + 1.0) 
                        * (ap0 * boost::math::tgamma(p0 + 1)) 
                        * (ap0 * boost::math::tgamma(p0 + 1));
                        
                        NekDouble overeta0 = 1.0 / (1.0 + etap0);
                        
                        // Derivative of the Legendre polynomials
                        // dLp  = derivative of the Legendre polynomial of order p
                        // dLpp = derivative of the Legendre polynomial of order p+1
                        // dLpm = derivative of the Legendre polynomial of order p-1
                        Polylib::jacobd(nquad0, z0.data(), &(dLp0[0]),  p0,   0.0, 0.0);
                        Polylib::jacobd(nquad0, z0.data(), &(dLpp0[0]), p0+1, 0.0, 0.0);
                        Polylib::jacobd(nquad0, z0.data(), &(dLpm0[0]), p0-1, 0.0, 0.0);
                        
                        // Building the DG_c_Left
                        for(i = 0; i < nquad0; ++i)
                        {
                            m_dGL_xi1[n][i]  = etap0 * dLpm0[i];
                            m_dGL_xi1[n][i] += dLpp0[i];
                            m_dGL_xi1[n][i] *= overeta0;
                            m_dGL_xi1[n][i]  = dLp0[i] - m_dGL_xi1[n][i];
                            m_dGL_xi1[n][i]  = 0.5 * sign0 * m_dGL_xi1[n][i];
                        }
                        
                        // Building the DG_c_Right
                        for(i = 0; i < nquad0; ++i)
                        {
                            m_dGR_xi1[n][i]  = etap0 * dLpm0[i];
                            m_dGR_xi1[n][i] += dLpp0[i];
                            m_dGR_xi1[n][i] *= overeta0;
                            m_dGR_xi1[n][i] += dLp0[i];
                            m_dGR_xi1[n][i]  = 0.5 * m_dGR_xi1[n][i];
                        }
                    }
                    break;
                }   
                case 2:
                {
                    LibUtilities::BasisSharedPtr BasisFR_Left0;
                    LibUtilities::BasisSharedPtr BasisFR_Right0;
                    LibUtilities::BasisSharedPtr BasisFR_Left1;
                    LibUtilities::BasisSharedPtr BasisFR_Right1;
                    
                    m_dGL_xi1 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    m_dGR_xi1 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    m_dGL_xi2 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    m_dGR_xi2 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    
                    for (n = 0; n < nElements; ++n)
                    {
                        base      = pFields[0]->GetExp(n)->GetBase();
                        nquad0    = base[0]->GetNumPoints();
                        nquad1    = base[1]->GetNumPoints();
                        nmodes0   = base[0]->GetNumModes();
                        nmodes1   = base[1]->GetNumModes(); 
                        
                        Array<OneD, const NekDouble> z0;
                        Array<OneD, const NekDouble> w0;   
                        Array<OneD, const NekDouble> z1;
                        Array<OneD, const NekDouble> w1;
                        
                        base[0]->GetZW(z0, w0);
                        base[1]->GetZW(z1, w1);
                        
                        m_dGL_xi1[n] = Array<OneD, NekDouble>(nquad0);
                        m_dGR_xi1[n] = Array<OneD, NekDouble>(nquad0);
                        m_dGL_xi2[n] = Array<OneD, NekDouble>(nquad1);
                        m_dGR_xi2[n] = Array<OneD, NekDouble>(nquad1);
                        
                        // Auxiliary vectors to build up the auxiliary 
                        // derivatives of the Legendre polynomials
                        Array<OneD,NekDouble> dLp0   (nquad0, 0.0);
                        Array<OneD,NekDouble> dLpp0  (nquad0, 0.0);
                        Array<OneD,NekDouble> dLpm0  (nquad0, 0.0);
                        Array<OneD,NekDouble> dLp1   (nquad1, 0.0);
                        Array<OneD,NekDouble> dLpp1  (nquad1, 0.0);
                        Array<OneD,NekDouble> dLpm1  (nquad1, 0.0);
                        
                        // Degree of the correction functions
                        int p0 = nmodes0 - 1;
                        int p1 = nmodes1 - 1;
                        
                        // Function sign to compute  left correction function
                        NekDouble sign0 = pow(-1.0, p0);                        
                        NekDouble sign1 = pow(-1.0, p1);
                        
                        // Factorial factor to build the scheme
                        NekDouble ap0 = boost::math::tgamma(2 * p0 + 1) 
                        / (pow(2.0, p0) 
                           * boost::math::tgamma(p0 + 1) 
                           * boost::math::tgamma(p0 + 1));
                        
                        NekDouble ap1 = boost::math::tgamma(2 * p1 + 1) 
                        / (pow(2.0, p1) 
                           * boost::math::tgamma(p1 + 1) 
                           * boost::math::tgamma(p1 + 1));
                        
                        // Scalar parameter which recovers the FR schemes
                        if (m_diffType == "LFRDG")
                        {
                            c0 = 0.0;
                            c1 = 0.0;
                        }
                        else if (m_diffType == "LFRSD")
                        {
                            c0 = 2.0 * p0 / ((2.0 * p0 + 1.0) * (p0 + 1.0) 
                                        * (ap0 * boost::math::tgamma(p0 + 1)) 
                                        * (ap0 * boost::math::tgamma(p0 + 1)));
                            
                            c1 = 2.0 * p1 / ((2.0 * p1 + 1.0) * (p1 + 1.0) 
                                        * (ap1 * boost::math::tgamma(p1 + 1)) 
                                        * (ap1 * boost::math::tgamma(p1 + 1)));
                        }
                        else if (m_diffType == "LFRHU")
                        {
                            c0 = 2.0 * (p0 + 1.0) / ((2.0 * p0 + 1.0) * p0 
                                        * (ap0 * boost::math::tgamma(p0 + 1)) 
                                        * (ap0 * boost::math::tgamma(p0 + 1)));
                            
                            c1 = 2.0 * (p1 + 1.0) / ((2.0 * p1 + 1.0) * p1 
                                        * (ap1 * boost::math::tgamma(p1 + 1)) 
                                        * (ap1 * boost::math::tgamma(p1 + 1)));
                        }
                        else if (m_diffType == "LFRcmin")
                        {
                            c0 = -2.0 / ((2.0 * p0 + 1.0) 
                                        * (ap0 * boost::math::tgamma(p0 + 1))
                                        * (ap0 * boost::math::tgamma(p0 + 1)));
                            
                            c1 = -2.0 / ((2.0 * p1 + 1.0) 
                                        * (ap1 * boost::math::tgamma(p1 + 1))
                                        * (ap1 * boost::math::tgamma(p1 + 1)));
                        }
                        else if (m_diffType == "LFRinf")
                        {
                            c0 = 10000000000000000.0;
                            c1 = 10000000000000000.0;
                        }
                        
                        NekDouble etap0 = 0.5 * c0 * (2.0 * p0 + 1.0) 
                        * (ap0 * boost::math::tgamma(p0 + 1)) 
                        * (ap0 * boost::math::tgamma(p0 + 1));
                        
                        NekDouble etap1 = 0.5 * c1 * (2.0 * p1 + 1.0) 
                        * (ap1 * boost::math::tgamma(p1 + 1)) 
                        * (ap1 * boost::math::tgamma(p1 + 1));
                        
                        NekDouble overeta0 = 1.0 / (1.0 + etap0);
                        NekDouble overeta1 = 1.0 / (1.0 + etap1);
                        
                        // Derivative of the Legendre polynomials
                        // dLp  = derivative of the Legendre polynomial of order p
                        // dLpp = derivative of the Legendre polynomial of order p+1
                        // dLpm = derivative of the Legendre polynomial of order p-1
                        Polylib::jacobd(nquad0, z0.data(), &(dLp0[0]),  p0,   0.0, 0.0);
                        Polylib::jacobd(nquad0, z0.data(), &(dLpp0[0]), p0+1, 0.0, 0.0);
                        Polylib::jacobd(nquad0, z0.data(), &(dLpm0[0]), p0-1, 0.0, 0.0);
                        
                        Polylib::jacobd(nquad1, z1.data(), &(dLp1[0]),  p1,   0.0, 0.0);
                        Polylib::jacobd(nquad1, z1.data(), &(dLpp1[0]), p1+1, 0.0, 0.0);
                        Polylib::jacobd(nquad1, z1.data(), &(dLpm1[0]), p1-1, 0.0, 0.0);
                        
                        // Building the DG_c_Left
                        for(i = 0; i < nquad0; ++i)
                        {
                            m_dGL_xi1[n][i]  = etap0 * dLpm0[i];
                            m_dGL_xi1[n][i] += dLpp0[i];
                            m_dGL_xi1[n][i] *= overeta0;
                            m_dGL_xi1[n][i]  = dLp0[i] - m_dGL_xi1[n][i];
                            m_dGL_xi1[n][i]  = 0.5 * sign0 * m_dGL_xi1[n][i];
                        }
                        
                        // Building the DG_c_Left
                        for(i = 0; i < nquad1; ++i)
                        {
                            m_dGL_xi2[n][i]  = etap1 * dLpm1[i];
                            m_dGL_xi2[n][i] += dLpp1[i];
                            m_dGL_xi2[n][i] *= overeta1;
                            m_dGL_xi2[n][i]  = dLp1[i] - m_dGL_xi2[n][i];
                            m_dGL_xi2[n][i]  = 0.5 * sign1 * m_dGL_xi2[n][i];
                        }
                        
                        // Building the DG_c_Right
                        for(i = 0; i < nquad0; ++i)
                        {
                            m_dGR_xi1[n][i]  = etap0 * dLpm0[i];
                            m_dGR_xi1[n][i] += dLpp0[i];
                            m_dGR_xi1[n][i] *= overeta0;
                            m_dGR_xi1[n][i] += dLp0[i];
                            m_dGR_xi1[n][i]  = 0.5 * m_dGR_xi1[n][i];
                        }
                        
                        // Building the DG_c_Right
                        for(i = 0; i < nquad1; ++i)
                        {
                            m_dGR_xi2[n][i]  = etap1 * dLpm1[i];
                            m_dGR_xi2[n][i] += dLpp1[i];
                            m_dGR_xi2[n][i] *= overeta1;
                            m_dGR_xi2[n][i] += dLp1[i];
                            m_dGR_xi2[n][i]  = 0.5 * m_dGR_xi2[n][i];
                        }
                    }
                    break;
                }
                case 3:
                {                    
                    m_dGL_xi1 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    m_dGR_xi1 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    m_dGL_xi2 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    m_dGR_xi2 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    m_dGL_xi3 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    m_dGR_xi3 = Array<OneD, Array<OneD, NekDouble> >(nElements);
                    
                    for (n = 0; n < nElements; ++n)
                    {
                        base      = pFields[0]->GetExp(n)->GetBase();
                        nquad0    = base[0]->GetNumPoints();
                        nquad1    = base[1]->GetNumPoints();
                        nquad2    = base[2]->GetNumPoints();
                        nmodes0   = base[0]->GetNumModes();
                        nmodes1   = base[1]->GetNumModes();
                        nmodes2   = base[2]->GetNumModes();
                        
                        Array<OneD, const NekDouble> z0;
                        Array<OneD, const NekDouble> w0;   
                        Array<OneD, const NekDouble> z1;
                        Array<OneD, const NekDouble> w1;
                        Array<OneD, const NekDouble> z2;
                        Array<OneD, const NekDouble> w2;
                        
                        base[0]->GetZW(z0, w0);
                        base[1]->GetZW(z1, w1);
                        base[1]->GetZW(z2, w2);
                        
                        m_dGL_xi1[n] = Array<OneD, NekDouble>(nquad0);
                        m_dGR_xi1[n] = Array<OneD, NekDouble>(nquad0);
                        m_dGL_xi2[n] = Array<OneD, NekDouble>(nquad1);
                        m_dGR_xi2[n] = Array<OneD, NekDouble>(nquad1);
                        m_dGL_xi3[n] = Array<OneD, NekDouble>(nquad2);
                        m_dGR_xi3[n] = Array<OneD, NekDouble>(nquad2);
                        
                        // Auxiliary vectors to build up the auxiliary 
                        // derivatives of the Legendre polynomials
                        Array<OneD,NekDouble> dLp0   (nquad0, 0.0);
                        Array<OneD,NekDouble> dLpp0  (nquad0, 0.0);
                        Array<OneD,NekDouble> dLpm0  (nquad0, 0.0);
                        Array<OneD,NekDouble> dLp1   (nquad1, 0.0);
                        Array<OneD,NekDouble> dLpp1  (nquad1, 0.0);
                        Array<OneD,NekDouble> dLpm1  (nquad1, 0.0);
                        Array<OneD,NekDouble> dLp2   (nquad2, 0.0);
                        Array<OneD,NekDouble> dLpp2  (nquad2, 0.0);
                        Array<OneD,NekDouble> dLpm2  (nquad2, 0.0);
                        
                        // Degree of the correction functions
                        int p0 = nmodes0 - 1;
                        int p1 = nmodes1 - 1;
                        int p2 = nmodes2 - 1;
                        
                        // Function sign to compute  left correction function
                        NekDouble sign0 = pow(-1.0, p0);
                        NekDouble sign1 = pow(-1.0, p1);
                        NekDouble sign2 = pow(-1.0, p2);
                        
                        // Factorial factor to build the scheme
                        NekDouble ap0 = boost::math::tgamma(2 * p0 + 1) 
                        / (pow(2.0, p0) 
                           * boost::math::tgamma(p0 + 1) 
                           * boost::math::tgamma(p0 + 1));
                        
                        // Factorial factor to build the scheme
                        NekDouble ap1 = boost::math::tgamma(2 * p1 + 1) 
                        / (pow(2.0, p1) 
                           * boost::math::tgamma(p1 + 1) 
                           * boost::math::tgamma(p1 + 1));
                        
                        // Factorial factor to build the scheme
                        NekDouble ap2 = boost::math::tgamma(2 * p2 + 1) 
                        / (pow(2.0, p2) 
                           * boost::math::tgamma(p2 + 1) 
                           * boost::math::tgamma(p2 + 1));
                        
                        // Scalar parameter which recovers the FR schemes
                        if (m_diffType == "LFRDG")
                        {
                            c0 = 0.0;
                            c1 = 0.0;
                            c2 = 0.0;
                        }
                        else if (m_diffType == "LFRSD")
                        {
                            c0 = 2.0 * p0 / ((2.0 * p0 + 1.0) * (p0 + 1.0) 
                                        * (ap0 * boost::math::tgamma(p0 + 1)) 
                                        * (ap0 * boost::math::tgamma(p0 + 1)));
                            
                            c1 = 2.0 * p1 / ((2.0 * p1 + 1.0) * (p1 + 1.0) 
                                        * (ap1 * boost::math::tgamma(p1 + 1)) 
                                        * (ap1 * boost::math::tgamma(p1 + 1)));
                            
                            c2 = 2.0 * p2 / ((2.0 * p2 + 1.0) * (p2 + 1.0) 
                                        * (ap2 * boost::math::tgamma(p2 + 1)) 
                                        * (ap2 * boost::math::tgamma(p2 + 1)));
                        }
                        else if (m_diffType == "LFRHU")
                        {
                            c0 = 2.0 * (p0 + 1.0) / ((2.0 * p0 + 1.0) * p0 
                                        * (ap0 * boost::math::tgamma(p0 + 1)) 
                                        * (ap0 * boost::math::tgamma(p0 + 1)));
                            
                            c1 = 2.0 * (p1 + 1.0) / ((2.0 * p1 + 1.0) * p1 
                                        * (ap1 * boost::math::tgamma(p1 + 1)) 
                                        * (ap1 * boost::math::tgamma(p1 + 1)));
                            
                            c2 = 2.0 * (p2 + 1.0) / ((2.0 * p2 + 1.0) * p2 
                                        * (ap2 * boost::math::tgamma(p2 + 1)) 
                                        * (ap2 * boost::math::tgamma(p2 + 1)));
                        }
                        else if (m_diffType == "LFRcmin")
                        {
                            c0 = -2.0 / ((2.0 * p0 + 1.0) 
                                        * (ap0 * boost::math::tgamma(p0 + 1))
                                        * (ap0 * boost::math::tgamma(p0 + 1)));
                            
                            c1 = -2.0 / ((2.0 * p1 + 1.0) 
                                        * (ap1 * boost::math::tgamma(p1 + 1))
                                        * (ap1 * boost::math::tgamma(p1 + 1)));
                            
                            c2 = -2.0 / ((2.0 * p2 + 1.0) 
                                        * (ap2 * boost::math::tgamma(p2 + 1))
                                        * (ap2 * boost::math::tgamma(p2 + 1)));
                        }
                        else if (m_diffType == "LFRinf")
                        {
                            c0 = 10000000000000000.0;
                            c1 = 10000000000000000.0;
                            c2 = 10000000000000000.0;
                        }
                        
                        NekDouble etap0 = 0.5 * c0 * (2.0 * p0 + 1.0) 
                        * (ap0 * boost::math::tgamma(p0 + 1)) 
                        * (ap0 * boost::math::tgamma(p0 + 1));
                        
                        NekDouble etap1 = 0.5 * c1 * (2.0 * p1 + 1.0) 
                        * (ap1 * boost::math::tgamma(p1 + 1)) 
                        * (ap1 * boost::math::tgamma(p1 + 1));
                        
                        NekDouble etap2 = 0.5 * c2 * (2.0 * p2 + 1.0) 
                        * (ap2 * boost::math::tgamma(p2 + 1)) 
                        * (ap2 * boost::math::tgamma(p2 + 1));
                        
                        NekDouble overeta0 = 1.0 / (1.0 + etap0);
                        NekDouble overeta1 = 1.0 / (1.0 + etap1);
                        NekDouble overeta2 = 1.0 / (1.0 + etap2);
                        
                        // Derivative of the Legendre polynomials
                        // dLp  = derivative of the Legendre polynomial of order p
                        // dLpp = derivative of the Legendre polynomial of order p+1
                        // dLpm = derivative of the Legendre polynomial of order p-1
                        Polylib::jacobd(nquad0, z0.data(), &(dLp0[0]),  p0,   0.0, 0.0);
                        Polylib::jacobd(nquad0, z0.data(), &(dLpp0[0]), p0+1, 0.0, 0.0);
                        Polylib::jacobd(nquad0, z0.data(), &(dLpm0[0]), p0-1, 0.0, 0.0);
                        
                        Polylib::jacobd(nquad1, z1.data(), &(dLp1[0]),  p1,   0.0, 0.0);
                        Polylib::jacobd(nquad1, z1.data(), &(dLpp1[0]), p1+1, 0.0, 0.0);
                        Polylib::jacobd(nquad1, z1.data(), &(dLpm1[0]), p1-1, 0.0, 0.0);
                        
                        Polylib::jacobd(nquad2, z2.data(), &(dLp2[0]),  p2,   0.0, 0.0);
                        Polylib::jacobd(nquad2, z2.data(), &(dLpp2[0]), p2+1, 0.0, 0.0);
                        Polylib::jacobd(nquad2, z2.data(), &(dLpm2[0]), p2-1, 0.0, 0.0);
                        
                        // Building the DG_c_Left
                        for(i = 0; i < nquad0; ++i)
                        {
                            m_dGL_xi1[n][i]  = etap0 * dLpm0[i];
                            m_dGL_xi1[n][i] += dLpp0[i];
                            m_dGL_xi1[n][i] *= overeta0;
                            m_dGL_xi1[n][i]  = dLp0[i] - m_dGL_xi1[n][i];
                            m_dGL_xi1[n][i]  = 0.5 * sign0 * m_dGL_xi1[n][i];
                        }
                        
                        // Building the DG_c_Left
                        for(i = 0; i < nquad1; ++i)
                        {
                            m_dGL_xi2[n][i]  = etap1 * dLpm1[i];
                            m_dGL_xi2[n][i] += dLpp1[i];
                            m_dGL_xi2[n][i] *= overeta1;
                            m_dGL_xi2[n][i]  = dLp1[i] - m_dGL_xi2[n][i];
                            m_dGL_xi2[n][i]  = 0.5 * sign1 * m_dGL_xi2[n][i];
                        }
                        
                        // Building the DG_c_Left
                        for(i = 0; i < nquad2; ++i)
                        {
                            m_dGL_xi3[n][i]  = etap2 * dLpm2[i];
                            m_dGL_xi3[n][i] += dLpp2[i];
                            m_dGL_xi3[n][i] *= overeta2;
                            m_dGL_xi3[n][i]  = dLp2[i] - m_dGL_xi3[n][i];
                            m_dGL_xi3[n][i]  = 0.5 * sign2 * m_dGL_xi3[n][i];
                        }
                        
                        // Building the DG_c_Right
                        for(i = 0; i < nquad0; ++i)
                        {
                            m_dGR_xi1[n][i]  = etap0 * dLpm0[i];
                            m_dGR_xi1[n][i] += dLpp0[i];
                            m_dGR_xi1[n][i] *= overeta0;
                            m_dGR_xi1[n][i] += dLp0[i];
                            m_dGR_xi1[n][i]  = 0.5 * m_dGR_xi1[n][i];
                        }
                        
                        // Building the DG_c_Right
                        for(i = 0; i < nquad1; ++i)
                        {
                            m_dGR_xi2[n][i]  = etap1 * dLpm1[i];
                            m_dGR_xi2[n][i] += dLpp1[i];
                            m_dGR_xi2[n][i] *= overeta1;
                            m_dGR_xi2[n][i] += dLp1[i];
                            m_dGR_xi2[n][i]  = 0.5 * m_dGR_xi2[n][i];
                        }
                        
                        // Building the DG_c_Right
                        for(i = 0; i < nquad2; ++i)
                        {
                            m_dGR_xi3[n][i]  = etap2 * dLpm2[i];
                            m_dGR_xi3[n][i] += dLpp2[i];
                            m_dGR_xi3[n][i] *= overeta2;
                            m_dGR_xi3[n][i] += dLp2[i];
                            m_dGR_xi3[n][i]  = 0.5 * m_dGR_xi3[n][i];
                        }
                    }
                    break;
                }
                default:
                {
                    ASSERTL0(false,"Expansion dimension not recognised");
                    break;
                }
            }
        }
        
        /**
         * @brief Calculate FR Diffusion for the linear problems
         * using an LDG interface flux.
         *
         */
        void DiffusionLFR::v_Diffuse(
            const int                                         nConvectiveFields,
            const Array<OneD, MultiRegions::ExpListSharedPtr> &fields,
            const Array<OneD, Array<OneD, NekDouble> >        &inarray,
                  Array<OneD, Array<OneD, NekDouble> >        &outarray)
        {
            int i, j, n;
            int phys_offset;
            //Array<TwoD, const NekDouble> gmat;
            //Array<OneD, const NekDouble> jac;
            Array<OneD,       NekDouble> auxArray1, auxArray2;
                        
            Array<OneD, LibUtilities::BasisSharedPtr> Basis;
            Basis = fields[0]->GetExp(0)->GetBase();
            
            int nElements    = fields[0]->GetExpSize();            
            int nDim         = fields[0]->GetCoordim(0);                        
            int nSolutionPts = fields[0]->GetTotPoints();
            int nCoeffs      = fields[0]->GetNcoeffs();
            
            Array<OneD, Array<OneD, NekDouble> > outarrayCoeff(nConvectiveFields);
            for (i = 0; i < nConvectiveFields; ++i)
            {
                outarrayCoeff[i]  = Array<OneD, NekDouble>(nCoeffs);
            }
                   
            // Compute interface numerical fluxes for inarray in physical space 
            v_NumFluxforScalar(fields, inarray, m_IF1);

            switch(nDim)
            {
                // 1D problems 
                case 1:
                {   
                    for (i = 0; i < nConvectiveFields; ++i)
                    {
                        // Computing the physical first-order discountinuous 
                        // derivative
                        for (n = 0; n < nElements; n++)
                        {
                            phys_offset = fields[0]->GetPhys_Offset(n);
                            
                            fields[i]->GetExp(n)->PhysDeriv(0, 
                                auxArray1 = inarray[i] + phys_offset, 
                                auxArray2 = m_DU1[i][0] + phys_offset);
                        }
                        
                        // Computing the standard first-order correction 
                        // derivative
                        v_DerCFlux_1D(nConvectiveFields, fields, inarray[i], 
                                      m_IF1[i][0], m_DFC1[i][0]);

                        // Back to the physical space using global operations
                        Vmath::Vdiv(nSolutionPts, &m_DFC1[i][0][0], 1, 
                                    &m_jac[0], 1, &m_DFC1[i][0][0], 1);
                        Vmath::Vdiv(nSolutionPts, &m_DFC1[i][0][0], 1, 
                                    &m_jac[0], 1, &m_DFC1[i][0][0], 1);
                        
                        // Computing total first order derivatives
                        Vmath::Vadd(nSolutionPts, &m_DFC1[i][0][0], 1, 
                                    &m_DU1[i][0][0], 1, &m_D1[i][0][0], 1);

                        Vmath::Vcopy(nSolutionPts, &m_D1[i][0][0], 1,
                                     &m_tmp1[i][0][0], 1);
                    }

                    // Computing interface numerical fluxes for m_D1 
                    // in physical space 
                    v_NumFluxforVector(fields, inarray, m_tmp1, m_IF2);
                                         
                    for (i = 0; i < nConvectiveFields; ++i)
                    {
                        // Computing the physical second-order discountinuous 
                        // derivative
                        for (n = 0; n < nElements; n++)
                        {
                            phys_offset = fields[0]->GetPhys_Offset(n);

                            fields[i]->GetExp(n)->PhysDeriv(0, 
                                auxArray1 = m_D1[i][0] + phys_offset, 
                                auxArray2 = m_DD1[i][0] + phys_offset);
                        }
                        
                        // Computing the standard second-order correction 
                        // derivative
                        v_DerCFlux_1D(nConvectiveFields, fields, m_D1[i][0], 
                                      m_IF2[i], m_DFC2[i][0]);

                        // Back to the physical space using global operations
                        Vmath::Vdiv(nSolutionPts, &m_DFC2[i][0][0], 1, 
                                    &m_jac[0], 1, &m_DFC2[i][0][0], 1);
                        Vmath::Vdiv(nSolutionPts, &m_DFC2[i][0][0], 1, 
                                    &m_jac[0], 1, &m_DFC2[i][0][0], 1);
                                                
                        // Adding the total divergence to outarray (RHS)
                        Vmath::Vadd(nSolutionPts, &m_DFC2[i][0][0], 1, 
                                    &m_DD1[i][0][0], 1, &outarray[i][0], 1);
                        
                        // Primitive Dealiasing 1D
                        if (!(Basis[0]->Collocation()))
                        {
                            fields[i]->FwdTrans(outarray[i], outarrayCoeff[i]);
                            fields[i]->BwdTrans(outarrayCoeff[i], outarray[i]);
                        }
                    }
                    break;
                }
                // 2D problems 
                case 2:
                {                       
                    for(i = 0; i < nConvectiveFields; ++i)
                    {               
                        for (j = 0; j < nDim; ++j)
                        {                             
                            // Temporary vectors
                            Array<OneD, NekDouble> u1_hat(nSolutionPts, 0.0);
                            Array<OneD, NekDouble> u2_hat(nSolutionPts, 0.0);
                             
                            if (j == 0) 
                            {
                                Vmath::Vmul(nSolutionPts, &inarray[i][0], 1,
                                            &m_gmat[0][0], 1, &u1_hat[0], 1);
                                     
                                Vmath::Vmul(nSolutionPts, &u1_hat[0], 1, 
                                            &m_jac[0], 1, &u1_hat[0], 1);
                                     
                                Vmath::Vmul(nSolutionPts, &inarray[i][0], 1,
                                            &m_gmat[1][0], 1, &u2_hat[0], 1);
                                     
                                Vmath::Vmul(nSolutionPts, &u2_hat[0], 1, 
                                            &m_jac[0], 1, &u2_hat[0], 1);
                            }
                            else if (j == 1)
                            {
                                Vmath::Vmul(nSolutionPts, &inarray[i][0], 1,
                                            &m_gmat[2][0], 1, &u1_hat[0], 1);
                                     
                                Vmath::Vmul(nSolutionPts, &u1_hat[0], 1, 
                                            &m_jac[0], 1, &u1_hat[0], 1);
                                     
                                Vmath::Vmul(nSolutionPts, &inarray[i][0], 1,
                                            &m_gmat[3][0], 1, &u2_hat[0], 1);
                                     
                                Vmath::Vmul(nSolutionPts, &u2_hat[0], 1, 
                                            &m_jac[0], 1, &u2_hat[0], 1);
                            }

                            for (n = 0; n < nElements; n++)
                            { 
                                phys_offset = fields[0]->GetPhys_Offset(n);

                                fields[i]->GetExp(n)->StdPhysDeriv(0,
                                    auxArray1 = u1_hat + phys_offset,
                                    auxArray2 = m_tmp1[i][j] + phys_offset);
                                
                                fields[i]->GetExp(n)->StdPhysDeriv(1,
                                    auxArray1 = u2_hat + phys_offset,
                                    auxArray2 = m_tmp2[i][j] + phys_offset);
                            }
                            
                            Vmath::Vadd(nSolutionPts, &m_tmp1[i][j][0], 1,
                                        &m_tmp2[i][j][0], 1,
                                        &m_DU1[i][j][0], 1);
                            
                            // Divide by the metric jacobian
                            Vmath::Vdiv(nSolutionPts, &m_DU1[i][j][0], 1,
                                        &m_jac[0], 1, &m_DU1[i][j][0], 1);
                            
                            // Computing the standard first-order correction 
                            // derivatives
                            v_DerCFlux_2D(nConvectiveFields, j, fields, 
                                          inarray[i], m_IF1[i][j], 
                                          m_DFC1[i][j]);
                        }
                        
                        // Multiplying derivatives by B matrix to get auxiliary 
                        // variables
                        for (j = 0; j < nSolutionPts; ++j)
                        {   
                            // std(q1)
                            m_BD1[i][0][j] = 
                            (m_gmat[0][j]*m_gmat[0][j] + 
                             m_gmat[2][j]*m_gmat[2][j]) *
                            m_DFC1[i][0][j] +
                            (m_gmat[1][j]*m_gmat[0][j] + 
                             m_gmat[3][j]*m_gmat[2][j]) *
                            m_DFC1[i][1][j];
                            
                            // std(q2)
                            m_BD1[i][1][j] =  
                            (m_gmat[1][j]*m_gmat[0][j] + 
                             m_gmat[3][j]*m_gmat[2][j]) *
                            m_DFC1[i][0][j] +
                            (m_gmat[1][j]*m_gmat[1][j] + 
                             m_gmat[3][j]*m_gmat[3][j]) *
                            m_DFC1[i][1][j];
                        }
                                                
                        // Multiplying derivatives by A^(-1) to get back 
                        // into the physical space 
                        for (j = 0; j < nSolutionPts; j++)
                        {
                            // q1 = A11^(-1)*std(q1) + A12^(-1)*std(q2)
                            m_DFC1[i][0][j] = 
                            m_gmat[3][j] * m_BD1[i][0][j] - 
                            m_gmat[2][j] * m_BD1[i][1][j];
                            
                            // q2 = A21^(-1)*std(q1) + A22^(-1)*std(q2)
                            m_DFC1[i][1][j] = 
                            m_gmat[0][j] * m_BD1[i][1][j] - 
                            m_gmat[1][j] * m_BD1[i][0][j];
                        }
                                                
                        // Computing the physical first-order derivatives
                        for (j = 0; j < nDim; ++j)
                        {
                            Vmath::Vadd(nSolutionPts, &m_DU1[i][j][0], 1,
                                        &m_DFC1[i][j][0], 1, 
                                        &m_D1[i][j][0], 1);
                        }
                    }
                                           
                    // Computing interface numerical fluxes for m_D1 
                    // in physical space 
                    v_NumFluxforVector(fields, inarray, m_D1, m_IF2);

                    // Computing the standard second-order discontinuous 
                    // derivatives 
                    for (i = 0; i < nConvectiveFields; ++i)
                    {
                        Array<OneD, NekDouble> f_hat(nSolutionPts, 0.0);
                        Array<OneD, NekDouble> g_hat(nSolutionPts, 0.0);
                        
                        for (j = 0; j < nSolutionPts; j++)
                        {
                            f_hat[j] = (m_D1[i][0][j] * m_gmat[0][j] + 
                            m_D1[i][1][j] * m_gmat[2][j])*m_jac[j];
                            
                            g_hat[j] = (m_D1[i][0][j] * m_gmat[1][j] + 
                            m_D1[i][1][j] * m_gmat[3][j])*m_jac[j];
                        }

                        for (n = 0; n < nElements; n++)
                        {
                            phys_offset = fields[0]->GetPhys_Offset(n);

                            fields[0]->GetExp(n)->StdPhysDeriv(0, 
                            auxArray1 = f_hat + phys_offset, 
                            auxArray2 = m_DD1[i][0] + phys_offset); 
                        
                            fields[0]->GetExp(n)->StdPhysDeriv(1, 
                            auxArray1 = g_hat + phys_offset, 
                            auxArray2 = m_DD1[i][1] + phys_offset);
                        }
                        
                        // Divergence of the standard discontinuous flux
                        Vmath::Vadd(nSolutionPts, 
                                    &m_DD1[i][0][0], 1,
                                    &m_DD1[i][1][0], 1, 
                                    &m_divFD[i][0], 1);
                        
                        // Divergence of the standard correction flux
                        if (Basis[0]->GetPointsType() ==
                            LibUtilities::eGaussGaussLegendre &&
                            Basis[1]->GetPointsType() ==
                            LibUtilities::eGaussGaussLegendre)
                        {
                            v_DivCFlux_2D_Gauss(nConvectiveFields, fields,
                                                f_hat, g_hat,
                                                m_IF2[i], m_divFC[i]);
                        }
                        else
                        {
                            v_DivCFlux_2D(nConvectiveFields, fields,
                                          m_D1[i][0], m_D1[i][1],
                                          m_IF2[i], m_divFC[i]);
                        }
                        
                        // Divergence of the standard final flux
                        Vmath::Vadd(nSolutionPts, &m_divFD[i][0], 1,
                                    &m_divFC[i][0], 1, &outarray[i][0], 1);
                         
                        // Dividing by the jacobian to get back into 
                        // physical space
                        Vmath::Vdiv(nSolutionPts, &outarray[i][0], 1,
                                    &m_jac[0], 1, &outarray[i][0], 1);
                        
                        // Primitive Dealiasing 2D
                        if (!(Basis[0]->Collocation()))
                        {
                            fields[i]->FwdTrans(outarray[i], outarrayCoeff[i]);
                            fields[i]->BwdTrans(outarrayCoeff[i], outarray[i]);
                        }
                    }//Close loop on nConvectiveFields
                    break;
                }   
                // 3D problems 
                case 3:
                {
                    ASSERTL0(false, "3D FRDG case not implemented yet");
                    break;
                }
            }
        }
        
        /**
         * @brief Builds the numerical flux for the 1st order derivatives
         *
         */
        void DiffusionLFR::v_NumFluxforScalar(
            const Array<OneD, MultiRegions::ExpListSharedPtr>        &fields,
            const Array<OneD, Array<OneD, NekDouble> >               &ufield,
                  Array<OneD, Array<OneD, Array<OneD, NekDouble> > > &uflux)
        {
            int i, j;
            int nTracePts  = fields[0]->GetTrace()->GetTotPoints();
            int nvariables = fields.num_elements();
            int nDim       = fields[0]->GetCoordim(0);  
            
            Array<OneD, NekDouble > Fwd     (nTracePts);
            Array<OneD, NekDouble > Bwd     (nTracePts);
            Array<OneD, NekDouble > Vn      (nTracePts, 0.0);
            Array<OneD, NekDouble > fluxtemp(nTracePts, 0.0);
                                    
            // Get the normal velocity Vn
            for (i = 0; i < nDim; ++i)
            {
                Vmath::Svtvp(nTracePts, 1.0, m_traceNormals[i], 1, 
                             Vn, 1, Vn, 1);
            }
            
            // Get the sign of (v \cdot n), v = an arbitrary vector
            // Evaluate upwind flux:
            // uflux = \hat{u} \phi \cdot u = u^{(+,-)} n
            for (j = 0; j < nDim; ++j)
            {
                for (i = 0; i < nvariables ; ++i)
                {
                    // Compute Fwd and Bwd value of ufield of i direction
                    fields[i]->GetFwdBwdTracePhys(ufield[i], Fwd, Bwd);
                    
                    // if Vn >= 0, flux = uFwd, i.e.,
                    // edge::eForward, if V*n>=0 <=> V*n_F>=0, pick uflux = uFwd
                    // edge::eBackward, if V*n>=0 <=> V*n_B<0, pick uflux = uFwd
                    
                    // else if Vn < 0, flux = uBwd, i.e.,
                    // edge::eForward, if V*n<0 <=> V*n_F<0, pick uflux = uBwd
                    // edge::eBackward, if V*n<0 <=> V*n_B>=0, pick uflux = uBwd
                    
                    fields[i]->GetTrace()->Upwind(Vn, Fwd, Bwd, fluxtemp);
                    
                    // Imposing weak boundary condition with flux
                    // if Vn >= 0, uflux = uBwd at Neumann, i.e.,
                    // edge::eForward, if V*n>=0 <=> V*n_F>=0, pick uflux = uBwd
                    // edge::eBackward, if V*n>=0 <=> V*n_B<0, pick uflux = uBwd
                    
                    // if Vn >= 0, uflux = uFwd at Neumann, i.e.,
                    // edge::eForward, if V*n<0 <=> V*n_F<0, pick uflux = uFwd
                    // edge::eBackward, if V*n<0 <=> V*n_B>=0, pick uflux = uFwd
                    
                    if(fields[0]->GetBndCondExpansions().num_elements())
                    {
                        v_WeakPenaltyforScalar(fields, i, ufield[i], fluxtemp);
                    }
                    
                    // if Vn >= 0, flux = uFwd*(tan_{\xi}^- \cdot \vec{n}), 
                    // i.e,
                    // edge::eForward, uFwd \(\tan_{\xi}^Fwd \cdot \vec{n})
                    // edge::eBackward, uFwd \(\tan_{\xi}^Bwd \cdot \vec{n})
                    
                    // else if Vn < 0, flux = uBwd*(tan_{\xi}^- \cdot \vec{n}), 
                    // i.e,
                    // edge::eForward, uBwd \(\tan_{\xi}^Fwd \cdot \vec{n})
                    // edge::eBackward, uBwd \(\tan_{\xi}^Bwd \cdot \vec{n})
                    
                    Vmath::Vcopy(nTracePts, fluxtemp, 1, uflux[i][j], 1);
                }
            }
        }
        
        /**
         * @brief Imposes appropriate bcs for the 1st order derivatives
         *
         */
        void DiffusionLFR::v_WeakPenaltyforScalar(
            const Array<OneD, MultiRegions::ExpListSharedPtr> &fields,
            const int                                          var,
            const Array<OneD, const NekDouble>                &ufield,
                  Array<OneD,       NekDouble>                &penaltyflux)
        {
            // Variable initialisation
            int i, e, id1, id2;
            int nBndEdgePts, nBndEdges;
            int cnt         = 0;
            int nBndRegions = fields[var]->GetBndCondExpansions().num_elements();
            int nTracePts   = fields[0]->GetTrace()->GetTotPoints();
            
            // Extract physical values of the fields at the trace space
            Array<OneD, NekDouble > uplus(nTracePts);
            fields[var]->ExtractTracePhys(ufield, uplus);
            
            // Impose boundary conditions
            for (i = 0; i < nBndRegions; ++i)
            {
                // Number of boundary edges related to bcRegion 'i'
                nBndEdges = fields[var]->
                GetBndCondExpansions()[i]->GetExpSize();
                
                // Weakly impose boundary conditions by modifying flux values
                for (e = 0; e < nBndEdges ; ++e)
                {
                    // Number of points on the expansion
                    nBndEdgePts = fields[var]->
                    GetBndCondExpansions()[i]->GetExp(e)->GetTotPoints();
                    
                    // Offset of the boundary expansion
                    id1 = fields[var]->
                    GetBndCondExpansions()[i]->GetPhys_Offset(e);

                    // Offset of the trace space related to boundary expansion
                    id2 = fields[0]->GetTrace()->
                    GetPhys_Offset(fields[0]->GetTraceMap()->
                                   GetBndCondTraceToGlobalTraceMap(cnt++));
                    
                    // Dirichlet bcs ==> uflux = gD
                    if (fields[var]->GetBndConditions()[i]->
                    GetBoundaryConditionType() == SpatialDomains::eDirichlet)
                    {
                        Vmath::Vcopy(nBndEdgePts, 
                                     &(fields[var]->
                                       GetBndCondExpansions()[i]->
                                       GetPhys())[id1], 1, 
                                     &penaltyflux[id2], 1);
                    }
                    // Neumann bcs ==> uflux = u+
                    else if ((fields[var]->GetBndConditions()[i])->
                    GetBoundaryConditionType() == SpatialDomains::eNeumann)
                    {
                        Vmath::Vcopy(nBndEdgePts, 
                                     &uplus[id2], 1, 
                                     &penaltyflux[id2], 1);
                    }
                }
            }
        }
        
        /**
         * @brief Build the numerical flux for the 2nd order derivatives
         *
         */
        void DiffusionLFR::v_NumFluxforVector(
            const Array<OneD, MultiRegions::ExpListSharedPtr>        &fields,
            const Array<OneD, Array<OneD, NekDouble> >               &ufield,
                  Array<OneD, Array<OneD, Array<OneD, NekDouble> > > &qfield,
                  Array<OneD, Array<OneD, NekDouble> >               &qflux)
        {
            int i, j;
            int nTracePts  = fields[0]->GetTrace()->GetTotPoints();
            int nvariables = fields.num_elements();
            int nDim       = fields[0]->GetCoordim(0);  
            
            NekDouble C11 = 0.0;
            Array<OneD, NekDouble > Fwd(nTracePts);
            Array<OneD, NekDouble > Bwd(nTracePts);
            Array<OneD, NekDouble > Vn (nTracePts, 0.0);
            
            Array<OneD, NekDouble > qFwd     (nTracePts);
            Array<OneD, NekDouble > qBwd     (nTracePts);
            Array<OneD, NekDouble > qfluxtemp(nTracePts, 0.0);            
            Array<OneD, NekDouble > uterm(nTracePts);
                        
            // Get the normal velocity Vn
            for(i = 0; i < nDim; ++i)
            {
                Vmath::Svtvp(nTracePts, 1.0, m_traceNormals[i], 1, 
                             Vn, 1, Vn, 1);
            }
            
            // Evaulate upwind flux:
            // qflux = \hat{q} \cdot u = q \cdot n - C_(11)*(u^+ - u^-)
            for (i = 0; i < nvariables; ++i)
            {
                qflux[i] = Array<OneD, NekDouble> (nTracePts, 0.0);
                for (j = 0; j < nDim; ++j)
                {
                    //  Compute Fwd and Bwd value of ufield of jth direction
                    fields[i]->GetFwdBwdTracePhys(qfield[i][j], qFwd, qBwd);
                    
                    // if Vn >= 0, flux = uFwd, i.e.,
                    // edge::eForward, if V*n>=0 <=> V*n_F>=0, pick 
                    // qflux = qBwd = q+
                    // edge::eBackward, if V*n>=0 <=> V*n_B<0, pick 
                    // qflux = qBwd = q-
                    
                    // else if Vn < 0, flux = uBwd, i.e.,
                    // edge::eForward, if V*n<0 <=> V*n_F<0, pick 
                    // qflux = qFwd = q-
                    // edge::eBackward, if V*n<0 <=> V*n_B>=0, pick 
                    // qflux = qFwd = q+
                    
                    fields[i]->GetTrace()->Upwind(Vn, qBwd, qFwd, qfluxtemp);
                    
                    Vmath::Vmul(nTracePts, m_traceNormals[j], 1, qfluxtemp, 1, 
                                qfluxtemp, 1);
                    
                    /*
                    // Generate Stability term = - C11 ( u- - u+ )
                    fields[i]->GetFwdBwdTracePhys(ufield[i], Fwd, Bwd);
                    
                    Vmath::Vsub(nTracePts, 
                                Fwd, 1, Bwd, 1, 
                                uterm, 1);
                    
                    Vmath::Smul(nTracePts, 
                                -1.0 * C11, uterm, 1, 
                                uterm, 1);
                    
                    // Flux = {Fwd, Bwd} * (nx, ny, nz) + uterm * (nx, ny)
                    Vmath::Vadd(nTracePts, 
                                uterm, 1, 
                                qfluxtemp, 1, 
                                qfluxtemp, 1);
                    */
                    
                    // Imposing weak boundary condition with flux
                    if (fields[0]->GetBndCondExpansions().num_elements())
                    {
                        v_WeakPenaltyforVector(fields, i, j, qfield[i][j], 
                                               qfluxtemp, C11);
                    }
                    
                    // q_hat \cdot n = (q_xi \cdot n_xi) or (q_eta \cdot n_eta)
                    // n_xi = n_x * tan_xi_x + n_y * tan_xi_y + n_z * tan_xi_z
                    // n_xi = n_x * tan_eta_x + n_y * tan_eta_y + n_z*tan_eta_z
                    Vmath::Vadd(nTracePts, qfluxtemp, 1, qflux[i], 1, 
                                qflux[i], 1);
                }
            }
        }
        
        
        
        /**
         * @brief Imposes appropriate bcs for the 2nd order derivatives
         *
         */
        void DiffusionLFR::v_WeakPenaltyforVector(
            const Array<OneD, MultiRegions::ExpListSharedPtr> &fields,
            const int                                          var,
            const int                                          dir,
            const Array<OneD, const NekDouble>                &qfield,
                  Array<OneD,       NekDouble>                &penaltyflux,
            NekDouble                                          C11)
        {
            int i, e, id1, id2;
            int nBndEdges, nBndEdgePts;
            int nBndRegions = fields[var]->GetBndCondExpansions().num_elements();
            int nTracePts   = fields[0]->GetTrace()->GetTotPoints();
            
            Array<OneD, NekDouble > uterm(nTracePts);
            Array<OneD, NekDouble > qtemp(nTracePts);
            int cnt = 0;
                        
            fields[var]->ExtractTracePhys(qfield, qtemp);
            
            for (i = 0; i < nBndRegions; ++i)
            {
                nBndEdges = fields[var]->
                GetBndCondExpansions()[i]->GetExpSize();
                
                // Weakly impose boundary conditions by modifying flux values
                for (e = 0; e < nBndEdges ; ++e)
                {
                    nBndEdgePts = fields[var]->
                    GetBndCondExpansions()[i]->GetExp(e)->GetTotPoints();
                    
                    id1 = fields[var]->
                    GetBndCondExpansions()[i]->GetPhys_Offset(e);
                    
                    id2 = fields[0]->GetTrace()->
                    GetPhys_Offset(fields[0]->GetTraceMap()->
                                   GetBndCondTraceToGlobalTraceMap(cnt++));
                    
                    // For Dirichlet boundary condition: 
                    //qflux = q+ - C_11 (u+ -    g_D) (nx, ny)
                    if (fields[var]->GetBndConditions()[i]->
                       GetBoundaryConditionType() == SpatialDomains::eDirichlet)
                    {
                        Vmath::Vmul(nBndEdgePts, &m_traceNormals[dir][id2], 1, 
                                    &qtemp[id2], 1, &penaltyflux[id2], 1);
                    }
                    // For Neumann boundary condition: qflux = g_N
                    else if ((fields[var]->GetBndConditions()[i])->
                        GetBoundaryConditionType() == SpatialDomains::eNeumann)
                    {
                        Vmath::Vmul(nBndEdgePts, &m_traceNormals[dir][id2], 1, 
                                    &(fields[var]->GetBndCondExpansions()[i]->
                                      GetPhys())[id1], 1, &penaltyflux[id2], 1);
                    }
                }
            }
        }
        
        /**
         * @brief Compute the derivative of the corrective flux for 1D problems.
         *
         * @param nConvectiveFields Number of fields.
         * @param fields            Pointer to fields.
         * @param flux              Volumetric flux in the physical space.
         * @param iFlux             Numerical interface flux in physical space.
         * @param derCFlux          Derivative of the corrective flux. 
         *
         */
        void DiffusionLFR::v_DerCFlux_1D(
            const int                                         nConvectiveFields,
            const Array<OneD, MultiRegions::ExpListSharedPtr> &fields,
            const Array<OneD, const NekDouble>                &flux, 
            const Array<OneD, const NekDouble>                &iFlux,
                  Array<OneD,       NekDouble>                &derCFlux)
        {
            int n;
            int nLocalSolutionPts, phys_offset, t_offset;
            
            Array<OneD,       NekDouble> auxArray1, auxArray2;
            Array<TwoD, const NekDouble> gmat;
            Array<OneD, const NekDouble> jac;
            
            LibUtilities::PointsKeyVector ptsKeys;
            LibUtilities::BasisSharedPtr Basis;
            Basis = fields[0]->GetExp(0)->GetBasis(0);
            
            int nElements    = fields[0]->GetExpSize();            
            int nSolutionPts = fields[0]->GetTotPoints();
            
            vector<bool> negatedFluxNormal = (boost::static_pointer_cast<MultiRegions::DisContField1D>(fields[0]))->GetNegatedFluxNormal();

            // Arrays to store the derivatives of the correction flux
            Array<OneD, NekDouble> DCL(nSolutionPts/nElements, 0.0); 
            Array<OneD, NekDouble> DCR(nSolutionPts/nElements, 0.0);
            
            // Arrays to store the intercell numerical flux jumps
            Array<OneD, NekDouble>  JumpL(nElements);
            Array<OneD, NekDouble>  JumpR(nElements);
            
            Array<OneD, Array<OneD, LocalRegions::ExpansionSharedPtr> >
                &elmtToTrace = fields[0]->GetTraceMap()->GetElmtToTrace();

            for (n = 0; n < nElements; ++n)
            {
                nLocalSolutionPts = fields[0]->GetExp(n)->GetTotPoints();
                phys_offset = fields[0]->GetPhys_Offset(n);
                
                Array<OneD, NekDouble> tmparrayX1(nLocalSolutionPts, 0.0);
                NekDouble tmpFluxVertex = 0;
                Vmath::Vcopy(nLocalSolutionPts,
                             &flux[phys_offset], 1,
                             &tmparrayX1[0], 1);
                
                fields[0]->GetExp(n)->GetVertexPhysVals(0, tmparrayX1,
                                                        tmpFluxVertex);
                
                t_offset = fields[0]->GetTrace()
                    ->GetPhys_Offset(elmtToTrace[n][0]->GetElmtId());

                if(negatedFluxNormal[2*n])
                {
                    JumpL[n] =  iFlux[t_offset] - tmpFluxVertex;
                }
                else
                {
                    JumpL[n] =  -iFlux[t_offset] - tmpFluxVertex;
                }
                
                t_offset = fields[0]->GetTrace()
                    ->GetPhys_Offset(elmtToTrace[n][1]->GetElmtId());

                fields[0]->GetExp(n)->GetVertexPhysVals(1, tmparrayX1,
                                                        tmpFluxVertex);
                if(negatedFluxNormal[2*n+1])
                {
                    JumpR[n] =  -iFlux[t_offset] - tmpFluxVertex;
                }
                else
                {
                    JumpR[n] =  iFlux[t_offset] - tmpFluxVertex;
                }
            }
            
            for (n = 0; n < nElements; ++n)
            {
                ptsKeys = fields[0]->GetExp(n)->GetPointsKeys();
                nLocalSolutionPts = fields[0]->GetExp(n)->GetTotPoints();
                phys_offset       = fields[0]->GetPhys_Offset(n);
                jac               = fields[0]->GetExp(n)
                                ->as<LocalRegions::Expansion1D>()->GetGeom1D()
                                ->GetMetricInfo()->GetJac(ptsKeys);
                
                JumpL[n] = JumpL[n] * jac[0];
                JumpR[n] = JumpR[n] * jac[0];
                
                // Left jump multiplied by left derivative of C function
                Vmath::Smul(nLocalSolutionPts, JumpL[n], 
                            &m_dGL_xi1[n][0], 1, &DCL[0], 1);
                
                // Right jump multiplied by right derivative of C function
                Vmath::Smul(nLocalSolutionPts, JumpR[n], 
                            &m_dGR_xi1[n][0], 1, &DCR[0], 1);
                
                // Assembling derivative of the correction flux
                Vmath::Vadd(nLocalSolutionPts, &DCL[0], 1, &DCR[0], 1, 
                            &derCFlux[phys_offset], 1);
            }
        }
        
        /**
         * @brief Compute the derivative of the corrective flux wrt a given 
         * coordinate for 2D problems.
         *
         * There could be a bug for deformed elements since first derivatives 
         * are not exactly the same results of DiffusionLDG as expected
         *
         * @param nConvectiveFields Number of fields.
         * @param fields            Pointer to fields.
         * @param flux              Volumetric flux in the physical space. 
         * @param numericalFlux     Numerical interface flux in physical space.
         * @param derCFlux          Derivative of the corrective flux.                    
         *
         * \todo: Switch on shapes eventually here.
         */
        void DiffusionLFR::v_DerCFlux_2D(
            const int                                         nConvectiveFields,
            const int                                         direction,
            const Array<OneD, MultiRegions::ExpListSharedPtr> &fields,
            const Array<OneD, const NekDouble>                &flux, 
            const Array<OneD,       NekDouble>                &iFlux,
                  Array<OneD,       NekDouble>                &derCFlux)
        {                   
            int n, e, i, j, cnt;
            
            Array<TwoD, const NekDouble> gmat;
            Array<OneD, const NekDouble> jac;
            
            int nElements = fields[0]->GetExpSize();
            
            int trace_offset, phys_offset;
            int nLocalSolutionPts;
            int nquad0, nquad1;
            int nEdgePts;  
            
            LibUtilities::PointsKeyVector ptsKeys;
            Array<OneD, NekDouble> auxArray1, auxArray2;
            Array<OneD, LibUtilities::BasisSharedPtr> base;
            Array<OneD, Array<OneD, LocalRegions::ExpansionSharedPtr> >
            &elmtToTrace = fields[0]->GetTraceMap()->GetElmtToTrace();
                        
            // Loop on the elements
            for (n = 0; n < nElements; ++n)
            {
                // Offset of the element on the global vector
                phys_offset = fields[0]->GetPhys_Offset(n);
                nLocalSolutionPts = fields[0]->GetExp(n)->GetTotPoints();
                ptsKeys = fields[0]->GetExp(n)->GetPointsKeys();
                
                jac  = fields[0]->GetExp(n)->as<LocalRegions::Expansion2D>()
                                ->GetGeom2D()->GetMetricInfo()->GetJac(ptsKeys);
                
                base = fields[0]->GetExp(n)->GetBase();
                nquad0 = base[0]->GetNumPoints();
                nquad1 = base[1]->GetNumPoints();
                
                Array<OneD, NekDouble> divCFluxE0(nLocalSolutionPts, 0.0);
                Array<OneD, NekDouble> divCFluxE1(nLocalSolutionPts, 0.0);
                Array<OneD, NekDouble> divCFluxE2(nLocalSolutionPts, 0.0);
                Array<OneD, NekDouble> divCFluxE3(nLocalSolutionPts, 0.0);
                
                // Loop on the edges
                for (e = 0; e < fields[0]->GetExp(n)->GetNedges(); ++e)
                {   
                    // Number of edge points of edge 'e'
                    nEdgePts = fields[0]->GetExp(n)->GetEdgeNumPoints(e);
                    
                    // Array for storing volumetric fluxes on each edge
                    Array<OneD, NekDouble> tmparray(nEdgePts, 0.0);
                    
                    // Offset of the trace space correspondent to edge 'e'
                    trace_offset = fields[0]->GetTrace()->GetPhys_Offset(
                                                elmtToTrace[n][e]->GetElmtId());
                    
                    // Get the normals of edge 'e'
                    //const Array<OneD, const Array<OneD, NekDouble> > &normals = 
                    //fields[0]->GetExp(n)->GetEdgeNormal(e);
                    
                    // Extract the edge values of the volumetric fluxes 
                    // on edge 'e' and order them accordingly to the order 
                    // of the trace space 
                    fields[0]->GetExp(n)->GetEdgePhysVals(e, elmtToTrace[n][e],
                                                          flux + phys_offset,
                                                          auxArray1 = tmparray);
                                                            
                    // Compute the fluxJumps per each edge 'e' and each 
                    // flux point
                    Array<OneD, NekDouble> fluxJumps(nEdgePts, 0.0);
                    Vmath::Vsub(nEdgePts, &iFlux[trace_offset], 1, 
                                &tmparray[0], 1, &fluxJumps[0], 1);
                    
                    // Check the ordering of the fluxJumps and reverse 
                    // it in case of backward definition of edge 'e'
                    if (fields[0]->GetExp(n)->GetEorient(e) == 
                        StdRegions::eBackwards)
                    {
                        Vmath::Reverse(nEdgePts, 
                                       &fluxJumps[0], 1,
                                       &fluxJumps[0], 1);
                    }
                    
                    // Deformed elements                        
                    if (fields[0]->GetExp(n)->as<LocalRegions::Expansion2D>()
                                 ->GetGeom2D()->GetMetricInfo()->GetGtype()
                        == SpatialDomains::eDeformed)
                    {
                        // Extract the Jacobians along edge 'e'
                        Array<OneD, NekDouble> jacEdge(nEdgePts, 0.0);
                        
                        fields[0]->GetExp(n)->GetEdgePhysVals(
                                                    e, elmtToTrace[n][e],
                                                    jac, auxArray1 = jacEdge);
                        
                        // Check the ordering of the fluxJumps and reverse 
                        // it in case of backward definition of edge 'e'
                        if (fields[0]->GetExp(n)->GetEorient(e) == 
                            StdRegions::eBackwards)
                        {
                            Vmath::Reverse(nEdgePts, 
                                           &jacEdge[0], 1,
                                           &jacEdge[0], 1);
                        }
                        
                        // Multiply the fluxJumps by the edge 'e' Jacobians
                        // to bring the fluxJumps into the standard space
                        for (j = 0; j < nEdgePts; j++)
                        {
                            fluxJumps[j] = fluxJumps[j] * jacEdge[j];
                        }
                    }
                    // Non-deformed elements
                    else
                    {   
                        // Multiply the fluxJumps by the edge 'e' Jacobians
                        // to bring the fluxJumps into the standard space
                        Vmath::Smul(nEdgePts, jac[0], fluxJumps, 1, 
                                    fluxJumps, 1);
                    }
                     
                    // Multiply jumps by derivatives of the correction functions
                    // All the quntities at this level should be defined into 
                    // the standard space
                    switch (e) 
                    {
                        case 0:
                            for (i = 0; i < nquad0; ++i)
                            {
                                // Multiply fluxJumps by Q factors
                                //fluxJumps[i] = -(m_Q2D_e0[n][i]) * fluxJumps[i];
                                
                                for (j = 0; j < nquad1; ++j)
                                {
                                    cnt = i + j*nquad0;
                                    divCFluxE0[cnt] = fluxJumps[i] * m_dGL_xi2[n][j];
                                }
                            }
                            break;
                        case 1:
                            for (i = 0; i < nquad1; ++i)
                            {
                                // Multiply fluxJumps by Q factors
                                //fluxJumps[i] = (m_Q2D_e1[n][i]) * fluxJumps[i];
                                
                                for (j = 0; j < nquad0; ++j)
                                {
                                    cnt = (nquad0)*i + j;
                                    divCFluxE1[cnt] = fluxJumps[i] * m_dGR_xi1[n][j];
                                }
                            }
                            break;
                        case 2:
                            for (i = 0; i < nquad0; ++i)
                            {
                                // Multiply fluxJumps by Q factors
                                //fluxJumps[i] = (m_Q2D_e2[n][i]) * fluxJumps[i];
                                
                                for (j = 0; j < nquad1; ++j)
                                {
                                    cnt = (nquad0 - 1) + j*nquad0 - i;
                                    divCFluxE2[cnt] = fluxJumps[i] * m_dGR_xi2[n][j];
                                }
                            }
                            break;
                        case 3:
                            for (i = 0; i < nquad1; ++i)
                            {
                                // Multiply fluxJumps by Q factors
                                //fluxJumps[i] = -(m_Q2D_e3[n][i]) * fluxJumps[i];
                                for (j = 0; j < nquad0; ++j)
                                {
                                    cnt = (nquad0*nquad1 - nquad0) + j - i*nquad0;
                                    divCFluxE3[cnt] = fluxJumps[i] * m_dGL_xi1[n][j];  
                                }
                            }
                            break;
                            
                        default:
                            ASSERTL0(false, "edge value (< 3) is out of range");
                            break;
                    }
                }
                
                // Summing the component of the flux for calculating the
                // derivatives per each direction
                if (direction == 0)
                {
                    Vmath::Vadd(nLocalSolutionPts, &divCFluxE1[0], 1, 
                                &divCFluxE3[0], 1, &derCFlux[phys_offset], 1);
                }
                else if (direction == 1)
                {
                    Vmath::Vadd(nLocalSolutionPts, &divCFluxE0[0], 1, 
                                &divCFluxE2[0], 1, &derCFlux[phys_offset], 1);
                }
            }
        }
        
        /**
         * @brief Compute the divergence of the corrective flux for 2D problems.
         *
         * @param nConvectiveFields   Number of fields.
         * @param fields              Pointer to fields.
         * @param fluxX1              X1 - volumetric flux in physical space.
         * @param fluxX2              X2 - volumetric flux in physical space.
         * @param numericalFlux       Interface flux in physical space.
         * @param divCFlux            Divergence of the corrective flux for 
         *                            2D problems.
         *
         * \todo: Switch on shapes eventually here.
         */
        void DiffusionLFR::v_DivCFlux_2D(
            const int                                         nConvectiveFields,
            const Array<OneD, MultiRegions::ExpListSharedPtr> &fields,
            const Array<OneD, const NekDouble>                &fluxX1, 
            const Array<OneD, const NekDouble>                &fluxX2, 
            const Array<OneD, const NekDouble>                &numericalFlux,
                  Array<OneD,       NekDouble>                &divCFlux)
        {                   
            int n, e, i, j, cnt;
            
            int nElements = fields[0]->GetExpSize();
            
            int nLocalSolutionPts;
            int nEdgePts;  
            int trace_offset; 
            int phys_offset;
            int nquad0;
            int nquad1;
            
            Array<OneD, NekDouble> auxArray1, auxArray2;
            Array<OneD, LibUtilities::BasisSharedPtr> base;
            
            Array<OneD, Array<OneD, LocalRegions::ExpansionSharedPtr> >
            &elmtToTrace = fields[0]->GetTraceMap()->GetElmtToTrace();
                        
            // Loop on the elements
            for(n = 0; n < nElements; ++n)
            {
                // Offset of the element on the global vector
                phys_offset = fields[0]->GetPhys_Offset(n);
                nLocalSolutionPts = fields[0]->GetExp(n)->GetTotPoints();
                
                base   = fields[0]->GetExp(n)->GetBase();
                nquad0 = base[0]->GetNumPoints();
                nquad1 = base[1]->GetNumPoints();
                
                Array<OneD, NekDouble> divCFluxE0(nLocalSolutionPts, 0.0);
                Array<OneD, NekDouble> divCFluxE1(nLocalSolutionPts, 0.0);
                Array<OneD, NekDouble> divCFluxE2(nLocalSolutionPts, 0.0);
                Array<OneD, NekDouble> divCFluxE3(nLocalSolutionPts, 0.0);
                
                // Loop on the edges
                for(e = 0; e < fields[0]->GetExp(n)->GetNedges(); ++e)
                {   
                    // Number of edge points of edge e
                    nEdgePts = fields[0]->GetExp(n)->GetEdgeNumPoints(e);
                    
                    Array<OneD, NekDouble> tmparrayX1(nEdgePts, 0.0);
                    Array<OneD, NekDouble> tmparrayX2(nEdgePts, 0.0);
                    Array<OneD, NekDouble> fluxN    (nEdgePts, 0.0);
                    Array<OneD, NekDouble> fluxT    (nEdgePts, 0.0);                    
                    Array<OneD, NekDouble> fluxJumps(nEdgePts, 0.0);
                    
                    // Offset of the trace space correspondent to edge e
                    trace_offset  = fields[0]->GetTrace()->GetPhys_Offset(
                                                elmtToTrace[n][e]->GetElmtId());
                    
                    // Get the normals of edge e
                    const Array<OneD, const Array<OneD, NekDouble> > &normals = 
                    fields[0]->GetExp(n)->GetEdgeNormal(e);
                    
                    // Extract the edge values of flux-x on edge e and order 
                    // them accordingly to the order of the trace space 
                    fields[0]->GetExp(n)->GetEdgePhysVals(
                                                    e, elmtToTrace[n][e],
                                                    fluxX1 + phys_offset,
                                                    auxArray1 = tmparrayX1);
                    
                    // Extract the edge values of flux-y on edge e and order 
                    // them accordingly to the order of the trace space
                    fields[0]->GetExp(n)->GetEdgePhysVals(
                                                    e, elmtToTrace[n][e],
                                                    fluxX2 + phys_offset,
                                                    auxArray1 = tmparrayX2);
                    
                    // Multiply the edge components of the flux by the normal
                    for (i = 0; i < nEdgePts; ++i)
                    {
                        fluxN[i] = 
                        tmparrayX1[i]*m_traceNormals[0][trace_offset+i] + 
                        tmparrayX2[i]*m_traceNormals[1][trace_offset+i];
                    }
                    
                    // Subtract to the Riemann flux the discontinuous flux 
                    Vmath::Vsub(nEdgePts, 
                                &numericalFlux[trace_offset], 1, 
                                &fluxN[0], 1, &fluxJumps[0], 1);
                    
                    // Check the ordering of the jump vectors
                    if (fields[0]->GetExp(n)->GetEorient(e) == 
                        StdRegions::eBackwards)
                    {
                        Vmath::Reverse(nEdgePts, 
                                       auxArray1 = fluxJumps, 1,
                                       auxArray2 = fluxJumps, 1);
                    }

                    NekDouble fac = fields[0]->GetExp(n)->EdgeNormalNegated(e) ?
                    -1.0 : 1.0;

                    for (i = 0; i < nEdgePts; ++i)
                    {
                        if (m_traceNormals[0][trace_offset+i] != fac*normals[0][i] 
                        || m_traceNormals[1][trace_offset+i] != fac*normals[1][i])
                        {
                            fluxJumps[i] = -fluxJumps[i];
                        }
                    }

                    // Multiply jumps by derivatives of the correction functions
                    switch (e) 
                    {
                        case 0:
                            for (i = 0; i < nquad0; ++i)
                            {
                                // Multiply fluxJumps by Q factors
                                fluxJumps[i] = -(m_Q2D_e0[n][i]) * fluxJumps[i];
                                
                                for (j = 0; j < nquad1; ++j)
                                {
                                    cnt = i + j*nquad0;
                                    divCFluxE0[cnt] = fluxJumps[i] * m_dGL_xi2[n][j];
                                }
                            }
                            break;
                        case 1:
                            for (i = 0; i < nquad1; ++i)
                            {
                                // Multiply fluxJumps by Q factors
                                fluxJumps[i] = (m_Q2D_e1[n][i]) * fluxJumps[i];
                                
                                for (j = 0; j < nquad0; ++j)
                                {
                                    cnt = (nquad0)*i + j;
                                    divCFluxE1[cnt] = fluxJumps[i] * m_dGR_xi1[n][j];
                                }
                            }
                            break;
                        case 2:
                            for (i = 0; i < nquad0; ++i)
                            {
                                // Multiply fluxJumps by Q factors
                                fluxJumps[i] = (m_Q2D_e2[n][i]) * fluxJumps[i];
                                
                                for (j = 0; j < nquad1; ++j)
                                {
                                    cnt = (nquad0 - 1) + j*nquad0 - i;
                                    divCFluxE2[cnt] = fluxJumps[i] * m_dGR_xi2[n][j];
                                }
                            }
                            break;
                        case 3:
                            for (i = 0; i < nquad1; ++i)
                            {
                                // Multiply fluxJumps by Q factors
                                fluxJumps[i] = -(m_Q2D_e3[n][i]) * fluxJumps[i];
                                for (j = 0; j < nquad0; ++j)
                                {
                                    cnt = (nquad0*nquad1 - nquad0) + j - i*nquad0;
                                    divCFluxE3[cnt] = fluxJumps[i] * m_dGL_xi1[n][j];   
                                }
                            }
                            break;
                            
                        default:
                            ASSERTL0(false,"edge value (< 3) is out of range");
                            break;
                    }
                }
                
                // Sum each edge contribution
                Vmath::Vadd(nLocalSolutionPts, &divCFluxE0[0], 1, 
                            &divCFluxE1[0], 1, &divCFlux[phys_offset], 1);
                
                Vmath::Vadd(nLocalSolutionPts, &divCFlux[phys_offset], 1, 
                            &divCFluxE2[0], 1, &divCFlux[phys_offset], 1);
                
                Vmath::Vadd(nLocalSolutionPts, &divCFlux[phys_offset], 1, 
                            &divCFluxE3[0], 1, &divCFlux[phys_offset], 1);
            }
        } 
        
        /**
         * @brief Compute the divergence of the corrective flux for 2D problems
         *        where POINTSTYPE="GaussGaussLegendre"
         *
         * @param nConvectiveFields   Number of fields.
         * @param fields              Pointer to fields.
         * @param fluxX1              X1-volumetric flux in physical space.
         * @param fluxX2              X2-volumetric flux in physical space.
         * @param numericalFlux       Interface flux in physical space.
         * @param divCFlux            Divergence of the corrective flux.
         *
         * \todo: Switch on shapes eventually here.
         */
        
        void DiffusionLFR::v_DivCFlux_2D_Gauss(
            const int nConvectiveFields,
            const Array<OneD, MultiRegions::ExpListSharedPtr> &fields,
            const Array<OneD, const NekDouble> &fluxX1,
            const Array<OneD, const NekDouble> &fluxX2,
            const Array<OneD, const NekDouble> &numericalFlux,
                  Array<OneD,       NekDouble> &divCFlux)
        {
            int n, e, i, j, cnt;
            
            int nElements   = fields[0]->GetExpSize();
            int nLocalSolutionPts;
            int nEdgePts;
            int trace_offset;
            int phys_offset;
            int nquad0;
            int nquad1;
            
            Array<OneD, NekDouble> auxArray1, auxArray2;
            Array<OneD, LibUtilities::BasisSharedPtr> base;
            
            Array<OneD, Array<OneD, LocalRegions::ExpansionSharedPtr> >
            &elmtToTrace = fields[0]->GetTraceMap()->GetElmtToTrace();
            
            // Loop on the elements
            for(n = 0; n < nElements; ++n)
            {
                // Offset of the element on the global vector
                phys_offset = fields[0]->GetPhys_Offset(n);
                nLocalSolutionPts = fields[0]->GetExp(n)->GetTotPoints();
                
                base = fields[0]->GetExp(n)->GetBase();
                nquad0 = base[0]->GetNumPoints();
                nquad1 = base[1]->GetNumPoints();
                
                Array<OneD, NekDouble> divCFluxE0(nLocalSolutionPts, 0.0);
                Array<OneD, NekDouble> divCFluxE1(nLocalSolutionPts, 0.0);
                Array<OneD, NekDouble> divCFluxE2(nLocalSolutionPts, 0.0);
                Array<OneD, NekDouble> divCFluxE3(nLocalSolutionPts, 0.0);
                
                // Loop on the edges
                for(e = 0; e < fields[0]->GetExp(n)->GetNedges(); ++e)
                {
                    // Number of edge points of edge e
                    nEdgePts = fields[0]->GetExp(n)->GetEdgeNumPoints(e);
                    
                    Array<OneD, NekDouble> fluxN     (nEdgePts, 0.0);
                    Array<OneD, NekDouble> fluxT     (nEdgePts, 0.0);
                    Array<OneD, NekDouble> fluxN_R   (nEdgePts, 0.0);
                    Array<OneD, NekDouble> fluxN_D   (nEdgePts, 0.0);
                    Array<OneD, NekDouble> fluxJumps (nEdgePts, 0.0);
                    
                    // Offset of the trace space correspondent to edge e
                    trace_offset = fields[0]->GetTrace()->GetPhys_Offset(
                                                elmtToTrace[n][e]->GetElmtId());
                    
                    // Get the normals of edge e
                    const Array<OneD, const Array<OneD, NekDouble> > &normals =
                    fields[0]->GetExp(n)->GetEdgeNormal(e);
                    
                    // Extract the trasformed normal flux at each edge
                    switch (e)
                    {
                        case 0:
                            // Extract the edge values of transformed flux-y on
                            // edge e and order them accordingly to the order of
                            // the trace space
                            fields[0]->GetExp(n)->GetEdgePhysVals(
                                                        e, elmtToTrace[n][e],
                                                        fluxX2 + phys_offset,
                                                        auxArray1 = fluxN_D);
                            
                            Vmath::Neg   (nEdgePts, fluxN_D, 1);
                            
                            // Extract the physical Rieamann flux at each edge
                            Vmath::Vcopy(nEdgePts,
                                         &numericalFlux[trace_offset], 1,
                                         &fluxN[0], 1);
                            
                            // Check the ordering of vectors
                            if (fields[0]->GetExp(n)->GetEorient(e) ==
                                StdRegions::eBackwards)
                            {
                                Vmath::Reverse(nEdgePts,
                                               auxArray1 = fluxN, 1,
                                               auxArray2 = fluxN, 1);
                                
                                Vmath::Reverse(nEdgePts,
                                               auxArray1 = fluxN_D, 1,
                                               auxArray2 = fluxN_D, 1);
                            }
                            
                            // Transform Riemann Fluxes in the standard element
                            for (i = 0; i < nquad0; ++i)
                            {
                                // Multiply Riemann Flux by Q factors
                                fluxN_R[i] = (m_Q2D_e0[n][i]) * fluxN[i];
                            }
                            
                            for (i = 0; i < nEdgePts; ++i)
                            {
                                if (m_traceNormals[0][trace_offset+i] !=
                                    normals[0][i] ||
                                    m_traceNormals[1][trace_offset+i] !=
                                    normals[1][i])
                                {
                                    fluxN_R[i] = -fluxN_R[i];
                                }
                            }
                            
                            // Subtract to the Riemann flux the discontinuous
                            // flux
                            Vmath::Vsub(nEdgePts,
                                        &fluxN_R[0], 1,
                                        &fluxN_D[0], 1, &fluxJumps[0], 1);
                            
                            // Multiplicate the flux jumps for the correction
                            // function
                            for (i = 0; i < nquad0; ++i)
                            {
                                for (j = 0; j < nquad1; ++j)
                                {
                                    cnt = i + j*nquad0;
                                    divCFluxE0[cnt] =
                                    -fluxJumps[i] * m_dGL_xi2[n][j];
                                }
                            }
                            break;
                        case 1:
                            // Extract the edge values of transformed flux-x on
                            // edge e and order them accordingly to the order of
                            // the trace space
                            fields[0]->GetExp(n)->GetEdgePhysVals(
                                                        e, elmtToTrace[n][e],
                                                        fluxX1 + phys_offset,
                                                        auxArray1 = fluxN_D);
                            
                            // Extract the physical Rieamann flux at each edge
                            Vmath::Vcopy(nEdgePts,
                                         &numericalFlux[trace_offset], 1,
                                         &fluxN[0], 1);
                            
                            // Check the ordering of vectors
                            if (fields[0]->GetExp(n)->GetEorient(e) ==
                                StdRegions::eBackwards)
                            {
                                Vmath::Reverse(nEdgePts,
                                               auxArray1 = fluxN, 1,
                                               auxArray2 = fluxN, 1);
                                
                                Vmath::Reverse(nEdgePts,
                                               auxArray1 = fluxN_D, 1,
                                               auxArray2 = fluxN_D, 1);
                            }
                            
                            // Transform Riemann Fluxes in the standard element
                            for (i = 0; i < nquad1; ++i)
                            {
                                // Multiply Riemann Flux by Q factors
                                fluxN_R[i] = (m_Q2D_e1[n][i]) * fluxN[i];
                            }
                            
                            for (i = 0; i < nEdgePts; ++i)
                            {
                                if (m_traceNormals[0][trace_offset+i] !=
                                    normals[0][i] ||
                                    m_traceNormals[1][trace_offset+i] !=
                                    normals[1][i])
                                {
                                    fluxN_R[i] = -fluxN_R[i];
                                }
                            }
                            
                            // Subtract to the Riemann flux the discontinuous
                            // flux
                            Vmath::Vsub(nEdgePts,
                                        &fluxN_R[0], 1,
                                        &fluxN_D[0], 1, &fluxJumps[0], 1);
                            
                            // Multiplicate the flux jumps for the correction
                            // function
                            for (i = 0; i < nquad1; ++i)
                            {
                                for (j = 0; j < nquad0; ++j)
                                {
                                    cnt = (nquad0)*i + j;
                                    divCFluxE1[cnt] =
                                    fluxJumps[i] * m_dGR_xi1[n][j];
                                }
                            }
                            break;
                        case 2:
                            
                            // Extract the edge values of transformed flux-y on
                            // edge e and order them accordingly to the order of
                            // the trace space
                            
                            fields[0]->GetExp(n)->GetEdgePhysVals(
                                                        e, elmtToTrace[n][e],
                                                        fluxX2 + phys_offset,
                                                        auxArray1 = fluxN_D);
                            
                            // Extract the physical Rieamann flux at each edge
                            Vmath::Vcopy(nEdgePts,
                                         &numericalFlux[trace_offset], 1,
                                         &fluxN[0], 1);
                            
                            // Check the ordering of vectors
                            if (fields[0]->GetExp(n)->GetEorient(e) ==
                                StdRegions::eBackwards)
                            {
                                Vmath::Reverse(nEdgePts,
                                               auxArray1 = fluxN, 1,
                                               auxArray2 = fluxN, 1);
                                
                                Vmath::Reverse(nEdgePts,
                                               auxArray1 = fluxN_D, 1,
                                               auxArray2 = fluxN_D, 1);
                            }
                            
                            // Transform Riemann Fluxes in the standard element
                            for (i = 0; i < nquad0; ++i)
                            {
                                // Multiply Riemann Flux by Q factors
                                fluxN_R[i] = (m_Q2D_e2[n][i]) * fluxN[i];
                            }
                            
                            for (i = 0; i < nEdgePts; ++i)
                            {
                                if (m_traceNormals[0][trace_offset+i] !=
                                    normals[0][i] ||
                                    m_traceNormals[1][trace_offset+i] !=
                                    normals[1][i])
                                {
                                    fluxN_R[i] = -fluxN_R[i];
                                }
                            }
                            
                            // Subtract to the Riemann flux the discontinuous
                            // flux
                            
                            Vmath::Vsub(nEdgePts,
                                        &fluxN_R[0], 1,
                                        &fluxN_D[0], 1, &fluxJumps[0], 1);
                            
                            // Multiplicate the flux jumps for the correction
                            // function
                            for (i = 0; i < nquad0; ++i)
                            {
                                for (j = 0; j < nquad1; ++j)
                                {
                                    cnt = (nquad0 - 1) + j*nquad0 - i;
                                    divCFluxE2[cnt] =
                                    fluxJumps[i] * m_dGR_xi2[n][j];
                                }
                            }
                            break;
                        case 3:
                            // Extract the edge values of transformed flux-x on
                            // edge e and order them accordingly to the order of
                            // the trace space
                            
                            fields[0]->GetExp(n)->GetEdgePhysVals(
                                                        e, elmtToTrace[n][e],
                                                        fluxX1 + phys_offset,
                                                        auxArray1 = fluxN_D);
                            Vmath::Neg   (nEdgePts, fluxN_D, 1);
                            
                            // Extract the physical Rieamann flux at each edge
                            Vmath::Vcopy(nEdgePts,
                                         &numericalFlux[trace_offset], 1,
                                         &fluxN[0], 1);
                            
                            // Check the ordering of vectors
                            if (fields[0]->GetExp(n)->GetEorient(e) ==
                                StdRegions::eBackwards)
                            {
                                Vmath::Reverse(nEdgePts,
                                               auxArray1 = fluxN, 1,
                                               auxArray2 = fluxN, 1);
                                
                                Vmath::Reverse(nEdgePts,
                                               auxArray1 = fluxN_D, 1,
                                               auxArray2 = fluxN_D, 1);
                            }
                            
                            // Transform Riemann Fluxes in the standard element
                            for (i = 0; i < nquad1; ++i)
                            {
                                // Multiply Riemann Flux by Q factors
                                fluxN_R[i] = (m_Q2D_e3[n][i]) * fluxN[i];
                            }
                            
                            for (i = 0; i < nEdgePts; ++i)
                            {
                                if (m_traceNormals[0][trace_offset+i] !=
                                    normals[0][i] ||
                                    m_traceNormals[1][trace_offset+i] !=
                                    normals[1][i])
                                {
                                    fluxN_R[i] = -fluxN_R[i];
                                }
                            }
                            
                            // Subtract to the Riemann flux the discontinuous
                            // flux
                            
                            Vmath::Vsub(nEdgePts,
                                        &fluxN_R[0], 1,
                                        &fluxN_D[0], 1, &fluxJumps[0], 1);
                            
                            // Multiplicate the flux jumps for the correction
                            // function
                            for (i = 0; i < nquad1; ++i)
                            {
                                for (j = 0; j < nquad0; ++j)
                                {
                                    cnt = (nquad0*nquad1 - nquad0) + j
                                    - i*nquad0;
                                    divCFluxE3[cnt] =
                                    -fluxJumps[i] * m_dGL_xi1[n][j];
                                }
                            }
                            break;
                        default:
                            ASSERTL0(false,"edge value (< 3) is out of range");
                            break;
                    }
                }
                
                
                // Sum each edge contribution
                Vmath::Vadd(nLocalSolutionPts, &divCFluxE0[0], 1,
                            &divCFluxE1[0], 1, &divCFlux[phys_offset], 1);
                
                Vmath::Vadd(nLocalSolutionPts, &divCFlux[phys_offset], 1,
                            &divCFluxE2[0], 1, &divCFlux[phys_offset], 1);
                
                Vmath::Vadd(nLocalSolutionPts, &divCFlux[phys_offset], 1,
                            &divCFluxE3[0], 1, &divCFlux[phys_offset], 1);
            }
        }
        
    }// close namespace SolverUtils
}// close namespace nektar++
