/*
** (c) 1996-2000 The Regents of the University of California (through
** E.O. Lawrence Berkeley National Laboratory), subject to approval by
** the U.S. Department of Energy.  Your use of this software is under
** license -- the license agreement is attached and included in the
** directory as license.txt or you may contact Berkeley Lab's Technology
** Transfer Department at TTD@lbl.gov.  NOTICE OF U.S. GOVERNMENT RIGHTS.
** The Software was developed under funding from the U.S. Government
** which consequently retains certain rights as follows: the
** U.S. Government has been granted for itself and others acting on its
** behalf a paid-up, nonexclusive, irrevocable, worldwide license in the
** Software to reproduce, prepare derivative works, and perform publicly
** and display publicly.  Beginning five (5) years after the date
** permission to assert copyright is obtained from the U.S. Department of
** Energy, and subject to any subsequent five (5) year renewals, the
** U.S. Government is granted for itself and others acting on its behalf
** a paid-up, nonexclusive, irrevocable, worldwide license in the
** Software to reproduce, prepare derivative works, distribute copies to
** the public, perform publicly and display publicly, and to permit
** others to do so.
*/


#include <algorithm>
#include <iostream>

#include <IntVect.H>
#include <Box.H>
#include <MultiFab.H>
#include <multigrid.H>
#include <diffuser.H>
#include <globals.H>
#include <MACPROJ_F.H>
#include <VISC_F.H>
#include <ParmParse.H>
#include <BCTypes.H>

int diffuser::numSmoothCoarsen = 2;
int diffuser::numSmoothRefine  = 2;
int diffuser::ng               = 4;

// ************************************************************************
// ** constructor **
// ************************************************************************

diffuser::diffuser(const BoxArray& Grids, const Geometry& Geom,
                   MultiFab* Area, MultiFab& Vol) :
            grids(Grids), geom(Geom), area(Area), vol(Vol)
{
  ParmParse pp("diff");
  pp.get("tol",tol);

  pp.query("numSmoothCoarsen",numSmoothCoarsen);
  pp.query("numSmoothRefine",numSmoothRefine);

  ng = 2*std::max(numSmoothCoarsen,numSmoothRefine);
}

// ************************************************************************
// ** destructor **
// ************************************************************************

diffuser::~diffuser()
{
}

// ************************************************************************
// ** solveVel **
// ************************************************************************

void diffuser::solveVel(MultiFab * staten, 
                        MultiFab * rhonph, 
                        Real mu)
{
  diffuser_mg *solver;

  const Real* dx = geom.CellSize();

  MultiFab* source = new MultiFab(grids,1,ng-1);
  MultiFab* resid  = new MultiFab(grids,1,ng-1);
  MultiFab* alpha  = new MultiFab(grids,1,ng-1);
  MultiFab* phi    = new MultiFab(grids,1,ng);

  //
  // Create the alpha array = rho * volume.
  //
  int nGrow = 1;
  MultiFab::Copy(*alpha,*rhonph,0,0,1,nGrow);
  for (MFIter mfi(*alpha); mfi.isValid(); ++mfi)
  {
     (*alpha)[mfi].mult(vol[mfi],0,0,1);
  }
  alpha->FillBoundary(0,1);
  if (geom.isAnyPeriodic())
    geom.FillPeriodicBoundary(*alpha,0,1,true);

  //
  // Construct the local area array (with additional ghost cells).
  //
  MultiFab* local_area[BL_SPACEDIM];
  for (int dir = 0; dir < BL_SPACEDIM; dir++)
  {
    BoxArray edge_grids(grids);
    edge_grids.surroundingNodes(dir);
    local_area[dir] = new MultiFab(edge_grids,1,ng-1);
    MultiFab::Copy(*local_area[dir],area[dir],0,0,1,0);
    local_area[dir]->FillBoundary(0,1);
    if (geom.isAnyPeriodic())
      geom.FillPeriodicBoundary(*local_area[dir],0,1,true);
  }

  for (int n = 0; n < BL_SPACEDIM; n++) 
  {

    //
    // Fill the external ghost cells of the phi array with time (n+1) data
    //  at inflow faces before multiplying staten by volume. 
    //
    MultiFab::Copy(*phi,*staten,n,0,1,1);
    Box zero_box(BoxLib::grow(grids.minimalBox(),1));
    for (int idir = 0; idir < BL_SPACEDIM; idir++)
    {
      if (domain_bc[2*idir] == INLET)
        zero_box.growLo(idir,-1);
      if (domain_bc[2*idir+1] == INLET)
        zero_box.growHi(idir,-1);
    }
    phi->setVal(0.0,zero_box,0,1,1);
    phi->FillBoundary(0,1);
    if (geom.isAnyPeriodic())
      geom.FillPeriodicBoundary(*phi,0,1,true);
   
    //
    // Multiply the RHS by rho and volume.
    //
    for (MFIter mfi(*staten); mfi.isValid(); ++mfi)
    {
       (*staten)[mfi].mult((*rhonph)[mfi],0,n,1);
       (*staten)[mfi].mult(vol[mfi],0,n,1);
    }
    staten->FillBoundary(n,1);
    if (geom.isAnyPeriodic())
      geom.FillPeriodicBoundary(*staten,n,1,true);
    //
 
    // 
    // Compute the norm of the RHS after volume-weighting.
    // 
    Real rhsnorm = -1.e20;
    for (MFIter mfi(*staten); mfi.isValid(); ++mfi)
    {
      Real norm = (*staten)[mfi].norm(mfi.validbox(),0,n,1);
      rhsnorm = std::max(rhsnorm,norm);
    }
    ParallelDescriptor::ReduceRealMax(rhsnorm);

    //
    // Fill "source" by putting the problem into residual-correction form.
    //  First copy staten into source to be sent into the residual routine.
    //
    MultiFab::Copy(*source,*staten,n,0,1,0);
    Real resnorm = rescorr(phi,source,source,local_area,alpha,n,mu);
    source->FillBoundary(0,1);
    if (geom.isAnyPeriodic())
      geom.FillPeriodicBoundary(*source,0,1,true);

    //
    // Reset the solution array.
    //
    phi->setVal(0.);

    if (ParallelDescriptor::IOProcessor())
    {
      std::cout << "Volume-weighted         RHS Norm is " << rhsnorm << std::endl;
      std::cout << "Volume-weighted Initial Residual is " << resnorm << std::endl;
    }

    if (resnorm > rhsnorm*tol) 
    {
      solver = new diffuser_mg(grids,geom,phi,source,resid,local_area,alpha,dx,0,n,mu);
      solver->solve(tol*rhsnorm,rhsnorm,numSmoothCoarsen,numSmoothRefine);
      delete solver;
      MultiFab::Copy(*staten,*phi,0,n,1,0);
     }
  }

  delete source;
  delete resid;
  delete phi;
  delete alpha;
  for (int n = 0; n < BL_SPACEDIM; n++)
    delete local_area[n];
}

// ************************************************************************
// ** solveScal **
// ************************************************************************

void diffuser::solveScal(MultiFab* staten, 
                         Real mu,
                         int n)
{
  diffuser_mg *solver;

  const Real* dx = geom.CellSize();

  MultiFab* source = new MultiFab(grids,1,ng-1);
  MultiFab* resid  = new MultiFab(grids,1,ng-1);
  MultiFab* alpha  = new MultiFab(grids,1,ng-1);
  MultiFab* phi    = new MultiFab(grids,1,ng);

  //
  // Create the alpha array = volume.
  //
  MultiFab::Copy(*alpha,vol,0,0,1,1);
  alpha->FillBoundary(0,1);
  if (geom.isAnyPeriodic())
    geom.FillPeriodicBoundary(*alpha,0,1,true);

  //
  // Multiply the RHS by volume before we solve.
  //
  for (MFIter mfi(*staten); mfi.isValid(); ++mfi)
  {
     (*staten)[mfi].mult(vol[mfi],vol[mfi].box(),0,n,1);
  }
  staten->FillBoundary(n,1);
  if (geom.isAnyPeriodic())
    geom.FillPeriodicBoundary(*staten,n,1,true);

  MultiFab* local_area[BL_SPACEDIM];
  for (int dir = 0; dir < BL_SPACEDIM; dir++)
  {
    BoxArray edge_grids(grids);
    edge_grids.surroundingNodes(dir);
    local_area[dir] = new MultiFab(edge_grids,1,ng-1);
    MultiFab::Copy(*local_area[dir],area[dir],0,0,1,0);
    local_area[dir]->FillBoundary(0,1);
    if (geom.isAnyPeriodic())
      geom.FillPeriodicBoundary(*local_area[dir],0,1,true);
  }

  //
  // Fill the external ghost cells of the phi array with time (n+1) data.
  //
  int nGrow = 1;
  MultiFab::Copy(*phi,*staten,n,0,1,nGrow);
  Box zero_box(BoxLib::grow(grids.minimalBox(),1));
  for (int idir = 0; idir < BL_SPACEDIM; idir++)
  {
    if (domain_bc[2*idir] == INLET)
      zero_box.growLo(idir,-1);
    if (domain_bc[2*idir+1] == INLET)
      zero_box.growHi(idir,-1);
  }
  phi->setVal(0.0,zero_box,0,1,1);
  phi->FillBoundary(0,1);
  if (geom.isAnyPeriodic())
    geom.FillPeriodicBoundary(*phi,0,1,true);
 
  // 
  // Compute the norm of the RHS after volume-weighting.
  // 
  Real rhsnorm = -1.e20;
  for (MFIter mfi(*staten); mfi.isValid(); ++mfi)
  {
    Real norm = (*staten)[mfi].norm(mfi.validbox(),0,n,1);
    rhsnorm = std::max(rhsnorm,norm);
  }
  ParallelDescriptor::ReduceRealMax(rhsnorm);

  //
  // Fill "source" by putting the problem into residual-correction form.
  //  First copy staten into source to be sent into the residual routine.
  //
  MultiFab::Copy(*source,*staten,n,0,1,nGrow);
  Real resnorm = rescorr(phi,source,source,local_area,alpha,n,mu);
  source->FillBoundary(0,1);
  if (geom.isAnyPeriodic())
    geom.FillPeriodicBoundary(*source,0,1,true);

  //
  // Reset the solution array.
  //
  phi->setVal(0.);

  if (ParallelDescriptor::IOProcessor())
  {
    std::cout << "Diffusive Volume-Weighted         RHS Norm is " << rhsnorm << std::endl;
    std::cout << "Diffusive Volume-Weighted Initial Residual is " << resnorm << std::endl;
  }

  if (resnorm > rhsnorm*tol) 
  {
    solver = new diffuser_mg(grids,geom,phi,source,resid,local_area,alpha,dx,0,n,mu);
    solver->solve(tol*rhsnorm,rhsnorm,numSmoothCoarsen,numSmoothRefine);
    delete solver;
    int nGrow = 0;
    MultiFab::Copy(*staten,*phi,0,n,1,nGrow);
  }

  delete source;
  delete resid;
  delete phi;
  delete alpha;
  for (int n = 0; n < BL_SPACEDIM; n++)
    delete local_area[n];
}

// ************************************************************************
// ** Put into residual-correction form. **
// ************************************************************************

Real
diffuser::rescorr(MultiFab* phi, MultiFab* source, MultiFab* resid,
                  MultiFab* area[], MultiFab* alpha, int idir, Real mu)
{
  int level = 0;
  int ng = phi->nGrow();
  const Real* dx = geom.CellSize();
  Real  norm = -1.e20;
  Real rnorm = -1.e20;
  const int is_rz = CoordSys::IsRZ();
  for (MFIter mfi(*phi); mfi.isValid(); ++mfi)
  {
    int i = mfi.index();
    const int* lo = grids[i].loVect();
    const int* hi = grids[i].hiVect();

    FORT_RESID((*resid)[mfi].dataPtr(),(*phi)[mfi].dataPtr(),
               (*source)[mfi].dataPtr(),
               (*area[0])[mfi].dataPtr(),(*area[1])[mfi].dataPtr(),
#if (BL_SPACEDIM == 3)
               (*area[2])[mfi].dataPtr(),
#endif
               (*alpha)[mfi].dataPtr(),ARLIM(lo),ARLIM(hi),dx,
#if (BL_SPACEDIM == 2)
               &is_rz,
#endif
               &norm,bc[i].dataPtr(),&level,&idir,&mu,&ng);
    rnorm = std::max(rnorm,norm);
  }
  ParallelDescriptor::ReduceRealMax(rnorm);
  return rnorm;
}

// ************************************************************************
// ** constructor **
// ************************************************************************

diffuser_mg::diffuser_mg(const BoxArray & Grids,
                         const Geometry& Geom,
                         MultiFab * Phi,
                         MultiFab * Source,
                         MultiFab * Resid,
                         MultiFab* Area[],
                         MultiFab* Alpha,
                         const Real* Dx, 
			 int Level, 
                         int Idir,
                         Real Mu) :
       multigrid(Grids, Geom, Phi, Source, Resid)
{
  alpha = Alpha;
  level = Level;
  idir = Idir;
  mu = Mu;

  for (int n = 0; n < BL_SPACEDIM; n++)
    area[n] = Area[n];

  // Check whether we can coarsen all the grids separately
  int len = grids[0].length()[0];
  for (int i = 0; i < grids.size(); i++)
    for (int n = 0; n < BL_SPACEDIM; n++)
      len = std::min(len,grids[i].length()[n]);

  bool is_odd = false;
  for (int i = 0; i < grids.size(); i++)
    for (int n = 0; n < BL_SPACEDIM; n++)
      if ( (grids[i].length()[n]&1) != 0 ) is_odd = true;

  Box prob_domain(grids.minimalBox());

  // Check whether we can coarsen the domain as a whole
  int domain_len = prob_domain.length()[0];
  for (int n = 1; n < BL_SPACEDIM; n++)
      domain_len = std::min(domain_len,prob_domain.length()[n]);

  bool domain_is_odd = false;
  for (int n = 0; n < BL_SPACEDIM; n++)
      if ( (prob_domain.length()[n]&1) != 0 ) domain_is_odd = true;

  if ( domain_len < 8 || domain_is_odd)
  {
    Next = NULL;  
  } 
  else 
  {
    BoxArray newgrids;

    if (len < 8 || is_odd)
    {
      BoxList pd;
      pd.push_back(prob_domain);
      newgrids.define(pd);
    }
    else
    {
      newgrids.define(grids);
    }

    int ng = phi->nGrow();

    newgrids.coarsen(2);
    MultiFab* newphi = new MultiFab(newgrids,1,ng);
    newphi->setVal(0.0);

    Real newdx[BL_SPACEDIM];
    for (int n = 0; n < BL_SPACEDIM; n++)
      newdx[n] = 2.0*dx[n];

    Geometry newgeom;
    newgeom.define(BoxLib::coarsen(geom.Domain(),2));

    MultiFab* newsource = new MultiFab(newgrids,1,ng-1);
    MultiFab* newresid  = new MultiFab(newgrids,1,ng-1);
    MultiFab* newalpha  = new MultiFab(newgrids,1,ng-1);

    MultiFab* newarea[BL_SPACEDIM];
    for (int n = 0; n < BL_SPACEDIM; n++)
    {
      BoxArray new_edge_grids(newgrids);
      new_edge_grids.surroundingNodes(n);
      newarea[n] = new MultiFab(new_edge_grids,1,ng-1);
    }

    if (grids.size() == newgrids.size()) 
    {
     // 
     // In this case, the BoxArray newgrids is a coarsened version of grids.
     // 
     for (MFIter axmfi(*area[0]); axmfi.isValid(); ++axmfi)
     {
      int i = axmfi.index();
      const int* lo = grids[i].loVect();
      const int* hi = grids[i].hiVect();

      const int* newlo = newgrids[i].loVect();
      const int* newhi = newgrids[i].hiVect();

#if (BL_SPACEDIM == 2)
      FORT_COARSIGMA((*area[0])[axmfi].dataPtr(),(*area[1])[axmfi].dataPtr(),
                     (*newarea[0])[axmfi].dataPtr(),(*newarea[1])[axmfi].dataPtr(),
                     ARLIM(lo), ARLIM(hi),
                     ARLIM(newlo), ARLIM(newhi),&ng);
#elif (BL_SPACEDIM == 3)
      FORT_COARSIGMA((*area[0])[axmfi].dataPtr(),(*area[1])[axmfi].dataPtr(),(*area[2])[axmfi].dataPtr(),
                     (*newarea[0])[axmfi].dataPtr(),(*newarea[1])[axmfi].dataPtr(),
                     (*newarea[2])[axmfi].dataPtr(),
                     ARLIM(lo), ARLIM(hi),
                     ARLIM(newlo), ARLIM(newhi),&ng);
#endif
     }
     for (int n = 0; n < BL_SPACEDIM; n++)
     {
       newarea[n]->FillBoundary(0,1);
       if (newgeom.isAnyPeriodic())
         newgeom.FillPeriodicBoundary(*newarea[n],0,1,true);
     }
    }
    else 
    {

     // 
     // In this case, we are going from more than one grid to one grid 
     //   (the size of the domain) in the BoxArray newgrids.
     // 
     BL_ASSERT(newgrids.size() == 1);

     BoxList pd;
     pd.push_back(prob_domain);
     BoxArray grids_mf(pd);

     MultiFab* area_mf[BL_SPACEDIM];
     for (int n = 0; n < BL_SPACEDIM; n++)
     {
       BoxArray edge_grids(grids_mf);
       edge_grids.surroundingNodes(n);
       area_mf[n] = new MultiFab(edge_grids,1,ng-1);
       area_mf[n]->copy(*area[n],0,0,1);
     }


     for (MFIter axmfi(*area_mf[0]); axmfi.isValid(); ++axmfi)
     {
      int i = axmfi.index();
      const int* lo = grids_mf[i].loVect();
      const int* hi = grids_mf[i].hiVect();

      const int* newlo = newgrids[i].loVect();
      const int* newhi = newgrids[i].hiVect();

#if (BL_SPACEDIM == 2)
      FORT_COARSIGMA((*area_mf[0])[axmfi].dataPtr(),(*area_mf[1])[axmfi].dataPtr(),
                     (*newarea[0])[axmfi].dataPtr(), (*newarea[1])[axmfi].dataPtr(),
                     ARLIM(lo), ARLIM(hi),
                     ARLIM(newlo), ARLIM(newhi),&ng);
#elif (BL_SPACEDIM == 3)
      FORT_COARSIGMA((*area_mf[0])[axmfi].dataPtr(),(*area_mf[1])[axmfi].dataPtr(),(*area_mf[2])[axmfi].dataPtr(),
                     (*newarea[0])[axmfi].dataPtr(), (*newarea[1])[axmfi].dataPtr(),
                     (*newarea[2])[axmfi].dataPtr(),
                     ARLIM(lo), ARLIM(hi),
                     ARLIM(newlo), ARLIM(newhi),&ng);
#endif
     }
     for (int n = 0; n < BL_SPACEDIM; n++)
     {
       if (newgeom.isAnyPeriodic())
         newgeom.FillPeriodicBoundary(*newarea[n],0,1,true);
     }
    }

    int nex = resid->nGrow();

    if (newgrids.size() > 1)
    {
      for (MFIter alphamfi(*alpha); alphamfi.isValid(); ++alphamfi)
      {
        int i = alphamfi.index();
        const int* lo = grids[i].loVect();
        const int* hi = grids[i].hiVect();
  
        const int* newlo = newgrids[i].loVect();
        const int* newhi = newgrids[i].hiVect();
  
        FORT_RESTRICT((*alpha)[alphamfi].dataPtr(),
                      (*newalpha)[alphamfi].dataPtr(),
                      ARLIM(lo), ARLIM(hi),
                      ARLIM(newlo), ARLIM(newhi),&nex);
      }
    } else {
  
      BoxList pd;
      Box prob_domain(grids.minimalBox());
      pd.push_back(prob_domain);
      BoxArray grids_mf(pd);

      MultiFab alpha_mf(grids_mf,1,alpha->nGrow());
      alpha_mf.copy(*alpha,0,0,1);

      for (MFIter alphamfi(alpha_mf); alphamfi.isValid(); ++alphamfi)
      {
        int i = alphamfi.index();
        const int* lo = grids_mf[i].loVect();
        const int* hi = grids_mf[i].hiVect();

        const int* newlo = newgrids[i].loVect();
        const int* newhi = newgrids[i].hiVect();

        FORT_RESTRICT(alpha_mf[alphamfi].dataPtr(),
                      (*newalpha)[alphamfi].dataPtr(),
                      ARLIM(lo), ARLIM(hi),
                      ARLIM(newlo), ARLIM(newhi),&nex);
      }
    }
    newalpha->FillBoundary(0,1);
    if (newgeom.isAnyPeriodic())
      newgeom.FillPeriodicBoundary(*newalpha,0,1,true);

    Next = new diffuser_mg(newgrids, newgeom,
                           newphi, newsource,
                           newresid, newarea, newalpha,
                           newdx, level-1,idir,mu);
  }
  next = Next;
}

// ************************************************************************
// ** destructor **
// ************************************************************************
diffuser_mg::~diffuser_mg()

{
  if (Next != NULL) {
    delete Next->phi;
    delete Next->source;
    delete Next->resid;
    for (int n = 0; n < BL_SPACEDIM; n++)
      delete Next->area[n];
    delete Next->alpha;
    delete Next;
  }
}

// ************************************************************************
// ** residual **
// ************************************************************************

Real diffuser_mg::residual()
{
  Real  norm = -1.e20;
  Real rnorm = -1.e20;
  const int is_rz = CoordSys::IsRZ();
  int ng = phi->nGrow();

  for (MFIter mfi(*phi); mfi.isValid(); ++mfi)
  {
    int i = mfi.index();
    const int* lo = grids[i].loVect();
    const int* hi = grids[i].hiVect();

    int* bcptr = bc[i].dataPtr();
    if (grids.size() == 1) bcptr = domain_bc;

    FORT_RESID((*resid)[mfi].dataPtr(),
               (*phi)[mfi].dataPtr(),(*source)[mfi].dataPtr(),
               (*area[0])[mfi].dataPtr(),(*area[1])[mfi].dataPtr(),
#if (BL_SPACEDIM == 3)
               (*area[2])[mfi].dataPtr(),
#endif
               (*alpha)[mfi].dataPtr(),ARLIM(lo),ARLIM(hi),dx,
#if (BL_SPACEDIM == 2)
               &is_rz,
#endif
               &norm,bcptr,&level,&idir,&mu,&ng);
    rnorm = std::max(rnorm,norm);
  }

  ParallelDescriptor::ReduceRealMax(rnorm);

  resid->FillBoundary(0,1);
  if (geom.isAnyPeriodic())
    geom.FillPeriodicBoundary(*resid,0,1,true);

  return rnorm;
}

// ************************************************************************
// ** step **
// ************************************************************************

void diffuser_mg::step(int nngsrb)
{
  int ng = phi->nGrow();
  const int is_rz = CoordSys::IsRZ();
  for (MFIter mfi(*phi); mfi.isValid(); ++mfi)
  {
    int i = mfi.index();
    const int* lo = grids[i].loVect();
    const int* hi = grids[i].hiVect();

    if (grids.size() > 1)
    {
      FORT_GSRBV((*phi)[mfi].dataPtr()  ,(*source)[mfi].dataPtr(),
                 (*area[0])[mfi].dataPtr(),(*area[1])[mfi].dataPtr(),
#if (BL_SPACEDIM == 3)
                 (*area[2])[mfi].dataPtr(),
#endif
                 (*alpha)[mfi].dataPtr(),
                 ARLIM(lo),ARLIM(hi),
                 dx,bc[i].dataPtr(),&level,&idir,&nngsrb,&mu,
#if (BL_SPACEDIM == 2)
                 &is_rz,
#endif
                 &ng);
    } else {
        FORT_GSRBV((*phi)[mfi].dataPtr()  ,(*source)[mfi].dataPtr(),
                   (*area[0])[mfi].dataPtr(),(*area[1])[mfi].dataPtr(),
#if (BL_SPACEDIM == 3)
                   (*area[2])[mfi].dataPtr(),
#endif
                   (*alpha)[mfi].dataPtr(),
                   ARLIM(lo),ARLIM(hi),
                   dx,domain_bc,&level,&idir,&nngsrb,&mu,
#if (BL_SPACEDIM == 2)
                   &is_rz,
#endif
                   &ng);
    }
  }
  phi->FillBoundary(0,1);
  if (geom.isAnyPeriodic())
    geom.FillPeriodicBoundary(*phi,0,1,true);
}

// ************************************************************************
// ** Restrict **
// ************************************************************************

void diffuser_mg::Restrict()
{
  int nex = resid->nGrow();
  if (grids.size() == next->grids.size())
  {
    for (MFIter mfi(*resid); mfi.isValid(); ++mfi)
    {
      int i = mfi.index();
      const int* lo = grids[i].loVect();
      const int* hi = grids[i].hiVect();
  
      const int * loc = next->grids[i].loVect();
      const int * hic = next->grids[i].hiVect();
  
      FORT_RESTRICT((*resid)[mfi].dataPtr(), (*next->source)[mfi].dataPtr(),
                    ARLIM(lo),ARLIM(hi),ARLIM(loc),ARLIM(hic),&nex);
    }
    next->source->FillBoundary(0,1);

  } else {
  
    BoxList pd;
    Box prob_domain(grids.minimalBox());
    pd.push_back(prob_domain);
    BoxArray grids_mf(pd);

    MultiFab res_mf(grids_mf,1,resid->nGrow());
    res_mf.copy(*resid,0,0,1);

    for (MFIter mfi(res_mf); mfi.isValid(); ++mfi)
    {
      int i = mfi.index();
      const int* lo = grids_mf[i].loVect();
      const int* hi = grids_mf[i].hiVect();

      const int * loc = next->grids[i].loVect();
      const int * hic = next->grids[i].hiVect();

      FORT_RESTRICT(res_mf[mfi].dataPtr(), (*next->source)[mfi].dataPtr(),
                    ARLIM(lo),ARLIM(hi),ARLIM(loc),ARLIM(hic),&nex);
    }
  }
  if (next->geom.isAnyPeriodic())
    next->geom.FillPeriodicBoundary(*next->source,0,1,true);
}

// ************************************************************************
// ** interpolate **
// ************************************************************************

void diffuser_mg::interpolate()
{
  int ng = phi->nGrow();
  if (next->grids.size() > 1)
  {
    for (MFIter mfi(*phi); mfi.isValid(); ++mfi)
    {
      int i = mfi.index();
      const int* lo = grids[i].loVect();
      const int* hi = grids[i].hiVect();
  
      const int * loc = next->grids[i].loVect();
      const int * hic = next->grids[i].hiVect();
  
      FORT_INTERPOLATE((*phi)[mfi].dataPtr(), (*next->phi)[mfi].dataPtr(),
                       ARLIM(lo),ARLIM(hi),ARLIM(loc),ARLIM(hic),&ng);
    }
  } else {

    BoxList pd;
    Box prob_domain(grids.minimalBox());
    pd.push_back(prob_domain);
    BoxArray grids_mf(pd);

    MultiFab phi_interp(grids_mf,1,phi->nGrow());
    phi_interp.copy(*phi,0,0,1);

    for (MFIter mfi(phi_interp); mfi.isValid(); ++mfi)
    {
      int i = mfi.index();
      const int* lo = grids_mf[i].loVect();
      const int* hi = grids_mf[i].hiVect();
  
      const int * loc = next->grids[i].loVect();
      const int * hic = next->grids[i].hiVect();
  
      FORT_INTERPOLATE(phi_interp[mfi].dataPtr(), (*next->phi)[mfi].dataPtr(),
                       ARLIM(lo),ARLIM(hi),ARLIM(loc),ARLIM(hic),&ng);
    }
    phi->copy(phi_interp,0,0,1);
  }

  phi->FillBoundary(0,1);
  if (geom.isAnyPeriodic())
    geom.FillPeriodicBoundary(*phi,0,1,true);
}
