UFO
ufo_rttovonedvarcheck_obs_mod.f90
Go to the documentation of this file.
1 ! (C) copyright 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 !> Fortran module which contains the observation data for a all the obs space
7 
9 
10 use kinds
11 use iso_c_binding
12 use missing_values_mod
13 use obsspace_mod
14 use oops_variables_mod
21 use ufo_vars_mod
22 
23 implicit none
24 private
25 
26 ! Ob info type definition
28 
29 integer :: iloc
30 integer, allocatable :: qcflags(:,:) ! current qc flags needed for channel selection
31 real(kind_real), allocatable :: yobs(:,:) ! observation value from obs files
32 real(kind_real), allocatable :: ybias(:,:) ! observation bias from obs files
33 real(kind_real), allocatable :: lat(:) ! observation latitude
34 real(kind_real), allocatable :: lon(:) ! observation longitude
35 real(kind_real), allocatable :: elevation(:) ! observation elevation
36 real(kind_real), allocatable :: sat_zen(:) ! observation satellite zenith angle
37 real(kind_real), allocatable :: sat_azi(:) ! observation satellite azimuth angle
38 real(kind_real), allocatable :: sol_zen(:) ! observation solar zenith angle
39 real(kind_real), allocatable :: sol_azi(:) ! observation solar azimuth angle
40 integer, allocatable :: surface_type(:) ! surface type
41 integer, allocatable :: niter(:) ! number of iterations
42 real(kind_real), allocatable :: final_cost(:) ! final cost at solution
43 real(kind_real), allocatable :: lwp(:) ! liquid water path from final iteration
44 real(kind_real), allocatable :: emiss(:,:) ! initial surface emissivity
45 real(kind_real), allocatable :: output_profile(:,:) ! output profile
46 real(kind_real), allocatable :: output_bt(:,:) ! output brightness temperature
47 real(kind_real), allocatable :: background_bt(:,:) ! 1st iteration brightness temperature
48 logical, allocatable :: calc_emiss(:) ! flag to request RTTOV calculate first guess emissivity
49 logical :: store1dvarlwp ! flag to output the LWP if the profile converges
50 
51 contains
52  procedure :: setup => ufo_rttovonedvarcheck_obs_setup
53  procedure :: delete => ufo_rttovonedvarcheck_obs_delete
54  procedure :: output => ufo_rttovonedvarcheck_obs_output
55 
56 end type
57 
58 contains
59 
60 !-------------------------------------------------------------------------------
61 !> Initialize observation object
62 !!
63 !! \author Met Office
64 !!
65 !! \date 09/06/2020: Created
66 !!
67 subroutine ufo_rttovonedvarcheck_obs_setup(self, & ! out
68  config, & ! in
69  nprofelements, & ! in
70  geovals, & ! in
71  vars, & ! in
72  ir_pcemis )
73 
74 implicit none
75 
76 ! subroutine arguments:
77 class(ufo_rttovonedvarcheck_obs), intent(out) :: self !< observation metadata type
78 type(ufo_rttovonedvarcheck), intent(in) :: config !< observation metadata type
79 integer, intent(in) :: nprofelements !< number of profile elements
80 type(ufo_geovals), intent(in) :: geovals !< model data at obs location
81 type(oops_variables), intent(in) :: vars !< channels for 1D-Var
82 type(ufo_rttovonedvarcheck_pcemis) :: ir_pcemis !< Infrared principal components object
83 
84 character(len=*), parameter :: routinename = "ufo_rttovonedvarcheck_obs_init"
85 real(kind_real) :: missing
86 integer :: jvar !< counters
87 integer :: jobs !< counters
88 character(len=max_string) :: var
89 character(len=max_string) :: varname
90 logical :: variable_present = .false.
91 logical :: model_surface_present = .false.
92 type(ufo_geoval), pointer :: geoval
93 
94 missing = missing_value(missing)
95 self % iloc = obsspace_get_nlocs(config % obsdb)
96 
97 ! allocate arrays
98 allocate(self % yobs(config % nchans, self % iloc))
99 allocate(self % ybias(config % nchans, self % iloc))
100 allocate(self % QCflags(config % nchans, self % iloc))
101 allocate(self % lat(self % iloc))
102 allocate(self % lon(self % iloc))
103 allocate(self % elevation(self % iloc))
104 allocate(self % sat_zen(self % iloc))
105 allocate(self % sat_azi(self % iloc))
106 allocate(self % sol_zen(self % iloc))
107 allocate(self % sol_azi(self % iloc))
108 allocate(self % surface_type(self % iloc))
109 allocate(self % niter(self % iloc))
110 allocate(self % final_cost(self % iloc))
111 allocate(self % LWP(self % iloc))
112 allocate(self % emiss(config % nchans, self % iloc))
113 allocate(self % output_profile(nprofelements, self % iloc))
114 allocate(self % output_BT(config % nchans, self % iloc))
115 allocate(self % background_BT(config % nchans, self % iloc))
116 allocate(self % calc_emiss(self % iloc))
117 
118 ! initialize arrays
119 self % yobs(:,:) = missing
120 self % ybias(:,:) = zero
121 self % QCflags(:,:) = 0
122 self % lat(:) = missing
123 self % lon(:) = missing
124 self % elevation(:) = missing
125 self % sat_zen(:) = missing
126 self % sat_azi(:) = zero
127 self % sol_zen(:) = zero
128 self % sol_azi(:) = zero
129 self % surface_type(:) = rtsea
130 self % niter(:) = 0
131 self % final_cost(:) = missing
132 self % LWP(:) = missing
133 self % emiss(:,:) = zero
134 self % output_profile(:,:) = missing
135 self % output_BT(:,:) = missing
136 self % background_BT(:,:) = missing
137 self % calc_emiss(:) = .true.
138 self % Store1DVarLWP = config % Store1DVarLWP
139 
140 ! read in observations and associated errors / biases for full ObsSpace
141 do jvar = 1, config % nchans
142 
143  var = vars % variable(jvar)
144  call obsspace_get_db(config % obsdb, "FortranQC", trim(var), self % QCflags(jvar,:))
145  call obsspace_get_db(config % obsdb, "ObsValue", trim(var), self % yobs(jvar,:))
146 
147  ! Optionally get the observation bias
148  variable_present = obsspace_has(config % obsdb, "ObsBias", trim(var))
149  if (variable_present) then
150  call obsspace_get_db(config % obsdb, "ObsBias", trim(var), self % ybias(jvar,:))
151  end if
152 
153 end do
154 
155 ! The obs bias may contain missing values, and the intention is for those
156 ! entries to be zero. Go through the bias data and replace the missing values
157 ! with zero.
158 do jobs = 1, self % iloc
159  do jvar = 1, config % nchans
160  if (self % ybias(jvar, jobs) == missing) then
161  self % ybias(jvar, jobs) = zero
162  endif
163  enddo
164 enddo
165 
166 if (.not. variable_present) write(*,*) "Using uncorrected brightness temperature"
167 
168 ! Subtract bias from the observations (apply bias correction)
169 self % yobs = self % yobs - self % ybias
170 
171 ! Read in prerequisites
172 call obsspace_get_db(config % obsdb, "MetaData", "latitude", self % lat(:))
173 call obsspace_get_db(config % obsdb, "MetaData", "longitude", self % lon(:))
174 call obsspace_get_db(config % obsdb, "MetaData", "sensor_zenith_angle", self % sat_zen(:))
175 
176 ! Read in optional angles
177 variable_present = obsspace_has(config % obsdb, "MetaData", "sensor_azimuth_angle")
178 if (variable_present) then
179  call obsspace_get_db(config % obsdb, "MetaData", "sensor_azimuth_angle", self % sat_azi(:))
180 end if
181 
182 variable_present = obsspace_has(config % obsdb, "MetaData", "solar_zenith_angle")
183 if (variable_present) then
184  call obsspace_get_db(config % obsdb, "MetaData", "solar_zenith_angle", self % sol_zen(:))
185 end if
186 
187 variable_present = obsspace_has(config % obsdb, "MetaData", "solar_azimuth_angle")
188 if (variable_present) then
189  call obsspace_get_db(config % obsdb, "MetaData", "solar_azimuth_angle", self % sol_azi(:))
190 end if
191 
192 ! Read in elevation for all obs
193 if (obsspace_has(config % obsdb, "MetaData", "elevation")) then
194  call obsspace_get_db(config % obsdb, "MetaData", "elevation", self % elevation(:))
195 else if (obsspace_has(config % obsdb, "MetaData", "surface_height")) then
196  call obsspace_get_db(config % obsdb, "MetaData", "surface_height", self % elevation(:))
197 else if (obsspace_has(config % obsdb, "MetaData", "model_orography")) then
198  call obsspace_get_db(config % obsdb, "MetaData", "model_orography", self % elevation(:))
199 else if (ufo_vars_getindex(geovals % variables, 'surface_altitude') > 0) then
200  call ufo_geovals_get_var(geovals, 'surface_altitude', geoval)
201  self % elevation(:) = geoval%vals(1, 1)
202 else
203  self % elevation(:) = zero
204 endif
205 
206 ! Read in surface type from ObsSpace or model data (deprecated)
207 if (obsspace_has(config % obsdb, "MetaData", "surface_type")) then
208  call obsspace_get_db(config % obsdb, "MetaData", "surface_type", self % surface_type(:))
209 else
210  call ufo_geovals_get_var(geovals, "surface_type", geoval)
211  self % surface_type(:) = geoval%vals(1, :)
212 endif
213 
214 ! Setup emissivity
215 if (config % pcemiss) then
216  write(*,*) "PC emissivity being used"
217  call ufo_rttovonedvarcheck_obs_initiremiss(self, config % nchans, ir_pcemis)
218 else
219  write(*,*) "Conventional emissivity being used"
220  call ufo_rttovonedvarcheck_obs_initmwemiss(self, config)
221 end if
222 
223 end subroutine ufo_rttovonedvarcheck_obs_setup
224 
225 !------------------------------------------------------------------------------
226 !> Delete the observation object
227 !!
228 !! \author Met Office
229 !!
230 !! \date 09/06/2020: Created
231 !!
232 subroutine ufo_rttovonedvarcheck_obs_delete(self) ! inout
233 
234 implicit none
235 
236 ! subroutine arguments:
237 class(ufo_rttovonedvarcheck_obs), intent(inout) :: self !< observation metadata type
238 
239 character(len=*), parameter :: routinename = "ufo_rttovonedvarcheck_obs_delete"
240 
241 ! deallocate arrays
242 if (allocated(self % QCflags)) deallocate(self % QCflags)
243 if (allocated(self % yobs)) deallocate(self % yobs)
244 if (allocated(self % ybias)) deallocate(self % ybias)
245 if (allocated(self % lat)) deallocate(self % lat)
246 if (allocated(self % lon)) deallocate(self % lon)
247 if (allocated(self % elevation)) deallocate(self % elevation)
248 if (allocated(self % sat_zen)) deallocate(self % sat_zen)
249 if (allocated(self % sat_azi)) deallocate(self % sat_azi)
250 if (allocated(self % sol_zen)) deallocate(self % sol_zen)
251 if (allocated(self % sol_azi)) deallocate(self % sol_azi)
252 if (allocated(self % surface_type)) deallocate(self % surface_type)
253 if (allocated(self % niter)) deallocate(self % niter)
254 if (allocated(self % final_cost)) deallocate(self % final_cost)
255 if (allocated(self % LWP)) deallocate(self % LWP)
256 if (allocated(self % emiss)) deallocate(self % emiss)
257 if (allocated(self % output_profile)) deallocate(self % output_profile)
258 if (allocated(self % output_BT)) deallocate(self % output_BT)
259 if (allocated(self % background_BT)) deallocate(self % background_BT)
260 if (allocated(self % calc_emiss)) deallocate(self % calc_emiss)
261 
263 
264 !------------------------------------------------------------------------------
265 !> Initialize the microwave emissivity array
266 !!
267 !! \details Heritage: Ops_SatRad_InitEmissivity.f90 - the MW part only
268 !!
269 !! \author Met Office
270 !!
271 !! \date 06/08/2020: Created
272 !!
274 
275 implicit none
276 
277 ! subroutine arguments:
278 type(ufo_rttovonedvarcheck_obs), intent(inout) :: self !< observation metadata type
279 type(ufo_rttovonedvarcheck), intent(in) :: config !< main rttovonedvarcheck type
280 
281 integer :: i
282 
283 !-------------
284 ! 2.1 Defaults
285 !-------------
286 
287 do i = 1, self % iloc
288 
289  ! Only calculate in RTTOV over sea
290  if (self % surface_type(i) == rtsea) then
291  self % calc_emiss(i) = .true.
292  else
293  self % calc_emiss(i) = .false.
294  end if
295 
296  ! The default emissivity for land is a very crude estimate - the same
297  ! for all surface types and all frequencies. However, we do not use
298  ! channels which see the surface over land where we rely on this default.
299  self % emiss(:,i) = zero
300  if (self % surface_type(i) == rtland) then
301  self % emiss(:,i) = config % EmissLandDefault
302  else if (self % surface_type(i) == rtice) then
303  self % emiss(:,i) = config % EmissSeaIceDefault
304  end if
305 
306 end do
307 
308 end subroutine
309 
310 !------------------------------------------------------------------------------
311 !> Initialize the infrared emissivity array
312 !!
313 !! \details Heritage: Ops_SatRad_InitEmissivity.f90 - the IR part only
314 !!
315 !! \author Met Office
316 !!
317 !! \date 06/08/2020: Created
318 !!
319 subroutine ufo_rttovonedvarcheck_obs_initiremiss(self, nchans, ir_pcemis)
320 
321 implicit none
322 
323 ! subroutine arguments:
324 class(ufo_rttovonedvarcheck_obs), intent(inout) :: self !< observation metadata type
325 integer, intent(in) :: nchans !< total number of channels
326 type(ufo_rttovonedvarcheck_pcemis) :: ir_pcemis !< Infrared principal components object
327 
328 ! local variables
329 real(kind_real), allocatable :: EmissPC(:,:)
330 integer :: i
331 integer :: nemisspc = 5
332 integer :: emis_x
333 integer :: emis_y
334 
335 ! Allocate and setup defaults - get RTTOV to calculate
336 self % emiss(:,:) = zero
337 self % calc_emiss(:) = .true.
338 
339 allocate(emisspc(self % iloc,nemisspc))
340 
341 !-------------------------
342 ! 1.2 Principal components
343 !-------------------------
344 
345 ! Initialise emissivity using principal components
346 if (ir_pcemis % initialised) then
347 
348  ! Don't do this if the RTTOV CAMEL atlas is being used.
349  ! Defaults above will let the RTTOV camel atlas be used.
350  !if (associated (ir_pcemiss % emis_eigen % PCGuess) .and. (.not. RTTOV_UseAtlas)) then
351  if (associated (ir_pcemis % emis_eigen % PCGuess)) then
352 
353  do i = 1, self % iloc
354 
355  ! Skip obs that may have invalid geographical coordinates
356  !IF (BTEST (Obs % QCflags(i), QC_ModelDomain)) CYCLE
357 
358  ! If there's an atlas available, try to use it.
359  if (associated (ir_pcemis % emis_atlas % EmisPC)) then
360 
361  ! Find the nearest lat/lon
362  emis_y = nint((self % lat(i) + 90.0_kind_real) / &
363  ir_pcemis % emis_atlas % gridstep + 1)
364  emis_x = nint((self % lon(i) + 180.0_kind_real) / &
365  ir_pcemis % emis_atlas % gridstep + 1)
366  if (emis_x > ir_pcemis % emis_atlas % nlon) then
367  emis_x = emis_x - ir_pcemis % emis_atlas % nlon
368  end if
369 
370  ! If the atlas is valid at this point, then use it,
371  ! otherwise use PCGuess. NB: missing or sea points are
372  ! flagged as -9.99 in the atlas.
373  if (any(ir_pcemis % emis_atlas % EmisPC(emis_x,emis_y,:) > -9.99_kind_real)) then
374  emisspc(i,:) = ir_pcemis % emis_atlas % EmisPC(emis_x,emis_y,1:nemisspc)
375  else
376  emisspc(i,:) = ir_pcemis % emis_eigen % PCGuess(1:nemisspc)
377  !! Flag invalid atlas points over land as bad surface
378  !IF (Obs % surface(i) == RTland) THEN
379  ! Obs % QCflags(i) = IBSET (Obs % QCflags(i), QC_BadSurface)
380  !END IF
381  END IF
382 
383  ELSE ! If no atlas present, use PCGuess.
384  emisspc(i,:) = ir_pcemis % emis_eigen % PCGuess(1:nemisspc)
385  END IF
386 
387  end do
388 
389  end if
390 
391 end if
392 
393 if (allocated(emisspc)) deallocate(emisspc)
394 
395 end subroutine
396 
397 !------------------------------------------------------------------------------
398 !> Store the 1D-Var analysis variables in obsspace for future assessment
399 !!
400 !! \details Heritage: Ops_SatRad_SetOutput_RTTOV12
401 !!
402 !! \author Met Office
403 !!
404 !! \date 02/09/2020: Created
405 !!
406 subroutine ufo_rttovonedvarcheck_obs_output(self, obsdb, prof_index, vars, nchans)
407 
408 implicit none
409 
410 ! subroutine arguments:
411 class(ufo_rttovonedvarcheck_obs), intent(inout) :: self !< observation metadata type
412 type(c_ptr), value, intent(in) :: obsdb !< pointer to the observation space
413 type(ufo_rttovonedvarcheck_profindex), intent(in) :: prof_index !< index to elements in the profile
414 type(oops_variables), intent(in) :: vars !< channels for 1D-Var
415 integer, intent(in) :: nchans ! number of channels in the obsspace
416 
417 ! local variables
418 integer :: jvar ! counter
419 integer :: nobs ! number of observations to be written to database
420 character(len=max_string) :: var
421 real(kind_real), allocatable :: surface_pressure(:)
422 real(kind_real) :: missing
423 
424 missing = missing_value(missing)
425 
426 ! Put QC flags and retrieved BT's back in database
427 do jvar = 1, nchans
428  var = vars % variable(jvar)
429  call obsspace_put_db(obsdb, "FortranQC", trim(var), self % QCflags(jvar,:))
430  call obsspace_put_db(obsdb, "OneDVar", trim(var), self % output_BT(jvar,:))
431  call obsspace_put_db(obsdb, "OneDVarBack", trim(var), self % background_BT(jvar,:))
432 end do
433 
434 ! Output Diagnostics
435 call obsspace_put_db(obsdb, "OneDVar", "FinalCost", self % final_cost(:))
436 nobs = size(self % final_cost(:))
437 call obsspace_put_db(obsdb, "OneDVar", "n_iterations", self % niter(:))
438 
439 if (self % Store1DVarLWP) then
440  call obsspace_put_db(obsdb, "OneDVar", "LWP", self % LWP(:))
441 end if
442 
443 !--
444 ! Output Retrieved profiles into ObsSpace
445 !--
446 ! 1) Temperature levels
447 ! 2) Humidity levels: convert q to rh (%)
448 ! 3) Ozone levels - no planned implementation
449 ! 4) Cloud Liquid Water from qtotal
450 ! 4b) Cloud Ice Water if rttovscat is activated from qtotal
451 
452 !--
453 ! 5) Surface pressure
454 !--
455 if (prof_index % pstar > 0) THEN
456  allocate(surface_pressure(nobs))
457  surface_pressure(:) = self % output_profile(prof_index % pstar, :)
458  where (surface_pressure /= missing)
459  surface_pressure = surface_pressure / pa_to_hpa ! hPa to Pa
460  end where
461  call obsspace_put_db(obsdb, "OneDVar", trim(var_ps), surface_pressure)
462  deallocate(surface_pressure)
463 end if
464 
465 !--
466 ! 6) Surface temperature
467 !--
468 if (prof_index % t2 > 0) THEN
469  call obsspace_put_db(obsdb, "OneDVar", trim(var_sfc_t2m), &
470  self % output_profile(prof_index % t2, :))
471 end if
472 
473 !--
474 ! 7) Surface humidity
475 !--
476 
477 !--
478 ! 8) Surface Windspeed
479 !--
480 ! Windspeed retrieval is directionless, i.e., there are no separate u and v
481 ! components.
482 if (prof_index % windspeed > 0) then
483  call obsspace_put_db(obsdb, "OneDVar", "surface_wind_speed", &
484  self % output_profile(prof_index % windspeed, :))
485 end if
486 
487 !--
488 ! 9) Skin temperature
489 !--
490 if (prof_index % tstar > 0) then
491  call obsspace_put_db(obsdb, "OneDVar", trim(var_sfc_tskin), &
492  self % output_profile(prof_index % tstar, :))
493 end if
494 
495 !--
496 ! 10) Total ozone - no planned implementation
497 ! 11) Cloud top pressure
498 ! 12) Cloud fraction
499 ! 14) IWP only meaningful is mwscattswitch is activated which means model levels too....
500 ! 15) Microwave emissivity
501 ! 16/17) QC related not being ported.
502 ! 18) Cloud type
503 ! 19) Channel information
504 ! 20) RTTOVSCATT cloud profiles
505 ! 21) IR cloud profiles
506 
507 end subroutine
508 
509 !-------------------------------------------------------------------------------
510 
real(kind_real), parameter, public zero
real(kind_real), parameter, public pa_to_hpa
subroutine, public ufo_geovals_get_var(self, varname, geoval)
Fortran module constants used throughout the rttovonedvarcheck filter.
integer, parameter, public rtsea
integer for sea surface type
integer, parameter, public rtland
integer for land surface type
integer, parameter, public rtice
integer for seaice surface type
Fortran module which contains the observation data for a all the obs space.
subroutine ufo_rttovonedvarcheck_obs_delete(self)
Delete the observation object.
subroutine ufo_rttovonedvarcheck_obs_setup(self, config, nprofelements, geovals, vars, ir_pcemis)
Initialize observation object.
subroutine ufo_rttovonedvarcheck_obs_output(self, obsdb, prof_index, vars, nchans)
Store the 1D-Var analysis variables in obsspace for future assessment.
subroutine ufo_rttovonedvarcheck_obs_initiremiss(self, nchans, ir_pcemis)
Initialize the infrared emissivity array.
subroutine ufo_rttovonedvarcheck_obs_initmwemiss(self, config)
Initialize the microwave emissivity array.
Fortran module which contains the methods for Infrared Principal Component Emissivity The science can...
Fortran module containing profile index.
Fortran module containing main type, setup and utilities for the main rttovonedvarcheck object.
integer function, public ufo_vars_getindex(vars, varname)
character(len=maxvarlen), parameter, public var_sfc_tskin
character(len=maxvarlen), parameter, public var_ps
character(len=maxvarlen), parameter, public var_sfc_t2m
type to hold interpolated field for one variable, one observation
type to hold interpolated fields required by the obs operators