17 #include <boost/algorithm/string.hpp>
19 namespace ba = boost::algorithm;
24 Reaction::Reaction(
int type)
28 , allow_nonreactant_orders(false)
29 , allow_negative_orders(false)
33 Reaction::Reaction(
int type,
const Composition& reactants_,
36 , reactants(reactants_)
40 , allow_nonreactant_orders(false)
41 , allow_negative_orders(false)
45 void Reaction::validate()
47 if (!allow_nonreactant_orders) {
48 for (
const auto& order : orders) {
49 if (reactants.find(order.first) == reactants.end()) {
50 throw CanteraError(
"Reaction::validate",
"Reaction order "
51 "specified for non-reactant species '" + order.first +
"'");
56 if (!allow_negative_orders) {
57 for (
const auto& order : orders) {
58 if (order.second < 0.0) {
59 throw CanteraError(
"Reaction::validate",
"Negative reaction "
60 "order specified for species '" + order.first +
"'");
66 std::string Reaction::reactantString()
const
68 std::ostringstream result;
69 for (
auto iter = reactants.begin(); iter != reactants.end(); ++iter) {
70 if (iter != reactants.begin()) {
73 if (iter->second != 1.0) {
74 result << iter->second <<
" ";
76 result << iter->first;
81 std::string Reaction::productString()
const
83 std::ostringstream result;
84 for (
auto iter = products.begin(); iter != products.end(); ++iter) {
85 if (iter != products.begin()) {
88 if (iter->second != 1.0) {
89 result << iter->second <<
" ";
91 result << iter->first;
96 std::string Reaction::equation()
const
99 return reactantString() +
" <=> " + productString();
101 return reactantString() +
" => " + productString();
105 ElementaryReaction::ElementaryReaction(
const Composition& reactants_,
110 , allow_negative_pre_exponential_factor(false)
114 ElementaryReaction::ElementaryReaction()
116 , allow_negative_pre_exponential_factor(false)
123 if (!allow_negative_pre_exponential_factor &&
126 "Undeclared negative pre-exponential factor found in reaction '"
131 ThirdBody::ThirdBody(
double default_eff)
132 : default_efficiency(default_eff)
136 ThreeBodyReaction::ThreeBodyReaction()
141 ThreeBodyReaction::ThreeBodyReaction(
const Composition& reactants_,
143 const Arrhenius& rate_,
144 const ThirdBody& tbody)
145 : ElementaryReaction(reactants_, products_, rate_)
159 FalloffReaction::FalloffReaction()
162 , allow_negative_pre_exponential_factor(false)
166 FalloffReaction::FalloffReaction(
168 const Arrhenius& low_rate_,
const Arrhenius& high_rate_,
169 const ThirdBody& tbody)
171 , low_rate(low_rate_)
172 , high_rate(high_rate_)
174 , falloff(new Falloff())
200 if (!allow_negative_pre_exponential_factor &&
203 throw CanteraError(
"FalloffReaction::validate",
"Negative "
204 "pre-exponential factor found for reaction '" +
equation() +
"'");
207 throw CanteraError(
"FalloffReaction::validate",
"High and "
208 "low rate pre-exponential factors must have the same sign."
213 ChemicallyActivatedReaction::ChemicallyActivatedReaction()
218 ChemicallyActivatedReaction::ChemicallyActivatedReaction(
220 const Arrhenius& low_rate_,
const Arrhenius& high_rate_,
221 const ThirdBody& tbody)
222 : FalloffReaction(reactants_, products_, low_rate_, high_rate_, tbody)
227 PlogReaction::PlogReaction()
232 PlogReaction::PlogReaction(
const Composition& reactants_,
234 : Reaction(
PLOG_RXN, reactants_, products_)
239 ChebyshevReaction::ChebyshevReaction()
244 ChebyshevReaction::ChebyshevReaction(
const Composition& reactants_,
246 const ChebyshevRate& rate_)
252 InterfaceReaction::InterfaceReaction()
253 : is_sticking_coefficient(false)
254 , use_motz_wise_correction(false)
259 InterfaceReaction::InterfaceReaction(
const Composition& reactants_,
261 const Arrhenius& rate_,
263 : ElementaryReaction(reactants_, products_, rate_)
264 , is_sticking_coefficient(isStick)
265 , use_motz_wise_correction(false)
270 ElectrochemicalReaction::ElectrochemicalReaction()
271 : film_resistivity(0.0)
273 , exchange_current_density_formulation(false)
277 ElectrochemicalReaction::ElectrochemicalReaction(
const Composition& reactants_,
279 const Arrhenius& rate_)
280 : InterfaceReaction(reactants_, products_, rate_)
281 , film_resistivity(0.0)
283 , exchange_current_density_formulation(false)
287 Arrhenius readArrhenius(
const XML_Node& arrhenius_node)
289 return Arrhenius(
getFloat(arrhenius_node,
"A",
"toSI"),
294 Units rateCoeffUnits(
const Reaction& R,
const Kinetics& kin,
295 int pressure_dependence=0)
297 if (R.reaction_type == INVALID_RXN) {
302 &&
dynamic_cast<const InterfaceReaction&
>(R).is_sticking_coefficient) {
308 Units rxn_phase_units = kin.thermo(kin.reactionPhaseIndex()).standardConcentrationUnits();
309 Units rcUnits = rxn_phase_units;
310 rcUnits *= Units(1.0, 0, 0, -1);
311 for (
const auto& order : R.orders) {
312 const auto& phase = kin.speciesPhase(order.first);
313 rcUnits *= phase.standardConcentrationUnits().pow(-order.second);
315 for (
const auto& stoich : R.reactants) {
318 if (stoich.first ==
"M") {
319 rcUnits *= rxn_phase_units.pow(-1);
320 }
else if (R.orders.find(stoich.first) == R.orders.end()) {
321 const auto& phase = kin.speciesPhase(stoich.first);
322 rcUnits *= phase.standardConcentrationUnits().pow(-stoich.second);
328 rcUnits *= rxn_phase_units.pow(-pressure_dependence);
332 Arrhenius readArrhenius(
const Reaction& R,
const AnyValue& rate,
333 const Kinetics& kin,
const UnitSystem& units,
334 int pressure_dependence=0)
337 Units rc_units = rateCoeffUnits(R, kin, pressure_dependence);
338 if (rate.is<AnyMap>()) {
339 auto& rate_map = rate.as<AnyMap>();
340 A = units.convert(rate_map[
"A"], rc_units);
341 b = rate_map[
"b"].asDouble();
342 Ta = units.convertActivationEnergy(rate_map[
"Ea"],
"K");
344 auto& rate_vec = rate.asVector<AnyValue>(3);
345 A = units.convert(rate_vec[0], rc_units);
346 b = rate_vec[1].asDouble();
347 Ta = units.convertActivationEnergy(rate_vec[2],
"K");
349 return Arrhenius(A, b, Ta);
361 std::vector<std::string> p;
364 size_t np = p.size();
365 for (
size_t n = 0; n < np; n++) {
371 throw CanteraError(
"readFalloff",
"Lindemann parameterization "
372 "takes no parameters, but {} were given", np);
376 if (np != 3 && np != 4) {
377 throw CanteraError(
"readFalloff",
"Troe parameterization takes "
378 "3 or 4 parameters, but {} were given", np);
382 if (np != 3 && np != 5) {
383 throw CanteraError(
"readFalloff",
"SRI parameterization takes "
384 "3 or 5 parameters, but {} were given", np);
388 throw CanteraError(
"readFalloff",
"Unrecognized falloff type: '{}'",
393 void readFalloff(FalloffReaction& R,
const AnyMap& node)
395 if (node.hasKey(
"Troe")) {
396 auto& f = node[
"Troe"].as<AnyMap>();
402 if (f.hasKey(
"T2")) {
403 params.push_back(f[
"T2"].asDouble());
406 }
else if (node.hasKey(
"SRI")) {
407 auto& f = node[
"SRI"].as<AnyMap>();
414 params.push_back(f[
"D"].asDouble());
417 params.push_back(f[
"E"].asDouble());
425 void readEfficiencies(ThirdBody& tbody,
const XML_Node& rc_node)
427 if (!rc_node.hasChild(
"efficiencies")) {
428 tbody.default_efficiency = 1.0;
431 const XML_Node& eff_node = rc_node.child(
"efficiencies");
432 tbody.default_efficiency =
fpValue(eff_node[
"default"]);
436 void readEfficiencies(ThirdBody& tbody,
const AnyMap& node)
438 tbody.default_efficiency = node.getDouble(
"default-efficiency", 1.0);
439 if (node.hasKey(
"efficiencies")) {
440 tbody.efficiencies = node[
"efficiencies"].asMap<
double>();
444 void setupReaction(Reaction& R,
const XML_Node& rxn_node)
451 std::vector<XML_Node*> orders = rxn_node.getChildren(
"order");
452 for (
size_t i = 0; i < orders.size(); i++) {
453 R.orders[orders[i]->attrib(
"species")] = orders[i]->fp_value();
457 R.id = rxn_node.attrib(
"id");
458 R.duplicate = rxn_node.hasAttrib(
"duplicate");
459 const std::string& rev = rxn_node[
"reversible"];
460 R.reversible = (rev ==
"true" || rev ==
"yes");
463 void parseReactionEquation(Reaction& R,
const AnyValue& equation,
464 const Kinetics& kin) {
467 std::vector<std::string> tokens;
469 tokens.push_back(
"+");
471 size_t last_used =
npos;
472 bool reactants =
true;
473 for (
size_t i = 1; i < tokens.size(); i++) {
474 if (tokens[i] ==
"+" || ba::starts_with(tokens[i],
"(+") ||
475 tokens[i] ==
"<=>" || tokens[i] ==
"=" || tokens[i] ==
"=>") {
476 std::string species = tokens[i-1];
479 if (last_used !=
npos && tokens[last_used] ==
"(+") {
481 species =
"(+" + species;
483 }
else if (last_used == i-1 && ba::starts_with(species,
"(+")
484 && ba::ends_with(species,
")")) {
487 }
else if (last_used == i-2) {
489 }
else if (last_used == i-3) {
492 }
catch (CanteraError& err) {
493 throw InputFileError(
"parseReactionEquation", equation,
497 throw InputFileError(
"parseReactionEquation", equation,
498 "Error parsing reaction string '{}'.\n"
499 "Current token: '{}'\nlast_used: '{}'",
500 equation.asString(), tokens[i],
501 (last_used ==
npos) ?
"n/a" : tokens[last_used]);
503 if (kin.kineticsSpeciesIndex(species) ==
npos
504 && stoich != -1 && species !=
"M") {
505 R.reaction_type = INVALID_RXN;
509 R.reactants[species] += stoich;
511 R.products[species] += stoich;
518 if (tokens[i] ==
"<=>" || tokens[i] ==
"=") {
521 }
else if (tokens[i] ==
"=>") {
522 R.reversible =
false;
528 void setupReaction(Reaction& R,
const AnyMap& node,
const Kinetics& kin)
530 parseReactionEquation(R, node[
"equation"], kin);
532 std::map<std::string, double> orders;
533 if (node.hasKey(
"orders")) {
534 for (
const auto& order : node[
"orders"].asMap<double>()) {
535 R.orders[order.first] = order.second;
536 if (kin.kineticsSpeciesIndex(order.first) ==
npos) {
537 R.reaction_type = INVALID_RXN;
543 R.id = node.getString(
"id",
"");
544 R.duplicate = node.getBool(
"duplicate",
false);
545 R.allow_negative_orders = node.getBool(
"negative-orders",
false);
546 R.allow_nonreactant_orders = node.getBool(
"nonreactant-orders",
false);
551 void setupElementaryReaction(ElementaryReaction& R,
const XML_Node& rxn_node)
553 const XML_Node& rc_node = rxn_node.child(
"rateCoeff");
554 if (rc_node.hasChild(
"Arrhenius")) {
555 R.rate = readArrhenius(rc_node.child(
"Arrhenius"));
556 }
else if (rc_node.hasChild(
"Arrhenius_ExchangeCurrentDensity")) {
557 R.rate = readArrhenius(rc_node.child(
"Arrhenius_ExchangeCurrentDensity"));
559 throw CanteraError(
"setupElementaryReaction",
"Couldn't find Arrhenius node");
561 if (rxn_node[
"negative_A"] ==
"yes") {
562 R.allow_negative_pre_exponential_factor =
true;
564 if (rxn_node[
"negative_orders"] ==
"yes") {
565 R.allow_negative_orders =
true;
567 if (rxn_node[
"nonreactant_orders"] ==
"yes") {
568 R.allow_nonreactant_orders =
true;
570 setupReaction(R, rxn_node);
573 void setupElementaryReaction(ElementaryReaction& R,
const AnyMap& node,
576 setupReaction(R, node, kin);
577 R.allow_negative_pre_exponential_factor = node.getBool(
"negative-A",
false);
578 R.rate = readArrhenius(R, node[
"rate-constant"], kin, node.units());
581 void setupThreeBodyReaction(ThreeBodyReaction& R,
const XML_Node& rxn_node)
583 readEfficiencies(R.third_body, rxn_node.child(
"rateCoeff"));
584 setupElementaryReaction(R, rxn_node);
587 void setupThreeBodyReaction(ThreeBodyReaction& R,
const AnyMap& node,
590 setupElementaryReaction(R, node, kin);
591 if (R.reactants.count(
"M") != 1 || R.products.count(
"M") != 1) {
592 throw InputFileError(
"setupThreeBodyReaction", node[
"equation"],
593 "Reaction equation '{}' does not contain third body 'M'",
594 node[
"equation"].asString());
596 R.reactants.erase(
"M");
597 R.products.erase(
"M");
598 readEfficiencies(R.third_body, node);
601 void setupFalloffReaction(FalloffReaction& R,
const XML_Node& rxn_node)
603 XML_Node& rc_node = rxn_node.child(
"rateCoeff");
604 std::vector<XML_Node*> rates = rc_node.getChildren(
"Arrhenius");
607 for (
size_t i = 0; i < rates.size(); i++) {
608 XML_Node& node = *rates[i];
609 if (node[
"name"] ==
"") {
610 R.high_rate = readArrhenius(node);
612 }
else if (node[
"name"] ==
"k0") {
613 R.low_rate = readArrhenius(node);
616 throw CanteraError(
"setupFalloffReaction",
"Found an Arrhenius XML "
617 "node with an unexpected type '" + node[
"name"] +
"'");
620 if (nLow != 1 || nHigh != 1) {
621 throw CanteraError(
"setupFalloffReaction",
"Did not find the correct "
622 "number of Arrhenius rate expressions");
624 if (rxn_node[
"negative_A"] ==
"yes") {
625 R.allow_negative_pre_exponential_factor =
true;
628 readEfficiencies(R.third_body, rc_node);
629 setupReaction(R, rxn_node);
632 void setupFalloffReaction(FalloffReaction& R,
const AnyMap& node,
635 setupReaction(R, node, kin);
638 std::string third_body;
639 for (
auto& reactant : R.reactants) {
640 if (reactant.second == -1 && ba::starts_with(reactant.first,
"(+")) {
641 third_body = reactant.first;
647 if (third_body ==
"") {
648 throw InputFileError(
"setupFalloffReaction", node[
"equation"],
649 "Reactants for reaction '{}' do not contain a pressure-dependent "
650 "third body", node[
"equation"].asString());
651 }
else if (R.products.count(third_body) == 0) {
652 throw InputFileError(
"setupFalloffReaction", node[
"equation"],
653 "Unable to match third body '{}' in reactants and products of "
654 "reaction '{}'", third_body, node[
"equation"].asString());
658 R.reactants.erase(third_body);
659 R.products.erase(third_body);
661 R.allow_negative_pre_exponential_factor = node.getBool(
"negative-A",
false);
662 if (third_body ==
"(+M)") {
663 readEfficiencies(R.third_body, node);
666 R.third_body.default_efficiency = 0;
667 R.third_body.efficiencies[third_body.substr(2, third_body.size() - 3)] = 1.0;
670 if (node[
"type"] ==
"falloff") {
671 R.low_rate = readArrhenius(R, node[
"low-P-rate-constant"], kin,
673 R.high_rate = readArrhenius(R, node[
"high-P-rate-constant"], kin,
676 R.low_rate = readArrhenius(R, node[
"low-P-rate-constant"], kin,
678 R.high_rate = readArrhenius(R, node[
"high-P-rate-constant"], kin,
685 void setupChemicallyActivatedReaction(ChemicallyActivatedReaction& R,
686 const XML_Node& rxn_node)
688 XML_Node& rc_node = rxn_node.child(
"rateCoeff");
689 std::vector<XML_Node*> rates = rc_node.getChildren(
"Arrhenius");
692 for (
size_t i = 0; i < rates.size(); i++) {
693 XML_Node& node = *rates[i];
694 if (node[
"name"] ==
"kHigh") {
695 R.high_rate = readArrhenius(node);
697 }
else if (node[
"name"] ==
"") {
698 R.low_rate = readArrhenius(node);
701 throw CanteraError(
"setupChemicallyActivatedReaction",
"Found an "
702 "Arrhenius XML node with an unexpected type '" + node[
"name"] +
"'");
705 if (nLow != 1 || nHigh != 1) {
706 throw CanteraError(
"setupChemicallyActivatedReaction",
"Did not find "
707 "the correct number of Arrhenius rate expressions");
710 readEfficiencies(R.third_body, rc_node);
711 setupReaction(R, rxn_node);
714 void setupPlogReaction(PlogReaction& R,
const XML_Node& rxn_node)
716 XML_Node& rc = rxn_node.child(
"rateCoeff");
717 std::multimap<double, Arrhenius> rates;
718 for (
size_t m = 0; m < rc.nChildren(); m++) {
719 const XML_Node& node = rc.child(m);
720 rates.insert({
getFloat(node,
"P",
"toSI"), readArrhenius(node)});
722 R.rate = Plog(rates);
723 setupReaction(R, rxn_node);
726 void setupPlogReaction(PlogReaction& R,
const AnyMap& node,
const Kinetics& kin)
728 setupReaction(R, node, kin);
729 std::multimap<double, Arrhenius> rates;
730 for (
const auto& rate : node.at(
"rate-constants").asVector<AnyMap>()) {
731 rates.insert({rate.convert(
"P",
"Pa"),
732 readArrhenius(R, AnyValue(rate), kin, node.units())});
734 R.rate = Plog(rates);
747 size_t nP = atoi(coeff_node[
"degreeP"].c_str());
748 size_t nT = atoi(coeff_node[
"degreeT"].c_str());
753 for (
size_t t = 0; t < nT; t++) {
754 for (
size_t p = 0; p < nP; p++) {
755 coeffs(t,p) = coeffs_flat[nP*t + p];
758 R.rate = ChebyshevRate(
getFloat(rc,
"Tmin",
"toSI"),
763 setupReaction(R, rxn_node);
766 void setupChebyshevReaction(ChebyshevReaction&R,
const AnyMap& node,
769 setupReaction(R, node, kin);
770 R.reactants.erase(
"(+M)");
771 R.products.erase(
"(+M)");
772 const auto& T_range = node[
"temperature-range"].asVector<AnyValue>(2);
773 const auto& P_range = node[
"pressure-range"].asVector<AnyValue>(2);
774 auto& vcoeffs = node[
"data"].asVector<
vector_fp>();
775 Array2D coeffs(vcoeffs.size(), vcoeffs[0].size());
776 for (
size_t i = 0; i < coeffs.nRows(); i++) {
777 if (vcoeffs[i].size() != vcoeffs[0].size()) {
778 throw InputFileError(
"setupChebyshevReaction", node[
"data"],
779 "Inconsistent number of coefficients in row {} of matrix", i + 1);
781 for (
size_t j = 0; j < coeffs.nColumns(); j++) {
782 coeffs(i, j) = vcoeffs[i][j];
785 const UnitSystem& units = node.units();
786 Units rcUnits = rateCoeffUnits(R, kin);
787 coeffs(0, 0) += std::log10(units.convert(1.0, rcUnits));
788 R.rate = ChebyshevRate(units.convert(T_range[0],
"K"),
789 units.convert(T_range[1],
"K"),
790 units.convert(P_range[0],
"Pa"),
791 units.convert(P_range[1],
"Pa"),
795 void setupInterfaceReaction(InterfaceReaction& R,
const XML_Node& rxn_node)
800 XML_Node& arr = rxn_node.child(
"rateCoeff").child(
"Arrhenius");
802 R.is_sticking_coefficient =
true;
803 R.sticking_species = arr[
"species"];
806 R.use_motz_wise_correction =
true;
808 R.use_motz_wise_correction =
false;
811 XML_Node* parent = rxn_node.parent();
812 if (parent && parent->name() ==
"reactionData"
814 R.use_motz_wise_correction =
true;
818 std::vector<XML_Node*> cov = arr.getChildren(
"coverage");
819 for (
const auto& node : cov) {
820 CoverageDependency& cdep = R.coverage_deps[node->attrib(
"species")];
821 cdep.a =
getFloat(*node,
"a",
"toSI");
825 setupElementaryReaction(R, rxn_node);
828 void setupInterfaceReaction(InterfaceReaction& R,
const AnyMap& node,
831 setupReaction(R, node, kin);
832 R.allow_negative_pre_exponential_factor = node.getBool(
"negative-A",
false);
834 if (node.hasKey(
"rate-constant")) {
835 R.rate = readArrhenius(R, node[
"rate-constant"], kin, node.units());
836 }
else if (node.hasKey(
"sticking-coefficient")) {
837 R.is_sticking_coefficient =
true;
838 R.rate = readArrhenius(R, node[
"sticking-coefficient"], kin, node.units());
839 R.use_motz_wise_correction = node.getBool(
"Motz-Wise",
840 kin.thermo().input().getBool(
"Motz-Wise",
false));
841 R.sticking_species = node.getString(
"sticking-species",
"");
843 throw InputFileError(
"setupInterfaceReaction", node,
844 "Reaction must include either a 'rate-constant' or"
845 " 'sticking-coefficient' node.");
848 if (node.hasKey(
"coverage-dependencies")) {
849 for (
const auto& item : node[
"coverage-dependencies"].as<AnyMap>()) {
851 if (item.second.is<AnyMap>()) {
852 auto& cov_map = item.second.as<AnyMap>();
853 a = cov_map[
"a"].asDouble();
854 m = cov_map[
"m"].asDouble();
855 E = node.units().convertActivationEnergy(cov_map[
"E"],
"K");
857 auto& cov_vec = item.second.asVector<AnyValue>(3);
858 a = cov_vec[0].asDouble();
859 m = cov_vec[1].asDouble();
860 E = node.units().convertActivationEnergy(cov_vec[2],
"K");
862 R.coverage_deps[item.first] = CoverageDependency(a, E, m);
867 void setupElectrochemicalReaction(ElectrochemicalReaction& R,
868 const XML_Node& rxn_node)
872 if (type ==
"butlervolmer") {
875 "To be removed after Cantera 2.5.");
876 }
else if (type ==
"butlervolmer_noactivitycoeffs") {
879 "To be removed after Cantera 2.5.");
880 }
else if (type ==
"surfaceaffinity") {
883 "To be removed after Cantera 2.5.");
884 }
else if (type ==
"global") {
887 "To be removed after Cantera 2.5.");
890 XML_Node& rc = rxn_node.
child(
"rateCoeff");
892 if (rc_type ==
"exchangecurrentdensity") {
893 R.exchange_current_density_formulation =
true;
894 }
else if (rc_type !=
"" && rc_type !=
"arrhenius") {
895 throw CanteraError(
"setupElectrochemicalReaction",
896 "Unknown rate coefficient type: '" + rc_type +
"'");
898 if (rc.hasChild(
"Arrhenius_ExchangeCurrentDensity")) {
899 R.exchange_current_density_formulation =
true;
902 if (rc.hasChild(
"electrochem") && rc.child(
"electrochem").hasAttrib(
"beta")) {
906 if (rxn_node.hasChild(
"filmResistivity")) {
908 "Not implemented. To be removed after Cantera 2.5.");
911 setupInterfaceReaction(R, rxn_node);
917 throw CanteraError(
"setupElectrochemicalReaction",
918 "A Butler-Volmer reaction must be reversible");
923 R.allow_nonreactant_orders =
true;
924 for (
const auto& sp : R.reactants) {
925 R.orders[sp.first] += sp.second * (1.0 - R.beta);
927 for (
const auto& sp : R.products) {
928 R.orders[sp.first] += sp.second * R.beta;
933 if (rxn_node.hasChild(
"reactionOrderFormulation")) {
935 "To be removed after Cantera 2.5.");
938 R.allow_nonreactant_orders =
true;
939 const XML_Node& rof_node = rxn_node.child(
"reactionOrderFormulation");
941 R.orders = initial_orders;
943 for (
const auto& sp : R.reactants) {
944 R.orders[sp.first] = 0.0;
948 for (
const auto& sp : R.reactants) {
949 double c =
getValue(initial_orders, sp.first, sp.second);
950 R.orders[sp.first] += c * (1.0 - R.beta);
952 for (
const auto& sp : R.products) {
953 double c =
getValue(initial_orders, sp.first, sp.second);
954 R.orders[sp.first] += c * R.beta;
957 throw CanteraError(
"setupElectrochemicalReaction",
"unknown model "
958 "for reactionOrderFormulation XML_Node: '" +
959 rof_node[
"model"] +
"'");
964 if (rxn_node.hasChild(
"orders")) {
966 for (
const auto& order : orders) {
967 R.orders[order.first] = order.second;
972 void setupElectrochemicalReaction(ElectrochemicalReaction& R,
973 const AnyMap& node,
const Kinetics& kin)
975 setupInterfaceReaction(R, node, kin);
976 R.beta = node.getDouble(
"beta", 0.5);
977 R.exchange_current_density_formulation = node.getBool(
978 "exchange-current-density-formulation",
false);
981 bool isElectrochemicalReaction(Reaction& R,
const Kinetics& kin)
986 for (
const auto& sp : R.products) {
987 size_t kkin = kin.kineticsSpeciesIndex(sp.first);
988 size_t i = kin.speciesPhaseIndex(kkin);
989 size_t kphase = kin.thermo(i).speciesIndex(sp.first);
990 e_counter[i] += sp.second * kin.thermo(i).charge(kphase);
994 for (
const auto& sp : R.reactants) {
995 size_t kkin = kin.kineticsSpeciesIndex(sp.first);
996 size_t i = kin.speciesPhaseIndex(kkin);
997 size_t kphase = kin.thermo(i).speciesIndex(sp.first);
998 e_counter[i] -= sp.second * kin.thermo(i).charge(kphase);
1002 for (
double delta_e : e_counter) {
1003 if (std::abs(delta_e) > 1e-4) {
1017 && (type ==
"edge" || type ==
"surface")) {
1018 type =
"electrochemical";
1022 if (type ==
"elementary" || type ==
"arrhenius" || type ==
"") {
1023 auto R = make_shared<ElementaryReaction>();
1024 setupElementaryReaction(*R, rxn_node);
1026 }
else if (type ==
"threebody" || type ==
"three_body") {
1027 auto R = make_shared<ThreeBodyReaction>();
1028 setupThreeBodyReaction(*R, rxn_node);
1030 }
else if (type ==
"falloff") {
1031 auto R = make_shared<FalloffReaction>();
1032 setupFalloffReaction(*R, rxn_node);
1034 }
else if (type ==
"chemact" || type ==
"chemically_activated") {
1035 auto R = make_shared<ChemicallyActivatedReaction>();
1036 setupChemicallyActivatedReaction(*R, rxn_node);
1038 }
else if (type ==
"plog" || type ==
"pdep_arrhenius") {
1039 auto R = make_shared<PlogReaction>();
1040 setupPlogReaction(*R, rxn_node);
1042 }
else if (type ==
"chebyshev") {
1043 auto R = make_shared<ChebyshevReaction>();
1044 setupChebyshevReaction(*R, rxn_node);
1046 }
else if (type ==
"interface" || type ==
"surface" || type ==
"edge" ||
1048 auto R = make_shared<InterfaceReaction>();
1049 setupInterfaceReaction(*R, rxn_node);
1051 }
else if (type ==
"electrochemical" ||
1052 type ==
"butlervolmer_noactivitycoeffs" ||
1053 type ==
"butlervolmer" ||
1054 type ==
"surfaceaffinity") {
1055 auto R = make_shared<ElectrochemicalReaction>();
1056 setupElectrochemicalReaction(*R, rxn_node);
1060 "Unknown reaction type '" + rxn_node[
"type"] +
"'");
1066 std::string type =
"elementary";
1067 if (node.
hasKey(
"type")) {
1068 type = node[
"type"].asString();
1074 parseReactionEquation(testReaction, node[
"equation"], kin);
1075 if (isElectrochemicalReaction(testReaction, kin)) {
1077 setupElectrochemicalReaction(*R, node, kin);
1078 return unique_ptr<Reaction>(move(R));
1081 setupInterfaceReaction(*R, node, kin);
1082 return unique_ptr<Reaction>(move(R));
1086 if (type ==
"elementary") {
1088 setupElementaryReaction(*R, node, kin);
1089 return unique_ptr<Reaction>(move(R));
1090 }
else if (type ==
"three-body") {
1092 setupThreeBodyReaction(*R, node, kin);
1093 return unique_ptr<Reaction>(move(R));
1094 }
else if (type ==
"falloff") {
1096 setupFalloffReaction(*R, node, kin);
1097 return unique_ptr<Reaction>(move(R));
1098 }
else if (type ==
"chemically-activated") {
1100 setupFalloffReaction(*R, node, kin);
1101 return unique_ptr<Reaction>(move(R));
1102 }
else if (type ==
"pressure-dependent-Arrhenius") {
1104 setupPlogReaction(*R, node, kin);
1105 return unique_ptr<Reaction>(move(R));
1106 }
else if (type ==
"Chebyshev") {
1108 setupChebyshevReaction(*R, node, kin);
1109 return unique_ptr<Reaction>(move(R));
1112 "Unknown reaction type '{}'", type);
1118 std::vector<shared_ptr<Reaction> > all_reactions;
1119 for (
const auto& rxnnode : node.
child(
"reactionData").
getChildren(
"reaction")) {
1122 return all_reactions;
1128 std::vector<shared_ptr<Reaction>> all_reactions;
1130 shared_ptr<Reaction> R(
newReaction(node, kinetics));
1131 if (R->reaction_type != INVALID_RXN) {
1132 all_reactions.emplace_back(R);
1135 "Reaction '{}' contains undeclared species.", R->equation());
1138 return all_reactions;
Header file for class Cantera::Array2D.
Parameterizations for reaction falloff functions.
Base class for kinetics managers and also contains the kineticsmgr module documentation (see Kinetics...
A map of string keys to values whose type can vary at runtime.
bool hasKey(const std::string &key) const
Returns true if the map contains an item named key.
A wrapper for a variable whose type is determined at runtime.
const std::vector< T > & asVector(size_t nMin=npos, size_t nMax=npos) const
Return the held value, if it is a vector of type T.
A class for 2D arrays stored in column-major (Fortran-compatible) form.
Arrhenius reaction rate type depends only on temperature.
double preExponentialFactor() const
Return the pre-exponential factor A (in m, kmol, s to powers depending on the reaction order)
Base class for exceptions thrown by Cantera classes.
A pressure-dependent reaction parameterized by a bi-variate Chebyshev polynomial in temperature and p...
A reaction where the rate decreases as pressure increases due to collisional stabilization of a react...
An interface reaction which involves charged species.
A reaction which follows mass-action kinetics with a modified Arrhenius reaction rate.
virtual void validate()
Ensure that the rate constant and other parameters for this reaction are valid.
A reaction that is first-order in [M] at low pressure, like a third-body reaction,...
virtual std::string reactantString() const
The reactant side of the chemical equation for this reaction.
virtual void validate()
Ensure that the rate constant and other parameters for this reaction are valid.
virtual std::string productString() const
The product side of the chemical equation for this reaction.
ThirdBody third_body
Relative efficiencies of third-body species in enhancing the reaction rate.
Arrhenius low_rate
The rate constant in the low-pressure limit.
Arrhenius high_rate
The rate constant in the high-pressure limit.
shared_ptr< Falloff > falloff
Falloff function which determines how low_rate and high_rate are combined to determine the rate const...
Base class for falloff function calculators.
A reaction occurring on an interface (i.e. a SurfPhase or an EdgePhase)
Public interface for kinetics managers.
size_t reactionPhaseIndex() const
Phase where the reactions occur.
thermo_t & thermo(size_t n=0)
This method returns a reference to the nth ThermoPhase object defined in this kinetics mechanism.
void skipUndeclaredSpecies(bool skip)
Determine behavior when adding a new reaction that contains species not defined in any of the phases ...
size_t nDim() const
Returns the number of spatial dimensions (1, 2, or 3)
A pressure-dependent reaction parameterized by logarithmically interpolating between Arrhenius rate e...
virtual void validate()
Ensure that the rate constant and other parameters for this reaction are valid.
Intermediate class which stores data about a reaction and its rate parameterization so that it can be...
virtual std::string reactantString() const
The reactant side of the chemical equation for this reaction.
virtual void validate()
Ensure that the rate constant and other parameters for this reaction are valid.
virtual std::string productString() const
The product side of the chemical equation for this reaction.
int reaction_type
Type of the reaction.
std::string equation() const
The chemical equation for this reaction.
double default_efficiency
The default third body efficiency for species not listed in efficiencies.
Composition efficiencies
Map of species to third body efficiency.
A reaction with a non-reacting third body "M" that acts to add or remove energy from the reacting spe...
virtual std::string reactantString() const
The reactant side of the chemical equation for this reaction.
virtual std::string productString() const
The product side of the chemical equation for this reaction.
Class XML_Node is a tree-based representation of the contents of an XML file.
bool hasChild(const std::string &ch) const
Tests whether the current node has a child node with a particular name.
std::vector< XML_Node * > getChildren(const std::string &name) const
Get a vector of pointers to XML_Node containing all of the children of the current node which match t...
XML_Node & child(const size_t n) const
Return a changeable reference to the n'th child of the current node.
CTML ("Cantera Markup Language") is the variant of XML that Cantera uses to store data.
const size_t npos
index returned by functions to indicate "no position"
std::map< std::string, double > Composition
Map from string names to doubles.
std::vector< double > vector_fp
Turn on the use of stl vectors for the basic array type within cantera Vector of doubles.
const double GasConstant
Universal Gas Constant [J/kmol/K].
Namespace for the Cantera kernel.
const U & getValue(const std::map< T, U > &m, const T &key, const U &default_val)
Const accessor for a value in a std::map.
const int BUTLERVOLMER_NOACTIVITYCOEFFS_RXN
This is a surface reaction that is formulated using the Butler-Volmer formulation and using concentra...
void getStringArray(const XML_Node &node, std::vector< std::string > &v)
This function interprets the value portion of an XML element as a string.
const int CHEBYSHEV_RXN
A general gas-phase pressure-dependent reaction where k(T,P) is defined in terms of a bivariate Cheby...
std::vector< shared_ptr< Reaction > > getReactions(const XML_Node &node)
Create Reaction objects for all <reaction> nodes in an XML document.
doublereal getFloat(const XML_Node &parent, const std::string &name, const std::string &type)
Get a floating-point value from a child element.
bool getOptionalFloat(const XML_Node &parent, const std::string &name, doublereal &fltRtn, const std::string &type)
Get an optional floating-point value from a child element.
const int PLOG_RXN
A pressure-dependent rate expression consisting of several Arrhenius rate expressions evaluated at di...
const int BUTLERVOLMER_RXN
This is a surface reaction that is formulated using the Butler-Volmer formulation.
shared_ptr< Reaction > newReaction(const XML_Node &rxn_node)
Create a new Reaction object for the reaction defined in rxn_node
doublereal fpValue(const std::string &val)
Translate a string into one doublereal value.
bool caseInsensitiveEquals(const std::string &input, const std::string &test)
Case insensitive equality predicate.
const int GLOBAL_RXN
A global reaction.
const int THREE_BODY_RXN
A gas-phase reaction that requires a third-body collision partner.
std::string toLowerCopy(const std::string &input)
Convert to lower case.
void readFalloff(FalloffReaction &R, const XML_Node &rc_node)
Parse falloff parameters, given a rateCoeff node.
const int ELEMENTARY_RXN
A reaction with a rate coefficient that depends only on temperature and voltage that also obeys mass-...
const int INTERFACE_RXN
A reaction occurring on an interface, e.g a surface or edge.
size_t getFloatArray(const XML_Node &node, vector_fp &v, const bool convert, const std::string &unitsString, const std::string &nodeName)
This function reads the current node or a child node of the current node with the default name,...
const int SURFACEAFFINITY_RXN
This is a surface reaction that is formulated using the affinity representation, common in the geoche...
const int CHEMACT_RXN
A chemical activation reaction.
doublereal fpValueCheck(const std::string &val)
Translate a string into one doublereal value, with error checking.
shared_ptr< Falloff > newFalloff(int type, const vector_fp &c)
Return a pointer to a new falloff function calculator.
const int FALLOFF_RXN
The general form for a gas-phase association or dissociation reaction, with a pressure-dependent rate...
void tokenizeString(const std::string &in_val, std::vector< std::string > &v)
This function separates a string up into tokens according to the location of white space.
compositionMap parseCompString(const std::string &ss, const std::vector< std::string > &names)
Parse a composition string into a map consisting of individual key:composition pairs.