/*
** (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: HyperCLaw.cpp,v 1.76 2002/03/13 23:43:09 vince Exp $
//

#include <CONSTANTS.H>
#include <CoordSys.H>
#include <Geometry.H>
#include <BoxDomain.H>
#include <ParmParse.H>
#include <ErrorList.H>
#include <HyperCLaw.H>
#include <ParallelDescriptor.H>
#include <LevelAdvance.H>
#include <Utility.H>
#include <VisMF.H>
#include <TagBox.H>

#include <HYPERCLAW_F.H>
#include <GODUNOV_F.H>
#include <PROB_F.H>
#include <DERIVE_F.H>

#include <cstdio>
#include <vector>
using std::vector;
using std::pair;
using std::cout;
using std::endl;

#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 FA_XPND(f) D_DECL(f[0].dataPtr(),f[1].dataPtr(),f[2].dataPtr())
#define FA_NORM_XPND(f) D_DECL(f[0].dataPtr(0),f[1].dataPtr(1),f[2].dataPtr(2))
#define FA_COMP_XPND(f,k) D_DECL(f[0].dataPtr(k),f[1].dataPtr(k),f[2].dataPtr(k))

static int  sum_interval = -1;
static Real fixed_dt     = -1.0;
static Real dt_cutoff    = 0.0;
static Real bogus_value  = 1.0e+20;

int            HyperCLaw::verbose;
Real           HyperCLaw::cfl           = 0.8;
Real           HyperCLaw::init_shrink   = 0.5;
Real           HyperCLaw::change_max    = 1.1;
Real           HyperCLaw::gravity;
int            HyperCLaw::initial_step;
ErrorList      HyperCLaw::err_list;
int            HyperCLaw::radius_grow   = 1;
BCRec          HyperCLaw::phys_bc;
int            HyperCLaw::NUM_STATE;
int            HyperCLaw::do_reflux     = 1;
int            HyperCLaw::do_richardson = 1;
#ifdef BL_USE_CHEM
ChemKinDriver* HyperCLaw::chemDriver = 0;
int            HyperCLaw::NumSpec    = 0;
int            HyperCLaw::FirstSpec  = -1;
int            HyperCLaw::LastSpec   = -1;

const std::string chemfile_DEF  = "chem.inp";
const std::string chemoutfile_DEF = "";
const std::string thermfile_DEF = "therm.dat";
const std::string tranfile_DEF  = "tran.dat";
#endif

void
HyperCLaw::variableSetUp ()
{
    BoxLib::Error("HyperCLaw::variableSetUp() not defined");
}

void
HyperCLaw::variableCleanUp () 
{
    desc_lst.clear();

#ifdef BL_USE_CHEM
    delete chemDriver;
    chemDriver = 0;
#endif
}

void
HyperCLaw::read_params ()
{
    ParmParse pp("hyp");
   
    pp.query("v",verbose);
    verbose = (verbose ? 1 : 0);
    pp.get("init_shrink",init_shrink);
    pp.get("cfl",cfl);
    pp.get("gravity",gravity);
    pp.query("change_max",change_max);
    pp.query("fixed_dt",fixed_dt);
    pp.query("bogus_value",bogus_value);
    pp.query("sum_interval",sum_interval);
    pp.query("do_reflux",do_reflux);
    do_reflux = (do_reflux ? 1 : 0);
    pp.query("do_richardson",do_richardson);
    do_richardson = (do_richardson ? 1 : 0);

    pp.get("dt_cutoff",dt_cutoff);

#ifdef BL_USE_CHEM
    std::string chemfile=chemfile_DEF; pp.query("chemfile",chemfile);
    std::string chemoutfile=chemoutfile_DEF; pp.query("chemoutfile",chemoutfile);
    std::string thermfile=thermfile_DEF; pp.query("thermfile",thermfile);
    std::string tranfile=tranfile_DEF; pp.query("tranfile",tranfile);
    chemDriver = new ChemKinDriver(chemfile, chemoutfile, thermfile, tranfile);
#endif
}

HyperCLaw::HyperCLaw ()
    :
    radius(PArrayManage)
{
    
    flux_reg = 0;
}

HyperCLaw::HyperCLaw (Amr&            papa,
                      int             lev,
                      const Geometry& level_geom,
                      const BoxArray& bl,
                      Real            time)
    :
    AmrLevel(papa,lev,level_geom,bl,time),
    radius(PArrayManage)
{
    buildMetrics();

    flux_reg = 0;

    if (level > 0 && do_reflux)
    {
        flux_reg = new FluxRegister(grids,crse_ratio,level,NUM_STATE);
    }
}

HyperCLaw::~HyperCLaw () 
{
    delete flux_reg;
    int ngrd = grids.size();
}

void
HyperCLaw::restart (Amr&     papa,
                    std::istream& is,
		    bool     bReadSpecial)
{
    AmrLevel::restart(papa,is,bReadSpecial);

    buildMetrics();

    BL_ASSERT(flux_reg == 0);

    if (level > 0 && do_reflux)
    {
        flux_reg = new FluxRegister(grids,crse_ratio,level,NUM_STATE);
    }
}

std::string
HyperCLaw::thePlotFileType () const
{
    //
    // Increment this whenever the writePlotFile() format changes.
    //
    static const std::string the_plot_file_type("HyperCLaw-V1.1");

    return the_plot_file_type;
}

void
HyperCLaw::writePlotFile (const std::string& dir,
                          std::ostream&       os,
                          VisMF::How     how)
#if 0
{
    int i, n;
    //
    // There is only one MultiFab written out at each level in HyperCLaw.
    //
    static const std::string MultiFabBaseName("/MultiFab");
    //
    // Build the directory to hold the MultiFabs at this level.
    // The name is relative to the directory containing the Header file.
    //
    char buf[64];
    sprintf(buf, "Level_%d", level);
    std::string Level = buf;
    //
    // Now for the full pathname of that directory.
    //
    std::string FullPath = dir;
    if (!FullPath.empty() && FullPath[FullPath.length()-1] != '/')
        FullPath += '/';
    FullPath += Level;
    //
    // Only the I/O processor makes the directory if it doesn't already exist.
    //
    if (ParallelDescriptor::IOProcessor())
        if (!BoxLib::UtilCreateDirectory(FullPath, 0755))
            BoxLib::CreateDirectoryFailed(FullPath);
    //
    // Force other processors to wait till directory is built.
    //
    ParallelDescriptor::Barrier();

    const StateDescriptor& d_cell = desc_lst[State_Type];
    //
    // The list of indices of State to write to plotfile.
    //
    std::vector<int> idx_map;

    for (i = 0; i < NUM_STATE; i++)
        if (parent->isStatePlotVar(d_cell.name(i)))
            idx_map.push_back(i);

    BL_ASSERT(idx_map.size() > 0 && idx_map.size() <= NUM_STATE);

    if (ParallelDescriptor::IOProcessor())
    {
        if (level == 0)
        {
            //
            // The first thing we write out is the plot file type.
            //
            os << thePlotFileType() << '\n';
            //
            // Only write out velocity and scalar data.
            //
            os << idx_map.size() << '\n';
            for (n = 0; n < idx_map.size(); n++)
                os << d_cell.name(idx_map[n]) << '\n';
            os << BL_SPACEDIM << '\n';
            os << parent->cumTime() << '\n';
            int f_lev = parent->finestLevel();
            os << f_lev << '\n';
            for (i = 0; i < BL_SPACEDIM; i++)
                os << Geometry::ProbLo(i) << ' ';
            os << '\n';
            for (i = 0; i < BL_SPACEDIM; i++)
                os << Geometry::ProbHi(i) << ' ';
            os << '\n';
            for (i = 0; i < f_lev; i++)
                os << parent->refRatio(i) << ' ';
            os << '\n';
            for (i = 0; i <= f_lev; i++)
                os << parent->Geom(i).Domain() << ' ';
            os << '\n';
            for (i = 0; i <= f_lev; i++)
                os << parent->levelSteps(i) << ' ';
            os << '\n';
            for (i = 0; i <= f_lev; i++)
            {
                const Real* dx_lev = parent->Geom(i).CellSize();
                for (int k = 0; k < BL_SPACEDIM; k++) os << dx_lev[k] << ' ';
                os << '\n';
            }
            os << (int) CoordSys::Coord() << '\n';
            os << "0\n"; // The bndry data.
        }
        //
        // Now write state data.
        //
        int ngrds          = grids.size();
        Real cur_time      = state[State_Type].curTime();
        MultiFab& cell_dat = state[State_Type].newData();

        os << level << ' ' << ngrds << ' ' << cur_time << '\n';
        os << parent->levelSteps(level) << '\n';

        for (i = 0; i < cell_dat.boxArray().size(); ++i)
        {
            for (n = 0; n < BL_SPACEDIM; n++)
                os << grid_loc[i].lo(n) << ' ' << grid_loc[i].hi(n) << '\n';
        }
        //
        // Finally, the full relative pathname of the MultiFab.
        // The name is relative to the Header file containing this name.
        // It's the name that gets written into the Header.
        //
        std::string PathNameInHeader = Level;
        PathNameInHeader += MultiFabBaseName;

        os << PathNameInHeader << '\n';
    }
    //
    // Now amend FullPath to contain full pathname of MF.
    //
    FullPath += MultiFabBaseName;

    const MultiFab& mf = state[State_Type].newData();

    if (idx_map.size() == NUM_STATE)
    {
        //
        // Set ghost cells to average of min/max during VisMF::Write().
        //
        VisMF::Write(mf,FullPath,how,true);
    }
    else
    {
        //
        // Make MultiFab containing copy of selected components.
        //
        // Note that we don't copy the ghost cells.
        //
        MultiFab nmf(mf.boxArray(), idx_map.size(), 0);

        for (MFIter mfi(nmf); mfi.isValid(); ++mfi)
        {
            FArrayBox&       fab_dst = nmf[mfi];
            const FArrayBox& fab_src = mf[mfi];

            for (i = 0; i < idx_map.size(); i++)
            {
                fab_dst.copy(fab_src, idx_map[i], i, 1);
            }
        }

        VisMF::Write(nmf,FullPath,how);
    }
}
#else
{
    int i, n;
    //
    // The list of indices of State to write to plotfile.
    // first component of pair is state_type,
    // second component of pair is component # within the state_type
    //
    vector<pair<int,int> > plot_var_map;
    for (int typ = 0; typ < desc_lst.size(); typ++)
        for (int comp = 0; comp < desc_lst[typ].nComp();comp++)
            if (parent->isStatePlotVar(desc_lst[typ].name(comp)) &&
                desc_lst[typ].getType() == IndexType::TheCellType())
                plot_var_map.push_back(pair<int,int>(typ,comp));

    int num_derive = 0;
    std::list<std::string> derive_names;
    const std::list<DeriveRec>& dlist = derive_lst.dlist();

    for (std::list<DeriveRec>::const_iterator it = dlist.begin();
	 it != dlist.end();
	 ++it)
      {
        if (parent->isDerivePlotVar(it->name()))
	{
            derive_names.push_back(it->name());
            num_derive++;
	}
      }
    int n_data_items = plot_var_map.size() + num_derive;
    Real cur_time = state[State_Type].curTime();

    if (level == 0 && ParallelDescriptor::IOProcessor())
    {
        //
        // The first thing we write out is the plotfile type.
        //
        os << thePlotFileType() << '\n';

        if (n_data_items == 0)
            BoxLib::Error("Must specify at least one valid data item to plot");

        os << n_data_items << '\n';

	//
	// Names of variables -- first state, then derived
	//
	for (i =0; i < plot_var_map.size(); i++)
        {
	    int typ = plot_var_map[i].first;
	    int comp = plot_var_map[i].second;
	    os << desc_lst[typ].name(comp) << '\n';
        }

	for ( std::list<std::string>::iterator it = derive_names.begin();
	      it != derive_names.end(); ++it)
        {
	    const DeriveRec* rec = derive_lst.get(*it);
            os << rec->variableName(0) << '\n';
        }
        os << BL_SPACEDIM << '\n';
        os << parent->cumTime() << '\n';
        int f_lev = parent->finestLevel();
        os << f_lev << '\n';
        for (i = 0; i < BL_SPACEDIM; i++)
            os << Geometry::ProbLo(i) << ' ';
        os << '\n';
        for (i = 0; i < BL_SPACEDIM; i++)
            os << Geometry::ProbHi(i) << ' ';
        os << '\n';
        for (i = 0; i < f_lev; i++)
            os << parent->refRatio(i)[0] << ' ';
        os << '\n';
        for (i = 0; i <= f_lev; i++)
            os << parent->Geom(i).Domain() << ' ';
        os << '\n';
        for (i = 0; i <= f_lev; i++)
            os << parent->levelSteps(i) << ' ';
        os << '\n';
        for (i = 0; i <= f_lev; i++)
        {
            for (int k = 0; k < BL_SPACEDIM; k++)
                os << parent->Geom(i).CellSize()[k] << ' ';
            os << '\n';
        }
        os << (int) CoordSys::Coord() << '\n';
        os << "0\n"; // Write bndry data.
    }
    // Build the directory to hold the MultiFab at this level.
    // The name is relative to the directory containing the Header file.
    //
    static const std::string BaseName = "/MultiFab";
    char buf[64];
    sprintf(buf, "Level_%d", level);
    std::string Level = buf;
    //
    // Now for the full pathname of that directory.
    //
    std::string FullPath = dir;
    if (!FullPath.empty() && FullPath[FullPath.size()-1] != '/')
        FullPath += '/';
    FullPath += Level;
    //
    // Only the I/O processor makes the directory if it doesn't already exist.
    //
    if (ParallelDescriptor::IOProcessor())
        if (!BoxLib::UtilCreateDirectory(FullPath, 0755))
            BoxLib::CreateDirectoryFailed(FullPath);
    //
    // Force other processors to wait till directory is built.
    //
    ParallelDescriptor::Barrier();

    if (ParallelDescriptor::IOProcessor())
    {
        os << level << ' ' << grids.size() << ' ' << cur_time << '\n';
        os << parent->levelSteps(level) << '\n';

        for (i = 0; i < grids.size(); ++i)
        {
            for (n = 0; n < BL_SPACEDIM; n++)
                os << grid_loc[i].lo(n) << ' ' << grid_loc[i].hi(n) << '\n';
        }
        //
        // The full relative pathname of the MultiFabs at this level.
        // The name is relative to the Header file containing this name.
        // It's the name that gets written into the Header.
        //
        if (n_data_items > 0)
        {
            std::string PathNameInHeader = Level;
            PathNameInHeader += BaseName;
            os << PathNameInHeader << '\n';
        }
    }
    //
    // We combine all of the multifabs -- state, derived, etc -- into one
    // multifab -- plotMF.
    // NOTE: we are assuming that each state variable has one component,
    // but a derived variable is allowed to have multiple components.
    int       cnt   = 0;
    const int nGrow = 0;
    MultiFab  plotMF(grids,n_data_items,nGrow);
    MultiFab* this_dat = 0;
    //
    // Cull data from state variables -- use no ghost cells.
    //
    for (i = 0; i < plot_var_map.size(); i++)
    {
	int typ  = plot_var_map[i].first;
	int comp = plot_var_map[i].second;
	this_dat = &state[typ].newData();
	MultiFab::Copy(plotMF,*this_dat,comp,cnt,1,nGrow);
	cnt++;
    }
    //
    // Cull data from derived variables.
    // 
    if (derive_names.size() > 0)
    {
	for (std::list<std::string>::iterator it = derive_names.begin();
	     it != derive_names.end(); ++it) 
	{
	    const DeriveRec* rec = derive_lst.get(*it);
	    MultiFab* derive_dat = derive(*it,cur_time,nGrow);
	    MultiFab::Copy(plotMF,*derive_dat,0,cnt,1,nGrow);
	    delete derive_dat;
	    cnt++;
	}
    }
    //
    // Use the Full pathname when naming the MultiFab.
    //
    std::string TheFullPath = FullPath;
    TheFullPath += BaseName;
    VisMF::Write(plotMF,TheFullPath,how,true);
}
#endif

void
HyperCLaw::buildMetrics ()
{
    const int ngrd = grids.size();

    radius.resize(ngrd);

    const Real* dx = geom.CellSize();

    for (int i = 0; i < ngrd; i++)
    {
        const Box& b = grids[i];
        int ilo      = b.smallEnd(0)-radius_grow;
        int ihi      = b.bigEnd(0)+radius_grow;
        int len      = ihi - ilo + 1;
        Real* rad    = new Real[len];
        radius.set(i,rad);
        if (CoordSys::IsCartesian())
        {
            for (int j = 0; j < len; j++)
            {
                rad[j] = 1.0;
            }
        }
        else
        {
            const Real xlo = grid_loc[i].lo(0) + (0.5 - radius_grow)*dx[0];

            for (int j = 0; j < len; j++)
            {
                rad[j] = xlo + j*dx[0];
            }
        }
    }
    //
    // Build volume and face area arrays.
    // volume is not PArrayManaged, must manually delete.
    //
    volume.clear();
    //
    // area is not PArrayManaged, must manually delete.
    //
    for (int dir = 0; dir < BL_SPACEDIM; dir++)
    {
        area[dir].clear();
    }
    geom.GetVolume(volume,grids,GEOM_GROW);
    for (int dir = 0; dir < BL_SPACEDIM; dir++)
    {
        geom.GetFaceArea(area[dir],grids,dir,GEOM_GROW);
    }
}

void
HyperCLaw::setTimeLevel (Real time,
                         Real dt_old,
                         Real dt_new)
{
    state[State_Type].setTimeLevel(time,dt_old,dt_new);
}

void
HyperCLaw::initData ()
{
    //
    // Loop over grids, call FORTRAN function to init with data.
    //
    int ns          = NUM_STATE;
    const Real* dx  = geom.CellSize();
    MultiFab& S_new = get_new_data(State_Type);
    Real cur_time   = state[State_Type].curTime();

    for (MFIter mfi(S_new); mfi.isValid(); ++mfi)
    {
        const int* glo = mfi.validbox().loVect();
        const int* ghi = mfi.validbox().hiVect();
        const int* slo = mfi.fabbox().loVect();
        const int* shi = mfi.fabbox().hiVect();
        FORT_INITDATA (&level,&cur_time, glo, ghi, &ns,
                       S_new[mfi].dataPtr(),ARLIM(slo), ARLIM(shi),
                       dx,grid_loc[mfi.index()].lo(),
                       grid_loc[mfi.index()].hi());
    }
}

void
HyperCLaw::init (AmrLevel &old)
{
    HyperCLaw* oldlev = (HyperCLaw*) &old;
    //
    // Create new grid data by fillpatching from old.
    //
    Real dt_new    = parent->dtLevel(level);
    Real cur_time  = oldlev->state[State_Type].curTime();
    Real prev_time = oldlev->state[State_Type].prevTime();
    Real dt_old    = cur_time - prev_time;
    setTimeLevel(cur_time,dt_old,dt_new);

    MultiFab& S_new = get_new_data(State_Type);

    FillPatchIterator fpi(old,S_new,0,cur_time,State_Type,0,NUM_STATE);

    for ( ; fpi.isValid(); ++fpi)
    {
        S_new[fpi].copy(fpi());
    }
}

//
// This version inits the data on a new level that did not
// exist before regridding.
//
void
HyperCLaw::init ()
{
    Real dt        = parent->dtLevel(level);
    Real cur_time  = getLevel(level-1).state[State_Type].curTime();
    Real prev_time = getLevel(level-1).state[State_Type].prevTime();

    Real dt_old = (cur_time - prev_time)/(Real)parent->MaxRefRatio(level-1);

    setTimeLevel(cur_time,dt_old,dt);
    MultiFab& S_new = get_new_data(State_Type);
    FillCoarseMultiFab(S_new, 0, cur_time, State_Type, 0, NUM_STATE);
}

Real
HyperCLaw::estTimeStep ()
{
    if (fixed_dt > 0.0)
        return fixed_dt;

    const Real* dx    = geom.CellSize();
    Real cur_time     = state[State_Type].curTime();
    Real estdt        = 1.0e+20;
    MultiFab& stateMF = get_new_data(State_Type);

    FArrayBox stateFab, c;

    for (MFIter mfi(stateMF); mfi.isValid(); ++mfi)
    {
        stateFab.resize(mfi.validbox(), NUM_STATE);
        c.resize(mfi.validbox(), 1);
        stateFab.copy(stateMF[mfi]);

        DEF_LIMITS(stateFab, state_dat, slo, shi);
        DEF_LIMITS(c, c_dat, clo, chi);

        Real dt;
        FORT_ESTDT(state_dat, ARLIM(slo), ARLIM(shi),
		   c_dat, ARLIM(clo), ARLIM(chi),
		   slo, shi, dx, &dt, &NUM_STATE);
        estdt = std::min(estdt,dt);
    }

    estdt *= cfl;

    ParallelDescriptor::ReduceRealMin(estdt);
    
    if (verbose && ParallelDescriptor::IOProcessor())
    {
        std::cout << "HyperCLaw::estTimeStep:  estdt = " << estdt << '\n';
    }
    return estdt;
}

void
HyperCLaw::computeNewDt (int                   finest_level,
                         int                   sub_cycle,
                         Array<int>&           n_cycle,
                         const Array<IntVect>& ref_ratio,
                         Array<Real>&          dt_min,
                         Array<Real>&          dt_level,
                         Real                  stop_time)
{
    //
    // We are at the end of a coarse grid timecycle.
    // Compute the timesteps for the next iteration.
    //
    if (level > 0)
        return;

    int i;
    n_cycle[0] = 1;
    for (i = 1; i <= finest_level; i++)
    {
        n_cycle[i] = sub_cycle ? parent->MaxRefRatio(i-1) : 1;
    }

    Real dt_0 = 1.0e+100;
    int n_factor = 1;
    for (i = 0; i <= finest_level; i++)
    {
          HyperCLaw& adv_level = getLevel(i);
          dt_min[i] = adv_level.estTimeStep();
          dt_min[i] = std::min(dt_min[i],change_max*dt_level[i]);
          n_factor *= n_cycle[i];
          dt_0 = std::min(dt_0,n_factor*dt_min[i]);
    }
    //
    // Limit dt's by the value of stop_time.
    //
    const Real eps = 0.001*dt_0;
    Real cur_time  = state[State_Type].curTime();
    if (stop_time >= 0.0) {
     if ((cur_time + dt_0) > (stop_time - eps))
        dt_0 = stop_time - cur_time;
    }

    n_factor = 1;
    for (i = 0; i <= finest_level; i++)
    {
        n_factor *= n_cycle[i];
        dt_level[i] = dt_0/n_factor;
    }
}

void
HyperCLaw::computeInitialDt (int                   finest_level,
                             int                   sub_cycle,
                             Array<int>&           n_cycle,
                             const Array<IntVect>& ref_ratio,
                             Array<Real>&          dt_level,
                             Real                  stop_time)
{
    //
    // Grids have been constructed, compute dt for all levels.
    //
    if (level > 0)
        return;

    int i;
    n_cycle[0] = 1;
    for (i = 1; i <= finest_level; i++)
    {
        n_cycle[i] = sub_cycle ? parent->MaxRefRatio(i-1) : 1;
    }

    Real dt_0 = 1.0e+100;
    int n_factor = 1;
    for (i = 0; i <= finest_level; i++)
    {
        dt_level[i] = getLevel(i).initialTimeStep();
        n_factor   *= n_cycle[i];
        dt_0 = std::min(dt_0,n_factor*dt_level[i]);
    }

    //
    // Limit dt's by the value of stop_time.
    //
    const Real eps = 0.001*dt_0;
    Real cur_time  = state[State_Type].curTime();
    if (stop_time >= 0.0) {
     if ((cur_time + dt_0) > (stop_time - eps))
        dt_0 = stop_time - cur_time;
    }

    n_factor = 1;
    for (i = 0; i <= finest_level; i++)
    {
        n_factor *= n_cycle[i];
        dt_level[i] = dt_0/n_factor;
    }
}

void
HyperCLaw::post_timestep (int iteration)
{
    //
    // Integration cycle on fine level grids is complete
    // do post_timestep stuff here.
    //
    int finest_level = parent->finestLevel();

    if (level < finest_level)
    {
        if (do_reflux)
            reflux();
        avgDown();
    }
    if (level == 0)
    {
        int nstep = parent->levelSteps(0);

        if ((sum_interval > 0) && (nstep%sum_interval == 0) )
        {
            sum_integrated_quantities();
        }         
    }
}

void
HyperCLaw::post_restart ()
{
    //
    // Build any additional data structures after restart.
    //
}

void
HyperCLaw::post_regrid (int /* lbase */,
                        int /* new_finest */)
{}

void
HyperCLaw::post_init (Real stop_time)
{
    if (level > 0)
        return;
    //
    // Average data down from finer levels
    // so that conserved data is consistant between levels.
    //
    int finest_level = parent->finestLevel();
    for (int k = finest_level-1; k>= 0; k--)
        getLevel(k).avgDown();
}

int
HyperCLaw::okToContinue ()
{
    if (level > 0)
        return 1;
    return  parent->dtLevel(0) > dt_cutoff;
}

//
// Return largest safe timestep that can be used.
//
Real
HyperCLaw::advance (Real time,
                    Real dt,
                    int  iteration,
                    int  ncycle)
{
    int finest_level = parent->finestLevel();

    if (do_reflux && level < finest_level)
    {
        //
        // Set reflux registers to zero.
        //
        getFluxReg(level+1).setVal(0.0);
    }

    for (int k = 0; k < NUM_STATE_TYPE; k++)
    {
        state[k].allocOldData();
        state[k].swapTimeLevels(dt);
    }

    //
    // Get pointers to Flux registers, or set pointer to zero if not there.
    //
    FluxRegister *fine    = 0;
    FluxRegister *current = 0;

    if (do_reflux && level < finest_level)
        fine = &getFluxReg(level+1);
    if (do_reflux && level > 0)
        current = &getFluxReg(level);
    //
    // Call LevelAdvance -- This will directly update the state.
    //
    Real dt_new = LevelAdvance(grids,
                               get_new_data(State_Type),
                               *this,
                               *fine,
                               *current,
                               State_Type,
                               0,
                               NUM_STATE,
                               do_reflux,
                               dt,
                               time,
                               geom.CellSize());

    return dt_new;
}

void
HyperCLaw::reflux ()
{
    if (!(level == parent->finestLevel()))
    {
        BL_ASSERT(do_reflux);

        const int ns = NUM_STATE;

        getFluxReg(level+1).Reflux(get_new_data(State_Type),1.0,0,0,ns,geom);
    }
}

void
HyperCLaw::avgDown ()
{
    if (level == parent->finestLevel()) return;

    const int  ncomp    = NUM_STATE;
    HyperCLaw& fine_lev = getLevel(level+1);
    MultiFab&  S_crse   = get_new_data(State_Type);
    MultiFab&  S_fine   = fine_lev.get_new_data(State_Type);
    MultiFab&  fvolume  = fine_lev.volume;

    BL_ASSERT(S_crse.boxArray() == volume.boxArray());
    BL_ASSERT(fvolume.boxArray() == S_fine.boxArray());
    //
    // Coarsen() the fine stuff on processors owning the fine data.
    //
    BoxArray crse_S_fine_BA(S_fine.boxArray().size());

    for (int i = 0; i < S_fine.boxArray().size(); ++i)
    {
        crse_S_fine_BA.set(i,BoxLib::coarsen(S_fine.boxArray()[i],fine_ratio));
    }

    MultiFab crse_S_fine(crse_S_fine_BA,ncomp,0);
    MultiFab crse_fvolume(crse_S_fine_BA,1,0);

    crse_fvolume.copy(volume);

    for (MFIter mfi(S_fine); mfi.isValid(); ++mfi)
    {
        const int        i        = mfi.index();
        const Box&       ovlp     = crse_S_fine_BA[i];
        FArrayBox&       crse_fab = crse_S_fine[i];
        const FArrayBox& crse_vol = crse_fvolume[i];
        FArrayBox&       fine_fab = S_fine[i];
        const FArrayBox& fine_vol = fvolume[i];

        DEF_LIMITS(crse_fab,c_dat,clo,chi);
        DEF_CLIMITS(crse_vol,cv_dat,cvlo,cvhi);
        DEF_CLIMITS(fine_fab,f_dat,flo,fhi);
        DEF_CLIMITS(fine_vol,fv_dat,fvlo,fvhi);

        FORT_AVGDOWN(c_dat,ARLIM(clo), ARLIM(chi),&ncomp,
		     cv_dat,ARLIM(cvlo),ARLIM(cvhi),
                     f_dat,ARLIM(flo),ARLIM(fhi),
		     fv_dat,ARLIM(fvlo),ARLIM(fvhi),
                     ovlp.loVect(),ovlp.hiVect(),fine_ratio.getVect());

    }

    S_crse.copy(crse_S_fine);
}

void
HyperCLaw::allocOldData ()
{
    for (int k = 0; k < NUM_STATE_TYPE; k++)
        state[k].allocOldData();
}

void
HyperCLaw::removeOldData()
{
    AmrLevel::removeOldData();
}

void
HyperCLaw::errorEst (TagBoxArray& tags,
                     int          clearval,
                     int          tagval,
                     Real         time,
		     int          n_error_buf,
		     int          ngrow)
{
    const int*  domain_lo = geom.Domain().loVect();
    const int*  domain_hi = geom.Domain().hiVect();
    const Real* dx        = geom.CellSize();
    const Real* prob_lo   = geom.ProbLo();

    for (int j = 0; j < err_list.size(); j++)
    {
        MultiFab* mf = derive(err_list[j].name(), time, err_list[j].nGrow());

        BL_ASSERT(!(mf == 0));

        for (MFIter mfi(*mf); mfi.isValid(); ++mfi)
        {
            Array<int>  itags = tags[mfi].tags();
            int*        tptr  = itags.dataPtr();
            const int*  tlo   = tags[mfi].box().loVect();
            const int*  thi   = tags[mfi].box().hiVect();
            const int*  lo    = mfi.validbox().loVect();
            const int*  hi    = mfi.validbox().hiVect();
            const Real* xlo   = grid_loc[mfi.index()].lo();
            Real*      dat    = mf->get(mfi).dataPtr();
            const int* dlo    = mf->get(mfi).box().loVect();
            const int* dhi    = mf->get(mfi).box().hiVect();
            const int  ncomp  = mf->get(mfi).nComp();

            err_list[j].errFunc()(tptr, ARLIM(tlo), ARLIM(thi), &tagval,
                                   &clearval, dat, ARLIM(dlo), ARLIM(dhi),
                                   lo,hi, &ncomp, domain_lo, domain_hi,
                                   dx, xlo, prob_lo, &time, &level);
            //
            // Don't forget to set the tags in the TagBox.
            //
            tags[mfi.index()].tags(itags);
        }

        delete mf;
    }
}

Real
HyperCLaw::sumDerive (const std::string& name,
                      Real           time)
{
    Real sum         = 0.0;
    int finest_level = parent->finestLevel();
    MultiFab* mf     = derive(name, time, 0);

    BL_ASSERT(!(mf == 0));

    for (MFIter mfi(*mf); mfi.isValid(); ++mfi)
    {
        FArrayBox& fab = mf->get(mfi);

        if (level < finest_level)
        {
            const BoxArray& f_box = parent->boxArray(level+1);

            for (int j = 0; j < f_box.size(); j++)
            {
                Box c_box = BoxLib::coarsen(f_box[j],fine_ratio) & grids[mfi.index()];

                if (c_box.ok())
                    fab.setVal(0.0,c_box,0);
            }
        }

        sum += fab.sum(0);
    }

    delete mf;

    ParallelDescriptor::ReduceRealSum(sum);

    return sum;
}

Real
HyperCLaw::volWgtSum (const std::string& name,
                      Real           time)
{
    Real sum         = 0.0;
    int finest_level = parent->finestLevel();
    int rz_flag      = CoordSys::IsRZ() ? 1 : 0;
    const Real* dx   = geom.CellSize();
    MultiFab* mf     = derive(name,time,0);

    BL_ASSERT(!(mf == 0));

    for (MFIter mfi(*mf); mfi.isValid(); ++mfi)
    {
        FArrayBox& fab = mf->get(mfi);

        if (level < finest_level)
        {
            const BoxArray& f_box = parent->boxArray(level+1);

            for (int j = 0; j < f_box.size(); j++)
            {
                Box c_box = BoxLib::coarsen(f_box[j],fine_ratio) & grids[mfi.index()];

                if (c_box.ok())
                    fab.setVal(0.0,c_box,0);
            }
        }
        Real s;
        const Real* dat = fab.dataPtr();
        const int* dlo  = fab.loVect();
        const int* dhi  = fab.hiVect();
        const int* lo   = grids[mfi.index()].loVect();
        const int* hi   = grids[mfi.index()].hiVect();
        Real* rad       = &radius[mfi.index()];
        int irlo        = lo[0]-radius_grow;
        int irhi        = hi[0]+radius_grow;

        Array<Real> tmp(hi[1]-lo[1]+1);
        //
        // Note that this routine will do a volume weighted sum of
        // whatever quantity is passed in, not strictly the "mass".
        //
        FORT_SUMMASS(dat, ARLIM(dlo), ARLIM(dhi), lo, hi, dx, &s, rad, &irlo,
                     &irhi, &rz_flag, tmp.dataPtr(), &lo[1], &hi[1]);
        sum += s;
    }

    delete mf;

    ParallelDescriptor::ReduceRealSum(sum);

    return sum;
}

void
HyperCLaw::FillCoarseMultiFab (MultiFab&     destMF,
                               int           dst_comp,
                               Real          time,
                               int           state_indx,
                               int           src_comp,
                               int           ncomp)
{
    FillCoarsePatch(destMF,dst_comp,time,state_indx,src_comp,ncomp);
}
