UFO
RefractivityCalculator.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 !-------------------------------------------------------------------------------
9 !> \brief Module for containing a general refractivity forward operator and its K-matrix
10 !!
11 !! \author Neill Bowler (Met Office)
12 !!
13 !! \date 6 May 2021
14 !!
15 !-------------------------------------------------------------------------------
16 
18 
19 use fckit_log_module, only: fckit_log
20 use missing_values_mod
21 use ufo_constants_mod, only: &
22  rd, & ! Gas constant for dry air
23  cp, & ! Heat capacity at constant pressure for air
24  rd_over_cp, & ! Ratio of gas constant to heat capacity
25  pref, & ! Reference pressure for calculating exner
26  pi, & ! Something to do with circles...
27  grav, & ! Gravitational field strength
28  ecc, & ! eccentricity
29  k_somig, & ! Somigliana's constant
30  g_equat, & ! equatorial gravity (ms-2)
31  a_earth, & ! semi-major axis of earth (m)
32  flatt, & ! flattening
33  m_ratio, & ! gravity ratio
34  mw_ratio, & ! Ratio of molecular weights of water and dry air
35  c_virtual, & ! Related to mw_ratio
36  n_alpha, & ! Refractivity constant a
37  n_beta ! Refractivity constant b
38 use kinds, only: kind_real
39 
40 implicit none
41 
43 public :: ufo_refractivity_kmat
44 
45 contains
46 
47 !-------------------------------------------------------------------------------
48 !> \brief Calculation of the refractivity from the pressure and specific humidity
49 !!
50 !! \details **ufo_calculate_refractivity**
51 !! * Checks the inputs that the pressure is not missing, is monotonically
52 !! decreasing and not negative.
53 !! * Perform the calculation on the model temperature levels. It first calculates
54 !! the exner function and virtual temperature from the inputs. Then using
55 !! these calculates the wet and dry components of the refractivity.
56 !! * If pseudo-level processing is being used, then calculate the temperature,
57 !! pressure and specific humidity on the pseudo-levels by interpolation. Then
58 !! calculate the refractivity on all levels.
59 !!
60 !! \author Neill Bowler (Met Office)
61 !!
62 !! \date 6 May 2021
63 !!
64 !-------------------------------------------------------------------------------
65 
66 SUBROUTINE ufo_calculate_refractivity (nlevP, &
67  nlevq, &
68  za, &
69  zb, &
70  P, &
71  q, &
72  vert_interp_ops, &
73  pseudo_ops, &
74  min_temp_grad, &
75  refracerr, &
76  nRefLevels, &
77  refractivity, &
78  model_heights, &
79  temperature, &
80  interp_pressure)
81 
82 IMPLICIT NONE
83 
84 ! Subroutine arguments:
85 INTEGER, INTENT(IN) :: nlevp !< no. of p levels in state vec.
86 INTEGER, INTENT(IN) :: nlevq !< no. of theta levels
87 REAL(kind_real), INTENT(IN) :: za(nlevp) !< heights of rho levs
88 REAL(kind_real), INTENT(IN) :: zb(nlevq) !< heights of theta levs
89 REAL(kind_real), INTENT(IN) :: p(nlevp) !< state vector
90 REAL(kind_real), INTENT(IN) :: q(nlevq) !< state vector
91 LOGICAL, INTENT(IN) :: vert_interp_ops !< Use log(p) for vertical interpolation?
92 LOGICAL, INTENT(IN) :: pseudo_ops !< Use pseudo-levels to reduce errors?
93 REAL(kind_real), INTENT(IN) :: min_temp_grad !< Minimum value for the vertical temperature gradient
94 LOGICAL, INTENT(OUT) :: refracerr !< refractivity error
95 INTEGER, INTENT(OUT) :: nreflevels !< no. of pseudo levs
96 REAL(kind_real), ALLOCATABLE, INTENT(OUT) :: refractivity(:) !< Ref. on pseudo levs
97 REAL(kind_real), ALLOCATABLE, INTENT(OUT) :: model_heights(:) !< height of pseudo levs
98 REAL(kind_real), OPTIONAL, INTENT(OUT) :: temperature(nlevq) !< Calculated temperature on model levels
99 REAL(kind_real), OPTIONAL, INTENT(OUT) :: interp_pressure(nlevq) !< Model pressure, interpolated to temperature levels
100 
101 ! Local declarations:
102 CHARACTER(len=*), PARAMETER :: routinename = "ufo_calculate_refractivity"
103 INTEGER :: i
104 INTEGER :: counter
105 REAL(kind_real) :: exner(nlevp)
106 REAL(kind_real) :: pb(nlevq)
107 REAL(kind_real) :: t_local(nlevq) ! Temp. on theta levs
108 REAL(kind_real) :: t_virtual
109 REAL(kind_real) :: ex_theta
110 REAL(kind_real) :: pwt1
111 REAL(kind_real) :: pwt2
112 REAL(kind_real) :: ndry
113 REAL(kind_real) :: nwet
114 REAL(kind_real), ALLOCATABLE :: p_pseudo(:)
115 REAL(kind_real), ALLOCATABLE :: q_pseudo(:)
116 REAL(kind_real), ALLOCATABLE :: t_pseudo(:)
117 REAL(kind_real) :: refracmodel(1:nlevq)
118 REAL(kind_real) :: gamma
119 REAL(kind_real) :: beta
120 REAL(kind_real) :: c ! continuity constant for hydrostatic pressure
121 CHARACTER(LEN=200) :: message ! Message to be output to user
122 
123 ! Allocate arrays for pseudo-level processing and output
124 IF (pseudo_ops) THEN
125  nreflevels = 2 * nlevq - 1
126  ALLOCATE (p_pseudo(nreflevels))
127  ALLOCATE (q_pseudo(nreflevels))
128  ALLOCATE (t_pseudo(nreflevels))
129 ELSE
130  nreflevels = nlevq
131 END IF
132 ALLOCATE (model_heights(nreflevels))
133 ALLOCATE (refractivity(nreflevels))
134 
135 ! Set up the P and q vectors from x
136 
137 ! Initialise refractivity arrays to missing Data
138 refracmodel(:) = missing_value(refracmodel(1))
139 refractivity(:) = missing_value(refractivity(1))
140 t_local(:) = missing_value(t_local(1))
141 refracerr = .false.
142 
143 DO i = 1, nlevq
144  IF (p(i) == missing_value(p(i))) THEN ! pressure missing
145  refracerr = .true.
146  WRITE(message, *) routinename, " Input pressure missing", i
147  CALL fckit_log % warning(message)
148  EXIT
149  END IF
150 
151  IF (p(i) - p(i + 1) < 0.0) THEN ! or non-monotonic pressure
152  refracerr = .true.
153  WRITE(message, *) routinename, " Input pressure non-monotonic", i, p(i), p(i+1)
154  CALL fckit_log % warning(message)
155  EXIT
156  END IF
157 END DO
158 
159 IF (any(p(:) <= 0.0)) THEN ! pressure zero or negative
160  refracerr = .true.
161  WRITE(message, *) routinename, " Input pressure not physical"
162  CALL fckit_log % warning(message)
163 END IF
164 
165 ! only proceed if pressure is valid
166 IF (.NOT. refracerr) THEN
167 
168  ! Calculate exner on rho levels.
169 
170  exner(:) = (p(:) / pref) ** rd_over_cp
171 
172  ! Calculate the refractivity on the same model levels as specific humidity
173  DO i = 1, nlevq
174  ! Calc. pressure pb
175  pwt1 = (za(i + 1) - zb(i)) / (za(i + 1) - za(i))
176 
177  pwt2 = 1.0 - pwt1
178 
179  ! Calculate the pressure on the theta level.
180  IF (vert_interp_ops) THEN
181  ! Assume ln(P) linear with height
182  pb(i) = exp(pwt1 * log(p(i)) + pwt2 * log(p(i + 1)))
183  ELSE
184  ! Assume Exner varies linearly with height
185  pb(i) = pref * (pwt1 * (p(i) / pref) ** rd_over_cp + pwt2 * (p(i + 1) / pref) ** rd_over_cp) ** (1.0 / rd_over_cp)
186  END IF
187 
188  ! Calculate the Exner on the theta level.
189  ex_theta = (pb(i) / pref) ** rd_over_cp
190 
191  ! Calculate mean layer T_virtual on staggered vertical levels
192 
193  t_virtual = grav * (za(i + 1) - za(i)) * ex_theta / &
194  (cp * (exner(i) - exner(i + 1)))
195 
196  t_local(i) = t_virtual / (1.0 + c_virtual * q(i))
197 
198  ! Wet component
199  nwet = n_beta * pb(i) * q(i) / (t_local(i) ** 2 * (mw_ratio + (1.0 - mw_ratio) * q(i)))
200 
201  ndry = n_alpha * pb(i) / t_local(i)
202 
203  refracmodel(i) = ndry + nwet
204 
205  END DO
206 
207  ! Do pseudo-level processing
208  IF (pseudo_ops) THEN
209  counter = 1
210  DO i = 1, nreflevels
211 
212  ! Odd 'i' (i.e. copies of actual model level values)
213  IF (mod(i, 2) > 0) THEN
214  model_heights(i) = zb(counter)
215  p_pseudo(i) = pb(counter)
216  q_pseudo(i) = q(counter)
217  t_pseudo(i) = t_local(counter)
218  counter = counter + 1
219 
220  ! Even 'i' (i.e. intermediate pseudo-levels)
221  ELSE
222  model_heights(i) = (zb(counter - 1) + zb(counter)) / 2.0
223 
224  ! Assume exponential variation when humidities are positive
225  IF (min(q(counter - 1), q(counter)) > 0.0) THEN
226  gamma = log(q(counter - 1) / q(counter)) / (zb(counter) - zb(counter - 1))
227  q_pseudo(i) = q(counter - 1) * exp(-gamma * (model_heights(i) - model_heights(i - 1)))
228 
229  ! Assume linear variation if humidities are -ve
230  ELSE
231  q_pseudo(i) = q(counter - 1) + (q(counter) - q(counter - 1)) / (zb(counter) - &
232  zb(counter - 1)) * (model_heights(i) - zb(counter - 1))
233  END IF
234 
235  ! T varies linearly with height
236  beta = (t_local(counter) - t_local(counter - 1)) / (zb(counter) - zb(counter - 1))
237  t_pseudo(i) = t_local(counter - 1) + beta * (model_heights(i) - zb(counter - 1))
238 
239  ! Pressure varies to maintain hydrostatic balance
240  IF (abs(t_local(counter) - t_local(counter - 1)) > min_temp_grad) THEN
241  c = ((pb(counter) / pb(counter - 1)) * (t_local(counter) / t_local(counter - 1)) ** (grav / (rd * beta)) - &
242  1.0) / (zb(counter) - zb(counter - 1))
243  p_pseudo(i) = (pb(counter - 1) * (t_pseudo(i) / t_local(counter - 1)) ** &
244  (-grav / (rd * beta))) * (1.0 + c * (model_heights(i) - zb(counter - 1)))
245  ELSE
246  ! If layer is isothermal, explicitly force P to vary exponentially to avoid singularity
247  p_pseudo(i) = pb(counter - 1) * exp(log(pb(counter) / pb(counter - 1)) * &
248  ((model_heights(i) - zb(counter - 1)) / (zb(counter) - zb(counter - 1))))
249  END IF
250  END IF
251  END DO
252 
253  refractivity = n_alpha * p_pseudo / t_pseudo + n_beta * p_pseudo * q_pseudo / &
254  (t_pseudo ** 2 * (mw_ratio + (1.0 - mw_ratio) * q_pseudo))
255  ELSE
256  refractivity = refracmodel
257  model_heights = zb
258  END IF
259 
260 END IF
261 
262 IF (PRESENT(temperature)) THEN
263  temperature(:) = t_local(:)
264 END IF
265 
266 IF (PRESENT(interp_pressure)) THEN
267  interp_pressure(:) = pb(:)
268 END IF
269 
270 IF (pseudo_ops) THEN
271  IF (ALLOCATED (p_pseudo)) DEALLOCATE (p_pseudo)
272  IF (ALLOCATED (q_pseudo)) DEALLOCATE (q_pseudo)
273  IF (ALLOCATED (t_pseudo)) DEALLOCATE (t_pseudo)
274 END IF
275 
276 END SUBROUTINE ufo_calculate_refractivity
277 
278 !-------------------------------------------------------------------------------
279 !> \brief Calculate general refractivity K matrix.
280 !!
281 !! \details **ufo_refractivity_kmat**
282 !! * Allocate intermediate matrices used in calculation.
283 !! * Calculate the refractivity and basic gradients on the temperature/theta
284 !! levels.
285 !! * If using pseudo-levels, then calculate the partial matrices on these
286 !! levels, and use these to calculate the final matrices (dref_dp and dref_dq).
287 !! * If not using pseudo-levels, then calculate the final matrices.
288 !! * Deallocate the temporary matrices.
289 !!
290 !! \author Neill Bowler (Met Office)
291 !!
292 !! \date 6 May 2021
293 !!
294 !-------------------------------------------------------------------------------
295 
296 !-------------------------------------------------------------------------------
297 !> Calculate general refractivity K matrix.
298 !-------------------------------------------------------------------------------
299 
300 SUBROUTINE ufo_refractivity_kmat(nlevP, &
301  nlevq, &
302  nRefLevels, &
303  za, &
304  zb, &
305  P, &
306  q, &
307  pseudo_ops, &
308  vert_interp_ops, &
309  min_temp_grad, &
310  dref_dP, &
311  dref_dq, &
312  dPb_dP, &
313  refractivity)
314 
315 IMPLICIT NONE
316 
317 ! Subroutine arguments:
318 INTEGER, INTENT(IN) :: nlevp !< no. of pressure levels
319 INTEGER, INTENT(IN) :: nlevq !< no. of specific humidity levels
320 INTEGER, INTENT(IN) :: nreflevels !< no. of refractivity levels
321 REAL(kind_real), INTENT(IN) :: za(nlevp) !< Height of the pressure levels
322 REAL(kind_real), INTENT(IN) :: zb(nlevq) !< Height of the specific humidity levels
323 REAL(kind_real), INTENT(IN) :: p(nlevp) !< Pressure
324 REAL(kind_real), INTENT(IN) :: q(nlevq) !< Specific humidity
325 LOGICAL, INTENT(IN) :: pseudo_ops !< Whether to use pseudo-levels in calculation
326 LOGICAL, INTENT(IN) :: vert_interp_ops !< Whether to interpolate vertically using exner or ln(p)
327 REAL(kind_real), INTENT(IN) :: min_temp_grad !< Minimum value for the vertical temperature gradient
328 REAL(kind_real), ALLOCATABLE, INTENT(OUT) :: dref_dp(:,:) !< kmatrix for p
329 REAL(kind_real), ALLOCATABLE, INTENT(OUT) :: dref_dq(:,:) !< kmatrix for q
330 REAL(kind_real), OPTIONAL, INTENT(OUT) :: dpb_dp(nlevq,nlevp) !< Gradient of pressure on theta levels wrt pressure on pressure levels
331 REAL(kind_real), OPTIONAL, INTENT(OUT), ALLOCATABLE :: refractivity(:) !< Calculated refractivity
332 
333 ! Local declarations:
334 INTEGER :: i
335 INTEGER :: counter
336 REAL(kind_real) :: pb(nlevq)
337 REAL(kind_real) :: t_virtual(nlevq)
338 REAL(kind_real) :: t(nlevq)
339 REAL(kind_real) :: refrac(nlevq) ! Refractivity on model levels
340 REAL(kind_real) :: extheta
341 REAL(kind_real) :: pwt1
342 REAL(kind_real) :: pwt2
343 REAL(kind_real) :: ndry
344 REAL(kind_real) :: nwet
345 REAL(kind_real) :: dex_dp(nlevp,nlevp)
346 REAL(kind_real) :: dpb_dp_local(nlevq,nlevp)
347 REAL(kind_real) :: dextheta_dpb(nlevq,nlevq)
348 REAL(kind_real) :: dtv_dextheta(nlevq,nlevq)
349 REAL(kind_real) :: dtv_dex(nlevq,nlevp)
350 REAL(kind_real) :: dt_dtv(nlevq,nlevq)
351 REAL(kind_real) :: dt_dq(nlevq,nlevq)
352 REAL(kind_real) :: dref_dpb(nlevq,nlevq)
353 REAL(kind_real) :: dref_dt(nlevq,nlevq)
354 REAL(kind_real) :: dref_dq_model(nlevq,nlevq) ! Gradient of refractivity wrt specific humidity on model levels
355 REAL(kind_real) :: m1(nreflevels,nlevq)
356 REAL(kind_real) :: m2(nreflevels,nlevp)
357 REAL(kind_real) :: m3(nreflevels,nlevq)
358 REAL(kind_real) :: m4(nreflevels,nlevq)
359 REAL(kind_real), ALLOCATABLE :: p_pseudo(:)
360 REAL(kind_real), ALLOCATABLE :: q_pseudo(:)
361 REAL(kind_real), ALLOCATABLE :: t_pseudo(:)
362 REAL(kind_real), ALLOCATABLE :: model_heights(:)
363 REAL(kind_real) :: gamma
364 REAL(kind_real) :: beta
365 REAL(kind_real) :: c ! continuity constant for hydrostatic pressure
366 REAL(kind_real) :: g_rb ! Frequently used term
367 REAL(kind_real) :: c_zz ! Frequently used term
368 REAL(kind_real) :: dpp_dt1 ! dP_pseudo / dT_below
369 REAL(kind_real) :: dpp_dtp ! dP_pseudo / dT_pseudo
370 REAL(kind_real) :: dtp_dt1 ! dT_pseudo / dT_below
371 REAL(kind_real) :: dpp_dbeta ! dP_pseudo / dbeta
372 REAL(kind_real) :: dbeta_dt1 ! dbeta / dT_below
373 REAL(kind_real) :: dtp_dbeta ! dT_pseudo / dbeta
374 REAL(kind_real) :: dbeta_dt2 ! dbeta / dT_above
375 REAL(kind_real) :: dpp_dc ! dP_pseudo / dc
376 REAL(kind_real) :: dc_dt1 ! dc / dT_below
377 REAL(kind_real) :: dc_dbeta ! dc / dbeta
378 REAL(kind_real) :: dc_dt2 ! dc / dT_above
379 REAL(kind_real) :: dpp_dp1 ! dP_pseudo / dP_below
380 REAL(kind_real) :: dc_dp1 ! dc / dP_below
381 REAL(kind_real) :: dc_dp2 ! dc / dP_above
382 REAL(kind_real), ALLOCATABLE :: dref_dppseudo(:,:)
383 REAL(kind_real), ALLOCATABLE :: dref_dtpseudo(:,:)
384 REAL(kind_real), ALLOCATABLE :: dref_dqpseudo(:,:)
385 REAL(kind_real), ALLOCATABLE :: dppseudo_dpb(:,:)
386 REAL(kind_real), ALLOCATABLE :: dtpseudo_dtb(:,:)
387 REAL(kind_real), ALLOCATABLE :: dqpseudo_dqb(:,:)
388 REAL(kind_real), ALLOCATABLE :: dppseudo_dt(:,:)
389 REAL(kind_real), ALLOCATABLE :: dtb_dp(:,:)
390 REAL(kind_real), ALLOCATABLE :: dppseudo_dp(:,:)
391 REAL(kind_real), ALLOCATABLE :: dtpseudo_dp(:,:)
392 
393 ALLOCATE (dref_dp(nreflevels,nlevp))
394 ALLOCATE (dref_dq(nreflevels,nlevq))
395 
396 IF (pseudo_ops) THEN
397  ALLOCATE (p_pseudo(nreflevels))
398  ALLOCATE (q_pseudo(nreflevels))
399  ALLOCATE (t_pseudo(nreflevels))
400  ALLOCATE (model_heights(nreflevels))
401  IF (PRESENT(refractivity)) ALLOCATE (refractivity(nreflevels))
402 
403  ALLOCATE (dref_dppseudo(nreflevels,nreflevels))
404  ALLOCATE (dref_dtpseudo(nreflevels,nreflevels))
405  ALLOCATE (dref_dqpseudo(nreflevels,nreflevels))
406  ALLOCATE (dppseudo_dpb(nreflevels,nlevq))
407  ALLOCATE (dppseudo_dt(nreflevels,nlevq))
408  ALLOCATE (dtpseudo_dtb(nreflevels,nlevq))
409  ALLOCATE (dqpseudo_dqb(nreflevels,nlevq))
410 
411  ALLOCATE (dtb_dp(nlevq,nlevp))
412  ALLOCATE (dppseudo_dp(nreflevels,nlevp))
413  ALLOCATE (dtpseudo_dp(nreflevels,nlevp))
414 
415  dref_dppseudo(:,:) = 0.0
416  dref_dtpseudo(:,:) = 0.0
417  dref_dqpseudo(:,:) = 0.0
418  dppseudo_dpb(:,:) = 0.0
419  dppseudo_dt(:,:) = 0.0
420  dtpseudo_dtb(:,:) = 0.0
421  dqpseudo_dqb(:,:) = 0.0
422  dppseudo_dp(:,:) = 0.0
423  dtpseudo_dp(:,:) = 0.0
424 END IF
425 
426 !-----------------------
427 ! 1. Initialise matrices
428 !-----------------------
429 
430 dref_dp(:,:) = 0.0
431 
433  nlevq, &
434  za, &
435  zb, &
436  p, &
437  q, &
438  vert_interp_ops, &
439  dt_dtv, &
440  dt_dq, &
441  dref_dpb, &
442  dref_dt, &
443  dref_dq_model, &
444  refrac, &
445  t, &
446  pb, &
447  dex_dp, &
448  dextheta_dpb, &
449  dtv_dextheta, &
450  dpb_dp_local, &
451  dtv_dex)
452 
453 IF (pseudo_ops) THEN
454  !----------------------------------!
455  !- Add intermediate pseudo-levels -!
456  !----------------------------------!
457  dtb_dp = matmul(dt_dtv, matmul(matmul(dtv_dextheta, dextheta_dpb), dpb_dp_local) + matmul(dtv_dex, dex_dp))
458  counter = 1
459  DO i = 1, nreflevels
460  ! Odd 'i' (i.e. copies of actual model level values)
461  IF (mod(i, 2) > 0) THEN
462  model_heights(i) = zb(counter)
463  p_pseudo(i) = pb(counter)
464  q_pseudo(i) = q(counter)
465  t_pseudo(i) = t(counter)
466  IF (PRESENT(refractivity)) refractivity(i) = n_alpha * p_pseudo(i) / t_pseudo(i) + &
467  n_beta * p_pseudo(i) * q_pseudo(i) / &
468  (t_pseudo(i) ** 2 * (mw_ratio + (1.0 - mw_ratio) * q_pseudo(i)))
469 
470  dref_dppseudo(i,i) = dref_dpb(counter,counter)
471  dref_dtpseudo(i,i) = dref_dt(counter,counter)
472  dref_dqpseudo(i,i) = dref_dq_model(counter,counter)
473 
474  dppseudo_dpb(i,counter) = 1.0
475  dtpseudo_dtb(i,counter) = 1.0
476  dqpseudo_dqb(i,counter) = 1.0
477 
478  counter = counter + 1
479 
480  ! Even 'i' (i.e. intermediate pseudo-levels)
481  ELSE
482  model_heights(i) = (zb(counter - 1) + zb(counter)) / 2.0
483  IF (min(q(counter - 1), q(counter)) > 0.0) THEN
484  gamma = log(q(counter - 1) / q(counter)) / (zb(counter) - zb(counter - 1))
485  q_pseudo(i) = q(counter - 1) * exp(-gamma * (model_heights(i) - model_heights(i - 1)))
486  ELSE
487  q_pseudo(i) = q(counter - 1) + (q(counter) - q(counter - 1)) / &
488  (zb(counter) - zb(counter - 1)) * (model_heights(i) - zb(counter - 1))
489  END IF
490 
491  beta = (t(counter) - t(counter - 1)) / (zb(counter) - zb(counter - 1))
492  t_pseudo(i) = t(counter - 1) + beta * (model_heights(i) - zb(counter - 1))
493  IF (abs(t(counter) - t(counter - 1)) > min_temp_grad) THEN
494  c = ((pb(counter) / pb(counter - 1)) * (t(counter) / t(counter - 1)) ** (grav / (rd * beta)) - 1.0) / &
495  (zb(counter) - zb(counter - 1))
496  p_pseudo(i) = (pb(counter - 1) * (t_pseudo(i) / t(counter - 1)) ** &
497  (-grav / (rd * beta))) * (1.0 + c * (model_heights(i) - zb(counter - 1)))
498  ELSE
499  p_pseudo(i) = pb(counter - 1) * exp(log(pb(counter) / pb(counter - 1)) * &
500  ((model_heights(i) - zb(counter - 1)) / (zb(counter) - zb(counter - 1))))
501  END IF
502 
503  ndry = n_alpha * p_pseudo(i) / t_pseudo(i)
504  nwet = n_beta * p_pseudo(i) * q_pseudo(i) / (t_pseudo(i) ** 2 &
505  *(mw_ratio + (1.0 - mw_ratio) * q_pseudo(i)))
506 
507  IF (PRESENT(refractivity)) refractivity(i) = ndry + nwet
508 
509  dref_dppseudo(i,i) = (ndry + nwet) / p_pseudo(i)
510  dref_dtpseudo(i,i) = -(ndry + 2.0 * nwet) / t_pseudo(i)
511  dref_dqpseudo(i,i) = n_beta * p_pseudo(i) * mw_ratio / (t_pseudo(i) * (mw_ratio + &
512  (1.0 - mw_ratio) * q_pseudo(i))) ** 2
513 
514  ! Transform P, q and T to pseudo-levels
515  IF (min(q(counter - 1), q(counter)) > 0.0) THEN
516  dqpseudo_dqb(i,counter - 1) = (q_pseudo(i) / q(counter - 1)) * (1.0 - &
517  (model_heights(i) - zb(counter - 1)) / (zb(counter) - zb(counter - 1)))
518 
519  dqpseudo_dqb(i,counter) = (q_pseudo(i) / q(counter)) * ((model_heights(i) - zb(counter - 1)) / &
520  (zb(counter) - zb(counter - 1)))
521  ELSE
522  dqpseudo_dqb(i,counter - 1) = (zb(counter) - model_heights(i)) / (zb(counter) - zb(counter - 1))
523 
524  dqpseudo_dqb(i,counter) = (model_heights(i) - zb(counter - 1)) / (zb(counter) - zb(counter - 1))
525  END IF
526 
527  dtpseudo_dtb(i,counter - 1) = (zb(counter) - model_heights(i)) / (zb(counter) - zb(counter - 1))
528 
529  dtpseudo_dtb(i,counter) = (model_heights(i) - zb(counter - 1)) / (zb(counter) - zb(counter - 1))
530 
531  ! Items that need changing for dPpseudo/dTb:
532 
533  g_rb = grav / (rd * beta)
534  c_zz = c + 1.0 / (zb(counter) - zb(counter - 1))
535 
536  dpp_dt1 = (g_rb / t(counter - 1)) * p_pseudo(i)
537  dpp_dtp = -(g_rb / t_pseudo(i)) * p_pseudo(i)
538  dtp_dt1 = dtpseudo_dtb(i,counter - 1)
539  dpp_dbeta = (g_rb / beta) * log(t_pseudo(i) / t(counter - 1)) * p_pseudo(i)
540  dbeta_dt1 = -1.0 / (zb(counter) - zb(counter - 1))
541  dtp_dbeta = model_heights(i) - zb(counter - 1)
542  dbeta_dt2 = 1.0 / (zb(counter) - zb(counter - 1))
543 
544  IF (abs(t(counter) - t(counter - 1)) > min_temp_grad) THEN
545  ! Incomplete computation of dPpseudo_dPb. Temperature derivatives done below
546  dpp_dc = (model_heights(i) - zb(counter - 1)) * pb(counter - 1) * (t_pseudo(i) / t(counter - 1)) ** (-g_rb)
547  dc_dt1 = -(g_rb / (t(counter - 1))) * c_zz
548  dc_dbeta = -(g_rb / beta) * log(t(counter) / t(counter - 1)) * c_zz
549  dc_dt2 = (g_rb / t(counter)) * c_zz
550  dpp_dp1 = p_pseudo(i) / pb(counter - 1)
551  dppseudo_dt(i,counter - 1) = dpp_dt1 + dpp_dtp * dtp_dt1 + dpp_dbeta * dbeta_dt1 + dpp_dc * &
552  (dc_dt1 + dc_dbeta * dbeta_dt1)
553  dppseudo_dt(i,counter) = (dpp_dbeta + dpp_dtp * dtp_dbeta + dpp_dc * dc_dbeta) * &
554  dbeta_dt2 + dpp_dc * dc_dt2
555 
556  dc_dp1 = -(1.0 / pb(counter - 1)) * c_zz
557  dc_dp2 = (1.0 / pb(counter)) * c_zz
558 
559  dppseudo_dpb(i,counter - 1) = dpp_dp1 + dpp_dc * dc_dp1
560  dppseudo_dpb(i,counter) = dpp_dc * dc_dp2
561  ELSE
562  dppseudo_dpb(i,counter - 1) = exp(log(pb(counter) / pb(counter - 1)) * ((model_heights(i) - zb(counter - 1)) / &
563  (zb(counter) - zb(counter - 1)))) * (1.0 - ((model_heights(i) - zb(counter - 1)) / (zb(counter) - zb(counter - 1))))
564  dppseudo_dpb(i,counter) = (pb(counter - 1) / pb(counter)) * ((model_heights(i) - zb(counter - 1)) / &
565  (zb(counter) - zb(counter - 1))) * exp(log(pb(counter) / pb(counter - 1)) * ((model_heights(i) - zb(counter - 1)) / &
566  (zb(counter) - zb(counter - 1))))
567  END IF
568 
569  END IF
570 
571  END DO
572 
573  ! temperature derivatives:
574  dppseudo_dp = matmul(dppseudo_dpb, dpb_dp_local) + matmul(dppseudo_dt, dtb_dp)
575  dtpseudo_dp = matmul(dtpseudo_dtb, dtb_dp)
576 
577  !-------------------------------------------------
578  ! 3. Evaluate the Kmatrix by matrix multiplication
579  !-------------------------------------------------
580 
581  ! calc K matrix for P on rho levels
582  dref_dp(:,:) = matmul(dref_dppseudo, dppseudo_dp) + matmul(dref_dtpseudo, dtpseudo_dp)
583 
584  ! calc Kmatrix for q on theta levels
585  dref_dq(:,:) = matmul(dref_dqpseudo, dqpseudo_dqb) + matmul(matmul(dref_dtpseudo, dtpseudo_dtb), dt_dq) + &
586  matmul(matmul(dref_dppseudo, dppseudo_dt), dt_dq)
587 
588 ! Normal model levels
589 ELSE
590 
591  if (PRESENT(refractivity)) refractivity = refrac
592 
593  !-------------------------------------------------
594  ! 3. Evaluate the Kmatrix by matrix multiplication
595  !-------------------------------------------------
596 
597  ! calc K matrix for P on rho levels
598  ! dNmod/dP = (dNmod/dPb * dPb/dP) + ....
599  dref_dp(:,:) = matmul(dref_dpb, dpb_dp_local)
600 
601  ! .... (dNmod/dT * dT/dTv * dTv/dEx *dEx/dP) + .....
602  m1(:,:) = matmul(dref_dt, dt_dtv)
603  m2(:,:) = matmul(m1, dtv_dex)
604  dref_dp(:,:) = dref_dp(:,:) + matmul(m2, dex_dp)
605 
606  ! .... (dNmod/dT * dT/dTv * dTv/dExtheta *dExtheta/dPb*dPb/dP)
607  m3(:,:) = matmul(m1, dtv_dextheta)
608  m4(:,:) = matmul(m3, dextheta_dpb)
609  dref_dp(:,:) = dref_dp(:,:) + matmul(m4, dpb_dp_local)
610 
611  ! calc Kmatrix for q on theta levels
612  ! dNmod/dq = (dNmod/dq) + (dNmod/dT*dT/dq)
613  dref_dq(:,:) = dref_dq_model(:,:) + matmul(dref_dt, dt_dq)
614 
615 END IF
616 
617 if (present(dpb_dp)) dpb_dp(:,:) = dpb_dp_local(:,:)
618 
619 IF (ALLOCATED (p_pseudo)) DEALLOCATE (p_pseudo)
620 IF (ALLOCATED (q_pseudo)) DEALLOCATE (q_pseudo)
621 IF (ALLOCATED (t_pseudo)) DEALLOCATE (t_pseudo)
622 IF (ALLOCATED (model_heights)) DEALLOCATE (model_heights)
623 IF (ALLOCATED (dref_dppseudo)) DEALLOCATE (dref_dppseudo)
624 IF (ALLOCATED (dref_dtpseudo)) DEALLOCATE (dref_dtpseudo)
625 IF (ALLOCATED (dref_dqpseudo)) DEALLOCATE (dref_dqpseudo)
626 IF (ALLOCATED (dppseudo_dpb)) DEALLOCATE (dppseudo_dpb)
627 IF (ALLOCATED (dtpseudo_dtb)) DEALLOCATE (dtpseudo_dtb)
628 IF (ALLOCATED (dqpseudo_dqb)) DEALLOCATE (dqpseudo_dqb)
629 IF (ALLOCATED (dppseudo_dt)) DEALLOCATE (dppseudo_dt)
630 IF (ALLOCATED (dtb_dp)) DEALLOCATE (dtb_dp)
631 IF (ALLOCATED (dppseudo_dp)) DEALLOCATE (dppseudo_dp)
632 IF (ALLOCATED (dtpseudo_dp)) DEALLOCATE (dtpseudo_dp)
633 
634 END SUBROUTINE ufo_refractivity_kmat
635 
636 !-------------------------------------------------------------------------------
637 !> \brief Calculate some partial derivatives of refractivity on model levels
638 !!
639 !! \details **ufo_refractivity_partial_derivatives**
640 !! * Calculate the pressure on model theta levels
641 !! * Calculate exner on model theta level
642 !! * Calculate mean layer T_virtual, and then various partial derivatives
643 !!
644 !! \author Neill Bowler (Met Office)
645 !!
646 !! \date 26 May 2021
647 !!
648 !-------------------------------------------------------------------------------
649 
651  nlevq, &
652  za, &
653  zb, &
654  P, &
655  q, &
656  vert_interp_ops, &
657  dT_dTv, &
658  dT_dq, &
659  dref_dPb, &
660  dref_dT, &
661  dref_dq, &
662  refractivity, &
663  T, &
664  Pb, &
665  dEx_dP, &
666  dExtheta_dPb, &
667  dTv_dExtheta, &
668  dPb_dP_local, &
669  dTv_dEx)
670 
671 IMPLICIT NONE
672 
673 ! Subroutine arguments:
674 INTEGER, INTENT(IN) :: nlevP !< no. of pressure levels
675 INTEGER, INTENT(IN) :: nlevq !< no. of specific humidity levels
676 REAL(kind_real), INTENT(IN) :: za(nlevp) !< Height of the pressure levels
677 REAL(kind_real), INTENT(IN) :: zb(nlevq) !< Height of the specific humidity levels
678 REAL(kind_real), INTENT(IN) :: P(nlevp) !< Pressure
679 REAL(kind_real), INTENT(IN) :: q(nlevq) !< Specific humidity
680 LOGICAL, INTENT(IN) :: vert_interp_ops !< Whether to interpolate vertically using exner or ln(p)
681 REAL(kind_real), INTENT(OUT) :: dT_dTv(nlevq,nlevq) !< Partial derivative of temperature wrt virtual temperature
682 REAL(kind_real), INTENT(OUT) :: dT_dq(nlevq,nlevq) !< Partial derivative of temperature wrt specific humidity
683 REAL(kind_real), INTENT(OUT) :: dref_dPb(nlevq,nlevq) !< Partial derivative of refractivity wrt pressure on model theta levels
684 REAL(kind_real), INTENT(OUT) :: dref_dT(nlevq,nlevq) !< Partial derivative of refractivity wrt temperature
685 REAL(kind_real), INTENT(OUT) :: dref_dq(nlevq,nlevq) !< Partial derivative of refractivity wrt specific humidity
686 REAL(kind_real), INTENT(OUT) :: refractivity(nlevq) !< Calculated refractivity
687 REAL(kind_real), INTENT(OUT) :: T(nlevq) !< Calculated temperature
688 REAL(kind_real), INTENT(OUT) :: Pb(nlevq) !< Pressure on model theta levels
689 REAL(kind_real), INTENT(OUT) :: dEx_dP(nlevP,nlevP) !< Partial derivative of exner wrt pressure
690 REAL(kind_real), INTENT(OUT) :: dExtheta_dPb(nlevq,nlevq) !< Partial derivative of refractivity wrt pressure (at ob location)
691 REAL(kind_real), INTENT(OUT) :: dTv_dExtheta(nlevq,nlevq) !< Virtual temperature divided by exner on theta levels
692 REAL(kind_real), INTENT(OUT) :: dPb_dP_local(nlevq,nlevP) !< Partial derivative of pressure on theta levels wrt pressure on pressure levels
693 REAL(kind_real), INTENT(OUT) :: dTv_dEx(nlevq,nlevP) !< Partial derivative of virtual temperature wrt exner
694 
695 ! Local declarations:
696 INTEGER :: i ! Loop variable
697 REAL(kind_real) :: Exner(nlevP) ! Exner on model pressure levels
698 REAL(kind_real) :: T_virtual(nlevq) ! Virtual temperature
699 REAL(kind_real) :: Extheta ! Exner on model theta levels
700 REAL(kind_real) :: pwt1 ! Weight given to the lower model level in interpolation
701 REAL(kind_real) :: pwt2 ! Weight given to the upper model level in interpolation
702 REAL(kind_real) :: Ndry ! Contribution to refractivity from dry terms
703 REAL(kind_real) :: Nwet ! Contribution to refractivity from wet terms
704 
705 !-----------------------
706 ! 1. Initialise matrices
707 !-----------------------
708 
709 dpb_dp_local(:,:) = 0.0
710 dextheta_dpb(:,:) = 0.0
711 dtv_dextheta(:,:) = 0.0
712 dtv_dex(:,:) = 0.0
713 dt_dtv(:,:) = 0.0
714 dt_dq(:,:) = 0.0
715 dref_dpb(:,:) = 0.0
716 dref_dt(:,:) = 0.0
717 dref_dq(:,:) = 0.0
718 dex_dp(:,:) = 0.0
719 
720 ! Calculate exner on rho levels.
721 exner(:) = (p(:) / pref) ** rd_over_cp
722 DO i = 1,nlevp
723  dex_dp(i,i) = rd_over_cp / pref * (p(i) / pref) ** (rd_over_cp - 1.0)
724 END DO
725 
726 !----------------------------------------------
727 ! 2. Calculate the refractivity on the temperature/theta levels
728 !----------------------------------------------
729 
730 DO i = 1, nlevq
731 
732  pwt1 = (za(i + 1) - zb(i)) / (za(i + 1) - za(i))
733 
734  pwt2 = 1.0 - pwt1
735 
736  ! calculate the pressure on the theta level.
737  IF (vert_interp_ops) THEN
738  pb(i) = exp(pwt1 * log(p(i)) + pwt2 * log(p(i + 1)))
739 
740  dpb_dp_local(i,i) = pb(i) * pwt1 / p(i)
741  dpb_dp_local(i,i + 1) = pb(i) * pwt2 / p(i + 1)
742  ELSE
743  ! Assume Exner varies linearly with height
744  pb(i) = pref * (pwt1 * (p(i) / pref) ** rd_over_cp + pwt2 * (p(i + 1) / pref) ** rd_over_cp) ** (1.0 / rd_over_cp)
745 
746  dpb_dp_local(i,i) = pwt1 * (pwt1 * (p(i) / pref) ** rd_over_cp + pwt2 * &
747  (p(i + 1) / pref) ** rd_over_cp) ** (1.0 / rd_over_cp - 1.0) * (p(i) / pref) ** (rd_over_cp - 1.0)
748  dpb_dp_local(i,i + 1) = pwt2 * (pwt1 * (p(i) / pref) ** rd_over_cp + pwt2 * &
749  (p(i + 1) / pref) ** rd_over_cp) ** (1.0 / rd_over_cp - 1.0) * (p(i + 1) / pref) ** (rd_over_cp-1.0)
750  END IF
751 
752  ! calculate Exner on the theta level.
753 
754  extheta = (pb(i) / pref) ** rd_over_cp
755 
756  dextheta_dpb(i,i) = rd_over_cp * (pb(i) ** (rd_over_cp - 1.0)) / (pref ** rd_over_cp)
757 
758  ! Calculate mean layer T_virtual on staggered vertical levels
759 
760  t_virtual(i) = grav * (za(i + 1) - za(i)) * extheta / (cp * (exner(i) - exner(i + 1)))
761 
762  dtv_dextheta(i,i) = t_virtual(i) / extheta
763 
764  dtv_dex(i,i) = -t_virtual(i) / (exner(i) - exner(i + 1))
765 
766  dtv_dex(i,i + 1) = t_virtual(i) / (exner(i) - exner(i + 1))
767 
768  t(i) = t_virtual(i) / (1.0 + c_virtual * q(i))
769 
770  dt_dtv(i,i) = 1.0 / (1.0 + c_virtual * q(i))
771 
772  dt_dq(i,i) = -c_virtual * t(i) / (1.0 + c_virtual * q(i))
773 
774  ! wet component
775 
776  nwet = n_beta * pb(i) * q(i) / (t(i) ** 2 * (mw_ratio + (1.0 - mw_ratio) * q(i)))
777 
778  dref_dq(i,i) = n_beta * pb(i) * mw_ratio / (t(i) * (mw_ratio + (1.0 - mw_ratio) * q(i))) ** 2
779 
780  ndry = n_alpha * pb(i) / t(i)
781 
782  refractivity(i) = ndry + nwet
783 
784  dref_dpb(i,i) = (ndry + nwet) / pb(i)
785 
786  dref_dt(i,i) = -(ndry + 2.0 * nwet) / t(i)
787 
788 END DO
789 
791 
792 
real(kind_real), parameter, public rd
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.
subroutine, public ufo_refractivity_kmat(nlevP, nlevq, nRefLevels, za, zb, P, q, pseudo_ops, vert_interp_ops, min_temp_grad, dref_dP, dref_dq, dPb_dP, refractivity)
Calculate general refractivity K matrix.