UFO
ObsErrorFactorConventional.cc
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2021 UCAR
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 
9 
10 #include <float.h>
11 
12 #include <algorithm>
13 #include <cmath>
14 #include <set>
15 #include <string>
16 #include <vector>
17 
18 #include "eckit/exception/Exceptions.h"
19 #include "ioda/ObsDataVector.h"
20 #include "ioda/ObsSpace.h"
21 #include "oops/util/Logger.h"
22 #include "oops/util/missingValues.h"
23 #include "ufo/filters/Variable.h"
24 #include "ufo/filters/Variables.h"
25 #include "ufo/utils/Constants.h"
26 
27 namespace ufo {
28 
29 static ObsFunctionMaker<ObsErrorFactorConventional> makerSteps_("ObsErrorFactorConventional");
30 
31 // -----------------------------------------------------------------------------
32 
33 ObsErrorFactorConventional::ObsErrorFactorConventional(const eckit::Configuration &config)
34  : invars_() {
35  oops::Log::debug() << "ObsErrorFactorConventional: config = " << config << std::endl;
36 
37  // Initialize options
39  options_->deserialize(config);
40 
41  // Variable to be inflated
42  const std::vector<std::string> inflatevars = options_->inflatevars.value();
43 
44  // QC flags come from files or by default from filters
45  const std::string qcgrp = options_->testQCflag.value();
46 
47  // Include list of required qc flags for test variable
48  for (size_t iv = 0; iv < inflatevars.size(); ++iv) {
49  invars_ += Variable(inflatevars[iv]+"@"+qcgrp);
50  }
51 
52  // Include list of required data from MetaData
53  invars_ += Variable("air_pressure@MetaData"); // observed obs pressure
54  invars_ += Variable("station_id@MetaData"); // obs station ID
55  invars_ += Variable("datetime@MetaData"); // obs date and time
56 
57  // Include list of required data from GeoVaLs
58  invars_ += Variable("air_pressure@GeoVaLs"); // need model pressure at half
59  // sigma levels (dimension=nsig in GSI)
60 }
61 
62 // -----------------------------------------------------------------------------
63 
65  oops::Log::debug() << "ObsErrorFactorCon: destructing " << std::endl;
66 }
67 
68 // -----------------------------------------------------------------------------
69 
71  ioda::ObsDataVector<float> & obserr) const {
72  const int missing = util::missingValue(missing);
73  static constexpr float con_g_rd = 500.0f*Constants::grav/(273.0f*Constants::rd);
74  const float tiny_float = FLT_MIN;
75 
76  // Get dimensions
77  const size_t nlocs = data.nlocs();
78  const size_t nlevs = data.nlevs(Variable("air_pressure@GeoVaLs"));
79 
80  // Get obs space
81  auto & obsdb = data.obsspace();
82 
83  // Get output variable size
84  int varsize = obserr.nvars();
85 
86  // Get yaml options
87  const std::vector<std::string> inflatevars = options_->inflatevars.value();
88  const std::string qcgrp = options_->testQCflag.value();
89 
90  // QC flags can be either from PreQC or filter flags
91  // qcthres is 0 if using flter flags
92  // qcthres is 3 (default) or defined through yaml if using PreQC
93  int qcthres = 0;
94  if (qcgrp == "PreQC") qcthres = options_->qcthreshold.value().value_or(3);
95 
96  // Get MetaData of obs
97  std::vector<float> ob_pressure(nlocs);
98  data.get(Variable("air_pressure@MetaData"), ob_pressure);
99  std::vector<std::string> ob_stationID(nlocs);
100  data.get(Variable("station_id@MetaData"), ob_stationID);
101  std::vector<util::DateTime> ob_datetime(nlocs);
102  data.get(Variable("datetime@MetaData"), ob_datetime);
103 
104  // Due to splitting across CPUs, it is possible we have zero obs, so just return nothing.
105  if (ob_datetime.empty()) {
106  return;
107  }
108 
109  // Get GeoVals of air pressure [Pa] in vertical column
110  std::vector<std::vector<float>> prsl(nlevs, std::vector<float>(nlocs));
111  for (size_t ilev = 0; ilev < nlevs; ++ilev) {
112  data.get(Variable("air_pressure@GeoVaLs"), ilev, prsl[ilev]);
113  }
114 
115  for (size_t iv = 0; iv < varsize; ++iv) { // Variable loop
116  // Get QC flags of test variable
117  std::vector<int> ob_variable_QCflag(nlocs);
118  std::vector<int> ob_QCflag(nlocs);
119  data.get(Variable(inflatevars[iv]+"@"+qcgrp), ob_variable_QCflag);
120 
121  // Only obs with QCflag <= qcthres will be checked in this obsFunc
122  for (size_t iloc = 0; iloc < nlocs; ++iloc) {
123  (ob_variable_QCflag[iloc] <= qcthres && ob_variable_QCflag[iloc] != missing) ?
124  ob_QCflag[iloc] = 0 : ob_QCflag[iloc] = 100;
125  }
126 
127  // Define variables to be used
128  std::vector<float> dprsl(nlevs-1);
129  float error_factor;
130  float pre1, pre2, maxpre, conpre, vmag, pdiffu, pdiffd, pdifftotal;
131  int ipass = 0;
132  int icount = 0;
133  int ireport = 0;
134 
135  ioda::ObsSpace::RecIdxIter irec; // Using obs grouping/sorting indices
136  for ( irec = obsdb.recidx_begin(); irec != obsdb.recidx_end(); ++irec ) { // record loop
137  std:: size_t rNum = obsdb.recidx_recnum(irec);
138  std::vector<std::size_t> rSort = obsdb.recidx_vector(irec);
139 
140  ireport++;
141  for (size_t iloc = 0; iloc < rSort.size(); ++iloc) { // profile loop
142  icount++;
143 
144  for (size_t ilev = 0; ilev < nlevs-1; ++ilev) {
145  // Background pressure level intervals [cb]
146  dprsl[ilev] = (prsl[ilev][rSort[iloc]]-prsl[ilev+1][rSort[iloc]])*0.001f;
147  }
148 
149  int indexlev = 0;
150  for (size_t ilev = 1; ilev < nlevs-1; ++ilev) {
151  if (ob_pressure[rSort[iloc]] < prsl[ilev][rSort[iloc]]) {
152  indexlev = ilev;
153  }
154  }
155 
156  pre1 = 0.5f*dprsl[indexlev];
157  pre2 = 0.02f*0.001f*prsl[indexlev][rSort[iloc]];
158  maxpre = std::max(pre1, pre2);
159  conpre = con_g_rd*0.001f*ob_pressure[rSort[iloc]];
160  vmag = std::min(maxpre, conpre);
161 
162  pdiffu = vmag;
163  pdiffd = vmag;
164 
165  if (ob_QCflag[rSort[iloc]] == 0) {
166  ipass++;
167  // Search obs from upper levels. If there are multiple observations
168  // inside the same model interval, use the toppest one to compute
169  // the pressure interval, pdiffu
170  int lloc = iloc+1;
171  while (lloc < rSort.size()) {
172  float tmp = abs(ob_pressure[rSort[iloc]]-ob_pressure[rSort[lloc]])*0.001f;
173  if (ob_QCflag[rSort[lloc]] == 0 && tmp < vmag) {
174  pdiffu = tmp;
175  break;
176  }
177  lloc++;
178  }
179 
180  // Search obs from lower levels. If there are multiple observations
181  // inside the same model interval, use the lowest one to compute
182  // the pressure interval, pdiffd
183  lloc = iloc-1;
184  while (lloc >= 0) {
185  float tmp = abs(ob_pressure[rSort[lloc]]-ob_pressure[rSort[iloc]])*0.001f;
186  if (ob_QCflag[rSort[lloc]] == 0 && tmp < vmag) {
187  pdiffd = tmp;
188  break;
189  }
190  lloc--;
191  }
192  }
193 
194  // When there are multiple observations inside the same model interval, the error_factor
195  // will be bigger than 1 based on the spacing of the these observations
196  pdifftotal = std::max(pdiffd+pdiffu, 5.0f * tiny_float);
197  error_factor = sqrt(2.0f*vmag/pdifftotal);
198 
199  // Output
200  obserr[iv][rSort[iloc]] = error_factor;
201  }
202  } // iloc loop
203  if (icount != nlocs) {
204  std::string errString = "The data should be sorted or icount and nlocs are not consistent: ";
205  oops::Log::error() << errString << icount << ", "<< nlocs << std::endl;
206  throw eckit::BadValue(errString);
207  } // irec loop
208  oops::Log::debug() << "ObsErrorFactorCon: inflate var, # of reports, total obs, filtered obs = "
209  << inflatevars[iv] << " " << ireport << " "<< nlocs << " " << ipass << std::endl;
210  } // iv loop
211 }
212 
213 // -----------------------------------------------------------------------------
214 
216  return invars_;
217 }
218 
219 // -----------------------------------------------------------------------------
220 
221 } // namespace ufo
void compute(const ObsFilterData &, ioda::ObsDataVector< float > &) const
compute the result of the function
ObsErrorFactorConventional(const eckit::Configuration &config)
std::unique_ptr< ObsErrorFactorConventionalParameters > options_
const ufo::Variables & requiredVariables() const
geovals required to compute the function
Options controlling ObsErrorFactorConventional ObsFunction.
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.
constexpr int missing
Definition: QCflags.h:20
integer function nlocs(this)
Return the number of observational locations in this Locations object.
Definition: RunCRTM.h:27
static ObsFunctionMaker< ObsErrorFactorConventional > makerSteps_("ObsErrorFactorConventional")
util::Duration abs(const util::Duration &duration)
static constexpr double grav
Definition: Constants.h:23
static constexpr double rd
Definition: Constants.h:26