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