13#include <boost/algorithm/string.hpp>
15namespace ba = boost::algorithm;
24 m_paths.push_back(path);
25 if (path->begin() ==
this) {
26 m_out += path->flow();
27 }
else if (path->end() ==
this) {
30 throw CanteraError(
"SpeciesNode::addPath",
"path added to wrong node");
34void SpeciesNode::printPaths()
36 for (
size_t i = 0; i < m_paths.size(); i++) {
37 cout << m_paths[i]->begin()->name <<
" --> "
38 << m_paths[i]->end()->name <<
": "
39 << m_paths[i]->flow() << endl;
43Path::Path(SpeciesNode* begin, SpeciesNode* end)
44 : m_a(begin), m_b(end)
50void Path::addReaction(
size_t rxnNumber,
double value,
const string& label)
52 m_rxn[rxnNumber] += value;
55 m_label[label] += value;
59void Path::writeLabel(ostream& s,
double threshold)
61 if (m_label.size() == 0) {
65 for (
const auto& [species, value] : m_label) {
67 if (m_label.size() == 1) {
68 s << species <<
"\\l";
69 }
else if (v > threshold) {
71 int percent = int(100*v + 0.5);
73 s <<
" (" << percent <<
"%)\\l";
84 for (
const auto& [k, node] :
m_nodes) {
90 for (
size_t n = 0; n < nn; n++) {
95vector<int> ReactionPathDiagram::reactions()
98 for (
size_t i = 0; i < nPaths(); i++) {
100 flmax = std::max(p->flow(), flmax);
103 for (
size_t i = 0; i < nPaths(); i++) {
104 for (
const auto& [iRxn, flux] : path(i)->reactionMap()) {
105 double flxratio = flux / flmax;
106 if (flxratio > threshold) {
112 for (
const auto& rxn :
m_rxns) {
113 r.push_back(
int(rxn));
118void ReactionPathDiagram::add(ReactionPathDiagram& d)
120 for (
size_t n = 0; n < nPaths(); n++) {
122 size_t k1 = p->begin()->number;
123 size_t k2 = p->end()->number;
124 p->setFlow(p->flow() + d.flow(k1,k2));
128void ReactionPathDiagram::findMajorPaths(
double athreshold,
size_t lda,
double* a)
131 for (
size_t n = 0; n < nNodes(); n++) {
132 for (
size_t m = n+1; m < nNodes(); m++) {
133 size_t k1 = m_speciesNumber[n];
134 size_t k2 = m_speciesNumber[m];
135 double fl = fabs(
netFlow(k1,k2));
136 netmax = std::max(fl, netmax);
139 for (
size_t n = 0; n < nNodes(); n++) {
140 for (
size_t m = n+1; m < nNodes(); m++) {
141 size_t k1 = m_speciesNumber[n];
142 size_t k2 = m_speciesNumber[m];
143 double fl = fabs(
netFlow(k1,k2));
144 if (fl > athreshold*netmax) {
151void ReactionPathDiagram::writeData(ostream& s)
154 for (
size_t i1 = 0; i1 < nNodes(); i1++) {
155 size_t k1 = m_speciesNumber[i1];
159 for (
size_t i1 = 0; i1 < nNodes(); i1++) {
160 size_t k1 = m_speciesNumber[i1];
161 for (
size_t i2 = i1+1; i2 < nNodes(); i2++) {
162 size_t k2 = m_speciesNumber[i2];
163 double f1 =
flow(k1, k2);
164 double f2 =
flow(k2, k1);
166 <<
" " << f1 <<
" " << -f2 << endl;
177 s <<
"digraph " << name <<
" {" << endl;
190 if (dot_options !=
"") {
191 s << dot_options << endl;
195 if (flow_type == NetFlow) {
199 for (
size_t i1 = 0; i1 < nNodes(); i1++) {
200 size_t k1 = m_speciesNumber[i1];
202 for (
size_t i2 = i1+1; i2 < nNodes(); i2++) {
203 size_t k2 = m_speciesNumber[i2];
208 flmax = std::max(flx, flmax);
214 flmax = std::max(flmax, 1e-10);
217 for (
size_t i1 = 0; i1 < nNodes(); i1++) {
218 size_t k1 = m_speciesNumber[i1];
219 for (
size_t i2 = i1+1; i2 < nNodes(); i2++) {
220 size_t k2 = m_speciesNumber[i2];
222 if (m_local !=
npos && k1 != m_local && k2 != m_local) {
233 flxratio = flx/flmax;
237 flxratio = -flx/flmax;
242 if (flxratio >= threshold) {
247 s <<
"s" << kbegin <<
" -> s" << kend;
248 s <<
"[fontname=\""+m_font+
"\", penwidth=";
250 if (arrow_width < 0) {
251 double lwidth = 1.0 - 4.0
252 * log10(flxratio/threshold)/log10(threshold) + 1.0;
255 << std::min(6.0, 0.5*lwidth);
258 s <<
", arrowsize=" << flxratio + 1;
263 s <<
", color=" <<
"\"" << hue <<
", "
265 <<
", " << bright <<
"\"" << endl;
267 if (flxratio > label_min) {
268 s <<
", label=\" " << flxratio;
270 if (
flow(kbegin, kend) > 0.0) {
272 <<
flow(kbegin, kend)/flmax <<
"\\l";
273 path(kbegin, kend)->writeLabel(s);
275 if (
flow(kend, kbegin) > 0.0) {
277 <<
flow(kend,kbegin)/flmax <<
"\\l";
278 path(kend, kbegin)->writeLabel(s);
290 for (
size_t i = 0; i < nPaths(); i++) {
291 flmax = std::max(path(i)->
flow(), flmax);
297 for (
size_t i = 0; i < nPaths(); i++) {
299 double flxratio = p->flow()/flmax;
300 if (m_local !=
npos) {
301 if (p->begin()->number != m_local
302 && p->end()->number != m_local) {
306 if (flxratio > threshold) {
307 p->begin()->visible =
true;
308 p->end()->visible =
true;
309 s <<
"s" << p->begin()->number
310 <<
" -> s" << p->end()->number;
312 if (arrow_width < 0) {
313 double lwidth = 1.0 - 4.0 * log10(flxratio/threshold)/log10(threshold)
315 s <<
"[fontname=\""+m_font+
"\", penwidth="
318 << std::min(6.0, 0.5*lwidth);
322 s <<
", arrowsize=" << flxratio + 1;
326 s <<
", color=" <<
"\"" << hue <<
", " << flxratio + 0.5
327 <<
", " << bright <<
"\"" << endl;
329 if (flxratio > label_min) {
330 s <<
", label = \" " << flxratio;
342 for (
const auto& [kSpecies, node] :
m_nodes) {
344 s <<
"s" << kSpecies <<
" [ fontname=\""+m_font+
"\", label=\"" << node->
name
348 s <<
" label = " <<
"\"" <<
"Scale = "
349 << flmax <<
"\\l " << title <<
"\";" << endl;
350 s <<
" fontname = \""+m_font+
"\";" << endl <<
"}" << endl;
354void ReactionPathDiagram::addNode(
size_t k,
const string& nm,
double x)
361 m_speciesNumber.push_back(k);
365void ReactionPathDiagram::linkNodes(
size_t k1,
size_t k2,
size_t rxn,
366 double value,
string legend)
368 Path* ff = m_paths[k1][k2];
371 m_paths[k1][k2] = ff;
372 m_pathlist.push_back(ff);
374 ff->addReaction(rxn, value, legend);
376 m_flxmax = std::max(ff->flow(), m_flxmax);
379vector<size_t> ReactionPathDiagram::species()
381 return m_speciesNumber;
384int ReactionPathBuilder::findGroups(ostream& logfile, Kinetics& s)
386 m_groups.resize(m_nr);
387 for (
size_t i = 0; i < m_nr; i++) {
388 logfile << endl <<
"Reaction " << i+1 <<
": "
389 << s.reaction(i)->equation();
391 if (m_determinate[i]) {
392 logfile <<
" ... OK." << endl;
393 }
else if (m_reac[i].size() == 2 && m_prod[i].size() == 2) {
395 size_t kr0 = m_reac[i][0];
396 size_t kr1 = m_reac[i][1];
399 size_t kp0 = m_prod[i][0];
400 size_t kp1 = m_prod[i][1];
403 const Group& r0 = m_sgroup[kr0];
404 const Group& r1 = m_sgroup[kr1];
405 const Group& p0 = m_sgroup[kp0];
406 const Group& p1 = m_sgroup[kp1];
408 const Group* group_a0=0, *group_b0=0, *group_c0=0,
409 *group_a1=0, *group_b1=0, *group_c1=0;
412 if (b0.valid() && b1.valid()) {
413 logfile <<
" ... ambiguous." << endl;
414 }
else if (!b0.valid() && !b1.valid()) {
415 logfile <<
" ... cannot express as A + BC = AB + C" << endl;
425 m_transfer[i][0][0] = r0;
426 m_transfer[i][1][0] = b0;
427 m_transfer[i][1][1] = p1;
433 m_transfer[i][1][1] = r1;
434 m_transfer[i][0][1] = b0;
435 m_transfer[i][0][0] = p0;
438 group_a0->fmt(logfile, m_elementSymbols);
440 group_b0->fmt(logfile,m_elementSymbols);
441 group_c0->fmt(logfile, m_elementSymbols);
443 group_a0->fmt(logfile, m_elementSymbols);
444 group_b0->fmt(logfile, m_elementSymbols);
446 group_c0->fmt(logfile, m_elementSymbols);
448 logfile <<
" [<= default] " << endl;
460 m_transfer[i][0][1] = r0;
461 m_transfer[i][1][1] = b0;
462 m_transfer[i][1][0] = p0;
470 m_transfer[i][1][0] = r1;
471 m_transfer[i][0][0] = b0;
472 m_transfer[i][0][1] = p1;
476 group_a1->fmt(logfile, m_elementSymbols);
478 group_b1->fmt(logfile, m_elementSymbols);
479 group_c1->fmt(logfile, m_elementSymbols);
481 group_a1->fmt(logfile, m_elementSymbols);
482 group_b1->fmt(logfile, m_elementSymbols);
484 group_c1->fmt(logfile, m_elementSymbols);
488 logfile <<
"... cannot parse. [ignored]" << endl;
494void ReactionPathBuilder::findElements(Kinetics& kin)
498 for (
size_t i = 0; i < kin.nPhases(); i++) {
499 ThermoPhase* p = &kin.thermo(i);
501 for (
size_t m = 0; m < p->nElements(); m++) {
502 string ename = p->elementName(m);
508 if (m_enamemap.find(ename) == m_enamemap.end()) {
509 m_enamemap[ename] = m_nel + 1;
510 m_elementSymbols.push_back(ename);
515 m_atoms.resize(kin.nTotalSpecies(), m_nel, 0.0);
517 for (
size_t m = 0; m < m_nel; m++) {
520 for (
size_t ip = 0; ip < kin.nPhases(); ip++) {
521 ThermoPhase* p = &kin.thermo(ip);
522 size_t mlocal = p->elementIndex(m_elementSymbols[m]);
523 for (
size_t kp = 0; kp < p->nSpecies(); kp++) {
524 if (mlocal !=
npos) {
525 m_atoms(k, m) = p->nAtoms(kp, mlocal);
533int ReactionPathBuilder::init(ostream& logfile, Kinetics& kin)
536 m_elementSymbols.clear();
538 m_ns = kin.nTotalSpecies();
539 m_nr = kin.nReactions();
543 vector<vector<size_t>> allProducts(m_nr);
544 vector<vector<size_t>> allReactants(m_nr);
545 for (
size_t i = 0; i < m_nr; i++) {
546 for (
size_t k = 0; k < m_ns; k++) {
547 for (
int n = 0; n < kin.reactantStoichCoeff(k, i); n++) {
548 allReactants[i].push_back(k);
550 for (
int n = 0; n < kin.productStoichCoeff(k, i); n++) {
551 allProducts[i].push_back(k);
562 m_determinate.resize(m_nr);
564 m_elatoms.resize(m_nel, m_nr);
566 for (
size_t i = 0; i < m_nr; i++) {
571 map<size_t, int> net;
572 size_t nr = allReactants[i].size();
573 size_t np = allProducts[i].size();
574 for (
size_t ir = 0; ir < nr; ir++) {
575 net[allReactants[i][ir]]--;
577 for (
size_t ip = 0; ip < np; ip++) {
578 net[allProducts[i][ip]]++;
581 for (
size_t k = 0; k < m_ns; k++) {
583 size_t nmol = -net[k];
584 for (
size_t jr = 0; jr < nmol; jr++) {
585 m_reac[i].push_back(k);
587 }
else if (net[k] > 0) {
588 size_t nmol = net[k];
589 for (
size_t jp = 0; jp < nmol; jp++) {
590 m_prod[i].push_back(k);
595 size_t nrnet = m_reac[i].size();
600 for (
size_t n = 0; n < nrnet; n++) {
601 size_t k = m_reac[i][n];
602 for (
size_t m = 0; m < m_nel; m++) {
603 m_elatoms(m,i) += m_atoms(k,m);
609 vector<int> comp(m_nel);
610 m_sgroup.resize(m_ns);
611 for (
size_t j = 0; j < m_ns; j++) {
612 for (
size_t m = 0; m < m_nel; m++) {
613 comp[m] = int(m_atoms(j,m));
615 m_sgroup[j] = Group(comp);
624 for (
size_t i = 0; i < m_nr; i++) {
625 size_t nr = m_reac[i].size();
626 size_t np = m_prod[i].size();
627 m_determinate[i] =
true;
628 for (
size_t m = 0; m < m_nel; m++) {
631 for (
size_t j = 0; j < nr; j++) {
632 if (m_atoms(m_reac[i][j],m) > 0) {
636 for (
size_t j = 0; j < np; j++) {
637 if (m_atoms(m_prod[i][j],m) > 0) {
641 if (nar > 1 && nap > 1) {
642 m_determinate[i] =
false;
648 findGroups(logfile, kin);
652string reactionLabel(
size_t i,
size_t kr,
size_t nr,
653 const vector<size_t>& slist,
const Kinetics& s)
656 for (
size_t j = 0; j < nr; j++) {
658 label +=
" + "+ s.kineticsSpeciesName(slist[j]);
661 if (ba::starts_with(s.reaction(i)->type(),
"three-body")) {
663 }
else if (ba::starts_with(s.reaction(i)->type(),
"falloff")) {
669int ReactionPathBuilder::build(Kinetics& s,
const string& element,
670 ostream& output, ReactionPathDiagram& r,
bool quiet)
672 map<size_t, int>
warn;
673 double threshold = 0.0;
674 size_t m = m_enamemap[element]-1;
680 s.getFwdRatesOfProgress(m_ropf.data());
681 s.getRevRatesOfProgress(m_ropr.data());
684 vector<string>& in_nodes = r.included();
685 vector<string>& out_nodes = r.excluded();
687 vector<int> status(s.nTotalSpecies(), 0);
688 for (
size_t ni = 0; ni < in_nodes.size(); ni++) {
689 status[s.kineticsSpeciesIndex(in_nodes[ni])] = 1;
691 for (
size_t ne = 0; ne < out_nodes.size(); ne++) {
692 status[s.kineticsSpeciesIndex(out_nodes[ne])] = -1;
695 for (
size_t i = 0; i < m_nr; i++) {
696 double ropf = m_ropf[i];
697 double ropr = m_ropr[i];
700 if (m_elatoms(m, i) > 0) {
701 size_t nr = m_reac[i].size();
702 size_t np = m_prod[i].size();
704 for (
size_t kr = 0; kr < nr; kr++) {
705 size_t kkr = m_reac[i][kr];
706 string fwdlabel = reactionLabel(i, kr, nr, m_reac[i], s);
708 for (
size_t kp = 0; kp < np; kp++) {
709 size_t kkp = m_prod[i][kp];
710 string revlabel =
"";
711 for (
size_t j = 0; j < np; j++) {
713 revlabel +=
" + "+ s.kineticsSpeciesName(m_prod[i][j]);
716 if (ba::starts_with(s.reaction(i)->type(),
"three-body")) {
718 }
else if (ba::starts_with(s.reaction(i)->type(),
"falloff")) {
719 revlabel +=
" (+ M)";
725 if ((kkr != kkp) && (m_atoms(kkr,m) > 0
726 && m_atoms(kkp,m) > 0)
727 && status[kkr] >= 0 && status[kkp] >= 0) {
734 if ((m_atoms(kkp,m) < m_elatoms(m, i)) &&
735 (m_atoms(kkr,m) < m_elatoms(m, i))) {
736 map<size_t, map<size_t, Group>>& g = m_transfer[i];
738 if (!
warn[i] && !quiet) {
740 output <<
"*************** REACTION IGNORED ***************" << endl;
741 output <<
"Warning: no rule to determine partitioning of " << element
742 << endl <<
" in reaction " << s.reaction(i)->equation() <<
"." << endl
743 <<
"*************** REACTION IGNORED **************" << endl;
752 f = g[kr][kp].nAtoms(m);
761 f = m_atoms(kkp,m) * m_atoms(kkr,m) / m_elatoms(m, i);
766 bool force_incl = ((status[kkr] == 1) || (status[kkp] == 1));
768 bool fwd_incl = ((fwd > threshold) ||
769 (fwd > 0.0 && force_incl));
770 bool rev_incl = ((rev > threshold) ||
771 (rev > 0.0 && force_incl));
772 if (fwd_incl || rev_incl) {
773 if (!r.hasNode(kkr)) {
774 r.addNode(kkr, s.kineticsSpeciesName(kkr), m_x[kkr]);
776 if (!r.hasNode(kkp)) {
777 r.addNode(kkp, s.kineticsSpeciesName(kkp), m_x[kkp]);
781 r.linkNodes(kkr, kkp,
int(i), fwd, fwdlabel);
784 r.linkNodes(kkp, kkr, -
int(i), rev, revlabel);
Classes for reaction path analysis.
Header file for class ThermoPhase, the base class for phases with thermodynamic properties,...
Base class for exceptions thrown by Cantera classes.
map< size_t, SpeciesNode * > m_nodes
map of species index to SpeciesNode
void exportToDot(std::ostream &s)
Export the reaction path diagram.
set< size_t > m_rxns
Indices of reactions that are included in the diagram.
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.
Nodes in reaction path graphs.
string name
Label on graph.
bool visible
Visible on graph;.
void addPath(Path *path)
add a path to or from this node
void warn(const string &warning, const string &method, const string &msg, const Args &... args)
Print a generic warning raised from method.
Namespace for the Cantera kernel.
const size_t npos
index returned by functions to indicate "no position"