Loading [MathJax]/extensions/tex2jax.js
Cantera  3.2.0a1
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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", "equation"},
206 {"head", "type"},
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 } else {
230 reactionNode.exclude("duplicate");
231 }
232 if (orders.size()) {
233 reactionNode["orders"] = orders;
234 } else {
235 reactionNode.exclude("orders");
236 }
237 if (allow_negative_orders) {
238 reactionNode["negative-orders"] = true;
239 } else {
240 reactionNode.exclude("negative-orders");
241 }
242 if (allow_nonreactant_orders) {
243 reactionNode["nonreactant-orders"] = true;
244 } else {
245 reactionNode.exclude("nonreactant-orders");
246 }
247
248 reactionNode.update(m_rate->parameters());
249
250 // strip information not needed for reconstruction
251 string rtype = reactionNode["type"].asString();
252 if (rtype == "pressure-dependent-Arrhenius") {
253 // skip
254 } else if (m_explicit_type && ba::ends_with(rtype, "Arrhenius")) {
255 // retain type information
256 if (m_third_body) {
257 reactionNode["type"] = "three-body";
258 } else {
259 reactionNode["type"] = "elementary";
260 }
261 } else if (ba::ends_with(rtype, "Arrhenius")) {
262 reactionNode.exclude("type");
263 } else if (m_explicit_type) {
264 reactionNode["type"] = type();
265 } else if (ba::ends_with(rtype, "Blowers-Masel")) {
266 reactionNode["type"] = "Blowers-Masel";
267 }
268
269 if (m_third_body) {
270 m_third_body->getParameters(reactionNode);
271 }
272}
273
274void Reaction::setParameters(const AnyMap& node, const Kinetics& kin)
275{
276 if (node.empty()) {
277 throw InputFileError("Reaction::setParameters", input,
278 "Cannot set reaction parameters from empty node.");
279 }
280
281 input = node;
282 input.copyMetadata(node);
283 setEquation(node["equation"].asString(), &kin);
284 // Non-stoichiometric reaction orders
285 if (node.hasKey("orders")) {
286 for (const auto& [name, order] : node["orders"].asMap<double>()) {
287 orders[name] = order;
288 if (kin.kineticsSpeciesIndex(name) == npos) {
289 setValid(false);
290 }
291 }
292 }
293
294 // Flags
295 id = node.getString("id", "");
296 duplicate = node.getBool("duplicate", false);
297 allow_negative_orders = node.getBool("negative-orders", false);
298 allow_nonreactant_orders = node.getBool("nonreactant-orders", false);
299
300 if (m_third_body) {
301 m_third_body->setParameters(node);
302 if (m_third_body->name() == "M" && m_third_body->efficiencies.size() == 1) {
303 m_third_body->explicit_3rd = true;
304 }
305 } else if (node.hasKey("default-efficiency") || node.hasKey("efficiencies")) {
306 throw InputFileError("Reaction::setParameters", input,
307 "Reaction '{}' specifies efficiency parameters\n"
308 "but does not involve third body colliders.", equation());
309 }
310}
311
312void Reaction::setRate(shared_ptr<ReactionRate> rate)
313{
314 if (!rate) {
315 throw InputFileError("Reaction::setRate", input,
316 "Reaction rate for reaction '{}' must not be empty.", equation());
317 }
318 m_rate = rate;
319 for (const auto& [id, callback] : m_setRateCallbacks) {
320 callback();
321 }
322}
323
324void Reaction::registerSetRateCallback(void* id, const function<void()>& callback)
325{
326 m_setRateCallbacks[id] = callback;
327}
328
329void Reaction::removeSetRateCallback(void* id)
330{
331 m_setRateCallbacks.erase(id);
332}
333
334string Reaction::reactantString() const
335{
336 std::ostringstream result;
337 for (auto iter = reactants.begin(); iter != reactants.end(); ++iter) {
338 if (iter != reactants.begin()) {
339 result << " + ";
340 }
341 if (iter->second != 1.0) {
342 result << iter->second << " ";
343 }
344 result << iter->first;
345 }
346 if (m_third_body) {
347 result << m_third_body->collider();
348 }
349 return result.str();
350}
351
352string Reaction::productString() const
353{
354 std::ostringstream result;
355 for (auto iter = products.begin(); iter != products.end(); ++iter) {
356 if (iter != products.begin()) {
357 result << " + ";
358 }
359 if (iter->second != 1.0) {
360 result << iter->second << " ";
361 }
362 result << iter->first;
363 }
364 if (m_third_body) {
365 result << m_third_body->collider();
366 }
367 return result.str();
368}
369
370string Reaction::equation() const
371{
372 if (reversible) {
373 return reactantString() + " <=> " + productString();
374 } else {
375 return reactantString() + " => " + productString();
376 }
377}
378
379void Reaction::setEquation(const string& equation, const Kinetics* kin)
380{
381 parseReactionEquation(*this, equation, input, kin);
382 string rate_type = (m_rate) ? m_rate->type() : input.getString("type", "");
383 if (ba::starts_with(rate_type, "three-body")) {
384 // state type when serializing
385 m_explicit_type = true;
386 } else if (rate_type == "elementary") {
387 // user override
388 m_explicit_type = true;
389 return;
390 } else if (kin && kin->thermo(0).nDim() != 3) {
391 // interface reactions
392 return;
393 } else if (rate_type == "electron-collision-plasma") {
394 // does not support third body
395 return;
396 }
397
398 string third_body;
399 size_t count = 0;
400 size_t countM = 0;
401 for (const auto& [name, stoich] : reactants) {
402 // detect explicitly specified collision partner
403 if (products.count(name)) {
404 third_body = name;
405 size_t generic = third_body == "M"
406 || third_body == "(+M)" || third_body == "(+ M)";
407 count++;
408 countM += generic;
409 if (stoich > 1 && products[third_body] > 1) {
410 count++;
411 countM += generic;
412 }
413 }
414 }
415
416 if (count == 0) {
417 if (ba::starts_with(rate_type, "three-body")) {
418 throw InputFileError("Reaction::setEquation", input,
419 "Reactants for reaction '{}'\n"
420 "do not contain a valid third body collider", equation);
421 }
422 return;
423
424 } else if (countM > 1) {
425 throw InputFileError("Reaction::setEquation", input,
426 "Multiple generic third body colliders 'M' are not supported", equation);
427
428 } else if (count > 1) {
429 // equations with more than one explicit third-body collider are handled as a
430 // regular elementary reaction unless the equation contains a generic third body
431 if (countM) {
432 // generic collider 'M' is selected as third body
433 third_body = "M";
434 } else if (m_third_body) {
435 // third body is defined as explicit object
436 auto& effs = m_third_body->efficiencies;
437 if (effs.size() != 1 || !reactants.count(effs.begin()->first)) {
438 throw InputFileError("Reaction::setEquation", input,
439 "Detected ambiguous third body colliders in reaction '{}'\n"
440 "ThirdBody object needs to specify a single species", equation);
441 }
442 third_body = effs.begin()->first;
443 m_third_body->explicit_3rd = true;
444 } else if (input.hasKey("efficiencies")) {
445 // third body is implicitly defined by efficiency
446 auto effs = input["efficiencies"].asMap<double>();
447 if (effs.size() != 1 || !reactants.count(effs.begin()->first)) {
448 throw InputFileError("Reaction::setEquation", input,
449 "Detected ambiguous third body colliders in reaction '{}'\n"
450 "Collision efficiencies need to specify single species", equation);
451 }
452 third_body = effs.begin()->first;
453 m_third_body = make_shared<ThirdBody>(third_body);
454 m_third_body->explicit_3rd = true;
455 } else if (input.hasKey("default-efficiency")) {
456 // insufficient disambiguation of third bodies
457 throw InputFileError("Reaction::setEquation", input,
458 "Detected ambiguous third body colliders in reaction '{}'\n"
459 "Third-body definition requires specification of efficiencies",
460 equation);
461 } else if (ba::starts_with(rate_type, "three-body")) {
462 // no disambiguation of third bodies
463 throw InputFileError("Reaction::setEquation", input,
464 "Detected ambiguous third body colliders in reaction '{}'\n"
465 "A valid ThirdBody or collision efficiency definition is required",
466 equation);
467 } else {
468 return;
469 }
470
471 } else if (third_body != "M" && !ba::starts_with(rate_type, "three-body")
472 && !ba::starts_with(third_body, "(+"))
473 {
474 // check for conditions of three-body reactions:
475 // - integer stoichiometric conditions
476 // - either reactant or product side involves exactly three species
477 size_t nreac = 0;
478 size_t nprod = 0;
479
480 // ensure that all reactants have integer stoichiometric coefficients
481 for (const auto& [name, stoich] : reactants) {
482 if (trunc(stoich) != stoich) {
483 return;
484 }
485 nreac += static_cast<size_t>(stoich);
486 }
487
488 // ensure that all products have integer stoichiometric coefficients
489 for (const auto& [name, stoich] : products) {
490 if (trunc(stoich) != stoich) {
491 return;
492 }
493 nprod += static_cast<size_t>(stoich);
494 }
495
496 // either reactant or product side involves exactly three species
497 if (nreac != 3 && nprod != 3) {
498 return;
499 }
500 }
501
502 if (m_third_body) {
503 string tName = m_third_body->name();
504 if (tName != third_body && third_body != "M" && tName != "M") {
505 throw InputFileError("Reaction::setEquation", input,
506 "Detected incompatible third body colliders in reaction '{}'\n"
507 "ThirdBody definition does not match equation", equation);
508 }
509 m_third_body->setName(third_body);
510 } else {
511 m_third_body = make_shared<ThirdBody>(third_body);
512 }
513
514 // adjust reactant coefficients
515 auto reac = reactants.find(third_body);
516 if (trunc(reac->second) != 1) {
517 reac->second -= 1.;
518 } else {
519 reactants.erase(reac);
520 }
521
522 // adjust product coefficients
523 auto prod = products.find(third_body);
524 if (trunc(prod->second) != 1) {
525 prod->second -= 1.;
526 } else {
527 products.erase(prod);
528 }
529}
530
531string Reaction::type() const
532{
533 if (!m_rate) {
534 throw CanteraError("Reaction::type", "Empty Reaction does not have a type");
535 }
536
537 string rate_type = m_rate->type();
538 string sub_type = m_rate->subType();
539 if (sub_type != "") {
540 return rate_type + "-" + sub_type;
541 }
542
543 if (m_third_body) {
544 return "three-body-" + rate_type;
545 }
546
547 return rate_type;
548}
549
550UnitStack Reaction::calculateRateCoeffUnits(const Kinetics& kin)
551{
552 if (!valid()) {
553 // If a reaction is invalid because of missing species in the Kinetics
554 // object, determining the units of the rate coefficient is impossible.
555 return UnitStack({});
556 }
557
558 // Determine the units of the rate coefficient
559 UnitStack rate_units(kin.thermo(0).standardConcentrationUnits());
560
561 // Set output units to standardConcentrationUnits per second
562 rate_units.join(1.);
563 rate_units.update(Units(1.0, 0, 0, -1), 1.);
564
565 for (const auto& [name, order] : orders) {
566 const auto& phase = kin.speciesPhase(name);
567 // Account for specified reaction orders
568 rate_units.update(phase.standardConcentrationUnits(), -order);
569 }
570 for (const auto& [name, stoich] : reactants) {
571 // Order for each reactant is the reactant stoichiometric coefficient,
572 // unless already overridden by user-specified orders
573 if (name == "M" || ba::starts_with(name, "(+")) {
574 // calculateRateCoeffUnits may be called before these pseudo-species
575 // have been stripped from the reactants
576 continue;
577 } else if (orders.find(name) == orders.end()) {
578 const auto& phase = kin.speciesPhase(name);
579 // Account for each reactant species
580 rate_units.update(phase.standardConcentrationUnits(), -stoich);
581 }
582 }
583
584 if (m_third_body && m_third_body->mass_action) {
585 // Account for third-body collision partner as the last entry
586 rate_units.join(-1);
587 }
588
589 Reaction::rate_units = rate_units.product();
590 return rate_units;
591}
592
593void updateUndeclared(vector<string>& undeclared,
594 const Composition& comp, const Kinetics& kin)
595{
596 for (const auto& [name, stoich]: comp) {
597 if (kin.kineticsSpeciesIndex(name) == npos) {
598 undeclared.emplace_back(name);
599 }
600 }
601}
602
603void Reaction::checkBalance(const Kinetics& kin) const
604{
605 Composition balr, balp;
606
607 // iterate over products and reactants
608 for (const auto& [name, stoich] : products) {
609 const ThermoPhase& ph = kin.speciesPhase(name);
610 size_t k = ph.speciesIndex(name);
611 for (size_t m = 0; m < ph.nElements(); m++) {
612 balr[ph.elementName(m)] = 0.0; // so that balr contains all species
613 balp[ph.elementName(m)] += stoich * ph.nAtoms(k, m);
614 }
615 }
616 for (const auto& [name, stoich] : reactants) {
617 const ThermoPhase& ph = kin.speciesPhase(name);
618 size_t k = ph.speciesIndex(name);
619 for (size_t m = 0; m < ph.nElements(); m++) {
620 balr[ph.elementName(m)] += stoich * ph.nAtoms(k, m);
621 }
622 }
623
624 string msg;
625 bool ok = true;
626 for (const auto& [elem, balance] : balr) {
627 double elemsum = balr[elem] + balp[elem];
628 double elemdiff = fabs(balp[elem] - balr[elem]);
629 if (elemsum > 0.0 && elemdiff / elemsum > 1e-4) {
630 ok = false;
631 msg += fmt::format(" {} {} {}\n",
632 elem, balr[elem], balp[elem]);
633 }
634 }
635 if (!ok) {
636 throw InputFileError("Reaction::checkBalance", input,
637 "The following reaction is unbalanced: {}\n"
638 " Element Reactants Products\n{}",
639 equation(), msg);
640 }
641
642 if (kin.thermo(0).nDim() == 3) {
643 return;
644 }
645
646 // Check that the number of surface sites is balanced
647 double reac_sites = 0.0;
648 double prod_sites = 0.0;
649 auto& surf = dynamic_cast<const SurfPhase&>(kin.thermo(0));
650 for (const auto& [name, stoich] : reactants) {
651 size_t k = surf.speciesIndex(name);
652 if (k != npos) {
653 reac_sites += stoich * surf.size(k);
654 }
655 }
656 for (const auto& [name, stoich] : products) {
657 size_t k = surf.speciesIndex(name);
658 if (k != npos) {
659 prod_sites += stoich * surf.size(k);
660 }
661 }
662 if (fabs(reac_sites - prod_sites) > 1e-5 * (reac_sites + prod_sites)) {
663 throw InputFileError("Reaction::checkBalance", input,
664 "Number of surface sites not balanced in reaction {}.\n"
665 "Reactant sites: {}\nProduct sites: {}",
666 equation(), reac_sites, prod_sites);
667 }
668}
669
670bool Reaction::checkSpecies(const Kinetics& kin) const
671{
672 // Check for undeclared species
673 vector<string> undeclared;
674 updateUndeclared(undeclared, reactants, kin);
675 updateUndeclared(undeclared, products, kin);
676 if (!undeclared.empty()) {
677 if (kin.skipUndeclaredSpecies()) {
678 return false;
679 } else {
680 throw InputFileError("Reaction::checkSpecies", input, "Reaction '{}'\n"
681 "contains undeclared species: '{}'",
682 equation(), ba::join(undeclared, "', '"));
683 }
684 }
685
686 undeclared.clear();
687 updateUndeclared(undeclared, orders, kin);
688 if (!undeclared.empty()) {
689 if (kin.skipUndeclaredSpecies()) {
690 return false;
691 } else {
692 if (input.hasKey("orders")) {
693 throw InputFileError("Reaction::checkSpecies", input["orders"],
694 "Reaction '{}'\n"
695 "defines reaction orders for undeclared species: '{}'",
696 equation(), ba::join(undeclared, "', '"));
697 }
698 // Error for empty input AnyMap (that is, XML)
699 throw InputFileError("Reaction::checkSpecies", input, "Reaction '{}'\n"
700 "defines reaction orders for undeclared species: '{}'",
701 equation(), ba::join(undeclared, "', '"));
702 }
703 }
704
705 if (m_third_body) {
706 return m_third_body->checkSpecies(*this, kin);
707 }
708
709 checkBalance(kin);
710
711 return true;
712}
713
714bool Reaction::usesElectrochemistry(const Kinetics& kin) const
715{
716 // Check electrochemistry
717 vector<double> e_counter(kin.nPhases(), 0.0);
718
719 // Find the number of electrons in the products for each phase
720 for (const auto& [name, stoich] : products) {
721 size_t kkin = kin.kineticsSpeciesIndex(name);
722 size_t i = kin.speciesPhaseIndex(kkin);
723 size_t kphase = kin.thermo(i).speciesIndex(name);
724 e_counter[i] += stoich * kin.thermo(i).charge(kphase);
725 }
726
727 // Subtract the number of electrons in the reactants for each phase
728 for (const auto& [name, stoich] : reactants) {
729 size_t kkin = kin.kineticsSpeciesIndex(name);
730 size_t i = kin.speciesPhaseIndex(kkin);
731 size_t kphase = kin.thermo(i).speciesIndex(name);
732 e_counter[i] -= stoich * kin.thermo(i).charge(kphase);
733 }
734
735 // If the electrons change phases then the reaction is electrochemical
736 for (double delta_e : e_counter) {
737 if (std::abs(delta_e) > 1e-4) {
738 return true;
739 }
740 }
741
742 return false;
743}
744
745
746ThirdBody::ThirdBody(const string& third_body)
747{
748 setName(third_body);
749}
750
751void ThirdBody::setName(const string& third_body)
752{
753 string name = third_body;
754 if (ba::starts_with(third_body, "(+ ")) {
755 mass_action = false;
756 name = third_body.substr(3, third_body.size() - 4);
757 } else if (ba::starts_with(third_body, "(+")) {
758 mass_action = false;
759 name = third_body.substr(2, third_body.size() - 3);
760 }
761
762 if (name == m_name) {
763 return;
764 }
765 if (name == "M" && efficiencies.size() == 1) {
766 // revert from explicit name to generic collider
767 m_name = name;
768 return;
769 }
770 if (efficiencies.size()) {
771 throw CanteraError("ThirdBody::setName",
772 "Conflicting efficiency definition for explicit third body '{}'", name);
773 }
774 m_name = name;
775 default_efficiency = 0.;
776 efficiencies[m_name] = 1.;
777}
778
779ThirdBody::ThirdBody(const AnyMap& node)
780{
781 setParameters(node);
782}
783
784void ThirdBody::setParameters(const AnyMap& node)
785{
786 if (node.hasKey("default-efficiency")) {
787 double value = node["default-efficiency"].asDouble();
788 if (m_name != "M" && value != 0.) {
789 throw InputFileError("ThirdBody::setParameters", node["default-efficiency"],
790 "Invalid default efficiency for explicit collider {};\n"
791 "value is optional and/or needs to be zero", m_name);
792 }
793 default_efficiency = value;
794 }
795 if (node.hasKey("efficiencies")) {
796 efficiencies = node["efficiencies"].asMap<double>();
797 }
798 if (m_name != "M"
799 && (efficiencies.size() != 1 || efficiencies.begin()->first != m_name))
800 {
801 throw InputFileError("ThirdBody::setParameters", node,
802 "Detected incompatible third body colliders definitions");
803 }
804}
805
806void ThirdBody::getParameters(AnyMap& node) const
807{
808 if (m_name == "M" || explicit_3rd) {
809 if (efficiencies.size()) {
810 node["efficiencies"] = efficiencies;
811 node["efficiencies"].setFlowStyle();
812 }
813 if (default_efficiency != 1.0 && !explicit_3rd) {
814 node["default-efficiency"] = default_efficiency;
815 }
816 }
817}
818
819double ThirdBody::efficiency(const string& k) const
820{
821 return getValue(efficiencies, k, default_efficiency);
822}
823
824string ThirdBody::collider() const
825{
826 if (mass_action) {
827 return " + " + m_name;
828 }
829 return " (+" + m_name + ")";
830}
831
832bool ThirdBody::checkSpecies(const Reaction& rxn, const Kinetics& kin) const
833{
834 vector<string> undeclared;
835 updateUndeclared(undeclared, efficiencies, kin);
836
837 if (!undeclared.empty()) {
838 if (!kin.skipUndeclaredThirdBodies()) {
839 if (rxn.input.hasKey("efficiencies")) {
840 throw InputFileError("ThirdBody::checkSpecies",
841 rxn.input["efficiencies"], "Reaction '{}'\n"
842 "defines third-body efficiencies for undeclared species: '{}'",
843 rxn.equation(), ba::join(undeclared, "', '"));
844 }
845 // Error for specified ThirdBody or empty input AnyMap
846 throw InputFileError("ThirdBody::checkSpecies", rxn.input, "Reaction '{}'\n"
847 "is a three-body reaction with undeclared species: '{}'",
848 rxn.equation(), ba::join(undeclared, "', '"));
849 } else if (kin.skipUndeclaredThirdBodies() && m_name != "M") {
850 // Prevent addition of reaction silently as "skip-undeclared-third-bodies"
851 // is set to true
852 return false;
853 }
854 }
855 return true;
856}
857
858
859unique_ptr<Reaction> newReaction(const string& type)
860{
861 return make_unique<Reaction>();
862}
863
864unique_ptr<Reaction> newReaction(const AnyMap& rxn_node, const Kinetics& kin)
865{
866 return make_unique<Reaction>(rxn_node, kin);
867}
868
869void parseReactionEquation(Reaction& R, const string& equation,
870 const AnyBase& reactionNode, const Kinetics* kin)
871{
872 // Parse the reaction equation to determine participating species and
873 // stoichiometric coefficients
874 vector<string> tokens;
875 tokenizeString(equation, tokens);
876 tokens.push_back("+"); // makes parsing last species not a special case
877
878 size_t last_used = npos; // index of last-used token
879 bool reactants = true;
880 for (size_t i = 1; i < tokens.size(); i++) {
881 if (tokens[i] == "+" || ba::starts_with(tokens[i], "(+") ||
882 tokens[i] == "<=>" || tokens[i] == "=" || tokens[i] == "=>") {
883 string species = tokens[i-1];
884
885 double stoich = 1.0;
886 bool mass_action = true;
887 if (last_used != npos && tokens[last_used] == "(+"
888 && ba::ends_with(species, ")")) {
889 // Falloff third body with space, such as "(+ M)"
890 mass_action = false;
891 species = "(+" + species;
892 } else if (last_used == i - 1 && ba::starts_with(species, "(+")
893 && ba::ends_with(species, ")")) {
894 // Falloff 3rd body written without space, such as "(+M)"
895 mass_action = false;
896 } else if (last_used == i - 2) {
897 // Species with no stoich. coefficient
898 } else if (last_used == i - 3) {
899 // Stoich. coefficient and species
900 try {
901 stoich = fpValueCheck(tokens[i-2]);
902 } catch (CanteraError& err) {
903 throw InputFileError("parseReactionEquation", reactionNode,
904 err.getMessage());
905 }
906 } else {
907 throw InputFileError("parseReactionEquation", reactionNode,
908 "Error parsing reaction string '{}'.\n"
909 "Current token: '{}'\nlast_used: '{}'",
910 equation, tokens[i],
911 (last_used == npos) ? "n/a" : tokens[last_used]);
912 }
913 if (!kin || (kin->kineticsSpeciesIndex(species) == npos
914 && mass_action && species != "M"))
915 {
916 R.setValid(false);
917 }
918
919 if (reactants) {
920 R.reactants[species] += stoich;
921 } else {
922 R.products[species] += stoich;
923 }
924
925 last_used = i;
926 }
927
928 // Tokens after this point are part of the products string
929 if (tokens[i] == "<=>" || tokens[i] == "=") {
930 R.reversible = true;
931 reactants = false;
932 } else if (tokens[i] == "=>") {
933 R.reversible = false;
934 reactants = false;
935 }
936 }
937}
938
939vector<shared_ptr<Reaction>> getReactions(const AnyValue& items, Kinetics& kinetics)
940{
941 vector<shared_ptr<Reaction>> all_reactions;
942 for (const auto& node : items.asVector<AnyMap>()) {
943 auto R = make_shared<Reaction>(node, kinetics);
944 R->validate(kinetics);
945 if (R->valid() && R->checkSpecies(kinetics)) {
946 all_reactions.emplace_back(R);
947 }
948 }
949 return all_reactions;
950}
951
952}
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:432
void exclude(const string &key)
Mark key as excluded from this map.
Definition AnyMap.cpp:1502
void copyMetadata(const AnyMap &other)
Copy metadata including input line/column from an existing AnyMap.
Definition AnyMap.cpp:1553
bool hasKey(const string &key) const
Returns true if the map contains an item named key.
Definition AnyMap.cpp:1477
bool empty() const
Return boolean indicating whether AnyMap is empty.
Definition AnyMap.cpp:1468
void setFlowStyle(bool flow=true)
Use "flow" style when outputting this AnyMap to YAML.
Definition AnyMap.cpp:1794
bool getBool(const string &key, bool default_) const
If key exists, return it as a bool, otherwise return default_.
Definition AnyMap.cpp:1575
const string & getString(const string &key, const string &default_) const
If key exists, return it as a string, otherwise return default_.
Definition AnyMap.cpp:1590
void update(const AnyMap &other, bool keepExisting=true)
Add items from other to this AnyMap.
Definition AnyMap.cpp:1493
A wrapper for a variable whose type is determined at runtime.
Definition AnyMap.h:88
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:750
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:239
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:1323
void skipUndeclaredThirdBodies(bool skip)
Determine behavior when adding a new reaction that contains third-body efficiencies for species not d...
Definition Kinetics.h:1335
size_t kineticsSpeciesIndex(size_t k, size_t n) const
The location of species k of phase n in species arrays.
Definition Kinetics.h:273
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:302
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:323
size_t nDim() const
Returns the number of spatial dimensions (1, 2, or 3)
Definition Phase.h:547
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:539
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:370
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:595
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:869
vector< shared_ptr< Reaction > > getReactions(const AnyValue &items, Kinetics &kinetics)
Create Reaction objects for each item (an AnyMap) in items.
Definition Reaction.cpp:939
void warn_deprecated(const string &source, const AnyBase &node, const string &message)
A deprecation warning for syntax in an input file.
Definition AnyMap.cpp:1997
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:859
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...