UFO
SatwindInversionCorrection.cc
Go to the documentation of this file.
1 /* -----------------------------------------------------------------------------
2  * (C) British Crown 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 <limits>
11 
12 #include "eckit/config/Configuration.h"
13 
14 #include "ioda/distribution/Accumulator.h"
15 #include "ioda/ObsDataVector.h"
16 #include "ioda/ObsSpace.h"
17 
18 #include "oops/util/Logger.h"
19 
20 #include "ufo/GeoVaLs.h"
22 
23 namespace ufo {
24 
25 // -----------------------------------------------------------------------------
26 
28  const Parameters_ & parameters,
29  std::shared_ptr<ioda::ObsDataVector<int> > flags,
30  std::shared_ptr<ioda::ObsDataVector<float> > obserr)
31  : FilterBase(obsdb, parameters, flags, obserr), parameters_(parameters)
32 {
33  oops::Log::trace() << "SatwindInversion contructor starting" << std::endl;
34  // Get parameters from options
36  // Include list of required data from GeoVaLs
37  allvars_ += Variable("air_temperature@GeoVaLs");
38  allvars_ += Variable("relative_humidity@GeoVaLs");
39  allvars_ += Variable("air_pressure@GeoVaLs");
40 }
41 
42 // -----------------------------------------------------------------------------
43 
45  oops::Log::trace() << "SatwindInversion destructed" << std::endl;
46 }
47 
48 // -----------------------------------------------------------------------------
49 /*! \brief A filter that modifies the assigned pressure of AMV observations if a
50  * temperature inversion is detected in the model profile and defined criteria
51  * are met.
52  *
53  * \details The model profile is searched for the presence of a temperature
54  * inversion. Where there are multiple temperature inversions, only the lowest one is found.
55  * This is intended only for use on low level AMVs, typically below 700 hPa height.
56  *
57  * The pressure of the AMV is corrected downwards in height if the following conditions are true:
58  * a) Originally assigned pressure is greater than or equal to min_pressure (Pa).
59  * b) AMV derived from IR and visible channels only.
60  * c) Temperature inversion is present in the model profile for pressures less than or equal to
61  * max_pressure (Pa).
62  * d) In order to be considered significant, the temperature difference across the top and base of
63  * the inversion must be greater than or equal to the inversion_temperature (K) value.
64  * e) Relative humidity at the top of the inversion is less than the rh_threshold value.
65  * f) AMV has been assigned above the height of the inversion base.
66  *
67  * The AMV is then re-assigned to the base of the inversion.
68  *
69  * Reference for initial implementation:
70  * Cotton, J., Forsythe, M., Warrick, F., (2016). Towards improved height assignment and
71  * quality control of AMVs in Met Office NWP. Proceedings for the 13th International Winds
72  * Workshop 27 June - 1 July 2016, Monterey, California, USA.
73  *
74  * Example:
75  * \code{.unparsed}
76  * obs filters:
77  * - filter: Satwind Inversion Correction
78  * observation pressure:
79  * name: air_pressure_levels@MetaData
80  * RH threshold: 50
81  * maximum pressure: 96000
82  * \endcode
83  *
84  * \author J.Cotton (Met Office)
85  *
86  * \date 07/05/2021: Created
87  */
88 
89 void SatwindInversionCorrection::applyFilter(const std::vector<bool> & apply,
90  const Variables & filtervars,
91  std::vector<std::vector<bool>> & flagged) const {
92  oops::Log::trace() << "SatwindInversionCorrection priorFilter" << std::endl;
93  print(oops::Log::trace());
94 
95  const float missing = util::missingValue(missing);
96  const size_t nlocs = obsdb_.nlocs();
97 
98 // Get parameters from options.
99  const float rh_threshold = parameters_.rh_threshold.value();
100  const float min_pressure = parameters_.min_pressure.value();
101  const float max_pressure = parameters_.max_pressure.value();
102  const float inversion_temperature = parameters_.inversion_temperature.value();
103 // get names of GeoVal variables
104  const std::string model_temp_name = "air_temperature";
105  const std::string model_rh_name = "relative_humidity";
106  const std::string model_vcoord_name = "air_pressure";
107 
108 // Get variables from ObsSpace
109 // Get the observation pressure
110  std::vector<float> obs_pressure;
111  data_.get(parameters_.obs_pressure, obs_pressure);
112 // Get wind computation method
113  std::vector<int> comp_method(obsdb_.nlocs());
114  obsdb_.get_db("MetaData", "wind_computation_method", comp_method);
115 // Get flags
116  std::vector<int> u_flags(obsdb_.nlocs());
117  std::vector<int> v_flags(obsdb_.nlocs());
118  if (obsdb_.has("QCFlags", "eastward_wind") && obsdb_.has("QCFlags", "northward_wind")) {
119  obsdb_.get_db("QCFlags", "eastward_wind", u_flags);
120  obsdb_.get_db("QCFlags", "northward_wind", v_flags);
121  } else {
122  throw eckit::Exception("eastward_wind@QCFlags or northward_wind@QCFlags not initialised");
123  }
124 // Get GeoVaLs
125  const ufo::GeoVaLs * gvals = data_.getGeoVaLs();
126 // Get number of vertical levels in GeoVaLs
127  const size_t nlevs = data_.nlevs(Variable(model_vcoord_name+"@GeoVaLs"));
128 // Vectors storing GeoVaL column for current location.
129  std::vector <double> model_temp_profile(gvals->nlevs(model_temp_name), 0.0);
130  std::vector <double> model_rh_profile(gvals->nlevs(model_rh_name), 0.0);
131  std::vector <double> model_vcoord_profile(gvals->nlevs(model_vcoord_name), 0.0);
132 
133 // Vector to store original pressure
134  std::vector<float> original_pressure(obs_pressure);
135 
136 // diagnostic variables to be summed over all processors at the end of the routine
137  std::unique_ptr<ioda::Accumulator<size_t>> countAccumulator =
138  obsdb_.distribution()->createAccumulator<size_t>();
139  enum {PDIFF};
140  std::unique_ptr<ioda::Accumulator<std::vector<double>>> totalsAccumulator =
141  obsdb_.distribution()->createAccumulator<double>(nlocs);
142 
143 // Loop through locations
144  for (size_t iloc=0; iloc < nlocs; ++iloc) {
145  if (apply[iloc]) {
146  // only consider low level AMVs from infrared or visible channels
147  if (obs_pressure[iloc] >= min_pressure &&
148  (comp_method[iloc] == CloudMotionMethod::infrared ||
149  comp_method[iloc] == CloudMotionMethod::visible)) {
150  // Get GeoVaLs at the specified location.
151  gvals->getAtLocation(model_temp_profile, model_temp_name, iloc);
152  gvals->getAtLocation(model_rh_profile, model_rh_name, iloc);
153  gvals->getAtLocation(model_vcoord_profile, model_vcoord_name, iloc);
154  // ---------------------------------------------------------------------------
155  // Search for inversion and if present find T and P of base and top
156  // ---------------------------------------------------------------------------
157  bool inversion = false;
158  bool firsttime = true;
159  float inversion_base = std::numeric_limits<float>::max();
160  float inversion_top = std::numeric_limits<float>::max();
161  float temp_inversion_base = std::numeric_limits<float>::max();
162  float temp_inversion_top = std::numeric_limits<float>::max();
163  // loop over levels starting from lowest
164  for (int ilev = 0; ilev < nlevs-2 ; ++ilev) {
165  // if haven't found inversion and pressure is less than min_pressure Pa then exit
166  if (inversion == false && model_vcoord_profile[ilev] < min_pressure) {
167  break;
168  }
169  // if model pressure is greater than max_pressure Pa then skip to next level
170  if (model_vcoord_profile[ilev] > max_pressure) {
171  continue;
172  }
173  // if haven't found inversion, check for increase in temperature
174  if (inversion == false && model_temp_profile[ilev+1] > model_temp_profile[ilev]) {
175  // T of level above is greater so take base at current level
176  inversion_base = model_vcoord_profile[ilev];
177  temp_inversion_base = model_temp_profile[ilev];
178  inversion = true;
179  }
180  // if inversion found, then detect level the temperature starts to decrease again above
181  // the inversion base
182  if (inversion &&
183  model_temp_profile[ilev+1] < model_temp_profile[ilev] &&
184  model_vcoord_profile[ilev] < inversion_base &&
185  firsttime) {
186  // Check humidity of inversion top
187  if (model_rh_profile[ilev] < rh_threshold) {
188  inversion_top = model_vcoord_profile[ilev];
189  temp_inversion_top = model_temp_profile[ilev];
190  firsttime = false;
191  } else {
192  // otherwise discard and keep looking for a new base/top higher up
193  inversion = false;
194  }
195  }
196  } // level loop
197  //
198  //---------------------------------------------------------------------------
199  // Correct pressure if inversion found and conditions are met
200  //---------------------------------------------------------------------------
201  if (inversion &&
202  (temp_inversion_top - temp_inversion_base) >= inversion_temperature &&
203  obs_pressure[iloc] < inversion_base) {
204  // re-assign to base of inversion
205  countAccumulator->addTerm(iloc, 1);
206  totalsAccumulator->addTerm(iloc, PDIFF, inversion_base - obs_pressure[iloc]);
207  obs_pressure[iloc] = inversion_base;
208  // set flag
211  }
212  }
213  } // apply
214  } // location loop
215  // write back corrected pressure, updated flags and original pressure
216  obsdb_.put_db("MetaData", "air_pressure_levels", obs_pressure);
217  obsdb_.put_db("QCFlags", "eastward_wind", u_flags);
218  obsdb_.put_db("QCFlags", "northward_wind", v_flags);
219  obsdb_.put_db("MetaData", "air_pressure_original", original_pressure);
220 
221  // sum number corrected and pressure differences
222  const std::size_t count = countAccumulator->computeResult();
223  const std::vector<double> totals = totalsAccumulator->computeResult();
224  if (count) {
225  oops::Log::info() << "Satwind Inversion: "<< count
226  << " observations with modified pressure" << std::endl;
227  oops::Log::info() << "Satwind Inversion: "<< totals[PDIFF] / count
228  << " Pa mean pressure difference" << std::endl;
229  }
230 }
231 
232 // -----------------------------------------------------------------------------
233 
234 void SatwindInversionCorrection::print(std::ostream & os) const {
235  os << "SatwindInversionCorrection: config = " << parameters_ << std::endl;
236 }
237 
238 // -----------------------------------------------------------------------------
239 
240 } // 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
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_
void applyFilter(const std::vector< bool > &, const Variables &, std::vector< std::vector< bool >> &) const override
A filter that modifies the assigned pressure of AMV observations if a temperature inversion is detect...
SatwindInversionCorrection(ioda::ObsSpace &, const Parameters_ &, std::shared_ptr< ioda::ObsDataVector< int > >, std::shared_ptr< ioda::ObsDataVector< float > >)
void print(std::ostream &) const override
Parameters controlling the operation of the SatwindInversionCorrection filter.
oops::Parameter< float > max_pressure
Maximum model pressure (Pa) to consider - set default.
oops::Parameter< float > min_pressure
Minimum AMV pressure (Pa) to consider for correction - set default.
oops::Parameter< float > inversion_temperature
Temperature difference (K) between inversion base and top - set default.
oops::RequiredParameter< Variable > obs_pressure
Name of the observation pressure variable to correct.
oops::RequiredParameter< float > rh_threshold
Relative humidity (%) threshold value.
@ SatwindInversionFlag
Inversion height corrected.
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
@ visible
Motion observed in the infrared channel.