UFO
ObsBiasCovariance.cc
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2018-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 
8 #include <Eigen/Core>
9 #include <cmath>
10 #include <fstream>
11 #include <memory>
12 #include <random>
13 #include <set>
14 
15 #include "ufo/ObsBiasCovariance.h"
16 
17 #include "ioda/distribution/Accumulator.h"
18 #include "ioda/Engines/Factory.h"
19 #include "ioda/Layout.h"
20 #include "ioda/ObsGroup.h"
21 #include "ioda/ObsSpace.h"
22 #include "ioda/ObsVector.h"
23 
24 #include "oops/util/IntSetParser.h"
25 #include "oops/util/Logger.h"
26 #include "oops/util/Random.h"
27 
28 #include "ufo/ObsBias.h"
29 #include "ufo/ObsBiasIncrement.h"
32 
33 namespace ufo {
34 
35 // -----------------------------------------------------------------------------
36 
38  const Parameters_ & params)
39  : odb_(odb), prednames_(0), vars_(odb.obsvariables()), variances_(),
40  preconditioner_(0),
41  ht_rinv_h_(0), obs_num_(0), analysis_variances_(0), minimal_required_obs_number_(0) {
42  oops::Log::trace() << "ObsBiasCovariance::Constructor starting" << std::endl;
43 
44  // Predictor factory
45  for (const PredictorParametersWrapper &wrapper :
46  params.variationalBC.value().predictors.value()) {
47  std::shared_ptr<PredictorBase> pred(PredictorFactory::create(wrapper.predictorParameters,
48  vars_));
49  prednames_.push_back(pred->name());
50  }
51 
52  if (prednames_.size()*vars_.size() > 0) {
53  if (params.covariance.value() == boost::none)
54  throw eckit::UserError("obs bias.covariance section missing from the YAML file");
55  const ObsBiasCovarianceParameters &biasCovParams = *params.covariance.value();
56 
57  // Get the minimal required filtered obs number
59 
60  // Override the variance range if provided
61  {
62  const std::vector<double> &range = biasCovParams.varianceRange.value();
63  ASSERT(range.size() == 2);
64  smallest_variance_ = range[0];
65  largest_variance_ = range[1];
66  }
67 
68  // Override the preconditioning step size if provided
69  step_size_ = biasCovParams.stepSize;
70 
71  // Override the largest analysis variance if provided
73 
74  // Initialize the variances to upper limit
75  variances_ = Eigen::VectorXd::Constant(prednames_.size()*vars_.size(), largest_variance_);
76 
77  // Initialize the hessian contribution to zero
78  ht_rinv_h_.resize(prednames_.size() * vars_.size());
79  std::fill(ht_rinv_h_.begin(), ht_rinv_h_.end(), 0.0);
80 
81  // Initialize the preconditioner to default step size
82  preconditioner_.resize(prednames_.size() * vars_.size());
83  std::fill(preconditioner_.begin(), preconditioner_.end(), step_size_);
84 
85  // Initialize obs_num_ to ZERO
86  obs_num_.resize(vars_.size());
87  std::fill(obs_num_.begin(), obs_num_.end(), 0);
88 
89  // Initialize analysis error variances to the upper limit
90  analysis_variances_.resize(prednames_.size() * vars_.size());
92 
93  // Initializes from given prior
94  if (biasCovParams.prior.value() != boost::none) {
95  const ObsBiasCovariancePriorParameters &priorParams = *biasCovParams.prior.value();
96 
97  // Get default inflation ratio
98  const double inflation_ratio = priorParams.inflation.value().ratio;
99 
100  // Check the large inflation ratio when obs number < minimal_required_obs_number
101  const double large_inflation_ratio = priorParams.inflation.value().ratioForSmallDataset;
102 
103  // read in Variances prior (analysis_variances_) and number of obs. (obs_num_)
104  // from previous cycle
105  this->read(priorParams);
106 
107  // set variances for bias predictor coeff. based on diagonal info
108  // of previous analysis error variance
109  std::size_t ii;
110  for (std::size_t j = 0; j < vars_.size(); ++j) {
111  const double inflation = (obs_num_[j] <= minimal_required_obs_number_) ?
112  large_inflation_ratio : inflation_ratio;
113  for (std::size_t p = 0; p < prednames_.size(); ++p) {
114  ii = j*prednames_.size() + p;
115  if (inflation > inflation_ratio)
117  variances_[ii] = inflation * analysis_variances_[ii] + smallest_variance_;
121  }
122  }
123  }
124  }
125 
126  oops::Log::trace() << "ObsBiasCovariance::Constructor is done" << std::endl;
127 }
128 
129 // -----------------------------------------------------------------------------
130 
132  oops::Log::trace() << "ObsBiasCovariance::read from file " << std::endl;
133 
134  if (params.inputFile.value() != boost::none) {
135  // Open an hdf5 file, read only
136  ioda::Engines::BackendNames backendName = ioda::Engines::BackendNames::Hdf5File;
137  ioda::Engines::BackendCreationParameters backendParams;
138  backendParams.fileName = *params.inputFile.value();
139  backendParams.action = ioda::Engines::BackendFileActions::Open;
140  backendParams.openMode = ioda::Engines::BackendOpenModes::Read_Only;
141 
142  // Create the backend and attach it to an ObsGroup
143  // Use the None DataLyoutPolicy for now to accommodate the current file format
144  ioda::Group backend = constructBackend(backendName, backendParams);
145  ioda::ObsGroup obsgroup = ioda::ObsGroup(backend,
146  ioda::detail::DataLayoutPolicy::generate(
147  ioda::detail::DataLayoutPolicy::Policies::None));
148 
149  // Read coefficients error variances into the Eigen array
150  ioda::Variable bcerrvar = obsgroup.vars["bias_coeff_errors"];
151  Eigen::ArrayXXf allbcerrors;
152  bcerrvar.readWithEigenRegular(allbcerrors);
153 
154  // Read nobs into Eigen array
155  ioda::Variable nobsvar = obsgroup.vars["number_obs_assimilated"];
156  Eigen::ArrayXf nobsassim;
157  nobsvar.readWithEigenRegular(nobsassim);
158 
159  // Find indices of predictors and variables/channels that we need in the data read from the file
160  const std::vector<int> pred_idx = getRequiredVariableIndices(obsgroup, "predictors",
161  prednames_.begin(), prednames_.end());
162  const std::vector<int> var_idx = getRequiredVarOrChannelIndices(obsgroup, vars_);
163 
164  // Filter predictors and channels that we need
165  // FIXME: may be possible by indexing allbcerrors(pred_idx, chan_idx) when Eigen 3.4
166  // is available
167  for (size_t jvar = 0; jvar < var_idx.size(); ++jvar) {
168  obs_num_[jvar] = nobsassim(var_idx[jvar]);
169  for (size_t jpred = 0; jpred < pred_idx.size(); ++jpred) {
170  analysis_variances_[jvar*pred_idx.size()+jpred] =
171  allbcerrors(pred_idx[jpred], var_idx[jvar]);
172  }
173  }
174  }
175 
176  oops::Log::trace() << "ObsBiasCovariance::read is done " << std::endl;
177 }
178 
179 // -----------------------------------------------------------------------------
180 
181 void ObsBiasCovariance::write(const eckit::Configuration & conf) {
182  oops::Log::trace() << "ObsBiasCovariance::write to file " << std::endl;
183  oops::Log::trace() << "ObsBiasCovariance::write is not implemented " << std::endl;
184  oops::Log::trace() << "ObsBiasCovariance::write is done " << std::endl;
185 }
186 
187 // -----------------------------------------------------------------------------
188 
189 void ObsBiasCovariance::linearize(const ObsBias & bias, const eckit::Configuration & innerConf) {
190  oops::Log::trace() << "ObsBiasCovariance::linearize starts" << std::endl;
191  if (bias) {
192  // Retrieve the QC flags and do statistics from second outer loop
193  const int jouter = innerConf.getInt("iteration");
194  if (jouter >= 1) {
195  std::unique_ptr<ioda::Accumulator<std::vector<size_t>>> obs_num_accumulator =
196  odb_.distribution()->createAccumulator<size_t>(obs_num_.size());
197 
198  // Retrieve the QC flags of previous outer loop and recalculate the number of effective obs.
199  const std::string qc_group_name = "EffectiveQC" + std::to_string(jouter-1);
200  const std::vector<std::string> vars = odb_.obsvariables().variables();
201  std::vector<int> qc_flags(odb_.nlocs(), 999);
202  for (std::size_t jvar = 0; jvar < vars.size(); ++jvar) {
203  if (odb_.has(qc_group_name, vars[jvar])) {
204  odb_.get_db(qc_group_name, vars[jvar], qc_flags);
205  for (std::size_t jloc = 0; jloc < qc_flags.size(); ++jloc)
206  if (qc_flags[jloc] == 0)
207  obs_num_accumulator->addTerm(jloc, jvar, 1);
208  } else {
209  throw eckit::UserError("Unable to find QC flags : " + vars[jvar] + "@" + qc_group_name);
210  }
211  }
212 
213  // Sum across the processors
214  obs_num_ = obs_num_accumulator->computeResult();
215 
216  const float missing = util::missingValue(missing);
217 
218  // compute the hessian contribution from Jo bias terms channel by channel
219  // retrieve the effective error (after QC) for this channel
220  const std::string err_group_name = "EffectiveError" + std::to_string(jouter-1);
221  ioda::ObsVector r_inv(odb_, err_group_name);
222 
223  // compute \mathrm{R}^{-1}
224  std::size_t nvars = r_inv.nvars();
225  for (size_t vv = 0; vv < nvars; ++vv) {
226  for (size_t ii = 0; ii < r_inv.nlocs(); ++ii) {
227  if (r_inv[ii*nvars + vv] != missing) {
228  r_inv[ii*nvars + vv] = 1.0f / pow(r_inv[ii*nvars + vv], 2);
229  } else {
230  r_inv[ii*nvars + vv] = 0.0f;
231  }
232  }
233  }
234 
235  // compute \mathrm{H}_\beta^\intercal \mathrm{R}^{-1} \mathrm{H}_\beta
236  // -----------------------------------------
237  std::unique_ptr<ioda::Accumulator<std::vector<double>>> ht_rinv_h_accumulator =
238  odb_.distribution()->createAccumulator<double>(ht_rinv_h_.size());
239  for (std::size_t p = 0; p < prednames_.size(); ++p) {
240  // retrieve the predictors
241  const ioda::ObsVector predx(odb_, prednames_[p] + "Predictor");
242 
243  // for each variable
244  ASSERT(r_inv.nlocs() == predx.nlocs());
245  std::size_t nvars = predx.nvars();
246  // only keep the diagnoal
247  for (size_t vv = 0; vv < nvars; ++vv) {
248  for (size_t ii = 0; ii < predx.nlocs(); ++ii)
249  ht_rinv_h_accumulator->addTerm(ii,
250  vv*prednames_.size() + p,
251  pow(predx[ii*nvars + vv], 2) * r_inv[ii*nvars + vv]);
252  }
253  }
254 
255  // Sum the hessian contributions across the tasks
256  ht_rinv_h_ = ht_rinv_h_accumulator->computeResult();
257  }
258 
259  // reset variances for bias predictor coeff. based on current data count
260  for (std::size_t j = 0; j < obs_num_.size(); ++j) {
262  for (std::size_t p = 0; p < prednames_.size(); ++p)
263  variances_[j*prednames_.size() + p] = smallest_variance_;
264  }
265  }
266 
267  // set a coeff. factor for variances of control variables
268  for (std::size_t j = 0; j < vars_.size(); ++j) {
269  for (std::size_t p = 0; p < prednames_.size(); ++p) {
270  const std::size_t index = j*prednames_.size() + p;
271  preconditioner_[index] = step_size_;
272  // L = \mathrm{A}^{-1}
273  if (obs_num_[j] > 0)
274  preconditioner_[index] = 1.0 / (1.0 / variances_[index] + ht_rinv_h_[index]);
276  if (ht_rinv_h_[index] > 0.0) {
277  analysis_variances_[index] = 1.0 / (1.0 / variances_[index] + ht_rinv_h_[index]);
278  } else {
280  }
281  }
282  }
283  }
284  }
285  oops::Log::trace() << "ObsBiasCovariance::linearize is done" << std::endl;
286 }
287 
288 // -----------------------------------------------------------------------------
289 
291  ObsBiasIncrement & dx2) const {
292  oops::Log::trace() << "ObsBiasCovariance::multiply starts" << std::endl;
293 
294  dx2.data().array() = dx1.data().array() * variances_.array();
295 
296  oops::Log::trace() << "ObsBiasCovariance::multiply is done" << std::endl;
297 }
298 
299 // -----------------------------------------------------------------------------
300 
302  ObsBiasIncrement & dx2) const {
303  oops::Log::trace() << "ObsBiasCovariance::inverseMultiply starts" << std::endl;
304 
305  dx2.data().array() = dx1.data().array() / variances_.array();
306 
307  oops::Log::trace() << "ObsBiasCovariance::inverseMultiply is done" << std::endl;
308 }
309 
310 // -----------------------------------------------------------------------------
311 
313  oops::Log::trace() << "ObsBiasCovariance::randomize starts" << std::endl;
314  if (dx) {
315  static util::NormalDistribution<double> dist(variances_.size());
316  for (std::size_t jj = 0; jj < variances_.size(); ++jj) {
317  dx.data()[jj] = dist[jj] * std::sqrt(variances_[jj]);
318  }
319  }
320  oops::Log::trace() << "ObsBiasCovariance::randomize is done" << std::endl;
321 }
322 
323 // -----------------------------------------------------------------------------
324 
325 } // namespace ufo
std::vector< std::string > prednames_
void read(const ObsBiasCovariancePriorParameters &)
std::size_t minimal_required_obs_number_
Eigen::VectorXd variances_
std::vector< std::size_t > obs_num_
void write(const eckit::Configuration &)
std::vector< double > ht_rinv_h_
void inverseMultiply(const ObsBiasIncrement &, ObsBiasIncrement &) const
void randomize(ObsBiasIncrement &) const
ObsBiasCovariance(ioda::ObsSpace &odb, const Parameters_ &params)
void multiply(const ObsBiasIncrement &, ObsBiasIncrement &) const
std::vector< double > preconditioner_
oops::Variables vars_
variables for which bias correction coefficients will be updated
void linearize(const ObsBias &, const eckit::Configuration &)
std::vector< double > analysis_variances_
oops::RequiredParameter< size_t > minimalRequiredObsNumber
oops::OptionalParameter< ObsBiasCovariancePriorParameters > prior
oops::Parameter< std::vector< double > > varianceRange
oops::Parameter< double > largestAnalysisVariance
oops::Parameter< double > stepSize
oops::RequiredParameter< ObsBiasCovariancePriorInflationParameters > inflation
oops::OptionalParameter< std::string > inputFile
Contains increments to bias correction coefficients.
const Eigen::VectorXd & data() const
Return bias coefficient increments.
Parameters influencing the bias correction process.
oops::Parameter< StaticOrVariationalBCParameters > variationalBC
List of predictors with coefficients determined by variational analysis (VarBC).
oops::OptionalParameter< ObsBiasCovarianceParameters > covariance
Options controlling the covariance matrix.
static std::unique_ptr< PredictorBase > create(const PredictorParametersBase &parameters, const oops::Variables &vars)
Create and return a new predictor.
Contains a polymorphic parameter holding an instance of a subclass of PredictorParametersBase.
constexpr int missing
Definition: QCflags.h:20
std::set< size_t > Group
Definition: RunCRTM.h:27
std::vector< int > getRequiredVariableIndices(const ioda::ObsGroup &obsgroup, const std::string &varname, typename std::vector< std::string >::const_iterator elements_to_look_for_begin, typename std::vector< std::string >::const_iterator elements_to_look_for_end)
std::vector< int > getRequiredVarOrChannelIndices(const ioda::ObsGroup &obsgroup, const oops::Variables &vars_to_look_for)