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