|
| 1 | +############# |
| 2 | +# Adapted from https://github.com/MakieOrg/GraphMakie.jl/issues/52#issuecomment-1018527479 |
| 3 | +############# |
| 4 | + |
| 5 | +""" |
| 6 | + SRGraphWrap{T} |
| 7 | +
|
| 8 | +Wrapper for the species-reaction graph containing edges for rate-dependence on species. Intended to allow plotting of multiple edges. |
| 9 | +""" |
| 10 | +struct SRGraphWrap{T} <: Graphs.AbstractGraph{T} |
| 11 | + g::SimpleDiGraph{T} |
| 12 | + rateedges::Vector{Graphs.SimpleEdge{T}} |
| 13 | + edgeorder::Vector{Int64} |
| 14 | +end |
| 15 | + |
| 16 | +# Create the SimpleDiGraph corresponding to the species and reactions |
| 17 | +function SRGraphWrap(rn::ReactionSystem) |
| 18 | + srg = species_reaction_graph(rn) |
| 19 | + rateedges = Vector{Graphs.SimpleEdge{Int}}() |
| 20 | + sm = speciesmap(rn); specs = species(rn) |
| 21 | + |
| 22 | + deps = Set() |
| 23 | + for (i, rx) in enumerate(reactions(rn)) |
| 24 | + empty!(deps) |
| 25 | + get_variables!(deps, rx.rate, specs) |
| 26 | + if !isempty(deps) |
| 27 | + for spec in deps |
| 28 | + specidx = sm[spec] |
| 29 | + push!(rateedges, Graphs.SimpleEdge(specidx, i + length(specs))) |
| 30 | + end |
| 31 | + end |
| 32 | + end |
| 33 | + edgelist = vcat(collect(Graphs.edges(srg)), rateedges) |
| 34 | + edgeorder = sortperm(edgelist) |
| 35 | + SRGraphWrap(srg, rateedges, edgeorder) |
| 36 | +end |
| 37 | + |
| 38 | +Base.eltype(g::SRGraphWrap) = eltype(g.g) |
| 39 | +Graphs.edgetype(g::SRGraphWrap) = edgetype(g.g) |
| 40 | +Graphs.has_edge(g::SRGraphWrap, s, d) = has_edge(g.g, s, d) |
| 41 | +Graphs.has_vertex(g::SRGraphWrap, i) = has_vertex(g.g, i) |
| 42 | +Graphs.inneighbors(g::SRGraphWrap{T}, i) where T = inneighbors(g.g, i) |
| 43 | +Graphs.outneighbors(g::SRGraphWrap{T}, i) where T = outneighbors(g.g, i) |
| 44 | +Graphs.ne(g::SRGraphWrap) = length(g.rateedges) + length(Graphs.edges(g.g)) |
| 45 | +Graphs.nv(g::SRGraphWrap) = nv(g.g) |
| 46 | +Graphs.vertices(g::SRGraphWrap) = vertices(g.g) |
| 47 | +Graphs.is_directed(g::SRGraphWrap) = is_directed(g.g) |
| 48 | + |
| 49 | +function Graphs.edges(g::SRGraphWrap) |
| 50 | + edgelist = vcat(collect(Graphs.edges(g.g)), g.rateedges)[g.edgeorder] |
| 51 | +end |
| 52 | + |
| 53 | +function gen_distances(g::SRGraphWrap; inc = 0.2) |
| 54 | + edgelist = edges(g) |
| 55 | + distances = zeros(length(edgelist)) |
| 56 | + for i in 2:Base.length(edgelist) |
| 57 | + edgelist[i] == edgelist[i-1] && (distances[i] = inc) |
| 58 | + end |
| 59 | + distances |
| 60 | +end |
| 61 | + |
| 62 | +""" |
| 63 | + plot_network(rn::ReactionSystem; interactive=false) |
| 64 | +
|
| 65 | +Converts a [`ReactionSystem`](@ref) into a GraphMakie plot of the species reaction graph |
| 66 | +(or Petri net representation). Reactions correspond to small green circles, and |
| 67 | +species to blue circles. |
| 68 | +
|
| 69 | +Notes: |
| 70 | +- Black arrows from species to reactions indicate reactants, and are labelled |
| 71 | + with their input stoichiometry. |
| 72 | +- Black arrows from reactions to species indicate products, and are labelled |
| 73 | + with their output stoichiometry. |
| 74 | +- Red arrows from species to reactions indicate that species is used within the |
| 75 | + rate expression. For example, in the reaction `k*A, B --> C`, there would be a |
| 76 | + red arrow from `A` to the reaction node. In `k*A, A+B --> C`, there would be |
| 77 | + red and black arrows from `A` to the reaction node. |
| 78 | +""" |
| 79 | +# TODO: update docs for interacting with plots. The `interactive` flag sets the ability to interactively drag nodes and edges in the generated plot. Only allowed if `GLMakie` is the loaded Makie backend. |
| 80 | +function Catalyst.plot_network(rn::ReactionSystem) |
| 81 | + srg = SRGraphWrap(rn) |
| 82 | + ns = length(species(rn)) |
| 83 | + nodecolors = vcat([:skyblue3 for i in 1:ns], |
| 84 | + [:green for i in ns+1:nv(srg)]) |
| 85 | + ilabels = vcat(map(s -> String(tosymbol(s, escape=false)), species(rn)), |
| 86 | + fill("", nv(srg.g) - ns)) |
| 87 | + nodesizes = vcat([30 for i in 1:ns], |
| 88 | + [10 for i in ns+1:nv(srg)]) |
| 89 | + |
| 90 | + ssm = substoichmat(rn); psm = prodstoichmat(rn) |
| 91 | + # Get stoichiometry of reaction |
| 92 | + edgelabels = map(Graphs.edges(srg.g)) do e |
| 93 | + string(src(e) > ns ? |
| 94 | + psm[dst(e), src(e)-ns] : |
| 95 | + ssm[src(e), dst(e)-ns]) |
| 96 | + end |
| 97 | + edgecolors = [:black for i in 1:ne(srg)] |
| 98 | + |
| 99 | + num_e = ne(srg.g) |
| 100 | + for i in 1:length(srg.edgeorder) |
| 101 | + if srg.edgeorder[i] > num_e |
| 102 | + edgecolors[i] = :red |
| 103 | + insert!(edgelabels, i, "") |
| 104 | + end |
| 105 | + end |
| 106 | + |
| 107 | + graphplot(srg; |
| 108 | + edge_color = edgecolors, |
| 109 | + elabels = edgelabels, |
| 110 | + elabels_rotation = 0, |
| 111 | + ilabels = ilabels, |
| 112 | + node_color = nodecolors, |
| 113 | + node_size = nodesizes, |
| 114 | + arrow_shift = :end, |
| 115 | + arrow_size = 20, |
| 116 | + curve_distance_usage = true, |
| 117 | + curve_distance = gen_distances(srg) |
| 118 | + ) |
| 119 | +end |
| 120 | + |
| 121 | +""" |
| 122 | + plot_complexes(rn::ReactionSystem; interactive=false) |
| 123 | +
|
| 124 | + Creates a GraphMakie plot of the [`ReactionComplex`](@ref)s in `rn`. Reactions |
| 125 | + correspond to arrows and reaction complexes to blue circles. |
| 126 | +
|
| 127 | + Notes: |
| 128 | + - Black arrows from complexes to complexes indicate reactions whose rate is a |
| 129 | + parameter or a `Number`. i.e. `k, A --> B`. |
| 130 | + - Red arrows from complexes to complexes indicate reactions whose rate |
| 131 | + depends on species. i.e. `k*C, A --> B` for `C` a species. |
| 132 | +""" |
| 133 | +function Catalyst.plot_complexes(rn::ReactionSystem) |
| 134 | + img = incidencematgraph(rn) |
| 135 | + specs = species(rn); rxs = reactions(rn) |
| 136 | + edgecolors = [:black for i in 1:ne(img)] |
| 137 | + nodelabels = complexlabels(rn) |
| 138 | + edgelabels = [repr(rx.rate) for rx in rxs] |
| 139 | + |
| 140 | + deps = Set() |
| 141 | + for (i, rx) in enumerate(rxs) |
| 142 | + empty!(deps) |
| 143 | + get_variables!(deps, rx.rate, specs) |
| 144 | + (!isempty(deps)) && (edgecolors[i] = :red) |
| 145 | + end |
| 146 | + |
| 147 | + graphplot(img; |
| 148 | + edge_color = edgecolors, |
| 149 | + elabels = edgelabels, |
| 150 | + ilabels = complexlabels(rn), |
| 151 | + node_color = :skyblue3, |
| 152 | + elabels_rotation = 0, |
| 153 | + arrow_shift = :end, |
| 154 | + curve_distance = 0.2 |
| 155 | + ) |
| 156 | +end |
| 157 | + |
| 158 | +function complexelem_tostr(e::Catalyst.ReactionComplexElement, specstrs) |
| 159 | + if e.speciesstoich == 1 |
| 160 | + return "$(specstrs[e.speciesid])" |
| 161 | + else |
| 162 | + return "$(e.speciesstoich)$(specstrs[e.speciesid])" |
| 163 | + end |
| 164 | +end |
| 165 | + |
| 166 | +# Get the strings corresponding to the reaction complexes |
| 167 | +function complexlabels(rn::ReactionSystem) |
| 168 | + labels = String[] |
| 169 | + |
| 170 | + specstrs = map(s -> String(tosymbol(s, escape=false)), species(rn)) |
| 171 | + complexes, B = reactioncomplexes(rn) |
| 172 | + |
| 173 | + for complex in complexes |
| 174 | + if isempty(complex) |
| 175 | + push!(labels, "∅") |
| 176 | + elseif length(complex) == 1 |
| 177 | + push!(labels, complexelem_tostr(complex[1], specstrs)) |
| 178 | + else |
| 179 | + elems = map(c -> complexelem_tostr(c, specstrs), complex) |
| 180 | + str = reduce((e1, e2) -> *(e1, " + ", e2), @view elems[2:end]; init = elems[1]) |
| 181 | + push!(labels, str) |
| 182 | + end |
| 183 | + end |
| 184 | + labels |
| 185 | +end |
0 commit comments