17#include <boost/algorithm/string/predicate.hpp>
20#include <boost/algorithm/string.hpp>
22namespace ba = boost::algorithm;
29 shared_ptr<ReactionRate> rate_,
30 shared_ptr<ThirdBody> tbody_)
31 : reactants(reactants_)
33 , m_from_composition(true)
34 , m_third_body(tbody_)
36 if (reactants.count(
"M") || products.count(
"M")) {
37 throw CanteraError(
"Reaction::Reaction",
38 "Third body 'M' must not be included in either reactant or product maps.");
44 for (
const auto& [name, stoich] : reactants) {
45 if (products.count(name)) {
46 third[name] = products.at(name) - stoich;
50 string name = tbody_->name();
51 if (reactants.count(name) && products.count(name)) {
52 throw CanteraError(
"Reaction::Reaction",
53 "'{}' not acting as third body collider must not be included in both "
54 "reactant and product maps.", name);
57 m_third_body->explicit_3rd =
true;
59 }
else if (!tbody_ && third.size() == 1) {
61 string name = third.begin()->first;
62 m_third_body = make_shared<ThirdBody>(name);
64 m_third_body->explicit_3rd =
true;
70Reaction::Reaction(
const string& equation,
71 shared_ptr<ReactionRate> rate_,
72 shared_ptr<ThirdBody> tbody_)
73 : m_third_body(tbody_)
76 setEquation(equation);
77 if (m_third_body && m_third_body->name() !=
"M") {
78 m_third_body->explicit_3rd =
true;
85 string rate_type = node.
getString(
"type",
"Arrhenius");
88 "Cannot instantiate Reaction with empty Kinetics object.");
91 setParameters(node, kin);
99 if (ba::starts_with(rate_type,
"three-body-")) {
101 rateNode[
"type"] = rate_type.substr(11, rate_type.size() - 11);
108 if (rateNode.
hasKey(
"rate-constant")) {
109 if (!ba::starts_with(rate_type,
"interface-")) {
110 rateNode[
"type"] =
"interface-" + rate_type;
112 }
else if (node.
hasKey(
"sticking-coefficient")) {
113 if (!ba::starts_with(rate_type,
"sticking-")) {
114 rateNode[
"type"] =
"sticking-" + rate_type;
116 }
else if (rate_type ==
"Arrhenius") {
118 "Unable to infer interface reaction type.");
125void Reaction::check()
127 if (!allow_nonreactant_orders) {
128 for (
const auto& [name, order] : orders) {
129 if (reactants.find(name) == reactants.end()) {
131 "Reaction order specified for non-reactant species '{}'", name);
136 if (!allow_negative_orders) {
137 for (
const auto& [name, order] : orders) {
140 "Negative reaction order specified for species '{}'", name);
149 if (reversible && !orders.empty()) {
151 "Reaction orders may only be given for irreversible reactions");
160 m_rate->check(equation());
162 string rate_type = m_rate->type();
164 if (rate_type ==
"falloff" || rate_type ==
"chemically-activated") {
165 if (m_third_body->mass_action && !m_from_composition) {
167 "Third-body collider does not use '(+{})' notation.",
168 m_third_body->name());
170 m_third_body->mass_action =
false;
171 }
else if (rate_type ==
"Chebyshev") {
172 if (m_third_body->name() ==
"M") {
173 warn_deprecated(
"Chebyshev reaction equation", input,
"Specifying 'M' "
174 "in the reaction equation for Chebyshev reactions is deprecated.");
175 m_third_body.reset();
177 }
else if (rate_type ==
"pressure-dependent-Arrhenius") {
178 if (m_third_body->name() ==
"M") {
180 "Found superfluous '{}' in pressure-dependent-Arrhenius reaction.",
181 m_third_body->name());
185 if (rate_type ==
"falloff" || rate_type ==
"chemically-activated") {
186 if (!m_from_composition) {
188 "Reaction equation for falloff reaction '{}'\n does not "
189 "contain valid pressure-dependent third body", equation());
191 m_third_body = make_shared<ThirdBody>(
"(+M)");
196AnyMap Reaction::parameters(
bool withInput)
const
204 static bool reg = AnyMap::addOrderingRules(
"Reaction",
205 {{
"head",
"equation"},
207 {
"tail",
"duplicate"},
209 {
"tail",
"negative-orders"},
210 {
"tail",
"nonreactant-orders"}
213 out[
"__type__"] =
"Reaction";
218void Reaction::getParameters(
AnyMap& reactionNode)
const
222 "Serialization of empty Reaction object is not supported.");
225 reactionNode[
"equation"] = equation();
228 reactionNode[
"duplicate"] =
true;
230 reactionNode.
exclude(
"duplicate");
233 reactionNode[
"orders"] = orders;
235 reactionNode.
exclude(
"orders");
237 if (allow_negative_orders) {
238 reactionNode[
"negative-orders"] =
true;
240 reactionNode.
exclude(
"negative-orders");
242 if (allow_nonreactant_orders) {
243 reactionNode[
"nonreactant-orders"] =
true;
245 reactionNode.
exclude(
"nonreactant-orders");
248 reactionNode.
update(m_rate->parameters());
251 string rtype = reactionNode[
"type"].asString();
252 if (rtype ==
"pressure-dependent-Arrhenius") {
254 }
else if (m_explicit_type && ba::ends_with(rtype,
"Arrhenius")) {
257 reactionNode[
"type"] =
"three-body";
259 reactionNode[
"type"] =
"elementary";
261 }
else if (ba::ends_with(rtype,
"Arrhenius")) {
263 }
else if (m_explicit_type) {
264 reactionNode[
"type"] = type();
265 }
else if (ba::ends_with(rtype,
"Blowers-Masel")) {
266 reactionNode[
"type"] =
"Blowers-Masel";
270 m_third_body->getParameters(reactionNode);
278 "Cannot set reaction parameters from empty node.");
283 setEquation(node[
"equation"].asString(), &kin);
285 if (node.
hasKey(
"orders")) {
286 for (
const auto& [name, order] : node[
"orders"].asMap<
double>()) {
287 orders[name] = order;
296 duplicate = node.
getBool(
"duplicate",
false);
297 allow_negative_orders = node.
getBool(
"negative-orders",
false);
298 allow_nonreactant_orders = node.
getBool(
"nonreactant-orders",
false);
301 m_third_body->setParameters(node);
302 if (m_third_body->name() ==
"M" && m_third_body->efficiencies.size() == 1) {
303 m_third_body->explicit_3rd =
true;
305 }
else if (node.
hasKey(
"default-efficiency") || node.
hasKey(
"efficiencies")) {
307 "Reaction '{}' specifies efficiency parameters\n"
308 "but does not involve third body colliders.", equation());
312void Reaction::setRate(shared_ptr<ReactionRate> rate)
316 "Reaction rate for reaction '{}' must not be empty.", equation());
319 for (
const auto& [
id, callback] : m_setRateCallbacks) {
324void Reaction::registerSetRateCallback(
void*
id,
const function<
void()>& callback)
326 m_setRateCallbacks[id] = callback;
329void Reaction::removeSetRateCallback(
void*
id)
331 m_setRateCallbacks.erase(
id);
334string Reaction::reactantString()
const
336 std::ostringstream result;
337 for (
auto iter = reactants.begin(); iter != reactants.end(); ++iter) {
338 if (iter != reactants.begin()) {
341 if (iter->second != 1.0) {
342 result << iter->second <<
" ";
344 result << iter->first;
347 result << m_third_body->collider();
352string Reaction::productString()
const
354 std::ostringstream result;
355 for (
auto iter = products.begin(); iter != products.end(); ++iter) {
356 if (iter != products.begin()) {
359 if (iter->second != 1.0) {
360 result << iter->second <<
" ";
362 result << iter->first;
365 result << m_third_body->collider();
370string Reaction::equation()
const
373 return reactantString() +
" <=> " + productString();
375 return reactantString() +
" => " + productString();
379void Reaction::setEquation(
const string& equation,
const Kinetics* kin)
382 string rate_type = (m_rate) ? m_rate->type() : input.getString(
"type",
"");
383 if (ba::starts_with(rate_type,
"three-body")) {
385 m_explicit_type =
true;
386 }
else if (rate_type ==
"elementary") {
388 m_explicit_type =
true;
390 }
else if (kin && kin->
thermo(0).
nDim() != 3) {
393 }
else if (rate_type ==
"electron-collision-plasma") {
401 for (
const auto& [name, stoich] : reactants) {
403 if (products.count(name)) {
405 size_t generic = third_body ==
"M"
406 || third_body ==
"(+M)" || third_body ==
"(+ M)";
409 if (stoich > 1 && products[third_body] > 1) {
417 if (ba::starts_with(rate_type,
"three-body")) {
419 "Reactants for reaction '{}'\n"
420 "do not contain a valid third body collider", equation);
424 }
else if (countM > 1) {
426 "Multiple generic third body colliders 'M' are not supported", equation);
428 }
else if (count > 1) {
434 }
else if (m_third_body) {
436 auto& effs = m_third_body->efficiencies;
437 if (effs.size() != 1 || !reactants.count(effs.begin()->first)) {
439 "Detected ambiguous third body colliders in reaction '{}'\n"
440 "ThirdBody object needs to specify a single species", equation);
442 third_body = effs.begin()->first;
443 m_third_body->explicit_3rd =
true;
444 }
else if (input.hasKey(
"efficiencies")) {
446 auto effs = input[
"efficiencies"].asMap<
double>();
447 if (effs.size() != 1 || !reactants.count(effs.begin()->first)) {
449 "Detected ambiguous third body colliders in reaction '{}'\n"
450 "Collision efficiencies need to specify single species", equation);
452 third_body = effs.begin()->first;
453 m_third_body = make_shared<ThirdBody>(third_body);
454 m_third_body->explicit_3rd =
true;
455 }
else if (input.hasKey(
"default-efficiency")) {
458 "Detected ambiguous third body colliders in reaction '{}'\n"
459 "Third-body definition requires specification of efficiencies",
461 }
else if (ba::starts_with(rate_type,
"three-body")) {
464 "Detected ambiguous third body colliders in reaction '{}'\n"
465 "A valid ThirdBody or collision efficiency definition is required",
471 }
else if (third_body !=
"M" && !ba::starts_with(rate_type,
"three-body")
472 && !ba::starts_with(third_body,
"(+"))
481 for (
const auto& [name, stoich] : reactants) {
482 if (trunc(stoich) != stoich) {
485 nreac +=
static_cast<size_t>(stoich);
489 for (
const auto& [name, stoich] : products) {
490 if (trunc(stoich) != stoich) {
493 nprod +=
static_cast<size_t>(stoich);
497 if (nreac != 3 && nprod != 3) {
503 string tName = m_third_body->name();
504 if (tName != third_body && third_body !=
"M" && tName !=
"M") {
506 "Detected incompatible third body colliders in reaction '{}'\n"
507 "ThirdBody definition does not match equation", equation);
509 m_third_body->setName(third_body);
511 m_third_body = make_shared<ThirdBody>(third_body);
515 auto reac = reactants.find(third_body);
516 if (trunc(reac->second) != 1) {
519 reactants.erase(reac);
523 auto prod = products.find(third_body);
524 if (trunc(prod->second) != 1) {
527 products.erase(prod);
531string Reaction::type()
const
534 throw CanteraError(
"Reaction::type",
"Empty Reaction does not have a type");
537 string rate_type = m_rate->type();
538 string sub_type = m_rate->subType();
539 if (sub_type !=
"") {
540 return rate_type +
"-" + sub_type;
544 return "three-body-" + rate_type;
565 for (
const auto& [name, order] : orders) {
568 rate_units.
update(phase.standardConcentrationUnits(), -order);
570 for (
const auto& [name, stoich] : reactants) {
573 if (name ==
"M" || ba::starts_with(name,
"(+")) {
577 }
else if (orders.find(name) == orders.end()) {
580 rate_units.
update(phase.standardConcentrationUnits(), -stoich);
584 if (m_third_body && m_third_body->mass_action) {
589 Reaction::rate_units = rate_units.
product();
593void updateUndeclared(vector<string>& undeclared,
596 for (
const auto& [name, stoich]: comp) {
598 undeclared.emplace_back(name);
603void Reaction::checkBalance(
const Kinetics& kin)
const
608 for (
const auto& [name, stoich] : products) {
611 for (
size_t m = 0; m < ph.
nElements(); m++) {
616 for (
const auto& [name, stoich] : reactants) {
619 for (
size_t m = 0; m < ph.
nElements(); m++) {
626 for (
const auto& [elem, balance] : balr) {
627 double elemsum = balr[elem] + balp[elem];
628 double elemdiff = fabs(balp[elem] - balr[elem]);
629 if (elemsum > 0.0 && elemdiff / elemsum > 1e-4) {
631 msg += fmt::format(
" {} {} {}\n",
632 elem, balr[elem], balp[elem]);
637 "The following reaction is unbalanced: {}\n"
638 " Element Reactants Products\n{}",
647 double reac_sites = 0.0;
648 double prod_sites = 0.0;
650 for (
const auto& [name, stoich] : reactants) {
653 reac_sites += stoich * surf.size(k);
656 for (
const auto& [name, stoich] : products) {
657 size_t k = surf.speciesIndex(name);
659 prod_sites += stoich * surf.size(k);
662 if (fabs(reac_sites - prod_sites) > 1e-5 * (reac_sites + prod_sites)) {
664 "Number of surface sites not balanced in reaction {}.\n"
665 "Reactant sites: {}\nProduct sites: {}",
666 equation(), reac_sites, prod_sites);
670bool Reaction::checkSpecies(
const Kinetics& kin)
const
673 vector<string> undeclared;
674 updateUndeclared(undeclared, reactants, kin);
675 updateUndeclared(undeclared, products, kin);
676 if (!undeclared.empty()) {
680 throw InputFileError(
"Reaction::checkSpecies", input,
"Reaction '{}'\n"
681 "contains undeclared species: '{}'",
682 equation(), ba::join(undeclared,
"', '"));
687 updateUndeclared(undeclared, orders, kin);
688 if (!undeclared.empty()) {
692 if (input.hasKey(
"orders")) {
695 "defines reaction orders for undeclared species: '{}'",
696 equation(), ba::join(undeclared,
"', '"));
699 throw InputFileError(
"Reaction::checkSpecies", input,
"Reaction '{}'\n"
700 "defines reaction orders for undeclared species: '{}'",
701 equation(), ba::join(undeclared,
"', '"));
706 return m_third_body->checkSpecies(*
this, kin);
714bool Reaction::usesElectrochemistry(
const Kinetics& kin)
const
717 vector<double> e_counter(kin.
nPhases(), 0.0);
720 for (
const auto& [name, stoich] : products) {
728 for (
const auto& [name, stoich] : reactants) {
736 for (
double delta_e : e_counter) {
737 if (std::abs(delta_e) > 1e-4) {
746ThirdBody::ThirdBody(
const string& third_body)
751void ThirdBody::setName(
const string& third_body)
753 string name = third_body;
754 if (ba::starts_with(third_body,
"(+ ")) {
756 name = third_body.substr(3, third_body.size() - 4);
757 }
else if (ba::starts_with(third_body,
"(+")) {
759 name = third_body.substr(2, third_body.size() - 3);
762 if (name == m_name) {
765 if (name ==
"M" && efficiencies.size() == 1) {
770 if (efficiencies.size()) {
772 "Conflicting efficiency definition for explicit third body '{}'", name);
775 default_efficiency = 0.;
776 efficiencies[m_name] = 1.;
779ThirdBody::ThirdBody(
const AnyMap& node)
784void ThirdBody::setParameters(
const AnyMap& node)
786 if (node.
hasKey(
"default-efficiency")) {
787 double value = node[
"default-efficiency"].asDouble();
788 if (m_name !=
"M" && value != 0.) {
789 throw InputFileError(
"ThirdBody::setParameters", node[
"default-efficiency"],
790 "Invalid default efficiency for explicit collider {};\n"
791 "value is optional and/or needs to be zero", m_name);
793 default_efficiency = value;
795 if (node.
hasKey(
"efficiencies")) {
796 efficiencies = node[
"efficiencies"].asMap<
double>();
799 && (efficiencies.size() != 1 || efficiencies.begin()->first != m_name))
802 "Detected incompatible third body colliders definitions");
806void ThirdBody::getParameters(
AnyMap& node)
const
808 if (m_name ==
"M" || explicit_3rd) {
809 if (efficiencies.size()) {
810 node[
"efficiencies"] = efficiencies;
813 if (default_efficiency != 1.0 && !explicit_3rd) {
814 node[
"default-efficiency"] = default_efficiency;
819double ThirdBody::efficiency(
const string& k)
const
821 return getValue(efficiencies, k, default_efficiency);
824string ThirdBody::collider()
const
827 return " + " + m_name;
829 return " (+" + m_name +
")";
834 vector<string> undeclared;
835 updateUndeclared(undeclared, efficiencies, kin);
837 if (!undeclared.empty()) {
841 rxn.
input[
"efficiencies"],
"Reaction '{}'\n"
842 "defines third-body efficiencies for undeclared species: '{}'",
843 rxn.
equation(), ba::join(undeclared,
"', '"));
847 "is a three-body reaction with undeclared species: '{}'",
848 rxn.
equation(), ba::join(undeclared,
"', '"));
861 return make_unique<Reaction>();
866 return make_unique<Reaction>(rxn_node, kin);
874 vector<string> tokens;
876 tokens.push_back(
"+");
878 size_t last_used =
npos;
879 bool reactants =
true;
880 for (
size_t i = 1; i < tokens.size(); i++) {
881 if (tokens[i] ==
"+" || ba::starts_with(tokens[i],
"(+") ||
882 tokens[i] ==
"<=>" || tokens[i] ==
"=" || tokens[i] ==
"=>") {
883 string species = tokens[i-1];
886 bool mass_action =
true;
887 if (last_used !=
npos && tokens[last_used] ==
"(+"
888 && ba::ends_with(species,
")")) {
891 species =
"(+" + species;
892 }
else if (last_used == i - 1 && ba::starts_with(species,
"(+")
893 && ba::ends_with(species,
")")) {
896 }
else if (last_used == i - 2) {
898 }
else if (last_used == i - 3) {
908 "Error parsing reaction string '{}'.\n"
909 "Current token: '{}'\nlast_used: '{}'",
911 (last_used ==
npos) ?
"n/a" : tokens[last_used]);
914 && mass_action && species !=
"M"))
929 if (tokens[i] ==
"<=>" || tokens[i] ==
"=") {
932 }
else if (tokens[i] ==
"=>") {
941 vector<shared_ptr<Reaction>> all_reactions;
943 auto R = make_shared<Reaction>(node, kinetics);
944 R->validate(kinetics);
945 if (R->valid() && R->checkSpecies(kinetics)) {
946 all_reactions.emplace_back(R);
949 return all_reactions;
Header file for class Cantera::Array2D.
Base class for kinetics managers and also contains the kineticsmgr module documentation (see Kinetics...
Factory class for reaction rate objects.
Header for a simple thermodynamics model of a surface phase derived from ThermoPhase,...
Header file for class ThermoPhase, the base class for phases with thermodynamic properties,...
Base class defining common data possessed by both AnyMap and AnyValue objects.
A map of string keys to values whose type can vary at runtime.
void exclude(const string &key)
Mark key as excluded from this map.
void copyMetadata(const AnyMap &other)
Copy metadata including input line/column from an existing AnyMap.
bool hasKey(const string &key) const
Returns true if the map contains an item named key.
bool empty() const
Return boolean indicating whether AnyMap is empty.
void setFlowStyle(bool flow=true)
Use "flow" style when outputting this AnyMap to YAML.
bool getBool(const string &key, bool default_) const
If key exists, return it as a bool, otherwise return default_.
const string & getString(const string &key, const string &default_) const
If key exists, return it as a string, otherwise return default_.
void update(const AnyMap &other, bool keepExisting=true)
Add items from other to this AnyMap.
A wrapper for a variable whose type is determined at runtime.
const vector< T > & asVector(size_t nMin=npos, size_t nMax=npos) const
Return the held value, if it is a vector of type T.
Base class for exceptions thrown by Cantera classes.
virtual string getMessage() const
Method overridden by derived classes to format the error message.
Public interface for kinetics managers.
ThermoPhase & thermo(size_t n=0)
This method returns a reference to the nth ThermoPhase object defined in this kinetics mechanism.
size_t nPhases() const
The number of phases participating in the reaction mechanism.
void skipUndeclaredSpecies(bool skip)
Determine behavior when adding a new reaction that contains species not defined in any of the phases ...
void skipUndeclaredThirdBodies(bool skip)
Determine behavior when adding a new reaction that contains third-body efficiencies for species not d...
size_t kineticsSpeciesIndex(size_t k, size_t n) const
The location of species k of phase n in species arrays.
ThermoPhase & speciesPhase(const string &nm)
This function looks up the name of a species and returns a reference to the ThermoPhase object of the...
size_t speciesPhaseIndex(size_t k) const
This function takes as an argument the kineticsSpecies index (that is, the list index in the list of ...
size_t nDim() const
Returns the number of spatial dimensions (1, 2, or 3)
size_t speciesIndex(const string &name) const
Returns the index of a species named 'name' within the Phase object.
double nAtoms(size_t k, size_t m) const
Number of atoms of element m in species k.
size_t nElements() const
Number of elements.
string elementName(size_t m) const
Name of the element with index m.
double charge(size_t k) const
Dimensionless electrical charge of a single molecule of species k The charge is normalized by the the...
Abstract base class which stores data about a reaction and its rate parameterization so that it can b...
bool reversible
True if the current reaction is reversible. False otherwise.
string equation() const
The chemical equation for this reaction.
Composition products
Product species and stoichiometric coefficients.
Composition reactants
Reactant species and stoichiometric coefficients.
AnyMap input
Input data used for specific models.
void setValid(bool valid)
Set validity flag of reaction.
A simple thermodynamic model for a surface phase, assuming an ideal solution model.
Base class for a phase with thermodynamic properties.
virtual Units standardConcentrationUnits() const
Returns the units of the "standard concentration" for this phase.
A representation of the units associated with a dimensional quantity.
double fpValueCheck(const string &val)
Translate a string into one double value, with error checking.
void tokenizeString(const string &in_val, vector< string > &v)
This function separates a string up into tokens according to the location of white space.
shared_ptr< ReactionRate > newReactionRate(const string &type)
Create a new empty ReactionRate object.
Namespace for the Cantera kernel.
const size_t npos
index returned by functions to indicate "no position"
void parseReactionEquation(Reaction &R, const string &equation, const AnyBase &reactionNode, const Kinetics *kin)
Parse reaction equation.
vector< shared_ptr< Reaction > > getReactions(const AnyValue &items, Kinetics &kinetics)
Create Reaction objects for each item (an AnyMap) in items.
void warn_deprecated(const string &source, const AnyBase &node, const string &message)
A deprecation warning for syntax in an input file.
const U & getValue(const map< T, U > &m, const T &key, const U &default_val)
Const accessor for a value in a map.
map< string, double > Composition
Map from string names to doubles.
unique_ptr< Reaction > newReaction(const string &type)
Create a new empty Reaction object.
Contains declarations for string manipulation functions within Cantera.
Unit aggregation utility.
void update(const Units &units, double exponent)
Update exponent of item with matching units; if it does not exist, add unit-exponent pair at end of s...
Units product() const
Calculate product of units-exponent stack.
void join(double exponent)
Join (update) exponent of standard units, where the updated exponent is the sum of the pre-existing e...
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...