IODA Bundle
LETKFSolver.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 
9 #ifndef OOPS_ASSIMILATION_LETKFSOLVER_H_
10 #define OOPS_ASSIMILATION_LETKFSOLVER_H_
11 
12 #include <Eigen/Dense>
13 #include <cfloat>
14 #include <memory>
15 #include <string>
16 #include <vector>
17 
18 #include "eckit/config/LocalConfiguration.h"
21 #include "oops/base/Departures.h"
23 #include "oops/base/Geometry.h"
26 #include "oops/base/ObsErrors.h"
28 #include "oops/base/ObsSpaces.h"
30 #include "oops/util/Logger.h"
31 #include "oops/util/Timer.h"
32 
33 namespace oops {
34 
35 /// Local Ensemble Tranform Kalman Filter solver
36 /*!
37  * An implementation of the LETKF from Hunt et al. 2007
38  * this version is implemented using Eigen algebra and
39  * temporary Eigen matrices for Xa and Xb
40  * this verion implements RTPP and RTPS.
41  *
42  * Hunt, B. R., Kostelich, E. J., & Szunyogh, I. (2007). Efficient data
43  * assimilation for spatiotemporal chaos: A local ensemble transform Kalman
44  * filter. Physica D: Nonlinear Phenomena, 230(1-2), 112-126.
45  */
46 template <typename MODEL, typename OBS>
47 class LETKFSolver : public LocalEnsembleSolver<MODEL, OBS> {
56 
57  public:
58  static const std::string classname() {return "oops::LETKFSolver";}
59 
60  LETKFSolver(ObsSpaces_ &, const Geometry_ &, const eckit::Configuration &, size_t);
61 
62  /// KF update + posterior inflation at a grid point location (GeometryIterator_)
64  const GeometryIterator_ &, IncrementEnsemble4D_ &) override;
65 
66  protected:
67  /// Computes weights for ensemble update with local observations
68  /// \param[in] omb Observation departures (nlocalobs)
69  /// \param[in] Yb Ensemble perturbations (nens, nlocalobs)
70  /// \param[in] invvarR Inverse of observation error variances (nlocalobs)
71  virtual void computeWeights(const Eigen::VectorXd & omb, const Eigen::MatrixXd & Yb,
72  const Eigen::VectorXd & invvarR);
73 
74  /// Applies weights and adds posterior inflation
76  const GeometryIterator_ &);
77 
79 
80  Eigen::MatrixXd Wa_; // transformation matrix for ens. perts. Xa=Xf*Wa
81  Eigen::VectorXd wa_; // transformation matrix for ens. mean xa=xf*wa
82 
83  // eigen solver matrices
84  Eigen::VectorXd eival_;
85  Eigen::MatrixXd eivec_;
86 
87  const size_t nens_; // ensemble size
88 };
89 
90 // -----------------------------------------------------------------------------
91 
92 template <typename MODEL, typename OBS>
94  const eckit::Configuration & config, size_t nens)
95  : LocalEnsembleSolver<MODEL, OBS>(obspaces, geometry, config, nens), nens_(nens)
96 {
97  options_.deserialize(config);
98  const LETKFInflationParameters & inflopt = options_.infl;
99 
100  Log::info() << "Using EIGEN implementation of LETKF" << std::endl;
101 
102  Log::info() << "Multiplicative inflation multCoeff=" <<
103  inflopt.mult << std::endl;
104 
105  if (inflopt.dortpp()) {
106  Log::info() << "RTPP inflation will be applied with rtppCoeff=" <<
107  inflopt.rtpp << std::endl;
108  } else {
109  Log::info() << "RTPP inflation is not applied rtppCoeff is out of bounds (0,1], rtppCoeff="
110  << inflopt.rtpp << std::endl;
111  }
112 
113  if (inflopt.dortps()) {
114  Log::info() << "RTPS inflation will be applied with rtpsCoeff=" <<
115  inflopt.rtps << std::endl;
116  } else {
117  Log::info() << "RTPS inflation is not applied rtpsCoeff is out of bounds (0,1], rtpsCoeff="
118  << inflopt.rtps << std::endl;
119  }
120 
121  // pre-allocate transformation matrices
122  Wa_.resize(nens_, nens_);
123  wa_.resize(nens_);
124 
125  // pre-allocate eigen sovler matrices
126  eival_.resize(nens_);
127  eivec_.resize(nens_, nens_);
128 }
129 
130 // -----------------------------------------------------------------------------
131 
132 template <typename MODEL, typename OBS>
134  const GeometryIterator_ & i,
135  IncrementEnsemble4D_ & ana_pert) {
136  util::Timer timer(classname(), "measurementUpdate");
137 
138  // create the local subset of observations
139  Departures_ locvector(this->obspaces_);
140  locvector.ones();
141  this->obsloc().computeLocalization(i, locvector);
142  locvector.mask(*(this->invVarR_));
143  Eigen::VectorXd local_omb_vec = this->omb_.packEigen(locvector);
144 
145  if (local_omb_vec.size() == 0) {
146  // no obs. so no need to update Wa_ and wa_
147  // ana_pert[i]=bkg_pert[i]
148  this->copyLocalIncrement(bkg_pert, i, ana_pert);
149  } else {
150  // if obs are present do normal KF update
151  // create local Yb
152  Eigen::MatrixXd local_Yb_mat = this->Yb_.packEigen(locvector);
153  // create local obs errors
154  Eigen::VectorXd local_invVarR_vec = this->invVarR_->packEigen(locvector);
155  // and apply localization
156  Eigen::VectorXd localization = locvector.packEigen(locvector);
157  local_invVarR_vec.array() *= localization.array();
158  computeWeights(local_omb_vec, local_Yb_mat, local_invVarR_vec);
159  applyWeights(bkg_pert, ana_pert, i);
160  }
161 }
162 
163 // -----------------------------------------------------------------------------
164 
165 template <typename MODEL, typename OBS>
166 void LETKFSolver<MODEL, OBS>::computeWeights(const Eigen::VectorXd & dy,
167  const Eigen::MatrixXd & Yb,
168  const Eigen::VectorXd & diagInvR ) {
169  // compute transformation matrix, save in Wa_, wa_
170  // uses C++ eigen interface
171  // implements LETKF from Hunt et al. 2007
172  util::Timer timer(classname(), "computeWeights");
173 
174  const LETKFInflationParameters & inflopt = options_.infl;
175 
176  // fill in the work matrix
177  // work = Y^T R^-1 Y + (nens-1)/infl I
178  double infl = inflopt.mult;
179  Eigen::MatrixXd work = Yb*(diagInvR.asDiagonal()*Yb.transpose());
180  work.diagonal() += Eigen::VectorXd::Constant(nens_, (nens_-1)/infl);
181 
182  // eigenvalues and eigenvectors of the above matrix
183  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(work);
184  eival_ = es.eigenvalues().real();
185  eivec_ = es.eigenvectors().real();
186 
187  // Pa = [ Yb^T R^-1 Yb + (nens-1)/infl I ] ^-1
188  work = eivec_ * eival_.cwiseInverse().asDiagonal() * eivec_.transpose();
189 
190  // Wa = sqrt[ (nens-1) Pa ]
191  Wa_ = eivec_
192  * ((nens_-1) * eival_.array().inverse()).sqrt().matrix().asDiagonal()
193  * eivec_.transpose();
194 
195  // wa = Pa Yb^T R^-1 dy
196  wa_ = work * (Yb * (diagInvR.asDiagonal()*dy));
197 }
198 
199 // -----------------------------------------------------------------------------
200 
201 template <typename MODEL, typename OBS>
203  IncrementEnsemble4D_ & ana_pert,
204  const GeometryIterator_ & i) {
205  // applies Wa_, wa_
206  util::Timer timer(classname(), "applyWeights");
207 
208  const LETKFInflationParameters & inflopt = options_.infl;
209 
210  LocalIncrement gptmpl = bkg_pert[0][0].getLocal(i);
211  std::vector<double> tmp1 = gptmpl.getVals();
212  size_t ngp = tmp1.size();
213 
214  // loop through analysis times and ens. members
215  for (size_t itime=0; itime < bkg_pert[0].size(); ++itime) {
216  // make grid point forecast pert ensemble array
217  Eigen::MatrixXd Xb(ngp, nens_);
218  // #pragma omp parallel for
219  for (size_t iens=0; iens < nens_; ++iens) {
220  LocalIncrement gp = bkg_pert[iens][itime].getLocal(i);
221  std::vector<double> tmp = gp.getVals();
222  for (size_t iv=0; iv < ngp; ++iv) {
223  Xb(iv, iens) = tmp[iv];
224  }
225  }
226 
227  // postmulptiply
228  Eigen::VectorXd xa = Xb*wa_; // ensemble mean update
229  Eigen::MatrixXd Xa = Xb*Wa_; // ensemble perturbation update
230 
231  // RTPP inflation
232  if (inflopt.dortpp()) {
233  Xa = (1-inflopt.rtpp)*Xa+inflopt.rtpp*Xb;
234  }
235 
236  // RTPS inflation
237  double eps = DBL_EPSILON;
238  if (inflopt.dortps()) {
239  // posterior spread
240  Eigen::ArrayXd asprd = Xa.array().square().rowwise().sum()/(nens_-1);
241  asprd = asprd.sqrt();
242  asprd = (asprd < eps).select(eps, asprd); // avoid nan overflow for vars with no spread
243 
244  // prior spread
245  Eigen::ArrayXd fsprd = Xb.array().square().rowwise().sum()/(nens_-1);
246  fsprd = fsprd.sqrt();
247  fsprd = (fsprd < eps).select(eps, fsprd);
248 
249  // rtps inflation factor
250  Eigen::ArrayXd rtpsInfl = inflopt.rtps*((fsprd-asprd)/asprd) + 1;
251  rtpsInfl = (rtpsInfl < inflopt.rtpsInflMin()).select(inflopt.rtpsInflMin(), rtpsInfl);
252  rtpsInfl = (rtpsInfl > inflopt.rtpsInflMax()).select(inflopt.rtpsInflMax(), rtpsInfl);
253 
254  // inlfate perturbation matrix
255  Xa.array().colwise() *= rtpsInfl;
256  }
257 
258  // assign Xa to ana_pert
259  // #pragma omp parallel for private(tmp1)
260  for (size_t iens=0; iens < nens_; ++iens) {
261  for (size_t iv=0; iv < ngp; ++iv) {
262  tmp1[iv] = Xa(iv, iens)+xa(iv); // if Xa = Xb*Wa;
263  }
264  gptmpl.setVals(tmp1);
265  ana_pert[iens][itime].setLocal(gptmpl, i);
266  }
267  }
268 }
269 
270 // -----------------------------------------------------------------------------
271 
272 } // namespace oops
273 #endif // OOPS_ASSIMILATION_LETKFSOLVER_H_
Ensemble of Departures (can hold ensemble perturbations in the observation space)
Difference between two observation vectors.
Definition: Departures.h:44
void mask(ObsDataVec_< int >)
Mask out departures where the passed in qc flags are > 0.
Definition: Departures.h:209
Eigen::VectorXd packEigen(const Departures &) const
Pack departures in an Eigen vector (excluding departures that are masked out)
Definition: Departures.h:223
Geometry class used in oops; subclass of interface class interface::Geometry.
Ensemble of 4D increments.
size_t size() const
Accessors.
Parameters for LETKF inflation.
Local Ensemble Tranform Kalman Filter solver.
Definition: LETKFSolver.h:47
Departures< OBS > Departures_
Definition: LETKFSolver.h:48
const size_t nens_
Definition: LETKFSolver.h:87
void measurementUpdate(const IncrementEnsemble4D_ &, const GeometryIterator_ &, IncrementEnsemble4D_ &) override
KF update + posterior inflation at a grid point location (GeometryIterator_)
Definition: LETKFSolver.h:133
ObsLocalizations< MODEL, OBS > ObsLocalizations_
Definition: LETKFSolver.h:54
Eigen::VectorXd wa_
Definition: LETKFSolver.h:81
LETKFSolverParameters options_
Definition: LETKFSolver.h:78
Geometry< MODEL > Geometry_
Definition: LETKFSolver.h:50
IncrementEnsemble4D< MODEL > IncrementEnsemble4D_
Definition: LETKFSolver.h:52
DeparturesEnsemble< OBS > DeparturesEnsemble_
Definition: LETKFSolver.h:49
LETKFSolver(ObsSpaces_ &, const Geometry_ &, const eckit::Configuration &, size_t)
Definition: LETKFSolver.h:93
ObsErrors< OBS > ObsErrors_
Definition: LETKFSolver.h:53
virtual void applyWeights(const IncrementEnsemble4D_ &, IncrementEnsemble4D_ &, const GeometryIterator_ &)
Applies weights and adds posterior inflation.
Definition: LETKFSolver.h:202
Eigen::VectorXd eival_
Definition: LETKFSolver.h:84
virtual void computeWeights(const Eigen::VectorXd &omb, const Eigen::MatrixXd &Yb, const Eigen::VectorXd &invvarR)
Definition: LETKFSolver.h:166
GeometryIterator< MODEL > GeometryIterator_
Definition: LETKFSolver.h:51
ObsSpaces< OBS > ObsSpaces_
Definition: LETKFSolver.h:55
Eigen::MatrixXd Wa_
Definition: LETKFSolver.h:80
static const std::string classname()
Definition: LETKFSolver.h:58
Eigen::MatrixXd eivec_
Definition: LETKFSolver.h:85
Parameter< LETKFInflationParameters > infl
Base class for LETKF-type solvers.
void setVals(std::vector< double > &)
const std::vector< double > & getVals() const
Container for ObsErrors for all observation types that are used in DA.
Definition: ObsErrors.h:32
Container for ObsLocalizations for all observation types that are used in DA.
The namespace for the main oops code.