MOM_netcdf.F90
1! This file is part of MOM6, the Modular Ocean Model version 6.
2! See the LICENSE file for licensing information.
3! SPDX-License-Identifier: Apache-2.0
4
5!> MOM6 interface to netCDF operations
6module mom_netcdf
7
8use, intrinsic :: iso_fortran_env, only : real32, real64
9
10use netcdf, only : nf90_create, nf90_open, nf90_close
11use netcdf, only : nf90_sync
12use netcdf, only : nf90_clobber, nf90_noclobber, nf90_write, nf90_nowrite
13use netcdf, only : nf90_enddef
14use netcdf, only : nf90_def_dim, nf90_def_var
15use netcdf, only : nf90_unlimited
16use netcdf, only : nf90_get_var
17use netcdf, only : nf90_put_var, nf90_put_att
18use netcdf, only : nf90_float, nf90_double
19use netcdf, only : nf90_strerror, nf90_noerr
20use netcdf, only : nf90_global
21use netcdf, only : nf90_inquire, nf90_inquire_dimension, nf90_inquire_variable
22use netcdf, only : nf90_inq_dimids, nf90_inq_varids
23use netcdf, only : nf90_max_name
24
25use mom_error_handler, only : mom_error, fatal
26use mom_io_infra, only : readonly_file, writeonly_file
27use mom_io_infra, only : append_file, overwrite_file
28
29implicit none ; private
30
31public :: netcdf_file_type
32public :: netcdf_axis
33public :: netcdf_field
34public :: open_netcdf_file
35public :: close_netcdf_file
36public :: flush_netcdf_file
37public :: register_netcdf_axis
38public :: register_netcdf_field
39public :: write_netcdf_field
40public :: write_netcdf_axis
41public :: write_netcdf_attribute
42public :: get_netcdf_size
43public :: get_netcdf_fields
44public :: get_netcdf_filename
45public :: read_netcdf_field
46
47
48!> Internal time value used to indicate an uninitialized time
49real, parameter :: nulltime = -1
50! NOTE: For now, we use the FMS-compatible value, but may change in the future.
51
52
53!> netCDF file abstraction
54type :: netcdf_file_type
55 private
56 integer :: ncid
57 !< netCDF file ID
58 character(len=:), allocatable :: filename
59 !< netCDF filename
60 logical :: define_mode
61 !< True if file is in define mode.
62 integer :: time_id
63 !< Time axis variable ID
64 real :: time
65 !< Current model time
66 integer :: time_level
67 !< Current time level for output
68end type netcdf_file_type
69
70
71!> Dimension axis for a netCDF file
72type :: netcdf_axis
73 private
74 character(len=:), allocatable, public :: label
75 !< Axis label name
76 real, allocatable :: points(:)
77 !< Grid points along the axis
78 integer :: dimid
79 !< netCDF dimension ID associated with axis
80 integer :: varid
81 !< netCDF variable ID associated with axis
82end type netcdf_axis
83
84
85!> Field variable for a netCDF file
86type netcdf_field
87 private
88 character(len=:), allocatable, public :: label
89 !< Variable name
90 integer :: varid
91 !< netCDF variable ID for field
92end type netcdf_field
93
94
95!> Write values to a field of a netCDF file
96interface write_netcdf_field
97 module procedure write_netcdf_field_4d
98 module procedure write_netcdf_field_3d
99 module procedure write_netcdf_field_2d
100 module procedure write_netcdf_field_1d
101 module procedure write_netcdf_field_0d
102end interface write_netcdf_field
103
104contains
105
106subroutine open_netcdf_file(handle, filename, mode)
107 type(netcdf_file_type), intent(inout) :: handle
108 !< netCDF file handle
109 character(len=*), intent(in) :: filename
110 !< netCDF filename
111 integer, intent(in), optional :: mode
112 !< Input MOM I/O mode
113
114 integer :: io_mode
115 ! MOM I/O mode
116 integer :: cmode
117 ! netCDF creation mode
118 integer :: rc
119 ! nf90_create return code
120 character(len=:), allocatable :: msg
121 ! netCDF error message buffer
122
123 ! I/O configuration
124 io_mode = writeonly_file
125 if (present(mode)) io_mode = mode
126
127 ! Translate the MOM I/O config to the netCDF mode
128 select case(io_mode)
129 case (writeonly_file)
130 rc = nf90_create(filename, nf90_noclobber, handle%ncid)
131 handle%define_mode = .true.
132 case (overwrite_file)
133 rc = nf90_create(filename, nf90_clobber, handle%ncid)
134 handle%define_mode = .true.
135 case (append_file)
136 rc = nf90_open(filename, nf90_write, handle%ncid)
137 handle%define_mode = .false.
138 case (readonly_file)
139 rc = nf90_open(filename, nf90_nowrite, handle%ncid)
140 handle%define_mode = .false.
141 case default
142 call mom_error(fatal, &
143 'open_netcdf_file: File ' // filename // ': Unknown mode.')
144 end select
145 call check_netcdf_call(rc, 'open_netcdf_file', 'File ' // filename)
146
147 handle%filename = filename
148
149 ! FMS writes the filename as an attribute
150 if (any(io_mode == [writeonly_file, overwrite_file])) &
151 call write_netcdf_attribute(handle, 'filename', filename)
152end subroutine open_netcdf_file
153
154
155!> Close an opened netCDF file.
156subroutine close_netcdf_file(handle)
157 type(netcdf_file_type), intent(in) :: handle
158
159 integer :: rc
160
161 rc = nf90_close(handle%ncid)
162 call check_netcdf_call(rc, 'close_netcdf_file', &
163 'File "' // handle%filename // '"')
164end subroutine close_netcdf_file
165
166
167!> Flush buffered output to the netCDF file
168subroutine flush_netcdf_file(handle)
169 type(netcdf_file_type), intent(in) :: handle
170
171 integer :: rc
172
173 rc = nf90_sync(handle%ncid)
174 call check_netcdf_call(rc, 'flush_netcdf_file', &
175 'File "' // handle%filename // '"')
176end subroutine flush_netcdf_file
177
178
179!> Change netCDF mode of handle from 'define' to 'write'.
180subroutine enable_netcdf_write(handle)
181 type(netcdf_file_type), intent(inout) :: handle
182
183 integer :: rc
184
185 if (handle%define_mode) then
186 rc = nf90_enddef(handle%ncid)
187 call check_netcdf_call(rc, 'enable_netcdf_write', &
188 'File "' // handle%filename // '"')
189 handle%define_mode = .false.
190 endif
191end subroutine enable_netcdf_write
192
193
194!> Register a netCDF variable
195function register_netcdf_field(handle, label, axes, longname, units) &
196 result(field)
197 type(netcdf_file_type), intent(in) :: handle
198 !< netCDF file handle
199 character(len=*), intent(in) :: label
200 !< netCDF field name in the file
201 type(netcdf_axis), intent(in) :: axes(:)
202 !< Axes along which field is defined
203 character(len=*), intent(in) :: longname
204 !< Long name of the netCDF field
205 character(len=*), intent(in) :: units
206 !< Field units of measurement
207 type(netcdf_field) :: field
208 !< netCDF field
209
210 integer :: rc
211 ! netCDF function return code
212 integer :: i
213 ! Loop index
214 integer, allocatable :: dimids(:)
215 ! netCDF dimension IDs of axes
216 integer :: xtype
217 ! netCDF data type
218
219 ! Gather the axis netCDF dimension IDs
220 allocate(dimids(size(axes)))
221 dimids(:) = [(axes(i)%dimid, i = 1, size(axes))]
222
223 field%label = label
224
225 ! Determine the corresponding netCDF data type
226 ! TODO: Support a `pack`-like argument
227 select case (kind(1.0))
228 case (real32)
229 xtype = nf90_float
230 case (real64)
231 xtype = nf90_double
232 case default
233 call mom_error(fatal, "register_netcdf_field: Unknown kind(real).")
234 end select
235
236 ! Register the field variable
237 rc = nf90_def_var(handle%ncid, label, xtype, dimids, field%varid)
238 call check_netcdf_call(rc, 'register_netcdf_field', &
239 'File "' // handle%filename // '", Field "' // label // '"')
240
241 ! Assign attributes
242
243 rc = nf90_put_att(handle%ncid, field%varid, 'long_name', longname)
244 call check_netcdf_call(rc, 'register_netcdf_field', &
245 'Attribute "long_name" of variable "' // label // '" in file "' &
246 // handle%filename // '"')
247
248 rc = nf90_put_att(handle%ncid, field%varid, 'units', units)
249 call check_netcdf_call(rc, 'register_netcdf_field', &
250 'Attribute "units" of variable "' // label // '" in file "' &
251 // handle%filename // '"')
252end function register_netcdf_field
253
254
255!> Create an axis and associated dimension in a netCDF file
256function register_netcdf_axis(handle, label, units, longname, points, &
257 cartesian, sense) result(axis)
258 type(netcdf_file_type), intent(inout) :: handle
259 !< netCDF file handle
260 character(len=*), intent(in) :: label
261 !< netCDF axis name in the file
262 character(len=*), intent(in), optional :: units
263 !< Axis units of measurement
264 character(len=*), intent(in), optional :: longname
265 !< Long name of the axis
266 real, intent(in), optional :: points(:)
267 !< Values of axis points (for fixed axes)
268 character(len=*), intent(in), optional :: cartesian
269 !< Character denoting axis direction: X, Y, Z, T, or N for none
270 integer, intent(in), optional :: sense
271 !< Axis direction; +1 if axis increases upward or -1 if downward
272
273 type(netcdf_axis) :: axis
274 !< netCDF coordinate axis
275
276 integer :: xtype
277 ! netCDF external data type
278 integer :: rc
279 ! netCDF function return code
280 logical :: unlimited
281 ! True if the axis is unlimited in size (e.g. time)
282 integer :: axis_size
283 ! Either the number of points in the axis, or unlimited flag
284 integer :: axis_sense
285 ! Axis direction; +1 if axis increases upward or -1 if downward
286 character(len=:), allocatable :: sense_attr
287 ! CF-compiant value of sense attribute (as 'positive')
288
289 ! Create the axis dimension
290 unlimited = .false.
291 if (present(cartesian)) then
292 if (cartesian == 'T') unlimited = .true.
293 endif
294
295 ! Either the axis is explicitly set with data or is declared as unlimited
296 if (present(points) .eqv. unlimited) then
297 call mom_error(fatal, &
298 "Axis must either have explicit points or be a time axis ('T').")
299 endif
300
301 axis%label = label
302
303 if (present(points)) then
304 axis_size = size(points)
305 allocate(axis%points(axis_size))
306 axis%points(:) = points(:)
307 else
308 axis_size = nf90_unlimited
309 endif
310
311 rc = nf90_def_dim(handle%ncid, label, axis_size, axis%dimid)
312 call check_netcdf_call(rc, 'register_netcdf_axis', &
313 'Dimension "' // label // '" in file "' // handle%filename // '"')
314
315 ! Determine the corresponding netCDF data type
316 ! TODO: Support a `pack`-like argument
317 select case (kind(1.0))
318 case (real32)
319 xtype = nf90_float
320 case (real64)
321 xtype = nf90_double
322 case default
323 call mom_error(fatal, "register_netcdf_axis: Unknown kind(real).")
324 end select
325
326 ! Create a variable corresponding to the axis
327 rc = nf90_def_var(handle%ncid, label, xtype, axis%dimid, axis%varid)
328 call check_netcdf_call(rc, 'register_netcdf_axis', &
329 'Variable ' // label // ' in file ' // handle%filename)
330
331 ! Define the time axis, if available
332 if (unlimited) then
333 handle%time_id = axis%varid
334 handle%time_level = 0
335 handle%time = nulltime
336 endif
337
338 ! Assign attributes if present
339 if (present(longname)) then
340 rc = nf90_put_att(handle%ncid, axis%varid, 'long_name', longname)
341 call check_netcdf_call(rc, 'register_netcdf_axis', &
342 'Attribute ''long_name'' of variable ' // label // ' in file ' &
343 // handle%filename)
344 endif
345
346 if (present(units)) then
347 rc = nf90_put_att(handle%ncid, axis%varid, 'units', units)
348 call check_netcdf_call(rc, 'register_netcdf_axis', &
349 'Attribute ''units'' of variable ' // label // ' in file ' &
350 // handle%filename)
351 endif
352
353 if (present(cartesian)) then
354 rc = nf90_put_att(handle%ncid, axis%varid, 'cartesian_axis', cartesian)
355 call check_netcdf_call(rc, 'register_netcdf_axis', &
356 'Attribute ''cartesian_axis'' of variable ' // label // ' in file ' &
357 // handle%filename)
358 endif
359
360 axis_sense = 0
361 if (present(sense)) axis_sense = sense
362
363 if (axis_sense /= 0) then
364 select case (axis_sense)
365 case (1)
366 sense_attr = 'up'
367 case (-1)
368 sense_attr = 'down'
369 case default
370 call mom_error(fatal, 'register_netcdf_axis: sense must be either ' &
371 // '0, 1, or -1.')
372 end select
373 rc = nf90_put_att(handle%ncid, axis%varid, 'positive', sense_attr)
374 call check_netcdf_call(rc, 'register_netcdf_axis', &
375 'Attribute "positive" of variable "' // label // '" in file "' &
376 // handle%filename // '"')
377 endif
378end function register_netcdf_axis
379
380
381!> Write a 4D array to a compatible netCDF field
382subroutine write_netcdf_field_4d(handle, field, values, time)
383 type(netcdf_file_type), intent(inout) :: handle
384 !< netCDF file handle
385 type(netcdf_field), intent(in) :: field
386 !< Field metadata
387 real, intent(in) :: values(:,:,:,:)
388 !< Field values
389 real, intent(in), optional :: time
390 !< Timestep index to write data
391
392 integer :: rc
393 ! netCDF return code
394 integer :: start(5)
395 ! Start indices, if timestep is included
396
397 ! Verify write mode
398 if (handle%define_mode) &
399 call enable_netcdf_write(handle)
400
401 if (present(time)) then
402 call update_netcdf_timestep(handle, time)
403 start(:4) = 1
404 start(5) = handle%time_level
405 rc = nf90_put_var(handle%ncid, field%varid, values, start)
406 else
407 rc = nf90_put_var(handle%ncid, field%varid, values)
408 endif
409 call check_netcdf_call(rc, 'write_netcdf_file', &
410 'File "' // handle%filename // '", Field "' // field%label // '"')
411end subroutine write_netcdf_field_4d
412
413
414!> Write a 3D array to a compatible netCDF field
415subroutine write_netcdf_field_3d(handle, field, values, time)
416 type(netcdf_file_type), intent(inout) :: handle
417 !< netCDF file handle
418 type(netcdf_field), intent(in) :: field
419 !< Field metadata
420 real, intent(in) :: values(:,:,:)
421 !< Field values
422 real, intent(in), optional :: time
423 !< Timestep index to write data
424
425 integer :: rc
426 ! netCDF return code
427 integer :: start(4)
428 ! Start indices, if timestep is included
429
430 ! Verify write mode
431 if (handle%define_mode) &
432 call enable_netcdf_write(handle)
433
434 if (present(time)) then
435 call update_netcdf_timestep(handle, time)
436 start(:3) = 1
437 start(4) = handle%time_level
438 rc = nf90_put_var(handle%ncid, field%varid, values, start)
439 else
440 rc = nf90_put_var(handle%ncid, field%varid, values)
441 endif
442 call check_netcdf_call(rc, 'write_netcdf_file', &
443 'File "' // handle%filename // '", Field "' // field%label // '"')
444end subroutine write_netcdf_field_3d
445
446
447!> Write a 2D array to a compatible netCDF field
448subroutine write_netcdf_field_2d(handle, field, values, time)
449 type(netcdf_file_type), intent(inout) :: handle
450 !< netCDF file handle
451 type(netcdf_field), intent(in) :: field
452 !< Field metadata
453 real, intent(in) :: values(:,:)
454 !< Field values
455 real, intent(in), optional :: time
456 !< Timestep index to write data
457
458 integer :: rc
459 ! netCDF return code
460 integer :: start(3)
461 ! Start indices, if timestep is included
462
463 ! Verify write mode
464 if (handle%define_mode) &
465 call enable_netcdf_write(handle)
466
467 if (present(time)) then
468 call update_netcdf_timestep(handle, time)
469 start(:2) = 1
470 start(3) = handle%time_level
471 rc = nf90_put_var(handle%ncid, field%varid, values, start)
472 else
473 rc = nf90_put_var(handle%ncid, field%varid, values)
474 endif
475 call check_netcdf_call(rc, 'write_netcdf_file', &
476 'File "' // handle%filename // '", Field "' // field%label // '"')
477end subroutine write_netcdf_field_2d
478
479
480!> Write a 1D array to a compatible netCDF field
481subroutine write_netcdf_field_1d(handle, field, values, time)
482 type(netcdf_file_type), intent(inout) :: handle
483 !< netCDF file handle
484 type(netcdf_field), intent(in) :: field
485 !< Field metadata
486 real, intent(in) :: values(:)
487 !< Field values
488 real, intent(in), optional :: time
489 !< Timestep index to write data
490
491 integer :: rc
492 ! netCDF return code
493 integer :: start(2)
494 ! Start indices, if timestep is included
495
496 ! Verify write mode
497 if (handle%define_mode) &
498 call enable_netcdf_write(handle)
499
500 if (present(time)) then
501 call update_netcdf_timestep(handle, time)
502 start(1) = 1
503 start(2) = handle%time_level
504 rc = nf90_put_var(handle%ncid, field%varid, values, start)
505 else
506 rc = nf90_put_var(handle%ncid, field%varid, values)
507 endif
508 call check_netcdf_call(rc, 'write_netcdf_file', &
509 'File "' // handle%filename // '", Field "' // field%label // '"')
510end subroutine write_netcdf_field_1d
511
512
513!> Write a scalar to a compatible netCDF field
514subroutine write_netcdf_field_0d(handle, field, scalar, time)
515 type(netcdf_file_type), intent(inout) :: handle
516 !< netCDF file handle
517 type(netcdf_field), intent(in) :: field
518 !< Field metadata
519 real, intent(in) :: scalar
520 !< Field values
521 real, intent(in), optional :: time
522 !< Timestep index to write data
523
524 integer :: rc
525 ! netCDF return code
526 integer :: start(1)
527 ! Start indices, if timestep is included
528
529 ! Verify write mode
530 if (handle%define_mode) &
531 call enable_netcdf_write(handle)
532
533 if (present(time)) then
534 call update_netcdf_timestep(handle, time)
535 start(1) = handle%time_level
536 rc = nf90_put_var(handle%ncid, field%varid, scalar, start)
537 else
538 rc = nf90_put_var(handle%ncid, field%varid, scalar)
539 endif
540 call check_netcdf_call(rc, 'write_netcdf_file', &
541 'File "' // handle%filename // '", Field "' // field%label // '"')
542end subroutine write_netcdf_field_0d
543
544
545!> Write axis points to associated netCDF variable
546subroutine write_netcdf_axis(handle, axis)
547 type(netcdf_file_type), intent(inout) :: handle
548 !< netCDF file handle
549 type(netcdf_axis), intent(in) :: axis
550 !< field variable
551
552 integer :: rc
553 ! netCDF return code
554
555 ! Verify write mode
556 if (handle%define_mode) &
557 call enable_netcdf_write(handle)
558
559 rc = nf90_put_var(handle%ncid, axis%varid, axis%points)
560 call check_netcdf_call(rc, 'write_netcdf_axis', &
561 'File "' // handle%filename // '", Axis "' // axis%label // '"')
562end subroutine write_netcdf_axis
563
564
565!> Write a global attribute to a netCDF file
566subroutine write_netcdf_attribute(handle, label, attribute)
567 type(netcdf_file_type), intent(in) :: handle
568 !< netCDF file handle
569 character(len=*), intent(in) :: label
570 !< File attribute
571 character(len=*), intent(in) :: attribute
572 !< File attribute value
573
574 integer :: rc
575 ! netCDF return code
576
577 rc = nf90_put_att(handle%ncid, nf90_global, label, attribute)
578 call check_netcdf_call(rc, 'write_netcdf_attribute', &
579 'File "' // handle%filename // '", Attribute "' // label // '"')
580end subroutine write_netcdf_attribute
581
582
583! This is a thin interface to nf90_inquire, designed to mirror the existing
584! I/O API. A more axis-aware system might not need this, but for now it's here
585!> Get the number of dimensions, variables, and timesteps in a netCDF file
586subroutine get_netcdf_size(handle, ndims, nvars, nsteps)
587 type(netcdf_file_type), intent(in) :: handle
588 !< netCDF input file
589 integer, intent(out), optional :: ndims
590 !< number of dimensions in the file
591 integer, intent(out), optional :: nvars
592 !< number of variables in the file
593 integer, intent(out), optional :: nsteps
594 !< number of values in the file's unlimited axis
595
596 integer :: rc
597 ! netCDF return code
598 integer :: unlimited_dimid
599 ! netCDF dimension ID for unlimited time axis
600
601 rc = nf90_inquire(handle%ncid, &
602 ndimensions=ndims, &
603 nvariables=nvars, &
604 unlimiteddimid=unlimited_dimid &
605 )
606 call check_netcdf_call(rc, 'get_netcdf_size', &
607 'File "' // handle%filename // '"')
608
609 rc = nf90_inquire_dimension(handle%ncid, unlimited_dimid, len=nsteps)
610 call check_netcdf_call(rc, 'get_netcdf_size', &
611 'File "' // handle%filename // '"')
612end subroutine get_netcdf_size
613
614
615!> Get the metadata of the registered fields in a netCDF file
616subroutine get_netcdf_fields(handle, axes, fields)
617 type(netcdf_file_type), intent(inout) :: handle
618 !< netCDF file handle
619 type(netcdf_axis), intent(inout), allocatable :: axes(:)
620 !< netCDF file axes
621 type(netcdf_field), intent(inout), allocatable :: fields(:)
622 !< netCDF file fields
623
624 integer :: ndims
625 ! Number of netCDF dimensions
626 integer :: nvars
627 ! Number of netCDF dimensions
628 type(netcdf_field), allocatable :: vars(:)
629 ! netCDF variables in handle
630 integer :: nfields
631 ! Number of fields in the file (i.e. non-axis variables)
632 integer, allocatable :: dimids(:)
633 ! netCDF dimension IDs of file
634 integer, allocatable :: varids(:)
635 ! netCDF variable IDs of file
636 integer :: unlim_dimid
637 ! netCDF dimension ID for the unlimited axis variable, if present
638 integer :: unlim_index
639 ! Index of the unlimited axis in axes(:), if present
640 character(len=NF90_MAX_NAME) :: label
641 ! Current dimension or variable label
642 integer :: len
643 ! Current dimension length
644 integer :: rc
645 ! netCDF return code
646 integer :: grp_ndims, grp_nvars
647 ! Group-based counts for nf90_inq_* (unused)
648 logical :: is_axis
649 ! True if the current variable is an axis
650 integer :: i, j, n
651
652 integer, save :: no_parent_groups = 0
653 ! Flag indicating exclusion of parent groups in netCDF file
654 ! NOTE: This must be passed as a variable, and cannot be declared as a
655 ! parameter.
656
657 rc = nf90_inquire(handle%ncid, &
658 ndimensions=ndims, &
659 nvariables=nvars, &
660 unlimiteddimid=unlim_dimid &
661 )
662 call check_netcdf_call(rc, 'get_netcdf_fields', &
663 'File "' // handle%filename // '"')
664
665 allocate(dimids(ndims))
666 rc = nf90_inq_dimids(handle%ncid, grp_ndims, dimids, no_parent_groups)
667 call check_netcdf_call(rc, 'get_netcdf_fields', &
668 'File "' // handle%filename // '"')
669
670 allocate(varids(nvars))
671 rc = nf90_inq_varids(handle%ncid, grp_nvars, varids)
672 call check_netcdf_call(rc, 'get_netcdf_fields', &
673 'File "' // trim(handle%filename) // '"')
674
675 ! Initialize unlim_index with an unreachable value (outside [1,ndims])
676 unlim_index = -1
677
678 allocate(axes(ndims))
679 do i = 1, ndims
680 rc = nf90_inquire_dimension(handle%ncid, dimids(i), name=label, len=len)
681 call check_netcdf_call(rc, 'get_netcdf_fields', &
682 'File "' // trim(handle%filename) // '"')
683
684 ! Check for the unlimited axis
685 if (dimids(i) == unlim_dimid) unlim_index = i
686
687 axes(i)%dimid = dimids(i)
688 axes(i)%label = trim(label)
689 allocate(axes(i)%points(len))
690 enddo
691
692 ! We cannot know if every axis also has a variable representation, so we
693 ! over-allocate vars(:) and fill as fields are identified.
694 allocate(vars(nvars))
695
696 nfields = 0
697 do i = 1, nvars
698 rc = nf90_inquire_variable(handle%ncid, varids(i), name=label)
699 call check_netcdf_call(rc, 'get_netcdf_fields', &
700 'File "' // trim(handle%filename) // '"')
701
702 ! Check if variable is an axis
703 is_axis = .false.
704 do j = 1, ndims
705 if (label == axes(j)%label) then
706 rc = nf90_get_var(handle%ncid, varids(i), axes(j)%points)
707 call check_netcdf_call(rc, 'get_netcdf_fields', &
708 'File "' // trim(handle%filename) // '"')
709 axes(j)%varid = varids(i)
710
711 if (j == unlim_index) then
712 handle%time_id = varids(i)
713 handle%time_level = size(axes(j)%points)
714 handle%time = nulltime
715 endif
716
717 is_axis = .true.
718 exit
719 endif
720 enddo
721 if (is_axis) cycle
722
723 nfields = nfields + 1
724 vars(nfields)%label = trim(label)
725 vars(nfields)%varid = varids(i)
726 enddo
727
728 allocate(fields(nfields))
729 fields(:) = vars(:nfields)
730end subroutine get_netcdf_fields
731
732!> Return the name of a file from a netCDF handle
733function get_netcdf_filename(handle)
734 type(netcdf_file_type), intent(in) :: handle !< A netCDF file handle
735 character(len=:), allocatable :: get_netcdf_filename !< The name of the file that this handle refers to.
736
737 get_netcdf_filename = handle%filename
738
739end function
740
741!> Read the values of a field from a netCDF file
742subroutine read_netcdf_field(handle, field, values, bounds)
743 type(netcdf_file_type), intent(in) :: handle
744 type(netcdf_field), intent(in) :: field
745 real, intent(out) :: values(:,:)
746 integer, optional, intent(in) :: bounds(2,2)
747
748 integer :: rc
749 ! netCDF return code
750 integer :: istart(2)
751 ! Axis start index
752 integer :: icount(2)
753 ! Axis index count
754
755 if (present(bounds)) then
756 istart(:) = bounds(1,:)
757 icount(:) = bounds(2,:) - bounds(1,:) + 1
758 rc = nf90_get_var(handle%ncid, field%varid, values, start=istart, count=icount)
759 else
760 rc = nf90_get_var(handle%ncid, field%varid, values)
761 endif
762 call check_netcdf_call(rc, 'read_netcdf_field', &
763 'File "' // trim(handle%filename) // '", Field "' // trim(field%label) // '"')
764end subroutine read_netcdf_field
765
766
767!> Set the current timestep of an open netCDF file
768subroutine update_netcdf_timestep(handle, time)
769 type(netcdf_file_type), intent(inout) :: handle
770 !< netCDF file handle
771 real, intent(in) :: time
772 !< New model time
773
774 integer :: start(1)
775 !< Time axis start index array
776 integer :: rc
777 !< netCDF return code
778
779 if (time > handle%time + epsilon(time)) then
780 handle%time = time
781 handle%time_level = handle%time_level + 1
782
783 ! Write new value to time axis
784 start = [handle%time_level]
785 rc = nf90_put_var(handle%ncid, handle%time_id, time, start=start)
786 call check_netcdf_call(rc, 'update_netcdf_timestep', &
787 'File "' // handle%filename // '"')
788 endif
789end subroutine update_netcdf_timestep
790
791
792!> Check netCDF function return codes, report the error log, and abort the run.
793subroutine check_netcdf_call(ncerr, header, message)
794 integer, intent(in) :: ncerr
795 !< netCDF error code
796 character(len=*), intent(in) :: header
797 !< Message header (usually calling subroutine)
798 character(len=*), intent(in) :: message
799 !< Error message (usually action which instigated the error)
800
801 character(len=:), allocatable :: errmsg
802 ! Full error message, including netCDF message
803
804 if (ncerr /= nf90_noerr) then
805 errmsg = trim(header) // ": " // trim(message) // new_line('/') &
806 // trim(nf90_strerror(ncerr))
807 call mom_error(fatal, errmsg)
808 endif
809end subroutine check_netcdf_call
810
811end module mom_netcdf