IODA Bundle
LocalEnsembleDA.h
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2019-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_RUNS_LOCALENSEMBLEDA_H_
9 #define OOPS_RUNS_LOCALENSEMBLEDA_H_
10 
11 #include <cmath>
12 #include <memory>
13 #include <string>
14 #include <vector>
15 
16 #include "eckit/config/LocalConfiguration.h"
19 #include "oops/base/Departures.h"
20 #include "oops/base/Geometry.h"
21 #include "oops/base/Increment.h"
24 #include "oops/base/Observations.h"
25 #include "oops/base/ObsSpaces.h"
26 #include "oops/base/State4D.h"
30 #include "oops/mpi/mpi.h"
31 #include "oops/runs/Application.h"
32 #include "oops/util/DateTime.h"
33 #include "oops/util/Duration.h"
34 #include "oops/util/Logger.h"
35 
36 
37 namespace oops {
38 
39 /// \brief Application for local ensemble data assimilation
40 template <typename MODEL, typename OBS> class LocalEnsembleDA : public Application {
51 
52  public:
53 // -----------------------------------------------------------------------------
54 
55  explicit LocalEnsembleDA(const eckit::mpi::Comm & comm = oops::mpi::world()) : Application(comm) {
56  instantiateLocalEnsembleSolverFactory<MODEL, OBS>();
57  instantiateObsErrorFactory<OBS>();
58  instantiateObsFilterFactory<OBS>();
59  }
60 
61 // -----------------------------------------------------------------------------
62 
63  virtual ~LocalEnsembleDA() = default;
64 
65 // -----------------------------------------------------------------------------
66 
67  int execute(const eckit::Configuration & fullConfig) const {
68  // Setup observation window
69  const util::DateTime winbgn(fullConfig.getString("window begin"));
70  const util::Duration winlen(fullConfig.getString("window length"));
71  const util::DateTime winend(winbgn + winlen);
72  Log::info() << "Observation window from " << winbgn << " to " << winend << std::endl;
73 
74  // Setup geometry
75  const eckit::LocalConfiguration geometryConfig(fullConfig, "geometry");
76  const Geometry_ geometry(geometryConfig, this->getComm());
77 
78  // Get observations configuration
79  eckit::LocalConfiguration obsConfig(fullConfig, "observations");
80  // Get driver configuration
81  const eckit::LocalConfiguration driverConfig(fullConfig, "driver");
82 
83  // if any of the obs. spaces uses Halo distribution it will need to know the geometry
84  // of the local grid on this PE
85  bool update_obsconfig = driverConfig.getBool("update obs config with geometry info", false);
86  if (update_obsconfig) updateConfigWithPatchGeometry(geometry, obsConfig);
87 
88  // Setup observations
89  const eckit::mpi::Comm & time = oops::mpi::myself();
90  ObsSpaces_ obsdb(obsConfig, this->getComm(), winbgn, winend, time);
91  Observations_ yobs(obsdb, "ObsValue");
92 
93  // Get background configurations
94  const eckit::LocalConfiguration bgConfig(fullConfig, "background");
95 
96  // Read all ensemble members
97  StateEnsemble4D_ ens_xx(geometry, bgConfig);
98  const size_t nens = ens_xx.size();
99  const Variables statevars = ens_xx.variables();
100 
101  // set up solver
102  std::unique_ptr<LocalSolver_> solver =
103  LocalEnsembleSolverFactory<MODEL, OBS>::create(obsdb, geometry, fullConfig, nens);
104  // test prints for the prior ensemble
105  bool do_test_prints = driverConfig.getBool("do test prints", true);
106  if (do_test_prints) {
107  for (size_t jj = 0; jj < nens; ++jj) {
108  Log::test() << "Initial state for member " << jj+1 << ":" << ens_xx[jj] << std::endl;
109  }
110  }
111 
112  // compute H(x)
113  bool readFromDisk = driverConfig.getBool("read HX from disk", false);
114  Observations_ yb_mean = solver->computeHofX(ens_xx, 0, readFromDisk);
115  Log::test() << "H(x) ensemble background mean: " << std::endl << yb_mean << std::endl;
116 
117  Departures_ ombg(yobs - yb_mean);
118  ombg.save("ombg");
119  Log::test() << "background y - H(x): " << std::endl << ombg << std::endl;
120 
121  // quit early if running in observer-only mode
122  bool observerOnly = driverConfig.getBool("run as observer only", false);
123  if (observerOnly) {
124  obsdb.save();
125  return 0;
126  }
127 
128  // calculate background mean
129  State4D_ bkg_mean = ens_xx.mean();
130  if (do_test_prints) {
131  Log::test() << "Background mean :" << bkg_mean << std::endl;
132  }
133 
134  // calculate background ensemble perturbations
135  IncrementEnsemble4D_ bkg_pert(ens_xx, bkg_mean, statevars);
136 
137  // initialize empty analysis perturbations
138  IncrementEnsemble4D_ ana_pert(geometry, statevars, ens_xx[0].validTimes(), bkg_pert.size());
139 
140  // run the solver at each gridpoint
141  Log::info() << "Beginning core local solver..." << std::endl;
142  for (GeometryIterator_ i = geometry.begin(); i != geometry.end(); ++i) {
143  solver->measurementUpdate(bkg_pert, i, ana_pert);
144  }
145  Log::info() << "Local solver completed." << std::endl;
146 
147  // wait all tasks to finish their solution, so the timing for functions below reports
148  // time which truly used (not from mpi_wait(), as all tasks need to sync before write).
149  oops::mpi::world().barrier();
150 
151  // calculate final analysis states
152  for (size_t jj = 0; jj < nens; ++jj) {
153  ens_xx[jj] = bkg_mean;
154  ens_xx[jj] += ana_pert[jj];
155  }
156 
157  // save the posterior mean and ensemble first (since they are needed for the next cycle)
158  // save the posterior mean
159  State4D_ ana_mean = ens_xx.mean(); // calculate analysis mean
160  if (do_test_prints) {
161  Log::test() << "Analysis mean :" << ana_mean << std::endl;
162  }
163  bool save_xamean = driverConfig.getBool("save posterior mean", false);
164  if (save_xamean) {
165  eckit::LocalConfiguration outConfig(fullConfig, "output");
166  outConfig.set("member", 0);
167  ana_mean.write(outConfig);
168  }
169 
170  // save the posterior ensemble
171  bool save_ens = driverConfig.getBool("save posterior ensemble", true);
172  if (save_ens) {
173  size_t mymember;
174  for (size_t jj=0; jj < nens; ++jj) {
175  mymember = jj+1;
176  eckit::LocalConfiguration outConfig(fullConfig, "output");
177  outConfig.set("member", mymember);
178  ens_xx[jj].write(outConfig);
179  }
180  }
181 
182  // below is the diagnostic output -----------------------------
183  // save the background mean
184  bool save_xbmean = driverConfig.getBool("save prior mean", false);
185  if (save_xbmean) {
186  eckit::LocalConfiguration outConfig(fullConfig, "output mean prior");
187  outConfig.set("member", 0);
188  bkg_mean.write(outConfig);
189  }
190 
191  // save the analysis increment
192  bool save_mean_increment = driverConfig.getBool("save posterior mean increment", false);
193  if (save_mean_increment) {
194  eckit::LocalConfiguration outConfig(fullConfig, "output increment");
195  outConfig.set("member", 0);
196  for (size_t itime = 0; itime < ana_mean.size(); ++itime) {
197  Increment_ ana_increment(ana_pert[0][itime], false);
198  ana_increment.diff(ana_mean[itime], bkg_mean[itime]);
199  ana_increment.write(outConfig);
200  if (do_test_prints) {
201  Log::test() << "Analysis mean increment :" << ana_increment << std::endl;
202  }
203  }
204  }
205 
206  // save the prior variance
207  bool save_variance = driverConfig.getBool("save prior variance", false);
208  if (save_variance) {
209  eckit::LocalConfiguration outConfig(fullConfig, "output variance prior");
210  outConfig.set("member", 0);
211  std::string strOut("Forecast variance :");
212  saveVariance(outConfig, bkg_pert, do_test_prints, strOut);
213  }
214 
215  // save the posterior variance
216  save_variance = driverConfig.getBool("save posterior variance", false);
217  if (save_variance) {
218  eckit::LocalConfiguration outConfig(fullConfig, "output variance posterior");
219  outConfig.set("member", 0);
220  std::string strOut("Analysis variance :");
221  saveVariance(outConfig, ana_pert, do_test_prints, strOut);
222  }
223 
224  // posterior observer
225  // note: if H(X) is read from file, it might have used different time slots for observation
226  // than LETKF background/analysis perturbations.
227  // hence one might not expect that oman and omaf are comparable
228  bool do_posterior_observer = driverConfig.getBool("do posterior observer", true);
229  if (do_posterior_observer) {
230  Observations_ ya_mean = solver->computeHofX(ens_xx, 1, false);
231  Log::test() << "H(x) ensemble analysis mean: " << std::endl << ya_mean << std::endl;
232 
233  // calculate analysis obs departures
234  Departures_ oman(yobs - ya_mean);
235  oman.save("oman");
236  Log::test() << "analysis y - H(x): " << std::endl << oman << std::endl;
237 
238  // display overall background/analysis RMS stats
239  Log::test() << "ombg RMS: " << ombg.rms() << std::endl
240  << "oman RMS: " << oman.rms() << std::endl;
241  }
242  obsdb.save();
243 
244  return 0;
245  }
246 
247 // -----------------------------------------------------------------------------
248 
249  private:
250  std::string appname() const {
251  return "oops::LocalEnsembleDA<" + MODEL::name() + ", " + OBS::name() + ">";
252  }
253 
255  eckit::LocalConfiguration & obsConfig) const {
256  std::vector<double> patchCenter(2, 0.0);
257  double patchRadius = 0.0;
258 
259  // since Halo distribution is only implemented in ioda, we can assume that
260  // x is lon and y is lat on Earth. then we need to compute average of x on a circle
261  eckit::geometry::Point2 gptmp;
262  const double radius_earth = 6.371e6;
263  const double deg2rad = 3.14159265/180.0;
264 
265  // compute patch center
266  double sinXmean = 0;
267  double cosXmean = 0;
268  double ymean = 0;
269  int n = 0;
270  for (GeometryIterator_ i = geometry.begin(); i != geometry.end(); ++i) {
271  gptmp = *i;
272  cosXmean += cos(gptmp.x()*deg2rad);
273  sinXmean += sin(gptmp.x()*deg2rad);
274  ymean += gptmp.y();
275  ++n;
276  }
277  cosXmean = cosXmean/static_cast<double>(n);
278  sinXmean = sinXmean/static_cast<double>(n);
279  patchCenter[0] = atan2(sinXmean, cosXmean)/deg2rad;
280  patchCenter[1] = ymean/static_cast<double>(n);
281 
282  // compute radius
283  eckit::geometry::Point2 center(patchCenter[0], patchCenter[1]);
284  patchRadius = 0;
285  for (GeometryIterator_ i = geometry.begin(); i != geometry.end(); ++i) {
286  double dist = eckit::geometry::Sphere::distance(radius_earth, center, *i);
287  patchRadius = fmax(patchRadius, dist);
288  }
289  Log::debug() << "patch center=" << patchCenter
290  << " patch radius=" << patchRadius << std::endl;
291 
292  // update observations configs with information on patch center and radius
293  std::vector<eckit::LocalConfiguration> obsConfigs = obsConfig.getSubConfigurations();
294  for (auto & conf : obsConfigs) {
295  conf.set("obs space.center", patchCenter);
296  conf.set("obs space.radius", patchRadius);
297  }
298  eckit::LocalConfiguration tmp;
299  tmp.set("observations", obsConfigs);
300  obsConfig = tmp.getSubConfiguration("observations");
301  }
302 
303  void saveVariance(const eckit::LocalConfiguration & outConfig, const IncrementEnsemble4D_ & perts,
304  const bool do_test_prints, const std::string & strOut) const {
305  // save and optionaly print varaince of an IncrementEnsemble4D_ object
306  size_t nens = perts.size();
307  const double nc = 1.0/(static_cast<double>(nens) - 1.0);
308  for (size_t itime = 0; itime < perts[0].size(); ++itime) {
309  Increment_ var(perts[0][itime], false);
310  for (size_t iens = 0; iens < nens; ++iens) {
311  Increment_ tmp(perts[iens][itime], true);
312  tmp.schur_product_with(perts[iens][itime]);
313  var += tmp;
314  }
315  var *= nc;
316  var.write(outConfig);
317  if (do_test_prints) {
318  Log::test() << strOut << var << std::endl;
319  }
320  }
321  }
322 
323 // -----------------------------------------------------------------------------
324 };
325 
326 } // namespace oops
327 #endif // OOPS_RUNS_LOCALENSEMBLEDA_H_
program test
const eckit::mpi::Comm & getComm() const
Definition: Application.h:36
Difference between two observation vectors.
Definition: Departures.h:44
void save(const std::string &) const
Save departures values.
Definition: Departures.h:249
double rms() const
Definition: Departures.h:193
Geometry class used in oops; subclass of interface class interface::Geometry.
Ensemble of 4D increments.
size_t size() const
Accessors.
Increment class used in oops.
Application for local ensemble data assimilation.
Departures< OBS > Departures_
virtual ~LocalEnsembleDA()=default
StateEnsemble4D< MODEL > StateEnsemble4D_
int execute(const eckit::Configuration &fullConfig) const
IncrementEnsemble4D< MODEL > IncrementEnsemble4D_
GeometryIterator< MODEL > GeometryIterator_
Observations< OBS > Observations_
void saveVariance(const eckit::LocalConfiguration &outConfig, const IncrementEnsemble4D_ &perts, const bool do_test_prints, const std::string &strOut) const
void updateConfigWithPatchGeometry(const Geometry_ &geometry, eckit::LocalConfiguration &obsConfig) const
LocalEnsembleDA(const eckit::mpi::Comm &comm=oops::mpi::world())
ObsSpaces< OBS > ObsSpaces_
std::string appname() const
Increment< MODEL > Increment_
LocalEnsembleSolver< MODEL, OBS > LocalSolver_
Geometry< MODEL > Geometry_
State4D< MODEL > State4D_
static std::unique_ptr< LocalEnsembleSolver< MODEL, OBS > > create(ObsSpaces_ &, const Geometry_ &, const eckit::Configuration &, size_t)
Base class for LETKF-type solvers.
void save() const
Save files.
Definition: ObsSpaces.h:95
Observations Class.
Definition: Observations.h:35
Four dimensional state (vector of 3D States)
Definition: State4D.h:29
size_t size() const
Get 3D model state.
Definition: State4D.h:43
void write(const eckit::Configuration &) const
I/O.
Definition: State4D.h:87
Ensemble of 4D states.
const Variables & variables() const
Information.
State4D_ mean() const
calculate ensemble mean
unsigned int size() const
Accessors.
GeometryIterator_ end() const
Iterator to the past-the-end gridpoint of Geometry (only used in LocalEnsembleDA)
GeometryIterator_ begin() const
Iterator to the first gridpoint of Geometry (only used in LocalEnsembleDA)
void schur_product_with(const Increment &other)
Compute Schur product of this Increment with other, assign to this Increment.
void diff(const State_ &state1, const State_ &state2)
Set this Increment to be difference between state1 and state2.
void write(const eckit::Configuration &) const
Write this Increment out to file.
const eckit::mpi::Comm & myself()
Default communicator with each MPI task by itself.
Definition: oops/mpi/mpi.cc:90
const eckit::mpi::Comm & world()
Default communicator with all MPI tasks (ie MPI_COMM_WORLD)
Definition: oops/mpi/mpi.cc:84
The namespace for the main oops code.