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