UFO
ProfileAverageTemperature.cc
Go to the documentation of this file.
1 /*
2  * (C) 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 
8 #include <set>
9 #include <sstream>
10 #include <string>
11 #include <utility>
12 
17 
19 
20 namespace ufo {
21 
22  static ProfileCheckMaker<ProfileAverageTemperature>
23  makerProfileAverageTemperature_("AverageTemperature");
24 
27  : ProfileCheckBase(options)
28  {}
29 
31  {
32  oops::Log::debug() << " Temperature averaging" << std::endl;
33 
34  // Produce vector of profiles containing data for the temperature averaging.
35  std::vector <std::string> variableNamesInt =
41  std::vector <std::string> variableNamesFloat =
53  std::vector <std::string> variableNamesGeoVaLs =
55 
56  if (options_.compareWithOPS.value()) {
57  variableNamesInt.insert
58  (variableNamesInt.end(),
59  {"OPS_" + std::string(ufo::VariableNames::modellevels_average_air_temperature_qcflags)});
60  variableNamesFloat.insert
61  (variableNamesFloat.end(),
62  {"OPS_" + std::string(ufo::VariableNames::modellevels_air_temperature_derived),
63  "OPS_"
64  + std::string(ufo::VariableNames::modellevels_average_air_temperature_derived)});
65  variableNamesGeoVaLs.insert
66  (variableNamesGeoVaLs.end(),
67  {ufo::VariableNames::geovals_air_temperature,
68  ufo::VariableNames::geovals_average_air_temperature,
69  ufo::VariableNames::geovals_average_air_temperature_qcflags});
70  }
71 
72  std::vector <ProfileDataHolder> profiles =
73  profileDataHandler.produceProfileVector
74  (variableNamesInt,
75  variableNamesFloat,
76  {},
77  variableNamesGeoVaLs,
78  {});
79 
80  // Run temperature averaging on each profile in the original ObsSpace,
81  // saving averaged output to the equivalent extended profile.
82  const size_t halfnprofs = profileDataHandler.getObsdb().nrecs() / 2;
83  for (size_t jprof = 0; jprof < halfnprofs; ++jprof) {
84  oops::Log::debug() << " Profile " << (jprof + 1) << " / " << halfnprofs << std::endl;
85  auto& profileOriginal = profiles[jprof];
86  auto& profileExtended = profiles[jprof + halfnprofs];
87  runCheckOnProfiles(profileOriginal, profileExtended);
88  }
89 
90  // Fill validation information if required.
91  if (options_.compareWithOPS.value()) {
92  oops::Log::debug() << " Filling validation data" << std::endl;
93  for (size_t jprof = 0; jprof < halfnprofs * 2; ++jprof) {
94  fillValidationData(profiles[jprof]);
95  }
96  }
97 
98  // Update data handler with profile information.
99  oops::Log::debug() << " Updating data handler" << std::endl;
100  profileDataHandler.updateAllProfiles(profiles);
101  }
102 
104  ProfileDataHolder &profileExtended)
105  {
106  // Check the two profiles are in the correct section of the ObsSpace.
109 
110  const size_t numProfileLevels = profileOriginal.getNumProfileLevels();
111  // Do not perform averaging if there is just one reported level.
112  if (numProfileLevels <= 1)
113  return;
114 
115  const std::vector <float> &tObs =
116  profileOriginal.get<float>(ufo::VariableNames::obs_air_temperature);
117  const std::vector <float> &tPGEBd =
118  profileOriginal.get<float>(ufo::VariableNames::pgebd_air_temperature);
119  std::vector <int> &tFlags =
120  profileOriginal.get<int>(ufo::VariableNames::qcflags_air_temperature);
121  std::vector <int> &rhFlags =
123  const std::vector <float> &tObsCorrection =
125  std::vector <int> &NumGapsT =
126  profileOriginal.get<int>(ufo::VariableNames::counter_NumGapsT);
127 
128  if (!oops::allVectorsSameNonZeroSize(tObs, tPGEBd, tFlags, rhFlags, tObsCorrection)) {
129  oops::Log::warning() << "At least one vector is the wrong size. "
130  << "Temperature averaging will not be performed." << std::endl;
131  oops::Log::warning() << "Vector sizes: "
132  << oops::listOfVectorSizes(tObs, tPGEBd, tFlags, rhFlags, tObsCorrection)
133  << std::endl;
134  // Do not throw an exception here because some sondes do not have temperature measurements.
135  // All model-level variables are mandatory and an exception will be thrown if they are
136  // not present.
137  // todo(ctgh): Revisit this (and other routines in which a similar choice has been made)
138  // when the organisation of the input data becomes clearer.
139  return;
140  }
141 
142  // Obtain GeoVaLs potential temperature.
143  const std::vector <float> &potempGeoVaLs =
145  if (potempGeoVaLs.empty())
146  throw eckit::BadValue("Potential temperature GeoVaLs vector is empty.", Here());
147  const size_t numModelLevels = potempGeoVaLs.size();
148 
149  // Obtain vectors that were produced in the AveragePressure routine.
150  const std::vector <float> &ExnerPA =
152  const std::vector <float> &ExnerPB =
153  profileExtended.get<float>(ufo::VariableNames::modellevels_ExnerP_derived);
154  const std::vector <float> &LogPA =
156  const std::vector <float> &LogPB =
157  profileExtended.get<float>(ufo::VariableNames::modellevels_logP_derived);
158  const std::vector <float> &RepLogP =
159  profileOriginal.get<float>(ufo::VariableNames::LogP_derived);
160  const std::vector <float> &BigGap =
161  profileOriginal.get<float>(ufo::VariableNames::bigPgaps_derived);
162 
163  if (!oops::allVectorsSameNonZeroSize(ExnerPA, LogPA) ||
164  !oops::allVectorsSameNonZeroSize(ExnerPB, LogPB) ||
165  !oops::allVectorsSameNonZeroSize(RepLogP, BigGap)) {
166  std::stringstream errorMessage;
167  errorMessage << "At least one model-level vector is the wrong size. "
168  << "Ensure that the AveragePressure routine has been run before this one."
169  << std::endl;
170  errorMessage << "Vector sizes: "
171  << oops::listOfVectorSizes(ExnerPA, LogPA,
172  ExnerPB, LogPB,
173  RepLogP, BigGap)
174  << std::endl;
175  throw eckit::BadValue(errorMessage.str(), Here());
176  }
177 
178  // Apply any corrections to observed temperature.
179  std::vector <float> tObsFinal;
180  correctVector(tObs, tObsCorrection, tObsFinal);
181 
182  // Flag reported value if the probability of gross error is too large.
183  // Values which have been flagged here, or previously, are not used in the averaging routines.
184  for (size_t jlev = 0; jlev < numProfileLevels; ++jlev) {
185  if (tPGEBd[jlev] > options_.AvgT_PGEskip.value()) {
187  // NB the relative humidity flags are modified in this routine and also
188  // in the routine that performs RH averaging.
190  }
191  }
192 
193  // Average observed temperatures onto model levels.
194  int NumGaps = 0; // Number of large gaps in reported profile.
195  std::vector <float> tModObs; // T observations averaged onto model levels.
196  std::vector <int> tFlagsModObs; // Flags associated with the averaging procedure.
197  std::vector <float> LogP_Min; // Min log(pressure) used in layer average.
198  std::vector <float> LogP_Max; // Max log(pressure) used in layer average.
199  // Minimum fraction of a model layer that must have been covered (in the vertical coordinate)
200  // by observed values in order for averaging onto that layer to be performed.
201  const float SondeDZFraction = options_.AvgT_SondeDZFraction.value();
203  tObsFinal,
204  RepLogP,
205  BigGap,
206  LogPA,
207  SondeDZFraction,
209  tFlagsModObs,
210  tModObs,
211  NumGaps,
212  &LogP_Max,
213  &LogP_Min);
214 
215  // Increment temperature gap counter if necessary.
216  if (NumGaps > 0) NumGapsT[0]++;
217 
218  // Convert model potential temperature to temperature.
219  // The model temperature is used in the partial layer corrections below
220  // and in subsequent checks on model levels.
221  std::vector <float> tModBkg = potempGeoVaLs;
222  std::transform(ExnerPB.begin(), ExnerPB.end(), tModBkg.begin(), tModBkg.begin(),
223  [this](float ExnerPBLev, float potempLev)
224  {return potempLev != missingValueFloat && ExnerPBLev != missingValueFloat ?
225  potempLev * ExnerPBLev : missingValueFloat;});
226 
227  // Recalculate average temperature by taking the thickness of the model layers into account.
228  // This procedure uses the values of LogP_Max and LogP_Min that were computed in the
229  // calculateVerticalAverage routine.
230  // This procedure computes a potential temperature O-B increment using linear interpolation
231  // of temperature between the layer boundaries.
232  // This increment is added to the background value to produce the averaged observation value.
233  const double logPref = std::log(ufo::Constants::pref);
234  for (int JLev = 0; JLev < numModelLevels; ++JLev) {
235  if (tModObs[JLev] == missingValueFloat ||
236  LogP_Max[JLev] == missingValueFloat ||
237  LogP_Min[JLev] == missingValueFloat ||
238  LogP_Max[JLev] == LogP_Min[JLev])
239  continue;
240  // Difference between between the maximum and minimum values of log(pressure)
241  // that were obtained when performing the temperature averaging for this layer.
242  const double DLogP = LogP_Max[JLev] - LogP_Min[JLev];
243  if (JLev < numModelLevels - 1) { // The current level is below the highest model level.
244  // Check whether this model layer is less than 99.5% full, in which case partial layer
245  // processing is used.
246  if (DLogP < 0.995 * (LogPA[JLev] - LogPA[JLev + 1])) {
247  // Lower model level used in the processing.
248  const int MLev1 = std::max(JLev - 1, 0);
249  // Upper model level used in the processing.
250  const int MLev2 = std::min(JLev + 1, static_cast<int>(numModelLevels) - 1);
251  // If any of the required quantities are missing,
252  // set the averaged temperature to the missing value.
253  if (tModBkg[MLev1] == missingValueFloat ||
254  tModBkg[MLev2] == missingValueFloat ||
255  tModBkg[JLev] == missingValueFloat ||
256  LogPB[MLev2] == missingValueFloat ||
257  LogPA[JLev] == missingValueFloat ||
258  LogPA[JLev + 1] == missingValueFloat) {
259  tModObs[JLev] = missingValueFloat;
260  } else {
261  // DExner is guaranteed to be nonzero thanks to the requirement
262  // that LogP_Max is not equal to LogP_Min.
263  const double DExner = // Difference between Exner pressures.
264  std::exp((LogP_Max[JLev] - logPref) * ufo::Constants::rd_over_cp) -
265  std::exp((LogP_Min[JLev] - logPref) * ufo::Constants::rd_over_cp);
266  // Compute potential temperature.
267  const double potemp = ufo::Constants::rd_over_cp * tModObs[JLev] * DLogP / DExner;
268  // Model temperature at level JLev.
269  const double TLev =
270  potempGeoVaLs[JLev] * (ExnerPA[JLev] - ExnerPA[JLev + 1]) /
271  (ufo::Constants::rd_over_cp * (LogPA[JLev] - LogPA[JLev + 1]));
272  // Log(P) at model layer midpoint in terms of Log(P).
273  const double ModLogP_mid = 0.5 * (LogPA[JLev] + LogPA[JLev + 1]);
274  // Model temperature gradient.
275  const double TGrad = (tModBkg[MLev1] - tModBkg[MLev2]) / (LogPB[MLev1] - LogPB[MLev2]);
276  // Model temperature at level P_Max, computed using midpoint and gradient.
277  const double TMax = TLev + (LogP_Min[JLev] - ModLogP_mid) * TGrad;
278  // Model temperature at level P_Min, computed using midpoint and gradient.
279  const double TMin = TLev + (LogP_Max[JLev] - ModLogP_mid) * TGrad;
280  // Model potential temperature for P_Max to P_Min.
281  const double potempBk =
282  ufo::Constants::rd_over_cp * (TMax + TMin) * DLogP / (2.0 * DExner);
283  // Temperature increment for partial layer.
284  const double Tinc = (potemp - potempBk) * ExnerPB[JLev];
285  // Update averaged temperature with increment.
286  tModObs[JLev] = tModBkg[JLev] + Tinc;
287  }
288  } else {
289  // This model layer has been fully covered.
290  // Determine difference between Exner pressures using model values.
291  const double DExner = ExnerPA[JLev] - ExnerPA[JLev + 1];
292  // Compute potential temperature.
293  const double potemp =
294  ufo::Constants::rd_over_cp * tModObs[JLev] * (LogPA[JLev] - LogPA[JLev + 1]) / DExner;
295  // Convert potential temperature back to temperature.
296  tModObs[JLev] = potemp * ExnerPB[JLev];
297  }
298  } else {
299  // Highest level to be processed.
300  const double DExner =
301  std::exp((LogP_Max[JLev] - logPref) * ufo::Constants::rd_over_cp) -
302  std::exp((LogP_Min[JLev] - logPref) * ufo::Constants::rd_over_cp);
303  // Compute potential temperature.
304  const double potemp = ufo::Constants::rd_over_cp * tModObs[JLev] * DLogP / DExner;
305  // Convert potential temperature back to temperature.
306  tModObs[JLev] = potemp * ExnerPB[JLev];
307  }
308  }
309 
310  // Store the model temperature.
311  profileExtended.set<float>
313 
314  // Store the temperature averaged onto model levels.
315  profileExtended.set<float>
317 
318  // Store the QC flags associated with temperature averaging.
319  profileExtended.set<int>
321  }
322 
324  {
325  // Retrieve, then save, the OPS versions of model temperature,
326  // temperature averaged onto model levels,
327  // and the QC flags associated with the averaging process.
328  profile.set<float>
330  std::move(profile.getGeoVaLVector
332 
333  profile.set<float>
335  std::move(profile.getGeoVaLVector
337 
338  // The QC flags are stored as floats but are converted to integers here.
339  // Due to the loss of precision, 5 must be added to the missing value.
340  const std::vector <float>& average_air_temperature_qcflags_float =
341  profile.getGeoVaLVector
343  std::vector <int> average_air_temperature_qcflags_int
344  (average_air_temperature_qcflags_float.begin(),
345  average_air_temperature_qcflags_float.end());
346  std::replace(average_air_temperature_qcflags_int.begin(),
347  average_air_temperature_qcflags_int.end(),
348  -2147483648L,
349  -2147483643L);
350  profile.set<int>
352  std::move(average_air_temperature_qcflags_int));
353  }
354 } // namespace ufo
Options controlling the operation of the ConventionalProfileProcessing filter.
oops::Parameter< bool > compareWithOPS
Compare with OPS values?
void runCheck(ProfileDataHandler &profileDataHandler) override
void runCheckOnProfiles(ProfileDataHolder &profileOriginal, ProfileDataHolder &profileExtended)
ProfileAverageTemperature(const ConventionalProfileProcessingParameters &options)
void fillValidationData(ProfileDataHolder &profileDataHolder)
Fill variables in validator (for comparison with OPS output).
Profile QC checker base class.
const float missingValueFloat
Missing value (float)
const ConventionalProfileProcessingParameters & options_
Configurable parameters.
void correctVector(const std::vector< T > &v1, const std::vector< T > &v2, std::vector< T > &vout)
Apply correction to vector of values.
Retrieve and store data for individual profiles. To do this, first the vector of values in the entire...
void updateAllProfiles(std::vector< ProfileDataHolder > &profiles)
Read values from a collection of profiles and update information related to each one.
std::vector< ProfileDataHolder > produceProfileVector(const std::vector< std::string > &variableNamesInt, const std::vector< std::string > &variableNamesFloat, const std::vector< std::string > &variableNamesString, const std::vector< std::string > &variableNamesGeoVaLs, const std::vector< std::string > &variableNamesObsDiags)
Produce a vector of all profiles, loading the requested variables into each one.
ioda::ObsSpace & getObsdb()
Return obsdb.
Profile data holder class.
int getNumProfileLevels() const
Get number of profile levels for this profile.
std::vector< T > & get(const std::string &fullname)
Retrieve a vector if it is present. If not, throw an exception.
void set(const std::string &fullname, std::vector< T > &&vec_in)
Set values in a vector.
std::vector< float > & getGeoVaLVector(const std::string &fullname)
Retrieve a GeoVaL vector if it is present. If not, throw an exception.
void checkObsSpaceSection(ufo::ObsSpaceSection section)
Check this profile is in the expected ObsSpace section (original or extended).
@ FinalRejectFlag
Final QC flag.
constexpr int profile
Definition: QCflags.h:34
Definition: RunCRTM.h:27
void calculateVerticalAverage(const std::vector< int > &flagsIn, const std::vector< float > &valuesIn, const std::vector< float > &coordIn, const std::vector< float > &bigGap, const std::vector< float > &coordOut, float DZFrac, ProfileAveraging::Method method, std::vector< int > &flagsOut, std::vector< float > &valuesOut, int &numGaps, std::vector< float > *coordMax, std::vector< float > *coordMin)
Profile vertical averaging onto model levels.
static ProfileCheckMaker< ProfileAverageTemperature > makerProfileAverageTemperature_("AverageTemperature")
static constexpr double rd_over_cp
Definition: Constants.h:35
static constexpr double pref
Definition: Constants.h:32
static constexpr const char *const extended_obs_space
Definition: VariableNames.h:94
static constexpr const char *const modellevels_ExnerP_derived
static constexpr const char *const obs_air_temperature
Definition: VariableNames.h:19
static constexpr const char *const bigPgaps_derived
static constexpr const char *const modellevels_logP_derived
static constexpr const char *const geovals_average_air_temperature
static constexpr const char *const qcflags_relative_humidity
static constexpr const char *const geovals_potential_temperature
static constexpr const char *const LogP_derived
static constexpr const char *const modellevels_logP_rho_derived
static constexpr const char *const modellevels_ExnerP_rho_derived
static constexpr const char *const pgebd_air_temperature
Definition: VariableNames.h:72
static constexpr const char *const geovals_average_air_temperature_qcflags
static constexpr const char *const modellevels_air_temperature_derived
static constexpr const char *const counter_NumGapsT
static constexpr const char *const geovals_air_temperature
static constexpr const char *const qcflags_air_temperature
Definition: VariableNames.h:99
static constexpr const char *const modellevels_average_air_temperature_derived
static constexpr const char *const obscorrection_air_temperature
static constexpr const char *const modellevels_average_air_temperature_qcflags