UFO
ObsBias.cc
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2017-2021 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 "ufo/ObsBias.h"
9 
10 #include <Eigen/Dense>
11 #include <algorithm>
12 #include <fstream>
13 #include <iomanip>
14 #include <set>
15 
16 #include "ioda/Engines/Factory.h"
17 #include "ioda/Engines/HH.h"
18 #include "ioda/Layout.h"
19 #include "ioda/ObsGroup.h"
20 #include "ioda/ObsVector.h"
21 
22 #include "oops/base/Variables.h"
23 #include "oops/util/IntSetParser.h"
24 #include "oops/util/Logger.h"
25 
26 #include "ufo/ObsBiasIncrement.h"
27 #include "ufo/ObsDiagnostics.h"
29 
30 namespace ufo {
31 
32 // -----------------------------------------------------------------------------
33 
34 ObsBias::ObsBias(ioda::ObsSpace & odb, const ObsBiasParameters & params)
35  : numStaticPredictors_(0), numVariablePredictors_(0), vars_(odb.obsvariables()),
36  rank_(odb.distribution()->rank()) {
37  oops::Log::trace() << "ObsBias::create starting." << std::endl;
38 
39  // Predictor factory
40  for (const PredictorParametersWrapper &wrapper :
41  params.staticBC.value().predictors.value()) {
42  initPredictor(wrapper);
44  }
45  for (const PredictorParametersWrapper &wrapper :
46  params.variationalBC.value().predictors.value()) {
47  initPredictor(wrapper);
49  }
50 
51  if (prednames_.size() * vars_.size() > 0) {
52  // Initialize the coefficients of variable predictors to 0. (Coefficients of static predictors
53  // are not stored; they are always equal to 1.)
54  biascoeffs_ = Eigen::VectorXd::Zero(numVariablePredictors_ * vars_.size());
55  // Read or initialize bias coefficients
56  this->read(params);
57  }
58 
59  oops::Log::trace() << "ObsBias::create done." << std::endl;
60 }
61 
62 // -----------------------------------------------------------------------------
63 
64 ObsBias::ObsBias(const ObsBias & other, const bool copy)
65  : predictors_(other.predictors_),
66  prednames_(other.prednames_),
67  numStaticPredictors_(other.numStaticPredictors_),
68  numVariablePredictors_(other.numVariablePredictors_),
69  vars_(other.vars_),
70  geovars_(other.geovars_), hdiags_(other.hdiags_), rank_(other.rank_) {
71  oops::Log::trace() << "ObsBias::copy ctor starting." << std::endl;
72 
73  // Initialize the biascoeffs
74  biascoeffs_ = Eigen::VectorXd::Zero(numVariablePredictors_ * vars_.size());
75 
76  // Copy the bias coeff data
77  if (copy && biascoeffs_.size() > 0) *this = other;
78 
79  oops::Log::trace() << "ObsBias::copy ctor done." << std::endl;
80 }
81 
82 // -----------------------------------------------------------------------------
83 
85  biascoeffs_ += dx.data();
86  return *this;
87 }
88 
89 // -----------------------------------------------------------------------------
90 
92  if (rhs.size() > 0 && this->size() == rhs.size()) {
95  prednames_ = rhs.prednames_;
98  vars_ = rhs.vars_;
99  geovars_ = rhs.geovars_;
100  hdiags_ = rhs.hdiags_;
101  rank_ = rhs.rank_;
102  }
103  return *this;
104 }
105 
106 // -----------------------------------------------------------------------------
107 
108 void ObsBias::read(const Parameters_ & params) {
109  oops::Log::trace() << "ObsBias::read and initialize from file, starting "<< std::endl;
110 
111  if (params.inputFile.value() != boost::none) {
112  // Open an hdf5 file with bias coefficients, read only
113  ioda::Engines::BackendNames backendName = ioda::Engines::BackendNames::Hdf5File;
114  ioda::Engines::BackendCreationParameters backendParams;
115  backendParams.fileName = *params.inputFile.value();
116  backendParams.action = ioda::Engines::BackendFileActions::Open;
117  backendParams.openMode = ioda::Engines::BackendOpenModes::Read_Only;
118 
119  // Create the backend and attach it to an ObsGroup
120  // Use the None DataLyoutPolicy for now to accommodate the current file format
121  ioda::Group backend = constructBackend(backendName, backendParams);
122  ioda::ObsGroup obsgroup = ioda::ObsGroup(backend,
123  ioda::detail::DataLayoutPolicy::generate(
124  ioda::detail::DataLayoutPolicy::Policies::None));
125 
126  // Read all coefficients into the Eigen array
127  ioda::Variable coeffvar = obsgroup.vars["bias_coefficients"];
128  Eigen::ArrayXXf allbiascoeffs;
129  coeffvar.readWithEigenRegular(allbiascoeffs);
130 
131  // Find indices of predictors and variables/channels that we need in the data read from the file
132  const std::vector<int> pred_idx = getRequiredVariableIndices(obsgroup, "predictors",
133  prednames_.begin() + numStaticPredictors_, prednames_.end());
134  const std::vector<int> var_idx = getRequiredVarOrChannelIndices(obsgroup, vars_);
135 
136  // Filter predictors and channels that we need
137  // FIXME: may be possible by indexing allbiascoeffs(pred_idx, chan_idx) when Eigen 3.4
138  // is available
139 
140  for (size_t jpred = 0; jpred < pred_idx.size(); ++jpred) {
141  for (size_t jvar = 0; jvar < var_idx.size(); ++jvar) {
142  biascoeffs_[index(jpred, jvar)] = allbiascoeffs(pred_idx[jpred], var_idx[jvar]);
143  }
144  }
145  } else {
146  if (numVariablePredictors_ > 0)
147  oops::Log::warning() << "ObsBias::prior file is NOT available, starting from ZERO"
148  << std::endl;
149  }
150 
151  oops::Log::trace() << "ObsBias::read and initilization done " << std::endl;
152 }
153 
154 // -----------------------------------------------------------------------------
155 /// Create ObsGroup with dimensions npredictors = size(predictors) and
156 /// nchannels = size(channels), variables predictors, channels and
157 /// bias_cooefficients (npredictors x nchannels)
158 ioda::ObsGroup saveBiasCoeffsWithChannels(ioda::Group & parent,
159  const std::vector<std::string> & predictors,
160  const std::vector<int> & channels,
161  const Eigen::MatrixXd & coeffs) {
162  // dimensions
163  ioda::NewDimensionScales_t dims {
164  ioda::NewDimensionScale<int>("npredictors", predictors.size()),
165  ioda::NewDimensionScale<int>("nchannels", channels.size())
166  };
167  // new ObsGroup
168  ioda::ObsGroup ogrp = ioda::ObsGroup::generate(parent, dims);
169 
170  // save the predictors
171  ioda::Variable predsVar = ogrp.vars.createWithScales<std::string>(
172  "predictors", {ogrp.vars["npredictors"]});
173  predsVar.write(predictors);
174  // and the variables
175  ioda::Variable chansVar = ogrp.vars.createWithScales<int>("channels", {ogrp.vars["nchannels"]});
176  chansVar.write(channels);
177 
178  // Set up the creation parameters for the bias coefficients variable
179  ioda::VariableCreationParameters float_params;
180  float_params.chunk = true; // allow chunking
181  float_params.compressWithGZIP(); // compress using gzip
182  float missing_value = util::missingValue(missing_value);
183  float_params.setFillValue<float>(missing_value);
184 
185  // Create a variable for bias coefficients, save bias coeffs to the variable
186  ioda::Variable biasVar = ogrp.vars.createWithScales<float>("bias_coefficients",
187  {ogrp.vars["npredictors"], ogrp.vars["nchannels"]}, float_params);
188  biasVar.writeWithEigenRegular(coeffs);
189  return ogrp;
190 }
191 
192 // -----------------------------------------------------------------------------
193 
194 void ObsBias::write(const Parameters_ & params) const {
195  // only write files out on the task with MPI rank 0
196  if (rank_ != 0) return;
197 
198  if (params.outputFile.value() != boost::none) {
199  // FIXME: only implemented for channels currently
200  if (vars_.channels().size() == 0) {
201  throw eckit::NotImplemented("ObsBias::write not implemented for variables without channels",
202  Here());
203  }
204  // Create a file, overwrite if exists
205  const std::string output_filename = *params.outputFile.value();
206  ioda::Group group = ioda::Engines::HH::createFile(output_filename,
207  ioda::Engines::BackendCreateModes::Truncate_If_Exists);
208 
209  // put only variable bias predictors into the predictors vector
210  std::vector<std::string> predictors(prednames_.begin() + numStaticPredictors_,
211  prednames_.end());
212  // map coefficients to 2D for saving
213  Eigen::Map<const Eigen::MatrixXd>
214  coeffs(biascoeffs_.data(), numVariablePredictors_, vars_.size());
215 
216  saveBiasCoeffsWithChannels(group, predictors, vars_.channels(), coeffs);
217  } else {
218  if (numVariablePredictors_ > 0) {
219  oops::Log::warning() << "obs bias.output file is NOT available, bias coefficients "
220  << "will not be saved." << std::endl;
221  }
222  }
223 }
224 
225 // -----------------------------------------------------------------------------
226 
227 double ObsBias::norm() const {
228  oops::Log::trace() << "ObsBias::norm starting." << std::endl;
229  double zz = 0.0;
230 
231  // Static predictors
232  const int numUnitCoeffs = numStaticPredictors_ * vars_.size();
233  zz += numUnitCoeffs;
234 
235  // Variable predictors
236  zz += biascoeffs_.squaredNorm();
237 
238  // Compute average and take square root
239  const int numCoeffs = numUnitCoeffs + biascoeffs_.size();
240  if (numCoeffs > 0) zz = std::sqrt(zz/numCoeffs);
241 
242  oops::Log::trace() << "ObsBias::norm done." << std::endl;
243  return zz;
244 }
245 
246 // -----------------------------------------------------------------------------
247 
249  biascoeffs_ = Eigen::VectorXd::Zero(numVariablePredictors_ * vars_.size());
250 }
251 
252 // -----------------------------------------------------------------------------
253 
254 std::vector<std::shared_ptr<const PredictorBase>> ObsBias::variablePredictors() const {
255  return std::vector<std::shared_ptr<const PredictorBase>>(
256  predictors_.begin() + numStaticPredictors_, predictors_.end());
257 }
258 
259 // -----------------------------------------------------------------------------
260 
261 void ObsBias::print(std::ostream & os) const {
262  if (this->size() > 0) {
263  // map bias coeffs to eigen matrix
264  Eigen::Map<const Eigen::MatrixXd>
265  coeffs(biascoeffs_.data(), numVariablePredictors_, vars_.size());
266  os << "Obs bias coefficients: " << std::endl;
267  os << "---------------------------------------------------------------" << std::endl;
268  for (std::size_t p = 0; p < numStaticPredictors_; ++p) {
269  os << std::fixed << std::setw(20) << prednames_[p]
270  << ": Min= " << std::setw(15) << std::setprecision(8)
271  << 1.0f
272  << ", Max= " << std::setw(15) << std::setprecision(8)
273  << 1.0f
274  << ", Norm= " << std::setw(15) << std::setprecision(8)
275  << std::sqrt(static_cast<double>(vars_.size()))
276  << std::endl;
277  }
278  for (std::size_t p = 0; p < numVariablePredictors_; ++p) {
279  os << std::fixed << std::setw(20) << prednames_[numStaticPredictors_ + p]
280  << ": Min= " << std::setw(15) << std::setprecision(8)
281  << coeffs.row(p).minCoeff()
282  << ", Max= " << std::setw(15) << std::setprecision(8)
283  << coeffs.row(p).maxCoeff()
284  << ", Norm= " << std::setw(15) << std::setprecision(8)
285  << coeffs.row(p).norm()
286  << std::endl;
287  }
288  os << "---------------------------------------------------------------";
289  }
290 }
291 
292 // -----------------------------------------------------------------------------
293 
295  std::shared_ptr<PredictorBase> pred(PredictorFactory::create(params.predictorParameters, vars_));
296  predictors_.push_back(pred);
297  prednames_.push_back(pred->name());
298  geovars_ += pred->requiredGeovars();
299  hdiags_ += pred->requiredHdiagnostics();
300 
301  // Reserve the space for ObsBiasTerm for predictor
302  if (vars_.channels().size() > 0) {
303  // At present we can label predictors with either the channel number or the variable
304  // name, but not both. So make sure there's only one multi-channel variable.
305  ASSERT(vars_.size() == vars_.channels().size());
306  for (auto & job : vars_.channels()) {
307  hdiags_ += oops::Variables({prednames_.back() + "_" + std::to_string(job)});
308  }
309  } else {
310  for (const std::string & variable : vars_.variables())
311  hdiags_ += oops::Variables({prednames_.back() + "_" + variable});
312  }
313 }
314 
315 // -----------------------------------------------------------------------------
316 
317 } // namespace ufo
ObsBias & operator=(const ObsBias &)
Definition: ObsBias.cc:91
size_t rank_
MPI rank, used to determine whether the task should output bias coeffs to a file.
Eigen::VectorXd biascoeffs_
bias correction coefficients (npredictors x nprimitivevariables)
Predictors predictors_
bias correction predictors
void zero()
Set all variable predictors coefficients to zero (used in the test)
Definition: ObsBias.cc:248
oops::Variables geovars_
Variables that need to be requested from the model (for computation of predictors)
ObsBias(ioda::ObsSpace &, const Parameters_ &)
Definition: ObsBias.cc:34
void print(std::ostream &) const override
Definition: ObsBias.cc:261
void initPredictor(const PredictorParametersWrapper &params)
Definition: ObsBias.cc:294
ObsBias & operator+=(const ObsBiasIncrement &)
Definition: ObsBias.cc:84
const Predictors & predictors() const
Return a reference to the vector of all (static and variable) predictors.
std::size_t size() const
std::vector< std::string > prednames_
predictor names
std::size_t numStaticPredictors_
number of static predictors (i.e. predictors with fixed coefficients all equal to 1)
double norm() const
Definition: ObsBias.cc:227
size_t index(size_t jpred, size_t jvar) const
index in the flattened biascoeffs_ for predictor jpred and variable jvar
std::vector< std::shared_ptr< const PredictorBase > > variablePredictors() const
Return the vector of variable predictors.
Definition: ObsBias.cc:254
oops::Variables hdiags_
Diagnostics that need to be requested from the obs operator (for computation of predictors)
void read(const Parameters_ &)
Read bias correction coefficients from file.
Definition: ObsBias.cc:108
oops::Variables vars_
corrected variables names (for now has to be same as "simulated variables")
std::size_t numVariablePredictors_
number of variable predictors (i.e. predictors with variable coefficients)
void write(const Parameters_ &) const
Definition: ObsBias.cc:194
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::Parameter< StaticOrVariationalBCParameters > staticBC
List of predictors with unit coefficients (unaffected by VarBC).
oops::OptionalParameter< std::string > outputFile
oops::OptionalParameter< std::string > inputFile
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.
oops::RequiredPolymorphicParameter< PredictorParametersBase, PredictorFactory > predictorParameters
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)
ioda::ObsGroup saveBiasCoeffsWithChannels(ioda::Group &parent, const std::vector< std::string > &predictors, const std::vector< int > &channels, const Eigen::MatrixXd &coeffs)
Definition: ObsBias.cc:158