UFO
ufo_gnssroonedvarcheck_mod.f90
Go to the documentation of this file.
1 ! (C) Copyright 2017-2020 Met Office
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 !> The main Fortran module for implementing the GNSS-RO onedvar check
7 
9 
10 use, intrinsic :: iso_c_binding
11 use fckit_configuration_module, only: fckit_configuration
12 use fckit_log_module, only : fckit_log
13 use iso_c_binding
14 use kinds
15 use missing_values_mod
16 use obsspace_mod
17 use oops_variables_mod
19 use ufo_vars_mod
27 
28 implicit none
29 private
33 
34 type, public :: ufo_gnssroonedvarcheck
35  character(len=800) :: qcname !< name of the filter
36  type(c_ptr) :: obsdb !< pointer to the observation space
37  type(fckit_configuration) :: conf !< contents of the yaml file
38  integer(c_int) :: onedvarflag !< flag uased by the qc manager for a 1D-var check
39  logical :: capsupersat !< Whether to remove super-saturation (wrt ice?)
40  real(kind_real) :: cost_funct_test !< Threshold value for the cost function convergence test
41  real(kind_real) :: delta_ct2 !< Threshold used in calculating convergence
42  real(kind_real) :: delta_factor !< Threshold used in calculating convergence
43  integer :: n_iteration_test !< Maximum number of iterations in the 1DVar
44  real(kind_real) :: ob_test !< Threshold for the O-B throughout the profile
45  real(kind_real) :: y_test !< Threshold on distance between observed and solution bending angles
46  character(len=800) :: bmatrix_filename !< Location of the B-matrix file
47  logical :: pseudo_ops !< Whether to use pseudo levels in forward operator
48  logical :: vert_interp_ops !< Whether to use ln(p) or exner in vertical interpolation
49  real(kind_real) :: min_temp_grad !< The minimum vertical temperature gradient allowed
51 
52 ! ------------------------------------------------------------------------------
53 contains
54 ! ------------------------------------------------------------------------------
55 !> Setup the main GNSS-RO onedvar object in Fortran
56 !!
57 !! \details Makes a call to the main setup routine.
58 !!
59 !! \author Met Office
60 !!
61 !! \date 09/06/2020: Created
62 !!
63 subroutine ufo_gnssroonedvarcheck_create(self, obsspace, bmatrix_filename, &
64  capsupersat, cost_funct_test, &
65  Delta_ct2, Delta_factor, min_temp_grad, &
66  n_iteration_test, OB_test, pseudo_ops, &
67  vert_interp_ops, y_test, onedvarflag)
68 
69  implicit none
70  type(ufo_gnssroonedvarcheck), intent(inout) :: self !< gnssroonedvarcheck main object
71  type(c_ptr), value, intent(in) :: obsspace !< observation database pointer
72  character(len=*), intent(in) :: bmatrix_filename !< Location of the B-matrix file
73  logical(c_bool), intent(in) :: capsupersat !< Whether to remove super-saturation (wrt ice?)
74  real(c_float), intent(in) :: cost_funct_test !< Threshold value for the cost function convergence test
75  real(c_float), intent(in) :: delta_ct2 !< Threshold used in calculating convergence
76  real(c_float), intent(in) :: delta_factor !< Threshold used in calculating convergence
77  real(c_float), intent(in) :: min_temp_grad !< The minimum vertical temperature gradient allowed
78  integer(c_int), intent(in) :: n_iteration_test !< Maximum number of iterations in the 1DVar
79  real(c_float), intent(in) :: ob_test !< Threshold for the O-B throughout the profile
80  logical(c_bool), intent(in) :: pseudo_ops !< Whether to use pseudo levels in forward operator
81  logical(c_bool), intent(in) :: vert_interp_ops !< Whether to use ln(p) or exner in vertical interpolation
82  real(c_float), intent(in) :: y_test !< Threshold on distance between observed and solution bending angles
83  integer(c_int), intent(in) :: onedvarflag !< flag for qc manager
84 
85  character(len=800) :: message
86 
87  self % obsdb = obsspace
88  self % onedvarflag = onedvarflag
89 
90  self % bmatrix_filename = bmatrix_filename
91  self % capsupersat = capsupersat
92  self % cost_funct_test = cost_funct_test
93  self % Delta_ct2 = delta_ct2
94  self % Delta_factor = delta_factor
95  self % min_temp_grad = min_temp_grad
96  self % n_iteration_test = n_iteration_test
97  self % OB_test = ob_test
98  self % pseudo_ops = pseudo_ops
99  self % vert_interp_ops = vert_interp_ops
100  self % y_test = y_test
101 
102  write(message, '(A)') 'GNSS-RO 1D-Var check: input parameters are:'
103  call fckit_log % debug(message)
104  write(message, '(2A)') 'bmatrix_filename = ', bmatrix_filename
105  call fckit_log % debug(message)
106  write(message, '(A,L1)') 'capsupersat = ', capsupersat
107  call fckit_log % debug(message)
108  write(message, '(A,F16.8)') 'cost_funct_test = ', cost_funct_test
109  call fckit_log % debug(message)
110  write(message, '(A,F16.8)') 'Delta_ct2 = ', delta_ct2
111  call fckit_log % debug(message)
112  write(message, '(A,F16.8)') 'Delta_factor = ', delta_factor
113  call fckit_log % debug(message)
114  write(message, '(A,F16.8)') 'min_temp_grad = ', min_temp_grad
115  call fckit_log % debug(message)
116  write(message, '(A,I7)') 'n_iteration_test = ', n_iteration_test
117  call fckit_log % debug(message)
118  write(message, '(A,F16.8)') 'OB_test = ', ob_test
119  call fckit_log % debug(message)
120  write(message, '(A,L1)') 'pseudo_ops = ', pseudo_ops
121  call fckit_log % debug(message)
122  write(message, '(A,L1)') 'vert_interp_ops = ', vert_interp_ops
123  call fckit_log % debug(message)
124  write(message, '(A,F16.8)') 'y_test = ', y_test
125  call fckit_log % debug(message)
126 
127 end subroutine ufo_gnssroonedvarcheck_create
128 
129 ! ------------------------------------------------------------------------------
130 !> Delete the main GNSS-RO onedvar object in Fortran
131 !!
132 !! \author Met Office
133 !!
134 !! \date 09/06/2020: Created
135 !!
137 
138  implicit none
139  type(ufo_gnssroonedvarcheck), intent(inout) :: self !< gnssroonedvarcheck main object
140 
141 end subroutine ufo_gnssroonedvarcheck_delete
142 
143 ! ------------------------------------------------------------------------------
144 !> The main routine that applys the GNSS-RO onedvar filter
145 !!
146 !! \details Heritage :
147 !!
148 !! This routine is called from the c++ apply method. The filter performs
149 !! a 1D-Var minimization
150 !!
151 !! \author Met Office
152 !!
153 !! \date 09/06/2020: Created
154 !!
155 subroutine ufo_gnssroonedvarcheck_apply(self, geovals, apply)
156 
157  implicit none
158 
159  type(ufo_gnssroonedvarcheck), intent(inout) :: self !< gnssroonedvarcheck main object
160  type(ufo_geovals), intent(in) :: geovals !< model values at observation space
161  logical, intent(in) :: apply(:) !< qc manager flags
162 
163  integer :: nobs ! Number of observations to be processed
164  type(ufo_geoval), pointer :: q ! Model background values of specific humidity
165  type(ufo_geoval), pointer :: prs ! Model background values of air pressure
166  type(ufo_geoval), pointer :: theta_heights ! Model heights of levels containing specific humidity
167  type(ufo_geoval), pointer :: rho_heights ! Model heights of levels containing air pressure
168  type(singlebg_type) :: back ! Model background fields
169  type(singleob_type) :: ob ! The profile of observations
170  type(bmatrix_type) :: b_matrix ! Background-error covariance matrix
171  real(kind_real), allocatable :: obslat(:) ! Latitude of the observation
172  real(kind_real), allocatable :: obslon(:) ! Longitude of the observation
173  real(kind_real), allocatable :: impact_param(:) ! Impact parameter of the observation
174  real(kind_real), allocatable :: radius_curv(:) ! Earth's radius of curvature at the observation tangent point
175  real(kind_real), allocatable :: undulation(:) ! Undulation - height of the geoid above the ellipsoid
176  real(kind_real), allocatable :: obs_bending_angle(:) ! Observed bending angle
177  real(kind_real), allocatable :: obs_err(:) ! Observation error, taken from a previous filter
178  integer, allocatable :: qc_flags(:) ! QC flags to be updated
179  integer, allocatable :: obssatid(:) ! Satellite identifier for each observation
180  integer, allocatable :: obsorigc(:) ! Originating centre for each observation
181  integer(c_size_t), allocatable :: record_number(:) ! Number used to identify unique profiles in the data
182  real(kind_real), allocatable :: sort_key(:) ! Key for the sorting (based on record number and impact parameter)
183  integer, allocatable :: index_vals(:) ! Indices of sorted observation
184  integer, allocatable :: unique(:) ! Set of unique profile numbers
185  integer :: start_point ! Starting index of the current profile
186  integer :: current_point ! Ending index of the current profile
187  integer :: iprofile ! Loop variable, profile number
188  integer :: nobs_profile ! Number of observations in the profile
189  character(len=800) :: message ! Message to be output
190  logical :: baerr ! Has there been an error in the bending angle calculation?
191  integer :: iband ! Selected latitude band of the B-matrix
192  integer :: iseason ! Selected season of the B-matrix
193  integer :: ipoint ! Loop variable, observation point
194  real(kind_real) :: dfs ! Degrees of freedom for signal in profile
195  real(kind_real) :: o_bdiff ! Average RMS(O-B) for profile
196  real(kind_real), allocatable :: tb(:) ! Calculated background temperature (derived from p,q)
197  real(kind_real), allocatable :: ts(:) ! 1DVar solution temperature
198 
199  ! Get the obs-space information
200  nobs = obsspace_get_nlocs(self % obsdb)
201  allocate(obslon(nobs))
202  allocate(obslat(nobs))
203  allocate(impact_param(nobs))
204  allocate(radius_curv(nobs))
205  allocate(undulation(nobs))
206  allocate(obs_bending_angle(nobs))
207  allocate(record_number(nobs))
208  allocate(obssatid(nobs))
209  allocate(obsorigc(nobs))
210  allocate(qc_flags(nobs))
211  allocate(obs_err(nobs))
212 
213  call obsspace_get_db(self % obsdb, "MetaData", "longitude", obslon)
214  call obsspace_get_db(self % obsdb, "MetaData", "latitude", obslat)
215  call obsspace_get_db(self % obsdb, "MetaData", "impact_parameter", impact_param)
216  call obsspace_get_db(self % obsdb, "MetaData", "earth_radius_of_curvature", radius_curv)
217  call obsspace_get_db(self % obsdb, "MetaData", "geoid_height_above_reference_ellipsoid", undulation)
218  call obsspace_get_db(self % obsdb, "ObsValue", "bending_angle", obs_bending_angle)
219  call obsspace_get_recnum(self % obsdb, record_number)
220  call obsspace_get_db(self % obsdb, "MetaData", "occulting_sat_id", obssatid)
221  call obsspace_get_db(self % obsdb, "MetaData", "originating_center", obsorigc)
222  call obsspace_get_db(self % obsdb, "FortranQC", "bending_angle", qc_flags)
223  call obsspace_get_db(self % obsdb, "FortranERR", "bending_angle", obs_err)
224 
225  ! get variables from geovals
226  call ufo_geovals_get_var(geovals, var_q, q) ! specific humidity
227  call ufo_geovals_get_var(geovals, var_prsi, prs) ! pressure
228  call ufo_geovals_get_var(geovals, var_z, theta_heights) ! Geopotential height of the normal model levels
229  call ufo_geovals_get_var(geovals, var_zi, rho_heights) ! Geopotential height of the pressure levels
230 
231  ! Read in the B-matrix and background profiles
232  call ops_gpsro_getbmatrix(self % bmatrix_filename, prs % nval, q % nval, b_matrix)
233  call allocate_singlebg(back, prs % nval, q % nval)
234  allocate(tb(q % nval), ts(q % nval))
235 
236  ! Read through the record numbers in order to find a profile of observations
237  ! Each profile shares the same record number
238  allocate(sort_key(nobs))
239  sort_key = record_number + (impact_param / maxval(impact_param))
240  call ops_realsortquick(sort_key, index_vals)
241  call find_unique(record_number, unique)
242 
243  ! For every profile that we have found, perform a 1DVar minimisation
244  current_point = 1
245  do iprofile = 1, size(unique)
246  start_point = current_point
247  WRITE (message, '(A,I0)') 'ObNumber ', iprofile
248  call fckit_log % info(message)
249  WRITE (message, '(A,F12.2)') 'Latitude ', obslat(index_vals(start_point))
250  call fckit_log % info(message)
251  WRITE (message, '(A,F12.2)') 'Longitude ', obslon(index_vals(start_point))
252  call fckit_log % info(message)
253  WRITE (message, '(A,I0)') 'Processing centre ', obsorigc(index_vals(start_point))
254  call fckit_log % info(message)
255  WRITE (message, '(A,I0)') 'Sat ID ', obssatid(index_vals(start_point))
256  call fckit_log % info(message)
257 
258  ! Work out which observations belong to the current profile
259  do current_point = start_point, nobs
260  if (unique(iprofile) /= record_number(index_vals(current_point))) exit
261  end do
262 
263  ! Load the geovals into the background structure
264  back % za(:) = rho_heights % vals(:, index_vals(start_point))
265  back % zb(:) = theta_heights % vals(:, index_vals(start_point))
266  back % p(:) = prs % vals(:, index_vals(start_point))
267  back % q(:) = q % vals(:, index_vals(start_point))
268 
269  ! Allocate the observations structure
270  nobs_profile = current_point - start_point
271  call allocate_singleob(ob, nobs_profile, prs % nval, q % nval)
272 
273  ! Load the observations information into the obsevations structure
274  ob % id = record_number(index_vals(start_point))
275  ob % latitude = obslat(index_vals(start_point))
276  ob % longitude = obslon(index_vals(start_point))
277  ob % niter = 0 ! We haven't yet run 1DVar
278  ob % jcost = missing_value(ob % jcost)
279  ob % bendingangle(:) % value = obs_bending_angle(index_vals(start_point:current_point-1))
280  ob % bendingangle(:) % oberr = obs_err(index_vals(start_point:current_point-1))
281  ob % impactparam(:) % value = impact_param(index_vals(start_point:current_point-1))
282  ob % qc_flags(:) = qc_flags(index_vals(start_point:current_point-1))
283  ob % ro_rad_curv % value = radius_curv(index_vals(start_point))
284  ob % ro_geoid_und % value = undulation(index_vals(start_point))
285 
286  ! Choose the latitude band and season of the B-matrix information
287  iseason = 1 ! Temporary -only one season at present!
288  iband = 1
289  DO
290  IF (b_matrix % band_up_lim(iband) > ob % latitude .OR. &
291  iband == b_matrix % nband) EXIT
292  iband = iband + 1
293  END DO
294 
295 ! Call the code to set up the 1D-Var calculation
296  call ops_gpsro_do1dvar_ba(prs % nval, & ! Number of pressure levels
297  q % nval, & ! Number of specific humidity levels
298  b_matrix % inverse(iseason,iband,:,:), & ! Inverse of the b-matrix
299  b_matrix % sigma(iseason,iband,:), & ! Standard deviations of the b-matrix
300  back, & ! Structure containing the model background information
301  ob, & ! Structure containing the observation information
302  self % pseudo_ops, & ! Whether to use pseudo-levels in calculation
303  self % vert_interp_ops, & ! Whether to interpolate using ln(p) or exner
304  self % min_temp_grad, & ! Minimum vertical temperature gradient allowed
305  self % cost_funct_test, & ! Threshold value for the cost function convergence test
306  self % y_test, & ! Threshold value for the yobs-ysol tes
307  self % n_iteration_test, & ! Maximum number of iterations
308  self % Delta_factor, & ! Delta
309  self % Delta_ct2, & ! Delta observations
310  self % OB_test, & ! Threshold value for the O-B test
311  self % capsupersat, & ! Whether to remove super-saturation
312  baerr, & ! Whether there are errors in the bending angle calculation
313  tb, & ! Calculated background temperature
314  ts, & ! 1DVar solution temperature
315  o_bdiff, & ! Difference between observations and background for profile
316  dfs) ! Estimated degrees of freedom for signal
317 
318  ! Flag bad profiles
319  do ipoint = 0, nobs_profile-1
320  if (qc_flags(start_point + ipoint) > 0) then
321  ! Do nothing, since the data are already flagged
322  else if (ob % bendingangle(ipoint+1) % PGEFinal > 0.5) then
323  qc_flags(start_point + ipoint) = self % onedvarflag
324  ob % qc_flags(ipoint+1) = self % onedvarflag
325  end if
326  end do
327 
328  write(message,'(A,2I5,2F10.3,I5,F16.6)') 'Profile stats: ', obssatid(index_vals(start_point)), &
329  obsorigc(index_vals(start_point)), ob % latitude, ob % longitude, &
330  ob % niter, ob % jcost
331  call fckit_log % debug(message)
332  do ipoint = 0, nobs_profile-1, 100
333  write(message,'(100I5)') qc_flags(index_vals(start_point+ipoint: &
334  min(start_point+ipoint+99, current_point-1)))
335  call fckit_log % debug(message)
336  end do
337 
338  call deallocate_singleob(ob)
339  end do
340  call obsspace_put_db(self % obsdb, "FortranQC", "bending_angle", qc_flags)
341 
342 end subroutine ufo_gnssroonedvarcheck_apply
343 
subroutine, public ufo_geovals_get_var(self, varname, geoval)
Fortran module for gnssro bending angle Met Office forward operator.
Fortran module for gnssro bending angle Met Office forward operator.
subroutine, public ops_gpsro_do1dvar_ba(nlevp, nlevq, BM1, Bsig, Back, Ob, GPSRO_pseudo_ops, GPSRO_vert_interp_ops, GPSRO_min_temp_grad, GPSRO_cost_funct_test, GPSRO_y_test, GPSRO_n_iteration_test, GPSRO_Delta_factor, GPSRO_Delta_ct2, GPSRO_OB_test, capsupersat, BAerr, Tb, Ts, O_Bdiff, DFS)
Set up the background error covariance matrix (B matrix).
subroutine, public ops_gpsro_getbmatrix(filename, cx_nlevp, cx_nlevq, Bmatrix)
The main Fortran module for implementing the GNSS-RO onedvar check.
subroutine, public ufo_gnssroonedvarcheck_create(self, obsspace, bmatrix_filename, capsupersat, cost_funct_test, Delta_ct2, Delta_factor, min_temp_grad, n_iteration_test, OB_test, pseudo_ops, vert_interp_ops, y_test, onedvarflag)
Setup the main GNSS-RO onedvar object in Fortran.
subroutine, public ufo_gnssroonedvarcheck_delete(self)
Delete the main GNSS-RO onedvar object in Fortran.
subroutine, public ufo_gnssroonedvarcheck_apply(self, geovals, apply)
The main routine that applys the GNSS-RO onedvar filter.
subroutine, public allocate_singleob(singleob, nobs, nlevp, nlevq)
Allocate the singleob_type structure, given a certain number of observations,.
subroutine, public deallocate_singleob(singleob)
Deallocate the singleob_type structure.
subroutine, public ops_realsortquick(key, index)
Generates a index array pointing to the elements of the array 'key'.
subroutine, public deallocate_singlebg(singlebg)
Dealloate the singlebg_type structure.
subroutine, public find_unique(input, output)
Find the unique entries in the input list.
subroutine, public allocate_singlebg(singlebg, nlevp, nlevq)
Allocate the structure to hold background information from a single profile.
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
type to hold interpolated field for one variable, one observation
type to hold interpolated fields required by the obs operators