IODA Bundle
GETKFSolver.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_ASSIMILATION_GETKFSOLVER_H_
9 #define OOPS_ASSIMILATION_GETKFSOLVER_H_
10 
11 #include <Eigen/Dense>
12 #include <cfloat>
13 #include <memory>
14 #include <string>
15 #include <utility>
16 #include <vector>
17 
18 #include "eckit/config/LocalConfiguration.h"
22 #include "oops/base/Departures.h"
24 #include "oops/base/Geometry.h"
27 #include "oops/base/ObsEnsemble.h"
28 #include "oops/base/Observations.h"
29 #include "oops/base/ObsSpaces.h"
30 #include "oops/base/State4D.h"
34 #include "oops/util/ConfigFunctions.h"
35 #include "oops/util/Logger.h"
36 #include "oops/util/Timer.h"
37 
38 namespace oops {
39 
40 /*!
41  * An implementation of the GETKF from Lei 2018 JAMES
42  *
43  * Lei, L., Whitaker, J. S., & Bishop, C. ( 2018). Improving assimilation
44  * of radiance observations by implementing model space localization in an
45  * ensemble Kalman filter. Journal of Advances in Modeling Earth Systems, 10,
46  * 3221– 3232. https://doi.org/10.1029/2018MS001468
47  */
48 template <typename MODEL, typename OBS>
49 class GETKFSolver : public LocalEnsembleSolver<MODEL, OBS> {
61 
62  public:
63  static const std::string classname() {return "oops::GETKFSolver";}
64 
65  /// Constructor (allocates Wa, wa, HZb_,
66  /// saves options from the config, computes VerticalLocEV_)
67  GETKFSolver(ObsSpaces_ &, const Geometry_ &, const eckit::Configuration &, size_t);
68 
69  Observations_ computeHofX(const StateEnsemble4D_ &, size_t, bool) override;
70 
71  /// entire KF update (computeWeights+applyWeights) for a grid point GeometryIterator_
73  IncrementEnsemble4D_ &) override;
74 
75  private:
76  /// Computes weights for ensemble update with local observations
77  /// \param[in] omb Observation departures (nlocalobs)
78  /// \param[in] Yb Ensemble perturbations for all the background memebers
79  /// (nens*neig, nlocalobs)
80  /// \param[in] YbOrig Ensemble perturbations for the members to be updated (nens, nlocalobs)
81  /// \param[in] invvarR Inverse of observation error variances (nlocalobs)
82  void computeWeights(const Eigen::VectorXd & omb, const Eigen::MatrixXd & Yb,
83  const Eigen::MatrixXd & YbOrig, const Eigen::VectorXd & invvarR);
84 
85  /// Applies weights and adds posterior inflation
87  const GeometryIterator_ &);
88 
89  private:
91  // parameters
92  size_t nens_;
95  size_t neig_;
96  size_t nanal_;
97 
99 
100  Eigen::MatrixXd Wa_; // transformation matrix for ens. perts. Xa_=Xf*Wa
101  Eigen::VectorXd wa_; // transformation matrix for ens. mean xa_=xf*wa
102 };
103 
104 // -----------------------------------------------------------------------------
105 
106 template <typename MODEL, typename OBS>
108  const eckit::Configuration & config, size_t nens)
109  : LocalEnsembleSolver<MODEL, OBS>(obspaces, geometry, config, nens),
110  nens_(nens), geometry_(geometry),
111  vertloc_(geometry_, config.getSubConfiguration("local ensemble DA.vertical localization")),
112  neig_(vertloc_.neig()), nanal_(neig_*nens_), HZb_(obspaces, nanal_)
113 {
114  options_.deserialize(config);
115  const LETKFInflationParameters & inflopt = options_.infl;
116 
117  // parse inflation options
118  Log::info() << "Multiplicative inflation multCoeff=" <<
119  inflopt.mult << std::endl;
120 
121  if (inflopt.dortpp()) {
122  Log::info() << "RTPP inflation will be applied with rtppCoeff=" <<
123  inflopt.rtpp << std::endl;
124  } else {
125  Log::info() << "RTPP inflation is not applied rtppCoeff is out of bounds (0,1], rtppCoeff="
126  << inflopt.rtpp << std::endl;
127  }
128 
129  if (inflopt.dortps()) {
130  Log::info() << "RTPS inflation will be applied with rtpsCoeff=" <<
131  inflopt.rtps << std::endl;
132  } else {
133  Log::info() << "RTPS inflation is not applied rtpsCoeff is out of bounds (0,1], rtpsCoeff="
134  << inflopt.rtps << std::endl;
135  }
136 
137  // pre-allocate transformation matrices
138  Wa_.resize(nanal_, nens);
139  wa_.resize(nanal_);
140 }
141 
142 // -----------------------------------------------------------------------------
143 template <typename MODEL, typename OBS>
145  size_t iteration, bool readFromFile) {
146  util::Timer timer(classname(), "computeHofX");
147 
148  // compute/read H(x) for the original ensemble members
149  // also computes omb_
150  Observations_ yb_mean =
151  LocalEnsembleSolver<MODEL, OBS>::computeHofX(ens_xx, iteration, readFromFile);
152  if (readFromFile) {
153  Log::debug() << "Read H(X) from disk" << std::endl;
154  // read modulated ensemble
155  Observations_ ytmp(yb_mean);
156  size_t ii = 0;
157  for (size_t iens = 0; iens < nens_; ++iens) {
158  for (size_t ieig = 0; ieig < neig_; ++ieig) {
159  ytmp.read("hofxm"+std::to_string(iteration)+"_"+std::to_string(ieig+1)+
160  "_"+std::to_string(iens+1));
161  HZb_[ii] = ytmp - yb_mean;
162  Log::test() << "H(Zx) - ymean for member " << iens+1 << " eig "<< ieig+1
163  << " :" << std::endl << HZb_[ii] << std::endl;
164  ii = ii + 1;
165  }
166  }
167  } else {
168  Log::debug() << "Computing H(X) online" << std::endl;
169  // modulate ensemble of obs
170  State4D_ xx_mean(ens_xx.mean());
171  IncrementEnsemble4D_ dx(ens_xx, xx_mean, xx_mean[0].variables());
172  IncrementEnsemble4D_ Ztmp(geometry_, xx_mean[0].variables(), ens_xx[0].validTimes(), neig_);
173  size_t ii = 0;
174  for (size_t iens = 0; iens < nens_; ++iens) {
175  vertloc_.modulateIncrement(dx[iens], Ztmp);
176  for (size_t ieig = 0; ieig < neig_; ++ieig) {
177  State4D_ tmpState = xx_mean;
178  tmpState += Ztmp[ieig];
179  Observations_ tmpObs(this->obspaces_);
180  eckit::LocalConfiguration config;
181  config.set("save hofx", false);
182  config.set("save qc", false);
183  config.set("save obs errors", false);
184  this->computeHofX4D(config, tmpState, tmpObs);
185  HZb_[ii] = tmpObs - yb_mean;
186  tmpObs.save("hofxm"+std::to_string(iteration)+"_"+std::to_string(ieig+1)+
187  "_"+std::to_string(iens+1));
188  Log::test() << "H(Zx) - ymean for member " << iens+1 << " eig "<< ieig+1 << " :"
189  << std::endl << HZb_[ii] << std::endl;
190  ii = ii + 1;
191  }
192  }
193  }
194  return yb_mean;
195 }
196 
197 // -----------------------------------------------------------------------------
198 
199 template <typename MODEL, typename OBS>
200 void GETKFSolver<MODEL, OBS>::computeWeights(const Eigen::VectorXd & dy,
201  const Eigen::MatrixXd & Yb,
202  const Eigen::MatrixXd & YbOrig,
203  const Eigen::VectorXd & R_invvar) {
204  // compute transformation matrix, save in Wa_, wa_
205  // Yb(nobs,neig*nens), YbOrig(nobs,nens)
206  // uses GSI GETKF code
207  util::Timer timer(classname(), "computeWeights");
208 
209  const int nobsl = dy.size();
210 
211  // cast eigen<double> to eigen<float>
212  Eigen::VectorXf dy_f = dy.cast<float>();
213  Eigen::MatrixXf Yb_f = Yb.cast<float>();
214  Eigen::MatrixXf YbOrig_f = YbOrig.cast<float>();
215  Eigen::VectorXf R_invvar_f = R_invvar.cast<float>();
216 
217  Eigen::MatrixXf Wa_f(nanal_, this->nens_);
218  Eigen::VectorXf wa_f(nanal_);
219 
220  // call into GSI interface to compute Wa and wa
221  const int getkf_inflation = 0;
222  const int denkf = 0;
223  const int getkf = 1;
224  letkf_core_f90(nobsl, Yb_f.data(), YbOrig_f.data(), dy_f.data(),
225  wa_f.data(), Wa_f.data(),
226  R_invvar_f.data(), nanal_, neig_,
227  getkf_inflation, denkf, getkf);
228  this->Wa_ = Wa_f.cast<double>();
229  this->wa_ = wa_f.cast<double>();
230 }
231 
232 // -----------------------------------------------------------------------------
233 
234 template <typename MODEL, typename OBS>
236  IncrementEnsemble4D_ & ana_pert,
237  const GeometryIterator_ & i) {
238  // apply Wa_, wa_
239  util::Timer timer(classname(), "applyWeights");
240 
241  const LETKFInflationParameters & inflopt = options_.infl;
242 
243  // allocate tmp arrays
244  LocalIncrement gptmpl = bkg_pert[0][0].getLocal(i);
245  std::vector<double> tmp = gptmpl.getVals();
246  size_t ngp = tmp.size();
247  Eigen::MatrixXd Xb(ngp, nens_);
248 
249  // loop through analysis times and ens. members
250  for (unsigned itime=0; itime < bkg_pert[0].size(); ++itime) {
251  // cast bkg_pert ensemble at grid point i as an Eigen matrix Xb
252  // modulates Xb
253  Xb = vertloc_.modulateIncrement(bkg_pert, i, itime);
254 
255  // postmulptiply
256  Eigen::VectorXd xa = Xb*wa_; // ensemble mean update
257  Eigen::MatrixXd Xa = Xb*Wa_; // ensemble perturbation update
258 
259  // compute non-modulated Xb for RTPP and RTPS
260  if (inflopt.dortpp() || inflopt.dortps()) {
261  Xb.resize(ngp, nens_);
262  for (size_t iens=0; iens < nens_; ++iens) {
263  LocalIncrement gp = bkg_pert[iens][itime].getLocal(i);
264  std::vector<double> tmp1 = gp.getVals();
265  for (size_t iv=0; iv < ngp; ++iv) {
266  Xb(iv, iens) = tmp1[iv];
267  }
268  }
269  }
270 
271  // RTPP inflation
272  if (inflopt.dortpp()) {
273  Xa = (1-inflopt.rtpp)*Xa+inflopt.rtpp*Xb;
274  }
275 
276  // RTPS inflation
277  double eps = DBL_EPSILON;
278  if (inflopt.dortps()) {
279  // posterior spread
280  Eigen::ArrayXd asprd = Xa.array().square().rowwise().sum()/(nens_-1);
281  asprd = asprd.sqrt();
282  asprd = (asprd < eps).select(eps, asprd); // avoid nan overflow for vars with no spread
283 
284  // prior spread
285  Eigen::ArrayXd fsprd = Xb.array().square().rowwise().sum()/(nens_-1);
286  fsprd = fsprd.sqrt();
287  fsprd = (fsprd < eps).select(eps, fsprd);
288 
289  // rtps inflation factor
290  Eigen::ArrayXd rtpsInfl = inflopt.rtps*((fsprd-asprd)/asprd) + 1;
291  rtpsInfl = (rtpsInfl < inflopt.rtpsInflMin()).select(inflopt.rtpsInflMin(), rtpsInfl);
292  rtpsInfl = (rtpsInfl > inflopt.rtpsInflMax()).select(inflopt.rtpsInflMax(), rtpsInfl);
293 
294  // inlfate perturbation matrix
295  Xa.array().colwise() *= rtpsInfl;
296  }
297 
298  // assign Xa_ to ana_pert
299  for (size_t iens=0; iens < nens_; ++iens) {
300  for (size_t iv=0; iv < ngp; ++iv) {
301  tmp[iv] = Xa(iv, iens)+xa(iv);
302  }
303  gptmpl.setVals(tmp);
304  ana_pert[iens][itime].setLocal(gptmpl, i);
305  }
306  }
307 }
308 
309 // -----------------------------------------------------------------------------
310 
311 template <typename MODEL, typename OBS>
313  const GeometryIterator_ & i,
314  IncrementEnsemble4D_ & ana_pert) {
315  util::Timer timer(classname(), "measurementUpdate");
316 
317  // create the local subset of observations
318  Departures_ locvector(this->obspaces_);
319  locvector.ones();
320  this->obsloc().computeLocalization(i, locvector);
321  locvector.mask(*(this->invVarR_));
322  Eigen::VectorXd local_omb_vec = this->omb_.packEigen(locvector);
323 
324  if (local_omb_vec.size() == 0) {
325  // no obs. so no need to update Wa_ and wa_
326  // ana_pert[i]=bkg_pert[i]
327  this->copyLocalIncrement(bkg_pert, i, ana_pert);
328  } else {
329  // if obs are present do normal KF update
330  // get local Yb & HZ
331  Eigen::MatrixXd local_Yb_mat = this->Yb_.packEigen(locvector);
332  Eigen::MatrixXd local_HZ_mat = this->HZb_.packEigen(locvector);
333  // create local obs errors
334  Eigen::VectorXd local_invVarR_vec = this->invVarR_->packEigen(locvector);
335  // and apply localization
336  Eigen::VectorXd localization = locvector.packEigen(locvector);
337  local_invVarR_vec.array() *= localization.array();
338  computeWeights(local_omb_vec, local_HZ_mat, local_Yb_mat, local_invVarR_vec);
339  applyWeights(bkg_pert, ana_pert, i);
340  }
341 }
342 
343 } // namespace oops
344 #endif // OOPS_ASSIMILATION_GETKFSOLVER_H_
program test
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
An implementation of the GETKF from Lei 2018 JAMES.
Definition: GETKFSolver.h:49
Departures< OBS > Departures_
Definition: GETKFSolver.h:50
void applyWeights(const IncrementEnsemble4D_ &, IncrementEnsemble4D_ &, const GeometryIterator_ &)
Applies weights and adds posterior inflation.
Definition: GETKFSolver.h:235
Eigen::VectorXd wa_
Definition: GETKFSolver.h:101
VerticalLocEV< MODEL > VerticalLocEV_
Definition: GETKFSolver.h:60
GETKFSolver(ObsSpaces_ &, const Geometry_ &, const eckit::Configuration &, size_t)
Definition: GETKFSolver.h:107
Eigen::MatrixXd Wa_
Definition: GETKFSolver.h:100
GeometryIterator< MODEL > GeometryIterator_
Definition: GETKFSolver.h:53
static const std::string classname()
Definition: GETKFSolver.h:63
DeparturesEnsemble< OBS > DeparturesEnsemble_
Definition: GETKFSolver.h:51
const Geometry_ & geometry_
Definition: GETKFSolver.h:93
IncrementEnsemble4D< MODEL > IncrementEnsemble4D_
Definition: GETKFSolver.h:54
DeparturesEnsemble_ HZb_
Definition: GETKFSolver.h:98
VerticalLocEV_ vertloc_
Definition: GETKFSolver.h:94
ObsEnsemble< OBS > ObsEnsemble_
Definition: GETKFSolver.h:55
Geometry< MODEL > Geometry_
Definition: GETKFSolver.h:52
void computeWeights(const Eigen::VectorXd &omb, const Eigen::MatrixXd &Yb, const Eigen::MatrixXd &YbOrig, const Eigen::VectorXd &invvarR)
Definition: GETKFSolver.h:200
StateEnsemble4D< MODEL > StateEnsemble4D_
Definition: GETKFSolver.h:59
Observations_ computeHofX(const StateEnsemble4D_ &, size_t, bool) override
computes ensemble H(xx), returns mean H(xx), saves as hofx iteration
Definition: GETKFSolver.h:144
void measurementUpdate(const IncrementEnsemble4D_ &, const GeometryIterator_ &, IncrementEnsemble4D_ &) override
entire KF update (computeWeights+applyWeights) for a grid point GeometryIterator_
Definition: GETKFSolver.h:312
State4D< MODEL > State4D_
Definition: GETKFSolver.h:58
ObsSpaces< OBS > ObsSpaces_
Definition: GETKFSolver.h:57
Observations< OBS > Observations_
Definition: GETKFSolver.h:56
LETKFSolverParameters options_
Definition: GETKFSolver.h:90
Geometry class used in oops; subclass of interface class interface::Geometry.
Ensemble of 4D increments.
size_t size() const
Accessors.
Parameters for LETKF inflation.
Parameter< LETKFInflationParameters > infl
Base class for LETKF-type solvers.
virtual Observations_ computeHofX(const StateEnsemble4D_ &xx, size_t iteration, bool readFromDisk)
computes ensemble H(xx), returns mean H(xx), saves as hofx iteration
void setVals(std::vector< double > &)
const std::vector< double > & getVals() const
Ensemble of observations (can hold ensemble of H(x))
Definition: ObsEnsemble.h:21
Observations Class.
Definition: Observations.h:35
void save(const std::string &) const
Save/read observations values.
Definition: Observations.h:141
void read(const std::string &)
Definition: Observations.h:148
Four dimensional state (vector of 3D States)
Definition: State4D.h:29
Ensemble of 4D states.
State4D_ mean() const
calculate ensemble mean
The namespace for the main oops code.
void letkf_core_f90(const int &, const float *, const float *, const float *, float *, float *, const float *, const int &, const int &, const int &, const int &, const int &)