UFO
ufo_avgkernel_mod.F90
Go to the documentation of this file.
1 ! (C) Copyright 2017-2020 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 averaging kernel observation operator
7 
9 
10  use oops_variables_mod
11  use ufo_vars_mod
12  use missing_values_mod
13  use kinds
14  use iso_c_binding
15 
16  implicit none
17  private
18  integer, parameter :: max_string=800
19 
20 !> Fortran derived type for the observation type
21  type, public :: ufo_avgkernel
22  private
23  type(oops_variables), public :: obsvars
24  type(oops_variables), public :: geovars
25  integer :: nlayers_kernel
26  real(kind_real), allocatable, dimension(:) :: ak_kernel, bk_kernel
27  character(kind=c_char,len=:), allocatable :: obskernelvar, tracervars(:)
28  logical :: troposphere, totalcolumn
29  real(kind_real) :: convert_factor_model, convert_factor_hofx
30  contains
31  procedure :: setup => ufo_avgkernel_setup
32  procedure :: simobs => ufo_avgkernel_simobs
33  final :: destructor
34  end type ufo_avgkernel
35 
36 contains
37 
38 ! ------------------------------------------------------------------------------
39 subroutine ufo_avgkernel_setup(self, f_conf)
40  use fckit_configuration_module, only: fckit_configuration
41  use ufo_constants_mod, only: one
42  implicit none
43  class(ufo_avgkernel), intent(inout) :: self
44  type(fckit_configuration), intent(in) :: f_conf
45  type(fckit_configuration) :: f_confOpts
46  integer :: nlevs_yaml
47  integer :: ivar, nvars
48  character(len=max_string) :: err_msg
49  character(len=:), allocatable :: str_array(:)
50 
51  ! get configuration for the averaging kernel operator
52  call f_conf%get_or_die("obs options",f_confopts)
53 
54  call f_confopts%get_or_die("nlayers_kernel", self%nlayers_kernel)
55  nlevs_yaml = f_confopts%get_size("ak")
56  if (nlevs_yaml /= self%nlayers_kernel+1) then
57  write(err_msg, *) "ufo_avgkernel_setup error: YAML nlayers_kernel != size of ak array"
58  call abor1_ftn(err_msg)
59  end if
60  nlevs_yaml = f_confopts%get_size("bk")
61  if (nlevs_yaml /= self%nlayers_kernel+1) then
62  write(err_msg, *) "ufo_avgkernel_setup error: YAML nlayers_kernel != size of bk array"
63  call abor1_ftn(err_msg)
64  end if
65 
66  ! allocate and read ak/bk for the averaging kernel
67  allocate(self%ak_kernel(nlevs_yaml))
68  allocate(self%bk_kernel(nlevs_yaml))
69  call f_confopts%get_or_die("ak", self%ak_kernel)
70  call f_confopts%get_or_die("bk", self%bk_kernel)
71 
72  ! get variable name from IODA for observation averaging kernel
73  if (.not. f_confopts%get("AvgKernelVar", self%obskernelvar)) then
74  self%obskernelvar = "averaging_kernel" ! default option
75  end if
76 
77  ! get name of geoval/tracer to use from the model
78  nvars = self%obsvars%nvars()
79  call f_confopts%get_or_die("tracer variables", str_array)
80  self%tracervars = str_array
81 
82  ! determine if this is a total column or troposphere calculation
83  if (.not. f_confopts%get("tropospheric column", self%troposphere)) self%troposphere = .false.
84  if (.not. f_confopts%get("total column", self%totalcolumn)) self%totalcolumn = .false.
85 
86  ! both of these cannot be true
87  if (self%troposphere .and. self%totalcolumn) then
88  write(err_msg, *) "ufo_avgkernel_setup error: both tropospheric and total column set to TRUE, only one can be TRUE"
89  call abor1_ftn(err_msg)
90  end if
91 
92  ! do we need a conversion factor, say between ppmv and unity?
93  if (.not. f_confopts%get("model units coeff", self%convert_factor_model)) self%convert_factor_model = one
94  if (.not. f_confopts%get("hofx units coeff", self%convert_factor_hofx)) self%convert_factor_hofx = one
95 
96  ! add variables to geovars that are needed
97  ! specified tracers
98  do ivar = 1, nvars
99  call self%geovars%push_back(self%tracervars(ivar))
100  end do
101  ! surface pressure
102  call self%geovars%push_back(var_ps)
103  ! column pressure both layer and interface
104  call self%geovars%push_back(var_prs)
105  call self%geovars%push_back(var_prsi)
106  ! need air temperature for number density conversion
107  call self%geovars%push_back(var_ts)
108  ! need geopotential height on levels to convert units
109  call self%geovars%push_back(var_zi)
110 
111 end subroutine ufo_avgkernel_setup
112 
113 ! ------------------------------------------------------------------------------
114 subroutine destructor(self)
115  implicit none
116  type(ufo_avgkernel), intent(inout) :: self
117 
118 end subroutine destructor
119 
120 ! ------------------------------------------------------------------------------
121 ! averaging kernel observation operator
122 subroutine ufo_avgkernel_simobs(self, geovals_in, obss, nvars, nlocs, hofx)
123  use kinds
126  use ufo_constants_mod, only: half
128  use iso_c_binding
129  use obsspace_mod
130  implicit none
131  class(ufo_avgkernel), intent(in) :: self
132  integer, intent(in) :: nvars, nlocs
133  type(ufo_geovals), intent(in) :: geovals_in
134  real(c_double), intent(inout) :: hofx(nvars, nlocs)
135  type(c_ptr), value, intent(in) :: obss
136 
137  ! Local variables
138  type(ufo_geoval), pointer :: prsi, prsl, psfc, temp, phii, tracer
139  integer :: ivar, iobs, ilev
140  character(len=MAXVARLEN) :: geovar, varstring
141  character(len=4) :: levstr
142  real(kind_real), allocatable, dimension(:,:) :: avgkernel_obs, prsl_obs, prsi_obs
143  real(kind_real), allocatable, dimension(:) :: airmass_tot, airmass_trop
144  integer, allocatable, dimension(:) :: troplev_obs
145  real(kind_real) :: hofx_tmp
146  type(ufo_geovals) :: geovals
147  real(c_double) :: missing
148 
149  missing = missing_value(missing)
150 
151  ! get geovals of atmospheric pressure
152  call ufo_geovals_copy(geovals_in, geovals) ! dont want to change geovals_in
153  call ufo_geovals_reorderzdir(geovals, self%geovars%variable(nvars+2), "bottom2top")
154  call ufo_geovals_get_var(geovals, self%geovars%variable(nvars+1), psfc)
155  call ufo_geovals_get_var(geovals, self%geovars%variable(nvars+2), prsl)
156  call ufo_geovals_get_var(geovals, self%geovars%variable(nvars+3), prsi)
157  call ufo_geovals_get_var(geovals, self%geovars%variable(nvars+4), temp)
158  call ufo_geovals_get_var(geovals, self%geovars%variable(nvars+5), phii)
159 
160  ! grab necesary metadata from IODA
161  ! get observation averaging kernel
162  ! once 2D arrays are allowed, rewrite/simplify this part
163  allocate(avgkernel_obs(self%nlayers_kernel, nlocs))
164  do ilev = 1, self%nlayers_kernel
165  write(levstr, fmt = "(I3)") ilev
166  levstr = adjustl(levstr)
167  varstring = trim(self%obskernelvar)//"_"//trim(levstr)
168  call obsspace_get_db(obss, "MetaData", trim(varstring), avgkernel_obs(ilev, :))
169  end do
170 
171  ! compute prsl_obs/prsi_obs from ak/bk/psfc
172  allocate(prsl_obs(self%nlayers_kernel, nlocs))
173  allocate(prsi_obs(self%nlayers_kernel+1, nlocs))
174  ! prsi_obs calculation
175  do ilev = 1, self%nlayers_kernel+1
176  prsi_obs(ilev,:) = self%ak_kernel(ilev) + self%bk_kernel(ilev) * psfc%vals(1,:)
177  end do
178  ! using simple averaging for now for prsl, can use more complex way later
179  do ilev = 1, self%nlayers_kernel
180  prsl_obs(ilev,:) = (prsi_obs(ilev,:) + prsi_obs(ilev+1,:)) * half
181  end do
182 
183  if (self%troposphere) then
184  allocate(troplev_obs(nlocs))
185  allocate(airmass_trop(nlocs))
186  allocate(airmass_tot(nlocs))
187  call obsspace_get_db(obss, "MetaData", "troposphere_layer_index", troplev_obs)
188  call obsspace_get_db(obss, "MetaData", "air_mass_factor_troposphere", airmass_trop)
189  call obsspace_get_db(obss, "MetaData", "air_mass_factor_total", airmass_tot)
190  end if
191 
192  ! loop through all variables
193  do ivar = 1, nvars
194  geovar = self%tracervars(ivar)
195  call ufo_geovals_get_var(geovals, geovar, tracer)
196  do iobs = 1, nlocs
197  if (avgkernel_obs(1,iobs) /= missing) then ! take care of missing obs
198  if (self%troposphere) then
199  call simulate_column_ob(self%nlayers_kernel, tracer%nval, avgkernel_obs(:,iobs), &
200  prsi_obs(:,iobs), prsi%vals(:,iobs), &
201  prsl_obs(:,iobs), prsl%vals(:,iobs), temp%vals(:,iobs),&
202  phii%vals(:,iobs), tracer%vals(:,iobs)*self%convert_factor_model, &
203  hofx_tmp, troplev_obs(iobs), airmass_tot(iobs), airmass_trop(iobs))
204  hofx(ivar,iobs) = hofx_tmp * self%convert_factor_hofx
205  else if (self%totalcolumn) then
206  call simulate_column_ob(self%nlayers_kernel, tracer%nval, avgkernel_obs(:,iobs), &
207  prsi_obs(:,iobs), prsi%vals(:,iobs), &
208  prsl_obs(:,iobs), prsl%vals(:,iobs), temp%vals(:,iobs),&
209  phii%vals(:,iobs), tracer%vals(:,iobs)*self%convert_factor_model, &
210  hofx_tmp)
211  hofx(ivar,iobs) = hofx_tmp * self%convert_factor_hofx
212  end if
213  else
214  hofx(ivar,iobs) = missing ! default if we are unable to compute averaging kernel
215  end if
216  end do
217  end do
218 
219 
220 end subroutine ufo_avgkernel_simobs
221 
222 
223 ! ------------------------------------------------------------------------------
224 
225 end module ufo_avgkernel_mod
subroutine simulate_column_ob(nlayers_obs, nlayers_model, avgkernel_obs, prsi_obs, prsi_model, prsl_obs, prsl_model, temp_model, zi_model, profile_model, hofx, troplev_obs, airmass_tot, airmass_trop)
Fortran module for averaging kernel observation operator.
subroutine destructor(self)
subroutine ufo_avgkernel_simobs(self, geovals_in, obss, nvars, nlocs, hofx)
integer, parameter max_string
subroutine ufo_avgkernel_setup(self, f_conf)
real(kind_real), parameter, public one
real(kind_real), parameter, public half
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)
character(len=maxvarlen), parameter, public var_prsi
character(len=maxvarlen), parameter, public var_prs
character(len=maxvarlen), parameter, public var_zi
character(len=maxvarlen), parameter, public var_ts
character(len=maxvarlen), parameter, public var_ps
Fortran derived type for the observation type.
type to hold interpolated field for one variable, one observation
type to hold interpolated fields required by the obs operators