diff --git a/include/cantera/kinetics/Falloff.h b/include/cantera/kinetics/Falloff.h index c982b4aba0..db2592c7b1 100644 --- a/include/cantera/kinetics/Falloff.h +++ b/include/cantera/kinetics/Falloff.h @@ -195,7 +195,7 @@ class FalloffRate : public ReactionRate "To be removed after Cantera 3.0; superseded by getFalloffCoeffs."); } - virtual void getParameters(AnyMap& node) const; + virtual void getParameters(AnyMap& node) const override; //! Evaluate reaction rate //! @param shared_data data shared by all reactions of a given type diff --git a/include/cantera/kinetics/Reaction.h b/include/cantera/kinetics/Reaction.h index a890a67337..1074c1cb65 100644 --- a/include/cantera/kinetics/Reaction.h +++ b/include/cantera/kinetics/Reaction.h @@ -195,7 +195,7 @@ class Reaction bool m_valid = true; //! Flag indicating that serialization uses explicit type - bool m_explicit_rate = false; + bool m_explicit_type = false; //! Flag indicating that object was instantiated from reactant/product compositions bool m_from_composition = false; @@ -239,6 +239,7 @@ class ThirdBody void setParameters(const AnyMap& node); //! Get third-body efficiencies from AnyMap *node* + //! @param node AnyMap receiving serialized parameters //! @since New in Cantera 3.0 void getParameters(AnyMap& node) const; @@ -269,6 +270,9 @@ class ThirdBody //! (`true` for three-body reactions, `false` for falloff reactions) bool mass_action = true; + //! Flag indicating whether third body requires explicit serialization + bool explicit_3rd = false; + protected: //! Name of the third body collider std::string m_name = "M"; diff --git a/interfaces/cython/cantera/reaction.pyx b/interfaces/cython/cantera/reaction.pyx index 63344c7982..f7da168d24 100644 --- a/interfaces/cython/cantera/reaction.pyx +++ b/interfaces/cython/cantera/reaction.pyx @@ -1146,7 +1146,7 @@ cdef class Reaction: def __cinit__(self, reactants=None, products=None, rate=None, *, equation=None, init=True, efficiencies=None, - Kinetics kinetics=None, third_body=None, **kwargs): + Kinetics kinetics=None, third_body=None): if kinetics: warnings.warn( "Parameter 'kinetics' is no longer used and will be removed after " diff --git a/src/kinetics/Reaction.cpp b/src/kinetics/Reaction.cpp index 87d5252ca3..ac5bee9265 100644 --- a/src/kinetics/Reaction.cpp +++ b/src/kinetics/Reaction.cpp @@ -37,6 +37,22 @@ Reaction::Reaction(const Composition& reactants_, , m_third_body(tbody_) { setRate(rate_); + + // set flags ensuring correct serialization output + bool count = 0; + for (const auto& reac : reactants) { + if (products.count(reac.first)) { + count = true; + break; + } + } + if (count) { + if (tbody_ && tbody_->name() != "M") { + m_third_body->explicit_3rd = true; + } else if (!tbody_) { + m_explicit_type = true; + } + } } Reaction::Reaction(const std::string& equation, @@ -46,6 +62,9 @@ Reaction::Reaction(const std::string& equation, { setEquation(equation); setRate(rate_); + if (tbody_ && tbody_->name() != "M") { + m_third_body->explicit_3rd = true; + } } Reaction::Reaction(const AnyMap& node, const Kinetics& kin) @@ -59,7 +78,13 @@ Reaction::Reaction(const AnyMap& node, const Kinetics& kin) setParameters(node, kin); size_t nDim = kin.thermo(kin.reactionPhaseIndex()).nDim(); if (nDim == 3) { - setRate(newReactionRate(node, calculateRateCoeffUnits(kin))); + if (ba::starts_with(rate_type, "three-body-")) { + AnyMap rateNode = node; + rateNode["type"] = rate_type.substr(11, rate_type.size() - 11); + setRate(newReactionRate(rateNode, calculateRateCoeffUnits(kin))); + } else { + setRate(newReactionRate(node, calculateRateCoeffUnits(kin))); + } } else { AnyMap rateNode = node; if (rateNode.hasKey("rate-constant")) { @@ -162,19 +187,21 @@ void Reaction::getParameters(AnyMap& reactionNode) const reactionNode.update(m_rate->parameters()); // strip information not needed for reconstruction - std::string type = reactionNode["type"].asString(); - if (type == "pressure-dependent-Arrhenius") { + std::string rtype = reactionNode["type"].asString(); + if (rtype == "pressure-dependent-Arrhenius") { // skip - } else if (m_explicit_rate && ba::ends_with(type, "Arrhenius")) { + } else if (m_explicit_type && ba::ends_with(rtype, "Arrhenius")) { // retain type information if (m_third_body) { reactionNode["type"] = "three-body"; } else { reactionNode["type"] = "elementary"; } - } else if (ba::ends_with(type, "Arrhenius")) { + } else if (ba::ends_with(rtype, "Arrhenius")) { reactionNode.erase("type"); - } else if (ba::ends_with(type, "Blowers-Masel")) { + } else if (m_explicit_type) { + reactionNode["type"] = type(); + } else if (ba::ends_with(rtype, "Blowers-Masel")) { reactionNode["type"] = "Blowers-Masel"; } @@ -211,6 +238,9 @@ void Reaction::setParameters(const AnyMap& node, const Kinetics& kin) if (m_third_body) { m_third_body->setParameters(node); + if (m_third_body->name() == "M" && m_third_body->efficiencies.size() == 1) { + m_third_body->explicit_3rd = true; + } } else if (node.hasKey("default-efficiency") || node.hasKey("efficiencies")) { throw InputFileError("Reaction::setParameters", input, "Reaction '{}' specifies efficiency parameters\n" @@ -310,12 +340,12 @@ void Reaction::setEquation(const std::string& equation, const Kinetics* kin) parseReactionEquation(*this, equation, input, kin); std::string rate_type = input.getString("type", ""); - if (rate_type == "three-body") { + if (ba::starts_with(rate_type, "three-body")) { // state type when serializing - m_explicit_rate = true; + m_explicit_type = true; } else if (rate_type == "elementary") { // user override - m_explicit_rate = true; + m_explicit_type = true; return; } else if (kin && kin->thermo(kin->reactionPhaseIndex()).nDim() != 3) { // interface reactions @@ -341,7 +371,7 @@ void Reaction::setEquation(const std::string& equation, const Kinetics* kin) } if (count == 0) { - if (rate_type == "three-body") { + if (ba::starts_with(rate_type, "three-body")) { throw InputFileError("Reaction::setEquation", input, "Reactants for reaction '{}'\n" "do not contain a valid third body collider", equation); @@ -355,12 +385,49 @@ void Reaction::setEquation(const std::string& equation, const Kinetics* kin) } else if (count > 1) { // equations with more than one explicit third-body collider are handled as a // regular elementary reaction unless the equation contains a generic third body - if (!countM) { + if (countM) { + // generic collider 'M' is selected as third body + third_body = "M"; + } else if (m_third_body) { + // third body is defined as explicit object + auto& effs = m_third_body->efficiencies; + if (effs.size() != 1 || !reactants.count(effs.begin()->first)) { + throw InputFileError("Reaction::setEquation", input, + "Detected ambiguous third body colliders in reaction '{}'\n" + "ThirdBody object needs to specify a single species", equation); + } + third_body = effs.begin()->first; + m_third_body->explicit_3rd = true; + } else if (input.hasKey("efficiencies")) { + // third body is implicitly defined by efficiency + auto effs = input["efficiencies"].asMap(); + if (effs.size() != 1 || !reactants.count(effs.begin()->first)) { + throw InputFileError("Reaction::setEquation", input, + "Detected ambiguous third body colliders in reaction '{}'\n" + "Collision efficiencies need to specify single species", equation); + } + third_body = effs.begin()->first; + m_third_body.reset(new ThirdBody(third_body)); + m_third_body->explicit_3rd = true; + } else if (input.hasKey("default-efficiency")) { + // insufficient disambiguation of third bodies + throw InputFileError("Reaction::setEquation", input, + "Detected ambiguous third body colliders in reaction '{}'\n" + "Third-body definition requires specification of efficiencies", + equation); + } else if (ba::starts_with(rate_type, "three-body")) { + // no disambiguation of third bodies + throw InputFileError("Reaction::setEquation", input, + "Detected ambiguous third body colliders in reaction '{}'\n" + "A valid ThirdBody or collision efficiency definition is required", + equation); + } else { return; } - third_body = "M"; - } else if (!ba::starts_with(third_body, "(+")) { + } else if (third_body != "M" && !ba::starts_with(rate_type, "three-body") + && !ba::starts_with(third_body, "(+")) + { // check for conditions of three-body reactions: // - integer stoichiometric conditions // - either reactant or product side involves exactly three species @@ -390,6 +457,12 @@ void Reaction::setEquation(const std::string& equation, const Kinetics* kin) } if (m_third_body) { + std::string tName = m_third_body->name(); + if (tName != third_body && third_body != "M" && tName != "M") { + throw InputFileError("Reaction::setEquation", input, + "Detected incompatible third body colliders in reaction '{}'\n" + "ThirdBody definition does not match equation", equation); + } m_third_body->setName(third_body); } else { m_third_body.reset(new ThirdBody(third_body)); @@ -656,9 +729,10 @@ void ThirdBody::setName(const std::string& third_body) if (name == m_name) { return; } - if (name == "M") { - throw CanteraError("ThirdBody::setName", - "Unable to revert explicit third body '{}' to 'M'", m_name); + if (name == "M" && efficiencies.size() == 1) { + // revert from explicit name to generic collider + m_name = name; + return; } if (efficiencies.size()) { throw CanteraError("ThirdBody::setName", @@ -684,21 +758,33 @@ void ThirdBody::setEfficiencies(const AnyMap& node) void ThirdBody::setParameters(const AnyMap& node) { if (node.hasKey("default-efficiency")) { - default_efficiency = node["default-efficiency"].asDouble(); + double value = node["default-efficiency"].asDouble(); + if (m_name != "M" && value != 0.) { + throw InputFileError("ThirdBody::setParameters", node["default-efficiency"], + "Invalid default efficiency for explicit collider {};\n" + "value is optional and/or needs to be zero", m_name); + } + default_efficiency = value; } if (node.hasKey("efficiencies")) { efficiencies = node["efficiencies"].asMap(); } + if (m_name != "M" + && (efficiencies.size() != 1 || efficiencies.begin()->first != m_name)) + { + throw InputFileError("ThirdBody::setParameters", node, + "Detected incompatible third body colliders definitions"); + } } void ThirdBody::getParameters(AnyMap& node) const { - if (m_name == "M") { + if (m_name == "M" || explicit_3rd) { if (efficiencies.size()) { node["efficiencies"] = efficiencies; node["efficiencies"].setFlowStyle(); } - if (default_efficiency != 1.0) { + if (default_efficiency != 1.0 && !explicit_3rd) { node["default-efficiency"] = default_efficiency; } } diff --git a/test/kinetics/kineticsFromScratch.cpp b/test/kinetics/kineticsFromScratch.cpp index d860c47f9a..f50dd0007f 100644 --- a/test/kinetics/kineticsFromScratch.cpp +++ b/test/kinetics/kineticsFromScratch.cpp @@ -149,6 +149,89 @@ TEST_F(KineticsFromScratch, multiple_third_bodies3) EXPECT_TRUE(R->usesThirdBody()); } +TEST_F(KineticsFromScratch, multiple_third_bodies4) +{ + std::string equation = "H2 + O2 => H2 + O2"; + auto rate = make_shared(1.2e11, -1.0, 0.0); + auto tbody = make_shared("O2"); + auto R = make_shared(equation, rate, tbody); + EXPECT_EQ(R->thirdBody()->name(), "O2"); + EXPECT_EQ(R->thirdBody()->default_efficiency, 0.); + + AnyMap input = R->parameters(false); + EXPECT_FALSE(input.hasKey("type")); + EXPECT_TRUE(input.hasKey("efficiencies")); + auto efficiencies = input["efficiencies"].asMap(); + EXPECT_EQ(efficiencies.size(), 1); + EXPECT_EQ(efficiencies.begin()->first, "O2"); + EXPECT_FALSE(input.hasKey("default-efficiency")); +} + +TEST_F(KineticsFromScratch, multiple_third_bodies5) +{ + std::string equation = "H2 + O2 => H2 + O2"; + auto rate = make_shared(1.2e11, -1.0, 0.0); + auto tbody = make_shared("AR"); // incompatible third body + ASSERT_THROW(Reaction(equation, rate, tbody), CanteraError); +} + +TEST_F(KineticsFromScratch, multiple_third_bodies6) +{ + Composition reac = parseCompString("H2:1"); + Composition prod = parseCompString("H2:1"); + auto rate = make_shared(1.2e11, -1.0, 0.0); + auto tbody = make_shared("O2"); + auto R = make_shared(reac, prod, rate, tbody); + EXPECT_EQ(R->thirdBody()->name(), "O2"); + EXPECT_EQ(R->thirdBody()->default_efficiency, 0.); + + AnyMap input = R->parameters(false); + EXPECT_FALSE(input.hasKey("type")); + EXPECT_TRUE(input.hasKey("efficiencies")); + auto efficiencies = input["efficiencies"].asMap(); + EXPECT_EQ(efficiencies.size(), 1); + EXPECT_EQ(efficiencies.begin()->first, "O2"); + EXPECT_FALSE(input.hasKey("default-efficiency")); +} + +TEST_F(KineticsFromScratch, multiple_third_bodies7) +{ + std::string equation = "CH2OCH + M <=> CH2CHO + M"; + auto rate = make_shared(1.2e11, -1.0, 0.0); + auto tbody = make_shared("O2"); + auto R = make_shared(equation, rate, tbody); + EXPECT_EQ(R->thirdBody()->name(), "M"); + + AnyMap input = R->parameters(false); + EXPECT_FALSE(input.hasKey("type")); + EXPECT_TRUE(input.hasKey("efficiencies")); + auto efficiencies = input["efficiencies"].asMap(); + EXPECT_EQ(efficiencies.size(), 1); + EXPECT_EQ(efficiencies.begin()->first, "O2"); + EXPECT_TRUE(input.hasKey("default-efficiency")); + EXPECT_EQ(input["default-efficiency"].asDouble(), 0.); +} + +TEST_F(KineticsFromScratch, multiple_third_bodies8) +{ + std::string equation = "CH2OCH + M <=> CH2CHO + M"; + auto rate = make_shared(1.2e11, -1.0, 0.0); + auto tbody = make_shared(); + tbody->efficiencies = parseCompString("O2:1"); + tbody->default_efficiency = 0.; + auto R = make_shared(equation, rate, tbody); + EXPECT_EQ(R->thirdBody()->name(), "M"); + + AnyMap input = R->parameters(false); + EXPECT_FALSE(input.hasKey("type")); + EXPECT_TRUE(input.hasKey("efficiencies")); + auto efficiencies = input["efficiencies"].asMap(); + EXPECT_EQ(efficiencies.size(), 1); + EXPECT_EQ(efficiencies.begin()->first, "O2"); + EXPECT_TRUE(input.hasKey("default-efficiency")); + EXPECT_EQ(input["default-efficiency"].asDouble(), 0.); +} + TEST_F(KineticsFromScratch, add_two_temperature_plasma) { std::string equation = "O + H => O + H"; diff --git a/test/kinetics/kineticsFromYaml.cpp b/test/kinetics/kineticsFromYaml.cpp index 703297ea82..2d011d3e6f 100644 --- a/test/kinetics/kineticsFromYaml.cpp +++ b/test/kinetics/kineticsFromYaml.cpp @@ -91,6 +91,107 @@ TEST(Reaction, ThreeBodyFromYaml2) const auto& rate = std::dynamic_pointer_cast(R->rate()); EXPECT_DOUBLE_EQ(rate->preExponentialFactor(), 1.2e11); + + AnyMap input = R->parameters(false); + EXPECT_FALSE(input.hasKey("type")); + EXPECT_TRUE(input.hasKey("efficiencies")); + + auto efficiencies = input["efficiencies"].asMap(); + EXPECT_EQ(efficiencies.size(), 2); + EXPECT_EQ(efficiencies["AR"], 0.83); + EXPECT_EQ(efficiencies["H2O"], 5.); +} + +TEST(Reaction, ThreeBodyFromYaml3) +{ + auto sol = newSolution("gri30.yaml", "", "None"); + AnyMap rxn = AnyMap::fromYamlString( + "{equation: CH2 + M <=> CH2(S) + M," + " rate-constant: {A: 5.0e+9, b: 0.0, Ea: 0.0}}"); + + auto R = new Reaction(rxn, *(sol->kinetics())); + EXPECT_EQ(R->type(), "three-body-Arrhenius"); + EXPECT_TRUE(R->usesThirdBody()); + EXPECT_EQ(R->thirdBody()->name(), "M"); + + const auto& rate = std::dynamic_pointer_cast(R->rate()); + EXPECT_DOUBLE_EQ(rate->preExponentialFactor(), 5.0e+9); +} + +TEST(Reaction, ThreeBodyFromYaml4) +{ + auto sol = newSolution("gri30.yaml", "", "None"); + AnyMap rxn = AnyMap::fromYamlString( + "{equation: CH2 + O2 <=> CH2(S) + O2," + " type: three-body," + " rate-constant: {A: 5.0e+9, b: 0.0, Ea: 0.0}}"); + + auto R = newReaction(rxn, *(sol->kinetics())); + EXPECT_EQ(R->type(), "three-body-Arrhenius"); + EXPECT_TRUE(R->usesThirdBody()); + EXPECT_EQ(R->thirdBody()->name(), "O2"); + + const auto& rate = std::dynamic_pointer_cast(R->rate()); + EXPECT_DOUBLE_EQ(rate->preExponentialFactor(), 5.0e+9); + + AnyMap input = R->parameters(false); + EXPECT_EQ(input.getString("type", ""), "three-body"); + EXPECT_FALSE(input.hasKey("efficiencies")); + EXPECT_FALSE(input.hasKey("default-efficiency")); +} + +TEST(Reaction, ThreeBodyFromYaml5) +{ + auto sol = newSolution("gri30.yaml", "", "None"); + AnyMap rxn = AnyMap::fromYamlString( + "{equation: CH2 + O2 <=> CH2 + O2," + " rate-constant: {A: 5.0e+9, b: 0.0, Ea: 0.0}," + " efficiencies: {O2: 1.}}"); + + auto R = newReaction(rxn, *(sol->kinetics())); + EXPECT_EQ(R->type(), "three-body-Arrhenius"); + EXPECT_TRUE(R->usesThirdBody()); + EXPECT_EQ(R->thirdBody()->name(), "O2"); + EXPECT_EQ(R->thirdBody()->default_efficiency, 0.); + + const auto& rate = std::dynamic_pointer_cast(R->rate()); + EXPECT_DOUBLE_EQ(rate->preExponentialFactor(), 5.0e+9); + + AnyMap input = R->parameters(false); + EXPECT_FALSE(input.hasKey("type")); + EXPECT_TRUE(input.hasKey("efficiencies")); + auto efficiencies = input["efficiencies"].asMap(); + EXPECT_EQ(efficiencies.size(), 1); + EXPECT_EQ(efficiencies.begin()->first, "O2"); + EXPECT_FALSE(input.hasKey("default-efficiency")); +} + +TEST(Reaction, ThreeBodyFromYaml6) +{ + auto sol = newSolution("gri30.yaml", "", "None"); + AnyMap rxn = AnyMap::fromYamlString( + "{equation: CH2 + O2 <=> CH2 + O2," + " type: three-body," + " rate-constant: {A: 5.0e+9, b: 0.0, Ea: 0.0}," + " default-efficiency: 0.," + " efficiencies: {O2: 1.}}"); + + auto R = newReaction(rxn, *(sol->kinetics())); + EXPECT_EQ(R->type(), "three-body-Arrhenius"); + EXPECT_TRUE(R->usesThirdBody()); + EXPECT_EQ(R->thirdBody()->name(), "O2"); + EXPECT_EQ(R->thirdBody()->default_efficiency, 0.); + + const auto& rate = std::dynamic_pointer_cast(R->rate()); + EXPECT_DOUBLE_EQ(rate->preExponentialFactor(), 5.0e+9); + + AnyMap input = R->parameters(false); + EXPECT_EQ(input.getString("type", ""), "three-body"); + EXPECT_TRUE(input.hasKey("efficiencies")); + auto efficiencies = input["efficiencies"].asMap(); + EXPECT_EQ(efficiencies.size(), 1); + EXPECT_EQ(efficiencies.begin()->first, "O2"); + EXPECT_FALSE(input.hasKey("default-efficiency")); } TEST(Reaction, ThreeBodyFromYamlMissingM) @@ -104,6 +205,75 @@ TEST(Reaction, ThreeBodyFromYamlMissingM) EXPECT_THROW(newReaction(rxn, *(sol->kinetics())), CanteraError); } +TEST(Reaction, ThreeBodyFromYamlMultiple) +{ + auto sol = newSolution("gri30.yaml", "", "None"); + AnyMap rxn = AnyMap::fromYamlString( + "{equation: CH2 + O2 <=> CH2 + O2," + " type: three-body," // ambiguous valid explicit third bodies + " rate-constant: {A: 5.0e+9, b: 0.0, Ea: 0.0}}"); + + EXPECT_THROW(newReaction(rxn, *(sol->kinetics())), CanteraError); +} + +TEST(Reaction, ThreeBodyFromYamlIncompatible1) +{ + auto sol = newSolution("gri30.yaml", "", "None"); + AnyMap rxn = AnyMap::fromYamlString( + "{equation: CH2 + O2 <=> CH2 + O2," + " rate-constant: {A: 5.0e+9, b: 0.0, Ea: 0.0}," + " default-efficiency: 0.," + " efficiencies: {AR: 1.}}"); // incompatible third body + + EXPECT_THROW(newReaction(rxn, *(sol->kinetics())), CanteraError); +} + +TEST(Reaction, ThreeBodyFromYamlIncompatible2) +{ + auto sol = newSolution("gri30.yaml", "", "None"); + AnyMap rxn = AnyMap::fromYamlString( + "{equation: CH2 + O2 <=> CH2(S) + O2," + " rate-constant: {A: 5.0e+9, b: 0.0, Ea: 0.0}," + " default-efficiency: 0.," + " efficiencies: {AR: 1.}}"); // incompatible single third body + + EXPECT_THROW(newReaction(rxn, *(sol->kinetics())), CanteraError); +} + +TEST(Reaction, ThreeBodyFromYamlIncompatible3) +{ + auto sol = newSolution("gri30.yaml", "", "None"); + AnyMap rxn = AnyMap::fromYamlString( + "{equation: CH2 + O2 <=> CH2 + O2," + " rate-constant: {A: 5.0e+9, b: 0.0, Ea: 0.0}," + " default-efficiency: 1.," // Needs to be zero + " efficiencies: {O2: 1.}}"); + + EXPECT_THROW(newReaction(rxn, *(sol->kinetics())), CanteraError); +} + +TEST(Reaction, ThreeBodyFromYamlIncompatible4) +{ + auto sol = newSolution("gri30.yaml", "", "None"); + AnyMap rxn = AnyMap::fromYamlString( + "{equation: CH2 + O2 <=> CH2 + O2," + " rate-constant: {A: 5.0e+9, b: 0.0, Ea: 0.0}," + " default-efficiency: 0.}"); // missing efficiencies field + + EXPECT_THROW(newReaction(rxn, *(sol->kinetics())), CanteraError); +} + +TEST(Reaction, ThreeBodyFromYamlIncompatible5) +{ + auto sol = newSolution("gri30.yaml", "", "None"); + AnyMap rxn = AnyMap::fromYamlString( + "{equation: CH2 + O2 <=> CH2 + O2," + " type: three-body," + " rate-constant: {A: 5.0e+9, b: 0.0, Ea: 0.0}}"); // third body ambiguous + + EXPECT_THROW(newReaction(rxn, *(sol->kinetics())), CanteraError); +} + TEST(Reaction, FalloffFromYaml1) { auto sol = newSolution("gri30.yaml", "", "None"); @@ -290,6 +460,37 @@ TEST(Reaction, BlowersMaselFromYaml) EXPECT_FALSE(R->allow_negative_orders); } +TEST(Reaction, ThreeBodyBlowersMaselFromYaml) +{ + auto sol = newSolution("gri30.yaml", "", "None"); + AnyMap rxn = AnyMap::fromYamlString( + "{equation: CH2 + O2 <=> CH2(S) + O2," + " type: three-body-Blowers-Masel," + " rate-constant: [3.87e+04 cm^3/mol/s, 2.7, 6260.0 cal/mol, 1e9 cal/mol]}"); + + auto R = newReaction(rxn, *(sol->kinetics())); + EXPECT_EQ(R->type(), "three-body-Blowers-Masel"); + EXPECT_TRUE(R->usesThirdBody()); + EXPECT_EQ(R->thirdBody()->name(), "O2"); + + const auto& rate = std::dynamic_pointer_cast(R->rate()); + EXPECT_DOUBLE_EQ(rate->preExponentialFactor(), 38.7); + + AnyMap input = R->parameters(false); + EXPECT_EQ(input.getString("type", ""), "three-body-Blowers-Masel"); +} + +TEST(Reaction, InvalidBlowersMaselFromYaml) +{ + auto sol = newSolution("gri30.yaml", "", "None"); + AnyMap rxn = AnyMap::fromYamlString( + "{equation: O + H2 <=> H + OH," + " type: three-body-Blowers-Masel," + " rate-constant: [3.87e+04 cm^3/mol/s, 2.7, 6260.0 cal/mol, 1e9 cal/mol]}"); + + EXPECT_THROW(newReaction(rxn, *(sol->kinetics())), CanteraError); +} + TEST(Reaction, TwoTempPlasmaFromYaml) { auto sol = newSolution("ET_test.yaml");