UFO
ufo_sfcpcorrected_mod.F90
Go to the documentation of this file.
1 ! (C) Copyright 2017-2020 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 for sfcpcorrected observation operator
7 
9 
10  use oops_variables_mod
11  use ufo_vars_mod
12  use missing_values_mod
13  use iso_c_binding
14  use kinds
15  use ufo_constants_mod, only : grav, rd, lclr, t2tv
17 
18  implicit none
19  private
20  integer, parameter :: max_string = 800
21 
22 !> Fortran derived type for the observation type
23  type, public :: ufo_sfcpcorrected
24  private
25  type(oops_variables), public :: obsvars ! Variables to be simulated
26  integer, allocatable, public :: obsvarindices(:) ! Indices of obsvars in the list of all
27  ! simulated variables in the ObsSpace
28  type(oops_variables), public :: geovars
29  character(len=MAXVARLEN) :: da_psfc_scheme
30  contains
31  procedure :: setup => ufo_sfcpcorrected_setup
32  procedure :: simobs => ufo_sfcpcorrected_simobs
33  end type ufo_sfcpcorrected
34 
35  character(len=MAXVARLEN), dimension(5) :: geovars_list = (/ var_ps, var_geomz, var_sfc_geomz, var_tv, var_prs /)
36 
37 contains
38 
39 ! ------------------------------------------------------------------------------
40 subroutine ufo_sfcpcorrected_setup(self, f_conf)
41 use fckit_configuration_module, only: fckit_configuration
42 use fckit_log_module, only : fckit_log
43 implicit none
44 class(ufo_sfcpcorrected), intent(inout) :: self
45 type(fckit_configuration), intent(in) :: f_conf
46 character(len=:), allocatable :: str_psfc_scheme, str_var_sfc_geomz, str_var_geomz
47 character(max_string) :: debug_msg
48 
49 !> In the case where a user wants to specify the geoVaLs variable name of model
50 !> height of vertical levels and/or the surface height. Example: MPAS is height
51 !> but FV-3 uses geopotential_height.
52 
53 if (f_conf%has("geovar_geomz")) then
54  call f_conf%get_or_die("geovar_geomz", str_var_geomz)
55  write(debug_msg,*) "ufo_sfcpcorrected_mod.F90: overriding default var_geomz with ", trim(str_var_geomz)
56  call fckit_log%debug(debug_msg)
57  geovars_list(2) = trim(str_var_geomz)
58 end if
59 if (f_conf%has("geovar_sfc_geomz")) then
60  call f_conf%get_or_die("geovar_sfc_geomz", str_var_sfc_geomz)
61  write(debug_msg,*) "ufo_sfcpcorrected_mod.F90: overriding default var_sfc_geomz with ", trim(str_var_sfc_geomz)
62  call fckit_log%debug(debug_msg)
63  geovars_list(3) = trim(str_var_sfc_geomz)
64 end if
65 
66 call self%geovars%push_back(geovars_list)
67 
68 self%da_psfc_scheme = "UKMO"
69 if (f_conf%has("da_psfc_scheme")) then
70  call f_conf%get_or_die("da_psfc_scheme",str_psfc_scheme)
71  self%da_psfc_scheme = str_psfc_scheme
72 end if
73 
74 end subroutine ufo_sfcpcorrected_setup
75 
76 ! ------------------------------------------------------------------------------
77 subroutine ufo_sfcpcorrected_simobs(self, geovals, obss, nvars, nlocs, hofx)
79 use obsspace_mod
81 use fckit_log_module, only : fckit_log
82 implicit none
83 class(ufo_sfcpcorrected), intent(in) :: self
84 integer, intent(in) :: nvars, nlocs
85 type(ufo_geovals), intent(in) :: geovals
86 real(c_double), intent(inout) :: hofx(nvars, nlocs)
87 type(c_ptr), value, intent(in) :: obss
88 
89 ! Local variables
90 real(c_double) :: missing
91 real(kind_real) :: H2000 = 2000.0
92 integer :: nobs, iobs, ivar, iobsvar, k, kbot, idx_geop
93 real(kind_real), allocatable :: cor_psfc(:)
94 type(ufo_geoval), pointer :: model_ps, model_p, model_sfc_geomz, model_tv, model_geomz
95 character(len=*), parameter :: myname_="ufo_sfcpcorrected_simobs"
96 character(max_string) :: err_msg
97 real(kind_real) :: wf
98 integer :: wi
99 logical :: variable_present
100 real(kind_real), dimension(:), allocatable :: obs_height, obs_t, obs_q, obs_psfc, obs_lat
101 real(kind_real), dimension(:), allocatable :: model_tvs, model_zs, model_level1, model_p_2000, model_tv_2000, model_psfc
102 real(kind_real) :: model_znew
103 
104 missing = missing_value(missing)
105 nobs = obsspace_get_nlocs(obss)
106 
107 ! check if nobs is consistent in geovals & nlocs
108 if (geovals%nlocs /= nobs) then
109  write(err_msg,*) myname_, ' error: nlocs of model and obs is inconsistent!'
110  call abor1_ftn(err_msg)
111 endif
112 
113 ! cor_psfc: observed surface pressure at model surface height, corresponding to P_o2m in da_intpsfc_prs* subroutines
114 allocate(cor_psfc(nobs))
115 cor_psfc = missing
116 
117 ! get obs variables
118 allocate(obs_height(nobs))
119 allocate(obs_psfc(nobs))
120 call obsspace_get_db(obss, "MetaData", "station_elevation",obs_height)
121 call obsspace_get_db(obss, "ObsValue", "surface_pressure", obs_psfc)
122 
123 ! get model variables; geovars_list = (/ var_ps, var_geomz, var_sfc_geomz, var_tv, var_prs /)
124 write(err_msg,'(a)') ' ufo_sfcpcorrected:'//new_line('a')// &
125  ' retrieving GeoVaLs with these names: '//trim(geovars_list(1))// &
126  ', '//trim(geovars_list(2))//', '//trim(geovars_list(3))// &
127  ', '//trim(geovars_list(4))//', '//trim(geovars_list(5))
128 call fckit_log%debug(err_msg)
129 call ufo_geovals_get_var(geovals, trim(geovars_list(1)), model_ps)
130 call ufo_geovals_get_var(geovals, trim(geovars_list(2)), model_geomz)
131 call ufo_geovals_get_var(geovals, trim(geovars_list(3)), model_sfc_geomz)
132 call ufo_geovals_get_var(geovals, trim(geovars_list(4)), model_tv)
133 call ufo_geovals_get_var(geovals, trim(geovars_list(5)), model_p)
134 
135 ! discover if the model vertical profiles are ordered top-bottom or not
136 kbot = 1
137 do iobs = 1, nlocs
138  if (obs_psfc(iobs).ne.missing) then
139  if (model_geomz%vals(1,iobs) .gt. model_geomz%vals(model_geomz%nval,iobs)) then
140  write(err_msg,'(a)') ' ufo_sfcpcorrected:'//new_line('a')// &
141  ' Model vertical height profile is from top to bottom'
142  call fckit_log%debug(err_msg)
143  kbot = model_geomz%nval
144  endif
145  exit
146  endif
147 enddo
148 
149 allocate(model_zs(nobs))
150 allocate(model_level1(nobs))
151 allocate(model_psfc(nobs))
152 
153 ! If needed, we can convert geopotential heights to geometric altitude
154 ! for the full model vertical column using gnssro_mod_transform. We need
155 ! to get the latitude of observation to do this.
156 idx_geop = -1
157 idx_geop = index(trim(geovars_list(2)),'geopotential')
158 if (idx_geop.gt.0) then
159  write(err_msg,'(a)') ' ufo_sfcpcorrected:'//new_line('a')// &
160  ' converting '//trim(geovars_list(2))// &
161  ' variable to z-geometric'
162  call fckit_log%debug(err_msg)
163  if (.not. allocated(obs_lat)) then
164  variable_present = obsspace_has(obss, "MetaData", "latitude")
165  if (variable_present) then
166  call fckit_log%debug(' allocating obs_lat array')
167  allocate(obs_lat(nobs))
168  call obsspace_get_db(obss, "MetaData", "latitude", obs_lat)
169  else
170  call abor1_ftn('Variable latitude@MetaData does not exist, aborting')
171  endif
172  endif
173  do iobs = 1, nlocs
174  if (obs_psfc(iobs).ne.missing) then
175  do k = 1, model_geomz%nval
176  call geop2geometric(latitude=obs_lat(iobs), &
177  geopotentialh=model_geomz%vals(k,iobs), &
178  geometricz=model_znew)
179  model_geomz%vals(k,iobs) = model_znew
180  enddo
181  endif
182  enddo
183 endif
184 
185 ! Now do the same if needed for surface geopotential height.
186 idx_geop = -1
187 idx_geop = index(trim(geovars_list(3)),'geopotential')
188 if (idx_geop.gt.0) then
189  write(err_msg,'(a)') ' ufo_sfcpcorrected:'//new_line('a')// &
190  ' converting '//trim(geovars_list(3))// &
191  ' variable to z-sfc-geometric'
192  call fckit_log%debug(err_msg)
193  if (.not. allocated(obs_lat)) then
194  variable_present = obsspace_has(obss, "MetaData", "latitude")
195  if (variable_present) then
196  call fckit_log%debug(' allocating obs_lat array')
197  allocate(obs_lat(nobs))
198  call obsspace_get_db(obss, "MetaData", "latitude", obs_lat)
199  else
200  call abor1_ftn('Variable latitude@MetaData does not exist, aborting')
201  endif
202  endif
203  do iobs = 1, nlocs
204  if (obs_psfc(iobs).ne.missing) then
205  call geop2geometric(latitude=obs_lat(iobs), &
206  geopotentialh=model_sfc_geomz%vals(1,iobs), &
207  geometricz=model_znew)
208  model_sfc_geomz%vals(1,iobs) = model_znew
209  endif
210  enddo
211 endif
212 
213 if (allocated(obs_lat)) deallocate(obs_lat)
214 
215 model_zs = model_sfc_geomz%vals(1,:)
216 model_psfc = model_ps%vals(1,:)
217 model_level1 = model_geomz%vals(kbot,:)
218 
219 ! do terrain height correction, two optional schemes
220 select case (trim(self%da_psfc_scheme))
221 
222 case ("WRFDA")
223  ! get extra obs values
224  variable_present = obsspace_has(obss, "ObsValue", "air_temperature")
225  if (variable_present) then
226  allocate(obs_t(nobs))
227  call obsspace_get_db(obss, "ObsValue", "air_temperature", obs_t)
228  end if
229  variable_present = obsspace_has(obss, "ObsValue", "specific_humidity")
230  if (variable_present) then
231  allocate(obs_q(nobs))
232  call obsspace_get_db(obss, "ObsValue", "specific_humidity", obs_q)
233  end if
234 
235  ! get extra model values
236  allocate(model_tvs(nobs))
237  model_tvs = model_tv%vals(kbot,:) + lclr * ( model_level1 - model_zs ) !Lclr = 0.0065 K/m
238 
239  ! correction
240  call da_intpsfc_prs(nobs, missing, cor_psfc, obs_height, obs_psfc, model_zs, model_tvs, obs_t, obs_q)
241 
242  deallocate(obs_t)
243  deallocate(obs_q)
244  deallocate(model_tvs)
245 
246 case ("UKMO")
247 
248  allocate(model_p_2000(nobs))
249  allocate(model_tv_2000(nobs))
250  do iobs = 1, nobs
251  ! vertical interpolation for getting model P and tv at 2000 m
252  call vert_interp_weights(model_geomz%nval, h2000, model_geomz%vals(:,iobs), wi, wf)
253  call vert_interp_apply(model_p%nval, model_p%vals(:,iobs), model_p_2000(iobs), wi, wf)
254  call vert_interp_apply(model_tv%nval, model_tv%vals(:,iobs), model_tv_2000(iobs), wi, wf)
255  end do
256 
257  ! correction
258  call da_intpsfc_prs_ukmo(nobs, missing, cor_psfc, obs_height, obs_psfc, model_zs, model_psfc, model_tv_2000, model_p_2000)
259 
260  deallocate(model_p_2000)
261  deallocate(model_tv_2000)
262 
263 case default
264  write(err_msg,*) "ufo_sfcpcorrected_mod.F90: da_psfc_scheme must be WRFDA or UKMO"
265  call fckit_log%debug(err_msg)
266  call abor1_ftn(err_msg)
267 end select
268 
269 ! update the obs surface pressure
270 do iobsvar = 1, size(self%obsvarindices)
271  ! Get the index of the row of hofx to fill
272  ivar = self%obsvarindices(iobsvar)
273  do iobs = 1, nlocs
274  if ( cor_psfc(iobs) /= missing) then
275  hofx(ivar,iobs) = obs_psfc(iobs) - cor_psfc(iobs) + model_psfc(iobs)
276  else
277  hofx(ivar,iobs) = model_psfc(iobs)
278  end if
279  enddo
280 enddo
281 
282 deallocate(obs_height)
283 deallocate(obs_psfc)
284 
285 deallocate(model_zs)
286 deallocate(model_level1)
287 deallocate(model_psfc)
288 
289 end subroutine ufo_sfcpcorrected_simobs
290 
291 ! ------------------------------------------------------------------------------
292 !> \Conduct terrain height correction for surface pressure
293 !!
294 !! \This subroutine is based on a subroutine from WRFDA da_intpsfc_prs.inc file
295 !! corresponding to sfc_assi_options = 1 in WRFDA's namelist
296 !!
297 !! \Date: June 2019: Created
298 !! \Method: hydrosatic equation
299 !!
300 !! P_o2m = P_o * exp [-grav/rd * (H_m-H_o) / (TV_m + TV_o)/2)
301 !!
302 !! Where:
303 !! H_m = model surface height
304 !! H_o = station height
305 !! TV_m = virtual temperature at model surface height
306 !! TV_o = virtual temperature at station height
307 !! P_o2m = pressure interpolated from station height to model surface height
308 !! P_o = pressure at station height
309 !! grav = gravitational acceleration
310 !! rd = gas constant per mole
311 !!
312 
313 subroutine da_intpsfc_prs (nobs, missing, P_o2m, H_o, P_o, H_m, TV_m, T_o, Q_o)
314 implicit none
315 integer, intent (in) :: nobs !<total observation number
316 real(c_double), intent (in) :: missing
317 real(kind_real), dimension(nobs), intent (out) :: P_o2m !<observed PS at model sfc height
318 real(kind_real), dimension(nobs), intent (in) :: H_o, P_o !<observed Height and PS
319 real(kind_real), dimension(nobs), intent (in) :: H_m, TV_m !<model sfc height and TV
320 real(kind_real), dimension(nobs), intent (in), optional :: T_o, Q_o !<obserbed T and Q
321 real(kind_real), dimension(nobs) :: TV_o, TV
322 
323 ! 1. model and observation virtual temperature
324 ! ---------------------------------------------
325 
326 if (present(t_o) .and. present(q_o)) then
327  tv_o = t_o * (1.0 + t2tv * q_o)
328 else if (present(t_o) .and. .not.(present(q_o))) then
329  tv_o = t_o
330 else
331  tv_o = tv_m
332 end if
333 
334 tv = 0.5 * (tv_m + tv_o)
335 
336 ! 2. extrapolate pressure from station height to model surface height
337 ! --------------------------------------------------------------------
338 
339 where ( h_o /= missing .and. p_o /= missing )
340  p_o2m = p_o * exp( - (h_m - h_o) * grav / (rd * tv) )
341 elsewhere
342  p_o2m = p_o
343 end where
344 
345 end subroutine da_intpsfc_prs
346 
347 ! ------------------------------------------------------------------------------
348 !> \Conduct terrain height correction for surface pressure
349 !!
350 !! \Reference: Ingleby,2013. UKMO Technical Report No: 582. Appendix 1.
351 !!
352 !! \Method: integrate the hydrosatic equation dp/dz=-rho*g/RT to get P_m2o first, equation:
353 !!
354 !! (P_m2o/P_m)=(T_m2o/T_m)** (grav/rd*L)
355 !!
356 !! Where:
357 !! P_m2o = model surface pressure at station height
358 !! P_m = model surface pressure
359 !! T_m = temperature at model surface height; derived from TV_2000
360 !! T_m2o = model surface temperature at station height
361 !! grav = gravitational acceleration
362 !! rd = gas constant per mole
363 !! Lclr = constant lapse rate (0.0065 K/m)
364 !!
365 !! To avoid dirunal/local variations, use TV_2000 (2000 m above the model surface height) instead of direct T_m
366 !!
367 !! T_m = TV_2000 * (P_o / P_2000) ** (rd*L/grav)
368 !!
369 !! Where:
370 !! P_2000 = background pressure at 2000 m
371 !! TV_2000 = background virtual temperature at 2000 m
372 !! P_o = pressure at station height
373 !!
374 !! Finally, in practice, adjust P_o to the model surface height using
375 !!
376 !! P_o2m = P_o * (P_m / P_m2o)
377 !!
378 
379 subroutine da_intpsfc_prs_ukmo (nobs, missing, P_o2m, H_o, P_o, H_m, P_m, TV_2000, P_2000)
380 implicit none
381 integer, intent (in) :: nobs !<total observation number
382 real(c_double), intent (in) :: missing
383 real(kind_real), dimension(nobs), intent (out) :: P_o2m !<observed PS at model sfc height
384 real(kind_real), dimension(nobs), intent (in) :: H_o, P_o !<observed Height and PS
385 real(kind_real), dimension(nobs), intent (in) :: H_m, P_m, TV_2000, P_2000 !<model Height, PS, TV at 2000 m, and P at 2000 m
386 real(kind_real) :: ind !<local variable
387 real(kind_real), dimension(nobs) :: P_m2o, T_m, T_m2o !<local variables: model PS at observed height, model sfc T,
388  !! and model T at observed height
389 
390 ! define the constant power exponent
391 ind = rd * lclr / grav
392 
393 where ( h_o /= missing .and. p_o /= missing )
394  ! calculate T_m -- background temperature at model surface height
395  ! T_m2o -- background temperature at station height
396  t_m = tv_2000 * (p_m / p_2000) ** ind
397  t_m2o = t_m + lclr * ( h_m - h_o)
398  p_m2o = p_m * (t_m2o / t_m) ** (1.0 / ind)
399 
400  ! In practice, P_o is adjusted to the model surface height
401  p_o2m = p_o * p_m / p_m2o
402 elsewhere
403  p_o2m = p_o
404 end where
405 
406 end subroutine da_intpsfc_prs_ukmo
407 
408 ! ------------------------------------------------------------------------------
409 
410 end module ufo_sfcpcorrected_mod
subroutine geop2geometric(latitude, geopotentialH, geometricZ, dzdh_jac)
real(kind_real), parameter, public grav
real(kind_real), parameter, public rd
real(kind_real), parameter, public t2tv
real(kind_real), parameter, public lclr
subroutine, public ufo_geovals_get_var(self, varname, geoval)
Fortran module for sfcpcorrected observation operator.
character(len=maxvarlen), dimension(5) geovars_list
subroutine da_intpsfc_prs_ukmo(nobs, missing, P_o2m, H_o, P_o, H_m, P_m, TV_2000, P_2000)
\Conduct terrain height correction for surface pressure
subroutine ufo_sfcpcorrected_setup(self, f_conf)
subroutine da_intpsfc_prs(nobs, missing, P_o2m, H_o, P_o, H_m, TV_m, T_o, Q_o)
\Conduct terrain height correction for surface pressure
integer, parameter max_string
subroutine ufo_sfcpcorrected_simobs(self, geovals, obss, nvars, nlocs, hofx)
character(len=maxvarlen), parameter, public var_geomz
character(len=maxvarlen), parameter, public var_prs
character(len=maxvarlen), parameter, public var_sfc_geomz
character(len=maxvarlen), parameter, public var_ps
character(len=maxvarlen), parameter, public var_tv
Fortran module to perform linear interpolation.
Definition: vert_interp.F90:8
subroutine vert_interp_weights(nlev, obl, vec, wi, wf)
Definition: vert_interp.F90:22
subroutine vert_interp_apply(nlev, fvec, f, wi, wf)
Definition: vert_interp.F90:73
type to hold interpolated field for one variable, one observation
type to hold interpolated fields required by the obs operators
Fortran derived type for the observation type.