1 /**
2  *  @file KineticsFactory.cpp
3  */
4 
5 // This file is part of Cantera. See License.txt in the top-level directory or
6 // at https://cantera.org/license.txt for license and copyright information.
7 
8 #include "cantera/kinetics/KineticsFactory.h"
9 #include "cantera/kinetics/GasKinetics.h"
10 #include "cantera/kinetics/InterfaceKinetics.h"
11 #include "cantera/kinetics/EdgeKinetics.h"
12 #include "cantera/kinetics/importKinetics.h"
13 #include "cantera/base/xml.h"
14 #include "cantera/base/stringUtils.h"
15 
16 using namespace std;
17 
18 namespace Cantera
19 {
20 
21 KineticsFactory* KineticsFactory::s_factory = 0;
22 std::mutex KineticsFactory::kinetics_mutex;
23 
newKinetics(XML_Node & phaseData,vector<ThermoPhase * > th)24 Kinetics* KineticsFactory::newKinetics(XML_Node& phaseData,
25                                        vector<ThermoPhase*> th)
26 {
27     // Look for a child of the XML element phase called "kinetics". It has an
28     // attribute name "model". Store the value of that attribute in the variable
29     // kintype
30     string kintype = phaseData.child("kinetics")["model"];
31 
32     // Create a kinetics object of the desired type
33     Kinetics* k = newKinetics(kintype);
34     // Now that we have the kinetics manager, we can import the reaction
35     // mechanism into it.
36     importKinetics(phaseData, th, k);
37 
38     // Return the pointer to the kinetics manager
39     return k;
40 }
41 
KineticsFactory()42 KineticsFactory::KineticsFactory() {
43     reg("none", []() { return new Kinetics(); });
44     addAlias("none", "Kinetics");
45     reg("gas", []() { return new GasKinetics(); });
46     addAlias("gas", "gaskinetics");
47     addAlias("gas", "Gas");
48     reg("surface", []() { return new InterfaceKinetics(); });
49     addAlias("surface", "interface");
50     addAlias("surface", "Surf");
51     reg("edge", []() { return new EdgeKinetics(); });
52     addAlias("edge", "Edge");
53 }
54 
newKinetics(const string & model)55 Kinetics* KineticsFactory::newKinetics(const string& model)
56 {
57     return create(toLowerCopy(model));
58 }
59 
newKinetics(const vector<ThermoPhase * > & phases,const AnyMap & phaseNode,const AnyMap & rootNode)60 unique_ptr<Kinetics> newKinetics(const vector<ThermoPhase*>& phases,
61                                  const AnyMap& phaseNode,
62                                  const AnyMap& rootNode)
63 {
64     unique_ptr<Kinetics> kin(KineticsFactory::factory()->newKinetics(
65         phaseNode.getString("kinetics", "none")));
66     for (auto& phase : phases) {
67         kin->addPhase(*phase);
68     }
69     kin->init();
70     addReactions(*kin, phaseNode, rootNode);
71     return kin;
72 }
73 
newKinetics(const std::vector<ThermoPhase * > & phases,const std::string & filename,const std::string & phase_name)74 unique_ptr<Kinetics> newKinetics(const std::vector<ThermoPhase*>& phases,
75                                  const std::string& filename,
76                                  const std::string& phase_name)
77 {
78     size_t dot = filename.find_last_of(".");
79     string extension;
80     if (dot != npos) {
81         extension = toLowerCopy(filename.substr(dot+1));
82     }
83 
84     if (extension == "yml" || extension == "yaml") {
85         AnyMap root = AnyMap::fromYamlFile(filename);
86         AnyMap& phaseNode = root["phases"].getMapWhere("name", phase_name);
87         return newKinetics(phases, phaseNode, root);
88     } else {
89         XML_Node* root = get_XML_File(filename);
90         XML_Node* xphase = get_XML_NameID("phase", "#"+phase_name, root);
91         if (!xphase) {
92             throw CanteraError("newKinetics",
93                 "Couldn't find phase named '{}' in file '{}'.",
94                 phase_name, filename);
95         }
96         return unique_ptr<Kinetics>(newKineticsMgr(*xphase, phases));
97     }
98 }
99 
addReactions(Kinetics & kin,const AnyMap & phaseNode,const AnyMap & rootNode)100 void addReactions(Kinetics& kin, const AnyMap& phaseNode, const AnyMap& rootNode)
101 {
102     kin.skipUndeclaredThirdBodies(
103         phaseNode.getBool("skip-undeclared-third-bodies", false));
104 
105     // Find sections containing reactions to add
106     vector<string> sections, rules;
107 
108     if (phaseNode.hasKey("reactions")) {
109         if (kin.kineticsType() == "Kinetics") {
110             throw InputFileError("addReactions", phaseNode["reactions"],
111                 "Phase entry includes a 'reactions' field but does not "
112                 "specify a kinetics model.");
113         }
114         const auto& reactionsNode = phaseNode.at("reactions");
115         if (reactionsNode.is<string>()) {
116             if (rootNode.hasKey("reactions")) {
117                 // Specification of the rule for adding species from the default
118                 // 'reactions' section, if it exists
119                 sections.push_back("reactions");
120                 rules.push_back(reactionsNode.asString());
121             } else if (reactionsNode.asString() != "none") {
122                 throw InputFileError("addReactions", reactionsNode,
123                     "Phase entry implies existence of 'reactions' section "
124                     "which does not exist in the current input file.");
125             }
126         } else if (reactionsNode.is<vector<string>>()) {
127             // List of sections from which all species should be added
128             for (const auto& item : reactionsNode.as<vector<string>>()) {
129                 sections.push_back(item);
130                 rules.push_back("all");
131             }
132         } else if (reactionsNode.is<vector<AnyMap>>()) {
133             // Mapping of rules to apply for each specified section containing
134             // reactions
135             for (const auto& item : reactionsNode.as<vector<AnyMap>>()) {
136                 sections.push_back(item.begin()->first);
137                 rules.push_back(item.begin()->second.asString());
138             }
139         }
140     } else if (kin.kineticsType() != "Kinetics") {
141         if (rootNode.hasKey("reactions")) {
142             // Default behavior is to add all reactions from the 'reactions'
143             // section, if a 'kinetics' model has been specified
144             sections.push_back("reactions");
145             rules.push_back("all");
146         } else {
147             throw InputFileError("addReactions", phaseNode,
148                 "Phase entry implies existence of 'reactions' section which "
149                 "does not exist in the current input file. Add the field "
150                 "'reactions: none' to the phase entry to specify a kinetics "
151                 "model with no reactions.");
152         }
153     }
154 
155     // Add reactions from each section
156     fmt::memory_buffer add_rxn_err;
157     for (size_t i = 0; i < sections.size(); i++) {
158         if (rules[i] == "all") {
159             kin.skipUndeclaredSpecies(false);
160         } else if (rules[i] == "declared-species") {
161             kin.skipUndeclaredSpecies(true);
162         } else if (rules[i] == "none") {
163             continue;
164         } else {
165             throw InputFileError("addReactions", phaseNode.at("reactions"),
166                 "Unknown rule '{}' for adding species from the '{}' section.",
167                 rules[i], sections[i]);
168         }
169         const auto& slash = boost::ifind_last(sections[i], "/");
170         if (slash) {
171             // specified section is in a different file
172             string fileName (sections[i].begin(), slash.begin());
173             string node(slash.end(), sections[i].end());
174             AnyMap reactions = AnyMap::fromYamlFile(fileName,
175                 rootNode.getString("__file__", ""));
176             for (const auto& R : reactions[node].asVector<AnyMap>()) {
177                 try {
178                     kin.addReaction(newReaction(R, kin), false);
179                 } catch (CanteraError& err) {
180                     format_to(add_rxn_err, "{}", err.what());
181                 }
182             }
183         } else {
184             // specified section is in the current file
185             for (const auto& R : rootNode.at(sections[i]).asVector<AnyMap>()) {
186                 try {
187                     kin.addReaction(newReaction(R, kin), false);
188                 } catch (CanteraError& err) {
189                     format_to(add_rxn_err, "{}", err.what());
190                 }
191             }
192         }
193     }
194 
195     kin.checkDuplicates();
196     if (add_rxn_err.size()) {
197         throw CanteraError("addReactions", to_string(add_rxn_err));
198     }
199 
200     kin.resizeReactions();
201 }
202 
203 }
204