From 84d5e217653047eab320fce54a02ad6afcfc260a Mon Sep 17 00:00:00 2001 From: Robert Hallberg Date: Fri, 19 Mar 2021 12:43:03 -0400 Subject: [PATCH] +FMS2 reads for ints & MOM_read_data_fms2 cleanup Added variants of MOM_read_data_0d, MOM_read_data_0d_int, MOM_read_data_1d, MOM_read_data_1d_int, and MOM_read_data_2d_region that use the FMS2 interfaces to read data. Also altered prepare_to_read_var so that it does not open a file (which had been an option before); one argument to prepare_to_read_var was no longer needed and so it was removed. Also added a public interface to find_varname_in_file that does the same as prepare_to_read_var but works on the other FMS2 file type without domain decomposition. Several unused and now redundant routines were removed from MOM_read_data_fms2.F90. Comments describing a number of variables were also added. All of these changes are confined to config_src/infra/FMS2, and all answers are bitwise identical. --- config_src/infra/FMS2/MOM_io_infra.F90 | 227 ++++++++++--- config_src/infra/FMS2/MOM_read_data_fms2.F90 | 335 +++++-------------- 2 files changed, 254 insertions(+), 308 deletions(-) diff --git a/config_src/infra/FMS2/MOM_io_infra.F90 b/config_src/infra/FMS2/MOM_io_infra.F90 index d4fe0b5387..c545462ad8 100644 --- a/config_src/infra/FMS2/MOM_io_infra.F90 +++ b/config_src/infra/FMS2/MOM_io_infra.F90 @@ -6,8 +6,8 @@ module MOM_io_infra use MOM_domain_infra, only : MOM_domain_type, rescale_comp_data, AGRID, BGRID_NE, CGRID_NE use MOM_domain_infra, only : domain2d, domain1d, CENTER, CORNER, NORTH_FACE, EAST_FACE use MOM_error_infra, only : MOM_error=>MOM_err, NOTE, FATAL, WARNING +use MOM_read_data_fms2, only : prepare_to_read_var, find_varname_in_file -use MOM_read_data_fms2, only : prepare_to_read_var use fms2_io_mod, only : fms2_open_file => open_file, check_if_open, fms2_close_file => close_file use fms2_io_mod, only : FmsNetcdfDomainFile_t, FmsNetcdfFile_t, fms2_read_data => read_data use fms2_io_mod, only : get_unlimited_dimension_name, get_num_dimensions, get_num_variables @@ -41,9 +41,9 @@ module MOM_io_infra public :: MOM_read_data, MOM_read_vector, write_metadata, write_field public :: field_exists, get_field_atts, get_field_size, get_axis_data, read_field_chksum public :: io_infra_init, io_infra_end, MOM_namelist_file, check_namelist_error, write_version -! These types are inherited from underlying infrastructure code, to act as containers for -! information about fields and axes, respectively, and are opaque to this module. -! public :: file_type, fieldtype, axistype +! These types act as containers for information about files, fields and axes, respectively, +! and may also wrap opaque types from the underlying infrastructure. +public :: file_type, fieldtype, axistype ! These are encoding constant parmeters. public :: ASCII_FILE, NETCDF_FILE, SINGLE_FILE, MULTIPLE public :: APPEND_FILE, READONLY_FILE, OVERWRITE_FILE, WRITEONLY_FILE @@ -102,7 +102,7 @@ module MOM_io_infra end interface flush_file !> Type for holding a handle to an open file and related information -type, public :: file_type ; private +type :: file_type ; private integer :: unit = -1 !< The framework identfier or netCDF unit number of an output file type(FmsNetcdfDomainFile_t), pointer :: fileobj => NULL() !< A domain-decomposed !! file object that is open for writing @@ -115,7 +115,7 @@ module MOM_io_infra end type file_type !> This type is a container for information about a variable in a file. -type, public :: fieldtype ; private +type :: fieldtype ; private character(len=256) :: name !< The name of this field in the files. type(mpp_fieldtype) :: FT !< The FMS1 field-type that this type wraps character(len=:), allocatable :: longname !< The long name for this field @@ -126,14 +126,14 @@ module MOM_io_infra end type fieldtype !> This type is a container for information about an axis in a file. -type, public :: axistype ; private +type :: axistype ; private character(len=256) :: name !< The name of this axis in the files. type(mpp_axistype) :: AT !< The FMS1 axis-type that this type wraps real, allocatable, dimension(:) :: ax_data !< The values of the data on the axis. end type axistype -!> For now, this is hard-coded to exercise the new FMS2 interfaces. -logical :: FMS2_reads = .true. +!> For now, these module-variables are hard-coded to exercise the new FMS2 interfaces. +logical :: FMS2_reads = .true. logical :: FMS2_writes = .true. contains @@ -353,8 +353,7 @@ subroutine open_file_type(IO_handle, filename, action, MOM_domain, threading, fi call fms2_close_file(fileObj_read) endif - success = fms2_open_file(IO_handle%fileobj, trim(filename), trim(mode), & - MOM_domain%mpp_domain, is_restart=.false.) + success = fms2_open_file(IO_handle%fileobj, trim(filename), trim(mode), MOM_domain%mpp_domain) if (.not.success) call MOM_error(FATAL, "Unable to open file "//trim(filename)) IO_handle%FMS2_file = .true. elseif (present(MOM_Domain)) then @@ -442,7 +441,8 @@ subroutine get_file_times(IO_handle, time_values, ntime) integer, optional, intent(out) :: ntime !< The number of time levels in the file character(len=256) :: dim_unlim_name ! name of the unlimited dimension in the file - integer :: ntimes + integer :: ntimes ! The number of time levels in the file + !### Modify this routine to optionally convert to time_type, using information about the dimensions? if (allocated(time_values)) deallocate(time_values) @@ -465,12 +465,13 @@ subroutine get_file_fields(IO_handle, fields) type(file_type), intent(in) :: IO_handle !< Handle for a file that is open for I/O type(fieldtype), dimension(:), intent(inout) :: fields !< Field-type descriptions of all of !! the variables in a file. - type(mpp_fieldtype), dimension(size(fields)) :: mpp_fields - character(len=256), dimension(size(fields)) :: var_names - character(len=256) :: units - character(len=2048) :: longname - integer(kind=int64), dimension(3) :: checksum_file - integer :: i, nvar + type(mpp_fieldtype), dimension(size(fields)) :: mpp_fields ! Fieldtype structures for the variables + character(len=256), dimension(size(fields)) :: var_names ! The names of all variables + character(len=256) :: units ! The units of a variable as recorded in the file + character(len=2048) :: longname ! The long-name of a variable as recorded in the file + integer(kind=int64), dimension(3) :: checksum_file ! The checksums for a variable in the file + integer :: nvar ! The number of variables in the file + integer :: i nvar = size(fields) ! Local variables @@ -647,20 +648,40 @@ subroutine MOM_read_data_0d(filename, fieldname, data, timelevel, scale, MOM_Dom optional, intent(in) :: MOM_Domain !< The MOM_Domain that describes the decomposition ! Local variables - type(FmsNetcdfDomainFile_t) :: fileobj ! A handle to a domain-decomposed file object - logical :: has_time_dim ! True if the variable has an unlimited time axis. + type(FmsNetcdfFile_t) :: fileObj ! A handle to a non-domain-decomposed file + type(FmsNetcdfDomainFile_t) :: fileobj_DD ! A handle to a domain-decomposed file object character(len=96) :: var_to_read ! Name of variable to read from the netcdf file - logical :: success ! True if the file was successfully opened + logical :: has_time_dim ! True if the variable has an unlimited time axis. + logical :: success ! True if the file was successfully opened if (present(MOM_Domain) .and. FMS2_reads) then ! Open the FMS2 file-set. - success = fms2_open_file(fileobj, filename, "read", MOM_domain%mpp_domain, is_restart=.false.) + success = fms2_open_file(fileobj_DD, filename, "read", MOM_domain%mpp_domain) if (.not.success) call MOM_error(FATAL, "Failed to open "//trim(filename)) ! Find the matching case-insensitive variable name in the file and prepare to read it. - call prepare_to_read_var(fileobj, fieldname, MOM_domain, "MOM_read_data_1d: ", filename, & + call prepare_to_read_var(fileobj_DD, fieldname, "MOM_read_data_0d: ", filename, & var_to_read, has_time_dim, timelevel) + ! Read the data. + if (present(timelevel) .and. has_time_dim) then + call fms2_read_data(fileobj_DD, var_to_read, data, unlim_dim_level=timelevel) + else + call fms2_read_data(fileobj_DD, var_to_read, data) + endif + + ! Close the file-set. + if (check_if_open(fileobj_DD)) call fms2_close_file(fileobj_DD) + elseif (FMS2_reads) then + ! Open the FMS2 file-set. + success = fms2_open_file(fileObj, trim(filename), "read") + if (.not.success) call MOM_error(FATAL, "Failed to open "//trim(filename)) + + ! Find the matching case-insensitive variable name in the file, and determine whether it + ! has a time dimension. + call find_varname_in_file(fileObj, fieldname, "MOM_read_data_0d: ", filename, & + var_to_read, has_time_dim, timelevel) + ! Read the data. if (present(timelevel) .and. has_time_dim) then call fms2_read_data(fileobj, var_to_read, data, unlim_dim_level=timelevel) @@ -695,20 +716,40 @@ subroutine MOM_read_data_1d(filename, fieldname, data, timelevel, scale, MOM_Dom optional, intent(in) :: MOM_Domain !< The MOM_Domain that describes the decomposition ! Local variables - type(FmsNetcdfDomainFile_t) :: fileobj ! A handle to a domain-decomposed file object - logical :: has_time_dim ! True if the variable has an unlimited time axis. + type(FmsNetcdfFile_t) :: fileObj ! A handle to a non-domain-decomposed file + type(FmsNetcdfDomainFile_t) :: fileobj_DD ! A handle to a domain-decomposed file object character(len=96) :: var_to_read ! Name of variable to read from the netcdf file - logical :: success ! True if the file was successfully opened + logical :: has_time_dim ! True if the variable has an unlimited time axis. + logical :: success ! True if the file was successfully opened if (present(MOM_Domain) .and. FMS2_reads) then ! Open the FMS2 file-set. - success = fms2_open_file(fileobj, filename, "read", MOM_domain%mpp_domain, is_restart=.false.) + success = fms2_open_file(fileobj_DD, filename, "read", MOM_domain%mpp_domain) if (.not.success) call MOM_error(FATAL, "Failed to open "//trim(filename)) ! Find the matching case-insensitive variable name in the file and prepare to read it. - call prepare_to_read_var(fileobj, fieldname, MOM_domain, "MOM_read_data_1d: ", filename, & + call prepare_to_read_var(fileobj_DD, fieldname, "MOM_read_data_1d: ", filename, & var_to_read, has_time_dim, timelevel) + ! Read the data. + if (present(timelevel) .and. has_time_dim) then + call fms2_read_data(fileobj_DD, var_to_read, data, unlim_dim_level=timelevel) + else + call fms2_read_data(fileobj_DD, var_to_read, data) + endif + + ! Close the file-set. + if (check_if_open(fileobj_DD)) call fms2_close_file(fileobj_DD) + elseif (FMS2_reads) then + ! Open the FMS2 file-set. + success = fms2_open_file(fileObj, trim(filename), "read") + if (.not.success) call MOM_error(FATAL, "Failed to open "//trim(filename)) + + ! Find the matching case-insensitive variable name in the file, and determine whether it + ! has a time dimension. + call find_varname_in_file(fileObj, fieldname, "MOM_read_data_1d: ", filename, & + var_to_read, has_time_dim, timelevel) + ! Read the data. if (present(timelevel) .and. has_time_dim) then call fms2_read_data(fileobj, var_to_read, data, unlim_dim_level=timelevel) @@ -747,17 +788,17 @@ subroutine MOM_read_data_2d(filename, fieldname, data, MOM_Domain, & ! Local variables type(FmsNetcdfDomainFile_t) :: fileobj ! A handle to a domain-decomposed file object - logical :: has_time_dim ! True if the variable has an unlimited time axis. character(len=96) :: var_to_read ! Name of variable to read from the netcdf file - logical :: success ! True if the file was successfully opened + logical :: has_time_dim ! True if the variable has an unlimited time axis. + logical :: success ! True if the file was successfully opened if (FMS2_reads) then ! Open the FMS2 file-set. - success = fms2_open_file(fileobj, filename, "read", MOM_domain%mpp_domain, is_restart=.false.) + success = fms2_open_file(fileobj, filename, "read", MOM_domain%mpp_domain) if (.not.success) call MOM_error(FATAL, "Failed to open "//trim(filename)) ! Find the matching case-insensitive variable name in the file and prepare to read it. - call prepare_to_read_var(fileobj, fieldname, MOM_domain, "MOM_read_data_2d: ", filename, & + call prepare_to_read_var(fileobj, fieldname, "MOM_read_data_2d: ", filename, & var_to_read, has_time_dim, timelevel, position) ! Read the data. @@ -801,9 +842,41 @@ subroutine MOM_read_data_2d_region(filename, fieldname, data, start, nread, MOM_ real, optional, intent(in) :: scale !< A scaling factor that the field is multiplied !! by before it is returned. - !### This subroutine does not have an FMS-2 variant yet. + ! Local variables + type(FmsNetcdfFile_t) :: fileObj ! A handle to a non-domain-decomposed file + type(FmsNetcdfDomainFile_t) :: fileobj_DD ! A handle to a domain-decomposed file object + character(len=96) :: var_to_read ! Name of variable to read from the netcdf file + logical :: success ! True if the file was successfully opened - if (present(MOM_Domain)) then + if (present(MOM_Domain) .and. FMS2_reads) then + ! Open the FMS2 file-set. + success = fms2_open_file(fileobj_DD, filename, "read", MOM_domain%mpp_domain) + if (.not.success) call MOM_error(FATAL, "Failed to open "//trim(filename)) + + ! Find the matching case-insensitive variable name in the file and prepare to read it. + call prepare_to_read_var(fileobj_DD, fieldname, "MOM_read_data_2d_region: ", & + filename, var_to_read) + + ! Read the data. + call fms2_read_data(fileobj_DD, var_to_read, data, corner=start(1:2), edge_lengths=nread(1:2)) + + ! Close the file-set. + if (check_if_open(fileobj_DD)) call fms2_close_file(fileobj_DD) + elseif (FMS2_reads) then + ! Open the FMS2 file-set. + success = fms2_open_file(fileObj, trim(filename), "read") + if (.not.success) call MOM_error(FATAL, "Failed to open "//trim(filename)) + + ! Find the matching case-insensitive variable name in the file, and determine whether it + ! has a time dimension. + call find_varname_in_file(fileObj, fieldname, "MOM_read_data_2d_region: ", filename, var_to_read) + + ! Read the data. + call fms2_read_data(fileobj, var_to_read, data, corner=start(1:2), edge_lengths=nread(1:2)) + + ! Close the file-set. + if (check_if_open(fileobj)) call fms2_close_file(fileobj) + elseif (present(MOM_Domain)) then ! Read the variable using the FMS-1 interface. call read_data(filename, fieldname, data, start, nread, domain=MOM_Domain%mpp_domain, & no_domain=no_domain) else @@ -838,17 +911,17 @@ subroutine MOM_read_data_3d(filename, fieldname, data, MOM_Domain, & ! Local variables type(FmsNetcdfDomainFile_t) :: fileobj ! A handle to a domain-decomposed file object - logical :: has_time_dim ! True if the variable has an unlimited time axis. character(len=96) :: var_to_read ! Name of variable to read from the netcdf file - logical :: success ! True if the file was successfully opened + logical :: has_time_dim ! True if the variable has an unlimited time axis. + logical :: success ! True if the file was successfully opened if (FMS2_reads) then ! Open the FMS2 file-set. - success = fms2_open_file(fileobj, filename, "read", MOM_domain%mpp_domain, is_restart=.false.) + success = fms2_open_file(fileobj, filename, "read", MOM_domain%mpp_domain) if (.not.success) call MOM_error(FATAL, "Failed to open "//trim(filename)) ! Find the matching case-insensitive variable name in the file and prepare to read it. - call prepare_to_read_var(fileobj, fieldname, MOM_domain, "MOM_read_data_3d: ", filename, & + call prepare_to_read_var(fileobj, fieldname, "MOM_read_data_3d: ", filename, & var_to_read, has_time_dim, timelevel, position) ! Read the data. @@ -895,11 +968,11 @@ subroutine MOM_read_data_4d(filename, fieldname, data, MOM_Domain, & if (FMS2_reads) then ! Open the FMS2 file-set. - success = fms2_open_file(fileobj, filename, "read", MOM_domain%mpp_domain, is_restart=.false.) + success = fms2_open_file(fileobj, filename, "read", MOM_domain%mpp_domain) if (.not.success) call MOM_error(FATAL, "Failed to open "//trim(filename)) ! Find the matching case-insensitive variable name in the file and prepare to read it. - call prepare_to_read_var(fileobj, fieldname, MOM_domain, "MOM_read_data_4d: ", filename, & + call prepare_to_read_var(fileobj, fieldname, "MOM_read_data_4d: ", filename, & var_to_read, has_time_dim, timelevel, position) ! Read the data. @@ -930,8 +1003,34 @@ subroutine MOM_read_data_0d_int(filename, fieldname, data, timelevel) integer, intent(inout) :: data !< The 1-dimensional array into which the data integer, optional, intent(in) :: timelevel !< The time level in the file to read - !### This needs an FMS2 variant, eventually. - call read_data(filename, fieldname, data, timelevel=timelevel, no_domain=.true.) + ! Local variables + type(FmsNetcdfFile_t) :: fileObj ! A handle to a non-domain-decomposed file + logical :: has_time_dim ! True if the variable has an unlimited time axis. + character(len=96) :: var_to_read ! Name of variable to read from the netcdf file + logical :: success ! If true, the file was opened successfully + + if (FMS2_reads) then + ! Open the FMS2 file-set. + success = fms2_open_file(fileObj, trim(filename), "read") + if (.not.success) call MOM_error(FATAL, "Failed to open "//trim(filename)) + + ! Find the matching case-insensitive variable name in the file, and determine whether it + ! has a time dimension. + call find_varname_in_file(fileObj, fieldname, "MOM_read_data_0d_int: ", filename, & + var_to_read, has_time_dim, timelevel) + + ! Read the data. + if (present(timelevel) .and. has_time_dim) then + call fms2_read_data(fileobj, var_to_read, data, unlim_dim_level=timelevel) + else + call fms2_read_data(fileobj, var_to_read, data) + endif + + ! Close the file-set. + if (check_if_open(fileobj)) call fms2_close_file(fileobj) + else + call read_data(filename, fieldname, data, timelevel=timelevel, no_domain=.true.) + endif end subroutine MOM_read_data_0d_int @@ -943,8 +1042,35 @@ subroutine MOM_read_data_1d_int(filename, fieldname, data, timelevel) integer, dimension(:), intent(inout) :: data !< The 1-dimensional array into which the data integer, optional, intent(in) :: timelevel !< The time level in the file to read - !### This needs an FMS2 variant, eventually. - call read_data(filename, fieldname, data, timelevel=timelevel, no_domain=.true.) + ! Local variables + type(FmsNetcdfFile_t) :: fileObj ! A handle to a non-domain-decomposed file for obtaining information + ! about the exiting time axis entries in append mode. + logical :: has_time_dim ! True if the variable has an unlimited time axis. + character(len=96) :: var_to_read ! Name of variable to read from the netcdf file + logical :: success ! If true, the file was opened successfully + + if (FMS2_reads) then + ! Open the FMS2 file-set. + success = fms2_open_file(fileObj, trim(filename), "read") + if (.not.success) call MOM_error(FATAL, "Failed to open "//trim(filename)) + + ! Find the matching case-insensitive variable name in the file, and determine whether it + ! has a time dimension. + call find_varname_in_file(fileObj, fieldname, "MOM_read_data_1d_int: ", filename, & + var_to_read, has_time_dim, timelevel) + + ! Read the data. + if (present(timelevel) .and. has_time_dim) then + call fms2_read_data(fileobj, var_to_read, data, unlim_dim_level=timelevel) + else + call fms2_read_data(fileobj, var_to_read, data) + endif + + ! Close the file-set. + if (check_if_open(fileobj)) call fms2_close_file(fileobj) + else + call read_data(filename, fieldname, data, timelevel=timelevel, no_domain=.true.) + endif end subroutine MOM_read_data_1d_int @@ -983,13 +1109,13 @@ subroutine MOM_read_vector_2d(filename, u_fieldname, v_fieldname, u_data, v_data if (FMS2_reads) then ! Open the FMS2 file-set. - success = fms2_open_file(fileobj, filename, "read", MOM_domain%mpp_domain, is_restart=.false.) + success = fms2_open_file(fileobj, filename, "read", MOM_domain%mpp_domain) if (.not.success) call MOM_error(FATAL, "Failed to open "//trim(filename)) ! Find the matching case-insensitive u- and v-variable names in the file and prepare to read them. - call prepare_to_read_var(fileobj, u_fieldname, MOM_domain, "MOM_read_vector_2d: ", filename, & + call prepare_to_read_var(fileobj, u_fieldname, "MOM_read_vector_2d: ", filename, & u_var, has_time_dim, timelevel, position=u_pos) - call prepare_to_read_var(fileobj, v_fieldname, MOM_domain, "MOM_read_vector_2d: ", filename, & + call prepare_to_read_var(fileobj, v_fieldname, "MOM_read_vector_2d: ", filename, & v_var, has_time_dim, timelevel, position=v_pos) ! Read the u-data and v-data. There would already been an error message for one @@ -1053,13 +1179,13 @@ subroutine MOM_read_vector_3d(filename, u_fieldname, v_fieldname, u_data, v_data if (FMS2_reads) then ! Open the FMS2 file-set. - success = fms2_open_file(fileobj, filename, "read", MOM_domain%mpp_domain, is_restart=.false.) + success = fms2_open_file(fileobj, filename, "read", MOM_domain%mpp_domain) if (.not.success) call MOM_error(FATAL, "Failed to open "//trim(filename)) ! Find the matching case-insensitive u- and v-variable names in the file and prepare to read them. - call prepare_to_read_var(fileobj, u_fieldname, MOM_domain, "MOM_read_vector_3d: ", filename, & + call prepare_to_read_var(fileobj, u_fieldname, "MOM_read_vector_3d: ", filename, & u_var, has_time_dim, timelevel, position=u_pos) - call prepare_to_read_var(fileobj, v_fieldname, MOM_domain, "MOM_read_vector_3d: ", filename, & + call prepare_to_read_var(fileobj, v_fieldname, "MOM_read_vector_3d: ", filename, & v_var, has_time_dim, timelevel, position=v_pos) ! Read the u-data and v-data, dangerously assuming either both or neither have time dimensions. @@ -1363,7 +1489,6 @@ subroutine write_metadata_field(IO_handle, field, axes, name, units, longname, & call register_variable_attribute(IO_handle%fileobj, trim(name), "checksum", & trim(checksum_string), len_trim(checksum_string)) endif - !### Add more attributes if they are present; remove attributes that are never used. else do i=1,ndims ; mpp_axes(i) = axes(i)%AT ; enddo call mpp_write_meta(IO_handle%unit, field%FT, mpp_axes, name, units, longname, & diff --git a/config_src/infra/FMS2/MOM_read_data_fms2.F90 b/config_src/infra/FMS2/MOM_read_data_fms2.F90 index 4732c019f4..c632e95177 100644 --- a/config_src/infra/FMS2/MOM_read_data_fms2.F90 +++ b/config_src/infra/FMS2/MOM_read_data_fms2.F90 @@ -1,36 +1,31 @@ -!> This module contains routines that wrap the fms2 read_data calls +!> This module contains routines that encapsulate common preparatory work for FMS2 read_data calls module MOM_read_data_fms2 ! This file is part of MOM6. See LICENSE.md for the license. use MOM_error_infra, only : MOM_error=>MOM_err, NOTE, FATAL, WARNING, is_root_PE -use MOM_domain_infra, only : MOM_domain_type, AGRID, BGRID_NE, CGRID_NE -use MOM_domain_infra, only : domain2d, CENTER, CORNER, NORTH_FACE, EAST_FACE +use MOM_domain_infra, only : CENTER, CORNER, NORTH_FACE, EAST_FACE use MOM_string_functions, only : lowercase -use fms2_io_mod, only : FmsNetcdfDomainFile_t, FmsNetcdfFile_t -use fms2_io_mod, only : fms2_open_file => open_file, fms2_close_file => close_file -use fms2_io_mod, only : get_num_variables, get_variable_names, check_if_open -use fms2_io_mod, only : read_data, variable_exists, get_variable_size, get_variable_units -use fms2_io_mod, only : get_variable_attribute, attribute_exists => variable_att_exists +use fms2_io_mod, only : FmsNetcdfDomainFile_t, FmsNetcdfFile_t, check_if_open +use fms2_io_mod, only : variable_exists, get_num_variables, get_variable_names +use fms2_io_mod, only : get_variable_size, get_variable_units +use fms2_io_mod, only : get_variable_attribute, variable_att_exists use fms2_io_mod, only : get_variable_num_dimensions, get_variable_dimension_names use fms2_io_mod, only : is_dimension_unlimited, get_dimension_size use fms2_io_mod, only : is_dimension_registered, register_axis implicit none ; private -public prepare_to_read_var -! public MOM_read_data_scalar, MOM_read_data_2d_noDD, MOM_read_data_1d_noDD +public prepare_to_read_var, find_varname_in_file contains -!> Find the case-insensitive name match with a variable in a domain-decomposed file-set -!! opening the file(s) as necessary, prepare FMS2 to read this variable, and return some -!! information needed to call read_data correctly for this variable and file. -subroutine prepare_to_read_var(fileobj, fieldname, domain, err_header, filename, var_to_read, & +!> Find the case-insensitive name match with a variable in an open domain-decomposed file-set, +!! prepare FMS2 to read this variable, and return some information needed to call fms2_read_data +!! correctly for this variable and file. +subroutine prepare_to_read_var(fileobj, fieldname, err_header, filename, var_to_read, & has_time_dim, timelevel, position) - type(FmsNetcdfDomainFile_t), intent(inout) :: fileobj !< A handle to an FMS2 file object, that - !! will be opened if necessary + type(FmsNetcdfDomainFile_t), intent(inout) :: fileobj !< An FMS2 handle to an open domain-decomposed file character(len=*), intent(in) :: fieldname !< The variable name to seek in the file - type(MOM_domain_type), intent(in) :: domain !< MOM domain attribute with the mpp_domain decomposition character(len=*), intent(in) :: err_header !< A descriptive prefix for error messages character(len=*), intent(in) :: filename !< The name of the file to read character(len=*), intent(out) :: var_to_read !< The variable name to read from the file @@ -39,33 +34,30 @@ subroutine prepare_to_read_var(fileobj, fieldname, domain, err_header, filename, integer, optional, intent(in) :: position !< A flag indicating where this variable is discretized ! Local variables - logical :: file_open_success !.true. if call to open_file is successful logical :: variable_found ! Is a case-insensitive version of the variable found in the netCDF file? - character(len=96), allocatable, dimension(:) :: var_names !< array for names of variables in a netCDF - !! file opened to read - character(len=96), allocatable :: dim_names(:) ! variable dimension names - integer :: nvars ! The number of variables in the file. - integer :: i, dim_unlim_size, num_var_dims, time_dim + character(len=256), allocatable, dimension(:) :: var_names ! The names of all the variables in the netCDF file + character(len=256), allocatable :: dim_names(:) ! The names of a variable's dimensions + integer :: nvars ! The number of variables in the file. + integer :: dim_unlim_size ! The current size of the unlimited (time) dimension in the file. + integer :: num_var_dims ! The number of dimensions a variable has in the file. + integer :: time_dim ! The position of the unlimited (time) dimension for a variable, or -1 + ! if it has no unlimited dimension. + integer :: i ! Open the file if necessary - if (.not.(check_if_open(fileobj))) then - file_open_success = fms2_open_file(fileobj, filename, "read", domain%mpp_domain, is_restart=.false.) - if (.not.file_open_success) call MOM_error(FATAL, trim(err_header)//" failed to open "//trim(filename)) - endif + if (.not.check_if_open(fileobj)) & + call MOM_error(FATAL, trim(err_header)//trim(filename)//" was not open in call to prepare_to_read_var.") ! Search for the variable in the file, looking for the case-sensitive name first. if (variable_exists(fileobj, trim(fieldname))) then var_to_read = trim(fieldname) - variable_found = .true. else ! Look for case-insensitive variable name matches. - var_to_read = "" - variable_found = .false. - nvars = get_num_variables(fileobj) if (nvars < 1) call MOM_error(FATAL, "nvars is less than 1 for file "//trim(filename)) allocate(var_names(nvars)) call get_variable_names(fileobj, var_names) + variable_found = .false. do i=1,nvars if (lowercase(trim(var_names(i))) == lowercase(trim(fieldname))) then variable_found = .true. @@ -116,8 +108,6 @@ subroutine prepare_to_read_var(fileobj, fieldname, domain, err_header, filename, end subroutine prepare_to_read_var !> register axes associated with a variable from a domain-decomposed netCDF file -!> @note The user must specify units for variables with longitude/x-axis and/or latitude/y-axis axes -!! to obtain the correct domain decomposition for the data buffer. subroutine MOM_register_variable_axes(fileObj, variableName, filename, position) type(FmsNetcdfDomainFile_t), intent(inout) :: fileObj !< Handle to an open FMS2 netCDF file object character(len=*), intent(in) :: variableName !< name of the variable @@ -125,19 +115,15 @@ subroutine MOM_register_variable_axes(fileObj, variableName, filename, position) integer, optional, intent(in) :: position !< A flag indicating where this data is discretized ! Local variables - character(len=40) :: units ! units corresponding to a specific variable dimension - character(len=40), allocatable, dimension(:) :: dim_names ! variable dimension names + character(len=256), allocatable, dimension(:) :: dim_names ! variable dimension names integer, allocatable, dimension(:) :: dimSizes ! variable dimension sizes logical, allocatable, dimension(:) :: is_x ! Is this a (likely domain-decomposed) x-axis logical, allocatable, dimension(:) :: is_y ! Is this a (likely domain-decomposed) y-axis logical, allocatable, dimension(:) :: is_t ! Is this a time axis or another unlimited axis integer :: ndims ! number of dimensions + integer :: xPos, yPos ! Discrete positions for x and y axes. Default is CENTER integer :: i - integer :: xPos, yPos ! domain positions for x and y axes. Default is CENTER - if (.not. check_if_open(fileObj)) call MOM_error(FATAL,"MOM_axis:register_variable_axes: The fileObj has "// & - "not been opened. Call fms2_open_file(fileObj,...) before "// & - "passing the fileObj argument to this function.") xPos = CENTER ; yPos = CENTER if (present(position)) then if ((position == CORNER) .or. (position == EAST_FACE)) xPos = EAST_FACE @@ -168,9 +154,7 @@ subroutine MOM_register_variable_axes(fileObj, variableName, filename, position) endif enddo - deallocate(dimSizes) - deallocate(dim_names) - deallocate(is_x, is_y, is_t) + deallocate(dimSizes, dim_names, is_x, is_y, is_t) end subroutine MOM_register_variable_axes !> Determine whether a variable's axes are associated with x-, y- or time-dimensions. Other @@ -184,11 +168,12 @@ subroutine categorize_axes(fileObj, filename, ndims, dim_names, is_x, is_y, is_t logical, dimension(ndims), intent(out) :: is_y !< Indicates if each dimension a (likely decomposed) y-axis logical, dimension(ndims), intent(out) :: is_t !< Indicates if each dimension unlimited (usually time) axis - integer :: i - character(len=256) :: cartesian ! A flag indicating a Cartesian direction - usually a single character. + ! Local variables + character(len=128) :: cartesian ! A flag indicating a Cartesian direction - usually a single character. character(len=512) :: dim_list ! A concatenated list of dimension names. - character(len=40) :: units ! units corresponding to a specific variable dimension + character(len=128) :: units ! units corresponding to a specific variable dimension logical :: x_found, y_found ! Indicate whether an x- or y- dimension have been found. + integer :: i x_found = .false. ; y_found = .false. is_x(:) = .false. ; is_y(:) = .false. @@ -197,16 +182,12 @@ subroutine categorize_axes(fileObj, filename, ndims, dim_names, is_x, is_y, is_t ! First look for indicative variable attributes if (.not.is_t(i)) then if (variable_exists(fileobj, trim(dim_names(i)))) then - if (attribute_exists(fileobj, trim(dim_names(i)), "cartesian_axis")) then + if (variable_att_exists(fileobj, trim(dim_names(i)), "cartesian_axis")) then call get_variable_attribute(fileobj, trim(dim_names(i)), "cartesian_axis", cartesian) cartesian = adjustl(cartesian) if ((index(cartesian, "X") == 1) .or. (index(cartesian, "x") == 1)) is_x(i) = .true. if ((index(cartesian, "Y") == 1) .or. (index(cartesian, "y") == 1)) is_y(i) = .true. if ((index(cartesian, "T") == 1) .or. (index(cartesian, "t") == 1)) is_t(i) = .true. - ! if (is_root_pe() .and. is_x(i)) & - ! call MOM_error(NOTE, "X-dimension determined from cartesian_axis for "//trim(dim_names(i))) - ! if (is_root_pe() .and. is_y(i)) & - ! call MOM_error(NOTE, "Y-dimension determined from cartesian_axis for "//trim(dim_names(i))) endif endif endif @@ -308,199 +289,44 @@ subroutine categorize_axis_from_name(dimname, is_x, is_y) end subroutine categorize_axis_from_name -!===== Everything below this pertains to reading non-decomposed variables ===! -!===== using FMS2 interfaces will probably be discarded eventually. =========! - -!!> This routine calls the fms_io read_data subroutine to read a scalar (0-D) field named "fieldname" -!! from file "filename". -subroutine MOM_read_data_scalar(filename, fieldname, data, timelevel, scale, leave_file_open) - character(len=*), intent(in) :: filename !< The name of the file to read - character(len=*), intent(in) :: fieldname !< The variable name of the data in the file - real, intent(inout) :: data !< The variable to read from read_data - integer, optional, intent(in) :: timelevel !< time level to read - real, optional, intent(in) :: scale !< A scaling factor that the field is multiplied by - logical, optional, intent(in) :: leave_file_open !< if .true., leave file open - - ! Local variables - type(FmsNetcdfFile_t) :: fileobj ! A handle to a simple netCDF file - logical :: close_the_file ! indicates whether to close the file after read_data is called. - character(len=96) :: var_to_read ! variable to read from the netcdf file - character(len=48) :: err_header ! A preamble for error messages - - err_header = "MOM_read_data_fms2:MOM_read_data_scalar: " - - ! Find the matching variable name in the file, opening it and reading metadata if necessary. - call find_varname_in_file(fileobj, fieldname, err_header, filename, var_to_read) - - ! read the data - if (present(timelevel)) then - call read_data(fileobj, trim(var_to_read), data, unlim_dim_level=timelevel) - else - call read_data(fileobj, trim(var_to_read), data) - endif - - ! Close the file, if necessary - close_the_file = .true. ; if (present(leave_file_open)) close_the_file = .not.(leave_file_open) - if (close_the_file .and. check_if_open(fileobj)) call fms2_close_file(fileobj) - - ! Rescale the data that was read if necessary. - if (present(scale)) then ; if (scale /= 1.0) then - data = scale*data - endif ; endif - -end subroutine MOM_read_data_scalar - -!> This routine calls the fms_io read_data subroutine to read 1-D non-domain-decomposed data field named "fieldname" -!! from file "filename". The routine multiplies the data by "scale" if the optional argument is included in the call. -subroutine MOM_read_data_1d_noDD(filename, fieldname, data, start_index, & - edge_lengths, timelevel, scale, leave_file_open) - character(len=*), intent(in) :: filename !< The name of the file to read - character(len=*), intent(in) :: fieldname !< The variable name of the data in the file - real, dimension(:), intent(inout) :: data !< The 1-dimensional data array to pass to read_data - integer, dimension(1), optional, intent(in) :: start_index !< starting index of data buffer. Default is 1 - integer, dimension(1), optional, intent(in) :: edge_lengths !< number of data values to read in; default is - !! the variable size - integer, optional, intent(in) :: timelevel !< time level to read - real, optional, intent(in) :: scale !< A scaling factor that the field is multiplied by - logical, optional, intent(in) :: leave_file_open !< if .true., leave file open - - ! Local variables - type(FmsNetcdfFile_t) :: fileobj ! A handle to a simple netCDF file - logical :: close_the_file ! indicates whether to close the file after read_data is called. - integer :: time_dim ! The dimension position of a variables unlimited time axis, or -1 if it has none. - integer, parameter :: ndim = 1 ! The dimensionality of the array being read - integer, dimension(ndim) :: start, nread ! indices for first data value and number of values to read - character(len=96) :: var_to_read ! variable to read from the netcdf file - character(len=48) :: err_header ! A preamble for error messages - - err_header = "MOM_read_data_fms2:MOM_read_data_1d_noDD: " - - ! Find the matching case-insensitive variable name in the file, opening the file if necessary. - call find_varname_in_file(fileobj, fieldname, err_header, filename, var_to_read) - - ! set the start and nread values that will be passed as the read_data corner and edge_lengths arguments - start(:) = 1 ; if (present(start_index)) start(:) = start_index(:) - nread(:) = shape(data) ; if (present(edge_lengths)) nread(:) = edge_lengths(:) - - time_dim = -1 - if (present(timelevel)) then - time_dim = get_time_dim(fileobj, var_to_read, err_header, filename, timelevel) - if (time_dim == ndim) then ; nread(ndim) = 1 ; start(ndim) = timelevel ; endif - endif - - ! read the data - if (time_dim > 0) then - call read_data(fileobj, trim(var_to_read), data, corner=start, edge_lengths=nread, & - unlim_dim_level=timelevel) - else - call read_data(fileobj, trim(var_to_read), data, corner=start, edge_lengths=nread) - endif - - ! Close the file, if necessary - close_the_file = .true. ; if (present(leave_file_open)) close_the_file = .not.(leave_file_open) - if (close_the_file .and. check_if_open(fileobj)) call fms2_close_file(fileobj) - - ! Rescale the data that was read if necessary. - if (present(scale)) then ; if (scale /= 1.0) then - data(:) = scale*data(:) - endif ; endif - -end subroutine MOM_read_data_1d_noDD - -!> This routine calls the fms_io read_data subroutine to read a 2-D non-domain-decomposed data field named "fieldname" -!! from file "filename". The routine multiplies the data by "scale" if the optional argument is included in the call. -subroutine MOM_read_data_2d_noDD(filename, fieldname, data, start_index, & - edge_lengths, timelevel, position, scale, leave_file_open) - character(len=*), intent(in) :: filename !< The name of the file to read - character(len=*), intent(in) :: fieldname !< The variable name of the data in the file - real, dimension(:,:), intent(inout) :: data !< The 2-dimensional data array to pass to read_data - integer, dimension(2), optional, intent(in) :: start_index !< starting indices of data buffer. Default is 1 - integer, dimension(2), optional, intent(in) :: edge_lengths !< number of data values to read in. - !! Default values are the variable dimension sizes - integer, optional, intent(in) :: timelevel !< time level to read - integer, optional, intent(in) :: position !< A flag indicating where this data is located - real, optional, intent(in) :: scale !< A scaling factor that the field is multiplied by - logical, optional, intent(in) :: leave_file_open !< if .true., leave file open - - ! Local variables - type(FmsNetcdfFile_t) :: fileobj ! A handle to a simple netCDF file - logical :: close_the_file ! indicates whether to close the file after read_data is called. - integer :: time_dim ! The dimension position of a variables unlimited time axis, or -1 if it has none. - integer, parameter :: ndim = 2 ! The dimensionality of the array being read - integer, dimension(ndim) :: start, nread ! indices for first data value and number of values to read - character(len=96) :: var_to_read ! variable to read from the netcdf file - character(len=48) :: err_header ! A preamble for error messages - - err_header = "MOM_read_data_fms2:MOM_read_data_2d_DD: " - - ! Find the matching case-insensitive variable name in the file, opening the file if necessary. - call find_varname_in_file(fileobj, fieldname, err_header, filename, var_to_read) - - ! set the start and nread values that will be passed as the read_data corner and edge_lengths arguments - start(:) = 1 ; if (present(start_index)) start(:) = start_index(:) - nread(:) = shape(data) ; if (present(edge_lengths)) nread(:) = edge_lengths(:) - - time_dim = -1 - if (present(timelevel)) then - time_dim = get_time_dim(fileobj, var_to_read, err_header, filename, timelevel) - if (time_dim == ndim) then ; nread(ndim) = 1 ; start(ndim) = timelevel ; endif - endif - - ! read the data - if (time_dim > 0) then - call read_data(fileobj, trim(var_to_read), data, corner=start, edge_lengths=nread, & - unlim_dim_level=timelevel) - else - call read_data(fileobj, trim(var_to_read), data, corner=start, edge_lengths=nread) - endif - - ! Close the file, if necessary - close_the_file = .true. ; if (present(leave_file_open)) close_the_file = .not.(leave_file_open) - if (close_the_file .and. check_if_open(fileobj)) call fms2_close_file(fileobj) - - ! Rescale the data that was read if necessary. - if (present(scale)) then ; if (scale /= 1.0) then - data(:,:) = scale*data(:,:) - endif ; endif - -end subroutine MOM_read_data_2d_noDD - !> Find the case-sensitive name of the variable in a netCDF file with a case-insensitive name match. -subroutine find_varname_in_file(fileobj, fieldname, err_header, filename, var_to_read) - type(FmsNetcdfFile_t), intent(inout) :: fileobj !< A handle to a file object, that - !! will be opened if necessary +!! Optionally also determine whether this variable has an unlimited time dimension. +subroutine find_varname_in_file(fileobj, fieldname, err_header, filename, var_to_read, has_time_dim, timelevel) + type(FmsNetcdfFile_t), intent(inout) :: fileobj !< An FMS2 handle to an open NetCDF file character(len=*), intent(in) :: fieldname !< The variable name to seek in the file character(len=*), intent(in) :: err_header !< A descriptive prefix for error messages character(len=*), intent(in) :: filename !< The name of the file to read character(len=*), intent(out) :: var_to_read !< The variable name to read from the file + logical, optional, intent(out) :: has_time_dim !< Indicates whether fieldname has a time dimension + integer, optional, intent(in) :: timelevel !< A time level to read ! Local variables - logical :: file_open_success !.true. if call to open_file is successful logical :: variable_found ! Is a case-insensitive version of the variable found in the netCDF file? - character(len=96), allocatable, dimension(:) :: var_names !< array for names of variables in a netCDF - !! file opened to read - integer :: nvars ! The number of variables in the file. + character(len=256), allocatable, dimension(:) :: var_names ! The names of all the variables in the netCDF file + character(len=256), allocatable :: dim_names(:) ! The names of a variable's dimensions + integer :: nvars ! The number of variables in the file + integer :: dim_unlim_size ! The current size of the unlimited (time) dimension in the file. + integer :: num_var_dims ! The number of dimensions a variable has in the file. + integer :: time_dim ! The position of the unlimited (time) dimension for a variable, or -1 + ! if it has no unlimited dimension. integer :: i - var_to_read = "" - ! Open the file if necessary - if (.not.(check_if_open(fileobj))) then - file_open_success = fms2_open_file(fileobj, filename, "read", is_restart=.false.) - if (.not.file_open_success) call MOM_error(FATAL, trim(err_header)//" failed to open "//trim(filename)) - endif + if (.not.check_if_open(fileobj)) & + call MOM_error(FATAL, trim(err_header)//trim(filename)//" was not open in call to find_varname_in_file.") - if (variable_exists(fileobj, fieldname)) then - var_to_read = fieldname - else - variable_found = .false. + ! Search for the variable in the file, looking for the case-sensitive name first. + if (variable_exists(fileobj, trim(fieldname))) then + var_to_read = trim(fieldname) + else ! Look for case-insensitive variable name matches. nvars = get_num_variables(fileobj) if (nvars < 1) call MOM_error(FATAL, "nvars is less than 1 for file "//trim(filename)) allocate(var_names(nvars)) call get_variable_names(fileobj, var_names) ! search for the variable in the file + variable_found = .false. do i=1,nvars if (lowercase(trim(var_names(i))) == lowercase(trim(fieldname))) then variable_found = .true. @@ -513,43 +339,38 @@ subroutine find_varname_in_file(fileobj, fieldname, err_header, filename, var_to deallocate(var_names) endif -end subroutine find_varname_in_file + ! FMS2 can not handle a timelevel argument if the variable does not have one in the file, + ! so some error checking and logic are required. + if (present(has_time_dim) .or. present(timelevel)) then + time_dim = -1 -!> Return the number of the time dimension for a variable in an open non-domain-decomposed file, -!! or -1 if it has no time (or other unlimited) dimension. -integer function get_time_dim(fileobj, var_to_read, err_header, filename, timelevel) - type(FmsNetcdfFile_t), intent(in) :: fileobj !< A handle to an open file object - character(len=*), intent(in) :: var_to_read !< The variable name to read from the file - character(len=*), intent(in) :: err_header !< A descriptive prefix for error messages - character(len=*), intent(in) :: filename !< The name of the file to read - integer, optional, intent(in) :: timelevel !< A time level to read + num_var_dims = get_variable_num_dimensions(fileobj, trim(var_to_read)) + allocate(dim_names(num_var_dims)) ; dim_names(:) = "" + call get_variable_dimension_names(fileobj, trim(var_to_read), dim_names) - ! Local variables - integer :: i, dim_unlim_size, num_var_dims - character(len=96), allocatable :: dim_names(:) ! variable dimension names - - num_var_dims = get_variable_num_dimensions(fileobj, trim(var_to_read)) - allocate(dim_names(num_var_dims)) ; dim_names(:) = "" - call get_variable_dimension_names(fileobj, trim(var_to_read), dim_names) - - get_time_dim = -1 - do i=1,num_var_dims - if (is_dimension_unlimited(fileobj, dim_names(i))) then - get_time_dim = i - if (present(timelevel)) then - call get_dimension_size(fileobj, dim_names(i), dim_unlim_size) - if (timelevel > dim_unlim_size) call MOM_error(FATAL, trim(err_header)//& - "Attempting to read a time level of "//trim(var_to_read)//& - " that exceeds the size of "//trim(filename)) + do i=1,num_var_dims + if (is_dimension_unlimited(fileobj, dim_names(i))) then + time_dim = i + if (present(timelevel)) then + call get_dimension_size(fileobj, dim_names(i), dim_unlim_size) + if ((timelevel > dim_unlim_size) .and. is_root_PE()) call MOM_error(FATAL, & + trim(err_header)//"Attempting to read a time level of "//trim(var_to_read)//& + " that exceeds the size of the time dimension in "//trim(filename)) + endif + exit endif - exit - endif - enddo - if (get_time_dim < 0) & - call MOM_error(WARNING, trim(err_header)//"time level specified, but the variable "//& + enddo + deallocate(dim_names) + + if (present(timelevel) .and. (time_dim < 0) .and. is_root_PE()) & + call MOM_error(WARNING, trim(err_header)//"time level specified, but the variable "//& trim(var_to_read)//" does not have an unlimited dimension in "//trim(filename)) - deallocate(dim_names) + if ((.not.present(timelevel)) .and. (time_dim > 0) .and. is_root_PE()) & + call MOM_error(WARNING, trim(err_header)//"The variable "//trim(var_to_read)//& + " has an unlimited dimension in "//trim(filename)//" but no time level is specified.") + if (present(has_time_dim)) has_time_dim = (time_dim > 0) + endif -end function get_time_dim +end subroutine find_varname_in_file end module MOM_read_data_fms2