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