IODA
test_append.cpp
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2020 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 #include <math.h>
9 
10 #include <algorithm>
11 #include <cstdlib>
12 #include <iostream>
13 #include <numeric>
14 #include <typeinfo>
15 
16 #include "Eigen/Dense"
17 #include "ioda/Engines/Factory.h"
18 #include "ioda/Exception.h"
19 #include "ioda/Layout.h"
20 #include "ioda/ObsGroup.h"
21 
22 #include "ioda/testconfig.h"
23 
24 void test_obsgroup_helper_funcs(std::string backendType, std::string fileName,
25  const std::string mappingFile = "") {
26  using namespace ioda;
27 
28  // Create test data
29  const int locations = 40;
30  const int channels = 30;
31  const int locationsX2 = 2 * locations;
32 
33  // Build data that holds 2 chunks (each chunk is locations in size)
34  // to see if can write the first chunk, resize the variable and write
35  // the second chunk.
36 
37  // Set nlocs (size: 2*locations) and nchans (size: channels) coordinate values
38  // nlocs set to 0..nlocs-1, and nchans set to 1..nchans
39  std::vector<int> nLocs(locationsX2);
40  std::iota(nLocs.begin(), nLocs.end(), 0);
41 
42  std::vector<int> nChans(channels);
43  std::iota(nChans.begin(), nChans.end(), 1);
44 
45  Eigen::ArrayXXf myDataExpected(locationsX2, channels);
46  std::vector<float> myLonExpected(locationsX2);
47  std::vector<float> myLatExpected(locationsX2);
48  auto mid_loc = static_cast<float>(locations);
49  auto mid_chan = static_cast<float>(channels) / 2.0f;
50  for (std::size_t i = 0; i < locationsX2; ++i) {
51  myLonExpected[i] = static_cast<float>(i % 8) * 3.0f;
52  myLatExpected[i] = static_cast<float>(i / 8) * 3.0f; // NOLINT(bugprone-integer-division)
53  for (std::size_t j = 0; j < channels; ++j) {
54  float del_i = static_cast<float>(i) - mid_loc;
55  float del_j = static_cast<float>(j) - mid_chan;
56  myDataExpected(i, j) = sqrt(del_i * del_i + del_j * del_j);
57  }
58  }
59 
60  // Split the data into two chunks
61  Eigen::ArrayXXf myDataExpected1(locations, channels);
62  Eigen::ArrayXXf myDataExpected2(locations, channels);
63  myDataExpected1 = myDataExpected.block<locations, channels>(0, 0);
64  myDataExpected2 = myDataExpected.block<locations, channels>(locations, 0);
65 
66  std::vector<float> myLatExpected1(myLatExpected.begin(), myLatExpected.begin() + locations);
67  std::vector<float> myLatExpected2(myLatExpected.begin() + locations, myLatExpected.end());
68  std::vector<float> myLonExpected1(myLonExpected.begin(), myLonExpected.begin() + locations);
69  std::vector<float> myLonExpected2(myLonExpected.begin() + locations, myLonExpected.end());
70  std::vector<int> nLocs1(nLocs.begin(), nLocs.begin() + locations);
71  std::vector<int> nLocs2(nLocs.begin() + locations, nLocs.end());
72 
73  // Create a backend
74  Engines::BackendNames backendName;
76  if (backendType == "file" || backendType == "fileRemapped") {
77  backendName = Engines::BackendNames::Hdf5File;
78 
79  backendParams.fileName = fileName;
82  } else if (backendType == "memory") {
83  backendName = Engines::BackendNames::ObsStore;
84  } else {
85  throw Exception("Unrecognized backend type", ioda_Here())
86  .add("backendType", backendType);
87  }
88  Group backend = constructBackend(backendName, backendParams);
89 
90  // Create an ObsGroup object and attach the backend
91  ObsGroup og;
92  if (backendType != "fileRemapped") {
94  backend, {
95  NewDimensionScale<int>("nlocs", locations, ioda::Unlimited, locations),
96  NewDimensionScale<int>("nchans", channels, channels, channels)
97  });
98  } else if (mappingFile == "") {
100  backend, {
101  NewDimensionScale<int>("nlocs", locations, ioda::Unlimited, locations),
102  NewDimensionScale<int>("nchans", channels, channels, channels)
103  });
104  } else {
106  backend,
107  {
108  NewDimensionScale<int>(
109  "nlocs", locations, ioda::Unlimited, locations),
110  NewDimensionScale<int>(
111  "nchans", channels, channels, channels) },
113  mappingFile));
114  }
115  Variable nlocs_var = og.vars.open("nlocs");
116  nlocs_var.write(nLocs1);
117 
118  Variable nchans_var = og.vars["nchans"];
119  nchans_var.write(nChans);
120 
121  // Set up creation parameters for variables
123  float_params.chunk = true;
124  float_params.compressWithGZIP();
125  float_params.setFillValue<float>(-999);
126 
127  Variable obs_var = og.vars.createWithScales<float>("ObsValue/myObs", {nlocs_var, nchans_var}, float_params);
128 
129  og.vars.createWithScales<float>("MetaData/latitude", {nlocs_var}, float_params);
130  Variable lat_var = og.vars.open("MetaData/latitude");
131 
132  og.vars.createWithScales<float>("MetaData/longitude", {nlocs_var}, float_params);
133  Variable lon_var = og.vars["MetaData/longitude"];
134 
135  // Add attributes to variables
136  obs_var.atts.add<std::string>("coordinates", {"longitude latitude nchans"}, {1})
137  .add<std::string>("long_name", {"obs I made up"}, {1})
138  .add<std::string>("units", {"K"}, {1})
139  .add<float>("valid_range", {0.0, 50.0}, {2});
140  lat_var.atts.add<std::string>("long_name", {"latitude"}, {1})
141  .add<std::string>("units", {"degrees_north"}, {1})
142  .add<float>("valid_range", {-90.0, 90.0}, {2});
143  lon_var.atts.add<std::string>("long_name", {"longitude"}, {1})
144  .add<std::string>("units", {"degrees_east"}, {1})
145  .add<float>("valid_range", {-360.0, 360.0}, {2});
146 
147  // Write data into group variable structure
148  obs_var.writeWithEigenRegular(myDataExpected1);
149  lat_var.write(myLatExpected1);
150  lon_var.write(myLonExpected1);
151 
152  // Append the second data chunk
153  // resize the nlocs variable - do this before writing
154  og.resize({std::pair<ioda::Variable, ioda::Dimensions_t>(nlocs_var, locationsX2)});
155 
156  // 1D vector selection objects
157  std::vector<ioda::Dimensions_t> memStarts(1, 0);
158  std::vector<ioda::Dimensions_t> memCounts(1, locations);
159  std::vector<ioda::Dimensions_t> fileStarts(1, locations);
160  std::vector<ioda::Dimensions_t> fileCounts(1, locations);
161 
162  ioda::Selection memSelect1D;
163  ioda::Selection fileSelect1D;
164  memSelect1D.extent({locations}).select({ioda::SelectionOperator::SET, memStarts, memCounts});
165  fileSelect1D.select({ioda::SelectionOperator::SET, fileStarts, fileCounts});
166 
167  // 2D selection objects
168  memStarts.push_back(0); // borrow the arrays from above
169  memCounts.push_back(channels);
170  fileStarts.push_back(0);
171  fileCounts.push_back(channels);
172 
173  ioda::Selection memSelect2D;
174  ioda::Selection fileSelect2D;
175  memSelect2D.extent({locations, channels}).select({ioda::SelectionOperator::SET, memStarts, memCounts});
176  fileSelect2D.select({ioda::SelectionOperator::SET, fileStarts, fileCounts});
177 
178  // Write the sencond data chunk
179  nlocs_var.write(nLocs2, memSelect1D, fileSelect1D);
180  obs_var.writeWithEigenRegular(myDataExpected2, memSelect2D, fileSelect2D);
181  lat_var.write(myLatExpected2, memSelect1D, fileSelect1D);
182  lon_var.write(myLonExpected2, memSelect1D, fileSelect1D);
183 
184  // Read data back and check values
185  Eigen::ArrayXXf myData(locationsX2, channels);
186  obs_var.readWithEigenRegular(myData);
187  if (!myData.isApprox(myDataExpected)) {
188  throw Exception("Test obs data mismatch", ioda_Here());
189  }
190 
191  std::vector<float> myLats(locationsX2, 0.0);
192  lat_var.read(myLats);
193  for (std::size_t i = 0; i < locationsX2; ++i) {
194  double check = fabs((myLats[i] / myLatExpected[i]) - 1.0);
195  if (check > 1.0e-3) {
196  throw Exception("Test lats mismatch outside tolerence (1e-3)", ioda_Here())
197  .add(" i", i)
198  .add(" myLatExpected[i]", myLatExpected[i])
199  .add(" myLats[i]", myLats[i]);
200  }
201  }
202 
203  std::vector<float> myLons(locationsX2, 0.0);
204  lon_var.read(myLons);
205  for (std::size_t i = 0; i < locationsX2; ++i) {
206  double check = fabs((myLons[i] / myLonExpected[i]) - 1.0);
207  if (check > 1.0e-3) {
208  throw Exception("Test lons mismatch outside tolerence (1e-3)", ioda_Here())
209  .add(" i", i)
210  .add(" myLonExpected[i]", myLonExpected[i])
211  .add(" myLons[i]", myLons[i]);
212  }
213  }
214 
215  // Some more checks
216  Expects(og.open("ObsValue").vars["myObs"].isDimensionScaleAttached(1, og.vars["nchans"]));
217 }
218 
219 int main(int argc, char** argv) {
220  if (argc < 2) {
221  std::cerr << "Need to specify backend type (file, memory)" << std::endl;
222  return 1;
223  }
224  std::string backendType(argv[1]);
225  try {
226  if (backendType == "file") {
227  std::cout << "Testing file backend" << std::endl;
228  test_obsgroup_helper_funcs(backendType, {"ioda-engines_obsgroup_append-file.hdf5"});
229  } else if (backendType == "memory") {
230  std::cout << "Testing memory backend" << std::endl;
231  test_obsgroup_helper_funcs(backendType, {""});
232  } else if (backendType == "fileRemapped") {
233  test_obsgroup_helper_funcs(backendType, {"ioda-engines_obsgroup_append-remapped-file.hdf5"});
234  // Layout_ObsGroup_ODB runs identically to Layout_ObsGroup if a yaml file is not provided
235  std::cout << "Testing ODB Data Layout Policy with explicit mapping file" << std::endl;
236  std::string mappingFile = std::string(IODA_ENGINES_TEST_SOURCE_DIR)
237  + "/obsgroup/odb_default_name_map.yaml";
238  test_obsgroup_helper_funcs(backendType, {"append-remapped.hdf5"}, mappingFile);
239  } else {
240  throw ioda::Exception("Unrecognized backend type:", ioda_Here())
241  .add("Backend type", backendType);
242  }
243  } catch (const std::exception& e) {
245  return 1;
246  }
247  return 0;
248 }
IODA's error system.
Definitions for setting up backends with file and memory I/O.
Contains definitions for how data are arranged in ioda internally.
Interfaces for ioda::ObsGroup and related classes.
The ioda exception class.
Definition: Exception.h:54
Exception & add(const std::string &key, const T value)
Add a key-value pair to the error message.
Definition: Exception.h:75
Groups are a new implementation of ObsSpaces.
Definition: Group.h:159
An ObsGroup is a specialization of a ioda::Group. It provides convenience functions and guarantees th...
Definition: ObsGroup.h:32
static ObsGroup generate(Group &emptyGroup, const NewDimensionScales_t &fundamentalDims, std::shared_ptr< const detail::DataLayoutPolicy > layout=nullptr)
Create an empty ObsGroup and populate it with the fundamental dimensions.
Definition: ObsGroup.cpp:72
A Selection represents the bounds of the data, in ioda or in userspace, that you are reading or writi...
Definition: Selection.h:48
Variables store data!
Definition: Variable.h:680
static std::shared_ptr< const DataLayoutPolicy > generate(const std::string &polid="")
Factory generator.
Definition: Layout.cpp:28
Variable_Implementation writeWithEigenRegular(const EigenClass &d, const Selection &mem_selection=Selection::all, const Selection &file_selection=Selection::all)
Write an Eigen object (a Matrix, an Array, a Block, a Map).
Definition: Variable.h:361
virtual Variable write(gsl::span< char > data, const Type &in_memory_dataType, const Selection &mem_selection=Selection::all, const Selection &file_selection=Selection::all)
The fundamental write function. Backends overload this function to implement all write operations.
Definition: Variable.cpp:317
BackendNames
Backend names.
Definition: Factory.h:27
IODA_DL Group constructBackend(BackendNames name, BackendCreationParameters &params)
This is a simple factory style function that will instantiate a different backend based on a given na...
Definition: Factory.cpp:123
@ Hdf5File
HDF5 file access.
@ ObsStore
ObsStore in-memory.
@ Truncate_If_Exists
If the file already exists, overwrite it.
Selection & extent(const VecDimensions_t &sz)
Provide the dimensions of the object that you are selecting from.
Definition: Selection.h:111
Selection & select(const SingleSelection &s)
Append a new selection.
Definition: Selection.h:103
constexpr int Unlimited
Specifies that a dimension is resizable to infinity.
IODA_DL void unwind_exception_stack(const std::exception &e, std::ostream &out=std::cerr, int level=0)
Convenience function for unwinding an exception stack.
Definition: Exception.cpp:48
#define ioda_Here()
Used to specify backend creation-time properties.
Definition: Factory.h:56
Used to specify Variable creation-time properties.
Definition: Has_Variables.h:57
VariableCreationParameters & setFillValue(DataType fill)
Definition: Has_Variables.h:69
bool chunk
Do we chunk this variable? Required for extendible / compressible Variables.
Definition: Has_Variables.h:84
int main(int argc, char **argv)
void test_obsgroup_helper_funcs(std::string backendType, std::string fileName, const std::string mappingFile="")
Definition: test_append.cpp:24