FMS  2024.03
Flexible Modeling System
blackboxio.F90
1 !***********************************************************************
2 !* GNU Lesser General Public License
3 !*
4 !* This file is part of the GFDL Flexible Modeling System (FMS).
5 !*
6 !* FMS is free software: you can redistribute it and/or modify it under
7 !* the terms of the GNU Lesser General Public License as published by
8 !* the Free Software Foundation, either version 3 of the License, or (at
9 !* your option) any later version.
10 !*
11 !* FMS is distributed in the hope that it will be useful, but WITHOUT
12 !* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 !* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 !* for more details.
15 !*
16 !* You should have received a copy of the GNU Lesser General Public
17 !* License along with FMS. If not, see <http://www.gnu.org/licenses/>.
18 !***********************************************************************
19 !> @defgroup blackboxio blackboxio
20 !> @ingroup fms2_io
21 !> @brief File utility functions for use within @ref fms2_io
22 
23 !> @addtogroup blackboxio
24 !> @{
25 module blackboxio
26 use netcdf
27 use mpp_domains_mod
28 use fms_io_utils_mod
29 use netcdf_io_mod
30 use fms_netcdf_domain_io_mod
31 use fms_netcdf_unstructured_domain_io_mod
32 use mpp_mod, only: mpp_pe
33 use, intrinsic :: iso_fortran_env, only: error_unit
34 use platform_mod
35 implicit none
36 private
37 
38 integer, private :: fms2_ncchksz = -1 !< Chunksize (bytes) used in nc_open and nc_create
39 
40 public :: blackboxio_init
49 
50 
51 contains
52 !> @brief Accepts the namelist fms2_io_nml variables relevant to blackboxio
53 subroutine blackboxio_init (chksz)
54 integer, intent(in) :: chksz
55  fms2_ncchksz = chksz
56 end subroutine blackboxio_init
57 
58 
59 !> @brief Create a new file path.
60 !! @internal
61 subroutine get_new_filename(path, new_path, directory, timestamp, new_name)
62 
63  character(len=*), intent(in) :: path !< File path.
64  character(len=*), intent(out) :: new_path !< New file path.
65  character(len=*), intent(in), optional :: directory !< Directory
66  character(len=*), intent(in), optional :: timestamp !< Time.
67  character(len=*), intent(in), optional :: new_name !< New file basename.
68 
69  character(len=FMS_PATH_LEN) :: dir
70  character(len=FMS_FILE_LEN) :: tstamp
71  character(len=FMS_PATH_LEN) :: nname
72 
73  dir = ""
74  if (present(directory)) then
75  call string_copy(dir, trim(directory)//"/")
76  endif
77  tstamp = ""
78  if (present(timestamp)) then
79  call string_copy(tstamp, trim(timestamp)//".")
80  endif
81  call string_copy(nname, trim(path))
82  if (present(new_name)) then
83  call string_copy(nname, trim(new_name))
84  endif
85  call string_copy(new_path, trim(dir)//trim(tstamp)//trim(nname))
86 end subroutine get_new_filename
87 
88 
89 !> @brief Create a unique filename (poor man's version of mktemp).
90 !! @internal
91 subroutine tempfile(filename)
92 
93  character(len=*), intent(out) :: filename !< New unique filename.
94 
95  real :: numr
96  integer :: numi
97 
98  do while(.true.)
99  call random_number(numr)
100  numi = transfer(numr, numi)
101  numi = iand(numi, z'FFFFFF')
102  write(filename, '(a,z6.6)') "tmp", numi
103  if (.not. file_exists(filename)) then
104  exit
105  endif
106  enddo
107 end subroutine tempfile
108 
109 
110 !> @brief Create a "diskless" netcdf file to act as a buffer to support our "register
111 !! data to a file without knowing its name" legacy restart I/O workflow.
112 !! @return Flag telling whether the creation of the buffer was successful.
113 !! @internal
114 function create_diskless_netcdf_file(fileobj, pelist, path) &
115  result(success)
116 
117  class(fmsnetcdffile_t), intent(inout) :: fileobj !< File object.
118  integer, dimension(:), intent(in), optional :: pelist !< List of ranks associated
119  !! with this file. If not
120  !! provided, only the current
121  !! rank will be able to
122  !! act on the file.
123  character(len=*), intent(in), optional :: path !< File path.
124  logical :: success
125 
126  integer :: cmode
127  integer :: err
128 
129  if (present(path)) then
130  call string_copy(fileobj%path, path)
131  else
132  call tempfile(fileobj%path)
133  endif
134  fileobj%nc_format = "classic"
135  fileobj%is_readonly = .false.
136  if (present(pelist)) then
137  allocate(fileobj%pelist(size(pelist)))
138  fileobj%pelist(:) = pelist(:)
139  else
140  allocate(fileobj%pelist(1))
141  fileobj%pelist(1) = mpp_pe()
142  endif
143  fileobj%io_root = fileobj%pelist(1)
144  fileobj%is_root = mpp_pe() .eq. fileobj%io_root
145  fileobj%is_restart = .true.
146  fileobj%is_diskless = .true.
147  cmode = ior(nf90_noclobber, nf90_classic_model)
148  cmode = ior(cmode, nf90_diskless)
149  if (fms2_ncchksz == -1) call error("create_diskless_netcdf_file :: fms2_ncchksz not set. Call fms2_io init first")
150  err = nf90_create(trim(fileobj%path), cmode, fileobj%ncid, chunksize=fms2_ncchksz)
151  success = err .eq. nf90_noerr
152  if (.not. success) then
153  deallocate(fileobj%pelist)
154  return
155  endif
156  allocate(fileobj%restart_vars(max_num_restart_vars))
157  fileobj%num_restart_vars = 0
158  allocate(fileobj%compressed_dims(max_num_compressed_dims))
159  fileobj%num_compressed_dims = 0
160 end function create_diskless_netcdf_file
161 
162 
163 !> @brief Copy metadata from one file object to another.
164 !! @internal
165 subroutine copy_metadata(fileobj, new_fileobj)
166 
167  class(fmsnetcdffile_t), intent(in), target :: fileobj !< File object.
168  class(fmsnetcdffile_t), intent(inout) :: new_fileobj !< New file object.
169 
170  integer :: err
171  integer :: natt
172  integer :: ndim
173  integer :: varndim
174  integer :: nvar
175  character(len=nf90_max_name) :: n
176  character(len=nf90_max_name) :: varname
177  character(len=nf90_max_name), dimension(nf90_max_dims) :: dimnames
178  integer, dimension(nf90_max_dims) :: dimlens
179  integer :: xtype
180  integer, dimension(nf90_max_var_dims) :: dimids
181  integer, dimension(nf90_max_var_dims) :: d
182  integer :: ulim_dimid
183  integer :: varid
184  integer :: i
185  integer :: j
186  integer :: k
187  integer(kind=i4_kind), dimension(:), allocatable :: buf_int
188  real(kind=r4_kind), dimension(:), allocatable :: buf_float
189  real(kind=r8_kind), dimension(:), allocatable :: buf_double
190  character(len=200) :: append_error_msg !< Msg to be appended to FATAL error message
191 
192  append_error_msg = "copy_metadata: original file:"//trim(fileobj%path)//" new file:"//trim(new_fileobj%path)
193  if (fileobj%is_root .and. .not. new_fileobj%is_readonly) then
194  !Copy global attributes to the new file.
195  call set_netcdf_mode(fileobj%ncid, define_mode)
196  call set_netcdf_mode(new_fileobj%ncid, define_mode)
197  err = nf90_inquire(fileobj%ncid, nattributes=natt)
198  call check_netcdf_code(err, append_error_msg)
199  do i = 1, natt
200  err = nf90_inq_attname(fileobj%ncid, nf90_global, i, n)
201  call check_netcdf_code(err, append_error_msg)
202  err = nf90_copy_att(fileobj%ncid, nf90_global, n, new_fileobj%ncid, nf90_global)
203  call check_netcdf_code(err, append_error_msg)
204  enddo
205 
206  !Copy the dimensions to the new file.
207  err = nf90_inquire(fileobj%ncid, ndimensions=ndim)
208  call check_netcdf_code(err, append_error_msg)
209  err = nf90_inquire(fileobj%ncid, unlimiteddimid=ulim_dimid)
210  call check_netcdf_code(err, append_error_msg)
211  do i = 1, ndim
212  err = nf90_inquire_dimension(fileobj%ncid, i, dimnames(i), dimlens(i))
213  call check_netcdf_code(err, append_error_msg)
214  if (i .eq. ulim_dimid) then
215  err = nf90_def_dim(new_fileobj%ncid, dimnames(i), nf90_unlimited, dimids(i))
216  ulim_dimid = dimids(i)
217  else
218  err = nf90_def_dim(new_fileobj%ncid, dimnames(i), dimlens(i), dimids(i))
219  endif
220  call check_netcdf_code(err, append_error_msg)
221  enddo
222 
223  !Copy the variables to the new file.
224  err = nf90_inquire(fileobj%ncid, nvariables=nvar)
225  call check_netcdf_code(err, append_error_msg)
226  do i = 1, nvar
227  err = nf90_inquire_variable(fileobj%ncid, i, varname, xtype, varndim, d, natt)
228  call check_netcdf_code(err, append_error_msg)
229 
230  !Map to new dimension ids.
231  do j = 1, varndim
232  err = nf90_inquire_dimension(fileobj%ncid, d(j), n)
233  call check_netcdf_code(err, append_error_msg)
234  do k = 1, ndim
235  if (string_compare(n, dimnames(k))) then
236  d(j) = dimids(k)
237  exit
238  endif
239  enddo
240  enddo
241 
242  !Define variable in new file.
243  err = nf90_def_var(new_fileobj%ncid, varname, xtype, d(1:varndim), varid)
244  call check_netcdf_code(err, append_error_msg)
245 
246  !If the variable is an "axis", copy its data to the new file.
247  if (varndim .eq. 1 .and. d(1) .ne. ulim_dimid) then
248  do k = 1, ndim
249  if (string_compare(varname, dimnames(k))) then
250  call set_netcdf_mode(fileobj%ncid, data_mode)
251  call set_netcdf_mode(new_fileobj%ncid, data_mode)
252  if (xtype .eq. nf90_int) then
253  allocate(buf_int(dimlens(k)))
254  err = nf90_get_var(fileobj%ncid, i, buf_int)
255  call check_netcdf_code(err, append_error_msg)
256  err = nf90_put_var(new_fileobj%ncid, varid, buf_int)
257  deallocate(buf_int)
258  elseif (xtype .eq. nf90_float) then
259  allocate(buf_float(dimlens(k)))
260  err = nf90_get_var(fileobj%ncid, i, buf_float)
261  call check_netcdf_code(err, append_error_msg)
262  err = nf90_put_var(new_fileobj%ncid, varid, buf_float)
263  deallocate(buf_float)
264  elseif (xtype .eq. nf90_double) then
265  allocate(buf_double(dimlens(k)))
266  err = nf90_get_var(fileobj%ncid, i, buf_double)
267  call check_netcdf_code(err, append_error_msg)
268  err = nf90_put_var(new_fileobj%ncid, varid, buf_double)
269  deallocate(buf_double)
270  else
271  call error(append_error_msg//" "//trim(varname)//" has an unsupported type, "&
272  //"only nf90_int, nf90_float, and nf90_double are currently supported")
273 
274  endif
275  call check_netcdf_code(err, append_error_msg)
276  call set_netcdf_mode(fileobj%ncid, define_mode)
277  call set_netcdf_mode(new_fileobj%ncid, define_mode)
278  exit
279  endif
280  enddo
281  endif
282 
283  !Copy variable attributes to the new file.
284  do j = 1, natt
285  err = nf90_inq_attname(fileobj%ncid, i, j, n)
286  call check_netcdf_code(err, append_error_msg)
287  err = nf90_copy_att(fileobj%ncid, i, n, new_fileobj%ncid, varid)
288  call check_netcdf_code(err, append_error_msg)
289  enddo
290  enddo
291  endif
292 
293  if (new_fileobj%is_restart) then
294  !Copy pointers to buffers (this is aliasing!).
295  do i = 1, fileobj%num_restart_vars
296  new_fileobj%restart_vars(i)%varname = fileobj%restart_vars(i)%varname
297  if (associated(fileobj%restart_vars(i)%data0d)) then
298  new_fileobj%restart_vars(i)%data0d => fileobj%restart_vars(i)%data0d
299  elseif (associated(fileobj%restart_vars(i)%data1d)) then
300  new_fileobj%restart_vars(i)%data1d => fileobj%restart_vars(i)%data1d
301  elseif (associated(fileobj%restart_vars(i)%data2d)) then
302  new_fileobj%restart_vars(i)%data2d => fileobj%restart_vars(i)%data2d
303  elseif (associated(fileobj%restart_vars(i)%data3d)) then
304  new_fileobj%restart_vars(i)%data3d => fileobj%restart_vars(i)%data3d
305  elseif (associated(fileobj%restart_vars(i)%data4d)) then
306  new_fileobj%restart_vars(i)%data4d => fileobj%restart_vars(i)%data4d
307  else
308  call error("this branch should not be reached.")
309  endif
310  enddo
311  new_fileobj%num_restart_vars = fileobj%num_restart_vars
312  endif
313 
314  !Copy compressed dimension metadata.
315  do i = 1, fileobj%num_compressed_dims
316  new_fileobj%compressed_dims(i)%dimname = fileobj%compressed_dims(i)%dimname
317  k = size(fileobj%compressed_dims(i)%npes_corner)
318  allocate(new_fileobj%compressed_dims(i)%npes_corner(k))
319  allocate(new_fileobj%compressed_dims(i)%npes_nelems(k))
320  do j = 1, k
321  new_fileobj%compressed_dims(i)%npes_corner(j) = fileobj%compressed_dims(i)%npes_corner(j)
322  new_fileobj%compressed_dims(i)%npes_nelems(j) = fileobj%compressed_dims(i)%npes_nelems(j)
323  enddo
324  new_fileobj%compressed_dims(i)%nelems = fileobj%compressed_dims(i)%nelems
325  enddo
326  new_fileobj%num_compressed_dims = fileobj%num_compressed_dims
327 end subroutine copy_metadata
328 
329 
330 !> @brief Make a copy of a file's metadata to support "intermediate restarts".
331 !! @internal
332 subroutine new_netcdf_file(fileobj, path, mode, new_fileobj, nc_format)
333 
334  class(fmsnetcdffile_t), intent(in), target :: fileobj !< File object.
335  character(len=*), intent(in) :: path !< Name of new file.
336  character(len=*), intent(in) :: mode !< File mode. Allowed values are:
337  !! "read", "append", "write", or
338  !! "overwrite".
339  class(fmsnetcdffile_t), intent(out) :: new_fileobj !< New file object.
340  character(len=*), intent(in), optional :: nc_format !< Netcdf format that
341  !! new files are written
342  !! as. Allowed values
343  !! are: "64bit", "classic",
344  !! or "netcdf4". Defaults to
345  !! "64bit".
346 
347  logical :: success
348 
349  !Open the new file.
350  success = netcdf_file_open(new_fileobj, path, mode, nc_format, &
351  fileobj%pelist, fileobj%is_restart)
352  if (.not. success) then
353  call error("error opening file "//trim(path)//".")
354  endif
355  call copy_metadata(fileobj, new_fileobj)
356 end subroutine new_netcdf_file
357 
358 
359 !> @brief Wrapper to distinguish interfaces.
360 !!
361 !> @return Flag telling whether the creation of the buffer was successful.
362 function create_diskless_netcdf_file_wrap(fileobj, pelist, path) &
363  result(success)
364 
365  type(fmsnetcdffile_t), intent(inout) :: fileobj !< File object.
366  integer, dimension(:), intent(in), optional :: pelist !< List of ranks associated
367  !! with this file. If not
368  !! provided, only the current
369  !! rank will be able to
370  !! act on the file.
371  character(len=*), intent(in), optional :: path !< File path.
372  logical :: success
373 
374  success = create_diskless_netcdf_file(fileobj, pelist, path)
376 
377 
378 !> @brief Support for writing new restarts from a diskless file.
379 subroutine netcdf_save_restart_wrap2(fileobj, unlim_dim_level, directory, timestamp, &
380  filename, nc_format)
381 
382  type(fmsnetcdffile_t), intent(in), target :: fileobj !< File object.
383  integer, intent(in), optional :: unlim_dim_level !< Unlimited dimension
384  !! level.
385  character(len=*), intent(in), optional :: directory !< Directory to write restart file to.
386  character(len=*), intent(in), optional :: timestamp !< Model time.
387  character(len=*), intent(in), optional :: filename !< New name for the file.
388  character(len=*), intent(in), optional :: nc_format !< Netcdf format that
389  !! new files are written
390  !! as. Allowed values
391  !! are: "64bit", "classic",
392  !! or "netcdf4". Defaults to
393  !! "64bit".
394 
395  character(len=FMS_PATH_LEN) :: new_name
396  type(fmsnetcdffile_t), target :: new_fileobj
397  type(fmsnetcdffile_t), pointer :: p
398  logical :: close_new_file
399 
400  call get_new_filename(fileobj%path, new_name, directory, timestamp, filename)
401  if (string_compare(fileobj%path, new_name)) then
402  p => fileobj
403  close_new_file = .false.
404  else
405  call new_netcdf_file(fileobj, new_name, "write", new_fileobj, nc_format)
406  p => new_fileobj
407  close_new_file = .true.
408  endif
409  call netcdf_save_restart(p, unlim_dim_level)
410  if (close_new_file) then
411  call netcdf_file_close(p)
412  endif
413 end subroutine netcdf_save_restart_wrap2
414 
415 
416 !> @brief Loop through registered restart variables and read them from
417 !! a netcdf file.
418 subroutine netcdf_restore_state_wrap(fileobj, unlim_dim_level, directory, timestamp, &
419  filename)
420 
421  type(fmsnetcdffile_t), intent(inout), target :: fileobj !< File object.
422  integer, intent(in), optional :: unlim_dim_level !< Unlimited dimension
423  !! level.
424  character(len=*), intent(in), optional :: directory !< Directory to write restart file to.
425  character(len=*), intent(in), optional :: timestamp !< Model time.
426  character(len=*), intent(in), optional :: filename !< New name for the file.
427 
428  character(len=FMS_PATH_LEN) :: new_name
429  type(fmsnetcdffile_t), target :: new_fileobj
430  type(fmsnetcdffile_t), pointer :: p
431  logical :: close_new_file
432 
433  call get_new_filename(fileobj%path, new_name, directory, timestamp, filename)
434  if (string_compare(fileobj%path, new_name)) then
435  p => fileobj
436  close_new_file = .false.
437  else
438  call new_netcdf_file(fileobj, new_name, "read", new_fileobj)
439  p => new_fileobj
440  close_new_file = .true.
441  endif
442  call netcdf_restore_state(p, unlim_dim_level)
443  if (close_new_file) then
444  call netcdf_file_close(p)
445  endif
446 end subroutine netcdf_restore_state_wrap
447 
448 
449 !> @brief Create a "diskless" netcdf file to act as a buffer to support our "register
450 !! data to a file without knowing its name" legacy restart I/O workflow.
451 !! @return Flag telling whether the creation of the buffer was successful.
452 function create_diskless_domain_file(fileobj, domain, path) &
453  result(success)
454 
455  type(fmsnetcdfdomainfile_t), intent(inout) :: fileobj !< File object.
456  type(domain2d), intent(in) :: domain !< Two-dimensional domain.
457  character(len=*), intent(in), optional :: path !< File path.
458  logical :: success
459 
460  type(domain2d), pointer :: io_domain
461  integer :: pelist_size
462  integer, dimension(:), allocatable :: pelist
463 
464  io_domain => mpp_get_io_domain(domain)
465  if (.not. associated(io_domain)) then
466  call error("The domain associated with the file: "//trim(fileobj%path)//" does not have an io_domain.")
467  endif
468  pelist_size = mpp_get_domain_npes(io_domain)
469  allocate(pelist(pelist_size))
470  call mpp_get_pelist(io_domain, pelist)
471  success = create_diskless_netcdf_file(fileobj, pelist, path)
472  deallocate(pelist)
473  fileobj%domain = domain
474  allocate(fileobj%xdims(max_num_domain_decomposed_dims))
475  fileobj%nx = 0
476  allocate(fileobj%ydims(max_num_domain_decomposed_dims))
477  fileobj%ny = 0
478  call string_copy(fileobj%non_mangled_path, fileobj%path)
479 end function create_diskless_domain_file
480 
481 
482 !> @brief Make a copy of a file's metadata to support "intermediate restarts".
483 !! @internal
484 subroutine new_domain_file(fileobj, path, mode, new_fileobj, nc_format)
485 
486  type(fmsnetcdfdomainfile_t), intent(in) :: fileobj !< File object.
487  character(len=*), intent(in) :: path !< Name of new file.
488  character(len=*), intent(in) :: mode !< File mode. Allowed values are:
489  !! "read", "append", "write", or "overwrite".
490  type(fmsnetcdfdomainfile_t), intent(out) :: new_fileobj !< File object.
491  character(len=*), intent(in), optional :: nc_format !< Netcdf format that
492  !! new files are written
493  !! as. Allowed values
494  !! are: "64bit", "classic",
495  !! or "netcdf4". Defaults to
496  !! "64bit".
497 
498  logical :: success
499  integer :: i
500 
501  success = open_domain_file(new_fileobj, path, mode, fileobj%domain, nc_format, &
502  fileobj%is_restart)
503  if (.not. success) then
504  call error("error opening file "//trim(path)//".")
505  endif
506  call copy_metadata(fileobj, new_fileobj)
507  do i = 1, fileobj%nx
508  call string_copy(new_fileobj%xdims(i)%varname, fileobj%xdims(i)%varname)
509  new_fileobj%xdims(i)%pos = fileobj%xdims(i)%pos
510  enddo
511  new_fileobj%nx = fileobj%nx
512  do i = 1, fileobj%ny
513  call string_copy(new_fileobj%ydims(i)%varname, fileobj%ydims(i)%varname)
514  new_fileobj%ydims(i)%pos = fileobj%ydims(i)%pos
515  enddo
516  new_fileobj%ny = fileobj%ny
517 end subroutine new_domain_file
518 
519 
520 !> @brief Loop through registered restart variables and write them to
521 !! a netcdf file.
522 subroutine save_domain_restart_wrap(fileobj, unlim_dim_level, directory, timestamp, &
523  filename, nc_format)
524 
525  type(fmsnetcdfdomainfile_t), intent(in), target :: fileobj !< File object.
526  integer, intent(in), optional :: unlim_dim_level !< Unlimited dimension level.
527  character(len=*), intent(in), optional :: directory !< Directory to write restart file to.
528  character(len=*), intent(in), optional :: timestamp !< Model time.
529  character(len=*), intent(in), optional :: filename !< New name for the file.
530  character(len=*), intent(in), optional :: nc_format !< Netcdf format that
531  !! new files are written
532  !! as. Allowed values
533  !! are: "64bit", "classic",
534  !! or "netcdf4". Defaults to
535  !! "64bit".
536 
537  character(len=FMS_PATH_LEN) :: new_name
538  type(fmsnetcdfdomainfile_t), target :: new_fileobj
539  type(fmsnetcdfdomainfile_t), pointer :: p
540  logical :: close_new_file
541 
542  call get_new_filename(fileobj%non_mangled_path, new_name, directory, timestamp, filename)
543  if (string_compare(fileobj%non_mangled_path, new_name)) then
544  p => fileobj
545  close_new_file = .false.
546  else
547  call new_domain_file(fileobj, new_name, "write", new_fileobj, nc_format)
548  p => new_fileobj
549  close_new_file = .true.
550  endif
551  call save_domain_restart(p, unlim_dim_level)
552  if (close_new_file) then
553  call close_domain_file(p)
554  endif
555 end subroutine save_domain_restart_wrap
556 
557 
558 !> @brief Loop through registered restart variables and read them from
559 !! a netcdf file.
560 subroutine restore_domain_state_wrap(fileobj, unlim_dim_level, directory, timestamp, &
561  filename, ignore_checksum)
562 
563  type(fmsnetcdfdomainfile_t), intent(in), target :: fileobj !< File object.
564  integer, intent(in), optional :: unlim_dim_level !< Unlimited dimension level.
565  character(len=*), intent(in), optional :: directory !< Directory to write restart file to.
566  character(len=*), intent(in), optional :: timestamp !< Model time.
567  character(len=*), intent(in), optional :: filename !< New name for the file.
568  logical, intent(in), optional :: ignore_checksum !< Checksum data integrity flag.
569 
570  character(len=FMS_PATH_LEN) :: new_name
571  type(fmsnetcdfdomainfile_t), target :: new_fileobj
572  type(fmsnetcdfdomainfile_t), pointer :: p
573  logical :: close_new_file
574 
575  call get_new_filename(fileobj%non_mangled_path, new_name, directory, timestamp, filename)
576  if (string_compare(fileobj%non_mangled_path, new_name)) then
577  p => fileobj
578  close_new_file = .false.
579  else
580  call new_domain_file(fileobj, new_name, "read", new_fileobj)
581  p => new_fileobj
582  close_new_file = .true.
583  endif
584  call restore_domain_state(p, unlim_dim_level, ignore_checksum=ignore_checksum)
585  if (close_new_file) then
586  call close_domain_file(p)
587  endif
588 end subroutine restore_domain_state_wrap
589 
590 
591 !> @brief Create a "diskless" netcdf file to act as a buffer to support our "register
592 !! data to a file without knowing its name" legacy restart I/O workflow.
593 !! @return Flag telling whether the creation of the buffer was successful.
594 function create_diskless_unstructured_domain_file(fileobj, domain, path) &
595  result(success)
596 
597  type(fmsnetcdfunstructureddomainfile_t), intent(inout) :: fileobj !< File object.
598  type(domainug), intent(in) :: domain !< Two-dimensional domain.
599  character(len=*), intent(in), optional :: path !< File path.
600  logical :: success
601 
602  type(domainug), pointer :: io_domain
603  integer :: pelist_size
604  integer, dimension(:), allocatable :: pelist
605 
606  io_domain => mpp_get_ug_io_domain(domain)
607  if (.not. associated(io_domain)) then
608  call error("The domain associated with the file: "//trim(fileobj%path)//" does have an io_domain.")
609  endif
610  pelist_size = mpp_get_ug_domain_npes(io_domain)
611  allocate(pelist(pelist_size))
612  call mpp_get_ug_domain_pelist(io_domain, pelist)
613  success = create_diskless_netcdf_file(fileobj, pelist, path)
614  deallocate(pelist)
615  fileobj%domain = domain
616  call string_copy(fileobj%non_mangled_path, fileobj%path)
618 
619 
620 !> @brief Make a copy of a file's metadata to support "intermediate restarts".
621 !! @internal
622 subroutine new_unstructured_domain_file(fileobj, path, mode, new_fileobj, nc_format)
623 
624  type(fmsnetcdfunstructureddomainfile_t), intent(in) :: fileobj !< File object.
625  character(len=*), intent(in) :: path !< Name of new file.
626  character(len=*), intent(in) :: mode !< File mode. Allowed values are:
627  !! "read", "append", "write", or "overwrite."
628  type(fmsnetcdfunstructureddomainfile_t), intent(out) :: new_fileobj !< New file object.
629  character(len=*), intent(in), optional :: nc_format !< Netcdf format that
630  !! new files are written
631  !! as. Allowed values
632  !! are: "64bit", "classic",
633  !! or "netcdf4". Defaults to
634  !! "64bit".
635 
636  logical :: success
637 
638  success = open_unstructured_domain_file(new_fileobj, path, mode, fileobj%domain, &
639  nc_format, fileobj%is_restart)
640  if (.not. success) then
641  call error("error while opening file "//trim(path)//".")
642  endif
643  call copy_metadata(fileobj, new_fileobj)
644 end subroutine new_unstructured_domain_file
645 
646 
647 !> @brief Wrapper to distinguish interfaces.
648 subroutine unstructured_write_restart_wrap(fileobj, unlim_dim_level, directory, timestamp, &
649  filename, nc_format)
650 
651  type(fmsnetcdfunstructureddomainfile_t), intent(in) :: fileobj !< File object.
652  integer, intent(in), optional :: unlim_dim_level !< Unlimited dimension level.
653  character(len=*), intent(in), optional :: directory !< Directory to write restart file to.
654  character(len=*), intent(in), optional :: timestamp !< Model time.
655  character(len=*), intent(in), optional :: filename !< New name for the file.
656  character(len=*), intent(in), optional :: nc_format !< Netcdf format that
657  !! new files are written
658  !! as. Allowed values
659  !! are: "64bit", "classic",
660  !! or "netcdf4". Defaults to
661  !! "64bit".
662 
663  character(len=256) :: new_name
664  type(fmsnetcdfunstructureddomainfile_t) :: new_fileobj
665 
666  call get_new_filename(fileobj%non_mangled_path, new_name, directory, timestamp, filename)
667  if (string_compare(fileobj%non_mangled_path, new_name)) then
668  call netcdf_save_restart(fileobj, unlim_dim_level)
669  else
670  call new_unstructured_domain_file(fileobj, new_name, "write", new_fileobj, nc_format)
671  call netcdf_save_restart(new_fileobj, unlim_dim_level)
672  call close_unstructured_domain_file(new_fileobj)
673  endif
674 end subroutine unstructured_write_restart_wrap
675 
676 
677 end module blackboxio
678 !> @}
679 ! close documentation grouping
logical function, public create_diskless_netcdf_file_wrap(fileobj, pelist, path)
Wrapper to distinguish interfaces.
Definition: blackboxio.F90:364
subroutine new_netcdf_file(fileobj, path, mode, new_fileobj, nc_format)
Make a copy of a file's metadata to support "intermediate restarts".
Definition: blackboxio.F90:333
logical function, public create_diskless_unstructured_domain_file(fileobj, domain, path)
Create a "diskless" netcdf file to act as a buffer to support our "register data to a file wi...
Definition: blackboxio.F90:596
subroutine, public unstructured_write_restart_wrap(fileobj, unlim_dim_level, directory, timestamp, filename, nc_format)
Wrapper to distinguish interfaces.
Definition: blackboxio.F90:650
subroutine, public save_domain_restart_wrap(fileobj, unlim_dim_level, directory, timestamp, filename, nc_format)
Loop through registered restart variables and write them to a netcdf file.
Definition: blackboxio.F90:524
subroutine tempfile(filename)
Create a unique filename (poor man's version of mktemp).
Definition: blackboxio.F90:92
logical function, public create_diskless_domain_file(fileobj, domain, path)
Create a "diskless" netcdf file to act as a buffer to support our "register data to a file wi...
Definition: blackboxio.F90:454
subroutine new_unstructured_domain_file(fileobj, path, mode, new_fileobj, nc_format)
Make a copy of a file's metadata to support "intermediate restarts".
Definition: blackboxio.F90:623
subroutine get_new_filename(path, new_path, directory, timestamp, new_name)
Create a new file path.
Definition: blackboxio.F90:62
subroutine new_domain_file(fileobj, path, mode, new_fileobj, nc_format)
Make a copy of a file's metadata to support "intermediate restarts".
Definition: blackboxio.F90:485
subroutine, public restore_domain_state_wrap(fileobj, unlim_dim_level, directory, timestamp, filename, ignore_checksum)
Loop through registered restart variables and read them from a netcdf file.
Definition: blackboxio.F90:562
subroutine, public netcdf_restore_state_wrap(fileobj, unlim_dim_level, directory, timestamp, filename)
Loop through registered restart variables and read them from a netcdf file.
Definition: blackboxio.F90:420
subroutine copy_metadata(fileobj, new_fileobj)
Copy metadata from one file object to another.
Definition: blackboxio.F90:166
subroutine, public blackboxio_init(chksz)
Accepts the namelist fms2_io_nml variables relevant to blackboxio.
Definition: blackboxio.F90:54
subroutine, public netcdf_save_restart_wrap2(fileobj, unlim_dim_level, directory, timestamp, filename, nc_format)
Support for writing new restarts from a diskless file.
Definition: blackboxio.F90:381
logical function create_diskless_netcdf_file(fileobj, pelist, path)
Create a "diskless" netcdf file to act as a buffer to support our "register data to a file wi...
Definition: blackboxio.F90:116
logical function, public file_exists(path)
Determine if a file exists.
subroutine, public error(mesg)
Print a message to stderr, then stop the program.
logical function, public string_compare(string1, string2, ignore_case)
Compare strings.
subroutine, public save_domain_restart(fileobj, unlim_dim_level)
Loop through registered restart variables and write them to a netcdf file.
logical function, public open_domain_file(fileobj, path, mode, domain, nc_format, is_restart, dont_add_res_to_filename)
Open a domain netcdf file.
subroutine, public close_domain_file(fileobj)
Close a domain netcdf file.
subroutine, public restore_domain_state(fileobj, unlim_dim_level, ignore_checksum)
Loop through registered restart variables and read them from a netcdf file.
logical function, public open_unstructured_domain_file(fileobj, path, mode, domain, nc_format, is_restart, dont_add_res_to_filename)
Open a netcdf file that is associated with an unstructured domain.
subroutine, public close_unstructured_domain_file(fileobj)
Wrapper to distinguish interfaces.
integer function mpp_get_domain_npes(domain)
Set user stack size.
type(domain2d) function, pointer mpp_get_io_domain(domain)
Set user stack size.
Retrieve list of PEs associated with a domain decomposition. The 1D version of this call returns an a...
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 mpp_pe()
Returns processor ID.
Definition: mpp_util.inc:407
subroutine, public netcdf_restore_state(fileobj, unlim_dim_level)
Loop through registered restart variables and read them from a netcdf file.
Definition: netcdf_io.F90:1127
subroutine, public netcdf_file_close(fileobj)
Close a netcdf file.
Definition: netcdf_io.F90:729
logical function, public netcdf_file_open(fileobj, path, mode, nc_format, pelist, is_restart, dont_add_res_to_filename)
Open a netcdf file.
Definition: netcdf_io.F90:542
subroutine, public set_netcdf_mode(ncid, mode)
Switch to the correct netcdf mode.
Definition: netcdf_io.F90:394
subroutine, public check_netcdf_code(err, msg)
Check for errors returned by netcdf.
Definition: netcdf_io.F90:378
subroutine, public netcdf_save_restart(fileobj, unlim_dim_level)
Loop through registered restart variables and write them to a netcdf file.
Definition: netcdf_io.F90:1085