diff --git a/include/cantera/kinetics/KineticsFactory.h b/include/cantera/kinetics/KineticsFactory.h index 61487b199..5295b36ba 100644 --- a/include/cantera/kinetics/KineticsFactory.h +++ b/include/cantera/kinetics/KineticsFactory.h @@ -91,6 +91,35 @@ inline Kinetics* newKineticsMgr(const std::string& model) return KineticsFactory::factory()->newKinetics(model); } +/*! + * Create a new kinetics manager, initialize it, and add reactions + * + * @param phases Vector of phases containing species which participate in + * reactions, with the phase where the reactions occur (lowest-dimensional + * phase) listed first. + * @param phaseNode Phase entry for the phase where the reactions occur. This + * phase definition is used to determine the source of the reactions added + * to the Kinetics object. + * @param rootNode The root node of the file containing the phase definition, + * which will be treated as the default source for reactions + */ +unique_ptr newKinetics(std::vector& phases, + const AnyMap& phaseNode, + const AnyMap& rootNode=AnyMap()); + +/*! + * Add reactions to a Kinetics object + * + * @param kin The Kinetics object to be initialized + * @param phaseNode Phase entry for the phase where the reactions occur. This + * phase definition is used to determine the source of the reactions added + * to the Kinetics object. + * @param rootNode The root node of the file containing the phase definition, + * which will be treated as the default source for reactions + */ +void addReactions(Kinetics& kin, const AnyMap& phaseNode, + const AnyMap& rootNode=AnyMap()); + } #endif diff --git a/src/kinetics/KineticsFactory.cpp b/src/kinetics/KineticsFactory.cpp index 520ea25b9..269f4cea6 100644 --- a/src/kinetics/KineticsFactory.cpp +++ b/src/kinetics/KineticsFactory.cpp @@ -41,6 +41,7 @@ Kinetics* KineticsFactory::newKinetics(XML_Node& phaseData, KineticsFactory::KineticsFactory() { reg("none", []() { return new Kinetics(); }); reg("gaskinetics", []() { return new GasKinetics(); }); + m_synonyms["gas"] = "gaskinetics"; reg("interface", []() { return new InterfaceKinetics(); }); reg("edge", []() { return new EdgeKinetics(); }); } @@ -50,4 +51,82 @@ Kinetics* KineticsFactory::newKinetics(const string& model) return create(toLowerCopy(model)); } +unique_ptr newKinetics(vector& phases, + const AnyMap& phaseNode, + const AnyMap& rootNode) +{ + unique_ptr kin(KineticsFactory::factory()->newKinetics( + phaseNode.getString("kinetics", "none"))); + for (auto& phase : phases) { + kin->addPhase(*phase); + } + kin->init(); + if (kin->kineticsType() != "Kinetics") { + addReactions(*kin, phaseNode, rootNode); + } + return kin; +} + +void addReactions(Kinetics& kin, const AnyMap& phaseNode, const AnyMap& rootNode) +{ + // Find sections containing reactions to add + vector sections, rules; + + if (phaseNode.hasKey("reactions")) { + const auto& reactionsNode = phaseNode.at("reactions"); + if (reactionsNode.is()) { + // Specification of the rule for adding species from the default + // 'reactions' section + sections.push_back("reactions"); + rules.push_back(reactionsNode.asString()); + } else if (reactionsNode.is>()) { + // List of sections from which all species should be added + for (const auto& item : reactionsNode.as>()) { + sections.push_back(item); + rules.push_back("all"); + } + } else if (reactionsNode.is>()) { + // Mapping of rules to apply for each specified section containing + // reactions + for (const auto& item : reactionsNode.as>()) { + sections.push_back(item.begin()->first); + rules.push_back(item.begin()->second.asString()); + } + } + } else { + // Default behavior is to add all reactions from the 'reactions' section + sections.push_back("reactions"); + rules.push_back("all"); + } + + // Add reactions from each section + for (size_t i = 0; i < sections.size(); i++) { + if (rules[i] == "all") { + kin.skipUndeclaredSpecies(false); + kin.skipUndeclaredThirdBodies(false); + } else if (rules[i] == "declared-species") { + kin.skipUndeclaredSpecies(true); + kin.skipUndeclaredThirdBodies(true); + } else if (rules[i] != "none") { + throw CanteraError("setupKinetics", "Unknown rule '{}' for adding " + "species from the '{}' section.", rules[i], sections[i]); + } + const auto& slash = boost::ifind_first(sections[i], "/"); + if (slash) { + // specified section is in a different file + string fileName (sections[i].begin(), slash.begin()); + string node(slash.end(), sections[i].end()); + AnyMap reactions = AnyMap::fromYamlFile(fileName); + for (const auto& R : reactions[node].asVector()) { + kin.addReaction(newReaction(R, kin)); + } + } else { + // specified section is in the current file + for (const auto& R : rootNode.at(sections[i]).asVector()) { + kin.addReaction(newReaction(R, kin)); + } + } + } +} + } diff --git a/test/data/ideal-gas.yaml b/test/data/ideal-gas.yaml index 9b8aeb91c..660f70e11 100644 --- a/test/data/ideal-gas.yaml +++ b/test/data/ideal-gas.yaml @@ -40,6 +40,20 @@ phases: thermo: ideal-gas state: {T: 300.0, P: 1 atm, X: {AR: 0.2, O2: 0.7, N2: 0.1}} +- name: simple-kinetics + thermo: ideal-gas + kinetics: gas + state: {T: 300.0, P: 1 atm, X: {AR: 0.2, O2: 0.7, N2: 0.1}} + +- name: remote-kinetics + thermo: ideal-gas + kinetics: gas + species: + - species: all + - species-elements.yaml/species: all + reactions: [reactions, species-elements.yaml/nox-reactions] + state: {T: 300.0, P: 1 atm, X: {AR: 0.2, O2: 0.7, N2: 0.1}} + elements: - symbol: Ar @@ -94,3 +108,36 @@ species: - [2.500000000E+00, 0.000000000E+00, 0.000000000E+00, 0.000000000E+00, 0.000000000E+00, -7.453750000E+02, 4.366000000E+00] note: "120186" + +- name: O + composition: {O: 1} + thermo: + model: NASA7 + temperature-ranges: [200, 1000, 3500] + data: + - [3.168267100E+00, -3.279318840E-03, 6.643063960E-06, -6.128066240E-09, + 2.112659710E-12, 2.912225920E+04, 2.051933460E+00] + - [2.569420780E+00, -8.597411370E-05, 4.194845890E-08, -1.001777990E-11, + 1.228336910E-15, 2.921757910E+04, 4.784338640E+00] + note: "L 1/90" + +- name: N + composition: {N: 1} + thermo: + model: NASA7 + temperature-ranges: [200.00, 1000.00, 6000.00] + data: + - [2.500000000E+00, 0.000000000E+00, 0.000000000E+00, 0.000000000E+00, + 0.000000000E+00, 5.610463700E+04, 4.193908700E+00] + - [2.415942900E+00, 1.748906500E-04, -1.190236900E-07, 3.022624500E-11, + -2.036098200E-15, 5.613377300E+04, 4.649609600E+00] + note: "L 6/88" + + +reactions: +- equation: N + NO <=> N2 + O + rate-constant: [2.70000E+13, 0, 355] + id: NOx-R1 + +- equation: N + O2 <=> NO + O + rate-constant: [9.00000E+09, 1, 6500] diff --git a/test/data/species-elements.yaml b/test/data/species-elements.yaml index 3382b93f3..0888e1415 100644 --- a/test/data/species-elements.yaml +++ b/test/data/species-elements.yaml @@ -30,3 +30,10 @@ species: - [4.823072900E+00, 2.627025100E-03, -9.585087400E-07, 1.600071200E-10, -9.775230300E-15, 8.073404800E+03, -2.201720700E+00] note: "L 7/88" + +nox-reactions: +- equation: N2O (+ M) <=> N2 + O (+ M) + type: falloff + high-P-rate-constant: [7.91000E+10, 0, 56020] + low-P-rate-constant: [6.37000E+14, 0, 56640] + efficiencies: {AR: 0.625} diff --git a/test/kinetics/kineticsFromYaml.cpp b/test/kinetics/kineticsFromYaml.cpp index 6d0c80c3f..f0b15f96a 100644 --- a/test/kinetics/kineticsFromYaml.cpp +++ b/test/kinetics/kineticsFromYaml.cpp @@ -1,6 +1,8 @@ #include "gtest/gtest.h" #include "cantera/base/Units.h" #include "cantera/IdealGasMix.h" +#include "cantera/kinetics/KineticsFactory.h" +#include "cantera/thermo/ThermoFactory.h" using namespace Cantera; @@ -163,3 +165,31 @@ TEST(Reaction, ChebyshevFromYaml) EXPECT_DOUBLE_EQ(CR.rate.Pmin(), 1000); EXPECT_NEAR(CR.rate.updateRC(std::log(T), 1.0/T), 130512.2773948636, 1e-9); } + +TEST(Kinetics, GasKineticsFromYaml1) +{ + AnyMap infile = AnyMap::fromYamlFile("ideal-gas.yaml"); + auto phaseNodes = infile["phases"].asMap("name"); + auto phaseNode = phaseNodes.at("simple-kinetics"); + shared_ptr thermo = newPhase(*phaseNode, infile); + std::vector phases{thermo.get()}; + auto kin = newKinetics(phases, *phaseNode, infile); + EXPECT_EQ(kin->nReactions(), (size_t) 2); + const auto& R = kin->reaction(0); + EXPECT_EQ(R->reactants.at("NO"), 1); + EXPECT_EQ(R->products.at("N2"), 1); + EXPECT_EQ(R->id, "NOx-R1"); + const auto& ER = std::dynamic_pointer_cast(R); + EXPECT_DOUBLE_EQ(ER->rate.preExponentialFactor(), 2.7e10); +} + +TEST(Kinetics, GasKineticsFromYaml2) +{ + AnyMap infile = AnyMap::fromYamlFile("ideal-gas.yaml"); + auto phaseNodes = infile["phases"].asMap("name"); + auto phaseNode = phaseNodes.at("remote-kinetics"); + shared_ptr thermo = newPhase(*phaseNode, infile); + std::vector phases{thermo.get()}; + auto kin = newKinetics(phases, *phaseNode, infile); + EXPECT_EQ(kin->nReactions(), (size_t) 3); +} diff --git a/test/thermo/thermoFromYaml.cpp b/test/thermo/thermoFromYaml.cpp index 23c65d613..42ce58cc5 100644 --- a/test/thermo/thermoFromYaml.cpp +++ b/test/thermo/thermoFromYaml.cpp @@ -63,7 +63,7 @@ TEST(ThermoFromYaml, speciesAll) auto phaseNodes = infile["phases"].asMap("name"); auto thermo = newPhase(*phaseNodes.at("species-all"), infile); EXPECT_EQ(thermo->nElements(), (size_t) 3); - EXPECT_EQ(thermo->nSpecies(), (size_t) 4); + EXPECT_EQ(thermo->nSpecies(), (size_t) 6); EXPECT_EQ(thermo->species(1)->name, "NO"); EXPECT_EQ(thermo->species(2)->name, "N2"); }