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