UFO
ufo_gnssro_refmetoffice_mod.F90
Go to the documentation of this file.
1 !-------------------------------------------------------------------------------
2 ! (C) British Crown Copyright 2021 Met Office
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 
8 !> Fortran module for gnssro refractivity Met Office forward operator
9 
11 
12 use iso_c_binding
13 use kinds
14 use ufo_vars_mod
19 use obsspace_mod
20 use missing_values_mod
22 use fckit_log_module, only : fckit_log
23 use ufo_constants_mod, only: &
24  rd, & ! Gas constant for dry air
25  grav, & ! Gravitational field strength
26  mw_ratio, & ! Ratio of molecular weights of water and dry air
27  c_virtual, & ! Related to mw_ratio
28  n_alpha, & ! Refractivity constant a
29  n_beta ! Refractivity constant b
30 
31 
32 implicit none
34 private
35 
36  !> Fortran derived type for gnssro trajectory
38  logical :: vert_interp_ops
39  logical :: pseudo_ops
40  real(kind_real) :: min_temp_grad
41  contains
42  procedure :: setup => ufo_gnssro_refmetoffice_setup
43  procedure :: simobs => ufo_gnssro_refmetoffice_simobs
45 
46 contains
47 
48 !-------------------------------------------------------------------------------
49 !> \brief Set up the Met Office GNSS-RO refractivity operator
50 !!
51 !! \details **ufo_gnssro_refmetoffice_setup**
52 !! * Get the optional settings for the forward model, and save them in the
53 !! object so that they can be used in the code.
54 !!
55 !! \author Neill Bowler (Met Office)
56 !!
57 !! \date 20 March 2021
58 !!
59 !-------------------------------------------------------------------------------
60 subroutine ufo_gnssro_refmetoffice_setup(self, vert_interp_ops, pseudo_ops, min_temp_grad)
61 
62 implicit none
63 
64 class(ufo_gnssro_refmetoffice), intent(inout) :: self
65 logical(c_bool), intent(in) :: vert_interp_ops
66 logical(c_bool), intent(in) :: pseudo_ops
67 real(c_float), intent(in) :: min_temp_grad
68 
69 self % vert_interp_ops = vert_interp_ops
70 self % pseudo_ops = pseudo_ops
71 self % min_temp_grad = min_temp_grad
72 
73 end subroutine ufo_gnssro_refmetoffice_setup
74 
75 !-------------------------------------------------------------------------------
76 !> \brief Calculate the model forecast of the observations
77 !!
78 !! \details **ufo_gnssro_refmetoffice_simobs**
79 !! * 1-dimensional GNSS-RO forward operator for the Met Office system
80 !!
81 !! \author Neill Bowler (Met Office)
82 !!
83 !! \date 20 March 2021
84 !!
85 !-------------------------------------------------------------------------------
86 subroutine ufo_gnssro_refmetoffice_simobs(self, geovals_in, obss, hofx, obs_diags)
87 
88  implicit none
89 
90  ! Arguments to this routine
91  class(ufo_gnssro_refmetoffice), intent(in) :: self ! The object in which this operator is contained
92  type(ufo_geovals), intent(in) :: geovals_in ! The model values, interpolated to the observation locations
93  real(kind_real), intent(inout) :: hofx(:) ! The model forecast of the observations
94  type(c_ptr), value, intent(in) :: obss ! The observations, and meta-data for those observations
95  type(ufo_geovals), intent(inout) :: obs_diags ! Observation diagnostics
96 
97  character(len=*), parameter :: myname_ = "ufo_gnssro_refmetoffice_simobs"
98  integer, parameter :: max_string = 800
99 
100  character(max_string) :: err_msg ! Error message for output
101  character(max_string) :: message ! General message for output
102  integer :: nobs ! Number of observations
103  integer :: ilev ! Loop variable, level number
104  integer :: iobs ! Loop variable, observation number
105  type(ufo_geoval), pointer :: q ! Model background values of specific humidity
106  type(ufo_geoval), pointer :: prs ! Model background values of air pressure
107  type(ufo_geoval), pointer :: theta_heights ! Model heights of levels containing specific humidity
108  type(ufo_geoval), pointer :: rho_heights ! Model heights of levels containing air pressure
109  real(kind_real), allocatable :: obsLat(:) ! Latitude of the observation
110  real(kind_real), allocatable :: obsLon(:) ! Longitude of the observation
111  real(kind_real), allocatable :: obs_height(:) ! Geopotential height of the observation
112  logical :: BAErr ! Was there an error in the calculation?
113  integer :: iVar ! Loop variable, obs diagnostics variable number
114  real(kind_real), allocatable :: refractivity(:) ! Refractivity on various model levels
115  real(kind_real), allocatable :: model_heights(:) ! Geopotential heights that refractivity is calculated on
116  type(ufo_geovals) :: geovals ! The model values, interpolated to the observation locations
117  ! and flipped in the vertical (if required)
118 
119  write(err_msg,*) "TRACE: ufo_gnssro_refmetoffice_simobs: begin"
120  call fckit_log%info(err_msg)
121 
122  ! If output to refractivity (and heights of the refractivity levels) is needed,
123  ! then use nval as a way to check whether the array has been initialised (since
124  ! it is called in a loop).
125  DO ivar = 1, obs_diags % nvar
126  IF (obs_diags % variables(ivar) == "refractivity" .OR. &
127  obs_diags % variables(ivar) == "model_heights") THEN
128  write(err_msg,*) "TRACE: ufo_gnssro_refmetoffice_simobs: initialising obs_diags for " // &
129  obs_diags % variables(ivar)
130  call fckit_log%info(err_msg)
131  obs_diags % geovals(ivar) % nval = 0
132  END IF
133  END DO
134 
135 ! check if nlocs is consistent in geovals & hofx
136  if (geovals_in%nlocs /= size(hofx)) then
137  write(err_msg,*) myname_, ' error: nlocs inconsistent!'
138  call abor1_ftn(err_msg)
139  endif
140 
141 ! make sure that the geovals are in the correct vertical order (surface first)
142  call ufo_geovals_copy(geovals_in, geovals) ! dont want to change geovals_in
143  call ufo_geovals_reorderzdir(geovals, var_prsi, "bottom2top")
144 
145  write(message, *) myname_, ' Running Met Office GNSS-RO forward operator with:'
146  call fckit_log%info(message)
147  write(message, *) 'vert_interp_ops =', self % vert_interp_ops, &
148  'pseudo_ops =', self % pseudo_ops, 'min_temp_grad =', self % min_temp_grad
149  call fckit_log%info(message)
150 
151 ! get variables from geovals
152  call ufo_geovals_get_var(geovals, var_q, q) ! specific humidity
153  call ufo_geovals_get_var(geovals, var_prsi, prs) ! pressure
154  call ufo_geovals_get_var(geovals, var_z, theta_heights) ! Geopotential height of the normal model levels
155  call ufo_geovals_get_var(geovals, var_zi, rho_heights) ! Geopotential height of the pressure levels
156 
157  write(message, '(A,10I6)') 'Q: ', q%nval, q%nlocs, shape(q%vals)
158  call fckit_log%info(message)
159  write(message, '(A,10I6)') 'Pressure: ', prs%nval, prs%nlocs, shape(prs%vals)
160  call fckit_log%info(message)
161 
162  nobs = obsspace_get_nlocs(obss)
163 
164 ! set obs space struture
165  allocate(obslon(nobs))
166  allocate(obslat(nobs))
167  allocate(obs_height(nobs))
168 
169  call obsspace_get_db(obss, "MetaData", "longitude", obslon)
170  call obsspace_get_db(obss, "MetaData", "latitude", obslat)
171  call obsspace_get_db(obss, "MetaData", "obs_height", obs_height)
172 
173  obs_loop: do iobs = 1, nobs
174 
175  call refmetoffice_forwardmodel(prs % nval, &
176  q % nval, &
177  rho_heights % vals(:,iobs), &
178  theta_heights % vals(:,iobs), &
179  prs % vals(:,iobs), &
180  q % vals(:,iobs), &
181  self % pseudo_ops, &
182  self % vert_interp_ops, &
183  self % min_temp_grad, &
184  1, &
185  obs_height(iobs:iobs), &
186  obslat(iobs), &
187  hofx(iobs:iobs), &
188  baerr, &
189  refractivity, &
190  model_heights)
191 
192  if (baerr) then
193  write(err_msg,*) "Error with observation processing ", iobs
194  call fckit_log % info(err_msg)
195  end if
196 
197  ! If required, then save the refractivity and model heights to the obs diagnostics.
198  ! Allocate the output arrays on the first iteration.
199  DO ivar = 1, obs_diags % nvar
200  IF (obs_diags % variables(ivar) == "refractivity") THEN
201  IF (iobs == 1) THEN
202  obs_diags % geovals(ivar) % nval = SIZE(refractivity)
203  ALLOCATE(obs_diags % geovals(ivar) % vals(SIZE(refractivity), obs_diags % nlocs))
204  END IF
205 
206  IF (baerr) THEN
207  obs_diags % geovals(ivar) % vals(:,iobs) = missing_value(obs_diags % geovals(ivar) % vals(1,1))
208  ELSE
209  obs_diags % geovals(ivar) % vals(:,iobs) = refractivity(:)
210  END IF
211  END IF
212 
213  IF (obs_diags % variables(ivar) == "model_heights") THEN
214  IF (iobs == 1) THEN
215  obs_diags % geovals(ivar) % nval = SIZE(model_heights)
216  ALLOCATE(obs_diags % geovals(ivar) % vals(SIZE(model_heights), obs_diags % nlocs))
217  END IF
218 
219  IF (baerr) THEN
220  obs_diags % geovals(ivar) % vals(:,iobs) = missing_value(obs_diags % geovals(ivar) % vals(1,1))
221  ELSE
222  obs_diags % geovals(ivar) % vals(:,iobs) = model_heights(:)
223  END IF
224  END IF
225  END DO
226  end do obs_loop
227 
228  deallocate(obslat)
229  deallocate(obslon)
230  deallocate(obs_height)
231  call ufo_geovals_delete(geovals)
232 
233  write(err_msg,*) "TRACE: ufo_gnssro_refmetoffice_simobs: completed"
234  call fckit_log%info(err_msg)
235 
236 end subroutine ufo_gnssro_refmetoffice_simobs
237 
238 !-------------------------------------------------------------------------------
239 !> \brief Interface routine for the GNSS-RO refractivity forward operator
240 !!
241 !! \details **RefMetOffice_ForwardModel**
242 !! * Calculate the refractivity on model or pseudo levels
243 !! * Vertically interpolate the model refractivity to the observation locations
244 !!
245 !! \author Neill Bowler (Met Office)
246 !!
247 !! \date 20 March 2021
248 !!
249 !-------------------------------------------------------------------------------
250 SUBROUTINE refmetoffice_forwardmodel(nlevp, &
251  nlevq, &
252  za, &
253  zb, &
254  pressure, &
255  humidity, &
256  GPSRO_pseudo_ops, &
257  GPSRO_vert_interp_ops, &
258  GPSRO_min_temp_grad, &
259  nobs, &
260  zobs, &
261  Latitude, &
262  ycalc, &
263  BAErr, &
264  refractivity, &
265  model_heights)
266 
267 INTEGER, INTENT(IN) :: nlevp ! no. of p levels in state vec.
268 INTEGER, INTENT(IN) :: nlevq ! no. of theta levels
269 REAL(kind_real), INTENT(IN) :: za(1:nlevp) ! heights of rho levs
270 REAL(kind_real), INTENT(IN) :: zb(1:nlevq) ! heights of theta levs
271 REAL(kind_real), INTENT(IN) :: pressure(1:nlevp) ! Model background pressure
272 REAL(kind_real), INTENT(IN) :: humidity(1:nlevq) ! Model background specific humidity
273 LOGICAL, INTENT(IN) :: GPSRO_pseudo_ops ! Option: Use pseudo-levels in vertical interpolation?
274 LOGICAL, INTENT(IN) :: GPSRO_vert_interp_ops ! Option: Use ln(p) for vertical interpolation? (rather than exner)
275 REAL(kind_real), INTENT(IN) :: GPSRO_min_temp_grad ! The minimum temperature gradient which is used
276 INTEGER, INTENT(IN) :: nobs ! Number of observations in the profile
277 REAL(kind_real), INTENT(IN) :: zobs(1:nobs) ! Impact parameter for the obs
278 REAL(kind_real), INTENT(IN) :: Latitude ! Latitude of this profile
279 REAL(kind_real), INTENT(INOUT) :: ycalc(1:nobs) ! Model forecast of the observations
280 LOGICAL, INTENT(OUT) :: BAErr ! Was an error encountered during the calculation?
281 REAL(kind_real), INTENT(INOUT), ALLOCATABLE :: refractivity(:) ! Model refractivity on model/pseudo levels
282 REAL(kind_real), INTENT(INOUT), ALLOCATABLE :: model_heights(:) ! Geopotential heights of the refractivity levels
283 !
284 ! Things that may need to be output, as they are used by the TL/AD calculation
285 !
286 INTEGER :: nRefLevels ! Number of levels in refractivity calculation
287 REAL(kind_real), ALLOCATABLE :: nr(:) ! Model calculation of impact parameters
288 !
289 ! Local parameters
290 !
291 integer, parameter :: max_string = 800 ! Length of strings
292 character(len=*), parameter :: myname_ = "Ops_GPSRO_ForwardModel"
293 !
294 ! Local variables
295 !
296 INTEGER :: num_pseudo ! Number of levels, including pseudo levels
297 REAL(kind_real) :: x(1:nlevp+nlevq) ! state vector
298 character(max_string) :: err_msg ! Error message to be output
299 INTEGER :: iObs ! Loop counter, observation number
300 INTEGER :: iLevel ! Number of model level just above observation
301 REAL(kind_real) :: temperature(nlevq)! Calculated profile temperature
302 REAL(kind_real) :: Pb(nlevq) ! Air pressure, interpolated to temperature-levels
303 REAL(kind_real) :: humidity_ob ! Specific humidity interpolated to observation height
304 REAL(kind_real) :: T_ob ! Temperature interpolated to observation height
305 REAL(kind_real) :: P_ob ! Pressure interpolated to observation height
306 REAL(kind_real) :: log_humid ! Interpolation coefficient for humidity
307 REAL(kind_real) :: temperature_grad ! Interpolation coefficient for temperature
308 REAL(kind_real) :: pressure_coeff ! Interpolation coefficient for pressure
309 REAL(kind_real) :: alpha_interp ! Interpolation coefficient for refractivity
310 REAL(kind_real) :: beta_interp ! Interpolation coefficient for refractivity
311 REAL(kind_real) :: model_height_diff ! Difference in height between two model levels
312 REAL(kind_real) :: obs_height_diff ! Height difference between observation and closest model level
313 
314 ! The model data must be on a staggered grid, with nlevp = nlevq+1
315 IF (nlevp /= nlevq + 1) THEN
316  write(err_msg,*) myname_ // ':' // ' Data must be on a staggered grid nlevp, nlevq = ', nlevp, nlevq
317  call fckit_log % warning(err_msg)
318  write(err_msg,*) myname_ // ':' // ' error: number of levels inconsistent!'
319  call abor1_ftn(err_msg)
320 END IF
321 
322 baerr = .false.
323 ycalc(:) = missing_value(ycalc(1))
324 
325 ! Calculate the refractivity on model or pseudo levels
326 CALL ufo_calculate_refractivity (nlevp, &
327  nlevq, &
328  za, &
329  zb, &
330  pressure, &
331  humidity, &
332  gpsro_pseudo_ops, &
333  gpsro_vert_interp_ops, &
334  gpsro_min_temp_grad, &
335  baerr, &
336  nreflevels, &
337  refractivity, &
338  model_heights, &
339  temperature, &
340  pb)
341 
342 ! Vertically interpolate the model refractivity to the observation locations
343 DO iobs = 1, nobs
344 
345  ! Use separate interpolation of input quantities in refractivity calculation.
346  ! This does not use the refractivity values calculated above, but uses the
347  ! calculated temperature, and pressure on temperature-levels.
348  IF (gpsro_pseudo_ops) THEN
349  IF (zb(nlevq) > zobs(iobs) .AND. zb(1) < zobs(iobs)) THEN
350  ! Find the model level immediately above the observation
351  ilevel = 1
352  DO
353  IF (zobs(iobs) < zb(ilevel + 1)) EXIT
354  ilevel = ilevel + 1
355  END DO
356 
357  model_height_diff = zb(ilevel + 1) - zb(ilevel)
358  obs_height_diff = zobs(iobs) - zb(ilevel)
359 
360  ! Interpolate P,T,q separately
361  IF (min(humidity(ilevel - 1), humidity(ilevel)) > 0.0) THEN
362  ! humidity varies exponentially with height
363  log_humid = log(humidity(ilevel) / humidity(ilevel + 1)) / model_height_diff
364  humidity_ob = humidity(ilevel) * exp(-log_humid * obs_height_diff)
365  ! Assume linear variation if humidities are -ve
366  ELSE
367  humidity_ob = humidity(ilevel) + (humidity(ilevel + 1) - humidity(ilevel)) / &
368  model_height_diff * obs_height_diff
369  END IF
370 
371  ! T varies linearly with height
372  temperature_grad = (temperature(ilevel + 1) - temperature(ilevel)) / &
373  model_height_diff
374  t_ob = temperature(ilevel) + temperature_grad * obs_height_diff
375 
376  ! P varies to maintain hydrostatic balance
377  IF (abs(temperature(ilevel + 1) - temperature(ilevel)) > gpsro_min_temp_grad) THEN
378  pressure_coeff = ((pb(ilevel + 1) / pb(ilevel)) * (temperature(ilevel + 1) / temperature(ilevel)) ** &
379  (grav / (rd * temperature_grad)) - 1.0) / model_height_diff
380  p_ob = (pb(ilevel) * (t_ob / temperature(ilevel)) ** (-grav / (rd * temperature_grad))) * &
381  (1.0 + pressure_coeff * obs_height_diff)
382  ELSE
383  ! If layer is isothermal, assume exponential variation to
384  ! avoid singularity
385  p_ob = pb(ilevel) * exp(log(pb(ilevel + 1) / pb(ilevel)) * &
386  obs_height_diff / model_height_diff)
387  END IF
388 
389 
390  ! Calculate refractivity
391 
392  ycalc(iobs) = n_alpha * p_ob / t_ob + n_beta * p_ob * humidity_ob / (t_ob ** 2 * &
393  (mw_ratio + (1.0 - mw_ratio) * humidity_ob))
394  END IF
395 
396  ELSE
397 
398  IF (model_heights(nreflevels) > zobs(iobs) .AND. model_heights(1) < zobs(iobs)) THEN
399  ! Find the model level immediately above the observation
400  ilevel = 1
401  DO
402  IF (zobs(iobs) < model_heights(ilevel + 1)) EXIT
403  ilevel = ilevel + 1
404  END DO
405 
406  ! Use simple assumption of exponentially varying refractivity
407  alpha_interp = (model_heights(ilevel + 1) - zobs(iobs)) / &
408  (model_heights(ilevel + 1) - model_heights(ilevel))
409  beta_interp = 1.0 - alpha_interp
410  ycalc(iobs) = exp(alpha_interp * log(refractivity(ilevel)) + &
411  beta_interp * log(refractivity(ilevel + 1)))
412  ELSE IF (zobs(iobs) /= missing_value(zobs(iobs))) THEN
413  print*, 'Height out of range', iobs, nreflevels, model_heights(nreflevels), zobs(iobs)
414  END IF
415  END IF
416 
417 END DO
418 
419 END SUBROUTINE refmetoffice_forwardmodel
420 
Fortran module to prepare for Lagrange polynomial interpolation. based on GSI: lagmod....
Definition: lag_interp.F90:4
subroutine, public lag_interp_const(q, x, n)
Definition: lag_interp.F90:24
subroutine, public lag_interp_smthweights(x, xt, aq, bq, w, dw, n)
Definition: lag_interp.F90:174
real(kind_real), parameter, public rd
type(registry_t), public ufo_geovals_registry
Linked list interface - defines registry_t type.
subroutine, public ufo_geovals_reorderzdir(self, varname, zdir)
subroutine, public ufo_geovals_copy(self, other)
Copy one GeoVaLs object into another.
subroutine, public ufo_geovals_delete(self)
subroutine, public ufo_geovals_get_var(self, varname, geoval)
Fortran module for gnssro refractivity Met Office forward operator.
subroutine ufo_gnssro_refmetoffice_setup(self, vert_interp_ops, pseudo_ops, min_temp_grad)
Set up the Met Office GNSS-RO refractivity operator.
subroutine ufo_gnssro_refmetoffice_simobs(self, geovals_in, obss, hofx, obs_diags)
Calculate the model forecast of the observations.
subroutine refmetoffice_forwardmodel(nlevp, nlevq, za, zb, pressure, humidity, GPSRO_pseudo_ops, GPSRO_vert_interp_ops, GPSRO_min_temp_grad, nobs, zobs, Latitude, ycalc, BAErr, refractivity, model_heights)
Interface routine for the GNSS-RO refractivity forward operator.
Module for containing a general refractivity forward operator and its K-matrix.
subroutine, public ufo_calculate_refractivity(nlevP, nlevq, za, zb, P, q, vert_interp_ops, pseudo_ops, min_temp_grad, refracerr, nRefLevels, refractivity, model_heights, temperature, interp_pressure)
Calculation of the refractivity from the pressure and specific humidity.
character(len=maxvarlen), parameter, public var_prsi
character(len=maxvarlen), parameter, public var_q
character(len=maxvarlen), parameter, public var_zi
character(len=maxvarlen), parameter, public var_z
Fortran module to perform linear interpolation.
Definition: vert_interp.F90:8
type to hold interpolated field for one variable, one observation
type to hold interpolated fields required by the obs operators