| Cantera 3.0.0

Loading...
Searching...
No Matches
SolutionArray.cpp
Go to the documentation of this file.
1/**
2 * @file SolutionArray.cpp
3 * Definition file for class SolutionArray.
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/SolutionArray.h"
10#include "cantera/base/Solution.h"
11#include "cantera/base/Storage.h"
12#include "cantera/base/stringUtils.h"
13#include "cantera/thermo/ThermoPhase.h"
14#include "cantera/thermo/SurfPhase.h"
15#include "cantera/base/utilities.h"
16#include <boost/algorithm/string.hpp>
17#include <fstream>
18#include <sstream>
19
20
21 namespace ba = boost::algorithm;
22
23 const std::map<std::string, std::string> aliasMap = {
24 {"T", "temperature"},
25 {"P", "pressure"},
26 {"D", "density"},
27 {"Y", "mass-fractions"},
28 {"X", "mole-fractions"},
29 {"C", "coverages"},
30 {"U", "specific-internal-energy"},
31 {"V", "specific-volume"},
32 {"H", "specific-enthalpy"},
33 {"S", "specific-entropy"},
34 {"Q", "vapor-fraction"},
35};
36
37 namespace Cantera
38{
39
40SolutionArray::SolutionArray(const shared_ptr<Solution>& sol,
41 int size, const AnyMap& meta)
42 : m_sol(sol)
43 , m_size(size)
44 , m_dataSize(size)
45 , m_meta(meta)
46{
47 if (!m_sol) {
48 throw CanteraError("SolutionArray::SolutionArray",
49 "Unable to create SolutionArray from invalid Solution object.");
50 }
51 m_stride = m_sol->thermo()->stateSize();
52 m_data = make_shared<vector<double>>(m_dataSize * m_stride, 0.);
53 m_extra = make_shared<map<string, AnyValue>>();
54 m_order = make_shared<map<int, string>>();
55 for (size_t i = 0; i < m_dataSize; ++i) {
56 m_active.push_back(static_cast<int>(i));
57 }
58 reset();
59 m_apiShape.resize(1);
60 m_apiShape[0] = static_cast<long>(m_dataSize);
61}
62
63SolutionArray::SolutionArray(const SolutionArray& other,
64 const vector<int>& selected)
65 : m_sol(other.m_sol)
66 , m_size(selected.size())
67 , m_dataSize(other.m_data->size())
68 , m_stride(other.m_stride)
69 , m_data(other.m_data)
70 , m_extra(other.m_extra)
71 , m_order(other.m_order)
72 , m_shared(true)
73 , m_active(selected)
74{
75 for (auto loc : m_active) {
76 if (loc < 0 || loc >= (int)m_dataSize) {
77 IndexError("SolutionArray::SolutionArray", "indices", loc, m_dataSize);
78 }
79 }
80 set<int> unique(selected.begin(), selected.end());
81 if (unique.size() < selected.size()) {
82 throw CanteraError("SolutionArray::SolutionArray", "Indices must be unique.");
83 }
84}
85
86 namespace { // restrict scope of helper functions to local translation unit
87
88 template<class T>
89 void resetSingle(AnyValue& extra, const vector<int>& slice);
90
91 template<class T>
92AnyValue getSingle(const AnyValue& extra, const vector<int>& slice);
93
94 template<class T>
95 void setSingle(AnyValue& extra, const AnyValue& data, const vector<int>& slice);
96
97 template<class T>
98 void resizeSingle(AnyValue& extra, size_t size, const AnyValue& value);
99
100 template<class T>
101 void resetMulti(AnyValue& extra, const vector<int>& slice);
102
103 template<class T>
104AnyValue getMulti(const AnyValue& extra, const vector<int>& slice);
105
106 template<class T>
107 void setMulti(AnyValue& extra, const AnyValue& data, const vector<int>& slice);
108
109 template<class T>
110 void resizeMulti(AnyValue& extra, size_t size, const AnyValue& value);
111
112 template<class T>
113 void setAuxiliarySingle(size_t loc, AnyValue& extra, const AnyValue& value);
114
115 template<class T>
116 void setAuxiliaryMulti(size_t loc, AnyValue& extra, const AnyValue& data);
117
118 template<class T>
119 void setScalar(AnyValue& extra, const AnyValue& data, const vector<int>& slice);
120
121} // end unnamed namespace
122
123 void SolutionArray::reset()
124{
125 size_t nState = m_sol->thermo()->stateSize();
126 vector<double> state(nState);
127 m_sol->thermo()->saveState(state); // thermo contains current state
128 for (size_t k = 0; k < m_size; ++k) {
129 std::copy(state.begin(), state.end(), m_data->data() + m_active[k] * m_stride);
130 }
131 for (auto& [key, extra] : *m_extra) {
132 if (extra.is<void>()) {
133 // cannot reset placeholder (uninitialized component)
134 } else if (extra.isVector<double>()) {
135 resetSingle<double>(extra, m_active);
136 } else if (extra.isVector<long int>()) {
137 resetSingle<long int>(extra, m_active);
138 } else if (extra.isVector<string>()) {
139 resetSingle<string>(extra, m_active);
140 } else if (extra.isVector<vector<double>>()) {
141 resetMulti<double>(extra, m_active);
142 } else if (extra.isVector<vector<long int>>()) {
143 resetMulti<long int>(extra, m_active);
144 } else if (extra.isVector<vector<string>>()) {
145 resetMulti<string>(extra, m_active);
146 } else {
147 throw NotImplementedError("SolutionArray::reset",
148 "Unable to reset component '{}' with type '{}'.",
149 key, extra.type_str());
150 }
151 }
152}
153
154 void SolutionArray::resize(int size)
155{
156 if (apiNdim() > 1) {
157 throw CanteraError("SolutionArray::resize",
158 "Resize is ambiguous for multi-dimensional arrays; use setApiShape "
159 "instead.");
160 }
161 if (m_data.use_count() > 1) {
162 throw CanteraError("SolutionArray::resize",
163 "Unable to resize as data are shared by multiple objects.");
164 }
165 _resize(static_cast<size_t>(size));
166 m_apiShape[0] = static_cast<long>(size);
167}
168
169 void SolutionArray::setApiShape(const vector<long int>& shape)
170{
171 size_t size = 1;
172 for (auto dim : shape) {
173 size *= dim;
174 }
175 if (m_shared && size != m_size) {
176 throw CanteraError("SolutionArray::setApiShape",
177 "Unable to set shape of shared data as sizes are inconsistent:\n"
178 "active size is {} but shape implies {}.", m_size, size);
179 }
180 if (!m_shared && size != m_dataSize) {
181 if (m_data.use_count() > 1) {
182 throw CanteraError("SolutionArray::setApiShape",
183 "Unable to set shape as data are shared by multiple objects.");
184 }
185 _resize(size);
186 }
187 m_apiShape = shape;
188}
189
190 void SolutionArray::_resize(size_t size)
191{
192 m_size = size;
193 m_dataSize = size;
194 m_data->resize(m_dataSize * m_stride, 0.);
195 for (auto& [key, data] : *m_extra) {
196 _resizeExtra(key);
197 }
198 m_active.clear();
199 for (size_t i = 0; i < m_dataSize; ++i) {
200 m_active.push_back(static_cast<int>(i));
201 }
202}
203
204 namespace { // restrict scope of helper functions to local translation unit
205
206vector<string> doubleColumn(string name, const vector<double>& comp,
207 int rows, int width)
208{
209 // extract data for processing
210 vector<double> data;
211 vector<string> raw;
212 string notation = fmt::format("{{:{}.{}g}}", width, (width - 1) / 2);
213 int csize = static_cast<int>(comp.size());
214 int dots = csize + 1;
215 if (csize <= rows) {
216 for (const auto& val : comp) {
217 data.push_back(val);
218 raw.push_back(boost::trim_copy(fmt::format(notation, val)));
219 }
220 } else {
221 dots = (rows + 1) / 2;
222 for (int row = 0; row < dots; row++) {
223 data.push_back(comp[row]);
224 raw.push_back(boost::trim_copy(fmt::format(notation, comp[row])));
225 }
226 for (int row = csize - rows / 2; row < csize; row++) {
227 data.push_back(comp[row]);
228 raw.push_back(boost::trim_copy(fmt::format(notation, comp[row])));
229 }
230 }
231
232 // determine notation; all entries use identical formatting
233 bool isFloat = false;
234 bool isScientific = false;
235 size_t head = 0;
236 size_t tail = 0;
237 size_t exp = 0;
238 for (const auto& repr : raw) {
239 string name = repr;
240 if (name[0] == '-') {
241 // leading negative sign is not considered
242 name = name.substr(1);
243 }
244 if (name.find('e') != string::npos) {
245 // scientific notation
246 if (!isScientific) {
247 head = 1;
248 tail = name.find('e') - name.find('.');
249 exp = 4; // size of exponent
250 } else {
251 tail = std::max(tail, name.find('e') - name.find('.'));
252 }
253 isFloat = true;
254 isScientific = true;
255 } else if (name.find('.') != string::npos) {
256 // floating point notation
257 isFloat = true;
258 if (!isScientific) {
259 head = std::max(head, name.find('.'));
260 tail = std::max(tail, name.size() - name.find('.'));
261 }
262 } else {
263 head = std::max(head, name.size());
264 }
265 }
266 size_t maxLen = std::max((size_t)4, head + tail + exp + isFloat + 1);
267 size_t over = std::max(0, (int)name.size() - (int)maxLen);
268 if (isScientific) {
269 // at least one entry has scientific notation
270 notation = fmt::format(" {{:>{}.{}e}}", over + maxLen, tail);
271 } else if (isFloat) {
272 // at least one entry is a floating point
273 notation = fmt::format(" {{:>{}.{}f}}", over + maxLen, tail);
274 } else {
275 // all entries are integers
276 notation = fmt::format(" {{:>{}.0f}}", over + maxLen);
277 }
278 maxLen = fmt::format(notation, 0.).size();
279
280 // assemble output
281 string section = fmt::format("{{:>{}}}", maxLen);
282 vector<string> col = {fmt::format(section, name)};
283 int count = 0;
284 for (const auto& val : data) {
285 col.push_back(fmt::format(notation, val));
286 count++;
287 if (count == dots) {
288 col.push_back(fmt::format(section, "..."));
289 }
290 }
291 return col;
292}
293
294vector<string> integerColumn(string name, const vector<long int>& comp,
295 int rows, int width)
296{
297 // extract data for processing
298 vector<long int> data;
299 string notation = fmt::format("{{:{}}}", width);
300 size_t maxLen = 2; // minimum column width is 2
301 int csize = static_cast<int>(comp.size());
302 int dots = csize + 1;
303 if (csize <= rows) {
304 for (const auto& val : comp) {
305 data.push_back(val);
306 string formatted = boost::trim_copy(fmt::format(notation, val));
307 if (formatted[0] == '-') {
308 formatted = formatted.substr(1);
309 }
310 maxLen = std::max(maxLen, formatted.size());
311 }
312 } else {
313 dots = (rows + 1) / 2;
314 for (int row = 0; row < dots; row++) {
315 data.push_back(comp[row]);
316 string formatted = boost::trim_copy(fmt::format(notation, comp[row]));
317 if (formatted[0] == '-') {
318 formatted = formatted.substr(1);
319 }
320 maxLen = std::max(maxLen, formatted.size());
321 }
322 for (int row = csize - rows / 2; row < csize; row++) {
323 data.push_back(comp[row]);
324 string formatted = boost::trim_copy(fmt::format(notation, comp[row]));
325 if (formatted[0] == '-') {
326 formatted = formatted.substr(1);
327 }
328 maxLen = std::max(maxLen, formatted.size());
329 }
330 }
331
332 if (name == "") {
333 // index column
334 notation = fmt::format("{{:<{}}}", maxLen);
335 } else {
336 // regular column
337 maxLen = std::max(maxLen, name.size());
338 notation = fmt::format(" {{:>{}}}", maxLen + 1);
339 }
340
341 // assemble output
342 vector<string> col = {fmt::format(notation, name)};
343 int count = 0;
344 for (const auto& val : data) {
345 col.push_back(fmt::format(notation, val));
346 count++;
347 if (count == dots) {
348 col.push_back(fmt::format(notation, ".."));
349 }
350 }
351 return col;
352}
353
354vector<string> stringColumn(string name, const vector<string>& comp,
355 int rows, int width)
356{
357 // extract data for processing
358 vector<string> data;
359 string notation = fmt::format("{{:{}}}", width);
360 size_t maxLen = 3; // minimum column width is 3
361 int csize = static_cast<int>(comp.size());
362 int dots = csize + 1;
363 if (csize <= rows) {
364 for (const auto& val : comp) {
365 data.push_back(val);
366 maxLen = std::max(maxLen,
367 boost::trim_copy(fmt::format(notation, val)).size());
368 }
369 } else {
370 dots = (rows + 1) / 2;
371 for (int row = 0; row < dots; row++) {
372 data.push_back(comp[row]);
373 maxLen = std::max(maxLen,
374 boost::trim_copy(fmt::format(notation, comp[row])).size());
375 }
376 for (int row = csize - rows / 2; row < csize; row++) {
377 data.push_back(comp[row]);
378 maxLen = std::max(maxLen,
379 boost::trim_copy(fmt::format(notation, comp[row])).size());
380 }
381 }
382
383 // assemble output
384 notation = fmt::format(" {{:>{}}}", maxLen);
385 vector<string> col = {fmt::format(notation, name)};
386 int count = 0;
387 for (const auto& val : data) {
388 col.push_back(fmt::format(notation, val));
389 count++;
390 if (count == dots) {
391 col.push_back(fmt::format(notation, "..."));
392 }
393 }
394 return col;
395}
396
397vector<string> formatColumn(string name, const AnyValue& comp, int rows, int width)
398{
399 if (comp.isVector<double>()) {
400 return doubleColumn(name, comp.asVector<double>(), rows, width);
401 }
402 if (comp.isVector<long int>()) {
403 return integerColumn(name, comp.asVector<long int>(), rows, width);
404 }
405 if (comp.isVector<string>()) {
406 return stringColumn(name, comp.asVector<string>(), rows, width);
407 }
408
409 // create alternative representation
410 string repr;
411 int size;
412 if (comp.isVector<vector<double>>()) {
413 repr = "[ <double> ]";
414 size = len(comp.asVector<vector<double>>());
415 } else if (comp.isVector<vector<long int>>()) {
416 repr = "[ <long int> ]";
417 size = len(comp.asVector<vector<long int>>());
418 } else if (comp.isVector<vector<string>>()) {
419 repr = "[ <string> ]";
420 size = len(comp.asVector<vector<string>>());
421 } else {
422 throw CanteraError(
423 "formatColumn", "Encountered invalid data for component '{}'.", name);
424 }
425 size_t maxLen = std::max(repr.size(), name.size());
426
427 // assemble output
428 string notation = fmt::format(" {{:>{}}}", maxLen);
429 repr = fmt::format(notation, repr);
430 vector<string> col = {fmt::format(notation, name)};
431 if (size <= rows) {
432 for (int row = 0; row < size; row++) {
433 col.push_back(repr);
434 }
435 } else {
436 int dots = (rows + 1) / 2;
437 for (int row = 0; row < dots; row++) {
438 col.push_back(repr);
439 }
440 col.push_back(fmt::format(notation, "..."));
441 for (int row = size - rows / 2; row < size; row++) {
442 col.push_back(repr);
443 }
444 }
445 return col;
446}
447
448} // end unnamed namespace
449
450 string SolutionArray::info(const vector<string>& keys, int rows, int width)
451{
452 fmt::memory_buffer b;
453 int col_width = 12; // targeted maximum column width
454 vector<string> components;
455 if (keys.size()) {
456 components = keys;
457 } else {
458 components = componentNames();
459 }
460 try {
461 // build columns
462 vector<long int> index;
463 for (const auto ix : m_active) {
464 index.push_back(ix);
465 }
466 vector<vector<string>> cols = {integerColumn("", index, rows, col_width)};
467 vector<vector<string>> tail; // trailing columns in reverse order
468 size_t size = cols.back().size();
469
470 // assemble columns fitting within a maximum width; if this width is exceeded,
471 // a "..." separator is inserted close to the center. Accordingly, the matrix
472 // needs to be assembled from two halves.
473 int front = 0;
474 int back = len(components) - 1;
475 int fLen = len(cols.back()[0]);
476 int bLen = 0;
477 int sep = 5; // separator width
478 bool done = false;
479 while (!done && front <= back) {
480 string key;
481 while (bLen + sep <= fLen && front <= back) {
482 // add trailing columns
483 key = components[back];
484 auto col = formatColumn(key, getComponent(key), rows, col_width);
485 if (len(col[0]) + fLen + bLen + sep > width) {
486 done = true;
487 break;
488 }
489 tail.push_back(col);
490 bLen += len(tail.back()[0]);
491 back--;
492 }
493 if (done || front > back) {
494 break;
495 }
496 while (fLen <= bLen + sep && front <= back) {
497 // add leading columns
498 key = components[front];
499 auto col = formatColumn(key, getComponent(key), rows, col_width);
500 if (len(col[0]) + fLen + bLen + sep > width) {
501 done = true;
502 break;
503 }
504 cols.push_back(col);
505 fLen += len(cols.back()[0]);
506 front++;
507 }
508 }
509 if (cols.size() + tail.size() < components.size() + 1) {
510 // add separator
511 cols.push_back(vector<string>(size + 1, " ..."));
512 }
513 // copy trailing columns
514 cols.insert(cols.end(), tail.rbegin(), tail.rend());
515
516 // assemble formatted output
517 for (size_t row = 0; row < size; row++) {
518 for (const auto& col : cols) {
519 fmt_append(b, col[row]);
520 }
521 fmt_append(b, "\n");
522 }
523
524 // add size information
525 fmt_append(b, "\n[{} rows x {} components; state='{}']",
526 m_size, components.size(), m_sol->thermo()->nativeMode());
527
528 } catch (CanteraError& err) {
529 return to_string(b) + err.what();
530 }
531 return to_string(b);
532}
533
534shared_ptr<ThermoPhase> SolutionArray::thermo()
535{
536 return m_sol->thermo();
537}
538
539vector<string> SolutionArray::componentNames() const
540{
541 vector<string> components;
542 // leading auxiliary components
543 int pos = 0;
544 while (m_order->count(pos)) {
545 components.push_back(m_order->at(pos));
546 pos++;
547 }
548
549 // state information
550 auto phase = m_sol->thermo();
551 for (auto code : phase->nativeMode()) {
552 string name = string(1, code);
553 if (name == "X" || name == "Y") {
554 for (auto& spc : phase->speciesNames()) {
555 components.push_back(spc);
556 }
557 } else {
558 components.push_back(name);
559 }
560 }
561
562 // trailing auxiliary components
563 pos = -1;
564 while (m_order->count(pos)) {
565 components.push_back(m_order->at(pos));
566 pos--;
567 }
568
569 return components;
570}
571
572 void SolutionArray::addExtra(const string& name, bool back)
573{
574 if (m_extra->count(name)) {
575 throw CanteraError("SolutionArray::addExtra",
576 "Component '{}' already exists.", name);
577 }
578 (*m_extra)[name] = AnyValue();
579 if (back) {
580 if (m_order->size()) {
581 // add name at end of back components
582 m_order->emplace(m_order->begin()->first - 1, name);
583 } else {
584 // first name after state components
585 m_order->emplace(-1, name);
586 }
587 } else {
588 if (m_order->size()) {
589 // insert name at end of front components
590 m_order->emplace(m_order->rbegin()->first + 1, name);
591 } else {
592 // name in leading position
593 m_order->emplace(0, name);
594 }
595 }
596}
597
598vector<string> SolutionArray::listExtra(bool all) const
599{
600 vector<string> names;
601 int pos = 0;
602 while (m_order->count(pos)) {
603 const auto& name = m_order->at(pos);
604 if (all || !m_extra->at(name).is<void>()) {
605 names.push_back(name);
606 }
607 pos++;
608 }
609
610 // trailing auxiliary components
611 pos = -1;
612 while (m_order->count(pos)) {
613 const auto& name = m_order->at(pos);
614 if (all || !m_extra->at(name).is<void>()) {
615 names.push_back(name);
616 }
617 pos--;
618 }
619 return names;
620}
621
622 bool SolutionArray::hasComponent(const string& name) const
623{
624 if (m_extra->count(name)) {
625 // auxiliary data
626 return true;
627 }
628 if (m_sol->thermo()->speciesIndex(name) != npos) {
629 // species
630 return true;
631 }
632 if (name == "X" || name == "Y") {
633 // reserved names
634 return false;
635 }
636 // native state
637 return (m_sol->thermo()->nativeState().count(name));
638}
639
640 AnyValue SolutionArray::getComponent(const string& name) const
641{
642 if (!hasComponent(name)) {
643 throw CanteraError("SolutionArray::getComponent",
644 "Unknown component '{}'.", name);
645 }
646
647 AnyValue out;
648 if (m_extra->count(name)) {
649 // extra component
650 const auto& extra = m_extra->at(name);
651 if (extra.is<void>()) {
652 return AnyValue();
653 }
654 if (m_size == m_dataSize) {
655 return extra; // slicing not necessary
656 }
657 if (extra.isVector<long int>()) {
658 return getSingle<long int>(extra, m_active);
659 }
660 if (extra.isVector<double>()) {
661 return getSingle<double>(extra, m_active);
662 }
663 if (extra.isVector<string>()) {
664 return getSingle<string>(extra, m_active);
665 }
666 if (extra.isVector<vector<double>>()) {
667 return getMulti<double>(extra, m_active);
668 }
669 if (extra.isVector<vector<long int>>()) {
670 return getMulti<long int>(extra, m_active);
671 }
672 if (extra.isVector<vector<string>>()) {
673 return getMulti<string>(extra, m_active);
674 }
675 throw NotImplementedError("SolutionArray::getComponent",
676 "Unable to get sliced data for component '{}' with type '{}'.",
677 name, extra.type_str());
678 }
679
680 // component is part of state information
681 vector<double> data(m_size);
682 size_t ix = m_sol->thermo()->speciesIndex(name);
683 if (ix == npos) {
684 // state other than species
685 ix = m_sol->thermo()->nativeState()[name];
686 } else {
687 // species information
688 ix += m_stride - m_sol->thermo()->nSpecies();
689 }
690 for (size_t k = 0; k < m_size; ++k) {
691 data[k] = (*m_data)[m_active[k] * m_stride + ix];
692 }
693 out = data;
694 return out;
695}
696
697 bool isSimpleVector(const AnyValue& any) {
698 return any.isVector<double>() || any.isVector<long int>() ||
699 any.isVector<string>() || any.isVector<bool>() ||
700 any.isVector<vector<double>>() || any.isVector<vector<long int>>() ||
701 any.isVector<vector<string>>() || any.isVector<vector<bool>>();
702}
703
704 void SolutionArray::setComponent(const string& name, const AnyValue& data)
705{
706 if (!hasComponent(name)) {
707 throw CanteraError("SolutionArray::setComponent",
708 "Unknown component '{}'.", name);
709 }
710 if (m_extra->count(name)) {
711 _setExtra(name, data);
712 return;
713 }
714
715 size_t size = data.vectorSize();
716 if (size == npos) {
717 throw CanteraError("SolutionArray::setComponent",
718 "Invalid type of component '{}': expected simple array type, "
719 "but received '{}'.", name, data.type_str());
720 }
721 if (size != m_size) {
722 throw CanteraError("SolutionArray::setComponent",
723 "Invalid size of component '{}': expected size {} but received {}.",
724 name, m_size, size);
725 }
726
727 auto& vec = data.asVector<double>();
728 size_t ix = m_sol->thermo()->speciesIndex(name);
729 if (ix == npos) {
730 ix = m_sol->thermo()->nativeState()[name];
731 } else {
732 ix += m_stride - m_sol->thermo()->nSpecies();
733 }
734 for (size_t k = 0; k < m_size; ++k) {
735 (*m_data)[m_active[k] * m_stride + ix] = vec[k];
736 }
737}
738
739 void SolutionArray::setLoc(int loc, bool restore)
740{
741 size_t loc_ = static_cast<size_t>(loc);
742 if (m_size == 0) {
743 throw CanteraError("SolutionArray::setLoc",
744 "Unable to set location in empty SolutionArray.");
745 } else if (loc < 0) {
746 if (m_loc == npos) {
747 throw CanteraError("SolutionArray::setLoc",
748 "Both current and buffered indices are invalid.");
749 }
750 return;
751 } else if (static_cast<size_t>(m_active[loc_]) == m_loc) {
752 return;
753 } else if (loc_ >= m_size) {
754 throw IndexError("SolutionArray::setLoc", "indices", loc_, m_size - 1);
755 }
756 m_loc = static_cast<size_t>(m_active[loc_]);
757 if (restore) {
758 size_t nState = m_sol->thermo()->stateSize();
759 m_sol->thermo()->restoreState(nState, m_data->data() + m_loc * m_stride);
760 }
761}
762
763 void SolutionArray::updateState(int loc)
764{
765 setLoc(loc, false);
766 size_t nState = m_sol->thermo()->stateSize();
767 m_sol->thermo()->saveState(nState, m_data->data() + m_loc * m_stride);
768}
769
770vector<double> SolutionArray::getState(int loc)
771{
772 setLoc(loc);
773 size_t nState = m_sol->thermo()->stateSize();
774 vector<double> out(nState);
775 m_sol->thermo()->saveState(out); // thermo contains current state
776 return out;
777}
778
779 void SolutionArray::setState(int loc, const vector<double>& state)
780{
781 size_t nState = m_sol->thermo()->stateSize();
782 if (state.size() != nState) {
783 throw CanteraError("SolutionArray::setState",
784 "Expected array to have length {}, but received an array of length {}.",
785 nState, state.size());
786 }
787 setLoc(loc, false);
788 m_sol->thermo()->restoreState(state);
789 m_sol->thermo()->saveState(nState, m_data->data() + m_loc * m_stride);
790}
791
792 void SolutionArray::normalize() {
793 auto phase = m_sol->thermo();
794 auto nativeState = phase->nativeState();
795 if (nativeState.size() < 3) {
796 return;
797 }
798 size_t nState = phase->stateSize();
799 vector<double> out(nState);
800 if (nativeState.count("Y")) {
801 size_t offset = nativeState["Y"];
802 for (int loc = 0; loc < static_cast<int>(m_size); loc++) {
803 setLoc(loc, true); // set location and restore state
804 phase->setMassFractions(m_data->data() + m_loc * m_stride + offset);
805 m_sol->thermo()->saveState(out);
806 setState(loc, out);
807 }
808 } else if (nativeState.count("X")) {
809 size_t offset = nativeState["X"];
810 for (int loc = 0; loc < static_cast<int>(m_size); loc++) {
811 setLoc(loc, true); // set location and restore state
812 phase->setMoleFractions(m_data->data() + m_loc * m_stride + offset);
813 m_sol->thermo()->saveState(out);
814 setState(loc, out);
815 }
816 } else {
817 throw NotImplementedError("SolutionArray::normalize",
818 "Not implemented for mode '{}'.", phase->nativeMode());
819 }
820}
821
822 AnyMap SolutionArray::getAuxiliary(int loc)
823{
824 setLoc(loc);
825 AnyMap out;
826 for (const auto& [key, extra] : *m_extra) {
827 if (extra.is<void>()) {
828 out[key] = extra;
829 } else if (extra.isVector<long int>()) {
830 out[key] = extra.asVector<long int>()[m_loc];
831 } else if (extra.isVector<double>()) {
832 out[key] = extra.asVector<double>()[m_loc];
833 } else if (extra.isVector<string>()) {
834 out[key] = extra.asVector<string>()[m_loc];
835 } else if (extra.isVector<vector<long int>>()) {
836 out[key] = extra.asVector<vector<long int>>()[m_loc];
837 } else if (extra.isVector<vector<double>>()) {
838 out[key] = extra.asVector<vector<double>>()[m_loc];
839 } else if (extra.isVector<vector<string>>()) {
840 out[key] = extra.asVector<vector<string>>()[m_loc];
841 } else {
842 throw NotImplementedError("SolutionArray::getAuxiliary",
843 "Unable to retrieve data for component '{}' with type '{}'.",
844 key, extra.type_str());
845 }
846 }
847 return out;
848}
849
850 void SolutionArray::setAuxiliary(int loc, const AnyMap& data)
851{
852 setLoc(loc, false);
853 for (const auto& [name, value] : data) {
854 if (!m_extra->count(name)) {
855 throw CanteraError("SolutionArray::setAuxiliary",
856 "Unknown auxiliary component '{}'.", name);
857 }
858 auto& extra = m_extra->at(name);
859 if (extra.is<void>()) {
860 if (m_dataSize > 1) {
861 throw CanteraError("SolutionArray::setAuxiliary",
862 "Unable to set location for type '{}': "
863 "component is not initialized.", name);
864 }
865 _initExtra(name, value);
866 _resizeExtra(name);
867 }
868 try {
869 if (extra.isVector<long int>()) {
870 setAuxiliarySingle<long int>(m_loc, extra, value);
871 } else if (extra.isVector<double>()) {
872 setAuxiliarySingle<double>(m_loc, extra, value);
873 } else if (extra.isVector<string>()) {
874 setAuxiliarySingle<string>(m_loc, extra, value);
875 } else if (extra.isVector<vector<long int>>()) {
876 setAuxiliaryMulti<long int>(m_loc, extra, value);
877 } else if (extra.isVector<vector<double>>()) {
878 setAuxiliaryMulti<double>(m_loc, extra, value);
879 } else if (extra.isVector<vector<string>>()) {
880 setAuxiliaryMulti<string>(m_loc, extra, value);
881 } else {
882 throw CanteraError("SolutionArray::setAuxiliary",
883 "Unable to set entry for type '{}'.", extra.type_str());
884 }
885 } catch (CanteraError& err) {
886 // make failed type conversions traceable
887 throw CanteraError("SolutionArray::setAuxiliary",
888 "Encountered incompatible value for component '{}':\n{}",
889 name, err.getMessage());
890 }
891 }
892}
893
894 AnyMap preamble(const string& desc)
895{
896 AnyMap data;
897 if (desc.size()) {
898 data["description"] = desc;
899 }
900 data["generator"] = "Cantera SolutionArray";
901 data["cantera-version"] = CANTERA_VERSION;
902 // escape commit to ensure commits are read correctly from YAML
903 // example: prevent '3491027e7' from being converted to an integer
904 data["git-commit"] = "'" + gitCommit() + "'";
905
906 // Add a timestamp indicating the current time
907 time_t aclock;
908 ::time(&aclock); // Get time in seconds
909 struct tm* newtime = localtime(&aclock); // Convert time to struct tm form
910 data["date"] = stripnonprint(asctime(newtime));
911
912 // Force metadata fields to the top of the file
913 if (data.hasKey("description")) {
914 data["description"].setLoc(-6, 0);
915 }
916 data["generator"].setLoc(-5, 0);
917 data["cantera-version"].setLoc(-4, 0);
918 data["git-commit"].setLoc(-3, 0);
919 data["date"].setLoc(-2, 0);
920
921 return data;
922}
923
924AnyMap& openField(AnyMap& root, const string& name)
925{
926 if (!name.size()) {
927 return root;
928 }
929
930 // locate field based on 'name'
931 vector<string> tokens;
932 tokenizePath(name, tokens);
933 AnyMap* ptr = &root; // use raw pointer to avoid copying
934 string path = "";
935 for (auto& field : tokens) {
936 path += "/" + field;
937 AnyMap& sub = *ptr;
938 if (sub.hasKey(field) && !sub[field].is<AnyMap>()) {
939 throw CanteraError("openField",
940 "Encountered invalid existing field '{}'.", path);
941 } else if (!sub.hasKey(field)) {
942 sub[field] = AnyMap();
943 }
944 ptr = &sub[field].as<AnyMap>();
945 }
946 return *ptr;
947}
948
949 void SolutionArray::writeHeader(const string& fname, const string& name,
950 const string& desc, bool overwrite)
951{
952 Storage file(fname, true);
953 if (file.checkGroup(name, true)) {
954 if (!overwrite) {
955 throw CanteraError("SolutionArray::writeHeader",
956 "Group name '{}' exists; use 'overwrite' argument to overwrite.", name);
957 }
958 file.deleteGroup(name);
959 file.checkGroup(name, true);
960 }
961 file.writeAttributes(name, preamble(desc));
962}
963
964 void SolutionArray::writeHeader(AnyMap& root, const string& name,
965 const string& desc, bool overwrite)
966{
967 AnyMap& data = openField(root, name);
968 if (!data.empty() && !overwrite) {
969 throw CanteraError("SolutionArray::writeHeader",
970 "Field name '{}' exists; use 'overwrite' argument to overwrite.", name);
971 }
972 data.update(preamble(desc));
973}
974
975 void SolutionArray::writeEntry(const string& fname, bool overwrite, const string& basis)
976{
977 if (apiNdim() != 1) {
978 throw CanteraError("SolutionArray::writeEntry",
979 "Tabular output of CSV data only works for 1D SolutionArray objects.");
980 }
981 set<string> speciesNames;
982 for (const auto& species : m_sol->thermo()->speciesNames()) {
983 speciesNames.insert(species);
984 }
985 bool mole;
986 if (basis == "") {
987 const auto& nativeState = m_sol->thermo()->nativeState();
988 mole = nativeState.find("X") != nativeState.end();
989 } else if (basis == "X" || basis == "mole") {
990 mole = true;
991 } else if (basis == "Y" || basis == "mass") {
992 mole = false;
993 } else {
994 throw CanteraError("SolutionArray::writeEntry",
995 "Invalid species basis '{}'.", basis);
996 }
997
998 auto names = componentNames();
999 size_t last = names.size() - 1;
1000 vector<AnyValue> components;
1001 vector<bool> isSpecies;
1002 fmt::memory_buffer header;
1003 for (const auto& key : names) {
1004 string label = key;
1005 size_t col;
1006 if (speciesNames.find(key) == speciesNames.end()) {
1007 // Pre-read component vectors
1008 isSpecies.push_back(false);
1009 components.emplace_back(getComponent(key));
1010 col = components.size() - 1;
1011 if (!components[col].isVector<double>() &&
1012 !components[col].isVector<long int>() &&
1013 !components[col].isVector<string>())
1014 {
1015 throw CanteraError("SolutionArray::writeEntry",
1016 "Multi-dimensional column '{}' is not supported for CSV output.",
1017 key);
1018 }
1019 } else {
1020 // Delay reading species data as base can be either mole or mass
1021 isSpecies.push_back(true);
1022 components.emplace_back(AnyValue());
1023 col = components.size() - 1;
1024 if (mole) {
1025 label = "X_" + label;
1026 } else {
1027 label = "Y_" + label;
1028 }
1029 }
1030 if (label.find("\"") != string::npos || label.find("\n") != string::npos) {
1031 throw NotImplementedError("SolutionArray::writeEntry",
1032 "Detected column name containing double quotes or line feeds: '{}'.",
1033 label);
1034 }
1035 string sep = (col == last) ? "" : ",";
1036 if (label.find(",") != string::npos) {
1037 fmt_append(header, "\"{}\"{}", label, sep);
1038 } else {
1039 fmt_append(header, "{}{}", label, sep);
1040 }
1041 }
1042
1043 // (Most) potential exceptions have been thrown; start writing data to file
1044 if (std::ifstream(fname).good()) {
1045 if (!overwrite) {
1046 throw CanteraError("SolutionArray::writeEntry",
1047 "File '{}' already exists; use option 'overwrite' to replace CSV file.",
1048 fname);
1049 }
1050 std::remove(fname.c_str());
1051 }
1052 std::ofstream output(fname);
1053 output << to_string(header) << std::endl;
1054
1055 size_t maxLen = npos;
1056 vector<double> buf(speciesNames.size(), 0.);
1057 for (int row = 0; row < static_cast<int>(m_size); row++) {
1058 fmt::memory_buffer line;
1059 if (maxLen != npos) {
1060 line.reserve(maxLen);
1061 }
1062 setLoc(row);
1063 if (mole) {
1064 m_sol->thermo()->getMoleFractions(buf.data());
1065 } else {
1066 m_sol->thermo()->getMassFractions(buf.data());
1067 }
1068
1069 size_t idx = 0;
1070 for (size_t col = 0; col < components.size(); col++) {
1071 string sep = (col == last) ? "" : ",";
1072 if (isSpecies[col]) {
1073 fmt_append(line, "{:.9g}{}", buf[idx++], sep);
1074 } else {
1075 auto& data = components[col];
1076 if (data.isVector<double>()) {
1077 fmt_append(line, "{:.9g}{}", data.asVector<double>()[row], sep);
1078 } else if (data.isVector<long int>()) {
1079 fmt_append(line, "{}{}", data.asVector<long int>()[row], sep);
1080 } else if (data.isVector<bool>()) {
1081 fmt_append(line, "{}{}",
1082 static_cast<bool>(data.asVector<bool>()[row]), sep);
1083 } else {
1084 auto value = data.asVector<string>()[row];
1085 if (value.find("\"") != string::npos ||
1086 value.find("\n") != string::npos)
1087 {
1088 throw NotImplementedError("SolutionArray::writeEntry",
1089 "Detected value containing double quotes or line feeds: "
1090 "'{}'", value);
1091 }
1092 if (value.find(",") != string::npos) {
1093 fmt_append(line, "\"{}\"{}", value, sep);
1094 } else {
1095 fmt_append(line, "{}{}", value, sep);
1096 }
1097 }
1098 }
1099 }
1100 output << to_string(line) << std::endl;
1101 maxLen = std::max(maxLen, line.size());
1102 }
1103}
1104
1105 void SolutionArray::writeEntry(const string& fname, const string& name,
1106 const string& sub, bool overwrite, int compression)
1107{
1108 if (name == "") {
1109 throw CanteraError("SolutionArray::writeEntry",
1110 "Group name specifying root location must not be empty.");
1111 }
1112 if (m_size < m_dataSize) {
1113 throw NotImplementedError("SolutionArray::writeEntry",
1114 "Unable to save sliced data.");
1115 }
1116 Storage file(fname, true);
1117 if (compression) {
1118 file.setCompressionLevel(compression);
1119 }
1120 string path = name;
1121 if (sub != "") {
1122 path += "/" + sub;
1123 } else {
1124 path += "/data";
1125 }
1126 if (file.checkGroup(path, true)) {
1127 if (!overwrite) {
1128 throw CanteraError("SolutionArray::writeEntry",
1129 "Group name '{}' exists; use 'overwrite' argument to overwrite.", name);
1130 }
1131 file.deleteGroup(path);
1132 file.checkGroup(path, true);
1133 }
1134 file.writeAttributes(path, m_meta);
1135 AnyMap more;
1136 if (apiNdim() == 1) {
1137 more["size"] = int(m_dataSize);
1138 } else {
1139 more["api-shape"] = m_apiShape;
1140 }
1141 more["components"] = componentNames();
1142 file.writeAttributes(path, more);
1143 if (!m_dataSize) {
1144 return;
1145 }
1146
1147 const auto& nativeState = m_sol->thermo()->nativeState();
1148 size_t nSpecies = m_sol->thermo()->nSpecies();
1149 for (auto& [key, offset] : nativeState) {
1150 if (key == "X" || key == "Y") {
1151 vector<vector<double>> prop;
1152 for (size_t i = 0; i < m_size; i++) {
1153 size_t first = offset + i * m_stride;
1154 prop.emplace_back(m_data->begin() + first,
1155 m_data->begin() + first + nSpecies);
1156 }
1157 AnyValue data;
1158 data = prop;
1159 file.writeData(path, key, data);
1160 } else {
1161 auto data = getComponent(key);
1162 file.writeData(path, key, data);
1163 }
1164 }
1165
1166 for (const auto& [key, value] : *m_extra) {
1167 if (isSimpleVector(value)) {
1168 file.writeData(path, key, value);
1169 } else if (value.is<void>()) {
1170 // skip unintialized component
1171 } else {
1172 throw NotImplementedError("SolutionArray::writeEntry",
1173 "Unable to save component '{}' with data type {}.",
1174 key, value.type_str());
1175 }
1176 }
1177}
1178
1179 void SolutionArray::writeEntry(AnyMap& root, const string& name, const string& sub,
1180 bool overwrite)
1181{
1182 if (name == "") {
1183 throw CanteraError("SolutionArray::writeEntry",
1184 "Field name specifying root location must not be empty.");
1185 }
1186 if (m_size < m_dataSize) {
1187 throw NotImplementedError("SolutionArray::writeEntry",
1188 "Unable to save sliced data.");
1189 }
1190 string path = name;
1191 if (sub != "") {
1192 path += "/" + sub;
1193 } else {
1194 path += "/data";
1195 }
1196 AnyMap& data = openField(root, path);
1197 bool preexisting = !data.empty();
1198 if (preexisting && !overwrite) {
1199 throw CanteraError("SolutionArray::writeEntry",
1200 "Field name '{}' exists; use 'overwrite' argument to overwrite.", name);
1201 }
1202 if (apiNdim() == 1) {
1203 data["size"] = int(m_dataSize);
1204 } else {
1205 data["api-shape"] = m_apiShape;
1206 }
1207 data.update(m_meta);
1208
1209 for (auto& [key, value] : *m_extra) {
1210 data[key] = value;
1211 }
1212
1213 auto phase = m_sol->thermo();
1214 if (m_size == 1) {
1215 setLoc(0);
1216 data["temperature"] = phase->temperature();
1217 data["pressure"] = phase->pressure();
1218 auto surf = std::dynamic_pointer_cast<SurfPhase>(phase);
1219 auto nSpecies = phase->nSpecies();
1220 vector<double> values(nSpecies);
1221 if (surf) {
1222 surf->getCoverages(&values[0]);
1223 } else {
1224 phase->getMassFractions(&values[0]);
1225 }
1226 AnyMap items;
1227 for (size_t k = 0; k < nSpecies; k++) {
1228 if (values[k] != 0.0) {
1229 items[phase->speciesName(k)] = values[k];
1230 }
1231 }
1232 if (surf) {
1233 data["coverages"] = std::move(items);
1234 } else {
1235 data["mass-fractions"] = std::move(items);
1236 }
1237 } else if (m_size > 1) {
1238 const auto& nativeState = phase->nativeState();
1239 for (auto& [key, offset] : nativeState) {
1240 if (key == "X" || key == "Y") {
1241 for (auto& spc : phase->speciesNames()) {
1242 data[spc] = getComponent(spc);
1243 }
1244 data["basis"] = key == "X" ? "mole" : "mass";
1245 } else {
1246 data[key] = getComponent(key);
1247 }
1248 }
1249 data["components"] = componentNames();
1250 }
1251
1252 // If this is not replacing an existing solution, put it at the end
1253 if (!preexisting) {
1254 data.setLoc(INT_MAX, 0);
1255 }
1256}
1257
1258 void SolutionArray::append(const vector<double>& state, const AnyMap& extra)
1259{
1260 if (apiNdim() > 1) {
1261 throw NotImplementedError("SolutionArray::append",
1262 "Unable to append multi-dimensional arrays.");
1263 }
1264
1265 int pos = size();
1266 resize(pos + 1);
1267 try {
1268 setState(pos, state);
1269 setAuxiliary(pos, extra);
1270 } catch (CanteraError& err) {
1271 // undo resize and rethrow error
1272 resize(pos);
1273 throw CanteraError("SolutionArray::append", err.getMessage());
1274 }
1275}
1276
1277 void SolutionArray::save(const string& fname, const string& name, const string& sub,
1278 const string& desc, bool overwrite, int compression,
1279 const string& basis)
1280{
1281 if (m_size < m_dataSize) {
1282 throw NotImplementedError("SolutionArray::save",
1283 "Unable to save sliced data.");
1284 }
1285 size_t dot = fname.find_last_of(".");
1286 string extension = (dot != npos) ? toLowerCopy(fname.substr(dot + 1)) : "";
1287 if (extension == "csv") {
1288 if (name != "") {
1289 warn_user("SolutionArray::save",
1290 "Parameter 'name' not used for CSV output.");
1291 }
1292 writeEntry(fname, overwrite, basis);
1293 return;
1294 }
1295 if (basis != "") {
1296 warn_user("SolutionArray::save",
1297 "Argument 'basis' is not used for HDF or YAML output.", basis);
1298 }
1299 if (extension == "h5" || extension == "hdf" || extension == "hdf5") {
1300 writeHeader(fname, name, desc, overwrite);
1301 writeEntry(fname, name, sub, true, compression);
1302 return;
1303 }
1304 if (extension == "yaml" || extension == "yml") {
1305 // Check for an existing file and load it if present
1306 AnyMap data;
1307 if (std::ifstream(fname).good()) {
1308 data = AnyMap::fromYamlFile(fname);
1309 }
1310 writeHeader(data, name, desc, overwrite);
1311 writeEntry(data, name, sub, true);
1312
1313 // Write the output file and remove the now-outdated cached file
1314 std::ofstream out(fname);
1315 out << data.toYamlString();
1316 AnyMap::clearCachedFile(fname);
1317 return;
1318 }
1319 throw CanteraError("SolutionArray::save",
1320 "Unknown file extension '{}'.", extension);
1321}
1322
1323 AnyMap SolutionArray::readHeader(const string& fname, const string& name)
1324{
1325 Storage file(fname, false);
1326 file.checkGroup(name);
1327 return file.readAttributes(name, false);
1328}
1329
1330 const AnyMap& locateField(const AnyMap& root, const string& name)
1331{
1332 if (!name.size()) {
1333 return root;
1334 }
1335
1336 // locate field based on 'name'
1337 vector<string> tokens;
1338 tokenizePath(name, tokens);
1339 const AnyMap* ptr = &root; // use raw pointer to avoid copying
1340 string path = "";
1341 for (auto& field : tokens) {
1342 path += "/" + field;
1343 const AnyMap& sub = *ptr;
1344 if (!sub.hasKey(field) || !sub[field].is<AnyMap>()) {
1345 throw CanteraError("SolutionArray::locateField",
1346 "No field or solution with name '{}'.", path);
1347 }
1348 ptr = &sub[field].as<AnyMap>();
1349 }
1350 return *ptr;
1351}
1352
1353 AnyMap SolutionArray::readHeader(const AnyMap& root, const string& name)
1354{
1355 auto sub = locateField(root, name);
1356 AnyMap header;
1357 for (const auto& [key, value] : sub) {
1358 if (!sub[key].is<AnyMap>()) {
1359 header[key] = value;
1360 }
1361 }
1362 return header;
1363}
1364
1365 AnyMap SolutionArray::restore(const string& fname,
1366 const string& name, const string& sub)
1367{
1368 size_t dot = fname.find_last_of(".");
1369 string extension = (dot != npos) ? toLowerCopy(fname.substr(dot + 1)) : "";
1370 AnyMap header;
1371 if (extension == "csv") {
1372 throw NotImplementedError("SolutionArray::restore",
1373 "CSV import not implemented; if using Python, data can be imported via "
1374 "'read_csv' instead.");
1375 }
1376 if (extension == "h5" || extension == "hdf" || extension == "hdf5") {
1377 readEntry(fname, name, sub);
1378 header = readHeader(fname, name);
1379 } else if (extension == "yaml" || extension == "yml") {
1380 const AnyMap& root = AnyMap::fromYamlFile(fname);
1381 readEntry(root, name, sub);
1382 header = readHeader(root, name);
1383 } else {
1384 throw CanteraError("SolutionArray::restore",
1385 "Unknown file extension '{}'; supported extensions include "
1386 "'h5'/'hdf'/'hdf5' and 'yml'/'yaml'.", extension);
1387 }
1388 return header;
1389}
1390
1391 void SolutionArray::_initExtra(const string& name, const AnyValue& value)
1392{
1393 if (!m_extra->count(name)) {
1394 throw CanteraError("SolutionArray::_initExtra",
1395 "Component '{}' does not exist.", name);
1396 }
1397 auto& extra = (*m_extra)[name];
1398 if (!extra.is<void>()) {
1399 throw CanteraError("SolutionArray::_initExtra",
1400 "Component '{}' is already initialized.", name);
1401 }
1402 try {
1403 if (value.is<long int>()) {
1404 extra = vector<long int>(m_dataSize, value.as<long int>());
1405 } else if (value.is<double>()) {
1406 extra = vector<double>(m_dataSize, value.as<double>());
1407 } else if (value.is<string>()) {
1408 extra = vector<string>(m_dataSize, value.as<string>());
1409 } else if (value.isVector<long int>()) {
1410 extra = vector<vector<long int>>(m_dataSize, value.asVector<long int>());
1411 } else if (value.isVector<double>()) {
1412 extra = vector<vector<double>>(m_dataSize, value.asVector<double>());
1413 } else if (value.isVector<string>()) {
1414 extra = vector<vector<string>>(m_dataSize, value.asVector<string>());
1415 } else if (value.is<void>()) {
1416 // placeholder for unknown type; settable in setComponent
1417 extra = AnyValue();
1418 } else {
1419 throw NotImplementedError("SolutionArray::_initExtra",
1420 "Unable to initialize component '{}' with type '{}'.",
1421 name, value.type_str());
1422 }
1423 } catch (CanteraError& err) {
1424 // make failed type conversions traceable
1425 throw CanteraError("SolutionArray::_initExtra",
1426 "Encountered incompatible value for initializing component '{}':\n{}",
1427 name, err.getMessage());
1428 }
1429}
1430
1431 void SolutionArray::_resizeExtra(const string& name, const AnyValue& value)
1432{
1433 if (!m_extra->count(name)) {
1434 throw CanteraError("SolutionArray::_resizeExtra",
1435 "Component '{}' does not exist.", name);
1436 }
1437 auto& extra = (*m_extra)[name];
1438 if (extra.is<void>()) {
1439 // cannot resize placeholder (uninitialized component)
1440 return;
1441 }
1442
1443 try {
1444 if (extra.isVector<long int>()) {
1445 resizeSingle<long int>(extra, m_dataSize, value);
1446 } else if (extra.isVector<double>()) {
1447 resizeSingle<double>(extra, m_dataSize, value);
1448 } else if (extra.isVector<string>()) {
1449 resizeSingle<string>(extra, m_dataSize, value);
1450 } else if (extra.isVector<vector<double>>()) {
1451 resizeMulti<double>(extra, m_dataSize, value);
1452 } else if (extra.isVector<vector<long int>>()) {
1453 resizeMulti<long int>(extra, m_dataSize, value);
1454 } else if (extra.isVector<vector<string>>()) {
1455 resizeMulti<string>(extra, m_dataSize, value);
1456 } else {
1457 throw NotImplementedError("SolutionArray::_resizeExtra",
1458 "Unable to resize using type '{}'.", extra.type_str());
1459 }
1460 } catch (CanteraError& err) {
1461 // make failed type conversions traceable
1462 throw CanteraError("SolutionArray::_resizeExtra",
1463 "Encountered incompatible value for resizing component '{}':\n{}",
1464 name, err.getMessage());
1465 }
1466}
1467
1468 void SolutionArray::_setExtra(const string& name, const AnyValue& data)
1469{
1470 if (!m_extra->count(name)) {
1471 throw CanteraError("SolutionArray::_setExtra",
1472 "Extra component '{}' does not exist.", name);
1473 }
1474
1475 auto& extra = m_extra->at(name);
1476 if (data.is<void>() && m_size == m_dataSize) {
1477 // reset placeholder
1478 extra = AnyValue();
1479 return;
1480 }
1481
1482 // uninitialized component
1483 if (extra.is<void>()) {
1484 if (m_size != m_dataSize) {
1485 throw CanteraError("SolutionArray::_setExtra",
1486 "Unable to replace '{}' for sliced data.", name);
1487 }
1488 if (data.vectorSize() == m_dataSize || data.matrixShape().first == m_dataSize) {
1489 // assign entire component
1490 extra = data;
1491 return;
1492 }
1493 // data contains default value
1494 if (m_dataSize == 0 && (data.isScalar() || data.vectorSize() > 0)) {
1495 throw CanteraError("SolutionArray::_setExtra",
1496 "Unable to initialize '{}' with non-empty values when SolutionArray is "
1497 "empty.", name);
1498 }
1499 if (m_dataSize && data.vectorSize() == 0) {
1500 throw CanteraError("SolutionArray::_setExtra",
1501 "Unable to initialize '{}' with empty array when SolutionArray is not "
1502 "empty." , name);
1503 }
1504 _initExtra(name, data);
1505 _resizeExtra(name, data);
1506 return;
1507 }
1508
1509 if (data.is<long int>()) {
1510 setScalar<long int>(extra, data, m_active);
1511 } else if (data.is<double>()) {
1512 setScalar<double>(extra, data, m_active);
1513 } else if (data.is<string>()) {
1514 setScalar<string>(extra, data, m_active);
1515 } else if (data.isVector<long int>()) {
1516 setSingle<long int>(extra, data, m_active);
1517 } else if (data.isVector<double>()) {
1518 setSingle<double>(extra, data, m_active);
1519 } else if (data.isVector<string>()) {
1520 setSingle<string>(extra, data, m_active);
1521 } else if (data.isVector<vector<long int>>()) {
1522 setMulti<long int>(extra, data, m_active);
1523 } else if (data.isVector<vector<double>>()) {
1524 setMulti<double>(extra, data, m_active);
1525 } else if (data.isVector<vector<string>>()) {
1526 setMulti<string>(extra, data, m_active);
1527 } else {
1528 throw NotImplementedError("SolutionArray::_setExtra",
1529 "Unable to set sliced data for component '{}' with type '{}'.",
1530 name, data.type_str());
1531 }
1532}
1533
1534 string SolutionArray::_detectMode(const set<string>& names, bool native)
1535{
1536 // check set of available names against state acronyms defined by Phase::fullStates
1537 string mode = "";
1538 const auto& nativeState = m_sol->thermo()->nativeState();
1539 bool usesNativeState = false;
1540 auto surf = std::dynamic_pointer_cast<SurfPhase>(m_sol->thermo());
1541 bool found;
1542 for (const auto& item : m_sol->thermo()->fullStates()) {
1543 found = true;
1544 string name;
1545 usesNativeState = true;
1546 for (size_t i = 0; i < item.size(); i++) {
1547 // pick i-th letter from "full" state acronym
1548 name = string(1, item[i]);
1549 if (surf && (name == "X" || name == "Y")) {
1550 // override native state to enable detection of surface phases
1551 name = "C";
1552 usesNativeState = false;
1553 break;
1554 }
1555 if (names.count(name)) {
1556 // property is stored using letter acronym
1557 usesNativeState &= nativeState.count(name) > 0;
1558 } else if (aliasMap.count(name) && names.count(aliasMap.at(name))) {
1559 // property is stored using property name
1560 usesNativeState &= nativeState.count(name) > 0;
1561 } else {
1562 found = false;
1563 break;
1564 }
1565 }
1566 if (found) {
1567 mode = (name == "C") ? item.substr(0, 2) + "C" : item;
1568 break;
1569 }
1570 }
1571 if (!found) {
1572 if (surf && names.count("T") && names.count("X") && names.count("density")) {
1573 // Legacy HDF format erroneously uses density (Cantera < 3.0)
1574 return "legacySurf";
1575 }
1576 if (names.count("mass-flux") && names.count("mass-fractions")) {
1577 // Legacy YAML format stores incomplete state information (Cantera < 3.0)
1578 return "legacyInlet";
1579 }
1580 throw CanteraError("SolutionArray::_detectMode",
1581 "Detected incomplete thermodynamic information. Full states for a '{}' "
1582 "phase\nmay be defined by the following modes:\n'{}'\n"
1583 "Available data are: '{}'", m_sol->thermo()->type(),
1584 ba::join(m_sol->thermo()->fullStates(), "', '"), ba::join(names, "', '"));
1585 }
1586 if (usesNativeState && native) {
1587 return "native";
1588 }
1589 return mode;
1590}
1591
1592set<string> SolutionArray::_stateProperties(
1593 const string& mode, bool alias)
1594{
1595 set<string> states;
1596 if (mode == "native") {
1597 for (const auto& [name, offset] : m_sol->thermo()->nativeState()) {
1598 states.insert(alias ? aliasMap.at(name) : name);
1599 }
1600 } else {
1601 for (const auto& m : mode) {
1602 const string name = string(1, m);
1603 states.insert(alias ? aliasMap.at(name) : name);
1604 }
1605 }
1606
1607 return states;
1608}
1609
1610 string getName(const set<string>& names, const string& name)
1611{
1612 if (names.count(name)) {
1613 return name;
1614 }
1615 const auto& alias = aliasMap.at(name);
1616 if (names.count(alias)) {
1617 return alias;
1618 }
1619 return name; // let exception be thrown elsewhere
1620}
1621
1622 void SolutionArray::readEntry(const string& fname, const string& name,
1623 const string& sub)
1624{
1625 Storage file(fname, false);
1626 if (name == "") {
1627 throw CanteraError("SolutionArray::readEntry",
1628 "Group name specifying root location must not be empty.");
1629 }
1630 string path = name;
1631 if (sub != "" && file.checkGroup(name + "/" + sub, true)) {
1632 path += "/" + sub;
1633 } else if (sub == "" && file.checkGroup(name + "/data", true)) {
1634 // default data location
1635 path += "/data";
1636 }
1637 if (!file.checkGroup(path)) {
1638 throw CanteraError("SolutionArray::readEntry",
1639 "Group name specifying data entry is empty.");
1640 }
1641 m_extra->clear();
1642 auto [size, names] = file.contents(path);
1643 m_meta = file.readAttributes(path, true);
1644 if (m_meta.hasKey("size")) {
1645 // one-dimensional array
1646 resize(m_meta["size"].as<long int>());
1647 m_meta.erase("size");
1648 } else if (m_meta.hasKey("api-shape")) {
1649 // API uses multiple dimensions to interpret C++ SolutionArray
1650 setApiShape(m_meta["api-shape"].asVector<long int>());
1651 m_meta.erase("api-shape");
1652 } else {
1653 // legacy format; size needs to be detected
1654 resize(static_cast<int>(size));
1655 }
1656
1657 if (m_size == 0) {
1658 return;
1659 }
1660
1661 // determine storage mode of state data
1662 string mode = _detectMode(names);
1663 set<string> states = _stateProperties(mode);
1664 if (states.count("C")) {
1665 if (names.count("X")) {
1666 states.erase("C");
1667 states.insert("X");
1668 mode = mode.substr(0, 2) + "X";
1669 } else if (names.count("Y")) {
1670 states.erase("C");
1671 states.insert("Y");
1672 mode = mode.substr(0, 2) + "Y";
1673 }
1674 }
1675
1676 // restore state data
1677 size_t nSpecies = m_sol->thermo()->nSpecies();
1678 size_t nState = m_sol->thermo()->stateSize();
1679 const auto& nativeStates = m_sol->thermo()->nativeState();
1680 if (mode == "native") {
1681 // native state can be written directly into data storage
1682 for (const auto& [name, offset] : nativeStates) {
1683 if (name == "X" || name == "Y") {
1684 AnyValue data;
1685 data = file.readData(path, name, m_size, nSpecies);
1686 auto prop = data.asVector<vector<double>>();
1687 for (size_t i = 0; i < m_dataSize; i++) {
1688 std::copy(prop[i].begin(), prop[i].end(),
1689 m_data->data() + offset + i * m_stride);
1690 }
1691 } else {
1692 AnyValue data;
1693 data = file.readData(path, getName(names, name), m_dataSize, 0);
1694 setComponent(name, data);
1695 }
1696 }
1697 } else if (mode == "TPX") {
1698 AnyValue data;
1699 data = file.readData(path, getName(names, "T"), m_dataSize, 0);
1700 vector<double> T = std::move(data.asVector<double>());
1701 data = file.readData(path, getName(names, "P"), m_dataSize, 0);
1702 vector<double> P = std::move(data.asVector<double>());
1703 data = file.readData(path, "X", m_dataSize, nSpecies);
1704 vector<vector<double>> X = std::move(data.asVector<vector<double>>());
1705 for (size_t i = 0; i < m_dataSize; i++) {
1706 m_sol->thermo()->setMoleFractions_NoNorm(X[i].data());
1707 m_sol->thermo()->setState_TP(T[i], P[i]);
1708 m_sol->thermo()->saveState(nState, m_data->data() + i * m_stride);
1709 }
1710 } else if (mode == "TDX") {
1711 AnyValue data;
1712 data = file.readData(path, getName(names, "T"), m_dataSize, 0);
1713 vector<double> T = std::move(data.asVector<double>());
1714 data = file.readData(path, getName(names, "D"), m_dataSize, 0);
1715 vector<double> D = std::move(data.asVector<double>());
1716 data = file.readData(path, "X", m_dataSize, nSpecies);
1717 vector<vector<double>> X = std::move(data.asVector<vector<double>>());
1718 for (size_t i = 0; i < m_dataSize; i++) {
1719 m_sol->thermo()->setMoleFractions_NoNorm(X[i].data());
1720 m_sol->thermo()->setState_TD(T[i], D[i]);
1721 m_sol->thermo()->saveState(nState, m_data->data() + i * m_stride);
1722 }
1723 } else if (mode == "TPY") {
1724 AnyValue data;
1725 data = file.readData(path, getName(names, "T"), m_dataSize, 0);
1726 vector<double> T = std::move(data.asVector<double>());
1727 data = file.readData(path, getName(names, "P"), m_dataSize, 0);
1728 vector<double> P = std::move(data.asVector<double>());
1729 data = file.readData(path, "Y", m_dataSize, nSpecies);
1730 vector<vector<double>> Y = std::move(data.asVector<vector<double>>());
1731 for (size_t i = 0; i < m_dataSize; i++) {
1732 m_sol->thermo()->setMassFractions_NoNorm(Y[i].data());
1733 m_sol->thermo()->setState_TP(T[i], P[i]);
1734 m_sol->thermo()->saveState(nState, m_data->data() + i * m_stride);
1735 }
1736 } else if (mode == "legacySurf") {
1737 // erroneous TDX mode (should be TPX or TPY) - Sim1D (Cantera 2.5)
1738 AnyValue data;
1739 data = file.readData(path, getName(names, "T"), m_dataSize, 0);
1740 vector<double> T = std::move(data.asVector<double>());
1741 data = file.readData(path, "X", m_dataSize, nSpecies);
1742 vector<vector<double>> X = std::move(data.asVector<vector<double>>());
1743 for (size_t i = 0; i < m_dataSize; i++) {
1744 m_sol->thermo()->setMoleFractions_NoNorm(X[i].data());
1745 m_sol->thermo()->setTemperature(T[i]);
1746 m_sol->thermo()->saveState(nState, m_data->data() + i * m_stride);
1747 }
1748 warn_user("SolutionArray::readEntry",
1749 "Detected legacy HDF format with incomplete state information\nfor name "
1750 "'{}' (pressure missing).", path);
1751 } else if (mode == "") {
1752 throw CanteraError("SolutionArray::readEntry",
1753 "Data are not consistent with full state modes.");
1754 } else {
1755 throw NotImplementedError("SolutionArray::readEntry",
1756 "Import of '{}' data is not supported.", mode);
1757 }
1758
1759 // restore remaining data
1760 if (m_meta.hasKey("components")) {
1761 const auto& components = m_meta["components"].asVector<string>();
1762 bool back = false;
1763 for (const auto& name : components) {
1764 if (hasComponent(name) || name == "X" || name == "Y") {
1765 back = true;
1766 } else {
1767 addExtra(name, back);
1768 AnyValue data;
1769 data = file.readData(path, name, m_dataSize);
1770 setComponent(name, data);
1771 }
1772 }
1773 m_meta.erase("components");
1774 } else {
1775 // data format used by Python h5py export (Cantera 2.5)
1776 warn_user("SolutionArray::readEntry", "Detected legacy HDF format.");
1777 for (const auto& name : names) {
1778 if (!hasComponent(name) && name != "X" && name != "Y") {
1779 addExtra(name);
1780 AnyValue data;
1781 data = file.readData(path, name, m_dataSize);
1782 setComponent(name, data);
1783 }
1784 }
1785 }
1786}
1787
1788 void SolutionArray::readEntry(const AnyMap& root, const string& name, const string& sub)
1789{
1790 if (name == "") {
1791 throw CanteraError("SolutionArray::readEntry",
1792 "Field name specifying root location must not be empty.");
1793 }
1794 auto path = locateField(root, name);
1795 if (path.hasKey("generator") && sub != "") {
1796 // read entry from subfolder (since Cantera 3.0)
1797 path = locateField(root, name + "/" + sub);
1798 } else if (sub == "" && path.hasKey("data")) {
1799 // default data location
1800 path = locateField(root, name + "/data");
1801 }
1802
1803 // set size and initialize
1804 long size = 0;
1805 if (path.hasKey("size")) {
1806 // one-dimensional array
1807 resize(path["size"].asInt());
1808 } else if (path.hasKey("api-shape")) {
1809 // API uses multiple dimensions to interpret C++ SolutionArray
1810 auto& shape = path["api-shape"].asVector<long int>();
1811 setApiShape(shape);
1812 } else {
1813 // legacy format (Cantera 2.6)
1814 size = path.getInt("points", 0);
1815 if (!path.hasKey("T") && !path.hasKey("temperature")) {
1816 // overwrite size - Sim1D erroneously assigns '1'
1817 size = (long)0;
1818 }
1819 resize(static_cast<int>(size));
1820 }
1821 m_extra->clear();
1822
1823 // restore data
1824 set<string> exclude = {"size", "api-shape", "points", "X", "Y"};
1825 set<string> names = path.keys();
1826 size_t nState = m_sol->thermo()->stateSize();
1827 if (m_dataSize == 0) {
1828 // no data points
1829 } else if (m_dataSize == 1) {
1830 // single data point
1831 string mode = _detectMode(names, false);
1832 if (mode == "TPY") {
1833 double T = path[getName(names, "T")].asDouble();
1834 double P = path[getName(names, "P")].asDouble();
1835 auto Y = path["mass-fractions"].asMap<double>();
1836 m_sol->thermo()->setState_TPY(T, P, Y);
1837 } else if (mode == "TPC") {
1838 auto surf = std::dynamic_pointer_cast<SurfPhase>(m_sol->thermo());
1839 double T = path[getName(names, "T")].asDouble();
1840 double P = path["pressure"].asDouble();
1841 m_sol->thermo()->setState_TP(T, P);
1842 auto cov = path["coverages"].asMap<double>();
1843 surf->setCoveragesByName(cov);
1844 } else if (mode == "legacyInlet") {
1845 // missing property - Sim1D (Cantera 2.6)
1846 mode = "TPY";
1847 double T = path[getName(names, "T")].asDouble();
1848 auto Y = path["mass-fractions"].asMap<double>();
1849 m_sol->thermo()->setState_TPY(T, m_sol->thermo()->pressure(), Y);
1850 warn_user("SolutionArray::readEntry",
1851 "Detected legacy YAML format with incomplete state information\n"
1852 "for name '{}' (pressure missing).", name + "/" + sub);
1853 } else if (mode == "") {
1854 throw CanteraError("SolutionArray::readEntry",
1855 "Data are not consistent with full state modes.");
1856 } else {
1857 throw NotImplementedError("SolutionArray::readEntry",
1858 "Import of '{}' data is not supported.", mode);
1859 }
1860 m_sol->thermo()->saveState(nState, m_data->data());
1861 auto props = _stateProperties(mode, true);
1862 exclude.insert(props.begin(), props.end());
1863 } else {
1864 // multiple data points
1865 if (path.hasKey("components")) {
1866 const auto& components = path["components"].asVector<string>();
1867 bool back = false;
1868 for (const auto& name : components) {
1869 if (hasComponent(name)) {
1870 back = true;
1871 } else {
1872 addExtra(name, back);
1873 }
1874 setComponent(name, path[name]);
1875 exclude.insert(name);
1876 }
1877 } else {
1878 // legacy YAML format does not provide for list of components
1879 for (const auto& [name, value] : path) {
1880 if (value.isVector<double>()) {
1881 const vector<double>& data = value.asVector<double>();
1882 if (data.size() == m_dataSize) {
1883 if (!hasComponent(name)) {
1884 addExtra(name);
1885 }
1886 setComponent(name, value);
1887 exclude.insert(name);
1888 }
1889 }
1890 }
1891 }
1892
1893 // check that state data are complete
1894 const auto& nativeState = m_sol->thermo()->nativeState();
1895 set<string> props;
1896 set<string> missingProps;
1897 for (const auto& [name, offset] : nativeState) {
1898 if (exclude.count(name)) {
1899 props.insert(name);
1900 } else {
1901 missingProps.insert(name);
1902 }
1903 }
1904
1905 set<string> TY = {"T", "Y"};
1906 if (props == TY && missingProps.count("D") && path.hasKey("pressure")) {
1907 // missing "D" - Sim1D (Cantera 2.6)
1908 double P = path["pressure"].asDouble();
1909 const size_t offset_T = nativeState.find("T")->second;
1910 const size_t offset_D = nativeState.find("D")->second;
1911 const size_t offset_Y = nativeState.find("Y")->second;
1912 for (size_t i = 0; i < m_dataSize; i++) {
1913 double T = (*m_data)[offset_T + i * m_stride];
1914 m_sol->thermo()->setState_TPY(
1915 T, P, m_data->data() + offset_Y + i * m_stride);
1916 (*m_data)[offset_D + i * m_stride] = m_sol->thermo()->density();
1917 }
1918 } else if (missingProps.size()) {
1919 throw CanteraError("SolutionArray::readEntry",
1920 "Incomplete state information: missing '{}'.",
1921 ba::join(missingProps, "', '"));
1922 }
1923 }
1924
1925 // add meta data
1926 for (const auto& [name, value] : path) {
1927 if (!exclude.count(name)) {
1928 m_meta[name] = value;
1929 }
1930 }
1931}
1932
1933 namespace { // restrict scope of helper functions to local translation unit
1934
1935 template<class T>
1936 AnyValue getSingle(const AnyValue& extra, const vector<int>& slice)
1937{
1938 vector<T> data(slice.size());
1939 const auto& vec = extra.asVector<T>();
1940 for (size_t k = 0; k < slice.size(); ++k) {
1941 data[k] = vec[slice[k]];
1942 }
1943 AnyValue out;
1944 out = data;
1945 return out;
1946}
1947
1948 template<class T>
1949AnyValue getMulti(const AnyValue& extra, const vector<int>& slice)
1950{
1951 vector<vector<T>> data(slice.size());
1952 const auto& vec = extra.asVector<vector<T>>();
1953 for (size_t k = 0; k < slice.size(); ++k) {
1954 data[k] = vec[slice[k]];
1955 }
1956 AnyValue out;
1957 out = data;
1958 return out;
1959}
1960
1961 template<class T>
1962 void setScalar(AnyValue& extra, const AnyValue& data, const vector<int>& slice)
1963{
1964 T value = data.as<T>();
1965 if (extra.isVector<T>()) {
1966 auto& vec = extra.asVector<T>();
1967 for (size_t k = 0; k < slice.size(); ++k) {
1968 vec[slice[k]] = value;
1969 }
1970 } else {
1971 throw CanteraError("SolutionArray::setScalar",
1972 "Incompatible input data: unable to assign '{}' data to '{}'.",
1973 data.type_str(), extra.type_str());
1974 }
1975}
1976
1977 template<class T>
1978 void setSingle(AnyValue& extra, const AnyValue& data, const vector<int>& slice)
1979{
1980 size_t size = slice.size();
1981 if (extra.vectorSize() == size && data.vectorSize() == size) {
1982 extra = data; // no slicing necessary; type can change
1983 return;
1984 }
1985 if (extra.matrixShape().first == size && data.vectorSize() == size) {
1986 extra = data; // no slicing necessary; shape and type can change
1987 return;
1988 }
1989 if (extra.type_str() != data.type_str()) {
1990 // do not allow changing of data type when slicing
1991 throw CanteraError("SolutionArray::setSingle",
1992 "Incompatible types: expected '{}' but received '{}'.",
1993 extra.type_str(), data.type_str());
1994 }
1995 const auto& vData = data.asVector<T>();
1996 if (vData.size() != size) {
1997 throw CanteraError("SolutionArray::setSingle",
1998 "Invalid input data size: expected {} entries but received {}.",
1999 size, vData.size());
2000 }
2001 auto& vec = extra.asVector<T>();
2002 for (size_t k = 0; k < size; ++k) {
2003 vec[slice[k]] = vData[k];
2004 }
2005}
2006
2007 template<class T>
2008 void setMulti(AnyValue& extra, const AnyValue& data, const vector<int>& slice)
2009{
2010 if (!data.isMatrix<T>()) {
2011 throw CanteraError("SolutionArray::setMulti",
2012 "Invalid input data shape: inconsistent number of columns.");
2013 }
2014 size_t size = slice.size();
2015 auto [rows, cols] = data.matrixShape();
2016 if (extra.matrixShape().first == size && rows == size) {
2017 extra = data; // no slicing necessary; type can change
2018 return;
2019 }
2020 if (extra.vectorSize() == size && rows == size) {
2021 extra = data; // no slicing necessary; shape and type can change
2022 return;
2023 }
2024 if (extra.type_str() != data.type_str()) {
2025 // do not allow changing of data type when slicing
2026 throw CanteraError("SolutionArray::setMulti",
2027 "Incompatible types: expected '{}' but received '{}'.",
2028 extra.type_str(), data.type_str());
2029 }
2030 if (rows != size) {
2031 throw CanteraError("SolutionArray::setMulti",
2032 "Invalid input data shape: expected {} rows but received {}.",
2033 size, rows);
2034 }
2035 if (extra.matrixShape().second != cols) {
2036 throw CanteraError("SolutionArray::setMulti",
2037 "Invalid input data shape: expected {} columns but received {}.",
2038 extra.matrixShape().second, cols);
2039 }
2040 const auto& vData = data.asVector<vector<T>>();
2041 auto& vec = extra.asVector<vector<T>>();
2042 for (size_t k = 0; k < slice.size(); ++k) {
2043 vec[slice[k]] = vData[k];
2044 }
2045}
2046
2047 template<class T>
2048 void resizeSingle(AnyValue& extra, size_t size, const AnyValue& value)
2049{
2050 T defaultValue;
2051 if (value.is<void>()) {
2052 defaultValue = vector<T>(1)[0];
2053 } else {
2054 defaultValue = value.as<T>();
2055 }
2056 extra.asVector<T>().resize(size, defaultValue);
2057}
2058
2059 template<class T>
2060 void resizeMulti(AnyValue& extra, size_t size, const AnyValue& value)
2061{
2062 vector<T> defaultValue;
2063 if (value.is<void>()) {
2064 defaultValue = vector<T>(extra.matrixShape().second);
2065 } else {
2066 defaultValue = value.as<vector<T>>();
2067 }
2068 extra.asVector<vector<T>>().resize(size, defaultValue);
2069}
2070
2071 template<class T>
2072 void resetSingle(AnyValue& extra, const vector<int>& slice)
2073{
2074 T defaultValue = vector<T>(1)[0];
2075 vector<T>& data = extra.asVector<T>();
2076 for (size_t k = 0; k < slice.size(); ++k) {
2077 data[slice[k]] = defaultValue;
2078 }
2079}
2080
2081 template<class T>
2082 void resetMulti(AnyValue& extra, const vector<int>& slice)
2083{
2084 vector<T> defaultValue = vector<T>(extra.matrixShape().second);
2085 vector<vector<T>>& data = extra.asVector<vector<T>>();
2086 for (size_t k = 0; k < slice.size(); ++k) {
2087 data[slice[k]] = defaultValue;
2088 }
2089}
2090
2091 template<class T>
2092 void setAuxiliarySingle(size_t loc, AnyValue& extra, const AnyValue& value)
2093{
2094 extra.asVector<T>()[loc] = value.as<T>();
2095}
2096
2097 template<class T>
2098 void setAuxiliaryMulti(size_t loc, AnyValue& extra, const AnyValue& data)
2099{
2100 const auto& value = data.asVector<T>();
2101 auto& vec = extra.asVector<vector<T>>();
2102 if (value.size() != vec[loc].size()) {
2103 throw CanteraError("SolutionArray::setAuxiliaryMulti",
2104 "New element size {} does not match existing column size {}.",
2105 value.size(), vec[loc].size());
2106 }
2107 vec[loc] = value;
2108}
2109
2110} // end unnamed namespace
2111
2112}
SolutionArray.h
Solution.h
Storage.h
SurfPhase.h
Header for a simple thermodynamics model of a surface phase derived from ThermoPhase,...
ThermoPhase.h
Header file for class ThermoPhase, the base class for phases with thermodynamic properties,...
Cantera::AnyBase::setLoc
void setLoc(int line, int column)
For values which are derived from an input file, set the line and column of this value in that file.
Definition AnyMap.cpp:574
Cantera::AnyMap
A map of string keys to values whose type can vary at runtime.
Definition AnyMap.h:427
Cantera::AnyMap::hasKey
bool hasKey(const string &key) const
Returns true if the map contains an item named key.
Definition AnyMap.cpp:1423
Cantera::AnyMap::empty
bool empty() const
Return boolean indicating whether AnyMap is empty.
Definition AnyMap.cpp:1418
Cantera::AnyMap::update
void update(const AnyMap &other, bool keepExisting=true)
Add items from other to this AnyMap.
Definition AnyMap.cpp:1438
Cantera::AnyValue
A wrapper for a variable whose type is determined at runtime.
Definition AnyMap.h:86
Cantera::AnyValue::isVector
bool isVector() const
Returns true if the held value is a vector of the specified type, such as vector<double>.
Definition AnyMap.inl.h:75
Cantera::AnyValue::matrixShape
pair< size_t, size_t > matrixShape() const
Returns rows and columns of a matrix.
Definition AnyMap.cpp:671
Cantera::AnyValue::vectorSize
size_t vectorSize() const
Returns size of the held vector.
Definition AnyMap.cpp:655
Cantera::AnyValue::isScalar
bool isScalar() const
Returns true if the held value is a scalar type (such as double, long int, string,...
Definition AnyMap.cpp:651
Cantera::AnyValue::is
bool is() const
Returns true if the held value is of the specified type.
Definition AnyMap.inl.h:68
Cantera::AnyValue::asVector
const vector< T > & asVector(size_t nMin=npos, size_t nMax=npos) const
Return the held value, if it is a vector of type T.
Definition AnyMap.inl.h:109
Cantera::AnyValue::as
const T & as() const
Get the value of this key as the specified type.
Definition AnyMap.inl.h:16
Cantera::AnyValue::type_str
string type_str() const
Returns a string specifying the type of the held value.
Definition AnyMap.cpp:643
Cantera::CanteraError
Base class for exceptions thrown by Cantera classes.
Definition ctexceptions.h:66
Cantera::CanteraError::what
const char * what() const override
Get a description of the error.
Definition ctexceptions.cpp:25
Cantera::CanteraError::getMessage
virtual string getMessage() const
Method overridden by derived classes to format the error message.
Definition ctexceptions.cpp:45
Cantera::IndexError
An array index is out of range.
Definition ctexceptions.h:165
Cantera::NotImplementedError
An error indicating that an unimplemented function has been called.
Definition ctexceptions.h:195
Cantera::Storage
A wrapper class handling storage to HDF.
Definition Storage.h:39
Cantera::Storage::contents
pair< size_t, set< string > > contents(const string &id) const
Retrieve contents of file from a specified location.
Definition Storage.cpp:609
Cantera::Storage::writeAttributes
void writeAttributes(const string &id, const AnyMap &meta)
Write attributes to a specified location.
Definition Storage.cpp:627
Cantera::Storage::deleteGroup
void deleteGroup(const string &id)
Delete group.
Definition Storage.cpp:603
Cantera::Storage::readAttributes
AnyMap readAttributes(const string &id, bool recursive) const
Read attributes from a specified location.
Definition Storage.cpp:621
Cantera::Storage::readData
AnyValue readData(const string &id, const string &name, size_t rows, size_t cols=npos) const
Read dataset from a specified location.
Definition Storage.cpp:633
Cantera::Storage::checkGroup
bool checkGroup(const string &id, bool permissive=false)
Check whether path location exists.
Definition Storage.cpp:597
Cantera::Storage::setCompressionLevel
void setCompressionLevel(int level)
Set compression level (0..9)
Definition Storage.cpp:585
Cantera::Storage::writeData
void writeData(const string &id, const string &name, const AnyValue &data)
Write dataset to a specified location.
Definition Storage.cpp:640
fmt_append
void fmt_append(fmt::memory_buffer &b, Args... args)
Versions 6.2.0 and 6.2.1 of fmtlib do not include this define before they include windows....
Definition fmt.h:29
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::toLowerCopy
string toLowerCopy(const string &input)
Convert to lower case.
Definition stringUtils.cpp:219
Cantera::len
U len(const T &container)
Get the size of a container, cast to a signed integer type.
Definition utilities.h:198
Cantera::dot
double dot(InputIter x_begin, InputIter x_end, InputIter2 y_begin)
Function that calculates a templated inner product.
Definition utilities.h:82
Cantera::warn_user
void warn_user(const string &method, const string &msg, const Args &... args)
Print a user warning raised from method as CanteraWarning.
Definition global.h:267
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
Cantera::offset
offset
Offsets of solution components in the 1D solution array.
Definition StFlow.h:24
stringUtils.h
Contains declarations for string manipulation functions within Cantera.
utilities.h
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...