IODA
test/ioda/ObsVector.h
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2009-2016 ECMWF.
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  * In applying this licence, ECMWF does not waive the privileges and immunities
7  * granted to it by virtue of its status as an intergovernmental organisation nor
8  * does it submit to any jurisdiction.
9  */
10 
11 #ifndef TEST_IODA_OBSVECTOR_H_
12 #define TEST_IODA_OBSVECTOR_H_
13 
14 #include <memory>
15 #include <string>
16 #include <vector>
17 
18 #include <boost/shared_ptr.hpp>
19 
20 #define ECKIT_TESTING_SELF_REGISTER_CASES 0
21 
22 #include "eckit/config/LocalConfiguration.h"
23 #include "eckit/testing/Test.h"
24 
25 #include "oops/mpi/mpi.h"
26 #include "oops/runs/Test.h"
27 #include "oops/test/TestEnvironment.h"
28 #include "oops/util/dot_product.h"
29 #include "oops/util/Logger.h"
30 
31 #include "ioda/IodaTrait.h"
32 #include "ioda/ObsSpace.h"
33 #include "ioda/ObsVector.h"
34 
35 namespace ioda {
36 namespace test {
37 
38 // -----------------------------------------------------------------------------
39 
40 class ObsVecTestFixture : private boost::noncopyable {
42 
43  public:
44  static std::vector<boost::shared_ptr<ObsSpace_> > & obspace() {return getInstance().ospaces_;}
45 
46  static void cleanup() {
47  auto &spaces = getInstance().ospaces_;
48  for (auto &space : spaces) {
49  space->save();
50  space.reset();
51  }
52  }
53 
54  private:
56  static ObsVecTestFixture theObsVecTestFixture;
57  return theObsVecTestFixture;
58  }
59 
61  util::DateTime bgn((::test::TestEnvironment::config().getString("window begin")));
62  util::DateTime end((::test::TestEnvironment::config().getString("window end")));
63 
64  std::vector<eckit::LocalConfiguration> conf;
65  ::test::TestEnvironment::config().get("observations", conf);
66 
67  for (std::size_t jj = 0; jj < conf.size(); ++jj) {
68  eckit::LocalConfiguration obsconf(conf[jj], "obs space");
69  std::string distname = obsconf.getString("distribution", "RoundRobin");
70  boost::shared_ptr<ObsSpace_> tmp(new ObsSpace_(obsconf, oops::mpi::world(),
71  bgn, end, oops::mpi::myself()));
72  ospaces_.push_back(tmp);
73  eckit::LocalConfiguration ObsDataInConf;
74  obsconf.get("obsdatain", ObsDataInConf);
75  }
76  }
77 
79 
80  std::vector<boost::shared_ptr<ObsSpace_> > ospaces_;
81 };
82 
83 // -----------------------------------------------------------------------------
84 
85 void testConstructor() {
86  typedef ObsVecTestFixture Test_;
87  typedef ioda::ObsVector ObsVector_;
88 
89  for (std::size_t jj = 0; jj < Test_::obspace().size(); ++jj) {
90  std::unique_ptr<ObsVector_> ov(new ObsVector_(*Test_::obspace()[jj]));
91  EXPECT(ov.get());
92 
93  ov.reset();
94  EXPECT(!ov.get());
95  }
96 }
97 
98 // -----------------------------------------------------------------------------
99 
101  typedef ObsVecTestFixture Test_;
102  typedef ioda::ObsVector ObsVector_;
103 
104  for (std::size_t jj = 0; jj < Test_::obspace().size(); ++jj) {
105  std::unique_ptr<ObsVector_> ov(new ObsVector_(*Test_::obspace()[jj]));
106 
107  std::unique_ptr<ObsVector_> other(new ObsVector_(*ov));
108  EXPECT(other.get());
109 
110  other.reset();
111  EXPECT(!other.get());
112 
113  EXPECT(ov.get());
114  }
115 }
116 
117 // -----------------------------------------------------------------------------
118 
119 void testNotZero() {
120  typedef ObsVecTestFixture Test_;
121  typedef ioda::ObsVector ObsVector_;
122  const double zero = 0.0;
123 
124  for (std::size_t jj = 0; jj < Test_::obspace().size(); ++jj) {
125  ObsVector_ ov(*Test_::obspace()[jj]);
126 
127  ov.random();
128 
129  const double ovov2 = dot_product(ov, ov);
130  EXPECT(ovov2 > zero);
131 
132  ov.zero();
133 
134  const double zz = dot_product(ov, ov);
135  EXPECT(zz == zero);
136  }
137 }
138 
139 // -----------------------------------------------------------------------------
140 
141 void testRead() {
142  typedef ObsVecTestFixture Test_;
143  typedef ioda::ObsVector ObsVector_;
144 
145  std::vector<eckit::LocalConfiguration> conf;
146  ::test::TestEnvironment::config().get("observations", conf);
147 
148  for (std::size_t jj = 0; jj < Test_::obspace().size(); ++jj) {
149  ioda::ObsSpace * Odb = Test_::obspace()[jj].get();
150 
151  // Grab the expected RMS value and tolerance from the obsdb_ configuration.
152  // Grab the obs space and test data configurations
153  eckit::LocalConfiguration testConfig;
154  conf[jj].get("test data", testConfig);
155  double ExpectedRms = testConfig.getDouble("rms ref");
156  double Tol = testConfig.getDouble("tolerance");
157 
158  // Read in a vector and check contents with norm function.
159  std::unique_ptr<ObsVector_> ov(new ObsVector_(*Odb, "ObsValue"));
160  double Rms = ov->rms();
161 
162  EXPECT(oops::is_close(Rms, ExpectedRms, Tol));
163  }
164 }
165 
166 // -----------------------------------------------------------------------------
167 
168 void testSave() {
169  typedef ObsVecTestFixture Test_;
170  typedef ioda::ObsVector ObsVector_;
171 
172  ioda::ObsSpace * Odb;
173  double Rms;
174  double ExpectedRms;
175 
176  std::vector<eckit::LocalConfiguration> conf;
177  ::test::TestEnvironment::config().get("observations", conf);
178 
179  for (std::size_t jj = 0; jj < Test_::obspace().size(); ++jj) {
180  Odb = Test_::obspace()[jj].get();
181  bool read_obs_from_separate_file =
182  conf[jj].getBool("obs space.read obs from separate file", false);
183 
184  // Read in a vector and save the rms value. Then write the vector into a
185  // test group, read it out of the test group and compare the rms of the
186  // vector read out of the test group with that of the original.
187  std::unique_ptr<ObsVector_> ov_orig(new ObsVector_(*Odb, "ObsValue"));
188  ExpectedRms = ov_orig->rms();
189 
190  if (!read_obs_from_separate_file)
191  ov_orig->save("ObsTest");
192 
193  std::unique_ptr<ObsVector_> ov_test(new ObsVector_(*Odb, "ObsTest"));
194  Rms = ov_test->rms();
195 
196  EXPECT(oops::is_close(Rms, ExpectedRms, 1.0e-12));
197  }
198 }
199 
200 // -----------------------------------------------------------------------------
201 /// \brief tests ObsVector::axpy methods
202 /// \details Tests the following for a random vector vec1:
203 /// 1. Calling ObsVector::axpy with a single number (2.0) returns the same result
204 /// as calling it with a vector of 2.0
205 /// 2. Calling ObsVector::axpy with vectors of coefficients that differ across
206 /// variables gives reasonable result. axpy is called twice, the coefficients
207 /// between the two different calls add to 2.0.
208 void testAxpy() {
209  typedef ObsVecTestFixture Test_;
210  typedef ioda::ObsVector ObsVector_;
211 
212  for (std::size_t jj = 0; jj < Test_::obspace().size(); ++jj) {
213  ioda::ObsSpace & obspace = *Test_::obspace()[jj];
214  ObsVector_ vec1(obspace);
215  vec1.random();
216 
217  // call axpy with coefficient 2.0 two different ways
218  ObsVector_ vec2(vec1);
219  vec2.axpy(2.0, vec1);
220  ObsVector_ vec3(vec1);
221  std::vector<double> beta(obspace.obsvariables().size(), 2.0);
222  vec3.axpy(beta, vec1);
223  oops::Log::test() << "Testing ObsVector::axpy" << std::endl;
224  oops::Log::test() << "x = " << vec1 << std::endl;
225  oops::Log::test() << "x.axpy(2, x) = " << vec2 << std::endl;
226  oops::Log::test() << "x.axpy(vector of 2, x) = " << vec3 << std::endl;
227  EXPECT(oops::is_close(vec2.rms(), vec3.rms(), 1.0e-8));
228 
229  // call axpy with vector of different values
230  std::vector<double> beta1(obspace.obsvariables().size());
231  std::vector<double> beta2(obspace.obsvariables().size());
232  for (size_t jj = 0; jj < beta1.size(); ++jj) {
233  beta1[jj] = static_cast<double>(jj)/beta1.size();
234  beta2[jj] = 2.0 - beta1[jj];
235  }
236  oops::Log::test() << "beta1 = " << beta1 << ", beta2 = " << beta2 << std::endl;
237  ObsVector_ vec4(vec1);
238  vec4.axpy(beta1, vec1);
239  oops::Log::test() << "x.axpy(beta1, x) = " << vec4 << std::endl;
240  vec4.axpy(beta2, vec1);
241  oops::Log::test() << "x.axpy(beta2, x) = " << vec4 << std::endl;
242  EXPECT(oops::is_close(vec4.rms(), vec3.rms(), 1.0e-8));
243  }
244 }
245 
246 /// \brief tests ObsVector::dot_product methods
247 /// \details Tests the following for a random vector vec1:
248 /// 1. Calling ObsVector::dot_product and calling ObsVector::multivar_dot_product
249 /// are consistent.
251  typedef ObsVecTestFixture Test_;
252  typedef ioda::ObsVector ObsVector_;
253 
254  for (std::size_t jj = 0; jj < Test_::obspace().size(); ++jj) {
255  ioda::ObsSpace & obspace = *Test_::obspace()[jj];
256  ObsVector_ vec1(obspace);
257  vec1.random();
258  ObsVector_ vec2(obspace);
259  vec2.random();
260 
261  double dp1 = vec1.dot_product_with(vec2);
262  std::vector<double> dp2 = vec1.multivar_dot_product_with(vec2);
263  oops::Log::test() << "Testing ObsVector::dot_product" << std::endl;
264  oops::Log::test() << "x1 = " << vec1 << std::endl;
265  oops::Log::test() << "x2 = " << vec2 << std::endl;
266  oops::Log::test() << "x1.dot_product_with(x2) = " << dp1 << std::endl;
267  oops::Log::test() << "x1.multivar_dot_product_with(x2) = " << dp2 << std::endl;
268 
269  // test that size of vector returned by multivar dot product is correct
270  EXPECT_EQUAL(dp2.size(), vec1.nvars());
271  // test that dot products are consistent (sum of all elements in multivar one
272  // is the same as the scalar one)
273  EXPECT(oops::is_close(dp1, std::accumulate(dp2.begin(), dp2.end(), 0.0), 1.0e-12));
274  }
275 }
276 
277 // -----------------------------------------------------------------------------
278 
280  typedef ioda::ObsVector ObsVector_;
281  typedef ioda::ObsSpace ObsSpace_;
282  typedef std::vector< std::shared_ptr< ObsVector_> > ObsVectors_;
283 
284  // Some of the ObsVector math routines require global communications,
285  // and so are performed differently for different distributions. But the
286  // answers should always be the same regardless of distribution.
287 
288  // get the list of distributions to test with
289  std::vector<std::string> dist_names =
290  ::test::TestEnvironment::config().getStringVector("distributions");
291  for (std::size_t ii = 0; ii < dist_names.size(); ++ii) {
292  oops::Log::debug() << "using distribution: " << dist_names[ii] << std::endl;
293  }
294 
295  // Get some config information that is the same regardless of distribution
296  util::DateTime bgn((::test::TestEnvironment::config().getString("window begin")));
297  util::DateTime end((::test::TestEnvironment::config().getString("window end")));
298  std::vector<eckit::LocalConfiguration> conf;
299  ::test::TestEnvironment::config().get("observations", conf);
300 
301  // for each distribution, create the set of obs vectors
302  std::vector< ObsVectors_ > dist_obsvecs;
303  std::vector< std::shared_ptr<ObsSpace_> > dist_obsdbs;
304  for (std::size_t dd = 0; dd < dist_names.size(); ++dd) {
305  ObsVectors_ obsvecs;
306  for (std::size_t jj = 0; jj < conf.size(); ++jj) {
307  // We want to cycle through the set of distributions that are specified
308  // in the list with the keyword "distributions" in the YAML. The test fixture
309  // has already constructed all of the obs spaces listed in the YAML, and we
310  // are repeating that action inside this loop. In other words, we are doubling
311  // up the obs space objects that a specified in the YAML.
312  //
313  // This is okay unless the YAML has specified an output file anywhere. The issue
314  // is that the output file is written during the destructor and the HDF library
315  // (unfortunately) tends to keep file descriptors open until the process terminates.
316  // Therefore it is possible for the file writes to collide, causing the test to
317  // crash, if the obs space created here is writing to the same file as the
318  // corresponding obs space in the test fixture.
319  //
320  // The fix is to tag on the name of the distribution on the output file name here
321  // to prevent the collision. The collision avoidance is absolutely guaranteed, but
322  // we can do it in a way that is unlikely to collide with any other output file
323  // names in the YAML. Note that this also prevents clobbering any output files
324  // specfied in the YAML.
325  eckit::LocalConfiguration obsconf(conf[jj], "obs space");
326  obsconf.set("distribution", dist_names[dd]);
327  if (obsconf.has("obsdataout.obsfile")) {
328  std::string fileName = obsconf.getString("obsdataout.obsfile");
329  std::string fileTag = std::string("_Dist_") + dist_names[dd];
330  std::size_t pos = fileName.find_last_of(".");
331  if (pos != std::string::npos) {
332  // have a suffix on the file name, insert tag before the suffix
333  fileName.insert(pos, fileTag);
334  } else {
335  // do not have a suffix on the file name, append tag to end of file name
336  fileName += fileTag;
337  }
338  obsconf.set("obsdataout.obsfile", fileName);
339  }
340 
341  // Instantiate the obs space with the distribution we are testing
342  std::shared_ptr<ObsSpace_> obsdb(new ObsSpace(obsconf, oops::mpi::world(),
343  bgn, end, oops::mpi::myself()));
344  std::shared_ptr<ObsVector_> obsvec(new ObsVector_(*obsdb, "ObsValue"));
345  oops::Log::debug() << dist_names[dd] << ": " << *obsvec << std::endl;
346  dist_obsdbs.push_back(obsdb);
347  obsvecs.push_back(obsvec);
348  }
349  dist_obsvecs.push_back(obsvecs);
350  }
351 
352  // For each ObsVector make sure the math is the same regardless of distribution.
353  // Test rms(), nobs(), dot_product_with()
354  for (std::size_t ii = 0; ii < conf.size(); ++ii) {
355  // get the values for the first distribution
356  int nobs = dist_obsvecs[0][ii]->nobs();
357  double rms = dist_obsvecs[0][ii]->rms();
358  double dot = dist_obsvecs[0][ii]->dot_product_with(*dist_obsvecs[0][ii]);
359 
360  // make sure the values are the same for all the other distributions
361  for (std::size_t jj = 1; jj < dist_obsvecs.size(); ++jj) {
362  int nobs2 = dist_obsvecs[jj][ii]->nobs();
363  double rms2 = dist_obsvecs[jj][ii]->rms();
364  double dot2 = dist_obsvecs[jj][ii]->dot_product_with(*dist_obsvecs[jj][ii]);
365 
366  EXPECT(nobs == nobs2);
367  EXPECT(oops::is_close(rms, rms2, 1.0e-12));
368  EXPECT(oops::is_close(dot, dot2, 1.0e-12));
369  }
370  }
371 }
372 
373 // -----------------------------------------------------------------------------
374 
375 void testCleanup() {
376  // This test removes the obsspaces from the test fixture and ensures that they evict
377  // their contents to disk successfully.
378  typedef ObsVecTestFixture Test_;
379 
380  Test_::cleanup();
381 }
382 
383 // -----------------------------------------------------------------------------
384 
385 class ObsVector : public oops::Test {
386  public:
388  virtual ~ObsVector() {}
389 
390  private:
391  std::string testid() const override {return "test::ObsVector<ioda::IodaTrait>";}
392 
393  void register_tests() const override {
394  std::vector<eckit::testing::Test>& ts = eckit::testing::specification();
395 
396  ts.emplace_back(CASE("ioda/ObsVector/testConstructor")
397  { testConstructor(); });
398  ts.emplace_back(CASE("ioda/ObsVector/testCopyConstructor")
399  { testCopyConstructor(); });
400  ts.emplace_back(CASE("ioda/ObsVector/testNotZero")
401  { testNotZero(); });
402  ts.emplace_back(CASE("ioda/ObsVector/testRead")
403  { testRead(); });
404  ts.emplace_back(CASE("ioda/ObsVector/testSave")
405  { testSave(); });
406  ts.emplace_back(CASE("ioda/ObsVector/testAxpy")
407  { testAxpy(); });
408  ts.emplace_back(CASE("ioda/ObsVector/testDotProduct")
409  { testDotProduct(); });
410  ts.emplace_back(CASE("ioda/ObsVector/testDistributedMath")
411  { testDistributedMath(); });
412  ts.emplace_back(CASE("ioda/ObsVector/testCleanup")
413  { testCleanup(); });
414  }
415 
416  void clear() const override {}
417 };
418 
419 // =============================================================================
420 
421 } // namespace test
422 } // namespace ioda
423 
424 #endif // TEST_IODA_OBSVECTOR_H_
Observation data class for IODA.
Definition: src/ObsSpace.h:116
const oops::Variables & obsvariables() const
return oops variables object (simulated variables)
Definition: src/ObsSpace.h:332
ObsVector class to handle vectors in observation space for IODA.
Definition: src/ObsVector.h:34
static ObsVecTestFixture & getInstance()
std::vector< boost::shared_ptr< ObsSpace_ > > ospaces_
static std::vector< boost::shared_ptr< ObsSpace_ > > & obspace()
std::string testid() const override
void clear() const override
void register_tests() const override
void testAxpy()
tests ObsVector::axpy methods
void testCopyConstructor()
void testDotProduct()
tests ObsVector::dot_product methods
CASE("Derived variable and unit conversion methods")
void testDistributedMath()