← back to index

config_src/infra/FMS2/MOM_interp_infra.F90

portedportable, not yet portedexecuted, not portableexecutable, not hit by this run

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!> This module wraps the FMS temporal and spatial interpolation routines
6module MOM_interp_infra
7
8use MOM_domain_infra, only : MOM_domain_type, domain2d
9use MOM_io_infra, only : axistype
10use MOM_io_infra, only : set_axis_data
11use MOM_time_manager, only : time_type
12use MOM_error_infra, only : MOM_error => MOM_err, FATAL
13use MOM_string_functions, only : lowercase
14use horiz_interp_mod, only : horiz_interp_new, horiz_interp, horiz_interp_init, horiz_interp_type
15use netcdf_io_mod, only : FmsNetcdfFile_t, netcdf_file_open, netcdf_file_close
16use netcdf_io_mod, only : get_num_variables, get_variable_names
17use time_interp_external2_mod, only : time_interp_external
18use time_interp_external2_mod, only : init_external_field, time_interp_external_init
19use time_interp_external2_mod, only : get_external_field_size
20use time_interp_external2_mod, only : get_external_field_missing
21
22! Use primitive netCDF, to replicate get_var_axes_info()
23use netcdf, only : nf90_open
24use netcdf, only : nf90_close
25use netcdf, only : nf90_inq_varid
26use netcdf, only : nf90_inquire_variable
27use netcdf, only : nf90_inquire_dimension
28use netcdf, only : nf90_get_var
29use netcdf, only : NF90_NOWRITE
30use netcdf, only : NF90_NOERR
31
32
33implicit none ; private
34
35public :: horiz_interp_type, horizontal_interp_init
36public :: time_interp_extern, init_extern_field, time_interp_extern_init
37public :: get_external_field_info
38public :: run_horiz_interp, build_horiz_interp_weights
39public :: external_field
40
41!< Handle of an external field for interpolation
42type :: external_field
43 private
44 integer :: id
45 !< FMS ID for the interpolated field
46 character(len=:), allocatable :: filename
47 !< Filename containing the field values
48 character(len=:), allocatable :: label
49 !< Field name in the file
50end type external_field
51
52!> Read a field based on model time, and rotate to the model domain.
53interface time_interp_extern
54 module procedure time_interp_extern_0d
55 module procedure time_interp_extern_2d
56 module procedure time_interp_extern_3d
57end interface time_interp_extern
58
59!> perform horizontal interpolation of field
60interface run_horiz_interp
61 module procedure horiz_interp_from_weights_field2d
62 module procedure horiz_interp_from_weights_field3d
63end interface
64
65!> build weights for horizontal interpolation of field
66interface build_horiz_interp_weights
67 module procedure build_horiz_interp_weights_2d_to_2d
68end interface build_horiz_interp_weights
69
70contains
71
72!> Do any initialization for the horizontal interpolation
730subroutine horizontal_interp_init()
740 call horiz_interp_init()
750end subroutine horizontal_interp_init
76
77!> Do any initialization for the time and space interpolation infrastructure
781subroutine time_interp_extern_init()
791 call time_interp_external_init()
801end subroutine time_interp_extern_init
81
82!> perform horizontal interpolation of a 2d field using pre-computed weights
83!! source and destination coordinates are 2d
840subroutine horiz_interp_from_weights_field2d(Interp, data_in, data_out, verbose, mask_in, mask_out, &
85 missing_value, missing_permit, err_msg)
86
87 type(horiz_interp_type), intent(in) :: Interp !< type containing interpolation options and weights
88 real, dimension(:,:), intent(in) :: data_in !< input data
89 real, dimension(:,:), intent(out) :: data_out !< output data
90 integer, optional, intent(in) :: verbose !< verbosity level
91 real, dimension(:,:), optional, intent(in) :: mask_in !< mask for input data
92 real, dimension(:,:), optional, intent(out) :: mask_out !< mask for output data
93 real, optional, intent(in) :: missing_value !< A value indicating missing data
94 integer, optional, intent(in) :: missing_permit !< number of allowed points with
95 !! missing value for interpolation (0-3)
96 character(len=*), optional, intent(out) :: err_msg !< error message
97
98 call horiz_interp(Interp, data_in, data_out, verbose, &
99 mask_in, mask_out, missing_value, missing_permit, &
1000 err_msg, new_missing_handle=.true. )
101
1020end subroutine horiz_interp_from_weights_field2d
103
104!> perform horizontal interpolation of a 3d field using pre-computed weights
105!! source and destination coordinates are 2d
1060subroutine horiz_interp_from_weights_field3d(Interp, data_in, data_out, verbose, mask_in, mask_out, &
107 missing_value, missing_permit, err_msg)
108
109 type(horiz_interp_type), intent(in) :: Interp !< type containing interpolation options and weights
110 real, dimension(:,:,:), intent(in) :: data_in !< input data
111 real, dimension(:,:,:), intent(out) :: data_out !< output data
112 integer, optional, intent(in) :: verbose !< verbosity level
113 real, dimension(:,:,:), optional, intent(in) :: mask_in !< mask for input data
114 real, dimension(:,:,:), optional, intent(out) :: mask_out !< mask for output data
115 real, optional, intent(in) :: missing_value !< A value indicating missing data
116 integer, optional, intent(in) :: missing_permit !< number of allowed points with
117 !! missing value for interpolation (0-3)
118 character(len=*), optional, intent(out) :: err_msg !< error message
119
120 call horiz_interp(Interp, data_in, data_out, verbose, mask_in, mask_out, &
1210 missing_value, missing_permit, err_msg)
122
1230end subroutine horiz_interp_from_weights_field3d
124
125
126!> build horizontal interpolation weights from source grid defined by 2d lon/lat to destination grid
127!! defined by 2d lon/lat
1280subroutine build_horiz_interp_weights_2d_to_2d(Interp, lon_in, lat_in, lon_out, lat_out, &
129 verbose, interp_method, num_nbrs, max_dist, &
1300 src_modulo, mask_in, mask_out, &
131 is_latlon_in, is_latlon_out)
132
133 type(horiz_interp_type), intent(inout) :: Interp !< type containing interpolation options and weights
134 real, dimension(:,:), intent(in) :: lon_in !< input longitude 2d
135 real, dimension(:,:), intent(in) :: lat_in !< input latitude 2d
136 real, dimension(:,:), intent(in) :: lon_out !< output longitude 2d
137 real, dimension(:,:), intent(in) :: lat_out !< output latitude 2d
138 integer, optional, intent(in) :: verbose !< verbosity level
139 character(len=*), optional, intent(in) :: interp_method !< interpolation method
140 integer, optional, intent(in) :: num_nbrs !< number of nearest neighbors
141 real, optional, intent(in) :: max_dist !< maximum region of influence
142 logical, optional, intent(in) :: src_modulo !< periodicity of E-W boundary
143 real, dimension(:,:), optional, intent(in) :: mask_in !< mask for input data
144 real, dimension(:,:), optional, intent(inout) :: mask_out !< mask for output data
145 logical, optional, intent(in) :: is_latlon_in !< input grid is regular lat/lon grid
146 logical, optional, intent(in) :: is_latlon_out !< output grid is regular lat/lon grid
147
148 call horiz_interp_new(Interp, lon_in, lat_in, lon_out, lat_out, &
149 verbose, interp_method, num_nbrs, max_dist, &
150 src_modulo, mask_in, mask_out, &
1510 is_latlon_in, is_latlon_out)
152
1530end subroutine build_horiz_interp_weights_2d_to_2d
154
155
156!> get size of an external field from field index
1570function get_extern_field_size(index)
158
159 integer, intent(in) :: index !< field index
160 integer :: get_extern_field_size(4) !< field size
161
1620 get_extern_field_size = get_external_field_size(index)
163
164end function get_extern_field_size
165
166
167!> get axes of an external field from field index
1680function get_extern_field_axes(field) result(axes)
169 type(external_field), intent(in) :: field
170 !< Field handle
171 type(axistype), dimension(4) :: axes
172 !< Field axes
173
174 integer :: ndims
175 ! Number of variable dimensions
1760 integer, allocatable :: dims(:)
177 ! netCDF dimension IDs of variable
178 character(len=256) :: dim_name
179 ! Dimension name
180 integer :: dim_len
181 ! Dimension length
182 integer :: var_dim
183 ! netCDF ID of the variable associated with dimension of the same name
1840 real, allocatable :: axis_points(:)
185 ! Axis values
186
187 integer :: ncid
188 ! netCDF file ID
189 integer :: varid
190 ! netCDF variable ID
191 integer :: rc
192 ! netCDF return code
193
194 ! netCDF requires the following to be length-1 arrays
195 integer :: nc_start(1)
196 ! netCDF start index
197 integer :: nc_count(1)
198 ! netCDF index count
199
200 integer :: d
201 ! Dimension index
202 character(len=2) :: d_str
203 ! Display string of d
204
205 ! This is a reimplementation of get_var_axes_info(), maybe it can be used
206 ! by the existing get_var_axes_info() ?
207
208 ! Open field%filename
2090 rc = nf90_open(trim(field%filename), NF90_NOWRITE, ncid)
2100 if (rc /= NF90_NOERR) &
2110 call MOM_error(FATAL, "Error opening file " // trim(field%filename) // ".")
212
213 ! Use field%label to get the netCDF varid
2140 rc = nf90_inq_varid(ncid, trim(field%label), varid)
2150 if (rc /= NF90_NOERR) &
216 call MOM_error(FATAL, "Error finding variable " // trim(field%label) &
2170 // " in " // trim(field%filename) // ".")
218
219 ! Use the varid to get the number of dims (ndims) and their IDs (dims(:))
220 ! Verify that ndims >= 3
2210 rc = nf90_inquire_variable(ncid, varid, ndims=ndims)
2220 if (rc /= NF90_NOERR) &
223 call MOM_error(FATAL, "Error querying variable " // trim(field%label) &
2240 // " in " // trim(field%filename) // ".")
225
2260 if (ndims < 3) &
227 call MOM_error(FATAL, trim(field%label) // " in " // trim(field%filename) &
2280 // " has too few dimensions to be read as a 3D array.")
229
2300 allocate(dims(ndims))
231
2320 rc = nf90_inquire_variable(ncid, varid, dimids=dims)
2330 if (rc /= NF90_NOERR) &
234 call MOM_error(FATAL, "Error querying variable " // trim(field%label) &
2350 // " in " // trim(field%filename) // ".")
236
2370 do d=1,ndims
238 ! Determine the name of each dimension
2390 rc = nf90_inquire_dimension(ncid, dims(d), dim_name, len=dim_len)
2400 if (rc /= NF90_NOERR) then
2410 write(d_str, '(i0)') d
242 call MOM_error(FATAL, "Error querying dimension " // trim(d_str) &
243 // " of " // trim(field%label) // " in " // trim(field%filename) &
2440 // ".")
245 endif
246
247 ! Now locate a variable with the same name as the dimension (e.g. "x")
2480 rc = nf90_inq_varid(ncid, dim_name, var_dim)
2490 if (rc /= NF90_NOERR) &
250 call MOM_error(FATAL, "Error finding dimension variable " &
251 // trim(dim_name) // " of " // trim(field%label) // " in " &
2520 // trim(field%filename))
253
2540 allocate(axis_points(dim_len))
255
256 ! Get the dimensional axis values
2570 nc_start(1) = 1
2580 nc_count(1) = dim_len
2590 rc = nf90_get_var(ncid, var_dim, axis_points, nc_start, nc_count)
2600 if (rc /= NF90_NOERR) &
261 call MOM_error(FATAL, "Error reading dimension " // trim(dim_name) &
262 // " axis data of " // trim(field%label) // " in " &
2630 // trim(field%filename))
264
265 ! write via set_axis_info() equivalent for axistype
2660 call set_axis_data(axes(d), dim_name, axis_points)
267
2680 deallocate(axis_points)
269 enddo
270
2710 deallocate(dims)
272
273 ! Close external file
2740 rc = nf90_close(ncid)
2750 if (rc /= NF90_NOERR) &
2760 call MOM_error(FATAL, "Error closing file "//trim(field%filename)//".")
277
2780end function get_extern_field_axes
279
280
281!> get missing value of an external field from field index
2820function get_extern_field_missing(index)
283
284 integer, intent(in) :: index !< field index
285 real :: get_extern_field_missing !< field missing value
286
2870 get_extern_field_missing = get_external_field_missing(index)
288
2890end function get_extern_field_missing
290
291
292!> Get information about the external fields.
2930subroutine get_external_field_info(field, size, axes, missing)
294 type(external_field), intent(in) :: field
295 !< handle for time interpolated external field returned from a previous
296 !! call to init_external_field()
297 integer, optional, intent(inout) :: size(4)
298 !< Dimension sizes for the input data
299 type(axistype), optional, intent(inout) :: axes(4)
300 !< Axis types for the input data
301 real, optional, intent(inout) :: missing
302 !< Missing value for the input data
303
3040 if (present(size)) then
3050 size(:) = get_extern_field_size(field%id)
306 endif
307
3080 if (present(axes)) then
3090 axes(:) = get_extern_field_axes(field)
310 endif
311
3120 if (present(missing)) then
3130 missing = get_extern_field_missing(field%id)
314 endif
3150end subroutine get_external_field_info
316
317
318!> Read a scalar field based on model time.
3190subroutine time_interp_extern_0d(field, time, data_in, verbose)
320 type(external_field), intent(in) :: field !< Handle for time interpolated field
321 type(time_type), intent(in) :: time !< The target time for the data
322 real, intent(inout) :: data_in !< The interpolated value
323 logical, optional, intent(in) :: verbose !< If true, write verbose output for debugging
324
3250 call time_interp_external(field%id, time, data_in, verbose=verbose)
3260end subroutine time_interp_extern_0d
327
328
329!> Read a 2d field from an external based on model time, potentially including horizontal
330!! interpolation and rotation of the data
3310subroutine time_interp_extern_2d(field, time, data_in, interp, verbose, horz_interp, mask_out)
332 type(external_field), intent(in) :: field !< Handle for time interpolated field
333 type(time_type), intent(in) :: time !< The target time for the data
334 real, dimension(:,:), intent(inout) :: data_in !< The array in which to store the interpolated values
335 integer, optional, intent(in) :: interp !< A flag indicating the temporal interpolation method
336 logical, optional, intent(in) :: verbose !< If true, write verbose output for debugging
337 type(horiz_interp_type), &
338 optional, intent(in) :: horz_interp !< A structure to control horizontal interpolation
339 logical, dimension(:,:), &
340 optional, intent(out) :: mask_out !< An array that is true where there is valid data
341
342 call time_interp_external(field%id, time, data_in, interp=interp, verbose=verbose, &
3430 horz_interp=horz_interp, mask_out=mask_out)
3440end subroutine time_interp_extern_2d
345
346
347!> Read a 3d field based on model time, and rotate to the model grid
3480subroutine time_interp_extern_3d(field, time, data_in, interp, verbose, horz_interp, mask_out)
349 type(external_field), intent(in) :: field !< Handle for time interpolated field
350 type(time_type), intent(in) :: time !< The target time for the data
351 real, dimension(:,:,:), intent(inout) :: data_in !< The array in which to store the interpolated values
352 integer, optional, intent(in) :: interp !< A flag indicating the temporal interpolation method
353 logical, optional, intent(in) :: verbose !< If true, write verbose output for debugging
354 type(horiz_interp_type), &
355 optional, intent(in) :: horz_interp !< A structure to control horizontal interpolation
356 logical, dimension(:,:,:), &
357 optional, intent(out) :: mask_out !< An array that is true where there is valid data
358
359 call time_interp_external(field%id, time, data_in, interp=interp, verbose=verbose, &
3600 horz_interp=horz_interp, mask_out=mask_out)
3610end subroutine time_interp_extern_3d
362
363
364!> initialize an external field
3650function init_extern_field(file, fieldname, MOM_domain, domain, verbose, &
366 threading, ierr, ignore_axis_atts, correct_leap_year_inconsistency) &
367 result(field)
368
369 character(len=*), intent(in) :: file !< The name of the file to read
370 character(len=*), intent(in) :: fieldname !< The name of the field in the file
371 integer, optional, intent(in) :: threading !< A flag specifying whether the root PE reads
372 !! the data and broadcasts it (SINGLE_FILE) or all
373 !! processors read (MULTIPLE, the default).
374 logical, optional, intent(in) :: verbose !< If true, write verbose output for debugging
375 type(domain2d), optional, intent(in) :: domain !< A domain2d type that describes the decomposition
376 type(MOM_domain_type), &
377 optional, intent(in) :: MOM_Domain !< A MOM_Domain that describes the decomposition
378 integer, optional, intent(out) :: ierr !< Returns a non-zero error code in case of failure
379 logical, optional, intent(in) :: ignore_axis_atts !< If present and true, do not issue a
380 !! fatal error if the axis Cartesian attribute is
381 !! not set to a recognized value.
382 logical, optional, intent(in) :: correct_leap_year_inconsistency !< If present and true,
383 !! then if, (1) a calendar containing leap years
384 !! is in use, and (2) the modulo time period of the
385 !! data is an integer number of years, then map
386 !! a model date of Feb 29. onto a common year on Feb. 28.
387 type(external_field) :: field !< Handle to external field
388
3890 type(FmsNetcdfFile_t) :: extern_file
390 ! Local instance of netCDF file used to locate case-insensitive field name
391 integer :: num_fields
392 ! Number of fields in external file
3930 character(len=256), allocatable :: extern_fieldnames(:)
394 ! List of field names in file
395 ! NOTE: length should NF90_MAX_NAME, but I don't know how to read it
396 character(len=:), allocatable :: label
397 ! Case-insensitive match to fieldname in file
398 logical :: rc
399 ! Return status
400 integer :: i
401 ! Loop index
402
4030 field%filename = file
404
405 ! FMS2's init_external_field is case sensitive, so we must replicate the
406 ! case-insensitivity of FMS1. This requires opening the file twice.
407
4080 rc = netcdf_file_open(extern_file, file, 'read')
4090 if (.not. rc) then
410 call MOM_error(FATAL, 'init_extern_file: file ' // trim(file) &
4110 // ' could not be opened.')
412 endif
413
414 ! TODO: broadcast = .false.?
4150 num_fields = get_num_variables(extern_file)
416
4170 allocate(extern_fieldnames(num_fields))
4180 call get_variable_names(extern_file, extern_fieldnames)
419
4200 do i = 1, num_fields
4210 if (lowercase(extern_fieldnames(i)) == lowercase(fieldname)) then
4220 field%label = extern_fieldnames(i)
4230 exit
424 endif
425 enddo
426
4270 call netcdf_file_close(extern_file)
428
4290 if (.not. allocated(field%label)) then
430 call MOM_error(FATAL, 'init_extern_field: field ' // trim(fieldname) &
4310 // ' not found in ' // trim(file) // '.')
432 endif
433
434 ! Pass to FMS2 implementation of init_external_field
435
436 ! NOTE: external fields are currently assumed to be on-grid, which holds
437 ! across the current codebase. In the future, we may need to either enforce
438 ! this or somehow relax this requirement.
439
4400 if (present(MOM_Domain)) then
441 field%id = init_external_field(file, field%label, domain=MOM_domain%mpp_domain, &
442 verbose=verbose, ierr=ierr, ignore_axis_atts=ignore_axis_atts, &
443 correct_leap_year_inconsistency=correct_leap_year_inconsistency, &
4440 ongrid=.true.)
445 else
446 field%id = init_external_field(file, field%label, domain=domain, &
447 verbose=verbose, ierr=ierr, ignore_axis_atts=ignore_axis_atts, &
448 correct_leap_year_inconsistency=correct_leap_year_inconsistency, &
4490 ongrid=.true.)
450 endif
4510end function init_extern_field
452
4530end module MOM_interp_infra