diff --git a/src/dynamics/mpas/driver/dyn_mpas_subdriver.F90 b/src/dynamics/mpas/driver/dyn_mpas_subdriver.F90
index 63ddc6f0..82228032 100644
--- a/src/dynamics/mpas/driver/dyn_mpas_subdriver.F90
+++ b/src/dynamics/mpas/driver/dyn_mpas_subdriver.F90
@@ -1,12 +1,17 @@
+! SPDX-FileCopyrightText: Copyright (C) 2024 U.S. National Science Foundation National Center for Atmospheric Research
+! SPDX-License-Identifier: Apache-2.0
+
+!> This module, the MPAS subdriver, manages the life cycle (i.e., initialization, running, and
+!> finalization) of MPAS as a dynamical core within CAM-SIMA as well as potentially other
+!> host models.
+!>
+!> It is a ground-up implementation that not only adheres to the Fortran 2018 standard, but also
+!> incorporates a modern object-oriented design. As such, the implementation details of MPAS are
+!> abstracted away from CAM-SIMA, which enables a more stable interface between the two.
+!>
+!> Users should begin by creating an "instance" of MPAS dynamical core from the `mpas_dynamical_core_type`
+!> derived type. Then, interaction with the instance is done through its public type-bound procedures.
module dyn_mpas_subdriver
- !-------------------------------------------------------------------------------
- ! module dyn_mpas_subdriver
- !
- ! This module manages the life cycle (i.e., initialization, running, and
- ! finalization) of MPAS as a dynamical core within CAM-SIMA.
- !
- !-------------------------------------------------------------------------------
-
use, intrinsic :: iso_fortran_env, only: output_unit
! Module(s) from external libraries.
#ifdef MPAS_USE_MPI_F08
@@ -26,7 +31,8 @@ module dyn_mpas_subdriver
public :: mpas_dynamical_core_type
abstract interface
- ! This interface is compatible with `endrun` from CAM-SIMA.
+ !> This procedure interface is modeled after the `endrun` subroutine from CAM-SIMA.
+ !> It will be called whenever MPAS dynamical core encounters a fatal error and cannot continue.
subroutine model_error_if(message, file, line)
character(*), intent(in) :: message
character(*), optional, intent(in) :: file
@@ -51,8 +57,9 @@ end subroutine model_error_if
integer, parameter :: mpas_dynamical_core_real_kind = rkind
!> The "class" of MPAS dynamical core.
- !> Important data structures like states of MPAS dynamical core are encapsulated inside this derived type to prevent misuse.
- !> Type-bound procedures provide well-defined APIs for CAM-SIMA to interact with MPAS dynamical core.
+ !> Important data structures like the internal states of MPAS dynamical core are encapsulated inside this derived type
+ !> to prevent misuse. Type-bound procedures provide stable and well-defined APIs for CAM-SIMA to interact with
+ !> MPAS dynamical core.
type :: mpas_dynamical_core_type
private
@@ -67,7 +74,7 @@ end subroutine model_error_if
integer :: mpi_rank = 0
logical :: mpi_rank_root = .false.
- ! Actual implementation is supplied at runtime.
+ ! Actual implementation is supplied at run-time.
procedure(model_error_if), nopass, pointer :: model_error => null()
type(core_type), pointer :: corelist => null()
@@ -104,7 +111,7 @@ end subroutine model_error_if
procedure, pass, public :: run => dyn_mpas_run
procedure, pass, public :: final => dyn_mpas_final
- ! Accessor subroutines for users to access internal states of MPAS dynamical core.
+ ! Accessor procedures for users to access the internal states of MPAS dynamical core.
procedure, pass, public :: get_constituent_name => dyn_mpas_get_constituent_name
procedure, pass, public :: get_constituent_index => dyn_mpas_get_constituent_index
@@ -161,9 +168,13 @@ end subroutine model_error_if
!> This derived type conveys information similar to the `var` and `var_array` elements in MPAS registry.
!> For example, in MPAS registry, the "xCell" variable is described as:
- !>
+ !> ```
+ !>
+ !> ```
!> Here, it is described as:
- !> var_info_type(name="xCell", type="real", rank=1)
+ !> ```
+ !> var_info_type(name="xCell", type="real", rank=1)
+ !> ```
!> However, note that MPAS treats the "Time" dimension specially. It is implemented as 1-d pointer arrays of
!> custom derived types. For a variable with the "Time" dimension, its rank needs to be subtracted by one.
type :: var_info_type
@@ -335,7 +346,7 @@ end subroutine dyn_mpas_debug_print
!> If `value` contains more than one element, the elements will be stringified, delimited by `separator`, then concatenated.
!> If `value` contains exactly one element, the element will be stringified without using `separator`.
!> If `value` contains zero element or is of unsupported data types, an empty character string is produced.
- !> If `separator` is not supplied, it defaults to `, ` (i.e., a comma and a space).
+ !> If `separator` is not supplied, it defaults to ", " (i.e., a comma and a space).
!> (KCW, 2024-02-04)
pure function stringify(value, separator)
use, intrinsic :: iso_fortran_env, only: int32, int64, real32, real64
@@ -435,13 +446,12 @@ end function stringify
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_init_phase1
!
- !> \brief Tracks `mpas_init` up to the point of reading namelist
+ !> \brief Track `mpas_init` up to the point of reading namelist.
!> \author Michael Duda
!> \date 19 April 2019
!> \details
!> This subroutine follows the stand-alone MPAS subdriver up to, but not
!> including, the point where namelist is read.
- !> \addenda
!> Ported and refactored for CAM-SIMA. (KCW, 2024-02-02)
!
!-------------------------------------------------------------------------------
@@ -530,9 +540,9 @@ subroutine dyn_mpas_init_phase1(self, mpi_comm, model_error_impl, log_level, log
! initialization steps.
!
! We need:
- ! 1) `domain_ptr` to be allocated;
- ! 2) `dmpar_init` to be completed for accessing `dminfo`;
- ! 3) `*_setup_core` to assign the `setup_log` procedure pointer.
+ ! 1. `domain_ptr` to be allocated;
+ ! 2. `dmpar_init` to be completed for accessing `dminfo`;
+ ! 3. `*_setup_core` to assign the `setup_log` procedure pointer.
ierr = self % domain_ptr % core % setup_log(self % domain_ptr % loginfo, self % domain_ptr, unitnumbers=mpas_log_unit)
if (ierr /= 0) then
@@ -540,20 +550,20 @@ subroutine dyn_mpas_init_phase1(self, mpi_comm, model_error_impl, log_level, log
subname, __LINE__)
end if
- ! At this point, we should be ready to read namelist in `dyn_comp::dyn_readnl`.
+ ! At this point, we should be ready to read namelist in `dyn_mpas_read_namelist`.
call self % debug_print(log_level_debug, subname // ' completed')
end subroutine dyn_mpas_init_phase1
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_read_namelist
!
- !> \brief Tracks `mpas_init` where namelist is being read
+ !> \brief Track `mpas_init` where namelist is being read.
!> \author Kuan-Chih Wang
!> \date 2024-02-09
!> \details
!> This subroutine calls upstream MPAS functionality for reading its own
!> namelist. After that, override designated namelist variables according to
- !> information provided from CAM-SIMA.
+ !> the information provided from CAM-SIMA.
!
!-------------------------------------------------------------------------------
subroutine dyn_mpas_read_namelist(self, namelist_path, &
@@ -591,13 +601,13 @@ subroutine dyn_mpas_read_namelist(self, namelist_path, &
subname, __LINE__)
end if
- ! Override designated namelist variables according to information provided from CAM-SIMA.
- ! These include runtime settings that cannot be determined beforehand.
+ ! Override designated namelist variables according to the information provided from CAM-SIMA.
+ ! These include run-time settings that cannot be determined beforehand.
call self % debug_print(log_level_info, 'Overriding designated namelist variables')
! CAM-SIMA seems to follow "NetCDF Climate and Forecast (CF) Metadata Conventions" for calendar names. See
- ! CF-1.11, section "4.4.1. Calendar".
+ ! CF-1.12, section "4.4.2. Calendar".
! However, this is not the case for MPAS. Translate calendar names between CF and MPAS.
select case (trim(adjustl(cf_calendar)))
case ('360_day')
@@ -605,7 +615,7 @@ subroutine dyn_mpas_read_namelist(self, namelist_path, &
case ('365_day', 'noleap')
mpas_calendar = 'gregorian_noleap'
case ('gregorian', 'standard')
- ! `gregorian` is a deprecated alternative name for `standard`.
+ ! "gregorian" is a deprecated alternative name for "standard".
mpas_calendar = 'gregorian'
case default
call self % model_error('Unsupported calendar type "' // trim(adjustl(cf_calendar)) // '"', &
@@ -618,9 +628,9 @@ subroutine dyn_mpas_read_namelist(self, namelist_path, &
call self % debug_print(log_level_debug, 'config_calendar_type = ' // stringify([config_pointer_c]))
nullify(config_pointer_c)
- ! MPAS represents date and time in ISO 8601 format. However, the separator between date and time is `_`
- ! instead of standard `T`.
- ! Format in `YYYY-MM-DD_hh:mm:ss` is acceptable.
+ ! MPAS represents date and time in ISO 8601 format. However, the separator between date and time is "_"
+ ! instead of standard "T".
+ ! Format in "YYYY-MM-DD_hh:mm:ss" is acceptable.
call self % get_variable_pointer(config_pointer_c, 'cfg', 'config_start_time')
config_pointer_c = stringify(start_date_time(1:3), '-') // '_' // stringify(start_date_time(4:6), ':')
@@ -633,7 +643,7 @@ subroutine dyn_mpas_read_namelist(self, namelist_path, &
call self % debug_print(log_level_debug, 'config_stop_time = ' // stringify([config_pointer_c]))
nullify(config_pointer_c)
- ! Format in `DD_hh:mm:ss` is acceptable.
+ ! Format in "DD_hh:mm:ss" is acceptable.
call self % get_variable_pointer(config_pointer_c, 'cfg', 'config_run_duration')
config_pointer_c = stringify([run_duration(1)]) // '_' // stringify(run_duration(2:4), ':')
@@ -654,20 +664,21 @@ subroutine dyn_mpas_read_namelist(self, namelist_path, &
call self % debug_print(log_level_debug, 'config_do_restart = ' // stringify([config_pointer_l]))
nullify(config_pointer_l)
+ ! At this point, we should be ready to follow up with the rest of MPAS framework initialization
+ ! in `dyn_mpas_init_phase2`.
call self % debug_print(log_level_debug, subname // ' completed')
end subroutine dyn_mpas_read_namelist
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_init_phase2
!
- !> \brief Tracks `mpas_init` after namelist has been read
+ !> \brief Track `mpas_init` after namelist has been read.
!> \author Michael Duda
!> \date 19 April 2019
!> \details
!> This subroutine follows the stand-alone MPAS subdriver from the point
!> where we call the second phase of MPAS framework initialization up
- !> to the check on the existence of the `streams.` file.
- !> \addenda
+ !> to the check on the existence of the "streams." file.
!> Ported and refactored for CAM-SIMA. (KCW, 2024-02-07)
!
!-------------------------------------------------------------------------------
@@ -704,7 +715,7 @@ subroutine dyn_mpas_init_phase2(self, pio_iosystem)
! Initialize MPAS framework with the supplied PIO system descriptor.
call mpas_framework_init_phase2(self % domain_ptr, io_system=pio_iosystem)
- ! Instantiate `streaminfo` but do not actually initialize it. Any queries made to it will always return `.false.`.
+ ! Instantiate `streaminfo`, but do not actually initialize it. Any queries made to it will always return `.false.`.
! This is the intended behavior because MPAS as a dynamical core is not responsible for managing IO.
self % domain_ptr % streaminfo => mpas_stream_inquiry_new_streaminfo()
@@ -752,25 +763,24 @@ subroutine dyn_mpas_init_phase2(self, pio_iosystem)
end if
! At this point, we should be ready to set up decompositions, build halos, allocate blocks, etc.
- ! in `dyn_grid::model_grid_init`.
+ ! in `dyn_mpas_init_phase3`.
call self % debug_print(log_level_debug, subname // ' completed')
end subroutine dyn_mpas_init_phase2
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_init_phase3
!
- !> \brief Tracks `mpas_init` up to the point of calling `atm_core_init`
+ !> \brief Track `mpas_init` up to the point of calling `atm_core_init`.
!> \author Michael Duda
!> \date 19 April 2019
!> \details
!> This subroutine follows the stand-alone MPAS subdriver after the check on
- !> the existence of the `streams.` file up to, but not including,
+ !> the existence of the "streams." file up to, but not including,
!> the point where `atm_core_init` is called. It completes MPAS framework
!> initialization, including the allocation of all blocks and fields managed
- !> by MPAS. However, scalars are allocated but not yet defined.
+ !> by MPAS. However, note that scalars are allocated, but not yet defined.
!> `dyn_mpas_define_scalar` must be called afterwards. Also note that MPAS uses
!> the term "scalar", but CAM-SIMA calls it "constituent".
- !> \addenda
!> Ported and refactored for CAM-SIMA. (KCW, 2024-03-06)
!
!-------------------------------------------------------------------------------
@@ -804,7 +814,7 @@ subroutine dyn_mpas_init_phase3(self, number_of_constituents, pio_file)
call self % debug_print(log_level_info, 'Number of constituents is ' // stringify([self % number_of_constituents]))
- ! Adding a config named `cam_pcnst` with the number of constituents will indicate to MPAS that
+ ! Adding a config named "cam_pcnst" with the number of constituents will indicate to MPAS that
! it is operating as a dynamical core, and therefore it needs to allocate scalars separately
! from other Registry-defined fields. The special logic is located in `atm_setup_block`.
! This must be done before calling `mpas_bootstrap_framework_phase1`.
@@ -834,8 +844,8 @@ subroutine dyn_mpas_init_phase3(self, number_of_constituents, pio_file)
! Finish setting up fields.
call mpas_bootstrap_framework_phase2(self % domain_ptr, pio_file_desc=pio_file)
- ! `num_scalars` is a dimension variable, but it only exists in MPAS `state` pool.
- ! Fix this inconsistency by also adding it to MPAS `dimension` pool.
+ ! "num_scalars" is a dimension variable, but it only exists in MPAS "state" pool.
+ ! Fix this inconsistency by also adding it to MPAS "dimension" pool.
call self % get_pool_pointer(mpas_pool, 'state')
call mpas_pool_get_dimension(mpas_pool, 'num_scalars', num_scalars)
@@ -854,13 +864,22 @@ subroutine dyn_mpas_init_phase3(self, number_of_constituents, pio_file)
nullify(mpas_pool)
nullify(num_scalars)
+ ! At this point, what follows next depends on the specific use case. In no particular order:
+ ! * Use `dyn_mpas_define_scalar` to define the names of constituents at run-time.
+ ! * Use `dyn_mpas_read_write_stream` to read mesh variables.
+ ! * Follow up with a call to `dyn_mpas_compute_unit_vector` immediately. This is by design.
+ ! * For setting analytic initial condition, use `get_variable_pointer` to inject data directly into MPAS memory.
+ ! * Use `dyn_mpas_compute_edge_wind` where appropriate.
+ ! * Use `dyn_mpas_exchange_halo` where appropriate.
+ ! * Use `dyn_mpas_read_write_stream` to read initial condition or restart.
+ ! * Finally, use `dyn_mpas_init_phase4` to conclude the initialization of MPAS dynamical core.
call self % debug_print(log_level_debug, subname // ' completed')
end subroutine dyn_mpas_init_phase3
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_define_scalar
!
- !> \brief Defines the names of constituents at run-time
+ !> \brief Define the names of constituents at run-time.
!> \author Michael Duda
!> \date 21 May 2020
!> \details
@@ -873,7 +892,6 @@ end subroutine dyn_mpas_init_phase3
!> this constrain. Index mapping between MPAS scalars and constituent names
!> can be looked up through `index_constituent_to_mpas_scalar` and
!> `index_mpas_scalar_to_constituent`.
- !> \addenda
!> Ported and refactored for CAM-SIMA. (KCW, 2024-05-19)
!
!-------------------------------------------------------------------------------
@@ -886,9 +904,9 @@ subroutine dyn_mpas_define_scalar(self, constituent_name, is_water_species)
character(*), intent(in) :: constituent_name(:)
logical, intent(in) :: is_water_species(:)
- ! Possible CCPP standard names of `qv`, which denotes water vapor mixing ratio.
- ! They are hard-coded here because MPAS needs to know where `qv` is.
- ! Index 1 is exactly what MPAS wants. Others also work, but need to be converted.
+ !> Possible CCPP standard names of `qv`, which denotes water vapor mixing ratio.
+ !> They are hard-coded here because MPAS needs to know where `qv` is.
+ !> Index 1 is exactly what MPAS wants. Others also work, but need to be converted.
character(*), parameter :: mpas_scalar_qv_standard_name(*) = [ character(strkind) :: &
'water_vapor_mixing_ratio_wrt_dry_air', &
'water_vapor_mixing_ratio_wrt_moist_air', &
@@ -981,7 +999,7 @@ subroutine dyn_mpas_define_scalar(self, constituent_name, is_water_species)
if (index_qv == 0) then
call self % model_error('Constituent names must contain one of: ' // &
- stringify(mpas_scalar_qv_standard_name) // ' and it must be a water species', subname, __LINE__)
+ stringify(mpas_scalar_qv_standard_name) // ', and it must be a water species', subname, __LINE__)
end if
end if
@@ -1059,7 +1077,7 @@ subroutine dyn_mpas_define_scalar(self, constituent_name, is_water_species)
call mpas_pool_add_dimension(mpas_pool, 'moist_start', index_water_start)
call mpas_pool_add_dimension(mpas_pool, 'moist_end', index_water_end)
- ! MPAS `state` pool has two time levels.
+ ! MPAS "state" pool has two time levels.
time_level = 2
do i = 1, time_level
@@ -1100,7 +1118,7 @@ subroutine dyn_mpas_define_scalar(self, constituent_name, is_water_species)
call mpas_pool_add_dimension(mpas_pool, 'moist_start', index_water_start)
call mpas_pool_add_dimension(mpas_pool, 'moist_end', index_water_end)
- ! MPAS `tend` pool only has one time level.
+ ! MPAS "tend" pool only has one time level.
time_level = 1
do i = 1, time_level
@@ -1131,7 +1149,7 @@ subroutine dyn_mpas_define_scalar(self, constituent_name, is_water_species)
nullify(mpas_pool)
- ! For consistency, also add dimension variables to MPAS `dimension` pool.
+ ! For consistency, also add dimension variables to MPAS "dimension" pool.
call mpas_pool_add_dimension(self % domain_ptr % blocklist % dimensions, 'index_qv', index_qv)
call mpas_pool_add_dimension(self % domain_ptr % blocklist % dimensions, 'moist_start', index_water_start)
@@ -1147,7 +1165,7 @@ end subroutine dyn_mpas_define_scalar
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_read_write_stream
!
- !> \brief Read or write an MPAS stream
+ !> \brief Read or write an MPAS stream.
!> \author Kuan-Chih Wang
!> \date 2024-03-15
!> \details
@@ -1259,7 +1277,7 @@ end subroutine dyn_mpas_read_write_stream
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_init_stream_with_pool
!
- !> \brief Initialize an MPAS stream with an accompanying MPAS pool
+ !> \brief Initialize an MPAS stream with an accompanying MPAS pool.
!> \author Kuan-Chih Wang
!> \date 2024-03-14
!> \details
@@ -1299,9 +1317,9 @@ subroutine dyn_mpas_init_stream_with_pool(self, mpas_pool, mpas_stream, pio_file
character(*), parameter :: subname = 'dyn_mpas_subdriver::dyn_mpas_init_stream_with_pool'
character(strkind) :: stream_filename
integer :: i, ierr, stream_format
- ! Whether a variable is present on the file (i.e., `pio_file`).
+ !> Whether a variable is present on the file (i.e., `pio_file`).
logical, allocatable :: var_is_present(:)
- ! Whether a variable is type, kind and rank compatible with what MPAS expects on the file (i.e., `pio_file`).
+ !> Whether a variable is type, kind, and rank compatible with what MPAS expects on the file (i.e., `pio_file`).
logical, allocatable :: var_is_tkr_compatible(:)
type(field0dchar), pointer :: field_0d_char
type(field1dchar), pointer :: field_1d_char
@@ -1637,11 +1655,11 @@ subroutine add_stream_attribute_0d(attribute_name, attribute_value)
trim(adjustl(attribute_name)), attribute_value, syncval=.false., ierr=ierr)
type is (logical)
if (attribute_value) then
- ! Logical `.true.` becomes character string `YES`.
+ ! Logical `.true.` becomes character string "YES".
call mpas_writestreamatt(mpas_stream, &
trim(adjustl(attribute_name)), 'YES', syncval=.false., ierr=ierr)
else
- ! Logical `.false.` becomes character string `NO`.
+ ! Logical `.false.` becomes character string "NO".
call mpas_writestreamatt(mpas_stream, &
trim(adjustl(attribute_name)), 'NO', syncval=.false., ierr=ierr)
end if
@@ -1691,10 +1709,12 @@ end subroutine add_stream_attribute_1d
end subroutine dyn_mpas_init_stream_with_pool
!> Parse a stream name, which consists of one or more stream name fragments, and return the corresponding variable information
- !> as a list of `var_info_type`. Multiple stream name fragments should be separated by `+` (i.e., a plus, meaning "addition"
- !> operation) or `-` (i.e., a minus, meaning "subtraction" operation).
- !> A stream name fragment can be a predefined stream name (e.g., "invariant", "input", "restart") or a single variable name.
- !> Duplicate variable names in the resulting list are discarded.
+ !> as a list of `var_info_type`. Multiple stream name fragments should be separated by "+" (i.e., a plus, meaning "addition"
+ !> operation) or "-" (i.e., a minus, meaning "subtraction" operation).
+ !> A stream name fragment can be a predefined stream name (e.g., "invariant", "input", etc.) or a single variable name.
+ !> For example, a stream name of "invariant+input+restart" means the union of variables in the "invariant", "input", and
+ !> "restart" streams.
+ !> Duplicate variable information in the resulting list is discarded.
!> (KCW, 2024-06-01)
pure function parse_stream_name(stream_name) result(var_info_list)
character(*), intent(in) :: stream_name
@@ -1782,7 +1802,7 @@ pure function parse_stream_name(stream_name) result(var_info_list)
end function parse_stream_name
!> Parse a stream name fragment and return the corresponding variable information as a list of `var_info_type`.
- !> A stream name fragment can be a predefined stream name (e.g., "invariant", "input", "restart") or a single variable name.
+ !> A stream name fragment can be a predefined stream name (e.g., "invariant", "input", etc.) or a single variable name.
!> (KCW, 2024-06-01)
pure function parse_stream_name_fragment(stream_name_fragment) result(var_info_list)
character(*), intent(in) :: stream_name_fragment
@@ -1837,6 +1857,7 @@ end function parse_stream_name_fragment
!> Return the index of unique elements in `array`, which can be any intrinsic data types, as an integer array.
!> If `array` contains zero element or is of unsupported data types, an empty integer array is produced.
+ !> For example, `index_unique([1, 2, 3, 1, 2, 3, 4, 5])` returns `[1, 2, 3, 7, 8]`.
!> (KCW, 2024-03-22)
pure function index_unique(array)
use, intrinsic :: iso_fortran_env, only: int32, int64, real32, real64
@@ -1916,13 +1937,13 @@ end function index_unique
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_check_variable_status
!
- !> \brief Check and return variable status on the given file
+ !> \brief Check and return variable status on the given file.
!> \author Kuan-Chih Wang
!> \date 2024-06-04
!> \details
!> On the given file (i.e., `pio_file`), this subroutine checks whether the
!> given variable (i.e., `var_info`) is present, and whether it is "TKR"
- !> compatible with what MPAS expects. "TKR" means type, kind and rank.
+ !> compatible with what MPAS expects. "TKR" means type, kind, and rank.
!> This subroutine can handle both ordinary variables and variable arrays.
!> They are indicated by the `var` and `var_array` elements, respectively,
!> in MPAS registry. For an ordinary variable, the checks are performed on
@@ -2348,13 +2369,12 @@ end subroutine dyn_mpas_check_variable_status
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_exchange_halo
!
- !> \brief Updates the halo layers of the named field
+ !> \brief Update the halo layers of the named field.
!> \author Michael Duda
!> \date 16 January 2020
!> \details
!> Given a field name that is defined in MPAS registry, this subroutine updates
!> the halo layers for that field.
- !> \addenda
!> Ported and refactored for CAM-SIMA. (KCW, 2024-03-18)
!
!-------------------------------------------------------------------------------
@@ -2531,21 +2551,21 @@ end subroutine dyn_mpas_exchange_halo
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_compute_unit_vector
!
- !> \brief Computes local east, north and edge-normal unit vectors
+ !> \brief Compute local east, north, and edge-normal unit vectors.
!> \author Michael Duda
!> \date 15 January 2020
!> \details
!> This subroutine computes the local east and north unit vectors at all cells,
- !> storing the results in MPAS `mesh` pool as `east` and `north`, respectively.
- !> It also computes the edge-normal unit vectors at all edges by calling
- !> `mpas_initialize_vectors`. Before calling this subroutine, MPAS `mesh` pool
- !> must contain `latCell` and `lonCell` that are valid for all cells (not just
- !> solve cells), plus any additional variables that are required by
+ !> storing the results in MPAS "mesh" pool as the "east" and "north" variables,
+ !> respectively. It also computes the edge-normal unit vectors at all edges by
+ !> calling `mpas_initialize_vectors`.
+ !> Before calling this subroutine, MPAS "mesh" pool must contain the "latCell"
+ !> and "lonCell" variables that are valid for all cells (not just solve cells),
+ !> plus any additional variables that are required by
!> `mpas_initialize_vectors`.
!> For stand-alone MPAS, the whole deal is handled by `init_dirs_forphys`
!> during physics initialization. However, MPAS as a dynamical core does
- !> not have physics, hence this subroutine.
- !> \addenda
+ !> not have physics, hence the existence of this subroutine.
!> Ported and refactored for CAM-SIMA. (KCW, 2024-04-23)
!
!-------------------------------------------------------------------------------
@@ -2613,20 +2633,20 @@ end subroutine dyn_mpas_compute_unit_vector
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_compute_edge_wind
!
- !> \brief Computes the edge-normal wind vectors at edge points
+ !> \brief Compute the edge-normal wind (tendency) vectors at edge points.
!> \author Michael Duda
!> \date 16 January 2020
!> \details
- !> This subroutine computes the edge-normal wind vectors at edge points
- !> (i.e., `u` in MPAS `state` pool) from the wind components at cell points
- !> (i.e., `uReconstruct{Zonal,Meridional}` in MPAS `diag` pool). In MPAS, the
- !> former are PROGNOSTIC variables, while the latter are DIAGNOSTIC variables
- !> that are "reconstructed" from the former. This subroutine is essentially the
- !> inverse function of that reconstruction. The purpose is to provide an
- !> alternative way for MPAS to initialize from zonal and meridional wind
- !> components at cell points. If `wind_tendency` is `.true.`, this subroutine
- !> operates on the wind tendency due to physics instead.
- !> \addenda
+ !> This subroutine computes the edge-normal wind vectors at edge points (i.e.,
+ !> the "u" variable in MPAS "state" pool) from the wind components at cell
+ !> points (i.e., the "uReconstruct{Zonal,Meridional}" variables in MPAS "diag"
+ !> pool). In MPAS, the former are PROGNOSTIC variables, while the latter are
+ !> DIAGNOSTIC variables that are "reconstructed" from the former.
+ !> This subroutine is essentially the inverse function of that reconstruction.
+ !> The purpose is to provide an alternative way for MPAS to initialize from
+ !> zonal and meridional wind components at cell points.
+ !> If `wind_tendency` is `.true.`, this subroutine operates on the wind
+ !> tendency due to physics instead.
!> Ported and refactored for CAM-SIMA. (KCW, 2024-05-08)
!
!-------------------------------------------------------------------------------
@@ -2734,14 +2754,13 @@ end subroutine dyn_mpas_compute_edge_wind
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_init_phase4
!
- !> \brief Tracks `atm_core_init` to finish MPAS dynamical core initialization
+ !> \brief Track `atm_core_init` to finish MPAS dynamical core initialization.
!> \author Michael Duda
!> \date 29 February 2020
!> \details
!> This subroutine completes MPAS dynamical core initialization.
!> Essentially, it closely follows what is done in `atm_core_init`, but without
!> any calls to MPAS diagnostics manager or MPAS stream manager.
- !> \addenda
!> Ported and refactored for CAM-SIMA. (KCW, 2024-05-25)
!
!-------------------------------------------------------------------------------
@@ -2857,7 +2876,7 @@ subroutine dyn_mpas_init_phase4(self, coupling_time_interval)
call self % model_error('Failed to build halo exchange groups', subname, __LINE__)
end if
- ! Variables in MPAS `state` pool have more than one time level. Copy the values from the first time level of
+ ! Variables in MPAS "state" pool have more than one time level. Copy the values from the first time level of
! such variables into all subsequent time levels to initialize them.
call self % get_variable_pointer(config_do_restart, 'cfg', 'config_do_restart')
@@ -3018,7 +3037,7 @@ end subroutine dyn_mpas_init_phase4
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_run
!
- !> \brief Integrates the dynamical states with time
+ !> \brief Integrate the dynamical states with time.
!> \author Michael Duda
!> \date 29 February 2020
!> \details
@@ -3027,7 +3046,6 @@ end subroutine dyn_mpas_init_phase4
!> the coupling time interval is reached.
!> Essentially, it closely follows what is done in `atm_core_run`, but without
!> any calls to MPAS diagnostics manager or MPAS stream manager.
- !> \addenda
!> Ported and refactored for CAM-SIMA. (KCW, 2024-06-21)
!
!-------------------------------------------------------------------------------
@@ -3094,7 +3112,7 @@ subroutine dyn_mpas_run(self)
! Current states are in time level 1. Upon exit, time level 2 will contain updated states.
call atm_do_timestep(self % domain_ptr, config_dt, self % number_of_time_steps)
- ! MPAS `state` pool has two time levels.
+ ! MPAS "state" pool has two time levels.
! Swap them after advancing a time step.
call mpas_pool_shift_time_levels(mpas_pool_state)
@@ -3121,7 +3139,7 @@ subroutine dyn_mpas_run(self)
call self % debug_print(log_level_info, 'Time integration of MPAS dynamical core ends at ' // trim(adjustl(date_time)))
- ! Compute diagnostic variables like `pressure`, `rho` and `theta` from time level 1 of MPAS `state` pool
+ ! Compute diagnostic variables like "pressure", "rho", and "theta" from time level 1 of MPAS "state" pool
! by calling upstream MPAS functionality.
call atm_compute_output_diagnostics(mpas_pool_state, 1, mpas_pool_diag, mpas_pool_mesh)
@@ -3134,7 +3152,7 @@ end subroutine dyn_mpas_run
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_final
!
- !> \brief Finalizes MPAS dynamical core as well as its framework
+ !> \brief Finalize MPAS dynamical core as well as its framework.
!> \author Michael Duda
!> \date 29 February 2020
!> \details
@@ -3144,7 +3162,6 @@ end subroutine dyn_mpas_run
!> Essentially, it closely follows what is done in `atm_core_finalize` and
!> `mpas_finalize`, except that here, there is no need to call MPAS diagnostics
!> manager or MPAS stream manager.
- !> \addenda
!> Ported and refactored for CAM-SIMA. (KCW, 2024-10-10)
!
!-------------------------------------------------------------------------------
@@ -3282,7 +3299,7 @@ end subroutine dyn_mpas_final
!-------------------------------------------------------------------------------
! function dyn_mpas_get_constituent_name
!
- !> \brief Query constituent name by its index
+ !> \brief Query constituent name by its index.
!> \author Kuan-Chih Wang
!> \date 2024-05-16
!> \details
@@ -3316,7 +3333,7 @@ end function dyn_mpas_get_constituent_name
!-------------------------------------------------------------------------------
! function dyn_mpas_get_constituent_index
!
- !> \brief Query constituent index by its name
+ !> \brief Query constituent index by its name.
!> \author Kuan-Chih Wang
!> \date 2024-05-16
!> \details
@@ -3352,7 +3369,7 @@ end function dyn_mpas_get_constituent_index
!-------------------------------------------------------------------------------
! function dyn_mpas_map_mpas_scalar_index
!
- !> \brief Map MPAS scalar index from constituent index
+ !> \brief Map MPAS scalar index from constituent index.
!> \author Kuan-Chih Wang
!> \date 2024-05-16
!> \details
@@ -3386,7 +3403,7 @@ end function dyn_mpas_map_mpas_scalar_index
!-------------------------------------------------------------------------------
! function dyn_mpas_map_constituent_index
!
- !> \brief Map constituent index from MPAS scalar index
+ !> \brief Map constituent index from MPAS scalar index.
!> \author Kuan-Chih Wang
!> \date 2024-05-16
!> \details
@@ -3420,12 +3437,12 @@ end function dyn_mpas_map_constituent_index
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_get_local_mesh_dimension
!
- !> \brief Returns local mesh dimensions
+ !> \brief Return local mesh dimensions.
!> \author Kuan-Chih Wang
!> \date 2024-05-09
!> \details
!> This subroutine returns local mesh dimensions, including:
- !> * Numbers of local mesh cells, edges, vertices and vertical levels
+ !> * Numbers of local mesh cells, edges, vertices, and vertical levels
!> on each individual task, both with/without halo points.
!
!-------------------------------------------------------------------------------
@@ -3488,16 +3505,15 @@ end subroutine dyn_mpas_get_local_mesh_dimension
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_get_global_mesh_dimension
!
- !> \brief Returns global mesh dimensions
+ !> \brief Return global mesh dimensions.
!> \author Michael Duda
!> \date 22 August 2019
!> \details
!> This subroutine returns global mesh dimensions, including:
- !> * Numbers of global mesh cells, edges, vertices and vertical levels
+ !> * Numbers of global mesh cells, edges, vertices, and vertical levels
!> across all tasks.
!> * Maximum numbers of mesh cells and edges/vertices among all tasks.
!> * Sphere radius.
- !> \addenda
!> Ported and refactored for CAM-SIMA. (KCW, 2024-03-25)
!
!-------------------------------------------------------------------------------
@@ -3593,7 +3609,7 @@ end subroutine dyn_mpas_get_pool_pointer
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_get_variable_pointer_*
!
- !> \brief A family of accessor subroutines for MPAS dynamical core instance
+ !> \brief A family of accessor subroutines for MPAS dynamical core instance.
!> \author Kuan-Chih Wang
!> \date 2024-03-21
!> \details
@@ -3602,7 +3618,7 @@ end subroutine dyn_mpas_get_pool_pointer
!> MPAS dynamical core instance. The `get_variable_pointer` generic interface
!> should be used instead of the specific ones.
!> WARNING:
- !> USE OF THIS SUBROUTINE FAMILY IS HIGHLY DISCOURAGED BECAUSE INTERNAL
+ !> USE OF THIS SUBROUTINE FAMILY IS HIGHLY DISCOURAGED BECAUSE THE INTERNAL
!> STATES OF MPAS DYNAMICAL CORE INSTANCE COULD BE MODIFIED THROUGH THE
!> RETURNED POINTER. THESE ARE UNCHARTED WATERS SO BE SURE WHAT YOU ARE
!> DOING.
@@ -3980,7 +3996,7 @@ end subroutine dyn_mpas_get_variable_pointer_r5
!-------------------------------------------------------------------------------
! subroutine dyn_mpas_get_variable_value_*
!
- !> \brief A family of accessor subroutines for MPAS dynamical core instance
+ !> \brief A family of accessor subroutines for MPAS dynamical core instance.
!> \author Kuan-Chih Wang
!> \date 2024-03-21
!> \details
diff --git a/src/dynamics/mpas/dyn_comp.F90 b/src/dynamics/mpas/dyn_comp.F90
index 5e686f25..803f281b 100644
--- a/src/dynamics/mpas/dyn_comp.F90
+++ b/src/dynamics/mpas/dyn_comp.F90
@@ -1,3 +1,13 @@
+! SPDX-FileCopyrightText: Copyright (C) 2024 U.S. National Science Foundation National Center for Atmospheric Research
+! 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 contains the instance of MPAS dynamical core, which is used extensively throughout CAM-SIMA.
+!> It provides core functionalities such as the initialization, running, and finalization of MPAS
+!> dynamical core. Various utility procedures for debug printing, exchanging constituent states,
+!> inquiring mesh dimensions, etc. are also provided here.
module dyn_comp
! Module(s) from CESM Share.
use shr_kind_mod, only: kind_r8 => shr_kind_r8, &
@@ -43,6 +53,7 @@ module dyn_comp
type(mpas_dynamical_core_type) :: mpas_dynamical_core
! Local and global mesh dimensions of MPAS dynamical core.
+ ! Protected module variables that can only be initialized by `dyn_inquire_mesh_dimensions`.
integer, protected :: ncells, ncells_solve, nedges, nedges_solve, nvertices, nvertices_solve, nvertlevels
integer, protected :: ncells_global, nedges_global, nvertices_global, ncells_max, nedges_max
real(kind_dyn_mpas), protected :: sphere_radius
@@ -113,7 +124,7 @@ subroutine dyn_readnl(namelist_path)
nullify(pio_iosystem)
- ! Get free units for MPAS so it can write its own log files, e.g., `log.atmosphere.0000.{out,err}`.
+ ! Get free units for MPAS so it can write its own log files, e.g., "log.atmosphere.0000.{out,err}".
log_unit(1) = shr_file_getunit()
log_unit(2) = shr_file_getunit()
@@ -136,7 +147,7 @@ subroutine dyn_readnl(namelist_path)
call dyn_debug_print(debugout_info, 'Reading namelist')
- ! Read MPAS-related namelist variables from `namelist_path`, e.g., `atm_in`.
+ ! Read MPAS-related namelist variables from `namelist_path`, e.g., "atm_in".
call mpas_dynamical_core % read_namelist(namelist_path, &
cam_calendar, start_date_time, stop_date_time, run_duration, initial_run)
@@ -331,7 +342,7 @@ subroutine dyn_init(cam_runtime_opts, dyn_in, dyn_out)
end subroutine dyn_init
!> Check for consistency in topography data. The presence of topography file is inferred from the `pio_file` pointer.
- !> If topography file is used, check that the `PHIS` variable, which denotes surface geopotential,
+ !> If topography file is used, check that the "PHIS" variable, which denotes surface geopotential,
!> is consistent with the surface geometric height in MPAS.
!> Otherwise, if topography file is not used, check that the surface geometric height in MPAS is zero.
!> (KCW, 2024-05-10)
@@ -352,7 +363,7 @@ subroutine check_topography_data(pio_file)
real(kind_r8), parameter :: error_tolerance = 1.0E-3_kind_r8 ! Error tolerance for consistency check.
real(kind_r8), allocatable :: surface_geometric_height(:) ! Computed from topography file.
real(kind_r8), allocatable :: surface_geopotential(:) ! Read from topography file.
- real(kind_dyn_mpas), pointer :: zgrid(:, :) ! From MPAS. Geometric height (meters) at layer interfaces.
+ real(kind_dyn_mpas), pointer :: zgrid(:, :) ! From MPAS. Geometric height (m) at layer interfaces.
call dyn_debug_print(debugout_debug, subname // ' entered')
@@ -416,9 +427,9 @@ subroutine set_analytic_initial_condition()
integer, allocatable :: global_grid_index(:)
real(kind_r8), allocatable :: buffer_2d_real(:, :), buffer_3d_real(:, :, :)
real(kind_r8), allocatable :: lat_rad(:), lon_rad(:)
- real(kind_r8), allocatable :: z_int(:, :) ! Geometric height (meters) at layer interfaces.
+ real(kind_r8), allocatable :: z_int(:, :) ! Geometric height (m) at layer interfaces.
! Dimension and vertical index orders follow CAM-SIMA convention.
- real(kind_dyn_mpas), pointer :: zgrid(:, :) ! Geometric height (meters) at layer interfaces.
+ real(kind_dyn_mpas), pointer :: zgrid(:, :) ! Geometric height (m) at layer interfaces.
! Dimension and vertical index orders follow MPAS convention.
call dyn_debug_print(debugout_debug, subname // ' entered')
diff --git a/src/dynamics/mpas/dyn_coupling.F90 b/src/dynamics/mpas/dyn_coupling.F90
index eeca26e2..2f5cd7ac 100644
--- a/src/dynamics/mpas/dyn_coupling.F90
+++ b/src/dynamics/mpas/dyn_coupling.F90
@@ -1,3 +1,10 @@
+! SPDX-FileCopyrightText: Copyright (C) 2024 U.S. National Science Foundation National Center for Atmospheric Research
+! 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 implements bidirectional coupling between dynamics and physics states.
module dyn_coupling
! Module(s) from CESM Share.
use shr_kind_mod, only: kind_r8 => shr_kind_r8, &
@@ -663,7 +670,7 @@ pure elemental function t_of_theta_rhod_qv(theta, rhod, qv) result(t)
! The paragraph below equation 2.7 in doi:10.5065/1DFH-6P97.
! The paragraph below equation 2 in doi:10.1175/MWR-D-11-00215.1.
!
- ! In short, solve the below equation set for $T$ in terms of $\theta$, $\rho_d$ and $q_v$:
+ ! In all, solve the below equation set for $T$ in terms of $\theta$, $\rho_d$ and $q_v$:
! \begin{equation*}
! \begin{cases}
! \theta &= T (\frac{P_0}{P})^{\frac{R_d}{C_p}} \\
@@ -701,7 +708,7 @@ pure elemental function theta_of_t_rhod_qv(t, rhod, qv) result(theta)
! The paragraph below equation 2.7 in doi:10.5065/1DFH-6P97.
! The paragraph below equation 2 in doi:10.1175/MWR-D-11-00215.1.
!
- ! In short, solve the below equation set for $\theta$ in terms of $T$, $\rho_d$ and $q_v$:
+ ! In all, solve the below equation set for $\theta$ in terms of $T$, $\rho_d$ and $q_v$:
! \begin{equation*}
! \begin{cases}
! \theta &= T (\frac{P_0}{P})^{\frac{R_d}{C_p}} \\
diff --git a/src/dynamics/mpas/dyn_grid.F90 b/src/dynamics/mpas/dyn_grid.F90
index 8a21b2a7..cfd9216c 100644
--- a/src/dynamics/mpas/dyn_grid.F90
+++ b/src/dynamics/mpas/dyn_grid.F90
@@ -1,3 +1,11 @@
+! SPDX-FileCopyrightText: Copyright (C) 2024 U.S. National Science Foundation National Center for Atmospheric Research
+! 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 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
@@ -247,9 +255,9 @@ subroutine init_physics_grid()
integer :: i
integer :: ierr
integer, pointer :: indextocellid(:) ! Global indexes of cell centers.
- real(kind_dyn_mpas), pointer :: areacell(:) ! Cell areas (square meters).
- real(kind_dyn_mpas), pointer :: latcell(:) ! Cell center latitudes (radians).
- real(kind_dyn_mpas), pointer :: loncell(:) ! Cell center longitudes (radians).
+ 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')
@@ -344,13 +352,13 @@ subroutine define_cam_grid()
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 (square meters).
- real(kind_dyn_mpas), pointer :: latcell(:) ! Cell center latitudes (radians).
- real(kind_dyn_mpas), pointer :: latedge(:) ! Edge node latitudes (radians).
- real(kind_dyn_mpas), pointer :: latvertex(:) ! Vertex node latitudes (radians).
- real(kind_dyn_mpas), pointer :: loncell(:) ! Cell center longitudes (radians).
- real(kind_dyn_mpas), pointer :: lonedge(:) ! Edge node longitudes (radians).
- real(kind_dyn_mpas), pointer :: lonvertex(:) ! Vertex node longitudes (radians).
+ 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`.
@@ -360,7 +368,7 @@ subroutine define_cam_grid()
! 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 (square meters). CANNOT be safely deallocated because `cam_grid_attribute_register`
+ ! 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`
diff --git a/src/dynamics/mpas/stepon.F90 b/src/dynamics/mpas/stepon.F90
index dfaf2c18..62d1b6ce 100644
--- a/src/dynamics/mpas/stepon.F90
+++ b/src/dynamics/mpas/stepon.F90
@@ -1,3 +1,10 @@
+! SPDX-FileCopyrightText: Copyright (C) 2024 U.S. National Science Foundation National Center for Atmospheric Research
+! 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 is a thin wrapper for plugging MPAS dynamical core into CAM-SIMA.
module stepon
! Module(s) from CCPP.
use ccpp_kinds, only: kind_phys