Program Listing for File rkl.cpp

Return to documentation for file (rkl/rkl.cpp)

// ***********************************************************************************
// Idefix MHD astrophysical code
// Copyright(C) 2020-2022 Geoffroy R. J. Lesur <geoffroy.lesur@univ-grenoble-alpes.fr>
// and other code contributors
// Licensed under CeCILL 2.1 License, see COPYING for more information
// ***********************************************************************************

#include <cmath>
#include <string>

#include "rkl.hpp"
#include "dataBlock.hpp"
#include "hydro.hpp"
#include "calcParabolicFlux.hpp"

#ifndef RKL_ORDER
  #define RKL_ORDER       2
#endif


void RKLegendre::AddVariable(int var, std::vector<int> &varListHost ) {
  bool haveit{false};

  // Check whether we have this variable in the list
  for(int i = 0 ; i < varListHost.size() ; i++) {
    if(varListHost[i] == var) haveit=true;
  }

  // We don't have it, then add it to the list
  if(!haveit) {
    varListHost.push_back(var);
  }
}

// Copy just the variables required by the RK scheme
void RKLegendre::Copy(IdefixArray4D<real> &out, IdefixArray4D<real> &in) {
  IdefixArray1D<int> vars = this->varList;

  idefix_for("RKL_Copy",
             0, nvarRKL,
             0, data->np_tot[KDIR],
             0, data->np_tot[JDIR],
             0, data->np_tot[IDIR],
             KOKKOS_LAMBDA(int n, int k, int j, int i) {
               const int var = vars(n);
               out(var,k,j,i) = in(var,k,j,i);
             });
}


void RKLegendre::Init(Input &input, DataBlock &datain) {
  idfx::pushRegion("RKLegendre::Init");

  // Save the datablock to which we are attached from now on
  this->data = &datain;

  cfl_rkl = input.GetOrSet<real> ("RKL","cfl",0, 0.5);

  // By default check nans in debug mode
  #ifdef DEBUG
  this->checkNan = true;
  #endif

  this->checkNan = input.GetOrSet<bool>("RKL","check_nan",0, this->checkNan);

  rmax_par = 100.0;

  // Make a list of variables

  std::vector<int> varListHost;
  // Create a list of variables
  // Viscosity
  if(data->hydro.viscosityStatus.isRKL) {
    haveVc = true;
    EXPAND( AddVariable(MX1, varListHost);   ,
            AddVariable(MX2, varListHost);   ,
            AddVariable(MX3, varListHost);   )

    #if HAVE_ENERGY
      AddVariable(ENG, varListHost);
    #endif
  }
  // Thermal diffusion
  #if HAVE_ENERGY
    if(data->hydro.thermalDiffusionStatus.isRKL) {
      haveVc = true;
      AddVariable(ENG, varListHost);
    }
  #endif
  // Ambipolar diffusion
  if(data->hydro.ambipolarStatus.isRKL || data->hydro.resistivityStatus.isRKL) {
    #if COMPONENTS == 3 && DIMENSIONS < 3
      haveVc = true;
      AddVariable(BX3, varListHost);
    #endif
    #if COMPONENTS >= 2 && DIMENSIONS < 2
      haveVc = true;
      AddVariable(BX2, varListHost);
    #endif
    #if HAVE_ENERGY
      haveVc = true;
      AddVariable(ENG, varListHost);
    #endif
    haveVs = true;
  }

  // Copy the list on the device
  varList = idfx::ConvertVectorToIdefixArray(varListHost);
  nvarRKL = varListHost.size();

  #ifdef WITH_MPI
    mpi.Init(datain.mygrid, varListHost, datain.nghost.data(), datain.np_int.data(), haveVs);
  #endif


  // Variable allocation

  dU = IdefixArray4D<real>("RKL_dU", NVAR,
                           data->np_tot[KDIR], data->np_tot[JDIR], data->np_tot[IDIR]);
  dU0 = IdefixArray4D<real>("RKL_dU0", NVAR,
                           data->np_tot[KDIR], data->np_tot[JDIR], data->np_tot[IDIR]);
  Uc0 = IdefixArray4D<real>("RKL_Uc0", NVAR,
                           data->np_tot[KDIR], data->np_tot[JDIR], data->np_tot[IDIR]);
  Uc1 = IdefixArray4D<real>("RKL_Uc1", NVAR,
                           data->np_tot[KDIR], data->np_tot[JDIR], data->np_tot[IDIR]);

  if(haveVs) {
    #ifdef EVOLVE_VECTOR_POTENTIAL
      dA = IdefixArray4D<real>("RKL_dA", AX3e+1,
                      data->np_tot[KDIR]+KOFFSET,
                      data->np_tot[JDIR]+JOFFSET,
                      data->np_tot[IDIR]+IOFFSET);
      dA0 = IdefixArray4D<real>("RKL_dA0", AX3e+1,
                        data->np_tot[KDIR]+KOFFSET,
                        data->np_tot[JDIR]+JOFFSET,
                        data->np_tot[IDIR]+IOFFSET);
      Ve0 = IdefixArray4D<real>("RKL_Ve0", AX3e+1,
                        data->np_tot[KDIR]+KOFFSET,
                        data->np_tot[JDIR]+JOFFSET,
                        data->np_tot[IDIR]+IOFFSET);
      Ve1 = IdefixArray4D<real>("RKL_Ve1", AX3e+1,
                        data->np_tot[KDIR]+KOFFSET,
                        data->np_tot[JDIR]+JOFFSET,
                        data->np_tot[IDIR]+IOFFSET);
    #else
      dB = IdefixArray4D<real>("RKL_dB", DIMENSIONS,
                        data->np_tot[KDIR]+KOFFSET,
                        data->np_tot[JDIR]+JOFFSET,
                        data->np_tot[IDIR]+IOFFSET);
      dB0 = IdefixArray4D<real>("RKL_dB0", DIMENSIONS,
                        data->np_tot[KDIR]+KOFFSET,
                        data->np_tot[JDIR]+JOFFSET,
                        data->np_tot[IDIR]+IOFFSET);
      Vs0 = IdefixArray4D<real>("RKL_Vs0", DIMENSIONS,
                        data->np_tot[KDIR]+KOFFSET,
                        data->np_tot[JDIR]+JOFFSET,
                        data->np_tot[IDIR]+IOFFSET);
      Vs1 = IdefixArray4D<real>("RKL_Vs1", DIMENSIONS,
                        data->np_tot[KDIR]+KOFFSET,
                        data->np_tot[JDIR]+JOFFSET,
                        data->np_tot[IDIR]+IOFFSET);
    #endif
  }

  idfx::popRegion();
}

void RKLegendre::ShowConfig() {
  #if RKL_ORDER == 1
    idfx::cout << "RKLegendre: 1st order scheme ENABLED." << std::endl;
  #elif RKL_ORDER == 2
    idfx::cout << "RKLegendre: 2nd order scheme ENABLED." << std::endl;
  #else
    IDEFIX_ERROR("Unknown RKL scheme order");
  #endif
  idfx::cout << "RKLegendre: RKL cfl set to " << cfl_rkl <<  "." << std::endl;
  if(haveVc) {
     idfx::cout << "RKLegendre: will evolve cell-centered fields Vc." << std::endl;
  }
  if(haveVs) {
     idfx::cout << "RKLegendre: will evolve face-centered fields Vs." << std::endl;
  }
  if(checkNan) {
    idfx::cout << "RKLegendre: will check consistency of solution in the integrator (slow!)."
               << std::endl;
  }
}

void RKLegendre::Cycle() {
  idfx::pushRegion("RKLegendre::Cycle");

  IdefixArray4D<real> dU = this->dU;
  IdefixArray4D<real> dU0 = this->dU0;
  IdefixArray4D<real> Uc = data->hydro.Uc;
  IdefixArray4D<real> Uc0 = this->Uc0;
  IdefixArray4D<real> Uc1 = this->Uc1;

  IdefixArray4D<real> dB = this->dB;
  IdefixArray4D<real> dB0 = this->dB0;
  IdefixArray4D<real> Vs = data->hydro.Vs;
  IdefixArray4D<real> Vs0 = this->Vs0;
  IdefixArray4D<real> Vs1 = this->Vs1;

  #ifdef EVOLVE_VECTOR_POTENTIAL
  IdefixArray4D<real> dA = this->dA;
  IdefixArray4D<real> dA0 = this->dA0;
  IdefixArray4D<real> Ve = data->hydro.Ve;
  IdefixArray4D<real> Ve0 = this->Ve0;
  IdefixArray4D<real> Ve1 = this->Ve1;
  #endif

  IdefixArray1D<int> varList = this->varList;
  real time = data->t;

  real dt_hyp = data->dt;

  // Tell the datablock that we're performing the RKL cycle
  data->rklCycle = true;

  // first RKL stage
  stage = 1;

  // Apply Boundary conditions on the full set of variables
  data->hydro.boundary.SetBoundaries(time);

  // Convert current state into conservative variable
  data->hydro.ConvertPrimToCons();

  // Coarsen the conservative variables if needed
  if(data->haveGridCoarsening) {
    data->Coarsen();
  }

  // Store the result in Uc0
  Copy(Uc0,Uc);
  if(haveVs) {
    #ifdef EVOLVE_VECTOR_POTENTIAL
      Kokkos::deep_copy(Ve0,Ve);
    #else
      Kokkos::deep_copy(Vs0,Vs);
    #endif
  }

  // evolve RKL stage
  EvolveStage(time);

  ComputeDt();

  Copy(dU0,dU);

  if(haveVs) {
    #ifdef EVOLVE_VECTOR_POTENTIAL
      Kokkos::deep_copy(dA0,dA);
    #else
      Kokkos::deep_copy(dB0,dB);
    #endif
  }

  // Compute number of RKL steps
  real nrkl;
  real scrh =  dt_hyp/dt;
#if RKL_ORDER == 1
  // Solution of quadratic Eq.
  // 2*dt_hyp/dt_exp = s^2 + s
  nrkl = 4.0*scrh / (1.0 + std::sqrt(1.0 + 8.0*scrh));
#elif RKL_ORDER == 2
  // Solution of quadratic Eq.
  // 4*dt_hyp/dt_exp = s^2 + s - 2
  nrkl = 4.0*(1.0 + 2.0*scrh)
          / (1.0 + sqrt(9.0 + 16.0*scrh));
#else
  //#error Invalid RKL_ORDER
#endif
  int rklstages = 1 + floor(nrkl);

  // Compute coefficients
  real w1, mu_tilde_j;
#if RKL_ORDER == 1
  w1 = 2.0/(rklstages*rklstages + rklstages);
  mu_tilde_j = w1;
#elif RKL_ORDER == 2
  real b_j, b_jm1, b_jm2, a_jm1;
  w1 = 4.0/(rklstages*rklstages + rklstages - 2.0);
  mu_tilde_j = w1/3.0;

  b_j = b_jm1 = b_jm2 = 1.0/3.0;
  a_jm1 = 1.0 - b_jm1;
#endif

#if RKL_ORDER == 1
  time = data->t + 0.5*dt_hyp*(stage*stage+stage)*w1;
#elif RKL_ORDER == 2
  time = data->t + 0.25*dt_hyp*(stage*stage+stage-2)*w1;
#endif
  if(haveVc) {
    idefix_for("RKL_Cycle_InitUc1",
              0, nvarRKL,
              data->beg[KDIR],data->end[KDIR],
              data->beg[JDIR],data->end[JDIR],
              data->beg[IDIR],data->end[IDIR],
      KOKKOS_LAMBDA (int n, int k, int j, int i) {
        int nv = varList(n);
        Uc1(nv,k,j,i) = Uc(nv,k,j,i);
        Uc(nv,k,j,i) = Uc1(nv,k,j,i) + mu_tilde_j*dt_hyp*dU0(nv,k,j,i);
      }
    );
  }
  if(haveVs) {
    #ifdef EVOLVE_VECTOR_POTENTIAL
      idefix_for("RKL_Cycle_InitVe1",
              0, AX3e+1,
              data->beg[KDIR],data->end[KDIR]+KOFFSET,
              data->beg[JDIR],data->end[JDIR]+JOFFSET,
              data->beg[IDIR],data->end[IDIR]+IOFFSET,
      KOKKOS_LAMBDA (int n, int k, int j, int i) {
        Ve1(n,k,j,i) = Ve(n,k,j,i);
        Ve(n,k,j,i) = Ve1(n,k,j,i) + mu_tilde_j*dt_hyp*dA0(n,k,j,i);
      });
      data->hydro.emf.ComputeMagFieldFromA(Ve,Vs);
    #else
      idefix_for("RKL_Cycle_InitVs1",
              0, DIMENSIONS,
              data->beg[KDIR],data->end[KDIR]+KOFFSET,
              data->beg[JDIR],data->end[JDIR]+JOFFSET,
              data->beg[IDIR],data->end[IDIR]+IOFFSET,
      KOKKOS_LAMBDA (int n, int k, int j, int i) {
        Vs1(n,k,j,i) = Vs(n,k,j,i);
        Vs(n,k,j,i) = Vs1(n,k,j,i) + mu_tilde_j*dt_hyp*dB0(n,k,j,i);
      });
    #endif
    data->hydro.boundary.ReconstructVcField(Uc);
  }

  // Coarsen conservative variables once they have been evolved
  if(data->haveGridCoarsening) {
    data->Coarsen();
  }

  // Convert current state into primitive variable
  data->hydro.ConvertConsToPrim();
  if(checkNan) {
    if(data->CheckNan()>0) {
      throw std::runtime_error(std::string("Nan found during RKL stage 1"));
    }
  }

  real mu_j, nu_j, gamma_j;
  // subStages loop
  for(stage=2; stage <= rklstages ; stage++) {
    //idfx::cout << "RKL: looping stages" << std::endl;
    // compute RKL coefficients
#if RKL_ORDER == 1
    mu_j       = (2.0*stage -1.0)/stage;
    mu_tilde_j = w1*mu_j;
    nu_j       = -(stage -1.0)/stage;
#elif RKL_ORDER == 2
    mu_j       = (2.0*stage -1.0)/stage * b_j/b_jm1;
    mu_tilde_j = w1*mu_j;
    gamma_j    = -a_jm1*mu_tilde_j;
    nu_j       = -(stage -1.0)*b_j/(stage*b_jm2);

    b_jm2 = b_jm1;
    b_jm1 = b_j;
    a_jm1 = 1.0 - b_jm1;
    b_j   = 0.5*(stage*stage+3.0*stage)/(stage*stage+3.0*stage+2.0);
#endif

    // Apply Boundary conditions
    this->SetBoundaries(time);

    // evolve RKL stage
    EvolveStage(time);
    if(haveVc) {
      // update Uc
      idefix_for("RKL_Cycle_UpdateUc",
              0, nvarRKL,
              data->beg[KDIR],data->end[KDIR],
              data->beg[JDIR],data->end[JDIR],
              data->beg[IDIR],data->end[IDIR],
        KOKKOS_LAMBDA (int n, int k, int j, int i) {
          const int nv = varList(n);
          real Y = mu_j*Uc(nv,k,j,i) + nu_j*Uc1(nv,k,j,i);
          Uc1(nv,k,j,i) = Uc(nv,k,j,i);
  #if RKL_ORDER == 1
          Uc(nv,k,j,i) = Y + dt_hyp*mu_tilde_j*dU(nv,k,j,i);
  #elif RKL_ORDER == 2
          Uc(nv,k,j,i) = Y + (1.0 - mu_j - nu_j)*Uc0(nv,k,j,i)
                                  + dt_hyp*mu_tilde_j*dU(nv,k,j,i)
                                  + gamma_j*dt_hyp*dU0(nv,k,j,i);
  #endif
          });
    }
    if(haveVs) {
      #ifdef EVOLVE_VECTOR_POTENTIAL
        // update Ve
        idefix_for("RKL_Cycle_UpdateVe",
                0, AX3e+1,
                data->beg[KDIR],data->end[KDIR]+KOFFSET,
                data->beg[JDIR],data->end[JDIR]+JOFFSET,
                data->beg[IDIR],data->end[IDIR]+IOFFSET,
          KOKKOS_LAMBDA (int n, int k, int j, int i) {
            real Y = mu_j*Ve(n,k,j,i) + nu_j*Ve1(n,k,j,i);
            Ve1(n,k,j,i) = Ve(n,k,j,i);
            #if RKL_ORDER == 1
              Ve(n,k,j,i) = Y + dt_hyp*mu_tilde_j*dA(n,k,j,i);
            #elif RKL_ORDER == 2
              Ve(n,k,j,i) = Y + (1.0 - mu_j - nu_j)*Ve0(n,k,j,i)
                                    + dt_hyp*mu_tilde_j*dA(n,k,j,i)
                                    + gamma_j*dt_hyp*dA0(n,k,j,i);
            #endif
          });
        data->hydro.emf.ComputeMagFieldFromA(Ve,Vs);
      #else
        // update Vs
        idefix_for("RKL_Cycle_UpdateVs",
                0, DIMENSIONS,
                data->beg[KDIR],data->end[KDIR]+KOFFSET,
                data->beg[JDIR],data->end[JDIR]+JOFFSET,
                data->beg[IDIR],data->end[IDIR]+IOFFSET,
          KOKKOS_LAMBDA (int n, int k, int j, int i) {
            real Y = mu_j*Vs(n,k,j,i) + nu_j*Vs1(n,k,j,i);
            Vs1(n,k,j,i) = Vs(n,k,j,i);
            #if RKL_ORDER == 1
              Vs(n,k,j,i) = Y + dt_hyp*mu_tilde_j*dB(n,k,j,i);
            #elif RKL_ORDER == 2
              Vs(n,k,j,i) = Y + (1.0 - mu_j - nu_j)*Vs0(n,k,j,i)
                                    + dt_hyp*mu_tilde_j*dB(n,k,j,i)
                                    + gamma_j*dt_hyp*dB0(n,k,j,i);
            #endif
          });
      #endif  // EVOLVE_VECTOR_POTENTIAL

      data->hydro.boundary.ReconstructVcField(Uc);
    }

    // Coarsen the flow if needed
    if(data->haveGridCoarsening) {
      data->Coarsen();
    }
    // Convert current state into primitive variable
    data->hydro.ConvertConsToPrim();

    if(checkNan) {
      if(data->CheckNan()>0) {
        throw std::runtime_error(std::string("Nan found during RKL stage ")+std::to_string(stage));
      }
    }

    // increment time
#if RKL_ORDER == 1
    time = data->t + 0.5*dt_hyp*(stage*stage+stage)*w1;
#elif RKL_ORDER == 2
    time = data->t + 0.25*dt_hyp*(stage*stage+stage-2)*w1;
#endif
  }

  // Tell the datablock that we're done
  data->rklCycle = false;
  idfx::popRegion();
}


void RKLegendre::ResetFlux() {
  idfx::pushRegion("RKLegendre::ResetFlux");
  IdefixArray4D<real> Flux = data->hydro.FluxRiemann;
  IdefixArray1D<int> vars = this->varList;
  idefix_for("RKL_ResetFlux",
             0,nvarRKL,
             0,data->np_tot[KDIR],
             0,data->np_tot[JDIR],
             0,data->np_tot[IDIR],
    KOKKOS_LAMBDA (int n, int k, int j, int i) {
      const int nv = vars(n);
      Flux(nv,k,j,i) = ZERO_F;
    }
  );
  idfx::popRegion();
}

void RKLegendre::ResetStage() {
  idfx::pushRegion("RKLegendre::ResetStage");

  IdefixArray4D<real> dU = this->dU;
  IdefixArray4D<real> Flux = data->hydro.FluxRiemann;
  #ifdef EVOLVE_VECTOR_POTENTIAL
    IdefixArray4D<real> dA = this->dA;
  #else
    IdefixArray4D<real> dB = this->dB;
  #endif
  IdefixArray3D<real> ex = data->hydro.emf.ex;
  IdefixArray3D<real> ey = data->hydro.emf.ey;
  IdefixArray3D<real> ez = data->hydro.emf.ez;
  IdefixArray1D<int> vars = this->varList;
  IdefixArray3D<real> invDt = data->hydro.InvDt;
  int stage = this->stage;
  int nvar = this->nvarRKL;
  bool haveVs=this->haveVs;

  bool haveVc = this->haveVc || (this->stage ==1 );


  idefix_for("RKL_ResetStage",
             0,data->np_tot[KDIR],
             0,data->np_tot[JDIR],
             0,data->np_tot[IDIR],
    KOKKOS_LAMBDA (int k, int j, int i) {
      if(haveVc) {
        for(int n = 0 ; n < nvar ; n++) {
          const int nv = vars(n);
          dU(nv,k,j,i) = ZERO_F;
        }
      }
      if(stage == 1)
        invDt(k,j,i) = ZERO_F;

      if(haveVs) {
        #ifdef EVOLVE_VECTOR_POTENTIAL
          for(int n=0; n < AX3e+1; n++) {
            dA(n,k,j,i) = ZERO_F;
          }
        #else
          for(int n=0; n < DIMENSIONS; n++) {
            dB(n,k,j,i) = ZERO_F;
          }
        #endif
        D_EXPAND( ez(k,j,i) = 0.0;    ,
                                      ,
                  ex(k,j,i) = 0.0;
                  ey(k,j,i) = 0.0;    )
      }
    });

  idfx::popRegion();
}


void RKLegendre::ComputeDt() {
  idfx::pushRegion("RKLegendre::ComputeDt");

  IdefixArray3D<real> invDt = data->hydro.InvDt;

  real newinvdt = ZERO_F;
  Kokkos::parallel_reduce("RKL_Timestep_reduction",
    Kokkos::MDRangePolicy<Kokkos::Rank<3, Kokkos::Iterate::Right, Kokkos::Iterate::Right>>
    ({0,0,0},{data->end[KDIR],data->end[JDIR],data->end[IDIR]}),
    KOKKOS_LAMBDA (int k, int j, int i, real &invdt) {
      invdt = std::fmax(invDt(k,j,i), invdt);
    },
    Kokkos::Max<real>(newinvdt)
  );

#ifdef WITH_MPI
  if(idfx::psize>1) {
    MPI_SAFE_CALL(MPI_Allreduce(MPI_IN_PLACE, &newinvdt, 1, realMPI, MPI_MAX, MPI_COMM_WORLD));
  }
#endif

  dt = 1.0/newinvdt;
  dt = (cfl_rkl*dt)/2.0; // parabolic time step

  idfx::popRegion();
}

template<int dir> void RKLegendre::LoopDir(real t) {
    ResetFlux();

    // CalcParabolicFlux
    data->hydro.CalcParabolicFlux<dir>(t);

    // Calc Right Hand Side
    CalcParabolicRHS<dir>(t);

    // Recursive: do next dimension
    LoopDir<dir+1>(t);
}

template<> void RKLegendre::LoopDir<DIMENSIONS>(real t) {
  // Do nothing
}

void RKLegendre::EvolveStage(real t) {
  idfx::pushRegion("RKLegendre::EvolveStage");

  ResetStage();

  if(haveVs && data->hydro.needRKLCurrent) data->hydro.CalcCurrent();

  // Loop on dimensions for the parabolic fluxes and RHS, starting from IDIR
  if(haveVc || stage == 1) LoopDir<IDIR>(t);

  if(haveVs) {
    data->hydro.emf.CalcNonidealEMF(t);
    data->hydro.emf.EnforceEMFBoundary();
    real dt=1.0;
    #ifdef EVOLVE_VECTOR_POTENTIAL
      data->hydro.emf.EvolveVectorPotential(dt, this->dA);
    #else
      data->hydro.emf.EvolveMagField(t, dt, this->dB);
    #endif
  }
  idfx::popRegion();
}

template <int dir>
void RKLegendre::CalcParabolicRHS(real t) {
  idfx::pushRegion("RKLegendre::CalcParabolicRHS");

  IdefixArray4D<real> Flux = data->hydro.FluxRiemann;
  IdefixArray3D<real> A    = data->A[dir];
  IdefixArray3D<real> dV   = data->dV;
  IdefixArray1D<real> x1m  = data->xl[IDIR];
  IdefixArray1D<real> x1   = data->x[IDIR];
  IdefixArray1D<real> sm   = data->sinx2m;
  IdefixArray1D<real> rt   = data->rt;
  IdefixArray1D<real> dmu  = data->dmu;
  IdefixArray1D<real> s    = data->sinx2;
  IdefixArray1D<real> dx   = data->dx[dir];
  IdefixArray1D<real> dx2  = data->dx[JDIR];
  IdefixArray3D<real> invDt = data->hydro.InvDt;
  IdefixArray3D<real> dMax = data->hydro.dMax;
  IdefixArray4D<real> viscSrc = data->hydro.viscosity.viscSrc;
  IdefixArray4D<real> dU = this->dU;
  IdefixArray1D<int> varList = this->varList;

  bool haveViscosity = data->hydro.viscosityStatus.isRKL;

  int ioffset,joffset,koffset;
  ioffset=joffset=koffset=0;
  // Determine the offset along which we do the extrapolation
  if(dir==IDIR) ioffset=1;
  if(dir==JDIR) joffset=1;
  if(dir==KDIR) koffset=1;


  idefix_for("CalcTotalFlux",
             0, this->nvarRKL,
             data->beg[KDIR],data->end[KDIR]+koffset,
             data->beg[JDIR],data->end[JDIR]+joffset,
             data->beg[IDIR],data->end[IDIR]+ioffset,
    KOKKOS_LAMBDA (int n, int k, int j, int i) {
      real Ax = A(k,j,i);

#if GEOMETRY != CARTESIAN
      if(Ax<SMALL_NUMBER)
        Ax=SMALL_NUMBER;    // Essentially to avoid singularity around poles
#endif

      const int nv = varList(n);

      Flux(nv,k,j,i) = Flux(nv,k,j,i) * Ax;

      // Curvature terms
#if    (GEOMETRY == POLAR       && COMPONENTS >= 2) \
    || (GEOMETRY == CYLINDRICAL && COMPONENTS == 3)
      if(dir==IDIR && nv==iMPHI) {
        // Conserve angular momentum, hence flux is R*Vphi
        Flux(iMPHI,k,j,i) = Flux(iMPHI,k,j,i) * FABS(x1m(i));
      }
#endif // GEOMETRY==POLAR OR CYLINDRICAL

#if GEOMETRY == SPHERICAL && COMPONENTS == 3
      if(dir==IDIR && nv==iMPHI) {
        Flux(iMPHI,k,j,i) = Flux(iMPHI,k,j,i) * FABS(x1m(i));
      } else if(dir==JDIR && nv==iMPHI) {
        Flux(iMPHI,k,j,i) = Flux(iMPHI,k,j,i) * FABS(sm(j));
      }
#endif // GEOMETRY == SPHERICAL && COMPONENTS == 3
    }
  );


  idefix_for("CalcRightHandSide",
             0, this->nvarRKL,
             data->beg[KDIR],data->end[KDIR],
             data->beg[JDIR],data->end[JDIR],
             data->beg[IDIR],data->end[IDIR],
    KOKKOS_LAMBDA (int n, int k, int j, int i) {
      constexpr const int ioffset = (dir==IDIR) ? 1 : 0;
      constexpr const int joffset = (dir==JDIR) ? 1 : 0;
      constexpr const int koffset = (dir==KDIR) ? 1 : 0;
      real rhs;

      const int nv = varList(n);

      rhs = -  ( Flux(nv, k+koffset, j+joffset, i+ioffset)
                     - Flux(nv, k, j, i))/dV(k,j,i);

      // Viscosity source terms
      if( haveViscosity && (nv-VX1 < COMPONENTS) && (nv-VX1>=0)) {
        rhs += viscSrc(nv-VX1,k,j,i);
      }

#if GEOMETRY != CARTESIAN
  #ifdef iMPHI
      if((dir==IDIR) && (nv == iMPHI)) {
        rhs /= x1(i);
      }
    #if (GEOMETRY == SPHERICAL) && (COMPONENTS == 3)
      if((dir==JDIR) && (nv == iMPHI)) {
        rhs /= FABS(s(j));
      }
    #endif // GEOMETRY
      // Nothing for KDIR
  #endif  // iMPHI
#endif // GEOMETRY != CARTESIAN

      // store the field components
      dU(nv,k,j,i) += rhs;
    });

  // Compute hyperbolic timestep only if we're in the first stage of the RKL loop
  if(stage==1) {
    // Grid coarsening
    bool haveGridCoarsening = false;
    IdefixArray2D<int> coarseningLevel;

    if(data->haveGridCoarsening) {
      haveGridCoarsening = data->coarseningDirection[dir];
      coarseningLevel = data->coarseningLevel[dir];
    }

    idefix_for("CalcDt",
             data->beg[KDIR],data->end[KDIR],
             data->beg[JDIR],data->end[JDIR],
             data->beg[IDIR],data->end[IDIR],
             KOKKOS_LAMBDA (int k, int j, int i) {
                constexpr const int ioffset = (dir==IDIR) ? 1 : 0;
                constexpr const int joffset = (dir==JDIR) ? 1 : 0;
                constexpr const int koffset = (dir==KDIR) ? 1 : 0;
               // Compute dt from max signal speed
                const int ig = ioffset*i + joffset*j + koffset*k;
                real dl = dx(ig);

                if(haveGridCoarsening) {
                  int factor;
                  //factor = 2^(coarsening-1)
                  if(dir==IDIR) {
                    factor = 1 << (coarseningLevel(k,j) - 1);
                  }
                  if(dir==JDIR) {
                    factor = 1 << (coarseningLevel(k,i) - 1);
                  }
                  if(dir==KDIR) {
                    factor = 1 << (coarseningLevel(j,i) - 1);
                  }
                  dl = dl * factor;
                }
                #if GEOMETRY == POLAR
                  if (dir==JDIR)
                    dl = dl*x1(i);

                #elif GEOMETRY == SPHERICAL
                  if (dir==JDIR)
                    dl = dl*rt(i);
                  else
                    if (dir==KDIR)
                      dl = dl*rt(i)*dmu(j)/dx2(j);
                 #endif

                invDt(k,j,i) += 0.5 * std::fmax(dMax(k+koffset,j+joffset,i+ioffset),
                                                        dMax(k,j,i)) / (dl*dl);
      });
  }

  idfx::popRegion();
}

void RKLegendre::SetBoundaries(real t) {
  idfx::pushRegion("RKLegendre::SetBoundaries");
  if(data->haveGridCoarsening) {
    data->hydro.CoarsenFlow(data->hydro.Vc);
    #if MHD==YES
      data->hydro.CoarsenMagField(data->hydro.Vs);
    #endif
  }

  // set internal boundary conditions
  // Disabled since this might affect fields that are NOT updated
  // by the MPI instance of RKLegendre
  //if(data->hydro.boundary.haveInternalBoundary)
  //   data->hydro.boundary.internalBoundaryFunc(*data, t);
  for(int dir=0 ; dir < DIMENSIONS ; dir++ ) {
      // MPI Exchange data when needed
      // We use the RKL instance MPI object to ensure that we only exchange the data
      // solved by RKL
    #ifdef WITH_MPI
    if(data->mygrid->nproc[dir]>1) {
      switch(dir) {
        case 0:
          this->mpi.ExchangeX1(data->hydro.Vc, data->hydro.Vs);
          break;
        case 1:
          this->mpi.ExchangeX2(data->hydro.Vc, data->hydro.Vs);
          break;
        case 2:
          this->mpi.ExchangeX3(data->hydro.Vc, data->hydro.Vs);
          break;
      }
    }
    #endif
    data->hydro.boundary.EnforceBoundaryDir(t, dir);
    #if MHD == YES
      // Reconstruct the normal field component when using CT
      if(haveVs) {
        data->hydro.boundary.ReconstructNormalField(dir);
      }
    #endif
  } // Loop on dimension ends

#if MHD == YES
  // Remake the cell-centered field.
  if(haveVs) {
    data->hydro.boundary.ReconstructVcField(data->hydro.Vc);
  }
#endif
  idfx::popRegion();
}