UFO
CloudCostFunction.cc
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2021 Met Office UK
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 <Eigen/Dense>
9 #include <algorithm>
10 #include <set>
11 
12 #include "ioda/ObsDataVector.h"
13 #include "oops/util/IntSetParser.h"
14 #include "oops/util/missingValues.h"
16 #include "ufo/filters/Variable.h"
18 
19 namespace ufo {
20 
22 
23 CloudCostFunction::CloudCostFunction(const eckit::LocalConfiguration & conf)
24  : invars_() {
25  // Initialize options
26  options_.deserialize(conf);
27 
28  // List of field names for B matrix
29  fields_ = options_.field_names.value();
30 
31  // Get channels for computing scattering index from options
32  std::set<int> chanset = oops::parseIntSet(options_.chanlist.value());
33  channels_.assign(chanset.begin(), chanset.end());
34 
35  // List of required data
36  for (size_t i = 0; i < fields_.size(); ++i) {
37  invars_ += Variable("brightness_temperature_jacobian_"+fields_[i]+"@ObsDiag", channels_);
38  }
39  invars_ += Variable("brightness_temperature@ObsValue", channels_);
40  invars_ += Variable("brightness_temperature@"+options_.HofXGroup.value(), channels_);
41  invars_ += Variable("latitude@MetaData");
42  if (options_.qtotal_lnq_gkg.value()) {
43  invars_ += Variable("specific_humidity@GeoVaLs");
44  invars_ += Variable("mass_content_of_cloud_liquid_water_in_atmosphere_layer@GeoVaLs");
45  invars_ += Variable("mass_content_of_cloud_ice_in_atmosphere_layer@GeoVaLs");
46  invars_ += Variable("air_pressure@GeoVaLs");
47  invars_ += Variable("air_temperature@GeoVaLs");
48  invars_ += Variable("surface_pressure@GeoVaLs");
49  invars_ += Variable("surface_temperature@GeoVaLs");
50  invars_ += Variable("specific_humidity_at_two_meters_above_surface@GeoVaLs");
51  }
52 }
53 
54 // -----------------------------------------------------------------------------
55 
57  ioda::ObsDataVector<float> & out) const {
58  // Get dimensions
59  const size_t nlocs = in.nlocs();
60  const size_t nchans = channels_.size();
61  ASSERT(nchans > 0);
62  ASSERT(out.nvars() == 1);
63 
64  // B, R error covariance objects
65  eckit::LocalConfiguration bMatrixConf;
66  bMatrixConf.set("BMatrix", options_.bmatrix_filepath.value());
67  bMatrixConf.set("background fields", options_.field_names.value());
68  bMatrixConf.set("qtotal", options_.qtotal_lnq_gkg.value());
69  MetOfficeBMatrixStatic staticB(bMatrixConf);
70 
71  eckit::LocalConfiguration rMatrixConf;
72  rMatrixConf.set("RMatrix", options_.rmatrix_filepath.value());
73  MetOfficeRMatrixRadiance staticR(rMatrixConf);
74 
75  bool split_rain = options_.qtotal_split_rain.value();
76 
77  const std::string clw_name = "mass_content_of_cloud_liquid_water_in_atmosphere_layer";
78  const std::string ciw_name = "mass_content_of_cloud_ice_in_atmosphere_layer";
79  std::vector<float> gv_pres(nlocs), gv_temp(nlocs), gv_qgas(nlocs), gv_clw(nlocs), gv_ciw(nlocs),
80  humidity_total(nlocs);
81 
82  // Determine if pressure is ascending or descending (B-matrix assumption)
83  size_t np = in.nlevs(Variable("air_pressure@GeoVaLs"));
84  std::vector<float> gv_pres_1(nlocs), gv_pres_N(nlocs);
85  in.get(Variable("air_pressure@GeoVaLs"), 0, gv_pres_1);
86  in.get(Variable("air_pressure@GeoVaLs"), np - 1, gv_pres_N);
87  const float missing = util::missingValue(missing);
88  ASSERT(gv_pres_1[0] != missing);
89  ASSERT(gv_pres_N[0] != missing);
90  bool p_ascending = (gv_pres_N[0] > gv_pres_1[0]);
91 
92  // Assemble combined Jacobian from component fields
93  std::vector<std::vector<std::vector<float>>>
94  jac_vec(nlocs, std::vector<std::vector<float>>(nchans, std::vector<float>()));
95  for (size_t ifield = 0; ifield < fields_.size(); ++ifield) {
96  if (options_.qtotal_lnq_gkg.value() &&
97  (fields_[ifield] == clw_name || fields_[ifield] == ciw_name)) {
98  // qtotal ln(g/kg) Jacobian calculated when "specific_humidity" is reached in field list
99  continue;
100  }
101  std::string jac_name = "brightness_temperature_jacobian_"+fields_[ifield]+"@ObsDiag";
102  size_t nlevs = in.nlevs(Variable(jac_name, channels_)[0]);
103  std::vector<float> jac_store(nlocs);
104  for (size_t ilev = 0; ilev < nlevs; ++ilev) {
105  const int level_gv = (p_ascending ? ilev : nlevs-ilev-1);
106  const int level_jac = (options_.reverse_Jacobian.value() ? nlevs-level_gv-1 : level_gv);
107  if (fields_[ifield] == "specific_humidity" && options_.qtotal_lnq_gkg.value()) {
108  in.get(Variable("air_pressure@GeoVaLs"), level_gv, gv_pres);
109  in.get(Variable("air_temperature@GeoVaLs"), level_gv, gv_temp);
110  in.get(Variable("specific_humidity@GeoVaLs"), level_gv, gv_qgas);
111  in.get(Variable(clw_name+"@GeoVaLs"), level_gv, gv_clw);
112  in.get(Variable(ciw_name+"@GeoVaLs"), level_gv, gv_ciw);
113  std::vector<float> qsaturated(nlocs);
114  ufo_ops_satrad_qsatwat_f90(qsaturated.data(), gv_temp.data(), gv_pres.data(),
115  static_cast<int>(nlocs));
116  for (size_t iloc = 0; iloc < nlocs; ++iloc) {
117  // Ensure specific humidity is within limits
118  gv_qgas[iloc] = std::max(gv_qgas[iloc], options_.min_q.value());
119  gv_qgas[iloc] = std::min(gv_qgas[iloc], qsaturated[iloc]);
120  humidity_total[iloc] = gv_qgas[iloc] + gv_clw[iloc]; // ice is neglected for partitioning
121  }
122  int qsplit_partition_mode = 1; // partition total water into vapour, liquid and ice
123  std::vector<float> qsplit_gas(nlocs), qsplit_clw(nlocs), qsplit_ciw(nlocs);
124  ufo_ops_satrad_qsplit_f90(qsplit_partition_mode, static_cast<int>(nlocs), gv_pres.data(),
125  gv_temp.data(), humidity_total.data(), qsplit_gas.data(),
126  qsplit_clw.data(), qsplit_ciw.data(), split_rain);
127  for (size_t iloc = 0; iloc < nlocs; ++iloc) {
128  // For scattering use sum of geovals for qtotal, otherwise use partitioned quantities
129  if (options_.scattering_switch.value()) {
130  humidity_total[iloc] += gv_ciw[iloc];
131  } else {
132  humidity_total[iloc] = qsplit_gas[iloc] + qsplit_clw[iloc] + qsplit_ciw[iloc];
133  }
134  }
135  }
136 
137  for (size_t ichan = 0; ichan < nchans; ++ichan) {
138  in.get(Variable(jac_name, channels_)[ichan], level_jac, jac_store);
139 
140  if (fields_[ifield] == "specific_humidity" && options_.qtotal_lnq_gkg.value()) {
141  std::vector<float> jac_clw(nlocs), jac_ciw(nlocs);
142  in.get(Variable("brightness_temperature_jacobian_"+clw_name+"@ObsDiag", channels_)[ichan],
143  level_jac, jac_clw);
144  in.get(Variable("brightness_temperature_jacobian_"+ciw_name+"@ObsDiag", channels_)[ichan],
145  level_jac, jac_ciw);
146  std::vector<float> dq_dqtotal(nlocs), dql_dqtotal(nlocs), dqi_dqtotal(nlocs);
147  int qsplit_derivative_mode = 2; // compute derivatives
148  ufo_ops_satrad_qsplit_f90(qsplit_derivative_mode, static_cast<int>(nlocs), gv_pres.data(),
149  gv_temp.data(), humidity_total.data(), dq_dqtotal.data(),
150  dql_dqtotal.data(), dqi_dqtotal.data(), split_rain);
151  // Jacobian dy/dx for observation y, humdity x in units kg/kg
152  // For alternative units of B-matrix humidity z in ln(g/kg)
153  // chain rule gives dy/dz = x.(dy/dx)
154  // Gradient due to ice is ignored unless we are using scattering radiative transfer
155  // dTb/dln(qt) = qt*(dTb/dq*dq/dqt + dTb/dql*dql/dqt [+ dTb/dqi*dqi/dqt])
156  for (size_t iloc = 0; iloc < nlocs; ++iloc) {
157  jac_store[iloc] *= dq_dqtotal[iloc];
158  jac_store[iloc] += jac_clw[iloc]*dql_dqtotal[iloc];
159  if (options_.scattering_switch.value()) {
160  jac_store[iloc] += jac_ciw[iloc]*dqi_dqtotal[iloc];
161  }
162  jac_store[iloc] *= humidity_total[iloc];
163  }
164  }
165 
166  if (fields_[ifield] == "specific_humidity_at_two_meters_above_surface"
167  && options_.qtotal_lnq_gkg.value()) {
168  in.get(Variable("surface_pressure@GeoVaLs"), level_gv, gv_pres);
169  in.get(Variable("surface_temperature@GeoVaLs"), level_gv, gv_temp);
170  in.get(Variable("specific_humidity_at_two_meters_above_surface@GeoVaLs"),
171  level_gv, gv_qgas);
172  std::vector<float> qsaturated(nlocs);
173  ufo_ops_satrad_qsatwat_f90(qsaturated.data(), gv_temp.data(), gv_pres.data(),
174  static_cast<int>(nlocs));
175  for (size_t iloc = 0; iloc < nlocs; ++iloc) {
176  gv_qgas[iloc] = std::max(gv_qgas[iloc], options_.min_q.value());
177  gv_qgas[iloc] = std::min(gv_qgas[iloc], qsaturated[iloc]);
178  // dTb/dln(q2m) = q2m*(dTb/dq2m)
179  jac_store[iloc] *= gv_qgas[iloc];
180  }
181  }
182 
183  for (size_t iloc = 0; iloc < nlocs; ++iloc) {
184  jac_vec[iloc][ichan].push_back(jac_store[iloc]);
185  }
186  }
187  }
188  }
189 
190  const size_t sizeB = staticB.getsize();
191  for (size_t iloc = 0; iloc < nlocs; ++iloc) {
192  for (size_t ichan = 0; ichan < nchans; ++ichan) {
193  ASSERT(jac_vec[iloc][ichan].size() == sizeB);
194  }
195  }
196 
197  // Get departures = ObsValue - HofX (where HofX is bias corrected)
198  std::vector<std::vector<float>> departures(nlocs, std::vector<float>(nchans));
199  std::vector<float> obsvalues(nlocs);
200  std::vector<float> bgvalues(nlocs);
201  std::vector<bool> is_out_of_bounds(nlocs, false);
202  for (size_t ichan = 0; ichan < nchans; ++ichan) {
203  in.get(Variable("brightness_temperature@ObsValue", channels_)[ichan], obsvalues);
204  in.get(Variable("brightness_temperature@"+options_.HofXGroup.value(), channels_)[ichan],
205  bgvalues);
206  for (size_t iloc = 0; iloc < nlocs; ++iloc) {
207  departures[iloc][ichan] = obsvalues[iloc] - bgvalues[iloc];
208  // Flag observations outside expected bounds
209  if (obsvalues[iloc] < options_.minTb.value() || obsvalues[iloc] > options_.maxTb.value()) {
210  is_out_of_bounds[iloc] = true;
211  }
212  }
213  }
214 
215  std::vector<float> latitude(nlocs);
216  in.get(Variable("latitude@MetaData"), latitude);
217  Eigen::MatrixXf Hmatrix(nchans, sizeB);
218 
219  for (size_t iloc = 0; iloc < nlocs; ++iloc) {
220  if (is_out_of_bounds[iloc]) {
221  out[0][iloc] = options_.maxCost.value();
222  continue;
223  }
224 
225  for (size_t ichan = 0; ichan < nchans; ++ichan) {
226  Hmatrix.row(ichan) = Eigen::Map<Eigen::VectorXf>(jac_vec[iloc][ichan].data(), sizeB);
227  }
228 
229  // Matrix of departures dy
230  Eigen::Map<Eigen::VectorXf> dy(departures[iloc].data(), nchans);
231 
232  // Calculate Scratch_matrix = H.B.H^T + R
233  Eigen::MatrixXf BHT;
234  staticB.multiply(latitude[iloc], Hmatrix.transpose(), BHT);
235  Eigen::MatrixXf HBHT = Hmatrix*BHT;
236  Eigen::MatrixXf Scratch_matrix;
237  staticR.add(channels_, HBHT, Scratch_matrix);
238 
239  // Calculate Scratch_matrix2 = Scratch_matrix^-1.dy using Cholesky decomposition
240  Eigen::LLT<Eigen::MatrixXf> decomposition(Scratch_matrix);
241  if (decomposition.info() == Eigen::NumericalIssue) {
242  oops::Log::warning() <<
243  "CloudCostFunction Scratch_matrix appears not to be positive definite" << std::endl;
244  out[0][iloc] = options_.maxCost.value();
245  continue;
246  }
247  Eigen::VectorXf Scratch_matrix2 = decomposition.solve(dy);
248 
249  // Final cost
250  float Cost_final = 0.5*dy.transpose()*Scratch_matrix2;
251  Cost_final /= static_cast<float>(nchans); // normalise by number of channels
252  out[0][iloc] = std::min(Cost_final, options_.maxCost.value());
253  }
254 }
255 
256 // -----------------------------------------------------------------------------
257 
259  return invars_;
260 }
261 
262 // -----------------------------------------------------------------------------
263 
264 } // namespace ufo
std::vector< std::string > fields_
std::vector< int > channels_
CloudCostFunctionParameters 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
CloudCostFunction(const eckit::LocalConfiguration &=eckit::LocalConfiguration())
oops::RequiredParameter< std::vector< std::string > > field_names
List of geovals describing fields required from the B-matrix.
oops::Parameter< bool > qtotal_split_rain
Include treatment of rain when splitting total humidity into constituent phases.
oops::Parameter< bool > qtotal_lnq_gkg
B-matrix file contains error covariances for ln(qtotal in units g/kg)
oops::Parameter< bool > reverse_Jacobian
Jacobian vertical ordering is reverse of geovals.
oops::Parameter< float > maxTb
Maximum bound for ObsValue brightness temperature.
oops::RequiredParameter< std::string > chanlist
Set of channels used in the calculation of the cost function.
oops::RequiredParameter< std::string > rmatrix_filepath
Path to location of file describing the R-matrix.
oops::Parameter< std::string > HofXGroup
Name of the H(x) group used in the cost function calculation.
oops::Parameter< float > maxCost
Maximum value for final cost returned by the ObsFunction.
oops::Parameter< float > min_q
Limit specific humidity to minimum value.
oops::RequiredParameter< std::string > bmatrix_filepath
Path to location of file describing the B-matrix.
oops::Parameter< float > minTb
Minimum bound for ObsValue brightness temperature.
oops::Parameter< bool > scattering_switch
Include gradient due to ice in brightness temperature total humidity Jacobian.
size_t getsize(void) const
Return bmatrix size (number of rows or columns of square matrix)
void multiply(const float, const Eigen::MatrixXf &, Eigen::MatrixXf &) const
Multiply input matrix by bmatrix array based on latitude.
void add(const std::vector< int > &, const Eigen::MatrixXf &, Eigen::MatrixXf &) const
Add r matrix variance onto input array.
ObsFilterData provides access to all data related to an ObsFilter.
size_t nlocs() const
Returns the number of locations in the associated ObsSpace.
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.
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
static ObsFunctionMaker< CloudCostFunction > makerCloudCostFunction_("CloudCostFunction")
void ufo_ops_satrad_qsatwat_f90(float *, const float *, const float *, const int &)
void ufo_ops_satrad_qsplit_f90(const int &, const int &, const float *, const float *, const float *, float *, float *, float *, const bool &)