Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update three body logic #1338

Merged
merged 11 commits into from
Jul 24, 2022
9 changes: 7 additions & 2 deletions include/cantera/kinetics/Reaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,10 @@ 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 third body collider is ambiguous
bool m_explicit_3rd = false;
ischoegl marked this conversation as resolved.
Show resolved Hide resolved

//! Flag indicating that object was instantiated from reactant/product compositions
bool m_from_composition = false;
Expand Down Expand Up @@ -239,8 +242,10 @@ class ThirdBody
void setParameters(const AnyMap& node);

//! Get third-body efficiencies from AnyMap *node*
//! @param node AnyMap receiving serialized parameters
//! @param explicit_3rd Flag triggering explicit third body efficiency output
//! @since New in Cantera 3.0
void getParameters(AnyMap& node) const;
void getParameters(AnyMap& node, bool explicit_3rd=false) const;
ischoegl marked this conversation as resolved.
Show resolved Hide resolved

//! Get the third-body efficiency for species *k*
double efficiency(const std::string& k) const;
Expand Down
113 changes: 95 additions & 18 deletions src/kinetics/Reaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ Reaction::Reaction(const Composition& reactants_,
, m_third_body(tbody_)
{
setRate(rate_);

// ensure safe serialization
ischoegl marked this conversation as resolved.
Show resolved Hide resolved
size_t count = 0;
for (const auto& reac : reactants) {
if (products.count(reac.first)) {
count++;
}
}
ischoegl marked this conversation as resolved.
Show resolved Hide resolved
if (count) {
if (tbody_ && tbody_->name() != "M") {
m_explicit_3rd = true;
} else if (!tbody_) {
m_explicit_type = true;
}
}
}

Reaction::Reaction(const std::string& equation,
Expand All @@ -59,7 +74,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")) {
Expand Down Expand Up @@ -162,24 +183,26 @@ 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";
}

if (m_third_body) {
m_third_body->getParameters(reactionNode);
m_third_body->getParameters(reactionNode, m_explicit_3rd);
}
}

Expand Down Expand Up @@ -210,7 +233,11 @@ void Reaction::setParameters(const AnyMap& node, const Kinetics& kin)
allow_nonreactant_orders = node.getBool("nonreactant-orders", false);

if (m_third_body) {
m_third_body->setParameters(node);
try {
m_third_body->setParameters(node);
} catch (CanteraError& err) {
throw InputFileError("Reaction::setParameters", input, err.getMessage());
}
} else if (node.hasKey("default-efficiency") || node.hasKey("efficiencies")) {
throw InputFileError("Reaction::setParameters", input,
"Reaction '{}' specifies efficiency parameters\n"
Expand Down Expand Up @@ -310,12 +337,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
Expand All @@ -341,7 +368,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);
Expand All @@ -355,12 +382,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) {
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.find(effs.begin()->first) == reactants.end())
{
throw InputFileError("Reaction::setEquation", input,
"Detected ambiguous third body colliders in reaction '{}'\n"
"ThirdBody definition is not valid", equation);
}
third_body = effs.begin()->first;
m_explicit_3rd = true;
} else if (input.hasKey("efficiencies") && input.hasKey("default-efficiency")) {
// third body is implicity defined by efficiency
auto effs = input["efficiencies"].asMap<double>();
if (effs.size() != 1
|| reactants.find(effs.begin()->first) == reactants.end())
{
throw InputFileError("Reaction::setEquation", input,
"Detected ambigous third body colliders in reaction '{}'\n"
"Invalid ThirdBody definition", equation);
}
third_body = effs.begin()->first;
m_explicit_3rd = true;
} else if (ba::starts_with(rate_type, "three-body")) {
// no disambiguation of third bodies
throw InputFileError("Reaction::setEquation", input,
"Detected ambigous third body colliders in reaction '{}'\n"
"A valid ThirdBody definition is required", equation);
} else if (input.hasKey("efficiencies") || input.hasKey("default-efficiency")) {
// insufficient disambiguation of third bodies
throw InputFileError("Reaction::setEquation", input,
"Detected ambigous third body colliders in reaction '{}'\n"
"A complete ThirdBody 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
Expand Down Expand Up @@ -390,7 +454,14 @@ void Reaction::setEquation(const std::string& equation, const Kinetics* kin)
}

if (m_third_body) {
m_third_body->setName(third_body);
std::string tName = m_third_body->name();
if (tName == "M") {
m_third_body->setName(third_body);
} else if (tName != third_body) {
throw InputFileError("Reaction::setEquation", input,
"Detected incompatible third body colliders in reaction '{}'\n"
"ThirdBody definition does not match equation", equation);
}
} else {
m_third_body.reset(new ThirdBody(third_body));
}
Expand Down Expand Up @@ -689,11 +760,17 @@ void ThirdBody::setParameters(const AnyMap& node)
if (node.hasKey("efficiencies")) {
efficiencies = node["efficiencies"].asMap<double>();
}
if (m_name != "M"
&& (efficiencies.size() != 1 || efficiencies.begin()->first != m_name))
{
throw CanteraError("Reaction::setEquation",
"Detected incompatible third body colliders definitions");
}
}

void ThirdBody::getParameters(AnyMap& node) const
void ThirdBody::getParameters(AnyMap& node, bool explicit_3rd) const
{
if (m_name == "M") {
if (m_name == "M" || explicit_3rd) {
if (efficiencies.size()) {
node["efficiencies"] = efficiencies;
node["efficiencies"].setFlowStyle();
Expand Down
43 changes: 43 additions & 0 deletions test/kinetics/kineticsFromScratch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,49 @@ 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<ArrheniusRate>(1.2e11, -1.0, 0.0);
auto tbody = make_shared<ThirdBody>("O2");
auto R = make_shared<Reaction>(equation, rate, tbody);

AnyMap input = R->parameters();
EXPECT_FALSE(input.hasKey("type"));
EXPECT_TRUE(input.hasKey("efficiencies"));
auto efficiencies = input["efficiencies"].asMap<double>();
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_bodies5)
{
std::string equation = "H2 + O2 => H2 + O2";
auto rate = make_shared<ArrheniusRate>(1.2e11, -1.0, 0.0);
auto tbody = make_shared<ThirdBody>("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<ArrheniusRate>(1.2e11, -1.0, 0.0);
auto tbody = make_shared<ThirdBody>("O2");
auto R = make_shared<Reaction>(reac, prod, rate, tbody);

AnyMap input = R->parameters();
EXPECT_FALSE(input.hasKey("type"));
EXPECT_TRUE(input.hasKey("efficiencies"));
auto efficiencies = input["efficiencies"].asMap<double>();
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";
Expand Down
Loading