MOM6
MOM_io.F90
Go to the documentation of this file.
1 !> This module contains I/O framework code
2 module mom_io
3 
4 ! This file is part of MOM6. See LICENSE.md for the license.
5 
6 
7 use mom_error_handler, only : mom_error, note, fatal, warning
10 use mom_grid, only : ocean_grid_type
14 
15 use ensemble_manager_mod, only : get_ensemble_id
16 use fms_mod, only : write_version_number, open_namelist_file, check_nml_error
17 use fms_io_mod, only : file_exist, field_size, read_data
18 use fms_io_mod, only : field_exists => field_exist, io_infra_end=>fms_io_exit
19 use fms_io_mod, only : get_filename_appendix => get_filename_appendix
20 use mpp_domains_mod, only : domain1d, mpp_get_domain_components
21 use mpp_domains_mod, only : center, corner, north_face=>north, east_face=>east
22 use mpp_io_mod, only : open_file => mpp_open, close_file => mpp_close
23 use mpp_io_mod, only : mpp_write_meta, write_field => mpp_write, mpp_get_info
24 use mpp_io_mod, only : mpp_get_atts, mpp_get_axes, mpp_get_axis_data, axistype
25 use mpp_io_mod, only : mpp_get_fields, fieldtype, axistype, flush_file => mpp_flush
26 use mpp_io_mod, only : append_file=>mpp_append, ascii_file=>mpp_ascii
27 use mpp_io_mod, only : multiple=>mpp_multi, netcdf_file=>mpp_netcdf
28 use mpp_io_mod, only : overwrite_file=>mpp_overwr, readonly_file=>mpp_rdonly
29 use mpp_io_mod, only : single_file=>mpp_single, writeonly_file=>mpp_wronly
30 use mpp_io_mod, only : mpp_append, mpp_multi, mpp_overwr, mpp_netcdf, mpp_rdonly
31 use mpp_io_mod, only : get_file_info=>mpp_get_info, get_file_atts=>mpp_get_atts
32 use mpp_io_mod, only : get_file_fields=>mpp_get_fields, get_file_times=>mpp_get_times
33 use mpp_io_mod, only : read_field=>mpp_read, io_infra_init=>mpp_io_init
34 
35 use netcdf
36 
37 implicit none ; private
38 
39 public :: close_file, create_file, field_exists, field_size, fieldtype, get_filename_appendix
40 public :: file_exists, flush_file, get_file_info, get_file_atts, get_file_fields
41 public :: get_file_times, open_file, read_axis_data, read_data, read_field
43 public :: reopen_file, slasher, write_field, write_version_number, mom_io_init
44 public :: open_namelist_file, check_nml_error, io_infra_init, io_infra_end
45 public :: append_file, ascii_file, multiple, netcdf_file, overwrite_file
46 public :: readonly_file, single_file, writeonly_file
47 public :: center, corner, north_face, east_face
49 
50 !> Type for describing a variable, typically a tracer
51 type, public :: vardesc
52  character(len=64) :: name !< Variable name in a NetCDF file
53  character(len=48) :: units !< Physical dimensions of the variable
54  character(len=240) :: longname !< Long name of the variable
55  character(len=8) :: hor_grid !< Horizontal grid: u, v, h, q, Cu, Cv, T, Bu, or 1
56  character(len=8) :: z_grid !< Vertical grid: L, i, or 1
57  character(len=8) :: t_grid !< Time description: s, p, or 1
58  character(len=64) :: cmor_field_name !< CMOR name
59  character(len=64) :: cmor_units !< CMOR physical dimensions of the variable
60  real :: conversion !< for unit conversions, such as needed to
61  !! convert from intensive to extensive
62 end type vardesc
63 
64 interface file_exists
65  module procedure file_exist
66  module procedure mom_file_exists
67 end interface
68 
69 interface mom_read_data
70  module procedure mom_read_data_3d
71  module procedure mom_read_data_2d
72  module procedure mom_read_data_1d
73 end interface
74 
75 
76 contains
77 
78 !> Routine creates a new NetCDF file. It also sets up
79 !! structures that describe this file and variables that will
80 !! later be written to this file. Type for describing a variable, typically a tracer
81 subroutine create_file(unit, filename, vars, novars, fields, threading, timeunit, G, dG, GV)
82  integer, intent(out) :: unit !< unit id of an open file or -1 on a
83  !! nonwriting PE with single file output
84  character(len=*), intent(in) :: filename !< full path to the file to create
85  type(vardesc), intent(in) :: vars(:) !< structures describing fields written to filename
86  integer, intent(in) :: novars !< number of fields written to filename
87  type(fieldtype), intent(inout) :: fields(:) !< array of fieldtypes for each variable
88  integer, optional, intent(in) :: threading !< SINGLE_FILE or MULTIPLE
89  real, optional, intent(in) :: timeunit !< length, in seconds, of the units for time. The
90  !! default value is 86400.0, for 1 day.
91  type(ocean_grid_type), optional, intent(in) :: G !< ocean horizontal grid structure; G or dG
92  !! is required if the new file uses any
93  !! horizontal grid axes.
94  type(dyn_horgrid_type), optional, intent(in) :: dG !< dynamic horizontal grid structure; G or dG
95  !! is required if the new file uses any
96  !! horizontal grid axes.
97  type(verticalgrid_type), optional, intent(in) :: GV !< ocean vertical grid structure, which is
98  !! required if the new file uses any
99  !! vertical grid axes.
100 
101  logical :: use_lath, use_lonh, use_latq, use_lonq, use_time
102  logical :: use_layer, use_int, use_periodic
103  logical :: one_file, domain_set
104  type(axistype) :: axis_lath, axis_latq, axis_lonh, axis_lonq
105  type(axistype) :: axis_layer, axis_int, axis_time, axis_periodic
106  type(axistype) :: axes(4)
107  type(mom_domain_type), pointer :: Domain => null()
108  type(domain1d) :: x_domain, y_domain
109  integer :: numaxes, pack, thread, k
110  integer :: isg, ieg, jsg, jeg, IsgB, IegB, JsgB, JegB
111  integer :: var_periods, num_periods=0
112  real, dimension(:), allocatable :: period_val
113  real, pointer, dimension(:) :: &
114  gridLatT => null(), & ! The latitude or longitude of T or B points for
115  gridlatb => null(), & ! the purpose of labeling the output axes.
116  gridlont => null(), gridlonb => null()
117  character(len=40) :: time_units, x_axis_units, y_axis_units
118  character(len=8) :: t_grid, t_grid_read
119 
120  use_lath = .false. ; use_lonh = .false.
121  use_latq = .false. ; use_lonq = .false.
122  use_time = .false. ; use_periodic = .false.
123  use_layer = .false. ; use_int = .false.
124 
125  thread = single_file
126  if (PRESENT(threading)) thread = threading
127 
128  domain_set = .false.
129  if (present(g)) then
130  domain_set = .true. ; domain => g%Domain
131  gridlatt => g%gridLatT ; gridlatb => g%gridLatB
132  gridlont => g%gridLonT ; gridlonb => g%gridLonB
133  x_axis_units = g%x_axis_units ; y_axis_units = g%y_axis_units
134  isg = g%isg ; ieg = g%ieg ; jsg = g%jsg ; jeg = g%jeg
135  isgb = g%IsgB ; iegb = g%IegB ; jsgb = g%JsgB ; jegb = g%JegB
136  elseif (present(dg)) then
137  domain_set = .true. ; domain => dg%Domain
138  gridlatt => dg%gridLatT ; gridlatb => dg%gridLatB
139  gridlont => dg%gridLonT ; gridlonb => dg%gridLonB
140  x_axis_units = dg%x_axis_units ; y_axis_units = dg%y_axis_units
141  isg = dg%isg ; ieg = dg%ieg ; jsg = dg%jsg ; jeg = dg%jeg
142  isgb = dg%IsgB ; iegb = dg%IegB ; jsgb = dg%JsgB ; jegb = dg%JegB
143  endif
144 
145  one_file = .true.
146  if (domain_set) then
147  one_file = ((thread == single_file) .or. .not.domain%use_io_layout)
148  endif
149 
150  if (one_file) then
151  call open_file(unit, filename, mpp_overwr, mpp_netcdf, threading=thread)
152  else
153  call open_file(unit, filename, mpp_overwr, mpp_netcdf, domain=domain%mpp_domain)
154  endif
155 
156 ! Define the coordinates.
157  do k=1,novars
158  select case (vars(k)%hor_grid)
159  case ('h') ; use_lath = .true. ; use_lonh = .true.
160  case ('q') ; use_latq = .true. ; use_lonq = .true.
161  case ('u') ; use_lath = .true. ; use_lonq = .true.
162  case ('v') ; use_latq = .true. ; use_lonh = .true.
163  case ('T') ; use_lath = .true. ; use_lonh = .true.
164  case ('Bu') ; use_latq = .true. ; use_lonq = .true.
165  case ('Cu') ; use_lath = .true. ; use_lonq = .true.
166  case ('Cv') ; use_latq = .true. ; use_lonh = .true.
167  case ('1') ! Do nothing.
168  case default
169  call mom_error(warning, "MOM_io create_file: "//trim(vars(k)%name)//&
170  " has unrecognized hor_grid "//trim(vars(k)%hor_grid))
171  end select
172  select case (vars(k)%z_grid)
173  case ('L') ; use_layer = .true.
174  case ('i') ; use_int = .true.
175  case ('1') ! Do nothing.
176  case default
177  call mom_error(fatal, "MOM_io create_file: "//trim(vars(k)%name)//&
178  " has unrecognized z_grid "//trim(vars(k)%z_grid))
179  end select
180  t_grid = adjustl(vars(k)%t_grid)
181  select case (t_grid(1:1))
182  case ('s', 'a', 'm') ; use_time = .true.
183  case ('p') ; use_periodic = .true.
184  if (len_trim(t_grid(2:8)) <= 0) call mom_error(fatal, &
185  "MOM_io create_file: No periodic axis length was specified in "//&
186  trim(vars(k)%t_grid) // " in the periodic axes of variable "//&
187  trim(vars(k)%name)//" in file "//trim(filename))
188  var_periods = -9999999
189  t_grid_read = adjustl(t_grid(2:8))
190  read(t_grid_read,*) var_periods
191  if (var_periods == -9999999) call mom_error(fatal, &
192  "MOM_io create_file: Failed to read the number of periods from "//&
193  trim(vars(k)%t_grid) // " in the periodic axes of variable "//&
194  trim(vars(k)%name)//" in file "//trim(filename))
195  if (var_periods < 1) call mom_error(fatal, "MOM_io create_file: "//&
196  "variable "//trim(vars(k)%name)//" in file "//trim(filename)//&
197  " uses a periodic time axis, and must have a positive "//&
198  "value for the number of periods in "//vars(k)%t_grid )
199  if ((num_periods > 0) .and. (var_periods /= num_periods)) &
200  call mom_error(fatal, "MOM_io create_file: "//&
201  "Only one value of the number of periods can be used in the "//&
202  "create_file call for file "//trim(filename)//". The second is "//&
203  "variable "//trim(vars(k)%name)//" with t_grid "//vars(k)%t_grid )
204 
205  num_periods = var_periods
206  case ('1') ! Do nothing.
207  case default
208  call mom_error(warning, "MOM_io create_file: "//trim(vars(k)%name)//&
209  " has unrecognized t_grid "//trim(vars(k)%t_grid))
210  end select
211  end do
212 
213  if ((use_lath .or. use_lonh .or. use_latq .or. use_lonq)) then
214  if (.not.domain_set) call mom_error(fatal, "create_file: "//&
215  "An ocean_grid_type or dyn_horgrid_type is required to create a file with a horizontal coordinate.")
216 
217  call mpp_get_domain_components(domain%mpp_domain, x_domain, y_domain)
218  endif
219  if ((use_layer .or. use_int) .and. .not.present(gv)) call mom_error(fatal, &
220  "create_file: A vertical grid type is required to create a file with a vertical coordinate.")
221 
222 ! Specify all optional arguments to mpp_write_meta: name, units, longname, cartesian, calendar, sense, domain, data, min)
223 ! Otherwise if optional arguments are added to mpp_write_meta the compiler may (and in case of GNU is) get confused and crash.
224  if (use_lath) &
225  call mpp_write_meta(unit, axis_lath, name="lath", units=y_axis_units, longname="Latitude", &
226  cartesian='Y', domain = y_domain, data=gridlatt(jsg:jeg))
227 
228  if (use_lonh) &
229  call mpp_write_meta(unit, axis_lonh, name="lonh", units=x_axis_units, longname="Longitude", &
230  cartesian='X', domain = x_domain, data=gridlont(isg:ieg))
231 
232  if (use_latq) &
233  call mpp_write_meta(unit, axis_latq, name="latq", units=y_axis_units, longname="Latitude", &
234  cartesian='Y', domain = y_domain, data=gridlatb(jsgb:jegb))
235 
236  if (use_lonq) &
237  call mpp_write_meta(unit, axis_lonq, name="lonq", units=x_axis_units, longname="Longitude", &
238  cartesian='X', domain = x_domain, data=gridlonb(isgb:iegb))
239 
240  if (use_layer) &
241  call mpp_write_meta(unit, axis_layer, name="Layer", units=trim(gv%zAxisUnits), &
242  longname="Layer "//trim(gv%zAxisLongName), cartesian='Z', &
243  sense=1, data=gv%sLayer(1:gv%ke))
244 
245  if (use_int) &
246  call mpp_write_meta(unit, axis_int, name="Interface", units=trim(gv%zAxisUnits), &
247  longname="Interface "//trim(gv%zAxisLongName), cartesian='Z', &
248  sense=1, data=gv%sInterface(1:gv%ke+1))
249 
250  if (use_time) then ; if (present(timeunit)) then
251  ! Set appropriate units, depending on the value.
252  if (timeunit < 0.0) then
253  time_units = "days" ! The default value.
254  else if ((timeunit >= 0.99) .and. (timeunit < 1.01)) then
255  time_units = "seconds"
256  else if ((timeunit >= 3599.0) .and. (timeunit < 3601.0)) then
257  time_units = "hours"
258  else if ((timeunit >= 86399.0) .and. (timeunit < 86401.0)) then
259  time_units = "days"
260  else if ((timeunit >= 3.0e7) .and. (timeunit < 3.2e7)) then
261  time_units = "years"
262  else
263  write(time_units,'(es8.2," s")') timeunit
264  endif
265 
266  call mpp_write_meta(unit, axis_time, name="Time", units=time_units, longname="Time", cartesian='T')
267  else
268  call mpp_write_meta(unit, axis_time, name="Time", units="days", longname="Time",cartesian= 'T')
269  endif ; endif
270 
271  if (use_periodic) then
272  if (num_periods <= 1) call mom_error(fatal, "MOM_io create_file: "//&
273  "num_periods for file "//trim(filename)//" must be at least 1.")
274  ! Define a periodic axis with unit labels.
275  allocate(period_val(num_periods))
276  do k=1,num_periods ; period_val(k) = real(k) ; enddo
277  call mpp_write_meta(unit, axis_periodic, name="Period", units="nondimensional", &
278  longname="Periods for cyclical varaiables", cartesian= 't', data=period_val)
279  deallocate(period_val)
280  endif
281 
282  do k=1,novars
283  numaxes = 0
284  select case (vars(k)%hor_grid)
285  case ('h') ; numaxes = 2 ; axes(1) = axis_lonh ; axes(2) = axis_lath
286  case ('q') ; numaxes = 2 ; axes(1) = axis_lonq ; axes(2) = axis_latq
287  case ('u') ; numaxes = 2 ; axes(1) = axis_lonq ; axes(2) = axis_lath
288  case ('v') ; numaxes = 2 ; axes(1) = axis_lonh ; axes(2) = axis_latq
289  case ('T') ; numaxes = 2 ; axes(1) = axis_lonh ; axes(2) = axis_lath
290  case ('Bu') ; numaxes = 2 ; axes(1) = axis_lonq ; axes(2) = axis_latq
291  case ('Cu') ; numaxes = 2 ; axes(1) = axis_lonq ; axes(2) = axis_lath
292  case ('Cv') ; numaxes = 2 ; axes(1) = axis_lonh ; axes(2) = axis_latq
293  case ('1') ! Do nothing.
294  case default
295  call mom_error(warning, "MOM_io create_file: "//trim(vars(k)%name)//&
296  " has unrecognized hor_grid "//trim(vars(k)%hor_grid))
297  end select
298  select case (vars(k)%z_grid)
299  case ('L') ; numaxes = numaxes+1 ; axes(numaxes) = axis_layer
300  case ('i') ; numaxes = numaxes+1 ; axes(numaxes) = axis_int
301  case ('1') ! Do nothing.
302  case default
303  call mom_error(fatal, "MOM_io create_file: "//trim(vars(k)%name)//&
304  " has unrecognized z_grid "//trim(vars(k)%z_grid))
305  end select
306  t_grid = adjustl(vars(k)%t_grid)
307  select case (t_grid(1:1))
308  case ('s', 'a', 'm') ; numaxes = numaxes+1 ; axes(numaxes) = axis_time
309  case ('p') ; numaxes = numaxes+1 ; axes(numaxes) = axis_periodic
310  case ('1') ! Do nothing.
311  case default
312  call mom_error(warning, "MOM_io create_file: "//trim(vars(k)%name)//&
313  " has unrecognized t_grid "//trim(vars(k)%t_grid))
314  end select
315  pack = 1
316 
317  call mpp_write_meta(unit, fields(k), axes(1:numaxes), vars(k)%name, vars(k)%units, &
318  vars(k)%longname, pack = pack)
319  enddo
320 
321  if (use_lath) call write_field(unit, axis_lath)
322  if (use_latq) call write_field(unit, axis_latq)
323  if (use_lonh) call write_field(unit, axis_lonh)
324  if (use_lonq) call write_field(unit, axis_lonq)
325  if (use_layer) call write_field(unit, axis_layer)
326  if (use_int) call write_field(unit, axis_int)
327  if (use_periodic) call write_field(unit, axis_periodic)
328 
329 end subroutine create_file
330 
331 
332 !> This routine opens an existing NetCDF file for output. If it
333 !! does not find the file, a new file is created. It also sets up
334 !! structures that describe this file and the variables that will
335 !! later be written to this file.
336 subroutine reopen_file(unit, filename, vars, novars, fields, threading, timeunit, G, dG, GV)
337  integer, intent(out) :: unit !< unit id of an open file or -1 on a
338  !! nonwriting PE with single file output
339  character(len=*), intent(in) :: filename !< full path to the file to create
340  type(vardesc), intent(in) :: vars(:) !< structures describing fields written to filename
341  integer, intent(in) :: novars !< number of fields written to filename
342  type(fieldtype), intent(inout) :: fields(:) !< array of fieldtypes for each variable
343  integer, optional, intent(in) :: threading !< SINGLE_FILE or MULTIPLE
344  real, optional, intent(in) :: timeunit !< length, in seconds, of the units for time. The
345  !! default value is 86400.0, for 1 day.
346  type(ocean_grid_type), optional, intent(in) :: G !< ocean horizontal grid structure; G or dG
347  !! is required if a new file uses any
348  !! horizontal grid axes.
349  type(dyn_horgrid_type), optional, intent(in) :: dG !< dynamic horizontal grid structure; G or dG
350  !! is required if a new file uses any
351  !! horizontal grid axes.
352  type(verticalgrid_type), optional, intent(in) :: GV !< ocean vertical grid structure, which is
353  !! required if a new file uses any
354  !! vertical grid axes.
355 
356  type(mom_domain_type), pointer :: Domain => null()
357  character(len=200) :: check_name, mesg
358  integer :: length, ndim, nvar, natt, ntime, thread
359  logical :: exists, one_file, domain_set
360 
361  thread = single_file
362  if (PRESENT(threading)) thread = threading
363 
364  check_name = filename
365  length = len(trim(check_name))
366  if (check_name(length-2:length) /= ".nc") check_name = trim(check_name)//".nc"
367  if (thread /= single_file) check_name = trim(check_name)//".0000"
368 
369  inquire(file=check_name,exist=exists)
370 
371  if (.not.exists) then
372  call create_file(unit, filename, vars, novars, fields, threading, timeunit, &
373  g=g, dg=dg, gv=gv)
374  else
375 
376  domain_set = .false.
377  if (present(g)) then
378  domain_set = .true. ; domain => g%Domain
379  elseif (present(dg)) then
380  domain_set = .true. ; domain => dg%Domain
381  endif
382 
383  one_file = .true.
384  if (domain_set) then
385  one_file = ((thread == single_file) .or. .not.domain%use_io_layout)
386  endif
387 
388  if (one_file) then
389  call open_file(unit, filename, mpp_append, mpp_netcdf, threading=thread)
390  else
391  call open_file(unit, filename, mpp_append, mpp_netcdf, domain=domain%mpp_domain)
392  endif
393  if (unit < 0) return
394 
395  call mpp_get_info(unit, ndim, nvar, natt, ntime)
396 
397  if (nvar == -1) then
398  write (mesg,*) "Reopening file ",trim(filename)," apparently had ",nvar,&
399  " variables. Clobbering and creating file with ",novars," instead."
400  call mom_error(warning,"MOM_io: "//mesg)
401  call create_file(unit, filename, vars, novars, fields, threading, timeunit, g=g, gv=gv)
402  elseif (nvar /= novars) then
403  write (mesg,*) "Reopening file ",trim(filename)," with ",novars,&
404  " variables instead of ",nvar,"."
405  call mom_error(fatal,"MOM_io: "//mesg)
406  endif
407 
408  if (nvar>0) call mpp_get_fields(unit,fields(1:nvar))
409 
410  ! Check the field names...
411 ! do i=1,nvar
412 ! call mpp_get_field_atts(fields(i),name)
413 ! !if (trim(name) /= trim(vars%name) then
414 ! !write (mesg,'("Reopening file ",a," variable ",a," is called ",a,".")',&
415 ! ! filename,vars%name,name);
416 ! !call MOM_error(NOTE,"MOM_io: "//mesg)
417 ! enddo
418  endif
419 
420 end subroutine reopen_file
421 
422 
423 subroutine read_axis_data(filename, axis_name, var)
424  character(len=*), intent(in) :: filename, axis_name
425  real, dimension(:), intent(out) :: var
426 
427  integer :: i,len,unit, ndim, nvar, natt, ntime
428  logical :: axis_found
429  type(axistype), allocatable :: axes(:)
430  type(axistype) :: time_axis
431  character(len=32) :: name, units
432 
433  call open_file(unit, trim(filename), action=mpp_rdonly, form=mpp_netcdf, &
434  threading=mpp_multi, fileset=single_file)
435 
436 !Find the number of variables (nvar) in this file
437  call mpp_get_info(unit, ndim, nvar, natt, ntime)
438 ! -------------------------------------------------------------------
439 ! Allocate space for the number of axes in the data file.
440 ! -------------------------------------------------------------------
441  allocate(axes(ndim))
442  call mpp_get_axes(unit, axes, time_axis)
443 
444  axis_found = .false.
445  do i = 1, ndim
446  call mpp_get_atts(axes(i), name=name,len=len,units=units)
447  if (name == axis_name) then
448  axis_found = .true.
449  call mpp_get_axis_data(axes(i),var)
450  exit
451  endif
452  enddo
453 
454  if (.not.axis_found) call mom_error(fatal, "MOM_io read_axis_data: "//&
455  "Unable to find axis "//trim(axis_name)//" in file "//trim(filename))
456 
457  deallocate(axes)
458 
459 end subroutine read_axis_data
460 
461 !> This function determines how many time levels a variable has.
462 function num_timelevels(filename, varname, min_dims) result(n_time)
463  character(len=*), intent(in) :: filename !< name of the file to read
464  character(len=*), intent(in) :: varname !< variable whose number of time levels
465  !! are to be returned
466  integer, optional, intent(in) :: min_dims !< The minimum number of dimensions a variable must have
467  !! if it has a time dimension. If the variable has 1 less
468  !! dimension than this, then 0 is returned.
469  integer :: n_time !< number of time levels varname has in filename
470 
471  logical :: found
472  character(len=200) :: msg
473  character(len=nf90_max_name) :: name
474  integer :: ncid, nvars, status, varid, ndims, n
475  integer, allocatable :: varids(:)
476  integer, dimension(nf90_max_var_dims) :: dimids
477 
478  n_time = -1
479  found = .false.
480 
481  status = nf90_open(filename, nf90_nowrite, ncid)
482  if (status /= nf90_noerr) then
483  call mom_error(warning,"num_timelevels: "//&
484  " Difficulties opening "//trim(filename)//" - "//&
485  trim(nf90_strerror(status)))
486  return
487  endif
488 
489  status = nf90_inquire(ncid, nvariables=nvars)
490  if (status /= nf90_noerr) then
491  call mom_error(warning,"num_timelevels: "//&
492  " Difficulties getting the number of variables in file "//&
493  trim(filename)//" - "//trim(nf90_strerror(status)))
494  return
495  endif
496 
497  if (nvars < 1) then
498  call mom_error(warning,"num_timelevels: "//&
499  " There appear not to be any variables in "//trim(filename))
500  return
501  endif
502 
503 
504  allocate(varids(nvars))
505 
506  status = nf90_inq_varids(ncid, nvars, varids)
507  if (status /= nf90_noerr) then
508  call mom_error(warning,"num_timelevels: "//&
509  " Difficulties getting the variable IDs in file "//&
510  trim(filename)//" - "//trim(nf90_strerror(status)))
511  deallocate(varids) ; return
512  endif
513 
514  do n = 1,nvars
515  status = nf90_inquire_variable(ncid, varids(n), name=name)
516  if (status /= nf90_noerr) then
517  call mom_error(warning,"num_timelevels: "//&
518  " Difficulties getting a variable name in file "//&
519  trim(filename)//" - "//trim(nf90_strerror(status)))
520  endif
521 
522  if (trim(lowercase(name)) == trim(lowercase(varname))) then
523  if (found) then
524  call mom_error(warning,"num_timelevels: "//&
525  " Two variables match the case-insensitive name "//trim(varname)//&
526  " in file "//trim(filename)//" - "//trim(nf90_strerror(status)))
527  else
528  varid = varids(n) ; found = .true.
529  endif
530  endif
531  enddo
532 
533  deallocate(varids)
534 
535  if (.not.found) then
536  call mom_error(warning,"num_timelevels: "//&
537  " variable "//trim(varname)//" was not found in file "//&
538  trim(filename))
539  return
540  endif
541 
542  status = nf90_inquire_variable(ncid, varid, ndims = ndims)
543  if (status /= nf90_noerr) then
544  call mom_error(warning,"num_timelevels: "//&
545  trim(nf90_strerror(status))//" Getting number of dimensions of "//&
546  trim(varname)//" in "//trim(filename))
547  return
548  endif
549 
550  if (present(min_dims)) then
551  if (ndims < min_dims-1) then
552  write(msg, '(I3)') min_dims
553  call mom_error(warning, "num_timelevels: variable "//trim(varname)//&
554  " in file "//trim(filename)//" has fewer than min_dims = "//trim(msg)//&
555  " dimensions.")
556  elseif (ndims == min_dims - 1) then
557  n_time = 0 ; return
558  endif
559  endif
560 
561  status = nf90_inquire_variable(ncid, varid, dimids = dimids(1:ndims))
562  if (status /= nf90_noerr) then
563  call mom_error(warning,"num_timelevels: "//&
564  trim(nf90_strerror(status))//" Getting last dimension ID for "//&
565  trim(varname)//" in "//trim(filename))
566  return
567  endif
568 
569  status = nf90_inquire_dimension(ncid, dimids(ndims), len=n_time)
570  if (status /= nf90_noerr) call mom_error(warning,"num_timelevels: "//&
571  trim(nf90_strerror(status))//" Getting number of time levels of "//&
572  trim(varname)//" in "//trim(filename))
573 
574  return
575 
576 end function num_timelevels
577 
578 
579 !> Returns a vardesc type whose elements have been filled with the provided
580 !! fields. The argument name is required, while the others are optional and
581 !! have default values that are empty strings or are appropriate for a 3-d
582 !! tracer field at the tracer cell centers.
583 function var_desc(name, units, longname, hor_grid, z_grid, t_grid, &
584  cmor_field_name, cmor_units, conversion, caller) result(vd)
585  character(len=*), intent(in) :: name !< variable name
586  character(len=*), optional, intent(in) :: units !< variable units
587  character(len=*), optional, intent(in) :: longname !< variable long name
588  character(len=*), optional, intent(in) :: hor_grid !< variable horizonal staggering
589  character(len=*), optional, intent(in) :: z_grid !< variable vertical staggering
590  character(len=*), optional, intent(in) :: t_grid !< time description: s, p, or 1
591  character(len=*), optional, intent(in) :: cmor_field_name !< CMOR name
592  character(len=*), optional, intent(in) :: cmor_units !< CMOR physical dimensions of variable
593  real , optional, intent(in) :: conversion !< for unit conversions, such as needed to
594  !! convert from intensive to extensive
595  character(len=*), optional, intent(in) :: caller !< calling routine?
596  type(vardesc) :: vd !< vardesc type that is created
597 
598  character(len=120) :: cllr
599  cllr = "var_desc"
600  if (present(caller)) cllr = trim(caller)
601 
602  call safe_string_copy(name, vd%name, "vd%name", cllr)
603 
604  vd%longname = "" ; vd%units = ""
605  vd%hor_grid = 'h' ; vd%z_grid = 'L' ; vd%t_grid = 's'
606 
607  vd%cmor_field_name = ""
608  vd%cmor_units = ""
609  vd%conversion = 1.0
610 
611  call modify_vardesc(vd, units=units, longname=longname, hor_grid=hor_grid, &
612  z_grid=z_grid, t_grid=t_grid, &
613  cmor_field_name=cmor_field_name,cmor_units=cmor_units, &
614  conversion=conversion, caller=cllr)
615 
616 end function var_desc
617 
618 
619 !> This routine modifies the named elements of a vardesc type.
620 !! All arguments are optional, except the vardesc type to be modified.
621 subroutine modify_vardesc(vd, name, units, longname, hor_grid, z_grid, t_grid,&
622  cmor_field_name, cmor_units, conversion, caller)
623  type(vardesc), intent(inout) :: vd !< vardesc type that is modified
624  character(len=*), optional, intent(in) :: name !< name of variable
625  character(len=*), optional, intent(in) :: units !< units of variable
626  character(len=*), optional, intent(in) :: longname !< long name of variable
627  character(len=*), optional, intent(in) :: hor_grid !< horizonal staggering of variable
628  character(len=*), optional, intent(in) :: z_grid !< vertical staggering of variable
629  character(len=*), optional, intent(in) :: t_grid !< time description: s, p, or 1
630  character(len=*), optional, intent(in) :: cmor_field_name !< CMOR name
631  character(len=*), optional, intent(in) :: cmor_units !< CMOR physical dimensions of variable
632  real , optional, intent(in) :: conversion !< for unit conversions, such as needed to
633  !! convert from intensive to extensive
634  character(len=*), optional, intent(in) :: caller !< calling routine?
635 
636  character(len=120) :: cllr
637  cllr = "mod_vardesc"
638  if (present(caller)) cllr = trim(caller)
639 
640  if (present(name)) call safe_string_copy(name, vd%name, "vd%name", cllr)
641 
642  if (present(longname)) call safe_string_copy(longname, vd%longname, &
643  "vd%longname of "//trim(vd%name), cllr)
644  if (present(units)) call safe_string_copy(units, vd%units, &
645  "vd%units of "//trim(vd%name), cllr)
646  if (present(hor_grid)) call safe_string_copy(hor_grid, vd%hor_grid, &
647  "vd%hor_grid of "//trim(vd%name), cllr)
648  if (present(z_grid)) call safe_string_copy(z_grid, vd%z_grid, &
649  "vd%z_grid of "//trim(vd%name), cllr)
650  if (present(t_grid)) call safe_string_copy(t_grid, vd%t_grid, &
651  "vd%t_grid of "//trim(vd%name), cllr)
652 
653  if (present(cmor_field_name)) call safe_string_copy(cmor_field_name, vd%cmor_field_name, &
654  "vd%cmor_field_name of "//trim(vd%name), cllr)
655  if (present(cmor_units)) call safe_string_copy(cmor_units, vd%cmor_units, &
656  "vd%cmor_units of "//trim(vd%name), cllr)
657 
658 end subroutine modify_vardesc
659 
660 
661 !> This routine queries vardesc
662 subroutine query_vardesc(vd, name, units, longname, hor_grid, z_grid, t_grid, &
663  cmor_field_name, cmor_units, conversion, caller)
664  type(vardesc), intent(in) :: vd !< vardesc type that is queried
665  character(len=*), optional, intent(out) :: name !< name of variable
666  character(len=*), optional, intent(out) :: units !< units of variable
667  character(len=*), optional, intent(out) :: longname !< long name of variable
668  character(len=*), optional, intent(out) :: hor_grid !< horiz staggering of variable
669  character(len=*), optional, intent(out) :: z_grid !< vert staggering of variable
670  character(len=*), optional, intent(out) :: t_grid !< time description: s, p, or 1
671  character(len=*), optional, intent(out) :: cmor_field_name !< CMOR name
672  character(len=*), optional, intent(out) :: cmor_units !< CMOR physical dimensions of variable
673  real , optional, intent(out) :: conversion !< for unit conversions, such as needed to
674  !! convert from intensive to extensive
675  character(len=*), optional, intent(in) :: caller !< calling routine?
676 
677 
678  character(len=120) :: cllr
679  cllr = "mod_vardesc"
680  if (present(caller)) cllr = trim(caller)
681 
682  if (present(name)) call safe_string_copy(vd%name, name, &
683  "vd%name of "//trim(vd%name), cllr)
684  if (present(longname)) call safe_string_copy(vd%longname, longname, &
685  "vd%longname of "//trim(vd%name), cllr)
686  if (present(units)) call safe_string_copy(vd%units, units, &
687  "vd%units of "//trim(vd%name), cllr)
688  if (present(hor_grid)) call safe_string_copy(vd%hor_grid, hor_grid, &
689  "vd%hor_grid of "//trim(vd%name), cllr)
690  if (present(z_grid)) call safe_string_copy(vd%z_grid, z_grid, &
691  "vd%z_grid of "//trim(vd%name), cllr)
692  if (present(t_grid)) call safe_string_copy(vd%t_grid, t_grid, &
693  "vd%t_grid of "//trim(vd%name), cllr)
694 
695  if (present(cmor_field_name)) call safe_string_copy(vd%cmor_field_name, cmor_field_name, &
696  "vd%cmor_field_name of "//trim(vd%name), cllr)
697  if (present(cmor_units)) call safe_string_copy(vd%cmor_units, cmor_units, &
698  "vd%cmor_units of "//trim(vd%name), cllr)
699 
700 end subroutine query_vardesc
701 
702 
703 !> Copies a string
704 subroutine safe_string_copy(str1, str2, fieldnm, caller)
705  character(len=*), intent(in) :: str1
706  character(len=*), intent(out) :: str2
707  character(len=*), optional, intent(in) :: fieldnm, caller
708 
709  if (len(trim(str1)) > len(str2)) then
710  if (present(fieldnm) .and. present(caller)) then
711  call mom_error(fatal, trim(caller)//" attempted to copy the overly long"//&
712  " string "//trim(str1)//" into "//trim(fieldnm))
713  else
714  call mom_error(fatal, "safe_string_copy: The string "//trim(str1)//&
715  " is longer than its intended target.")
716  endif
717  endif
718  str2 = trim(str1)
719 end subroutine safe_string_copy
720 
721 
722 !> Returns a name with "%#E" or "%E" replaced with the ensemble member number.
723 function ensembler(name, ens_no_in) result(en_nm)
724  character(len=*), intent(in) :: name
725  integer, optional, intent(in) :: ens_no_in
726  character(len=len(name)) :: en_nm
727 
728  ! This function replaces "%#E" or "%E" with the ensemble number anywhere it
729  ! occurs in name, with %E using 4 or 6 digits (depending on the ensemble size)
730  ! and %#E using # digits, where # is a number from 1 to 9.
731 
732  character(len=len(name)) :: tmp
733  character(10) :: ens_num_char
734  character(3) :: code_str
735  integer :: ens_no
736  integer :: n, is, ie
737 
738  en_nm = trim(name)
739  if (index(name,"%") == 0) return
740 
741  if (present(ens_no_in)) then
742  ens_no = ens_no_in
743  else
744  ens_no = get_ensemble_id()
745  endif
746 
747  write(ens_num_char, '(I10)') ens_no ; ens_num_char = adjustl(ens_num_char)
748  do
749  is = index(en_nm,"%E")
750  if (is == 0) exit
751  if (len(en_nm) < len(trim(en_nm)) + len(trim(ens_num_char)) - 2) &
752  call mom_error(fatal, "MOM_io ensembler: name "//trim(name)// &
753  " is not long enough for %E expansion for ens_no "//trim(ens_num_char))
754  tmp = en_nm(1:is-1)//trim(ens_num_char)//trim(en_nm(is+2:))
755  en_nm = tmp
756  enddo
757 
758  if (index(name,"%") == 0) return
759 
760  write(ens_num_char, '(I10.10)') ens_no
761  do n=1,9 ; do
762  write(code_str, '("%",I1,"E")') n
763 
764  is = index(en_nm,code_str)
765  if (is == 0) exit
766  if (ens_no < 10**n) then
767  if (len(en_nm) < len(trim(en_nm)) + n-3) call mom_error(fatal, &
768  "MOM_io ensembler: name "//trim(name)//" is not long enough for %E expansion.")
769  tmp = en_nm(1:is-1)//trim(ens_num_char(11-n:10))//trim(en_nm(is+3:))
770  else
771  call mom_error(fatal, "MOM_io ensembler: Ensemble number is too large "//&
772  "to be encoded with "//code_str//" in "//trim(name))
773  endif
774  en_nm = tmp
775  enddo ; enddo
776 
777 end function ensembler
778 
779 
780 !> Returns true if the named file or its domain-decomposed variant exists.
781 function mom_file_exists(file_name, MOM_Domain)
782  character(len=*), intent(in) :: file_name
783  type(mom_domain_type), intent(in) :: MOM_domain
784 
785 ! This function uses the fms_io function file_exist to determine whether
786 ! a named file (or its decomposed variant) exists.
787 
788  logical :: MOM_file_exists
789 
790  mom_file_exists = file_exist(file_name, mom_domain%mpp_domain)
791 
792 end function mom_file_exists
793 
794 
795 !> This function uses the fms_io function read_data to read 1-D
796 !! data field named "fieldname" from file "filename".
797 subroutine mom_read_data_1d(filename, fieldname, data)
798  character(len=*), intent(in) :: filename, fieldname
799  real, dimension(:), intent(inout) :: data ! 1 dimensional data
800 
801  call read_data(filename, fieldname, data)
802 
803 end subroutine mom_read_data_1d
804 
805 
806 !> This function uses the fms_io function read_data to read a distributed
807 !! 2-D data field named "fieldname" from file "filename". Valid values for
808 !! "position" include CORNER, CENTER, EAST_FACE and NORTH_FACE.
809 subroutine mom_read_data_2d(filename, fieldname, data, MOM_Domain, &
810  timelevel, position)
811  character(len=*), intent(in) :: filename, fieldname
812  real, dimension(:,:), intent(inout) :: data ! 2 dimensional data
813  type(mom_domain_type), intent(in) :: MOM_Domain
814  integer, optional, intent(in) :: timelevel, position
815 
816  call read_data(filename, fieldname, data, mom_domain%mpp_domain, &
817  timelevel=timelevel, position=position)
818 
819 end subroutine mom_read_data_2d
820 
821 
822 !> This function uses the fms_io function read_data to read a distributed
823 !! 2-D data field named "fieldname" from file "filename". Valid values for
824 !! "position" include CORNER, CENTER, EAST_FACE and NORTH_FACE.
825 subroutine mom_read_data_3d(filename, fieldname, data, MOM_Domain, &
826  timelevel, position)
827  character(len=*), intent(in) :: filename, fieldname
828  real, dimension(:,:,:), intent(inout) :: data ! 2 dimensional data
829  type(mom_domain_type), intent(in) :: MOM_Domain
830  integer, optional, intent(in) :: timelevel, position
831 
832  call read_data(filename, fieldname, data, mom_domain%mpp_domain, &
833  timelevel=timelevel, position=position)
834 
835 end subroutine mom_read_data_3d
836 
837 
838 !> Initialize the MOM_io module
839 subroutine mom_io_init(param_file)
840  type(param_file_type), intent(in) :: param_file !< structure indicating the open file to
841  !! parse for model parameter values.
842 
843 ! This include declares and sets the variable "version".
844 #include "version_variable.h"
845  character(len=40) :: mdl = "MOM_io" ! This module's name.
846 
847  call log_version(param_file, mdl, version)
848 
849 end subroutine mom_io_init
850 
851 
852 
853 !> \namespace mom_io
854 !!
855 !! This file contains a number of subroutines that manipulate
856 !! NetCDF files and handle input and output of fields. These
857 !! subroutines, along with their purpose, are:
858 !!
859 !! * create_file: create a new file and set up structures that are
860 !! needed for subsequent output and write out the coordinates.
861 !! * reopen_file: reopen an existing file for writing and set up
862 !! structures that are needed for subsequent output.
863 !! * open_input_file: open the indicated file for reading only.
864 !! * close_file: close an open file.
865 !! * synch_file: flush the buffers, completing all pending output.
866 !!
867 !! * write_field: write a field to an open file.
868 !! * write_time: write a value of the time axis to an open file.
869 !! * read_field: read a field from an open file.
870 !! * read_time: read a time from an open file.
871 !!
872 !! * name_output_file: provide a name for an output file based on a
873 !! name root and the time of the output.
874 !! * find_input_file: find a file that has been previously written by
875 !! MOM and named by name_output_file and open it for reading.
876 !!
877 !! * handle_error: write an error code and quit.
878 
879 
880 
881 end module mom_io
subroutine, public mom_io_init(param_file)
Initialize the MOM_io module.
Definition: MOM_io.F90:840
subroutine, public read_axis_data(filename, axis_name, var)
Definition: MOM_io.F90:424
character(len=len(name)) function, public ensembler(name, ens_no_in)
Returns a name with "%#E" or "%E" replaced with the ensemble member number.
Definition: MOM_io.F90:724
type(vardesc) function, public var_desc(name, units, longname, hor_grid, z_grid, t_grid, cmor_field_name, cmor_units, conversion, caller)
Returns a vardesc type whose elements have been filled with the provided fields. The argument name is...
Definition: MOM_io.F90:585
Ocean grid type. See mom_grid for details.
Definition: MOM_grid.F90:19
Provides the ocean grid type.
Definition: MOM_grid.F90:2
subroutine safe_string_copy(str1, str2, fieldnm, caller)
Copies a string.
Definition: MOM_io.F90:705
subroutine mom_read_data_3d(filename, fieldname, data, MOM_Domain, timelevel, position)
This function uses the fms_io function read_data to read a distributed 2-D data field named "fieldnam...
Definition: MOM_io.F90:827
This module contains I/O framework code.
Definition: MOM_io.F90:2
subroutine mom_read_data_1d(filename, fieldname, data)
This function uses the fms_io function read_data to read 1-D data field named "fieldname" from file "...
Definition: MOM_io.F90:798
character(len=len(input_string)) function, public lowercase(input_string)
character(len=len(dir)+2) function, public slasher(dir)
Returns a directory name that is terminated with a "/" or "./" if the argument is an empty string...
subroutine mom_read_data_2d(filename, fieldname, data, MOM_Domain, timelevel, position)
This function uses the fms_io function read_data to read a distributed 2-D data field named "fieldnam...
Definition: MOM_io.F90:811
logical function mom_file_exists(file_name, MOM_Domain)
Returns true if the named file or its domain-decomposed variant exists.
Definition: MOM_io.F90:782
Type for describing a variable, typically a tracer.
Definition: MOM_io.F90:51
The MOM_domain_type contains information about the domain decompositoin.
subroutine, public query_vardesc(vd, name, units, longname, hor_grid, z_grid, t_grid, cmor_field_name, cmor_units, conversion, caller)
This routine queries vardesc.
Definition: MOM_io.F90:664
subroutine, public reopen_file(unit, filename, vars, novars, fields, threading, timeunit, G, dG, GV)
This routine opens an existing NetCDF file for output. If it does not find the file, a new file is created. It also sets up structures that describe this file and the variables that will later be written to this file.
Definition: MOM_io.F90:337
subroutine, public create_file(unit, filename, vars, novars, fields, threading, timeunit, G, dG, GV)
Routine creates a new NetCDF file. It also sets up structures that describe this file and variables t...
Definition: MOM_io.F90:82
subroutine, public mom_error(level, message, all_print)
subroutine, public modify_vardesc(vd, name, units, longname, hor_grid, z_grid, t_grid, cmor_field_name, cmor_units, conversion, caller)
This routine modifies the named elements of a vardesc type. All arguments are optional, except the vardesc type to be modified.
Definition: MOM_io.F90:623
integer function, public num_timelevels(filename, varname, min_dims)
This function determines how many time levels a variable has.
Definition: MOM_io.F90:463