UFO
ObsErrorCrossVarCov.cc
Go to the documentation of this file.
1 /*
2  * (C) Copyright 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 
9 
10 #include <vector>
11 
12 #include "ioda/Engines/Factory.h"
13 #include "ioda/Engines/HH.h"
14 #include "ioda/Layout.h"
15 #include "ioda/ObsGroup.h"
16 
17 namespace ufo {
18 
19 // -----------------------------------------------------------------------------
20 
22  ioda::ObsSpace & obspace,
23  const eckit::mpi::Comm &timeComm)
24  : ObsErrorBase(timeComm),
25  stddev_(obspace, "ObsError"),
26  varcorrelations_(Eigen::MatrixXd::Identity(stddev_.nvars(), stddev_.nvars()))
27 {
28  // Open and read error correlations from the hdf5 file
29  ioda::Engines::BackendNames backendName = ioda::Engines::BackendNames::Hdf5File;
30  ioda::Engines::BackendCreationParameters backendParams;
31  backendParams.fileName = options.inputFile;
32  backendParams.action = ioda::Engines::BackendFileActions::Open;
33  backendParams.openMode = ioda::Engines::BackendOpenModes::Read_Only;
34 
35  ioda::Group backend = constructBackend(backendName, backendParams);
36  ioda::ObsGroup obsgroup = ioda::ObsGroup(backend,
37  ioda::detail::DataLayoutPolicy::generate(
38  ioda::detail::DataLayoutPolicy::Policies::None));
39 
40  ioda::Variable corrvar = obsgroup.vars["obserror_correlations"];
41  corrvar.readWithEigenRegular(varcorrelations_);
42  // Check that the sizes are correct
43  const size_t nvars = stddev_.nvars();
44  if ((varcorrelations_.rows() != nvars) || (varcorrelations_.cols() != nvars)) {
45  std::string errormsg = std::string("Correlation matrix for R, specified in ") +
46  options.inputFile.value() + std::string(" should be size ") +
47  std::to_string(nvars) + std::string(" by ") + std::to_string(nvars);
48  throw eckit::UserError(errormsg, Here());
49  }
50 }
51 
52 // -----------------------------------------------------------------------------
53 
54 void ObsErrorCrossVarCov::update(const ioda::ObsVector & obserr) {
55  stddev_ = obserr;
56 }
57 
58 // -----------------------------------------------------------------------------
59 
60 void ObsErrorCrossVarCov::multiply(ioda::ObsVector & dy) const {
61  // R * dy = D^{1/2} * C * D^{1/2} * dy
62  // where D^{1/2} - diagonal matrix with stddev_ on the diagonal
63  // C - correlations
64 
65  // D^{1/2] * dy
66  dy *= stddev_;
67 
68  // C * D^{1/2} * dy
69  const size_t nlocs = dy.nlocs();
70  const size_t nvars = dy.nvars();
71  const double missing = util::missingValue(double());
72  // preallocate data
73  std::vector<int> usedobs_indices(nvars);
74  Eigen::VectorXd dy_at_loc(nvars);
75  Eigen::MatrixXd corr = Eigen::MatrixXd::Identity(nvars, nvars);
76 
77  // loop over all observations locations
78  for (size_t jloc = 0; jloc < nlocs; ++jloc) {
79  // find values to be used (the ones that passed QC), and create subset of used values
80  // at this location (dy_at_loc) and submatrix of correlations for the used values (corr)
81  size_t nused = 0;
82  for (size_t jvar = 0; jvar < nvars; ++jvar) {
83  if (dy[jloc*nvars + jvar] != missing) usedobs_indices[nused++] = jvar;
84  }
85  for (size_t jvar = 0; jvar < nused; ++jvar) {
86  int ind = usedobs_indices[jvar];
87  dy_at_loc(jvar) = dy[jloc*nvars + ind];
88  for (size_t jvar2 = jvar+1; jvar2 < nused; ++jvar2) {
89  int ind2 = usedobs_indices[jvar2];
90  corr(jvar, jvar2) = varcorrelations_(ind, ind2);
91  corr(jvar2, jvar) = varcorrelations_(ind2, ind);
92  }
93  }
94  // multiply by C
95  dy_at_loc.head(nused) = corr.block(0, 0, nused, nused) * dy_at_loc.head(nused);
96  // save results in dy
97  for (size_t jvar = 0; jvar < nused; ++jvar) {
98  dy[jloc*nvars + usedobs_indices[jvar]] = dy_at_loc[jvar];
99  }
100  }
101 
102  // D^{1/2} * C * D^{1/2} * dy
103  dy *= stddev_;
104 }
105 
106 // -----------------------------------------------------------------------------
107 
108 void ObsErrorCrossVarCov::inverseMultiply(ioda::ObsVector & dy) const {
109  // R^{-1} * dy = D^{-1/2} * C^{-1} * D^{-1/2} * dy
110  // where D^{1/2} - diagonal matrix with stddev_ on the diagonal
111  // C - correlations
112 
113  // D^{-1/2] * dy
114  dy /= stddev_;
115 
116  // C^{-1} * D^{-1/2} * dy
117  const size_t nlocs = dy.nlocs();
118  const size_t nvars = dy.nvars();
119  const double missing = util::missingValue(double());
120  // preallocate data
121  std::vector<int> usedobs_indices(nvars);
122  Eigen::VectorXd dy_at_loc(nvars);
123  Eigen::MatrixXd corr = Eigen::MatrixXd::Identity(nvars, nvars);
124  // loop over all observations locations
125  for (size_t jloc = 0; jloc < nlocs; ++jloc) {
126  // find values to be used (the ones that passed QC), and create subset of used values
127  // at this location (dy_at_loc) and submatrix of correlations for the used values (corr)
128  size_t nused = 0;
129  for (size_t jvar = 0; jvar < nvars; ++jvar) {
130  if (dy[jloc*nvars + jvar] != missing) usedobs_indices[nused++] = jvar;
131  }
132  for (size_t jvar = 0; jvar < nused; ++jvar) {
133  int ind = usedobs_indices[jvar];
134  dy_at_loc(jvar) = dy[jloc*nvars + ind];
135  for (size_t jvar2 = jvar+1; jvar2 < nused; ++jvar2) {
136  int ind2 = usedobs_indices[jvar2];
137  // only need the lower triangle for llt() below; not filling upper triangle
138  corr(jvar2, jvar) = varcorrelations_(ind2, ind);
139  }
140  }
141  // Multiply by inverse of C, using standard Cholesky decomposition from Eigen library
142  // https://eigen.tuxfamily.org/dox/classEigen_1_1LLT.html
143  corr.topLeftCorner(nused, nused).llt().solveInPlace(dy_at_loc.head(nused));
144  // save results in dy
145  for (size_t jvar = 0; jvar < nused; ++jvar) {
146  dy[jloc*nvars + usedobs_indices[jvar]] = dy_at_loc[jvar];
147  }
148  }
149 
150  // D^{-1/2} * C^{-1} * D^{-1/2} * dy
151  dy /= stddev_;
152 }
153 
154 // -----------------------------------------------------------------------------
155 
156 void ObsErrorCrossVarCov::randomize(ioda::ObsVector & dy) const {
157  dy.random();
158  multiply(dy);
159 }
160 
161 // -----------------------------------------------------------------------------
162 
163 void ObsErrorCrossVarCov::save(const std::string & name) const {
164  stddev_.save(name);
165 }
166 
167 // -----------------------------------------------------------------------------
168 
169 std::unique_ptr<ioda::ObsVector> ObsErrorCrossVarCov::getObsErrors() const {
170  return std::make_unique<ioda::ObsVector>(stddev_);
171 }
172 
173 // -----------------------------------------------------------------------------
174 
175 std::unique_ptr<ioda::ObsVector> ObsErrorCrossVarCov::getInverseVariance() const {
176  std::unique_ptr<ioda::ObsVector> inverseVariance = std::make_unique<ioda::ObsVector>(stddev_);
177  *inverseVariance *= stddev_;
178  inverseVariance->invert();
179  return inverseVariance;
180 }
181 
182 // -----------------------------------------------------------------------------
183 void ObsErrorCrossVarCov::print(std::ostream & os) const {
184  os << "Observation error covariance with cross-variable correlations." << std::endl;
185  os << " Obs error stddev: " << stddev_ << std::endl;
186  os << " Cross-variable correlations: " << std::endl << varcorrelations_;
187 }
188 
189 // -----------------------------------------------------------------------------
190 
191 
192 } // namespace ufo
ObsErrorCrossVarCov(const Parameters_ &, ioda::ObsSpace &, const eckit::mpi::Comm &timeComm)
Initialize observation errors.
void randomize(ioda::ObsVector &y) const override
Generate y as a random perturbation.
void multiply(ioda::ObsVector &y) const override
std::unique_ptr< ioda::ObsVector > getObsErrors() const override
Return obs errors std deviation.
Eigen::MatrixXd varcorrelations_
Correlations between variables.
void save(const std::string &name) const override
Save obs error standard deviations under name group name.
void update(const ioda::ObsVector &stddev) override
Update obs error standard deviations to be equal to stddev.
ioda::ObsVector stddev_
Observation error standard deviations.
void inverseMultiply(ioda::ObsVector &y) const override
void print(std::ostream &) const override
Print covariance details (for logging)
std::unique_ptr< ioda::ObsVector > getInverseVariance() const override
Return inverse of obs error variance.
Parameters for obs errors with cross-variable correlations.
oops::RequiredParameter< std::string > inputFile
Input file containing correlations.
constexpr int missing
Definition: QCflags.h:20
std::set< size_t > Group
integer function nlocs(this)
Return the number of observational locations in this Locations object.
Definition: RunCRTM.h:27