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