Cantera  3.0.0
Loading...
Searching...
No Matches
AnyMap.cpp
Go to the documentation of this file.
1//! @file AnyMap.cpp
2
3// This file is part of Cantera. See License.txt in the top-level directory or
4// at https://cantera.org/license.txt for license and copyright information.
5
7#include "application.h"
8#include "cantera/base/yaml.h"
10#include "cantera/base/global.h"
12#include <boost/algorithm/string.hpp>
13#include <fstream>
14#include <mutex>
15#include <unordered_set>
16
17namespace ba = boost::algorithm;
18
19namespace { // helper functions
20
21std::mutex yaml_cache_mutex;
22std::mutex yaml_field_order_mutex;
23using namespace Cantera;
24
25bool isFloat(const string& val)
26{
27 // This function duplicates the logic of fpValueCheck, but doesn't throw
28 // exceptions if the string isn't a float
29 string str = ba::trim_copy(val);
30 if (str.empty()) {
31 return false;
32 }
33 int numDot = 0;
34 int numExp = 0;
35 int istart = 0;
36 int numDigit = 0;
37 char ch = str[0];
38 if (ch == '+' || ch == '-') {
39 istart = 1;
40 if (str.size() == 1) {
41 return false;
42 }
43 }
44 for (size_t i = istart; i < str.size(); i++) {
45 ch = str[i];
46 if (isdigit(ch)) {
47 numDigit++;
48 } else if (ch == '.') {
49 numDot++;
50 if (numDot > 1) {
51 return false;
52 }
53 if (numExp > 0) {
54 return false;
55 }
56 } else if (ch == 'e' || ch == 'E') {
57 numExp++;
58 if (numExp > 1 || numDigit == 0 || i == str.size() - 1) {
59 return false;
60 }
61 ch = str[i+1];
62 if (ch == '+' || ch == '-') {
63 if (i + 1 == str.size() - 1) {
64 return false;
65 }
66 i++;
67 }
68 } else {
69 return false;
70 }
71 }
72 return true;
73}
74
75bool isInt(const string& val)
76{
77 string str = ba::trim_copy(val);
78 if (str.empty()) {
79 return false;
80 }
81 int istart = 0;
82 char ch = str[0];
83 if (ch == '+' || ch == '-') {
84 istart = 1;
85 if (str.size() == 1) {
86 return false;
87 }
88 }
89 for (size_t i = istart; i < str.size(); i++) {
90 if (!isdigit(str[i])) {
91 return false;
92 }
93 }
94 return true;
95}
96
97bool isBool(const string& val) {
98 string str = ba::trim_copy(val);
99 return (val == "true" || val == "True" || val == "false" || val == "False");
100}
101
102enum class Type : char {
103 Unknown = 0,
104 Integer = 1,
105 Double = 2,
106 String = 4,
107 Bool = 8,
108 Map = 16,
109 Sequence = 32
110};
111
112Type operator|(Type lhs, Type rhs)
113{
114 return Type(static_cast<char>(lhs) | static_cast<char>(rhs));
115}
116
117Type elementTypes(const YAML::Node& node)
118{
119 // See what kinds of elements we have:
120 Type types = Type::Unknown;
121 for (const auto& el : node) {
122 if (el.IsMap()) {
123 types = types | Type::Map;
124 } else if (el.IsSequence()) {
125 types = types | Type::Sequence;
126 } else if (el.IsScalar()) {
127 string nodestr = el.as<string>();
128 if (isInt(nodestr)) {
129 types = types | Type::Integer;
130 } else if (isFloat(nodestr)) {
131 types = types | Type::Double;
132 } else if (isBool(nodestr)) {
133 types = types | Type::Bool;
134 } else {
135 types = types | Type::String;
136 }
137 }
138 }
139 return types;
140}
141
142long int getPrecision(const Cantera::AnyValue& precisionSource)
143{
144 long int precision = 15;
145 auto& userPrecision = precisionSource.getMetadata("precision");
146 if (userPrecision.is<long int>()) {
147 precision = userPrecision.asInt();
148 }
149 return precision;
150}
151
152string formatDouble(double x, long int precision)
153{
154 // This function ensures that trailing zeros resulting from round-off error
155 // are removed. Values are only rounded if at least three digits are removed,
156 // or the displayed value has multiple trailing zeros.
157 if (x == 0.0) {
158 return "0.0";
159 }
160
161 // Build string with full precision
162 bool useExp = std::abs(x) < 1e-2 || std::abs(x) >= 1e4;
163 int log10x = 0;
164 size_t last;
165 string s0;
166 if (useExp) {
167 s0 = fmt::format(fmt::format("{:.{}e}", x, precision));
168 // last digit of significand
169 last = s0.size() - 5;
170 if (s0[last + 1] == 'e') {
171 // pass - most values use four letter exponent (examples: e+05, e-03)
172 } else if (s0[last] == 'e') {
173 last--; // exponents larger than e+99 or smaller than e-99 (example: e+100)
174 } else {
175 last = s0.find('e') - 1; // backstop; slower, but will always work
176 }
177 } else {
178 log10x = static_cast<int>(std::floor(std::log10(std::abs(x))));
179 s0 = fmt::format("{:.{}f}", x, precision - log10x);
180 last = s0.size() - 1; // last digit
181 }
182 if (s0[last - 2] == '0' && s0[last - 1] == '0' && s0[last] < '5') {
183 // Value ending in '00x' and should be rounded down
184 } else if (s0[last - 2] == '9' && s0[last - 1] == '9' && s0[last] > '4') {
185 // Value ending in '99y' and should be rounded up
186 } else if (s0[last - 1] == '0' && s0[last] == '0') {
187 // Remove trailing zeros
188 } else {
189 // Value should not be rounded / do not round last digit
190 return s0;
191 }
192
193 // Remove trailing zeros
194 string s1;
195 if (s0[last - 1] == '0') {
196 s1 = s0; // Recycle original string
197 } else if (useExp) {
198 s1 = fmt::format(fmt::format("{:.{}e}", x, precision - 2));
199 } else {
200 s1 = fmt::format("{:.{}f}", x, precision - log10x - 2);
201 }
202 size_t digit = last - 2;
203 while (s1[digit] == '0' && s1[digit - 1] != '.') {
204 digit--;
205 }
206
207 // Assemble rounded value and return
208 if (useExp) {
209 size_t eloc = s1.find('e');
210 s0 = string(s1.begin() + eloc, s1.end());
211 }
212 s1 = string(s1.begin(), s1.begin() + digit + 1);
213 if (useExp) {
214 return s1 + s0;
215 }
216 return s1;
217}
218
219struct Quantity
220{
221 AnyValue value;
222 Units units;
223 bool isActivationEnergy;
224 AnyValue::unitConverter converter;
225
226 bool operator==(const Quantity& other) const {
227 return value == other.value && units == other.units
228 && isActivationEnergy == other.isActivationEnergy;
229 }
230};
231
233
234} // end anonymous namespace
235
236namespace YAML { // YAML converters
237
238using namespace Cantera;
239static const int max_line_length = 87;
240
241template<>
242struct convert<Cantera::AnyMap> {
243 static Node encode(const Cantera::AnyMap& rhs) {
244 throw NotImplementedError("AnyMap::encode");
245 }
246
247 static bool decode(const Node& node, Cantera::AnyMap& target) {
248 target.setLoc(node.Mark().line, node.Mark().column);
249 if (node.IsSequence()) {
250 // Convert a top-level list to a map with the key "items"
251 target["items"] = node.as<AnyValue>();
252 return true;
253 } else if (!node.IsMap()) {
254 string text = YAML::Dump(node);
255 if (text.size() > 300) {
256 text.resize(300);
257 }
258 throw CanteraError("YAML::convert<AnyMap>",
259 "YAML node is not a map. Node begins with:\n'''\n{}\n'''", text);
260 }
261 for (const auto& child : node) {
262 string key = child.first.as<string>();
263 const auto& loc = child.second.Mark();
264 AnyValue& value = target.createForYaml(key, loc.line, loc.column);
265 if (child.second.IsMap()) {
266 value = child.second.as<AnyMap>();
267 } else {
268 value = child.second.as<AnyValue>();
269 value.setKey(key);
270 }
271 }
272 return true;
273 }
274};
275
276YAML::Emitter& operator<<(YAML::Emitter& out, const AnyMap& rhs)
277{
278 bool flow = rhs.getBool("__flow__", false);
279 if (flow) {
280 out << YAML::Flow;
281 out << YAML::BeginMap;
282 size_t width = 15;
283 for (const auto& [name, value] : rhs.ordered()) {
284 string valueStr;
285 bool foundType = true;
286 if (value.is<double>()) {
287 valueStr = formatDouble(value.asDouble(), getPrecision(value));
288 } else if (value.is<string>()) {
289 valueStr = value.asString();
290 } else if (value.is<long int>()) {
291 valueStr = fmt::format("{}", value.asInt());
292 } else if (value.is<bool>()) {
293 valueStr = fmt::format("{}", value.asBool());
294 } else {
295 foundType = false;
296 }
297
298 if (foundType) {
299 // Check if this item will fit on the current line, including spaces
300 // for delimiters and whitespace. If not, wrap to the next line.
301 if (width + name.size() + valueStr.size() + 4 > max_line_length) {
302 out << YAML::Newline;
303 width = 15;
304 }
305 out << name;
306 out << valueStr;
307 width += name.size() + valueStr.size() + 4;
308 } else {
309 // Put items of an unknown (compound) type on a line alone
310 out << YAML::Newline;
311 out << name;
312 out << value;
313 width = 99; // Force newline after this item as well
314 }
315 }
316 } else {
317 out << YAML::BeginMap;
318 for (const auto& [key, value] : rhs.ordered()) {
319 out << key;
320 out << value;
321 }
322 }
323 out << YAML::EndMap;
324 return out;
325}
326
327//! Write YAML strings spanning multiple lines if input includes endline '\n'
328void emitString(YAML::Emitter& out, const string& str0) {
329 size_t endline = str0.rfind('\n');
330 if (endline == string::npos) {
331 out << str0;
332 return;
333 }
334
335 // Remove trailing line break
336 string str1 = str0;
337 if (endline == str1.size() - 1) {
338 str1.erase(endline, 1);
339 endline = str1.rfind('\n');
340 }
341
342 // Deblank lines (remove whitespace surrounding line breaks)
343 while (endline != string::npos) {
344 size_t len = 1;
345 while (str1[endline + len] == ' ') {
346 len++; // account for whitespace after line break
347 }
348 while (str1[endline - 1] == ' ') {
349 len++; // account for whitespace before line break
350 endline--;
351 }
352 if (len > 1) {
353 // remove surrounding whitespace
354 str1.replace(endline, len, "\n");
355 }
356 endline = str1.rfind('\n', endline - 1);
357 }
358 out << YAML::Literal << str1;
359}
360
361//! Write a vector in YAML "flow" style, wrapping lines to avoid exceeding the
362//! preferred maximum line length (set by `max_line_length`). Specialized for
363//! `vector<double>` to be able to use the custom `formatDouble` function with
364//! a given precision.
365void emitFlowVector(YAML::Emitter& out, const vector<double>& v, long int precision)
366{
367 out << YAML::Flow;
368 out << YAML::BeginSeq;
369 size_t width = 15; // wild guess, but no better value is available
370 for (auto& x : v) {
371 string xstr = formatDouble(x, precision);
372 // Wrap to the next line if this item would exceed the target line length
373 if (width + xstr.size() > max_line_length) {
374 out << YAML::Newline;
375 width = 15;
376 }
377 out << xstr;
378 width += xstr.size() + 2; // Update width including comma and space
379 }
380 out << YAML::EndSeq;
381}
382
383//! Write a vector in YAML "flow" style, wrapping lines to avoid exceeding the
384//! preferred maximum line length (set by `max_line_length`).
385template <typename T>
386void emitFlowVector(YAML::Emitter& out, const vector<T>& v)
387{
388 out << YAML::Flow;
389 out << YAML::BeginSeq;
390 size_t width = 15; // wild guess, but no better value is available
391 for (const T& x : v) {
392 string xstr = fmt::format("{}", x);
393 // Wrap to the next line if this item would exceed the target line length
394 if (width + xstr.size() > max_line_length) {
395 out << YAML::Newline;
396 width = 15;
397 }
398 out << xstr;
399 width += xstr.size() + 2;
400 }
401 out << YAML::EndSeq;
402}
403
404YAML::Emitter& operator<<(YAML::Emitter& out, const AnyValue& rhs)
405{
406 if (rhs.isScalar()) {
407 if (rhs.is<string>()) {
408 emitString(out, rhs.asString());
409 } else if (rhs.is<double>()) {
410 out << formatDouble(rhs.asDouble(), getPrecision(rhs));
411 } else if (rhs.is<long int>()) {
412 out << rhs.asInt();
413 } else if (rhs.is<bool>()) {
414 out << rhs.asBool();
415 } else {
416 throw CanteraError("operator<<(YAML::Emitter&, AnyValue&)",
417 "Don't know how to encode value of type '{}' with key '{}'",
418 rhs.type_str(), rhs.m_key);
419 }
420 } else if (rhs.is<AnyMap>()) {
421 out << rhs.as<AnyMap>();
422 } else if (rhs.is<vector<AnyMap>>()) {
423 out << rhs.asVector<AnyMap>();
424 } else if (rhs.is<vector<double>>()) {
425 emitFlowVector(out, rhs.asVector<double>(), getPrecision(rhs));
426 } else if (rhs.is<vector<string>>()) {
427 emitFlowVector(out, rhs.asVector<string>());
428 } else if (rhs.is<vector<long int>>()) {
429 emitFlowVector(out, rhs.asVector<long int>());
430 } else if (rhs.is<vector<bool>>()) {
431 emitFlowVector(out, rhs.asVector<bool>());
432 } else if (rhs.is<vector<Cantera::AnyValue>>()) {
433 out << rhs.asVector<Cantera::AnyValue>();
434 } else if (rhs.is<vector<vector<double>>>()) {
435 const auto& v = rhs.asVector<vector<double>>();
436 long int precision = getPrecision(rhs);
437 out << YAML::BeginSeq;
438 for (const auto& u : v) {
439 emitFlowVector(out, u, precision);
440 }
441 out << YAML::EndSeq;
442 } else if (rhs.is<vector<vector<string>>>()) {
443 const auto& v = rhs.asVector<vector<string>>();
444 out << YAML::BeginSeq;
445 for (const auto& u : v) {
446 emitFlowVector(out, u);
447 }
448 out << YAML::EndSeq;
449 } else if (rhs.is<vector<vector<long int>>>()) {
450 const auto& v = rhs.asVector<vector<long int>>();
451 out << YAML::BeginSeq;
452 for (const auto& u : v) {
453 emitFlowVector(out, u);
454 }
455 out << YAML::EndSeq;
456 } else if (rhs.is<vector<vector<bool>>>()) {
457 const auto& v = rhs.asVector<vector<bool>>();
458 out << YAML::BeginSeq;
459 for (const auto& u : v) {
460 emitFlowVector(out, u);
461 }
462 out << YAML::EndSeq;
463 } else {
464 throw CanteraError("operator<<(YAML::Emitter&, AnyValue&)",
465 "Don't know how to encode value of type '{}' with key '{}'",
466 rhs.type_str(), rhs.m_key);
467 }
468 return out;
469}
470
471
472template<>
473struct convert<Cantera::AnyValue> {
474 static Node encode(const Cantera::AnyValue& rhs) {
475 throw NotImplementedError("");
476 }
477
478 static bool decode(const Node& node, Cantera::AnyValue& target) {
479 target.setLoc(node.Mark().line, node.Mark().column);
480 if (node.IsScalar()) {
481 // Scalar nodes are int, doubles, or strings
482 string nodestr = node.as<string>();
483 if (node.Tag() == "!") {
484 // Prevent quoted strings from being implicitly converted to
485 // numeric types. For example, the quoted YAML string '12345' should not
486 // be interpreted as an integer
487 target = nodestr;
488 } else if (isInt(nodestr)) {
489 try {
490 target = node.as<long int>();
491 } catch (YAML::BadConversion&) {
492 // This exception is raised if the value doesn't fit in a
493 // long int, in which case we would rather store it
494 // (possibly inexactly) as a double.
495 target = node.as<double>();
496 }
497 } else if (isFloat(nodestr)) {
498 target = fpValue(nodestr);
499 } else if (isBool(nodestr)) {
500 target = node.as<bool>();
501 } else {
502 target = nodestr;
503 }
504 return true;
505 } else if (node.IsSequence()) {
506 // Convert sequences of the same element type to vectors of that type
507 Type types = elementTypes(node);
508 if (types == Type::Integer) {
509 target = node.as<vector<long int>>();
510 } else if (types == (Type::Integer | Type::Double) || types == Type::Double) {
511 vector<double> values;
512 for (const auto& elem : node) {
513 values.push_back(fpValue(elem.as<string>()));
514 }
515 target = std::move(values);
516 } else if (types == Type::String) {
517 target = node.as<vector<string>>();
518 } else if (types == Type::Bool) {
519 target = node.as<vector<bool>>();
520 } else if (types == Type::Map) {
521 target = node.as<vector<AnyMap>>();
522 } else if (types == Type::Sequence) {
523 // Create nested vectors when data types are compatible
524 Type subtypes = Type::Unknown;
525 for (const auto& el : node) {
526 subtypes = subtypes | elementTypes(el);
527 }
528 if (subtypes == Type::Integer) {
529 target = node.as<vector<vector<long int>>>();
530 } else if (subtypes == (Type::Integer | Type::Double) || subtypes == Type::Double) {
531 vector<vector<double>> values;
532 for (const auto& row : node) {
533 values.emplace_back();
534 for (const auto& value : row) {
535 values.back().push_back(fpValue(value.as<string>()));
536 }
537 }
538 target = std::move(values);
539 } else if (subtypes == Type::String) {
540 target = node.as<vector<vector<string>>>();
541 } else if (subtypes == Type::Bool) {
542 target = node.as<vector<vector<bool>>>();
543 } else {
544 target = node.as<vector<AnyValue>>();
545 }
546 } else {
547 // If types are different, create a vector of generic values
548 target = node.as<vector<AnyValue>>();
549 }
550 return true;
551 } else if (node.IsMap()) {
552 target = node.as<AnyMap>();
553 return true;
554 } else if (node.IsNull()) {
555 target = Empty;
556 return true;
557 }
558 return false;
559 }
560};
561
562}
563
564namespace Cantera {
565
566std::unordered_map<string,
567 pair<AnyMap, std::filesystem::file_time_type>> AnyMap::s_cache;
568
569std::unordered_map<string, vector<string>> AnyMap::s_headFields;
570std::unordered_map<string, vector<string>> AnyMap::s_tailFields;
571
572// Methods of class AnyBase
573
574void AnyBase::setLoc(int line, int column)
575{
576 m_line = line;
577 m_column = column;
578}
579
580const AnyValue& AnyBase::getMetadata(const string& key) const
581{
582 if (m_metadata && m_metadata->hasKey(key)) {
583 return m_metadata->at(key);
584 } else {
585 return Empty;
586 }
587}
588
589// Methods of class AnyValue
590
591AnyValue::AnyValue()
592 : m_equals(eq_comparer<size_t>)
593{}
594
595AnyValue::~AnyValue() = default;
596
597bool AnyValue::operator==(const AnyValue& other) const
598{
599 return m_equals(m_value, other.m_value);
600}
601
602bool AnyValue::operator!=(const AnyValue& other) const
603{
604 return !m_equals(m_value, other.m_value);
605}
606
608{
609 return as<AnyMap>()[key];
610}
611
612const AnyValue& AnyValue::operator[](const string& key) const
613{
614 return as<AnyMap>().at(key);
615}
616
617bool AnyValue::hasKey(const string& key) const {
618 return (is<AnyMap>() && as<AnyMap>().hasKey(key));
619}
620
621void AnyValue::setKey(const string &key) { m_key = key; }
622
623const std::type_info &AnyValue::type() const {
624 return m_value.type();
625}
626
627void AnyValue::propagateMetadata(shared_ptr<AnyMap>& metadata)
628{
629 m_metadata = metadata;
630 if (is<AnyMap>()) {
631 as<AnyMap>().propagateMetadata(m_metadata);
632 } else if (is<vector<AnyValue>>()) {
633 for (auto& item : asVector<AnyValue>()) {
634 item.propagateMetadata(m_metadata);
635 }
636 } else if (is<vector<AnyMap>>()) {
637 for (auto& item : asVector<AnyMap>()) {
638 item.propagateMetadata(m_metadata);
639 }
640 }
641}
642
643string AnyValue::type_str() const {
644 return demangle(type());
645}
646
647bool AnyValue::empty() const {
648 return !m_value.has_value();
649}
650
651bool AnyValue::isScalar() const {
652 return is<double>() || is<long int>() || is<string>() || is<bool>();
653}
654
655size_t AnyValue::vectorSize() const {
656 if (isVector<double>()) {
657 return as<vector<double>>().size();
658 }
659 if (isVector<long int>()) {
660 return as<vector<long int>>().size();
661 }
662 if (isVector<string>()) {
663 return as<vector<string>>().size();
664 }
665 if (isVector<bool>()) {
666 return as<vector<bool>>().size();
667 }
668 return npos;
669}
670
671pair<size_t, size_t> AnyValue::matrixShape() const {
672 if (isVector<vector<double>>()) {
673 auto& mat = as<vector<vector<double>>>();
674 if (isMatrix<double>()) {
675 if (mat.size()) {
676 return {mat.size(), mat[0].size()};
677 }
678 return {mat.size(), 0};
679 }
680 return {mat.size(), npos};
681 }
682 if (isVector<vector<long int>>()) {
683 auto& mat = as<vector<vector<long int>>>();
684 if (isMatrix<long int>()) {
685 if (mat.size()) {
686 return {mat.size(), mat[0].size()};
687 }
688 return {mat.size(), 0};
689 }
690 return {mat.size(), npos};
691 }
692 if (isVector<vector<string>>()) {
693 auto& mat = as<vector<vector<string>>>();
694 if (isMatrix<string>()) {
695 if (mat.size()) {
696 return {mat.size(), mat[0].size()};
697 }
698 return {mat.size(), 0};
699 }
700 return {mat.size(), npos};
701 }
702 if (isVector<vector<bool>>()) {
703 auto& mat = as<vector<vector<bool>>>();
704 if (isMatrix<bool>()) {
705 if (mat.size()) {
706 return {mat.size(), mat[0].size()};
707 }
708 return {mat.size(), 0};
709 }
710 return {mat.size(), npos};
711 }
712 return {npos, npos};
713}
714
715// Specializations for "string" and "const char*"
716
717AnyValue::AnyValue(const string& value)
718 : m_value{value}
719 , m_equals(eq_comparer<string>)
720{}
721
722AnyValue::AnyValue(const char* value)
723 : m_value{string(value)}
724 , m_equals(eq_comparer<string>)
725{}
726
727AnyValue &AnyValue::operator=(const string &value) {
728 m_value = value;
729 m_equals = eq_comparer<string>;
730 return *this;
731}
732
733AnyValue &AnyValue::operator=(const char *value) {
734 m_value = string(value);
735 m_equals = eq_comparer<string>;
736 return *this;
737}
738
739const string &AnyValue::asString() const {
740 return as<string>();
741}
742
743bool AnyValue::operator==(const string& other) const
744{
745 if (m_value.type() == typeid(string)) {
746 return std::any_cast<string>(m_value) == other;
747 } else {
748 return false;
749 }
750}
751
752bool AnyValue::operator!=(const string& other) const
753{
754 return !(*this == other);
755}
756
757bool operator==(const string& lhs, const AnyValue& rhs)
758{
759 return rhs == lhs;
760}
761
762bool operator!=(const string& lhs, const AnyValue& rhs)
763{
764 return rhs != lhs;
765}
766
767// Specialization for "Quantity"
768
769void AnyValue::setQuantity(double value, const string& units, bool is_act_energy) {
770 m_value = Quantity{AnyValue(value), Units(units), is_act_energy};
771 m_equals = eq_comparer<Quantity>;
772}
773
774void AnyValue::setQuantity(double value, const Units& units) {
775 m_value = Quantity{AnyValue(value), units, false};
776 m_equals = eq_comparer<Quantity>;
777}
778
779void AnyValue::setQuantity(const vector<double>& values, const string& units) {
780 AnyValue v;
781 v = values;
782 m_value = Quantity{v, Units(units), false};
783 m_equals = eq_comparer<Quantity>;
784}
785
786void AnyValue::setQuantity(const AnyValue& value, const unitConverter& converter)
787{
788 m_value = Quantity{value, Units(0.0), false, converter};
789 m_equals = eq_comparer<Quantity>;
790}
791
792template<>
793bool AnyValue::is<vector<double>>() const
794{
795 if (m_value.type() == typeid(vector<double>)) {
796 return true;
797 } else if (m_value.type() == typeid(vector<AnyValue>)) {
798 for (const auto& item : as<vector<AnyValue>>()) {
799 if (!(item.is<double>()
800 || (item.is<Quantity>() && item.as<Quantity>().value.is<double>())))
801 {
802 return false;
803 }
804 }
805 return true;
806 } else {
807 return false;
808 }
809}
810
811// Specializations for "double"
812
813AnyValue::AnyValue(double value)
814 : m_value{value}
815 , m_equals(eq_comparer<double>)
816{}
817
818AnyValue &AnyValue::operator=(double value) {
819 m_value = value;
820 m_equals = eq_comparer<double>;
821 return *this;
822}
823
825 return as<double>();
826}
827
828const double& AnyValue::asDouble() const {
829 return as<double>();
830}
831
832bool AnyValue::operator==(const double& other) const
833{
834 if (m_value.type() == typeid(double)) {
835 return std::any_cast<double>(m_value) == other;
836 } else if (m_value.type() == typeid(long int)) {
837 return std::any_cast<long int>(m_value) == other;
838 } else {
839 return false;
840 }
841}
842
843bool AnyValue::operator!=(const double& other) const
844{
845 return !(*this == other);
846}
847
848bool operator==(const double& lhs, const AnyValue& rhs)
849{
850 return rhs == lhs;
851}
852
853bool operator!=(const double& lhs, const AnyValue& rhs)
854{
855 return rhs != lhs;
856}
857
858// Specializations for "bool"
859
860AnyValue::AnyValue(bool value)
861 : m_value{value}
862 , m_equals(eq_comparer<bool>)
863{}
864
865AnyValue &AnyValue::operator=(bool value) {
866 m_value = value;
867 m_equals = eq_comparer<bool>;
868 return *this;
869}
870
872 return as<bool>();
873}
874
875const bool& AnyValue::asBool() const {
876 return as<bool>();
877}
878
879// Specializations for "long int" and "int"
880
881AnyValue::AnyValue(long int value)
882 : m_value{value}
883 , m_equals(eq_comparer<long int>)
884{}
885
886AnyValue::AnyValue(int value)
887 : m_value{static_cast<long int>(value)}
888 , m_equals(eq_comparer<long int>)
889{}
890
891AnyValue &AnyValue::operator=(long int value) {
892 m_value = value;
893 m_equals = eq_comparer<long int>;
894 return *this;
895}
896
897AnyValue &AnyValue::operator=(int value) {
898 m_value = static_cast<long int>(value);
899 m_equals = eq_comparer<long int>;
900 return *this;
901}
902
903long int& AnyValue::asInt() {
904 return as<long int>();
905}
906
907const long int& AnyValue::asInt() const {
908 return as<long int>();
909}
910
911bool AnyValue::operator==(const long int& other) const
912{
913 if (m_value.type() == typeid(long int)) {
914 return std::any_cast<long int>(m_value) == other;
915 } else if (m_value.type() == typeid(double)) {
916 return std::any_cast<double>(m_value) == other;
917 } else {
918 return false;
919 }
920}
921
922bool AnyValue::operator!=(const long int& other) const
923{
924 return !(*this == other);
925}
926
927bool AnyValue::operator==(const int& other) const
928{
929 return *this == static_cast<long int>(other);
930}
931
932bool AnyValue::operator!=(const int& other) const
933{
934 return *this != static_cast<long int>(other);
935}
936
937bool operator==(const long int& lhs, const AnyValue& rhs)
938{
939 return rhs == lhs;
940}
941
942bool operator!=(const long int& lhs, const AnyValue& rhs)
943{
944 return rhs != lhs;
945}
946
947bool operator==(const int& lhs, const AnyValue& rhs)
948{
949 return rhs == lhs;
950}
951
952bool operator!=(const int& lhs, const AnyValue& rhs)
953{
954 return rhs != lhs;
955}
956
957// Specializations for "AnyMap"
958
959AnyValue::AnyValue(const AnyMap& value)
960 : m_value{value}
961 , m_equals(eq_comparer<AnyMap>)
962{}
963
964AnyValue& AnyValue::operator=(const AnyMap& value) {
965 m_value = value;
966 m_equals = eq_comparer<AnyMap>;
967 return *this;
968}
969
970AnyValue& AnyValue::operator=(AnyMap&& value) {
971 m_value = std::move(value);
972 m_equals = eq_comparer<AnyMap>;
973 return *this;
974}
975
976std::unordered_map<string, const AnyMap*> AnyValue::asMap(const string& name) const
977{
978 std::unordered_map<string, const AnyMap*> mapped;
979 for (const auto& item : asVector<AnyMap>()) {
980 auto key = item[name].asString();
981 if (mapped.count(key)) {
982 throw InputFileError("AnyValue::asMap", *this,
983 "Duplicate key '{}'", key);
984 }
985 mapped.emplace(std::make_pair(key, &item));
986 }
987 return mapped;
988}
989
990std::unordered_map<string, AnyMap*> AnyValue::asMap(const string& name)
991{
992 std::unordered_map<string, AnyMap*> mapped;
993 for (auto& item : asVector<AnyMap>()) {
994 auto key = item.at(name).asString();
995 if (mapped.count(key)) {
996 throw InputFileError("AnyValue::asMap", *this,
997 "Duplicate key '{}'", key);
998 }
999 mapped.emplace(std::make_pair(key, &item));
1000 }
1001 return mapped;
1002}
1003
1004const AnyMap& AnyValue::getMapWhere(const string& key, const string& value) const
1005{
1006 if (is<vector<AnyMap>>()) {
1007 if (value == "") {
1008 return asVector<AnyMap>().at(0);
1009 }
1010 for (auto& item : asVector<AnyMap>()) {
1011 if (item.hasKey(key) && item[key] == value) {
1012 return item;
1013 }
1014 }
1015 throw InputFileError("AnyValue::getMapWhere", *this,
1016 "List does not contain a map where '{}' = '{}'", key, value);
1017 } else if (is<AnyMap>()) {
1018 if (value == "" || (hasKey(key) && as<AnyMap>()[key] == value)) {
1019 return as<AnyMap>();
1020 } else {
1021 throw InputFileError("AnyValue::getMapWhere", *this,
1022 "Map does not contain a key where '{}' = '{}'", key, value);
1023 }
1024 } else if (is<void>()) {
1025 throw InputFileError("AnyValue::getMapWhere", *this,
1026 "Key '{}' not found", m_key);
1027 } else {
1028 throw InputFileError("AnyValue::getMapWhere", *this,
1029 "Element is not a mapping or list of mappings.\n"
1030 "Looking for a mapping with key '{}' = '{}'", key, value);
1031 }
1032}
1033
1034AnyMap& AnyValue::getMapWhere(const string& key, const string& value, bool create)
1035{
1036 if (is<vector<AnyMap>>()) {
1037 if (value == "") {
1038 return asVector<AnyMap>().at(0);
1039 }
1040 for (auto& item : asVector<AnyMap>()) {
1041 if (item.hasKey(key) && item[key] == value) {
1042 return item;
1043 }
1044 }
1045 if (create) {
1046 // If the map wasn't found, insert it
1047 auto& vec = asVector<AnyMap>();
1048 AnyMap child;
1049 child[key] = value;
1050 vec.push_back(std::move(child));
1051 return vec.back();
1052 } else {
1053 throw InputFileError("AnyValue::getMapWhere", *this,
1054 "List does not contain a map where '{}' = '{}'", key, value);
1055 }
1056 } else if (is<AnyMap>()) {
1057 if (value == "" || (hasKey(key) && as<AnyMap>()[key] == value)) {
1058 return as<AnyMap>();
1059 } else if (create) {
1060 AnyMap newChild;
1061 newChild[key] = value;
1062 vector<AnyMap> nodes{std::move(as<AnyMap>()), std::move(newChild)};
1063 operator=(std::move(nodes));
1064 return asVector<AnyMap>().back();
1065 } else {
1066 throw InputFileError("AnyValue::getMapWhere", *this,
1067 "Map does not contain a key where '{}' = '{}'", key, value);
1068 }
1069 } else if (is<void>() && create) {
1070 AnyMap child;
1071 child[key] = value;
1072 operator=(std::move(child));
1073 return as<AnyMap>();
1074 } else if (is<void>()) {
1075 throw InputFileError("AnyValue::getMapWhere", *this,
1076 "Key '{}' not found", m_key);
1077 } else {
1078 throw InputFileError("AnyValue::getMapWhere", *this,
1079 "Element is not a mapping or list of mappings.\n"
1080 "Looking for a mapping with key '{}' = '{}'", key, value);
1081 }
1082}
1083
1084bool AnyValue::hasMapWhere(const string& key, const string& value) const
1085{
1086 if (is<vector<AnyMap>>()) {
1087 if (value == "") {
1088 return true;
1089 }
1090 for (auto& item : asVector<AnyMap>()) {
1091 if (item.hasKey(key) && item[key] == value) {
1092 return true;
1093 }
1094 }
1095 return false;
1096 } else if (is<AnyMap>()) {
1097 if (value == "" || (hasKey(key) && as<AnyMap>()[key] == value)) {
1098 return true;
1099 } else {
1100 return false;
1101 }
1102 } else {
1103 return false;
1104 }
1105}
1106
1107pair<int, int> AnyValue::order() const
1108{
1109 return {m_line, m_column};
1110}
1111
1112void AnyValue::applyUnits(shared_ptr<UnitSystem>& units)
1113{
1114 if (is<AnyMap>()) {
1115 AnyMap& m = as<AnyMap>();
1116
1117 if (m.getBool("__unconvertible__", false)) {
1118 AnyMap delta = units->getDelta(UnitSystem());
1119 if (delta.hasKey("length") || delta.hasKey("quantity")
1120 || delta.hasKey("time"))
1121 {
1122 throw CanteraError("AnyValue::applyUnits", "AnyMap contains values"
1123 " that cannot be converted to non-default unit systems\n(probably"
1124 " reaction rates not associated with a Kinetics object)");
1125 }
1126 }
1127 // Units declaration applicable to this map
1128 m.applyUnits(units);
1129 } else if (is<vector<AnyMap>>()) {
1130 auto& list = as<vector<AnyMap>>();
1131 if (list.size() && list[0].hasKey("units") && list[0].size() == 1) {
1132 // First item in the list is a units declaration, which applies to
1133 // the items in the list
1134 auto deltaUnits = list[0]["units"];
1135 list[0].m_data.erase("units");
1136 for (auto& item : list) {
1137 if (item.hasKey("units")) {
1138 if (item.size() == 1) {
1139 // Any additional units declarations are errors
1140 throw InputFileError("AnyValue::applyUnits", item,
1141 "Found units entry as not the first item in a list.");
1142 } else {
1143 // Merge with a child units declaration
1144 auto& childUnits = item["units"].as<AnyMap>();
1145 for (auto& [dimension, unit] : deltaUnits) {
1146 if (!childUnits.hasKey(dimension)) {
1147 childUnits[dimension] = unit;
1148 }
1149 }
1150 }
1151 } else if (item.hasKey("__units__")) {
1152 // Merge with a child units declaration
1153 auto& childUnits = item["__units__"].as<AnyMap>();
1154 for (auto& [dimension, unit] : deltaUnits) {
1155 if (!childUnits.hasKey(dimension)) {
1156 childUnits[dimension] = unit;
1157 }
1158 }
1159 } else {
1160 item["__units__"] = deltaUnits;
1161 }
1162 item.applyUnits(units);
1163 }
1164 // Remove the "units" map after it has been applied
1165 list.erase(list.begin());
1166 } else {
1167 // Simple downward propagation of the current units
1168 for (auto& item : list) {
1169 // Any later units declarations are errors
1170 if (item.size() == 1 && item.hasKey("units")) {
1171 throw InputFileError("AnyValue::applyUnits", item,
1172 "Found units entry as not the first item in a list.");
1173 }
1174 item.applyUnits(units);
1175 }
1176 }
1177 } else if (is<vector<AnyValue>>()) {
1178 for (auto& v : as<vector<AnyValue>>()) {
1179 v.applyUnits(units);
1180 }
1181 } else if (is<Quantity>()) {
1182 auto& Q = as<Quantity>();
1183 if (Q.converter) {
1184 Q.converter(Q.value, *units);
1185 m_equals = Q.value.m_equals;
1186 // Replace the value last since Q is a reference to m_value and won't be
1187 // valid after this
1188 m_value = Q.value.m_value;
1189 } else if (Q.value.is<double>()) {
1190 if (Q.isActivationEnergy) {
1191 *this = Q.value.as<double>() / units->convertActivationEnergyTo(1.0, Q.units);
1192 } else {
1193 *this = Q.value.as<double>() / units->convertTo(1.0, Q.units);
1194 }
1195 } else if (Q.value.is<vector<double>>()) {
1196 double factor = 1.0 / units->convertTo(1.0, Q.units);
1197 auto& old = Q.value.asVector<double>();
1198 vector<double> converted(old.size());
1199 scale(old.begin(), old.end(), converted.begin(), factor);
1200 *this = std::move(converted);
1201 } else {
1202 throw CanteraError("AnyValue::applyUnits", "Don't know how to "
1203 "convert Quantity with held type '{}' in key '{}'",
1204 Q.value.type_str(), m_key);
1205 }
1206 }
1207}
1208
1210{
1211 as<AnyMap>().setFlowStyle();
1212}
1213
1214// Explicit template specializations to allow certain conversions
1215
1216template<>
1217const vector<AnyValue>& AnyValue::asVector<AnyValue>(size_t nMin, size_t nMax) const
1218{
1219 if (!is<vector<AnyValue>>()) {
1220 vector<AnyValue> v;
1221 if (is<vector<double>>()) {
1222 for (const auto& el : asVector<double>()) {
1223 v.push_back(AnyValue(el));
1224 }
1225 const_cast<AnyValue*>(this)->m_value = v;
1226 } else if (is<vector<long int>>()) {
1227 for (const auto& el : asVector<long int>()) {
1228 v.push_back(AnyValue(el));
1229 }
1230 const_cast<AnyValue*>(this)->m_value = v;
1231 } else if (is<vector<string>>()) {
1232 for (const auto& el : asVector<string>()) {
1233 v.push_back(AnyValue(el));
1234 }
1235 const_cast<AnyValue*>(this)->m_value = v;
1236 }
1237 // If none of these special cases match, the value won't be replaced,
1238 // and an exception will be thrown.
1239 }
1240 const auto& vv = as<vector<AnyValue>>();
1241 m_equals = eq_comparer<vector<AnyValue>>;
1242 checkSize(vv, nMin, nMax);
1243 return vv;
1244}
1245
1246template<>
1247vector<AnyValue>& AnyValue::asVector<AnyValue>(size_t nMin, size_t nMax)
1248{
1249 auto& v = const_cast<vector<AnyValue>&>(
1250 const_cast<const AnyValue*>(this)->asVector<AnyValue>());
1251 checkSize(v, nMin, nMax);
1252 return v;
1253}
1254
1255template<>
1256const vector<double>& AnyValue::asVector<double>(size_t nMin, size_t nMax) const
1257{
1258 if (is<vector<long int>>()) {
1259 vector<double> v;
1260 for (const auto& el : asVector<long int>()) {
1261 v.push_back(el);
1262 }
1263 const_cast<AnyValue*>(this)->m_value = v;
1264 }
1265 const auto& vv = as<vector<double>>();
1266 m_equals = eq_comparer<vector<double>>;
1267 checkSize(vv, nMin, nMax);
1268 return vv;
1269}
1270
1271template<>
1272vector<double>& AnyValue::asVector<double>(size_t nMin, size_t nMax)
1273{
1274 if (is<vector<long int>>()) {
1275 vector<double> v;
1276 for (const auto& el : asVector<long int>()) {
1277 v.push_back(el);
1278 }
1279 m_value = v;
1280 }
1281 auto& vv = as<vector<double>>();
1282 m_equals = eq_comparer<vector<double>>;
1283 checkSize(vv, nMin, nMax);
1284 return vv;
1285}
1286
1287template<>
1288const vector<vector<double>>& AnyValue::asVector<vector<double>>(size_t nMin, size_t nMax) const
1289{
1290 if (is<vector<vector<long int>>>()) {
1291 vector<vector<double>> v;
1292 for (const auto& outer : asVector<vector<long int>>()) {
1293 v.push_back(vector<double>());
1294 for (const auto& inner : outer) {
1295 v.back().push_back(inner);
1296 }
1297 }
1298 const_cast<AnyValue*>(this)->m_value = v;
1299 }
1300 const auto& vv = as<vector<vector<double>>>();
1301 m_equals = eq_comparer<vector<vector<double>>>;
1302 checkSize(vv, nMin, nMax);
1303 return vv;
1304}
1305
1306template<>
1307vector<vector<double>>& AnyValue::asVector<vector<double>>(size_t nMin, size_t nMax)
1308{
1309 if (is<vector<vector<long int>>>()) {
1310 vector<vector<double>> v;
1311 for (const auto& outer : asVector<vector<long int>>()) {
1312 v.push_back(vector<double>());
1313 for (const auto& inner : outer) {
1314 v.back().push_back(inner);
1315 }
1316 }
1317 m_value = v;
1318 }
1319 auto& vv = as<vector<vector<double>>>();
1320 m_equals = eq_comparer<vector<vector<double>>>;
1321 checkSize(vv, nMin, nMax);
1322 return vv;
1323}
1324
1325template<>
1326const vector<AnyMap>& AnyValue::asVector<AnyMap>(size_t nMin, size_t nMax) const
1327{
1328 if (is<AnyMap>()) {
1329 vector<AnyMap> v;
1330 v.push_back(std::move(as<AnyMap>()));
1331 const_cast<AnyValue*>(this)->m_value = std::move(v);
1332 } else if (is<vector<AnyValue>>() && asVector<AnyValue>().empty()) {
1333 const_cast<AnyValue*>(this)->m_value = vector<AnyMap>();
1334 }
1335 const auto& vv = as<vector<AnyMap>>();
1336 checkSize(vv, nMin, nMax);
1337 return vv;
1338}
1339
1340template<>
1341vector<AnyMap>& AnyValue::asVector<AnyMap>(size_t nMin, size_t nMax)
1342{
1343 if (is<AnyMap>()) {
1344 vector<AnyMap> v;
1345 v.push_back(std::move(as<AnyMap>()));
1346 m_value = std::move(v);
1347 } else if (is<vector<AnyValue>>() && asVector<AnyValue>().empty()) {
1348 m_value = vector<AnyMap>();
1349 }
1350 auto& vv = as<vector<AnyMap>>();
1351 checkSize(vv, nMin, nMax);
1352 return vv;
1353}
1354
1355// Methods of class AnyMap
1356
1357AnyMap::AnyMap()
1358 : m_units(new UnitSystem())
1359{
1360}
1361
1362AnyValue& AnyMap::operator[](const string& key)
1363{
1364 const auto& iter = m_data.find(key);
1365 if (iter == m_data.end()) {
1366 // Create a new key to return
1367 AnyValue& value = m_data.emplace(key, AnyValue()).first->second;
1368 value.setKey(key);
1369 if (m_metadata) {
1371 }
1372
1373 // A pseudo-location used to set the ordering when outputting to
1374 // YAML so nodes added this way will come before nodes from YAML,
1375 // with insertion order preserved.
1376 value.setLoc(-1, m_column);
1377 m_column += 10;
1378
1379 return value;
1380 } else {
1381 // Return an already-existing item
1382 return iter->second;
1383 }
1384}
1385
1386const AnyValue& AnyMap::operator[](const string& key) const
1387{
1388 try {
1389 return m_data.at(key);
1390 } catch (std::out_of_range&) {
1391 throw InputFileError("AnyMap::operator[]", *this,
1392 "Key '{}' not found.\nExisting keys: {}", key, keys_str());
1393 }
1394}
1395
1396AnyValue& AnyMap::createForYaml(const string& key, int line, int column)
1397{
1398 AnyValue& value = m_data.emplace(key, AnyValue()).first->second;
1399 value.setKey(key);
1400 if (m_metadata) {
1402 }
1403
1404 value.setLoc(line, column);
1405 return value;
1406}
1407
1408const AnyValue& AnyMap::at(const string& key) const
1409{
1410 try {
1411 return m_data.at(key);
1412 } catch (std::out_of_range&) {
1413 throw InputFileError("AnyMap::at", *this,
1414 "Key '{}' not found.\nExisting keys: {}", key, keys_str());
1415 }
1416}
1417
1418bool AnyMap::empty() const
1419{
1420 return m_data.size() == 0;
1421}
1422
1423bool AnyMap::hasKey(const string& key) const
1424{
1425 return (m_data.find(key) != m_data.end());
1426}
1427
1428void AnyMap::erase(const string& key)
1429{
1430 m_data.erase(key);
1431}
1432
1434{
1435 m_data.clear();
1436}
1437
1438void AnyMap::update(const AnyMap& other, bool keepExisting)
1439{
1440 for (const auto& [key, value] : other) {
1441 if (!keepExisting || !hasKey(key)) {
1442 (*this)[key] = value;
1443 }
1444 }
1445}
1446
1447string AnyMap::keys_str() const
1448{
1449 fmt::memory_buffer b;
1450 auto iter = this->begin();
1451 if (iter != this->end()) {
1452 fmt_append(b, "{}", iter->first);
1453 ++iter;
1454 }
1455 while (iter != this->end()) {
1456 fmt_append(b, ", {}", iter->first);
1457 ++iter;
1458 }
1459 return to_string(b);
1460}
1461
1462set<string> AnyMap::keys() const
1463{
1464 set<string> out;
1465 auto iter = this->begin();
1466 while (iter != this->end()) {
1467 out.insert(iter->first);
1468 ++iter;
1469 }
1470 return out;
1471}
1472
1473void AnyMap::propagateMetadata(shared_ptr<AnyMap>& metadata)
1474{
1475 m_metadata = metadata;
1476 for (auto& [name, value] : m_data) {
1477 value.propagateMetadata(m_metadata);
1478 }
1479}
1480
1481void AnyMap::setMetadata(const string& key, const AnyValue& value)
1482{
1483 if (m_metadata) {
1484 // Fork the metadata tree at this point to avoid affecting parent nodes
1485 m_metadata = make_shared<AnyMap>(*m_metadata);
1486 } else {
1487 m_metadata = make_shared<AnyMap>();
1488 }
1489 (*m_metadata)[key] = value;
1491}
1492
1494{
1495 m_line = other.m_line;
1496 m_column = other.m_column;
1497 if (!other.m_metadata) {
1498 return;
1499 }
1500
1501 if (m_metadata) {
1502 // Fork the metadata tree at this point to avoid affecting parent nodes
1503 m_metadata = make_shared<AnyMap>(*m_metadata);
1504 } else {
1505 m_metadata = make_shared<AnyMap>();
1506 }
1507
1508 for (const auto& [key, value] : *other.m_metadata) {
1509 (*m_metadata)[key] = value;
1510 }
1511
1513}
1514
1515bool AnyMap::getBool(const string& key, bool default_) const
1516{
1517 return (hasKey(key)) ? m_data.at(key).asBool() : default_;
1518}
1519
1520double AnyMap::getDouble(const string& key, double default_) const
1521{
1522 return (hasKey(key)) ? m_data.at(key).asDouble() : default_;
1523}
1524
1525long int AnyMap::getInt(const string& key, long int default_) const
1526{
1527 return (hasKey(key)) ? m_data.at(key).asInt() : default_;
1528}
1529
1530const string& AnyMap::getString(const string& key, const string& default_) const
1531{
1532 return (hasKey(key)) ? m_data.at(key).asString() : default_;
1533}
1534
1535double AnyMap::convert(const string& key, const string& dest) const
1536{
1537 return units().convert(at(key), dest);
1538}
1539
1540double AnyMap::convert(const string& key, const Units& dest) const
1541{
1542 return units().convert(at(key), dest);
1543}
1544
1545double AnyMap::convert(const string& key, const string& dest,
1546 double default_) const
1547{
1548 if (hasKey(key)) {
1549 return units().convert(at(key), dest);
1550 } else {
1551 return default_;
1552 }
1553}
1554
1555vector<double> AnyMap::convertVector(const string& key, const string& dest,
1556 size_t nMin, size_t nMax) const
1557{
1558 return units().convert(at(key).asVector<AnyValue>(nMin, nMax), dest);
1559}
1560
1561AnyMap::Iterator::Iterator(
1562 const std::unordered_map<string, AnyValue>::const_iterator& start,
1563 const std::unordered_map<string, AnyValue>::const_iterator& stop)
1564{
1565 m_iter = start;
1566 m_stop = stop;
1567 while (m_iter != m_stop
1568 && ba::starts_with(m_iter->first, "__")
1569 && ba::ends_with(m_iter->first, "__")) {
1570 ++m_iter;
1571 }
1572}
1573
1574AnyMap::Iterator& AnyMap::Iterator::operator++()
1575{
1576 ++m_iter;
1577 while (m_iter != m_stop
1578 && ba::starts_with(m_iter->first, "__")
1579 && ba::ends_with(m_iter->first, "__")) {
1580 ++m_iter;
1581 }
1582 return *this;
1583}
1584
1585
1586AnyMap::OrderedProxy::OrderedProxy(const AnyMap& data)
1587 : m_data(&data)
1588{
1589 // Units always come first
1590 if (m_data->hasKey("__units__") && m_data->at("__units__").as<AnyMap>().size()) {
1591 m_units = make_unique<pair<const string, AnyValue>>(
1592 "units", m_data->at("__units__"));
1593 m_units->second.setFlowStyle();
1594 m_ordered.emplace_back(pair<int, int>{-2, 0}, m_units.get());
1595 }
1596
1597 int head = 0; // sort key of the first programmatically-added item
1598 int tail = 0; // sort key of the last programmatically-added item
1599 for (auto& item : *m_data) {
1600 const auto& order = item.second.order();
1601 if (order.first == -1) { // Item is not from an input file
1602 head = std::min(head, order.second);
1603 tail = std::max(tail, order.second);
1604 }
1605 m_ordered.emplace_back(order, &item);
1606 }
1607 std::sort(m_ordered.begin(), m_ordered.end());
1608
1609 // Adjust sort keys for items that should moved to the beginning or end of
1610 // the list
1611 if (m_data->hasKey("__type__")) {
1612 bool order_changed = false;
1613 const auto& itemType = m_data->at("__type__").asString();
1614 std::unique_lock<std::mutex> lock(yaml_field_order_mutex);
1615 if (AnyMap::s_headFields.count(itemType)) {
1616 for (const auto& key : AnyMap::s_headFields[itemType]) {
1617 for (auto& [order, item] : m_ordered) {
1618 if (order.first >= 0) {
1619 // This and following items come from an input file and
1620 // should not be re-ordered
1621 break;
1622 }
1623 if (item->first == key) {
1624 order.second = --head;
1625 order_changed = true;
1626 }
1627 }
1628 }
1629 }
1630 if (AnyMap::s_tailFields.count(itemType)) {
1631 for (const auto& key : AnyMap::s_tailFields[itemType]) {
1632 for (auto& [order, item] : m_ordered) {
1633 if (order.first >= 0) {
1634 // This and following items come from an input file and
1635 // should not be re-ordered
1636 break;
1637 }
1638 if (item->first == key) {
1639 order.second = ++tail;
1640 order_changed = true;
1641 }
1642 }
1643 }
1644 }
1645
1646 if (order_changed) {
1647 std::sort(m_ordered.begin(), m_ordered.end());
1648 }
1649 }
1650}
1651
1652AnyMap::OrderedIterator AnyMap::OrderedProxy::begin() const
1653{
1654 return OrderedIterator(m_ordered.begin(), m_ordered.end());
1655}
1656
1657AnyMap::OrderedIterator AnyMap::OrderedProxy::end() const
1658{
1659 return OrderedIterator(m_ordered.end(), m_ordered.end());
1660}
1661
1662AnyMap::OrderedIterator::OrderedIterator(
1663 const AnyMap::OrderedProxy::OrderVector::const_iterator& start,
1664 const AnyMap::OrderedProxy::OrderVector::const_iterator& stop)
1665{
1666 m_iter = start;
1667 m_stop = stop;
1668}
1669
1670bool AnyMap::operator==(const AnyMap& other) const
1671{
1672 // First, make sure that 'other' has all of the non-hidden keys that are in
1673 // this map
1674 for (auto& [key, value] : *this) {
1675 if (!other.hasKey(key)) {
1676 return false;
1677 }
1678 }
1679 // Then check for equality, using the non-hidden keys from 'other'
1680 for (auto & [key, value] : other) {
1681 if (!hasKey(key) || value != at(key)) {
1682 return false;
1683 }
1684 }
1685 return true;
1686}
1687
1688bool AnyMap::operator!=(const AnyMap& other) const
1689{
1690 return m_data != other.m_data;
1691}
1692
1694{
1696}
1697
1698void AnyMap::applyUnits(shared_ptr<UnitSystem>& units) {
1699 if (hasKey("units")) {
1700 m_data["__units__"] = std::move(m_data["units"]);
1701 m_data.erase("units");
1702 }
1703 if (hasKey("__units__")) {
1704 m_units = make_shared<UnitSystem>(*units);
1705 m_units->setDefaults(m_data["__units__"].asMap<string>());
1706 } else {
1707 m_units = units;
1708 }
1709 for (auto& [name, item] : m_data) {
1710 item.applyUnits(m_units);
1711 }
1712}
1713
1715{
1716 if (hasKey("__units__")) {
1717 for (const auto& [dimension, value] : units.getDelta(*m_units)) {
1718 m_data["__units__"][dimension] = value;
1719 }
1720 } else {
1721 m_data["__units__"] = units.getDelta(*m_units);
1722 }
1723 m_units = make_shared<UnitSystem>(units);
1724}
1725
1726void AnyMap::setFlowStyle(bool flow) {
1727 (*this)["__flow__"] = flow;
1728}
1729
1730bool AnyMap::addOrderingRules(const string& objectType,
1731 const vector<vector<string>>& specs)
1732{
1733 std::unique_lock<std::mutex> lock(yaml_field_order_mutex);
1734 for (const auto& spec : specs) {
1735 if (spec.at(0) == "head") {
1736 s_headFields[objectType].push_back(spec.at(1));
1737 } else if (spec.at(0) == "tail") {
1738 s_tailFields[objectType].push_back(spec.at(1));
1739 } else {
1740 throw CanteraError("AnyMap::addOrderingRules",
1741 "Unknown ordering rule '{}'", spec.at(0));
1742 }
1743 }
1744 return true;
1745}
1746
1747void AnyMap::clearCachedFile(const string& filename)
1748{
1749 string fullName = findInputFile(filename);
1750 if (s_cache.count(fullName)) {
1751 s_cache.erase(fullName);
1752 }
1753}
1754
1755AnyMap AnyMap::fromYamlString(const string& yaml) {
1756 AnyMap amap;
1757 try {
1758 YAML::Node node = YAML::Load(yaml);
1759 amap = node.as<AnyMap>();
1760 } catch (YAML::Exception& err) {
1761 AnyMap fake;
1762 fake.setLoc(err.mark.line, err.mark.column);
1763 fake.setMetadata("file-contents", AnyValue(yaml));
1764 throw InputFileError("AnyMap::fromYamlString", fake, err.msg);
1765 }
1766 amap.setMetadata("file-contents", AnyValue(yaml));
1767 amap.applyUnits();
1768 return amap;
1769}
1770
1771AnyMap AnyMap::fromYamlFile(const string& name, const string& parent_name)
1772{
1773 string fullName;
1774 // See if a file with this name exists in a path relative to the parent file
1775 size_t islash = parent_name.find_last_of("/\\");
1776 if (islash != npos) {
1777 string parent_path = parent_name.substr(0, islash);
1778 if (std::ifstream(parent_path + "/" + name).good()) {
1779 fullName = parent_path + "/" + name;
1780 }
1781 }
1782 // Otherwise, search the Cantera include path for the file
1783 if (fullName.empty()) {
1784 fullName = findInputFile(name);
1785 }
1786
1787 // Check for an already-parsed YAML file with the same last-modified time,
1788 // and return that if possible
1789 auto mtime = std::filesystem::last_write_time(fullName);
1790 std::unique_lock<std::mutex> lock(yaml_cache_mutex);
1791 auto iter = s_cache.find(fullName);
1792 if (iter != s_cache.end() && iter->second.second == mtime) {
1793 return iter->second.first;
1794 }
1795
1796 if (!std::ifstream(fullName).good()) {
1797 throw CanteraError("AnyMap::fromYamlFile", "Input file '{}' not found "
1798 "on the Cantera search path.", name);
1799 }
1800
1801 // Generate an AnyMap from the YAML file and store it in the cache
1802 auto& [cache_item, cache_time] = s_cache[fullName];
1803 cache_time = mtime;
1804 try {
1805 YAML::Node node = YAML::LoadFile(fullName);
1806 cache_item = node.as<AnyMap>();
1807 cache_item.setMetadata("filename", AnyValue(fullName));
1808 cache_item.applyUnits();
1809 } catch (YAML::Exception& err) {
1810 s_cache.erase(fullName);
1811 AnyMap fake;
1812 fake.setLoc(err.mark.line, err.mark.column);
1813 fake.setMetadata("filename", AnyValue(fullName));
1814 throw InputFileError("AnyMap::fromYamlFile", fake, err.msg);
1815 } catch (CanteraError&) {
1816 s_cache.erase(fullName);
1817 throw;
1818 }
1819 cache_item["__file__"] = fullName;
1820
1821 if (cache_item.hasKey("deprecated")) {
1822 warn_deprecated(fullName, cache_item["deprecated"].asString());
1823 }
1824
1825 // Return a copy of the AnyMap
1826 return cache_item;
1827}
1828
1829string AnyMap::toYamlString() const
1830{
1831 YAML::Emitter out;
1832 const_cast<AnyMap*>(this)->applyUnits();
1833 out << *this;
1834 out << YAML::Newline;
1835 return out.c_str();
1836}
1837
1839 return v.as<AnyMap>().begin();
1840}
1841
1842AnyMap::Iterator end(const AnyValue& v) {
1843 return v.as<AnyMap>().end();
1844}
1845
1846namespace {
1847void formatInputFile(fmt::memory_buffer& b, const shared_ptr<AnyMap>& metadata,
1848 const string& filename, int lineno, int column, int lineno2=-1, int column2=-1)
1849{
1850 if (lineno2 == -1) {
1851 lineno2 = lineno;
1852 column2 = column;
1853 }
1854
1855 fmt_append(b, "| Line |\n");
1856 if (!metadata->hasKey("file-contents")) {
1857 std::ifstream infile(findInputFile(filename));
1858 std::stringstream buffer;
1859 buffer << infile.rdbuf();
1860 (*metadata)["file-contents"] = buffer.str();
1861 }
1862 string line;
1863 int i = 0;
1864 int lastShown = -1;
1865 std::stringstream contents((*metadata)["file-contents"].asString());
1866 while (std::getline(contents, line)) {
1867 if (i == lineno || i == lineno2) {
1868 fmt_append(b, "> {: 5d} > {}\n", i+1, line);
1869 fmt_append(b, "{:>{}}\n", "^", column + 11);
1870 lastShown = i;
1871 } else if ((lineno + 4 > i && lineno < i + 6) ||
1872 (lineno2 + 4 > i && lineno2 < i + 6)) {
1873 if (lastShown >= 0 && i - lastShown > 1) {
1874 fmt_append(b, "...\n");
1875 }
1876 fmt_append(b, "| {: 5d} | {}\n", i+1, line);
1877 lastShown = i;
1878 }
1879 i++;
1880 }
1881}
1882}
1883
1884string InputFileError::formatError(const string& message, int lineno, int column,
1885 const shared_ptr<AnyMap>& metadata)
1886{
1887 if (!metadata) {
1888 return message;
1889 }
1890 string filename = metadata->getString("filename", "input string");
1891
1892 fmt::memory_buffer b;
1893 fmt_append(b, "Error on line {} of {}:\n{}\n", lineno+1, filename, message);
1894 formatInputFile(b, metadata, filename, lineno, column);
1895 return to_string(b);
1896}
1897
1898string InputFileError::formatError2(const string& message, int line1, int column1,
1899 const shared_ptr<AnyMap>& metadata1,
1900 int line2, int column2,
1901 const shared_ptr<AnyMap>& metadata2)
1902{
1903 if (!metadata1 || !metadata2) {
1904 return message;
1905 }
1906 string filename1 = metadata1->getString("filename", "input string");
1907 string filename2 = metadata2->getString("filename", "input string");
1908
1909 fmt::memory_buffer b;
1910 if (filename1 == filename2) {
1911 fmt_append(b, "Error on lines {} and {} of {}:\n",
1912 std::min(line1, line2) + 1, std::max(line1, line2) + 1, filename1);
1913 fmt_append(b, "{}\n", message);
1914 formatInputFile(b, metadata1, filename1, line1, column1, line2, column2);
1915 } else {
1916 fmt_append(b, "Error on line {} of {} and line {} of {}:\n{}\n",
1917 line1+1, filename1, line2+1, filename2, message);
1918 formatInputFile(b, metadata1, filename1, line1, column1);
1919 fmt_append(b, "\n");
1920 formatInputFile(b, metadata2, filename2, line2, column2);
1921 }
1922
1923 return to_string(b);
1924}
1925
1926void warn_deprecated(const string& source, const AnyBase& node, const string& message)
1927{
1928 if (!node.m_metadata) {
1929 warn_deprecated(source, message);
1930 return;
1931 }
1932
1933 string filename = node.m_metadata->getString("filename", "input string");
1934 fmt::memory_buffer b;
1935 fmt_append(b, message);
1936 fmt_append(b, "\n");
1937 fmt_append(b, "On line {} of {}:\n", node.m_line+1, filename);
1938 formatInputFile(b, node.m_metadata, filename, node.m_line, node.m_column);
1939 warn_deprecated(source, to_string(b));
1940}
1941
1942}
void emitString(YAML::Emitter &out, const string &str0)
Write YAML strings spanning multiple lines if input includes endline ' '.
Definition AnyMap.cpp:328
void emitFlowVector(YAML::Emitter &out, const vector< double > &v, long int precision)
Write a vector in YAML "flow" style, wrapping lines to avoid exceeding the preferred maximum line len...
Definition AnyMap.cpp:365
Base class defining common data possessed by both AnyMap and AnyValue objects.
Definition AnyMap.h:34
int m_column
If m_line >= 0, the column where this value occurs in the input file.
Definition AnyMap.h:55
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
int m_line
The line where this value occurs in the input file.
Definition AnyMap.h:51
friend void warn_deprecated(const string &source, const AnyBase &node, const string &message)
A deprecation warning for syntax in an input file.
Definition AnyMap.cpp:1926
const AnyValue & getMetadata(const string &key) const
Get a value from the metadata applicable to the AnyMap tree containing this node.
Definition AnyMap.cpp:580
shared_ptr< AnyMap > m_metadata
Metadata relevant to an entire AnyMap tree, such as information about.
Definition AnyMap.h:59
Defined to allow use with range-based for loops.
Definition AnyMap.h:540
Defined to allow the OrderedProxy class to be used with range-based for loops.
Definition AnyMap.h:594
A map of string keys to values whose type can vary at runtime.
Definition AnyMap.h:427
static AnyMap fromYamlString(const string &yaml)
Create an AnyMap from a string containing a YAML document.
Definition AnyMap.cpp:1755
Iterator begin() const
Defined to allow use with range-based for loops.
Definition AnyMap.h:563
AnyValue & createForYaml(const string &key, int line, int column)
Used to create a new item which will be populated from a YAML input string, where the item with key o...
Definition AnyMap.cpp:1396
set< string > keys() const
Return an unordered set of keys.
Definition AnyMap.cpp:1462
size_t size() const
Returns the number of elements in this map.
Definition AnyMap.h:622
long int getInt(const string &key, long int default_) const
If key exists, return it as a long int, otherwise return default_.
Definition AnyMap.cpp:1525
void copyMetadata(const AnyMap &other)
Copy metadata including input line/column from an existing AnyMap.
Definition AnyMap.cpp:1493
static std::unordered_map< string, pair< AnyMap, std::filesystem::file_time_type > > s_cache
Cache for previously-parsed input (YAML) files.
Definition AnyMap.h:710
double getDouble(const string &key, double default_) const
If key exists, return it as a double, otherwise return default_.
Definition AnyMap.cpp:1520
bool hasKey(const string &key) const
Returns true if the map contains an item named key.
Definition AnyMap.cpp:1423
const UnitSystem & units() const
Return the default units that should be used to convert stored values.
Definition AnyMap.h:630
Iterator end() const
Defined to allow use with range-based for loops.
Definition AnyMap.h:568
bool empty() const
Return boolean indicating whether AnyMap is empty.
Definition AnyMap.cpp:1418
static void clearCachedFile(const string &filename)
Remove the specified file from the input cache if it is present.
Definition AnyMap.cpp:1747
void applyUnits()
Use the supplied UnitSystem to set the default units, and recursively process overrides from nodes na...
Definition AnyMap.cpp:1693
static std::unordered_map< string, vector< string > > s_headFields
Information about fields that should appear first when outputting to YAML.
Definition AnyMap.h:715
double convert(const string &key, const string &units) const
Convert the item stored by the given key to the units specified in units.
Definition AnyMap.cpp:1535
void setMetadata(const string &key, const AnyValue &value)
Set a metadata value that applies to this AnyMap and its children.
Definition AnyMap.cpp:1481
AnyValue & operator[](const string &key)
Get the value of the item stored in key.
Definition AnyMap.cpp:1362
void setFlowStyle(bool flow=true)
Use "flow" style when outputting this AnyMap to YAML.
Definition AnyMap.cpp:1726
void propagateMetadata(shared_ptr< AnyMap > &file)
Propagate metadata to any child elements.
Definition AnyMap.cpp:1473
bool getBool(const string &key, bool default_) const
If key exists, return it as a bool, otherwise return default_.
Definition AnyMap.cpp:1515
static std::unordered_map< string, vector< string > > s_tailFields
Information about fields that should appear last when outputting to YAML.
Definition AnyMap.h:720
void clear()
Erase all items in the mapping.
Definition AnyMap.cpp:1433
shared_ptr< UnitSystem > m_units
The default units that are used to convert stored values.
Definition AnyMap.h:704
const string & getString(const string &key, const string &default_) const
If key exists, return it as a string, otherwise return default_.
Definition AnyMap.cpp:1530
void erase(const string &key)
Erase the value held by key.
Definition AnyMap.cpp:1428
static AnyMap fromYamlFile(const string &name, const string &parent_name="")
Create an AnyMap from a YAML file.
Definition AnyMap.cpp:1771
std::unordered_map< string, AnyValue > m_data
The stored data.
Definition AnyMap.h:701
const AnyValue & at(const string &key) const
Get the value of the item stored in key.
Definition AnyMap.cpp:1408
void update(const AnyMap &other, bool keepExisting=true)
Add items from other to this AnyMap.
Definition AnyMap.cpp:1438
static bool addOrderingRules(const string &objectType, const vector< vector< string > > &specs)
Add global rules for setting the order of elements when outputting AnyMap objects to YAML.
Definition AnyMap.cpp:1730
void setUnits(const UnitSystem &units)
Set the unit system for this AnyMap.
Definition AnyMap.cpp:1714
string keys_str() const
Return a string listing the keys in this AnyMap, for use in error messages, for example.
Definition AnyMap.cpp:1447
vector< double > convertVector(const string &key, const string &units, size_t nMin=npos, size_t nMax=npos) const
Convert a vector of dimensional values.
Definition AnyMap.cpp:1555
A wrapper for a variable whose type is determined at runtime.
Definition AnyMap.h:86
const string & asString() const
Return the held value, if it is a string.
Definition AnyMap.cpp:739
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
void setKey(const string &key)
Set the name of the key storing this value in an AnyMap.
Definition AnyMap.cpp:621
pair< int, int > order() const
Return values used to determine the sort order when outputting to YAML.
Definition AnyMap.cpp:1107
bool hasMapWhere(const string &key, const string &value) const
Returns true when getMapWhere() would succeed.
Definition AnyMap.cpp:1084
void setQuantity(double value, const string &units, bool is_act_energy=false)
Assign a scalar quantity with units as a string, for example {3.0, "m^2"}.
Definition AnyMap.cpp:769
bool hasKey(const string &key) const
Returns true if this AnyValue is an AnyMap and that map contains a key with the given name.
Definition AnyMap.cpp:617
map< string, T > asMap() const
Return the held AnyMap as a map where all of the values have the specified type.
Definition AnyMap.inl.h:162
bool & asBool()
Return the held value, if it is a bool.
Definition AnyMap.cpp:871
bool empty() const
Return boolean indicating whether AnyValue is empty.
Definition AnyMap.cpp:647
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
long int & asInt()
Return the held value, if it is a long int.
Definition AnyMap.cpp:903
void applyUnits(shared_ptr< UnitSystem > &units)
See AnyMap::applyUnits()
Definition AnyMap.cpp:1112
const std::type_info & type() const
Returns the type of the held value.
Definition AnyMap.cpp:623
double & asDouble()
Return the held value as a double, if it is a double or a long int.
Definition AnyMap.cpp:824
bool isScalar() const
Returns true if the held value is a scalar type (such as double, long int, string,...
Definition AnyMap.cpp:651
AnyValue & operator[](const string &key)
If this AnyValue is an AnyMap, return the value stored in key.
Definition AnyMap.cpp:607
string m_key
Key of this value in a parent AnyMap
Definition AnyMap.h:301
AnyMap & getMapWhere(const string &key, const string &value, bool create=false)
Treating the value as vector<AnyMap>, return the item where the given key has the specified value.
Definition AnyMap.cpp:1034
void setFlowStyle(bool flow=true)
See AnyMap::setFlowStyle()
Definition AnyMap.cpp:1209
void propagateMetadata(shared_ptr< AnyMap > &file)
Propagate metadata to any child elements.
Definition AnyMap.cpp:627
std::any m_value
The held value.
Definition AnyMap.h:304
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.
Error thrown for problems processing information contained in an AnyMap or AnyValue.
Definition AnyMap.h:738
An error indicating that an unimplemented function has been called.
Unit conversion utility.
Definition Units.h:169
double convert(double value, const string &src, const string &dest) const
Convert value from the units of src to the units of dest.
Definition Units.cpp:538
AnyMap getDelta(const UnitSystem &other) const
Get the changes to the defaults from other to this UnitSystem.
Definition Units.cpp:757
A representation of the units associated with a dimensional quantity.
Definition Units.h:35
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
This file contains definitions for utility functions and text for modules, inputfiles and logging,...
double fpValue(const string &val)
Translate a string into one double value.
string demangle(const std::type_info &type)
Convert a type name to a human readable string, using boost::core::demangle if available.
Definition global.cpp:213
string findInputFile(const string &name)
Find an input file.
Definition global.cpp:169
U len(const T &container)
Get the size of a container, cast to a signed integer type.
Definition utilities.h:198
void scale(InputIter begin, InputIter end, OutputIter out, S scale_factor)
Multiply elements of an array by a scale factor.
Definition utilities.h:104
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:195
Contains declarations for string manipulation functions within Cantera.
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...