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