Loading [MathJax]/jax/output/HTML-CSS/config.js
Cantera  3.2.0a1
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
ReactionPath.cpp
Go to the documentation of this file.
1/**
2 * @file ReactionPath.cpp
3 * Implementation file for classes used in reaction path analysis.
4 */
5
6// This file is part of Cantera. See License.txt in the top-level directory or
7// at https://cantera.org/license.txt for license and copyright information.
8
13
14#include <boost/algorithm/string.hpp>
15
16namespace ba = boost::algorithm;
17
18using namespace std;
19
20namespace Cantera
21{
22
24{
25 m_paths.push_back(path);
26 if (path->begin() == this) {
27 m_out += path->flow();
28 } else if (path->end() == this) {
29 m_in += path->flow();
30 } else {
31 throw CanteraError("SpeciesNode::addPath", "path added to wrong node");
32 }
33}
34
35void SpeciesNode::printPaths()
36{
37 for (size_t i = 0; i < m_paths.size(); i++) {
38 cout << m_paths[i]->begin()->name << " --> "
39 << m_paths[i]->end()->name << ": "
40 << m_paths[i]->flow() << endl;
41 }
42}
43
45 : m_a(begin), m_b(end)
46{
47 begin->addPath(this);
48 end->addPath(this);
49}
50
51void Path::addReaction(size_t rxnNumber, double value, const string& label)
52{
53 m_rxn[rxnNumber] += value;
54 m_total += value;
55 if (label != "") {
56 m_label[label] += value;
57 }
58}
59
60void Path::writeLabel(ostream& s, double threshold)
61{
62 if (m_label.size() == 0) {
63 return;
64 }
65 double v;
66 for (const auto& [species, value] : m_label) {
67 v = value / m_total;
68 if (m_label.size() == 1) {
69 s << species << "\\l";
70 } else if (v > threshold) {
71 s << species;
72 int percent = int(100*v + 0.5);
73 if (percent < 100) {
74 s << " (" << percent << "%)\\l";
75 } else {
76 s << "\\l";
77 }
78 }
79 }
80}
81
82ReactionPathDiagram::ReactionPathDiagram(
83 shared_ptr<Kinetics> kin, const string& element_) : element(element_), m_kin(kin)
84{
85 if (!m_kin) {
86 throw CanteraError("ReactionPathDiagram::ReactionPathDiagram",
87 "Kinetics object must not be empty.");
88 }
89 m_builder = make_shared<ReactionPathBuilder>();
90 m_builder->init(m_log, *m_kin.get());
91}
92
94{
95 // delete the nodes
96 for (const auto& [k, node] : m_nodes) {
97 delete node;
98 }
99
100 // delete the paths
101 size_t nn = nPaths();
102 for (size_t n = 0; n < nn; n++) {
103 delete m_pathlist[n];
104 }
105}
106
107vector<int> ReactionPathDiagram::reactions()
108{
109 double flmax = 0.0;
110 for (size_t i = 0; i < nPaths(); i++) {
111 Path* p = path(i);
112 flmax = std::max(p->flow(), flmax);
113 }
114 m_rxns.clear();
115 for (size_t i = 0; i < nPaths(); i++) {
116 for (const auto& [iRxn, flux] : path(i)->reactionMap()) {
117 double flxratio = flux / flmax;
118 if (flxratio > threshold) {
119 m_rxns.insert(iRxn);
120 }
121 }
122 }
123 vector<int> r;
124 for (const auto& rxn : m_rxns) {
125 r.push_back(int(rxn));
126 }
127 return r;
128}
129
130void ReactionPathDiagram::add(ReactionPathDiagram& d)
131{
132 for (size_t n = 0; n < nPaths(); n++) {
133 Path* p = path(n);
134 size_t k1 = p->begin()->number;
135 size_t k2 = p->end()->number;
136 p->setFlow(p->flow() + d.flow(k1,k2));
137 }
138}
139
140void ReactionPathDiagram::add(shared_ptr<ReactionPathDiagram> d)
141{
142 add(*d.get());
143}
144
145void ReactionPathDiagram::findMajorPaths(double athreshold, size_t lda, double* a)
146{
147 double netmax = 0.0;
148 for (size_t n = 0; n < nNodes(); n++) {
149 for (size_t m = n+1; m < nNodes(); m++) {
150 size_t k1 = m_speciesNumber[n];
151 size_t k2 = m_speciesNumber[m];
152 double fl = fabs(netFlow(k1,k2));
153 netmax = std::max(fl, netmax);
154 }
155 }
156 for (size_t n = 0; n < nNodes(); n++) {
157 for (size_t m = n+1; m < nNodes(); m++) {
158 size_t k1 = m_speciesNumber[n];
159 size_t k2 = m_speciesNumber[m];
160 double fl = fabs(netFlow(k1,k2));
161 if (fl > athreshold*netmax) {
162 a[lda*k1 + k2] = 1;
163 }
164 }
165 }
166}
167
169{
170 if (flow_type == OneWayFlow) {
171 return "OneWayFlow";
172 }
173 return "NetFlow";
174}
175
176void ReactionPathDiagram::setFlowType(const string& fType)
177{
178 if (fType == "OneWayFlow") {
179 flow_type = OneWayFlow;
180 } else if (fType == "NetFlow") {
181 flow_type = NetFlow;
182 } else {
183 throw CanteraError("ReactionPathDiagram::setFlowType",
184 "Unknown flow type '{}'", fType);
185 }
186}
187
189{
190 m_builder->build(*m_kin.get(), element, m_log, *this, true);
191 m_isBuilt = true;
192}
193
195{
196 if (!m_isBuilt) {
197 build();
198 }
199 std::stringstream out;
200 exportToDot(out);
201 return out.str();
202}
203
205{
206 if (!m_isBuilt) {
207 build();
208 }
209 std::stringstream out;
210 writeData(out);
211 return out.str();
212}
213
215{
216 return m_log.str();
217}
218
219void ReactionPathDiagram::writeData(ostream& s)
220{
221 s << title << endl;
222 for (size_t i1 = 0; i1 < nNodes(); i1++) {
223 size_t k1 = m_speciesNumber[i1];
224 s << m_nodes[k1]->name << " ";
225 }
226 s << endl;
227 for (size_t i1 = 0; i1 < nNodes(); i1++) {
228 size_t k1 = m_speciesNumber[i1];
229 for (size_t i2 = i1+1; i2 < nNodes(); i2++) {
230 size_t k2 = m_speciesNumber[i2];
231 double f1 = flow(k1, k2);
232 double f2 = flow(k2, k1);
233 s << m_nodes[k1]->name << " " << m_nodes[k2]->name
234 << " " << f1 << " " << -f2 << endl;
235 }
236 }
237}
238
240{
241 double flmax = 0.0;
242 s.precision(3);
243
244 // a directed graph
245 s << "digraph " << name << " {" << endl;
246
247 // the graph will be no larger than x_size, y_size
248 if (x_size > 0.0) {
249 if (y_size < 0.0) {
250 y_size = x_size;
251 }
252 s << "size = \""
253 << x_size << ","
254 << y_size << "\";"
255 << endl;
256 }
257
258 if (dot_options != "") {
259 s << dot_options << endl;
260 }
261
262 // draw paths representing net flows
263 if (flow_type == NetFlow) {
264 // if no scale was specified, normalize net flows by the maximum net
265 // flow
266 if (scale <= 0.0) {
267 for (size_t i1 = 0; i1 < nNodes(); i1++) {
268 size_t k1 = m_speciesNumber[i1];
269 node(k1)->visible = false;
270 for (size_t i2 = i1+1; i2 < nNodes(); i2++) {
271 size_t k2 = m_speciesNumber[i2];
272 double flx = netFlow(k1, k2);
273 if (flx < 0.0) {
274 flx = -flx;
275 }
276 flmax = std::max(flx, flmax);
277 }
278 }
279 } else {
280 flmax = scale;
281 }
282 flmax = std::max(flmax, 1e-10);
283
284 // loop over all unique pairs of nodes
285 for (size_t i1 = 0; i1 < nNodes(); i1++) {
286 size_t k1 = m_speciesNumber[i1];
287 for (size_t i2 = i1+1; i2 < nNodes(); i2++) {
288 size_t k2 = m_speciesNumber[i2];
289 double flx = netFlow(k1, k2);
290 if (m_local != npos && k1 != m_local && k2 != m_local) {
291 flx = 0.0;
292 }
293 if (flx != 0.0) {
294 double flxratio;
295 size_t kbegin, kend;
296 // set beginning and end of the path based on the sign of
297 // the net flow
298 if (flx > 0.0) {
299 kbegin = k1;
300 kend = k2;
301 flxratio = flx/flmax;
302 } else {
303 kbegin = k2;
304 kend = k1;
305 flxratio = -flx/flmax;
306 }
307
308 // write out path specification if the net flow is greater
309 // than the threshold
310 if (flxratio >= threshold) {
311 // make nodes visible
312 node(kbegin)->visible = true;
313 node(kend)->visible = true;
314
315 s << "s" << kbegin << " -> s" << kend;
316 s << "[fontname=\""+m_font+"\", penwidth=";
317
318 if (arrow_width < 0) {
319 double lwidth = 1.0 - 4.0
320 * log10(flxratio/threshold)/log10(threshold) + 1.0;
321 s << lwidth;
322 s << ", arrowsize="
323 << std::min(6.0, 0.5*lwidth);
324 } else {
325 s << arrow_width;
326 s << ", arrowsize=" << flxratio + 1;
327 }
328
329 double hue = 0.7;
330 double bright = 0.9;
331 s << ", color=" << "\"" << hue << ", "
332 << flxratio + 0.5
333 << ", " << bright << "\"" << endl;
334
335 if (flxratio > label_min) {
336 s << ", label=\" " << flxratio;
337 if (show_details) {
338 if (flow(kbegin, kend) > 0.0) {
339 s << "\\l fwd: "
340 << flow(kbegin, kend)/flmax << "\\l";
341 path(kbegin, kend)->writeLabel(s);
342 }
343 if (flow(kend, kbegin) > 0.0) {
344 s << " \\l rev: "
345 << flow(kend,kbegin)/flmax << "\\l";
346 path(kend, kbegin)->writeLabel(s);
347 }
348 }
349 s << "\"";
350 }
351 s << "];" << endl;
352 }
353 }
354 }
355 }
356 } else {
357 if (scale < 0) {
358 for (size_t i = 0; i < nPaths(); i++) {
359 flmax = std::max(path(i)->flow(), flmax);
360 }
361 } else {
362 flmax = scale;
363 }
364
365 for (size_t i = 0; i < nPaths(); i++) {
366 Path* p = path(i);
367 double flxratio = p->flow()/flmax;
368 if (m_local != npos) {
369 if (p->begin()->number != m_local
370 && p->end()->number != m_local) {
371 flxratio = 0.0;
372 }
373 }
374 if (flxratio > threshold) {
375 p->begin()->visible = true;
376 p->end()->visible = true;
377 s << "s" << p->begin()->number
378 << " -> s" << p->end()->number;
379
380 if (arrow_width < 0) {
381 double lwidth = 1.0 - 4.0 * log10(flxratio/threshold)/log10(threshold)
382 + 1.0;
383 s << "[fontname=\""+m_font+"\", penwidth="
384 << lwidth;
385 s << ", arrowsize="
386 << std::min(6.0, 0.5*lwidth);
387 } else {
388 s << ", penwidth="
389 << arrow_width;
390 s << ", arrowsize=" << flxratio + 1;
391 }
392 double hue = 0.7;
393 double bright = 0.9;
394 s << ", color=" << "\"" << hue << ", " << flxratio + 0.5
395 << ", " << bright << "\"" << endl;
396
397 if (flxratio > label_min) {
398 s << ", label = \" " << flxratio;
399 if (show_details) {
400 s << "\\l";
401 p->writeLabel(s);
402 }
403 s << "\"";
404 }
405 s << "];" << endl;
406 }
407 }
408 }
409 s.precision(2);
410 for (const auto& [kSpecies, node] : m_nodes) {
411 if (node->visible) {
412 s << "s" << kSpecies << " [ fontname=\""+m_font+"\", label=\"" << node->name
413 << "\"];" << endl;
414 }
415 }
416 s << " label = " << "\"" << "Scale = "
417 << flmax << "\\l " << title << "\";" << endl;
418 s << " fontname = \""+m_font+"\";" << endl << "}" << endl;
419}
420
421
422void ReactionPathDiagram::addNode(size_t k, const string& nm, double x)
423{
424 if (!m_nodes[k]) {
425 m_nodes[k] = new SpeciesNode;
426 m_nodes[k]->number = k;
427 m_nodes[k]->name = nm;
428 m_nodes[k]->value = x;
429 m_speciesNumber.push_back(k);
430 }
431}
432
433void ReactionPathDiagram::linkNodes(size_t k1, size_t k2, size_t rxn,
434 double value, string legend)
435{
436 Path* ff = m_paths[k1][k2];
437 if (!ff) {
438 ff= new Path(m_nodes[k1], m_nodes[k2]);
439 m_paths[k1][k2] = ff;
440 m_pathlist.push_back(ff);
441 }
442 ff->addReaction(rxn, value, legend);
443 m_rxns.insert(rxn);
444 m_flxmax = std::max(ff->flow(), m_flxmax);
445}
446
447vector<size_t> ReactionPathDiagram::species()
448{
449 return m_speciesNumber;
450}
451
453{
454 m_groups.resize(m_nr);
455 for (size_t i = 0; i < m_nr; i++) { // loop over reactions
456 logfile << endl << "Reaction " << i+1 << ": "
457 << s.reaction(i)->equation();
458
459 if (m_determinate[i]) {
460 logfile << " ... OK." << endl;
461 } else if (m_reac[i].size() == 2 && m_prod[i].size() == 2) {
462 // indices for the two reactants
463 size_t kr0 = m_reac[i][0];
464 size_t kr1 = m_reac[i][1];
465
466 // indices for the two products
467 size_t kp0 = m_prod[i][0];
468 size_t kp1 = m_prod[i][1];
469
470 // references to the Group objects representing the reactants
471 const Group& r0 = m_sgroup[kr0];
472 const Group& r1 = m_sgroup[kr1];
473 const Group& p0 = m_sgroup[kp0];
474 const Group& p1 = m_sgroup[kp1];
475
476 const Group* group_a0=0, *group_b0=0, *group_c0=0,
477 *group_a1=0, *group_b1=0, *group_c1=0;
478 Group b0 = p0 - r0;
479 Group b1 = p1 - r0;
480 if (b0.valid() && b1.valid()) {
481 logfile << " ... ambiguous." << endl;
482 } else if (!b0.valid() && !b1.valid()) {
483 logfile << " ... cannot express as A + BC = AB + C" << endl;
484 } else {
485 logfile << endl;
486 }
487
488 if (b0.valid()) {
489 if (b0.sign() > 0) {
490 group_a0 = &r0;
491 group_b0 = &b0;
492 group_c0 = &p1;
493 m_transfer[i][0][0] = r0;
494 m_transfer[i][1][0] = b0;
495 m_transfer[i][1][1] = p1;
496 } else {
497 group_a0 = &r1;
498 group_c0 = &p0;
499 b0 *= -1;
500 group_b0 = &b0;
501 m_transfer[i][1][1] = r1;
502 m_transfer[i][0][1] = b0;
503 m_transfer[i][0][0] = p0;
504 }
505 logfile << " ";
506 group_a0->fmt(logfile, m_elementSymbols);
507 logfile << " + ";
508 group_b0->fmt(logfile,m_elementSymbols);
509 group_c0->fmt(logfile, m_elementSymbols);
510 logfile << " = ";
511 group_a0->fmt(logfile, m_elementSymbols);
512 group_b0->fmt(logfile, m_elementSymbols);
513 logfile << " + ";
514 group_c0->fmt(logfile, m_elementSymbols);
515 if (b1.valid()) {
516 logfile << " [<= default] " << endl;
517 } else {
518 logfile << endl;
519 }
520 }
521
522 if (b1.valid()) {
523 if (b1.sign() > 0) {
524 group_a1 = &r0;
525 group_b1 = &b1;
526 group_c1 = &p0;
527 if (!b0.valid()) {
528 m_transfer[i][0][1] = r0;
529 m_transfer[i][1][1] = b0;
530 m_transfer[i][1][0] = p0;
531 }
532 } else {
533 group_a1 = &r1;
534 group_c1 = &p1;
535 b1 *= -1;
536 group_b1 = &b1;
537 if (!b0.valid()) {
538 m_transfer[i][1][0] = r1;
539 m_transfer[i][0][0] = b0;
540 m_transfer[i][0][1] = p1;
541 }
542 }
543 logfile << " ";
544 group_a1->fmt(logfile, m_elementSymbols);
545 logfile << " + ";
546 group_b1->fmt(logfile, m_elementSymbols);
547 group_c1->fmt(logfile, m_elementSymbols);
548 logfile << " = ";
549 group_a1->fmt(logfile, m_elementSymbols);
550 group_b1->fmt(logfile, m_elementSymbols);
551 logfile << " + ";
552 group_c1->fmt(logfile, m_elementSymbols);
553 logfile << endl;
554 }
555 } else {
556 logfile << "... cannot parse. [ignored]" << endl;
557 }
558 }
559 return 1;
560}
561
562void ReactionPathBuilder::findElements(Kinetics& kin)
563{
564 m_enamemap.clear();
565 m_nel = 0;
566 for (size_t i = 0; i < kin.nPhases(); i++) {
567 ThermoPhase* p = &kin.thermo(i);
568 // iterate over the elements in this phase
569 for (size_t m = 0; m < p->nElements(); m++) {
570 string ename = p->elementName(m);
571
572 // if no entry is found for this element name, then it is a new
573 // element. In this case, add the name to the list of names,
574 // increment the element count, and add an entry to the
575 // name->(index+1) map.
576 if (m_enamemap.find(ename) == m_enamemap.end()) {
577 m_enamemap[ename] = m_nel + 1;
578 m_elementSymbols.push_back(ename);
579 m_nel++;
580 }
581 }
582 }
583 m_atoms.resize(kin.nTotalSpecies(), m_nel, 0.0);
584 // iterate over the elements
585 for (size_t m = 0; m < m_nel; m++) {
586 size_t k = 0;
587 // iterate over the phases
588 for (size_t ip = 0; ip < kin.nPhases(); ip++) {
589 ThermoPhase* p = &kin.thermo(ip);
590 size_t mlocal = p->elementIndex(m_elementSymbols[m]);
591 for (size_t kp = 0; kp < p->nSpecies(); kp++) {
592 if (mlocal != npos) {
593 m_atoms(k, m) = p->nAtoms(kp, mlocal);
594 }
595 k++;
596 }
597 }
598 }
599}
600
601int ReactionPathBuilder::init(ostream& logfile, Kinetics& kin)
602{
603 m_transfer.clear();
604 m_elementSymbols.clear();
605 findElements(kin);
606 m_ns = kin.nTotalSpecies();
607 m_nr = kin.nReactions();
608
609 // all reactants / products, even ones appearing on both sides of the
610 // reaction
611 vector<vector<size_t>> allProducts(m_nr);
612 vector<vector<size_t>> allReactants(m_nr);
613 for (size_t i = 0; i < m_nr; i++) {
614 for (size_t k = 0; k < m_ns; k++) {
615 for (int n = 0; n < kin.reactantStoichCoeff(k, i); n++) {
616 allReactants[i].push_back(k);
617 }
618 for (int n = 0; n < kin.productStoichCoeff(k, i); n++) {
619 allProducts[i].push_back(k);
620 }
621 }
622 }
623
624 // m_reac and m_prod exclude indices for species that appear on
625 // both sides of the reaction, so that the diagram contains no loops.
626 m_reac.resize(m_nr);
627 m_prod.resize(m_nr);
628 m_ropf.resize(m_nr);
629 m_ropr.resize(m_nr);
630 m_determinate.resize(m_nr);
631 m_x.resize(m_ns); // not currently used ?
632 m_elatoms.resize(m_nel, m_nr);
633
634 for (size_t i = 0; i < m_nr; i++) {
635 // construct the lists of reactant and product indices, not including
636 // molecules that appear on both sides.
637 m_reac[i].clear();
638 m_prod[i].clear();
639 map<size_t, int> net;
640 size_t nr = allReactants[i].size();
641 size_t np = allProducts[i].size();
642 for (size_t ir = 0; ir < nr; ir++) {
643 net[allReactants[i][ir]]--;
644 }
645 for (size_t ip = 0; ip < np; ip++) {
646 net[allProducts[i][ip]]++;
647 }
648
649 for (size_t k = 0; k < m_ns; k++) {
650 if (net[k] < 0) {
651 size_t nmol = -net[k];
652 for (size_t jr = 0; jr < nmol; jr++) {
653 m_reac[i].push_back(k);
654 }
655 } else if (net[k] > 0) {
656 size_t nmol = net[k];
657 for (size_t jp = 0; jp < nmol; jp++) {
658 m_prod[i].push_back(k);
659 }
660 }
661 }
662
663 size_t nrnet = m_reac[i].size();
664
665 // compute number of atoms of each element in each reaction, excluding
666 // molecules that appear on both sides of the reaction. We only need to
667 // compute this for the reactants, since the elements are conserved.
668 for (size_t n = 0; n < nrnet; n++) {
669 size_t k = m_reac[i][n];
670 for (size_t m = 0; m < m_nel; m++) {
671 m_elatoms(m,i) += m_atoms(k,m);
672 }
673 }
674 }
675
676 // build species groups
677 vector<int> comp(m_nel);
678 m_sgroup.resize(m_ns);
679 for (size_t j = 0; j < m_ns; j++) {
680 for (size_t m = 0; m < m_nel; m++) {
681 comp[m] = int(m_atoms(j,m));
682 }
683 m_sgroup[j] = Group(comp);
684 }
685
686 // determine whether or not the reaction is "determinate", meaning that
687 // there is no ambiguity about which reactant is the source for any element
688 // in any product. This is false if more than one reactant contains a given
689 // element, *and* more than one product contains the element. In this case,
690 // additional information is needed to determine the partitioning of the
691 // reactant atoms of that element among the products.
692 for (size_t i = 0; i < m_nr; i++) {
693 size_t nr = m_reac[i].size();
694 size_t np = m_prod[i].size();
695 m_determinate[i] = true;
696 for (size_t m = 0; m < m_nel; m++) {
697 int nar = 0;
698 int nap = 0;
699 for (size_t j = 0; j < nr; j++) {
700 if (m_atoms(m_reac[i][j],m) > 0) {
701 nar++;
702 }
703 }
704 for (size_t j = 0; j < np; j++) {
705 if (m_atoms(m_prod[i][j],m) > 0) {
706 nap++;
707 }
708 }
709 if (nar > 1 && nap > 1) {
710 m_determinate[i] = false;
711 break;
712 }
713 }
714 }
715
716 findGroups(logfile, kin);
717 return 1;
718}
719
720string reactionLabel(size_t i, size_t kr, size_t nr,
721 const vector<size_t>& slist, const Kinetics& s)
722{
723 string label = "";
724 for (size_t j = 0; j < nr; j++) {
725 if (j != kr) {
726 label += " + "+ s.kineticsSpeciesName(slist[j]);
727 }
728 }
729 if (ba::starts_with(s.reaction(i)->type(), "three-body")) {
730 label += " + M ";
731 } else if (ba::starts_with(s.reaction(i)->type(), "falloff")) {
732 label += " (+ M)";
733 }
734 return label;
735}
736
737int ReactionPathBuilder::build(Kinetics& s, const string& element,
738 ostream& output, ReactionPathDiagram& r, bool quiet)
739{
740 map<size_t, int> warn;
741 double threshold = 0.0;
742 size_t m = m_enamemap[element]-1;
743 r.element = element;
744 if (m == npos) {
745 return -1;
746 }
747
748 s.getFwdRatesOfProgress(m_ropf.data());
749 s.getRevRatesOfProgress(m_ropr.data());
750
751 // species explicitly included or excluded
752 vector<string>& in_nodes = r.included();
753 vector<string>& out_nodes = r.excluded();
754
755 vector<int> status(s.nTotalSpecies(), 0);
756 for (size_t ni = 0; ni < in_nodes.size(); ni++) {
757 status[s.kineticsSpeciesIndex(in_nodes[ni])] = 1;
758 }
759 for (size_t ne = 0; ne < out_nodes.size(); ne++) {
760 status[s.kineticsSpeciesIndex(out_nodes[ne])] = -1;
761 }
762
763 for (size_t i = 0; i < m_nr; i++) {
764 double ropf = m_ropf[i];
765 double ropr = m_ropr[i];
766
767 // loop over reactions involving element m
768 if (m_elatoms(m, i) > 0) {
769 size_t nr = m_reac[i].size();
770 size_t np = m_prod[i].size();
771
772 for (size_t kr = 0; kr < nr; kr++) {
773 size_t kkr = m_reac[i][kr];
774 string fwdlabel = reactionLabel(i, kr, nr, m_reac[i], s);
775
776 for (size_t kp = 0; kp < np; kp++) {
777 size_t kkp = m_prod[i][kp];
778 string revlabel = "";
779 for (size_t j = 0; j < np; j++) {
780 if (j != kp) {
781 revlabel += " + "+ s.kineticsSpeciesName(m_prod[i][j]);
782 }
783 }
784 if (ba::starts_with(s.reaction(i)->type(), "three-body")) {
785 revlabel += " + M ";
786 } else if (ba::starts_with(s.reaction(i)->type(), "falloff")) {
787 revlabel += " (+ M)";
788 }
789
790 // calculate the flow only for pairs that are not the same
791 // species, both contain atoms of element m, and both are
792 // allowed to appear in the diagram
793 if ((kkr != kkp) && (m_atoms(kkr,m) > 0
794 && m_atoms(kkp,m) > 0)
795 && status[kkr] >= 0 && status[kkp] >= 0) {
796 // if neither species contains the full number of atoms
797 // of element m in the reaction, then we must consider
798 // the type of reaction to determine which reactant
799 // species was the source of a given m-atom in the
800 // product
801 double f;
802 if ((m_atoms(kkp,m) < m_elatoms(m, i)) &&
803 (m_atoms(kkr,m) < m_elatoms(m, i))) {
804 map<size_t, map<size_t, Group>>& g = m_transfer[i];
805 if (g.empty()) {
806 if (!warn[i] && !quiet) {
807 output << endl;
808 output << "*************** REACTION IGNORED ***************" << endl;
809 output << "Warning: no rule to determine partitioning of " << element
810 << endl << " in reaction " << s.reaction(i)->equation() << "." << endl
811 << "*************** REACTION IGNORED **************" << endl;
812 output << endl;
813 warn[i] = 1;
814 }
815 f = 0.0;
816 } else {
817 if (!g[kr][kp]) {
818 f = 0.0;
819 } else {
820 f = g[kr][kp].nAtoms(m);
821 }
822 }
823 } else {
824 // no ambiguity about where the m-atoms come from or
825 // go to. Either all reactant m atoms end up in one
826 // product, or only one reactant contains all the
827 // m-atoms. In either case, the number of atoms
828 // transferred is given by the same expression.
829 f = m_atoms(kkp,m) * m_atoms(kkr,m) / m_elatoms(m, i);
830 }
831
832 double fwd = ropf*f;
833 double rev = ropr*f;
834 bool force_incl = ((status[kkr] == 1) || (status[kkp] == 1));
835
836 bool fwd_incl = ((fwd > threshold) ||
837 (fwd > 0.0 && force_incl));
838 bool rev_incl = ((rev > threshold) ||
839 (rev > 0.0 && force_incl));
840 if (fwd_incl || rev_incl) {
841 if (!r.hasNode(kkr)) {
842 r.addNode(kkr, s.kineticsSpeciesName(kkr), m_x[kkr]);
843 }
844 if (!r.hasNode(kkp)) {
845 r.addNode(kkp, s.kineticsSpeciesName(kkp), m_x[kkp]);
846 }
847 }
848 if (fwd_incl) {
849 r.linkNodes(kkr, kkp, int(i), fwd, fwdlabel);
850 }
851 if (rev_incl) {
852 r.linkNodes(kkp, kkr, -int(i), rev, revlabel);
853 }
854 }
855 }
856 }
857 }
858 }
859 return 1;
860}
861
862shared_ptr<ReactionPathDiagram> newReactionPathDiagram(
863 shared_ptr<Kinetics> kin, const string& element)
864{
865 return shared_ptr<ReactionPathDiagram>(
866 new ReactionPathDiagram(kin, element));
867}
868
869}
Classes for reaction path analysis.
Header file for class ThermoPhase, the base class for phases with thermodynamic properties,...
virtual void resize(size_t n, size_t m, double v=0.0)
Resize the array, and fill the new entries with 'v'.
Definition Array.cpp:47
Base class for exceptions thrown by Cantera classes.
void resize(size_t n, size_t m, double v=0.0) override
Resize the matrix.
Class Group is an internal class used by class ReactionPath.
Definition Group.h:21
bool valid() const
True if all non-zero atom numbers have the same sign.
Definition Group.h:99
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
shared_ptr< Reaction > reaction(size_t i)
Return the Reaction object for reaction i.
Definition Kinetics.cpp:743
size_t nPhases() const
The number of phases participating in the reaction mechanism.
Definition Kinetics.h:184
size_t nTotalSpecies() const
The total number of species in all phases participating in the kinetics mechanism.
Definition Kinetics.h:251
const SpeciesNode * begin() const
Upstream node.
void addReaction(size_t rxnNumber, double value, const string &label="")
Add a reaction to the path.
void writeLabel(std::ostream &s, double threshold=0.005)
Write the label for a path connecting two species, indicating the percent of the total flow due to ea...
double flow()
The total flow in this path.
Path(SpeciesNode *begin, SpeciesNode *end)
Constructor.
const SpeciesNode * end() const
Downstream node.
size_t elementIndex(const string &name) const
Return the index of element named 'name'.
Definition Phase.cpp:55
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
int findGroups(std::ostream &logfile, Kinetics &s)
Analyze a reaction to determine which reactants lead to which products.
map< size_t, map< size_t, map< size_t, Group > > > m_transfer
m_transfer[reaction][reactant number][product number] where "reactant number" means the number of the...
Reaction path diagrams (graphs).
map< size_t, SpeciesNode * > m_nodes
map of species index to SpeciesNode
shared_ptr< ReactionPathBuilder > m_builder
Shared pointer to ReactionPathBuilder.
string element
Element used for the construction of a reaction path diagram.
string getDot()
Export string in dot format.
double arrow_width
The arrow width. If < 0, then scale with flux value.
bool m_isBuilt
Boolean indicating whether diagram is built.
string title
Reaction path diagram title.
std::stringstream m_log
Logging stream.
void exportToDot(std::ostream &s)
Export the reaction path diagram.
void findMajorPaths(double threshold, size_t lda, double *a)
Undocumented.
double y_size
Maximum size (y-dimension).
string dot_options
Options for the 'dot' program.
void build()
Build the reaction path diagram.
double scale
The scaling factor for the fluxes.
bool show_details
Boolean flag to show details.
string m_font
Reaction path diagram font.
string name
Name used for dot export.
string getLog()
Get logging messages generated while building the reaction path diagram.
double x_size
Maximum size (x-dimension).
shared_ptr< Kinetics > m_kin
Kinetics used by ReactionPathBuilder.
double label_min
Minimum relative flux for labels.
set< size_t > m_rxns
Indices of reactions that are included in the diagram.
void setFlowType(const string &fType)
Get the way flows are drawn. Either 'NetFlow' or 'OneWayFlow'.
virtual ~ReactionPathDiagram()
Destructor.
double flow(size_t k1, size_t k2)
The one-way flow from node k1 to node k2.
double netFlow(size_t k1, size_t k2)
The net flow from node k1 to node k2.
const string flowType() const
Get the way flows are drawn. Either 'NetFlow' or 'OneWayFlow'.
flow_t flow_type
The way flows are drawn. Either 'NetFlow' or 'OneWayFlow'.
string getData()
Get a (roughly) human-readable representation of the reaction path diagram.
double threshold
Threshold for the minimum flux relative value that will be plotted.
Nodes in reaction path graphs.
string name
Label on graph.
bool visible
Visible on graph;.
size_t number
Species number.
void addPath(Path *path)
add a path to or from this node
Base class for a phase with thermodynamic properties.
Definitions for the classes that are thrown when Cantera experiences an error condition (also contain...
void warn(const string &warning, const string &method, const string &msg, const Args &... args)
Print a generic warning raised from method.
Definition global.h:247
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
shared_ptr< ReactionPathDiagram > newReactionPathDiagram(shared_ptr< Kinetics > kin, const string &element)
Create a new reaction path diagram.