Skip to content

Commit

Permalink
Restore functionality for reading slices of 3d data with FMS2
Browse files Browse the repository at this point in the history
   - The recent MOM_io modifications in support of FMS2_io accidentally
    removed support for reading on-grid data (same horizontal grid as
    model) k-slices. This is needed in some configurations in the model
    state initialization.
  • Loading branch information
MJHarrison-GFDL committed Jul 19, 2023
1 parent 2342a58 commit 0691b12
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 8 deletions.
23 changes: 22 additions & 1 deletion config_src/infra/FMS2/MOM_coms_infra.F90
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module MOM_coms_infra
!> Communicate an array, string or scalar from one PE to others
interface broadcast
module procedure broadcast_char, broadcast_int32_0D, broadcast_int64_0D, broadcast_int1D
module procedure broadcast_real0D, broadcast_real1D, broadcast_real2D
module procedure broadcast_real0D, broadcast_real1D, broadcast_real2D, broadcast_real3D
end interface broadcast

!> Compute a checksum for a field distributed over a PE list. If no PE list is
Expand Down Expand Up @@ -260,6 +260,27 @@ subroutine broadcast_real2D(dat, length, from_PE, PElist, blocking)

end subroutine broadcast_real2D

!> Communicate a 3-D array of reals from one PE to others
subroutine broadcast_real3D(dat, length, from_PE, PElist, blocking)
real, dimension(:,:,:), intent(inout) :: dat !< The data to communicate and destination
integer, intent(in) :: length !< The total number of data elements
integer, optional, intent(in) :: from_PE !< The source PE, by default the root PE
integer, optional, intent(in) :: PElist(:) !< The list of participating PEs, by default the
!! active PE set as previously set via Set_PElist.
logical, optional, intent(in) :: blocking !< If true, barriers are added around the call

integer :: src_PE ! The processor that is sending the data
logical :: do_block ! If true add synchronizing barriers

do_block = .false. ; if (present(blocking)) do_block = blocking
if (present(from_PE)) then ; src_PE = from_PE ; else ; src_PE = root_PE() ; endif

if (do_block) call mpp_sync(PElist)
call mpp_broadcast(dat, length, src_PE, PElist)
if (do_block) call mpp_sync_self(PElist)

end subroutine broadcast_real3D

! field_chksum wrappers

!> Compute a checksum for a field distributed over a PE list. If no PE list is
Expand Down
70 changes: 69 additions & 1 deletion config_src/infra/FMS2/MOM_io_infra.F90
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ module MOM_io_infra
!> Read a data field from a file
interface read_field
module procedure read_field_4d
module procedure read_field_3d
module procedure read_field_3d, read_field_3d_region
module procedure read_field_2d, read_field_2d_region
module procedure read_field_1d, read_field_1d_int
module procedure read_field_0d, read_field_0d_int
Expand Down Expand Up @@ -1030,6 +1030,74 @@ subroutine read_field_3d(filename, fieldname, data, MOM_Domain, &

end subroutine read_field_3d

!> This routine uses the fms_io subroutine read_data to read a region from a distributed or
!! global 3-D data field named "fieldname" from file "filename".
subroutine read_field_3d_region(filename, fieldname, data, start, nread, MOM_domain, &
no_domain, scale)
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 3-dimensional array into which the data
!! should be read
integer, dimension(:), intent(in) :: start !< The starting index to read in each of 3
!! dimensions. For this 3-d read, the
!! 4th value is always 1.
integer, dimension(:), intent(in) :: nread !< The number of points to read in each of 4
!! dimensions. For this 3-d read, the
!! 4th values are always 1.
type(MOM_domain_type), &
optional, intent(in) :: MOM_Domain !< The MOM_Domain that describes the decomposition
logical, optional, intent(in) :: no_domain !< If present and true, this variable does not
!! use domain decomposion.
real, optional, intent(in) :: scale !< A scaling factor that the field is multiplied
!! by before it is returned.

! 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
! 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, "read_field_2d_region: ", &
filename, var_to_read)

! Read the data.
call fms2_read_data(fileobj_DD, var_to_read, data, corner=start(1:3), edge_lengths=nread(1:3))

! Close the file-set.
if (check_if_open(fileobj_DD)) call fms2_close_file(fileobj_DD)
else
! 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, "read_field_2d_region: ", filename, var_to_read)

! Read the data.
call fms2_read_data(fileobj, var_to_read, data, corner=start(1:3), edge_lengths=nread(1:3))

! Close the file-set.
if (check_if_open(fileobj)) call fms2_close_file(fileobj)
endif

if (present(scale)) then ; if (scale /= 1.0) then
if (present(MOM_Domain)) then
call rescale_comp_data(MOM_Domain, data, scale)
else
! Dangerously rescale the whole array
data(:,:,:) = scale*data(:,:,:)
endif
endif ; endif

end subroutine read_field_3d_region

!> This routine uses the fms_io subroutine read_data to read a distributed
!! 4-D data field named "fieldname" from file "filename". Valid values for
!! "position" include CORNER, CENTER, EAST_FACE and NORTH_FACE.
Expand Down
20 changes: 16 additions & 4 deletions src/framework/MOM_horizontal_regridding.F90
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ subroutine horiz_interp_and_extrap_tracer_record(filename, varnam, recnum, G, tr
real, dimension(:,:), allocatable :: tr_in !< A 2-d array for holding input data on its
!! native horizontal grid, with units that change
!! as the input data is interpreted [a] then [A ~> a]
real, dimension(:,:,:), allocatable :: tr_in_full !< A 3-d array for holding input data on the
!! model horizontal grid, with units that change
!! as the input data is interpreted [a] then [A ~> a]
real, dimension(:,:), allocatable :: tr_inp !< Native horizontal grid data extended to the poles
!! with units that change as the input data is
!! interpreted [a] then [A ~> a]
Expand Down Expand Up @@ -448,6 +451,7 @@ subroutine horiz_interp_and_extrap_tracer_record(filename, varnam, recnum, G, tr

if (is_ongrid) then
allocate(tr_in(is:ie,js:je), source=0.0)
allocate(tr_in_full(is:ie,js:je,kd), source=0.0)
allocate(mask_in(is:ie,js:je), source=0.0)
else
call horizontal_interp_init()
Expand All @@ -470,14 +474,19 @@ subroutine horiz_interp_and_extrap_tracer_record(filename, varnam, recnum, G, tr

! Loop through each data level and interpolate to model grid.
! After interpolating, fill in points which will be needed to define the layers.

if (is_ongrid) then
start(1) = is+G%HI%idg_offset ; start(2) = js+G%HI%jdg_offset ; start(3) = 1
count(1) = ie-is+1 ; count(2) = je-js+1 ; count(3) = kd ; start(4) = 1 ; count(4) = 1
call MOM_read_data(trim(filename), trim(varnam), tr_in_full, start, count, G%Domain)
endif

do k=1,kd
mask_in(:,:) = 0.0
tr_out(:,:) = 0.0

if (is_ongrid) then
start(1) = is+G%HI%idg_offset ; start(2) = js+G%HI%jdg_offset ; start(3) = k
count(1) = ie-is+1 ; count(2) = je-js+1 ; count(3) = 1 ; start(4) = 1 ; count(4) = 1
call MOM_read_data(trim(filename), trim(varnam), tr_in, start, count, G%Domain)
tr_in(is:ie,js:je) = tr_in_full(is:ie,js:je,k)
do j=js,je
do i=is,ie
if (abs(tr_in(i,j)-missing_val_in) > abs(roundoff*missing_val_in)) then
Expand Down Expand Up @@ -594,7 +603,10 @@ subroutine horiz_interp_and_extrap_tracer_record(filename, varnam, recnum, G, tr

enddo ! kd

deallocate(lon_in, lat_in)
if (allocated(lat_inp)) deallocate(lat_inp)
deallocate(tr_in)
if (allocated(tr_inp)) deallocate(tr_inp)
if (allocated(tr_in_full)) deallocate(tr_in_full)

end subroutine horiz_interp_and_extrap_tracer_record

Expand Down
154 changes: 152 additions & 2 deletions src/framework/MOM_io.F90
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ module MOM_io
module procedure MOM_read_data_2d
module procedure MOM_read_data_2d_region
module procedure MOM_read_data_3d
module procedure MOM_read_data_3d_region
module procedure MOM_read_data_4d
end interface MOM_read_data

Expand Down Expand Up @@ -137,7 +138,7 @@ module MOM_io
interface read_variable
module procedure read_variable_0d, read_variable_0d_int
module procedure read_variable_1d, read_variable_1d_int
module procedure read_variable_2d
module procedure read_variable_2d, read_variable_3d
end interface read_variable

!> Read a global or variable attribute from a named netCDF file using netCDF calls
Expand Down Expand Up @@ -1161,7 +1162,7 @@ subroutine read_variable_2d(filename, varname, var, start, nread, ncid_in)
allocate(field_nread(field_ndims))
field_nread(:2) = field_shape(:2)
field_nread(3:) = 1
if (present(nread)) field_shape(:2) = nread(:2)
if (present(nread)) field_nread(:2) = nread(:2)

rc = nf90_get_var(ncid, varid, var, field_start, field_nread)

Expand All @@ -1182,6 +1183,119 @@ subroutine read_variable_2d(filename, varname, var, start, nread, ncid_in)
call broadcast(var, size(var), blocking=.true.)
end subroutine read_variable_2d


subroutine read_variable_3d(filename, varname, var, start, nread, ncid_in)
character(len=*), intent(in) :: filename !< Name of file to be read
character(len=*), intent(in) :: varname !< Name of variable to be read
real, intent(out) :: var(:,:,:) !< Output array of variable [arbitrary]
integer, optional, intent(in) :: start(:) !< Starting index on each axis.
integer, optional, intent(in) :: nread(:) !< Number of values to be read along each axis
integer, optional, intent(in) :: ncid_in !< netCDF ID of an opened file.
!! If absent, the file is opened and closed within this routine.

integer :: ncid, varid
integer :: field_ndims, dim_len
integer, allocatable :: field_dimids(:), field_shape(:)
integer, allocatable :: field_start(:), field_nread(:)
integer :: i, rc
character(len=*), parameter :: hdr = "read_variable_3d: "

! Validate shape of start and nread
if (present(start)) then
if (size(start) < 2) &
call MOM_error(FATAL, hdr // trim(nf90_strerror(rc)) &
// " start must have at least two dimensions.")
endif

if (present(nread)) then
if (size(nread) < 2) &
call MOM_error(FATAL, hdr // trim(nf90_strerror(rc)) &
// " nread must have at least two dimensions.")

if (any(nread(3:) > 1)) &
call MOM_error(FATAL, hdr // trim(nf90_strerror(rc)) &
// " nread may only read a single level in higher dimensions.")
endif

! Since start and nread may be reshaped, we cannot rely on netCDF to ensure
! that their lengths are equivalent, and must do it here.
if (present(start) .and. present(nread)) then
if (size(start) /= size(nread)) &
call MOM_error(FATAL, hdr // trim(nf90_strerror(rc)) &
// " start and nread must have the same length.")
endif

! Open and read `varname` from `filename`
if (is_root_pe()) then
if (present(ncid_in)) then
ncid = ncid_in
else
call open_file_to_Read(filename, ncid)
endif

call get_varid(varname, ncid, filename, varid, match_case=.false.)
if (varid < 0) call MOM_error(FATAL, "Unable to get netCDF varid for "//trim(varname)//&
" in "//trim(filename))

! Query for the dimensionality of the input field
rc = nf90_inquire_variable(ncid, varid, ndims=field_ndims)
if (rc /= NF90_NOERR) call MOM_error(FATAL, hdr // trim(nf90_strerror(rc)) //&
": Difficulties reading "//trim(varname)//" from "//trim(filename))

! Confirm that field is at least 2d
if (field_ndims < 2) &
call MOM_error(FATAL, hdr // trim(nf90_strerror(rc)) // " " // &
trim(varname) // " from " // trim(filename) // " is not a 2D field.")

! If start and nread are present, then reshape them to match field dims
if (present(start) .or. present(nread)) then
allocate(field_shape(field_ndims))
allocate(field_dimids(field_ndims))

rc = nf90_inquire_variable(ncid, varid, dimids=field_dimids)
if (rc /= NF90_NOERR) call MOM_error(FATAL, hdr // trim(nf90_strerror(rc)) //&
": Difficulties reading "//trim(varname)//" from "//trim(filename))

do i = 1, field_ndims
rc = nf90_inquire_dimension(ncid, field_dimids(i), len=dim_len)
if (rc /= NF90_NOERR) &
call MOM_error(FATAL, hdr // trim(nf90_strerror(rc)) &
// ": Difficulties reading dimensions from " // trim(filename))
field_shape(i) = dim_len
enddo

! Reshape start(:) and nreads(:) in case ranks differ
allocate(field_start(field_ndims))
field_start(:) = 1
if (present(start)) then
dim_len = min(size(start), size(field_start))
field_start(:dim_len) = start(:dim_len)
endif

allocate(field_nread(field_ndims))
field_nread(:3) = field_shape(:3)
!field_nread(3:) = 1
if (present(nread)) field_nread(:3) = nread(:3)

rc = nf90_get_var(ncid, varid, var, field_start, field_nread)

deallocate(field_start)
deallocate(field_nread)
deallocate(field_shape)
deallocate(field_dimids)
else
rc = nf90_get_var(ncid, varid, var)
endif

if (rc /= NF90_NOERR) call MOM_error(FATAL, hdr // trim(nf90_strerror(rc)) //&
" Difficulties reading "//trim(varname)//" from "//trim(filename))

if (.not.present(ncid_in)) call close_file_to_read(ncid, filename)
endif

call broadcast(var, size(var), blocking=.true.)
end subroutine read_variable_3d

!> Read a character-string global or variable attribute
subroutine read_attribute_str(filename, attname, att_val, varname, found, all_read, ncid_in)
character(len=*), intent(in) :: filename !< Name of the file to read
Expand Down Expand Up @@ -2198,6 +2312,42 @@ subroutine MOM_read_data_3d(filename, fieldname, data, MOM_Domain, &
endif
end subroutine MOM_read_data_3d

!> Read a 3d region array from file using infrastructure I/O.
subroutine MOM_read_data_3d_region(filename, fieldname, data, start, nread, MOM_domain, &
no_domain, scale, turns)
character(len=*), intent(in) :: filename !< Input filename
character(len=*), intent(in) :: fieldname !< Field variable name
real, dimension(:,:,:), intent(inout) :: data !< Field value in arbitrary units [A ~> a]
integer, dimension(:), intent(in) :: start !< Starting index for each axis.
integer, dimension(:), intent(in) :: nread !< Number of values to read along each axis.
type(MOM_domain_type), optional, intent(in) :: MOM_Domain !< Model domain decomposition
logical, optional, intent(in) :: no_domain !< If true, field does not use
!! domain decomposion.
real, optional, intent(in) :: scale !< A scaling factor that the variable is multiplied by
!! before it is returned to convert from the units in the file
!! to the internal units for this variable [A a-1 ~> 1]
integer, optional, intent(in) :: turns !< Number of quarter turns from
!! input to model grid

integer :: qturns ! Number of quarter turns
real, allocatable :: data_in(:,:,:) ! Field array on the input grid in arbitrary units [A ~> a]

qturns = 0
if (present(turns)) qturns = modulo(turns, 4)

if (qturns == 0) then
call read_field(filename, fieldname, data, start, nread, &
MOM_Domain=MOM_Domain, no_domain=no_domain, scale=scale &
)
else
call allocate_rotated_array(data, [1,1,1], -qturns, data_in)
call read_field(filename, fieldname, data_in, start, nread, &
MOM_Domain=MOM_Domain%domain_in, no_domain=no_domain, scale=scale &
)
call rotate_array(data_in, qturns, data)
deallocate(data_in)
endif
end subroutine MOM_read_data_3d_region

!> Read a 4d array from file using infrastructure I/O.
subroutine MOM_read_data_4d(filename, fieldname, data, MOM_Domain, &
Expand Down

0 comments on commit 0691b12

Please sign in to comment.