Cantera  4.0.0a1
Loading...
Searching...
No Matches
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, span<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], false);
591 if (mlocal != npos) {
592 for (size_t kp = 0; kp < p->nSpecies(); kp++) {
593 m_atoms(k, m) = p->nAtoms(kp, mlocal);
594 k++;
595 }
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);
749 s.getRevRatesOfProgress(m_ropr);
750
751 // species explicitly included or excluded
752 auto in_nodes = r.included();
753 auto 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:52
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:103
Public interface for kinetics managers.
Definition Kinetics.h:126
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:835
size_t nPhases() const
The number of phases participating in the reaction mechanism.
Definition Kinetics.h:189
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 nElements() const
Number of elements.
Definition Phase.cpp:30
size_t elementIndex(const string &name, bool raise=true) const
Return the index of element named 'name'.
Definition Phase.cpp:51
string elementName(size_t m) const
Name of the element with index m.
Definition Phase.cpp:43
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.
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.
void findMajorPaths(double threshold, size_t lda, span< double > a)
Undocumented.
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:183
shared_ptr< ReactionPathDiagram > newReactionPathDiagram(shared_ptr< Kinetics > kin, const string &element)
Create a new reaction path diagram.