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