IODA Bundle
oops/generic/VerticalLocEV.h
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2020 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 #ifndef OOPS_GENERIC_VERTICALLOCEV_H_
9 #define OOPS_GENERIC_VERTICALLOCEV_H_
10 
11 #include <Eigen/Dense>
12 
13 #include <string>
14 #include <vector>
15 
16 #include "eckit/config/Configuration.h"
17 
18 #include "oops/base/Geometry.h"
19 #include "oops/base/Increment4D.h"
22 #include "oops/generic/gc99.h"
24 #include "oops/util/Logger.h"
25 #include "oops/util/ObjectCounter.h"
26 #include "oops/util/parameters/Parameter.h"
27 #include "oops/util/parameters/Parameters.h"
28 #include "oops/util/parameters/RequiredParameter.h"
29 #include "oops/util/Printable.h"
30 
31 namespace oops {
32 
33 /// Parameters for vertical localization
34 /*!
35  * See Lei 2018 JAMES for more details
36  *
37  * Lei, L., Whitaker, J. S., & Bishop, C. ( 2018). Improving assimilation
38  * of radiance observations by implementing model space localization in an
39  * ensemble Kalman filter. Journal of Advances in Modeling Earth Systems, 10,
40  * 3221– 3232. https://doi.org/10.1029/2018MS001468
41  */
42 class VerticalLocalizationParameters : public Parameters {
43  OOPS_CONCRETE_PARAMETERS(VerticalLocalizationParameters, Parameters)
44  public:
45  // fraction of the variance retained after the eigen spectrum
46  // of the vertical localization function is truncated
47  // 1 -- retain all eigen vectors
48  // 0 -- retain the first eigen vector
49  Parameter<double> VertLocToll{"fraction of retained variance", 1.0, this};
50  // localization distance at which Gaspari-Cohn = 0
51  RequiredParameter<double> VertLocDist{"lengthscale", this};
52  // localization distance at which Gaspari-Cohn = 0
53  RequiredParameter<std::string> VertLocUnits{"lengthscale units", this};
54 };
55 
56 // ----------------------------------------------------------------------------
57 template<typename MODEL>
58 class VerticalLocEV: public util::Printable,
59  private util::ObjectCounter<VerticalLocEV<MODEL>> {
64 
65  public:
66  static const std::string classname() {return "oops::VerticalLocEV";}
67 
68  VerticalLocEV(const Geometry_ & , const eckit::Configuration &);
69 
70 // modulate an increment
72 
73 // modulate an incrementEnsemble at a {gridPoint, timeSlice}
74  Eigen::MatrixXd modulateIncrement(const IncrementEnsemble4D_ &,
75  const GeometryIterator_ &, size_t) const;
76 
77 // returns number of retained eigen modes
78  size_t neig() const {return neig_;}
79 
80 // tests if the truncation and rescaling are done properly
81  bool testTruncateEvecs(const Geometry_ &);
82 
83  private:
84 // compute vertical correlations and eigen vectors
85  Eigen::MatrixXd computeCorrMatrix(const Geometry_ &);
86  void computeCorrMatrixEvec(const Eigen::MatrixXd &);
87 
88 // truncate Evec sequence
89  size_t truncateEvecs();
90 
91 // replicate eigen vectors for state vars
92  std::vector<double> replicateEigenVector(const oops::Variables &,
93  size_t) const;
94 
95  private:
96  void print(std::ostream &) const {}
98  Eigen::MatrixXd Evecs_;
99  Eigen::VectorXd Evals_;
100  size_t neig_;
101 };
102 // -----------------------------------------------------------------------------
103 template<typename MODEL>
105  const eckit::Configuration & conf) {
106  // read vertical localization configuration
107  options_.deserialize(conf);
108 
109  // compute vertical corrleation matrix fo nLevs
110  Eigen::MatrixXd cov = computeCorrMatrix(geom);
111  // compute truncated correlation matrix
112  computeCorrMatrixEvec(cov);
113  neig_ = truncateEvecs();
114  }
115 
116 // -------------------------------------------------------------------------------------------------
117 template<typename MODEL>
118  Eigen::MatrixXd VerticalLocEV<MODEL>::computeCorrMatrix(const Geometry_ & geom) {
119  std::string locUnits = options_.VertLocUnits;
120  oops::Log::debug() << "locUnits: " << locUnits << std::endl;
121  std::vector<double> vCoord = geom.verticalCoord(locUnits);
122  size_t nlevs = vCoord.size();
123 
124  // compute vertical correlations and eigen vectors
125  Eigen::MatrixXd cov = Eigen::MatrixXd::Zero(nlevs, nlevs);
126  for (size_t jj=0; jj < nlevs; ++jj) {
127  for (size_t ii=jj; ii < nlevs; ++ii) {
128  cov(ii, jj) = oops::gc99(std::abs(vCoord[jj]-vCoord[ii])/options_.VertLocDist);
129  }
130  }
131  oops::Log::debug() << "corr: " << cov << std::endl;
132 
133  return cov;
134  }
135 
136 
137 // -------------------------------------------------------------------------------------------------
138 template<typename MODEL>
139  void VerticalLocEV<MODEL>::computeCorrMatrixEvec(const Eigen::MatrixXd & cov) {
140  // compute eigen decomposition of vertical correlation matrix
141  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(cov);
142  Evals_ = es.eigenvalues().real();
143  Evecs_ = es.eigenvectors().real();
144 
145  // reverse order, largest eigen value first.
146  Evals_.reverseInPlace();
147  Evecs_.rowwise().reverseInPlace();
148  }
149 
150 // -------------------------------------------------------------------------------------------------
151 template<typename MODEL>
153  // truncate Evec sequence to retain "toll" percetage of total variance
154  double evalsum = Evals_.sum();
155 
156  // assert condition on the trace of the correlation matrix
157  if ((evalsum-Evals_.size()) > 1e-5) {
158  Log::error() << "VerticalLocEV trace(cov)~=cov.size: trace(cov)=" <<
159  evalsum << "cov.size=" << Evals_.size() << std::endl;
160  throw eckit::BadValue("VerticalLocEV trace(cov)~=cov.size");
161  }
162 
163  // compute number of evals bellow tolerence
164  double frac = 0.0;
165  size_t neig = 0;
166  for (int ii=0; ii < Evals_.size(); ++ii) {
167  frac += Evals_[ii];
168  neig += 1;
169  if (frac/evalsum >= options_.VertLocToll) { break; }
170  }
171  frac = frac/evalsum;
172 
173  oops::Log::info() << "Retaining frac: " << frac << std::endl;
174  oops::Log::info() << "Retaining neig vectors in vert. loc.: " << neig << std::endl;
175 
176  // compute Eivec(:,i)=Eivec(:,i)*sqrt(Evals(i)/frac)
177  // i.e. make Evecs_=sgrt(cov)
178  Evals_.array() *= 1/frac;
179  for (int ii=0; ii < Evals_.size(); ++ii) {
180  Evecs_.array().col(ii) *= std::sqrt(Evals_(ii));
181  }
182 
183  // truncate to keep neig
184  Evecs_.conservativeResize(Eigen::NoChange, neig);
185  Evals_.conservativeResize(neig);
186 
187  // assert that the trace of the new and old cov is the same
188  if ((evalsum-Evals_.sum()) > 1e-5) {
189  Log::error() << "VerticalLocEV::truncateEvecs trace(cov)~=trace(covTrunc): " <<
190  " trace(cov)=" << evalsum << " trace(covTrunc)=" << Evals_.sum() <<
191  " diff=" << (evalsum-Evals_.sum()) << std::endl;
192  throw eckit::BadValue("VerticalLocEV::truncateEvecs trace(cov)~=trace(covTrunc)");
193  }
194 
195  // return number of truncated eigen values
196  return neig;
197  }
198 
199 
200 // -----------------------------------------------------------------------------
201 template<typename MODEL>
203  // make reference solution
204  Eigen::MatrixXd cov = computeCorrMatrix(geom);
205  // only the lower traingle of cov is stored
206  // transform to full matrix
207  Eigen::MatrixXd covOld = cov+cov.transpose();
208  covOld.diagonal() = covOld.diagonal()/2;
209 
210  // recreate covNew from stored Evecs
211  Eigen::MatrixXd covNew = Evecs_*Evecs_.transpose();
212 
213  oops::Log::debug() << "Trace covNew " << covNew.diagonal().sum() << std::endl;
214  oops::Log::debug() << "Trace cov " << covOld.diagonal().sum() << std::endl;
215 
216  oops::Log::debug() << "norm(covNew) " << covNew.norm() << std::endl;
217  oops::Log::debug() << "norm(cov) " << covOld.norm() << std::endl;
218 
219  covNew = covNew.array() - covOld.array();
220  oops::Log::debug() << "norm(covNew-cov) " << covNew.norm() << std::endl;
221 
222  bool rv = (covNew.diagonal().sum() - cov.diagonal().sum()) < 1e-5;
223  return rv;
224 }
225 
226 // -----------------------------------------------------------------------------
227 template<typename MODEL>
229  IncrementEnsemble4D_ & incrsOut) const {
230  // modulate an increment incr using Eivec_
231  // returns incrsOut
232 
233  // create temporary vector that would be used to store the full
234  // eigen vector of the correlation matrix
235  // at this point Evecs_ stores eigen vector.size()=nLevs
236  // we need nLevs*nVars
237  // TODO(Issue #812) catch a special case where some variables in the gp are not 3D
238  // consider doing it once instead of replicating this for each gp (Issue #813)
239  oops::Variables vars = incr[0].variables();
240 
241  for (size_t ieig=0; ieig < neig_; ++ieig) {
242  std::vector<double> EvecRepl = replicateEigenVector(vars, ieig);
243  // modulate an increment
244  for (size_t itime=0; itime < incr.size(); ++itime) {
245  const Geometry_ & geom = incr[itime].geometry();
246  for (GeometryIterator_ gpi = geom.begin(); gpi != geom.end(); ++gpi) {
247  oops::LocalIncrement gp = incr[itime].getLocal(gpi);
248  gp *= EvecRepl;
249  incrsOut[ieig][itime].setLocal(gp, gpi);
250  }
251  }
252  }
253 }
254 
255 // -----------------------------------------------------------------------------
256 template<typename MODEL>
258  const IncrementEnsemble4D_ & incrs,
259  const GeometryIterator_ & gi,
260  size_t itime) const {
261  // modulate an increment at grid point
262 
263  oops::Variables vars = incrs[0][0].variables();
264  size_t nv = Evecs_.rows()*vars.size();
265  size_t nens = incrs.size();
266  Eigen::MatrixXd Z(nv, neig_*nens);
267  std::vector<double> etmp2(nv);
268 
269  size_t ii = 0;
270  for (size_t iens=0; iens < nens; ++iens) {
271  oops::LocalIncrement gp = incrs[iens][itime].getLocal(gi);
272  etmp2 = gp.getVals();
273  for (size_t ieig=0; ieig < neig_; ++ieig) {
274  std::vector<double> EvecRepl = replicateEigenVector(vars, ieig);
275  // modulate and assign
276  for (size_t iv=0; iv < nv; ++iv) {
277  Z(iv, ii) = EvecRepl[iv]*etmp2[iv];
278  }
279  ii += 1;
280  }
281  }
282  return Z;
283 }
284 template<typename MODEL>
286  const oops::Variables & v, size_t ieig) const {
287  // populate eigen vector for all variables nLevs*nVars
288  std::vector<double> etmp(Evecs_.rows()*v.size());
289 
290  size_t ii = 0;
291  for (unsigned ivar=0; ivar < v.size(); ++ivar) {
292  for (unsigned ilev=0; ilev < Evecs_.rows(); ++ilev) {
293  etmp[ii] = Evecs_(ilev, ieig);
294  ii += 1;
295  }
296  }
297  return etmp;
298 }
299 
300 } // namespace oops
301 
302 #endif // OOPS_GENERIC_VERTICALLOCEV_H_
Geometry class used in oops; subclass of interface class interface::Geometry.
4D model state Increment (vector of 3D Increments)
Definition: Increment4D.h:32
Geometry_ geometry() const
Get geometry.
Definition: Increment4D.h:52
size_t size() const
Definition: Increment4D.h:57
Ensemble of 4D increments.
size_t size() const
Accessors.
const std::vector< double > & getVals() const
size_t size() const
std::vector< double > replicateEigenVector(const oops::Variables &, size_t) const
Increment4D< MODEL > Increment4D_
Eigen::MatrixXd computeCorrMatrix(const Geometry_ &)
VerticalLocalizationParameters options_
void print(std::ostream &) const
void computeCorrMatrixEvec(const Eigen::MatrixXd &)
VerticalLocEV(const Geometry_ &, const eckit::Configuration &)
GeometryIterator< MODEL > GeometryIterator_
IncrementEnsemble4D< MODEL > IncrementEnsemble4D_
void modulateIncrement(const Increment4D_ &, IncrementEnsemble4D_ &) const
static const std::string classname()
bool testTruncateEvecs(const Geometry_ &)
Parameters for vertical localization.
RequiredParameter< std::string > VertLocUnits
GeometryIterator_ end() const
Iterator to the past-the-end gridpoint of Geometry (only used in LocalEnsembleDA)
std::vector< double > verticalCoord(std::string &) const
Values of vertical coordinate in units specified by string (only used in GETKF)
GeometryIterator_ begin() const
Iterator to the first gridpoint of Geometry (only used in LocalEnsembleDA)
The namespace for the main oops code.
double gc99(const double &distnorm)