| Cantera 3.0.0

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