UFO
Cal_Humidity.cc
Go to the documentation of this file.
1 /*
2  * (C) 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 #include "ufo/utils/Constants.h"
10 
11 namespace ufo {
12 /**************************************************************************************************/
13 // Cal_RelativeHumidity
14 /**************************************************************************************************/
15 
16 static TransformMaker<Cal_RelativeHumidity>
17  makerCal_RelativeHumidity_("RelativeHumidity");
18 
20  const VariableTransformsParameters &options,
21  const ObsFilterData &data,
22  const std::shared_ptr<ioda::ObsDataVector<int>> &flags)
23  : TransformBase(options, data, flags) {}
24 
25 /**************************************************************************************************/
26 
27 void Cal_RelativeHumidity::runTransform(const std::vector<bool> &apply) {
28  oops::Log::trace() << " --> Retrieve Relative humidity"
29  << std::endl;
30  oops::Log::trace() << " --> method: " << method() << std::endl;
31  oops::Log::trace() << " --> formulation: " << formulation() << std::endl;
32  oops::Log::trace() << " --> obsName: " << obsName() << std::endl;
33 
34  // Get the right method
35  switch (method()) {
37  methodUKMO(apply);
38  break;
39  }
42  default: {
43  methodDEFAULT(apply);
44  break;
45  }
46  }
47 }
48 
49 /**************************************************************************************************/
50 /*
51 Calculates relative humidity (RH_ice) from dew point temperature,
52 or converts RH_water to RH_ice
53 
54 Method: -
55  Saturated specific humidity at the dew point (ie w.r.t water), and saturated
56  specific humidity (w.r.t ice below 0C) at the air temperature are calculated.
57  Relative humidity is then calculated using :
58 
59  RH = (QSAT(DEW POINT)/QSAT(DRY BULB))*100
60 
61  For some temperatures (e.g. when dew point = temperature), supersaturation
62  w.r.t ice may occur.
63  The option AllowSuperSaturation (false by default) controls whether upper air relative humidity
64  is capped at 100%.
65  If the pressure, dew point or temperature take extreme
66  values or are missing, the relative humidity is set to missing data.
67 */
68 void Cal_RelativeHumidity::methodUKMO(const std::vector<bool> &apply) {
69  const size_t nlocs_ = obsdb_.nlocs();
70  // return if no data
71  if (obsdb_.nlocs() == 0) {
72  return;
73  }
74 
75  float Q_sub_s_ice, Q_sub_s_w;
76  float pressure, temperature, dewPoint;
77  std::vector<float> airTemperature;
78  std::vector<float> dewPointTemperature;
79  std::vector<float> relativeHumidity;
80  std::vector<float> airPressure;
81  bool surfaceData = true;
82  bool hasBeenUpdated = false;
83 
84  // Here we can only use data that have not been rejected by quality control
85  // so making sure UseValidDataOnly_ is set to True
86  SetUseValidDataOnly(true);
87 
88  // 0. Innitialise the ouput array
89  // -----------------------------------------------------------------------------------------------
90 
91  // 1. get the right variables
92  // -----------------------------------------------------------------------------------------------
93  // Compulsory surface observation
94  // First looking for surface observation
95  // Then looking for upperair data
96  if (obsdb_.has("ObsValue", "pressure_surface") &&
97  obsdb_.has("ObsValue", "air_temperature_surface") &&
98  obsdb_.has("ObsValue", "dew_point_temperature_surface")) {
99  getObservation("ObsValue", "pressure_surface",
100  airPressure, true);
101  getObservation("ObsValue", "air_temperature_surface",
102  airTemperature, true);
103  getObservation("ObsValue", "dew_point_temperature_surface",
104  dewPointTemperature, true);
105  getObservation("ObsValue", "relative_humidity_surface",
106  relativeHumidity);
107  } else {
108  getObservation("ObsValue", "air_pressure",
109  airPressure, true);
110  getObservation("ObsValue", "air_temperature",
111  airTemperature, true);
112  getObservation("ObsValue", "dew_point_temperature",
113  dewPointTemperature, true);
114  getObservation("ObsValue", "relative_humidity",
115  relativeHumidity);
116  surfaceData = false;
117  }
118  if (relativeHumidity.empty()) {
119  relativeHumidity.assign(nlocs_, missingValueFloat);
120  }
121 
122  // 2. making sure we have what we need is here
123  // -----------------------------------------------------------------------------------------------
124  if (!oops::allVectorsSameNonZeroSize(airPressure, airTemperature, dewPointTemperature)) {
125  oops::Log::warning() << "Vector sizes: "
126  << oops::listOfVectorSizes(airPressure, airTemperature,
127  dewPointTemperature)
128  << std::endl;
129  throw eckit::BadValue("At least one vector is the wrong size or empty out of "
130  "P, T and Td", Here());
131  }
132 
133  // Lambda function to evaluate Saturated specific humidity
134  // temp_1: airTemprature or dewPointTemperature
135  // temp_2: airTemperature
136  // -----------------------------------------------------------------------------------------------
137  auto evaluateSatSpecHumidity = [&](float temp_1, float temp_2){
138  float e_sub_s_w, e_sub_s_ice;
139  // sat. vapor pressure from Dewpoint temperature - wrt water
140  e_sub_s_w = formulas::SatVaporPres_fromTemp(temp_1,
141  formulation());
142  e_sub_s_w = formulas::SatVaporPres_correction(e_sub_s_w,
143  temp_1,
144  pressure,
145  formulation());
146  // Convert sat. vapor pressure (wrt water) to saturated specific humidity (water)
147  Q_sub_s_w = formulas::Qsat_From_Psat(e_sub_s_w, pressure);
148 
149  // sat. vapor pressure from Drybulb temperature - wrt ice
150  e_sub_s_ice = formulas::SatVaporPres_fromTemp(temp_2,
152  e_sub_s_ice = formulas::SatVaporPres_correction(e_sub_s_ice,
153  temp_2,
154  pressure,
155  formulation());
156  // Convert sat. vapor pressure (wrt ice) to saturated specific humidity (ice)
157  Q_sub_s_ice = formulas::Qsat_From_Psat(e_sub_s_ice, pressure);
158  };
159 
160  // 3. Loop over each record
161  // -----------------------------------------------------------------------------------------------
162  for (ioda::ObsSpace::RecIdxIter irec = obsdb_.recidx_begin();
163  irec != obsdb_.recidx_end(); ++irec) {
164  const std::vector<std::size_t> &rSort = obsdb_.recidx_vector(irec);
165 
166  // 3.1 Loop over each record
167  for (size_t iloc : rSort) {
168  // if the data have been excluded by the where statement
169  if (!apply[iloc]) continue;
170 
171  // store some variables
172  pressure = airPressure[iloc];
173  temperature = airTemperature[iloc];
174  dewPoint = dewPointTemperature[iloc];
175 
176  // There is very little sensitivity of calculated RH to P
177  // (less than 0.1% to change from 1000 to 800 hPa)
178  // --> so for surface observation that do not have any airPressure
179  // we set it to 1000 hPa.
180  if (pressure == missingValueFloat && surfaceData) {
181  pressure = 100000.0; // default pressure in Pascal
182  }
183 
184  // Cycle if observatoin are valid
185  if (temperature == missingValueFloat ||
186  pressure <= 1.0) continue;
187 
188  // if dewpoint temperature is reported (most stations)
189  if (dewPoint > 1.0) {
190  // calculate saturated specific humidity wrt water and ice
191  evaluateSatSpecHumidity(dewPoint, temperature);
192 
193  // if saturated specific humidity wrt water and ice are positive
194  // calculate relative humidity
195  if (Q_sub_s_w > 0 && Q_sub_s_ice > 0) {
196  relativeHumidity[iloc] = (Q_sub_s_w / Q_sub_s_ice) * 100.0;
197  if (!AllowSuperSaturation())
198  relativeHumidity[iloc] = std::min(100.0f, relativeHumidity[iloc]);
199  hasBeenUpdated = true;
200  }
201  // if relative humidity (Rh) is reported (small minority of stations)
202  // update from Rh wrt water to Rh wrt ice for temperatures below freezing
203  } else if (relativeHumidity[iloc] != missingValueFloat &&
204  temperature < ufo::Constants::t0c) {
205  // calculate saturated specific humidity wrt water and ice
206  evaluateSatSpecHumidity(temperature, temperature);
207  // update from Rh wrt water to Rh wrt ice for temperatures below freezing
208  relativeHumidity[iloc] *= (Q_sub_s_w / Q_sub_s_ice);
209  if (!AllowSuperSaturation())
210  relativeHumidity[iloc] = std::min(100.0f, relativeHumidity[iloc]);
211 
212  hasBeenUpdated = true;
213  }
214  }
215  }
216 
217  // assign the derived relative humidity as DerivedObsValue
218  if (hasBeenUpdated) {
219  if (surfaceData) {
220  putObservation("relative_humidity_surface", relativeHumidity);
221  } else {
222  putObservation("relative_humidity", relativeHumidity);
223  }
224  }
225 }
226 
227 /**************************************************************************************************/
228 
229 void Cal_RelativeHumidity::methodDEFAULT(const std::vector<bool> &apply) {
230  const size_t nlocs = obsdb_.nlocs();
231 
232  float esat, qvs, qv, satVaporPres;
233 
234  std::vector<float> specificHumidity;
235  std::vector<float> airTemperature;
236  std::vector<float> pressure;
237  std::vector<float> relativeHumidity(nlocs);
238 
239  getObservation("ObsValue", "specific_humidity",
240  specificHumidity, true);
241  getObservation("ObsValue", "air_temperature",
242  airTemperature, true);
243  getObservation("MetaData", "air_pressure",
244  pressure, false);
245  if (pressure.empty()) {
246  getObservation("ObsValue", "surface_pressure",
247  pressure, true);
248  }
249 
250  if (!oops::allVectorsSameNonZeroSize(specificHumidity, airTemperature, pressure)) {
251  oops::Log::warning() << "Vector sizes: "
252  << oops::listOfVectorSizes(specificHumidity, airTemperature,
253  pressure)
254  << std::endl;
255  throw eckit::BadValue("At least one vector is the wrong size or empty out of "
256  "specific_humidity, air_temperature and pressure", Here());
257  }
258 
259  // Initialise this vector with missing value
260  relativeHumidity.assign(nlocs, missingValueFloat);
261 
262  // Loop over all obs
263  for (size_t jobs = 0; jobs < nlocs; ++jobs) {
264  // if the data have been excluded by the where statement
265  if (!apply[jobs]) continue;
266 
267  if (specificHumidity[jobs] != missingValueFloat &&
268  airTemperature[jobs] != missingValueFloat && pressure[jobs] != missingValueFloat) {
269  // Calculate saturation vapor pressure from temperature according to requested formulation
270  // Double-check result is always lower than 15% of incoming pressure.
271  satVaporPres = formulas::SatVaporPres_fromTemp(airTemperature[jobs], formulation());
272  esat = std::min(pressure[jobs]*0.15f, satVaporPres);
273 
274  // Convert sat. vapor pressure to sat water vapor mixing ratio
275  qvs = 0.622 * esat/(pressure[jobs]-esat);
276 
277  // Convert specific humidity to water vapor mixing ratio
278  qv = std::max(1.0e-12f, specificHumidity[jobs]/(1.0f-specificHumidity[jobs]));
279 
280  // Final RH (which can be greater than 100%) is q/qsat, but set sensible lowest limit
281  relativeHumidity[jobs] = std::max(1.0e-6f, qv/qvs);
282  }
283  }
284 
285  putObservation("relative_humidity", relativeHumidity);
286 }
287 
288 /************************************************************************************/
289 // Cal_SpecificHumidity
290 /************************************************************************************/
292  makerCal_SpecificHumidity_("SpecificHumidity");
293 
295  const VariableTransformsParameters &options,
296  const ObsFilterData &data,
297  const std::shared_ptr<ioda::ObsDataVector<int>> &flags)
298  : TransformBase(options, data, flags) {}
299 
300 /************************************************************************************/
301 
302 void Cal_SpecificHumidity::runTransform(const std::vector<bool> &apply) {
303  oops::Log::trace() << " Retrieve Specific Humidity" << std::endl;
304  oops::Log::trace() << " --> method: " << method() << std::endl;
305  oops::Log::trace() << " --> formulation: " << formulation() << std::endl;
306  oops::Log::trace() << " --> obsName: " << obsName() << std::endl;
307 
308  // Get the right method
309  switch (method()) {
313  default: {
314  methodDEFAULT(apply);
315  break;
316  }
317  }
318 }
319 /************************************************************************************/
320 
321 void Cal_SpecificHumidity::methodDEFAULT(const std::vector<bool> &apply) {
322  const size_t nlocs = obsdb_.nlocs();
323  float esat, qvs, qv, satVaporPres;
324  std::vector<float> relativeHumidity;
325  std::vector<float> airTemperature;
326  std::vector<float> pressure;
327  std::vector<float> specificHumidity(nlocs);
328 
329  getObservation("ObsValue", "relative_humidity",
330  relativeHumidity, true);
331  getObservation("ObsValue", "air_temperature",
332  airTemperature, true);
333  getObservation("MetaData", "air_pressure",
334  pressure, false);
335  if (pressure.empty()) {
336  getObservation("ObsValue", "surface_pressure",
337  pressure, true);
338  }
339 
340  if (!oops::allVectorsSameNonZeroSize(relativeHumidity, airTemperature, pressure)) {
341  oops::Log::warning() << "Vector sizes: "
342  << oops::listOfVectorSizes(relativeHumidity, airTemperature,
343  pressure)
344  << std::endl;
345  throw eckit::BadValue("At least one vector is the wrong size or empty out of "
346  "relative_humidity, air_temperature and pressure", Here());
347  }
348 
349  // Initialise this vector with missing value
350  specificHumidity.assign(nlocs, missingValueFloat);
351 
352  // Loop over all obs
353  for (size_t jobs = 0; jobs < nlocs; ++jobs) {
354  if (relativeHumidity[jobs] != missingValueFloat &&
355  airTemperature[jobs] != missingValueFloat && pressure[jobs] != missingValueFloat) {
356  // Calculate saturation vapor pressure from temperature according to requested formulation
357  // Double-check result is always lower than 15% of incoming pressure.
358  satVaporPres = formulas::SatVaporPres_fromTemp(airTemperature[jobs], formulation());
359  esat = std::min(pressure[jobs]*0.15f, satVaporPres);
360 
361  // Convert sat. vapor pressure to sat water vapor mixing ratio
362  qvs = 0.622 * esat/(pressure[jobs]-esat);
363 
364  // Using RH, calculate water vapor mixing ratio
365  qv = std::max(1.0e-12f, relativeHumidity[jobs]*qvs);
366 
367  // Final RH (which can be greater than 100%) is q/qsat, but set sensible lowest limit
368  specificHumidity[jobs] = std::max(1.0e-12f, qv/(1.0f+qv));
369  }
370  }
371  putObservation("specific_humidity", specificHumidity);
372 }
373 } // namespace ufo
374 
void methodDEFAULT(const std::vector< bool > &apply)
Cal_RelativeHumidity(const VariableTransformsParameters &options, const ObsFilterData &data, const std::shared_ptr< ioda::ObsDataVector< int >> &flags)
Definition: Cal_Humidity.cc:19
void runTransform(const std::vector< bool > &apply) override
Run variable conversion.
Definition: Cal_Humidity.cc:27
void methodUKMO(const std::vector< bool > &apply)
Definition: Cal_Humidity.cc:68
Cal_SpecificHumidity(const VariableTransformsParameters &options, const ObsFilterData &data, const std::shared_ptr< ioda::ObsDataVector< int >> &flags)
void runTransform(const std::vector< bool > &apply) override
Run variable conversion.
void methodDEFAULT(const std::vector< bool > &apply)
ObsFilterData provides access to all data related to an ObsFilter.
Base class for variable conversion.
Definition: TransformBase.h:53
std::string obsName() const
subclasses to access the observation name
formulas::MethodFormulation method() const
subclasses to access Method and formualtion used for the calculation
void SetUseValidDataOnly(bool t)
void putObservation(const std::string &varName, const std::vector< T > &obsVector)
Save a transformed variable to the DerivedObsValue group of the obs space.
formulas::MethodFormulation formulation() const
const float missingValueFloat
Missing value (float)
ioda::ObsSpace & obsdb_
Observation space.
void getObservation(const std::string &originalTag, const std::string &varName, std::vector< T > &obsVector, bool require=false) const
templated function for float, int data types
Definition: TransformBase.h:94
bool AllowSuperSaturation() const
Transform maker.
Options controlling the operation of the variablestansform filter.
float Qsat_From_Psat(float Psat, float P, MethodFormulation formulation)
Calculates Saturated specific humidity or saturated vapour pressure using saturation vapour pressure.
Definition: Formulas.cc:163
float SatVaporPres_correction(float e_sub_s, float temp_K, float pressure, MethodFormulation formulation)
Calculates saturated vapour pressure from temperature.
Definition: Formulas.cc:132
float SatVaporPres_fromTemp(float temp_K, MethodFormulation formulation)
Calculates saturated vapour pressure from temperature.
Definition: Formulas.cc:45
@ NCAR
NCAR specific formulation.
Definition: Formulas.h:29
@ LandoltBornstein
Definition: Formulas.h:36
@ NOAA
NOAA specific formulation.
Definition: Formulas.h:30
@ UKMO
UKMO specific formulation.
Definition: Formulas.h:28
integer function nlocs(this)
Return the number of observational locations in this Locations object.
Definition: RunCRTM.h:27
static TransformMaker< Cal_RelativeHumidity > makerCal_RelativeHumidity_("RelativeHumidity")
static TransformMaker< Cal_SpecificHumidity > makerCal_SpecificHumidity_("SpecificHumidity")
static constexpr double t0c
Definition: Constants.h:24