Appearance
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
- forest_object
- initialize
- initialize_from_manifest
- finalize
- compute_global_dt
- evolve_one_step
- is_done
- post_step
- simulate
- simulate_from_manifest
- check_beta_admissibility
- populate_inter_realm_topology
- apply_reflux_corrections
Derived Types
forest_object
Behavior-only orchestrator of an array of realms.
Components
| Name | Type | Attributes | Description |
|---|---|---|---|
n | integer(kind=I4P) | Number of realms in the forest (set by initialize from size(realm)). | |
flux_register | type(flux_register_object) | Coarse-fine interface reflux machinery. |
Type-Bound Procedures
| Name | Attributes | Description |
|---|---|---|
initialize | pass(self) | Sequence each realm's initialize_forest at startup (single shared INI). |
initialize_from_manifest | pass(self) | Like initialize, but each realm reads its own INI from a forest manifest. |
finalize | pass(self) | Sequence each realm's finalize_forest at shutdown. |
compute_global_dt | pass(self) | Min-reduce each realm's compute_local_dt_forest across the forest. |
evolve_one_step | pass(self) | Iterate realm(😃%advance_one_step_forest(dt) for one global timestep. |
is_done | pass(self) | AND-reduce each realm's is_done_forest across the forest. |
post_step | pass(self) | Iterate realm(😃%post_step_forest for the per-step diagnostics/IO block. |
simulate | pass(self) | Main entry point (single shared INI): drive the full simulation. |
simulate_from_manifest | pass(self) | Main entry point (per-realm INIs via forest manifest). |
populate_inter_realm_topology | pass(self) | Translate manifest face-pairs into maps of neighbors. |
apply_reflux_corrections | pass(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
| Name | Type | Intent | Attributes | Description |
|---|---|---|---|---|
self | class(forest_object) | inout | The forest. | |
realm | class(realm_object) | inout | The realms to initialize. | |
filename | character(len=*) | in | Input 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
| Name | Type | Intent | Attributes | Description |
|---|---|---|---|---|
self | class(forest_object) | inout | The forest. | |
realm | class(realm_object) | inout | The realms to initialize. | |
manifest | type(forest_manifest_t) | in | Parsed 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
| Name | Type | Intent | Attributes | Description |
|---|---|---|---|---|
self | class(forest_object) | in | The forest. | |
realm | class(realm_object) | inout | The 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
| Name | Type | Intent | Attributes | Description |
|---|---|---|---|---|
self | class(forest_object) | in | The forest. | |
realm | class(realm_object) | inout | The realms to query. | |
dt | real(kind=R8P) | out | Global 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
| Name | Type | Intent | Attributes | Description |
|---|---|---|---|---|
self | class(forest_object) | inout | The forest. | |
realm | class(realm_object) | inout | target | The 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
| Name | Type | Intent | Attributes | Description |
|---|---|---|---|---|
self | class(forest_object) | in | The forest. | |
realm | class(realm_object) | inout | The realms to query. | |
done | logical | out | Forest-global termination predicate. |
Call graph
post_step
Run every realm's post-step diagnostics / IO / AMR block.
fortran
subroutine post_step(self, realm)Arguments
| Name | Type | Intent | Attributes | Description |
|---|---|---|---|---|
self | class(forest_object) | in | The forest. | |
realm | class(realm_object) | inout | The 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
| Name | Type | Intent | Attributes | Description |
|---|---|---|---|---|
self | class(forest_object) | inout | The forest. | |
realm | class(realm_object) | inout | The realms to evolve. | |
filename | character(len=*) | in | Input 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
| Name | Type | Intent | Attributes | Description |
|---|---|---|---|---|
self | class(forest_object) | inout | The forest. | |
realm | class(realm_object) | inout | The realms to evolve. | |
manifest | type(forest_manifest_t) | in | Parsed 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
| Name | Type | Intent | Attributes | Description |
|---|---|---|---|---|
realm | class(realm_object) | inout | The initialized realms. | |
manifest | type(forest_manifest_t) | in | Parsed 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
| Name | Type | Intent | Attributes | Description |
|---|---|---|---|---|
self | class(forest_object) | inout | The forest. | |
realm | class(realm_object) | inout | Initialized realms whose adam%maps gets populated. | |
manifest | type(forest_manifest_t) | in | Parsed 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
| Name | Type | Intent | Attributes | Description |
|---|---|---|---|---|
self | class(forest_object) | in | The forest (holds the flux register). | |
realm | class(realm_object) | inout | Realms; each realm's reflux TBP fires once. | |
stage | integer(kind=I4P) | in | Integrator stage 1..K_total. | |
dt | real(kind=R8P) | in | Time step. |
Call graph