FMS  2026.01.01-dev
Flexible Modeling System
fms_io_utils.F90
1 !***********************************************************************
2 !* Apache License 2.0
3 !*
4 !* This file is part of the GFDL Flexible Modeling System (FMS).
5 !*
6 !* Licensed under the Apache License, Version 2.0 (the "License");
7 !* you may not use this file except in compliance with the License.
8 !* You may obtain a copy of the License at
9 !*
10 !* http://www.apache.org/licenses/LICENSE-2.0
11 !*
12 !* FMS is distributed in the hope that it will be useful, but WITHOUT
13 !* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied;
14 !* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15 !* PARTICULAR PURPOSE. See the License for the specific language
16 !* governing permissions and limitations under the License.
17 !***********************************************************************
18 !> @defgroup fms_io_utils_mod fms_io_utils_mod
19 !> @ingroup fms2_io
20 !> @brief Misc. utility routines for use in @ref fms2_io
21 
22 !> @addtogroup fms_io_utils_mod
23 !> @{
24 module fms_io_utils_mod
25 use, intrinsic :: iso_fortran_env, only: error_unit
26 !use mpp_mod, only : get_ascii_file_num_lines_and_length, read_ascii_file
27 #ifdef _OPENMP
28 use omp_lib
29 #endif
30 use mpp_mod
31 use mpp_domains_mod, only: domain2d, domainug, mpp_get_ntile_count, &
33  mpp_get_ug_domain_ntiles, mpp_get_ug_domain_tile_id
34 use platform_mod
35 use fms_string_utils_mod, only: string_copy
36 implicit none
37 private
38 
39 character(len=32), save :: filename_appendix = '' !< Appendix added to the restart filename
40 
41 public :: char_linked_list
42 public :: error
43 public :: file_exists
44 public :: openmp_thread_trap
45 public :: string_copy
46 public :: is_in_list
47 public :: append_to_list
48 public :: destroy_list
51 public :: allocate_array
52 public :: put_array_section
53 public :: get_array_section
54 public :: get_data_type_string
55 public :: open_check
56 public :: string_compare
58 public :: ascii_read
59 public :: parse_mask_table
60 public :: get_mosaic_tile_file
61 public :: get_filename_appendix
62 public :: set_filename_appendix
63 public :: get_instance_filename
65 
66 !> @}
67 
68 !> @brief A linked list of strings
69 !> @ingroup fms_io_utils_mod
71  character(len=128) :: string
72  type(char_linked_list), pointer :: head => null()
73 endtype char_linked_list
74 
75 !> Reads in the mask_table file in the ASCII format from a given path and populates a maskmap array that used for
76 !! domain decomposition.
77 !!
78 !! The maskmap array is a logical array with the same shape as the domain layout, with each index representing a
79 !! specific pe within the decomposition. This array is intended to be used as input for the mpp_define_domain
80 !! routines maskmap argument, to essentially exclude certain pe's from a domain decomposition.
81 !!
82 !! Mask table format is as follows:
83 !!
84 !! <number-of-ranks-to-mask>
85 !! <domain-layout>
86 !! <rank-to-mask>
87 !! <any-additional-ranks-to-mask>
88 !!
89 !! For example, for a domain layout of (2,2),
90 !! -----------------
91 !! | (1,2) | (2,2) |
92 !! | ------------- |
93 !! | (1,1) | (2,1) |
94 !! -----------------
95 !!
96 !! The below mask table masks out 2 pes: the top left corner (1,1) and the bottom right corner (4,4).
97 !! To clarify, this means 2 pes would be required, rather than 4 if the domain decomposition was not masked.
98 !!
99 !! 2
100 !! 2,2
101 !! 1,1
102 !! 2,2
103 !!
104 !! This interface includes support for both 2D and 3D mask tables.
105 !!
106 !> @ingroup fms_io_utils_mod
108  module procedure parse_mask_table_2d
109  module procedure parse_mask_table_3d
110 end interface parse_mask_table
111 
112 !> Constructs the file name to be used when utilizing a multi-tile mosaic.
113 !! This is currenly used in the diag_manager and data_override in so that any output files
114 !! will include the tile number of the writer. Uses the format "filename.tileN.nc" and requires
115 !! the filename to already include the .nc suffix.
116 !> @ingroup fms_io_utils_mod
118  module procedure get_mosaic_tile_file_sg
119  module procedure get_mosaic_tile_file_ug
120 end interface get_mosaic_tile_file
121 
122 !> Interface to allocate data real, integer, or character buffers for use in read/write routines.
123 !! Not meant to be used externally.
124 !> @ingroup fms_io_utils_mod
125 interface allocate_array
126  module procedure allocate_array_i4_kind_1d
127  module procedure allocate_array_i4_kind_2d
128  module procedure allocate_array_i4_kind_3d
129  module procedure allocate_array_i4_kind_4d
130  module procedure allocate_array_i4_kind_5d
131  module procedure allocate_array_i8_kind_1d
132  module procedure allocate_array_i8_kind_2d
133  module procedure allocate_array_i8_kind_3d
134  module procedure allocate_array_i8_kind_4d
135  module procedure allocate_array_i8_kind_5d
136  module procedure allocate_array_r4_kind_1d
137  module procedure allocate_array_r4_kind_2d
138  module procedure allocate_array_r4_kind_3d
139  module procedure allocate_array_r4_kind_4d
140  module procedure allocate_array_r4_kind_5d
141  module procedure allocate_array_r8_kind_1d
142  module procedure allocate_array_r8_kind_2d
143  module procedure allocate_array_r8_kind_3d
144  module procedure allocate_array_r8_kind_4d
145  module procedure allocate_array_r8_kind_5d
146  module procedure allocate_array_char_1d
147  module procedure allocate_array_char_2d
148  module procedure allocate_array_char_3d
149  module procedure allocate_array_char_4d
150  module procedure allocate_array_char_5d
151  module procedure allocate_array_char_6d
152 end interface allocate_array
153 
154 
155 !> Utility routine that copies a smaller array into a larger one using the starting indices and sizes provided.
156 !! Example usage:
157 !!
158 !! call put_array_section( section, array, start, sizes )
159 !!
160 !! Is equivalent to
161 !!
162 !! array (start(1):start(1)+sizes(1), start(2):start(2)+sizes(2), ... ) = section(:,:,...)
163 !!
164 !! This interface supports integers and reals for both 4 and 8 kind, and up to 5 dimensions.
165 !> @ingroup fms_io_utils_mod
167  module procedure put_array_section_i4_kind_1d
168  module procedure put_array_section_i4_kind_2d
169  module procedure put_array_section_i4_kind_3d
170  module procedure put_array_section_i4_kind_4d
171  module procedure put_array_section_i4_kind_5d
172  module procedure put_array_section_i8_kind_1d
173  module procedure put_array_section_i8_kind_2d
174  module procedure put_array_section_i8_kind_3d
175  module procedure put_array_section_i8_kind_4d
176  module procedure put_array_section_i8_kind_5d
177  module procedure put_array_section_r4_kind_1d
178  module procedure put_array_section_r4_kind_2d
179  module procedure put_array_section_r4_kind_3d
180  module procedure put_array_section_r4_kind_4d
181  module procedure put_array_section_r4_kind_5d
182  module procedure put_array_section_r8_kind_1d
183  module procedure put_array_section_r8_kind_2d
184  module procedure put_array_section_r8_kind_3d
185  module procedure put_array_section_r8_kind_4d
186  module procedure put_array_section_r8_kind_5d
187 end interface put_array_section
188 
189 
190 !> Utility routine that copies a subset of data from a larger array into a smaller one using the
191 !! starting indices and sizes provided.
192 !! Example usage:
193 !!
194 !! call get_array_section( section, array, start, sizes )
195 !!
196 !! Is equivalent to
197 !!
198 !! section(:,:,...) = array (start(1):start(1)+sizes(1), start(2):start(2)+sizes(2), ... )
199 !!
200 !! This interface supports integers and reals for both 4 and 8 kind, and up to 5 dimensions.
201 !> @ingroup fms_io_utils_mod
203  module procedure get_array_section_i4_kind_1d
204  module procedure get_array_section_i4_kind_2d
205  module procedure get_array_section_i4_kind_3d
206  module procedure get_array_section_i4_kind_4d
207  module procedure get_array_section_i4_kind_5d
208  module procedure get_array_section_i8_kind_1d
209  module procedure get_array_section_i8_kind_2d
210  module procedure get_array_section_i8_kind_3d
211  module procedure get_array_section_i8_kind_4d
212  module procedure get_array_section_i8_kind_5d
213  module procedure get_array_section_r4_kind_1d
214  module procedure get_array_section_r4_kind_2d
215  module procedure get_array_section_r4_kind_3d
216  module procedure get_array_section_r4_kind_4d
217  module procedure get_array_section_r4_kind_5d
218  module procedure get_array_section_r8_kind_1d
219  module procedure get_array_section_r8_kind_2d
220  module procedure get_array_section_r8_kind_3d
221  module procedure get_array_section_r8_kind_4d
222  module procedure get_array_section_r8_kind_5d
223 end interface get_array_section
224 
225 
226 !> Sets a string describing the datatype corresponding to the sdata argument
227 !! Example:
228 !!
229 !! call get_data_type_string(sdata, string)
230 !!
231 !! String values and associated data types are:
232 !! - "int" integers(kind=4)
233 !! - "i8_kind" integers(kind=8)
234 !! - "float" real(kind=4)
235 !! - "double" real(kind=8)
236 !! - "char" character(len=*)
237 !> @ingroup fms_io_utils_mod
239  module procedure get_data_type_string_0d
240  module procedure get_data_type_string_1d
241  module procedure get_data_type_string_2d
242  module procedure get_data_type_string_3d
243  module procedure get_data_type_string_4d
244  module procedure get_data_type_string_5d
245 end interface get_data_type_string
246 
247 !> @addtogroup fms_io_utils_mod
248 !> @{
249 contains
250 
251 
252 !> @brief Print a message to stderr, then stop the program.
253 subroutine error(mesg)
254 
255  character(len=*), intent(in) :: mesg !< Message that will be printed to
256  !! stderr.
257 
258  call mpp_error(fatal, trim(mesg))
259 end subroutine error
260 
261 
262 !> @brief Determine if a file exists.
263 !! @return Flag telling if the file exists.
264 function file_exists(path) &
265  result(exists)
266 
267  character(len=*), intent(in) :: path !< Path to file.
268  logical :: exists
269 
270 !$omp critical (file_existence_inquiry)
271  inquire(file=trim(path), exist=exists)
272 !$omp end critical (file_existence_inquiry)
273 end function file_exists
274 
275 
276 !> @brief Catch OpenMP parallel machines.
277 subroutine openmp_thread_trap()
278 
279 #ifdef _OPENMP
280  if (omp_get_level() .gt. 0) then
281  call error("this routine is not thread-safe. Please do not" &
282  //" call it in an OpenMP threaded region.")
283  endif
284 #endif
285 end subroutine openmp_thread_trap
286 
287 !> @brief Compare strings.
288 !! @return Flag telling if the strings are the same.
289 function string_compare(string1, string2, ignore_case) &
290  result(same)
291 
292  character(len=*), intent(in) :: string1
293  character(len=*), intent(in) :: string2
294  logical,intent(in), optional :: ignore_case
295  logical :: same
296 
297  if (len_trim(string1) .ne. len_trim(string2)) then
298  same = .false.
299  return
300  endif
301  if (present(ignore_case)) then
302  if (ignore_case) then
303  same = trim(lowercase(string1)) .eq. trim(lowercase(string2))
304  return
305  endif
306  endif
307  same = trim(string1) .eq. trim(string2)
308 end function string_compare
309 
310 
311 !> @brief Determine if a string exists in a character linked list.
312 !! @return Flag telling if the string is found in the list.
313 function is_in_list(list, string, ignore_case) &
314  result(in_list)
315 
316  type(char_linked_list), pointer, intent(in) :: list !< Linked list.
317  character(len=*), intent(in) :: string !< Input string.
318  logical, intent(in), optional :: ignore_case !< Flag to ignore case.
319  logical :: in_list
320 
321  type(char_linked_list), pointer :: p
322 
323  in_list = .false.
324  p => list
325  do while(associated(p))
326  if (string_compare(string, p%string, ignore_case)) then
327  in_list = .true.
328  return
329  else
330  p => p%head
331  endif
332  enddo
333 end function is_in_list
334 
335 
336 !> @brief Add node to character linked list.
337 subroutine append_to_list(list, string)
338  type(char_linked_list), pointer, intent(inout) :: list !< Linked list.
339  character(len=*), intent(in) :: string !< Input string.
340 
341  type(char_linked_list), pointer :: node
342  type(char_linked_list), pointer :: p
343 
344  allocate(node)
345  call string_copy(node%string, string)
346  node%head => null()
347  if (associated(list)) then
348  p => list
349  do while (associated(p%head))
350  p => p%head
351  enddo
352  p%head => node
353  else
354  list => node
355  endif
356 end subroutine append_to_list
357 
358 
359 !> @brief Deallocate all nodes on a character linked list.
360 subroutine destroy_list(list)
361 
362  type(char_linked_list), pointer, intent(inout) :: list !< Linked list.
363 
364  type(char_linked_list), pointer :: p
365  type(char_linked_list), pointer :: p2
366  p => list
367  do while (associated(p))
368  p2 => p%head
369  deallocate(p)
370  p => p2
371  enddo
372  list => null()
373 end subroutine destroy_list
374 
375 
376 !> @brief Determine if the "domain tile string" (.tilex.) exists in the input filename.
377 !! @internal
378 function has_domain_tile_string(string) &
379  result(has_string)
380 
381  character(len=*), intent(in) :: string !< Input string.
382  logical :: has_string
383 
384  integer :: l
385  integer :: i, j
386 
387  has_string = .false.
388 ! Assigns i to the index where ".tile" starts
389  i = index(trim(string), ".tile", back=.true.)
390  if (i .ne. 0) then
391  l = len_trim(string)
392 ! Sets i to the index after .tile
393  i = i + 5
394  j = i
395  do while (i .le. l)
396 ! If the ith characters is a dot but i not equal to the index after .tile set has_string to true
397  if (verify(string(i:i), ".") .eq. 0 .and. j .ne. i) then
398  has_string = .true.
399  exit
400 ! If the ith characters is NOT a number exit function and has_string will stay as false
401  elseif (verify(string(i:i), "0123456789") .ne. 0) then
402  exit
403  endif
404  i = i + 1
405  enddo
406  endif
407 end function has_domain_tile_string
408 
409 
410 !> @brief Add the domain tile id to an input filepath.
411 !! @internal
412 subroutine domain_tile_filepath_mangle(dest, source, domain_tile_id)
413 
414  character(len=*), intent(inout) :: dest !< Output filepath.
415  character(len=*), intent(in) :: source !< Input filepath.
416  integer, intent(in) :: domain_tile_id !< Domain tile id.
417 
418  integer :: i
419 
420  if (has_domain_tile_string(source)) then
421  call error("The file "//trim(source)//" has a domain tile id (tileX) added. Check your open_file call")
422  endif
423  i = index(trim(source), ".nc", back=.true.)
424  if (i .eq. 0) then
425  call error("The file "//trim(source)//" does not contain .nc. Check your open_file call")
426  endif
427  write(dest, '(a,i0,a)') source(1:i-1)//".tile", &
428  domain_tile_id, source(i:len_trim(source))
429 end subroutine domain_tile_filepath_mangle
430 
431 
432 !> @brief Determine if the "I/O domain tile string" (.nc.xxxx) exists in the input filename.
433 !! @internal
434 function has_io_domain_tile_string(string) &
435  result(has_string)
436 
437  character(len=*), intent(in) :: string !< Input string.
438  logical :: has_string
439 
440  integer :: i
441  integer :: l
442 
443  has_string = .false.
444  i = index(trim(string), ".nc.", back=.true.)
445  if (i .ne. 0) then
446  l = len_trim(string)
447  i = i + 1
448  do while (i .le. l)
449  if (verify(string(i:i), "0123456789") .ne. 0) then
450  return
451  endif
452  i = i + 1
453  enddo
454  has_string = .true.
455  endif
456 end function has_io_domain_tile_string
457 
458 
459 !> @brief Add the I/O domain tile id to an input filepath.
460 subroutine io_domain_tile_filepath_mangle(dest, source, io_domain_tile_id)
461 
462  character(len=*), intent(inout) :: dest !< Output filepath.
463  character(len=*), intent(in) :: source !< Input filepath.
464  integer, intent(in) :: io_domain_tile_id !< I/O domain tile id.
465 
466  if (has_io_domain_tile_string(source)) then
467  call error("The file "//trim(source)// &
468  & " has already had a domain tile id (.nc.XXXX) added. Check your open_file call.")
469  endif
470  write(dest,'(a,i4.4)') trim(source)//".", io_domain_tile_id
471 end subroutine io_domain_tile_filepath_mangle
472 
473 
474 !> @brief Determine if the "restart string" (.res.) exists in the input filename.
475 !! @internal
476 function has_restart_string(string) &
477  result(has_string)
478 
479  character(len=*), intent(in) :: string !< Input string.
480  logical :: has_string
481 
482  has_string = index(trim(string), ".res.", back=.true.) .ne. 0
483 end function has_restart_string
484 
485 
486 !> @brief Add ".res" to an input file path.
487 subroutine restart_filepath_mangle(dest, source)
488 
489  character(len=*), intent(inout) :: dest
490  character(len=*), intent(in) :: source
491 
492  integer :: i
493 
494  if (has_restart_string(source)) then
495  call string_copy(dest, source)
496  return
497  endif
498  if (has_domain_tile_string(source)) then
499  i = index(trim(source), ".tile", back=.true.)
500  else
501  i = index(trim(source), ".nc", back=.true.)
502  if (i .eq. 0) then
503  call error("The file "//trim(source)//" does not contain .nc. Check your open_file call")
504  endif
505  endif
506  call string_copy(dest, source(1:i-1)//".res"//source(i:len_trim(source)))
507 end subroutine restart_filepath_mangle
508 
509 subroutine open_check(flag, fname)
510 
511  logical, intent(in) :: flag
512  character(len=*), intent(in), optional :: fname !< The file name
513 
514  if (.not. flag) then
515  if (present(fname)) then
516  call mpp_error(fatal, "Error occurred while opening file "//trim(fname))
517  else
518  call mpp_error(fatal, "Error occurred while opening file.")
519  endif
520  endif
521 end subroutine open_check
522 
523 !> @brief Read the ascii text from filename `ascii_filename`into string array
524 !! `ascii_var`
525 subroutine ascii_read(ascii_filename, ascii_var, num_lines, max_length)
526  character(len=*), intent(in) :: ascii_filename !< The file name to be read
527  character(len=:), dimension(:), allocatable, intent(out) :: ascii_var !< The
528  !! string
529  !! array
530  integer, optional, intent(out) :: num_lines !< Optional argument to return number of lines in file
531  integer, optional, intent(out) :: max_length !< Optional argument to return max_length of line in file
532  integer, dimension(2) :: lines_and_length !< lines = 1, length = 2
533  if(allocated(ascii_var)) deallocate(ascii_var)
534  lines_and_length = get_ascii_file_num_lines_and_length(ascii_filename)
535  allocate(character(len=lines_and_length(2))::ascii_var(lines_and_length(1)))
536  call read_ascii_file(ascii_filename, lines_and_length(2), ascii_var)
537  if(present(num_lines)) num_lines = lines_and_length(1)
538  if(present(max_length)) max_length = lines_and_length(2)
539 end subroutine ascii_read
540 
541 !> @brief Populate 2D maskmap from mask_table given a model
542 subroutine parse_mask_table_2d(mask_table, maskmap, modelname)
543 
544  character(len=*), intent(in) :: mask_table !< Mask table to be read in
545  logical, intent(out) :: maskmap(:,:) !< 2D Mask output
546  character(len=*), intent(in) :: modelname !< Model to which this applies
547 
548  integer :: nmask, layout(2)
549  integer, allocatable :: mask_list(:,:)
550  character(len=:), dimension(:), allocatable :: mask_table_contents
551  integer :: iocheck, n, stdoutunit, offset
552  character(len=128) :: record
553 
554  maskmap = .true.
555  nmask = 0
556  stdoutunit = stdout()
557  call ascii_read(mask_table, mask_table_contents)
558  if( mpp_pe() == mpp_root_pe() ) then
559  read(mask_table_contents(1), fmt=*, iostat=iocheck) nmask
560  if (iocheck > 0) then
561  call mpp_error(fatal, "fms2_io(parse_mask_table_2d): Error in reading nmask from file variable")
562  elseif (iocheck < 0) then
563  call mpp_error(fatal, "fms2_io(parse_mask_table_2d): Error: nmask not completely read from file variable")
564  endif
565  write(stdoutunit,*)"parse_mask_table: Number of domain regions masked in ", trim(modelname), " = ", nmask
566  if( nmask > 0 ) then
567  !--- read layout from mask_table and confirm it matches the shape of maskmap
568  read(mask_table_contents(2), fmt=*, iostat=iocheck) layout
569  if (iocheck > 0) then
570  call mpp_error(fatal, "fms2_io(parse_mask_table_2d): Error in reading layout from file variable")
571  elseif (iocheck < 0) then
572  call mpp_error(fatal, "fms2_io(parse_mask_table_2d): Error: layout not completely read from file variable")
573  endif
574  if( (layout(1) .NE. size(maskmap,1)) .OR. (layout(2) .NE. size(maskmap,2)) )then
575  write(stdoutunit,*)"layout=", layout, ", size(maskmap) = ", size(maskmap,1), size(maskmap,2)
576  call mpp_error(fatal, "fms2_io(parse_mask_table_2d): layout in file "//trim(mask_table)// &
577  "does not match size of maskmap for "//trim(modelname))
578  endif
579  !--- make sure mpp_npes() == layout(1)*layout(2) - nmask
580  if( mpp_npes() .NE. layout(1)*layout(2) - nmask ) call mpp_error(fatal, &
581  .NE."fms2_io(parse_mask_table_2d): mpp_npes() layout(1)*layout(2) - nmask for "//trim(modelname))
582  endif
583  endif
584 
585  call mpp_broadcast(nmask, mpp_root_pe())
586 
587  if(nmask==0) return
588 
589  allocate(mask_list(nmask,2))
590 
591  if( mpp_pe() == mpp_root_pe() ) then
592  n = 0
593  offset = 3
594  do while (offset + n < size(mask_table_contents)+1)
595  read(mask_table_contents(n+offset),'(a)',iostat=iocheck) record
596  if (iocheck > 0) then
597  call mpp_error(fatal, "fms2_io(parse_mask_table_2d): Error in reading record from file variable")
598  elseif (iocheck < 0) then
599  call mpp_error(fatal, "fms2_io(parse_mask_table_2d): Error: record not completely read from file variable")
600  endif
601  if (record(1:1) == '#') then
602  offset = offset + 1
603  cycle
604  elseif (record(1:10) == ' ') then
605  offset = offset + 1
606  cycle
607  endif
608  n = n + 1
609  if( n > nmask ) then
610  call mpp_error(fatal, "fms2_io(parse_mask_table_2d): number of mask_list entry "// &
611  "is greater than nmask in file "//trim(mask_table) )
612  endif
613  read(record,*,iostat=iocheck) mask_list(n,1), mask_list(n,2)
614  if (iocheck > 0) then
615  call mpp_error(fatal, "fms2_io(parse_mask_table_2d): Error in reading mask_list from record variable")
616  elseif (iocheck < 0) then
617  call mpp_error(fatal, &
618  & "fms2_io(parse_mask_table_2d): Error: mask_list not completely read from record variable")
619  endif
620  enddo
621 
622  !--- make sure the number of entry for mask_list is nmask
623  if( n .NE. nmask) call mpp_error(fatal, &
624  "fms2_io(parse_mask_table_2d): number of mask_list entry does not match nmask in file "//trim(mask_table))
625  endif
626 
627  call mpp_broadcast(mask_list, 2*nmask, mpp_root_pe())
628  do n = 1, nmask
629  maskmap(mask_list(n,1),mask_list(n,2)) = .false.
630  enddo
631 
632  deallocate(mask_list, mask_table_contents)
633 
634 end subroutine parse_mask_table_2d
635 
636 
637 !> @brief Populate 3D maskmap from mask_table given a model
638 subroutine parse_mask_table_3d(mask_table, maskmap, modelname)
639 
640  character(len=*), intent(in) :: mask_table !< Mask table to be read in
641  logical, intent(out) :: maskmap(:,:,:) !< 2D Mask output
642  character(len=*), intent(in) :: modelname !< Model to which this applies
643 
644  integer :: nmask, layout(2)
645  integer, allocatable :: mask_list(:,:)
646  character(len=:), dimension(:), allocatable :: mask_table_contents
647  integer :: iocheck, n, stdoutunit, ntiles, offset
648  character(len=128) :: record
649 
650  maskmap = .true.
651  nmask = 0
652  stdoutunit = stdout()
653  call ascii_read(mask_table, mask_table_contents)
654  if( mpp_pe() == mpp_root_pe() ) then
655  read(mask_table_contents(1), fmt=*, iostat=iocheck) nmask
656  if (iocheck > 0) then
657  call mpp_error(fatal, "fms2_io(parse_mask_table_3d): Error in reading nmask from file variable")
658  elseif (iocheck < 0) then
659  call mpp_error(fatal, "fms2_io(parse_mask_table_3d): Error: nmask not completely read from file variable")
660  endif
661  write(stdoutunit,*)"parse_mask_table: Number of domain regions masked in ", trim(modelname), " = ", nmask
662  if( nmask > 0 ) then
663  !--- read layout from mask_table and confirm it matches the shape of maskmap
664  read(mask_table_contents(2), fmt=*, iostat=iocheck) layout(1), layout(2), ntiles
665  if (iocheck > 0) then
666  call mpp_error(fatal, "fms2_io(parse_mask_table_3d): Error in reading layout from file variable")
667  elseif (iocheck < 0) then
668  call mpp_error(fatal, "fms2_io(parse_mask_table_3d): Error: layout not completely read from file variable")
669  endif
670  if( (layout(1) .NE. size(maskmap,1)) .OR. (layout(2) .NE. size(maskmap,2)) )then
671  write(stdoutunit,*)"layout=", layout, ", size(maskmap) = ", size(maskmap,1), size(maskmap,2)
672  call mpp_error(fatal, "fms2_io(parse_mask_table_3d): layout in file "//trim(mask_table)// &
673  "does not match size of maskmap for "//trim(modelname))
674  endif
675  if( ntiles .NE. size(maskmap,3) ) then
676  write(stdoutunit,*)"ntiles=", ntiles, ", size(maskmap,3) = ", size(maskmap,3)
677  call mpp_error(fatal, "fms2_io(parse_mask_table_3d): ntiles in file "//trim(mask_table)// &
678  "does not match size of maskmap for "//trim(modelname))
679  endif
680  !--- make sure mpp_npes() == layout(1)*layout(2) - nmask
681  if( mpp_npes() .NE. layout(1)*layout(2)*ntiles - nmask ) then
682  print*, "layout=", layout, nmask, mpp_npes()
683  call mpp_error(fatal, &
684  .NE."fms2_io(parse_mask_table_3d): mpp_npes() layout(1)*layout(2) - nmask for "//trim(modelname))
685  endif
686  endif
687  endif
688 
689  call mpp_broadcast(nmask, mpp_root_pe())
690 
691  if(nmask==0) return
692 
693  allocate(mask_list(nmask,3))
694 
695  if( mpp_pe() == mpp_root_pe() ) then
696  n = 0
697  offset = 3
698  do while (offset + n < size(mask_table_contents)+1)
699  read(mask_table_contents(n+offset),'(a)',iostat=iocheck) record
700  if (iocheck > 0) then
701  call mpp_error(fatal, "fms2_io(parse_mask_table_3d): Error in reading record from file variable")
702  elseif (iocheck < 0) then
703  call mpp_error(fatal, "fms2_io(parse_mask_table_3d): Error: record not completely read from file variable")
704  endif
705  if (record(1:1) == '#') then
706  offset = offset + 1
707  cycle
708  elseif (record(1:10) == ' ') then
709  offset = offset + 1
710  cycle
711  endif
712  n = n + 1
713  if( n > nmask ) then
714  call mpp_error(fatal, "fms2_io(parse_mask_table_3d): number of mask_list entry "// &
715  "is greater than nmask in file "//trim(mask_table) )
716  endif
717  read(record,*,iostat=iocheck) mask_list(n,1), mask_list(n,2), mask_list(n,3)
718  if (iocheck > 0) then
719  call mpp_error(fatal, "fms2_io(parse_mask_table_3d): Error in reading mask_list from record variable")
720  elseif (iocheck < 0) then
721  call mpp_error(fatal, &
722  & "fms2_io(parse_mask_table_3d): Error: mask_list not completely read from record variable")
723  endif
724  enddo
725 
726  !--- make sure the number of entry for mask_list is nmask
727  if( n .NE. nmask) call mpp_error(fatal, &
728  "fms2_io(parse_mask_table_3d): number of mask_list entry does not match nmask in file "//trim(mask_table))
729 ! call mpp_close(unit)
730  endif
731 
732  call mpp_broadcast(mask_list, 3*nmask, mpp_root_pe())
733  do n = 1, nmask
734  maskmap(mask_list(n,1),mask_list(n,2),mask_list(n,3)) = .false.
735  enddo
736  deallocate(mask_list, mask_table_contents)
737 end subroutine parse_mask_table_3d
738 
739 !> @brief Determine tile_file for structured grid based on filename and current
740 !! tile on mpp_domain (this is mostly used for ongrid data_overrides)
741 subroutine get_mosaic_tile_file_sg(file_in, file_out, is_no_domain, domain, tile_count)
742  character(len=*), intent(in) :: file_in !< name of 'base' file
743  character(len=*), intent(out) :: file_out !< name of tile_file
744  logical, intent(in) :: is_no_domain !< are we providing a
745  !! domain
746  type(domain2d), intent(in), optional, target :: domain !< domain provided
747  integer, intent(in), optional :: tile_count !< tile count
748 
749  character(len=FMS_FILE_LEN) :: basefile
750  character(len=6) :: tilename
751  character(len=2) :: my_tile_str
752  integer :: lens, ntiles, ntileMe, tile, my_tile_id
753  integer, dimension(:), allocatable :: tile_id
754  type(domain2d), pointer, save :: d_ptr =>null()
755  logical :: domain_exist
756 
757  if(index(file_in, '.nc', back=.true.)==0) then
758  basefile = trim(file_in)
759  else
760  lens = len_trim(file_in)
761  if(file_in(lens-2:lens) .NE. '.nc') call mpp_error(fatal, &
762  'get_mosaic_tile_file_sg: .nc should be at the end of file '//trim(file_in))
763  basefile = file_in(1:lens-3)
764  end if
765 
766  !--- get the tile name
767  ntiles = 1
768  my_tile_id = 1
769  domain_exist = .false.
770  if(PRESENT(domain))then
771  domain_exist = .true.
772  ntiles = mpp_get_ntile_count(domain)
773  d_ptr => domain
774  endif
775 
776  if(domain_exist) then
777  ntileme = mpp_get_current_ntile(d_ptr)
778  allocate(tile_id(ntileme))
779  tile_id = mpp_get_tile_id(d_ptr)
780  tile = 1
781  if(present(tile_count)) tile = tile_count
782  my_tile_id = tile_id(tile)
783  endif
784 
785  if(ntiles > 1 .or. my_tile_id > 1 )then
786  write(my_tile_str, '(I0)') my_tile_id
787  tilename = 'tile'//trim(my_tile_str)
788  if(index(basefile,'.'//trim(tilename),back=.true.) == 0)then
789  basefile = trim(basefile)//'.'//trim(tilename);
790  end if
791  end if
792  if(allocated(tile_id)) deallocate(tile_id)
793 
794  file_out = trim(basefile)//'.nc'
795 
796  d_ptr =>null()
797 
798 end subroutine get_mosaic_tile_file_sg
799 
800 !> @brief Determine tile_file for unstructured grid based on filename and current
801 !! tile on mpp_domain (this is mostly used for ongrid data_overrides)
802 subroutine get_mosaic_tile_file_ug(file_in, file_out, domain)
803  character(len=*), intent(in) :: file_in !< name of base file
804  character(len=*), intent(out) :: file_out !< name of tile file
805  type(domainug), intent(in), optional :: domain !< domain provided
806 
807  character(len=FMS_FILE_LEN) :: basefile
808  character(len=6) :: tilename
809  character(len=2) :: my_tile_str
810  integer :: lens, ntiles, my_tile_id
811 
812  if(index(file_in, '.nc', back=.true.)==0) then
813  basefile = trim(file_in)
814  else
815  lens = len_trim(file_in)
816  if(file_in(lens-2:lens) .NE. '.nc') call mpp_error(fatal, &
817  'fms_io_mod: .nc should be at the end of file '//trim(file_in))
818  basefile = file_in(1:lens-3)
819  end if
820 
821  !--- get the tile name
822  ntiles = 1
823  my_tile_id = 1
824  if(PRESENT(domain))then
825  ntiles = mpp_get_ug_domain_ntiles(domain)
826  my_tile_id = mpp_get_ug_domain_tile_id(domain)
827  endif
828 
829  if(ntiles > 1 .or. my_tile_id > 1 )then
830  write(my_tile_str, '(I0)') my_tile_id
831  tilename = 'tile'//trim(my_tile_str)
832  if(index(basefile,'.'//trim(tilename),back=.true.) == 0)then
833  basefile = trim(basefile)//'.'//trim(tilename);
834  end if
835  end if
836 
837  file_out = trim(basefile)//'.nc'
838 
839 end subroutine get_mosaic_tile_file_ug
840 
841 !> @brief Writes filename appendix to "string_out"
842 subroutine get_filename_appendix(string_out)
843  character(len=*) , intent(out) :: string_out !< String to write the filename_appendix to
844 
845  string_out = trim(filename_appendix)
846 
847 end subroutine get_filename_appendix
848 
849 !> @brief Clears the filename_appendix module variable
851 
852  filename_appendix = ''
853 
854 end subroutine nullify_filename_appendix
855 
856 !> @brief Save "string_in" as a module variable that will added to the filename
857 !! of the restart files
858 subroutine set_filename_appendix(string_in)
859  character(len=*) , intent(in) :: string_in !< String that will be saved as a module variable
860 
861  ! Check if string has already been added
862  if (len_trim(filename_appendix) > 0) then
863  call error("Set_filename_appendix: The filename appendix has already be set " &
864  //"call 'nullify_filename_appendix' first")
865  endif
866 
867  filename_appendix = trim(string_in)
868 
869 end subroutine set_filename_appendix
870 
871 !> @brief Adds the filename_appendix to name_in and sets it as name_out
872 subroutine get_instance_filename(name_in,name_out)
873  character(len=*) , intent(in) :: name_in !< Buffer to add the filename_appendix to
874  character(len=*), intent(inout) :: name_out !< name_in with the filename_appendix
875 
876  integer :: length !< Length of name_in
877  integer :: i !< no description
878 
879  length = len_trim(name_in)
880  name_out = name_in(1:length)
881 
882  if(len_trim(filename_appendix) > 0) then
883  !< If .tileXX is in the filename add the appendix before it
884  if (has_domain_tile_string(name_in)) then
885  i = index(trim(name_in), ".tile", back=.true.)
886  name_out = name_in(1:i-1) //'.'//trim(filename_appendix)//name_in(i:length)
887  return
888  endif
889 
890  !< If .nc is in the filename add the appendix before it
891  i = index(trim(name_in), ".nc", back=.true.)
892  if ( i .ne. 0 ) then
893  name_out = name_in(1:i-1)//'.'//trim(filename_appendix)//name_in(i:length)
894  else
895  i = index(trim(name_in), ".yaml", back=.true.)
896  if (i .ne. 0) then
897  !< If .yaml is in the filename add the appendix before it
898  name_out = name_in(1:i-1)//'.'//trim(filename_appendix)//name_in(i:length)
899  else
900  !< If .nc and .yaml are not in the name, add the appendix at the end of the file
901  name_out = name_in(1:length) //'.'//trim(filename_appendix)
902  endif
903  end if
904  end if
905 
906 end subroutine get_instance_filename
907 
908 include "array_utils.inc"
909 include "array_utils_char.inc"
910 include "get_data_type_string.inc"
911 
912 
913 end module fms_io_utils_mod
914 !> @}
915 ! close documentation grouping
subroutine allocate_array_r8_kind_1d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine get_data_type_string_4d(sdata, type_string)
Return a string describing the input data's type.
subroutine put_array_section_i8_kind_2d(section, array, s, c)
Put a section of an array into a larger array.
subroutine put_array_section_r8_kind_4d(section, array, s, c)
Put a section of an array into a larger array.
subroutine allocate_array_char_5d(buf, sizes, initialize)
Allocate character arrays using an input array of sizes.
subroutine put_array_section_i4_kind_2d(section, array, s, c)
Put a section of an array into a larger array.
Definition: array_utils.inc:76
subroutine put_array_section_i4_kind_1d(section, array, s, c)
Put a section of an array into a larger array.
Definition: array_utils.inc:39
subroutine, public get_instance_filename(name_in, name_out)
Adds the filename_appendix to name_in and sets it as name_out.
subroutine allocate_array_char_3d(buf, sizes, initialize)
Allocate character arrays using an input array of sizes.
subroutine get_array_section_i8_kind_4d(section, array, s, c)
Get a section of larger array.
logical function has_io_domain_tile_string(string)
Determine if the "I/O domain tile string" (.nc.xxxx) exists in the input filename.
subroutine get_array_section_r4_kind_3d(section, array, s, c)
Get a section of larger array.
subroutine, public ascii_read(ascii_filename, ascii_var, num_lines, max_length)
Read the ascii text from filename ascii_filenameinto string array ascii_var
subroutine get_mosaic_tile_file_ug(file_in, file_out, domain)
Determine tile_file for unstructured grid based on filename and current tile on mpp_domain (this is m...
subroutine get_array_section_r8_kind_1d(section, array, s, c)
Get a section of larger array.
subroutine get_array_section_i8_kind_3d(section, array, s, c)
Get a section of larger array.
subroutine allocate_array_i8_kind_2d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine put_array_section_i4_kind_3d(section, array, s, c)
Put a section of an array into a larger array.
subroutine, public restart_filepath_mangle(dest, source)
Add ".res" to an input file path.
subroutine put_array_section_r8_kind_2d(section, array, s, c)
Put a section of an array into a larger array.
subroutine put_array_section_r8_kind_3d(section, array, s, c)
Put a section of an array into a larger array.
subroutine get_array_section_i8_kind_5d(section, array, s, c)
Get a section of larger array.
subroutine get_data_type_string_5d(sdata, type_string)
Return a string describing the input data's type.
subroutine get_array_section_r4_kind_1d(section, array, s, c)
Get a section of larger array.
logical function, public file_exists(path)
Determine if a file exists.
subroutine put_array_section_i8_kind_1d(section, array, s, c)
Put a section of an array into a larger array.
subroutine put_array_section_i8_kind_5d(section, array, s, c)
Put a section of an array into a larger array.
subroutine allocate_array_r4_kind_3d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine allocate_array_i8_kind_1d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine parse_mask_table_2d(mask_table, maskmap, modelname)
Populate 2D maskmap from mask_table given a model.
subroutine allocate_array_r8_kind_4d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine allocate_array_r8_kind_3d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine, public open_check(flag, fname)
logical function has_restart_string(string)
Determine if the "restart string" (.res.) exists in the input filename.
subroutine, public nullify_filename_appendix()
Clears the filename_appendix module variable.
subroutine, public io_domain_tile_filepath_mangle(dest, source, io_domain_tile_id)
Add the I/O domain tile id to an input filepath.
subroutine allocate_array_i8_kind_5d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine allocate_array_i4_kind_2d(buf, sizes)
Allocate arrays using an input array of sizes.
Definition: array_utils.inc:63
subroutine, public get_filename_appendix(string_out)
Writes filename appendix to "string_out".
subroutine allocate_array_r4_kind_1d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine, public set_filename_appendix(string_in)
Save "string_in" as a module variable that will added to the filename of the restart files.
subroutine get_array_section_r8_kind_5d(section, array, s, c)
Get a section of larger array.
logical function, public string_compare(string1, string2, ignore_case)
Compare strings.
subroutine get_data_type_string_1d(sdata, type_string)
Return a string describing the input data's type.
subroutine put_array_section_r4_kind_5d(section, array, s, c)
Put a section of an array into a larger array.
subroutine allocate_array_char_4d(buf, sizes, initialize)
Allocate character arrays using an input array of sizes.
subroutine allocate_array_r8_kind_5d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine allocate_array_r4_kind_4d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine get_array_section_i4_kind_5d(section, array, s, c)
Get a section of larger array.
subroutine allocate_array_char_6d(buf, sizes, initialize)
Allocate character arrays using an input array of sizes.
subroutine get_array_section_i4_kind_3d(section, array, s, c)
Get a section of larger array.
subroutine allocate_array_r4_kind_2d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine allocate_array_i4_kind_4d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine put_array_section_i8_kind_4d(section, array, s, c)
Put a section of an array into a larger array.
subroutine allocate_array_i4_kind_3d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine put_array_section_r4_kind_3d(section, array, s, c)
Put a section of an array into a larger array.
subroutine parse_mask_table_3d(mask_table, maskmap, modelname)
Populate 3D maskmap from mask_table given a model.
subroutine get_mosaic_tile_file_sg(file_in, file_out, is_no_domain, domain, tile_count)
Determine tile_file for structured grid based on filename and current tile on mpp_domain (this is mos...
subroutine get_data_type_string_0d(sdata, type_string)
Return a string describing the input data's type.
subroutine get_array_section_i8_kind_1d(section, array, s, c)
Get a section of larger array.
subroutine get_array_section_r4_kind_4d(section, array, s, c)
Get a section of larger array.
subroutine put_array_section_r8_kind_1d(section, array, s, c)
Put a section of an array into a larger array.
subroutine get_array_section_i4_kind_2d(section, array, s, c)
Get a section of larger array.
Definition: array_utils.inc:88
subroutine get_data_type_string_3d(sdata, type_string)
Return a string describing the input data's type.
subroutine get_array_section_r8_kind_2d(section, array, s, c)
Get a section of larger array.
subroutine, public destroy_list(list)
Deallocate all nodes on a character linked list.
subroutine get_array_section_r8_kind_4d(section, array, s, c)
Get a section of larger array.
logical function has_domain_tile_string(string)
Determine if the "domain tile string" (.tilex.) exists in the input filename.
subroutine allocate_array_i8_kind_3d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine get_data_type_string_2d(sdata, type_string)
Return a string describing the input data's type.
subroutine put_array_section_i4_kind_5d(section, array, s, c)
Put a section of an array into a larger array.
subroutine get_array_section_i4_kind_4d(section, array, s, c)
Get a section of larger array.
subroutine, public append_to_list(list, string)
Add node to character linked list.
subroutine put_array_section_i4_kind_4d(section, array, s, c)
Put a section of an array into a larger array.
subroutine, public openmp_thread_trap()
Catch OpenMP parallel machines.
subroutine allocate_array_i4_kind_5d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine allocate_array_char_1d(buf, sizes, initialize)
Allocate character arrays using an input array of sizes.
subroutine allocate_array_r4_kind_5d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine allocate_array_i4_kind_1d(buf, sizes)
Allocate arrays using an input array of sizes.
Definition: array_utils.inc:26
subroutine put_array_section_r8_kind_5d(section, array, s, c)
Put a section of an array into a larger array.
subroutine get_array_section_i4_kind_1d(section, array, s, c)
Get a section of larger array.
Definition: array_utils.inc:51
subroutine put_array_section_i8_kind_3d(section, array, s, c)
Put a section of an array into a larger array.
subroutine get_array_section_i8_kind_2d(section, array, s, c)
Get a section of larger array.
subroutine put_array_section_r4_kind_1d(section, array, s, c)
Put a section of an array into a larger array.
subroutine allocate_array_i8_kind_4d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine put_array_section_r4_kind_4d(section, array, s, c)
Put a section of an array into a larger array.
subroutine allocate_array_r8_kind_2d(buf, sizes)
Allocate arrays using an input array of sizes.
subroutine allocate_array_char_2d(buf, sizes, initialize)
Allocate character arrays using an input array of sizes.
subroutine, public domain_tile_filepath_mangle(dest, source, domain_tile_id)
Add the domain tile id to an input filepath.
logical function, public is_in_list(list, string, ignore_case)
Determine if a string exists in a character linked list.
subroutine get_array_section_r4_kind_2d(section, array, s, c)
Get a section of larger array.
subroutine get_array_section_r8_kind_3d(section, array, s, c)
Get a section of larger array.
subroutine get_array_section_r4_kind_5d(section, array, s, c)
Get a section of larger array.
subroutine put_array_section_r4_kind_2d(section, array, s, c)
Put a section of an array into a larger array.
Interface to allocate data real, integer, or character buffers for use in read/write routines....
Utility routine that copies a subset of data from a larger array into a smaller one using the startin...
Sets a string describing the datatype corresponding to the sdata argument Example:
Constructs the file name to be used when utilizing a multi-tile mosaic. This is currenly used in the ...
Reads in the mask_table file in the ASCII format from a given path and populates a maskmap array that...
Utility routine that copies a smaller array into a larger one using the starting indices and sizes pr...
A linked list of strings.
subroutine, public string_copy(dest, source, check_for_null)
Safely copy a string from one buffer to another.
integer function, dimension(size(domain%tile_id(:))) mpp_get_tile_id(domain)
Returns the tile_id on current pe.
integer function mpp_get_current_ntile(domain)
Returns number of tile on current pe.
integer function mpp_get_ntile_count(domain)
Returns number of tiles in mosaic.
The domain2D type contains all the necessary information to define the global, compute and data domai...
Domain information for managing data on unstructured grids.
integer function stdout()
This function returns the current standard fortran unit numbers for output.
Definition: mpp_util.inc:42
subroutine read_ascii_file(FILENAME, LENGTH, Content, PELIST)
Reads any ascii file into a character array and broadcasts it to the non-root mpi-tasks....
Definition: mpp_util.inc:1469
integer function mpp_npes()
Returns processor count for current pelist.
Definition: mpp_util.inc:420
integer function, dimension(2) get_ascii_file_num_lines_and_length(FILENAME, PELIST)
Function to determine the maximum line length and number of lines from an ascii file.
Definition: mpp_util.inc:1376
integer function mpp_pe()
Returns processor ID.
Definition: mpp_util.inc:406
Perform parallel broadcasts.
Definition: mpp.F90:1153
Error handler.
Definition: mpp.F90:385