UFO
SatwindIndivErrors.cc
Go to the documentation of this file.
1 /* -----------------------------------------------------------------------------
2  * (C) British Crown Copyright 2020 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 
10 
11 #include <cmath>
12 #include <iostream>
13 #include <memory>
14 #include <string>
15 
16 #include "ioda/distribution/Accumulator.h"
17 #include "ioda/ObsDataVector.h"
18 #include "ioda/ObsSpace.h"
19 #include "oops/util/missingValues.h"
20 #include "ufo/filters/Variable.h"
21 
22 #include "eckit/exception/Exceptions.h"
23 #include "oops/util/Logger.h"
24 
25 namespace ufo {
26 
28 
29 // -----------------------------------------------------------------------------
30 
31 SatwindIndivErrors::SatwindIndivErrors(const eckit::LocalConfiguration & conf)
32  : invars_() {
33  // Check options
34  options_.deserialize(conf);
35 
36  // Get parameters from options.
37  std::string const profile = options_.profile.value();
38  std::string const vcoord = options_.vcoord.value();
39 
40  // Include list of required data from ObsSpace
41  invars_ += Variable(vcoord+"@MetaData");
42  invars_ += Variable(profile+"@hofx");
45 
46  // Include list of required data from GeoVaLs
47  invars_ += Variable(vcoord+"@GeoVaLs");
48  invars_ += Variable(profile+"@GeoVaLs");
49 }
50 
51 // -----------------------------------------------------------------------------
52 
54 
55 // -----------------------------------------------------------------------------
56 /*! \brief Function to calculate situation dependent observation errors for satwinds
57  *
58  * \details The errors are calculated by combining an estimate of the error in
59  * the vector, with an estimate of the error in vector due to an error in the
60  * pressure (i.e. height assignment). The latter will depend on the AMV height
61  * error and the background vertical wind shear. In the future we aim to use
62  * estimates of the vector error and height error from the data producers.
63  *
64  * Currently the vector error estimate \f$E_{vector}\f$
65  * is based on the quality index (QI) (ideally model-independent) and is calculated as:
66  * \f[
67  * E_{vector} = \text{EuMult}\left(QI \times 0.01\right) + \text{EuAdd}
68  * \f]
69  * The defaults are EuMult=-5.0 and EuAdd=7.5, which gives errors in the range
70  * from 2.5 m/s (at QI=100) to 4.5 m/s (at QI=60).
71  *
72  * The height error estimate \f$E_{p}\f$ is currently set by a look up table
73  * (dependent on e.g. satellite, channel, pressure level).
74  * The values are based on the RMS of model best-fit pressure minus AMV observed
75  * pressure distributions. These are calculated from several months of data.
76  *
77  * The error in vector due to the height error, \f$E_{vpress}\f$, is calculated as:
78  * \f[
79  * E_{vpress} = \sqrt{\frac{\sum{W_{i}\left(v_{i}-v_{n}\right)^{2}}}
80  * {\sum{W_{i}}}
81  * }
82  * \f]
83  * where
84  * \f[
85  * W_{i} = \exp\left(- \left( \left( p_{i} - p_{n} \right)^2 / 2E_{p}^2 \right)\right) \times dP_{i}
86  * \f]
87  * i = model level \n
88  * N = number of model levels (sum over) \n
89  * \f$v_i\f$ = wind component on model level \n
90  * \f$v_n\f$ = wind component at observation location \n
91  * \f$p_i\f$ = pressure on model level \n
92  * \f$p_n\f$ = pressure at observation location \n
93  * \f$E_p\f$ = error in height assignment \n
94  * \f$dP_i\f$ = layer thickness \n
95  *
96  * This is calculated separately for the u and v components giving separate
97  * u and v component errors.
98  * \f[
99  * E_{total}^2 = E_{vector}^2 + E_{vpress}^2
100  * \f]
101  *
102  * \author J.Cotton (Met Office)
103  *
104  * \date 05/01/2021: Created
105  */
106 
108  ioda::ObsDataVector<float> & obserr) const {
109  // Get obs space
110  auto & obsdb = in.obsspace();
111 
112  // Get parameters from options
113  float const eu_add = options_.eu_add.value();
114  float const eu_mult = options_.eu_mult.value();
115  float const min_press = options_.min_press.value(); // Pa
116  std::string const profile = options_.profile.value();
117  std::string const vcoord = options_.vcoord.value();
118  oops::Log::debug() << "Wind profile for calculating observation errors is "
119  << profile << std::endl;
120  oops::Log::debug() << "Vertical coordinate is " << vcoord << std::endl;
121 
122  std::ostringstream errString;
123 
124  // check profile name matches one of eastward_wind or northward_wind
125  if ( profile != "eastward_wind" && profile != "northward_wind" ) {
126  errString << "Wind component must be one of eastward_wind or northward_wind" << std::endl;
127  throw eckit::BadValue(errString.str());
128  }
129 
130  // check vcoord name matches one of air_pressure or air_pressure_levels
131  if ( vcoord != "air_pressure" && vcoord != "air_pressure_levels" ) {
132  errString << "Vertical coordinate must be air_pressure or air_pressure_levels" << std::endl;
133  throw eckit::BadValue(errString.str());
134  }
135 
136  // Get dimensions
137  const size_t nlocs = in.nlocs();
138  const size_t nlevs = in.nlevs(Variable(profile+"@GeoVaLs"));
139 
140  // local variables
141  float const missing = util::missingValue(missing);
142 
143  // Get variables from ObsSpace if present. If not, throw an exception
144  std::vector<float> ob_p;
145  std::vector<float> bg_windcomponent;
146  std::vector<float> pressure_error;
147  std::vector<float> ob_qi;
148  in.get(Variable(vcoord+"@MetaData"), ob_p);
149  in.get(Variable(profile+"@hofx"), bg_windcomponent);
150  in.get(options_.pressure_error, pressure_error);
151  in.get(options_.quality_index, ob_qi);
152 
153  // Get variables from GeoVals
154  // Get pressure [Pa] (nlevs, nlocs)
155  std::vector<std::vector<float>> cx_p(nlevs, std::vector<float>(nlocs));
156  for (size_t ilev = 0; ilev < nlevs; ++ilev) {
157  in.get(Variable(vcoord+"@GeoVaLs"), ilev, cx_p[ilev]);
158  }
159 
160  // Get wind component (nlevs, nlocs)
161  std::vector<std::vector<float>> cx_windcomponent(nlevs, std::vector<float>(nlocs));
162  for (size_t ilev = 0; ilev < nlevs; ++ilev) {
163  in.get(Variable(profile+"@GeoVaLs"), ilev, cx_windcomponent[ilev]);
164  }
165 
166  // diagnostic variables to be summed over all processors at the end of the routine
167  std::unique_ptr<ioda::Accumulator<size_t>> countQiAccumulator =
168  obsdb.distribution()->createAccumulator<size_t>();
169 
170  // Loop through locations
171  for (size_t iloc=0; iloc < nlocs; ++iloc) {
172  // Initialize at each location
173  float error_press = 0.0; // default wind error contribution from error in pressure, ms-1
174  float error_vector = 3.5; // default wind error contribution from error in vector, ms-1
175  double weight = 0.0;
176  double sum_top = 0.0;
177  double sum_weight = 0.0;
178  obserr[0][iloc] = missing;
179 
180  // Check for valid pressure error estimate.
181  // The choice of minimum pressure error is somewhat arbitrary.
182  // Having a minimum helps catch cases where we may have forgotten to specify in Pa
183  // and instead used hPa, e.g. 60 Pa instead of 6000 Pa.
184  float const min_pressure_error = 500; // 5 hPa
185  float const max_pressure_error = 50000; // 500 hPa
186  if ( pressure_error[iloc] == missing ||
187  pressure_error[iloc] < min_pressure_error ||
188  pressure_error[iloc] > max_pressure_error ) {
189  errString << "Pressure error invalid: " << pressure_error[iloc] << " Pa" << std::endl;
190  throw eckit::BadValue(errString.str());
191  }
192 
193  // Calculate vector error using QI
194  if ( (ob_qi[iloc] != missing) &&
195  (ob_qi[iloc] > 0.0) &&
196  (ob_qi[iloc] <= 100.0)) {
197  error_vector = eu_mult * (ob_qi[iloc] * 0.01) + eu_add;
198  } else {
199  countQiAccumulator->addTerm(iloc, 1);
200  }
201 
202  // Calculate the error in vector due to error in pressure
203  // Start at level number 2 (ilev=1) as need to difference with ilev-1
204  for (size_t ilev = 1; ilev < nlevs; ++ilev) {
205  // ignore contribution above min_press in atmosphere
206  if (cx_p[ilev][iloc] < min_press) {
207  continue;
208  }
209  // Calculate weight for each background level, avoiding zero divide.
210  if (pressure_error[iloc] > 0) {
211  weight = exp(-0.5 * pow(cx_p[ilev][iloc] - ob_p[iloc], 2) /
212  pow(pressure_error[iloc], 2) )
213  * std::abs(cx_p[ilev][iloc] - cx_p[ilev - 1][iloc]);
214  } else {
215  weight = 0.0;
216  }
217 
218  // ignore level if weight is very small
219  double const min_weight = 0.00001;
220  if (weight < min_weight) {
221  continue;
222  }
223 
224  sum_top = sum_top + weight * pow(cx_windcomponent[ilev][iloc] - bg_windcomponent[iloc], 2);
225  sum_weight = sum_weight + weight;
226  }
227 
228  if (sum_weight > 0) {
229  error_press = sqrt(sum_top / sum_weight);
230  }
231 
232  obserr[0][iloc] = sqrt(pow(error_vector, 2) +
233  pow(error_press, 2) );
234  }
235  // sum number of bad QI values
236  const std::size_t countQi = countQiAccumulator->computeResult();
237  if (countQi > 0) {
238  oops::Log::warning() << "Satwind Indiv Errors: " << countQi
239  << " observations with bad/missing QI. Using default vector err in these cases" << std::endl;
240  }
241 }
242 
243 // -----------------------------------------------------------------------------
244 
246  return invars_;
247 }
248 
249 // -----------------------------------------------------------------------------
250 
251 } // namespace ufo
ObsFilterData provides access to all data related to an ObsFilter.
size_t nlocs() const
Returns the number of locations in the associated ObsSpace.
ioda::ObsSpace & obsspace() const
Returns reference to ObsSpace associated with ObsFilterData.
size_t nlevs(const Variable &) const
Returns the number of levels in the specified variable.
void get(const Variable &varname, std::vector< float > &values) const
Fills a std::vector with values of the specified variable.
SatwindIndivErrorsParameters options_
void compute(const ObsFilterData &, ioda::ObsDataVector< float > &) const
Function to calculate situation dependent observation errors for satwinds.
const ufo::Variables & requiredVariables() const
geovals required to compute the function
SatwindIndivErrors(const eckit::LocalConfiguration &)
oops::RequiredParameter< Variable > pressure_error
Name of the variable containing the input height error estimates (Pa)
oops::Parameter< float > min_press
Ignore contribution above height of minimum pressure (Pa)
oops::RequiredParameter< float > eu_mult
Vector error estimate multiply.
oops::RequiredParameter< Variable > quality_index
Name of the variable containing quality index values for use in the vector error calculation.
oops::RequiredParameter< std::string > vcoord
String containing the vertical coordinate to use for the wind component.
oops::RequiredParameter< std::string > profile
String containing the name of the wind component we are calculating the error for.
oops::RequiredParameter< float > eu_add
Vector error estimate addition.
constexpr int missing
Definition: QCflags.h:20
constexpr int profile
Definition: QCflags.h:34
integer function nlocs(this)
Return the number of observational locations in this Locations object.
Definition: RunCRTM.h:27
util::Duration abs(const util::Duration &duration)
static ObsFunctionMaker< SatwindIndivErrors > makerSatwindIndivErrors_("SatwindIndivErrors")