Adaptive-mesh-refinement controller for 2-D DG models: the driver that closes the serial
AMR loop around a live, time-stepping model. One call to Adapt performs one adaptation
epoch between time steps:
estimate - the Legendre modal-decay indicator flags each element refine/keep/coarsen
from the current solution (driving variable ivar, e.g. pressure);
cap - refine flags on leaves already at maxLevel are demoted to keep, bounding
the finest resolution (and the stable time step) a priori;
halo - refine flags spread to face neighbours for nHalo passes, so a feature
moving at speed c stays inside the refined band provided the adaptation
cadence satisfies kdtc <= nHalo * h_fine;
mutate - AdaptFromFlags + Balance2to1 update the quad-forest; if the leaf set is
unchanged the epoch is a no-op and the model is untouched;
transfer - BuildTransferPlan maps old leaves to new (copy / exact prolongation /
conservative restriction), EmitMesh produces the solver-ready
nonconforming mesh, and a new SEMQuad geometry is generated;
regrid - model%Regrid rebinds the model's storage and boundary conditions to the
new mesh, preserving its time state and parameters; the transferred
solution is applied and uploaded to the device.
The controller owns the forest, the indicator, and the meshes/geometries it emits. The mesh
is rebuilt each epoch and the previous one freed after the model is rebound. Geometry instead
lives in two long-lived buffers that alternate (AMR Stage 6c), so it is resized rather than
reallocated and the previous epoch's geometry remains available while the new one is built.
The base mesh and geometry the model was initialized with belong to the caller and are
never freed here, but the base mesh must outlive the controller: it supplies the
boundary-condition metadata and the communicator for every emitted mesh. After
controller%Free the model's mesh/geometry pointers are dangling, so free or stop using the
model first.
MPI (AMR Stage 5): the forest is rank-replicated. At Init the global base-mesh tables are
allgathered so every rank builds an identical forest; each epoch the rank-local indicator
flags are allgathered (one small collective) so every rank applies identical mutations and
computes identical transfer plans and global connectivity. EmitMesh re-decomposes the new
leaf list into contiguous (space-filling-curve) ranges, so repartitioning and load balance
are implicit in every epoch. Solution migration gathers the old rank-local solutions into a
global field and applies the plan to the new rank-local range only - simple and correct at
single-node scale; a point-to-point exchange is a drop-in replacement behind the same
interface. All collectives run between time steps at the adaptation cadence; nothing is
added to the time-stepping loop.
Because refinement halves the element scale per level, an explicit-stability time step
chosen for the base mesh must shrink with the finest active level;
RecommendedTimeStep(dtBase) = dtBase / 2**MaxLevel gives the level-based bound to pass to
ForwardStep after each epoch.
Debug switches for the Stage 6c incremental geometry path, resolved once from the
environment on first use. Present so that a suspected geometry problem can be split
between the reuse copy and the compacted generation without rebuilding:
SELF_AMR_GEOM_NO_REUSE=1 regenerate every element (the reuse predicate never fires)
SELF_AMR_GEOM_VERIFY=1 additionally generate the full geometry the old way and compare
element by element, reporting the first quantity that differs
Nodes of different colours represent the following:
Solid arrows point from a submodule to the (sub)module which it is
descended from. Dashed arrows point from a module or program unit to
modules which it uses. Where possible, edges connecting nodes are given different colours to make them easier to distinguish in large graphs.
Cumulative geometry-reuse accounting (AMR Stage 6c), so a run can report what fraction of
elements avoided regeneration rather than leaving the payoff to be estimated.
Scratch geometry holding just the elements an epoch actually changed, generated compacted
and then scattered into place. Persistent and resized, so it allocates only when an epoch
changes more elements than any epoch before it.
whether activeMesh was emitted by us (vs the caller's)
Two long-lived geometry buffers, alternated each epoch (AMR Stage 6c). Geometry is now
resized in place rather than allocated and freed per epoch, which is what lets Stage 6b's
amortization apply to it; alternating means the PREVIOUS epoch's geometry is still intact
while the new one is filled, which the incremental reuse path needs. geomSlot records
which buffer activeGeom currently is, or 0 while it is still the caller's geometry.
real(kind=prec),
public
::
refineThreshold
=
0.0_prec
Type-Bound Procedures
procedure, public :: Adapt => Adapt_AMRController2D
Level-based explicit-stability time step: refinement halves the element scale per
level, so a time step dtBase that is stable on the base (level-0) mesh scales to
dtBase / 2**MaxLevel on the current forest. Deterministic and exact for the quadtree
(child elements are exact half-scale subdivisions); no geometry reduction is needed.
Perform one adaptation epoch on the model (see the module documentation). On return,
adapted reports whether the mesh changed; when it did, the model is already rebound to
the new mesh with the solution transferred (conservatively), and the caller should
re-evaluate its time step (RecommendedTimeStep) before the next ForwardStep. When the
leaf set is unchanged the model is untouched.
Allgather an integer array with perElem entries per element from the decomposition's
contiguous rank-local element ranges into the global element ordering.
Allgather a real(prec) array with perElem entries per element from the decomposition's
contiguous rank-local element ranges into the global element ordering.
Fill newGeom for the emitted mesh, reusing the previous epoch's geometry for every element
that did not change and generating only the rest (AMR Stage 6c). nReused reports how many
elements were copied rather than computed.
Release the forest, the indicator, and any controller-emitted mesh/geometry. The
caller-owned base mesh/geometry are untouched. A model still pointing at a
controller-emitted mesh must not be used after this call.
Build a rank-replicated forest over a decomposed base mesh: allgather the global
node coordinates, side table, and material ids (by the decomposition's contiguous
element ownership) and initialize the forest from the global tables. Collective over
the mesh communicator; runs once, at controller initialization.
public subroutine Init_AMRController2D(this, model, refineThreshold, coarsenThreshold, ivar, maxLevel, nHalo)
Attach the controller to an initialized model. The model's current mesh becomes the
forest's base mesh (level 0); its geometry interpolant drives the indicator and the
solution transfer. Thresholds are the sigma = log10 modal-energy-ratio cut-offs of the
refinement indicator (refineThreshold > coarsenThreshold; see
SELF_RefinementIndicator_2D). ivar is the driving solution variable (or
SELF_AMR_ALLVARS). maxLevel >= 0 caps the refinement depth; nHalo >= 0 sets the
refine-flag halo width in elements.
Select the geometry buffer to fill this epoch: whichever of the two is not currently
active, so the previous epoch's geometry stays intact and readable (AMR Stage 6c).
public subroutine VerifyGeometry(this, newMesh, newGeom)
Generate the geometry the original way and report, per quantity, the largest discrepancy
against the incrementally built one. Diagnostic only; gated by SELF_AMR_GEOM_VERIFY.