Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

use FillPatcher class for ghost cell filling when using AMR #133

Merged
merged 15 commits into from
Oct 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion extern/amrex
Submodule amrex updated 66 files
+3 −3 .github/workflows/clang.yml
+4 −4 .github/workflows/cuda.yml
+1 −1 .github/workflows/docs.yml
+14 −14 .github/workflows/gcc.yml
+3 −3 .github/workflows/hip.yml
+2 −2 .github/workflows/intel.yml
+2 −2 .github/workflows/macos.yml
+1 −1 .github/workflows/sensei.yml
+2 −2 .github/workflows/style.yml
+3 −3 .github/workflows/windows.yml
+6 −2 Docs/sphinx_documentation/source/Basics.rst
+2 −2 Docs/sphinx_documentation/source/BuildingAMReX.rst
+1 −1 Docs/sphinx_documentation/source/GPU.rst
+14 −8 Docs/sphinx_documentation/source/LinearSolvers.rst
+131 −11 Src/Amr/AMReX_AmrLevel.H
+87 −0 Src/Amr/AMReX_AmrLevel.cpp
+8 −3 Src/AmrCore/AMReX_FillPatchUtil.H
+30 −39 Src/AmrCore/AMReX_FillPatchUtil_I.H
+585 −0 Src/AmrCore/AMReX_FillPatcher.H
+3 −2 Src/AmrCore/AMReX_MFInterp_1D_C.H
+3 −2 Src/AmrCore/AMReX_MFInterp_2D_C.H
+3 −2 Src/AmrCore/AMReX_MFInterp_3D_C.H
+1 −0 Src/AmrCore/CMakeLists.txt
+2 −0 Src/AmrCore/Make.package
+11 −0 Src/Base/AMReX_BCRec.H
+4 −2 Src/Base/AMReX_BC_TYPES.H
+331 −0 Src/Base/AMReX_CTOParallelForImpl.H
+1 −1 Src/Base/AMReX_FArrayBox.H
+4 −0 Src/Base/AMReX_Geometry.H
+16 −4 Src/Base/AMReX_Geometry.cpp
+2 −0 Src/Base/AMReX_GpuLaunch.H
+3 −0 Src/Base/AMReX_MFIter.H
+16 −0 Src/Base/AMReX_MFIter.cpp
+17 −0 Src/Base/AMReX_MultiFabUtil.H
+201 −114 Src/Base/AMReX_MultiFabUtil.cpp
+293 −0 Src/Base/AMReX_RungeKutta.H
+3 −0 Src/Base/AMReX_bc_types_mod.F90
+2 −0 Src/Base/CMakeLists.txt
+2 −1 Src/Base/Make.package
+77 −78 Src/EB/AMReX_EB2_3D_C.cpp
+4 −0 Src/LinearSolvers/MLMG/AMReX_MLCellABecLap.H
+111 −0 Src/LinearSolvers/MLMG/AMReX_MLCellABecLap.cpp
+5 −0 Src/LinearSolvers/MLMG/AMReX_MLCellLinOp.H
+2 −0 Src/LinearSolvers/MLMG/AMReX_MLCellLinOp.cpp
+22 −22 Src/LinearSolvers/MLMG/AMReX_MLEBNodeFDLap_2D_K.H
+4 −2 Src/LinearSolvers/MLMG/AMReX_MLEBNodeFDLaplacian.H
+45 −7 Src/LinearSolvers/MLMG/AMReX_MLEBNodeFDLaplacian.cpp
+2 −0 Src/LinearSolvers/MLMG/AMReX_MLLinOp.H
+3 −0 Src/LinearSolvers/MLMG/AMReX_MLLinOp.cpp
+2 −0 Src/LinearSolvers/MLMG/AMReX_MLMG.cpp
+2 −1 Tests/Amr/Advection_AmrCore/Source/AdvancePhiAllLevels.cpp
+2 −1 Tests/Amr/Advection_AmrCore/Source/AdvancePhiAtLevel.cpp
+9 −2 Tests/Amr/Advection_AmrCore/Source/AmrCoreAdv.H
+46 −16 Tests/Amr/Advection_AmrCore/Source/AmrCoreAdv.cpp
+1 −1 Tests/Amr/Advection_AmrCore/Source/Src_K/Make.package
+1 −1 Tests/Amr/Advection_AmrLevel/Source/AmrLevelAdv.H
+22 −15 Tests/Amr/Advection_AmrLevel/Source/AmrLevelAdv.cpp
+1 −1 Tests/CMakeLists.txt
+7 −0 Tests/CTOParFor/CMakeLists.txt
+20 −0 Tests/CTOParFor/GNUmakefile
+4 −0 Tests/CTOParFor/Make.package
+64 −0 Tests/CTOParFor/main.cpp
+2 −0 Tests/GPU/CNS/Source/CNS.H
+5 −0 Tests/GPU/CNS/Source/CNS.cpp
+9 −25 Tests/GPU/CNS/Source/CNS_advance.cpp
+10 −10 Tests/GPU/CNS/Source/diffusion/CNS_diffusion_K.H
48 changes: 23 additions & 25 deletions src/RadhydroSimulation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,11 @@ template <typename problem_t> class RadhydroSimulation : public AMRSimulation<pr
void FixupState(int level) override;

// implement FillPatch function
void FillPatch(int lev, amrex::Real time, amrex::MultiFab &mf, int icomp, int ncomp) override;
void FillPatch(int lev, amrex::Real time, amrex::MultiFab &mf, int icomp, int ncomp, FillPatchType fptype) override;

// functions to operate on state vector before/after interpolating between levels
static void PreInterpState(amrex::FArrayBox &fab, amrex::Box const &box, int scomp, int ncomp);
static void PostInterpState(amrex::FArrayBox &fab, amrex::Box const &box, int scomp, int ncomp);
static void PreInterpState(amrex::MultiFab &mf, int scomp, int ncomp);
static void PostInterpState(amrex::MultiFab &mf, int scomp, int ncomp);

// compute axis-aligned 1D profile of user_f(x, y, z)
template <typename F>
Expand Down Expand Up @@ -565,7 +565,7 @@ void RadhydroSimulation<problem_t>::FixupState(int lev)
template <typename problem_t>
void RadhydroSimulation<problem_t>::FillPatch(int lev, amrex::Real time,
amrex::MultiFab &mf, int icomp,
int ncomp) {
int ncomp, FillPatchType fptype) {
BL_PROFILE("AMRSimulation::FillPatch()");

amrex::Vector<amrex::MultiFab *> cmf;
Expand All @@ -583,50 +583,48 @@ void RadhydroSimulation<problem_t>::FillPatch(int lev, amrex::Real time,
GetData(lev - 1, time, cmf, ctime);
}

FillPatchWithData(lev, time, mf, cmf, ctime, fmf, ftime, icomp, ncomp,
FillPatchWithData(lev, time, mf, cmf, ctime, fmf, ftime, icomp, ncomp, fptype,
PreInterpState, PostInterpState);
}

template <typename problem_t>
void RadhydroSimulation<problem_t>::PreInterpState(amrex::FArrayBox &fab, amrex::Box const &box,
int scomp, int ncomp)
void RadhydroSimulation<problem_t>::PreInterpState(amrex::MultiFab &mf, int scomp, int ncomp)
{
BL_PROFILE("RadhydroSimulation::PreInterpState()");

auto const &cons = fab.array();
amrex::ParallelFor(box, [=] AMREX_GPU_DEVICE(int i, int j, int k) {
const auto rho = cons(i, j, k, HydroSystem<problem_t>::density_index);
const auto px = cons(i, j, k, HydroSystem<problem_t>::x1Momentum_index);
const auto py = cons(i, j, k, HydroSystem<problem_t>::x2Momentum_index);
const auto pz = cons(i, j, k, HydroSystem<problem_t>::x3Momentum_index);
const auto Etot = cons(i, j, k, HydroSystem<problem_t>::energy_index);
auto const &cons = mf.arrays();
amrex::ParallelFor(mf, [=] AMREX_GPU_DEVICE(int bx, int i, int j, int k) {
const auto rho = cons[bx](i, j, k, HydroSystem<problem_t>::density_index);
const auto px = cons[bx](i, j, k, HydroSystem<problem_t>::x1Momentum_index);
const auto py = cons[bx](i, j, k, HydroSystem<problem_t>::x2Momentum_index);
const auto pz = cons[bx](i, j, k, HydroSystem<problem_t>::x3Momentum_index);
const auto Etot = cons[bx](i, j, k, HydroSystem<problem_t>::energy_index);
const auto kinetic_energy = (px*px + py*py + pz*pz) / (2.0*rho);

// replace hydro total energy with specific internal energy (SIE)
const auto e = (Etot - kinetic_energy) / rho;
cons(i, j, k, HydroSystem<problem_t>::energy_index) = e;
cons[bx](i, j, k, HydroSystem<problem_t>::energy_index) = e;
});
}

template <typename problem_t>
void RadhydroSimulation<problem_t>::PostInterpState(amrex::FArrayBox &fab, amrex::Box const &box,
int scomp, int ncomp)
void RadhydroSimulation<problem_t>::PostInterpState(amrex::MultiFab &mf, int scomp, int ncomp)
{
BL_PROFILE("RadhydroSimulation::PostInterpState()");

auto const &cons = fab.array();
amrex::ParallelFor(box, [=] AMREX_GPU_DEVICE(int i, int j, int k) {
const auto rho = cons(i, j, k, HydroSystem<problem_t>::density_index);
const auto px = cons(i, j, k, HydroSystem<problem_t>::x1Momentum_index);
const auto py = cons(i, j, k, HydroSystem<problem_t>::x2Momentum_index);
const auto pz = cons(i, j, k, HydroSystem<problem_t>::x3Momentum_index);
const auto e = cons(i, j, k, HydroSystem<problem_t>::energy_index);
auto const &cons = mf.arrays();
amrex::ParallelFor(mf, [=] AMREX_GPU_DEVICE(int bx, int i, int j, int k) {
const auto rho = cons[bx](i, j, k, HydroSystem<problem_t>::density_index);
const auto px = cons[bx](i, j, k, HydroSystem<problem_t>::x1Momentum_index);
const auto py = cons[bx](i, j, k, HydroSystem<problem_t>::x2Momentum_index);
const auto pz = cons[bx](i, j, k, HydroSystem<problem_t>::x3Momentum_index);
const auto e = cons[bx](i, j, k, HydroSystem<problem_t>::energy_index);
const auto Eint = rho * e;
const auto kinetic_energy = (px*px + py*py + pz*pz) / (2.0*rho);

// recompute hydro total energy from Eint + KE
const auto Etot = Eint + kinetic_energy;
cons(i, j, k, HydroSystem<problem_t>::energy_index) = Etot;
cons[bx](i, j, k, HydroSystem<problem_t>::energy_index) = Etot;
});
}

Expand Down
135 changes: 77 additions & 58 deletions src/simulation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "AMReX_Extension.H"
#include "AMReX_FArrayBox.H"
#include "AMReX_FillPatchUtil.H"
#include "AMReX_FillPatcher.H"
#include "AMReX_FluxRegister.H"
#include "AMReX_GpuQualifiers.H"
#include "AMReX_INT.H"
Expand Down Expand Up @@ -75,6 +76,8 @@ using namespace conduit;
using namespace ascent;
#endif

enum class FillPatchType { fillpatch_class, fillpatch_function };

// Main simulation class; solvers should inherit from this
template <typename problem_t> class AMRSimulation : public amrex::AmrCore {
public:
Expand Down Expand Up @@ -158,20 +161,22 @@ template <typename problem_t> class AMRSimulation : public amrex::AmrCore {
void fillBoundaryConditions(amrex::MultiFab &S_filled, amrex::MultiFab &state,
int lev, amrex::Real time,
PreInterpHook const &pre_interp,
PostInterpHook const&post_interp);
PostInterpHook const&post_interp,
FillPatchType fptype = FillPatchType::fillpatch_class);

template <typename PreInterpHook, typename PostInterpHook>
void FillPatchWithData(int lev, amrex::Real time, amrex::MultiFab &mf,
amrex::Vector<amrex::MultiFab *> &coarseData,
amrex::Vector<amrex::Real> &coarseTime,
amrex::Vector<amrex::MultiFab *> &fineData,
amrex::Vector<amrex::Real> &fineTime, int icomp,
int ncomp, PreInterpHook const &pre_interp,
int ncomp, FillPatchType fptype,
PreInterpHook const &pre_interp,
PostInterpHook const &post_interp);

static void InterpHookNone(amrex::FArrayBox &fab, amrex::Box const &box, int scomp, int ncomp);
static void InterpHookNone(amrex::MultiFab &mf, int scomp, int ncomp);
virtual void FillPatch(int lev, amrex::Real time, amrex::MultiFab &mf, int icomp,
int ncomp);
int ncomp, FillPatchType fptype);
void FillCoarsePatch(int lev, amrex::Real time, amrex::MultiFab &mf,
int icomp, int ncomp);
void GetData(int lev, amrex::Real time,
Expand Down Expand Up @@ -231,6 +236,9 @@ template <typename problem_t> class AMRSimulation : public amrex::AmrCore {
// the reflux operation
amrex::Vector<std::unique_ptr<amrex::YAFluxRegister>> flux_reg_;

// This is for fillpatch during timestepping, but not for regridding.
amrex::Vector<std::unique_ptr<amrex::FillPatcher<amrex::MultiFab>>> fillpatcher_;

// Nghost = number of ghost cells for each array
int nghost_ = 4; // PPM needs nghost >= 3, PPM+flattening needs nghost >= 4
int ncomp_cc_ = 0; // = number of components (conserved variables) for each array
Expand Down Expand Up @@ -294,6 +302,7 @@ void AMRSimulation<problem_t>::initialize(
state_old_cc_.resize(nlevs_max);
max_signal_speed_.resize(nlevs_max);
flux_reg_.resize(nlevs_max + 1);
fillpatcher_.resize(nlevs_max + 1);
cellUpdatesEachLevel_.resize(nlevs_max, 0);

BCs_cc_ = boundaryConditions;
Expand Down Expand Up @@ -913,6 +922,8 @@ auto AMRSimulation<problem_t>::timeStepWithSubcycling(int lev, amrex::Real time,

AverageDownTo(lev); // average lev+1 down to lev
FixupState(lev); // fix any unphysical states created by reflux or averaging

fillpatcher_[lev+1].reset(); // because the data on lev have changed.
}

return stepsLeft;
Expand Down Expand Up @@ -1018,7 +1029,7 @@ void AMRSimulation<problem_t>::RemakeLevel(
amrex::MultiFab old_state(ba, dm, ncomp, nghost);
amrex::MultiFab max_signal_speed(ba, dm, 1, nghost);

FillPatch(level, time, new_state, 0, ncomp);
FillPatch(level, time, new_state, 0, ncomp, FillPatchType::fillpatch_function);

std::swap(new_state, state_new_cc_[level]);
std::swap(old_state, state_old_cc_[level]);
Expand All @@ -1043,23 +1054,48 @@ void AMRSimulation<problem_t>::ClearLevel(int level) {
state_old_cc_[level].clear();
max_signal_speed_[level].clear();
flux_reg_[level].reset(nullptr);
fillpatcher_[level].reset(nullptr);
}

template <typename problem_t>
void AMRSimulation<problem_t>::InterpHookNone(
amrex::FArrayBox &fab, amrex::Box const &box, int scomp, int ncomp)
void AMRSimulation<problem_t>::InterpHookNone(amrex::MultiFab &mf, int scomp, int ncomp)
{
// do nothing
}


template <typename problem_t> struct setBoundaryFunctor {
AMREX_GPU_DEVICE void
operator()(const amrex::IntVect &iv, amrex::Array4<amrex::Real> const &dest,
const int &dcomp, const int &numcomp,
amrex::GeometryData const &geom, const amrex::Real &time,
const amrex::BCRec *bcr, int bcomp, const int &orig_comp) const {
AMRSimulation<problem_t>::setCustomBoundaryConditions(
iv, dest, dcomp, numcomp, geom, time, bcr, bcomp, orig_comp);
}
};

template <typename problem_t>
AMREX_GPU_DEVICE AMREX_FORCE_INLINE void
AMRSimulation<problem_t>::setCustomBoundaryConditions(
const amrex::IntVect &iv, amrex::Array4<amrex::Real> const &dest, int dcomp,
int numcomp, amrex::GeometryData const &geom, const amrex::Real time,
const amrex::BCRec *bcr, int bcomp, int orig_comp) {
// user should implement if needed using template specialization
// (This is only called when amrex::BCType::ext_dir is set for a given
// boundary.)

// set boundary condition for cell 'iv'
}

// Compute a new multifab 'mf' by copying in state from valid region and filling
// ghost cells
// NOTE: This implementation is only used by AdvectionSimulation.
// RadhydroSimulation provides its own implementation.
template <typename problem_t>
void AMRSimulation<problem_t>::FillPatch(int lev, amrex::Real time,
amrex::MultiFab &mf, int icomp,
int ncomp) {
int ncomp, FillPatchType fptype) {
BL_PROFILE("AMRSimulation::FillPatch()");

amrex::Vector<amrex::MultiFab *> cmf;
Expand All @@ -1077,7 +1113,7 @@ void AMRSimulation<problem_t>::FillPatch(int lev, amrex::Real time,
GetData(lev - 1, time, cmf, ctime);
}

FillPatchWithData(lev, time, mf, cmf, ctime, fmf, ftime, icomp, ncomp,
FillPatchWithData(lev, time, mf, cmf, ctime, fmf, ftime, icomp, ncomp, fptype,
InterpHookNone, InterpHookNone);
}

Expand Down Expand Up @@ -1112,37 +1148,12 @@ void AMRSimulation<problem_t>::MakeNewLevelFromScratch(
// check that state_new_cc_[lev] is properly filled
AMREX_ALWAYS_ASSERT(!state_new_cc_[level].contains_nan(0, ncomp));

// fill ghost zones
// fill ghost zones (needed for some refinement criteria)
fillBoundaryConditions(state_new_cc_[level], state_new_cc_[level], level, time,
InterpHookNone, InterpHookNone);
InterpHookNone, InterpHookNone, FillPatchType::fillpatch_function);

// copy to state_old_cc_ (including ghost zones)
state_old_cc_[level].ParallelCopy(state_new_cc_[level], 0, 0, ncomp, nghost,
nghost);
}

template <typename problem_t> struct setBoundaryFunctor {
AMREX_GPU_DEVICE void
operator()(const amrex::IntVect &iv, amrex::Array4<amrex::Real> const &dest,
const int &dcomp, const int &numcomp,
amrex::GeometryData const &geom, const amrex::Real &time,
const amrex::BCRec *bcr, int bcomp, const int &orig_comp) const {
AMRSimulation<problem_t>::setCustomBoundaryConditions(
iv, dest, dcomp, numcomp, geom, time, bcr, bcomp, orig_comp);
}
};

template <typename problem_t>
AMREX_GPU_DEVICE AMREX_FORCE_INLINE void
AMRSimulation<problem_t>::setCustomBoundaryConditions(
const amrex::IntVect &iv, amrex::Array4<amrex::Real> const &dest, int dcomp,
int numcomp, amrex::GeometryData const &geom, const amrex::Real time,
const amrex::BCRec *bcr, int bcomp, int orig_comp) {
// user should implement if needed using template specialization
// (This is only called when amrex::BCType::ext_dir is set for a given
// boundary.)

// set boundary condition for cell 'iv'
state_old_cc_[level].ParallelCopy(state_new_cc_[level], 0, 0, ncomp, nghost, nghost);
}

template <typename problem_t>
Expand All @@ -1152,7 +1163,8 @@ void AMRSimulation<problem_t>::fillBoundaryConditions(amrex::MultiFab &S_filled,
int const lev,
amrex::Real const time,
PreInterpHook const &pre_interp,
PostInterpHook const &post_interp) {
PostInterpHook const &post_interp,
FillPatchType fptype) {
BL_PROFILE("AMRSimulation::fillBoundaryConditions()");

// On a single level, any periodic boundaries are filled first
Expand Down Expand Up @@ -1181,7 +1193,8 @@ void AMRSimulation<problem_t>::fillBoundaryConditions(amrex::MultiFab &S_filled,
}

FillPatchWithData(lev, time, S_filled, coarseData, coarseTime, fineData,
fineTime, 0, S_filled.nComp(), pre_interp, post_interp);
fineTime, 0, S_filled.nComp(), fptype,
pre_interp, post_interp);
} else { // level 0
// fill internal and periodic boundaries, ignoring corners (cross=true)
// (there is no performance benefit for this in practice)
Expand Down Expand Up @@ -1220,9 +1233,21 @@ void AMRSimulation<problem_t>::FillPatchWithData(
amrex::Vector<amrex::Real> &coarseTime,
amrex::Vector<amrex::MultiFab *> &fineData,
amrex::Vector<amrex::Real> &fineTime, int icomp, int ncomp,
FillPatchType fptype,
PreInterpHook const &pre_interp, PostInterpHook const &post_interp) {
BL_PROFILE("AMRSimulation::FillPatchWithData()");

// use CellConservativeLinear interpolation onto fine grid
amrex::Interpolater *mapper = &amrex::cell_cons_interp;

if (fptype == FillPatchType::fillpatch_class) {
if (fillpatcher_[lev] == nullptr) {
fillpatcher_[lev] = std::make_unique<amrex::FillPatcher<amrex::MultiFab>>(
grids[lev], dmap[lev], geom[lev], grids[lev - 1], dmap[lev - 1], geom[lev - 1],
mf.nGrowVect(), mf.nComp(), mapper);
}
}

// create functor to fill ghost zones at domain boundaries
// (note that domain boundaries may be present at any refinement level)
amrex::GpuBndryFuncFab<setBoundaryFunctor<problem_t>> boundaryFunctor(
Expand All @@ -1240,19 +1265,19 @@ void AMRSimulation<problem_t>::FillPatchWithData(
coarsePhysicalBoundaryFunctor(geom[lev - 1], BCs_cc_,
boundaryFunctor);

// use CellConservativeLinear interpolation onto fine grid
amrex::Interpolater *mapper = &amrex::cell_cons_interp;
// amrex::MFInterpolater *mapper = &amrex::mf_cell_cons_interp;
// amrex::MFInterpolater *mapper = &amrex::mf_pc_interp;

// copies interior zones, fills ghost zones with space-time interpolated
// data
amrex::FillPatchTwoLevels(mf, time, coarseData, coarseTime, fineData,
fineTime, 0, icomp, ncomp, geom[lev - 1],
geom[lev], coarsePhysicalBoundaryFunctor, 0,
finePhysicalBoundaryFunctor, 0, refRatio(lev - 1),
mapper, BCs_cc_, 0,
pre_interp, post_interp);
if (fptype == FillPatchType::fillpatch_class) {
fillpatcher_[lev]->fill(mf, mf.nGrowVect(), time,
coarseData, coarseTime, fineData, fineTime, 0, icomp, ncomp,
coarsePhysicalBoundaryFunctor, 0, finePhysicalBoundaryFunctor, 0,
BCs_cc_, 0, pre_interp, post_interp);
} else {
amrex::FillPatchTwoLevels(mf, time, coarseData, coarseTime, fineData, fineTime, 0, icomp, ncomp,
geom[lev - 1], geom[lev], coarsePhysicalBoundaryFunctor, 0,
finePhysicalBoundaryFunctor, 0, refRatio(lev - 1), mapper, BCs_cc_, 0, pre_interp,
post_interp);
}
}
}

Expand Down Expand Up @@ -1304,16 +1329,10 @@ void AMRSimulation<problem_t>::GetData(int lev, amrex::Real time,
data.clear();
datatime.clear();

const amrex::Real teps =
(tNew_[lev] - tOld_[lev]) * 1.e-3; // generous roundoff error threshold

if (time > tNew_[lev] - teps &&
time < tNew_[lev] + teps) { // if time == tNew_[lev] within roundoff
if (amrex::almostEqual(time, tNew_[lev], 5)) { // if time == tNew_[lev] within roundoff
data.push_back(&state_new_cc_[lev]);
datatime.push_back(tNew_[lev]);
} else if (time > tOld_[lev] - teps &&
time <
tOld_[lev] + teps) { // if time == tOld_[lev] within roundoff
} else if (amrex::almostEqual(time, tOld_[lev], 5)) { // if time == tOld_[lev] within roundoff
data.push_back(&state_old_cc_[lev]);
datatime.push_back(tOld_[lev]);
} else { // otherwise return both old and new states for interpolation
Expand Down