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);
591 for (
size_t kp = 0; kp < p->nSpecies(); kp++) {
592 if (mlocal !=
npos) {
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.data());
749 s.getRevRatesOfProgress(m_ropr.data());
752 vector<string>& in_nodes = r.included();
753 vector<string>& 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 elementIndex(const string &name) const
Return the index of element named 'name'.
size_t nElements() const
Number of elements.
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.
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.
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.