Skip to content

adam_forest_object

ADAM, forest class definition — orchestrator of a forest of realms.

The forest tends an array of realms (realm_object extensions, e.g. prism_cpu_object, prism_fnl_object). It is a behavior-only class: it owns no derived-type state. Each realm lives in the program driver as a concrete monomorphic array (type(prism_cpu_object) :: realm(N)); the forest receives the array as class(realm_object), intent(inout) :: realm(:) and orchestrates inter-realm operations:

  • sequence per-realm initialize / finalize calls
  • reduce per-realm dt to a global dt (min reduction)
  • iterate per-realm timestep advance
  • iterate per-realm post-step diagnostics / IO
  • reduce per-realm termination predicate to a global done (AND reduction)

The forest NEVER reaches inside a realm's private state; it only invokes the realm-side TBPs that carry the _forest suffix. Together these TBPs form the orchestrator contract (see adam_realm_object).

Class-with-TBPs (not module-of-routines) so future forest-level configuration (MPI sub-communicator topology, inter-realm connectivity descriptor) can be added as intrinsic-typed state without a module API break.

See docs/guide/forest.md for the conceptual overview of the multi-realm machinery (manifest schema, α/β cadence trade-offs, phase cycle) and src/lib/common/README.md → "Forest orchestration" for the library-developer contract surface.

Source: src/lib/common/adam_forest_object.F90

Dependencies

Contents

Derived Types

forest_object

Behavior-only orchestrator of an array of realms.

Components

NameTypeAttributesDescription
ninteger(kind=I4P)Number of realms in the forest (set by initialize from size(realm)).
flux_registertype(flux_register_object)Coarse-fine interface reflux machinery.

Type-Bound Procedures

NameAttributesDescription
initializepass(self)Sequence each realm's initialize_forest at startup (single shared INI).
initialize_from_manifestpass(self)Like initialize, but each realm reads its own INI from a forest manifest.
finalizepass(self)Sequence each realm's finalize_forest at shutdown.
compute_global_dtpass(self)Min-reduce each realm's compute_local_dt_forest across the forest.
evolve_one_steppass(self)Iterate realm(😃%advance_one_step_forest(dt) for one global timestep.
is_donepass(self)AND-reduce each realm's is_done_forest across the forest.
post_steppass(self)Iterate realm(😃%post_step_forest for the per-step diagnostics/IO block.
simulatepass(self)Main entry point (single shared INI): drive the full simulation.
simulate_from_manifestpass(self)Main entry point (per-realm INIs via forest manifest).
populate_inter_realm_topologypass(self)Translate manifest face-pairs into maps of neighbors.
apply_reflux_correctionspass(self)Apply Berger-Colella reflux to coarse-side.

Subroutines

initialize

Initialize the forest and every realm it tends.

fortran
subroutine initialize(self, realm, filename)

Arguments

NameTypeIntentAttributesDescription
selfclass(forest_object)inoutThe forest.
realmclass(realm_object)inoutThe realms to initialize.
filenamecharacter(len=*)inInput parameters file name (shared across realms for v1).

Call graph

initialize_from_manifest

Initialize the forest and every realm using per-realm INIs from a manifest.

Like initialize but each realm receives its OWN INI path (manifest%realm_ini(is)) instead of a shared filename. After all realms are initialized, translates the manifest's face-pair list into per-realm maps%inter_realm_neighbors entries.

The driver MUST allocate realm(size = manifest%realms_number) with the concrete app type before calling this — the forest does not !< allocate the realm array (it cannot, since realm_object is abstract and each app has its own extension).

fortran
subroutine initialize_from_manifest(self, realm, manifest)

Arguments

NameTypeIntentAttributesDescription
selfclass(forest_object)inoutThe forest.
realmclass(realm_object)inoutThe realms to initialize.
manifesttype(forest_manifest_t)inParsed manifest (per-realm INI paths + topology).

Call graph

finalize

Shut down the forest and every realm it tends.

Iterates realm(is)%finalize_forest in increasing index order; each realm closes its IO files, releases its resources, and finalizes its MPI handler.

fortran
subroutine finalize(self, realm)

Arguments

NameTypeIntentAttributesDescription
selfclass(forest_object)inThe forest.
realmclass(realm_object)inoutThe realms to shut down.

Call graph

compute_global_dt

Compute the forest-global stability-limited dt.

Each realm reports its local dt via compute_local_dt_forest; the forest takes the min across all realms (intra-process) and then across all MPI ranks (MPI_ALLREDUCE on MPI_COMM_WORLD). Returns the bit-identical global min every rank should advance by.

fortran
subroutine compute_global_dt(self, realm, dt)

Arguments

NameTypeIntentAttributesDescription
selfclass(forest_object)inThe forest.
realmclass(realm_object)inoutThe realms to query.
dtreal(kind=R8P)outGlobal stability-limited dt.

Call graph

evolve_one_step

Advance every realm by one global timestep — α end-of-step barrier semantics.

N=1 fast path: realm owns the whole step (advance_one_step_forest). No seam machinery is exercised (there are no peer seams to refresh).

N>1 multi-realm path: per-seam coupling cadence (α / β).

The forest drives an integrator-agnostic K-stage clock with K_realm(is) = realm(is)%stages_per_step_forest() and K_max = max(K_realm). Asymmetric per-realm K is a first-class operating mode under α; the per-stage TBPs (begin/end_stage_forest) are gated behind k <= K_realm(is) so a realm with K < K_max no-ops the trailing stages.

Each inter-realm seam carries a coupling_cadence from the manifest (cached on the realm as seam_local_cadence(p)):

α (CADENCE_END_OF_STEP, default; AMReX-aligned coarse-fine convention) Mid-step peer ghosts are INTENTIONALLY STALE-BY-ONE-STEP. During stages 1..K, each realm reads the peer ghosts established by the previous end-of-step exchange (or by the initial-condition seam fill at populate_inter_realm_topology time, for the first step). This is structurally identical to AMReX's FillCoarsePatch reading coarse t^n data during fine sub-steps (Berger-Oliger 1984; AMReX_Amr.cpp::timeStep). Phase 5 (below) is the α seam coherence boundary: once per step, after close_step_forest, every realm's stage_active == 0 and the receiver reads peer's committed q. α admits asymmetric per-realm K (the K-equality guard is removed). Cost: first-order seam coupling in time.

β (CADENCE_STAGE_COINCIDENT, opt-in) Peer ghosts are refreshed once per RK substage, inside the K loop (Phase 2 below), before end_stage_forest reads them. Admissible only when both endpoint realms agree on (scheme_time, rk_scheme, nv, K) — enforced at forest init by check_beta_admissibility. Recovers bit-equivalence to a monolithic single-realm run on the union grid when admissible. Seam coupling order matches the per-realm interior order. Spatial operator MAY differ per realm.

Per-seam selection: the same forest may carry α seams and β seams simultaneously. Phase 2 iterates seams and fires only for β; Phase 5 iterates seams and fires only for α. A seam is filled exactly once per step under either cadence (Phase 2 may fire K times for β, but at successive substages, never duplicating the same substage).

Reflux cadence (α.r1):

The flux register's third axis is collapsed to 1. PRISM realms gate apply_reflux_to_stage_forest and accumulate_seam_fluxes_fv on the realm's own final RK substage (stage == rk%nrk). Earlier substages no-op. The mid-step apply_reflux_corrections call below therefore fires for every k, but only the k == K_realm(is) invocation does real work on realm is. Independent of α/β: β does not restore Wang 2018 per-stage RK-weighted reflux (deferred).

Phase outline:

Phase 0 — open_step_forest (per-realm prologue) For k = 1..K_max: Phase 1 — begin_stage_forest (per-realm, gated by k <= K_realm(is)) Phase 2 — fill_seam_from_peer_forest (per-seam, β-gated) Phase 3 — end_stage_forest (per-realm, gated by k <= K_realm(is)) + reduce_fine_sums + apply_reflux_corrections (reflux body is α.r1 end-of-step gated inside the realm) Phase 4 — close_step_forest (per-realm epilogue) Phase 5 — fill_seam_from_peer_forest (per-seam, α-gated)

LOAD-BEARING INVARIANT (β): Phase 2 must complete on ALL realms before Phase 3 starts on ANY realm. The orchestrator's serial inner loops within a rank give this for free under the Phase-A replicated-forest layout. If a future refactor interleaves Phase 2 and Phase 3, the read-after-overwrite race returns.

Future per-realm-dt subcycling (Berger-Colella "case 4") and γ (dense-output peer reads) are deferred. β with asymmetric K would require γ-class interpolation; not in scope.

fortran
subroutine evolve_one_step(self, realm)

Arguments

NameTypeIntentAttributesDescription
selfclass(forest_object)inoutThe forest.
realmclass(realm_object)inouttargetThe realms to advance.

Call graph

is_done

Decide whether the whole forest has finished evolving.

Each realm reports its local predicate via is_done_forest; the forest AND-reduces across all realms (intra-process) and then across all MPI ranks (MPI_ALLREDUCE on MPI_COMM_WORLD). AND-reduction means the forest keeps evolving as long as ANY realm wants to — matching the legacy single-realm semantics for v1 (with one realm the global predicate equals that realm's local one).

fortran
subroutine is_done(self, realm, done)

Arguments

NameTypeIntentAttributesDescription
selfclass(forest_object)inThe forest.
realmclass(realm_object)inoutThe realms to query.
donelogicaloutForest-global termination predicate.

Call graph

post_step

Run every realm's post-step diagnostics / IO / AMR block.

fortran
subroutine post_step(self, realm)

Arguments

NameTypeIntentAttributesDescription
selfclass(forest_object)inThe forest.
realmclass(realm_object)inoutThe realms to query.

Call graph

simulate

Drive the full simulation: initialize, time-loop, finalize.

Top-level entry point the program driver calls. The time loop is: initialize → loop {evolve_one_step → post_step → is_done} → finalize. Each step invokes the orchestrator-contract TBPs on every realm; the per-realm body decides what app-specific work to do internally.

fortran
subroutine simulate(self, realm, filename)

Arguments

NameTypeIntentAttributesDescription
selfclass(forest_object)inoutThe forest.
realmclass(realm_object)inoutThe realms to evolve.
filenamecharacter(len=*)inInput parameters file name.

Call graph

simulate_from_manifest

Drive the full simulation using per-realm INIs from a manifest.

Like simulate but uses initialize_from_manifest to populate each realm from its own INI file, and (via that initialize) wires the inter-realm topology from the manifest. The time-loop body is identical to simulate.

fortran
subroutine simulate_from_manifest(self, realm, manifest)

Arguments

NameTypeIntentAttributesDescription
selfclass(forest_object)inoutThe forest.
realmclass(realm_object)inoutThe realms to evolve.
manifesttype(forest_manifest_t)inParsed manifest.

Call graph

check_beta_admissibility

Enforce β admissibility contract on every stage_coincident seam.

For each manifest face-pair with coupling_cadence == CADENCE_STAGE_COINCIDENT, query both endpoint realms' coupling_descriptor_forest and verify they agree on (scheme_time, rk_scheme, nv). On any mismatch error_stop with a precise diagnostic naming the offending pair and the specific descriptor field that disagrees. Per-realm K participation (stages_per_step_forest()) is verified too: under β both endpoints must report the same K (asymmetric-K is α's domain, not β's).

Runs once at initialize_from_manifest time, after every realm's initialize_forest has populated numerics, rk, physics.

fortran
subroutine check_beta_admissibility(realm, manifest)

Arguments

NameTypeIntentAttributesDescription
realmclass(realm_object)inoutThe initialized realms.
manifesttype(forest_manifest_t)inParsed manifest.

Call graph

populate_inter_realm_topology

Translate manifest face-pairs into per-realm maps%inter_realm_neighbors.

Each manifest face-pair becomes TWO entries — one in realm_a's adam%maps%inter_realm_neighbors (looking outward toward realm_b) and one in realm_b's (looking back). Both entries carry the SAME coupling kind. Block indices are NOT resolved at this stage; the manifest declares realm-level coupling (which realm-face touches which realm-face), and the realm-side override of exchange_inter_realm_halos_forest is responsible for enumerating per-block face cells at exchange time. This keeps the manifest small and avoids encoding block layouts that depend on AMR / decomposition state not known at INI parse time.

fortran
subroutine populate_inter_realm_topology(self, realm, manifest)

Arguments

NameTypeIntentAttributesDescription
selfclass(forest_object)inoutThe forest.
realmclass(realm_object)inoutInitialized realms whose adam%maps gets populated.
manifesttype(forest_manifest_t)inParsed manifest.

Call graph

apply_reflux_corrections

Dispatch the Berger-Colella reflux correction to every realm.

The forest's role here is purely an orchestrator: it iterates the realm array and invokes each realm's apply_reflux_to_stage_forest TBP. The realm-side body filters flux_register%face(:) by face%coarse_realm == self%realm_index (read from the realm's own component, set by the forest at initialize-time) and writes the per-cell correction into its OWN integrator-private stage buffer (for RK realms: self%rk%q_rk(:, ..., stage), weighted by self%rk%ark(stage)).

The forest never reaches realm%rk directly: the integrator-specific weight pickup, the buffer name, and the per-cell write all live realm-side. This is the integrator-agnostic split — see realm_object%apply_reflux_to_stage_forest for the contract, and prism_cpu_object for the RK-specific override.

Empty register fast path: when the register has no faces (single- realm forest, no seams declared) the per-realm calls each short- circuit on flux_register%nfaces == 0 and the dispatch is a true no-op for the N=1 path.

fortran
subroutine apply_reflux_corrections(self, realm, stage, dt)

Arguments

NameTypeIntentAttributesDescription
selfclass(forest_object)inThe forest (holds the flux register).
realmclass(realm_object)inoutRealms; each realm's reflux TBP fires once.
stageinteger(kind=I4P)inIntegrator stage 1..K_total.
dtreal(kind=R8P)inTime step.

Call graph