UFO
ufo_gnssro_refmetoffice_tlad_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 !> Fortran module for gnssro refractivity Met Office's tangent linear and adjoint
8 
10 
11 use iso_c_binding
12 use kinds
13 use ufo_vars_mod
18 use obsspace_mod
20 use missing_values_mod
21 use fckit_log_module, only : fckit_log
24 use ufo_constants_mod, only: &
25  rd, & ! Gas constant for dry air
26  cp, & ! Heat capacity at constant pressure for air
27  rd_over_cp, & ! Ratio of gas constant to heat capacity
28  grav, & ! Gravitational field strength
29  pref, & ! Reference pressure for calculating exner
30  mw_ratio, & ! Ratio of molecular weights of water and dry air
31  c_virtual, & ! Related to mw_ratio
32  n_alpha, & ! Refractivity constant a
33  n_beta ! Refractivity constant b
34 
35 
36 integer, parameter :: max_string=800
37 
38 !> Fortran derived type for gnssro trajectory
40  private
41  logical :: vert_interp_ops !< Do vertical interpolation using ln(p) or exner?
42  logical :: pseudo_ops !< Use pseudo-levels in the refractivity calculation
43  logical :: flip_it !< Do the outputs need to be flipped in order
44  real(kind_real) :: min_temp_grad !< The minimum temperature gradient before a profile is considered isothermal
45  ! Used in pseudo-levels calculation
46  integer :: nlevp !< The number of pressure levels
47  integer :: nlevq !< The number of specific humidity (or temperature) levels
48  integer :: nlocs !< The number of observations
49  real(kind_real), allocatable :: k(:,:) !< The K-matrix (Jacobian)
50  contains
51  procedure :: setup => ufo_gnssro_refmetoffice_tlad_setup
52  procedure :: delete => ufo_gnssro_refmetoffice_tlad_delete
53  procedure :: settraj => ufo_gnssro_refmetoffice_tlad_settraj
54  procedure :: simobs_tl => ufo_gnssro_refmetoffice_simobs_tl
55  procedure :: simobs_ad => ufo_gnssro_refmetoffice_simobs_ad
57 
58 contains
59 
60 !-------------------------------------------------------------------------------
61 !> \brief Set up the Met Office GNSS-RO refractivity TL/AD
62 !!
63 !! \details **ufo_gnssro_refmetoffice_tlad_setup**
64 !! * Get the optional settings for the forward model and its linear, and save
65 !! them in the object so that they can be used in the code.
66 !!
67 !! \author Neill Bowler (Met Office)
68 !!
69 !! \date 26 May 2021
70 !!
71 !-------------------------------------------------------------------------------
72 subroutine ufo_gnssro_refmetoffice_tlad_setup(self, vert_interp_ops, pseudo_ops, min_temp_grad)
73 
74 implicit none
75 
76 class(ufo_gnssro_refmetoffice_tlad), intent(inout) :: self
77 logical(c_bool), intent(in) :: vert_interp_ops
78 logical(c_bool), intent(in) :: pseudo_ops
79 real(c_float), intent(in) :: min_temp_grad
80 
81 self % vert_interp_ops = vert_interp_ops
82 self % pseudo_ops = pseudo_ops
83 self % min_temp_grad = min_temp_grad
84 
86 
87 !-------------------------------------------------------------------------------
88 !> \brief Set up the K-matrix for (Jacobian) for the Met Office's GNSS-RO
89 !! refractivity operator
90 !!
91 !! \details **ufo_gnssro_refmetoffice_tlad_settraj**
92 !! * It is necessary to run this routine before calling the TL or AD routines.
93 !! * Get the geovals specifying the state around which to linearise, flipping
94 !! the vertical order if required.
95 !! * Call the helper function to calculate the K-matrix for each observation.
96 !!
97 !! \author Neill Bowler (Met Office)
98 !!
99 !! \date 26 May 2021
100 !!
101 !-------------------------------------------------------------------------------
102 subroutine ufo_gnssro_refmetoffice_tlad_settraj(self, geovals, obss)
103 
104  implicit none
105 ! Subroutine arguments
106  class(ufo_gnssro_refmetoffice_tlad), intent(inout) :: self !< The object that we use to save data in
107  type(ufo_geovals), intent(in) :: geovals !< The input geovals
108  type(c_ptr), value, intent(in) :: obss !< The input observations
109 
110 ! Local parameters
111  character(len=*), parameter :: myname_="ufo_gnssro_refmetoffice_tlad_settraj"
112 
113 ! Local variables
114  character(max_string) :: err_msg ! Messages to be output to the user
115  type(ufo_geoval), pointer :: q ! The model geovals - specific humidity
116  type(ufo_geoval), pointer :: prs ! The model geovals - atmospheric pressure
117  type(ufo_geoval), pointer :: rho_heights ! The model geovals - heights of the pressure-levels
118  type(ufo_geoval), pointer :: theta_heights ! The model geovals - heights of the theta-levels (stores q)
119  type(ufo_geoval), pointer :: p_temp ! The model geovals - atmospheric pressure before flipping
120  integer :: iobs ! Loop variable, observation number
121 
122  real(kind_real), allocatable :: obs_height(:) ! Geopotential height of the observation
123  type(ufo_geovals) :: geovals_local ! The model values, interpolated to the observation locations
124  ! and flipped in the vertical (if required)
125 
126  write(err_msg,*) "TRACE: ufo_gnssro_refmetoffice_tlad_settraj: begin"
127  call fckit_log%info(err_msg)
128 
129 ! Make sure that any previous values of geovals don't get carried over
130  call self%delete()
131 
132 ! make sure that the geovals are in the correct vertical order (surface first)
133  call ufo_geovals_copy(geovals, geovals_local) ! dont want to change geovals input
134  call ufo_geovals_get_var(geovals_local, var_prsi, p_temp)
135  if( p_temp%vals(1,1) < p_temp%vals(p_temp%nval,1) ) then
136  self%flip_it = .true.
137  else
138  self%flip_it = .false.
139  endif
140  call ufo_geovals_reorderzdir(geovals_local, var_prsi, "bottom2top")
141 
142 ! get model state variables from geovals
143  call ufo_geovals_get_var(geovals_local, var_q, q) ! specific humidity
144  call ufo_geovals_get_var(geovals_local, var_prsi, prs) ! pressure
145  call ufo_geovals_get_var(geovals_local, var_z, theta_heights) ! Geopotential height of the normal model levels
146  call ufo_geovals_get_var(geovals_local, var_zi, rho_heights) ! Geopotential height of the pressure levels
147 
148 ! Keep copy of dimensions
149  self % nlevp = prs % nval
150  self % nlevq = q % nval
151  self % nlocs = obsspace_get_nlocs(obss)
152 
153 ! Get the meta-data from the observations
154  allocate(obs_height(self%nlocs))
155  call obsspace_get_db(obss, "MetaData", "obs_height", obs_height)
156  allocate(self % K(1:self%nlocs, 1:prs%nval + q%nval))
157 
158 ! For each observation, calculate the K-matrix
159  obs_loop: do iobs = 1, self % nlocs
160  CALL jacobian_interface(prs % nval, & ! Number of pressure levels
161  q % nval, & ! Number of specific humidity levels
162  rho_heights % vals(:,iobs), & ! Heights of the pressure levels
163  theta_heights % vals(:,iobs), & ! Heights of the specific humidity levels
164  prs % vals(:,iobs), & ! Values of the pressure
165  q % vals(:,iobs), & ! Values of the specific humidity
166  self % pseudo_ops, & ! Whether to use pseudo-levels in the calculation
167  self % vert_interp_ops, & ! Whether to interpolate using log(pressure)
168  self % min_temp_grad, & ! Minimum allowed vertical temperature gradient
169  1, & ! Number of observations in the profile
170  obs_height(iobs:iobs), & ! Impact parameter for this observation
171  self % K(iobs:iobs,1:prs%nval+q%nval)) ! K-matrix (Jacobian of the observation with respect to the inputs)
172  end do obs_loop
173 
174 ! Note that this routine has been run.
175  self%ltraj = .true.
176 
177  deallocate(obs_height)
178 
180 
181 !-------------------------------------------------------------------------------
182 !> \brief Given an increment to the model state, calculate an increment to the
183 !! observation
184 !!
185 !! \details **ufo_gnssro_refmetoffice_simobs_tl**
186 !! * Check that set trajectory has been previously called.
187 !! * Get the geovals for the increment.
188 !! * For each observation apply the K-matrix to calculate the increment to the
189 !! observation.
190 !!
191 !! \author Neill Bowler (Met Office)
192 !!
193 !! \date 26 May 2021
194 !!
195 !-------------------------------------------------------------------------------
196 
197 subroutine ufo_gnssro_refmetoffice_simobs_tl(self, geovals, hofx, obss)
198 
199  implicit none
200 
201 ! Subroutine arguments
202  class(ufo_gnssro_refmetoffice_tlad), intent(in) :: self !< Object which is being used to transfer information
203  type(ufo_geovals), intent(in) :: geovals !< Model perturbations
204  real(kind_real), intent(inout) :: hofx(:) !< Increment to the observations
205  type(c_ptr), value, intent(in) :: obss !< Input - the observations
206 
207 ! Local parameters
208  character(len=*), parameter :: myname_="ufo_gnssro_refmetoffice_simobs_tl"
209 
210 ! Local variables
211  integer :: iobs ! Loop variable, observation number
212  integer :: nlocs ! Number of observations
213  character(max_string) :: err_msg ! Message to be output
214  type(ufo_geoval), pointer :: q_d ! Increment to the specific humidity
215  type(ufo_geoval), pointer :: prs_d ! Increment to the air pressure
216  real(kind_real), allocatable :: x_d(:) ! Increment to the complete state
217 
218  write(err_msg,*) "TRACE: ufo_gnssro_refmetoffice_simobs_tl: begin"
219  call fckit_log%info(err_msg)
220 
221 ! Check if trajectory was set
222  if (.not. self%ltraj) then
223  write(err_msg,*) myname_, ' trajectory wasnt set!'
224  call abor1_ftn(err_msg)
225  endif
226 
227 ! Check if nlocs is consistent in geovals & hofx
228  if (geovals%nlocs /= size(hofx)) then
229  write(err_msg,*) myname_, ' error: nlocs inconsistent!'
230  call abor1_ftn(err_msg)
231  endif
232 
233 ! Get variables from geovals
234  call ufo_geovals_get_var(geovals, var_q, q_d) ! specific humidity
235  call ufo_geovals_get_var(geovals, var_prsi, prs_d) ! pressure on rho levels
236 
237  nlocs = self % nlocs ! number of observations
238 
239  allocate(x_d(1:prs_d%nval+q_d%nval))
240 ! Loop through the obs, calculating the increment to the observation
241  obs_loop: do iobs = 1, nlocs ! order of loop doesn't matter
242 
243  x_d(1:prs_d%nval) = prs_d % vals(:,iobs)
244  x_d(prs_d%nval+1:prs_d%nval+q_d%nval) = q_d % vals(:,iobs)
245  hofx(iobs) = sum(self % K(iobs,:) * x_d)
246 
247  end do obs_loop
248 
249  deallocate(x_d)
250 
251  write(err_msg,*) "TRACE: ufo_gnssro_refmetoffice_simobs_tl: complete"
252  call fckit_log%info(err_msg)
253 
254  return
255 
257 
258 !-------------------------------------------------------------------------------
259 !> \brief Given an increment to the observation, find the equivalent increment
260 !! to the model state
261 !!
262 !! \details **ufo_gnssro_refmetoffice_simobs_ad**
263 !! * Check that set trajectory has previously been called.
264 !! * Get the geovals for the model increment, and allocate these if they have
265 !! not already been allocated.
266 !! * For each observation calculate the observation increment using the K-matrix
267 !!
268 !! \author Neill Bowler (Met Office)
269 !!
270 !! \date 26 May 2021
271 !!
272 !-------------------------------------------------------------------------------
273 
274 subroutine ufo_gnssro_refmetoffice_simobs_ad(self, geovals, hofx, obss)
275 
276  use typesizes, only: wp => eightbytereal
277 
278  implicit none
279 
280 ! Subroutine arguments
281  class(ufo_gnssro_refmetoffice_tlad), intent(in) :: self !< Object which is being used to transfer information
282  type(ufo_geovals), intent(inout) :: geovals !< Calculated perturbations to model state
283  real(kind_real), intent(in) :: hofx(:) !< Increment to the observations
284  type(c_ptr), value, intent(in) :: obss !< Input - the observations
285 
286 ! Local parameters
287  character(len=*), parameter :: myname_="ufo_gnssro_refmetoffice_simobs_ad"
288 
289 ! Local variables
290  real(c_double) :: missing ! Missing data values
291  type(ufo_geoval), pointer :: q_d ! Pointer to the specific humidity perturbations
292  type(ufo_geoval), pointer :: prs_d ! Pointer to the pressure perturbations
293  integer :: iobs ! Loop variable, observation number
294  real(kind_real), allocatable :: x_d(:) ! Perturbation to the full model state
295  character(max_string) :: err_msg ! Message to be output
296 
297  write(err_msg,*) "TRACE: ufo_gnssro_refmetoffice_simobs_ad: begin"
298  call fckit_log%info(err_msg)
299 
300 ! Check if trajectory was set
301  if (.not. self%ltraj) then
302  write(err_msg,*) myname_, ' trajectory wasnt set!'
303  call abor1_ftn(err_msg)
304  endif
305 
306 ! Check if nlocs is consistent in geovals & hofx
307  if (geovals%nlocs /= size(hofx)) then
308  write(err_msg,*) myname_, ' error: nlocs inconsistent!'
309  call abor1_ftn(err_msg)
310  endif
311 
312 ! Get variables from geovals
313  call ufo_geovals_get_var(geovals, var_q, q_d) ! specific humidity
314  call ufo_geovals_get_var(geovals, var_prsi, prs_d) ! pressure
315 
316  missing = missing_value(missing)
317  allocate(x_d(1:prs_d%nval + q_d%nval))
318 
319 ! Loop through the obs, calculating the increment to the model state
320  obs_loop: do iobs = 1, self % nlocs
321 
322  if (hofx(iobs) /= missing) then
323  x_d = self % K(iobs,:) * hofx(iobs)
324  if (self%flip_it) then
325  prs_d % vals(:,iobs) = prs_d % vals(:,iobs) + x_d(prs_d%nval:1:-1)
326  q_d % vals(:,iobs) = q_d % vals(:,iobs) + x_d(prs_d%nval+q_d%nval:prs_d%nval+1:-1)
327  else
328  prs_d % vals(:,iobs) = prs_d % vals(:,iobs) + x_d(1:prs_d%nval)
329  q_d % vals(:,iobs) = q_d % vals(:,iobs) + x_d(prs_d%nval+1:prs_d%nval+q_d%nval)
330  end if
331  end if
332 
333  end do obs_loop
334 
335  deallocate(x_d)
336 
337  write(err_msg,*) "TRACE: ufo_gnssro_refmetoffice_simobs_ad: complete"
338  call fckit_log%info(err_msg)
339 
340  return
341 
343 
344 !-------------------------------------------------------------------------------
345 !> \brief Tidy up the variables that are used for passing information
346 !!
347 !! \details **ufo_gnssro_refmetoffice_tlad_delete**
348 !! * Set lengths to zero, and deallocate K-matrix
349 !!
350 !! \author Neill Bowler (Met Office)
351 !!
352 !! \date 26 May 2021
353 !!
354 !-------------------------------------------------------------------------------
355 
357 
358  implicit none
359  class(ufo_gnssro_refmetoffice_tlad), intent(inout) :: self
360  character(len=*), parameter :: myname_="ufo_gnssro_refmetoffice_tlad_delete"
361 
362  self%nlocs = 0
363  self%nlevp = 0
364  self%nlevq = 0
365  if (allocated(self%K)) deallocate(self%K)
366  self%ltraj = .false.
367 
369 
370 !-------------------------------------------------------------------------------
371 !> \brief Calculate the K-matrix used in the TL/AD
372 !!
373 !! \details **jacobian_interface**
374 !! * Allocate temporary arrays
375 !! * Call partial-derivatives code which calculates various quantities on model
376 !! levels
377 !! * Loop over each observation, doing the following
378 !! * If using pseudo-levels, calculate specific humidity, pressure and
379 !! temperature on intermediate levels, and interpolate these appropriately.
380 !! Then calculate the refractivity gradients for each observation,
381 !! interpolated from the pseudo-levels.
382 !! * If not using pseudo-levels, assume that refractivity varies exponentially
383 !! with height and calculate gradients.
384 !! * Calculate K-matrix from the component gradients.
385 !!
386 !! \author Neill Bowler (Met Office)
387 !!
388 !! \date 26 May 2021
389 !!
390 !-------------------------------------------------------------------------------
391 
392 SUBROUTINE jacobian_interface(nlevp, &
393  nlevq, &
394  za, &
395  zb, &
396  p, &
397  q, &
398  pseudo_ops, &
399  vert_interp_ops, &
400  min_temp_grad, &
401  nobs, &
402  zobs, &
403  Kmat)
404 
405 IMPLICIT NONE
406 
407 ! Subroutine arguments:
408 integer, intent(in) :: nlevp !< Number of pressure levels
409 integer, intent(in) :: nlevq !< Number of specific humidity levels
410 real(kind_real), intent(in) :: za(:) !< Height of the pressure levels
411 real(kind_real), intent(in) :: zb(:) !< Height of the specific humidity levels
412 real(kind_real), intent(in) :: p(:) !< Input pressure
413 real(kind_real), intent(in) :: q(:) !< Input specific humidity
414 logical, intent(in) :: vert_interp_ops !< Use log(p) for vertical interpolation?
415 logical, intent(in) :: pseudo_ops !< Use pseudo-levels to reduce errors?
416 real(kind_real), intent(in) :: min_temp_grad !< Minimum value for the vertical temperature gradient
417 integer, intent(in) :: nobs !< Number of observations
418 real(kind_real), intent(in) :: zobs(:) !< Height of the observations
419 real(kind_real), intent(out) :: Kmat(:,:) !< K-matrix (Jacobian)
420 
421 ! Local declarations:
422 integer :: n ! Loop variable, observation number
423 integer :: i ! Loop variable
424 real(kind_real) :: Exner(nlevp) ! Exner pressure, calculated on model pressure levels
425 real(kind_real) :: Pb(nlevq) ! Pressure on model theta levels
426 real(kind_real) :: Tv(nlevq) ! Virtual temperature on model theta levels
427 real(kind_real) :: T(nlevq) ! Temperature on model theta levels
428 real(kind_real) :: dExtheta_dPb(nlevq,nlevq) ! Partial derivative of theta wrt pressure (on model theta levels)
429 real(kind_real), ALLOCATABLE :: drefob_dPob(:,:) ! Partial derivative of refractivity wrt pressure (at ob location)
430 real(kind_real), ALLOCATABLE :: drefob_dTob(:,:) ! Partial derivative of refractivity wrt temperature (at ob location)
431 real(kind_real), ALLOCATABLE :: drefob_dqob(:,:) ! Partial derivative of refractivity wrt specific humidity (at ob location)
432 real(kind_real), ALLOCATABLE :: dqob_dqb(:,:) ! Partial derivative of specific humidity at ob location wrt specific humidity on model theta levels
433 real(kind_real), ALLOCATABLE :: dTob_dTb(:,:) ! Partial derivative of temperature at ob location wrt temperature on model theta levels
434 real(kind_real), ALLOCATABLE :: dPob_dT(:,:) ! Partial derivative of pressure at ob location wrt temperature on model theta levels
435 real(kind_real), ALLOCATABLE :: dPob_dPb(:,:) ! Partial derivative of pressure at ob location wrt pressure on model theta levels
436 real(kind_real), ALLOCATABLE :: dPob_dP(:,:) ! Partial derivative of pressure at ob location wrt pressure on model pressure levels
437 real(kind_real), ALLOCATABLE :: dTob_dP(:,:) ! Partial derivative of temperature at ob location wrt pressure on model pressure levels
438 real(kind_real), ALLOCATABLE :: dTb_dp(:,:) ! Partial derivative of temperature on model theta levels wrt pressure on model pressure levels
439 real(kind_real) :: c ! Continuity constant for hydrostatic pressure
440 real(kind_real) :: P_ob ! Model pressure at the ob location
441 real(kind_real) :: q_ob ! Model specific humidity at the ob location
442 real(kind_real) :: T_ob ! Model temperature at the ob location
443 real(kind_real) :: gamma ! Vertical gradient of log(q)
444 real(kind_real) :: beta ! Vertical temperature gradient
445 real(kind_real) :: Ndry ! Component of refractivity due to dry terms
446 real(kind_real) :: Nwet ! Component of refractivity due to wet terms
447 real(kind_real) :: refrac(nlevq) ! Refractivity on model theta levels
448 real(kind_real) :: dEx_dP(nlevp,nlevp) ! Partial derivative of exner wrt pressure
449 real(kind_real) :: dPb_dp(nlevq,nlevp) ! Partial derivative of pressure on theta levels wrt pressure on pressure levels
450 real(kind_real) :: dTv_dExtheta(nlevq,nlevq) ! Virtual temperature divided by exner on theta levels
451 real(kind_real) :: dTv_dEx(nlevq,nlevp) ! Partial derivative of virtual temperature wrt exner
452 real(kind_real) :: dT_dTv(nlevq,nlevq) ! Partial derivative of temperature wrt virtual temperature
453 real(kind_real) :: dT_dq(nlevq,nlevq) ! Partial derivative of temperature wrt specific humidity
454 real(kind_real) :: dref_dPb(nlevq,nlevq) ! Partial derivative of refractivity wrt pressure on theta levels
455 real(kind_real) :: dref_dT(nlevq,nlevq) ! Partial derivative of refractivity wrt temperature
456 real(kind_real) :: dref_dq(nlevq,nlevq) ! Partial derivative of refractivity wrt specific humidity
457 real(kind_real) :: dNref_dref(nobs,nlevq) ! Partial derivative of refractivity at the ob loation wrt model refractivity
458 real(kind_real) :: kmatP(nobs,nlevp) ! K-matrix contribution for pressure terms
459 real(kind_real) :: kmatq(nobs,nlevq) ! K-matrix contribution for specific humidity terms
460 real(kind_real) :: Nref(nobs) ! Model refractivity at observation locations
461 real(kind_real) :: m1(nobs,nlevq) ! Temporary matrix product used in calculation
462 real(kind_real) :: m2(nobs,nlevp) ! Temporary matrix product used in calculation
463 real(kind_real) :: m3(nobs,nlevq) ! Temporary matrix product used in calculation
464 real(kind_real) :: m4(nobs,nlevq) ! Temporary matrix product used in calculation
465 real(kind_real) :: g_RB ! Frequently used term
466 real(kind_real) :: c_ZZ ! Frequently used term
467 real(kind_real) :: dPo_dT1 ! dP_ob / dT_below
468 real(kind_real) :: dPo_dTo ! dP_ob / dT_ob
469 real(kind_real) :: dTo_dT1 ! dT_ob / dT_below
470 real(kind_real) :: dPo_dbeta ! dP_ob / dbeta
471 real(kind_real) :: dbeta_dT1 ! dbeta / dT_below
472 real(kind_real) :: dTo_dbeta ! dT_ob / dbeta
473 real(kind_real) :: dbeta_dT2 ! dbeta / dT_above
474 real(kind_real) :: dPo_dc ! dP_ob / dc
475 real(kind_real) :: dc_dT1 ! dc / dT_below
476 real(kind_real) :: dc_dbeta ! dc / dbeta
477 real(kind_real) :: dc_dt2 ! dc / dT_above
478 real(kind_real) :: dPo_dP1 ! dP_ob / dP_below
479 real(kind_real) :: dc_dP1 ! dc / dP_below
480 real(kind_real) :: dc_dP2 ! dc / dP_above
481 real(kind_real) :: model_height_diff ! Difference in height between two model levels
482 real(kind_real) :: obs_height_diff ! Difference in height between the observation and the model level below it
483 real(kind_real) :: height_diff_ratio ! obs_height_diff / model_height_diff
484 
485 !-----------------------
486 ! 1. Allocate/initialise matrices
487 !-----------------------
488 
489 IF (pseudo_ops) THEN
490  ALLOCATE (drefob_dpob(nobs,nobs))
491  ALLOCATE (drefob_dtob(nobs,nobs))
492  ALLOCATE (drefob_dqob(nobs,nobs))
493  ALLOCATE (dqob_dqb(nobs,nlevq))
494  ALLOCATE (dtob_dtb(nobs,nlevq))
495  ALLOCATE (dpob_dt(nobs,nlevq))
496  ALLOCATE (dpob_dpb(nobs,nlevq))
497  ALLOCATE (dpob_dp(nobs,nlevp))
498  ALLOCATE (dtob_dp(nobs,nlevp))
499  ALLOCATE (dtb_dp(nlevq,nlevp))
500 
501  drefob_dpob(:,:) = 0.0
502  drefob_dtob(:,:) = 0.0
503  drefob_dqob(:,:) = 0.0
504  dqob_dqb(:,:) = 0.0
505  dtob_dtb(:,:) = 0.0
506  dpob_dt(:,:) = 0.0
507  dpob_dpb(:,:) = 0.0
508  dpob_dp(:,:) = 0.0
509  dtob_dp(:,:) = 0.0
510  dtb_dp(:,:) = 0.0
511 END IF
512 
514  nlevq, &
515  za, &
516  zb, &
517  p, &
518  q, &
519  vert_interp_ops, &
520  dt_dtv, &
521  dt_dq, &
522  dref_dpb, &
523  dref_dt, &
524  dref_dq, &
525  refrac, &
526  t, &
527  pb, &
528  dex_dp, &
529  dextheta_dpb, &
530  dtv_dextheta, &
531  dpb_dp, &
532  dtv_dex)
533 
534 
535 dnref_dref(:,:) = 0.0
536 kmatp(:,:) = 0.0
537 kmatq(:,:) = 0.0
538 kmat(:,:) = 0.0
539 
540 !-------------------------------------------------
541 ! 2. Calculate the partial derivatives at the observation locations
542 !-------------------------------------------------
543 
544 DO n = 1, nobs
545  i = 1
546 
547  if (zobs(n) >= zb(nlevq) .or. zobs(n) == missing_value(zobs(n))) cycle
548  DO
549  ! Find model layer containing ob
550  IF (zobs(n) < zb(i + 1)) EXIT
551 
552  i = i + 1
553 
554  END DO
555 
556  IF (pseudo_ops) THEN
557  ! Calculate some quantities that will be re-used
558  model_height_diff = zb(i + 1) - zb(i)
559  obs_height_diff = zobs(n) - zb(i)
560  height_diff_ratio = obs_height_diff / model_height_diff
561 
562  ! Interpolate P,T,q separately
563  IF (min(q(i), q(i + 1)) > 0.0) THEN
564  ! q varies exponentially with height
565  gamma = log(q(i) / q(i + 1)) / model_height_diff
566  q_ob = q(i) * exp(-gamma * obs_height_diff)
567 
568  dqob_dqb(n,i) = (q_ob / q(i)) * (1.0 - height_diff_ratio)
569  dqob_dqb(n,i + 1) = (q_ob / q(i + 1)) * height_diff_ratio
570 
571  ! Assume linear variation if humidities are -ve to avoid singularity
572  ELSE
573  q_ob = q(i) + (q(i + 1) - q(i)) * height_diff_ratio
574 
575  dqob_dqb(n,i) = (zb(i + 1) - zobs(n)) / model_height_diff
576  dqob_dqb(n,i + 1) = height_diff_ratio
577  END IF
578 
579  ! T varies linearly with height
580  beta = (t(i + 1) - t(i)) / model_height_diff
581  t_ob = t(i) + beta * obs_height_diff
582 
583  dtob_dtb(n,i) = (zb(i + 1) - zobs(n)) / model_height_diff
584  dtob_dtb(n,i + 1) = height_diff_ratio
585 
586  ! P varies to maintain hydrostatic balance
587  IF (abs(t(i + 1) - t(i)) > min_temp_grad) THEN
588  c = ((pb(i + 1) / pb(i)) * (t(i + 1)/t(i)) ** (grav / (rd * beta)) - 1.0) / model_height_diff
589  p_ob = (pb(i) * (t_ob / t(i)) ** &
590  (-grav / (rd * beta))) * (1.0 + c * obs_height_diff)
591  ELSE
592  ! If layer is isothermal, assume exponential variation to
593  ! avoid singularity
594  p_ob = pb(i) * exp(log(pb(i + 1) / pb(i)) * height_diff_ratio)
595  END IF
596 
597  ! Temporary variables to keep equations neater
598  g_rb = grav / (rd * beta)
599  c_zz = c + 1.0 / model_height_diff
600 
601  dpo_dt1 = (g_rb / t(i)) * p_ob
602  dpo_dto = -(g_rb / t_ob) * p_ob
603  dto_dt1 = dtob_dtb(n,i)
604  dpo_dbeta = (g_rb / beta) * log(t_ob / t(i)) * p_ob
605  dbeta_dt1 = -1.0 / model_height_diff
606  dto_dbeta = obs_height_diff
607  dbeta_dt2 = 1.0 / model_height_diff
608 
609  ! Pressure Jacobians for hydrostatic/exponential cases
610  IF (abs(t(i + 1) - t(i)) > min_temp_grad) THEN
611  dpo_dc = obs_height_diff * pb(i) * (t_ob / t(i)) ** (-g_rb)
612  dc_dt1 = -(g_rb / (t(i))) * (c_zz)
613  dc_dbeta = -(g_rb / beta) * log(t(i + 1) / t(i)) * c_zz
614  dc_dt2 = (g_rb / t(i + 1)) * c_zz
615  dpo_dp1 = p_ob / pb(i)
616  dpob_dt(n,i) = dpo_dt1 + dpo_dto * dto_dt1 + dpo_dbeta * dbeta_dt1 + dpo_dc * &
617  (dc_dt1 + dc_dbeta * dbeta_dt1)
618  dpob_dt(n,i+1) = (dpo_dbeta + dpo_dto * dto_dbeta + dpo_dc * dc_dbeta) * &
619  dbeta_dt2 + dpo_dc * dc_dt2
620 
621  dc_dp1 = -(1.0 / pb(i)) * c_zz
622  dc_dp2 = (1.0 / pb(i + 1)) * c_zz
623 
624  dpob_dpb(n,i) = dpo_dp1 + dpo_dc * dc_dp1
625  dpob_dpb(n,i + 1) = dpo_dc * dc_dp2
626  ELSE
627  dpob_dpb(n,i) = exp(log(pb(i + 1) / pb(i)) * height_diff_ratio) * &
628  (1.0 - height_diff_ratio)
629  dpob_dpb(n,i + 1) = (pb(i) / pb(i + 1)) * height_diff_ratio * &
630  exp(log(pb(i + 1) / pb(i)) * height_diff_ratio)
631  END IF
632 
633  ! Calculate refractivity
634  ndry = n_alpha * p_ob / t_ob
635  nwet = n_beta * p_ob * q_ob / (t_ob ** 2 * (mw_ratio + (1.0 - mw_ratio) * q_ob))
636  nref(n) = ndry + nwet
637 
638  drefob_dpob(n,n) = nref(n) / p_ob
639  drefob_dtob(n,n) = -(ndry + 2.0 * nwet) / t_ob
640  drefob_dqob(n,n) = n_beta * p_ob * mw_ratio / (t_ob * (mw_ratio + (1.0 - mw_ratio) * q_ob)) ** 2
641 
642  ELSE
643 
644  ! Use simple assumption of exponentially varying refractivity
645 
646  gamma = (zb(i + 1) - zobs(n)) / (zb(i + 1) - zb(i))
647 
648  beta = 1.0 - gamma
649 
650  nref(n) = exp(gamma * log(refrac(i)) + beta * log(refrac(i + 1)))
651 
652  dnref_dref(n,i) = nref(n) * gamma / refrac(i)
653 
654  dnref_dref(n,i + 1) = nref(n) * beta / refrac(i + 1)
655  END IF
656 
657 END DO
658 
659 !-------------------------------------------------
660 ! 3. Evaluate the Kmatrix by matrix multiplication
661 !-------------------------------------------------
662 
663 IF (pseudo_ops) THEN
664 
665  ! Derivatives:
666  ! dPob/dP = (dPob/dPb * dPb/dP) + (dPob/dT * dT/dTv * dTv/dEx * dEx/dP)
667  dpob_dp = matmul(dpob_dpb, dpb_dp) + matmul(dpob_dt, matmul(dt_dtv, matmul(dtv_dex, dex_dp)))
668  ! dTob/dP = (dTob/dT * dT/dTv * dTv/dEx * dEx/dP)
669  dtob_dp = matmul(dtob_dtb, matmul(dt_dtv, matmul(dtv_dex, dex_dp)))
670 
671  ! calc K matrix for p on rho levels
672  ! dNob/dP = (dNob/dPob * dPob/dP) + (dNob/dTob *dTob/dP)
673  kmatp(:,:) = matmul(drefob_dpob, dpob_dp) + matmul(drefob_dtob, dtob_dp)
674 
675  ! calc Kmatrix for q on theta levels
676  ! dNob/dq = (dNob/dqob * dqob/dq) + (dNob/dTob * dTob/dT * dT/dq) + (dNob/dPob * dPob/dT * dT/dq)
677  kmatq(:,:) = matmul(drefob_dqob, dqob_dqb) + matmul(matmul(drefob_dtob, dtob_dtb), dt_dq) + &
678  matmul(matmul(drefob_dpob, dpob_dt), dt_dq)
679 
680 ELSE
681 
682  ! calc K matrix for p on rho levels
683  ! dNob/dP = (dNob/dN * dN/dPb * dPb/dP) + ....
684  kmatp(:,:) = matmul(dnref_dref, matmul(dref_dpb, dpb_dp))
685 
686  ! .... (dNob/dN * dN/dT * dT/dTv * dTv/dEx * dEx/dP) + .....
687  m1(:,:) = matmul(dnref_dref, matmul(dref_dt, dt_dtv))
688  m2(:,:) = matmul(m1, dtv_dex)
689  kmatp(:,:) = kmatp(:,:) + matmul(m2, dex_dp)
690 
691  ! .... (dNob/dN * dN/dT * dT/dTv * dTv/dExtheta * dExtheta/dPb * dPb/dP)
692  m3(:,:) = matmul(m1, dtv_dextheta)
693  m4(:,:) = matmul(m3, dextheta_dpb)
694  kmatp(:,:) = kmatp(:,:) + matmul(m4,dpb_dp)
695 
696  ! calc Kmatrix for q on theta levels
697  ! dNob/dq = (dNob/dN * dN/dq) + (dNob/dN * dN/dT * dT/dq)
698  kmatq(:,:) = kmatq(:,:) + matmul(dnref_dref, matmul(dref_dt, dt_dq))
699 
700 END IF
701 
702 ! Calculate the full kmatrix in correct units
703 
704 kmat(1:nobs, 1:nlevp) = 1.0e2 * kmatp(1:nobs, 1:nlevp) ! h/Pa
705 
706 kmat(1:nobs, nlevp+1:nlevp+nlevq) = 1.0e-3 * kmatq(1:nobs, 1:nlevq) ! g/kg
707 
708 !-------------------------------------------------------------------------------
709 ! 4. Deallocate the temporary arrays
710 !-------------------------------------------------------------------------------
711 
712 IF (ALLOCATED (drefob_dpob)) DEALLOCATE (drefob_dpob)
713 IF (ALLOCATED (drefob_dtob)) DEALLOCATE (drefob_dtob)
714 IF (ALLOCATED (drefob_dqob)) DEALLOCATE (drefob_dqob)
715 IF (ALLOCATED (dqob_dqb)) DEALLOCATE (dqob_dqb)
716 IF (ALLOCATED (dtob_dtb)) DEALLOCATE (dtob_dtb)
717 IF (ALLOCATED (dpob_dt)) DEALLOCATE (dpob_dt)
718 IF (ALLOCATED (dpob_dpb)) DEALLOCATE (dpob_dpb)
719 IF (ALLOCATED (dpob_dp)) DEALLOCATE (dpob_dp)
720 IF (ALLOCATED (dtob_dp)) DEALLOCATE (dtob_dp)
721 IF (ALLOCATED (dtb_dp)) DEALLOCATE (dtb_dp)
722 
723 END SUBROUTINE jacobian_interface
724 
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_get_var(self, varname, geoval)
Fortran module for gnssro refractivity Met Office's tangent linear and adjoint.
subroutine ufo_gnssro_refmetoffice_tlad_delete(self)
Tidy up the variables that are used for passing information.
subroutine ufo_gnssro_refmetoffice_tlad_setup(self, vert_interp_ops, pseudo_ops, min_temp_grad)
Set up the Met Office GNSS-RO refractivity TL/AD.
subroutine ufo_gnssro_refmetoffice_simobs_tl(self, geovals, hofx, obss)
Given an increment to the model state, calculate an increment to the observation.
subroutine ufo_gnssro_refmetoffice_simobs_ad(self, geovals, hofx, obss)
Given an increment to the observation, find the equivalent increment to the model state.
subroutine ufo_gnssro_refmetoffice_tlad_settraj(self, geovals, obss)
Set up the K-matrix for (Jacobian) for the Met Office's GNSS-RO refractivity operator.
subroutine jacobian_interface(nlevp, nlevq, za, zb, p, q, pseudo_ops, vert_interp_ops, min_temp_grad, nobs, zobs, Kmat)
Calculate the K-matrix used in the TL/AD.
Module for containing a general refractivity forward operator and its K-matrix.
subroutine ufo_refractivity_partial_derivatives(nlevP, nlevq, za, zb, P, q, vert_interp_ops, dT_dTv, dT_dq, dref_dPb, dref_dT, dref_dq, refractivity, T, Pb, dEx_dP, dExtheta_dPb, dTv_dExtheta, dPb_dP_local, dTv_dEx)
Calculate some partial derivatives of refractivity on model levels.
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