UFO
HydrometeorCheckATMS.cc
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2019 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 <cmath>
11 
12 #include <algorithm>
13 #include <iomanip>
14 #include <iostream>
15 #include <set>
16 #include <string>
17 #include <vector>
18 
19 #include "ioda/ObsDataVector.h"
20 #include "oops/util/IntSetParser.h"
23 #include "ufo/filters/Variable.h"
24 #include "ufo/utils/Constants.h"
25 
26 
27 namespace ufo {
28 
30 
31 // -----------------------------------------------------------------------------
32 
33 HydrometeorCheckATMS::HydrometeorCheckATMS(const eckit::LocalConfiguration & conf)
34  : invars_() {
35  // Initialize options
36  options_.deserialize(conf);
37 
38  // Get channels from options
39  std::set<int> channelset = oops::parseIntSet(options_.channelList);
40  std::copy(channelset.begin(), channelset.end(), std::back_inserter(channels_));
41  ASSERT(channels_.size() > 0);
42 
43  // Get test groups from options
44  const std::string &biastermgrp = options_.testBiasTerm.value();
45  const std::string &biasgrp = options_.testBias.value();
46  const std::string &hofxgrp = options_.testHofX.value();
47 
48  // Include required variables from ObsDiag
49  invars_ += Variable("brightness_temperature_jacobian_surface_emissivity@ObsDiag", channels_);
50  invars_ += Variable("brightness_temperature_assuming_clear_sky@ObsDiag", channels_);
51 
52  // Include list of required data from ObsSpace
53  invars_ += Variable("brightness_temperature@ObsValue", channels_);
54  invars_ += Variable("brightness_temperature@"+biasgrp, channels_);
55  invars_ += Variable("brightness_temperature@"+hofxgrp, channels_);
56  invars_ += Variable("constant@"+biastermgrp, channels_);
57  invars_ += Variable("scan_angle_order_4@"+biastermgrp, channels_);
58  invars_ += Variable("scan_angle_order_3@"+biastermgrp, channels_);
59  invars_ += Variable("scan_angle_order_2@"+biastermgrp, channels_);
60  invars_ += Variable("scan_angle@"+biastermgrp, channels_);
61 
62  // Include list of required data from GeoVaLs
63  invars_ += Variable("water_area_fraction@GeoVaLs");
64  invars_ += Variable("land_area_fraction@GeoVaLs");
65  invars_ += Variable("latitude@MetaData");
66  invars_ += Variable("longitude@MetaData");
67 
68  // Include list of required data from ObsFunction
69  const Variable &obserrfunc = options_.obserrFunction.value();
70  invars_ += obserrfunc;
71 
72  const Variable &clwretfunc = options_.clwretFunction.value();
73  invars_ += clwretfunc;
74 }
75 
76 // -----------------------------------------------------------------------------
77 
79 
80 // -----------------------------------------------------------------------------
81 
83  ioda::ObsDataVector<float> & out) const {
84  // Get dimensions
85  size_t nlocs = in.nlocs();
86  size_t nchans = channels_.size();
87 
88  // Set channel index (AMSU-A like channels)
89  int ich238 = 0, ich314 = 1, ich503 = 2, ich528 = 4, ich536 = 5;
90  int ich544 = 6, ich549 = 7, ich890 = 15;
91 
92  // Set channel index (MHS like channels)
93  int ich1650 = 16, ich1830a = 17, ich1830b = 18, ich1830c = 19, ich1830d = 20, ich1830e = 21;
94 
95  // Get test groups from options
96  const std::string &biastermgrp = options_.testBiasTerm.value();
97  const std::string &biasgrp = options_.testBias.value();
98  const std::string &hofxgrp = options_.testHofX.value();
99 
100  // Get clear-sky observation error from options
101  const std::vector<float> &obserr0 = options_.obserrClearSky.value();
102 
103  // Get area fraction of each surface type
104  std::vector<float> water_frac(nlocs);
105  in.get(Variable("water_area_fraction@GeoVaLs"), water_frac);
106 
107  std::vector<float> land_frac(nlocs);
108  in.get(Variable("land_area_fraction@GeoVaLs"), land_frac);
109 
110  std::vector<float> lat(nlocs);
111  in.get(Variable("latitude@MetaData"), lat);
112 
113  std::vector<float> lon(nlocs);
114  in.get(Variable("longitude@MetaData"), lon);
115 
116  // Get surface temperature jacobian
117  std::vector<std::vector<float>> dbtde(nchans, std::vector<float>(nlocs));
118  for (size_t ichan = 0; ichan < nchans; ++ichan) {
119  in.get(Variable("brightness_temperature_jacobian_surface_emissivity@ObsDiag", channels_)[ichan],
120  dbtde[ichan]);
121  }
122 
123  // Get HofX for clear-sky simulation (53.6 GHz)
124  std::vector<float> hofxclr536(nlocs);
125  in.get(Variable("brightness_temperature_assuming_clear_sky@ObsDiag", channels_)[ich536],
126  hofxclr536);
127 
128  // Get HofX for clear-sky simulation (89 GHz)
129  std::vector<float> hofxclr890(nlocs);
130  in.get(Variable("brightness_temperature_assuming_clear_sky@ObsDiag", channels_)[ich890],
131  hofxclr890);
132 
133  // Get HofX for clear-sky simulation (165 GHz)
134  std::vector<float> hofxclr1650(nlocs);
135  in.get(Variable("brightness_temperature_assuming_clear_sky@ObsDiag", channels_)[ich1650],
136  hofxclr1650);
137 
138  // Get ObsBiasTerm: constant term for 23.8GHz channel
139  std::vector<float> bias_const238(nlocs);
140  in.get(Variable("constant@"+biastermgrp, channels_)[ich238], bias_const238);
141 
142  // Get ObsBiasTerm: scan angle terms for 23.8GHz channel
143  size_t nangs = 4;
144  std::vector<float> values(nlocs);
145  std::vector<std::string> scanterms(nangs);
146  std::vector<float> bias_scanang238(nlocs);
147  scanterms[0] = "scan_angle_order_4@"+biastermgrp;
148  scanterms[1] = "scan_angle_order_3@"+biastermgrp;
149  scanterms[2] = "scan_angle_order_2@"+biastermgrp;
150  scanterms[3] = "scan_angle@"+biastermgrp;
151  for (size_t iang = 0; iang < nangs; ++iang) {
152  in.get(Variable(scanterms[iang], channels_)[ich238], values);
153  for (size_t iloc = 0; iloc < nlocs; ++iloc) {
154  bias_scanang238[iloc] = bias_scanang238[iloc] + values[iloc];
155  }
156  }
157 
158  // Calculate bias-corrected innovation: Observation - HofX (HofX includes bias correction)
159  std::vector<std::vector<float>> btobs(nchans, std::vector<float>(nlocs));
160  // Still read bias: it's used for correcting clear-sky simulated radiances below
161  std::vector<std::vector<float>> bias(nchans, std::vector<float>(nlocs));
162  std::vector<std::vector<float>> innov(nchans, std::vector<float>(nlocs));
163  std::vector<float> hofx(nlocs);
164  for (size_t ichan = 0; ichan < nchans; ++ichan) {
165  in.get(Variable("brightness_temperature@ObsValue", channels_)[ichan], btobs[ichan]);
166  in.get(Variable("brightness_temperature@"+biasgrp, channels_)[ichan], bias[ichan]);
167  in.get(Variable("brightness_temperature@"+hofxgrp, channels_)[ichan], hofx);
168  for (size_t iloc = 0; iloc < nlocs; ++iloc) {
169  innov[ichan][iloc] = btobs[ichan][iloc] - hofx[iloc];
170  }
171  }
172 
173  // Get all-sky observation error from ObsFunction
174  const Variable &obserrvar = options_.obserrFunction.value();
175  ioda::ObsDataVector<float> obserr(in.obsspace(), obserrvar.toOopsVariables());
176  in.get(obserrvar, obserr);
177 
178  // Get CLW retrieval based on observation from ObsFunction
179  const Variable &clwretvar = options_.clwretFunction.value();
180  ioda::ObsDataVector<float> clwobs(in.obsspace(), clwretvar.toOopsVariables());
181  in.get(clwretvar, clwobs);
182 
183  // Set parameters
184  float w1f6 = 1.0/10.0, w2f6 = 1.0/0.80;
185  float w1f4 = 1.0/0.30, w2f4 = 1.0/1.80;
186 
187  std::vector<std::vector<int>> affected_channels(nchans, std::vector<int>(nlocs));
188  // Loop over locations
189  // Combined cloud-precipitation-surface checks
190  for (size_t iloc = 0; iloc < nlocs; ++iloc) {
191  // Check surface type
192  bool luse = (water_frac[iloc] > 0.99 || land_frac[iloc] > 0.99);
193 
194  // Initialization
195  for (size_t ich = 0; ich < nchans; ++ich) {
196  affected_channels[ich][iloc] = 0;
197  }
198 
199  // Calculate cloud effect from 53.6 GHz (Channel 6)
200  float cldeff_obs536 = 0.0;
201  if (water_frac[iloc] > 0.99) {
202  cldeff_obs536 = btobs[ich536][iloc] - hofxclr536[iloc] - bias[ich536][iloc];
203  }
204 
205  // Calculate cloud effect from 89 GHz (Channel 16)
206  float cldeff_obs890 = 0.0;
207  if (water_frac[iloc] > 0.99) {
208  cldeff_obs890 = btobs[ich890][iloc] - hofxclr890[iloc] - bias[ich890][iloc];
209  }
210 
211  // Calculate cloud effect from 165 GHz (Channel 17)
212  float cldeff_obs1650 = 0.0;
213  if (water_frac[iloc] > 0.99) {
214  cldeff_obs1650 = btobs[ich1650][iloc] - hofxclr1650[iloc] - bias[ich1650][iloc];
215  }
216 
217  // Calculate scattering effect
218  std::vector<float> factch4(nlocs);
219  std::vector<float> factch6(nlocs);
220  float btobsbc238 = btobs[ich238][iloc] - bias_const238[iloc]
221  - bias_scanang238[iloc];
222  float clwx = 0.6;
223  float dsval = 0.8;
224  if (water_frac[iloc] > 0.99) {
225  clwx = 0.0;
226  dsval = ((2.410 - 0.0098 * btobsbc238) * innov[ich238][iloc] +
227  0.454 * innov[ich314][iloc] - innov[ich890][iloc]) * w1f6;
228  dsval = std::max(static_cast<float>(0.0), dsval);
229  }
230  factch4[iloc] = pow(clwx, 2) + pow(innov[ich528][iloc] * w2f4, 2);
231  factch6[iloc] = pow(dsval, 2) + pow(innov[ich544][iloc] * w2f6, 2);
232 
233  // Window channel sanity check
234  // If any of the window channels is bad, skip all window channels
235  // List of surface sensitivity channels
236  std::vector<float> OmFs{std::abs(innov[ich238][iloc]), std::abs(innov[ich314][iloc]),
237  std::abs(innov[ich503][iloc]), std::abs(innov[ich528][iloc]),
238  std::abs(innov[ich536][iloc]), std::abs(innov[ich544][iloc]),
239  std::abs(innov[ich890][iloc])};
240  bool result = false;
241  result = any_of(OmFs.begin(), OmFs.end(), [](float x){return x > 200.0;});
242 
243  if (result) {
244  // remove channels 1-7, 16, 17-22
245  for (size_t ich = ich238; ich <= ich544; ++ich) {
246  affected_channels[ich][iloc] = 1;
247  }
248  affected_channels[ich890][iloc] = 1;
249  for (size_t ich = ich1650; ich <= ich1830e; ++ich) {
250  affected_channels[ich][iloc] = 1;
251  }
252  } else {
253  // Hydrometeor check over water surface
254  if (water_frac[iloc] > 0.99) {
255  // Cloud water retrieval sanity check
256  if (clwobs[0][iloc] > 999.0) {
257  for (size_t ich = ich238; ich <= ich544; ++ich) {
258  affected_channels[ich][iloc] = 1;
259  }
260  affected_channels[ich890][iloc] = 1;
261  for (size_t ich = ich1650; ich <= ich1830e; ++ich) {
262  affected_channels[ich][iloc] = 1;
263  }
264  }
265  // Precipitation check (factch6: 54.4 GHz)
266  if (factch6[iloc] >= 1.0) {
267  for (size_t ich = ich238; ich <= ich544; ++ich) {
268  affected_channels[ich][iloc] = 1;
269  }
270  affected_channels[ich890][iloc] = 1;
271  for (size_t ich = ich1650; ich <= ich1830e; ++ich) {
272  affected_channels[ich][iloc] = 1;
273  }
274  // Scattering check (53.6GHz cloud effect)
275  } else if (cldeff_obs536 < -0.5) {
276  for (size_t ich = ich238; ich <= ich544; ++ich) {
277  affected_channels[ich][iloc] = 1;
278  }
279  affected_channels[ich890][iloc] = 1;
280  for (size_t ich = ich1650; ich <= ich1830e; ++ich) {
281  affected_channels[ich][iloc] = 1;
282  }
283  // Scattering checks (89GHz vs 166GHz)
284  } else if (std::abs(cldeff_obs890 - cldeff_obs1650) > 10.0) {
285  affected_channels[ich890][iloc] = 1;
286  for (size_t ich = ich1650; ich <= ich1830e; ++ich) {
287  affected_channels[ich][iloc] = 1;
288  }
289  if (std::abs(cldeff_obs890 - cldeff_obs1650) > 15.0) {
290  for (size_t ich = ich238; ich <= ich544; ++ich) {
291  affected_channels[ich][iloc] = 1;
292  }
293  }
294  // Sensitivity of BT to the surface emissivity check
295  } else {
296  float thrd238 = 0.025, thrd314 = 0.015, thrd503 = 0.030, thrd890 = 0.030;
297  float de238 = 0.0, de314 = 0.0, de503 = 0.0, de890 = 0.0;
298  float dbtde238 = dbtde[ich238][iloc];
299  float dbtde314 = dbtde[ich314][iloc];
300  float dbtde503 = dbtde[ich503][iloc];
301  float dbtde890 = dbtde[ich890][iloc];
302  if (dbtde238 != 0.0) de238 = std::abs(innov[ich238][iloc]) / dbtde238 *
303  (obserr0[ich238] / obserr[ich238][iloc]) *
304  (1.0 - std::max(1.0, 10.0*clwobs[0][iloc]));
305  if (dbtde314 != 0.0) de314 = std::abs(innov[ich314][iloc]) / dbtde314 *
306  (obserr0[ich314] / obserr[ich314][iloc]) *
307  (1.0 - std::max(1.0, 10.0*clwobs[0][iloc]));
308  if (dbtde503 != 0.0) de503 = std::abs(innov[ich503][iloc]) / dbtde503 *
309  (obserr0[ich503] / obserr[ich503][iloc]) *
310  (1.0 - std::max(1.0, 10.0*clwobs[0][iloc]));
311  if (dbtde890 != 0.0) de890 = std::abs(innov[ich890][iloc]) / dbtde890 *
312  (obserr0[ich890] / obserr[ich890][iloc]) *
313  (1.0 - std::max(1.0, 10.0*clwobs[0][iloc]));
314  bool qcemiss = false;
315  qcemiss = de238 > thrd238 || de314 > thrd314 || de503 > thrd503 || de890 > thrd890;
316  if (qcemiss) {
317  for (size_t ich = ich238; ich <= ich536; ++ich) {
318  affected_channels[ich][iloc] = 1;
319  }
320  affected_channels[ich890][iloc] = 1;
321  for (size_t ich = ich1650; ich <= ich1830e; ++ich) {
322  affected_channels[ich][iloc] = 1;
323  }
324  }
325  }
326  } else {
327  // Hydrometeor check over non-water (land/sea ice/snow) surface
328  // Precipitation check (factch6)
329  if (factch6[iloc] >= 1.0 || luse == false) {
330  for (size_t ich = ich238; ich <= ich544; ++ich) {
331  affected_channels[ich][iloc] = 1;
332  }
333  affected_channels[ich890][iloc] = 1;
334  for (size_t ich = ich1650; ich <= ich1830e; ++ich) {
335  affected_channels[ich][iloc] = 1;
336  }
337  // Thick cloud check (factch4)
338  } else if (factch4[iloc] > 0.5) {
339  for (size_t ich = ich238; ich <= ich536; ++ich) {
340  affected_channels[ich][iloc] = 1;
341  }
342  affected_channels[ich890][iloc] = 1;
343  for (size_t ich = ich1650; ich <= ich1830e; ++ich) {
344  affected_channels[ich][iloc] = 1;
345  }
346  // Sensitivity of BT to the surface emissivity check
347  } else {
348  float thrd238 = 0.020, thrd314 = 0.015, thrd503 = 0.035, thrd890 = 0.015;
349  float de238 = 0.0, de314 = 0.0, de503 = 0.0, de890 = 0.0;
350  float dbtde238 = dbtde[ich238][iloc];
351  float dbtde314 = dbtde[ich314][iloc];
352  float dbtde503 = dbtde[ich503][iloc];
353  float dbtde890 = dbtde[ich890][iloc];
354  if (dbtde238 != 0.0) de238 = std::abs(innov[ich238][iloc]) / dbtde238;
355  if (dbtde314 != 0.0) de314 = std::abs(innov[ich314][iloc]) / dbtde314;
356  if (dbtde503 != 0.0) de503 = std::abs(innov[ich503][iloc]) / dbtde503;
357  if (dbtde890 != 0.0) de890 = std::abs(innov[ich890][iloc]) / dbtde890;
358  bool qcemiss = false;
359  qcemiss = de238 > thrd238 || de314 > thrd314 || de503 > thrd503 || de890 > thrd890;
360  if (qcemiss) {
361  for (size_t ich = ich238; ich <= ich536; ++ich) {
362  affected_channels[ich][iloc] = 1;
363  }
364  affected_channels[ich890][iloc] = 1;
365  for (size_t ich = ich1650; ich <= ich1830e; ++ich) {
366  affected_channels[ich][iloc] = 1;
367  }
368  }
369  }
370  // surface type
371  }
372  // window channel sanity check
373  }
374  // loop over locations
375  }
376  // Output
377  for (size_t ichan = 0; ichan < nchans; ++ichan) {
378  for (size_t iloc = 0; iloc < nlocs; ++iloc) {
379  out[ichan][iloc] = affected_channels[ichan][iloc];
380  }
381  }
382 }
383 
384 // -----------------------------------------------------------------------------
385 
387  return invars_;
388 }
389 
390 // -----------------------------------------------------------------------------
391 
392 } // namespace ufo
HydrometeorCheckATMS(const eckit::LocalConfiguration &)
HydrometeorCheckATMSParameters options_
const ufo::Variables & requiredVariables() const
geovals required to compute the function
void compute(const ObsFilterData &, ioda::ObsDataVector< float > &) const
compute the result of the function
oops::Parameter< std::string > testBiasTerm
oops::RequiredParameter< std::vector< float > > obserrClearSky
Observation error for each channel under clear-sky condition.
oops::RequiredParameter< std::string > channelList
List of channels available for assimilation.
oops::RequiredParameter< Variable > clwretFunction
Function used to retrieve the cloud liquid water from observation (CLWRetMW)
oops::RequiredParameter< Variable > obserrFunction
oops::Parameter< std::string > testHofX
Name of the HofX group used to replace the default group (default is HofX)
oops::Parameter< std::string > testBias
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.
void get(const Variable &varname, std::vector< float > &values) const
Fills a std::vector with values of the specified variable.
oops::Variables toOopsVariables() const
Definition: Variable.cc:139
integer function nlocs(this)
Return the number of observational locations in this Locations object.
Definition: RunCRTM.h:27
static ObsFunctionMaker< HydrometeorCheckATMS > makerHydrometeorCheckATMS_("HydrometeorCheckATMS")
util::Duration abs(const util::Duration &duration)