FV3 Bundle
ioda_obsdb_mod.F90
Go to the documentation of this file.
1 !
2 ! (C) Copyright 2017 UCAR
3 !
4 ! This software is licensed under the terms of the Apache Licence Version 2.0
5 ! which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
6 
7 !> Fortran module handling radiosonde observation space
8 
10 
11 use iso_c_binding
12 use kinds
14 use fckit_log_module, only : fckit_log
15 #ifdef HAVE_ODB_API
16 use odb_helper_mod, only: &
18 #endif
19 
20 implicit none
21 private
22 integer, parameter :: max_string=800
23 
24 public ioda_obsdb
25 public ioda_obsdb_setup
26 public ioda_obsdb_delete
28 public ioda_obsdb_getlocs
31 public ioda_obsdb_get_vec
32 public ioda_obsdb_put_vec
33 
34 ! ------------------------------------------------------------------------------
35 
36 !> Fortran derived type to hold a set of observation variables
37 type :: ioda_obsdb
38  integer :: fvlen !< length of vectors in the input file
39  integer :: nobs !< number of observations for this process
40  integer :: nlocs !< number of locations for this process
41  integer :: nvars !< number of variables
42  integer, allocatable :: dist_indx(:) !< indices to select elements from input file vectors
43 
44  character(len=max_string) :: obstype
45 
46  character(len=max_string) :: filename
47 
48  character(len=max_string) :: fileout
49 
50  type(ioda_obs_variables) :: obsvars !< observation variables
51 #ifdef HAVE_ODB
52  real(kind=c_double), allocatable :: odb_data(:,:)
53 #endif
54 end type ioda_obsdb
55 
56 contains
57 
58 ! ------------------------------------------------------------------------------
59 
60 subroutine ioda_obsdb_setup(self, fvlen, nobs, dist_indx, nlocs, nvars, filename, fileout, obstype)
61 
66 
67 implicit none
68 
69 type(ioda_obsdb), intent(inout) :: self
70 integer, intent(in) :: fvlen
71 integer, intent(in) :: nobs
72 integer, intent(in) :: dist_indx(:)
73 integer, intent(in) :: nlocs
74 integer, intent(in) :: nvars
75 character(len=*), intent(in) :: filename
76 character(len=*), intent(in) :: fileout
77 character(len=*), intent(in) :: obstype
78 
79 integer :: input_file_type
80 integer :: iunit
81 character(len=:), allocatable :: var_list(:)
82 logical, allocatable :: var_select(:)
83 integer :: file_nvars
84 integer :: var_list_item_len
85 integer :: i
86 integer(selected_int_kind(8)), allocatable :: dim_sizes(:)
87 type(ioda_obs_var), pointer :: vptr
88 
89 self%fvlen = fvlen
90 self%nobs = nobs
91 allocate(self%dist_indx(nobs))
92 self%dist_indx = dist_indx
93 self%nlocs = nlocs
94 self%nvars = nvars
95 self%filename = filename
96 self%fileout = fileout
97 self%obstype = obstype
98 call self%obsvars%setup()
99 
100 input_file_type = ioda_obsdb_get_ftype(self%filename)
101 select case (input_file_type)
102  case (0)
103  ! We want the new C++ ObsSpace to read in the contents of the input file
104  ! during the constructor. For testing purposes, do that now using the
105  ! ioda_obsdb_getvar interface. The ioda_obsdb_getvar interface opens the file,
106  ! so at this point construct a list of variable names to read in, then close
107  ! the file, and then read in the variables using ioda_obsdb_getvar.
108  call nc_diag_read_init(self%filename, iunit)
109 
110  call nc_diag_read_noid_get_var_names(num_vars=file_nvars, var_name_mlen=var_list_item_len)
111  allocate(character(var_list_item_len)::var_list(file_nvars))
112  call nc_diag_read_noid_get_var_names(var_names=var_list)
113 
114  allocate(var_select(file_nvars))
115  do i = 1, file_nvars
116  ! Only read in the 1D (vector) arrays that match the fvlen size
117  dim_sizes = nc_diag_read_ret_var_dims(trim(var_list(i)))
118  var_select(i) = (size(dim_sizes) .eq. 1) .and. (dim_sizes(1) .eq. fvlen)
119  deallocate(dim_sizes)
120  enddo
121 
122  call nc_diag_read_close(self%filename)
123 
124  case (1)
125 
126  file_nvars = 3
127  var_list_item_len = 80
128  allocate(character(var_list_item_len)::var_list(file_nvars))
129  allocate(var_select(file_nvars))
130 
131  var_list(1) = "latitude"
132  var_list(2) = "longitude"
133  var_list(3) = "time"
134 
135  var_select(1) = .true.
136  var_select(2) = .true.
137  var_select(2) = .true.
138 
139  case (2)
140 
141 endselect
142 
143 ! Read in the selected file variables
144 do i = 1, file_nvars
145  if (var_select(i)) then
146  call ioda_obsdb_getvar(self, trim(var_list(i)), vptr)
147  endif
148 enddo
149 
150 deallocate(var_list)
151 deallocate(var_select)
152 
153 end subroutine ioda_obsdb_setup
154 
155 ! ------------------------------------------------------------------------------
156 
157 subroutine ioda_obsdb_delete(self)
158 implicit none
159 type(ioda_obsdb), intent(inout) :: self
160 
161 call ioda_obsdb_write(self)
162 
163 self%fvlen = 0
164 self%nobs = 0
165 if (allocated(self%dist_indx)) deallocate(self%dist_indx)
166 self%nlocs = 0
167 self%nvars = 0
168 self%filename = ""
169 self%obstype = ""
170 
171 call self%obsvars%delete()
172 
173 end subroutine ioda_obsdb_delete
174 
175 ! ------------------------------------------------------------------------------
176 
177 subroutine ioda_obsdb_getlocs(self, locs, t1, t2)
179 use datetime_mod
180 use duration_mod
181 implicit none
182 type(ioda_obsdb), intent(in) :: self
183 type(ioda_locs), intent(inout) :: locs
184 type(datetime), intent(in) :: t1, t2
185 
186 character(len=*),parameter:: myname = "ioda_obsdb_getlocs"
187 character(len=255) :: record
188 integer :: failed
189 type(ioda_obs_var), pointer :: vptr
190 integer :: istep
191 integer :: ilocs, i
192 integer, dimension(:), allocatable :: indx
193 real(kind_real), dimension(:), allocatable :: time, lon, lat
194 type(duration), dimension(:), allocatable :: dt
195 type(datetime), dimension(:), allocatable :: t
196 type(datetime) :: reftime
197 
198 character(21) :: tstr, tstr2
199 
200 ! Assume that obs are organized with the first location going with the
201 ! first nobs/nlocs obs, the second location going with the next nobs/nlocs
202 ! obs, etc.
203 istep = self%nobs / self%nlocs
204 
205 ! Local copies pre binning
206 allocate(time(self%nlocs), lon(self%nlocs), lat(self%nlocs))
207 allocate(indx(self%nlocs))
208 
209 call ioda_obsdb_getvar(self, "longitude", vptr)
210 lon = vptr%vals
211 
212 call ioda_obsdb_getvar(self, "latitude", vptr)
213 lat = vptr%vals
214 
215 call ioda_obsdb_getvar(self, "time", vptr)
216 time = vptr%vals
217 
218 
219 allocate(dt(self%nlocs), t(self%nlocs))
220 
221 !Time coming from file is integer representing distance to time in file name
222 !Use hardcoded date for now but needs to come from ObsSpace somehow.
223 !AS: not going to fix it for now, will wait for either suggestions or new IODA.
224 call datetime_create("2018-04-15T00:00:00Z", reftime)
225 
226 call datetime_to_string(reftime, tstr)
227 
228 do i = 1, self%nlocs
229  dt(i) = int(3600*time(i))
230  t(i) = reftime
231  call datetime_update(t(i), dt(i))
232 enddo
233 
234 call datetime_to_string(t1,tstr)
235 call datetime_to_string(t2,tstr2)
236 
237 ! Find number of locations in this timeframe
238 ilocs = 0
239 do i = 1, self%nlocs
240  if (t(i) > t1 .and. t(i) <= t2) then
241  ilocs = ilocs + 1
242  indx(ilocs) = i
243  call datetime_to_string(t(i),tstr)
244  endif
245 enddo
246 
247 deallocate(dt, t)
248 
249 !Setup ioda locations
250 call ioda_locs_setup(locs, ilocs)
251 do i = 1, ilocs
252  locs%lon(i) = lon(indx(i))
253  locs%lat(i) = lat(indx(i))
254  locs%time(i) = time(indx(i))
255 enddo
256 locs%indx = indx(1:ilocs)
257 
258 deallocate(time, lon, lat)
259 deallocate(indx)
260 
261 write(record,*) myname,': allocated/assinged obs-data'
262 call fckit_log%info(record)
263 
264 end subroutine ioda_obsdb_getlocs
265 
266 ! ------------------------------------------------------------------------------
267 
268 subroutine ioda_obsdb_getvar(self, vname, vptr)
270 #ifdef HAVE_ODB_API
271 
272 use odb_helper_mod, only: get_vars
273 use mpi, only: mpi_comm_rank, mpi_comm_size, mpi_comm_world, mpi_init, mpi_finalize
274 
275 #endif
276 
277 use netcdf, only: nf90_float, nf90_double, nf90_int
279 use nc_diag_read_mod, only: nc_diag_read_get_dim
280 use nc_diag_read_mod, only: nc_diag_read_check_var
281 use nc_diag_read_mod, only: nc_diag_read_get_var_dims
282 use nc_diag_read_mod, only: nc_diag_read_get_var_type
283 use nc_diag_read_mod, only: nc_diag_read_get_var
285 
286 implicit none
287 
288 type(ioda_obsdb), intent(in) :: self
289 character(len=*), intent(in) :: vname
290 type(ioda_obs_var), pointer :: vptr
291 
292 integer :: iunit
293 character(len=max_string) :: err_msg
294 integer :: i
295 integer :: j
296 integer :: iflat
297 integer :: ndims
298 integer, allocatable :: dimsizes(:)
299 real, allocatable :: field1d_sngl(:)
300 real, allocatable :: field2d_sngl(:,:)
301 integer, allocatable :: field1d_int(:)
302 integer, allocatable :: field2d_int(:,:)
303 real(kind_real), allocatable :: field1d_dbl(:)
304 real(kind_real), allocatable :: field2d_dbl(:,:)
305 integer :: input_file_type
306 integer :: vartype
307 
308 ! Look for the variable. If it is already present, vptr will be
309 ! pointing to it. If not, vptr will not be associated, and we
310 ! need to create the new variable and read in the data from the
311 ! netcdf file.
312 call self%obsvars%get_node(vname, vptr)
313 if (.not.associated(vptr)) then
314  call self%obsvars%add_node(vname, vptr)
315 
316  input_file_type = ioda_obsdb_get_ftype(self%filename)
317  select case (input_file_type)
318  case (0)
319 
320  ! Open the file, and do some checks
321  call nc_diag_read_init(self%filename, iunit)
322 
323  ! Does the variable exist?
324  if (.not.nc_diag_read_check_var(iunit, vname)) then
325  write(err_msg,*) 'ioda_obsdb_getvar: var ', trim(vname), ' does not exist'
326  call abor1_ftn(trim(err_msg))
327  endif
328 
329  ! Get the dimension sizes of the variable and use these to allocate
330  ! the storage for the variable.
331  call nc_diag_read_get_var_dims(iunit, vname, ndims, dimsizes)
332 
333  ! Determine the data type (single vs double precision)
334  vartype = nc_diag_read_get_var_type(iunit, vname)
335 
336  if (ndims .gt. 1) then
337  write(err_msg,*) 'ioda_obsdb_getvar: var ', trim(vname), ' must have rank = 1'
338  call abor1_ftn(trim(err_msg))
339  endif
340 
341  if (dimsizes(1) .ne. self%fvlen) then
342  write(err_msg,*) 'ioda_obsdb_getvar: var ', trim(vname), ' size (', dimsizes(1), ') must equal fvlen (', self%fvlen, ')'
343  call abor1_ftn(trim(err_msg))
344  endif
345 
346  ! The dimensionality of the variables in the netcdf file match what we want in the obsdb.
347  vptr%nobs = self%nobs
348  allocate(vptr%vals(vptr%nobs))
349 
350  if (vartype == nf90_double) then
351  allocate(field1d_dbl(dimsizes(1)))
352  call nc_diag_read_get_var(iunit, vname, field1d_dbl)
353  vptr%vals = field1d_dbl(self%dist_indx)
354  deallocate(field1d_dbl)
355  elseif (vartype == nf90_float) then
356  allocate(field1d_sngl(dimsizes(1)))
357  call nc_diag_read_get_var(iunit, vname, field1d_sngl)
358  vptr%vals = field1d_sngl(self%dist_indx)
359  deallocate(field1d_sngl)
360  elseif (vartype == nf90_int) then
361  allocate(field1d_int(dimsizes(1)))
362  call nc_diag_read_get_var(iunit, vname, field1d_int)
363  vptr%vals = field1d_int(self%dist_indx)
364  deallocate(field1d_int)
365  endif
366 
367  deallocate(dimsizes)
368 
369  call nc_diag_read_close(self%filename)
370 
371  case (1)
372 
373 #ifdef HAVE_ODB_API
374  select case (vname)
375  case ("latitude")
376  call get_vars (self % filename, ["lat"], "entryno = 1", field2d_dbl)
377  case ("longitude")
378  call get_vars (self % filename, ["lon"], "entryno = 1", field2d_dbl)
379  case ("time")
380  call get_vars (self % filename, ["time"], "entryno = 1", field2d_dbl)
381  end select
382  vptr%nobs = size(field2d_dbl,dim=2)
383  allocate(vptr%vals(vptr%nobs))
384  vptr % vals(:) = field2d_dbl(1,:)
385 #endif
386 
387  case (2)
388 
389 #ifdef HAVE_ODB
390 #endif
391 
392  end select
393 
394 endif
395 
396 end subroutine ioda_obsdb_getvar
397 
398 ! ------------------------------------------------------------------------------
399 
400 subroutine ioda_obsdb_get_vec(self, vname, vdata)
402 implicit none
403 type(ioda_obsdb), intent(in) :: self
404 character(len=*), intent(in) :: vname
405 real(kind_real), intent(out) :: vdata(:)
406 
407 character(len=*),parameter:: myname = "ioda_obsdb_get_vec"
408 character(len=255) :: record
409 type(ioda_obs_var), pointer :: vptr
410 
411 call ioda_obsdb_getvar(self, vname, vptr)
412 
413 vdata = vptr%vals
414 
415 end subroutine ioda_obsdb_get_vec
416 
417 ! ------------------------------------------------------------------------------
418 
419 subroutine ioda_obsdb_put_vec(self, vname, vdata)
421 implicit none
422 
423 type(ioda_obsdb), intent(in) :: self
424 character(len=*), intent(in) :: vname
425 real(kind_real), intent(in) :: vdata(:)
426 
427 type(ioda_obs_var), pointer :: vptr
428 character(len=*),parameter :: myname = "ioda_obsdb_put_vec"
429 character(len=255) :: record
430 
431 call self%obsvars%get_node(vname, vptr)
432 if (.not.associated(vptr)) then
433  call self%obsvars%add_node(vname, vptr)
434  vptr%nobs = self%nobs
435  allocate(vptr%vals(vptr%nobs))
436  vptr%vals = vdata
437 else
438  write(record,*) myname,' var= ', trim(vname), ':this column already exists'
439  call abor1_ftn(record)
440 endif
441 
442 end subroutine ioda_obsdb_put_vec
443 
444 ! ------------------------------------------------------------------------------
445 
446 subroutine ioda_obsdb_generate(self, fvlen, nobs, dist_indx, nlocs, nvars, obstype, lat, lon1, lon2)
447 implicit none
448 type(ioda_obsdb), intent(inout) :: self
449 integer, intent(in) :: fvlen
450 integer, intent(in) :: nobs
451 integer, intent(in) :: dist_indx(:)
452 integer, intent(in) :: nlocs
453 integer, intent(in) :: nvars
454 character(len=*) :: obstype
455 real, intent(in) :: lat, lon1, lon2
456 
457 character(len=*),parameter :: myname = "ioda_obsdb_generate"
458 integer :: i
459 type(ioda_obs_var), pointer :: vptr
460 character(len=max_string) :: vname
461 
462 ! 4th argument is the filename containing obs values, which is not used for this method.
463 call ioda_obsdb_setup(self, fvlen, nobs, dist_indx, nlocs, nvars, "", "", obstype)
464 
465 ! Create variables and generate the values specified by the arguments.
466 vname = "latitude"
467 call self%obsvars%get_node(vname, vptr)
468 if (.not.associated(vptr)) then
469  call self%obsvars%add_node(vname, vptr)
470 endif
471 vptr%nobs = self%nobs
472 vptr%vals = lat
473 
474 vname = "longitude"
475 call self%obsvars%get_node(vname, vptr)
476 if (.not.associated(vptr)) then
477  call self%obsvars%add_node(vname, vptr)
478 endif
479 vptr%nobs = self%nobs
480 do i = 1, nobs
481  vptr%vals(i) = lon1 + (i-1)*(lon2-lon1)/(nobs-1)
482 enddo
483 
484 end subroutine ioda_obsdb_generate
485 
486 ! ------------------------------------------------------------------------------
487 
488 subroutine ioda_obsdb_var_to_ovec(self, ovec, vname)
490 implicit none
491 type(ioda_obsdb), intent(in) :: self
492 real(kind=kind_real), intent(inout) :: ovec(:)
493 character(len=*), intent(in) :: vname
494 
495 character(len=*),parameter:: myname = "ioda_obsdb_var_to_ovec"
496 character(len=255) :: record
497 type(ioda_obs_var), pointer :: vptr
498 
499 call ioda_obsdb_getvar(self, vname, vptr)
500 
501 ovec(:) = vptr%vals(:)
502 
503 end subroutine ioda_obsdb_var_to_ovec
504 
505 ! ------------------------------------------------------------------------------
506 
507 subroutine ioda_obsdb_write(self)
508 use netcdf
509 implicit none
510 
511 type(ioda_obsdb), intent(in) :: self
512 
513 type(ioda_obs_var), pointer :: vptr
514 character(len=*),parameter :: myname = "ioda_obsdb_write"
515 character(len=255) :: record
516 integer :: i,ncid,dimid_1d(1),dimid_nobs
517 integer, allocatable :: ncid_var(:)
518 
519 
520 
521 if (self%fileout(len_trim(self%fileout)-3:len_trim(self%fileout)) == ".nc4" .or. &
522  self%fileout(len_trim(self%fileout)-3:len_trim(self%fileout)) == ".nc") then
523 
524  write(record,*) myname, ':write diag in netcdf, filename=', trim(self%fileout)
525  call fckit_log%info(record)
526 
527  call check('nf90_create', nf90_create(trim(self%fileout),nf90_hdf5,ncid))
528  call check('nf90_def_dim', nf90_def_dim(ncid,trim(self%obstype)//'_nobs',self%nobs, dimid_nobs))
529  dimid_1d = (/ dimid_nobs /)
530 
531  i = 0
532  allocate(ncid_var(self%obsvars%n_nodes))
533  vptr => self%obsvars%head
534  do while (associated(vptr))
535  i = i + 1
536  call check('nf90_def_var', nf90_def_var(ncid,trim(vptr%vname)//'_'//trim(self%obstype),nf90_double,dimid_1d,ncid_var(i)))
537  vptr => vptr%next
538  enddo
539 
540  call check('nf90_enddef', nf90_enddef(ncid))
541 
542  i = 0
543  vptr => self%obsvars%head
544  do while (associated(vptr))
545  i = i + 1
546  call check('nf90_put_var', nf90_put_var(ncid,ncid_var(i),vptr%vals))
547  vptr => vptr%next
548  enddo
549 
550  call check('nf90_close', nf90_close(ncid))
551  deallocate(ncid_var)
552 
553 else if (self%fileout(len_trim(self%fileout)-3:len_trim(self%fileout)) == ".odb") then
554 
555  write(record,*) myname, ':write diag in odb2, filename=', trim(self%fileout)
556  call fckit_log%info(record)
557 
558 else
559 
560  write(record,*) myname, ':no output'
561  call fckit_log%info(record)
562 
563 endif
564 
565 end subroutine ioda_obsdb_write
566 
567 ! ------------------------------------------------------------------------------
568 
569 subroutine ioda_obsdb_dump(self)
570  implicit none
571 
572  type(ioda_obsdb), intent(in) :: self
573 
574  type(ioda_obs_var), pointer :: vptr
575 
576  print*, "DEBUG: ioda_obsdb_dump: fvlen: ", self%fvlen
577  print*, "DEBUG: ioda_obsdb_dump: nobs: ", self%nobs
578  print*, "DEBUG: ioda_obsdb_dump: nlocs: ", self%nlocs
579  print*, "DEBUG: ioda_obsdb_dump: nvars: ", self%nvars
580  print*, "DEBUG: ioda_obsdb_dump: obstype: ", trim(self%obstype)
581  print*, "DEBUG: ioda_obsdb_dump: filename: ", trim(self%filename)
582 
583  print*, "DEBUG: ioda_obsdb_dump: count: ", self%obsvars%n_nodes
584 
585  vptr => self%obsvars%head
586  do while (associated(vptr))
587  print*, "DEBUG: ioda_obsdb_dump: vname: ", trim(vptr%vname)
588  print*, "DEBUG: ioda_obsdb_dump: shape, size: ", shape(vptr%vals), size(vptr%vals)
589  print*, "DEBUG: ioda_obsdb_dump: vals (first 5): ", vptr%vals(1:5)
590  print*, "DEBUG: ioda_obsdb_dump: vals (last 5): ", vptr%vals(vptr%nobs-4:vptr%nobs)
591 
592  vptr => vptr%next
593  enddo
594 
595  print*, "DEBUG: ioda_obsdb_dump:"
596 
597 end subroutine ioda_obsdb_dump
598 
599 ! ------------------------------------------------------------------------------
600 subroutine check(action, status)
602 use netcdf, only: nf90_noerr, nf90_strerror
603 
604 implicit none
605 
606 integer, intent (in) :: status
607 character (len=*), intent (in) :: action
608 
609 if(status /= nf90_noerr) then
610  print *, "During action: ", trim(action), ", received error: ", trim(nf90_strerror(status))
611  stop 2
612 end if
613 
614 end subroutine check
615 
616 ! ------------------------------------------------------------------------------
617 function ioda_obsdb_get_ftype(fname) result(ftype)
618  implicit none
619 
620  character(len=*), intent(in) :: fname
621  integer :: ftype
622 
623  ! result goes into ftype
624  ! 0 - netcdf
625  ! 1 - ODB API
626  ! 2 - ODB
627  if (fname(len_trim(fname)-3:len_trim(fname)) == ".nc4" .or. &
628  fname(len_trim(fname)-2:len_trim(fname)) == ".nc") then
629  ftype = 0
630  elseif (fname(len_trim(fname)-3:len_trim(fname)) == ".odb") then
631  ftype = 1
632  else
633  ftype = 2
634  endif
635 end function ioda_obsdb_get_ftype
636 
637 end module ioda_obsdb_mod
subroutine count_query_results(filename, query, num_results)
subroutine ioda_obsdb_dump(self)
Fortran derived type to hold a set of observation variables.
subroutine, public ioda_obsdb_setup(self, fvlen, nobs, dist_indx, nlocs, nvars, filename, fileout, obstype)
subroutine ioda_obsdb_getvar(self, vname, vptr)
subroutine check(action, status)
Fortran derived type to hold observation locations.
Fortran module handling radiosonde observation space.
subroutine, public ioda_obsdb_get_vec(self, vname, vdata)
integer function, public ioda_obsdb_get_ftype(fname)
subroutine, public ioda_obsdb_delete(self)
Fortran module with ODB API utility routines.
subroutine get_vars(filename, columns, filter, data)
subroutine, public ioda_obsdb_getlocs(self, locs, t1, t2)
subroutine, public ioda_obsdb_put_vec(self, vname, vdata)
subroutine nc_diag_read_close(filename, file_ncdr_id, from_pop)
subroutine, public ioda_obsdb_var_to_ovec(self, ovec, vname)
Fortran module handling observation locations.
subroutine ioda_obsdb_write(self)
integer, parameter max_string
subroutine, public ioda_obsdb_generate(self, fvlen, nobs, dist_indx, nlocs, nvars, obstype, lat, lon1, lon2)
subroutine, public ioda_locs_setup(self, nlocs)
subroutine nc_diag_read_init(filename, file_ncdr_id, from_push)