Cantera  3.1.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
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
30namespace h5 = HighFive;
31
32#ifdef CT_USE_HIGHFIVE_BOOLEAN
33// HighFive 2.7.1 introduces stable native boolean support
34typedef 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
38enum 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
52namespace 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
71void 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
80bool 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
91bool 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
115bool 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
140bool 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
162void 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
272bool 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
300void 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
367void 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
484void 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
585void Storage::setCompressionLevel(int level)
586{
587 throw CanteraError("Storage::setCompressionLevel",
588 "Saving to HDF requires HighFive installation.");
589}
590
591bool Storage::hasGroup(const string& id) const
592{
593 throw CanteraError("Storage::hasGroup",
594 "Saving to HDF requires HighFive installation.");
595}
596
597bool Storage::checkGroup(const string& id, bool permissive)
598{
599 throw CanteraError("Storage::checkGroup",
600 "Saving to HDF requires HighFive installation.");
601}
602
603void 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
615bool Storage::hasAttribute(const string& id, const string& attr) const
616{
617 throw CanteraError("Storage::hasAttribute",
618 "Saving to HDF requires HighFive installation.");
619}
620
621AnyMap Storage::readAttributes(const string& id, bool recursive) const
622{
623 throw CanteraError("Storage::readAttributes",
624 "Saving to HDF requires HighFive installation.");
625}
626
627void Storage::writeAttributes(const string& id, const AnyMap& meta)
628{
629 throw CanteraError("Storage::writeAttributes",
630 "Saving to HDF requires HighFive installation.");
631}
632
633AnyValue 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
640void 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}
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:87
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.
void tokenizePath(const string &in_val, vector< string > &v)
This function separates a string up into tokens according to the location of path separators.
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