Point-to-point migration of the pre-regrid solution into a rank's old-element WINDOW: the
memory-space-agnostic core of AMR Stage-5 v2, shared by every backend and both dimensions.
An adapting epoch repartitions the mesh, so the rank that will own a new element and the rank
that owned its old source need not be the same. Both partitions are contiguous ranges of the
same space-filling-curve leaf order and the transfer plan is rank-replicated, so each rank can
compute the contiguous window of old elements its own new range references (PlanWindows, which
needs the plan and therefore stays with the AMR controllers) and then receive exactly that
window - rather than allgathering the whole old field, which is what v1 did.
Two properties make the schedule cheap, and both are worth understanding before editing this
module:
Routing needs NO communication. Send and receive runs are the same intersection
(OwnedRun) evaluated from the same replicated offsetElem and window tables on both ends
of every pair, so the two schedules match by construction: no handshake, no count
exchange, no collective. Reaching for MPI_Alltoallv here would pay for a negotiation that
replication has already answered.
NOTHING needs packing. A run of elements at a fixed variable is contiguous in both the
source field and the window, so each message reads and writes the real storage directly.
That costs one message per (peer,variable) instead of one per peer, and it removes a pack
buffer, an unpack loop and two whole-field copies.
Everything here works on FLAT buffers, indexed as (perElem, nElem, nvar) in Fortran order with
perElem = (N+1)2 in 2-D and (N+1)3 in 3-D. That is what lets one copy of the schedule
serve both dimensions and both backends: the GPU backend hands these routines Fortran
descriptors over DEVICE allocations and MPI receives straight into device memory (the idiom of
SideExchangeStart in SELF_MappedScalar_3D.f90; GPU-aware MPI is already an unconditional
requirement of any multi-rank GPU run in SELF), while the portable backend hands them host
allocatables.
The buffer arguments are assumed-size, so a caller with a rank-4 or rank-5 window passes the
whole array and relies on sequence association. Every such actual argument in SELF is
contiguous (explicit-shape dummies, allocatables, or pointer,contiguous), so no copy-in
temporary can appear - which matters because MPI is handed the addresses.
Runs once per adapting epoch, between time steps - never inside the time-stepping loop.
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.
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.
The complete host-memory migration: post the messages, copy the part of the window this
rank already owns while they are in flight, then wait. This is the portable backend's whole
implementation; the GPU backend calls PostOldWindowExchange / FinishOldWindowExchange
itself so that it can serve the local part with a device-to-device copy instead.
Wait on the messages PostOldWindowExchange posted. A no-op when nothing was posted, which
is the common case for a symmetric refinement: under a contiguous space-filling-curve
repartition each rank's new range is sourced from its own old range, so the window is
entirely local and not a single byte moves.
Arguments
Type
Intent
Optional
Attributes
Name
integer,
intent(inout)
::
requests(:)
integer,
intent(in)
::
msgCount
public subroutine OwnedRun(offsetElem, r, first, last, a, b)
The run of elements that rank r-1 owns and that also lies in [first,last]: a..b, empty when
b < a. offsetElem is a decomposition's contiguous ownership table, so rank r-1 owns
offsetElem(r)+1 .. offsetElem(r+1).
Post every message of this rank's migration schedule and return without waiting: the
receives that fill the remote runs of its window, then the sends that serve the runs of its
old range that other ranks' windows need. Receives are posted before sends, as everywhere
else in SELF.
reals per element per variable: (N+1)2 in 2-D, (N+1)3 in 3-D
integer,
intent(in)
::
nvar
integer,
intent(in)
::
nLocalOld
elements this rank owned before the epoch (uLocal's stride)
real(kind=prec),
intent(in)
::
uLocal(*)
(perElem,nLocalOld,nvar), first element = this rank's first
integer,
intent(in)
::
winFirst(1:decomp%nRanks)
integer,
intent(in)
::
winLast(1:decomp%nRanks)
integer,
intent(in)
::
wFirst
this rank's window, normalized (wFirst > wLast if empty)
integer,
intent(in)
::
wLast
real(kind=prec),
intent(inout)
::
uWin(*)
(perElem,nWinElem,nvar), first element = global old wFirst
intent(inout), not out: the receives write it through MPI rather than through the dummy,
and an intent(out) dummy would license a compiler to treat it as undefined on entry.