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;