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