UFO
ufo_aodext_tlad_mod.F90
Go to the documentation of this file.
1 ! (C) Copyright 2021 UCAR
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 for aodext tl/ad observation operator
7 
9 
10  use kinds
11  use missing_values_mod
12  use oops_variables_mod
13  use ufo_vars_mod
14 
15  implicit none
16  private
17 
18  type, public :: ufo_aodext_tlad
19  private
20  type(oops_variables), public :: obsvars
21  type(oops_variables), public :: geovars
22  real(kind_real), allocatable :: airdens(:,:) !(km, nlocs) airdens profile interp at obs loc [kg.m-3]
23  real(kind_real), allocatable :: delp(:,:) !(km, nlocs) air pressure thickness profiles at obs loc[Pa]
24  real(kind_real), allocatable :: ext(:,:,:) !(km, nlocs, nch) extinction profiles at obs loc [km-1]
25  real(kind_real), allocatable :: obss_wavelength(:)!(nvars) observed AOD wavelengths [nm]
26  real(kind_real), public, allocatable :: wavelength(:)!(nch) background extinction profile's wavelengths[nm]
27  integer :: nlayers, nprofiles
28 
29  contains
30  procedure :: setup => ufo_aodext_tlad_setup
31  procedure :: settraj => ufo_aodext_tlad_settraj
32  procedure :: simobs_tl => ufo_aodext_simobs_tl
33  procedure :: simobs_ad => ufo_aodext_simobs_ad
34  final :: destructor
35  end type ufo_aodext_tlad
36 
37 !> Default variables required from model
38  character(len=maxvarlen), dimension(3), parameter :: extdefault = (/var_ext1, var_ext2, var_ext3/)
39 ! needs at least 2 profiles of extinction in bkg for angstrom law
40 
41 contains
42 
43 !----------------
44 integer function b_channel( bracket, nprofiles, bkg_wavelengths, obs_wavelength ) ! given a wavelength, return the index of bracket wavelengths
45 implicit none
46 integer, intent(in) :: bracket ! bracket index (1 or 2)
47 integer, intent(in) :: nprofiles ! number of bkg profiles
48 real(kind_real), dimension(nprofiles) :: bkg_wavelengths ! array of bkg wavelengths
49 real(kind_real) :: obs_wavelength ! observed wavelength
50 
51 character(len=maxvarlen) :: err_msg
52 integer :: j
53  j = 1
54  do while(j < nprofiles)
55  if(obs_wavelength .GE. bkg_wavelengths(j) .and.&
56  obs_wavelength .LT. bkg_wavelengths(j+1)) then
57  if (bracket == 1) then
58  b_channel = j
59  else if (bracket == 2) then
60  b_channel = j + 1
61  else
62  write(err_msg,*) "ufo_aodext_mod: function b_channel: bracket index should be 1 (lower) or 2 (upper)"
63  call abor1_ftn(err_msg)
64  endif
65  j = j+ 1
66  else if(obs_wavelength .GT. bkg_wavelengths(j) .and.&
67  obs_wavelength .LE. bkg_wavelengths(j+1)) then
68  if (bracket == 1) then
69  b_channel = j
70  else if (bracket == 2) then
71  b_channel = j + 1
72  else
73  write(err_msg,*) "ufo_aodext_mod: function b_channel: bracket index should be 1 (lower) or 2 (upper)"
74  call abor1_ftn(err_msg)
75  endif
76  j = j +1
77  endif
78  j = j + 1
79  enddo
80  return
81 end function b_channel
82 !-----------------------------------
83 subroutine ufo_aodext_tlad_setup(self, f_conf)
84 use fckit_configuration_module, only: fckit_configuration
85 implicit none
86 class(ufo_aodext_tlad), intent(inout) :: self
87 type(fckit_configuration), intent(in) :: f_conf
88 
89 !Locals
90 integer n
91 character(len=maxvarlen) :: err_msg
92 
93  ! setup input variables varin (updated model variables)
94  call f_conf%get_or_die("nprofiles", self%nprofiles)
95 
96  if (self%nprofiles < 2 .or. self%nprofiles > 3) then
97  write(err_msg,*) 'ufo_aodext_tlad_setup: number of extinction profiles must be 2 or 3'
98  call abor1_ftn(err_msg)
99  endif
100 
101  do n = 1, self%nprofiles
102  call self%geovars%push_back(extdefault(n))
103  enddo
104 
105  ! Wavelengths for bkg extinction profiles, specified in yaml
106  allocate(self%wavelength(self%nprofiles))
107  call f_conf%get_or_die("bkg_wavelengths", self%wavelength)
108  !Check that the wavelengths are in an ascending order
109  n = 1
110  do while (n < self%nprofiles)
111  if(self%wavelength(n) > self%wavelength(n+1)) then
112  write(err_msg,*) ' ufo_aodext_tlad_setup: bkg wavelengths should be in an ascending order'
113  call abor1_ftn(err_msg)
114  endif
115  n = n + 1
116  enddo
117 end subroutine ufo_aodext_tlad_setup
118 
119 ! ------------------------------------------------------------------------------
120 subroutine destructor(self)
121 implicit none
122 type(ufo_aodext_tlad), intent(inout) :: self
123 
124  if (allocated(self%airdens)) deallocate(self%airdens)
125  if (allocated(self%delp)) deallocate(self%delp)
126  if (allocated(self%ext)) deallocate(self%ext)
127  if (allocated(self%obss_wavelength)) deallocate(self%obss_wavelength)
128  if (allocated(self%wavelength)) deallocate(self%wavelength)
129 
130 end subroutine destructor
131 
132 ! ------------------------------------------------------------------------------
133 subroutine ufo_aodext_tlad_settraj(self, geovals, obss)
134 use iso_c_binding
136 use obsspace_mod
137 implicit none
138 class(ufo_aodext_tlad), intent(inout) :: self
139 type(ufo_geovals), intent(in) :: geovals
140 type(c_ptr), value, intent(in) :: obss
141 
142 !locals
143  character(len=MAXVARLEN) :: geovar
144  integer :: nch, nvars, nlocs
145 
146 type(ufo_geoval), pointer :: ext_profile
147 type(ufo_geoval), pointer :: delp_profile
148 type(ufo_geoval), pointer :: airdens_profile
149 
150  ! Get number of locations
151  nlocs = obsspace_get_nlocs(obss)
152 
153  ! Get the number of obs type, for AOD it is the number of wavelengths
154  nvars = self%obsvars%nvars()
155 
156  ! Get airdens, delp, ext and number of layers from geovals
157  call ufo_geovals_get_var(geovals, var_delp, delp_profile)
158  self%nlayers = delp_profile%nval ! number of model layers
159 
160  allocate(self%delp(self%nlayers,nlocs))
161  self%delp = delp_profile%vals
162 
163  call ufo_geovals_get_var(geovals, var_airdens, airdens_profile)
164  allocate(self%airdens(self%nlayers,nlocs))
165  self%airdens = airdens_profile%vals
166 
167  allocate(self%ext(self%nlayers, nlocs, self%nprofiles))
168  do nch = 1, self%nprofiles
169  geovar = self%geovars%variable(nch)
170  call ufo_geovals_get_var(geovals, geovar, ext_profile)
171  self%ext(:,:,nch) = ext_profile%vals
172  enddo
173 
174  ! Get some metadata from obsspace, observed AOD wavelengths
175  ! -----------------------
176  allocate(self%obss_wavelength(nvars))
177  call obsspace_get_db(obss,"VarMetaData", "obs_wavelength", self%obss_wavelength)
178 
179 end subroutine ufo_aodext_tlad_settraj
180 
181 ! ------------------------------------------------------------------------------
182 ! Input geovals parameter represents dx for tangent linear model
183 subroutine ufo_aodext_simobs_tl(self, geovals, obss, nvars, nlocs, hofx)
184 use iso_c_binding
185 use kinds
187 use obsspace_mod
188 use ufo_constants_mod, only: grav, zero
189 
190 implicit none
191 class(ufo_aodext_tlad), intent(in) :: self
192 type(ufo_geovals), intent(in) :: geovals
193 integer, intent(in) :: nvars, nlocs
194 real(c_double), intent(inout) :: hofx(nvars, nlocs)
195 type(c_ptr), value, intent(in) :: obss
196 
197 !locals
198 real(kind_real), dimension(:,:,:), allocatable :: ext_tl !(km, nlocs, nch) extinction profiles perturbations
199 real(kind_real), dimension(:,:), allocatable :: aod_bkg !(nlocs, nch) AOD computed from modeled ext profiles
200 real(kind_real), dimension(:,:), allocatable :: aod_bkg_tl !(nlocs, nch) AOD tangent linear
201 real(kind_real), dimension(:,:), allocatable :: angstrom !(nvars, nlocs) Angstrom coefficient calculated from bkg AOD and wavelength
202 real(kind_real), dimension(:,:), allocatable :: angstrom_tl !(nvars, nlocs) Angstrom coefficient tangent linear
203 real(kind_real), dimension(:,:), allocatable :: logm !(nvars, nlocs) denominator of Angstrom parameter
204 
205 real(kind_real) :: arg1, arg1_tl, tmp, coef, coef_tl
206 
207 integer :: nch, nobs, ic, i, j, km
208 
209 type(ufo_geoval), pointer :: ext_profile
210 character(len=MAXVARLEN) :: geovar
211 real(c_double) :: missing
212 
213  ! Get extinction profile perturbations interpolated at obs loc
214  allocate(ext_tl(self%nlayers, nlocs, self%nprofiles))
215 
216  do nch = 1, self%nprofiles
217  geovar = self%geovars%variable(nch)
218  call ufo_geovals_get_var(geovals, geovar, ext_profile)
219  ext_tl(:,:,nch) = ext_profile%vals
220  enddo
221 
222  allocate(aod_bkg(nlocs, self%nprofiles))
223  allocate(aod_bkg_tl(nlocs, self%nprofiles))
224  aod_bkg = zero
225  aod_bkg_tl = zero
226 
227  do nch = 1, self%nprofiles
228  do nobs = 1, nlocs
229  do km = 1, self%nlayers
230 
231  aod_bkg(nobs,nch) = aod_bkg(nobs, nch) + (self%ext(km,nobs,nch) * self%delp(km,nobs)&
232  * 1./(self%airdens(km,nobs) * grav*1000.0_kind_real))
233  aod_bkg_tl(nobs,nch) = aod_bkg_tl(nobs, nch) + (ext_tl(km,nobs,nch) * self%delp(km,nobs)&
234  * 1./(self%airdens(km,nobs) * grav*1000.0_kind_real))
235 
236  enddo
237  enddo
238  enddo
239 
240  allocate(angstrom_tl(nvars,nlocs))
241  allocate(angstrom(nvars,nlocs))
242  allocate(logm(nvars,nlocs))
243 
244  missing =missing_value(missing)
245  hofx = zero
246  angstrom_tl = zero
247 
248  do nobs = 1, nlocs
249  do ic = 1, nvars
250 
251  if(self%obss_wavelength(ic) < self%wavelength(1) .or.&
252  self%obss_wavelength(ic) > self%wavelength(self%nprofiles)) then
253  hofx(ic, nobs) = missing
254  else
255  i = b_channel(1, self%nprofiles, self%wavelength, self%obss_wavelength(ic))
256  j = b_channel(2, self%nprofiles, self%wavelength, self%obss_wavelength(ic))
257 
258  logm(ic, nobs) = log(self%wavelength(i)/self%wavelength(j))
259  tmp = aod_bkg(nobs, i) / aod_bkg(nobs, j)
260  arg1_tl = (aod_bkg_tl(nobs, i)-tmp*aod_bkg_tl(nobs,j))/aod_bkg(nobs,j)
261  arg1 = tmp
262 
263  angstrom_tl(ic, nobs) = arg1_tl/(logm(ic, nobs) * arg1)
264  angstrom(ic, nobs) = log(arg1)/logm(ic,nobs)
265 
266  coef = (self%wavelength(i)/self%wavelength(j))**angstrom(ic, nobs)
267  coef_tl = coef * log(self%wavelength(i)/self%wavelength(j))*angstrom_tl(ic,nobs)
268  hofx(ic, nobs) = coef * aod_bkg_tl(nobs, i) + aod_bkg(nobs, i)* coef_tl
269  endif
270  enddo
271  enddo
272  deallocate( angstrom_tl, angstrom, aod_bkg, aod_bkg_tl, ext_tl, logm)
273 
274 end subroutine ufo_aodext_simobs_tl
275 
276 ! ------------------------------------------------------------------------------
277 subroutine ufo_aodext_simobs_ad(self, geovals, obss, nvars, nlocs, hofx)
278 use kinds
279 use iso_c_binding
281 use obsspace_mod
282 use ufo_constants_mod, only: grav, zero
283 
284 implicit none
285 class(ufo_aodext_tlad), intent(in) :: self
286 type(ufo_geovals), intent(inout) :: geovals
287 integer, intent(in) :: nvars, nlocs
288 real(c_double), intent(in) :: hofx(nvars, nlocs)
289 type(c_ptr), value, intent(in) :: obss
290 
291 
292 real(kind_real), dimension(:,:,:), allocatable :: ext_ad !(km, nlocs, nch) Adjoint of ext profiles at obs loc
293 real(kind_real), dimension(:,:), allocatable :: aod_bkg !(nlocs, nch) AOD computed from modeled ext profiles
294 real(kind_real), dimension(:,:), allocatable :: aod_bkg_ad !(nlocs, nch) Adjoint of AOD
295 real(kind_real), dimension(:,:), allocatable :: angstrom !(nvars, nlocs) Angstrom coefficient
296 real(kind_real), dimension(:,:), allocatable :: angstrom_ad!(nvars, nlocs) Adjoint of Angstrom coefficient
297 real(kind_real), dimension(:,:), allocatable :: logm !(nvars, nlocs) Denominator of Angstrom coeff
298 
299 real(kind_real) :: arg1, arg1_ad, tmp, tmp_ad, coef, coef_ad
300 
301 integer :: nch, nobs, km, ic, i, j
302 
303 character(len=MAXVARLEN) :: geovar
304 type(ufo_geoval), pointer :: ext_profile
305 real(c_double) :: missing
306 
307  allocate(aod_bkg(nlocs, self%nprofiles))
308  aod_bkg = zero
309 
310  do nch = 1, self%nprofiles
311  do nobs = 1, nlocs
312  do km = 1, self%nlayers
313 
314  aod_bkg(nobs,nch) = aod_bkg(nobs, nch) + (self%ext(km,nobs,nch) * self%delp(km,nobs) &
315  * 1./(self%airdens(km,nobs)*grav*1000.0_kind_real))
316 
317  enddo
318  enddo
319  enddo
320 
321  missing = missing_value(missing)
322  allocate(angstrom(nvars,nlocs))
323  allocate(logm(nvars,nlocs))
324  do nobs = 1, nlocs
325  do ic = 1, nvars
326 
327  if(self%obss_wavelength(ic) < self%wavelength(1) .or.&
328  self%obss_wavelength(ic) > self%wavelength(self%nprofiles)) then
329  angstrom(ic, nobs) = missing
330  else
331 
332  i = b_channel(1, self%nprofiles, self%wavelength, self%obss_wavelength(ic))
333  j = b_channel(2, self%nprofiles, self%wavelength, self%obss_wavelength(ic))
334 
335  logm(ic, nobs) = log(self%wavelength(i)/self%wavelength(j))
336 
337  angstrom(ic, nobs) = log(aod_bkg(nobs,i)/aod_bkg(nobs,j))/logm(ic, nobs)
338  endif
339  enddo
340  enddo
341 
342  allocate(aod_bkg_ad(nlocs, self%nprofiles))
343  allocate(angstrom_ad(nvars,nlocs))
344  aod_bkg_ad = zero
345  angstrom_ad = zero
346 
347  do nobs = nlocs, 1, -1
348  do ic = nvars, 1, -1
349 
350  if( hofx(ic,nobs)/=missing.and.angstrom(ic,nobs)/=missing )then
351 
352  i = b_channel(1, self%nprofiles, self%wavelength, self%obss_wavelength(ic))
353  j = b_channel(2, self%nprofiles, self%wavelength, self%obss_wavelength(ic))
354 
355  coef = (self%wavelength(i)/self%wavelength(j))**angstrom(ic, nobs)
356  aod_bkg_ad(nobs, i) = aod_bkg_ad(nobs, i) + coef * hofx(ic, nobs)
357 
358  angstrom_ad(ic, nobs) = angstrom_ad(ic,nobs) + coef * log(self%wavelength(i)/self%wavelength(j)) &
359  * aod_bkg(nobs, i) * hofx(ic, nobs)
360 
361  j = b_channel(2, self%nprofiles, self%wavelength, self%obss_wavelength(ic))
362 
363  tmp = aod_bkg(nobs, i) / aod_bkg(nobs, j)
364  tmp_ad = angstrom_ad(ic, nobs)/(aod_bkg(nobs,j)*tmp*logm(ic, nobs))
365  angstrom_ad = zero
366  aod_bkg_ad(nobs, i) = aod_bkg_ad(nobs,i) + tmp_ad
367  aod_bkg_ad(nobs, j) = aod_bkg_ad(nobs,j) - tmp * tmp_ad
368  endif
369  enddo
370  enddo
371 
372  allocate(ext_ad(self%nlayers, nlocs, self%nprofiles))
373  ext_ad = zero
374 
375  do nch = self%nprofiles, 1, -1
376  do nobs = nlocs, 1, -1
377  do km = self%nlayers, 1, -1
378  ext_ad(km, nobs, nch) = ext_ad(km, nobs, nch) + self%delp(km, nobs) &
379  * aod_bkg_ad(nobs, nch) * 1./(self%airdens(km, nobs) * grav * 1000.)
380  enddo
381  enddo
382  enddo
383 
384  !Get pointer to ext profiles in geovals and put adjoint of extinction profiles into geovals
385  do nch = self%nprofiles, 1, -1
386 
387  geovar = self%geovars%variable(nch)
388  call ufo_geovals_get_var(geovals, geovar, ext_profile)
389  ext_profile%vals(:,:) = ext_ad(:,:,nch)
390 
391  enddo
392  deallocate(angstrom_ad, angstrom, aod_bkg_ad, aod_bkg, ext_ad)
393 
394 end subroutine ufo_aodext_simobs_ad
395 
396 ! ------------------------------------------------------------------------------
397 
398 end module ufo_aodext_tlad_mod
Fortran module for aodext tl/ad observation operator.
character(len=maxvarlen), dimension(3), parameter extdefault
Default variables required from model.
integer function b_channel(bracket, nprofiles, bkg_wavelengths, obs_wavelength)
subroutine ufo_aodext_simobs_ad(self, geovals, obss, nvars, nlocs, hofx)
subroutine destructor(self)
subroutine ufo_aodext_tlad_settraj(self, geovals, obss)
subroutine ufo_aodext_tlad_setup(self, f_conf)
subroutine ufo_aodext_simobs_tl(self, geovals, obss, nvars, nlocs, hofx)
real(kind_real), parameter, public grav
real(kind_real), parameter, public zero
subroutine, public ufo_geovals_get_var(self, varname, geoval)
character(len=maxvarlen), parameter, public var_ext2
character(len=maxvarlen), parameter, public var_airdens
character(len=maxvarlen), parameter, public var_ext3
character(len=maxvarlen), parameter, public var_delp
character(len=maxvarlen), parameter, public var_ext1
type to hold interpolated field for one variable, one observation
type to hold interpolated fields required by the obs operators