14#include <boost/algorithm/string.hpp>
16namespace ba = boost::algorithm;
25 m_paths.push_back(path);
26 if (path->
begin() ==
this) {
27 m_out += path->
flow();
28 }
else if (path->
end() ==
this) {
31 throw CanteraError(
"SpeciesNode::addPath",
"path added to wrong node");
35void SpeciesNode::printPaths()
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;
45 : m_a(begin), m_b(end)
53 m_rxn[rxnNumber] += value;
56 m_label[label] += value;
62 if (m_label.size() == 0) {
66 for (
const auto& [species, value] : m_label) {
68 if (m_label.size() == 1) {
69 s << species <<
"\\l";
70 }
else if (v > threshold) {
72 int percent = int(100*v + 0.5);
74 s <<
" (" << percent <<
"%)\\l";
82ReactionPathDiagram::ReactionPathDiagram(
83 shared_ptr<Kinetics> kin,
const string& element_) : element(element_), m_kin(kin)
86 throw CanteraError(
"ReactionPathDiagram::ReactionPathDiagram",
87 "Kinetics object must not be empty.");
89 m_builder = make_shared<ReactionPathBuilder>();
96 for (
const auto& [k, node] :
m_nodes) {
101 size_t nn = nPaths();
102 for (
size_t n = 0; n < nn; n++) {
103 delete m_pathlist[n];
107vector<int> ReactionPathDiagram::reactions()
110 for (
size_t i = 0; i < nPaths(); i++) {
112 flmax = std::max(p->
flow(), flmax);
115 for (
size_t i = 0; i < nPaths(); i++) {
116 for (
const auto& [iRxn, flux] : path(i)->reactionMap()) {
117 double flxratio = flux / flmax;
124 for (
const auto& rxn :
m_rxns) {
125 r.push_back(
int(rxn));
130void ReactionPathDiagram::add(ReactionPathDiagram& d)
132 for (
size_t n = 0; n < nPaths(); n++) {
134 size_t k1 = p->begin()->number;
135 size_t k2 = p->end()->number;
136 p->setFlow(p->flow() + d.flow(k1,k2));
140void ReactionPathDiagram::add(shared_ptr<ReactionPathDiagram> d)
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);
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) {
178 if (fType ==
"OneWayFlow") {
180 }
else if (fType ==
"NetFlow") {
184 "Unknown flow type '{}'", fType);
199 std::stringstream out;
209 std::stringstream out;
219void ReactionPathDiagram::writeData(ostream& s)
222 for (
size_t i1 = 0; i1 < nNodes(); i1++) {
223 size_t k1 = m_speciesNumber[i1];
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);
234 <<
" " << f1 <<
" " << -f2 << endl;
245 s <<
"digraph " <<
name <<
" {" << endl;
267 for (
size_t i1 = 0; i1 < nNodes(); i1++) {
268 size_t k1 = m_speciesNumber[i1];
270 for (
size_t i2 = i1+1; i2 < nNodes(); i2++) {
271 size_t k2 = m_speciesNumber[i2];
276 flmax = std::max(flx, flmax);
282 flmax = std::max(flmax, 1e-10);
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];
290 if (m_local !=
npos && k1 != m_local && k2 != m_local) {
301 flxratio = flx/flmax;
305 flxratio = -flx/flmax;
315 s <<
"s" << kbegin <<
" -> s" << kend;
316 s <<
"[fontname=\""+
m_font+
"\", penwidth=";
319 double lwidth = 1.0 - 4.0
323 << std::min(6.0, 0.5*lwidth);
326 s <<
", arrowsize=" << flxratio + 1;
331 s <<
", color=" <<
"\"" << hue <<
", "
333 <<
", " << bright <<
"\"" << endl;
336 s <<
", label=\" " << flxratio;
338 if (
flow(kbegin, kend) > 0.0) {
340 <<
flow(kbegin, kend)/flmax <<
"\\l";
343 if (
flow(kend, kbegin) > 0.0) {
345 <<
flow(kend,kbegin)/flmax <<
"\\l";
358 for (
size_t i = 0; i < nPaths(); i++) {
359 flmax = std::max(path(i)->
flow(), flmax);
365 for (
size_t i = 0; i < nPaths(); i++) {
367 double flxratio = p->
flow()/flmax;
368 if (m_local !=
npos) {
383 s <<
"[fontname=\""+
m_font+
"\", penwidth="
386 << std::min(6.0, 0.5*lwidth);
390 s <<
", arrowsize=" << flxratio + 1;
394 s <<
", color=" <<
"\"" << hue <<
", " << flxratio + 0.5
395 <<
", " << bright <<
"\"" << endl;
398 s <<
", label = \" " << flxratio;
410 for (
const auto& [kSpecies, node] :
m_nodes) {
412 s <<
"s" << kSpecies <<
" [ fontname=\""+
m_font+
"\", label=\"" << node->
name
416 s <<
" label = " <<
"\"" <<
"Scale = "
417 << flmax <<
"\\l " <<
title <<
"\";" << endl;
418 s <<
" fontname = \""+
m_font+
"\";" << endl <<
"}" << endl;
422void ReactionPathDiagram::addNode(
size_t k,
const string& nm,
double x)
429 m_speciesNumber.push_back(k);
433void ReactionPathDiagram::linkNodes(
size_t k1,
size_t k2,
size_t rxn,
434 double value,
string legend)
436 Path* ff = m_paths[k1][k2];
439 m_paths[k1][k2] = ff;
440 m_pathlist.push_back(ff);
442 ff->addReaction(rxn, value, legend);
444 m_flxmax = std::max(ff->flow(), m_flxmax);
447vector<size_t> ReactionPathDiagram::species()
449 return m_speciesNumber;
454 m_groups.resize(m_nr);
455 for (
size_t i = 0; i < m_nr; i++) {
456 logfile << endl <<
"Reaction " << i+1 <<
": "
459 if (m_determinate[i]) {
460 logfile <<
" ... OK." << endl;
461 }
else if (m_reac[i].size() == 2 && m_prod[i].size() == 2) {
463 size_t kr0 = m_reac[i][0];
464 size_t kr1 = m_reac[i][1];
467 size_t kp0 = m_prod[i][0];
468 size_t kp1 = m_prod[i][1];
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];
476 const Group* group_a0=0, *group_b0=0, *group_c0=0,
477 *group_a1=0, *group_b1=0, *group_c1=0;
481 logfile <<
" ... ambiguous." << endl;
483 logfile <<
" ... cannot express as A + BC = AB + C" << endl;
506 group_a0->fmt(logfile, m_elementSymbols);
508 group_b0->fmt(logfile,m_elementSymbols);
509 group_c0->fmt(logfile, m_elementSymbols);
511 group_a0->fmt(logfile, m_elementSymbols);
512 group_b0->fmt(logfile, m_elementSymbols);
514 group_c0->fmt(logfile, m_elementSymbols);
516 logfile <<
" [<= default] " << endl;
544 group_a1->fmt(logfile, m_elementSymbols);
546 group_b1->fmt(logfile, m_elementSymbols);
547 group_c1->fmt(logfile, m_elementSymbols);
549 group_a1->fmt(logfile, m_elementSymbols);
550 group_b1->fmt(logfile, m_elementSymbols);
552 group_c1->fmt(logfile, m_elementSymbols);
556 logfile <<
"... cannot parse. [ignored]" << endl;
562void ReactionPathBuilder::findElements(
Kinetics& kin)
566 for (
size_t i = 0; i < kin.
nPhases(); i++) {
569 for (
size_t m = 0; m < p->
nElements(); m++) {
576 if (m_enamemap.find(ename) == m_enamemap.end()) {
577 m_enamemap[ename] = m_nel + 1;
578 m_elementSymbols.push_back(ename);
585 for (
size_t m = 0; m < m_nel; m++) {
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);
601int ReactionPathBuilder::init(ostream& logfile, Kinetics& kin)
604 m_elementSymbols.clear();
606 m_ns = kin.nTotalSpecies();
607 m_nr = kin.nReactions();
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);
618 for (
int n = 0; n < kin.productStoichCoeff(k, i); n++) {
619 allProducts[i].push_back(k);
630 m_determinate.resize(m_nr);
632 m_elatoms.
resize(m_nel, m_nr);
634 for (
size_t i = 0; i < m_nr; i++) {
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]]--;
645 for (
size_t ip = 0; ip < np; ip++) {
646 net[allProducts[i][ip]]++;
649 for (
size_t k = 0; k < m_ns; k++) {
651 size_t nmol = -net[k];
652 for (
size_t jr = 0; jr < nmol; jr++) {
653 m_reac[i].push_back(k);
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);
663 size_t nrnet = m_reac[i].size();
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);
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));
683 m_sgroup[j] = Group(comp);
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++) {
699 for (
size_t j = 0; j < nr; j++) {
700 if (m_atoms(m_reac[i][j],m) > 0) {
704 for (
size_t j = 0; j < np; j++) {
705 if (m_atoms(m_prod[i][j],m) > 0) {
709 if (nar > 1 && nap > 1) {
710 m_determinate[i] =
false;
720string reactionLabel(
size_t i,
size_t kr,
size_t nr,
721 const vector<size_t>& slist,
const Kinetics& s)
724 for (
size_t j = 0; j < nr; j++) {
726 label +=
" + "+ s.kineticsSpeciesName(slist[j]);
729 if (ba::starts_with(s.reaction(i)->type(),
"three-body")) {
731 }
else if (ba::starts_with(s.reaction(i)->type(),
"falloff")) {
737int ReactionPathBuilder::build(Kinetics& s,
const string& element,
738 ostream& output, ReactionPathDiagram& r,
bool quiet)
740 map<size_t, int>
warn;
741 double threshold = 0.0;
742 size_t m = m_enamemap[element]-1;
748 s.getFwdRatesOfProgress(m_ropf);
749 s.getRevRatesOfProgress(m_ropr);
752 auto in_nodes = r.included();
753 auto out_nodes = r.excluded();
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;
759 for (
size_t ne = 0; ne < out_nodes.size(); ne++) {
760 status[s.kineticsSpeciesIndex(out_nodes[ne])] = -1;
763 for (
size_t i = 0; i < m_nr; i++) {
764 double ropf = m_ropf[i];
765 double ropr = m_ropr[i];
768 if (m_elatoms(m, i) > 0) {
769 size_t nr = m_reac[i].size();
770 size_t np = m_prod[i].size();
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);
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++) {
781 revlabel +=
" + "+ s.kineticsSpeciesName(m_prod[i][j]);
784 if (ba::starts_with(s.reaction(i)->type(),
"three-body")) {
786 }
else if (ba::starts_with(s.reaction(i)->type(),
"falloff")) {
787 revlabel +=
" (+ M)";
793 if ((kkr != kkp) && (m_atoms(kkr,m) > 0
794 && m_atoms(kkp,m) > 0)
795 && status[kkr] >= 0 && status[kkp] >= 0) {
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];
806 if (!
warn[i] && !quiet) {
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;
820 f = g[kr][kp].nAtoms(m);
829 f = m_atoms(kkp,m) * m_atoms(kkr,m) / m_elatoms(m, i);
834 bool force_incl = ((status[kkr] == 1) || (status[kkp] == 1));
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]);
844 if (!r.hasNode(kkp)) {
845 r.addNode(kkp, s.kineticsSpeciesName(kkp), m_x[kkp]);
849 r.linkNodes(kkr, kkp,
int(i), fwd, fwdlabel);
852 r.linkNodes(kkp, kkr, -
int(i), rev, revlabel);
863 shared_ptr<Kinetics> kin,
const string& element)
865 return shared_ptr<ReactionPathDiagram>(
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'.
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.
bool valid() const
True if all non-zero atom numbers have the same sign.
Public interface for kinetics managers.
ThermoPhase & thermo(size_t n=0)
This method returns a reference to the nth ThermoPhase object defined in this kinetics mechanism.
shared_ptr< Reaction > reaction(size_t i)
Return the Reaction object for reaction i.
size_t nPhases() const
The number of phases participating in the reaction mechanism.
size_t nTotalSpecies() const
The total number of species in all phases participating in the kinetics mechanism.
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.
size_t elementIndex(const string &name, bool raise=true) const
Return the index of element named 'name'.
string elementName(size_t m) const
Name of the element with index m.
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.
Namespace for the Cantera kernel.
const size_t npos
index returned by functions to indicate "no position"
shared_ptr< ReactionPathDiagram > newReactionPathDiagram(shared_ptr< Kinetics > kin, const string &element)
Create a new reaction path diagram.