FV3 Bundle
ufo_gnssro_bndgsi_mod.F90
Go to the documentation of this file.
1 !
2 ! This software is licensed under the terms of the Apache Licence Version 2.0
3 ! which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
4 
5 !> Fortran module to handle gnssro bending angle observations following
6 !> the NCEP/GSI (2018 Aug) implementation
7 
9  use iso_c_binding
10  use kinds
11  use ufo_vars_mod
12  use ufo_geovals_mod
14  use vert_interp_mod
15  use ufo_basis_mod, only: ufo_basis
17  use obsspace_mod
18 
19  implicit none
20  public :: ufo_gnssro_bndgsi
21  private
22 
23  !> Fortran derived type for gnssro trajectory
24  type, extends(ufo_basis) :: ufo_gnssro_bndgsi
25  contains
26  procedure :: simobs => ufo_gnssro_bndgsi_simobs
27  end type ufo_gnssro_bndgsi
28 
29  contains
30 ! ------------------------------------------------------------------------------
31  subroutine ufo_gnssro_bndgsi_simobs(self, geovals, hofx, obss)
35  use obsspace_mod, only: obsspace_get_db
36  implicit none
37  class(ufo_gnssro_bndGSI), intent(in) :: self
38  type(ufo_geovals), intent(in) :: geovals
39  real(kind_real), intent(inout) :: hofx(:)
40  type(c_ptr), value, intent(in) :: obss
41 
42  character(len=*), parameter :: myname_ ="ufo_gnssro_bndgsi_simobs"
43  logical, parameter :: use_compress = .true.
44  real(kind_real), parameter :: r1em6 = 1.0e-6_kind_real
45  real(kind_real), parameter :: r1em3 = 1.0e-3_kind_real
46  real(kind_real), parameter :: six = 6.0_kind_real
47  real(kind_real), parameter :: ds = 10000.0_kind_real
48  integer, parameter :: nlevAdd = 13 !num of additional levels on top of exsiting model levels
49  integer, parameter :: newAdd = 20 !num of additional levels on top of extended levels
50  integer, parameter :: ngrd = 80 !num of new veritcal grids for bending angle computation
51  integer, parameter :: max_string = 800
52  real(kind_real), parameter :: miss_values = -99999.00
53  real(kind_real), parameter :: crit_gradRefr = 157.0_kind_real !criteria for the refractivity gradient
54 
55  character(max_string) :: err_msg
56  integer :: iobs, ilev, igrd, klev
57  integer :: nlev, nlev1, nobs, nlevExt, nlevCheck
58  real(kind_real) :: rnlevExt
59  real(kind_real) :: w4(4), dw4(4)
60  integer :: ierr
61  type(ufo_geoval), pointer :: t, gphi, prsi, q
62  real(kind_real), allocatable :: gesT(:,:), gesZi(:,:), gesPi(:,:), gesQ(:,:)
63  real(kind_real), allocatable :: refr(:,:), radius(:,:)
64  real(kind_real), allocatable :: refrIndex(:), refrXrad(:), geomzi(:), refrXrad_new(:)
65  real(kind_real), allocatable :: lagConst(:,:)
66  real(kind_real), allocatable :: obsLat(:), obsImpP(:), obsLocR(:), obsGeoid(:)
67  real(kind_real) :: specHmean, tmean
68  real(kind_real) :: d_refrXrad
69  real(kind_real) :: derivRefr_s(ngrd),grids(ngrd),refrXrad_s(ngrd)
70  real(kind_real) :: sIndx, sIndxExt
71  integer :: indx
72  real(kind_real) :: bndIntgd, bendingAngle, obsImpH, gradRefr
73  logical :: obs_check, qc_layer_SR
74  integer :: nobs_outIntgl
75  integer :: count_SR, top_layer_SP, top_layer_SR, bot_layer_SR !for super refraction
76  integer :: count_rejection
77  real(kind_real) :: jacob
78  nobs = 0
79  nlev = 0
80  nlev1 = 0
81  nlevext = 0
82  sindxext = one
83  hofx(:) = miss_values
84 
85  ! check if nobs is consistent in geovals & hofx
86  if (geovals%nobs /= size(hofx)) then
87  write(err_msg,*) myname_, ' error: nobs inconsistent!'
88  call abor1_ftn(err_msg)
89  endif
90  ! check if prsi (pressure at model interface levels) variable is in geovals and get it
91  call ufo_geovals_get_var(geovals, var_prsi, prsi,status=ierr)
92  if (ierr/=0) then
93  write(err_msg,*) myname_, trim(var_prsi), ' doesnt exist'
94  call abor1_ftn(err_msg)
95  endif
96  ! check if t (temperature) variable is in geovals and get it
97  call ufo_geovals_get_var(geovals, var_t, t,status=ierr)
98  if (ierr/=0) then
99  write(err_msg,*) myname_, trim(var_t), ' doesnt exist'
100  call abor1_ftn(err_msg)
101  endif
102  ! check if q(specific humidity) variable is in geovals and get it
103  call ufo_geovals_get_var(geovals, var_q, q,status=ierr)
104  if (ierr/=0) then
105  write(err_msg,*) myname_, trim(var_q), ' doesnt exist'
106  call abor1_ftn(err_msg)
107  endif
108  ! check if gphi (geopotential height at model interface levels) variable is in geovals and get it
109  call ufo_geovals_get_var(geovals, var_zi, gphi,status=ierr)
110  if (ierr/=0) then
111  write(err_msg,*) myname_, trim(var_zi), ' doesnt exist'
112  call abor1_ftn(err_msg)
113  endif
114 
115 
116  nlev = t%nval ! number of model levels
117  nlev1 = prsi%nval ! number of model interface levels
118  if (nlev1 /= nlev+1) then
119  write(err_msg,*) myname_, ' Numbers of vertical profiles, nlev1 and nlev, dont match'
120  call abor1_ftn(err_msg)
121  endif
122  nlevext = nlev + nlevadd
123  nlevcheck = min(23, nlev) !number of levels to check super refraction
124  nobs = geovals%nobs ! number of observations
125 
126  allocate(gespi(nlev1,nobs))
127  allocate(geszi(nlev1,nobs))
128  allocate(gest(nlev,nobs))
129  allocate(gesq(nlev,nobs))
130 
131  !FV3 background is from top to bottom. Reserse the vertical order
132  do ilev=1, nlev
133  gest(ilev,:) = t%vals(nlev-ilev+1,:)
134  gesq(ilev,:) = q%vals(nlev-ilev+1,:)
135  enddo
136 
137  do ilev=1, nlev1
138  gespi(ilev,:) = prsi%vals(nlev1-ilev+1,:)
139  geszi(ilev,:) = gphi%vals(nlev1-ilev+1,:)
140  enddo
141 
142  do igrd = 0, ngrd-1
143  grids(igrd+1) = igrd * ds
144  end do
145 
146  allocate(geomzi(nlev)) !geometric height at interface model levels
147  allocate(radius(nlev,nobs)) !distance between earth center to model interface level
148 
149  allocate(refr(nlevext,nobs)) !refractivity N
150  allocate(refrindex(nlev)) !refractivity index n
151  allocate(refrxrad(0:nlevext+1)) !x=nr, r: radius
152  allocate(lagconst(3,nlevext)) !x=nr, r: radius
153  allocate(refrxrad_new(nlevext+newadd))
154 
155  allocate(obslat(nobs))
156  allocate(obsimpp(nobs))
157  allocate(obslocr(nobs))
158  allocate(obsgeoid(nobs))
159 
160  call obsspace_get_db(obss, "Metadata", "Latitude", obslat)
161  call obsspace_get_db(obss, "Metadata", "IMPP", obsimpp) !observed impact parameter; meter
162  call obsspace_get_db(obss, "Metadata", "ELRC", obslocr) !local radius of earth; meter
163  call obsspace_get_db(obss, "Metadata", "GEODU", obsgeoid) !Geoid; meter
164 
165  nobs_outintgl = 0 !initialize count of observations out of integral grids
166  count_rejection = 0
167 
168  obs_loop: do iobs = 1, nobs
169  do ilev = 1,nlev
170 
171  ! compute guess geometric height from geopotential height at model interface levels
172  call geop2geometric( obslat(iobs), geszi(ilev,iobs), geomzi(ilev), jacob)
173 
174  ! compute guess radius
175  radius(ilev,iobs) = geomzi(ilev) + obsgeoid(iobs) + obslocr(iobs) ! radius r
176 
177  ! compute guess refractivity and refractivity index at model interface levels
178  if(ilev > 1) then
179  spechmean = (gesq(ilev,iobs) + gesq(ilev-1,iobs))/two
180  tmean = (gest(ilev,iobs) + gest(ilev-1,iobs) )/two
181  else
182  spechmean = gesq(1,iobs)
183  tmean = gest(1,iobs)
184  endif
185  call compute_refractivity(tmean, spechmean, gespi(ilev,iobs), refr(ilev,iobs),use_compress) !refr N
186  refrindex(ilev) = one + (r1em6*refr(ilev,iobs)) ! refr index n
187  refrxrad(ilev) = refrindex(ilev) * radius(ilev,iobs) ! x=nr
188 
189  end do
190 
191  ! data rejection based on model background !
192  ! (1) skip data below the model levels
193  call get_coordinate_value(obsimpp(iobs), sindx,refrxrad(1),nlev,"increasing")
194  if (sindx < one .or. sindx > float(nlev)) then
195  count_rejection = count_rejection + 1
196  cycle
197  end if
198 
199  ! (2) super-refraction
200  qc_layer_sr=.false.
201  count_sr=0
202  top_layer_sr=0
203  bot_layer_sr=0
204  obsimph = (obsimpp(iobs) - obslocr(iobs)) * r1em3 !impact heigt: a-r_earth
205  if (obsimph <= six) then
206  do klev=nlevcheck,1,-1
207 
208  ! check for model SR layer
209  gradrefr = 1000.0_kind_real * (refr(klev+1,iobs)-refr(klev,iobs)) / (radius(klev+1,iobs)-radius(klev,iobs))
210 
211  if (abs(gradrefr)>= half*crit_gradrefr) then !Super refractivity - likely, to be used in obs SR qc
212  qc_layer_sr=.true. !SR-likely layer detected
213  endif
214 
215  !if (((ref_rad(klev+1)-ref_rad(klev))/(radius(klev+1,iobs)-radius(klev,iobs))) < zero) then
216  if (abs(gradrefr) >= 0.75_kind_real*crit_gradrefr) then !relax to close-to-SR conditions
217  count_sr=count_sr+1 ! layers of SR
218 
219  if (count_sr > 1 ) then
220  !if(abs(bot_layer_SR-klev) > 1) write(6,*) 'WARNING GPSRO: non-consecutive SR layers'
221  bot_layer_sr=klev
222  else
223  top_layer_sr=klev
224  bot_layer_sr=top_layer_sr
225  endif
226 
227  endif
228  end do
229  if (top_layer_sr >= 1 .and. obsimpp(iobs) <= refrxrad(top_layer_sr+2)) then !obs inside model SR layer
230  count_rejection = count_rejection + 1
231  cycle
232  end if
233  endif
234 
235 
236  ! Extend atmosphere above interface level nlev
237  d_refrxrad = refrxrad(nlev) - refrxrad(nlev-1)
238  do ilev=1,nlevadd
239  refrxrad(nlev+ilev)=refrxrad(nlev)+ ilev*d_refrxrad ! extended x_i
240  refr(nlev+ilev,iobs)=refr(nlev+ilev-1,iobs)**2/refr(nlev+ilev-2,iobs) ! exended N_i
241  end do
242 
243  refrxrad(0)=refrxrad(3)
244  refrxrad(nlevext+1)=refrxrad(nlevext-2)
245 
246  do ilev = 1,nlevext
247  call lag_interp_const(lagconst(:,ilev),refrxrad(ilev-1:ilev+1),3)
248  enddo
249 
250  ! Set up a new equally-spaced vertical grid for integral
251 
252  derivrefr_s = zero
253  grids_loop: do igrd =1,ngrd
254  !use the new grids (s) for bending angle computation
255  refrxrad_s(igrd)=sqrt(grids(igrd)**2 + obsimpp(iobs)**2) !x_s^2=s^2+a^2
256 
257  call get_coordinate_value(refrxrad_s(igrd), sindx,refrxrad(1:nlevext),nlevext,"increasing")
258 
259  rnlevext = float(nlevext)
260 
261  if (sindx < rnlevext) then !obs inside the new grid
262  !HS if (sIndx > zero .and. sIndx < rnlevExt) then !obs inside the new grid
263  indx=sindx
264 
265 ! Compute derivative at new grids (dN/ds) using Lagrange interpolators
266  call lag_interp_smthweights(refrxrad(indx-1:indx+2),refrxrad_s(igrd),&
267  lagconst(:,indx),lagconst(:,indx+1),&
268  w4,dw4,4)
269  if(indx==1) then
270  w4(4)=w4(4)+w4(1); w4(1:3)=w4(2:4);w4(4)=zero
271  dw4(4)=dw4(4)+dw4(1);dw4(1:3)=dw4(2:4);dw4(4)=zero
272  indx=indx+1
273  endif
274  if(indx==nlevext-1) then
275  w4(1)=w4(1)+w4(4); w4(2:4)=w4(1:3);w4(1)=zero
276  dw4(1)=dw4(1)+dw4(4); dw4(2:4)=dw4(1:3);dw4(1)=zero
277  indx=indx-1
278  endif
279  !need make sure dot_product is available or add the code
280  derivrefr_s(igrd)=dot_product(dw4,refr(indx-1:indx+2,iobs)) !derivative dN/dx_s
281  derivrefr_s(igrd)=max(zero,abs(derivrefr_s(igrd)))
282 
283  else
284  obs_check=.true.
285  nobs_outintgl=nobs_outintgl+1
286  d_refrxrad=refrxrad(nlev)-refrxrad(nlev-1)
287  do klev=1,newadd
288  refrxrad_new(nlevext+klev)=refrxrad(nlevext)+ klev*d_refrxrad ! extended x_i
289  end do
290  do klev=1,nlevext
291  refrxrad_new(klev)=refrxrad(klev)
292  enddo
293  call get_coordinate_value(refrxrad_s(igrd), sindx,refrxrad_new(1:nlevext+newadd),nlevext+newadd,"increasing")
294  sindxext=max(sindx,sindxext)
295  endif !obs in new grid
296  end do grids_loop
297 
298 ! bending angle (radians)
299  bendingangle = ds*derivrefr_s(1)/refrxrad_s(1)
300  do igrd = 2,ngrd
301  bndintgd = ds*derivrefr_s(igrd)/refrxrad_s(igrd)
302  bendingangle = bendingangle + two*bndintgd
303  end do
304  bendingangle=r1em6 * obsimpp(iobs) * bendingangle
305  hofx(iobs) = bendingangle
306 
307  end do obs_loop
308 
309  write(6,*) 'bndGSI: hofx ', &
310  'min = ', minval(hofx, mask=hofx > miss_values), 'min index = ', minloc(hofx), &
311  'max = ', maxval(hofx, mask=hofx > miss_values), 'max index = ', maxloc(hofx)
312  write(6,*) 'bndGSI: ', count_rejection, ' out of ', nobs, ' rejected due to model vertical range and super refraction'
313 
314  !for tuning the nlevExt. New grids (s) should be in range with nlevExt. If not, adjust the hardwired
315  if (nobs_outintgl>=1) then
316  write(6,*)'bndGSI: Warning',nobs_outintgl,'obs outside integration grid. Increase nlevExt to',&
317  int(sindxext)
318  endif
319 
320  deallocate(obslat)
321  deallocate(obsimpp)
322  deallocate(obslocr)
323  deallocate(obsgeoid)
324 
325  deallocate(gespi)
326  deallocate(geszi)
327  deallocate(gest)
328  deallocate(gesq)
329  deallocate(refr)
330  deallocate(refrindex)
331  deallocate(refrxrad)
332  deallocate(geomzi)
333  deallocate(radius)
334  deallocate(lagconst)
335  deallocate(refrxrad_new)
336 
337  end subroutine ufo_gnssro_bndgsi_simobs
338 ! ------------------------------------------------------------------------------
339 end module ufo_gnssro_bndgsi_mod
subroutine, public ufo_geovals_get_var(self, varname, geoval, status)
real(fp), parameter, public zero
character(len=maxvarlen), public var_prsi
subroutine, public lag_interp_smthweights(x, xt, aq, bq, w, dw, n)
Definition: lag_interp.F90:276
real(kind_real), parameter, public half
subroutine, public get_coordinate_value(fin, fout, x, nx, flag)
Fortran module to prepare for Lagrange polynomial interpolation. based on GSI: lagmod.f90.
Definition: lag_interp.F90:4
subroutine geop2geometric(latitude, geopotentialH, geometricZ, gp2gm)
Fortran module to perform linear interpolation.
Definition: vert_interp.F90:8
real(fp), parameter, public one
character(len=maxvarlen), public var_q
real(fp), parameter, public two
subroutine ufo_gnssro_bndgsi_simobs(self, geovals, hofx, obss)
type(registry_t), public ufo_geovals_registry
Linked list interface - defines registry_t type.
#define max(a, b)
Definition: mosaic_util.h:33
subroutine, public lag_interp_const(q, x, n)
Definition: lag_interp.F90:28
Fortran module to handle gnssro bending angle observations following the NCEP/GSI (2018 Aug) implemen...
#define min(a, b)
Definition: mosaic_util.h:32
character(len=maxvarlen), public var_zi
character(len=maxvarlen), public var_t
subroutine compute_refractivity(temperature, specH, pressure, refr, use_compress)
Fortran interface to ObsSpace.
Definition: obsspace_mod.F90:9
Fortran derived type for gnssro trajectory.