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