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
2 changes: 1 addition & 1 deletion include/cantera/kinetics/Falloff.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion include/cantera/kinetics/Reaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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";
Expand Down
2 changes: 1 addition & 1 deletion interfaces/cython/cantera/reaction.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down
124 changes: 105 additions & 19 deletions src/kinetics/Reaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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")) {
Expand Down Expand Up @@ -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";
}

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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<double>();
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
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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",
Expand All @@ -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<double>();
}
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;
}
}
Expand Down
83 changes: 83 additions & 0 deletions test/kinetics/kineticsFromScratch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ArrheniusRate>(1.2e11, -1.0, 0.0);
auto tbody = make_shared<ThirdBody>("O2");
auto R = make_shared<Reaction>(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<double>();
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<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);
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<double>();
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<ArrheniusRate>(1.2e11, -1.0, 0.0);
auto tbody = make_shared<ThirdBody>("O2");
auto R = make_shared<Reaction>(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<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_bodies8)
{
std::string equation = "CH2OCH + M <=> CH2CHO + M";
auto rate = make_shared<ArrheniusRate>(1.2e11, -1.0, 0.0);
auto tbody = make_shared<ThirdBody>();
tbody->efficiencies = parseCompString("O2:1");
tbody->default_efficiency = 0.;
auto R = make_shared<Reaction>(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<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