-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a MeshData
variant for refinement tagging
#1182
base: develop
Are you sure you want to change the base?
Changes from 16 commits
6814173
6df75e7
2fdc2ed
5cb25b4
3ad74e1
16a1c58
6e1b905
2b6545c
ccf72b0
d7d536f
fa37fb9
401f412
638e077
b6e0e6b
2b3f32a
622882d
5b7863b
06e0ade
3cf589d
7ea604c
d2a92b0
dd4a652
fe95b47
554a1d4
781d032
c1beb9e
9e8b17f
bcbc1c7
b2a5e52
46517ab
f9313d3
20abf5a
1408bf0
5b91a7a
382a6a7
4ee02c5
862c2a9
bd50d24
09464c2
df06a50
511c77c
328dc33
5e657c0
dd928ab
0cbbdac
01517f7
3ddbb9f
454324f
9cdf3aa
ed3e16f
cc01266
5a2470b
cc97120
9338c54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,12 +94,59 @@ std::shared_ptr<StateDescriptor> Initialize(ParameterInput *pin) { | |
pkg->AddField<Conserved::divD>( | ||
Metadata({Metadata::Cell, Metadata::Derived, Metadata::OneCopy})); | ||
|
||
pkg->CheckRefinementBlock = CheckRefinement; | ||
bool check_refine_mesh = | ||
pin->GetOrAddBoolean("parthenon/mesh", "CheckRefineMesh", true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should not mix downstream/example parameters with Parthenon intrinsic parameters, i.e., |
||
if (check_refine_mesh) { | ||
pkg->CheckRefinementMesh = CheckRefinementMesh; | ||
} else { | ||
pkg->CheckRefinementBlock = CheckRefinement; | ||
} | ||
pkg->EstimateTimestepMesh = EstimateTimestep; | ||
pkg->FillDerivedMesh = FillDerived; | ||
return pkg; | ||
} | ||
|
||
void CheckRefinementMesh(MeshData<Real> *md, | ||
parthenon::ParArray1D<AmrTag> &delta_levels) { | ||
// refine on advected, for example. could also be a derived quantity | ||
static auto desc = parthenon::MakePackDescriptor<Conserved::phi>(md); | ||
auto pack = desc.GetPack(md); | ||
|
||
auto pkg = md->GetMeshPointer()->packages.Get("advection_package"); | ||
const auto &refine_tol = pkg->Param<Real>("refine_tol"); | ||
const auto &derefine_tol = pkg->Param<Real>("derefine_tol"); | ||
|
||
auto ib = md->GetBoundsI(IndexDomain::entire); | ||
auto jb = md->GetBoundsJ(IndexDomain::entire); | ||
auto kb = md->GetBoundsK(IndexDomain::entire); | ||
auto scatter_levels = delta_levels.ToScatterView<Kokkos::Experimental::ScatterMax>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, and for the other calls: We might need a Also kokkos/kokkos#6363 is not an issue here because the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand what
yes, they are reset at the inital call to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That is a very good question. I just followed the example on p120 of file:///home/pgrete/Downloads/KokkosTutorial_ORNL20.pdf when I implemented the scatterview for the histograms. I'm going to ask on the Kokkos Slack. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright, I got some info:
So, with the current pattern, it seems that we need a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. awesome, thanks @pgrete!
As it is now the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think so, too. |
||
parthenon::par_for_outer( | ||
PARTHENON_AUTO_LABEL, 0, 0, 0, pack.GetNBlocks() - 1, 0, | ||
pack.GetMaxNumberOfVars() - 1, kb.s, kb.e, | ||
KOKKOS_LAMBDA(parthenon::team_mbr_t team_member, const int b, const int n, | ||
const int k) { | ||
typename Kokkos::MinMax<Real>::value_type minmax; | ||
par_reduce_inner( | ||
parthenon::inner_loop_pattern_ttr_tag, team_member, jb.s, jb.e, ib.s, ib.e, | ||
[&](const int j, const int i, | ||
typename Kokkos::MinMax<Real>::value_type &lminmax) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any specific reason for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. That makes sense. In that case, I suggest to open an issue (before merging) this to keep track of updating these functions once #1142 is in. |
||
lminmax.min_val = (pack(n, k, j, i) < lminmax.min_val ? pack(n, k, j, i) | ||
: lminmax.min_val); | ||
lminmax.max_val = (pack(n, k, j, i) > lminmax.max_val ? pack(n, k, j, i) | ||
: lminmax.max_val); | ||
}, | ||
Kokkos::MinMax<Real>(minmax)); | ||
|
||
auto levels_access = scatter_levels.access(); | ||
auto flag = AmrTag::same; | ||
if (minmax.max_val > refine_tol && minmax.min_val < derefine_tol) | ||
flag = AmrTag::refine; | ||
if (minmax.max_val < derefine_tol) flag = AmrTag::derefine; | ||
levels_access(b).update(flag); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this work as expected (in particular with the split across There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think so. It should be equivalent to doing Kokkos::atomic_max(&delta_levels(b), flag); So the race condition is across There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, right, yes. I didn't see see. Might be worth adding a comment in that direction (at least to the common/non-example function in |
||
}); | ||
delta_levels.ContributeScatter(scatter_levels); | ||
} | ||
|
||
AmrTag CheckRefinement(MeshBlockData<Real> *rc) { | ||
// refine on advected, for example. could also be a derived quantity | ||
static auto desc = parthenon::MakePackDescriptor<Conserved::phi>(rc); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,6 +95,26 @@ AmrTag AMRFirstDerivative::operator()(const MeshBlockData<Real> *rc) const { | |
return Refinement::FirstDerivative(bnds, q, refine_criteria, derefine_criteria); | ||
} | ||
|
||
void AMRFirstDerivative::operator()(MeshData<Real> *mc, | ||
ParArray1D<AmrTag> &delta_levels) const { | ||
auto ib = mc->GetBoundsI(IndexDomain::interior); | ||
auto jb = mc->GetBoundsJ(IndexDomain::interior); | ||
auto kb = mc->GetBoundsK(IndexDomain::interior); | ||
auto bnds = AMRBounds(ib, jb, kb); | ||
auto dims = mc->GetMeshPointer()->resolved_packages->FieldMetadata(field).Shape(); | ||
int n5(0), n4(0); | ||
if (dims.size() > 2) { | ||
n5 = dims[1]; | ||
n4 = dims[2]; | ||
} else if (dims.size() > 1) { | ||
n5 = dims[0]; | ||
n4 = dims[1]; | ||
} | ||
const int idx = comp4 + n4 * (comp5 + n5 * comp6); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if we don't have a simpler way to get a flat index (I imagine there must be more places with thus use case but I don't recall offhand). |
||
Refinement::FirstDerivative(bnds, mc, field, idx, delta_levels, refine_criteria, | ||
derefine_criteria, max_level); | ||
} | ||
|
||
AmrTag AMRSecondDerivative::operator()(const MeshBlockData<Real> *rc) const { | ||
if (!rc->HasVariable(field) || !rc->IsAllocated(field)) { | ||
return AmrTag::same; | ||
|
@@ -105,4 +125,24 @@ AmrTag AMRSecondDerivative::operator()(const MeshBlockData<Real> *rc) const { | |
return Refinement::SecondDerivative(bnds, q, refine_criteria, derefine_criteria); | ||
} | ||
|
||
void AMRSecondDerivative::operator()(MeshData<Real> *mc, | ||
ParArray1D<AmrTag> &delta_levels) const { | ||
auto ib = mc->GetBoundsI(IndexDomain::interior); | ||
auto jb = mc->GetBoundsJ(IndexDomain::interior); | ||
auto kb = mc->GetBoundsK(IndexDomain::interior); | ||
auto bnds = AMRBounds(ib, jb, kb); | ||
auto dims = mc->GetMeshPointer()->resolved_packages->FieldMetadata(field).Shape(); | ||
int n5(0), n4(0); | ||
if (dims.size() > 2) { | ||
n5 = dims[1]; | ||
n4 = dims[2]; | ||
} else if (dims.size() > 1) { | ||
n5 = dims[0]; | ||
n4 = dims[1]; | ||
} | ||
const int idx = comp4 + n4 * (comp5 + n5 * comp6); | ||
Refinement::SecondDerivative(bnds, mc, field, idx, delta_levels, refine_criteria, | ||
derefine_criteria, max_level); | ||
} | ||
|
||
} // namespace parthenon |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,19 +20,25 @@ | |
#include <utility> | ||
|
||
#include "amr_criteria/amr_criteria.hpp" | ||
#include "interface/make_pack_descriptor.hpp" | ||
#include "interface/mesh_data.hpp" | ||
#include "interface/meshblock_data.hpp" | ||
#include "interface/state_descriptor.hpp" | ||
#include "kokkos_abstraction.hpp" | ||
#include "mesh/mesh.hpp" | ||
#include "mesh/mesh_refinement.hpp" | ||
#include "mesh/meshblock.hpp" | ||
#include "parameter_input.hpp" | ||
#include "utils/instrument.hpp" | ||
|
||
namespace parthenon { | ||
namespace Refinement { | ||
|
||
std::shared_ptr<StateDescriptor> Initialize(ParameterInput *pin) { | ||
auto ref = std::make_shared<StateDescriptor>("Refinement"); | ||
bool check_refine_mesh = | ||
pin->GetOrAddBoolean("parthenon/mesh", "CheckRefineMesh", true); | ||
ref->AddParam("check_refine_mesh", check_refine_mesh); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I now see that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The main use is for deciding which As it is written nothing checks whether both callbacks are enrolled, but I can add that as you've described. I only used the switch to easily check that it was giving the same refinement patterns. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd be nice to get additional input here (@lroberts36 @jdolence @bprather @brryan @Yurlungur ) with regard to whether this a pattern we generally want to introduce to the codebase. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would vote for not introducing a runtime parameter that switches the callback function. I think we should be moving to using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Entirely removing the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The runtime switch isn't for the callbacks, but rather for the refinement criteria. The callbacks are treated the same as the If we don't have the runtime switch then I think the |
||
|
||
int numcrit = 0; | ||
while (true) { | ||
|
@@ -48,7 +54,32 @@ std::shared_ptr<StateDescriptor> Initialize(ParameterInput *pin) { | |
return ref; | ||
} | ||
|
||
AmrTag CheckAllRefinement(MeshBlockData<Real> *rc) { | ||
ParArray1D<AmrTag> CheckAllRefinement(MeshData<Real> *mc) { | ||
const int nblocks = mc->NumBlocks(); | ||
// maybe not great to allocate this all the time | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed. How about making this a private |
||
auto delta_levels = ParArray1D<AmrTag>(Kokkos::View<AmrTag *>( | ||
Kokkos::view_alloc(Kokkos::WithoutInitializing, "delta_levels"), nblocks)); | ||
Kokkos::deep_copy(delta_levels.KokkosView(), AmrTag::derefine); | ||
|
||
Mesh *pm = mc->GetMeshPointer(); | ||
static const bool check_refine_mesh = | ||
pm->packages.Get("Refinement")->Param<bool>("check_refine_mesh"); | ||
|
||
for (auto &pkg : pm->packages.AllPackages()) { | ||
auto &desc = pkg.second; | ||
desc->CheckRefinement(mc, delta_levels); | ||
|
||
if (check_refine_mesh) { | ||
for (auto &amr : desc->amr_criteria) { | ||
(*amr)(mc, delta_levels); | ||
} | ||
} | ||
} | ||
|
||
return delta_levels; | ||
} | ||
|
||
AmrTag CheckAllRefinement(MeshBlockData<Real> *rc, const AmrTag &level) { | ||
// Check all refinement criteria and return the maximum recommended change in | ||
// refinement level: | ||
// delta_level = -1 => recommend derefinement | ||
|
@@ -62,15 +93,19 @@ AmrTag CheckAllRefinement(MeshBlockData<Real> *rc) { | |
// neighboring blocks. Similarly for "do nothing" | ||
PARTHENON_INSTRUMENT | ||
MeshBlock *pmb = rc->GetBlockPointer(); | ||
// delta_level holds the max over all criteria. default to derefining. | ||
AmrTag delta_level = AmrTag::derefine; | ||
static const bool check_refine_mesh = | ||
pmb->packages.Get("Refinement")->Param<bool>("check_refine_mesh"); | ||
// delta_level holds the max over all criteria. default to derefining, or level from | ||
// MeshData check. | ||
AmrTag delta_level = level; | ||
for (auto &pkg : pmb->packages.AllPackages()) { | ||
auto &desc = pkg.second; | ||
delta_level = std::max(delta_level, desc->CheckRefinement(rc)); | ||
if (delta_level == AmrTag::refine) { | ||
// since 1 is the max, we can return without having to look at anything else | ||
return AmrTag::refine; | ||
} | ||
if (check_refine_mesh) continue; | ||
// call parthenon criteria that were registered | ||
for (auto &amr : desc->amr_criteria) { | ||
// get the recommended change in refinement level from this criteria | ||
|
@@ -150,9 +185,111 @@ AmrTag SecondDerivative(const AMRBounds &bnds, const ParArray3D<Real> &q, | |
return AmrTag::same; | ||
} | ||
|
||
void SetRefinement_(MeshBlockData<Real> *rc) { | ||
void FirstDerivative(const AMRBounds &bnds, MeshData<Real> *mc, const std::string &field, | ||
const int &idx, ParArray1D<AmrTag> &delta_levels, | ||
const Real refine_criteria_, const Real derefine_criteria_, | ||
const int max_level_) { | ||
const auto desc = | ||
MakePackDescriptor(mc->GetMeshPointer()->resolved_packages.get(), {field}); | ||
auto pack = desc.GetPack(mc); | ||
const int ndim = mc->GetMeshPointer()->ndim; | ||
const int nvars = pack.GetMaxNumberOfVars(); | ||
|
||
const Real refine_criteria = refine_criteria_; | ||
const Real derefine_criteria = derefine_criteria_; | ||
const int max_level = max_level_; | ||
const int var = idx; | ||
auto scatter_levels = delta_levels.ToScatterView<Kokkos::Experimental::ScatterMax>(); | ||
par_for_outer( | ||
PARTHENON_AUTO_LABEL, 0, 0, 0, pack.GetNBlocks() - 1, bnds.ks, bnds.ke, bnds.js, | ||
bnds.je, | ||
KOKKOS_LAMBDA(team_mbr_t team_member, const int b, const int k, const int j) { | ||
Real maxd = 0.; | ||
par_reduce_inner( | ||
inner_loop_pattern_ttr_tag, team_member, bnds.is, bnds.ie, | ||
[&](const int i, Real &maxder) { | ||
Real scale = std::abs(pack(b, var, k, j, i)); | ||
Real d = 0.5 * | ||
std::abs((pack(b, var, k, j, i + 1) - pack(b, var, k, j, i - 1))) / | ||
(scale + TINY_NUMBER); | ||
maxder = (d > maxder ? d : maxder); | ||
if (ndim > 1) { | ||
d = 0.5 * | ||
std::abs((pack(b, var, k, j + 1, i) - pack(b, var, k, j - 1, i))) / | ||
(scale + TINY_NUMBER); | ||
maxder = (d > maxder ? d : maxder); | ||
} | ||
if (ndim > 2) { | ||
d = 0.5 * | ||
std::abs((pack(b, var, k + 1, j, i) - pack(b, var, k - 1, j, i))) / | ||
(scale + TINY_NUMBER); | ||
maxder = (d > maxder ? d : maxder); | ||
} | ||
}, | ||
Kokkos::Max<Real>(maxd)); | ||
auto levels_access = scatter_levels.access(); | ||
auto flag = AmrTag::same; | ||
if (maxd > refine_criteria && pack.GetLevel(b, 0, 0, 0) < max_level) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why has the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. That makes sense. I'm still not sure this check should belong inside the kernel (rather than being a general feature of those refinement criteria derived from what's available within the refinement package). Having said that, I'm happy to fix this later so that this PR can go in quicker. |
||
flag = AmrTag::refine; | ||
if (maxd < derefine_criteria) flag = AmrTag::derefine; | ||
levels_access(b).update(flag); | ||
}); | ||
delta_levels.ContributeScatter(scatter_levels); | ||
} | ||
|
||
void SecondDerivative(const AMRBounds &bnds, MeshData<Real> *mc, const std::string &field, | ||
const int &idx, ParArray1D<AmrTag> &delta_levels, | ||
const Real refine_criteria_, const Real derefine_criteria_, | ||
const int max_level_) { | ||
const auto desc = | ||
MakePackDescriptor(mc->GetMeshPointer()->resolved_packages.get(), {field}); | ||
auto pack = desc.GetPack(mc); | ||
const int ndim = mc->GetMeshPointer()->ndim; | ||
const int nvars = pack.GetMaxNumberOfVars(); | ||
|
||
const Real refine_criteria = refine_criteria_; | ||
const Real derefine_criteria = derefine_criteria_; | ||
const int max_level = max_level_; | ||
const int var = idx; | ||
auto scatter_levels = delta_levels.ToScatterView<Kokkos::Experimental::ScatterMax>(); | ||
par_for_outer( | ||
PARTHENON_AUTO_LABEL, 0, 0, 0, pack.GetNBlocks() - 1, bnds.ks, bnds.ke, bnds.js, | ||
bnds.je, | ||
KOKKOS_LAMBDA(team_mbr_t team_member, const int b, const int k, const int j) { | ||
Real maxd = 0.; | ||
par_reduce_inner( | ||
inner_loop_pattern_ttr_tag, team_member, bnds.is, bnds.ie, | ||
[&](const int i, Real &maxder) { | ||
Real aqt = std::abs(pack(b, var, k, j, i)) + TINY_NUMBER; | ||
Real qavg = 0.5 * (pack(b, var, k, j, i + 1) + pack(b, var, k, j, i - 1)); | ||
Real d = std::abs(qavg - pack(b, var, k, j, i)) / (std::abs(qavg) + aqt); | ||
maxder = (d > maxder ? d : maxder); | ||
if (ndim > 1) { | ||
qavg = 0.5 * (pack(b, var, k, j + 1, i) + pack(b, var, k, j - 1, i)); | ||
d = std::abs(qavg - pack(b, var, k, j, i)) / (std::abs(qavg) + aqt); | ||
maxder = (d > maxder ? d : maxder); | ||
} | ||
if (ndim > 2) { | ||
qavg = 0.5 * (pack(b, var, k + 1, j, i) + pack(b, var, k - 1, j, i)); | ||
d = std::abs(qavg - pack(b, var, k, j, i)) / (std::abs(qavg) + aqt); | ||
maxder = (d > maxder ? d : maxder); | ||
} | ||
}, | ||
Kokkos::Max<Real>(maxd)); | ||
auto levels_access = scatter_levels.access(); | ||
auto flag = AmrTag::same; | ||
if (maxd > refine_criteria && pack.GetLevel(b, 0, 0, 0) < max_level) | ||
flag = AmrTag::refine; | ||
if (maxd < derefine_criteria) flag = AmrTag::derefine; | ||
levels_access(b).update(flag); | ||
}); | ||
delta_levels.ContributeScatter(scatter_levels); | ||
} | ||
|
||
void SetRefinement_(MeshBlockData<Real> *rc, | ||
const AmrTag &delta_level = AmrTag::derefine) { | ||
auto pmb = rc->GetBlockPointer(); | ||
pmb->pmr->SetRefinement(CheckAllRefinement(rc)); | ||
pmb->pmr->SetRefinement(CheckAllRefinement(rc, delta_level)); | ||
} | ||
|
||
template <> | ||
|
@@ -165,8 +302,11 @@ TaskStatus Tag(MeshBlockData<Real> *rc) { | |
template <> | ||
TaskStatus Tag(MeshData<Real> *rc) { | ||
PARTHENON_INSTRUMENT | ||
ParArray1D<AmrTag> delta_levels = CheckAllRefinement(rc); | ||
auto delta_levels_h = delta_levels.GetHostMirrorAndCopy(); | ||
|
||
for (int i = 0; i < rc->NumBlocks(); i++) { | ||
SetRefinement_(rc->GetBlockData(i).get()); | ||
SetRefinement_(rc->GetBlockData(i).get(), delta_levels_h(i)); | ||
} | ||
return TaskStatus::complete; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might need a
if (pmesh->adaptive) {
here, don't we?Also, conceptually, we previously checked for refinement after the (physical) boundary conditions were set, which I think is more appropriate.
Independent of this PR , it just occurred to me that we're setting the timestep inside the
Step()
of the driver and only outside do the actual refinement.Doesn't this potentially cause issues with violating the cfl on fine blocks after a block was refined?
Also pinging @jdolence @lroberts36 @Yurlungur @bprather here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we do need
pmesh->adaptive
.Regarding the time step control... I agree in principle that could be a problem but we never encountered any issues, so I wonder if there's something I'm missing there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Its fixed now and I also moved to the
ApplyBoundaryConditionsMD
to keep everything all in the same task region to tag after applying the BC.I think because the refined variable is 10x all the other components it dominates the CFL condition. As a little experiment I offset the spatial profile of the non-refined components and gave one of them a 50x multiplier to try and force where the timestep is overestimated before a refinement. There is definitely some non-monotonicity compared to uniformly refined case.