UFO
ModelBestFitPressure.cc
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2021 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 
9 
10 #include <algorithm>
11 #include <cmath>
12 #include <cstdlib>
13 #include <limits>
14 #include <vector>
15 
16 #include "ioda/distribution/Accumulator.h"
17 #include "ioda/ObsDataVector.h"
18 #include "ioda/ObsSpace.h"
19 
20 #include "oops/util/FloatCompare.h"
21 #include "oops/util/Logger.h"
22 
23 #include "ufo/GeoVaLs.h"
25 
26 namespace ufo {
27 
28 // -----------------------------------------------------------------------------
29 
30 ModelBestFitPressure::ModelBestFitPressure(ioda::ObsSpace & obsdb, const Parameters_ & parameters,
31  std::shared_ptr<ioda::ObsDataVector<int> > flags,
32  std::shared_ptr<ioda::ObsDataVector<float> > obserr)
33  : FilterBase(obsdb, parameters, flags, obserr),
34  parameters_(parameters)
35 {
36  oops::Log::trace() << "ModelBestFitPressure contructor starting" << std::endl;
37  // Get parameters from options
39  // Include list of required data from GeoVals
40  allvars_ += Variable("air_pressure_levels@GeoVaLs");
41  allvars_ += Variable("eastward_wind@GeoVaLs");
42  allvars_ += Variable("northward_wind@GeoVaLs");
43 }
44 
45 // -----------------------------------------------------------------------------
46 
48  oops::Log::trace() << "ModelBestFitPressure destructed" << std::endl;
49 }
50 
51 // -----------------------------------------------------------------------------
52 /*! \brief A filter that calculates the pressure at which the AMV wind vector (u,v)
53  * is a best match to the model wind profile. The best-fit pressure is also checked
54  * to see if it is well-constrained (True/False)
55  *
56  * Example:
57  * \code{.unparsed}
58  * obs filter:
59  * - filter: Model Best Fit Pressure
60  * observation pressure:
61  * name: air_pressure_levels@MetaData
62  * top pressure: 10000
63  * pressure band half-width: 10000
64  * upper vector diff: 4
65  * lower vector diff: 2
66  * tolerance vector diff: 1.0e-8
67  * tolerance pressure: 0.01
68  * calculate bestfit winds: true
69  * \endcode
70  *
71  * \author A.Martins (Met Office)
72  *
73  * \date 18/05/2021: Created
74  */
75 void ModelBestFitPressure::applyFilter(const std::vector<bool> & apply,
76  const Variables & filtervars,
77  std::vector<std::vector<bool>> & flagged) const {
78  oops::Log::trace() << "ModelBestFitPressure applyFilter" << std::endl;
79 
80  const float missing = util::missingValue(missing);
81  const size_t nlocs = obsdb_.nlocs();
82 
83  // Get parameters from options.
84  const float top_pressure = parameters_.top_pressure.value();
85  const float pressure_band_half_width = parameters_.pressure_band_half_width.value();
86  const float upper_vector_diff = parameters_.upper_vector_diff.value();
87  const float lower_vector_diff = parameters_.lower_vector_diff.value();
88  const float tolerance_vector_diff = parameters_.tolerance_vector_diff.value();
89  const float tolerance_pressure = parameters_.tolerance_pressure.value();
90  const bool calculate_best_fit_winds = parameters_.calculate_best_fit_winds.value();
91  // get names of GeoVal variables
92  const std::string model_pressure_name = parameters_.obs_pressure.value().variable();
93  const std::string model_eastvec_name = "eastward_wind";
94  const std::string model_northvec_name = "northward_wind";
95 
96  // Get GeoVaLs
97  const ufo::GeoVaLs * gvals = data_.getGeoVaLs();
98  // Get number of vertical levels in GeoVaLs
99  const size_t num_level = data_.nlevs(Variable(model_eastvec_name + "@GeoVaLs"));
100 
101  std::vector<float> satwind_best_fit_press(nlocs, missing);
102  std::vector<float> satwind_best_fit_eastward_wind;
103  std::vector<float> satwind_best_fit_northward_wind;
104  if (calculate_best_fit_winds) {
105  satwind_best_fit_eastward_wind.resize(nlocs, missing);
106  satwind_best_fit_northward_wind.resize(nlocs, missing);
107  }
108 
109  // Get the observation pressure
110  std::vector<float> obs_pressure(nlocs);
111  data_.get(parameters_.obs_pressure, obs_pressure);
112 
113  // wind vector obs
114  std::vector<float> obs_eastward(nlocs);
115  obsdb_.get_db("ObsValue", model_eastvec_name, obs_eastward);
116  std::vector<float> obs_northward(nlocs);
117  obsdb_.get_db("ObsValue", model_northvec_name, obs_northward);
118 
119  // Get flags
120  std::vector<int> u_flags(obsdb_.nlocs());
121  std::vector<int> v_flags(obsdb_.nlocs());
122  if (obsdb_.has("QCFlags", model_eastvec_name) &&
123  obsdb_.has("QCFlags", model_northvec_name)) {
124  obsdb_.get_db("QCFlags", model_eastvec_name, u_flags);
125  obsdb_.get_db("QCFlags", model_northvec_name, v_flags);
126  } else {
127  throw eckit::Exception("eastward_wind@QCFlags or northward_wind@QCFlags not initialised");
128  }
129 
130  // Vectors storing GeoVaL column for each location.
131  std::vector <float> model_pressure_profile(gvals->nlevs(model_pressure_name), 0.0);
132  std::vector <float> model_eastvec_profile(gvals->nlevs(model_eastvec_name), 0.0);
133  std::vector <float> model_northvec_profile(gvals->nlevs(model_northvec_name), 0.0);
134  std::vector <float> vec_diff(num_level);
135  // diagnostic variable to be summed over all processors at the end of the routine
136  std::unique_ptr<ioda::Accumulator<size_t>> countAccumulator =
137  obsdb_.distribution()->createAccumulator<size_t>();
138 
139  for (size_t idata = 0; idata < nlocs; ++idata) {
140  if (apply[idata]) {
141  // Get GeoVaLs at the specified location.
142  gvals->getAtLocation(model_pressure_profile, model_pressure_name, idata);
143  gvals->getAtLocation(model_eastvec_profile, model_eastvec_name, idata);
144  gvals->getAtLocation(model_northvec_profile, model_northvec_name, idata);
145 
146  // check ascending or decending (this code assume pressures are in descending order)
147  // if the model pressures are ascending, reverse order
148  if (std::is_sorted(model_pressure_profile.begin(),
149  model_pressure_profile.end()) ||
150  model_pressure_profile.front() < model_pressure_profile.back()) {
151  std::reverse(model_pressure_profile.begin(), model_pressure_profile.end());
152  std::reverse(model_eastvec_profile.begin(), model_eastvec_profile.end());
153  std::reverse(model_northvec_profile.begin(), model_northvec_profile.end());
154  }
155 
156  // check gvals are descending
157  ASSERT(!std::is_sorted(model_pressure_profile.begin(),
158  model_pressure_profile.end()));
159 
160  float min_vector_diff = std::numeric_limits<float>::max();
161  const size_t UNINITIALIZED = std::numeric_limits<size_t>::max();
162  size_t imin = UNINITIALIZED;
163 
164  // 1) Calculate vector difference between observed and background at all levels.
165  // Calculate best-fit pressure using vector difference.
166  // Find model level best-fit pressure (minimum vector difference).
167  // Use parabolic fit to find best-fit pressure.
168  for (size_t ilev = 0; ilev < num_level - 1; ++ilev) {
169  vec_diff[ilev] = std::hypot(obs_eastward[idata] - model_eastvec_profile[ilev],
170  obs_northward[idata] - model_northvec_profile[ilev]);
171  if (model_pressure_profile[ilev] < top_pressure) continue;
172  if (vec_diff[ilev] < min_vector_diff) {
173  min_vector_diff = vec_diff[ilev];
174  imin = ilev;
175  }
176  }
177  // check if imin set
178  if (imin == UNINITIALIZED) {
179  throw eckit::Exception("No model level pressure above top_pressure",
180  Here());
181  }
182 
183  // use parabolic fit to find best-fit pressure
184  float pressure_1;
185  const float pressure_2 = model_pressure_profile[imin];
186  float pressure_3;
187  float vec_diff_1;
188  const float vec_diff_2 = vec_diff[imin];
189  float vec_diff_3;
190 
191  // if bottom model level
192  if (imin == 0) {
193  satwind_best_fit_press[idata] = pressure_2;
194  } else {
195  pressure_1 = model_pressure_profile[imin - 1];
196  pressure_3 = model_pressure_profile[imin + 1];
197  vec_diff_1 = vec_diff[imin - 1];
198  vec_diff_3 = vec_diff[imin + 1];
199 
200  // if top of allowed region
201  if (pressure_3 < top_pressure) {
202  satwind_best_fit_press[idata] = pressure_2;
203  // if vec_diff_2 /= vec_diff_3
204  } else if (!(std::fabs(vec_diff_2 - vec_diff_3) <= tolerance_vector_diff)) {
205  const float top = (((pressure_2 - pressure_1) *
206  (pressure_2 - pressure_1) *
207  (vec_diff_2 - vec_diff_3)) -
208  ((pressure_2 - pressure_3) *
209  (pressure_2 - pressure_3) *
210  (vec_diff_2 - vec_diff_1)));
211  const float bottom = (((pressure_2 - pressure_1) *
212  (vec_diff_2 - vec_diff_3)) -
213  ((pressure_2 - pressure_3) *
214  (vec_diff_2 - vec_diff_1)));
215  satwind_best_fit_press[idata] = pressure_2 -
216  (0.5f * (top / bottom));
217  // if vec_diff_2 = vec_diff_3 set best fit pressure = pressure_2
218  } else {
219  satwind_best_fit_press[idata] = pressure_2;
220  }
221  }
222 
223  // 2) Find bestfit eastward and northwards winds by linear interpolation,
224  // if calculate_best_fit_winds set to true (default: false)
225  if (calculate_best_fit_winds) {
226  size_t lev_below;
227  size_t lev_above;
228  float prop;
229  if (std::fabs(pressure_2 - satwind_best_fit_press[idata]) <=
230  tolerance_pressure) {
231  satwind_best_fit_eastward_wind[idata] = model_eastvec_profile[imin];
232  satwind_best_fit_northward_wind[idata] = model_northvec_profile[imin];
233  } else {
234  if (pressure_2 < satwind_best_fit_press[idata]) {
235  lev_below = imin - 1;
236  lev_above = imin;
237  prop = (satwind_best_fit_press[idata] - pressure_1) /
238  (pressure_2 - pressure_1);
239  } else {
240  lev_below = imin;
241  lev_above = imin + 1;
242  prop = (satwind_best_fit_press[idata] - pressure_2) /
243  (pressure_3 - pressure_2);
244  }
245  ASSERT(prop >= 0 && prop <= 1);
246  satwind_best_fit_eastward_wind[idata] = model_eastvec_profile[lev_below] *
247  (1.0f - prop) + model_eastvec_profile[lev_above] * prop;
248  satwind_best_fit_northward_wind[idata] = model_northvec_profile[lev_below] *
249  (1.0f - prop) + model_northvec_profile[lev_above] * prop;
250  }
251  }
252 
253  // 3) Check if best-fit pressure is well constrained then set flag SatwindPoorConstraint.
254  if (min_vector_diff <= upper_vector_diff) {
255  for (size_t ilev = 0; ilev < num_level - 1; ++ilev) {
256  if (model_pressure_profile[ilev] < top_pressure) continue;
257  if ((model_pressure_profile[ilev] <
258  satwind_best_fit_press[idata] - pressure_band_half_width ||
259  model_pressure_profile[ilev] >
260  satwind_best_fit_press[idata] + pressure_band_half_width) &&
261  vec_diff[ilev] <= min_vector_diff + lower_vector_diff) {
262  countAccumulator->addTerm(idata, 1);
265  break;
266  }
267  }
268  } else {
269  countAccumulator->addTerm(idata, 1);
272  }
273  } // apply
274  } // location loop
275 
276  const std::size_t iconstraint = countAccumulator->computeResult();
277  if (iconstraint > 0) {
278  oops::Log::info() << "Satwind Poor constraint: "<< iconstraint
279  << " observations with modified pressure" << std::endl;
280  }
281  // write back flags and best-fit pressure/ winds
282  obsdb_.put_db("QCFlags", model_eastvec_name, u_flags);
283  obsdb_.put_db("QCFlags", model_northvec_name, v_flags);
284  obsdb_.put_db("DerivedValue", "model_bestfit_pressure", satwind_best_fit_press);
285  if (calculate_best_fit_winds) {
286  obsdb_.put_db("DerivedValue", "model_bestfit_eastward_wind",
287  satwind_best_fit_eastward_wind);
288  obsdb_.put_db("DerivedValue", "model_bestfit_northward_wind",
289  satwind_best_fit_northward_wind);
290  }
291 }
292 
293 // -----------------------------------------------------------------------------
294 
295 void ModelBestFitPressure::print(std::ostream & os) const {
296  os << "ModelBestFitPressure filter" << parameters_ << std::endl;
297 }
298 
299 // -----------------------------------------------------------------------------
300 
301 } // namespace ufo
Base class for UFO QC filters.
Definition: FilterBase.h:45
GeoVaLs: geophysical values at locations.
size_t nlevs(const std::string &var) const
Return number of levels for a specified variable.
Definition: GeoVaLs.cc:310
void getAtLocation(std::vector< double > &, const std::string &, const int) const
Get GeoVaLs at a specified location.
Definition: GeoVaLs.cc:380
void applyFilter(const std::vector< bool > &, const Variables &, std::vector< std::vector< bool >> &) const override
A filter that calculates the pressure at which the AMV wind vector (u,v) is a best match to the model...
void print(std::ostream &) const override
ModelBestFitPressure(ioda::ObsSpace &, const Parameters_ &, std::shared_ptr< ioda::ObsDataVector< int > >, std::shared_ptr< ioda::ObsDataVector< float > >)
Parameters controlling the operation of the ModelBestFitPressure filter.
oops::RequiredParameter< Variable > obs_pressure
Name of the observation pressure variable to correct.
oops::Parameter< float > upper_vector_diff
Maximum vector difference allowed, for calculating constraint.
oops::Parameter< bool > calculate_best_fit_winds
To calculate bestfit eastward/northward winds by linear interpolation.
oops::Parameter< float > pressure_band_half_width
oops::Parameter< float > tolerance_vector_diff
oops::Parameter< float > tolerance_pressure
oops::Parameter< float > lower_vector_diff
Minimum vector difference allowed, for calculating constraint.
const GeoVaLs * getGeoVaLs() const
Returns reference to GeoVaLs required by 1DVar.
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.
ufo::Variables allvars_
ioda::ObsSpace & obsdb_
@ SatwindPoorConstraint
Best-fit pressure is not well constrained.
constexpr int missing
Definition: QCflags.h:20
ObsPair reverse(const ObsPair &pair)
integer function nlocs(this)
Return the number of observational locations in this Locations object.
Definition: RunCRTM.h:27