FMS 2025.01.02-dev
Flexible Modeling System
Loading...
Searching...
No Matches
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!> @{
25module blackboxio
26use netcdf
27use mpp_domains_mod
28use fms_io_utils_mod
29use netcdf_io_mod
30use fms_netcdf_domain_io_mod
31use fms_netcdf_unstructured_domain_io_mod
32use mpp_mod, only: mpp_pe
33use, intrinsic :: iso_fortran_env, only: error_unit
34use platform_mod
35implicit none
36private
37
38integer, private :: fms2_ncchksz = -1 !< Chunksize (bytes) used in nc_open and nc_create
39
40public :: blackboxio_init
49
50
51contains
52!> @brief Accepts the namelist fms2_io_nml variables relevant to blackboxio
53subroutine blackboxio_init (chksz)
54integer, intent(in) :: chksz
55 fms2_ncchksz = chksz
56end subroutine blackboxio_init
57
58
59!> @brief Create a new file path.
60!! @internal
61subroutine 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))
86end subroutine get_new_filename
87
88
89!> @brief Create a unique filename (poor man's version of mktemp).
90!! @internal
91subroutine 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
107end 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
114function 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
161
162
163!> @brief Copy metadata from one file object to another.
164!! @internal
165subroutine 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
327end subroutine copy_metadata
328
329
330!> @brief Make a copy of a file's metadata to support "intermediate restarts".
331!! @internal
332subroutine 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)
356end 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.
362function 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.
379subroutine 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
413end subroutine netcdf_save_restart_wrap2
414
415
416!> @brief Loop through registered restart variables and read them from
417!! a netcdf file.
418subroutine 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
446end 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.
452function 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)
480
481
482!> @brief Make a copy of a file's metadata to support "intermediate restarts".
483!! @internal
484subroutine 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
517end subroutine new_domain_file
518
519
520!> @brief Loop through registered restart variables and write them to
521!! a netcdf file.
522subroutine 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
555end subroutine save_domain_restart_wrap
556
557
558!> @brief Loop through registered restart variables and read them from
559!! a netcdf file.
560subroutine 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
588end 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.
594function 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
622subroutine 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)
644end subroutine new_unstructured_domain_file
645
646
647!> @brief Wrapper to distinguish interfaces.
648subroutine 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
675
676
677end module blackboxio
678!> @}
679! close documentation grouping
subroutine get_new_filename(path, new_path, directory, timestamp, new_name)
Create a new file path.
subroutine, public blackboxio_init(chksz)
Accepts the namelist fms2_io_nml variables relevant to blackboxio.
subroutine, public netcdf_save_restart_wrap2(fileobj, unlim_dim_level, directory, timestamp, filename, nc_format)
Support for writing new restarts from a diskless file.
subroutine new_unstructured_domain_file(fileobj, path, mode, new_fileobj, nc_format)
Make a copy of a file's metadata to support "intermediate restarts".
subroutine new_netcdf_file(fileobj, path, mode, new_fileobj, nc_format)
Make a copy of a file's metadata to support "intermediate restarts".
subroutine copy_metadata(fileobj, new_fileobj)
Copy metadata from one file object to another.
subroutine new_domain_file(fileobj, path, mode, new_fileobj, nc_format)
Make a copy of a file's metadata to support "intermediate restarts".
subroutine tempfile(filename)
Create a unique filename (poor man's version of mktemp).
subroutine, public unstructured_write_restart_wrap(fileobj, unlim_dim_level, directory, timestamp, filename, nc_format)
Wrapper to distinguish interfaces.
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...
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.
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...
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.
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...
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.
logical function, public create_diskless_netcdf_file_wrap(fileobj, pelist, path)
Wrapper to distinguish interfaces.
logical function, public string_compare(string1, string2, ignore_case)
Compare strings.
logical function, public file_exists(path)
Determine if a file exists.
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 save_domain_restart(fileobj, unlim_dim_level)
Loop through registered restart variables and write them to a 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.
subroutine, public close_unstructured_domain_file(fileobj)
Wrapper to distinguish interfaces.
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.
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.
subroutine, public netcdf_restore_state(fileobj, unlim_dim_level)
Loop through registered restart variables and read them from a netcdf file.
subroutine, public set_netcdf_mode(ncid, mode)
Switch to the correct netcdf mode.
subroutine, public check_netcdf_code(err, msg)
Check for errors returned by netcdf.
logical function, public netcdf_file_open(fileobj, path, mode, nc_format, pelist, is_restart, dont_add_res_to_filename)
Open a netcdf file.
subroutine, public netcdf_save_restart(fileobj, unlim_dim_level)
Loop through registered restart variables and write them to a netcdf file.
subroutine, public netcdf_file_close(fileobj)
Close a netcdf file.