IODA
test/ioda/ObsSpace.h
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2018-2021 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 TEST_IODA_OBSSPACE_H_
9 #define TEST_IODA_OBSSPACE_H_
10 
11 #include <cmath>
12 #include <set>
13 #include <string>
14 #include <vector>
15 
16 #define ECKIT_TESTING_SELF_REGISTER_CASES 0
17 
18 #include <boost/noncopyable.hpp>
19 #include <boost/shared_ptr.hpp>
20 
21 #include "eckit/config/LocalConfiguration.h"
22 #include "eckit/testing/Test.h"
23 
24 #include "oops/mpi/mpi.h"
25 #include "oops/runs/Test.h"
26 #include "oops/test/TestEnvironment.h"
27 
28 #include "ioda/distribution/Accumulator.h"
29 #include "ioda/distribution/DistributionUtils.h"
30 #include "ioda/IodaTrait.h"
31 #include "ioda/ObsSpace.h"
32 
33 namespace ioda {
34 namespace test {
35 
36 // -----------------------------------------------------------------------------
37 
38 class ObsSpaceTestFixture : private boost::noncopyable {
39  public:
40  static ioda::ObsSpace & obspace(const std::size_t ii) {
41  return *getInstance().ospaces_.at(ii);
42  }
43  static std::size_t size() {return getInstance().ospaces_.size();}
44  static void cleanup() {
45  auto &spaces = getInstance().ospaces_;
46  for (auto &space : spaces) {
47  space->save();
48  space.reset();
49  }
50  }
51 
52  private:
54  static ObsSpaceTestFixture theObsSpaceTestFixture;
55  return theObsSpaceTestFixture;
56  }
57 
59  util::DateTime bgn(::test::TestEnvironment::config().getString("window begin"));
60  util::DateTime end(::test::TestEnvironment::config().getString("window end"));
61 
62  std::vector<eckit::LocalConfiguration> conf;
63  ::test::TestEnvironment::config().get("observations", conf);
64 
65  for (std::size_t jj = 0; jj < conf.size(); ++jj) {
66  eckit::LocalConfiguration obsconf(conf[jj], "obs space");
67  std::string distname = obsconf.getString("distribution", "RoundRobin");
68  boost::shared_ptr<ioda::ObsSpace> tmp(new ioda::ObsSpace(obsconf, oops::mpi::world(),
69  bgn, end, oops::mpi::myself()));
70  ospaces_.push_back(tmp);
71  }
72  }
73 
75 
76  std::vector<boost::shared_ptr<ioda::ObsSpace> > ospaces_;
77 };
78 
79 // -----------------------------------------------------------------------------
80 
81 void testConstructor() {
82  typedef ObsSpaceTestFixture Test_;
83 
84  std::vector<eckit::LocalConfiguration> conf;
85  ::test::TestEnvironment::config().get("observations", conf);
86 
87  for (std::size_t jj = 0; jj < Test_::size(); ++jj) {
88  // Grab the obs space and test data configurations
89  eckit::LocalConfiguration obsConfig;
90  eckit::LocalConfiguration testConfig;
91  conf[jj].get("obs space", obsConfig);
92  conf[jj].get("test data", testConfig);
93 
94  std::string DistMethod = obsConfig.getString("distribution", "RoundRobin");
95 
96  const ObsSpace &odb = Test_::obspace(jj);
97 
98  // Get the numbers of locations (nlocs) from the ObsSpace object
99  std::size_t GlobalNlocs = odb.globalNumLocs();
100  std::size_t Nlocs = odb.nlocs();
101  std::size_t Nvars = odb.nvars();
102 
103  // Get the purturbation seed from the ObsSpace object
104  int obsPertSeed = odb.params().obsPertSeed();
105 
106  // Get the expected nlocs from the obspace object's configuration
107  std::size_t ExpectedGlobalNlocs = testConfig.getUnsigned("nlocs");
108  std::size_t ExpectedNvars = testConfig.getUnsigned("nvars");
109 
110  // Get the expected purturbation seed from the config object
111  int ExpectedObsPertSeed = testConfig.getUnsigned("obs perturbations seed");
112 
113  // Get the obs grouping/sorting parameters from the ObsSpace object
114  std::vector<std::string> ObsGroupVars = odb.obs_group_vars();
115  std::string ObsSortVar = odb.obs_sort_var();
116  std::string ObsSortOrder = odb.obs_sort_order();
117 
118  // Get the expected obs grouping/sorting parameters from the configuration
119  std::vector<std::string> ExpectedObsGroupVars =
120  testConfig.getStringVector("expected group variables");
121  std::string ExpectedObsSortVar = testConfig.getString("expected sort variable");
122  std::string ExpectedObsSortOrder = testConfig.getString("expected sort order");
123 
124  oops::Log::debug() << "GlobalNlocs, ExpectedGlobalNlocs: " << GlobalNlocs << ", "
125  << ExpectedGlobalNlocs << std::endl;
126  oops::Log::debug() << "Nvars, ExpectedNvars: " << Nvars << ", "
127  << ExpectedNvars << std::endl;
128  // records are ambigious for halo distribution
129  // e.g. consider airplane (a single record in round robin) flying accros the globe
130  // for Halo distr this record will be considered unique on each PE
131  if (DistMethod != "Halo") {
132  std::size_t NRecs = 0;
133  std::set<std::size_t> recIndices;
134  auto accumulator = odb.distribution()->createAccumulator<std::size_t>();
135  for (std::size_t loc = 0; loc < Nlocs; ++loc) {
136  if (bool isNewRecord = recIndices.insert(odb.recnum()[loc]).second) {
137  accumulator->addTerm(loc, 1);
138  ++NRecs;
139  }
140  }
141  std::size_t ExpectedNRecs = odb.nrecs();
142  EXPECT_EQUAL(NRecs, ExpectedNRecs);
143 
144  // Calculate the global number of unique records
145  std::size_t GlobalNRecs = accumulator->computeResult();
146  std::size_t ExpectedGlobalNrecs = testConfig.getUnsigned("nrecs");
147  EXPECT_EQUAL(GlobalNRecs, ExpectedGlobalNrecs);
148  }
149 
150  oops::Log::debug() << "ObsGroupVars, ExpectedObsGroupVars: " << ObsGroupVars << ", "
151  << ExpectedObsGroupVars << std::endl;
152  oops::Log::debug() << "ObsSortVar, ExpectedObsSortVar: " << ObsSortVar << ", "
153  << ExpectedObsSortVar << std::endl;
154  oops::Log::debug() << "ObsSortOrder, ExpectedObsSortOrder: " << ObsSortOrder << ", "
155  << ExpectedObsSortOrder << std::endl;
156 
157  // get the standard nlocs and nchans dimension names and compare with expected values
158  std::string nlocsName = odb.get_dim_name(ioda::ObsDimensionId::Nlocs);
159  std::string nchansName = odb.get_dim_name(ioda::ObsDimensionId::Nchans);
160 
161  EXPECT(GlobalNlocs == ExpectedGlobalNlocs);
162  EXPECT(Nvars == ExpectedNvars);
163 
164  EXPECT(obsPertSeed == ExpectedObsPertSeed);
165 
166  EXPECT(ObsGroupVars == ExpectedObsGroupVars);
167  EXPECT(ObsSortVar == ExpectedObsSortVar);
168  EXPECT(ObsSortOrder == ExpectedObsSortOrder);
169 
170  EXPECT(nlocsName == "nlocs");
171  EXPECT(nchansName == "nchans");
172 
173  EXPECT(odb.get_dim_id("nlocs") == ioda::ObsDimensionId::Nlocs);
174  EXPECT(odb.get_dim_id("nchans") == ioda::ObsDimensionId::Nchans);
175  }
176 }
177 
178 // -----------------------------------------------------------------------------
179 
180 void testGetDb() {
181  typedef ObsSpaceTestFixture Test_;
182 
183  std::vector<eckit::LocalConfiguration> conf;
184  ::test::TestEnvironment::config().get("observations", conf);
185 
186  for (std::size_t jj = 0; jj < Test_::size(); ++jj) {
187  // Grab the obs space and test data configurations
188  eckit::LocalConfiguration obsConfig;
189  eckit::LocalConfiguration testConfig;
190  conf[jj].get("obs space", obsConfig);
191  conf[jj].get("test data", testConfig);
192 
193  std::string DistMethod = obsConfig.getString("distribution", "RoundRobin");
194 
195  // Set up a pointer to the ObsSpace object for convenience
196  ioda::ObsSpace * Odb = &(Test_::obspace(jj));
197  std::size_t Nlocs = Odb->nlocs();
198 
199  // Get the variables section from the test data and perform checks accordingly
200  std::vector<eckit::LocalConfiguration> varconf =
201  testConfig.getSubConfigurations("variables");
202  double Tol = testConfig.getDouble("tolerance");
203  for (std::size_t i = 0; i < varconf.size(); ++i) {
204  // Read in the variable group, name and expected norm values from the configuration
205  std::string VarName = varconf[i].getString("name");
206  std::string GroupName = varconf[i].getString("group");
207  std::string VarType = varconf[i].getString("type");
208 
209  // Do different checks according to type
210  if (VarType == "float") {
211  // Check the type from ObsSpace
212  ObsDtype VarDataType = Odb->dtype(GroupName, VarName);
213  EXPECT(VarDataType == ObsDtype::Float);
214 
215  // Check auto-conversion to double from ObsSpace float
216  // Check the norm
217  std::vector<double> TestVec(Nlocs);
218  Odb->get_db(GroupName, VarName, TestVec);
219 
220  // Calculate the norm of the vector
221  double ExpectedVnorm = varconf[i].getDouble("norm");
222  double Vnorm = dotProduct(*Odb->distribution(), 1, TestVec, TestVec);
223  Vnorm = sqrt(Vnorm);
224 
225  EXPECT(oops::is_close(Vnorm, ExpectedVnorm, Tol));
226  } else if (VarType == "integer") {
227  // Check the type from ObsSpace
228  ObsDtype VarDataType = Odb->dtype(GroupName, VarName);
229  EXPECT(VarDataType == ObsDtype::Integer);
230 
231  // Check the norm
232  std::vector<int> TestVec(Nlocs);
233  Odb->get_db(GroupName, VarName, TestVec);
234 
235  // Calculate the norm of the vector
236  double ExpectedVnorm = varconf[i].getDouble("norm");
237  double Vnorm = dotProduct(*Odb->distribution(), 1, TestVec, TestVec);
238  Vnorm = sqrt(Vnorm);
239 
240  EXPECT(oops::is_close(Vnorm, ExpectedVnorm, Tol));
241  } else if (VarType == "string") {
242  // Check the type from ObsSpace
243  ObsDtype VarDataType = Odb->dtype(GroupName, VarName);
244  EXPECT(VarDataType == ObsDtype::String);
245 
246  // Check the first and last values of the vector
247  std::string ExpectedFirstValue = varconf[i].getString("first value");
248  std::string ExpectedLastValue = varconf[i].getString("last value");
249  std::vector<std::string> TestVec(Nlocs);
250  Odb->get_db(GroupName, VarName, TestVec);
251  EXPECT(TestVec[0] == ExpectedFirstValue);
252  EXPECT(TestVec[Nlocs-1] == ExpectedLastValue);
253  }
254  }
255  }
256 }
257 
258 // -----------------------------------------------------------------------------
259 
260 void testPutDb() {
261  typedef ObsSpaceTestFixture Test_;
262 
263  std::string VarName("DummyVar");
264 
265  for (std::size_t jj = 0; jj < Test_::size(); ++jj) {
266  // Set up a pointer to the ObsSpace object for convenience
267  ioda::ObsSpace * Odb = &(Test_::obspace(jj));
268 
269  // Create a dummy vector to put into the database
270  // Load up the vector with contrived data, put the vector then
271  // get the vector and see if the contrived data made it through.
272  std::size_t Nlocs = Odb->nlocs();
273  std::vector<double> TestVec(Nlocs);
274  std::vector<double> ExpectedVec(Nlocs);
275 
276  for (std::size_t i = 0; i < Nlocs; ++i) {
277  ExpectedVec[i] = static_cast<double>(i);
278  }
279 
280  // Put the vector into the database. Then read the vector back from the database
281  // and compare to the original
282  Odb->put_db("MetaData", VarName, ExpectedVec);
283  Odb->get_db("MetaData", VarName, TestVec);
284 
285  bool VecMatch = true;
286  for (std::size_t i = 0; i < Nlocs; ++i) {
287  VecMatch = VecMatch && (static_cast<int>(ExpectedVec[i]) == static_cast<int>(TestVec[i]));
288  }
289 
290  EXPECT(VecMatch);
291  }
292 }
293 
294 // -----------------------------------------------------------------------------
295 
297  typedef ObsSpaceTestFixture Test_;
298 
299  std::string VarName("DummyVar");
300 
301  for (std::size_t jj = 0; jj < Test_::size(); ++jj) {
302  // Set up a pointer to the ObsSpace object for convenience
303  ioda::ObsSpace * Odb = &(Test_::obspace(jj));
304 
305  // Create a dummy vector to put into the database
306  // All rows read from the input file should be read only.
307  // All rows added since the read of the input file should be writeable.
308  std::size_t Nlocs = Odb->nlocs();
309  std::vector<double> TestVec(Nlocs);
310  std::vector<double> ExpectedVec(Nlocs);
311 
312  for (std::size_t i = 0; i < Nlocs; ++i) {
313  ExpectedVec[i] = static_cast<double>(i);
314  }
315 
316  // Put the vector into the database. Then read the vector back from the database
317  // and compare to the original
318  Odb->put_db("TestGroup", VarName, ExpectedVec);
319  Odb->get_db("TestGroup", VarName, TestVec);
320 
321  bool VecMatch = true;
322  for (std::size_t i = 0; i < Nlocs; ++i) {
323  VecMatch = VecMatch && (static_cast<int>(ExpectedVec[i]) == static_cast<int>(TestVec[i]));
324  }
325  EXPECT(VecMatch);
326 
327  // Now update the vector with the original multiplied by 2.
328  for (std::size_t i = 0; i < Nlocs; ++i) {
329  ExpectedVec[i] = ExpectedVec[i] * 2;
330  }
331 
332  Odb->put_db("TestGroup", VarName, ExpectedVec);
333  Odb->get_db("TestGroup", VarName, TestVec);
334 
335  VecMatch = true;
336  for (std::size_t i = 0; i < Nlocs; ++i) {
337  VecMatch = VecMatch && (static_cast<int>(ExpectedVec[i]) == static_cast<int>(TestVec[i]));
338  }
339  EXPECT(VecMatch);
340  }
341 }
342 
343 // -----------------------------------------------------------------------------
344 
346  typedef ObsSpaceTestFixture Test_;
347 
348  for (std::size_t jj = 0; jj < Test_::size(); ++jj) {
349  // Set up a pointer to the ObsSpace object for convenience
350  ioda::ObsSpace * Odb = &(Test_::obspace(jj));
351 
352  // Create a dummy array to put into the database
353  // Load up the array with contrived data, put the array then
354  // get the array and see if the contrived data made it through.
355  // If Nchans comes back equal to zero, it means that this obs space does not
356  // have an nchans dimension. In this case, this test is reduced to testing
357  // a 1D vector.
358  std::size_t Nlocs = Odb->nlocs();
359  std::size_t Nchans = Odb->nchans();
360 
361  std::vector<int> TestValues;
362  std::vector<int> ExpectedValues;
363  std::vector<std::string> dimList;
364 
365  int numElements = Nlocs;
366  dimList.push_back(Odb->get_dim_name(ObsDimensionId::Nlocs));
367  if (Nchans > 0) {
368  numElements *= Nchans;
369  dimList.push_back(Odb->get_dim_name(ObsDimensionId::Nchans));
370  }
371 
372  // Load up the expected values with numbers 0..n-1.
373  TestValues.resize(numElements);
374  ExpectedValues.resize(numElements);
375  int testValue = 0;
376  for (std::size_t i = 0; i < numElements; ++i) {
377  ExpectedValues[i] = testValue;
378  testValue++;
379  }
380 
381  // Put the data into the ObsSpace, then get the data back from the ObsSpace and
382  // compare to the original.
383  Odb->put_db("MultiDimData", "DummyVar", ExpectedValues, dimList);
384  Odb->get_db("MultiDimData", "DummyVar", TestValues, {} /*select all channels*/);
385  EXPECT(TestValues == ExpectedValues);
386 
387  const int numOddChannels = Nchans/2;
388  if (numOddChannels > 0) {
389  // Test retrieval of only the odd channels.
390 
391  const std::vector<int>& channels = Odb->obsvariables().channels();
392  ASSERT(channels.size() == Nchans);
393 
394  std::vector<int> chanSelect;
395  for (int i = 0; i < numOddChannels; ++i) {
396  const std::size_t channelIndex = 1 + 2 * i;
397  chanSelect.push_back(channels[channelIndex]);
398  }
399 
400  ExpectedValues.clear();
401  for (std::size_t loc = 0; loc < Nlocs; ++loc) {
402  for (int i = 0; i < numOddChannels; ++i) {
403  const std::size_t channelIndex = 1 + 2 * i;
404  ExpectedValues.push_back(loc * Nchans + channelIndex);
405  }
406  }
407 
408  Odb->get_db("MultiDimData", "DummyVar", TestValues, chanSelect);
409  EXPECT_EQUAL(TestValues, ExpectedValues);
410  }
411 
412  if (numOddChannels > 0) {
413  // Test retrieval of a single channel using the old syntax
414  // (variable name with a channel suffix)
415 
416  const std::vector<int>& channels = Odb->obsvariables().channels();
417  const int channelIndex = 1;
418  const int channelNumber = channels[channelIndex];
419 
420  ExpectedValues.clear();
421  for (std::size_t loc = 0; loc < Nlocs; ++loc)
422  ExpectedValues.push_back(loc * Nchans + channelIndex);
423 
424  Odb->get_db("MultiDimData", "DummyVar_" + std::to_string(channelNumber), TestValues);
425  EXPECT_EQUAL(TestValues, ExpectedValues);
426  }
427  }
428 }
429 
430 // -----------------------------------------------------------------------------
431 
432 void testCleanup() {
433  // This test removes the obsspaces and ensures that they evict their contents
434  // to disk successfully.
435  typedef ObsSpaceTestFixture Test_;
436 
437  Test_::cleanup();
438 }
439 
440 // -----------------------------------------------------------------------------
441 
442 class ObsSpace : public oops::Test {
443  public:
444  ObsSpace() {}
445  virtual ~ObsSpace() {}
446 
447  private:
448  std::string testid() const override {return "test::ObsSpace<ioda::IodaTrait>";}
449 
450  void register_tests() const override {
451  std::vector<eckit::testing::Test>& ts = eckit::testing::specification();
452 
453  ts.emplace_back(CASE("ioda/ObsSpace/testConstructor")
454  { testConstructor(); });
455  ts.emplace_back(CASE("ioda/ObsSpace/testGetDb")
456  { testGetDb(); });
457  ts.emplace_back(CASE("ioda/ObsSpace/testPutDb")
458  { testPutDb(); });
459  ts.emplace_back(CASE("ioda/ObsSpace/testWriteableGroup")
460  { testWriteableGroup(); });
461  ts.emplace_back(CASE("ioda/ObsSpace/testMultiDimTransfer")
462  { testMultiDimTransfer(); });
463  ts.emplace_back(CASE("ioda/ObsSpace/testCleanup")
464  { testCleanup(); });
465  }
466 
467  void clear() const override {}
468 };
469 
470 // -----------------------------------------------------------------------------
471 
472 } // namespace test
473 } // namespace ioda
474 
475 #endif // TEST_IODA_OBSSPACE_H_
Observation data class for IODA.
Definition: src/ObsSpace.h:116
ObsDtype dtype(const std::string &group, const std::string &name) const
return data type for group/variable
Definition: ObsSpace.cc:240
void put_db(const std::string &group, const std::string &name, const std::vector< int > &vdata, const std::vector< std::string > &dimList={ "nlocs" })
transfer data from vdata to the obs container
Definition: ObsSpace.cc:306
size_t nchans() const
return the number of channels in the container. If this is not a radiance obs type,...
Definition: src/ObsSpace.h:180
std::string get_dim_name(const ObsDimensionId dimId) const
return the standard dimension name for the given dimension id
Definition: src/ObsSpace.h:192
std::shared_ptr< const Distribution > distribution() const
return MPI distribution object
Definition: src/ObsSpace.h:335
size_t nlocs() const
return the number of locations in the obs space. Note that nlocs may be smaller than global unique nl...
Definition: src/ObsSpace.h:176
void get_db(const std::string &group, const std::string &name, std::vector< int > &vdata, const std::vector< int > &chanSelect={ }) const
transfer data from the obs container to vdata
Definition: ObsSpace.cc:270
const oops::Variables & obsvariables() const
return oops variables object (simulated variables)
Definition: src/ObsSpace.h:332
std::string testid() const override
void register_tests() const override
void clear() const override
std::vector< boost::shared_ptr< ioda::ObsSpace > > ospaces_
static ioda::ObsSpace & obspace(const std::size_t ii)
static ObsSpaceTestFixture & getInstance()
void testWriteableGroup()
void testMultiDimTransfer()
CASE("Derived variable and unit conversion methods")
double dotProduct(const Distribution &dist, std::size_t numVariables, const std::vector< double > &v1, const std::vector< double > &v2)