UFO
test/ufo/DataExtractor.h
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2021 Met Office UK
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_DATAEXTRACTOR_H_
9 #define TEST_UFO_DATAEXTRACTOR_H_
10 
12 
13 #include <iomanip>
14 #include <memory>
15 #include <set>
16 #include <string>
17 #include <vector>
18 
19 #include "eckit/testing/Test.h"
20 #include "oops/runs/Test.h"
21 #include "oops/util/Expect.h"
22 #include "oops/util/FloatCompare.h"
23 
24 namespace ufo {
25 namespace test {
26 
27 float missing = util::missingValue(missing);
28 
29 
30 template <typename T, typename R>
31 float run_basic(const T obVal0, const R obVal1, const std::vector<T> &varValues0,
32  const std::vector<R> &varValues1) {
33  const int dimIndex0 = 0;
34  const int dimIndex1 = 1;
35  const std::string &varName0 = "var0";
36  const std::string &varName1 = "var1";
37  const std::array<ConstrainedRange, 3> ranges {
38  ConstrainedRange(varValues0.size()),
39  ConstrainedRange(varValues1.size()),
40  ConstrainedRange(1)};
41  assert(varValues0.size() == 5);
42  assert(varValues1.size() == 3);
43 
44  boost::multi_array<float, 3> interpolatedArray(boost::extents[5][3][1]);
45  std::vector<float> tmp = {1, 2, 3, 4, 5,
46  6, 7, 8, 9, 10,
47  11, 12, 13, 14, 15};
48  size_t ind = 0;
49  for (int j=0; j < interpolatedArray.shape()[1]; j++) {
50  for (int i=0; i < interpolatedArray.shape()[0]; i++) {
51  interpolatedArray[i][j][0] = tmp[ind];
52  ind++;
53  }
54  }
55  auto array = get2DSlice(interpolatedArray, dimIndex0, dimIndex1, ranges);
56  return bilinearInterpolation(varName0, varValues0, obVal0, ranges[dimIndex0],
57  varName1, varValues1, obVal1, ranges[dimIndex1],
58  array);
59 }
60 
61 
62 // For int/float calls
63 template <typename T, typename R>
64 float run_basic(const T obVal0, const R obVal1) {
65  const std::vector<T> varValues0 {2, 4, 6, 8, 10};
66  const std::vector<R> varValues1 {2, 4, 6};
67  return run_basic(obVal0, obVal1, varValues0, varValues1);
68 }
69 
70 
71 CASE("ufo/DataExtractor/bilinearinterp/float_linear") {
72  // Effectively becomes linear interpolation along dim1.
73  const float res = run_basic(4.0f, 4.2f);
74  EXPECT(oops::is_close_absolute(res, 7.5f, 1e-5f, 0,
75  oops::TestVerbosity::LOG_SUCCESS_AND_FAILURE));
76 }
77 
78 
79 CASE("ufo/DataExtractor/bilinearinterp/float_linear_at_lower_boundary_dim0") {
80  // Check handling where our point is located on the lower boundary.
81  const float res = run_basic(2, 4);
82  EXPECT(oops::is_close_absolute(res, 6.0f, 1e-5f, 0,
83  oops::TestVerbosity::LOG_SUCCESS_AND_FAILURE));
84 }
85 
86 
87 CASE("ufo/DataExtractor/bilinearinterp/float_linear_at_lower_boundary_dim1") {
88  // Check handling where our point is located on the lower boundary.
89  const float res = run_basic(4, 2);
90  EXPECT(oops::is_close_absolute(res, 2.0f, 1e-5f, 0,
91  oops::TestVerbosity::LOG_SUCCESS_AND_FAILURE));
92 }
93 
94 
95 CASE("ufo/DataExtractor/bilinearinterp/float_blinear") {
96  // Simple bilinear interpolation.
97  const float res = run_basic(4.2, 4.2);
98  EXPECT(oops::is_close_absolute(res, 7.6f, 1e-5f, 0,
99  oops::TestVerbosity::LOG_SUCCESS_AND_FAILURE));
100 }
101 
102 
103 CASE("ufo/DataExtractor/bilinearinterp/extrapolation_lower_bound_dim0") {
104  // Lower bound extrapolation dim0
105  const float res = run_basic(0, 4.2);
106  EXPECT_EQUAL(res, missing);
107 }
108 
109 
110 CASE("ufo/DataExtractor/bilinearinterp/extrapolation_lower_bound_dim1") {
111  // Lower bound extrapolation dim1
112  const float res = run_basic(4.2, 0.0);
113  EXPECT_EQUAL(res, missing);
114 }
115 
116 
117 CASE("ufo/DataExtractor/bilinearinterp/extrapolation_upper_bound_dim0") {
118  // Upper bound extrapolation dim0
119  const float res = run_basic(20.0, 4.2);
120  EXPECT_EQUAL(res, missing);
121 }
122 
123 
124 CASE("ufo/DataExtractor/bilinearinterp/extrapolation_upper_bound_dim1") {
125  // Upper bound extrapolation dim1
126  const float res = run_basic(4.2, 20);
127  EXPECT_EQUAL(res, missing);
128 }
129 
130 
131 CASE("ufo/DataExtractor/bilinearinterp/int_int_dtype") {
132  const float res = run_basic(3, 3);
133  EXPECT(oops::is_close_absolute(res, 4.0f, 1e-5f, 0,
134  oops::TestVerbosity::LOG_SUCCESS_AND_FAILURE));
135 }
136 
137 
138 CASE("ufo/DataExtractor/bilinearinterp/int_float_dtype") {
139  const float res = run_basic(3, 3.0);
140  EXPECT(oops::is_close_absolute(res, 4.0f, 1e-5f, 0,
141  oops::TestVerbosity::LOG_SUCCESS_AND_FAILURE));
142 }
143 
144 
145 CASE("ufo/DataExtractor/bilinearinterp/float_int_dtype") {
146  const float res = run_basic(3.0, 3);
147  EXPECT(oops::is_close_absolute(res, 4.0f, 1e-5f, 0,
148  oops::TestVerbosity::LOG_SUCCESS_AND_FAILURE));
149 }
150 
151 
152 CASE("ufo/DataExtractor/bilinearinterp/string_dtype") {
153  const std::vector<std::string> strVarValues0 {"2", "4", "6", "8", "10"};
154  const std::vector<std::string> strVarValues1 {"2", "4", "6"};
155  const std::vector<float> floatVarValues0 {2, 4, 6, 8, 10};
156  const std::vector<float> floatVarValues1 {2, 4, 6};
157  const std::string msg = "Bilinear interpolation cannot be performed along coordinate axes "
158  "indexed by string variables such as ";
159  EXPECT_THROWS_MSG(run_basic(4.2f, std::string("4.2"), floatVarValues0, strVarValues1),
160  (msg + "var1.").c_str());
161  EXPECT_THROWS_MSG(run_basic(std::string("4.2"), 4.2f, strVarValues0, floatVarValues1),
162  (msg + "var0.").c_str());
163  EXPECT_THROWS_MSG(run_basic(std::string("4.2"), std::string("4.2"),
164  strVarValues0, strVarValues1),
165  (msg + "var0 or var1.").c_str());
166 }
167 
168 
169 float run_missing(const float obVal0, const float obVal1, const std::vector<float> data) {
170  const std::vector<float> varValues0 {2, 4, 6, 8, 10};
171  const std::vector<float> varValues1 {2, 4, 6};
172  const std::array<ConstrainedRange, 3> ranges {
173  ConstrainedRange(varValues0.size()),
174  ConstrainedRange(varValues1.size()),
175  ConstrainedRange(1)};
176 
177  boost::multi_array<float, 3> interpolatedArray(boost::extents[5][3][1]);
178  size_t ind = 0;
179  for (int j=0; j < interpolatedArray.shape()[1]; j++) {
180  for (int i=0; i < interpolatedArray.shape()[0]; i++) {
181  interpolatedArray[i][j][0] = data[ind];
182  ind++;
183  }
184  }
185  auto array = get2DSlice(interpolatedArray, 0, 1, ranges);
186  return bilinearInterpolation("var0", varValues0, obVal0, ranges[0],
187  "var1", varValues1, obVal1, ranges[1],
188  array);
189 }
190 
191 
192 CASE("ufo/DataExtractor/bilinearinterp/one_missing") {
193  // If one missing, pick closes of non missing neighbours.
194  const std::vector<float> data = {missing, 2, 3, 4, 5,
195  6, 7, 8, 9, 10,
196  11, 12, 13, 14, 15};
197  // Pick closes non-missing neighbour.
198  float res = run_missing(3.1, 2.5, data);
199  EXPECT(oops::is_close_absolute(res, 2.0f, 1e-5f, 0,
200  oops::TestVerbosity::LOG_SUCCESS_AND_FAILURE));
201 
202  // Different neighbour is closest.
203  res = run_missing(2.5, 3.1, data);
204  EXPECT(oops::is_close_absolute(res, 6.0f, 1e-5f, 0,
205  oops::TestVerbosity::LOG_SUCCESS_AND_FAILURE));
206 
207  // Avoid missing closest value.
208  res = run_missing(2.5, 2.1, data);
209  EXPECT(oops::is_close_absolute(res, 2.0f, 1e-5f, 0,
210  oops::TestVerbosity::LOG_SUCCESS_AND_FAILURE));
211 }
212 
213 
214 CASE("ufo/DataExtractor/bilinearinterp/all_missing") {
215  // If one missing, pick closest of non missing neighbours.
216  const std::vector<float> data = {missing, missing, 3, 4, 5,
217  missing, missing, 8, 9, 10,
218  11, 12, 13, 14, 15};
219  const float res = run_missing(3.1, 2.5, data);
220  EXPECT_EQUAL(res, missing);
221 }
222 
223 
224 float run_range_constrained(const float obVal0, const float obVal1,
225  const std::array<ConstrainedRange, 3> &ranges) {
226  // Coordinates containing values that would change the answer if not excluded
227  // via the "range" specified.
228  const std::vector<float> varValues0 {2, 4, 2, 4, 6};
229  const std::vector<float> varValues1 {3, 2, 4};
230 
231  const std::vector<float> data = {1, 6, 11,
232  2, 7, 12,
233  3, 8, 13,
234  4, 9, 14,
235  5, 10, 15};
236  boost::multi_array<float, 3> interpolatedArray(boost::extents[3][5][1]);
237  size_t ind = 0;
238  for (int j=0; j < interpolatedArray.shape()[1]; j++) {
239  for (int i=0; i < interpolatedArray.shape()[0]; i++) {
240  interpolatedArray[i][j][0] = data[ind];
241  ind++;
242  }
243  }
244  auto array = get2DSlice(interpolatedArray, 1, 0, ranges);
245  return bilinearInterpolation("var1", varValues1, obVal1, ranges[0],
246  "var0", varValues0, obVal0, ranges[1],
247  array);
248 }
249 
250 
251 CASE("ufo/DataExtractor/bilinearinterp/range_constrain") {
252  // Let's constrain the range and change the dimension mapping.
253  const float obVal0 = 3.0, obVal1 = 3.0;
254 
256  con0.constrain(2, 5); // range will ignore 1st two.
258  con1.constrain(1, 3); // range will ignore 1st.
260  const std::array<ConstrainedRange, 3> ranges {con1, con0, con2};
261 
262  const float res = run_range_constrained(obVal0, obVal1, ranges);
263  EXPECT(oops::is_close_absolute(res, 11.0f, 1e-5f, 0,
264  oops::TestVerbosity::LOG_SUCCESS_AND_FAILURE));
265 }
266 
267 
268 CASE("ufo/DataExtractor/bilinearinterp/range_constrain_extrapolation") {
269  // Constraining the range, such that, what would otherwise be within bounds is now out of bounds
270  // so returns missing.
271  const float obVal0 = 5.0, obVal1 = 3.0;
272 
274  con0.constrain(2, 4); // range will ignore 1st two and the final element.
276  con1.constrain(1, 3); // range will ignore 1st.
278  const std::array<ConstrainedRange, 3> ranges {con1, con0, con2};
279 
280  const float res = run_range_constrained(obVal0, obVal1, ranges);
281  EXPECT_EQUAL(res, missing);
282 }
283 
284 
285 CASE("ufo/DataExtractor/bilinearinterp/range_constrain_3D_array") {
286  // Constraining the second dimension. array shape: (5, 6, 3); 2D slice: (:, 2, :)
287  const std::vector<float> varValues0 {2, 4, 6, 8, 10};
288  const std::vector<float> varValues1 {2, 4, 6};
289  const int dimIndex0 = 0;
290  const int dimIndex1 = 2;
291  const std::string &varName0 = "var0";
292  const std::string &varName1 = "var1";
293  std::array<ConstrainedRange, 3> ranges {
294  ConstrainedRange(varValues0.size()),
295  ConstrainedRange(6),
296  ConstrainedRange(varValues1.size())};
297  ranges[1].constrain(2, 3);
298 
299  boost::multi_array<float, 3> interpolatedArray(boost::extents[5][6][3]);
300  const std::vector<float> tmp = {1, 2, 3, 4, 5,
301  6, 7, 8, 9, 10,
302  11, 12, 13, 14, 15};
303  size_t ind = 0;
304  for (int j=0; j < interpolatedArray.shape()[2]; j++) {
305  for (int i=0; i < interpolatedArray.shape()[0]; i++) {
306  interpolatedArray[i][2][j] = tmp[ind];
307  ind++;
308  }
309  }
310  auto array = get2DSlice(interpolatedArray, dimIndex0, dimIndex1, ranges);
311  const float res = bilinearInterpolation(varName0, varValues0, 4.2f, ranges[dimIndex0],
312  varName1, varValues1, 4.2f, ranges[dimIndex1],
313  array);
314  EXPECT(oops::is_close_absolute(res, 7.6f, 1e-5f, 0,
315  oops::TestVerbosity::LOG_SUCCESS_AND_FAILURE));
316 }
317 
318 
319 CASE("ufo/DataExtractor/get2DSlice/not_2d_slice") {
320  // The range specified means that we don't have a single 2D slice of the array
321  // All dimensions here are unconstrained.
322  const std::array<ConstrainedRange, 3> ranges {ConstrainedRange(2), ConstrainedRange(2),
323  ConstrainedRange(2)};
324  boost::multi_array<float, 3> interpolatedArray(boost::extents[2][2][2]);
325 
326  const std::string msg = "Unable to fetch a 2D array slice with the provided constraints.";
327  EXPECT_THROWS_MSG(get2DSlice(interpolatedArray, 0, 1, ranges), msg.c_str());
328 }
329 
330 
331 CASE("ufo/DataExtractor/get1DSlice/not_1d_slice") {
332  // The range specified means that we don't have a single 1D slice of the array
333  // All dimensions here are unconstrained.
334  const std::array<ConstrainedRange, 3> ranges {ConstrainedRange(2), ConstrainedRange(2),
335  ConstrainedRange(2)};
336  boost::multi_array<float, 3> interpolatedArray(boost::extents[2][2][2]);
337 
338  const std::string msg = "Unable to fetch a 1D array slice with the provided constraints.";
339  EXPECT_THROWS_MSG(get1DSlice(interpolatedArray, 0, ranges), msg.c_str());
340 }
341 
342 
343 class DataExtractor : public oops::Test {
344  public:
346 
347  private:
348  std::string testid() const override {return "ufo::test::DataExtractor";}
349 
350  void register_tests() const override {}
351 
352  void clear() const override {}
353 };
354 
355 } // namespace test
356 } // namespace ufo
357 
358 #endif // TEST_UFO_DATAEXTRACTOR_H_
A range of indices.
void constrain(int newBegin, int newEnd)
Constrain the range.
std::string testid() const override
void register_tests() const override
float run_range_constrained(const float obVal0, const float obVal1, const std::array< ConstrainedRange, 3 > &ranges)
CASE("ufo/DataExtractor/bilinearinterp/float_linear")
float run_missing(const float obVal0, const float obVal1, const std::vector< float > data)
float run_basic(const T obVal0, const R obVal1, const std::vector< T > &varValues0, const std::vector< R > &varValues1)
Definition: RunCRTM.h:27
float bilinearInterpolation(const std::string &varName0, const std::vector< T > &varValues0, const T &obVal0, const ConstrainedRange &range0, const std::string &varName1, const std::vector< R > &varValues1, const R &obVal1, const ConstrainedRange &range1, const DataExtractorPayload< float >::const_array_view< 2 >::type &interpolatedArray)
Perform bilinear interpolation at 'location' at location obVal0 and obVal1, corresponding to the firs...
const DataExtractorPayload< T >::template const_array_view< 2 >::type get2DSlice(const DataExtractorPayload< T > &array, const size_t dimIndex0, const size_t dimIndex1, const std::array< ConstrainedRange, 3 > &ranges)
Fetch a 2D sliced view of a boost multi_array object.
const DataExtractorPayload< T >::template const_array_view< 1 >::type get1DSlice(const DataExtractorPayload< T > &array, const size_t dimIndex, const std::array< ConstrainedRange, 3 > &ranges)
Fetch a 1D sliced view of a boost multi_array object.