Old-leaf -> new-leaf solution transfer plan for one adaptation epoch of the 3-D oct-forest: the driver layer of AMR Stage 3 that connects the element-local transfer operators (SELF_SolutionTransfer_3D) to the forest mutation (SELF_OctreeMesh_3D).
One adaptation epoch mutates the forest between two leaf configurations:
nOld = forest%nLeaves ; oldLeaf = forest%leaf(1:nOld) ! snapshot BEFORE mutating
call forest%AdaptFromFlags(flag) ! at most one call, then
call forest%Balance2to1() ! any number of further
! refinements (RefineNode too)
call BuildTransferPlan(forest,nOld,oldLeaf,plan) ! AFTER the last mutation
Solution element index = leaf-list position (the element ordering EmitMesh produces), so the plan records, for every new leaf, where its data comes from in the old element ordering:
SELF_TRANSFER_COPY : the leaf survived unchanged; copy old element sourceElem. SELF_TRANSFER_PROLONG : the leaf descends from old leaf sourceElem; interpolate the old element's degree-N polynomial down the octree path (one step per level, so depth > 1 handles a fresh child that 2:1 balancing refined again in the same epoch). Exact, no loss. SELF_TRANSFER_RESTRICT : the leaf is (an ancestor of) a coarsened family; L2-project the eight old children family(1:8) onto their parent, then prolong down depth >= 0 further steps (depth > 0 occurs when a just-coarsened parent is immediately re-refined by 2:1 balancing). Conservative.
The reconstruction is possible after the fact because forest node ids are stable: refinement only appends nodes and coarsening only detaches children, whose level/parent/octant entries persist. Each new leaf therefore ascends its parent chain until it meets either an old leaf or a node holding a complete eight-child old-leaf family; anything else means the snapshot does not describe the epoch that produced the forest, and the builder stops with an error.
ApplyTransferPlan executes the plan on nodal data u(1:N+1,1:N+1,1:N+1,1:nElem,1:nVar) (the layout of MappedScalar3D %interior; units are those of the transferred fields) and inherits the Stage-3 operator identities: prolongation is exact polynomial interpolation and restriction is the conservative L2 projection, so the Jacobian-weighted cell integral of every variable is conserved and a refine-then-coarsen round trip is the identity to roundoff. Transfer runs once per adaptation epoch, between time steps; it is not a per-step hot path.
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| integer, | public, | parameter | :: | SELF_TRANSFER_COPY | = | 0 | |
| integer, | public, | parameter | :: | SELF_TRANSFER_PROLONG | = | 1 | |
| integer, | public, | parameter | :: | SELF_TRANSFER_RESTRICT | = | 2 |
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| integer, | public, | allocatable | :: | depth(:) | (nNew) number of prolongation steps below the source |
||
| integer, | public, | allocatable | :: | family(:,:) | (8,nNew) old element index per child octant (RESTRICT) |
||
| integer, | public | :: | maxDepth | = | 0 | largest prolongation depth in the plan |
|
| integer, | public | :: | nNew | = | 0 | new element count (= forest%nLeaves at build time) |
|
| integer, | public | :: | nOld | = | 0 | old (pre-epoch) element count |
|
| integer, | public, | allocatable | :: | path(:,:) | (>=maxDepth,nNew) octant taken at each step, top-down |
||
| integer, | public, | allocatable | :: | sourceElem(:) | (nNew) old element index (COPY/PROLONG; 0 otherwise) |
||
| integer, | public, | allocatable | :: | sourceKind(:) | (nNew) SELF_TRANSFER_COPY / PROLONG / RESTRICT |
| procedure, public :: Free => Free_TransferPlan3D |
Execute a transfer plan on nodal element data: uNew(:,:,:,li,:) receives old element data copied, prolonged (exact interpolation), or restricted (conservative L2 projection) according to plan entry li. interp must be the solution interpolant the data lives on (its mortar operators drive the transfer). Runs once per adaptation epoch - not a per-time-step hot path - so clarity is preferred over fused loops here.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(TransferPlan3D), | intent(in) | :: | plan | |||
| type(Lagrange), | intent(in) | :: | interp | |||
| integer, | intent(in) | :: | nVar | |||
| real(kind=prec), | intent(in) | :: | uOld(1:interp%N+1,1:interp%N+1,1:interp%N+1,1:plan%nOld,1:nVar) | |||
| real(kind=prec), | intent(out) | :: | uNew(1:interp%N+1,1:interp%N+1,1:interp%N+1,1:plan%nNew,1:nVar) |
Execute the contiguous sub-range eFirst..eLast of a transfer plan: uNew(:,:,:,k,:) receives the data of new element eFirst+k-1. uOld is the full (global) old field; the output is only the requested slice. This is the gather-then-slice (AMR Stage-5 v1) decomposed-mesh entry point: each rank passes its own contiguous range of the new element ordering and a gathered global old solution, and fills exactly its rank-local storage. ApplyTransferPlanWindow is the point-to-point (v2) entry point, which needs only the sub-range of the old field the requested new range actually references; this routine is that one over the whole old field, so the two are bit-identical by construction rather than by inspection.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(TransferPlan3D), | intent(in) | :: | plan | |||
| type(Lagrange), | intent(in) | :: | interp | |||
| integer, | intent(in) | :: | nVar | |||
| real(kind=prec), | intent(in) | :: | uOld(1:interp%N+1,1:interp%N+1,1:interp%N+1,1:plan%nOld,1:nVar) | |||
| integer, | intent(in) | :: | eFirst | |||
| integer, | intent(in) | :: | eLast | |||
| real(kind=prec), | intent(out) | :: | uNew(1:interp%N+1,1:interp%N+1,1:interp%N+1,1:eLast-eFirst+1,1:nVar) |
Execute the contiguous sub-range eFirst..eLast of a transfer plan against a WINDOW of the old field: uOld holds old elements oldFirst..oldLast only, indexed in the global old element numbering that plan%sourceElem and plan%family use, rather than the whole global field. uNew(:,:,:,k,:) receives the data of new element eFirst+k-1.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(TransferPlan3D), | intent(in) | :: | plan | |||
| type(Lagrange), | intent(in) | :: | interp | |||
| integer, | intent(in) | :: | nVar | |||
| real(kind=prec), | intent(in) | :: | uOld(1:interp%N+1,1:interp%N+1,1:interp%N+1,oldFirst:oldLast,1:nVar) | |||
| integer, | intent(in) | :: | oldFirst | |||
| integer, | intent(in) | :: | oldLast | |||
| integer, | intent(in) | :: | eFirst | |||
| integer, | intent(in) | :: | eLast | |||
| real(kind=prec), | intent(out) | :: | uNew(1:interp%N+1,1:interp%N+1,1:interp%N+1,1:eLast-eFirst+1,1:nVar) |
Build the old->new transfer plan for the adaptation epoch that took the forest from the leaf configuration (nOld, oldLeaf) - a snapshot of (forest%nLeaves, forest%leaf) taken before mutating - to its current leaf configuration. See the module documentation for the allowed mutations within one epoch. The plan is valid until the forest is mutated again.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(OctreeMesh3D), | intent(in) | :: | forest | |||
| integer, | intent(in) | :: | nOld | |||
| integer, | intent(in) | :: | oldLeaf(1:nOld) | |||
| type(TransferPlan3D), | intent(out) | :: | plan |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(TransferPlan3D), | intent(inout) | :: | this |