UFO
ufo_groundgnss_ropp_utils_mod.F90
Go to the documentation of this file.
1 ! (C) Copyright 2017-2018 UCAR
2 !
3 ! This software is licensed under the terms of the Apache Licence Version 2.0
4 ! which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5 
6 !> Fortran module to handle ground-based gnss observations following
7 !> the ROPP (2018 Aug) implementation
8 
10 
11 !use iso_c_binding
12 use fckit_log_module, only: fckit_log
13 use kinds, only: kind_real
14 
15 ! ROPP data type and library subroutines
16 use typesizes, only: wp => eightbytereal
17 use datetimetypes, only: dp
18 use ropp_fm_types, only: state1dfm, obs1drefrac
19 use geodesy, only: gravity, r_eff, geometric2geopotential, geopotential2geometric
20 use arrays, only: callocate
21 
22 implicit none
23 public :: init_ropp_1d_statevec
25 public :: init_ropp_1d_obvec
27 public :: ropp_tidy_up_1d
28 public :: ropp_tidy_up_tlad_1d
29 public :: calc_model_z
30 public :: calc_station_phi
31 public :: gnss_ztd_integral
32 private
33 
34 contains
35 
36 ! ------------------------------------------------------------------------------------
37 ! ------------------------------------------------------------------------------------
38 
39 subroutine init_ropp_1d_statevec(step_time, rlon, rlat, temp, shum, &
40  pres, phi, lm, z_sfc, x, iflip)
41 ! Description:
42 ! subroutine to fill a ROPP state vector structure with
43 ! model background fields: Temperature, pressure, specific
44 ! humidity at full-levels, and surface geopotential height.
45 !
46 ! Inputs:
47 ! temp background temperature
48 ! shum background specific humidity
49 ! pres background pressure
50 ! phi geopotential height
51 ! z_sfc terrain height of background field
52 ! lm number of vertical levels in the background
53 !
54 ! Outputs:
55 ! x Forward model state vector
56 ! ------------------------------------------------------------------------------------
57  implicit none
58 
59  real(kind=dp), intent(in) :: step_time ! JulianTime of bg
60  real(kind=kind_real), intent(in) :: rlat, rlon ! latitude and longitude
61  real(kind=kind_real), intent(in) :: z_sfc ! Surface geometric height
62  integer, intent(in) :: lm ! number of model level
63  real(kind=kind_real), dimension(lm), intent(in) :: temp, shum, pres, phi ! model state variables
64  integer, optional, intent(in) :: iflip ! order indicator of model heights
65  type(state1dfm), intent(out) :: x ! ROPP 1d state vector data type
66 
67 ! Local variables
68  real(kind=kind_real) :: rlon_local ! ROPP uses -180/180
69  integer :: n, i, j, k
70  integer :: local_flip
71 
72 
73  x%non_ideal = .false. ! Non-ideal gas flag
74  x%direct_ion = .false. ! Calculate L1,L2 bangles
75  x%state_ok = .true. ! State vector ok?
76 
77 ! ROPP Longitude value is -180.0 to 180.0
78  x%lat = real(rlat,kind=wp)
79  rlon_local = rlon
80  if (rlon_local .gt. 180) rlon_local = rlon_local - 360.
81  x%lon = real(rlon_local,kind=wp)
82  x%time = real(step_time, kind=wp)
83 
84 ! Number of levels in background profile. What about (lm+1) field ?
85  x%n_lev = lm
86 
87 !--------------------------------------------------------------
88 ! allocate arrays for temperature, specific humidity, pressure
89 ! and geopotential height data
90 !--------------------------------------------------------------
91  if (associated(x%temp)) deallocate(x%temp)
92  if (associated(x%shum)) deallocate(x%shum)
93  if (associated(x%pres)) deallocate(x%pres)
94  if (associated(x%geop)) deallocate(x%geop)
95 
96  allocate(x%temp(x%n_lev))
97  allocate(x%shum(x%n_lev))
98  allocate(x%pres(x%n_lev))
99  allocate(x%geop(x%n_lev))
100 
101 !----------------------------------------------------
102 ! ROPP FM requires vertical height profile to be of the ascending order.
103 ! (see ropp_io_ascend ( ROdata )). We may need to flip the data.
104 !----------------------------------------------------
105  local_flip = 0
106  if ( present(iflip) ) then
107  local_flip = iflip
108  end if
109  if ( local_flip .eq. 1) then
110  do k = 1, lm
111  x%temp(lm+1-k) = real(temp(k),kind=wp)
112  x%shum(lm+1-k) = real(shum(k),kind=wp)
113  x%pres(lm+1-k) = real(pres(k),kind=wp)
114  x%geop(lm+1-k) = real(phi(k),kind=wp)
115  end do
116  else
117  x%temp(1:lm) = real(temp(1:lm),kind=wp)
118  x%shum(1:lm) = real(shum(1:lm),kind=wp)
119  x%pres(1:lm) = real(pres(1:lm),kind=wp)
120  x%geop(1:lm) = real(phi(1:lm),kind=wp)
121  end if
122 
123 ! surface geopotential height value
124  x%geop_sfc = geometric2geopotential(real(rlat,kind=wp), real(z_sfc,kind=wp))
125 !------------------------------------------------
126 ! covariance matrix, is this used by ROPP FM?
127 !------------------------------------------------
128  x%cov_ok = .true.
129 
130 ! Allocate memory
131 ! For ECMWF example, Covariance matrix for temperature sigma and
132 ! specific humidity sigma, and surface pressure. There the
133 ! size of covariance matrix is 2 * nlevel + 1.
134 
135  n = (2*x%n_lev)+1 ! Number of elements in the state vector
136 
137  if (associated(x%cov%d)) deallocate(x%cov%d)
138  call callocate(x%cov%d, n*(n+1)/2) ! From ROPP utility library
139 
140  do i = 1, x%n_lev
141  x%cov%d(i + i*(i-1)/2) = 1.0_wp
142  end do
143 
144  do i = 1, x%n_lev
145  j = x%n_lev + i
146  x%cov%d(j + j*(j-1)/2) = 1.0_wp
147  enddo
148 
149  x%cov%d(n + n*(n-1)/2) = 1.0_wp
150 
151 !------------------------------
152 ! Rest of the covariance marix
153 !------------------------------
154  if (associated(x%cov%e)) deallocate(x%cov%e)
155  if (associated(x%cov%f)) deallocate(x%cov%f)
156  if (associated(x%cov%s)) deallocate(x%cov%s)
157 
158  x%cov%fact_chol = .false.
159  x%cov%equi_chol = 'N'
160 
161 end subroutine init_ropp_1d_statevec
162 
163 !------------------------------------------------------------------------------------
164 !------------------------------------------------------------------------------------
165 
166 subroutine init_ropp_1d_statevec_ad(temp_d, shum_d, pres_d, phi_d, &
167  lm, x_ad, iflip)
168 
169 ! Description:
170 ! subroutine to fill a ROPP state vector structure with
171 ! model background fields: Temperature, pressure, specific
172 ! humidity at full-levels, and surface geopotential height.
173 !
174 ! Inputs:
175 ! temp background temperature
176 ! shum background specific humidity
177 ! pres background pressure
178 ! phi geopotential height
179 ! lm number of vertical levels in the background
180 !
181 ! Outputs:
182 ! x Forward model state vector
183 !
184 ! ###############################################################
185  type(state1dfm), intent(inout) :: x_ad
186  integer, intent(in) :: lm
187  integer, optional, intent(in) :: iflip
188  real(kind=kind_real), dimension(lm), intent(inout) :: temp_d, shum_d, pres_d, phi_d
189 
190 ! Local variables
191  integer :: k
192  integer :: local_flip
193 !-------------------------------------------------------------------------
194 
195 
196 ! all this appears to be is a function to flip arguments before passing to ROPP
197  local_flip = 0
198  if ( present(iflip) ) then
199  local_flip = iflip
200  end if
201 
202  if ( local_flip == 1 ) then
203  do k = 1, lm
204  temp_d(k) = real(x_ad%temp(lm+1-k),kind=kind_real)
205  shum_d(k) = real(x_ad%shum(lm+1-k),kind=kind_real)
206  pres_d(k) = real(x_ad%pres(lm+1-k),kind=kind_real)
207  phi_d(k) = real(x_ad%geop(lm+1-k),kind=kind_real)
208  end do
209  else
210  temp_d(1:lm) = real(x_ad%temp(1:lm),kind=kind_real)
211  shum_d(1:lm) = real(x_ad%shum(1:lm),kind=kind_real)
212  pres_d(1:lm) = real(x_ad%pres(1:lm),kind=kind_real)
213  phi_d(1:lm) = real(x_ad%geop(1:lm),kind=kind_real)
214  end if
215  x_ad%temp(:) = 0.0_wp
216  x_ad%shum(:) = 0.0_wp
217  x_ad%pres(:) = 0.0_wp
218  x_ad%geop(:) = 0.0_wp
219 
220 end subroutine init_ropp_1d_statevec_ad
221 
222 !------------------------------------------------------------------------------------
223 !------------------------------------------------------------------------------------
224 
225 subroutine calc_station_phi(rlat, station_height, station_phi)
226 
227 ! Description:
228 ! convert station geometric height to geopotential height
229 ! provide inputs of:
230 ! lat, station_height
231 !
232 ! Inputs:
233 ! rlat latitude
234 ! station_height station geometric height
235 !
236 ! Outputs:
237 ! station_phi: station geopotential height
238 !-----------------------------------------------------------------------------------
239  implicit none
240 
241  real(kind=kind_real), intent(in) :: rlat, station_height
242  real(kind=kind_real), intent(out) :: station_phi
243 
244  ! not used in forward operator at this time -- simulate at model levels only
245  station_phi = geometric2geopotential(real(rlat,kind=wp), real(station_height,kind=wp))
246 
247 end subroutine calc_station_phi
248 
249 !------------------------------------------------------------------------------------
250 !------------------------------------------------------------------------------------
251 
252 subroutine calc_model_z(nlev, rlat, model_phi, model_z)
253 
254 ! Description:
255 ! convert model geopotential height to geometric height
256 ! provide inputs of:
257 ! number_of_model_levels, lat, model_geopotential
258 !
259 ! Inputs:
260 ! nlev number of model levels
261 ! rlat latitude
262 ! model_phi model geopotential height
263 !
264 ! Outputs:
265 ! model_z: model geometric height
266 !-----------------------------------------------------------------------------------
267  implicit none
268 
269  integer, intent(in) :: nlev
270  real(kind=kind_real), intent(in) :: rlat
271  real(kind=kind_real), dimension(nlev), intent(in) :: model_phi
272  real(kind=kind_real), dimension(nlev), intent(out) :: model_z
273 
274  integer ilev
275 
276  ! not used in forward operator at this time -- simulate at model levels only
277  do ilev = 1, nlev
278  model_z(ilev) = geopotential2geometric(real(rlat,kind=wp), real(model_phi(ilev),kind=wp))
279  end do
280 
281 end subroutine calc_model_z
282 
283 !------------------------------------------------------------------------------------
284 !------------------------------------------------------------------------------------
285 
286 subroutine init_ropp_1d_obvec(nlev, ichk, ob_time, rlat, rlon, station_phi, x, y)
287 
288 ! Description:
289 ! subroutine to fill a ROPP observation vector structure
290 ! observation provides the inputs of:
291 ! lat, lon, time
292 !
293 ! forward model will provide the simulation of refractivity
294 !
295 ! Inputs:
296 ! ob_time time of the observation
297 ! rlat latitude
298 ! rlon longitude
299 !
300 ! Outputs:
301 ! y: Partially filled Forward model observation vector
302 !-----------------------------------------------------------------------------------
303  implicit none
304 ! Input model state vector
305  type(state1dfm), intent(in) :: x ! ROPP 1d state vector data type
306 ! Output observation vector
307  type(obs1drefrac), intent(out) :: y ! ROPP 1d Refractivity "observations" vector data type
308 
309  integer, intent(in) :: nlev ! number of model level
310  integer, dimension(nlev), intent(in) :: ichk ! quality flag for observation
311  real(kind=kind_real), intent(in) :: rlat, rlon ! latitude and longitude
312  real(kind=kind_real), intent(in) :: station_phi ! observation geopoential height
313  real(kind=dp), intent(in) :: ob_time ! JulianTime of obs
314 ! Local variables
315  real(kind=kind_real) :: rlon_local ! ROPP uses -180/180
316  integer :: i
317 
318  y%time = real(ob_time,kind=wp)
319  y%lat = real(rlat,kind=wp)
320  rlon_local = rlon
321  if (rlon_local .gt. 180) rlon_local = rlon_local - 360.
322  y%lon = real(rlon_local,kind=wp)
323 
324 !----------------------------------------------------
325 ! allocate refractivity & weights
326 !----------------------------------------------------
327  if (associated(y%refrac)) then
328  deallocate(y%refrac)
329  deallocate(y%weights)
330  deallocate(y%geop)
331  nullify(y%refrac)
332  nullify(y%weights)
333  nullify(y%geop)
334  end if
335 
336  allocate(y%refrac(1:nlev)) ! value computed in fwd model
337  allocate(y%weights(1:nlev)) ! value set in fwd model
338  allocate(y%geop(1:nlev)) ! value set in fwd model
339 
340  do i=1,nlev
341  if (ichk(i) .le. 0) then
342  y%weights(i) = 1.0_wp ! following t_fascod example
343  else
344  y%weights(i) = 0.0_wp
345  end if
346  y%geop(i) = x%geop(i)
347  end do
348 
349  y%refrac(:) = 0.0_wp
350 
351 !---------------------------------------------
352 ! covariance matrix, is this used by ROPP FM?
353 !--------------------------------------------
354  y%obs_ok = .true.
355 
356 end subroutine init_ropp_1d_obvec
357 
358 !-------------------------------------------------------------------------
359 !-------------------------------------------------------------------------
360 subroutine init_ropp_1d_obvec_tlad(nlev, &
361  rlat,rlon,x,y,y_p)
362  implicit none
363 ! Input state vector
364  type(state1dfm), intent(in) :: x
365 ! Output observation vector
366  type(obs1drefrac), intent(out) :: y,y_p
367 
368  integer, intent(in) :: nlev
369  real(kind=kind_real), intent(in) :: rlat,rlon
370  real(kind=kind_real) :: rlon_local
371 
372 !-------------------------------------------------------------------------
373  y%time = real(0.0, kind=wp)!)real(ob_time,kind=wp)
374  y%lat = real(rlat,kind=wp)
375  rlon_local = rlon
376  if (rlon_local .gt. 180) rlon_local = rlon_local - 360.
377  y%lon = real(rlon_local,kind=wp)
378 
379 ! allocate refractivity
380 !---------------------------------------------------------
381  allocate(y%refrac(1:nlev)) ! value computed in fwd model
382  allocate(y%weights(1:nlev))
383  allocate(y%geop(1:nlev))
384  allocate(y_p%refrac(1:nlev)) ! value computed in fwd model
385  allocate(y_p%weights(1:nlev))
386  allocate(y_p%geop(1:nlev))
387 
388  y%weights(:) = 1.0
389  y_p%weights(:) = 1.0
390  y%refrac(:) = 0.0
391  y_p%refrac(:) = 0.0
392  y%geop(:) = x%geop(:)
393  y_p%geop(:) = x%geop(:)
394 
395  y%obs_ok = .true.
396 
397 end subroutine init_ropp_1d_obvec_tlad
398 
399 ! ------------------------------------------------------------------------------
400 ! Description:
401 ! integrate refractivities to obtain the zenith total delay
402 ! the station height is used to determine a correction
403 ! to match to the observation height
404 !
405 ! Inputs:
406 ! lm number of model levels
407 ! model_refrac refractivities to integrate
408 ! model_z model geometric heights
409 ! ob_terr observation station height
410 !
411 ! Outputs:
412 ! model_ztd integrated zenith total delay
413 ! l_linear logical which indicates if exponential terrain adjustment
414 ! not possible, and linear was assumed
415 !
416 ! ------------------------------------------------------------------------------
417 subroutine gnss_ztd_integral(lm, model_refrac, model_z, ob_terr, model_ztd, l_linear)
418 
419  implicit none
420 
421  integer, intent(in) :: lm
422  real(kind=kind_real), intent(in), dimension(lm) :: model_refrac, model_z
423  real(kind=kind_real), intent(in) :: ob_terr
424  real(kind=kind_real), intent(out) :: model_ztd
425  logical, intent(inout) :: l_linear
426 
427 ! local variables
428  integer, parameter :: max_string = 800
429  character(max_string) :: err_msg
430  real(kind=kind_real) :: ddzd, scaled_refrac, c_i
431  integer :: k
432  !-------------------------------------------------------------------------------
433  ! integral of ROPP refractivity values to compute zenith total delay (ztd)
434  !-------------------------------------------------------------------------------
435  l_linear = .false.
436  model_ztd = 0.0
437  ddzd = 0.0
438  do k = 1, lm
439  if (k==1) then
440  if ( model_refrac(k) <= 0 .or. model_refrac(k+1) <= 0 ) then
441  write(err_msg,'(a,2es13.3)') ' unphysical refractivity ', &
442  model_refrac(k), model_refrac(k+1)
443  call fckit_log%info(err_msg)
444  cycle
445  end if
446  ! lower boundary condition -- station below lowest model level
447  if ( ob_terr < model_z(k) ) then
448  c_i = ( log(model_refrac(k+1)) - log(model_refrac(k)) ) / &
449  ( model_z(k) - model_z(k+1) )
450  if ( c_i == 0 ) then
451  write(err_msg,'(a,2es13.3)') ' model refractivity repeating1 ', &
452  model_refrac(k), model_refrac(k+1)
453  call fckit_log%info(err_msg)
454  l_linear = .true.
455  ddzd = model_refrac(k)*(model_z(k+1) - ob_terr) ! use linear estimate
456  cycle
457  end if
458  ddzd = -1.0 * model_refrac(k)/c_i * exp(c_i*model_z(k)) * &
459  ( exp(-c_i*model_z(k+1)) - exp(-c_i*ob_terr) )
460  end if
461  else if ( ob_terr >= model_z(k)) then
462  cycle
463  else
464  if ( model_refrac(k) <= 0 .or. model_refrac(k-1) <= 0 ) then
465  write(err_msg,'(a,2es13.3)') ' unphysical refractivity ', &
466  model_refrac(k), model_refrac(k-1)
467  call fckit_log%info(err_msg)
468  cycle
469  end if
470  ! alternate lower boundary condition -- station between levels
471  if ( ob_terr >= model_z(k-1) .and. ob_terr < model_z(k) ) then
472  c_i = ( log(model_refrac(k)) - log(model_refrac(k-1)) ) / &
473  ( model_z(k-1) - model_z(k) )
474  if ( c_i == 0 ) then
475  write(err_msg,'(a,2es13.3)') ' model refractivity repeating2 ', &
476  model_refrac(k), model_refrac(k-1)
477  call fckit_log%info(err_msg)
478  l_linear = .true.
479  ddzd = model_refrac(k) * ( ob_terr - model_z(k-1) ) ! use linear estimate
480 ! ddzd = ddzd + 0.5*(model_refrac(k)+model_refrac(k-1))*(model_z(k) - ob_terr) ! not needed
481  cycle
482  end if
483  scaled_refrac = model_refrac(k-1) * exp( -c_i * (ob_terr - model_z(k-1)) )
484  ddzd = -1.0 * scaled_refrac/c_i * exp(c_i*ob_terr) * &
485  ( exp(-c_i*model_z(k)) - exp(-c_i*ob_terr) )
486 ! ddzd = ddzd + 0.5*(model_refrac(k)+model_refrac(k-1))*(model_z(k) - ob_terr) ! not needed
487  else ! normal integral up the column
488  ddzd = 0.5*(model_refrac(k)+model_refrac(k-1))*(model_z(k) - model_z(k-1))
489  end if
490  end if
491  model_ztd = model_ztd + ddzd
492  enddo
493 
494  ! a very bad place to put this suggestions?
495  model_ztd = model_ztd * 1.e-6
496 
497 end subroutine gnss_ztd_integral
498 
499 !-------------------------------------------------------------------------
500 !-------------------------------------------------------------------------
501 subroutine ropp_tidy_up_1d(x,y)
502  implicit none
503  type(state1dfm), intent(inout) :: x
504  type(obs1drefrac), intent(inout) :: y
505 ! x
506  if (associated(x%temp)) deallocate(x%temp)
507  if (associated(x%shum)) deallocate(x%shum)
508  if (associated(x%pres)) deallocate(x%pres)
509  if (associated(x%geop)) deallocate(x%geop)
510 ! y
511  if (associated(y%refrac)) deallocate(y%refrac)
512  if (associated(y%geop)) deallocate(y%geop)
513  if (associated(y%weights)) deallocate(y%weights)
514 
515 end subroutine ropp_tidy_up_1d
516 
517 !-------------------------------------------------------------------------
518 !-------------------------------------------------------------------------
519 subroutine ropp_tidy_up_tlad_1d(x,x_p,y,y_p)
520  implicit none
521  type(state1dfm), intent(inout) :: x,x_p ! x_p can be either x_tl or x_ad
522  type(obs1drefrac), intent(inout) :: y,y_p ! y_p can be either y_tl or y_ad
523 ! x
524  deallocate(x%temp)
525  deallocate(x%shum)
526  deallocate(x%pres)
527  deallocate(x%geop)
528 
529  deallocate(x_p%temp)
530  deallocate(x_p%shum)
531  deallocate(x_p%pres)
532  deallocate(x_p%geop)
533 ! y
534  deallocate(y%refrac)
535  deallocate(y_p%refrac)
536 
537  deallocate(y%weights)
538  deallocate(y_p%weights)
539 
540  deallocate(y%geop)
541  deallocate(y_p%geop)
542 
543 end subroutine ropp_tidy_up_tlad_1d
544 
545 !-------------------------------------------------------------------------
546 
Fortran module to handle ground-based gnss observations following the ROPP (2018 Aug) implementation.
subroutine, public calc_model_z(nlev, rlat, model_phi, model_z)
subroutine, public ropp_tidy_up_tlad_1d(x, x_p, y, y_p)
subroutine, public init_ropp_1d_obvec(nlev, ichk, ob_time, rlat, rlon, station_phi, x, y)
subroutine, public gnss_ztd_integral(lm, model_refrac, model_z, ob_terr, model_ztd, l_linear)
subroutine, public init_ropp_1d_statevec_ad(temp_d, shum_d, pres_d, phi_d, lm, x_ad, iflip)
subroutine, public calc_station_phi(rlat, station_height, station_phi)
subroutine, public init_ropp_1d_obvec_tlad(nlev, rlat, rlon, x, y, y_p)
subroutine, public init_ropp_1d_statevec(step_time, rlon, rlat, temp, shum, pres, phi, lm, z_sfc, x, iflip)