Cantera  3.1.0a2
Loading...
Searching...
No Matches
Reaction.cpp
Go to the documentation of this file.
1/**
2 * @file Reaction.cpp
3 */
4
5// This file is part of Cantera. See License.txt in the top-level directory or
6// at https://cantera.org/license.txt for license and copyright information.
7
13#include "cantera/base/Array.h"
14#include "cantera/base/AnyMap.h"
17#include <boost/algorithm/string/predicate.hpp>
18#include <sstream>
19
20#include <boost/algorithm/string.hpp>
21
22namespace ba = boost::algorithm;
23
24namespace Cantera
25{
26
27Reaction::Reaction(const Composition& reactants_,
28 const Composition& products_,
29 shared_ptr<ReactionRate> rate_,
30 shared_ptr<ThirdBody> tbody_)
31 : reactants(reactants_)
32 , products(products_)
33 , m_from_composition(true)
34 , m_third_body(tbody_)
35{
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.");
39 }
40 setRate(rate_);
41
42 // set flags ensuring correct serialization output
43 Composition third;
44 for (const auto& [name, stoich] : reactants) {
45 if (products.count(name)) {
46 third[name] = products.at(name) - stoich;
47 }
48 }
49 if (tbody_) {
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);
55 }
56 if (name != "M") {
57 m_third_body->explicit_3rd = true;
58 }
59 } else if (!tbody_ && third.size() == 1) {
60 // implicit third body
61 string name = third.begin()->first;
62 m_third_body = make_shared<ThirdBody>(name);
63 if (name != "M") {
64 m_third_body->explicit_3rd = true;
65 }
66 }
67 check();
68}
69
70Reaction::Reaction(const string& equation,
71 shared_ptr<ReactionRate> rate_,
72 shared_ptr<ThirdBody> tbody_)
73 : m_third_body(tbody_)
74{
75 setRate(rate_);
76 setEquation(equation);
77 if (m_third_body && m_third_body->name() != "M") {
78 m_third_body->explicit_3rd = true;
79 }
80 check();
81}
82
83Reaction::Reaction(const AnyMap& node, const Kinetics& kin)
84{
85 string rate_type = node.getString("type", "Arrhenius");
86 if (!kin.nPhases()) {
87 throw InputFileError("Reaction", node,
88 "Cannot instantiate Reaction with empty Kinetics object.");
89 }
90
91 setParameters(node, kin);
92 size_t nDim = kin.thermo(0).nDim();
93 if (!valid()) {
94 // If the reaction isn't valid (for example, contains undefined species),
95 // setting up the rate constant won't work
96 return;
97 }
98 if (nDim == 3) {
99 if (ba::starts_with(rate_type, "three-body-")) {
100 AnyMap rateNode = node;
101 rateNode["type"] = rate_type.substr(11, rate_type.size() - 11);
102 setRate(newReactionRate(rateNode, calculateRateCoeffUnits(kin)));
103 } else {
104 setRate(newReactionRate(node, calculateRateCoeffUnits(kin)));
105 }
106 } else {
107 AnyMap rateNode = node;
108 if (rateNode.hasKey("rate-constant")) {
109 if (!ba::starts_with(rate_type, "interface-")) {
110 rateNode["type"] = "interface-" + rate_type;
111 }
112 } else if (node.hasKey("sticking-coefficient")) {
113 if (!ba::starts_with(rate_type, "sticking-")) {
114 rateNode["type"] = "sticking-" + rate_type;
115 }
116 } else if (rate_type == "Arrhenius") { // no explicitly-specified type
117 throw InputFileError("Reaction::Reaction", input,
118 "Unable to infer interface reaction type.");
119 }
120 setRate(newReactionRate(rateNode, calculateRateCoeffUnits(kin)));
121 }
122 check();
123}
124
125void Reaction::check()
126{
127 if (!allow_nonreactant_orders) {
128 for (const auto& [name, order] : orders) {
129 if (reactants.find(name) == reactants.end()) {
130 throw InputFileError("Reaction::validate", input,
131 "Reaction order specified for non-reactant species '{}'", name);
132 }
133 }
134 }
135
136 if (!allow_negative_orders) {
137 for (const auto& [name, order] : orders) {
138 if (order < 0.0) {
139 throw InputFileError("Reaction::validate", input,
140 "Negative reaction order specified for species '{}'", name);
141 }
142 }
143 }
144
145 // If reaction orders are specified, then this reaction does not follow
146 // mass-action kinetics, and is not an elementary reaction. So check that it
147 // is not reversible, since computing the reverse rate from thermochemistry
148 // only works for elementary reactions.
149 if (reversible && !orders.empty()) {
150 throw InputFileError("Reaction::validate", input,
151 "Reaction orders may only be given for irreversible reactions");
152 }
153
154 if (!m_rate) {
155 return;
156 }
157
158 // Check reaction rate evaluator to ensure changes introduced after object
159 // instantiation are considered.
160 m_rate->check(equation());
161
162 string rate_type = m_rate->type();
163 if (m_third_body) {
164 if (rate_type == "falloff" || rate_type == "chemically-activated") {
165 if (m_third_body->mass_action && !m_from_composition) {
166 throw InputFileError("Reaction::setRate", input,
167 "Third-body collider does not use '(+{})' notation.",
168 m_third_body->name());
169 }
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();
176 }
177 } else if (rate_type == "pressure-dependent-Arrhenius") {
178 if (m_third_body->name() == "M") {
179 throw InputFileError("Reaction::setRate", input,
180 "Found superfluous '{}' in pressure-dependent-Arrhenius reaction.",
181 m_third_body->name());
182 }
183 }
184 } else {
185 if (rate_type == "falloff" || rate_type == "chemically-activated") {
186 if (!m_from_composition) {
187 throw InputFileError("Reaction::setRate", input,
188 "Reaction equation for falloff reaction '{}'\n does not "
189 "contain valid pressure-dependent third body", equation());
190 }
191 m_third_body = make_shared<ThirdBody>("(+M)");
192 }
193 }
194}
195
196AnyMap Reaction::parameters(bool withInput) const
197{
198 AnyMap out;
199 getParameters(out);
200 if (withInput) {
201 out.update(input);
202 }
203
204 static bool reg = AnyMap::addOrderingRules("Reaction",
205 {{"head", "type"},
206 {"head", "equation"},
207 {"tail", "duplicate"},
208 {"tail", "orders"},
209 {"tail", "negative-orders"},
210 {"tail", "nonreactant-orders"}
211 });
212 if (reg) {
213 out["__type__"] = "Reaction";
214 }
215 return out;
216}
217
218void Reaction::getParameters(AnyMap& reactionNode) const
219{
220 if (!m_rate) {
221 throw CanteraError("Reaction::getParameters",
222 "Serialization of empty Reaction object is not supported.");
223 }
224
225 reactionNode["equation"] = equation();
226
227 if (duplicate) {
228 reactionNode["duplicate"] = true;
229 }
230 if (orders.size()) {
231 reactionNode["orders"] = orders;
232 }
233 if (allow_negative_orders) {
234 reactionNode["negative-orders"] = true;
235 }
236 if (allow_nonreactant_orders) {
237 reactionNode["nonreactant-orders"] = true;
238 }
239
240 reactionNode.update(m_rate->parameters());
241
242 // strip information not needed for reconstruction
243 string rtype = reactionNode["type"].asString();
244 if (rtype == "pressure-dependent-Arrhenius") {
245 // skip
246 } else if (m_explicit_type && ba::ends_with(rtype, "Arrhenius")) {
247 // retain type information
248 if (m_third_body) {
249 reactionNode["type"] = "three-body";
250 } else {
251 reactionNode["type"] = "elementary";
252 }
253 } else if (ba::ends_with(rtype, "Arrhenius")) {
254 reactionNode.erase("type");
255 } else if (m_explicit_type) {
256 reactionNode["type"] = type();
257 } else if (ba::ends_with(rtype, "Blowers-Masel")) {
258 reactionNode["type"] = "Blowers-Masel";
259 }
260
261 if (m_third_body) {
262 m_third_body->getParameters(reactionNode);
263 }
264}
265
266void Reaction::setParameters(const AnyMap& node, const Kinetics& kin)
267{
268 if (node.empty()) {
269 throw InputFileError("Reaction::setParameters", input,
270 "Cannot set reaction parameters from empty node.");
271 }
272
273 input = node;
274 input.copyMetadata(node);
275 setEquation(node["equation"].asString(), &kin);
276 // Non-stoichiometric reaction orders
277 if (node.hasKey("orders")) {
278 for (const auto& [name, order] : node["orders"].asMap<double>()) {
279 orders[name] = order;
280 if (kin.kineticsSpeciesIndex(name) == npos) {
281 setValid(false);
282 }
283 }
284 }
285
286 // Flags
287 id = node.getString("id", "");
288 duplicate = node.getBool("duplicate", false);
289 allow_negative_orders = node.getBool("negative-orders", false);
290 allow_nonreactant_orders = node.getBool("nonreactant-orders", false);
291
292 if (m_third_body) {
293 m_third_body->setParameters(node);
294 if (m_third_body->name() == "M" && m_third_body->efficiencies.size() == 1) {
295 m_third_body->explicit_3rd = true;
296 }
297 } else if (node.hasKey("default-efficiency") || node.hasKey("efficiencies")) {
298 throw InputFileError("Reaction::setParameters", input,
299 "Reaction '{}' specifies efficiency parameters\n"
300 "but does not involve third body colliders.", equation());
301 }
302}
303
304void Reaction::setRate(shared_ptr<ReactionRate> rate)
305{
306 if (!rate) {
307 throw InputFileError("Reaction::setRate", input,
308 "Reaction rate for reaction '{}' must not be empty.", equation());
309 }
310 m_rate = rate;
311}
312
313string Reaction::reactantString() const
314{
315 std::ostringstream result;
316 for (auto iter = reactants.begin(); iter != reactants.end(); ++iter) {
317 if (iter != reactants.begin()) {
318 result << " + ";
319 }
320 if (iter->second != 1.0) {
321 result << iter->second << " ";
322 }
323 result << iter->first;
324 }
325 if (m_third_body) {
326 result << m_third_body->collider();
327 }
328 return result.str();
329}
330
331string Reaction::productString() const
332{
333 std::ostringstream result;
334 for (auto iter = products.begin(); iter != products.end(); ++iter) {
335 if (iter != products.begin()) {
336 result << " + ";
337 }
338 if (iter->second != 1.0) {
339 result << iter->second << " ";
340 }
341 result << iter->first;
342 }
343 if (m_third_body) {
344 result << m_third_body->collider();
345 }
346 return result.str();
347}
348
349string Reaction::equation() const
350{
351 if (reversible) {
352 return reactantString() + " <=> " + productString();
353 } else {
354 return reactantString() + " => " + productString();
355 }
356}
357
358void Reaction::setEquation(const string& equation, const Kinetics* kin)
359{
360 parseReactionEquation(*this, equation, input, kin);
361 string rate_type = (m_rate) ? m_rate->type() : input.getString("type", "");
362 if (ba::starts_with(rate_type, "three-body")) {
363 // state type when serializing
364 m_explicit_type = true;
365 } else if (rate_type == "elementary") {
366 // user override
367 m_explicit_type = true;
368 return;
369 } else if (kin && kin->thermo(0).nDim() != 3) {
370 // interface reactions
371 return;
372 } else if (rate_type == "electron-collision-plasma") {
373 // does not support third body
374 return;
375 }
376
377 string third_body;
378 size_t count = 0;
379 size_t countM = 0;
380 for (const auto& [name, stoich] : reactants) {
381 // detect explicitly specified collision partner
382 if (products.count(name)) {
383 third_body = name;
384 size_t generic = third_body == "M"
385 || third_body == "(+M)" || third_body == "(+ M)";
386 count++;
387 countM += generic;
388 if (stoich > 1 && products[third_body] > 1) {
389 count++;
390 countM += generic;
391 }
392 }
393 }
394
395 if (count == 0) {
396 if (ba::starts_with(rate_type, "three-body")) {
397 throw InputFileError("Reaction::setEquation", input,
398 "Reactants for reaction '{}'\n"
399 "do not contain a valid third body collider", equation);
400 }
401 return;
402
403 } else if (countM > 1) {
404 throw InputFileError("Reaction::setEquation", input,
405 "Multiple generic third body colliders 'M' are not supported", equation);
406
407 } else if (count > 1) {
408 // equations with more than one explicit third-body collider are handled as a
409 // regular elementary reaction unless the equation contains a generic third body
410 if (countM) {
411 // generic collider 'M' is selected as third body
412 third_body = "M";
413 } else if (m_third_body) {
414 // third body is defined as explicit object
415 auto& effs = m_third_body->efficiencies;
416 if (effs.size() != 1 || !reactants.count(effs.begin()->first)) {
417 throw InputFileError("Reaction::setEquation", input,
418 "Detected ambiguous third body colliders in reaction '{}'\n"
419 "ThirdBody object needs to specify a single species", equation);
420 }
421 third_body = effs.begin()->first;
422 m_third_body->explicit_3rd = true;
423 } else if (input.hasKey("efficiencies")) {
424 // third body is implicitly defined by efficiency
425 auto effs = input["efficiencies"].asMap<double>();
426 if (effs.size() != 1 || !reactants.count(effs.begin()->first)) {
427 throw InputFileError("Reaction::setEquation", input,
428 "Detected ambiguous third body colliders in reaction '{}'\n"
429 "Collision efficiencies need to specify single species", equation);
430 }
431 third_body = effs.begin()->first;
432 m_third_body = make_shared<ThirdBody>(third_body);
433 m_third_body->explicit_3rd = true;
434 } else if (input.hasKey("default-efficiency")) {
435 // insufficient disambiguation of third bodies
436 throw InputFileError("Reaction::setEquation", input,
437 "Detected ambiguous third body colliders in reaction '{}'\n"
438 "Third-body definition requires specification of efficiencies",
439 equation);
440 } else if (ba::starts_with(rate_type, "three-body")) {
441 // no disambiguation of third bodies
442 throw InputFileError("Reaction::setEquation", input,
443 "Detected ambiguous third body colliders in reaction '{}'\n"
444 "A valid ThirdBody or collision efficiency definition is required",
445 equation);
446 } else {
447 return;
448 }
449
450 } else if (third_body != "M" && !ba::starts_with(rate_type, "three-body")
451 && !ba::starts_with(third_body, "(+"))
452 {
453 // check for conditions of three-body reactions:
454 // - integer stoichiometric conditions
455 // - either reactant or product side involves exactly three species
456 size_t nreac = 0;
457 size_t nprod = 0;
458
459 // ensure that all reactants have integer stoichiometric coefficients
460 for (const auto& [name, stoich] : reactants) {
461 if (trunc(stoich) != stoich) {
462 return;
463 }
464 nreac += static_cast<size_t>(stoich);
465 }
466
467 // ensure that all products have integer stoichiometric coefficients
468 for (const auto& [name, stoich] : products) {
469 if (trunc(stoich) != stoich) {
470 return;
471 }
472 nprod += static_cast<size_t>(stoich);
473 }
474
475 // either reactant or product side involves exactly three species
476 if (nreac != 3 && nprod != 3) {
477 return;
478 }
479 }
480
481 if (m_third_body) {
482 string tName = m_third_body->name();
483 if (tName != third_body && third_body != "M" && tName != "M") {
484 throw InputFileError("Reaction::setEquation", input,
485 "Detected incompatible third body colliders in reaction '{}'\n"
486 "ThirdBody definition does not match equation", equation);
487 }
488 m_third_body->setName(third_body);
489 } else {
490 m_third_body = make_shared<ThirdBody>(third_body);
491 }
492
493 // adjust reactant coefficients
494 auto reac = reactants.find(third_body);
495 if (trunc(reac->second) != 1) {
496 reac->second -= 1.;
497 } else {
498 reactants.erase(reac);
499 }
500
501 // adjust product coefficients
502 auto prod = products.find(third_body);
503 if (trunc(prod->second) != 1) {
504 prod->second -= 1.;
505 } else {
506 products.erase(prod);
507 }
508}
509
510string Reaction::type() const
511{
512 if (!m_rate) {
513 throw CanteraError("Reaction::type", "Empty Reaction does not have a type");
514 }
515
516 string rate_type = m_rate->type();
517 string sub_type = m_rate->subType();
518 if (sub_type != "") {
519 return rate_type + "-" + sub_type;
520 }
521
522 if (m_third_body) {
523 return "three-body-" + rate_type;
524 }
525
526 return rate_type;
527}
528
529UnitStack Reaction::calculateRateCoeffUnits(const Kinetics& kin)
530{
531 if (!valid()) {
532 // If a reaction is invalid because of missing species in the Kinetics
533 // object, determining the units of the rate coefficient is impossible.
534 return UnitStack({});
535 }
536
537 // Determine the units of the rate coefficient
538 UnitStack rate_units(kin.thermo(0).standardConcentrationUnits());
539
540 // Set output units to standardConcentrationUnits per second
541 rate_units.join(1.);
542 rate_units.update(Units(1.0, 0, 0, -1), 1.);
543
544 for (const auto& [name, order] : orders) {
545 const auto& phase = kin.speciesPhase(name);
546 // Account for specified reaction orders
547 rate_units.update(phase.standardConcentrationUnits(), -order);
548 }
549 for (const auto& [name, stoich] : reactants) {
550 // Order for each reactant is the reactant stoichiometric coefficient,
551 // unless already overridden by user-specified orders
552 if (name == "M" || ba::starts_with(name, "(+")) {
553 // calculateRateCoeffUnits may be called before these pseudo-species
554 // have been stripped from the reactants
555 continue;
556 } else if (orders.find(name) == orders.end()) {
557 const auto& phase = kin.speciesPhase(name);
558 // Account for each reactant species
559 rate_units.update(phase.standardConcentrationUnits(), -stoich);
560 }
561 }
562
563 if (m_third_body && m_third_body->mass_action) {
564 // Account for third-body collision partner as the last entry
565 rate_units.join(-1);
566 }
567
568 Reaction::rate_units = rate_units.product();
569 return rate_units;
570}
571
572void updateUndeclared(vector<string>& undeclared,
573 const Composition& comp, const Kinetics& kin)
574{
575 for (const auto& [name, stoich]: comp) {
576 if (kin.kineticsSpeciesIndex(name) == npos) {
577 undeclared.emplace_back(name);
578 }
579 }
580}
581
582void Reaction::checkBalance(const Kinetics& kin) const
583{
584 Composition balr, balp;
585
586 // iterate over products and reactants
587 for (const auto& [name, stoich] : products) {
588 const ThermoPhase& ph = kin.speciesPhase(name);
589 size_t k = ph.speciesIndex(name);
590 for (size_t m = 0; m < ph.nElements(); m++) {
591 balr[ph.elementName(m)] = 0.0; // so that balr contains all species
592 balp[ph.elementName(m)] += stoich * ph.nAtoms(k, m);
593 }
594 }
595 for (const auto& [name, stoich] : reactants) {
596 const ThermoPhase& ph = kin.speciesPhase(name);
597 size_t k = ph.speciesIndex(name);
598 for (size_t m = 0; m < ph.nElements(); m++) {
599 balr[ph.elementName(m)] += stoich * ph.nAtoms(k, m);
600 }
601 }
602
603 string msg;
604 bool ok = true;
605 for (const auto& [elem, balance] : balr) {
606 double elemsum = balr[elem] + balp[elem];
607 double elemdiff = fabs(balp[elem] - balr[elem]);
608 if (elemsum > 0.0 && elemdiff / elemsum > 1e-4) {
609 ok = false;
610 msg += fmt::format(" {} {} {}\n",
611 elem, balr[elem], balp[elem]);
612 }
613 }
614 if (!ok) {
615 throw InputFileError("Reaction::checkBalance", input,
616 "The following reaction is unbalanced: {}\n"
617 " Element Reactants Products\n{}",
618 equation(), msg);
619 }
620
621 if (kin.thermo(0).nDim() == 3) {
622 return;
623 }
624
625 // Check that the number of surface sites is balanced
626 double reac_sites = 0.0;
627 double prod_sites = 0.0;
628 auto& surf = dynamic_cast<const SurfPhase&>(kin.thermo(0));
629 for (const auto& [name, stoich] : reactants) {
630 size_t k = surf.speciesIndex(name);
631 if (k != npos) {
632 reac_sites += stoich * surf.size(k);
633 }
634 }
635 for (const auto& [name, stoich] : products) {
636 size_t k = surf.speciesIndex(name);
637 if (k != npos) {
638 prod_sites += stoich * surf.size(k);
639 }
640 }
641 if (fabs(reac_sites - prod_sites) > 1e-5 * (reac_sites + prod_sites)) {
642 throw InputFileError("Reaction::checkBalance", input,
643 "Number of surface sites not balanced in reaction {}.\n"
644 "Reactant sites: {}\nProduct sites: {}",
645 equation(), reac_sites, prod_sites);
646 }
647}
648
649bool Reaction::checkSpecies(const Kinetics& kin) const
650{
651 // Check for undeclared species
652 vector<string> undeclared;
653 updateUndeclared(undeclared, reactants, kin);
654 updateUndeclared(undeclared, products, kin);
655 if (!undeclared.empty()) {
656 if (kin.skipUndeclaredSpecies()) {
657 return false;
658 } else {
659 throw InputFileError("Reaction::checkSpecies", input, "Reaction '{}'\n"
660 "contains undeclared species: '{}'",
661 equation(), ba::join(undeclared, "', '"));
662 }
663 }
664
665 undeclared.clear();
666 updateUndeclared(undeclared, orders, kin);
667 if (!undeclared.empty()) {
668 if (kin.skipUndeclaredSpecies()) {
669 return false;
670 } else {
671 if (input.hasKey("orders")) {
672 throw InputFileError("Reaction::checkSpecies", input["orders"],
673 "Reaction '{}'\n"
674 "defines reaction orders for undeclared species: '{}'",
675 equation(), ba::join(undeclared, "', '"));
676 }
677 // Error for empty input AnyMap (that is, XML)
678 throw InputFileError("Reaction::checkSpecies", input, "Reaction '{}'\n"
679 "defines reaction orders for undeclared species: '{}'",
680 equation(), ba::join(undeclared, "', '"));
681 }
682 }
683
684 if (m_third_body) {
685 return m_third_body->checkSpecies(*this, kin);
686 }
687
688 checkBalance(kin);
689
690 return true;
691}
692
693bool Reaction::usesElectrochemistry(const Kinetics& kin) const
694{
695 // Check electrochemistry
696 vector<double> e_counter(kin.nPhases(), 0.0);
697
698 // Find the number of electrons in the products for each phase
699 for (const auto& [name, stoich] : products) {
700 size_t kkin = kin.kineticsSpeciesIndex(name);
701 size_t i = kin.speciesPhaseIndex(kkin);
702 size_t kphase = kin.thermo(i).speciesIndex(name);
703 e_counter[i] += stoich * kin.thermo(i).charge(kphase);
704 }
705
706 // Subtract the number of electrons in the reactants for each phase
707 for (const auto& [name, stoich] : reactants) {
708 size_t kkin = kin.kineticsSpeciesIndex(name);
709 size_t i = kin.speciesPhaseIndex(kkin);
710 size_t kphase = kin.thermo(i).speciesIndex(name);
711 e_counter[i] -= stoich * kin.thermo(i).charge(kphase);
712 }
713
714 // If the electrons change phases then the reaction is electrochemical
715 for (double delta_e : e_counter) {
716 if (std::abs(delta_e) > 1e-4) {
717 return true;
718 }
719 }
720
721 return false;
722}
723
724
725ThirdBody::ThirdBody(const string& third_body)
726{
727 setName(third_body);
728}
729
730void ThirdBody::setName(const string& third_body)
731{
732 string name = third_body;
733 if (ba::starts_with(third_body, "(+ ")) {
734 mass_action = false;
735 name = third_body.substr(3, third_body.size() - 4);
736 } else if (ba::starts_with(third_body, "(+")) {
737 mass_action = false;
738 name = third_body.substr(2, third_body.size() - 3);
739 }
740
741 if (name == m_name) {
742 return;
743 }
744 if (name == "M" && efficiencies.size() == 1) {
745 // revert from explicit name to generic collider
746 m_name = name;
747 return;
748 }
749 if (efficiencies.size()) {
750 throw CanteraError("ThirdBody::setName",
751 "Conflicting efficiency definition for explicit third body '{}'", name);
752 }
753 m_name = name;
754 default_efficiency = 0.;
755 efficiencies[m_name] = 1.;
756}
757
758ThirdBody::ThirdBody(const AnyMap& node)
759{
760 setParameters(node);
761}
762
763void ThirdBody::setParameters(const AnyMap& node)
764{
765 if (node.hasKey("default-efficiency")) {
766 double value = node["default-efficiency"].asDouble();
767 if (m_name != "M" && value != 0.) {
768 throw InputFileError("ThirdBody::setParameters", node["default-efficiency"],
769 "Invalid default efficiency for explicit collider {};\n"
770 "value is optional and/or needs to be zero", m_name);
771 }
772 default_efficiency = value;
773 }
774 if (node.hasKey("efficiencies")) {
775 efficiencies = node["efficiencies"].asMap<double>();
776 }
777 if (m_name != "M"
778 && (efficiencies.size() != 1 || efficiencies.begin()->first != m_name))
779 {
780 throw InputFileError("ThirdBody::setParameters", node,
781 "Detected incompatible third body colliders definitions");
782 }
783}
784
785void ThirdBody::getParameters(AnyMap& node) const
786{
787 if (m_name == "M" || explicit_3rd) {
788 if (efficiencies.size()) {
789 node["efficiencies"] = efficiencies;
790 node["efficiencies"].setFlowStyle();
791 }
792 if (default_efficiency != 1.0 && !explicit_3rd) {
793 node["default-efficiency"] = default_efficiency;
794 }
795 }
796}
797
798double ThirdBody::efficiency(const string& k) const
799{
800 return getValue(efficiencies, k, default_efficiency);
801}
802
803string ThirdBody::collider() const
804{
805 if (mass_action) {
806 return " + " + m_name;
807 }
808 return " (+" + m_name + ")";
809}
810
811bool ThirdBody::checkSpecies(const Reaction& rxn, const Kinetics& kin) const
812{
813 vector<string> undeclared;
814 updateUndeclared(undeclared, efficiencies, kin);
815
816 if (!undeclared.empty()) {
817 if (!kin.skipUndeclaredThirdBodies()) {
818 if (rxn.input.hasKey("efficiencies")) {
819 throw InputFileError("ThirdBody::checkSpecies",
820 rxn.input["efficiencies"], "Reaction '{}'\n"
821 "defines third-body efficiencies for undeclared species: '{}'",
822 rxn.equation(), ba::join(undeclared, "', '"));
823 }
824 // Error for specified ThirdBody or empty input AnyMap
825 throw InputFileError("ThirdBody::checkSpecies", rxn.input, "Reaction '{}'\n"
826 "is a three-body reaction with undeclared species: '{}'",
827 rxn.equation(), ba::join(undeclared, "', '"));
828 } else if (kin.skipUndeclaredThirdBodies() && m_name != "M") {
829 // Prevent addition of reaction silently as "skip-undeclared-third-bodies"
830 // is set to true
831 return false;
832 }
833 }
834 return true;
835}
836
837
838unique_ptr<Reaction> newReaction(const string& type)
839{
840 return make_unique<Reaction>();
841}
842
843unique_ptr<Reaction> newReaction(const AnyMap& rxn_node, const Kinetics& kin)
844{
845 return make_unique<Reaction>(rxn_node, kin);
846}
847
848void parseReactionEquation(Reaction& R, const string& equation,
849 const AnyBase& reactionNode, const Kinetics* kin)
850{
851 // Parse the reaction equation to determine participating species and
852 // stoichiometric coefficients
853 vector<string> tokens;
854 tokenizeString(equation, tokens);
855 tokens.push_back("+"); // makes parsing last species not a special case
856
857 size_t last_used = npos; // index of last-used token
858 bool reactants = true;
859 for (size_t i = 1; i < tokens.size(); i++) {
860 if (tokens[i] == "+" || ba::starts_with(tokens[i], "(+") ||
861 tokens[i] == "<=>" || tokens[i] == "=" || tokens[i] == "=>") {
862 string species = tokens[i-1];
863
864 double stoich = 1.0;
865 bool mass_action = true;
866 if (last_used != npos && tokens[last_used] == "(+"
867 && ba::ends_with(species, ")")) {
868 // Falloff third body with space, such as "(+ M)"
869 mass_action = false;
870 species = "(+" + species;
871 } else if (last_used == i - 1 && ba::starts_with(species, "(+")
872 && ba::ends_with(species, ")")) {
873 // Falloff 3rd body written without space, such as "(+M)"
874 mass_action = false;
875 } else if (last_used == i - 2) {
876 // Species with no stoich. coefficient
877 } else if (last_used == i - 3) {
878 // Stoich. coefficient and species
879 try {
880 stoich = fpValueCheck(tokens[i-2]);
881 } catch (CanteraError& err) {
882 throw InputFileError("parseReactionEquation", reactionNode,
883 err.getMessage());
884 }
885 } else {
886 throw InputFileError("parseReactionEquation", reactionNode,
887 "Error parsing reaction string '{}'.\n"
888 "Current token: '{}'\nlast_used: '{}'",
889 equation, tokens[i],
890 (last_used == npos) ? "n/a" : tokens[last_used]);
891 }
892 if (!kin || (kin->kineticsSpeciesIndex(species) == npos
893 && mass_action && species != "M"))
894 {
895 R.setValid(false);
896 }
897
898 if (reactants) {
899 R.reactants[species] += stoich;
900 } else {
901 R.products[species] += stoich;
902 }
903
904 last_used = i;
905 }
906
907 // Tokens after this point are part of the products string
908 if (tokens[i] == "<=>" || tokens[i] == "=") {
909 R.reversible = true;
910 reactants = false;
911 } else if (tokens[i] == "=>") {
912 R.reversible = false;
913 reactants = false;
914 }
915 }
916}
917
918vector<shared_ptr<Reaction>> getReactions(const AnyValue& items, Kinetics& kinetics)
919{
920 vector<shared_ptr<Reaction>> all_reactions;
921 for (const auto& node : items.asVector<AnyMap>()) {
922 auto R = make_shared<Reaction>(node, kinetics);
923 R->validate(kinetics);
924 if (R->valid() && R->checkSpecies(kinetics)) {
925 all_reactions.emplace_back(R);
926 }
927 }
928 return all_reactions;
929}
930
931}
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.
Definition AnyMap.h:34
A map of string keys to values whose type can vary at runtime.
Definition AnyMap.h:427
void copyMetadata(const AnyMap &other)
Copy metadata including input line/column from an existing AnyMap.
Definition AnyMap.cpp:1493
bool hasKey(const string &key) const
Returns true if the map contains an item named key.
Definition AnyMap.cpp:1423
bool empty() const
Return boolean indicating whether AnyMap is empty.
Definition AnyMap.cpp:1418
void setFlowStyle(bool flow=true)
Use "flow" style when outputting this AnyMap to YAML.
Definition AnyMap.cpp:1726
bool getBool(const string &key, bool default_) const
If key exists, return it as a bool, otherwise return default_.
Definition AnyMap.cpp:1515
const string & getString(const string &key, const string &default_) const
If key exists, return it as a string, otherwise return default_.
Definition AnyMap.cpp:1530
void erase(const string &key)
Erase the value held by key.
Definition AnyMap.cpp:1428
void update(const AnyMap &other, bool keepExisting=true)
Add items from other to this AnyMap.
Definition AnyMap.cpp:1438
A wrapper for a variable whose type is determined at runtime.
Definition AnyMap.h:86
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.
Definition AnyMap.inl.h:109
Base class for exceptions thrown by Cantera classes.
virtual string getMessage() const
Method overridden by derived classes to format the error message.
Error thrown for problems processing information contained in an AnyMap or AnyValue.
Definition AnyMap.h:738
Public interface for kinetics managers.
Definition Kinetics.h:125
ThermoPhase & thermo(size_t n=0)
This method returns a reference to the nth ThermoPhase object defined in this kinetics mechanism.
Definition Kinetics.h:242
size_t nPhases() const
The number of phases participating in the reaction mechanism.
Definition Kinetics.h:184
void skipUndeclaredSpecies(bool skip)
Determine behavior when adding a new reaction that contains species not defined in any of the phases ...
Definition Kinetics.h:1326
void skipUndeclaredThirdBodies(bool skip)
Determine behavior when adding a new reaction that contains third-body efficiencies for species not d...
Definition Kinetics.h:1338
size_t kineticsSpeciesIndex(size_t k, size_t n) const
The location of species k of phase n in species arrays.
Definition Kinetics.h:276
ThermoPhase & speciesPhase(const string &nm)
This function looks up the name of a species and returns a reference to the ThermoPhase object of the...
Definition Kinetics.cpp:260
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 ...
Definition Kinetics.cpp:281
size_t nDim() const
Returns the number of spatial dimensions (1, 2, or 3)
Definition Phase.h:546
size_t speciesIndex(const string &name) const
Returns the index of a species named 'name' within the Phase object.
Definition Phase.cpp:129
double nAtoms(size_t k, size_t m) const
Number of atoms of element m in species k.
Definition Phase.cpp:103
size_t nElements() const
Number of elements.
Definition Phase.cpp:30
string elementName(size_t m) const
Name of the element with index m.
Definition Phase.cpp:49
double charge(size_t k) const
Dimensionless electrical charge of a single molecule of species k The charge is normalized by the the...
Definition Phase.h:538
Abstract base class which stores data about a reaction and its rate parameterization so that it can b...
Definition Reaction.h:25
bool reversible
True if the current reaction is reversible. False otherwise.
Definition Reaction.h:126
string equation() const
The chemical equation for this reaction.
Definition Reaction.cpp:349
Composition products
Product species and stoichiometric coefficients.
Definition Reaction.h:114
Composition reactants
Reactant species and stoichiometric coefficients.
Definition Reaction.h:111
AnyMap input
Input data used for specific models.
Definition Reaction.h:139
void setValid(bool valid)
Set validity flag of reaction.
Definition Reaction.h:89
A simple thermodynamic model for a surface phase, assuming an ideal solution model.
Definition SurfPhase.h:98
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.
Definition Units.h:35
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.
Definition AnyMap.cpp:564
const size_t npos
index returned by functions to indicate "no position"
Definition ct_defs.h:180
void parseReactionEquation(Reaction &R, const string &equation, const AnyBase &reactionNode, const Kinetics *kin)
Parse reaction equation.
Definition Reaction.cpp:848
vector< shared_ptr< Reaction > > getReactions(const AnyValue &items, Kinetics &kinetics)
Create Reaction objects for each item (an AnyMap) in items.
Definition Reaction.cpp:918
void warn_deprecated(const string &source, const AnyBase &node, const string &message)
A deprecation warning for syntax in an input file.
Definition AnyMap.cpp:1926
const U & getValue(const map< T, U > &m, const T &key, const U &default_val)
Const accessor for a value in a map.
Definition utilities.h:190
map< string, double > Composition
Map from string names to doubles.
Definition ct_defs.h:177
unique_ptr< Reaction > newReaction(const string &type)
Create a new empty Reaction object.
Definition Reaction.cpp:838
Contains declarations for string manipulation functions within Cantera.
Unit aggregation utility.
Definition Units.h:105
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...
Definition Units.cpp:362
Units product() const
Calculate product of units-exponent stack.
Definition Units.cpp:377
void join(double exponent)
Join (update) exponent of standard units, where the updated exponent is the sum of the pre-existing e...
Definition Units.cpp:352
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...