Skip to content

SURF GridComp: Refactoring plan for MAPL v3 preparation #1419

@tclune

Description

@tclune

Background

In preparation for migration from MAPL v2 to MAPL v3, the Surface GridComp
and its children need to be cleaned up to:

  • Minimize the footprint of v3-specific changes
  • Support concurrent maintenance of v2 and v3 branches during the lengthy
    validation period
  • Isolate and minimize use of MAPL_LocStream (which will be replaced by
    ESMF_Mesh / ESMF_XGrid in v3)

This issue documents the analysis and proposed refactoring plan for developer
review before work begins.


Component Inventory

The Surface GridComp lives under:

GEOSsurface_GridComp/
  GEOS_SurfaceGridComp.F90          (~10,925 lines — main orchestrator)
  Shared/
    SurfParams.F90
    StieglitzSnow.F90
    OASIMalbedoMod.f
  GEOSlake_GridComp/
    GEOS_LakeGridComp.F90
  GEOSland_GridComp/
    GEOS_LandGridComp.F90           (~1,762 lines)
    GEOScatch_GridComp/
      GEOS_CatchGridComp.F90
    GEOScatchCN_GridComp/
      GEOS_CatchCNGridComp.F90
      GEOScatchCNCLM40_GridComp/GEOS_CatchCNCLM40GridComp.F90
      GEOScatchCNCLM45_GridComp/GEOS_CatchCNCLM45GridComp.F90
    GEOSigni_GridComp/
      GEOS_IgniGridComp.F90
    GEOSroute_GridComp/
      GEOS_RouteGridComp.F90        (under active development — not in scope)
    GEOSvegdyn_GridComp/
      GEOS_VegdynGridComp.F90
  GEOSlandice_GridComp/
    GEOS_LandIceGridComp.F90
  GEOSsaltwater_GridComp/
    GEOS_SaltWaterGridComp.F90
    GEOS_OpenWaterGridComp.F90
    GEOS_SimpleSeaiceGridComp.F90
    GEOS_SeaiceInterfaceGridComp.F90
    GEOS_CICE4ColumnPhysGridComp.F90
    GEOS_ObioImportsGridComp.F90

Child GridComp registrations (via MAPL_AddChild):

  • GEOS_SurfaceGridComp → SALTWATER, LAKE, LANDICE, LAND
  • GEOS_LandGridComp → VEGDYN, CATCH (or CATCHCN array), IGNI
  • GEOS_SaltWaterGridComp → SEAICETHERMO (one of three options), OPENWATER, OBIOIMPORTS (conditional)

Key Findings from Analysis

1. Scale of LocStream usage

File LocStream usages
GEOS_SurfaceGridComp.F90 344 MAPL_LocStreamTransform calls + LocStreamCreate/LocStreamCreateXform in Initialize
GEOS_SeaiceInterfaceGridComp.F90 A2O/O2A MAPL_LocStreamXform objects held in ESMF private state
GEOS_CatchGridComp.F90 4 MAPL_LocStreamGet calls (tile geometry)
GEOS_CatchCNCLM40/45GridComp.F90 2 each
GEOS_LakeGridComp.F90, GEOS_LandGridComp.F90, GEOS_SaltWaterGridComp.F90 1–2 each
GEOS_RouteGridComp.F90 Several (out of scope)

No raw ESMF_LocStream, ESMF_Mesh, or ESMF_XGrid usage exists anywhere —
all spatial mapping goes through MAPL abstractions.

2. Spec declaration volume

File Import specs Export specs Internal specs
GEOS_SurfaceGridComp.F90 43 296 ~12
GEOS_LandGridComp.F90 424 (duplicated for LSM_CHOICE 1 vs 2/3)
Other children varies varies varies

Total across the tree: estimated 2,500+ hand-written spec calls.

3. Code structure issues

  • GEOS_SurfaceGridComp.F90 is ~10,925 lines containing SetServices specs,
    Initialize, Run1, Run2, DOTYPE (~1,385 lines), a ~3,000-line diagnostic
    transform block, and 6 helper subroutines — all in one file
  • The DOTYPE subroutine (per-child Run2 dispatch) and DOCDS (per-child Run1)
    are structurally parallel but written separately
  • The Run2 diagnostic block has ~150 near-identical
    if(associated(VAR)) call MAPL_LocStreamTransform(...) blocks with no
    data-driven loop
  • Fire danger output appears 3 times (instantaneous / daily / daily-with-suffix)
    as manually duplicated blocks
  • Run1 and Run2 share identical boilerplate preambles (ESMF_GridCompGet,
    MAPL_GetObjectFromGC, MAPL_Get)

4. Legacy patterns

Pattern Location
use MAPL_Mod, use MAPL_Constants GEOS_RouteGridComp.F90
use MAPL_BaseMod catch_incr.F90
use MAPL_ExceptionHandling dbg_cnlsm_offline.F90, SurfParams.F90
VERIFY_(STATUS) macro (~2,228 uses) GEOS_SurfaceGridComp.F90 (mixed with newer _RC)
Manual ESMF_UserCompSetInternalState for T_SURFACE_STATE GEOS_SurfaceGridComp.F90 — predates MAPL internal state management

5. AQUA_PLANET compile-time flag

AQUA_PLANET (#ifdef) appears only in GEOS_SurfaceGridComp.F90 (4 occurrences,
3 logical blocks). It is compile-time because NUM_CHILDREN is a Fortran
parameter used to size static arrays XFORM_IN(NUM_CHILDREN) and
XFORM_OUT(NUM_CHILDREN). Once those arrays are moved out of the main module
(see Phase 2 below), the constraint disappears and AQUA_PLANET can become a
runtime .rc key.


Proposed Refactoring Plan

The overarching constraints are:

  • SetServices, Initialize, Run*, and Finalize remain in the main
    GridComp module as thin wrappers; most MAPL API calls stay in the main body
  • MAPL_LocStreamTransform (and related LocStream APIs) are isolated behind a
    wrapper module so that the v3 swap touches only that module
  • The ROUTE GridComp is not touched (under active development)

Phase 1 — ACG Migration (Highest Priority, Start Here)

Convert all MAPL_AddImportSpec / MAPL_AddExportSpec / MAPL_AddInternalSpec
calls to ACG registry files using the v2 ACG generator (no 3g flag).
GWD GridComp (GWD_StateSpecs.rc) is the established reference example.

Create <Name>_StateSpecs.rc for each component. In each CMakeLists.txt:

mapl_acg(${this} <Name>_StateSpecs.rc
  IMPORT_SPECS EXPORT_SPECS INTERNAL_SPECS GET_POINTERS DECLARE_POINTERS)

In each .F90, replace the spec-declaration block in SetServices with:

! Any logical expressions needed for COND columns must be established here,
! before the #include lines (e.g., LSM_CHOICE, DO_CICE_THERMO, etc.)
#include "<Name>_Import___.h"
#include "<Name>_Export___.h"
#include "<Name>_Internal___.h"

Special cases:

  • Conditional specs (CICE-only, CatchCN-only, fire-danger, GOSWIM aerosol-in-snow)
    map to the COND column in the ACG schema. The required logical expressions
    must be computed before the #include lines.
  • GEOS_LandGridComp's CASE(1) vs CASE(2,3) duplication collapses — specs
    that differ by LSM choice carry an appropriate COND entry (e.g., LSM_CHOICE == 1)
  • Internal specs (Run1→Run2 bridge fields: CT, CQ, CM, CN, TS, QS, etc.) go in
    category: INTERNAL

Components to convert (each gets its own _StateSpecs.rc):

Component File
Surface GEOS_SurfaceGridComp.F90
Land GEOS_LandGridComp.F90
Lake GEOS_LakeGridComp.F90
LandIce GEOS_LandIceGridComp.F90
SaltWater GEOS_SaltWaterGridComp.F90
OpenWater GEOS_OpenWaterGridComp.F90
SimpleSeaice GEOS_SimpleSeaiceGridComp.F90
CICE4ColumnPhys GEOS_CICE4ColumnPhysGridComp.F90
SeaiceInterface GEOS_SeaiceInterfaceGridComp.F90
ObioImports GEOS_ObioImportsGridComp.F90
Catch GEOS_CatchGridComp.F90
CatchCN GEOS_CatchCNGridComp.F90
CatchCNCLM40 GEOS_CatchCNCLM40GridComp.F90
CatchCNCLM45 GEOS_CatchCNCLM45GridComp.F90
Vegdyn GEOS_VegdynGridComp.F90
Igni GEOS_IgniGridComp.F90

Estimated reduction: ~2,500+ lines of boilerplate removed across the tree.


Phase 2 — LocStream Isolation

Create GEOSsurface_GridComp/Shared/GEOS_SurfaceTransform.F90 — a new utility
module. After this phase, MAPL_LocStreamTransform and related MAPL LocStream
APIs never appear directly in any GridComp .F90. The v3 migration then becomes
a change confined to this one module.

module GEOS_SurfaceTransformMod
  use MAPL
  implicit none
  private

  ! Opaque transform state.
  ! v2 implementation: wraps MAPL_LocStreamXFORM
  ! v3 implementation: will wrap ESMF_XGrid weights
  type, public :: SurfaceXFORM
    private
    type(MAPL_LocStreamXFORM) :: xform_in
    type(MAPL_LocStreamXFORM) :: xform_out
  end type

  public :: SurfaceTransformInit   ! Initialize per-child transforms (Initialize phase)
  public :: ScatterToTiles         ! Atm grid → tile space (1D fields)
  public :: GatherFromTiles        ! Tile space → atm grid (1D fields)
  public :: ScatterUngridded       ! Atm grid → tile space (with ungridded dim)
  public :: GatherUngridded        ! Tile space → atm grid (with ungridded dim)
  public :: GetTileFractions       ! FROCEAN, FRLAND, FRLAKE, FRLANDICE
  public :: GetTileGeometry        ! NT_GLOBAL, TILEGRID for child GridComps
  public :: ScatterAtmToOcean      ! A2O transform (for SeaiceInterface)
  public :: GatherOceanToAtm       ! O2A transform (for SeaiceInterface)
end module

The helper routines currently at the bottom of GEOS_SurfaceGridComp.F90
(MKTILE_1D, MKTILE_UNGRIDDED, FILLIN_TILE*, FILLOUT_TILE*) are
transform helpers and move here as well.

Changes in GEOS_SurfaceGridComp.F90:

  • T_SURFACE_STATE loses fixed-size XFORM_IN(NUM_CHILDREN) /
    XFORM_OUT(NUM_CHILDREN); gains type(SurfaceXFORM), allocatable :: XFORM(:)
    (now allocatable — removes the parameter constraint on NUM_CHILDREN)
  • All 344 MAPL_LocStreamTransform calls → ScatterToTiles / GatherFromTiles
  • The Initialize LocStream setup block → SurfaceTransformInit
  • Fractional area block → GetTileFractions

Changes in GEOS_SeaiceInterfaceGridComp.F90:

  • MAPL_LocStreamXform pointers in ESMF private state → type(SurfaceXFORM)
  • A2O/O2A transform calls → ScatterAtmToOcean / GatherOceanToAtm

Changes in child GridComps (Lake, Catch, CatchCNCLM40/45, etc.):

  • MAPL_Get(..., LocStream=) + MAPL_LocStreamGet(..., TILEGRID=)
    GetTileGeometry(GC, NT_GLOBAL, TILEGRID, RC=STATUS)

v3 migration path: Only GEOS_SurfaceTransform.F90 changes. Each wrapper's
body swaps from MAPL_LocStreamTransform to the appropriate ESMF_XGrid /
ESMF_Mesh regrid call.


Phase 3 — Structural Decomposition of GEOS_SurfaceGridComp.F90

SetServices, Initialize, Run*, and Finalize remain in the main module
as thin wrappers. Large computational blocks move to new files in Shared/.

New File Extracted Content Approx Lines
GEOS_SurfaceChildRun.F90 SurfaceChildRun1 (ex-DOCDS, ~280 ln) + SurfaceChildRun2 (ex-DOTYPE, ~1,385 ln) ~1,700
GEOS_SurfaceDiagnostics.F90 WriteSurfaceDiagnostics — the ~3,000-line tile→grid transform block in Run2 ~3,000
GEOS_SurfaceLayerDiag.F90 ComputeSurfaceLayerDiag — MO/Louis surface layer quantities (U10M, T2M, Q2M, USTAR, …) ~300

After Phases 1 + 3, GEOS_SurfaceGridComp.F90 shrinks from ~10,925 lines to
roughly 2,000–3,000 lines of clean orchestration code.

Example of what Run2 becomes:

subroutine Run2(GC, IM, EX, CLOCK, RC)
  call GetSurfaceRunState(GC, MAPL, LOCSTREAM, GIM, GEX, XFORM, RC=STATUS)
  ! scatter key atmospheric inputs to tile space
  call SurfaceChildRun2(NUM_CHILDREN, GCS, GIM, GEX, LOCSTREAM, XFORM, RC=STATUS)
  call WriteSurfaceDiagnostics(LOCSTREAM, GIM, GEX, RC=STATUS)
  call ComputeSurfaceLayerDiag(CT, CQ, SPEED, TA, TS, QS, ..., RC=STATUS)
end subroutine

Phase 4 — Modernize Error Handling and Module Imports

Do this after Phase 1, before Phases 2 and 3 to avoid conflicts with
structural changes. All steps are mechanical / scriptable.

4a. Replace all VERIFY_(STATUS) with _VERIFY(STATUS) throughout all
Surface GridComp files (~2,228 instances in GEOS_SurfaceGridComp.F90 alone).

4b. Consolidate legacy sub-module USE statements:

File Replace With
GEOS_RouteGridComp.F90 use MAPL_Mod + use MAPL_Constants use MAPL
catch_incr.F90 use MAPL_BaseMod use MAPL
dbg_cnlsm_offline.F90 use MAPL_ExceptionHandling use MAPL
SurfParams.F90 use MAPL_ExceptionHandling use MAPL

4c. After Phase 2: Remove the ESMF_UserCompSetInternalState /
ESMF_UserCompGetInternalState pattern for T_SURFACE_STATE. The XFORM arrays
will have moved to GEOS_SurfaceTransform.F90. The remaining members of
T_SURFACE_STATE (e.g., RoutingType) continue as an ESMF private state
derived type; a module variable is an acceptable intermediate step.


Phase 5 — Eliminate Remaining Code Duplication

5a. Collapse the fire-danger diagnostic triple-block into a loop over suffixes:

character(len=8), parameter :: FDI_SUFFIX(3) = [' ','_DAILY ','_DAILY_']
character(len=5), parameter :: FDI_FIELD(7)  = ['FFMC ','DMC  ','DC   ','ISI  ','BUI  ','FWI  ','DSR  ']
! single nested loop replaces ~60 lines of copy-paste blocks

5b. Audit aerosol deposition arrays for consistent loop patterns.

5c. Extract the shared Run1/Run2 preamble (ESMF_GridCompGet /
MAPL_GetObjectFromGC / MAPL_Get sequence, duplicated verbatim) into a
GetSurfaceRunState(GC, ...) utility subroutine.


Phase 6 — AQUA_PLANET Runtime Conversion (After Phase 2)

AQUA_PLANET is compile-time only because NUM_CHILDREN must be a Fortran
parameter to size XFORM_IN(NUM_CHILDREN) / XFORM_OUT(NUM_CHILDREN).
Phase 2 makes those arrays allocatable, removing the constraint.

  1. Add to GEOS_SurfaceGridComp.rc:
    AQUA_PLANET: 0    # 0 = full surface model; 1 = ocean-only aqua planet
    
  2. NUM_CHILDREN, CHILD_MASK, and XFORM(:) become runtime-allocated
  3. Replace the 3 #ifdef / #ifndef AQUA_PLANET blocks with
    if (AQUA_PLANET == 1) runtime conditionals
  4. Remove any -DAQUA_PLANET compile flag from the build system

Recommended Execution Sequence

Phase 1 (ACG — parallelizable per component)
    ↓
Phase 4a (VERIFY_ → _VERIFY, scripted)
Phase 4b (use MAPL sub-module cleanup)
    ↓
Phase 2 (LocStream isolation → GEOS_SurfaceTransform.F90)
    ↓
Phase 4c (T_SURFACE_STATE cleanup)    ← can run concurrently
Phase 6  (AQUA_PLANET → runtime)      ← can run concurrently
    ↓
Phase 3  (structural decomposition)
    ↓
Phase 5  (duplication cleanup)

Phases within the same row have no dependency on each other and can proceed
concurrently on separate branches/PRs.


Open Questions for Developers

  1. GEOS_ObioImportsGridComp — Is this component a candidate for
    removal or restructuring, or should it be migrated as-is?
  2. T_SURFACE_STATE successor — After Phase 2, the remaining members
    of this derived type are just RoutingType. Should this persist as a
    named ESMF private state, or be absorbed differently?
  3. ACG COND column for GEOS_LandGridComp — The CASE(1) vs CASE(2,3)
    split currently determines which export specs are registered. What is the
    correct variable name / expression available at spec-declaration time that
    should appear in the COND column?

Metadata

Metadata

Assignees

Labels

mapl3-migrationMigrate a GridComp to MAPL3 APIs (late phase)

Type

No fields configured for Smell.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions