IODA Bundle
ExtendedObsSpace.h
Go to the documentation of this file.
1 /*
2  * (C) Crown copyright 2021, Met Office
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_EXTENDEDOBSSPACE_H_
9 #define TEST_IODA_EXTENDEDOBSSPACE_H_
10 
11 #include <algorithm>
12 #include <sstream>
13 #include <string>
14 #include <vector>
15 
16 #define ECKIT_TESTING_SELF_REGISTER_CASES 0
17 
18 #include "eckit/config/LocalConfiguration.h"
19 #include "eckit/testing/Test.h"
20 
21 #include "oops/mpi/mpi.h"
22 #include "oops/runs/Test.h"
23 #include "oops/test/TestEnvironment.h"
24 #include "oops/util/Expect.h"
25 
26 #include "ioda/IodaTrait.h"
27 #include "ioda/ObsSpace.h"
28 
29 namespace ioda {
30 
31 namespace test {
32 
33 void testExtendedObsSpace(const eckit::LocalConfiguration &conf) {
34  // Produce and configure ObsSpace object
35  util::DateTime bgn(conf.getString("window begin"));
36  util::DateTime end(conf.getString("window end"));
37  const eckit::LocalConfiguration obsSpaceConf(conf, "obs space");
39  obsParams.validateAndDeserialize(obsSpaceConf);
40 
41  // Instantiate ObsSpace, allowing for exceptions to be thrown.
42  const bool expectThrows = conf.getBool("expectThrows", false);
43  if (expectThrows) {
44  EXPECT_THROWS(ioda::ObsSpace obsDataThrow(obsParams,
46  bgn, end, oops::mpi::myself()));
47  return;
48  }
49  ioda::ObsSpace obsdata(obsParams, oops::mpi::world(), bgn, end, oops::mpi::myself());
50 
51  // This test only works for grouped data.
52  if (obsdata.obs_group_vars().empty())
53  throw eckit::BadValue("Must set 'group variables' configuration option", Here());
54  // This test only works if the correct ObsSpace extension options have been supplied.
55  if (!obsSpaceConf.has("extension"))
56  throw eckit::BadValue("Must set 'extension' configuration option", Here());
57  // Number of levels per record in the extended ObsSpace.
58  const int nlevs = obsSpaceConf.getInt("extension.average profiles onto model levels", 0);
59  // The extension is not performed if the number of levels is less than or equal to zero.
60  if (nlevs <= 0) return;
61 
62  const int MPIsize = obsdata.comm().size();
63  const int MPIrank = obsdata.comm().rank();
64 
65  // Compare number of locations with expected value.
66  const int nlocs = static_cast<int>(obsdata.nlocs());
67  std::stringstream expectednumber;
68  expectednumber << "expected nlocs (" << MPIsize << " PE, rank " << MPIrank << ")";
69  const int nlocs_expected = conf.getInt(expectednumber.str());
70  EXPECT_EQUAL(nlocs, nlocs_expected);
71 
72  // Compare global number of locations with expected value.
73  const int gnlocs = static_cast<int>(obsdata.globalNumLocs());
74  expectednumber.str("");
75  expectednumber << "expected gnlocs (" << MPIsize << " PE, rank " << MPIrank << ")";
76  const int gnlocs_expected = conf.getInt(expectednumber.str());
77  EXPECT_EQUAL(gnlocs, gnlocs_expected);
78 
79  // Compare number of records with expected value.
80  const int nrecs = static_cast<int>(obsdata.nrecs());
81  expectednumber.str("");
82  expectednumber << "expected nrecs (" << MPIsize << " PE, rank " << MPIrank << ")";
83  const int nrecs_expected = conf.getInt(expectednumber.str());
84  EXPECT_EQUAL(nrecs, nrecs_expected);
85 
86  // Given the extended records have nlevs entries each,
87  // calculate the corresponding index at which extended_obs_space switches from 0 to 1.
88  std::vector <int> extended_obs_space(nlocs);
89  obsdata.get_db("MetaData", "extended_obs_space", extended_obs_space);
90  const size_t extendedObsSpaceStart =
91  std::find(extended_obs_space.begin(),
92  extended_obs_space.end(), 1) - extended_obs_space.begin();
93  // Check the index of the start of the extended ObsSpace is
94  // a multiple of nlevs from the final index.
95  EXPECT_EQUAL((nlocs - extendedObsSpaceStart) % nlevs, 0);
96  // Check the values of extended_obs_space.
97  for (int iloc = 0; iloc < extendedObsSpaceStart; ++iloc)
98  EXPECT_EQUAL(extended_obs_space[iloc], 0);
99  for (int iloc = extendedObsSpaceStart; iloc < nlocs; ++iloc)
100  EXPECT_EQUAL(extended_obs_space[iloc], 1);
101 
102  // Get all ObsValue and ObsError vectors that will be simulated.
103  // For each vector check that the values in the extended ObsSpace are all missing.
104  const float missingValueFloat = util::missingValue(missingValueFloat);
105  std::vector <float> val(nlocs);
106  std::vector <float> err(nlocs);
107  const oops::Variables& obsvars = obsdata.obsvariables();
108  for (int ivar = 0; ivar < obsvars.size(); ++ivar) {
109  const std::string varname = obsvars[ivar];
110  obsdata.get_db("ObsValue", varname, val);
111  obsdata.get_db("ObsError", varname, err);
112  for (int iloc = extendedObsSpaceStart; iloc < nlocs; ++iloc) {
113  EXPECT_EQUAL(val[iloc], missingValueFloat);
114  EXPECT_EQUAL(err[iloc], missingValueFloat);
115  }
116  }
117 
118  // If the variable station_id@MetaData is present,
119  // check that its values in the extended ObsSpace are all missing.
120  if (obsdata.has("MetaData", "station_id")) {
121  const std::string missingValueString = util::missingValue(missingValueString);
122  std::vector <std::string> statid(nlocs);
123  obsdata.get_db("MetaData", "station_id", statid);
124  for (int iloc = extendedObsSpaceStart; iloc < nlocs; ++iloc)
125  EXPECT_EQUAL(statid[iloc], missingValueString);
126  }
127 
128  // Check the values of any variables that have been filled with non-missing
129  // values in the extended section.
130  // List of variables to check.
131  const std::vector <std::string> extendedVarsToCheck =
132  {"latitude", "longitude", "air_pressure"};
133  // List of variables that should be filled with non-missing values.
134  const std::vector <std::string> &nonMissingExtendedVars =
135  obsSpaceConf.getStringVector("extension.variables filled with non-missing values",
136  {"latitude", "longitude", "datetime",
137  "air_pressure", "air_pressure_levels"});
138  std::vector <float> val_extended(nlocs);
139  for (auto &varname : extendedVarsToCheck) {
140  // Only variables that are present in the ObsSpace are checked.
141  if (obsdata.has("MetaData", varname)) {
142  const bool filledWithNonMissing =
143  std::find(nonMissingExtendedVars.begin(), nonMissingExtendedVars.end(), varname) !=
144  nonMissingExtendedVars.end();
145  obsdata.get_db("MetaData", varname, val_extended);
146  if (filledWithNonMissing) {
147  for (int iloc = extendedObsSpaceStart; iloc < nlocs; ++iloc)
148  EXPECT(val_extended[iloc] != missingValueFloat);
149  } else {
150  for (int iloc = extendedObsSpaceStart; iloc < nlocs; ++iloc)
151  EXPECT_EQUAL(val_extended[iloc], missingValueFloat);
152  }
153  }
154  }
155 
156  // Compare record numbers on this processor.
157  // There should be an even number of records; the second half should have indices shifted
158  // by a constant offset with respect to the first half. This offset should be equal to the
159  // original number of records.
160  const std::vector<std::size_t> recidx_all_recnums = obsdata.recidx_all_recnums();
161  // Determine the original record numbers by dividing the global number of records by two.
162  EXPECT_EQUAL(nrecs % 2, 0);
163  std::size_t nrecs_original = nrecs / 2;
164  const std::vector<std::size_t> original_recnums(recidx_all_recnums.begin(),
165  recidx_all_recnums.begin() + nrecs / 2);
166  const std::vector<std::size_t> extended_recnums(recidx_all_recnums.begin() + nrecs / 2,
167  recidx_all_recnums.end());
168  std::size_t gnrecs_original = original_recnums.empty() ? 0 : (original_recnums.back() + 1);
169  obsdata.distribution()->max(gnrecs_original);
170 
171  std::vector<std::size_t> extended_recnums_expected;
172  for (std::size_t i : original_recnums)
173  extended_recnums_expected.push_back(i + gnrecs_original);
174  EXPECT_EQUAL(extended_recnums, extended_recnums_expected);
175 
176  // Compare indices across all processors.
177  // Gather all indices, sort them, and produce a vector of unique indices.
178  std::vector <size_t> index_processors = obsdata.index();
179  obsdata.distribution()->allGatherv(index_processors);
180  std::sort(index_processors.begin(), index_processors.end());
181  auto it_unique = std::unique(index_processors.begin(), index_processors.end());
182  index_processors.erase(it_unique, index_processors.end());
183  // Produce expected indices.
184  std::vector <size_t> index_processors_expected(index_processors.size());
185  std::iota(index_processors_expected.begin(), index_processors_expected.end(), 0);
186  // Compare actual and expected indices.
187  EXPECT_EQUAL(index_processors, index_processors_expected);
188 
189  obsdata.save();
190 }
191 
192 class ExtendedObsSpace : public oops::Test {
193  private:
194  std::string testid() const override {return "ioda::test::ExtendedObsSpace";}
195 
196  void register_tests() const override {
197  std::vector<eckit::testing::Test>& ts = eckit::testing::specification();
198 
199  const eckit::LocalConfiguration conf(::test::TestEnvironment::config());
200  for (const std::string & testCaseName : conf.keys())
201  {
202  const eckit::LocalConfiguration testCaseConf(::test::TestEnvironment::config(), testCaseName);
203  ts.emplace_back(CASE("ioda/ExtendedObsSpace/" + testCaseName, testCaseConf)
204  {
205  testExtendedObsSpace(testCaseConf);
206  });
207  }
208  }
209 
210  void clear() const override {}
211 };
212 
213 } // namespace test
214 } // namespace ioda
215 
216 #endif // TEST_IODA_EXTENDEDOBSSPACE_H_
void clear() const override
std::string testid() const override
void register_tests() const override
static const eckit::Configuration & config()
dictionary obsvars
void testExtendedObsSpace(const eckit::LocalConfiguration &conf)
CASE("Derived variable, unit conversion, and exception checking methods")
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