dyn_mpas_init_stream_with_pool Subroutine

private subroutine dyn_mpas_init_stream_with_pool(self, mpas_pool, mpas_stream, pio_file, stream_mode, stream_name)

Uses

    • mpas_derived_types
    • pio
    • mpas_pool_routines
    • mpas_io_streams
  • proc~~dyn_mpas_init_stream_with_pool~~UsesGraph proc~dyn_mpas_init_stream_with_pool mpas_dynamical_core_type%dyn_mpas_init_stream_with_pool mpas_derived_types mpas_derived_types proc~dyn_mpas_init_stream_with_pool->mpas_derived_types mpas_io_streams mpas_io_streams proc~dyn_mpas_init_stream_with_pool->mpas_io_streams mpas_pool_routines mpas_pool_routines proc~dyn_mpas_init_stream_with_pool->mpas_pool_routines pio pio proc~dyn_mpas_init_stream_with_pool->pio

In the context of MPAS, the concept of a "pool" resembles a group of (related) variables, while the concept of a "stream" resembles a file. This subroutine initializes an MPAS stream with an accompanying MPAS pool by adding variable and attribute information to them. After that, MPAS is ready to perform IO on them. Analogous to the build_stream and mpas_stream_mgr_add_field subroutines in MPAS stream manager.

Type Bound

mpas_dynamical_core_type

Arguments

Type IntentOptional Attributes Name
class(mpas_dynamical_core_type), intent(in) :: self
type(mpas_pool_type), intent(out), pointer :: mpas_pool
type(mpas_stream_type), intent(out), pointer :: mpas_stream
type(file_desc_t), intent(in), pointer :: pio_file
character(len=*), intent(in) :: stream_mode
character(len=*), intent(in) :: stream_name

Calls

proc~~dyn_mpas_init_stream_with_pool~~CallsGraph proc~dyn_mpas_init_stream_with_pool mpas_dynamical_core_type%dyn_mpas_init_stream_with_pool mpas_createstream mpas_createstream proc~dyn_mpas_init_stream_with_pool->mpas_createstream mpas_pool_add_config mpas_pool_add_config proc~dyn_mpas_init_stream_with_pool->mpas_pool_add_config mpas_pool_create_pool mpas_pool_create_pool proc~dyn_mpas_init_stream_with_pool->mpas_pool_create_pool mpas_pool_get_field mpas_pool_get_field proc~dyn_mpas_init_stream_with_pool->mpas_pool_get_field mpas_streamaddfield mpas_streamaddfield proc~dyn_mpas_init_stream_with_pool->mpas_streamaddfield none~add_stream_attribute add_stream_attribute proc~dyn_mpas_init_stream_with_pool->none~add_stream_attribute pio_file_is_open pio_file_is_open proc~dyn_mpas_init_stream_with_pool->pio_file_is_open proc~dyn_mpas_check_variable_status mpas_dynamical_core_type%dyn_mpas_check_variable_status proc~dyn_mpas_init_stream_with_pool->proc~dyn_mpas_check_variable_status proc~dyn_mpas_debug_print mpas_dynamical_core_type%dyn_mpas_debug_print proc~dyn_mpas_init_stream_with_pool->proc~dyn_mpas_debug_print proc~parse_stream_name parse_stream_name proc~dyn_mpas_init_stream_with_pool->proc~parse_stream_name proc~stringify stringify proc~dyn_mpas_init_stream_with_pool->proc~stringify none~add_stream_attribute_0d add_stream_attribute_0d none~add_stream_attribute->none~add_stream_attribute_0d none~add_stream_attribute_1d add_stream_attribute_1d none~add_stream_attribute->none~add_stream_attribute_1d proc~dyn_mpas_check_variable_status->mpas_pool_get_field proc~dyn_mpas_check_variable_status->pio_file_is_open proc~dyn_mpas_check_variable_status->proc~dyn_mpas_debug_print proc~dyn_mpas_check_variable_status->proc~stringify constituentnames constituentnames proc~dyn_mpas_check_variable_status->constituentnames pio_inq_varid pio_inq_varid proc~dyn_mpas_check_variable_status->pio_inq_varid pio_inq_varndims pio_inq_varndims proc~dyn_mpas_check_variable_status->pio_inq_varndims pio_inq_vartype pio_inq_vartype proc~dyn_mpas_check_variable_status->pio_inq_vartype proc~dyn_mpas_debug_print->proc~stringify proc~index_unique index_unique proc~parse_stream_name->proc~index_unique proc~parse_stream_name_fragment parse_stream_name_fragment proc~parse_stream_name->proc~parse_stream_name_fragment none~add_stream_attribute_0d->proc~dyn_mpas_debug_print mpas_writestreamatt mpas_writestreamatt none~add_stream_attribute_0d->mpas_writestreamatt none~add_stream_attribute_1d->proc~dyn_mpas_debug_print none~add_stream_attribute_1d->mpas_writestreamatt

Called by

proc~~dyn_mpas_init_stream_with_pool~~CalledByGraph proc~dyn_mpas_init_stream_with_pool mpas_dynamical_core_type%dyn_mpas_init_stream_with_pool proc~dyn_mpas_read_write_stream mpas_dynamical_core_type%dyn_mpas_read_write_stream proc~dyn_mpas_read_write_stream->proc~dyn_mpas_init_stream_with_pool proc~dyn_init dyn_init proc~dyn_init->proc~dyn_mpas_read_write_stream proc~dyn_variable_dump dyn_variable_dump proc~dyn_variable_dump->proc~dyn_mpas_read_write_stream proc~model_grid_init model_grid_init proc~model_grid_init->proc~dyn_mpas_read_write_stream proc~dyn_final dyn_final proc~dyn_final->proc~dyn_variable_dump proc~stepon_final stepon_final proc~stepon_final->proc~dyn_final

Variables

Type Visibility Attributes Name Initial
type(field0dchar), private, pointer :: field_0d_char
type(field0dinteger), private, pointer :: field_0d_integer
type(field0dreal), private, pointer :: field_0d_real
type(field1dchar), private, pointer :: field_1d_char
type(field1dinteger), private, pointer :: field_1d_integer
type(field1dreal), private, pointer :: field_1d_real
type(field2dinteger), private, pointer :: field_2d_integer
type(field2dreal), private, pointer :: field_2d_real
type(field3dinteger), private, pointer :: field_3d_integer
type(field3dreal), private, pointer :: field_3d_real
type(field4dreal), private, pointer :: field_4d_real
type(field5dreal), private, pointer :: field_5d_real
integer, private :: i
integer, private :: ierr
character(len=strkind), private :: stream_filename
integer, private :: stream_format
character(len=*), private, parameter :: subname = 'dyn_mpas_subdriver::dyn_mpas_init_stream_with_pool'
type(var_info_type), private, allocatable :: var_info_list(:)
logical, private, allocatable :: var_is_present(:)

Whether a variable is present on the file (i.e., pio_file).

logical, private, allocatable :: var_is_tkr_compatible(:)

Whether a variable is type, kind, and rank compatible with what MPAS expects on the file (i.e., pio_file).


Interfaces

interface add_stream_attribute

  • subroutine add_stream_attribute_0d(attribute_name, attribute_value)

    Helper subroutine for adding a 0-d stream attribute by calling mpas_writestreamatt with error checking. (KCW, 2024-03-14)

    Arguments

    Type IntentOptional Attributes Name
    character(len=*), intent(in) :: attribute_name
    class(*), intent(in) :: attribute_value
  • subroutine add_stream_attribute_1d(attribute_name, attribute_value)

    Helper subroutine for adding a 1-d stream attribute by calling mpas_writestreamatt with error checking. (KCW, 2024-03-14)

    Arguments

    Type IntentOptional Attributes Name
    character(len=*), intent(in) :: attribute_name
    class(*), intent(in) :: attribute_value(:)

Subroutines

subroutine add_stream_attribute_0d(attribute_name, attribute_value)

Helper subroutine for adding a 0-d stream attribute by calling mpas_writestreamatt with error checking. (KCW, 2024-03-14)

Arguments

Type IntentOptional Attributes Name
character(len=*), intent(in) :: attribute_name
class(*), intent(in) :: attribute_value

subroutine add_stream_attribute_1d(attribute_name, attribute_value)

Helper subroutine for adding a 1-d stream attribute by calling mpas_writestreamatt with error checking. (KCW, 2024-03-14)

Arguments

Type IntentOptional Attributes Name
character(len=*), intent(in) :: attribute_name
class(*), intent(in) :: attribute_value(:)

Source Code

    subroutine dyn_mpas_init_stream_with_pool(self, mpas_pool, mpas_stream, pio_file, stream_mode, stream_name)
        ! Module(s) from external libraries.
        use pio, only: file_desc_t, pio_file_is_open
        ! Module(s) from MPAS.
        use mpas_derived_types, only: field0dchar, field1dchar, &
                                      field0dinteger, field1dinteger, field2dinteger, field3dinteger, &
                                      field0dreal, field1dreal, field2dreal, field3dreal, field4dreal, field5dreal, &
                                      mpas_io_native_precision, mpas_io_pnetcdf, mpas_io_read, mpas_io_write, &
                                      mpas_pool_type, mpas_stream_noerr, mpas_stream_type
        use mpas_io_streams, only: mpas_createstream, mpas_streamaddfield
        use mpas_pool_routines, only: mpas_pool_add_config, mpas_pool_create_pool, mpas_pool_get_field

        class(mpas_dynamical_core_type), intent(in) :: self
        type(mpas_pool_type), pointer, intent(out) :: mpas_pool
        type(mpas_stream_type), pointer, intent(out) :: mpas_stream
        type(file_desc_t), pointer, intent(in) :: pio_file
        character(*), intent(in) :: stream_mode
        character(*), intent(in) :: stream_name

        interface add_stream_attribute
            procedure :: add_stream_attribute_0d
            procedure :: add_stream_attribute_1d
        end interface add_stream_attribute

        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`).
        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`).
        logical, allocatable :: var_is_tkr_compatible(:)
        type(field0dchar), pointer :: field_0d_char
        type(field1dchar), pointer :: field_1d_char
        type(field0dinteger), pointer :: field_0d_integer
        type(field1dinteger), pointer :: field_1d_integer
        type(field2dinteger), pointer :: field_2d_integer
        type(field3dinteger), pointer :: field_3d_integer
        type(field0dreal), pointer :: field_0d_real
        type(field1dreal), pointer :: field_1d_real
        type(field2dreal), pointer :: field_2d_real
        type(field3dreal), pointer :: field_3d_real
        type(field4dreal), pointer :: field_4d_real
        type(field5dreal), pointer :: field_5d_real
        type(var_info_type), allocatable :: var_info_list(:)

        call self % debug_print(log_level_debug, subname // ' entered')

        nullify(field_0d_char)
        nullify(field_1d_char)
        nullify(field_0d_integer)
        nullify(field_1d_integer)
        nullify(field_2d_integer)
        nullify(field_3d_integer)
        nullify(field_0d_real)
        nullify(field_1d_real)
        nullify(field_2d_real)
        nullify(field_3d_real)
        nullify(field_4d_real)
        nullify(field_5d_real)

        call mpas_pool_create_pool(mpas_pool)

        allocate(mpas_stream, stat=ierr)

        if (ierr /= 0) then
            call self % model_error('Failed to allocate stream "' // trim(adjustl(stream_name)) // '"', subname, __LINE__)
        end if

        ! Not actually used because a PIO file descriptor is directly supplied.
        stream_filename = 'external stream'
        stream_format = mpas_io_pnetcdf

        call self % debug_print(log_level_verbose, 'Checking PIO file descriptor')

        if (.not. associated(pio_file)) then
            call self % model_error('Invalid PIO file descriptor', subname, __LINE__)
        end if

        if (.not. pio_file_is_open(pio_file)) then
            call self % model_error('Invalid PIO file descriptor', subname, __LINE__)
        end if

        select case (trim(adjustl(stream_mode)))
            case ('r', 'read')
                call self % debug_print(log_level_verbose, 'Creating stream "' // trim(adjustl(stream_name)) // '" for reading')

                call mpas_createstream( &
                    mpas_stream, self % domain_ptr % iocontext, stream_filename, stream_format, mpas_io_read,  &
                    clobberrecords=.false., clobberfiles=.false., truncatefiles=.false., &
                    precision=mpas_io_native_precision, pio_file_desc=pio_file, ierr=ierr)
            case ('w', 'write')
                call self % debug_print(log_level_verbose, 'Creating stream "' // trim(adjustl(stream_name)) // '" for writing')

                call mpas_createstream( &
                    mpas_stream, self % domain_ptr % iocontext, stream_filename, stream_format, mpas_io_write, &
                    clobberrecords=.false., clobberfiles=.false., truncatefiles=.false., &
                    precision=mpas_io_native_precision, pio_file_desc=pio_file, ierr=ierr)
            case default
                call self % model_error('Unsupported stream mode "' // trim(adjustl(stream_mode)) // '"', subname, __LINE__)
        end select

        if (ierr /= mpas_stream_noerr) then
            call self % model_error('Failed to create stream "' // trim(adjustl(stream_name)) // '"', subname, __LINE__)
        end if

        var_info_list = parse_stream_name(stream_name)

        ! Add variables contained in `var_info_list` to stream.
        do i = 1, size(var_info_list)
            call self % debug_print(log_level_debug, 'var_info_list(' // stringify([i]) // ') % name = ' // &
                stringify([var_info_list(i) % name]))
            call self % debug_print(log_level_debug, 'var_info_list(' // stringify([i]) // ') % type = ' // &
                stringify([var_info_list(i) % type]))
            call self % debug_print(log_level_debug, 'var_info_list(' // stringify([i]) // ') % rank = ' // &
                stringify([var_info_list(i) % rank]))

            if (trim(adjustl(stream_mode)) == 'r' .or. trim(adjustl(stream_mode)) == 'read') then
                call self % check_variable_status(var_is_present, var_is_tkr_compatible, pio_file, var_info_list(i))

                ! Do not hard crash the model if a variable is missing and cannot be read.
                ! This can happen if users attempt to initialize/restart the model with data generated by
                ! older versions of MPAS. Print a debug message to let users decide if this is acceptable.
                if (.not. any(var_is_present)) then
                    call self % debug_print(log_level_verbose, 'Skipping variable "' // trim(adjustl(var_info_list(i) % name)) // &
                        '" due to not present')

                    cycle
                end if

                if (any(var_is_present .and. .not. var_is_tkr_compatible)) then
                    call self % debug_print(log_level_verbose, 'Skipping variable "' // trim(adjustl(var_info_list(i) % name)) // &
                        '" due to not TKR compatible')

                    cycle
                end if
            end if

            ! Add "<variable name>" to pool with the value of `1`.
            ! The existence of "<variable name>" in pool causes it to be considered for IO in MPAS.
            call mpas_pool_add_config(mpas_pool, trim(adjustl(var_info_list(i) % name)), 1)
            ! Add "<variable name>:packages" to pool with the value of an empty character string.
            ! This causes "<variable name>" to be always considered active for IO in MPAS.
            call mpas_pool_add_config(mpas_pool, trim(adjustl(var_info_list(i) % name) // ':packages'), '')

            ! Add "<variable name>" to stream.
            call self % debug_print(log_level_verbose, 'Adding variable "' // trim(adjustl(var_info_list(i) % name)) // &
                '" to stream "' // trim(adjustl(stream_name)) // '"')

            select case (trim(adjustl(var_info_list(i) % type)))
                case ('character')
                    select case (var_info_list(i) % rank)
                        case (0)
                            call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, &
                                trim(adjustl(var_info_list(i) % name)), field_0d_char, timelevel=1)

                            if (.not. associated(field_0d_char)) then
                                call self % model_error('Failed to find variable "' // trim(adjustl(var_info_list(i) % name)) // &
                                    '"', subname, __LINE__)
                            end if

                            call mpas_streamaddfield(mpas_stream, field_0d_char, ierr=ierr)

                            nullify(field_0d_char)
                        case (1)
                            call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, &
                                trim(adjustl(var_info_list(i) % name)), field_1d_char, timelevel=1)

                            if (.not. associated(field_1d_char)) then
                                call self % model_error('Failed to find variable "' // trim(adjustl(var_info_list(i) % name)) // &
                                    '"', subname, __LINE__)
                            end if

                            call mpas_streamaddfield(mpas_stream, field_1d_char, ierr=ierr)

                            nullify(field_1d_char)
                        case default
                            call self % model_error('Unsupported variable rank ' // stringify([var_info_list(i) % rank]) // &
                                ' for "' // trim(adjustl(var_info_list(i) % name)) // '"', subname, __LINE__)
                    end select
                case ('integer')
                    select case (var_info_list(i) % rank)
                        case (0)
                            call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, &
                                trim(adjustl(var_info_list(i) % name)), field_0d_integer, timelevel=1)

                            if (.not. associated(field_0d_integer)) then
                                call self % model_error('Failed to find variable "' // trim(adjustl(var_info_list(i) % name)) // &
                                    '"', subname, __LINE__)
                            end if

                            call mpas_streamaddfield(mpas_stream, field_0d_integer, ierr=ierr)

                            nullify(field_0d_integer)
                        case (1)
                            call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, &
                                trim(adjustl(var_info_list(i) % name)), field_1d_integer, timelevel=1)

                            if (.not. associated(field_1d_integer)) then
                                call self % model_error('Failed to find variable "' // trim(adjustl(var_info_list(i) % name)) // &
                                    '"', subname, __LINE__)
                            end if

                            call mpas_streamaddfield(mpas_stream, field_1d_integer, ierr=ierr)

                            nullify(field_1d_integer)
                        case (2)
                            call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, &
                                trim(adjustl(var_info_list(i) % name)), field_2d_integer, timelevel=1)

                            if (.not. associated(field_2d_integer)) then
                                call self % model_error('Failed to find variable "' // trim(adjustl(var_info_list(i) % name)) // &
                                    '"', subname, __LINE__)
                            end if

                            call mpas_streamaddfield(mpas_stream, field_2d_integer, ierr=ierr)

                            nullify(field_2d_integer)
                        case (3)
                            call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, &
                                trim(adjustl(var_info_list(i) % name)), field_3d_integer, timelevel=1)

                            if (.not. associated(field_3d_integer)) then
                                call self % model_error('Failed to find variable "' // trim(adjustl(var_info_list(i) % name)) // &
                                    '"', subname, __LINE__)
                            end if

                            call mpas_streamaddfield(mpas_stream, field_3d_integer, ierr=ierr)

                            nullify(field_3d_integer)
                        case default
                            call self % model_error('Unsupported variable rank ' // stringify([var_info_list(i) % rank]) // &
                                ' for "' // trim(adjustl(var_info_list(i) % name)) // '"', subname, __LINE__)
                    end select
                case ('real')
                    select case (var_info_list(i) % rank)
                        case (0)
                            call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, &
                                trim(adjustl(var_info_list(i) % name)), field_0d_real, timelevel=1)

                            if (.not. associated(field_0d_real)) then
                                call self % model_error('Failed to find variable "' // trim(adjustl(var_info_list(i) % name)) // &
                                    '"', subname, __LINE__)
                            end if

                            call mpas_streamaddfield(mpas_stream, field_0d_real, ierr=ierr)

                            nullify(field_0d_real)
                        case (1)
                            call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, &
                                trim(adjustl(var_info_list(i) % name)), field_1d_real, timelevel=1)

                            if (.not. associated(field_1d_real)) then
                                call self % model_error('Failed to find variable "' // trim(adjustl(var_info_list(i) % name)) // &
                                    '"', subname, __LINE__)
                            end if

                            call mpas_streamaddfield(mpas_stream, field_1d_real, ierr=ierr)

                            nullify(field_1d_real)
                        case (2)
                            call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, &
                                trim(adjustl(var_info_list(i) % name)), field_2d_real, timelevel=1)

                            if (.not. associated(field_2d_real)) then
                                call self % model_error('Failed to find variable "' // trim(adjustl(var_info_list(i) % name)) // &
                                    '"', subname, __LINE__)
                            end if

                            call mpas_streamaddfield(mpas_stream, field_2d_real, ierr=ierr)

                            nullify(field_2d_real)
                        case (3)
                            call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, &
                                trim(adjustl(var_info_list(i) % name)), field_3d_real, timelevel=1)

                            if (.not. associated(field_3d_real)) then
                                call self % model_error('Failed to find variable "' // trim(adjustl(var_info_list(i) % name)) // &
                                    '"', subname, __LINE__)
                            end if

                            call mpas_streamaddfield(mpas_stream, field_3d_real, ierr=ierr)

                            nullify(field_3d_real)
                        case (4)
                            call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, &
                                trim(adjustl(var_info_list(i) % name)), field_4d_real, timelevel=1)

                            if (.not. associated(field_4d_real)) then
                                call self % model_error('Failed to find variable "' // trim(adjustl(var_info_list(i) % name)) // &
                                    '"', subname, __LINE__)
                            end if

                            call mpas_streamaddfield(mpas_stream, field_4d_real, ierr=ierr)

                            nullify(field_4d_real)
                        case (5)
                            call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, &
                                trim(adjustl(var_info_list(i) % name)), field_5d_real, timelevel=1)

                            if (.not. associated(field_5d_real)) then
                                call self % model_error('Failed to find variable "' // trim(adjustl(var_info_list(i) % name)) // &
                                    '"', subname, __LINE__)
                            end if

                            call mpas_streamaddfield(mpas_stream, field_5d_real, ierr=ierr)

                            nullify(field_5d_real)
                        case default
                            call self % model_error('Unsupported variable rank ' // stringify([var_info_list(i) % rank]) // &
                                ' for "' // trim(adjustl(var_info_list(i) % name)) // '"', subname, __LINE__)
                    end select
                case default
                    call self % model_error('Unsupported variable type "' // trim(adjustl(var_info_list(i) % type)) // &
                        '" for "' // trim(adjustl(var_info_list(i) % name)) // '"', subname, __LINE__)
            end select

            if (ierr /= mpas_stream_noerr) then
                call self % model_error('Failed to add variable "' // trim(adjustl(var_info_list(i) % name)) // &
                    '" to stream "' // trim(adjustl(stream_name)) // '"', subname, __LINE__)
            end if
        end do

        if (trim(adjustl(stream_mode)) == 'w' .or. trim(adjustl(stream_mode)) == 'write') then
            ! Add MPAS-specific attributes to stream.

            ! Attributes related to MPAS core (i.e., `core_type`).
            call add_stream_attribute('conventions', self % domain_ptr % core % conventions)
            call add_stream_attribute('core_name', self % domain_ptr % core % corename)
            call add_stream_attribute('git_version', self % domain_ptr % core % git_version)
            call add_stream_attribute('model_name', self % domain_ptr % core % modelname)
            call add_stream_attribute('source', self % domain_ptr % core % source)

            ! Attributes related to MPAS domain (i.e., `domain_type`).
            call add_stream_attribute('is_periodic', self % domain_ptr % is_periodic)
            call add_stream_attribute('mesh_spec', self % domain_ptr % mesh_spec)
            call add_stream_attribute('on_a_sphere', self % domain_ptr % on_a_sphere)
            call add_stream_attribute('parent_id', self % domain_ptr % parent_id)
            call add_stream_attribute('sphere_radius', self % domain_ptr % sphere_radius)
            call add_stream_attribute('x_period', self % domain_ptr % x_period)
            call add_stream_attribute('y_period', self % domain_ptr % y_period)
        end if

        call self % debug_print(log_level_debug, subname // ' completed')
    contains
        !> Helper subroutine for adding a 0-d stream attribute by calling `mpas_writestreamatt` with error checking.
        !> (KCW, 2024-03-14)
        subroutine add_stream_attribute_0d(attribute_name, attribute_value)
            ! Module(s) from MPAS.
            use mpas_io_streams, only: mpas_writestreamatt

            character(*), intent(in) :: attribute_name
            class(*), intent(in) :: attribute_value

            call self % debug_print(log_level_verbose, 'Adding attribute "' // trim(adjustl(attribute_name)) // &
                '" to stream "' // trim(adjustl(stream_name)) // '"')

            select type (attribute_value)
                type is (character(*))
                    call mpas_writestreamatt(mpas_stream, &
                        trim(adjustl(attribute_name)), trim(adjustl(attribute_value)), syncval=.false., ierr=ierr)
                type is (integer)
                    call mpas_writestreamatt(mpas_stream, &
                        trim(adjustl(attribute_name)), attribute_value, syncval=.false., ierr=ierr)
                type is (logical)
                    if (attribute_value) then
                        ! 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".
                        call mpas_writestreamatt(mpas_stream, &
                            trim(adjustl(attribute_name)), 'NO', syncval=.false., ierr=ierr)
                    end if
                type is (real(rkind))
                    call mpas_writestreamatt(mpas_stream, &
                        trim(adjustl(attribute_name)), attribute_value, syncval=.false., ierr=ierr)
                class default
                    call self % model_error('Unsupported attribute type (Must be one of: character, integer, logical, real)', &
                        subname, __LINE__)
            end select

            if (ierr /= mpas_stream_noerr) then
                call self % model_error('Failed to add attribute "' // trim(adjustl(attribute_name)) // &
                    '" to stream "' // trim(adjustl(stream_name)) // '"', subname, __LINE__)
            end if
        end subroutine add_stream_attribute_0d

        !> Helper subroutine for adding a 1-d stream attribute by calling `mpas_writestreamatt` with error checking.
        !> (KCW, 2024-03-14)
        subroutine add_stream_attribute_1d(attribute_name, attribute_value)
            ! Module(s) from MPAS.
            use mpas_io_streams, only: mpas_writestreamatt

            character(*), intent(in) :: attribute_name
            class(*), intent(in) :: attribute_value(:)

            call self % debug_print(log_level_verbose, 'Adding attribute "' // trim(adjustl(attribute_name)) // &
                '" to stream "' // trim(adjustl(stream_name)) // '"')

            select type (attribute_value)
                type is (integer)
                    call mpas_writestreamatt(mpas_stream, &
                        trim(adjustl(attribute_name)), attribute_value, syncval=.false., ierr=ierr)
                type is (real(rkind))
                    call mpas_writestreamatt(mpas_stream, &
                        trim(adjustl(attribute_name)), attribute_value, syncval=.false., ierr=ierr)
                class default
                    call self % model_error('Unsupported attribute type (Must be one of: integer, real)', &
                        subname, __LINE__)
            end select

            if (ierr /= mpas_stream_noerr) then
                call self % model_error('Failed to add attribute "' // trim(adjustl(attribute_name)) // &
                    '" to stream "' // trim(adjustl(stream_name)) // '"', subname, __LINE__)
            end if
        end subroutine add_stream_attribute_1d
    end subroutine dyn_mpas_init_stream_with_pool