netxsimdg
Loading...
Searching...
No Matches
ParaGridIO.cpp
Go to the documentation of this file.
1
9
12#include "include/NZLevels.hpp"
13#include "include/gridNames.hpp"
14
15#include <ncDim.h>
16#include <ncFile.h>
17#include <ncGroup.h>
18#include <ncVar.h>
19
20#include <algorithm>
21#include <cstdlib>
22#include <map>
23#include <string>
24
25namespace Nextsim {
26
27const std::map<std::string, ModelArray::Type> ParaGridIO::dimensionKeys = {
28 { "yx", ModelArray::Type::H },
29 { "zyx", ModelArray::Type::Z },
30 { "yxdg_comp", ModelArray::Type::DG },
31 { "yxdgstress_comp", ModelArray::Type::DGSTRESS },
32 { "ycgxcg", ModelArray::Type::CG },
33 { "yvertexxvertexncoords", ModelArray::Type::VERTEX },
34};
35
36// Which dimensions are DG dimension, which could be legitimately missing
37const std::map<ModelArray::Dimension, bool> ParaGridIO::isDG = {
38 // clang-format off
39 { ModelArray::Dimension::X, false },
40 { ModelArray::Dimension::Y, false },
41 { ModelArray::Dimension::Z, false },
42 { ModelArray::Dimension::XCG, true },
43 { ModelArray::Dimension::YCG, true },
44 { ModelArray::Dimension::DG, true },
45 { ModelArray::Dimension::DGSTRESS, true },
46 // NCOORDS is a number of components, but not in the same way as the DG components.
47 { ModelArray::Dimension::NCOORDS, false },
48 // clang-format on
49};
50
51std::map<ModelArray::Dimension, ModelArray::Type> ParaGridIO::dimCompMap;
52std::map<std::string, netCDF::NcFile> ParaGridIO::openFiles;
53std::map<std::string, size_t> ParaGridIO::timeIndexByFile;
54
55void ParaGridIO::makeDimCompMap()
56{
57 dimCompMap = {
58 { ModelArray::componentMap.at(ModelArray::Type::DG), ModelArray::Type::DG },
59 { ModelArray::componentMap.at(ModelArray::Type::DGSTRESS), ModelArray::Type::DGSTRESS },
60 { ModelArray::componentMap.at(ModelArray::Type::VERTEX), ModelArray::Type::VERTEX },
61 };
62 // Also initialize the static map of tables and register the atexit
63 // function here, since it should only ever run once
64 // openFiles.clear();
65 std::atexit(closeAllFiles);
66}
67
68ParaGridIO::~ParaGridIO() = default;
69
70ModelState ParaGridIO::getModelState(const std::string& filePath)
71{
72 netCDF::NcFile ncFile(filePath, netCDF::NcFile::read);
73 netCDF::NcGroup metaGroup(ncFile.getGroup(IStructure::metadataNodeName()));
74 netCDF::NcGroup dataGroup(ncFile.getGroup(IStructure::dataNodeName()));
75
76 // Dimensions and DG components
77 std::multimap<std::string, netCDF::NcDim> dimMap = dataGroup.getDims();
78 for (auto entry : ModelArray::definedDimensions) {
79 if (dimCompMap.count(entry.first) > 0)
80 // TODO Assertions that DG in the file equals the compile time DG in the model. See #205
81 continue;
82
83 ModelArray::DimensionSpec& dimensionSpec = entry.second;
84 netCDF::NcDim dim = dataGroup.getDim(dimensionSpec.name);
85 if (entry.first == ModelArray::Dimension::Z) {
86 // A special case, as the number of levels in the file might not be
87 // the number that the selected ice thermodynamics requires.
88 ModelArray::setDimension(entry.first, NZLevels::get());
89 } else {
90 ModelArray::setDimension(entry.first, dim.getSize());
91 }
92 }
93
94 ModelState state;
95
96 // Get all vars in the data group, and load them into a new ModelState
97
98 for (auto entry : dataGroup.getVars()) {
99 const std::string& varName = entry.first;
100 netCDF::NcVar& var = entry.second;
101 // Determine the type from the dimensions
102 std::vector<netCDF::NcDim> varDims = var.getDims();
103 std::string dimKey = "";
104 for (netCDF::NcDim& dim : varDims) {
105 dimKey += dim.getName();
106 }
107 if (!dimensionKeys.count(dimKey)) {
108 throw std::out_of_range(
109 std::string("No ModelArray::Type corresponds to the dimensional key ") + dimKey);
110 }
111 ModelArray::Type newType = dimensionKeys.at(dimKey);
112 state.data[varName] = ModelArray(newType);
113 ModelArray& data = state.data.at(varName);
114 data.resize();
115
116 if (newType == ModelArray::Type::Z) {
117 std::vector<size_t> startVector(ModelArray::nDimensions(newType), 0);
118 std::vector<size_t> extentVector = ModelArray::dimensions(newType);
119 // Reverse the extent vector to go from logical (x, y, z) ordering
120 // of indexes to netCDF storage ordering.
121 std::reverse(extentVector.begin(), extentVector.end());
122 var.getVar(startVector, extentVector, &data[0]);
123 } else {
124 var.getVar(&data[0]);
125 }
126 }
127 ncFile.close();
128 return state;
129}
130
131ModelState ParaGridIO::readForcingTimeStatic(
132 const std::set<std::string>& forcings, const TimePoint& time, const std::string& filePath)
133{
134 netCDF::NcFile ncFile(filePath, netCDF::NcFile::read);
135 netCDF::NcGroup metaGroup(ncFile.getGroup(IStructure::metadataNodeName()));
136 netCDF::NcGroup dataGroup(ncFile.getGroup(IStructure::dataNodeName()));
137
138 ModelState state;
139
140 // Read the time axis
141 netCDF::NcDim timeDim = dataGroup.getDim(timeName);
142 // Read the time variable
143 netCDF::NcVar timeVar = dataGroup.getVar(timeName);
144 // Calculate the index of the largest time value on the axis below our target
145 size_t targetTIndex;
146 // Get the time axis as a vector
147 std::vector<double> timeVec(timeDim.getSize());
148 timeVar.getVar(timeVec.data());
149 // Get the index of the largest TimePoint less than the target.
150 targetTIndex = std::find_if(begin(timeVec), end(timeVec), [time](double t) {
151 return (TimePoint() + Duration(t)) > time;
152 }) - timeVec.begin();
153 // Rather than the first that is greater than, get the last that is less
154 // than or equal to. But don't go out of bounds.
155 if (targetTIndex > 0)
156 --targetTIndex;
157 // ASSUME all forcings are HFields: finite volume fields on the same
158 // grid as ice thickness
159 std::vector<size_t> indexArray = { targetTIndex };
160 std::vector<size_t> extentArray = { 1 };
161
162 // Loop over the dimensions of H
163 const std::vector<ModelArray::Dimension>& dimensions
164 = ModelArray::typeDimensions.at(ModelArray::Type::H);
165 for (auto riter = dimensions.rbegin(); riter != dimensions.rend(); ++riter) {
166 indexArray.push_back(0);
167 extentArray.push_back(ModelArray::definedDimensions.at(*riter).length);
168 }
169
170 for (const std::string& varName : forcings) {
171 netCDF::NcVar var = dataGroup.getVar(varName);
172 state.data[varName] = ModelArray(ModelArray::Type::H);
173 ModelArray& data = state.data.at(varName);
174 data.resize();
175
176 var.getVar(indexArray, extentArray, &data[0]);
177 }
178 ncFile.close();
179 return state;
180}
181
183 const ModelState& state, const ModelMetadata& metadata, const std::string& filePath)
184{
185 netCDF::NcFile ncFile(filePath, netCDF::NcFile::replace);
186
188 netCDF::NcGroup metaGroup = ncFile.addGroup(IStructure::metadataNodeName());
189 netCDF::NcGroup dataGroup = ncFile.addGroup(IStructure::dataNodeName());
191
192 // Dump the dimensions and number of components
193 std::map<ModelArray::Dimension, netCDF::NcDim> ncFromMAMap;
194 for (auto entry : ModelArray::definedDimensions) {
195 ModelArray::Dimension dim = entry.first;
196 size_t dimSz = (dimCompMap.count(dim)) ? ModelArray::nComponents(dimCompMap.at(dim))
197 : dimSz = entry.second.length;
198 ncFromMAMap[dim] = dataGroup.addDim(entry.second.name, dimSz);
199 // TODO Do I need to add data, even if it is just integers 0...n-1?
200 }
201
202 // Also create the sets of dimensions to be connected to the data fields
203 std::map<ModelArray::Type, std::vector<netCDF::NcDim>> dimMap;
204 for (auto entry : ModelArray::typeDimensions) {
205 ModelArray::Type type = entry.first;
206 std::vector<netCDF::NcDim> ncDims;
207 for (auto iter = entry.second.rbegin(); iter != entry.second.rend(); ++iter) {
208 ModelArray::Dimension& maDim = *iter;
209 ncDims.push_back(ncFromMAMap.at(maDim));
210 }
211 dimMap[type] = ncDims;
212 }
213
214 // Everything that has components needs that dimension, too. This always varies fastest, and so
215 // is last in the vector of dimensions.
216 for (auto entry : dimCompMap) {
217 dimMap.at(entry.second).push_back(ncFromMAMap.at(entry.first));
218 }
219
220 std::set<std::string> restartFields = { hiceName, ciceName, hsnowName, ticeName, sstName,
221 sssName, maskName, coordsName }; // TODO and others
222 // Loop through either the above list (isRestart) or all provided fields(!isRestart)
223 for (auto entry : state.data) {
224 if (restartFields.count(entry.first)) {
225 // Get the type, then relevant vector of NetCDF dimensions
226 ModelArray::Type type = entry.second.getType();
227 std::vector<netCDF::NcDim>& ncDims = dimMap.at(type);
228 netCDF::NcVar var(dataGroup.addVar(entry.first, netCDF::ncDouble, ncDims));
229 var.putAtt(mdiName, netCDF::ncDouble, MissingData::value);
230 var.putVar(entry.second.getData());
231 }
232 }
233
234 ncFile.close();
235}
236
238 const ModelState& state, const ModelMetadata& meta, const std::string& filePath)
239{
240 bool isNew = openFiles.count(filePath) <= 0;
241 size_t nt = (isNew) ? 0 : ++timeIndexByFile.at(filePath);
242 if (isNew) {
243 // Open a new file and emplace it in the map of open files.
244 openFiles.try_emplace(filePath, filePath, netCDF::NcFile::replace);
245 // Set the initial time to be zero (assigned above)
246 timeIndexByFile[filePath] = nt;
247 }
248 // Get the file handle
249 netCDF::NcFile& ncFile = openFiles.at(filePath);
250
251 // Get the netCDF groups, creating them if necessary
252 netCDF::NcGroup metaGroup = (isNew) ? ncFile.addGroup(IStructure::metadataNodeName())
253 : ncFile.getGroup(IStructure::metadataNodeName());
254 netCDF::NcGroup dataGroup = (isNew) ? ncFile.addGroup(IStructure::dataNodeName())
255 : ncFile.getGroup(IStructure::dataNodeName());
256
257 if (isNew) {
258 // Write the common structure and time metadata
261 }
262 // Get the unlimited time dimension, creating it if necessary
263 netCDF::NcDim timeDim = (isNew) ? dataGroup.addDim(timeName) : dataGroup.getDim(timeName);
264
265 // All of the dimensions defined by the data at a particular timestep.
266 std::map<ModelArray::Dimension, netCDF::NcDim> ncFromMAMap;
267 for (auto entry : ModelArray::definedDimensions) {
268 ModelArray::Dimension dim = entry.first;
269 size_t dimSz = (dimCompMap.count(dim)) ? ModelArray::nComponents(dimCompMap.at(dim))
270 : dimSz = entry.second.length;
271 ncFromMAMap[dim] = (isNew) ? dataGroup.addDim(entry.second.name, dimSz)
272 : dataGroup.getDim(entry.second.name);
273 }
274
275 // Also create the sets of dimensions to be connected to the data fields
276 std::map<ModelArray::Type, std::vector<netCDF::NcDim>> dimMap;
277 // Create the index and size arrays
278 // The index arrays always start from zero, except in the first/time axis
279 std::map<ModelArray::Type, std::vector<size_t>> indexArrays;
280 std::map<ModelArray::Type, std::vector<size_t>> extentArrays;
281 for (auto entry : ModelArray::typeDimensions) {
282 ModelArray::Type type = entry.first;
283 std::vector<netCDF::NcDim> ncDims;
284 std::vector<size_t> indexArray;
285 std::vector<size_t> extentArray;
286
287 // Deal with VERTEX in each case
288 // Add the time dimension for all types that are not VERTEX
289 if (type != ModelArray::Type::VERTEX) {
290 ncDims.push_back(timeDim);
291 indexArray.push_back(nt);
292 extentArray.push_back(1UL);
293 } else if (!isNew) {
294 // For VERTEX in an existing file, there is nothing more to be done
295 continue;
296 }
297 for (auto iter = entry.second.rbegin(); iter != entry.second.rend(); ++iter) {
298 ModelArray::Dimension& maDim = *iter;
299 ncDims.push_back(ncFromMAMap.at(maDim));
300 indexArray.push_back(0);
301 extentArray.push_back(ModelArray::definedDimensions.at(maDim).length);
302 }
303 dimMap[type] = ncDims;
304 indexArrays[type] = indexArray;
305 extentArrays[type] = extentArray;
306 }
307 // Everything that has components needs that dimension, too
308 for (auto entry : dimCompMap) {
309 // Skip VERTEX fields on existing files
310 if (entry.second == ModelArray::Type::VERTEX && !isNew)
311 continue;
312 dimMap.at(entry.second).push_back(ncFromMAMap.at(entry.first));
313 indexArrays.at(entry.second).push_back(0);
314 extentArrays.at(entry.second).push_back(ModelArray::nComponents(entry.second));
315 }
316
317 // Create a special timeless set of dimensions for the landmask
318 std::vector<netCDF::NcDim> maskDims;
319 std::vector<size_t> maskIndexes;
320 std::vector<size_t> maskExtents;
321 if (isNew) {
322 for (ModelArray::Dimension& maDim : ModelArray::typeDimensions.at(ModelArray::Type::H)) {
323 maskDims.push_back(ncFromMAMap.at(maDim));
324 }
325 maskIndexes = { 0, 0 };
326 maskExtents = { ModelArray::definedDimensions
327 .at(ModelArray::typeDimensions.at(ModelArray::Type::H)[0])
328 .length,
329 ModelArray::definedDimensions.at(ModelArray::typeDimensions.at(ModelArray::Type::H)[1])
330 .length };
331 }
332
333 // Put the time axis variable
334 std::vector<netCDF::NcDim> timeDimVec = { timeDim };
335 netCDF::NcVar timeVar((isNew) ? dataGroup.addVar(timeName, netCDF::ncDouble, timeDimVec)
336 : dataGroup.getVar(timeName));
337 double secondsSinceEpoch = (meta.time() - TimePoint()).seconds();
338 timeVar.putVar({ nt }, { 1 }, &secondsSinceEpoch);
339
340 // Write the data
341 for (auto entry : state.data) {
342 ModelArray::Type type = entry.second.getType();
343 // Skip timeless fields (mask, coordinates) on existing files
344 if (!isNew && (entry.first == maskName || type == ModelArray::Type::VERTEX))
345 continue;
346 if (entry.first == maskName) {
347 // Land mask in a new file (since it was skipped above in existing files)
348 netCDF::NcVar var(dataGroup.addVar(maskName, netCDF::ncDouble, maskDims));
349 // No missing data
350 var.putVar(maskIndexes, maskExtents, entry.second.getData());
351
352 } else {
353 std::vector<netCDF::NcDim>& ncDims = dimMap.at(type);
354 // Get the variable object, either creating a new one or getting the existing one
355 netCDF::NcVar var((isNew) ? dataGroup.addVar(entry.first, netCDF::ncDouble, ncDims)
356 : dataGroup.getVar(entry.first));
357 if (isNew)
358 var.putAtt(mdiName, netCDF::ncDouble, MissingData::value);
359
360 var.putVar(indexArrays.at(type), extentArrays.at(type), entry.second.getData());
361 }
362 }
363}
364
365void ParaGridIO::close(const std::string& filePath)
366{
367 if (openFiles.count(filePath) > 0) {
368 openFiles.at(filePath).close();
369 openFiles.erase(openFiles.find(filePath));
370 timeIndexByFile.erase(timeIndexByFile.find(filePath));
371 }
372}
373
374void ParaGridIO::closeAllFiles()
375{
376 size_t closedFiles = 0;
377 for (const auto& [name, handle] : openFiles) {
378 if (!handle.isNull()) {
379 close(name);
380 closedFiles++;
381 }
382 /* If the following break is not checked for and performed, for some
383 * reason the iteration will continue to iterate over invalid
384 * string/NcFile pairs. */
385 if (closedFiles >= openFiles.size())
386 break;
387 }
388}
389
390} /* namespace Nextsim */
static netCDF::NcGroup & writeRestartMetadata(netCDF::NcGroup &metaGroup, const ModelMetadata &metadata)
Writes the standard restart file metadata to a metadata node.
static netCDF::NcGroup & writeStructureType(netCDF::NcFile &rootGroup, const ModelMetadata &metadata)
Writes the structure type to the root of the restart file for future retrieval.
static const std::string dataNodeName()
Returns the name of the data node.
static const std::string metadataNodeName()
Returns the name of the metadata node.
A class that holds the array data for the model.
static TypeDimensions typeDimensions
The dimensions that make up each defined type. Defined in ModelArrayDetails.cpp.
const MultiDim & dimensions() const
Returns a vector<size_t> of the size of each dimension of this type of ModelArray.
static void setDimension(Dimension dim, size_t length)
Sets the length of an individual dimension before propagating it to the defined array types.
size_t nComponents() const
Returns the number of discontinuous Galerkin components held in this type of ModelArray.
size_t nDimensions() const
Returns the number of dimensions of this type of ModelArray.
static std::map< Dimension, DimensionSpec > definedDimensions
The name and length of each dimension that is defined.
const TimePoint & time() const
Returns the current model time.
void writeDiagnosticTime(const ModelState &state, const ModelMetadata &meta, const std::string &filePath) override
Writes diagnostic data to a file.
void dumpModelState(const ModelState &state, const ModelMetadata &meta, const std::string &filePath) override
Writes the ModelState to a given file location from the provided model data and metadata.
ModelState getModelState(const std::string &filePath) override
static void close(const std::string &filePath)