Cantera  3.3.0a1
Loading...
Searching...
No Matches
Storage.cpp
Go to the documentation of this file.
1/**
2 * @file Storage.cpp
3 * Definition file for class Storage.
4 */
5
6// This file is part of Cantera. See License.txt in the top-level directory or
7// at https://cantera.org/license.txt for license and copyright information.
8
10#include "cantera/base/global.h"
12
13#if CT_USE_HDF5
14
15#include <highfive/H5Attribute.hpp>
16#include <highfive/H5DataSet.hpp>
17#include <highfive/H5DataSpace.hpp>
18#include <highfive/H5DataType.hpp>
19#include <highfive/H5File.hpp>
20#include <highfive/H5Group.hpp>
21
22namespace h5 = HighFive;
23typedef h5::details::Boolean H5Boolean;
24
25#endif
26
27namespace Cantera
28{
29
30#if CT_USE_HDF5
31
32Storage::Storage(string fname, bool write) : m_write(write)
33{
34 if (m_write) {
35 m_file = make_unique<h5::File>(fname, h5::File::OpenOrCreate);
36 } else {
37 auto fullName = findInputFile(fname);
38 m_file = make_unique<h5::File>(fullName, h5::File::ReadOnly);
39 }
40}
41
42Storage::~Storage()
43{
44 m_file->flush();
45}
46
47void Storage::setCompressionLevel(int level)
48{
49 if (level < 0 || level > 9) {
50 throw CanteraError("Storage::setCompressionLevel",
51 "Invalid compression level '{}' (needs to be 0..9).", level);
52 }
53 m_compressionLevel = level;
54}
55
56bool Storage::hasGroup(const string& id) const
57{
58 if (!m_file->exist(id)) {
59 return false;
60 }
61 if (m_file->getObjectType(id) != h5::ObjectType::Group) {
62 return false;
63 }
64 return true;
65}
66
67bool Storage::checkGroupRead(const string& id) const
68{
69 vector<string> tokens;
70 tokenizePath(id, tokens);
71 string grp = tokens[0];
72 if (!hasGroup(grp)) {
73 throw CanteraError("Storage::checkGroupRead",
74 "No group with id '{}' found at root.", grp);
75 }
76
77 string path = grp;
78 h5::Group sub = m_file->getGroup(grp);
79 tokens.erase(tokens.begin());
80 for (auto& grp : tokens) {
81 if (!hasGroup(path + "/" + grp)) {
82 throw CanteraError("Storage::checkGroupRead",
83 "No group with id '{}' found at '{}'.", grp, path);
84 }
85 path += "/" + grp;
86 sub = sub.getGroup(grp);
87 }
88 return true;
89}
90
91bool Storage::checkGroupWrite(const string& id, bool permissive)
92{
93 if (!m_write) {
94 throw CanteraError("Storage::checkGroupWrite",
95 "Cannot write to file opened in read mode.");
96 }
97 if (id == "") {
98 throw CanteraError("Storage::checkGroupWrite",
99 "Cannot write to empty group id '' (root location).");
100 }
101 if (!m_file->exist(id)) {
102 if (!permissive) {
103 throw CanteraError("Storage::checkGroupWrite",
104 "Specified group with id '{}' does not exist.", id);
105 }
106 m_file->createGroup(id);
107 return false;
108 }
109 if (m_file->getObjectType(id) != h5::ObjectType::Group) {
110 throw CanteraError("Storage::checkGroupWrite",
111 "Unable to write to existing object with id '{}'.", id);
112 }
113 return true;
114}
115
116bool Storage::checkGroup(const string& id, bool permissive)
117{
118 try {
119 if (m_write) {
120 return checkGroupWrite(id, permissive);
121 }
122 return checkGroupRead(id);
123 } catch (const CanteraError& err) {
124 if (permissive) {
125 return false;
126 }
127 throw CanteraError("Storage::checkGroup", err.getMessage());
128 } catch (const std::exception& err) {
129 if (permissive) {
130 return false;
131 }
132 // convert HighFive exception
133 throw CanteraError("Storage::checkGroup",
134 "Encountered exception for group '{}':\n{}", id, err.what());
135 }
136}
137
138void Storage::deleteGroup(const string& id)
139{
140 try {
141 m_file->unlink(id);
142 } catch (const std::exception& err) {
143 // convert HighFive exception
144 throw CanteraError("Storage::deleteGroup",
145 "Encountered exception while deleting group '{}':\n{}", id, err.what());
146 }
147}
148
149pair<size_t, set<string>> Storage::contents(const string& id) const
150{
151 try {
152 checkGroupRead(id);
153 } catch (const CanteraError& err) {
154 throw CanteraError("Storage::contents",
155 "Caught exception for group '{}':\n", id, err.getMessage());
156 }
157 h5::Group sub = m_file->getGroup(id);
158 set<string> names;
159 size_t nDims = npos;
160 size_t nElements = 0;
161 for (auto& name : sub.listObjectNames()) {
162 if (sub.getObjectType(name) == h5::ObjectType::Dataset) {
163 h5::DataSpace space = sub.getDataSet(name).getSpace();
164 names.insert(name);
165 if (space.getNumberDimensions() < nDims) {
166 nDims = space.getNumberDimensions();
167 nElements = space.getElementCount();
168 }
169 }
170 }
171 if (nDims != 1 && nDims != npos) {
172 throw NotImplementedError("Storage::content",
173 "Encountered invalid data with {} dimensions.", nDims);
174 }
175 return std::make_pair(nElements, names);
176}
177
178AnyMap readH5Attributes(const h5::Group& sub, bool recursive)
179{
180 // restore meta data from attributes
181 AnyMap out;
182 for (auto& name : sub.listAttributeNames()) {
183 h5::Attribute attr = sub.getAttribute(name);
184 h5::DataType dtype = attr.getDataType();
185 h5::DataTypeClass dclass = dtype.getClass();
186 if (dclass == h5::DataTypeClass::Float) {
187 if (attr.getSpace().getElementCount() > 1) {
188 vector<double> values;
189 attr.read(values);
190 out[name] = values;
191 } else {
192 double value;
193 attr.read(value);
194 out[name] = value;
195 }
196 } else if (dclass == h5::DataTypeClass::Integer) {
197 if (attr.getSpace().getElementCount() > 1) {
198 vector<long int> values;
199 attr.read(values);
200 out[name] = values;
201 } else {
202 long int value;
203 attr.read(value);
204 out[name] = value;
205 }
206 } else if (dclass == h5::DataTypeClass::String) {
207 if (attr.getSpace().getElementCount() > 1) {
208 vector<string> values;
209 attr.read(values);
210 out[name] = values;
211 } else {
212 string value;
213 attr.read(value);
214 out[name] = value;
215 }
216 } else if (dclass == h5::DataTypeClass::Enum) {
217 // only booleans are supported
218 if (attr.getSpace().getElementCount() > 1) {
219 vector<H5Boolean> values;
220 attr.read(values);
221 vector<bool> bValues;
222 for (auto v : values) {
223 bValues.push_back(bool(v));
224 }
225 out[name] = bValues;
226 } else {
227 H5Boolean value;
228 attr.read(value);
229 out[name] = bool(value);
230 }
231 } else {
232 throw NotImplementedError("readH5Attributes",
233 "Unable to read attribute '{}' with type '{}'", name, dtype.string());
234 }
235 }
236
237 if (recursive) {
238 for (auto& name : sub.listObjectNames()) {
239 if (sub.getObjectType(name) == h5::ObjectType::Group) {
240 out[name] = readH5Attributes(sub.getGroup(name), recursive);
241 }
242 }
243 }
244
245 return out;
246}
247
248bool Storage::hasAttribute(const string& id, const string& attr) const
249{
250 try {
251 checkGroupRead(id);
252 } catch (const CanteraError& err) {
253 throw CanteraError("Storage::hasAttribute",
254 "Caught exception for group '{}':\n", id, err.getMessage());
255 }
256 h5::Group sub = m_file->getGroup(id);
257 auto names = sub.listAttributeNames();
258 return std::find(names.begin(), names.end(), attr) != names.end();
259}
260
261AnyMap Storage::readAttributes(const string& id, bool recursive) const
262{
263 try {
264 checkGroupRead(id);
265 h5::Group sub = m_file->getGroup(id);
266 return readH5Attributes(sub, recursive);
267 } catch (const Cantera::NotImplementedError& err) {
268 throw NotImplementedError("Storage::readAttribute",
269 "{} in group '{}'.", err.getMessage(), id);
270 } catch (const CanteraError& err) {
271 throw CanteraError("Storage::readAttribute",
272 "Caught exception for group '{}':\n", id, err.getMessage());
273 }
274}
275
276void writeH5Attributes(h5::Group sub, const AnyMap& meta)
277{
278 for (auto& [name, item] : meta) {
279 if (sub.hasAttribute(name)) {
280 throw NotImplementedError("writeH5Attributes",
281 "Unable to overwrite existing Attribute '{}'", name);
282 }
283 if (item.is<long int>()) {
284 int value = item.asInt();
285 h5::Attribute attr = sub.createAttribute<long int>(
286 name, h5::DataSpace::From(value));
287 attr.write(value);
288 } else if (item.is<double>()) {
289 double value = item.asDouble();
290 h5::Attribute attr = sub.createAttribute<double>(
291 name, h5::DataSpace::From(value));
292 attr.write(value);
293 } else if (item.is<string>()) {
294 string value = item.asString();
295 h5::Attribute attr = sub.createAttribute<string>(
296 name, h5::DataSpace::From(value));
297 attr.write(value);
298 } else if (item.is<bool>()) {
299 bool bValue = item.asBool();
300 H5Boolean value = bValue ?
301 H5Boolean::HighFiveTrue : H5Boolean::HighFiveFalse;
302 h5::Attribute attr = sub.createAttribute<H5Boolean>(
303 name, h5::DataSpace::From(value));
304 attr.write(value);
305 } else if (item.is<vector<long int>>()) {
306 auto values = item.as<vector<long int>>();
307 h5::Attribute attr = sub.createAttribute<long int>(
308 name, h5::DataSpace::From(values));
309 attr.write(values);
310 } else if (item.is<vector<double>>()) {
311 auto values = item.as<vector<double>>();
312 h5::Attribute attr = sub.createAttribute<double>(
313 name, h5::DataSpace::From(values));
314 attr.write(values);
315 } else if (item.is<vector<string>>()) {
316 auto values = item.as<vector<string>>();
317 h5::Attribute attr = sub.createAttribute<string>(
318 name, h5::DataSpace::From(values));
319 attr.write(values);
320 } else if (item.is<vector<bool>>()) {
321 auto bValue = item.as<vector<bool>>();
322 vector<H5Boolean> values;
323 for (auto b : bValue) {
324 values.push_back(b ?
325 H5Boolean::HighFiveTrue : H5Boolean::HighFiveFalse);
326 }
327 h5::Attribute attr = sub.createAttribute<H5Boolean>(
328 name, h5::DataSpace::From(values));
329 attr.write(values);
330 } else if (item.is<AnyMap>()) {
331 // step into recursion
332 auto value = item.as<AnyMap>();
333 auto grp = sub.createGroup(name);
334 writeH5Attributes(grp, value);
335 } else {
336 throw NotImplementedError("writeH5Attributes",
337 "Unable to write attribute '{}' with type '{}'",
338 name, item.type_str());
339 }
340 }
341}
342
343void Storage::writeAttributes(const string& id, const AnyMap& meta)
344{
345 try {
346 checkGroupWrite(id, false);
347 h5::Group sub = m_file->getGroup(id);
348 writeH5Attributes(sub, meta);
349 } catch (const Cantera::NotImplementedError& err) {
350 throw NotImplementedError("Storage::writeAttribute",
351 "{} in group '{}'.", err.getMessage(), id);
352 } catch (const CanteraError& err) {
353 // rethrow with public method attribution
354 throw CanteraError("Storage::writeAttributes", "{}", err.getMessage());
355 } catch (const std::exception& err) {
356 // convert HighFive exception
357 throw CanteraError("Storage::writeAttributes",
358 "Encountered exception for group '{}':\n{}", id, err.what());
359 }
360}
361
362AnyValue Storage::readData(const string& id,
363 const string& name, size_t rows, size_t cols) const
364{
365 try {
366 checkGroupRead(id);
367 } catch (const CanteraError& err) {
368 throw CanteraError("Storage::readData",
369 "Caught exception for group '{}':\n", id, err.getMessage());
370 }
371 h5::Group sub = m_file->getGroup(id);
372 if (!sub.exist(name)) {
373 throw CanteraError("Storage::readData",
374 "DataSet '{}' not found in group '{}'.", name, id);
375 }
376 h5::DataSet dataset = sub.getDataSet(name);
377 h5::DataSpace space = dataset.getSpace();
378 size_t ndim = space.getNumberDimensions();
379 if (cols == 0 && ndim != 1) {
380 throw CanteraError("Storage::readData",
381 "Shape of DataSet '{}' is inconsistent; expected one dimensions but "
382 "received {}.", name, ndim);
383 } else if (cols != 0 && cols != npos && ndim != 2) {
384 throw CanteraError("Storage::readData",
385 "Shape of DataSet '{}' is inconsistent; expected two dimensions but "
386 "received {}.", name, ndim);
387 }
388 if (ndim == 0 || ndim > 2) {
389 throw NotImplementedError("Storage::readData",
390 "Cannot process DataSet '{}' as data has {} dimensions.", name, ndim);
391 }
392 const auto& shape = space.getDimensions();
393 if (shape[0] != rows) {
394 throw CanteraError("Storage::readData",
395 "Shape of DataSet '{}' is inconsistent; expected {} rows "
396 "but received {}.", name, rows, shape[0]);
397 }
398 if (cols != 0 && cols != npos && shape[1] != cols) {
399 throw CanteraError("Storage::readData",
400 "Shape of DataSet '{}' is inconsistent; expected {} columns "
401 "but received {}.", name, cols, shape[1]);
402 }
403 AnyValue out;
404 const auto datatype = dataset.getDataType().getClass();
405 if (datatype == h5::DataTypeClass::Float) {
406 try {
407 if (ndim == 1) {
408 vector<double> data;
409 dataset.read(data);
410 out = data;
411 } else { // ndim == 2
412 vector<vector<double>> data;
413 dataset.read(data);
414 out = data;
415 }
416 } catch (const std::exception& err) {
417 throw NotImplementedError("Storage::readData",
418 "Encountered HighFive exception for DataSet '{}' in group '{}':\n{}",
419 name, id, err.what());
420 }
421 } else if (datatype == h5::DataTypeClass::Integer) {
422 try {
423 if (ndim == 1) {
424 vector<long int> data;
425 dataset.read(data);
426 out = data;
427 } else { // ndim == 2
428 vector<vector<long int>> data;
429 dataset.read(data);
430 out = data;
431 }
432 } catch (const std::exception& err) {
433 throw NotImplementedError("Storage::readData",
434 "Encountered HighFive exception for DataSet '{}' in group '{}':\n{}",
435 name, id, err.what());
436 }
437 } else if (datatype == h5::DataTypeClass::String) {
438 try {
439 if (ndim == 1) {
440 vector<string> data;
441 dataset.read(data);
442 out = data;
443 } else { // ndim == 2
444 vector<vector<string>> data;
445 dataset.read(data);
446 out = data;
447 }
448 } catch (const std::exception& err) {
449 throw NotImplementedError("Storage::readData",
450 "Encountered HighFive exception for DataSet '{}' in group '{}':\n{}",
451 name, id, err.what());
452 }
453 } else {
454 throw NotImplementedError("Storage::readData",
455 "DataSet '{}' is not readable.", name);
456 }
457 return out;
458}
459
460void Storage::writeData(const string& id, const string& name, const AnyValue& data)
461{
462 try {
463 checkGroupWrite(id, false);
464 } catch (const CanteraError& err) {
465 // rethrow with public method attribution
466 throw CanteraError("Storage::writeData", "{}", err.getMessage());
467 } catch (const std::exception& err) {
468 // convert HighFive exception
469 throw CanteraError("Storage::writeData",
470 "Encountered exception for group '{}':\n{}", id, err.what());
471 }
472 h5::Group sub = m_file->getGroup(id);
473 if (sub.exist(name)) {
474 throw NotImplementedError("Storage::writeData",
475 "Unable to overwrite existing DataSet '{}' in group '{}'.", name, id);
476 }
477 size_t size = data.vectorSize();
478 auto [rows, cols] = data.matrixShape();
479 if (size == npos && rows == npos) {
480 throw CanteraError("Storage::writeData",
481 "Cannot write DataSet '{}' in group '{}' as input data with type\n"
482 "'{}'\nis neither a vector nor a matrix.", name, id, data.type_str());
483 }
484 vector<size_t> dims{data.vectorSize()};
485 if (data.isVector<long int>()) {
486 h5::DataSet dataset = sub.createDataSet<long int>(name, h5::DataSpace(dims));
487 dataset.write(data.asVector<long int>());
488 return;
489 }
490 if (data.isVector<double>()) {
491 h5::DataSet dataset = sub.createDataSet<double>(name, h5::DataSpace(dims));
492 dataset.write(data.asVector<double>());
493 return;
494 }
495 if (data.isVector<string>()) {
496 h5::DataSet dataset = sub.createDataSet<string>(name, h5::DataSpace(dims));
497 dataset.write(data.asVector<string>());
498 return;
499 }
500 if (cols != npos) {
501 dims.clear();
502 dims.push_back(rows);
503 dims.push_back(cols);
504 } else {
505 throw NotImplementedError("Storage::writeData",
506 "Cannot write DataSet '{}' in group '{}' as input data with type\n"
507 "'{}'\nis not supported.", name, id, data.type_str());
508 }
509 if (m_compressionLevel) {
510 // Set chunk size to single chunk and apply compression level; for caveats, see
511 // https://stackoverflow.com/questions/32994766/compressed-files-bigger-in-h5py
512 h5::DataSpace space(dims, dims); //{h5::DataSpace::UNLIMITED, dims[1]});
513 h5::DataSetCreateProps props;
514 props.add(h5::Chunking(vector<hsize_t>{dims[0], dims[1]}));
515 props.add(h5::Deflate(m_compressionLevel));
516 if (data.isVector<vector<long int>>()) {
517 h5::DataSet dataset = sub.createDataSet<long int>(name, space, props);
518 dataset.write(data.asVector<vector<long int>>());
519 } else if (data.isVector<vector<double>>()) {
520 h5::DataSet dataset = sub.createDataSet<double>(name, space, props);
521 dataset.write(data.asVector<vector<double>>());
522 } else if (data.isVector<vector<string>>()) {
523 h5::DataSet dataset = sub.createDataSet<string>(name, space, props);
524 dataset.write(data.asVector<vector<string>>());
525 } else {
526 throw NotImplementedError("Storage::writeData",
527 "Cannot write DataSet '{}' in group '{}' as input data with type\n"
528 "'{}'\nis not supported.", name, id, data.type_str());
529 }
530 } else {
531 h5::DataSpace space(dims);
532 if (data.isVector<vector<long int>>()) {
533 h5::DataSet dataset = sub.createDataSet<long int>(name, space);
534 dataset.write(data.asVector<vector<long int>>());
535 } else if (data.isVector<vector<double>>()) {
536 h5::DataSet dataset = sub.createDataSet<double>(name, space);
537 dataset.write(data.asVector<vector<double>>());
538 } else if (data.isVector<vector<string>>()) {
539 h5::DataSet dataset = sub.createDataSet<string>(name, space);
540 dataset.write(data.asVector<vector<string>>());
541 } else {
542 throw NotImplementedError("Storage::writeData",
543 "Cannot write DataSet '{}' in group '{}' as input data with type\n"
544 "'{}'\nis not supported.", name, id, data.type_str());
545 }
546 }
547}
548
549#else
550
551Storage::Storage(string fname, bool write)
552{
553 throw CanteraError("Storage::Storage",
554 "Saving to HDF requires HighFive installation.");
555}
556
557Storage::~Storage()
558{
559}
560
561void Storage::setCompressionLevel(int level)
562{
563 throw CanteraError("Storage::setCompressionLevel",
564 "Saving to HDF requires HighFive installation.");
565}
566
567bool Storage::hasGroup(const string& id) const
568{
569 throw CanteraError("Storage::hasGroup",
570 "Saving to HDF requires HighFive installation.");
571}
572
573bool Storage::checkGroup(const string& id, bool permissive)
574{
575 throw CanteraError("Storage::checkGroup",
576 "Saving to HDF requires HighFive installation.");
577}
578
579void Storage::deleteGroup(const string& id)
580{
581 throw CanteraError("Storage::deleteGroup",
582 "Saving to HDF requires HighFive installation.");
583}
584
585pair<size_t, set<string>> Storage::contents(const string& id) const
586{
587 throw CanteraError("Storage::contents",
588 "Saving to HDF requires HighFive installation.");
589}
590
591bool Storage::hasAttribute(const string& id, const string& attr) const
592{
593 throw CanteraError("Storage::hasAttribute",
594 "Saving to HDF requires HighFive installation.");
595}
596
597AnyMap Storage::readAttributes(const string& id, bool recursive) const
598{
599 throw CanteraError("Storage::readAttributes",
600 "Saving to HDF requires HighFive installation.");
601}
602
603void Storage::writeAttributes(const string& id, const AnyMap& meta)
604{
605 throw CanteraError("Storage::writeAttributes",
606 "Saving to HDF requires HighFive installation.");
607}
608
609AnyValue Storage::readData(const string& id,
610 const string& name, size_t rows, size_t cols) const
611{
612 throw CanteraError("Storage::readData",
613 "Saving to HDF requires HighFive installation.");
614}
615
616void Storage::writeData(const string& id,
617 const string& name, const AnyValue& data)
618{
619 throw CanteraError("Storage::writeData",
620 "Saving to HDF requires HighFive installation.");
621}
622
623#endif
624
625}
A map of string keys to values whose type can vary at runtime.
Definition AnyMap.h:431
A wrapper for a variable whose type is determined at runtime.
Definition AnyMap.h:88
Base class for exceptions thrown by Cantera classes.
virtual string getMessage() const
Method overridden by derived classes to format the error message.
An error indicating that an unimplemented function has been called.
This file contains definitions for utility functions and text for modules, inputfiles and logging,...
void tokenizePath(const string &in_val, vector< string > &v)
This function separates a string up into tokens according to the location of path separators.
string findInputFile(const string &name)
Find an input file.
Definition global.cpp:169
Namespace for the Cantera kernel.
Definition AnyMap.cpp:595
const size_t npos
index returned by functions to indicate "no position"
Definition ct_defs.h:180