/*
** (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.
*/

//
// $Id: ProjOutFlowBC.cpp,v 1.28 2003/02/21 22:49:10 car Exp $
//
#include <winstd.H>

#include "ProjOutFlowBC.H"
#include "PROJOUTFLOWBC_F.H"
#include "ParmParse.H"

#define DEF_LIMITS(fab,fabdat,fablo,fabhi)   \
const int* fablo = (fab).loVect();           \
const int* fabhi = (fab).hiVect();           \
Real* fabdat = (fab).dataPtr();

#define DEF_CLIMITS(fab,fabdat,fablo,fabhi)  \
const int* fablo = (fab).loVect();           \
const int* fabhi = (fab).hiVect();           \
const Real* fabdat = (fab).dataPtr();

#define DEF_BOX_LIMITS(box,boxlo,boxhi)      \
const int* boxlo = (box).loVect();           \
const int* boxhi = (box).hiVect();

#if (BL_SPACEDIM == 3)
Real    ProjOutFlowBC::tol     = 1.0e-10; 
Real    ProjOutFlowBC::abs_tol = 5.0e-10;

int  ProjOutFlowBC_MG::verbose           = 0;
bool ProjOutFlowBC_MG::useCGbottomSolver = true;
Real ProjOutFlowBC_MG::cg_tol            = 1.0e-2;
Real ProjOutFlowBC_MG::cg_abs_tol        = 5.0e-12;
Real ProjOutFlowBC_MG::cg_max_jump       = 10.0;
int  ProjOutFlowBC_MG::cg_maxiter        = 40;
int  ProjOutFlowBC_MG::maxIters          = 40;
#endif

static
Box
semiSurroundingNodes (const Box& baseBox,
                      int        direction)
{
    Box sBox = BoxLib::surroundingNodes(baseBox);
    sBox.growHi(direction,-1);
    return sBox;
}

ProjOutFlowBC::ProjOutFlowBC ()
{
    ParmParse pp("projoutflow");
#if (BL_SPACEDIM == 3)
    pp.query("tol",tol);
    pp.query("abs_tol",abs_tol);
#endif
}

#if 1
void 
ProjOutFlowBC::computeBC (FArrayBox       velMF[][2*BL_SPACEDIM],
                          FArrayBox        divuMF[2*BL_SPACEDIM],
                          FArrayBox        rhoMF[2*BL_SPACEDIM],
                          FArrayBox        phiMF[2*BL_SPACEDIM],
                          const Geometry&   geom, 
                          Orientation*      outFaces,
                          int               numOutFlowFaces,
                          Real              gravity)
{
    BL_ASSERT(numOutFlowFaces <= 2*BL_SPACEDIM);
    int i,iface;

    int faces[2*BL_SPACEDIM];
    for (i = 0; i < numOutFlowFaces; i++) faces[i] = int(outFaces[i]);

    const Real* dx    = geom.CellSize();
    const Box& domain = geom.Domain();

    int lenx = domain.length()[0];
    int leny = domain.length()[1];

    int zeroIt[2*BL_SPACEDIM];
    for (int i = 0; i < numOutFlowFaces; i++) zeroIt[i] = 0;

#if (BL_SPACEDIM == 2)
    Real* rcen[2*BL_SPACEDIM];
    Real* redge[2*BL_SPACEDIM];
#endif

    FArrayBox ccExt[2*BL_SPACEDIM];

    int isPeriodic[BL_SPACEDIM];
    for (int dir = 0; dir < BL_SPACEDIM; dir++)
        isPeriodic[dir] = geom.isPeriodic(dir);

    IntVect loFiltered, hiFiltered;
    int isPeriodicFiltered[2*BL_SPACEDIM][BL_SPACEDIM];
    Real dxFiltered[2*BL_SPACEDIM][BL_SPACEDIM];

    for (int iface = 0; iface < numOutFlowFaces; iface++) {

    int outDir        = outFaces[iface].coordDir();
    //
    // Filter out the direction we don't care about.
    //
    int ncStripWidth = 1;
    Box origBox = BoxLib::adjCell(domain,outFaces[iface],ncStripWidth);
    IntVect lo = origBox.smallEnd();
    IntVect hi = origBox.bigEnd();
    //
    // Rearrange the box, dx, and isPeriodic so that the dimension that is 1
    // is the last dimension.
    //
    int cnt = 0;
    for (int dir = 0; dir < BL_SPACEDIM; dir++)
    {
        if (dir != outDir)
	{
            loFiltered[cnt] = lo[dir];
            hiFiltered[cnt] = hi[dir];
            dxFiltered[iface][cnt] = dx[dir];
            isPeriodicFiltered[iface][cnt] = isPeriodic[dir];
            cnt++;
	}
        else
        {
            loFiltered[BL_SPACEDIM-1] = lo[dir];
            hiFiltered[BL_SPACEDIM-1] = hi[dir];
            dxFiltered[iface][BL_SPACEDIM-1] = dx[dir];
            isPeriodicFiltered[iface][BL_SPACEDIM-1] = isPeriodic[dir];
	}
    }

    Box       faceBox(loFiltered,hiFiltered);
    for (int dir = 0; dir < BL_SPACEDIM-1; dir++)
      faceBox.grow(dir,1);

//  One for rho, one for divu, (BL_SPACEDIM-1) for velocity.
    ccExt[iface].resize(faceBox,BL_SPACEDIM+1);

#if (BL_SPACEDIM == 2)
    //
    // Make edge-centered and cc r (set = 1 if cartesian)
    //
    int perpDir = 1 - outDir;
    int r_len = domain.length()[perpDir]+1;
    rcen[iface] = new Real[r_len-1];
    redge[iface] = new Real[r_len];

    // Here we know the ordering of faces is XLO,YLO,XHI,YHI.
    if (CoordSys::IsRZ()) {
      if (faces[iface] == 0) {
        for (i=0;i<r_len  ;i++) redge[iface][i] = geom.ProbLo()[0];
        for (i=0;i<r_len-1;i++)  rcen[iface][i] = geom.ProbLo()[0];
      } else if (faces[iface] == 2) {
        for (i=0;i<r_len  ;i++) redge[iface][i] = geom.ProbHi()[0];
        for (i=0;i<r_len-1;i++)  rcen[iface][i] = geom.ProbHi()[0];
      } else if (faces[iface] == 1 || faces[iface]== 3) {
        for (i=0;i<r_len  ;i++) redge[iface][i] = geom.ProbLo()[0] + i     *dx[0];
        for (i=0;i<r_len-1;i++)  rcen[iface][i] = geom.ProbLo()[0] +(i+0.5)*dx[0];
      }
    } else {
      for (i = 0; i < r_len  ; i++) redge[iface][i] = 1.;
      for (i = 0; i < r_len-1; i++)  rcen[iface][i] = 1.;
    }
#else
    Array<Real> rcen;
    int r_len = 0;
#endif

    DEF_BOX_LIMITS(origBox,origLo,origHi);

    const int* ccElo = ccExt[iface].loVect();
    const int* ccEhi = ccExt[iface].hiVect();
    const Real*  rhoEPtr = ccExt[iface].dataPtr(0);
    const Real* divuEPtr = ccExt[iface].dataPtr(1);
    const Real*    uEPtr = ccExt[iface].dataPtr(2);

    DEF_LIMITS(divuMF[iface], divuPtr, divulo, divuhi);
    DEF_LIMITS( rhoMF[iface],  rhoPtr,  rholo,  rhohi);
    DEF_LIMITS( velMF[0][iface],  velPtr,  vello,  velhi);

    //
    // Extrapolate the velocities, divu, and rho to the outflow edge in
    // the shifted coordinate system (where the last dimension is 1).
    //
    Real hx = dx[0];
    FORT_EXTRAP_PROJ(ARLIM(vello),  ARLIM(velhi), velPtr,
                     ARLIM(divulo), ARLIM(divuhi), divuPtr,
                     ARLIM(rholo),  ARLIM(rhohi),rhoPtr,
#if (BL_SPACEDIM == 2)
                     &r_len,redge[iface],
#endif
                     ARLIM(ccElo),ARLIM(ccEhi),uEPtr,
                     ARLIM(ccElo),ARLIM(ccEhi),divuEPtr,
                     ARLIM(ccElo),ARLIM(ccEhi),rhoEPtr,
                     origLo,origHi,&faces[iface],&zeroIt[iface],&hx);
    }

    int connected = 0;

//  Test for whether multiple faces are touching.
//    therefore not touching.
    int numRegions = 1;
    if ( (numOutFlowFaces == 2) &&
         (outFaces[0].coordDir() == outFaces[1].coordDir()) )
       numRegions = 2;

    if (numOutFlowFaces == 2) {
       if (outFaces[0].coordDir() != outFaces[1].coordDir()) 
         connected = 1;
    } else if (numOutFlowFaces > 2) {
         connected = 1;
    }

     // Since we only use a constant dx in the Fortran,
     //  we'll assume for now we can choose either one.
     if (numRegions == 1 && numOutFlowFaces > 1)
       BL_ASSERT(dx[0] == dx[1]);

//   Note numRegions = 1 or 2, those are the only possibilities.
     for (int ireg = 0; ireg < numRegions; ireg++) 
     {

  // Define connected region.  In both 2-d and 3-d, if there are
  //   multiple outflow faces and it's not just two across from
  //   each other, then the multiple faces form a *single* 
  //   connected region.

       int zeroAll = zeroIt[ireg];
       if (numRegions == 1)
        for (int i = 0; i < numOutFlowFaces; i++)
         if (zeroIt[i] == 0) zeroAll = 0;

// HACK HACK
        zeroAll = 1;

       if (zeroAll) {

         for (int i=0; i < numOutFlowFaces; i++) {
           phiMF[i].setVal(0);
         }

       } else {

         int faces[2*BL_SPACEDIM];
         int numOutFlowFacesInRegion;
         if (numRegions == 1)
         {
           for (int i=0; i < numOutFlowFaces; i++) 
             faces[i] = int(outFaces[i]);
             numOutFlowFacesInRegion = numOutFlowFaces;
         } else if (numRegions == 2) {
             faces[0] = int(outFaces[ireg]);
             numOutFlowFacesInRegion = 1;
         }

#if (BL_SPACEDIM == 2)
         // Here we know the ordering of faces is XLO,XHI,YLO,YHI.

         int length = 0;
         Real *ccEptr0,*ccEptr1,*ccEptr2,*ccEptr3;
         Real *r0,*r1,*r2,*r3;
         for (int i=0; i < numOutFlowFacesInRegion; i++) 
         {
           if (faces[i] == 0) {
             ccEptr0 = ccExt[i].dataPtr();
                  r0 = rcen[i];
             length = length + leny;
           } else if (faces[i] == 1) {
             ccEptr1 = ccExt[i].dataPtr();
                  r1 = rcen[i];
             length = length + lenx;
           } else if (faces[i] == 2) {
             ccEptr2 = ccExt[i].dataPtr();
                  r2 = rcen[i];
             length = length + leny;
           } else if (faces[i] == 3) {
             ccEptr3 = ccExt[i].dataPtr();
                  r3 = rcen[i];
             length = length + lenx;
           }
         }

         IntVect loconn;
         IntVect hiconn;

         loconn[0] = 0;
         hiconn[0] = length-1;
         loconn[BL_SPACEDIM-1] = 0;
         hiconn[BL_SPACEDIM-1] = 0;
         Box connected_region(loconn,hiconn);
         FArrayBox ccE_conn(connected_region,1);
  
         hiconn[0] = length;
         Box nodal_connected_region(loconn,hiconn);
         FArrayBox x(nodal_connected_region,1);
         FArrayBox s(nodal_connected_region,1);
         s.setVal(0.);

         ccE_conn.setVal(1.e200);

         int per = 0;
         if ( (numOutFlowFaces == 1) || 
              (numRegions == 2) ) per = isPeriodicFiltered[ireg][0];

         FORT_FILL_ONED(&lenx,&leny,&length,faces,&numOutFlowFacesInRegion,
                        ccEptr0, ccEptr1, ccEptr2, ccEptr3,
                        r0,r1,r2,r3,
                        ccE_conn.dataPtr(),s.dataPtr(),&per,
                        &(dx[0]),&(dx[1]));

         if (numOutFlowFaces == 2*BL_SPACEDIM) per = 1;

         FORT_HGPHIBC(dx,
                      ccE_conn.dataPtr(0),
                      s.dataPtr(),
                      x.dataPtr(),
                      &length,&per);

         Real *phiptr0,*phiptr1,*phiptr2,*phiptr3;

         for (int i=0; i < numOutFlowFacesInRegion; i++) 
         {
           if (faces[i] == 0) {
             phiptr0 = phiMF[i].dataPtr();
           }
           if (faces[i] == 1) {
             phiptr1 = phiMF[i].dataPtr();
           }
           if (faces[i] == 2) {
             phiptr2 = phiMF[i].dataPtr();
           }
           if (faces[i] == 3) {
             phiptr3 = phiMF[i].dataPtr();
           }
         }

         FORT_ALLPHI_FROM_X(&lenx,&leny,&length,faces,&numOutFlowFaces,
                            phiptr0, phiptr1, phiptr2, phiptr3,
                            x.dataPtr());
#else

        // Assert that, if faces are connected, one of the coordinate
        //  directions has no outflow faces.
        int outx = 0;
        int outy = 0;
        int outz = 0;
        for (int iface = 0; iface < numOutFlowFaces; iface++) {
          int outDir        = outFaces[iface].coordDir();
          if (outDir == 0) outx = 1;
          if (outDir == 1) outy = 1;
          if (outDir == 2) outz = 1;
        }
        int sum_dirs = outx + outy + outz;
        BL_ASSERT (sum_dirs > 0 && sum_dirs < 3);
        BL_ASSERT(dx[1] == dx[2]);

        // Here we know the ordering of faces is XLO,YLO,ZLO,XHI,YHI,ZHI.

        // FOR NOW: ASSERT THAT NO OUTFLOW FACES IN Z-DIR!
        BL_ASSERT (outz == 0);

        int lenz = domain.length()[2];

        int length = 0;
        int  width = lenz;
        Real *ccEptr0,*ccEptr1,*ccEptr2,*ccEptr3,*ccEptr4,*ccEptr5;
        for (int i=0; i < numOutFlowFaces; i++) 
        {
          if (faces[i] == 0) {
            ccEptr0 = ccExt[i].dataPtr();
            length = length + leny*lenz;
          } else if (faces[i] == 1) {
            ccEptr1 = ccExt[i].dataPtr();
            length = length + lenx*lenz;
          } else if (faces[i] == 2) {
            ccEptr2 = ccExt[i].dataPtr();
            length = length + lenx*leny;
          } else if (faces[i] == 3) {
            ccEptr3 = ccExt[i].dataPtr();
            length = length + leny*lenz;
          } else if (faces[i] == 4) {
            ccEptr4 = ccExt[i].dataPtr();
            length = length + lenx*lenz;
          } else if (faces[i] == 5) {
            ccEptr5 = ccExt[i].dataPtr();
            length = length + lenx*leny;
          } else {
            std::cout << "OOPS - DIDNT PROGRAM FOR Z-OUTFLOW FACES! " << i << 
                    " " << faces[i] << std::endl;
            exit(0);
          }
        }

        IntVect loconn;
        IntVect hiconn;

        loconn[0] = 0;
        hiconn[0] = length-1;
        loconn[1] = 0;
        hiconn[1] = width-1;
        loconn[BL_SPACEDIM-1] = 0;
        hiconn[BL_SPACEDIM-1] = 0;
        Box connected_region(loconn,hiconn);
        FArrayBox ccE_conn(connected_region,BL_SPACEDIM+1);
 
        hiconn[0] = length;
        hiconn[1] = width;
        Box nodal_connected_region(loconn,hiconn);
        FArrayBox phiFiltered(nodal_connected_region,1);
        phiFiltered.setVal(0.);

        FORT_FILL_TWOD(&lenx,&leny,&lenz,&length,&width,
                       faces,&numOutFlowFaces,
                       ccEptr0, ccEptr1, ccEptr2, ccEptr3, ccEptr4, ccEptr5,
                       ccE_conn.dataPtr());
      
        FArrayBox rhs_temp, beta;
  
        int* per = new int[2];
        per[0] = (numOutFlowFaces == 2*BL_SPACEDIM) ? 1 : 0;
        per[1] = isPeriodic[BL_SPACEDIM-1];
        
        computeCoefficients(rhs_temp,beta,ccE_conn,connected_region,dxFiltered[0],per);

        //
        // Need phi to have ghost cells.
        //
        Box phiGhostBox = OutFlowBC::SemiGrow(phiFiltered.box(),1,BL_SPACEDIM-1);
        FArrayBox phi(phiGhostBox,1);
        phi.setVal(0);
        phi.copy(phiFiltered);
      
        Box grownRhs = OutFlowBC::SemiGrow(rhs_temp.box(),1,BL_SPACEDIM-1);
        FArrayBox rhs(grownRhs,1);
        rhs.setVal(0);
        rhs.copy(rhs_temp);
        FArrayBox resid(rhs.box(),1);
        ProjOutFlowBC_MG proj_mg(connected_region,&phi,&rhs,&resid,&beta,
                                 dxFiltered[0],per);

        proj_mg.solve(tol,abs_tol,2,2,proj_mg.MaxIters(),proj_mg.Verbose());
      
        DEF_LIMITS(phi,phiPtr,phi_lo,phi_hi);
        DEF_BOX_LIMITS(connected_region,lo,hi);
        //
        // Subtract the average phi.
        //
//      FORT_HGSUBTRACTAVGPHI(ARLIM(phi_lo),ARLIM(phi_hi),phiPtr,
//                            lo,hi,isPeriodicFiltered);

        //
        // Translate the solution back to the original coordinate system.
        //
//      FORT_HG_RESHIFT_PHI(ARLIM(phiFab_lo),ARLIM(phiFab_hi),phiFabPtr,
//                          ARLIM(phi_lo),ARLIM(phi_hi),phiPtr,&face);

        Real *phiptr0,*phiptr1,*phiptr2,*phiptr3,*phiptr4,*phiptr5;

        for (int i=0; i < numOutFlowFaces; i++) 
        {
           if (faces[i] == 0) {
             phiptr0 = phiMF[i].dataPtr();
           } else 
           if (faces[i] == 1) {
             phiptr1 = phiMF[i].dataPtr();
           } else 
           if (faces[i] == 2) {
             phiptr2 = phiMF[i].dataPtr();
           } else 
           if (faces[i] == 3) {
             phiptr3 = phiMF[i].dataPtr();
           } else 
           if (faces[i] == 4) {
             phiptr4 = phiMF[i].dataPtr();
           } else 
           if (faces[i] == 5) {
             phiptr5 = phiMF[i].dataPtr();
           }
        }
        FORT_ALLPHI_FROM_X(&lenx,&leny,&lenz,&length,&width,faces,&numOutFlowFaces,
                           phiptr0, phiptr1, phiptr2, phiptr3, phiptr4, phiptr5,
                           phi.dataPtr(),ARLIM(phi_lo),ARLIM(phi_hi));
#endif
       }
    }

    if (std::abs(gravity) > 0.) 
     for (int iface = 0; iface < numOutFlowFaces; iface++) 
     {
      int face          = int(outFaces[iface]);
      int outDir        = outFaces[iface].coordDir();

      DEF_LIMITS(phiMF[iface], phiPtr,philo,phihi);
      DEF_LIMITS(rhoMF[iface], rhoPtr,rholo,rhohi);
      if (outDir != (BL_SPACEDIM-1))
        FORT_RHOGBC(rhoPtr,ARLIM(rholo),ARLIM(rhohi),
                    phiPtr,ARLIM(philo),ARLIM(phihi),
                    &face,&gravity,dx);
  
    }
}
#endif

void 
ProjOutFlowBC::computeRhoG (FArrayBox*         rhoMF,
                            FArrayBox*         phiMF,
                            const Geometry&    geom, 
                            Orientation*       outFaces,
                            int                numOutFlowFaces,
                            Real               gravity)

{
    const Real* dx    = geom.CellSize();
    const Box& domain = geom.Domain();

    for (int iface = 0; iface < numOutFlowFaces; iface++) {

      int face          = int(outFaces[iface]);
      int outDir        = outFaces[iface].coordDir();

      DEF_LIMITS(phiMF[iface], phiPtr,philo,phihi);
      DEF_LIMITS(rhoMF[iface], rhoPtr,rholo,rhohi);

      if (outDir != (BL_SPACEDIM-1) && std::abs(gravity) > 0.0) 
        FORT_RHOGBC(rhoPtr,ARLIM(rholo),ARLIM(rhohi),
                    phiPtr,ARLIM(philo),ARLIM(phihi),
                    &face,&gravity,dx);
    }
}

#if (BL_SPACEDIM == 3)
void 
ProjOutFlowBC::computeCoefficients (FArrayBox&   rhs,
                                    FArrayBox&   beta,
                                    FArrayBox&   ccExt,
                                    Box&         faceBox,
                                    Real*        dxFiltered,
                                    int*         isPeriodicFiltered)
{
    Box rhsBox  = semiSurroundingNodes(faceBox,BL_SPACEDIM-1);
    Box betaBox = OutFlowBC::SemiGrow(faceBox,1,BL_SPACEDIM-1);
  
    beta.resize(betaBox,1);
    rhs.resize(rhsBox,1);

    DEF_BOX_LIMITS(faceBox,faceLo,faceHi);
    DEF_LIMITS(beta,  betaPtr, betalo,betahi);
    DEF_LIMITS(rhs, rhsPtr, rhslo,rhshi);
    const int* ccElo = ccExt.loVect();
    const int* ccEhi = ccExt.hiVect();
    Real*  rhoEPtr = ccExt.dataPtr(0);
    Real* divuEPtr = ccExt.dataPtr(1);
    Real*    uEPtr = ccExt.dataPtr(2);

    FORT_COMPUTE_COEFF(ARLIM(rhslo),ARLIM(rhshi),rhsPtr,
                       ARLIM(betalo),ARLIM(betahi),betaPtr,
                       ARLIM(ccElo),ARLIM(ccEhi),uEPtr,
                       ARLIM(ccElo),ARLIM(ccEhi),divuEPtr,
                       ARLIM(ccElo),ARLIM(ccEhi),rhoEPtr,
                       faceLo,faceHi,
                       dxFiltered,isPeriodicFiltered);
}

ProjOutFlowBC_MG::ProjOutFlowBC_MG (const Box& Domain,
                                    FArrayBox* Phi,
                                    FArrayBox* Rhs,
                                    FArrayBox* Resid,
                                    FArrayBox* Beta,
                                    Real*      H,
                                    int*       IsPeriodic)
    :
    OutFlowBC_MG(Domain,Phi,Rhs,Resid,Beta,H,IsPeriodic,true)
{
    static int first = true;

    if (first)
    {
        first = false;

        ParmParse pp("proj_mg");

        pp.query("v",verbose);
        pp.query("useCGbottomSolver",useCGbottomSolver);
        pp.query("cg_tol",cg_tol);
        pp.query("cg_abs_tol",cg_abs_tol);
        pp.query("cg_max_jump",cg_max_jump);
        pp.query("cg_maxiter",cg_maxiter);
        pp.query("maxIters",maxIters);
    }

    const IntVect& len = domain.length();

    int min_length = 4;
    bool test_side[BL_SPACEDIM-1];
    for (int dir = 0; dir < BL_SPACEDIM-1; dir++)
        test_side[dir] = (len[dir]&1) != 0 || len[dir] < min_length;

    if (D_TERM(1 && ,test_side[0], || test_side[1]))
    {
        if (useCGbottomSolver)
        {
            Box temp1Box = OutFlowBC::SemiGrow(domain,1,BL_SPACEDIM-1);
            Box temp2Box = semiSurroundingNodes(temp1Box,BL_SPACEDIM-1);
            cgwork = new FArrayBox(temp2Box,4);
        }
    }
    else
    {
        Real newh[BL_SPACEDIM];
        for (int dir = 0; dir < BL_SPACEDIM; dir++)
            newh[dir] = 2*h[dir];

        Box newdomain = OutFlowBC::SemiCoarsen(domain,2,BL_SPACEDIM-1);
        Box grownBox  = OutFlowBC::SemiGrow(newdomain,1,BL_SPACEDIM-1);
        Box nodes     = semiSurroundingNodes(newdomain,BL_SPACEDIM-1);
        Box newp_size = OutFlowBC::SemiGrow(nodes,1,BL_SPACEDIM-1);

        FArrayBox* newphi    = new FArrayBox(newp_size,1);
        FArrayBox* newresid  = new FArrayBox(newp_size,1);
        FArrayBox* newrhs    = new FArrayBox(newp_size,1);
        FArrayBox* newbeta   = new FArrayBox(grownBox,1);
        newphi->setVal(0);
        newresid->setVal(0);
        newbeta->setVal(0);
   
        DEF_BOX_LIMITS(domain,dom_lo,dom_hi);
        DEF_BOX_LIMITS(newdomain,new_lo,new_hi);
        DEF_LIMITS(*beta,betaPtr,beta_lo,beta_hi);
        DEF_LIMITS(*newbeta,newbetaPtr,newbeta_lo,newbeta_hi);

        FORT_COARSIG(betaPtr,ARLIM(beta_lo),ARLIM(beta_hi),
                     newbetaPtr,ARLIM(newbeta_lo),ARLIM(newbeta_hi),
                     dom_lo,dom_hi,new_lo,new_hi,isPeriodic);

        next = new ProjOutFlowBC_MG(newdomain,newphi,newrhs,newresid, 
                                    newbeta,newh,isPeriodic);
    }
}

ProjOutFlowBC_MG::~ProjOutFlowBC_MG () {}


Real
ProjOutFlowBC_MG::residual ()
{
    Real rnorm;

    FArrayBox dgphi(semiSurroundingNodes(domain,BL_SPACEDIM-1),1);

    DEF_BOX_LIMITS(domain,lo,hi);
    DEF_LIMITS(*rhs,rhsPtr,rhslo,rhshi);
    DEF_LIMITS(*beta,betaPtr,betalo,betahi);
    DEF_LIMITS(*phi,phiPtr,philo,phihi);
    DEF_LIMITS(*resid,residPtr,residlo,residhi);
    DEF_LIMITS(dgphi,dgphiPtr,dglo,dghi);

    resid->setVal(0);

    FORT_HGRESID (ARLIM(rhslo), ARLIM(rhshi),  rhsPtr,
                  ARLIM(betalo), ARLIM(betahi),  betaPtr,
                  ARLIM(philo), ARLIM(phihi),  phiPtr,
                  ARLIM(residlo), ARLIM(residhi),  residPtr,
                  ARLIM(dglo), ARLIM(dghi), dgphiPtr,
                  lo, hi, h, isPeriodic, &rnorm);

    return rnorm;
}

void 
ProjOutFlowBC_MG::step (int nGSRB)
{
    if (cgwork != 0)
    {
        Real resnorm  = 0.0;

        FArrayBox dest0(phi->box(),1);

        DEF_BOX_LIMITS(domain,lo,hi);
        DEF_LIMITS(*phi,phiPtr,phi_lo,phi_hi);
        DEF_LIMITS(*resid,residPtr,resid_lo,resid_hi);
        DEF_LIMITS(dest0,dest0Ptr,dest0_lo,dest0_hi);
        DEF_LIMITS(*rhs,rhsPtr,rhs_lo,rhs_hi);
        DEF_LIMITS(*beta, betaPtr, beta_lo,beta_hi); 
        DEF_LIMITS(*cgwork,dummPtr,cg_lo,cg_hi);

        FORT_SOLVEHG(phiPtr,ARLIM(phi_lo),ARLIM(phi_hi),
                     dest0Ptr, ARLIM(dest0_lo),ARLIM(dest0_hi),
                     rhsPtr,ARLIM(rhs_lo),ARLIM(rhs_hi),
                     betaPtr, ARLIM(beta_lo),ARLIM(beta_hi),
                     cgwork->dataPtr(0),ARLIM(cg_lo),ARLIM(cg_hi),
                     cgwork->dataPtr(1), ARLIM(cg_lo),ARLIM(cg_hi),
                     cgwork->dataPtr(2), ARLIM(cg_lo),ARLIM(cg_hi),
                     cgwork->dataPtr(3),ARLIM(cg_lo),ARLIM(cg_hi),
                     residPtr, ARLIM(resid_lo),ARLIM(resid_hi),
                     lo,hi,h,isPeriodic,&cg_maxiter,&cg_tol,
                     &cg_abs_tol,&cg_max_jump,&resnorm);
    }
    else
    {
        gsrb(nGSRB);
    }
}

void 
ProjOutFlowBC_MG::Restrict ()
{
    DEF_BOX_LIMITS(domain,lo,hi);
    DEF_BOX_LIMITS(next->theDomain(),loc,hic);
    DEF_LIMITS(*resid,residPtr,resid_lo,resid_hi);
    DEF_LIMITS(*(next->theRhs()),rescPtr,resc_lo,resc_hi);

    next->theRhs()->setVal(0);
  
    FORT_RESTRICT(residPtr,ARLIM(resid_lo),ARLIM(resid_hi), 
                  rescPtr, ARLIM(resc_lo),ARLIM(resc_hi),
                  lo,hi,loc,hic,isPeriodic);
}

void
ProjOutFlowBC_MG::interpolate ()
{
    FArrayBox temp(phi->box(),1);

    temp.setVal(0);
    
    DEF_BOX_LIMITS(domain,lo,hi);
    DEF_BOX_LIMITS(next->theDomain(),loc,hic);
    DEF_LIMITS(*phi,phiPtr,phi_lo,phi_hi);
    DEF_LIMITS(temp,tempPtr,temp_lo,temp_hi);
    DEF_LIMITS(*(next->thePhi()),deltacPtr,deltac_lo,deltac_hi);
    DEF_LIMITS(*beta,betaPtr,beta_lo,beta_hi);

    FORT_INTERP(phiPtr, ARLIM(phi_lo),ARLIM(phi_hi),
                tempPtr, ARLIM(temp_lo),ARLIM(temp_hi), 
                deltacPtr, ARLIM(deltac_lo),ARLIM(deltac_hi), 
                betaPtr, ARLIM(beta_lo),ARLIM(beta_hi), 
                lo,hi,loc,hic,isPeriodic);
}

void
ProjOutFlowBC_MG::gsrb (int nstep)
{
    FArrayBox dgphi(semiSurroundingNodes(domain,BL_SPACEDIM-1),1);

    DEF_BOX_LIMITS(domain,lo,hi);
    DEF_LIMITS(*phi,phiPtr,philo,phihi);
    DEF_LIMITS(*beta,  betaPtr, betalo,betahi);
    DEF_LIMITS(*rhs, rhsPtr, rhslo,rhshi);
    DEF_LIMITS(dgphi,dgphiPtr,dglo,dghi);

    FORT_HGRELAX(ARLIM(rhslo),ARLIM(rhshi),rhsPtr,
                 ARLIM(betalo),ARLIM(betahi),betaPtr,
                 ARLIM(philo),ARLIM(phihi),phiPtr,
                 ARLIM(dglo),ARLIM(dghi),dgphiPtr,
                 lo,hi,h,isPeriodic,&nstep);
}
#endif
