Program Listing for File fargo.cpp

Return to documentation for file (dataBlock/fargo.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 <string>
#include <vector>

#include "idefix.hpp"
#include "hydro.hpp"
#include "dataBlock.hpp"
#include "fargo.hpp"

// If no high order fargo, then choose the order according to reconstruction
#ifndef HIGH_ORDER_FARGO
  #if ORDER >= 3
  #define HIGH_ORDER_FARGO
  #endif
#endif

#ifdef HIGH_ORDER_FARGO
KOKKOS_FORCEINLINE_FUNCTION real PPMLim(real dvp, real dvm) {
  if(dvp*dvm >0.0) {
    real dqc = 0.5*(dvp+dvm);
    real d2q = 2.0*( fabs(dvp) < fabs(dvm) ? dvp : dvm);
    return( fabs(d2q) < fabs(dqc) ? d2q : dqc);
  }
  return(ZERO_F);
}

KOKKOS_INLINE_FUNCTION real FargoFlux(const IdefixArray4D<real> &Vin, int n, int k, int j, int i,
                                      int so, int ds, int sbeg, real eps,
                                      bool haveDomainDecomposition) {
  // compute shifted indices, taking into account the fact that we're periodic
  int sop1 = so+1;
  if(!haveDomainDecomposition && (sop1-sbeg >= ds)) sop1 = sop1-ds;
  int sop2 = sop1+1;
  if(!haveDomainDecomposition && (sop2-sbeg >= ds)) sop2 = sop2-ds;

  int som1 = so-1;
  if(!haveDomainDecomposition && (som1-sbeg< 0 )) som1 = som1+ds;
  int som2 = som1-1;
  if(!haveDomainDecomposition && (som2-sbeg< 0 )) som2 = som2+ds;

  real dqm2,dqm1,dqp1,dqp2, q0,qm1, qp1;
  #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
    q0 = Vin(n,k,so,i);
    qm1 = Vin(n,k,som1,i);
    qp1 = Vin(n,k,sop1,i);
    dqm2 = qm1 - Vin(n,k,som2,i);
    dqm1 = q0 - qm1;
    dqp1 = qp1 - q0;
    dqp2 = Vin(n,k,sop2,i) - qp1;
  #elif GEOMETRY == SPHERICAL
    q0 = Vin(n,so,j,i);
    qm1 = Vin(n,som1,j,i);
    qp1 = Vin(n,sop1,j,i);
    dqm2 = qm1 - Vin(n,som2,j,i);
    dqm1 = q0 - qm1;
    dqp1 = qp1 - q0;
    dqp2 = Vin(n,sop2,j,i) - qp1;
  #endif
    // slope limited values around the reference point
    real dqlm = PPMLim(dqm1,dqm2);
    real dql0 = PPMLim(dqp1,dqm1);
    real dqlp = PPMLim(dqp2,dqp1);

    real dqp = 0.5 * dqp1 - (dqlp - dql0) / 6.0;
    real dqm = -0.5 * dqm1 - (dql0 - dqlm) / 6.0;

    if(dqp*dqm>0.0) {
       dqp = dqm = 0.0;
    } else {
      if(FABS(dqp) >= 2.0*FABS(dqm)) dqp = -2.0*dqm;
      if(FABS(dqm) >= 2.0*FABS(dqp)) dqm = -2.0*dqp;
    }

    real qp = q0 + dqp;
    real qm = q0 + dqm;

    real dqc = dqp - dqm;
    real d2q = dqp + dqm;

    real F;
    if(eps > 0.0) {
      F = eps*(qp - 0.5*eps*(dqc + d2q*(3.0 - 2.0*eps)));
    } else {
      F = eps*(qm - 0.5*eps*(dqc - d2q*(3.0 + 2.0*eps)));
    }

  return(F);
}

#else// HIGH_ORDER_FARGO
KOKKOS_INLINE_FUNCTION real FargoFlux(const IdefixArray4D<real> &Vin, int n, int k, int j, int i,
                                      int so, int ds, int sbeg, real eps,
                                      bool haveDomainDecomposition) {
  // compute shifted indices, taking into account the fact that we're periodic
  int sop1 = so+1;
  if(!haveDomainDecomposition && (sop1-sbeg >= ds)) sop1 = sop1-ds;
  int som1 = so-1;
  if(!haveDomainDecomposition && (som1-sbeg< 0 )) som1 = som1+ds;
  int sign = (eps>=0) ? 1 : -1;
  real F, dqm, dqp, dq, V0;
  #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
    V0 = Vin(n,k,so,i);
    dqm = V0 - Vin(n,k,som1,i);
    dqp = Vin(n,k,sop1,i) - V0;
  #elif GEOMETRY == SPHERICAL
    V0 = Vin(n,so,j,i);
    dqm = V0 - Vin(n,som1,j,i);
    dqp = Vin(n,sop1,j,i) - V0;
  #endif
    dq = (dqp*dqm > ZERO_F ? TWO_F*dqp*dqm/(dqp + dqm) : ZERO_F);
    F = eps*(V0 + sign*0.5*dq*(1.0-sign*eps));
  return(F);
}

#endif // HIGH_ORDER_FARGO

KOKKOS_INLINE_FUNCTION int modPositive(int x, int divisor) {
  int m = x % divisor;
  return m + ((m >> 31) & divisor); // equivalent to m + (m < 0 ? divisor : 0);
}


void Fargo::Init(Input &input, DataBlock *data) {
  idfx::pushRegion("Fargo::Init");
  this->data = data;
  this->hydro = &(data->hydro);

  // A bit of arithmetic to get the sizes of the working array
  this->nghost = data->nghost;
  this->beg = data->beg;
  this->end = data->end;

  if(input.CheckBlock("Fargo")) {
    std::string opType = input.Get<std::string>("Fargo","velocity",0);
    if(opType.compare("userdef")==0) {
       #if GEOMETRY != SPHERICAL && GEOMETRY != POLAR
        IDEFIX_ERROR("Fargo+userdef is only compatible with SPHERICAL and POLAR geometries");
      #endif
      this->type=userdef;
    } else if(opType.compare("shearingbox")==0) {
      this->type=shearingbox;
      #if GEOMETRY != CARTESIAN
        // Actually, this has never really been tested, so assumes it doesn't work...
        IDEFIX_ERROR("Fargo+shearingbox is only compatible with cartesian geometry");
      #endif
    } else {
      IDEFIX_ERROR("Unknown fargo velocity in the input file. "
      "Only userdef and shearingbox are allowed");
    }
    this->maxShift = input.GetOrSet<int>("Fargo", "maxShift",0, 10);
  } else {
    // DEPRECATED: initialisation from the [Hydro] block
    if(input.CheckEntry("Hydro","fargo")>=0) {
      std::string opType = input.Get<std::string>("Hydro","fargo",0);
      if(opType.compare("userdef")==0) {
        this->type=userdef;
      } else if(opType.compare("shearingbox")==0) {
        this->type=shearingbox;
        #if GEOMETRY != CARTESIAN
          IDEFIX_ERROR("Fargo+shearingbox only compatible with cartesian geometry");
        #endif
      } else {
        IDEFIX_ERROR("Unknown fargo option in the input file. "
        "Only userdef and shearingbox are allowed");
      }
    } else {
      IDEFIX_ERROR("Fargo should be enabled in the input file with either userdef "
                    "or shearingbox options");
    }
  }

  #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
    // Check if there is a domain decomposition in the intended fargo direction
    if(data->mygrid->nproc[JDIR]>1) {
      haveDomainDecomposition = true;
      this->nghost[JDIR] += this->maxShift;
      this->beg[JDIR] += this->maxShift;
      this->end[JDIR] += this->maxShift;
      if(data->np_int[JDIR] < this->maxShift + data->nghost[JDIR]) {
        IDEFIX_ERROR("Subdomain size < Fargo:maxShift. "
                     "Try reducting the number of processes along X2");
      }
    }
    if(this->type==userdef)
      this->meanVelocity = IdefixArray2D<real>("FargoVelocity",data->np_tot[KDIR],
                                                             data->np_tot[IDIR]);
  #elif GEOMETRY == SPHERICAL
    // Check if there is a domain decomposition in the intended fargo direction
    if(data->mygrid->nproc[KDIR]>1) {
      haveDomainDecomposition = true;
      this->nghost[KDIR] += this->maxShift;
      this->beg[KDIR] += this->maxShift;
      this->end[KDIR] += this->maxShift;
      if(data->np_int[KDIR] < this->maxShift + data->nghost[KDIR]) {
        IDEFIX_ERROR("Subdomain size < Fargo:maxShift. "
                     "Try reducting the number of processes along X3");
      }
    }
    this->meanVelocity = IdefixArray2D<real>("FargoVelocity",data->np_tot[JDIR],
                                                             data->np_tot[IDIR]);
  #else
    IDEFIX_ERROR("Fargo is not compatible with the GEOMETRY you intend to use");
  #endif


  // Initialise our scratch space
  this->scrhUc = IdefixArray4D<real>("FargoVcScratchSpace",NVAR
                                      ,end[KDIR]-beg[KDIR] + 2*nghost[KDIR]
                                      ,end[JDIR]-beg[JDIR] + 2*nghost[JDIR]
                                      ,end[IDIR]-beg[IDIR] + 2*nghost[IDIR]);

  #if MHD == YES
    if(haveDomainDecomposition) {
      this->scrhVs = IdefixArray4D<real>("FargoVsScratchSpace",DIMENSIONS
                                          ,end[KDIR]-beg[KDIR] + 2*nghost[KDIR]+KOFFSET
                                          ,end[JDIR]-beg[JDIR] + 2*nghost[JDIR]+JOFFSET
                                          ,end[IDIR]-beg[IDIR] + 2*nghost[IDIR]+IOFFSET);


    } else {
      // A separate allocation for scrhVs is only needed with domain decomposition, otherwise,
      // we just make a reference to scrhVs
      this->scrhVs = hydro->Vs;
    }
  #endif
  #ifdef WITH_MPI
    if(haveDomainDecomposition) {
      std::vector<int> vars;
      for(int i=0 ; i < NVAR ; i++) {
        vars.push_back(i);
      }
      #if MHD == YES
        this->mpi.Init(data->mygrid, vars, this->nghost.data(), data->np_int.data(), true);
      #else
        this->mpi.Init(data->mygrid, vars, this->nghost.data(), data->np_int.data());
      #endif
    }
  #endif


  idfx::popRegion();
}

void Fargo::ShowConfig() {
  idfx::pushRegion("Fargo::ShowConfig");
  if(type==userdef) {
    idfx::cout << "Fargo: ENABLED with user-defined velocity function." << std::endl;
    if(!fargoVelocityFunc) {
      IDEFIX_ERROR("No Fargo velocity function has been enabled.");
    }
  } else if(type==shearingbox) {
    idfx::cout << "Fargo: ENABLED with shearing-box velocity function." << std::endl;
  } else {
    IDEFIX_ERROR("Something went wrong during Fargo initialisation");
  }
  #ifdef HIGH_ORDER_FARGO
    idfx::cout << "Fargo: using high order PPM advection scheme." << std::endl;
  #else
    idfx::cout << "Fargo: using standard PLM advection scheme." << std::endl;
  #endif
  if(haveDomainDecomposition) {
    idfx::cout << "Fargo: using domain decomposition along the azimuthal direction"
               << " with maxShift=" << this->maxShift << std::endl;
  }
  idfx::popRegion();
}

void Fargo::EnrollVelocity(FargoVelocityFunc myFunc) {
  if(this->type!=userdef) {
    IDEFIX_WARNING("Fargo velocity function enrollment requires Hydro/Fargo "
                 "to be set to userdef in .ini file");
  }
  this->fargoVelocityFunc = myFunc;
}

// This function fetches Fargo velocity when required
void Fargo::GetFargoVelocity(real t) {
  idfx::pushRegion("Fargo::GetFargoVelocity");
  if(type != userdef)
    IDEFIX_ERROR("Fargo::GetFargoVelocity should not be called when fargo != userdef");
  if(velocityHasBeenComputed == false) {
    if(fargoVelocityFunc== NULL) {
      IDEFIX_ERROR("No Fargo velocity function has been defined");
    }
    fargoVelocityFunc(*data, meanVelocity);
    velocityHasBeenComputed = true;
    if(this->haveDomainDecomposition) {
      CheckMaxDisplacement();
    }
  }
  idfx::popRegion();
}

// This function checks that the velocity provided does not lead to an azimuthal displacement
// larger than the one allowed
void Fargo::CheckMaxDisplacement() {
    IdefixArray2D<real> meanV = this->meanVelocity;
    int ibeg, iend, jbeg, jend;
    IdefixArray1D<real> xi;
    IdefixArray1D<real> xj;
    IdefixArray1D<real> dxk;
    [[maybe_unused]] FargoType fargoType = type;
    [[maybe_unused]] real sbS = hydro->sbS;
    real invDt = 0;

    // Get domain size
    #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
      ibeg = data->beg[IDIR];
      iend = data->end[IDIR];
      jbeg = data->beg[KDIR];
      jend = data->end[KDIR];
      xi = data->x[IDIR];
      xj = data->x[KDIR];
      dxk = data->dx[JDIR];
    #elif GEOMETRY == SPHERICAL
      ibeg = data->beg[IDIR];
      iend = data->end[IDIR];
      jbeg = data->beg[JDIR];
      jend = data->end[JDIR];
      xi = data->x[IDIR];
      xj = data->x[JDIR];
      dxk = data->dx[KDIR];
    #endif


    idefix_reduce("CheckMaxDt", jbeg, jend, ibeg, iend,
      KOKKOS_LAMBDA(int j, int i, real &invDtLoc) {
        real w,dphi;
        #if GEOMETRY == CARTESIAN
          if(fargoType==userdef) {
            w = meanV(j,i);
          } else if(fargoType==shearingbox) {
            w = sbS*xi(i);
          }
        #elif GEOMETRY == POLAR
          w = meanV(j,i)/xi(i);
        #elif GEOMETRY == SPHERICAL
          w = meanV(j,i)/(xi(i)*sin(xj(j)));
        #endif
        dphi = dxk(0);  // dphi is supposedly constant when using fargo.
        invDtLoc = FMAX(invDtLoc, FABS(w/dphi));
      },
      Kokkos::Max<real>(invDt));
  #ifdef WITH_MPI
    if(idfx::psize>1) {
          MPI_SAFE_CALL(MPI_Allreduce(MPI_IN_PLACE, &invDt, 1, realMPI, MPI_MAX, MPI_COMM_WORLD));
        }
  #endif
  this->dtMax = this->maxShift / invDt;
}

void Fargo::AddVelocity(const real t) {
  idfx::pushRegion("Fargo::AddVelocity");
  if(type==userdef) {
    GetFargoVelocity(t);
  }
  IdefixArray1D<real> x1 = data->x[IDIR];
  IdefixArray4D<real> Vc = hydro->Vc;
  IdefixArray2D<real> meanV = this->meanVelocity;
  [[maybe_unused]] FargoType fargoType = type;
  [[maybe_unused]] real sbS = hydro->sbS;

  idefix_for("FargoAddVelocity",
              0,data->np_tot[KDIR],
              0,data->np_tot[JDIR],
              0,data->np_tot[IDIR],
              KOKKOS_LAMBDA(int k, int j, int i) {
                #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
                  if(fargoType == userdef) {
                    Vc(VX2,k,j,i) += meanV(k,i);
                  } else if(fargoType == shearingbox) {
                    Vc(VX2,k,j,i) += sbS*x1(i);
                  }
                #elif GEOMETRY == SPHERICAL
                  Vc(VX3,k,j,i) += meanV(j,i);
                #endif
              });

  idfx::popRegion();
}

void Fargo::SubstractVelocity(const real t) {
  idfx::pushRegion("Fargo::SubstractVelocity");
  if(type==userdef) {
    GetFargoVelocity(t);
  }
  IdefixArray1D<real> x1 = data->x[IDIR];
  IdefixArray4D<real> Vc = hydro->Vc;
  [[maybe_unused]] IdefixArray2D<real> meanV = this->meanVelocity;
  [[maybe_unused]] FargoType fargoType = type;
  [[maybe_unused]] real sbS = hydro->sbS;

  idefix_for("FargoSubstractVelocity",
              0,data->np_tot[KDIR],
              0,data->np_tot[JDIR],
              0,data->np_tot[IDIR],
              KOKKOS_LAMBDA(int k, int j, int i) {
                #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
                  if(fargoType == userdef) {
                    Vc(VX2,k,j,i) -= meanV(k,i);
                  } else if(fargoType == shearingbox) {
                    Vc(VX2,k,j,i) -= sbS*x1(i);
                  }
                #elif GEOMETRY == SPHERICAL
                  Vc(VX3,k,j,i) -= meanV(j,i);
                #endif
              });

  idfx::popRegion();
}

void Fargo::StoreToScratch() {
  IdefixArray4D<real> Uc = hydro->Uc;
  IdefixArray4D<real> scrhUc = this->scrhUc;
  bool haveDomainDecomposition = this->haveDomainDecomposition;
  int maxShift = this->maxShift;

  idefix_for("Fargo:StoreUc",
            0,NVAR,
            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) {
              if(!haveDomainDecomposition) {
                scrhUc(n,k,j,i) = Uc(n,k,j,i);
              } else {
                #if GEOMETRY==POLAR || GEOMETRY==CARTESIAN
                  scrhUc(n,k,j+maxShift,i) = Uc(n,k,j,i);
                #elif GEOMETRY == SPHERICAL
                  scrhUc(n,k+maxShift,j,i) = Uc(n,k,j,i);
                #endif
              }
            });

  #if MHD == YES
    #ifdef EVOLVE_VECTOR_POTENTIAL
      // Update Vs to its latest
      hydro->emf.ComputeMagFieldFromA(hydro->Ve,hydro->Vs);
    #endif
    // in MHD mode, we need to copy Vs only when there is domain decomposition, otherwise,
    // we just make a reference (this is already done by init)
    if(haveDomainDecomposition) {
      IdefixArray4D<real> Vs = hydro->Vs;
      IdefixArray4D<real> scrhVs = this->scrhVs;
      idefix_for("Fargo:StoreVs",
              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) {
                  #if GEOMETRY==POLAR || GEOMETRY==CARTESIAN
                    scrhVs(n,k,j+maxShift,i) = Vs(n,k,j,i);
                  #elif GEOMETRY == SPHERICAL
                    scrhVs(n,k+maxShift,j,i) = Vs(n,k,j,i);
                  #endif
              });
    }
  #endif
  #if WITH_MPI
    if(haveDomainDecomposition) {
      #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
        this->mpi.ExchangeX2(scrhUc, scrhVs);
      #elif GEOMETRY == SPHERICAL
        this->mpi.ExchangeX3(scrhUc, scrhVs);
      #endif
    }
  #endif
}

void Fargo::ShiftSolution(const real t, const real dt) {
  idfx::pushRegion("Fargo::ShiftSolution");

  #if GEOMETRY == CYLINDRICAL
    IDEFIX_ERROR("Fargo is not compatible with cylindrical geometry "
                 "(which is intended to be 2D axisymmetric)");
  #else

  // Refresh the fargo velocity function
  if(type==userdef) {
    GetFargoVelocity(t);
  }
  if(haveDomainDecomposition && dt>dtMax) {
    std::stringstream message;
    message << "Your dt is too large with your domain decomposition and Fargo." << std::endl
            << "Got dt=" << dt << " and Fargo:dtmax=" << dtMax << "." << std::endl
            << "Try to increase [Fargo]:maxShift to a value larger than "
            << static_cast<int>(ceil(dt/(dtMax/maxShift))) << std::endl;
    IDEFIX_ERROR(message);
  }

  IdefixArray4D<real> Uc = hydro->Uc;
  IdefixArray4D<real> scrh = this->scrhUc;
  IdefixArray2D<real> meanV = this->meanVelocity;
  IdefixArray1D<real> x1 = data->x[IDIR];
  IdefixArray1D<real> x2 = data->x[JDIR];
  IdefixArray1D<real> dx2 = data->dx[JDIR];
  IdefixArray1D<real> dx3 = data->dx[KDIR];
  IdefixArray1D<real> sinx2 = data->sinx2;
  IdefixArray1D<real> sinx2m = data->sinx2m;
  [[maybe_unused]] FargoType fargoType = type;
  [[maybe_unused]] real sbS = hydro->sbS;
  bool haveDomainDecomposition = this->haveDomainDecomposition;
  int maxShift = this->maxShift;

  real Lphi;
  int sbeg, send;
  #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
    Lphi = data->mygrid->xend[JDIR] - data->mygrid->xbeg[JDIR];
    sbeg = data->beg[JDIR];
    send = data->end[JDIR];
  #elif GEOMETRY == SPHERICAL
    Lphi = data->mygrid->xend[KDIR] - data->mygrid->xbeg[KDIR];
    sbeg = data->beg[KDIR];
    send = data->end[KDIR];
  #else
    Lphi = 1.0;   // Do nothing, but initialize this.
  #endif

  // move Uc to scratch, and fill the ghost zones if required.
  StoreToScratch();

  idefix_for("Fargo:ShiftVc",
              0,NVAR,
              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) {
                real w,dphi;
                int s;
                #if GEOMETRY == CARTESIAN
                 if(fargoType==userdef) {
                  w = meanV(k,i);
                 } else if(fargoType==shearingbox) {
                  w = sbS*x1(i);
                 }
                 dphi = dx2(j);
                 s = j;
                #elif GEOMETRY == POLAR
                 w = meanV(k,i)/x1(i);
                 dphi = dx2(j);
                 s = j;
                #elif GEOMETRY == SPHERICAL
                 w = meanV(j,i)/(x1(i)*sinx2(j));
                 dphi = dx3(k);
                 s = k;
                #endif

                // Compute the offset in phi, modulo the full domain size
                real dL = std::fmod(w*dt, Lphi);

                // Translate this into # of cells
                int m = static_cast<int> (std::floor(dL/dphi+HALF_F));

                // get the remainding shift
                real eps = dL/dphi - m;

                // origin index before the shift
                // Note the trick to get a positive module i%%n = (i%n + n)%n;
                int ds = send-sbeg;

                // so is the "origin" index
                int so;
                if(haveDomainDecomposition) {
                  so = s-m + maxShift;    // maxshift corresponds to the offset between
                                          // the indices in scrh and in Uc
                } else {
                  so = sbeg + modPositive(s-m-sbeg, ds);
                }

                // Define Left and right fluxes
                // Fluxes are defined from slop-limited interpolation
                // Using Van-leer slope limiter (consistently with the main advection scheme)
                real Fl,Fr;

                if(eps>=ZERO_F) {
                  int som1 = so-1;
                  if(!haveDomainDecomposition && som1-sbeg< 0 ) som1 = som1+ds;
                  Fl = FargoFlux(scrh, n, k, j, i, som1, ds, sbeg, eps, haveDomainDecomposition);
                  Fr = FargoFlux(scrh, n, k, j, i, so, ds, sbeg, eps, haveDomainDecomposition);
                } else {
                  int sop1 = so+1;
                  if(!haveDomainDecomposition && sop1-sbeg >= ds) sop1 = sop1-ds;
                  Fl = FargoFlux(scrh, n, k, j, i, so, ds, sbeg, eps, haveDomainDecomposition);
                  Fr = FargoFlux(scrh, n, k, j, i, sop1, ds, sbeg, eps, haveDomainDecomposition);
                }

                #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
                  Uc(n,k,s,i) = scrh(n,k,so,i) - (Fr - Fl);
                #elif GEOMETRY == SPHERICAL
                  Uc(n,s,j,i) = scrh(n,so,j,i) - (Fr - Fl);
                #endif
              });

#if MHD == YES
  IdefixArray4D<real> scrhVs = this->scrhVs;
  IdefixArray3D<real> ex = hydro->emf.Ex1;
  IdefixArray3D<real> ey = hydro->emf.Ex2;
  IdefixArray3D<real> ez = hydro->emf.Ex3;
  IdefixArray1D<real> x1m = data->xl[IDIR];
  IdefixArray1D<real> x2m = data->xl[JDIR];
  IdefixArray1D<real> dmu = data->dmu;
  IdefixArray1D<real> dx1 = data->dx[IDIR];



  #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
    IdefixArray3D<real> ek = ez;
  #elif GEOMETRY == SPHERICAL
    IdefixArray3D<real> ek = ey;
  #endif

  idefix_for("Fargo:ComputeEk",
    data->beg[KDIR],data->end[KDIR]+KOFFSET,
    data->beg[JDIR],data->end[JDIR]+JOFFSET,
    data->beg[IDIR],data->end[IDIR]+IOFFSET,
    KOKKOS_LAMBDA(int k, int j, int i) {
      real w,dphi;
      int s;
      #if GEOMETRY == CARTESIAN
        if(fargoType==userdef) {
         w = 0.5*(meanV(k,i-1)+meanV(k,i));
        } else if(fargoType==shearingbox) {
         w = sbS*x1m(i);
        }
        dphi = dx2(j);
        s = j;
      #elif GEOMETRY == POLAR
        w = 0.5*(meanV(k,i-1)+meanV(k,i))/x1m(i);
        dphi = dx2(j);
        s = j;
      #elif GEOMETRY == SPHERICAL
        w = 0.5*(meanV(j,i-1)/x1(i-1)+meanV(j,i)/x1(i))/sinx2(j);
        dphi = dx3(k);
        s = k;
      #endif

      // Compute the offset in phi, modulo the full domain size
      real dL = std::fmod(w*dt, Lphi);

      // Translate this into # of cells
      int m = static_cast<int> (std::floor(dL/dphi+HALF_F));

      // get the remainding shift
      real eps = dL/dphi - m;

      // origin index before the shift
      // Note the trick to get a positive module i%%n = (i%n + n)%n;
      int n = send-sbeg;

      // so is the "origin" index
      int so;
      if(haveDomainDecomposition) {
        so = s-m + maxShift;    // maxshift corresponds to the offset between
                                // the indices in scrh and in Uc
      } else {
        so = sbeg + modPositive(s-m-sbeg,n);
      }

      #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
        if(eps>=ZERO_F) {
          int som1;
          if(haveDomainDecomposition) {
            som1 = so - 1;
          } else {
            som1 = sbeg + modPositive(so-1-sbeg,n);
          }
          ek(k,s,i) = FargoFlux(scrhVs, BX1s, k, j, i, som1, n, sbeg, eps, haveDomainDecomposition);

        } else {
          ek(k,s,i) = FargoFlux(scrhVs, BX1s, k, j, i, so, n, sbeg, eps, haveDomainDecomposition);
        }
        if(m>0) {
          for(int ss = s-m ; ss < s ; ss++) {
            int sc;
            if(haveDomainDecomposition) {
              sc = ss;
            } else {
              sc = sbeg + modPositive(ss-sbeg,n);
            }
            ek(k,s,i) += scrhVs(BX1s,k,sc,i);
          }
        } else {
          for(int ss = s ; ss < s-m ; ss++) {
            int sc;
            if(haveDomainDecomposition) {
              sc = ss;
            } else {
              sc = sbeg + modPositive(ss-sbeg,n);
            }
            ek(k,s,i) -= scrhVs(BX1s,k,sc,i);
          }
        }
      #elif GEOMETRY == SPHERICAL
        if(eps>=ZERO_F) {
          int som1;
          if(haveDomainDecomposition) {
            som1 = so - 1;
          } else {
            som1 = sbeg + modPositive(so-1-sbeg,n);
          }
          ek(s,j,i) = FargoFlux(scrhVs, BX1s, k, j, i, som1, n, sbeg, eps, haveDomainDecomposition);

        } else {
          ek(s,j,i) = FargoFlux(scrhVs, BX1s, k, j, i, so, n, sbeg, eps, haveDomainDecomposition);
        }
        if(m>0) {
          for(int ss = s-m ; ss < s ; ss++) {
            int sc;
            if(haveDomainDecomposition) {
              sc = ss;
            } else {
              sc = sbeg + modPositive(ss-sbeg,n);
            }
            ek(s,j,i) += scrhVs(BX1s,sc,j,i);
          }
        } else {
          for(int ss = s ; ss < s-m ; ss++) {
            int sc;
            if(haveDomainDecomposition) {
              sc = ss;
            } else {
              sc = sbeg + modPositive(ss-sbeg,n);
            }
            ek(s,j,i) -= scrhVs(BX1s,sc,j,i);
          }
        }
      #endif  // GEOMETRY

      ek(k,j,i) *= dphi;
    });

#if DIMENSIONS == 3
  // In cartesian and polar coordinates, ei is actually -Ex
  IdefixArray3D<real> ei = ex;

  idefix_for("Fargo:ComputeEi",
    data->beg[KDIR],data->end[KDIR]+KOFFSET,
    data->beg[JDIR],data->end[JDIR]+JOFFSET,
    data->beg[IDIR],data->end[IDIR]+IOFFSET,
    KOKKOS_LAMBDA(int k, int j, int i) {
      real w,dphi;
      int s;
      #if GEOMETRY == CARTESIAN
        if(fargoType==userdef) {
         w = 0.5*(meanV(k,i)+meanV(k-1,i));
        } else if(fargoType==shearingbox) {
         w = sbS*x1(i);
        }

        dphi = dx2(j);
        s = j;
      #elif GEOMETRY == POLAR
        w = 0.5*(meanV(k-1,i)+meanV(k,i))/x1(i);
        dphi = dx2(j);
        s = j;
      #elif GEOMETRY == SPHERICAL
        w = 0.5*(meanV(j-1,i)/sinx2(j-1)+meanV(j,i)/sinx2(j))/(x1(i));
        dphi = dx3(k);
        s = k;
      #endif

      // Compute the offset in phi, modulo the full domain size
      real dL = std::fmod(w*dt, Lphi);

      // Translate this into # of cells
      int m = static_cast<int> (std::floor(dL/dphi+HALF_F));

      // get the remainding shift
      real eps = dL/dphi - m;

      // origin index before the shift
      // Note the trick to get a positive module i%%n = (i%n + n)%n;
      int n = send-sbeg;

      // so is the "origin" index
      int so;
      if(haveDomainDecomposition) {
        so = s-m + maxShift;    // maxshift corresponds to the offset between
                                // the indices in scrh and in Uc
      } else {
        so = sbeg + modPositive(s-m-sbeg,n);
      }

      // Compute EMF due to the shift via second order reconstruction
      #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
        if(eps>=ZERO_F) {
          int som1;
          if(haveDomainDecomposition) {
            som1 = so - 1;
          } else {
            som1 = sbeg + modPositive(so-1-sbeg,n);
          }
          ei(k,s,i) = FargoFlux(scrhVs, BX3s, k, j, i, som1, n, sbeg, eps, haveDomainDecomposition);
        } else {
          ei(k,s,i) = FargoFlux(scrhVs, BX3s, k, j, i, so, n, sbeg, eps, haveDomainDecomposition);
        }
        if(m>0) {
          for(int ss = s-m ; ss < s ; ss++) {
            int sc;
            if(haveDomainDecomposition) {
              sc = ss;
            } else {
              sc = sbeg + modPositive(ss-sbeg,n);
            }
            ei(k,s,i) += scrhVs(BX3s,k,sc,i);
          }
        } else {
          for(int ss = s ; ss < s-m ; ss++) {
            int sc;
            if(haveDomainDecomposition) {
              sc = ss;
            } else {
              sc = sbeg + modPositive(ss-sbeg,n);
            }
            ei(k,s,i) -= scrhVs(BX3s,k,sc,i);
          }
        }
      #elif GEOMETRY == SPHERICAL
      if(eps>=ZERO_F) {
        int som1;
        if(haveDomainDecomposition) {
          som1 = so - 1;
        } else {
          som1 = sbeg + modPositive(so-1-sbeg,n);
        }
        ei(s,j,i) = FargoFlux(scrhVs, BX2s, k, j, i, som1, n, sbeg, eps, haveDomainDecomposition);
      } else {
        ei(s,j,i) = FargoFlux(scrhVs, BX2s, k, j, i, so, n, sbeg, eps, haveDomainDecomposition);
      }
      if(m>0) {
        for(int ss = s-m ; ss < s ; ss++) {
          int sc;
          if(haveDomainDecomposition) {
            sc = ss;
          } else {
            sc = sbeg + modPositive(ss-sbeg,n);
          }
          ei(s,j,i) += scrhVs(BX2s,sc,j,i);
        }
      } else {
        for(int ss = s ; ss < s-m ; ss++) {
          int sc;
          if(haveDomainDecomposition) {
            sc = ss;
          } else {
            sc = sbeg + modPositive(ss-sbeg,n);
          }
          ei(s,j,i) -= scrhVs(BX2s,sc,j,i);
        }
      }

      #endif  // GEOMETRY

      ei(k,j,i) *= dphi;
    });
#endif

  // Update field components according to the computed EMFS
  #ifndef EVOLVE_VECTOR_POTENTIAL
    IdefixArray4D<real> Vs = hydro->Vs;
    idefix_for("Fargo::EvolvMagField",
              data->beg[KDIR],data->end[KDIR]+KOFFSET,
              data->beg[JDIR],data->end[JDIR]+JOFFSET,
              data->beg[IDIR],data->end[IDIR]+IOFFSET,
      KOKKOS_LAMBDA (int k, int j, int i) {
  #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
        Vs(BX1s,k,j,i) += -  (ez(k,j+1,i) - ez(k,j,i) ) / dx2(j);

  #elif GEOMETRY == SPHERICAL
        Vs(BX1s,k,j,i) += -  sinx2(j)*dx2(j)/dmu(j)*(ey(k+1,j,i) - ey(k,j,i) ) / dx3(k);
  #endif

  #if GEOMETRY == CARTESIAN
        Vs(BX2s,k,j,i) += D_EXPAND(    0.0                          ,
                              + (ez(k,j,i+1) - ez(k,j,i)) / dx1(i)  ,
                              + (ex(k+1,j,i) - ex(k,j,i)) / dx3(k)  );
  #elif GEOMETRY == POLAR
        Vs(BX2s,k,j,i) += D_EXPAND(    0.0                                          ,
                              + (x1m(i+1) * ez(k,j,i+1) - x1m(i)*ez(k,j,i)) / dx1(i)  ,
                              + x1(i) *  (ex(k+1,j,i) - ex(k,j,i)) / dx3(k)  );
  #elif GEOMETRY == SPHERICAL
      #if DIMENSIONS == 3
        Vs(BX2s,k,j,i) += - (ex(k+1,j,i) - ex(k,j,i)) / dx3(k);
      #endif
  #endif // GEOMETRY

  #if DIMENSIONS == 3
    #if GEOMETRY == CARTESIAN || GEOMETRY == POLAR
      Vs(BX3s,k,j,i) -=  (ex(k,j+1,i) - ex(k,j,i) ) / dx2(j);
    #elif GEOMETRY == SPHERICAL
      real A1p = x1m(i+1)*x1m(i+1);
      real A1m = x1m(i)*x1m(i);
      real A2m = FABS(sinx2m(j));
      real A2p = FABS(sinx2m(j+1));
      Vs(BX3s,k,j,i) += sinx2(j) * (A1p * ey(k,j,i+1) - A1m * ey(k,j,i))/(x1(i)*dx1(i))
                        + (A2p * ex(k,j+1,i) - A2m * ex(k,j,i))/dx2(j);
    #endif
  #endif// DIMENSIONS
    });

  #else // EVOLVE_VECTOR_POTENTIAL
    // evolve field using vector potential
    IdefixArray4D<real> Ve = hydro->Ve;
    idefix_for("Fargo::EvolvMagField",
              data->beg[KDIR],data->end[KDIR]+KOFFSET,
              data->beg[JDIR],data->end[JDIR]+JOFFSET,
              data->beg[IDIR],data->end[IDIR]+IOFFSET,
      KOKKOS_LAMBDA (int k, int j, int i) {
        #if GEOMETRY == CARTESIAN
          #if DIMENSIONS == 3
            Ve(AX1e,k,j,i) += ex(k,j,i);
          #endif
          Ve(AX3e,k,j,i) += - ez(k,j,i);
        #elif GEOMETRY == POLAR
          #if DIMENSIONS == 3
            Ve(AX1e,k,j,i) += x1(i) * ex(k,j,i);
          #endif
          Ve(AX3e,k,j,i) +=  - x1m(i) * ez(k,j,i);
        #elif GEOMETRY == SPHERICAL
          #if DIMENSIONS == 3
            Ve(AX1e,k,j,i) += - x1(i) * sinx2m(j) * ex(k,j,i);
            Ve(AX2e,k,j,i) +=   x1m(i) * sinx2(j) * ey(k,j,i);
          #endif
        #endif
      });

  #endif // EVOLVE_VECTOR_POTENTIAL


#endif // MHD
#endif // GEOMETRY==CYLINDRICAL

  idfx::popRegion();
}