dyn_grid.F90 Source File


This file depends on

sourcefile~~dyn_grid.f90~~EfferentGraph sourcefile~dyn_grid.f90 dyn_grid.F90 sourcefile~dyn_comp.f90 dyn_comp.F90 sourcefile~dyn_grid.f90->sourcefile~dyn_comp.f90 sourcefile~dyn_mpas_subdriver.f90 dyn_mpas_subdriver.F90 sourcefile~dyn_grid.f90->sourcefile~dyn_mpas_subdriver.f90 sourcefile~dyn_comp.f90->sourcefile~dyn_mpas_subdriver.f90

Source Code

! Copyright (C) 2025 University Corporation for Atmospheric Research (UCAR)
! SPDX-License-Identifier: Apache-2.0

!> This module, part of the MPAS interface, integrates MPAS dynamical core with CAM-SIMA by
!> implementing the necessary APIs and managing their interaction.
!>
!> It reads and uses the information from MPAS mesh to initialize various model grids
!> (e.g., dynamics, physics) for CAM-SIMA in terms of dynamics decomposition.
module dyn_grid
    ! Module(s) from CAM-SIMA.
    use cam_grid_support, only: max_hcoordname_len

    implicit none

    private
    ! Provide APIs required by CAM-SIMA.
    public :: model_grid_init

    public :: dyn_grid_id
    public :: dyn_grid_name

    ! Grid names that are to be registered with CAM-SIMA by calling `cam_grid_register`.
    ! Grid ids can be determined by calling `dyn_grid_id`.
    character(*), parameter :: dyn_grid_name(*) = [ character(max_hcoordname_len) :: &
        'mpas_cell',  &
        'cam_cell',   &
        'mpas_edge',  &
        'mpas_vertex' &
    ]
contains
    !> Initialize various model grids (e.g., dynamics, physics) in terms of dynamics decomposition.
    !> Additionally, MPAS framework initialization and reading time-invariant (e.g., grid/mesh) variables
    !> are also being completed in this subroutine.
    !> (KCW, 2024-03-29)
    !
    ! Called by `cam_init` in `src/control/cam_comp.F90`.
    subroutine model_grid_init()
        ! Module(s) from CAM-SIMA.
        use cam_abortutils, only: endrun
        use cam_constituents, only: num_advected
        use cam_initfiles, only: initial_file_get_id
        use cam_logfile, only: debugout_debug, debugout_info, debugout_verbose
        use dyn_comp, only: dyn_debug_print, dyn_inquire_mesh_dimensions, mpas_dynamical_core, nvertlevels
        use dynconst, only: dynconst_init
        use string_utils, only: stringify
        use vert_coord, only: pver, vert_coord_init
        ! Module(s) from external libraries.
        use pio, only: file_desc_t

        character(*), parameter :: subname = 'dyn_grid::model_grid_init'
        type(file_desc_t), pointer :: pio_file

        call dyn_debug_print(debugout_debug, subname // ' entered')

        nullify(pio_file)

        ! Initialize mathematical and physical constants for dynamics.
        call dynconst_init()

        call dyn_debug_print(debugout_info, 'Initializing vertical coordinate indexes')

        ! Initialize vertical coordinate indexes.
        ! `pver` comes from CAM-SIMA namelist.
        ! `pverp` is set only after this call. Call it as soon as possible.
        call vert_coord_init(1, pver)

        ! Get PIO file descriptor for initial file.
        pio_file => initial_file_get_id()

        call dyn_debug_print(debugout_info, 'Initializing MPAS dynamical core (Phase 3/4)')

        ! Finish MPAS framework initialization, including
        ! the allocation of all blocks and fields managed by MPAS.
        call mpas_dynamical_core % init_phase3(num_advected, pio_file)

        call dyn_debug_print(debugout_info, 'Initializing MPAS mesh variables')

        ! Read time-invariant (e.g., grid/mesh) variables.
        call mpas_dynamical_core % read_write_stream(pio_file, 'r', 'invariant')

        nullify(pio_file)

        ! Compute local east, north and edge-normal unit vectors whenever time-invariant (e.g., grid/mesh) variables are read.
        call mpas_dynamical_core % compute_unit_vector()

        ! Inquire local and global mesh dimensions.
        call dyn_inquire_mesh_dimensions()

        ! Check for consistency in numbers of vertical layers.
        if (nvertlevels /= pver) then
            call dyn_debug_print(debugout_verbose, 'Number of vertical layers in CAM-SIMA namelist, pver = ' // &
                stringify([pver]))
            call dyn_debug_print(debugout_verbose, 'Number of vertical layers in initial file, nvertlevels = ' // &
                stringify([nvertlevels]))

            call endrun('Numbers of vertical layers mismatch', subname, __LINE__)
        end if

        call dyn_debug_print(debugout_info, 'Initializing reference pressure')

        ! Initialize reference pressure for use by physics.
        call init_reference_pressure()

        call dyn_debug_print(debugout_info, 'Initializing physics grid')

        ! Initialize physics grid.
        call init_physics_grid()

        call dyn_debug_print(debugout_info, 'Registering dynamics grid with CAM-SIMA')

        ! Register dynamics grid with CAM-SIMA.
        call define_cam_grid()

        call dyn_debug_print(debugout_debug, subname // ' completed')
    end subroutine model_grid_init

    !> Initialize reference pressure for use by physics.
    !> (KCW, 2024-03-25)
    subroutine init_reference_pressure()
        ! Module(s) from CAM-SIMA.
        use cam_abortutils, only: check_allocate
        use cam_history_support, only: add_vert_coord
        use cam_logfile, only: debugout_debug, debugout_verbose
        use dyn_comp, only: dyn_debug_print, mpas_dynamical_core
        use dynconst, only: constant_p0 => pref
        use ref_pres, only: ref_pres_init
        use std_atm_profile, only: std_atm_pres
        use string_utils, only: stringify
        use vert_coord, only: pver, pverp
        ! Module(s) from CESM Share.
        use shr_kind_mod, only: kind_r8 => shr_kind_r8
        ! Module(s) from MPAS.
        use dyn_mpas_subdriver, only: kind_dyn_mpas => mpas_dynamical_core_real_kind

        character(*), parameter :: subname = 'dyn_grid::init_reference_pressure'
        ! Number of pure pressure levels at model top.
        integer, parameter :: num_pure_p_lev = 0
        integer :: ierr
        integer :: k, l
        ! Reference pressure (Pa) at layer interfaces and midpoints.
        real(kind_r8), allocatable :: p_ref_int(:)
        real(kind_r8), allocatable :: p_ref_mid(:)
        ! In MPAS, the vertical coordinate is denoted as lowercase "zeta", a Greek alphabet.
        ! `zu` denotes zeta at u-wind levels (i.e., at layer midpoints).
        ! `zw` denotes zeta at w-wind levels (i.e., at layer interfaces).
        ! `dzw` denotes the delta/difference between `zw`.
        ! `rdzw` denotes the reciprocal of `dzw`.
        real(kind_r8), allocatable :: dzw(:)
        real(kind_dyn_mpas), pointer :: rdzw(:)
        real(kind_r8), pointer :: zu(:) ! CANNOT be safely deallocated because `add_vert_coord`
                                        ! just uses pointers to point at it internally.
        real(kind_r8), pointer :: zw(:) ! CANNOT be safely deallocated because `add_vert_coord`
                                        ! just uses pointers to point at it internally.

        call dyn_debug_print(debugout_debug, subname // ' entered')

        nullify(rdzw)
        nullify(zu)
        nullify(zw)

        ! Compute reference height.
        call mpas_dynamical_core % get_variable_pointer(rdzw, 'mesh', 'rdzw')

        allocate(dzw(pver), stat=ierr)
        call check_allocate(ierr, subname, 'dzw(pver)', 'dyn_grid', __LINE__)

        dzw(:) = 1.0_kind_r8 / real(rdzw(:), kind_r8)

        nullify(rdzw)

        allocate(zw(pverp), stat=ierr)
        call check_allocate(ierr, subname, 'zw(pverp)', 'dyn_grid', __LINE__)
        allocate(zu(pver), stat=ierr)
        call check_allocate(ierr, subname, 'zu(pver)', 'dyn_grid', __LINE__)

        ! In MPAS, zeta coordinates are stored in increasing order (i.e., bottom to top of atmosphere).
        ! In CAM-SIMA, however, index order is reversed (i.e., top to bottom of atmosphere).
        ! Compute in reverse below.
        zw(pverp) = 0.0_kind_r8

        do k = pver, 1, -1
            zw(k) = zw(k + 1) + dzw(pver - k + 1)
            zu(k) = 0.5_kind_r8 * (zw(k + 1) + zw(k))
        end do

        deallocate(dzw)

        ! Register zeta coordinates with history.
        call add_vert_coord('ilev', pverp, 'Height (zeta) level at layer interfaces', 'm', zw, &
            positive='up')
        call add_vert_coord('lev', pver, 'Height (zeta) level at layer midpoints', 'm', zu, &
            positive='up')

        ! Compute reference pressure from reference height.
        allocate(p_ref_int(pverp), stat=ierr)
        call check_allocate(ierr, subname, 'p_ref_int(pverp)', 'dyn_grid', __LINE__)

        call std_atm_pres(zw, p_ref_int, user_specified_ps=constant_p0)

        allocate(p_ref_mid(pver), stat=ierr)
        call check_allocate(ierr, subname, 'p_ref_mid(pver)', 'dyn_grid', __LINE__)

        p_ref_mid(:) = 0.5_kind_r8 * (p_ref_int(1:pver) + p_ref_int(2:pverp))

        ! Print a nice table of reference height and pressure.
        call dyn_debug_print(debugout_verbose, 'Reference layer information:')
        call dyn_debug_print(debugout_verbose, '----- | -------------- | --------------')
        call dyn_debug_print(debugout_verbose, 'Index |     Height (m) | Pressure (hPa)')

        do k = 1, pver
            call dyn_debug_print(debugout_verbose, repeat('-', 5) // ' |  ' // stringify([zw(k)]) // &
                ' |  ' // stringify([p_ref_int(k) / 100.0_kind_r8]))

            l = len(stringify([k]))

            call dyn_debug_print(debugout_verbose, repeat(' ', 5 - l) // stringify([k]) // ' |  ' // stringify([zu(k)]) // &
                ' |  ' // stringify([p_ref_mid(k) / 100.0_kind_r8]))
        end do

        k = pverp

        call dyn_debug_print(debugout_verbose, repeat('-', 5) // ' |  ' // stringify([zw(k)]) // &
            ' |  ' // stringify([p_ref_int(k) / 100.0_kind_r8]))

        call ref_pres_init(p_ref_int, p_ref_mid, num_pure_p_lev)

        deallocate(p_ref_int)
        deallocate(p_ref_mid)

        nullify(zu)
        nullify(zw)

        call dyn_debug_print(debugout_debug, subname // ' completed')
    end subroutine init_reference_pressure

    !> Initialize physics grid in terms of dynamics decomposition.
    !> Provide grid and mapping information between global and local indexes to physics by calling `phys_grid_init`.
    !> (KCW, 2024-03-27)
    subroutine init_physics_grid()
        ! Module(s) from CAM-SIMA.
        use cam_abortutils, only: check_allocate
        use cam_logfile, only: debugout_debug
        use dyn_comp, only: dyn_debug_print, mpas_dynamical_core, ncells_global, ncells_solve, sphere_radius
        use dynconst, only: constant_pi => pi, rad_to_deg
        use physics_column_type, only: kind_pcol, physics_column_t
        use physics_grid, only: phys_grid_init
        use spmd_utils, only: iam
        use string_utils, only: stringify
        ! Module(s) from CESM Share.
        use shr_kind_mod, only: kind_r8 => shr_kind_r8
        ! Module(s) from MPAS.
        use dyn_mpas_subdriver, only: kind_dyn_mpas => mpas_dynamical_core_real_kind

        character(*), parameter :: subname = 'dyn_grid::init_physics_grid'
        character(max_hcoordname_len), allocatable :: dyn_attribute_name(:)
        integer :: hdim1_d, hdim2_d ! First and second horizontal dimensions of physics grid.
        integer :: i
        integer :: ierr
        integer, pointer :: indextocellid(:)        ! Global indexes of cell centers.
        real(kind_dyn_mpas), pointer :: areacell(:) ! Cell areas (m2).
        real(kind_dyn_mpas), pointer :: latcell(:)  ! Cell center latitudes (rad).
        real(kind_dyn_mpas), pointer :: loncell(:)  ! Cell center longitudes (rad).
        type(physics_column_t), allocatable :: dyn_column(:) ! Grid and mapping information between global and local indexes.

        call dyn_debug_print(debugout_debug, subname // ' entered')

        nullify(areacell)
        nullify(indextocellid)
        nullify(latcell)
        nullify(loncell)

        hdim1_d = ncells_global

        ! Setting `hdim2_d` to `1` indicates unstructured grid.
        hdim2_d = 1

        call mpas_dynamical_core % get_variable_pointer(areacell, 'mesh', 'areaCell')
        call mpas_dynamical_core % get_variable_pointer(indextocellid, 'mesh', 'indexToCellID')
        call mpas_dynamical_core % get_variable_pointer(latcell, 'mesh', 'latCell')
        call mpas_dynamical_core % get_variable_pointer(loncell, 'mesh', 'lonCell')

        allocate(dyn_column(ncells_solve), stat=ierr)
        call check_allocate(ierr, subname, 'dyn_column(ncells_solve)', 'dyn_grid', __LINE__)

        do i = 1, ncells_solve
            ! Column information.
            dyn_column(i) % lat_rad = real(latcell(i), kind_pcol)
            dyn_column(i) % lon_rad = real(loncell(i), kind_pcol)
            dyn_column(i) % lat_deg = real(real(latcell(i), kind_r8) * rad_to_deg, kind_pcol)
            dyn_column(i) % lon_deg = real(real(loncell(i), kind_r8) * rad_to_deg, kind_pcol)
            ! Cell areas normalized to unit sphere.
            dyn_column(i) % area    = real(areacell(i) / (sphere_radius ** 2), kind_pcol)
            ! Cell weights normalized to unity.
            dyn_column(i) % weight  = &
                real(real(areacell(i), kind_r8) / (4.0_kind_r8 * constant_pi * real(sphere_radius, kind_r8) ** 2), kind_pcol)

            ! File decomposition.
            ! For unstructured grid, `coord_indices` is not used by `phys_grid_init`.
            dyn_column(i) % coord_indices(:) = -1
            ! In this case, `global_col_num` is used instead.
            dyn_column(i) % global_col_num   = indextocellid(i)

            ! Dynamics decomposition.
            dyn_column(i) % dyn_task         = iam
            dyn_column(i) % global_dyn_block = indextocellid(i)
            dyn_column(i) % local_dyn_block  = i
            ! `dyn_block_index` is not used due to no dynamics block offset, but it still needs to be allocated.
            allocate(dyn_column(i) % dyn_block_index(0), stat=ierr)
            call check_allocate(ierr, subname, 'dyn_column(' // stringify([i]) // ') % dyn_block_index(0)', &
                'dyn_grid', __LINE__)
        end do

        nullify(areacell)
        nullify(indextocellid)
        nullify(latcell)
        nullify(loncell)

        ! `phys_grid_init` expects to receive the `area` attribute from dynamics.
        ! However, do not let it because dynamics grid is different from physics grid.
        allocate(dyn_attribute_name(0), stat=ierr)
        call check_allocate(ierr, subname, 'dyn_attribute_name(0)', 'dyn_grid', __LINE__)

        call phys_grid_init(hdim1_d, hdim2_d, 'mpas', dyn_column, 'mpas_cell', dyn_attribute_name)

        deallocate(dyn_column)
        deallocate(dyn_attribute_name)

        call dyn_debug_print(debugout_debug, subname // ' completed')
    end subroutine init_physics_grid

    !> This subroutine defines and registers four variants of dynamics grids in terms of dynamics decomposition.
    !> Their names are listed in `dyn_grid_name` and their corresponding ids can be determined by calling `dyn_grid_id`.
    !> * "mpas_cell": Grid that is centered at MPAS "cell" points.
    !> * "cam_cell": Grid that is also centered at MPAS "cell" points but uses standard CAM-SIMA coordinate and dimension names.
    !> * "mpas_edge": Grid that is centered at MPAS "edge" points.
    !> * "mpas_vertex": Grid that is centered at MPAS "vertex" points.
    !> (KCW, 2024-03-28)
    subroutine define_cam_grid()
        ! Module(s) from CAM-SIMA.
        use cam_abortutils, only: check_allocate
        use cam_grid_support, only: cam_grid_attribute_register, cam_grid_register, &
                                    horiz_coord_create, horiz_coord_t
        use cam_logfile, only: debugout_debug, debugout_verbose
        use cam_map_utils, only: kind_imap => imap
        use dyn_comp, only: dyn_debug_print, mpas_dynamical_core, &
                            ncells_global, nedges_global, nvertices_global, &
                            ncells_solve, nedges_solve, nvertices_solve, &
                            sphere_radius
        use dynconst, only: constant_pi => pi, rad_to_deg
        use string_utils, only: stringify
        ! Module(s) from CESM Share.
        use shr_kind_mod, only: kind_r8 => shr_kind_r8
        ! Module(s) from MPAS.
        use dyn_mpas_subdriver, only: kind_dyn_mpas => mpas_dynamical_core_real_kind

        character(*), parameter :: subname = 'dyn_grid::define_cam_grid'
        integer :: i
        integer :: ierr
        integer, pointer :: indextocellid(:)   ! Global indexes of cell centers.
        integer, pointer :: indextoedgeid(:)   ! Global indexes of edge nodes.
        integer, pointer :: indextovertexid(:) ! Global indexes of vertex nodes.
        real(kind_dyn_mpas), pointer :: areacell(:)  ! Cell areas (m2).
        real(kind_dyn_mpas), pointer :: latcell(:)   ! Cell center latitudes (rad).
        real(kind_dyn_mpas), pointer :: latedge(:)   ! Edge node latitudes (rad).
        real(kind_dyn_mpas), pointer :: latvertex(:) ! Vertex node latitudes (rad).
        real(kind_dyn_mpas), pointer :: loncell(:)   ! Cell center longitudes (rad).
        real(kind_dyn_mpas), pointer :: lonedge(:)   ! Edge node longitudes (rad).
        real(kind_dyn_mpas), pointer :: lonvertex(:) ! Vertex node longitudes (rad).

        ! Global grid indexes. CAN be safely deallocated because its values are copied internally by
        ! `cam_grid_attribute_register` and `horiz_coord_create`.
        ! `kind_imap` is an integer kind of `PIO_OFFSET_KIND`.
        integer(kind_imap), pointer :: global_grid_index(:)
        ! Global grid maps. CANNOT be safely deallocated because `cam_grid_register`
        ! just uses pointers to point at it internally.
        ! `kind_imap` is an integer kind of `PIO_OFFSET_KIND`.
        integer(kind_imap), pointer :: global_grid_map(:, :)
        ! Cell areas (m2). CANNOT be safely deallocated because `cam_grid_attribute_register`
        ! just uses pointers to point at it internally.
        real(kind_r8), pointer :: cell_area(:)
        ! Cell weights normalized to unity. CANNOT be safely deallocated because `cam_grid_attribute_register`
        ! just uses pointers to point at it internally.
        real(kind_r8), pointer :: cell_weight(:)
        ! Latitude coordinates. CANNOT be safely deallocated because `cam_grid_register`
        ! just uses pointers to point at it internally.
        type(horiz_coord_t), pointer :: lat_coord
        ! Longitude coordinates. CANNOT be safely deallocated because `cam_grid_register`
        ! just uses pointers to point at it internally.
        type(horiz_coord_t), pointer :: lon_coord

        call dyn_debug_print(debugout_debug, subname // ' entered')

        nullify(indextocellid, indextoedgeid, indextovertexid)
        nullify(areacell)
        nullify(latcell, loncell)
        nullify(latedge, lonedge)
        nullify(latvertex, lonvertex)

        nullify(global_grid_index, global_grid_map)
        nullify(cell_area, cell_weight)
        nullify(lat_coord, lon_coord)

        ! Construct coordinate and grid objects for cell center grid (i.e., "mpas_cell").
        ! Standard MPAS coordinate and dimension names are used.

        call mpas_dynamical_core % get_variable_pointer(areacell, 'mesh', 'areaCell')
        call mpas_dynamical_core % get_variable_pointer(indextocellid, 'mesh', 'indexToCellID')
        call mpas_dynamical_core % get_variable_pointer(latcell, 'mesh', 'latCell')
        call mpas_dynamical_core % get_variable_pointer(loncell, 'mesh', 'lonCell')

        allocate(global_grid_index(ncells_solve), stat=ierr)
        call check_allocate(ierr, subname, 'global_grid_index(ncells_solve)', 'dyn_grid', __LINE__)

        global_grid_index(:) = int(indextocellid(1:ncells_solve), kind_imap)

        lat_coord => horiz_coord_create('latCell', 'nCells', ncells_global, 'latitude', 'degrees_north', &
            1, ncells_solve, real(latcell, kind_r8) * rad_to_deg, map=global_grid_index)
        lon_coord => horiz_coord_create('lonCell', 'nCells', ncells_global, 'longitude', 'degrees_east', &
            1, ncells_solve, real(loncell, kind_r8) * rad_to_deg, map=global_grid_index)

        allocate(cell_area(ncells_solve), stat=ierr)
        call check_allocate(ierr, subname, 'cell_area(ncells_solve)', 'dyn_grid', __LINE__)
        allocate(cell_weight(ncells_solve), stat=ierr)
        call check_allocate(ierr, subname, 'cell_weight(ncells_solve)', 'dyn_grid', __LINE__)
        allocate(global_grid_map(3, ncells_solve), stat=ierr)
        call check_allocate(ierr, subname, 'global_grid_map(3, ncells_solve)', 'dyn_grid', __LINE__)

        do i = 1, ncells_solve
            cell_area(i)   = real(areacell(i), kind_r8)
            cell_weight(i) = real(areacell(i), kind_r8) / (4.0_kind_r8 * constant_pi * real(sphere_radius, kind_r8) ** 2)

            global_grid_map(1, i) = int(i, kind_imap)
            global_grid_map(2, i) = int(1, kind_imap)
            global_grid_map(3, i) = global_grid_index(i)
        end do

        call dyn_debug_print(debugout_verbose, 'Registering dynamics grid "mpas_cell" with id ' // &
            stringify([dyn_grid_id('mpas_cell')]))

        call cam_grid_register('mpas_cell', dyn_grid_id('mpas_cell'), lat_coord, lon_coord, global_grid_map, &
            unstruct=.true., block_indexed=.false.)
        call cam_grid_attribute_register('mpas_cell', 'cell_area', 'MPAS cell area', 'nCells', cell_area, &
            map=global_grid_index)
        call cam_grid_attribute_register('mpas_cell', 'cell_weight', 'MPAS cell weight', 'nCells', cell_weight, &
            map=global_grid_index)

        nullify(areacell)
        nullify(cell_area, cell_weight)
        nullify(lat_coord, lon_coord)

        ! Construct coordinate and grid objects for cell center grid (i.e., "cam_cell").
        ! Standard CAM-SIMA coordinate and dimension names are used.

        lat_coord => horiz_coord_create('lat', 'ncol', ncells_global, 'latitude', 'degrees_north', &
            1, ncells_solve, real(latcell, kind_r8) * rad_to_deg, map=global_grid_index)
        lon_coord => horiz_coord_create('lon', 'ncol', ncells_global, 'longitude', 'degrees_east', &
            1, ncells_solve, real(loncell, kind_r8) * rad_to_deg, map=global_grid_index)

        call dyn_debug_print(debugout_verbose, 'Registering dynamics grid "cam_cell" with id ' // &
            stringify([dyn_grid_id('cam_cell')]))

        call cam_grid_register('cam_cell', dyn_grid_id('cam_cell'), lat_coord, lon_coord, global_grid_map, &
            unstruct=.true., block_indexed=.false.)

        nullify(indextocellid)
        nullify(latcell, loncell)

        deallocate(global_grid_index)
        nullify(global_grid_index, global_grid_map)
        nullify(lat_coord, lon_coord)

        ! Construct coordinate and grid objects for edge node grid (i.e., "mpas_edge").
        ! Standard MPAS coordinate and dimension names are used.

        call mpas_dynamical_core % get_variable_pointer(indextoedgeid, 'mesh', 'indexToEdgeID')
        call mpas_dynamical_core % get_variable_pointer(latedge, 'mesh', 'latEdge')
        call mpas_dynamical_core % get_variable_pointer(lonedge, 'mesh', 'lonEdge')

        allocate(global_grid_index(nedges_solve), stat=ierr)
        call check_allocate(ierr, subname, 'global_grid_index(nedges_solve)', 'dyn_grid', __LINE__)

        global_grid_index(:) = int(indextoedgeid(1:nedges_solve), kind_imap)

        lat_coord => horiz_coord_create('latEdge', 'nEdges', nedges_global, 'latitude', 'degrees_north', &
            1, nedges_solve, real(latedge, kind_r8) * rad_to_deg, map=global_grid_index)
        lon_coord => horiz_coord_create('lonEdge', 'nEdges', nedges_global, 'longitude', 'degrees_east', &
            1, nedges_solve, real(lonedge, kind_r8) * rad_to_deg, map=global_grid_index)

        allocate(global_grid_map(3, nedges_solve), stat=ierr)
        call check_allocate(ierr, subname, 'global_grid_map(3, nedges_solve)', 'dyn_grid', __LINE__)

        do i = 1, nedges_solve
            global_grid_map(1, i) = int(i, kind_imap)
            global_grid_map(2, i) = int(1, kind_imap)
            global_grid_map(3, i) = global_grid_index(i)
        end do

        call dyn_debug_print(debugout_verbose, 'Registering dynamics grid "mpas_edge" with id ' // &
            stringify([dyn_grid_id('mpas_edge')]))

        call cam_grid_register('mpas_edge', dyn_grid_id('mpas_edge'), lat_coord, lon_coord, global_grid_map, &
            unstruct=.true., block_indexed=.false.)

        nullify(indextoedgeid)
        nullify(latedge, lonedge)

        deallocate(global_grid_index)
        nullify(global_grid_index, global_grid_map)
        nullify(lat_coord, lon_coord)

        ! Construct coordinate and grid objects for vertex node grid (i.e., "mpas_vertex").
        ! Standard MPAS coordinate and dimension names are used.

        call mpas_dynamical_core % get_variable_pointer(indextovertexid, 'mesh', 'indexToVertexID')
        call mpas_dynamical_core % get_variable_pointer(latvertex, 'mesh', 'latVertex')
        call mpas_dynamical_core % get_variable_pointer(lonvertex, 'mesh', 'lonVertex')

        allocate(global_grid_index(nvertices_solve), stat=ierr)
        call check_allocate(ierr, subname, 'global_grid_index(nvertices_solve)', 'dyn_grid', __LINE__)

        global_grid_index(:) = int(indextovertexid(1:nvertices_solve), kind_imap)

        lat_coord => horiz_coord_create('latVertex', 'nVertices', nvertices_global, 'latitude', 'degrees_north', &
            1, nvertices_solve, real(latvertex, kind_r8) * rad_to_deg, map=global_grid_index)
        lon_coord => horiz_coord_create('lonVertex', 'nVertices', nvertices_global, 'longitude', 'degrees_east', &
            1, nvertices_solve, real(lonvertex, kind_r8) * rad_to_deg, map=global_grid_index)

        allocate(global_grid_map(3, nvertices_solve), stat=ierr)
        call check_allocate(ierr, subname, 'global_grid_map(3, nvertices_solve)', 'dyn_grid', __LINE__)

        do i = 1, nvertices_solve
           global_grid_map(1, i) = int(i, kind_imap)
           global_grid_map(2, i) = int(1, kind_imap)
           global_grid_map(3, i) = global_grid_index(i)
        end do

        call dyn_debug_print(debugout_verbose, 'Registering dynamics grid "mpas_vertex" with id ' // &
            stringify([dyn_grid_id('mpas_vertex')]))

        call cam_grid_register('mpas_vertex', dyn_grid_id('mpas_vertex'), lat_coord, lon_coord, global_grid_map, &
            unstruct=.true., block_indexed=.false.)

        nullify(indextovertexid)
        nullify(latvertex, lonvertex)

        deallocate(global_grid_index)
        nullify(global_grid_index, global_grid_map)
        nullify(lat_coord, lon_coord)

        call dyn_debug_print(debugout_debug, subname // ' completed')
    end subroutine define_cam_grid

    !> Helper function for returning grid id given its name.
    !> (KCW, 2024-03-27)
    pure function dyn_grid_id(name)
        ! Module(s) from CAM-SIMA.
        use physics_grid, only: phys_decomp

        character(*), intent(in) :: name
        integer :: dyn_grid_id

        integer :: i

        do i = 1, size(dyn_grid_name)
            if (trim(adjustl(dyn_grid_name(i))) == trim(adjustl(name))) then
                ! Grid ids count from `phys_decomp` + 1.
                ! This avoids id collisions between dynamics and physics grids.
                dyn_grid_id = phys_decomp + i

                return
            end if
        end do

        dyn_grid_id = 0
    end function dyn_grid_id
end module dyn_grid