Skip to content

adam_flux_register_object ​

ADAM, flux register class definition

Berger-Colella reflux machinery for coarse-fine interfaces. Accumulates per-stage fluxes on registered seam faces from both the coarse and the fine side; at end-of-stage, applies the stage-weighted correction (Δt/Δx)·(F_coarse_used − Σ F_fine) to the coarse-side conserved variables.

Integrator-agnostic vocabulary: the per-face accumulator's third axis is nominally sized by n_stages (the realm's stages_per_step_forest() return value). For RK realms this happens to equal rk%nrk, but the register API uses the neutral name so a non-RK integrator can register / accumulate / correct against the same machinery without any vocabulary mismatch.

α.r1 (current operating mode): third-axis collapsed to 1. Under the α end-of-step barrier seam coupling, per-stage RK-weighted reflux refinement (Wang 2018) is dropped to admit asymmetric per-realm stage counts (K_realm(is) /= K_max). The accumulator collapses to a single end-of-step bucket: F_coarse(:,:,1) and F_fine_sum(:,:,1) hold the realm-final-stage face flux, and the correction is applied once per realm per step at stage == stages_per_step_forest(). The n_stages argument on register_face is retained for API stability but the third-axis allocation is forced to 1 regardless of the value passed; the per-stage discipline at the call site lives in the caller (the forest passes n_stages=1, PRISM's accumulate_seam_fluxes_fv passes stage=1 and gates the accumulation on stage_idx == rk%nrk). This is the AMReX Reflux cadence; same-K and asymmetric-K both reduce to the same Berger-Colella end-of-step correction.

See docs/guide/forest.md for the conceptual overview of seam coupling cadence and src/lib/common/README.md → "Forest orchestration" for the contract surface.

Two seam kinds share the same accumulator topology:

  • SEAM_KIND_INTRA_REALM_AMR — fine-coarse adjacency between two AMR levels inside one realm. Fine and coarse sides typically co-resident on the same rank (ADAM keeps levels co-resident by construction).
  • SEAM_KIND_INTER_REALM — face coupling between two realms in a forest. Fine and coarse sides may live on different ranks; cross- rank seam transport is not yet implemented.

Lifecycle (driven by forest_object):

  • init / regrid: register_face populates face(:)
  • per step (top): reset zeroes accumulators
  • per stage: accumulate_*_flux collect contributions from compute_residuals_*
  • end of stage: apply_reflux reduces (MPI) and corrects the coarse q

Memory cost is O(seam-area × nv × n_stages), bounded by the surface area of the AMR boundaries — independent of the interior cell count.

Architectural invariants:

  • All accumulators are intrinsic-typed arrays; no derived-type pointer components reachable from kernels.
  • The register owns no field data — only the per-face flux skin. Field pointers (q, dq) are threaded in by callers, not stored.

Seam-kind codes (see flux_register_face_t%seam_kind).

Encoded as small integers (rather than a Fortran enum) for the same reason the FACE_* codes in adam_maps_object are: they participate in checkpoint dumps and INI-level diagnostics, so stable integer values are wanted.

Source: src/lib/common/adam_flux_register_object.F90

Dependencies

Contents ​

Variables ​

NameTypeAttributesDescription
SEAM_KIND_INTRA_REALM_AMRinteger(kind=I4P)parameterCoarse-fine AMR jump inside one realm.
SEAM_KIND_INTER_REALMinteger(kind=I4P)parameterInter-realm seam between two realms in a forest.

Derived Types ​

flux_register_face_t ​

Per-seam-face flux accumulator.

One entry per coarse face touching a fine-side neighbourhood: either a fine-level patch (SEAM_KIND_INTRA_REALM_AMR) or another realm (SEAM_KIND_INTER_REALM). For a 2:1 refinement in 3D, the coarse face is covered by 4 fine faces; fine_block(:) lists them.

Both accumulators (F_coarse, F_fine_sum) are sized (nv, nface_cells, 1) under the α.r1 single-stage end-of-step contract (see module-level note). The third axis is retained dimensionally so the accumulate_*_flux(face_index, stage, flux_face) API survives unchanged; α-mode callers always pass stage = 1. The fine side accumulates into the same coarse-face skin via 2:1 averaging at the time of accumulation, so the two arrays are pointwise-comparable.

Components ​

NameTypeAttributesDescription
seam_kindinteger(kind=I4P)SEAM_KIND_* (set by register_face).
coarse_realminteger(kind=I4P)Realm owning the coarse side (1-based; 0 sentinel = unset).
coarse_rankinteger(kind=I4P)MPI rank owning the coarse block (issue #28 D4; -1 sentinel = unset).
coarse_blockinteger(kind=I4P)Block index on the coarse side (owner-rank-LOCAL).
coarse_faceinteger(kind=I4P)Face code (FACE_X_MAX..FACE_Z_MIN from adam_maps_object).
fine_realminteger(kind=I4P)Realm owning the fine side (1-based; 0 sentinel = unset).
fine_blockinteger(kind=I4P)allocatableFine blocks covering this coarse face (size 4 for 2:1 refinement in 3D).
nface_cellsinteger(kind=I4P)Coarse cells covered by this face (coarse-side count).
F_coarsereal(kind=R8P)allocatableCoarse-side end-of-step flux: (nv, nface_cells, 1) under α.r1.
F_fine_sumreal(kind=R8P)allocatableFine-side end-of-step accumulator: (nv, nface_cells, 1) under α.r1.

flux_register_object ​

Flux register class definition.

Owns the collection of registered seam faces. Lives on the forest (one instance per forest, shared across realms) so that the cross-realm reduce in apply_reflux has a single ownership point. Intra-realm AMR faces and inter-realm faces share the same face(:) array — they differ only in their seam_kind tag.

Components ​

NameTypeAttributesDescription
is_initialized_logicalFlag: register has been initialized.
nfacesinteger(kind=I4P)Number of registered faces (size of face(:) after initialize).
facetype(flux_register_face_t)allocatableAll registered seam faces; unallocated until initialize.

Type-Bound Procedures ​

NameAttributesDescription
initializepass(self)Allocate face(:) with nfaces capacity (called once after topology is
register_facepass(self)Populate one entry in face(:); called by topology pass at init/regrid.
accumulate_coarse_fluxpass(self)Add a coarse-side per-stage flux to F_coarse on the matching face
accumulate_fine_fluxpass(self)Add a fine-side per-stage flux to F_fine_sum on the matching face
reduce_fine_sumspass(self)MPI-reduce F_fine_sum across ranks so coarse owner has complete sum (Phase
resetpass(self)Zero F_coarse and F_fine_sum on every face; called at top-of-step.
destroypass(self)Deallocate face(:) and reset state to uninitialized.

Subroutines ​

initialize ​

Allocate face(:) with nfaces capacity.

Called once after the topology pass has counted how many seam faces exist. register_face is then called nfaces times to populate the entries.

Idempotent: a re-initialization with the same forest topology yields the same allocation; under a regrid that changes block layouts, the caller is expected to call destroy first, then initialize with the new face count. (No regrid hook yet; this is the schema-reserved path for AMR-active follow-ups.)

Calling with nfaces == 0 is valid: it flips is_initialized_ so the lifecycle hooks (reset, apply_reflux) become safe no-ops on the empty register. This is the N=1 single-realm path.

fortran
subroutine initialize(self, nfaces)

Arguments

NameTypeIntentAttributesDescription
selfclass(flux_register_object)inoutThe register.
nfacesinteger(kind=I4P)inNumber of seam faces to register.

Call graph

register_face ​

Populate one entry in face(:); called by topology pass at init/regrid.

For each coarse-fine adjacency discovered during topology population (in forest_object%populate_inter_realm_topology for inter-realm seams, and in the analogous AMR-jump walk for intra-realm seams), the topology pass calls this routine to fill in the corresponding face(face_index) entry. The accumulator arrays F_coarse and F_fine_sum are allocated here with shape (nv, nface_cells, n_stages) and zeroed.

Same-resolution case (current rmf-2realm with COUPLING_MIRROR and no intra-realm AMR jumps): coarse_realm and fine_realm are the two sides of the seam; the labels are conventional — both sides carry the same resolution, both fluxes are nominally equal, and the resulting correction is expected to be round-off zero in expectation. The accumulator structure is identical to the true coarse-fine AMR case; the same-resolution case just exercises the value-zero branch.

α.r1 storage convention. The n_stages argument is validated (>= 1) for API hygiene but the third-axis allocation is forced to 1 regardless of the value passed: the single end-of-step Berger- Colella correction is the same expression whether the realm uses SSP-RK-3 or SSP-RK-5, and same-K Wang-2018 per-stage refinement is deferred (see module-level note). Callers under α should pass n_stages = 1_I4P to make intent explicit, but passing the realm's stages_per_step_forest() is also accepted (the surplus depth is silently dropped). When per-stage storage is reintroduced (γ research RFC #17 or a future Wang-2018 path), this routine grows a sizing branch and the contract goes back to per-stage.

fortran
subroutine register_face(self, face_index, seam_kind, coarse_realm, coarse_rank, coarse_block, coarse_face, fine_realm, fine_block, nface_cells, nv, n_stages)

Arguments

NameTypeIntentAttributesDescription
selfclass(flux_register_object)inoutThe register.
face_indexinteger(kind=I4P)inIndex into face(:) to populate (1-based, 1..nfaces).
seam_kindinteger(kind=I4P)inSEAM_KIND_*.
coarse_realminteger(kind=I4P)inRealm owning the coarse side (1-based).
coarse_rankinteger(kind=I4P)inMPI rank owning the coarse block (issue #28 D4).
coarse_blockinteger(kind=I4P)inBlock index on the coarse side (owner-rank-LOCAL).
coarse_faceinteger(kind=I4P)inFace code (FACE_X_MAX..FACE_Z_MIN).
fine_realminteger(kind=I4P)inRealm owning the fine side (1-based).
fine_blockinteger(kind=I4P)inoptionalFine blocks covering the coarse face;
nface_cellsinteger(kind=I4P)inCoarse cells covered by this face.
nvinteger(kind=I4P)inNumber of variables (state-vector width).
n_stagesinteger(kind=I4P)inRealm's stages_per_step_forest() (advisory under α.r1:

Call graph

accumulate_coarse_flux ​

Add a coarse-side per-stage flux contribution to F_coarse(:,:,stage).

Called by PRISM's compute_residuals_fv_centered when processing a face that has been identified as a coarse-side seam face. The face_index is looked up via a precomputed seam_face_index(b, face_code) table owned by the residual routine; the lookup is O(1).

Accumulation is additive: a face may receive contributions from multiple accumulate_*_flux calls within one stage (e.g. if the FV scheme processes the same face from both the cell-i and cell-(i+1) side); the register holds the running sum.

fortran
subroutine accumulate_coarse_flux(self, face_index, stage, flux_face)

Arguments

NameTypeIntentAttributesDescription
selfclass(flux_register_object)inoutThe register.
face_indexinteger(kind=I4P)inIndex into face(:) of the seam face being updated.
stageinteger(kind=I4P)inIntegrator stage 1..n_stages.
flux_facereal(kind=R8P)inPer-face flux contribution shaped (nv, nface_cells).

Call graph

accumulate_fine_flux ​

Add a fine-side per-stage flux contribution to F_fine_sum(:,:,stage).

Called by PRISM's compute_residuals_fv_centered when processing a face identified as a fine-side seam face. The contribution is assumed to be already mapped to the coarse-face skin (same-resolution case: identity mapping; AMR case: 2:1 averaging of 4 fine faces into one coarse slot, performed by the caller before this call). Storing the coarse-skin-shaped contribution directly keeps the register topology simple at the cost of caller-side averaging discipline for AMR.

fortran
subroutine accumulate_fine_flux(self, face_index, stage, flux_face)

Arguments

NameTypeIntentAttributesDescription
selfclass(flux_register_object)inoutThe register.
face_indexinteger(kind=I4P)inIndex into face(:) of the seam face being updated.
stageinteger(kind=I4P)inIntegrator stage 1..n_stages.
flux_facereal(kind=R8P)inPer-face flux contribution shaped (nv, nface_cells); already

Call graph

reduce_fine_sums ​

Complete the per-face accumulators across MPI ranks (issue #28 D3).

Blocks are MPI-distributed inside a realm, and after #28 D1 the accumulation is ownership-disjoint: every (block, face) contribution is accumulated by EXACTLY ONE rank (the block's owner), all other ranks hold zeros in that slot. An in-place SUM-allreduce of each face's accumulators is therefore the exact completion — the coarse owner receives the fine halves accumulated on other ranks, and every rank ends holding the same complete register, which makes the end-of-step diagnostics rank-invariant and lets the ownership-gated apply (#28 D4) read complete data.

Intra-realm AMR registers are REPLICATED (identical face list on every rank — the registration pass walks the replicated tree), so the per-face collectives match across ranks by construction. Inter-realm registers are NOT replicated: register_inter_realm_seams registers each rank's OWN realm-a-local seam blocks, so face lists differ per rank and a per-face collective would not match. Those registers hold only the contributions of the seam blocks this rank owns, so the reduce SKIPS them. The two kinds can coexist in one forest (issue #37): the intra-realm faces are registered FIRST (cursors 1..n_intra, identical on every rank) and the rank-local inter-realm ones after them, so reducing exactly the intra-realm faces issues the same collective sequence on every rank.

The reduce happens here (not in apply_reflux_corrections on forest_object) because it is purely register-internal: it exchanges accumulator data between ranks owning parts of the same face entry, without any realm-side q access.

fortran
subroutine reduce_fine_sums(self)

Arguments

NameTypeIntentAttributesDescription
selfclass(flux_register_object)inoutThe register.

Call graph

reset ​

Zero F_coarse and F_fine_sum on every registered face.

Called by forest_object%evolve_one_step at top-of-step. After this call, every face's accumulators are zero; per-stage flux contributions land into clean slots.

Skeleton commit: safe no-op when face(:) is unallocated (the case in this commit, since initialize does not allocate yet).

fortran
subroutine reset(self)

Arguments

NameTypeIntentAttributesDescription
selfclass(flux_register_object)inoutThe register.

Call graph

destroy ​

Deallocate face(:) and reset state to uninitialized.

fortran
subroutine destroy(self)

Arguments

NameTypeIntentAttributesDescription
selfclass(flux_register_object)inoutThe register.

Call graph

restrict_fine_face_to_quadrant ​

quadrant of the coarse-face skin slab (#13 §7.5 M3).

fine_face(1:nv, 1:inner_n, 1:outer_n) is the fine block's face flux on the two tangential axes (inner fastest). Each coarse cell of the quadrant gets the arithmetic mean of the 2x2 fine cells under it; the coarse-skin slab is sized (1:nv, 1:inner_n*outer_n) with the linear index c = (oc-1)*inner_n+ic (the SAME convention accumulate_seam_fluxes_fv packs the coarse face with), and only this block's quadrant is written — cells outside stay untouched, so the four fine blocks of a 2:1 face fill disjoint quadrants that together cover the whole coarse face exactly once.

Conservative averaging (the 0.25 factor) is the correct face-FLUX restriction: the coarse-face flux per unit area equals the mean of the fine-face fluxes per unit area covering it (Berger-Colella 1989 §4; Olivares 2019 Eq. 26-27), so F_coarse - F_fine_sum telescopes to round-off for a consistent scheme.

Attributes: pure

fortran
subroutine restrict_fine_face_to_quadrant(fine_face, inner_n, outer_n, ioff, joff, slab)

Arguments

NameTypeIntentAttributesDescription
fine_facereal(kind=R8P)inFine face flux (nv, inner_n, outer_n).
inner_ninteger(kind=I4P)inCoarse-face tangential cell counts.
outer_ninteger(kind=I4P)inCoarse-face tangential cell counts.
ioffinteger(kind=I4P)inQuadrant offset along (inner, outer) ∈ {0,1}.
joffinteger(kind=I4P)inQuadrant offset along (inner, outer) ∈ {0,1}.
slabreal(kind=R8P)inoutCoarse-skin slab (nv, inner_n*outer_n); quadrant written.

Call graph