UFO
test/ufo/GeoVaLs.h
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2019 UK 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_UFO_GEOVALS_H_
9 #define TEST_UFO_GEOVALS_H_
10 
11 #include <cmath>
12 #include <iomanip>
13 #include <iostream>
14 #include <memory>
15 #include <string>
16 #include <vector>
17 
18 #define ECKIT_TESTING_SELF_REGISTER_CASES 0
19 
20 #include "eckit/config/LocalConfiguration.h"
21 #include "eckit/testing/Test.h"
22 #include "ioda/ObsSpace.h"
23 #include "oops/mpi/mpi.h"
24 #include "oops/runs/Test.h"
25 #include "oops/util/FloatCompare.h"
26 #include "oops/util/Logger.h"
27 #include "test/TestEnvironment.h"
28 #include "ufo/GeoVaLs.h"
29 #include "ufo/Locations.h"
30 #include "ufo/ObsOperator.h"
31 
32 namespace ufo {
33 namespace test {
34 
35 // -----------------------------------------------------------------------------
36 
37 void testGeoVaLs() {
38  const eckit::LocalConfiguration conf(::test::TestEnvironment::config());
39  util::DateTime bgn(conf.getString("window begin"));
40  util::DateTime end(conf.getString("window end"));
41 
42  std::vector<eckit::LocalConfiguration> confs;
43  conf.get("geovals test", confs);
44  for (size_t jconf = 0; jconf < confs.size(); ++jconf) {
45 /// Setup ObsSpace
46  const eckit::LocalConfiguration obsconf(confs[jconf], "obs space");
47  ioda::ObsTopLevelParameters obsparams;
48  obsparams.validateAndDeserialize(obsconf);
49  ioda::ObsSpace ospace(obsparams, oops::mpi::world(), bgn, end, oops::mpi::myself());
50 
51 /// Setup GeoVaLs
52  const eckit::LocalConfiguration gconf(confs[jconf], "geovals");
53  const oops::Variables ingeovars(gconf, "state variables");
54  const GeoVaLs gval(gconf, ospace, ingeovars);
55 
56  const double tol = gconf.getDouble("tolerance");
57 
58 /// Check that GeoVaLs default constructor works
59  oops::Log::trace() <<
60  "GeoVaLs default constructor - does not allocate fields" << std::endl;
61  GeoVaLs gv_temp(ospace.distribution(), ingeovars);
62 
63 /// Check that GeoVaLs constructor to create a GeoVaLs with one location works
64  if (gconf.has("one location check")) {
65  oops::Log::trace() << "Check that GeoVaLs constructor for one location works" << std::endl;
66  const eckit::LocalConfiguration gconfone(gconf, "one location check");
67  const std::string var = gconfone.getString("variable");
68  const std::vector<int> ind = gconfone.getIntVector("indices");
69  const std::vector<float> values = gconfone.getFloatVector("values");
70  const float oneloctol = gconfone.getFloat("tolerance");
71 
72  // Loop over each location and test just the lowest level
73  oops::TestVerbosity verbosity = oops::TestVerbosity::LOG_SUCCESS_AND_FAILURE;
74  for (std::size_t i = 0; i < ind.size(); ++i) {
75  GeoVaLs gv_one(gval, ind[i]);
76  std::vector<float> gv_val(1);
77  gv_one.getAtLevel(gv_val, var, 0);
78  EXPECT(oops::is_close_absolute(gv_val[0], values[i], oneloctol, 0, verbosity));
79  }
80  } else {
81  oops::Log::trace() << "Test just the constructor for a one location GeoVaLs" << std::endl;
82  int index = 0;
83  GeoVaLs gv_one(gval, index);
84  }
85 
86  GeoVaLs gv(gval);
87  if (gconf.has("reorderzdir check")) {
88  const eckit::LocalConfiguration gconfchk(gconf, "reorderzdir check");
89  const std::string flipto = gconfchk.getString("direction");
90  std::string flipback = (flipto == "bottom2top") ? "top2bottom" : "bottom2top";
91  std::size_t nobs = ospace.nlocs();
92  gv.reorderzdir("air_pressure_levels", flipto);
93  std::vector<float> gvar(nobs);
94  std::vector<float> gvarref(nobs);
95  float sum;
96  for (size_t i = 0; i < ingeovars.size(); ++i) {
97  size_t nlevs = gv.nlevs(ingeovars[i]);
98  sum = 0;
99  for (size_t k = 0; k < nlevs; ++k) {
100  size_t kk = nlevs - k - 1;
101  gv.getAtLevel(gvar, ingeovars[i], k);
102  gval.getAtLevel(gvarref, ingeovars[i], kk);
103  for (size_t j = 0; j < nobs; ++j) {
104  gvar[j] = gvar[j] - gvarref[j];
105  sum += sum + gvar[j];
106  }
107  }
108  }
109  gv.reorderzdir("air_pressure_levels", flipback);
110  const double tol = gconfchk.getDouble("tolerance");
111  EXPECT(abs(sum) < tol);
112  }
113 
114 /// Check that GeoVaLs merge followed by a split gives back the original geovals
115  oops::Log::trace() <<
116  "GeoVaLs merge followed by a split gives back the original geovals" << std::endl;
117 
118  double dp_gval = gval.dot_product_with(gval);
119  oops::Log::debug()<< "initial gval dot product with itself " << dp_gval << std::endl;
120 
121  gv.zero();
122  gv.merge(gval, gval);
123 
124  double dp_gv_merged = gv.dot_product_with(gv);
125  oops::Log::debug()<< "gv dot product with itself after merge " << dp_gv_merged << std::endl;
126  EXPECT(abs(dp_gv_merged - 2.0 * dp_gval)/dp_gv_merged < tol);
127 
128  GeoVaLs gv1(ospace.distribution(), gv.getVars());
129  GeoVaLs gv2(ospace.distribution(), gv.getVars());
130  gv.split(gv1, gv2);
131 
132  double dp_gv1_split = gv1.dot_product_with(gv1);
133  double dp_gv2_split = gv2.dot_product_with(gv2);
134 
135  oops::Log::debug()<< "gv1 gv2 dot products with itself after split "
136  << dp_gv1_split << " " << dp_gv2_split << std::endl;
137 
138  EXPECT(gv1.rms() == gv2.rms());
139  EXPECT(gv1.rms() == gval.rms());
140  EXPECT(abs(dp_gv1_split - dp_gv2_split)/dp_gv1_split < tol);
141  EXPECT(abs(dp_gv1_split - dp_gval)/dp_gv1_split < tol);
142 
143  oops::Log::trace() <<
144  "GeoVaLs merge followed by a split test succeeded" << std::endl;
145 
146 /// Check that GeoVaLs & operator *= (const std::vector<float>);
147  oops::Log::trace() <<
148  "Check that GeoVaLs & operator *= (const std::vector<float>);" << std::endl;
149 
150  std::size_t nlocs = ospace.nlocs();
151  {
152  std::vector<float> tw(nlocs, 1.0f);
153  gv1 *= tw;
154  EXPECT(gv1.rms() == gval.rms());
155  }
156  {
157  std::vector<float> tw(nlocs, 2.0f);
158  gv1 *= tw;
159  double rms1, rms2;
160  rms1 = gv1.rms();
161  rms2 = 2.0 * gval.rms();
162  oops::Log::debug()<< "rms1, rms2 = " << rms1 << " " << rms2 << std::endl;
163  EXPECT(rms1 == rms2);
164  }
165  oops::Log::trace() <<
166  "GeoVaLs & operator *= (const std::vector<float>); test succeeded" << std::endl;
167  }
168 }
169 
170 /// \brief Tests GeoVaLs::allocate, GeoVals::put, GeoVaLs::get,
171 /// GeoVaLs::putAtLevel, GeoVaLs::getAtLevel,
172 /// GeoVaLs::putAtLocation and GeoVaLs::getAtLocation.
174  const eckit::LocalConfiguration conf(::test::TestEnvironment::config());
175  const eckit::LocalConfiguration testconf(conf, "geovals get test");
176 
177  /// Test 2D variables
178  const std::string var1 = "variable1";
179  const std::string var2 = "variable2";
180  const oops::Variables testvars({var1, var2});
181 
182  /// Setup GeoVaLs that are not filled in or allocated; test that they are not allocated
183  const Locations locs(testconf, oops::mpi::world());
184  GeoVaLs gval(locs, testvars);
185  oops::Log::test() << "GeoVals allocate test: created empty GeoVaLs with " << testvars <<
186  " variables and nlocs=" << gval.nlocs() << std::endl;
187  EXPECT_EQUAL(gval.nlevs(var1), 0);
188  EXPECT_EQUAL(gval.nlevs(var2), 0);
189 
190  /// Allocate only the first variable, and test that it's allocated correctly
191  const oops::Variables testvar1({var1});
192  const int nlevs1 = 10;
193  gval.allocate(nlevs1, testvar1);
194  oops::Log::test() << "Allocated " << var1 << "; nlevs(var1) = " << gval.nlevs(var1) <<
195  "; nlevs(var2) = " << gval.nlevs(var2) << std::endl;
196  EXPECT_EQUAL(gval.nlevs(var1), nlevs1);
197  EXPECT_EQUAL(gval.nlevs(var2), 0);
198 
199  /// Allocate only the second variable (2D variable), test that it's allocated correctly
200  const oops::Variables testvar2({var2});
201  const int nlevs2 = 1;
202  gval.allocate(nlevs2, testvar2);
203  oops::Log::test() << "Allocated " << var2 << "; nlevs(var1) = " << gval.nlevs(var1) <<
204  "; nlevs(var2) = " << gval.nlevs(var2) << std::endl;
205  EXPECT_EQUAL(gval.nlevs(var1), nlevs1);
206  EXPECT_EQUAL(gval.nlevs(var2), nlevs2);
207 
208  /// Set all values for the first level of a 2D variable to an arbitrary number with the
209  /// put method. Then check that the get method returns the expected values in each case.
210  /// Do this for several data types.
211  /// (1) doubles
212  const double fillvalue_double = 3.01234567890123;
213  oops::Log::test() << "Put(double) fill value: " << fillvalue_double << std::endl;
214  const std::vector<double> refvalues_double(gval.nlocs(), fillvalue_double);
215  gval.putAtLevel(refvalues_double, var2, 0);
216  std::vector<double> testvalues_double(gval.nlocs(), 0);
217  gval.get(testvalues_double, var2);
218  oops::Log::test() << "Get(double) result: " << testvalues_double << std::endl;
219  EXPECT_EQUAL(testvalues_double, refvalues_double);
220  /// (2) floats
221  const float fillvalue_float = 4.1f;
222  oops::Log::test() << "Put(float) fill value: " << fillvalue_float << std::endl;
223  const std::vector<float> refvalues_float(gval.nlocs(), fillvalue_float);
224  gval.putAtLevel(refvalues_float, var2, 0);
225  std::vector<float> testvalues_float(gval.nlocs(), 0);
226  gval.get(testvalues_float, var2);
227  oops::Log::test() << "Get(float) result: " << testvalues_float << std::endl;
228  EXPECT_EQUAL(testvalues_float, refvalues_float);
229  /// (3) ints
230  const int fillvalue_int = 5;
231  oops::Log::test() << "Put(int) fill value: " << fillvalue_int << std::endl;
232  const std::vector<int> refvalues_int(gval.nlocs(), fillvalue_int);
233  gval.putAtLevel(refvalues_int, var2, 0);
234  std::vector<int> testvalues_int(gval.nlocs(), 0);
235  gval.get(testvalues_int, var2);
236  oops::Log::test() << "Get(int) result: " << testvalues_int << std::endl;
237  EXPECT_EQUAL(testvalues_int, refvalues_int);
238 
239  /// Check that the getAtLocation method returns what we put in the GeoVaLs.
240  /// The reference GeoVaLs at indices (jlev, jloc) are equal to jlev + jloc.
241  for (size_t jlev = 0; jlev < nlevs1; ++jlev) {
242  std::vector<double> refvalues_loc_double(gval.nlocs());
243  std::iota(refvalues_loc_double.begin(), refvalues_loc_double.end(), jlev);
244  gval.putAtLevel(refvalues_loc_double, var1, jlev);
245  }
246  for (size_t jloc = 0; jloc < gval.nlocs(); ++jloc) {
247  // Get the test vector at this location.
248  std::vector<double> testvalues_loc_double(gval.nlevs(var1));
249  gval.getAtLocation(testvalues_loc_double, var1, jloc);
250  // Recreate reference vector for this location.
251  std::vector<double> refvalues_loc_double(gval.nlevs(var1));
252  std::iota(refvalues_loc_double.begin(), refvalues_loc_double.end(), jloc);
253  // Compare the two vectors.
254  EXPECT_EQUAL(testvalues_loc_double, refvalues_loc_double);
255  // Repeat the test for floats.
256  std::vector<float> testvalues_loc_float(gval.nlevs(var1));
257  gval.getAtLocation(testvalues_loc_float, var1, jloc);
258  std::vector<float> refvalues_loc_float(refvalues_loc_double.begin(),
259  refvalues_loc_double.end());
260  EXPECT_EQUAL(testvalues_loc_float, refvalues_loc_float);
261  // Repeat the test for ints.
262  std::vector<int> testvalues_loc_int(gval.nlevs(var1));
263  gval.getAtLocation(testvalues_loc_int, var1, jloc);
264  std::vector<int> refvalues_loc_int(refvalues_loc_double.begin(),
265  refvalues_loc_double.end());
266  EXPECT_EQUAL(testvalues_loc_int, refvalues_loc_int);
267  }
268 
269  /// Check that the putAtLocation method correctly puts values in the GeoVaLs.
270  /// This is similar to the previous test but the putting and getting routines
271  /// are transposed.
272  /// This is done separately for each data type, using different fill values each time.
273  /// (1) doubles
274  /// The reference GeoVaLs at indices (jlev, jloc) are equal to jlev + jloc.
275  oops::Log::test() << "putAtLoction with doubles" << std::endl;
276  for (size_t jloc = 0; jloc < gval.nlocs(); ++jloc) {
277  std::vector<double> refvalues_double(gval.nlevs(var1));
278  std::iota(refvalues_double.begin(), refvalues_double.end(), jloc);
279  gval.putAtLocation(refvalues_double, var1, jloc);
280  }
281  oops::Log::test() << "testing putAtLoction with doubles" << std::endl;
282  for (size_t jlev = 0; jlev < gval.nlevs(var1); ++jlev) {
283  // Get the test vector on this level.
284  std::vector <double> testvalues_double(gval.nlocs());
285  gval.getAtLevel(testvalues_double, var1, jlev);
286  // Recreate reference vector for this level.
287  std::vector <double> refvalues_double(gval.nlocs());
288  std::iota(refvalues_double.begin(), refvalues_double.end(), jlev);
289  // Compare the two vectors.
290  EXPECT_EQUAL(testvalues_double, refvalues_double);
291  }
292  /// (2) floats
293  /// The reference GeoVaLs at indices (jlev, jloc) are equal to jlev + jloc + 1.
294  oops::Log::test() << "putAtLoction with floats" << std::endl;
295  for (size_t jloc = 0; jloc < gval.nlocs(); ++jloc) {
296  std::vector<float> refvalues_float(gval.nlevs(var1));
297  std::iota(refvalues_float.begin(), refvalues_float.end(), jloc + 1);
298  gval.putAtLocation(refvalues_float, var1, jloc);
299  }
300  oops::Log::test() << "testing putAtLoction with floats" << std::endl;
301  for (size_t jlev = 0; jlev < gval.nlevs(var1); ++jlev) {
302  // Get the test vector on this level.
303  std::vector <float> testvalues_float(gval.nlocs());
304  gval.getAtLevel(testvalues_float, var1, jlev);
305  // Recreate reference vector for this level.
306  std::vector <float> refvalues_float(gval.nlocs());
307  std::iota(refvalues_float.begin(), refvalues_float.end(), jlev + 1);
308  // Compare the two vectors.
309  EXPECT_EQUAL(testvalues_float, refvalues_float);
310  }
311  /// (3) ints
312  /// The reference GeoVaLs at indices (jlev, jloc) are equal to jlev + jloc + 2.
313  oops::Log::test() << "putAtLoction with ints" << std::endl;
314  for (size_t jloc = 0; jloc < gval.nlocs(); ++jloc) {
315  std::vector<int> refvalues_int(gval.nlevs(var1));
316  std::iota(refvalues_int.begin(), refvalues_int.end(), jloc + 2);
317  gval.putAtLocation(refvalues_int, var1, jloc);
318  }
319  oops::Log::test() << "testing putAtLoction with ints" << std::endl;
320  for (size_t jlev = 0; jlev < gval.nlevs(var1); ++jlev) {
321  // Get the test vector on this level.
322  std::vector <int> testvalues_int(gval.nlocs());
323  gval.getAtLevel(testvalues_int, var1, jlev);
324  // Recreate reference vector for this level.
325  std::vector <int> refvalues_int(gval.nlocs());
326  std::iota(refvalues_int.begin(), refvalues_int.end(), jlev + 2);
327  // Compare the two vectors.
328  EXPECT_EQUAL(testvalues_int, refvalues_int);
329  }
330 
331  /// Check code paths that throw exceptions for the getAtLocation method.
332  std::vector<double> testvalues_loc_wrongsize(gval.nlevs(var1) + 1, 0.0);
333  EXPECT_THROWS(gval.getAtLocation(testvalues_loc_wrongsize, var1, 1));
334  std::vector<double> testvalues_loc(gval.nlevs(var1), 0.0);
335  EXPECT_THROWS(gval.getAtLocation(testvalues_loc, var1, -1));
336  EXPECT_THROWS(gval.getAtLocation(testvalues_loc, var1, gval.nlocs()));
337 
338  /// Check code paths that throw exceptions for the putAtLocation method.
339  EXPECT_THROWS(gval.putAtLocation(testvalues_loc_wrongsize, var1, 1));
340  EXPECT_THROWS(gval.putAtLocation(testvalues_loc, var1, -1));
341  EXPECT_THROWS(gval.putAtLocation(testvalues_loc, var1, gval.nlocs()));
342 
343  /// test 3D put and get
344  for (size_t jlev = 0; jlev < nlevs1; ++jlev) {
345  const float fillvalue = 3.0*(jlev+1);
346  const std::vector<double> refvalues(gval.nlocs(), fillvalue);
347  gval.putAtLevel(refvalues, var1, jlev);
348  oops::Log::test() << jlev << " level: put fill value: " << fillvalue << std::endl;
349  std::vector<double> testvalues(gval.nlocs(), 0);
350  gval.getAtLevel(testvalues, var1, jlev);
351  oops::Log::test() << jlev << " level: get result: " << testvalues << std::endl;
352  EXPECT_EQUAL(testvalues, refvalues);
353  }
354 }
355 
356 /// \brief Tests GeoVaLs(const Locations &, const Variables &, const std::vector<size_t> &)
357 /// constructor. Tests that levels get correctly allocated, and that the GeoVaLs are zeroed
358 /// out.
360  const eckit::LocalConfiguration conf(::test::TestEnvironment::config());
361  const eckit::LocalConfiguration testconf(conf, "geovals get test");
362 
363  const std::string var1 = "variable1";
364  const std::string var2 = "variable2";
365  const oops::Variables testvars({var1, var2});
366 
367  /// Setup GeoVaLs that are not filled in or allocated; test that they are not allocated
368  const Locations locs(testconf, oops::mpi::world());
369  const std::vector<size_t> testnlevs({10, 1});
370  GeoVaLs gval(locs, testvars, testnlevs);
371  oops::Log::test() << "Created and allocated GeoVaLs: " << var1 << "; nlevs(var1) = " <<
372  gval.nlevs(var1) << ", " << var2 << "; nlevs(var2) = " <<
373  gval.nlevs(var2) << std::endl;
374  EXPECT_EQUAL(gval.nlevs(var1), 10);
375  EXPECT_EQUAL(gval.nlevs(var2), 1);
376 
377  const std::vector<double> refvalues(gval.nlocs(), 0.0);
378  const std::vector<float> refvalues_float(gval.nlocs(), 0.0);
379  const std::vector<int> refvalues_int(gval.nlocs(), 0);
380 
381  /// check that get method returns zeroes (initial values in GeoVaLs)
382  std::vector<double> testvalues(gval.nlocs(), 1.0);
383  gval.get(testvalues, var2);
384  oops::Log::test() << "Get result: " << testvalues << std::endl;
385  EXPECT_EQUAL(testvalues, refvalues);
386  std::vector<float> testvalues_float(gval.nlocs(), 1.0);
387  gval.get(testvalues_float, var2);
388  oops::Log::test() << "Get(float) result: " << testvalues_float << std::endl;
389  EXPECT_EQUAL(testvalues_float, refvalues_float);
390  std::vector<int> testvalues_int(gval.nlocs(), 1);
391  gval.get(testvalues_int, var2);
392  oops::Log::test() << "Get(int) result: " << testvalues_int << std::endl;
393  EXPECT_EQUAL(testvalues_int, refvalues_int);
394 }
395 
396 
397 // -----------------------------------------------------------------------------
398 
399 class GeoVaLs : public oops::Test {
400  public:
401  GeoVaLs() = default;
402  virtual ~GeoVaLs() = default;
403  private:
404  std::string testid() const override {return "ufo::test::GeoVaLs";}
405 
406  void register_tests() const override {
407  std::vector<eckit::testing::Test>& ts = eckit::testing::specification();
408 
409  ts.emplace_back(CASE("ufo/GeoVaLs/testGeoVaLs")
410  { testGeoVaLs(); });
411  ts.emplace_back(CASE("ufo/GeoVaLs/testGeoVaLsAllocatePutGet")
413  ts.emplace_back(CASE("ufo/GeoVaLs/testGeoVaLsConstructor")
414  { testGeoVaLsConstructor(); });
415  }
416 
417  void clear() const override {}
418 };
419 
420 // ----------;
421 
422 } // namespace test
423 } // namespace ufo
424 
425 #endif // TEST_UFO_GEOVALS_H_
void clear() const override
void register_tests() const override
virtual ~GeoVaLs()=default
std::string testid() const override
void testGeoVaLsConstructor()
Tests GeoVaLs(const Locations &, const Variables &, const std::vector<size_t> &) constructor....
void testGeoVaLsAllocatePutGet()
Tests GeoVaLs::allocate, GeoVals::put, GeoVaLs::get, GeoVaLs::putAtLevel, GeoVaLs::getAtLevel,...
CASE("ufo/DataExtractor/bilinearinterp/float_linear")
void testGeoVaLs()
integer function nlocs(this)
Return the number of observational locations in this Locations object.
Definition: RunCRTM.h:27
util::Duration abs(const util::Duration &duration)