1 // This file is part of Cantera. See License.txt in the top-level directory or
2 // at https://cantera.org/license.txt for license and copyright information.
3 
4 #include "cantera/base/YamlWriter.h"
5 #include "cantera/base/AnyMap.h"
6 #include "cantera/base/Solution.h"
7 #include "cantera/base/stringUtils.h"
8 #include "cantera/thermo/ThermoPhase.h"
9 #include "cantera/thermo/Species.h"
10 #include "cantera/kinetics/Kinetics.h"
11 #include "cantera/kinetics/Reaction.h"
12 #include "cantera/transport/TransportBase.h"
13 
14 #include <fstream>
15 #include <chrono>
16 
17 namespace Cantera {
18 
YamlWriter()19 YamlWriter::YamlWriter()
20     : m_float_precision(15)
21     , m_skip_user_defined(false)
22 {
23 }
24 
addPhase(shared_ptr<Solution> soln)25 void YamlWriter::addPhase(shared_ptr<Solution> soln) {
26     for (auto& phase : m_phases) {
27         if (phase->name() == soln->name()) {
28             throw CanteraError("YamlWriter::addPhase",
29                 "Duplicate phase name '{}'", soln->name());
30         }
31     }
32     m_phases.push_back(soln);
33 }
34 
addPhase(shared_ptr<ThermoPhase> thermo,shared_ptr<Kinetics> kin,shared_ptr<Transport> tran)35 void YamlWriter::addPhase(shared_ptr<ThermoPhase> thermo,
36                           shared_ptr<Kinetics> kin,
37                           shared_ptr<Transport> tran) {
38     auto soln = Solution::create();
39     soln->setThermo(thermo);
40     soln->setKinetics(kin);
41     soln->setTransport(tran);
42     addPhase(soln);
43 }
44 
toYamlString() const45 std::string YamlWriter::toYamlString() const
46 {
47     AnyMap output;
48     output["generator"] = "YamlWriter";
49     output["cantera-version"] = CANTERA_VERSION;
50     time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
51     output["date"] = trimCopy(std::ctime(&now));
52 
53     // Build phase definitions
54     std::vector<AnyMap> phaseDefs(m_phases.size());
55     size_t nspecies_total = 0;
56     for (size_t i = 0; i < m_phases.size(); i++) {
57         phaseDefs[i] = m_phases[i]->parameters(!m_skip_user_defined);
58         nspecies_total += m_phases[i]->thermo()->nSpecies();
59     }
60     output["phases"] = phaseDefs;
61 
62     // Build species definitions for all phases
63     std::vector<AnyMap> speciesDefs;
64     speciesDefs.reserve(nspecies_total);
65     std::unordered_map<std::string, size_t> speciesDefIndex;
66     for (const auto& phase : m_phases) {
67         const auto thermo = phase->thermo();
68         for (const auto& name : thermo->speciesNames()) {
69             const auto& species = thermo->species(name);
70             AnyMap speciesDef = species->parameters(thermo.get(), !m_skip_user_defined);
71 
72             if (speciesDefIndex.count(name) == 0) {
73                 speciesDefs.emplace_back(speciesDef);
74                 speciesDefIndex[name] = speciesDefs.size() - 1;
75             } else if (speciesDefs[speciesDefIndex[name]] != speciesDef) {
76                 throw CanteraError("YamlWriter::toYamlString",
77                     "Multiple species with different definitions are not "
78                     "supported:\n>>>>>>\n{}\n======\n{}\n<<<<<<\n",
79                     speciesDef.toYamlString(),
80                     speciesDefs[speciesDefIndex[name]].toYamlString());
81             }
82         }
83     }
84     output["species"] = speciesDefs;
85 
86     // build reaction definitions for all phases
87     std::map<std::string, std::vector<AnyMap>> allReactions;
88     for (const auto& phase : m_phases) {
89         const auto kin = phase->kinetics();
90         if (!kin || !kin->nReactions()) {
91             continue;
92         }
93         std::vector<AnyMap> reactions;
94         for (size_t i = 0; i < kin->nReactions(); i++) {
95             reactions.push_back(kin->reaction(i)->parameters(!m_skip_user_defined));
96         }
97         allReactions[phase->name()] = std::move(reactions);
98     }
99 
100     // Figure out which phase definitions have identical sets of reactions,
101     // and can share a reaction definition section
102 
103     // key: canonical phase in allReactions
104     // value: phases using this reaction set
105     std::map<std::string, std::vector<std::string>> phaseGroups;
106 
107     for (const auto& phase : m_phases) {
108         const auto kin = phase->kinetics();
109         std::string name = phase->name();
110         if (!kin || !kin->nReactions()) {
111             continue;
112         }
113         bool match = false;
114         for (auto& group : phaseGroups) {
115             if (allReactions[group.first] == allReactions[name]) {
116                 group.second.push_back(name);
117                 allReactions.erase(name);
118                 match = true;
119                 break;
120             }
121         }
122         if (!match) {
123             phaseGroups[name].push_back(name);
124         }
125     }
126 
127     // Generate the reactions section(s) in the output file
128     if (phaseGroups.size() == 1) {
129         output["reactions"] = std::move(allReactions[phaseGroups.begin()->first]);
130     } else {
131         for (const auto& group : phaseGroups) {
132             std::string groupName;
133             for (auto& name : group.second) {
134                 groupName += name + "-";
135             }
136             groupName += "reactions";
137             output[groupName] = std::move(allReactions[group.first]);
138 
139             for (auto& name : group.second) {
140                 AnyMap& phaseDef = output["phases"].getMapWhere("name", name);
141                 phaseDef["reactions"] = std::vector<std::string>{groupName};
142             }
143         }
144     }
145 
146     output.setMetadata("precision", AnyValue(m_float_precision));
147     output.setUnits(m_output_units);
148     return output.toYamlString();
149 }
150 
toYamlFile(const std::string & filename) const151 void YamlWriter::toYamlFile(const std::string& filename) const
152 {
153     std::ofstream out(filename);
154     out << toYamlString();
155 }
156 
setUnits(const std::map<std::string,std::string> & units)157 void YamlWriter::setUnits(const std::map<std::string, std::string>& units)
158 {
159     m_output_units = UnitSystem();
160     m_output_units.setDefaults(units);
161 }
162 
setUnitSystem(const UnitSystem & units)163 void YamlWriter::setUnitSystem(const UnitSystem& units)
164 {
165     m_output_units = units;
166 }
167 
168 }
169