From 69f69aeb22a732eee8650f03fbe2f401229dc85f Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 14 Nov 2024 14:23:18 -0500 Subject: [PATCH 01/20] graph init --- ext/CatalystGraphMakieExtension.jl | 8 +- .../rn_graph_plot.jl | 185 ++++++++++++++++++ src/network_analysis.jl | 35 ++++ 3 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 ext/CatalystGraphMakieExtension/rn_graph_plot.jl diff --git a/ext/CatalystGraphMakieExtension.jl b/ext/CatalystGraphMakieExtension.jl index ae409b645e..378eb96f3a 100644 --- a/ext/CatalystGraphMakieExtension.jl +++ b/ext/CatalystGraphMakieExtension.jl @@ -1,11 +1,13 @@ module CatalystGraphMakieExtension # Fetch packages. -using Catalyst, GraphMakie +using Catalyst, GraphMakie, Graphs import Catalyst: lattice_plot, lattice_animation, extract_vals -import Graphs: AbstractGraph, SimpleGraph +import Graphs: AbstractGraph, SimpleGraph, SimpleDiGraph, SimpleEdge, src, dst, ne, nv +import Catalyst: speciesreactiongraph, incidencematgraph # Creates and exports hc_steady_states function. include("CatalystGraphMakieExtension/graph_makie_extension_spatial_modelling.jl") - +include("CatalystGraphMakieExtension/rn_graph_plot.jl") +export SRGraph, ComplexGraph end diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl new file mode 100644 index 0000000000..c9b2864b3c --- /dev/null +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -0,0 +1,185 @@ +############# +# Adapted from https://github.com/MakieOrg/GraphMakie.jl/issues/52#issuecomment-1018527479 +############# + +""" + SRGraphWrap{T} + +Wrapper for the species-reaction graph containing edges for rate-dependence on species. Intended to allow plotting of multiple edges. +""" +struct SRGraphWrap{T} <: AbstractGraph{T} + g::SimpleDiGraph{T} + rateedges::Vector{SimpleEdge} +end + +Base.eltype(g::SRGraphWrap) = eltype(g.g) +Graphs.edgetype(g::SRGraphWrap) = edgetype(g.g) +Graphs.has_edge(g::SRGraphWrap, s, d) = has_edge(g.g, s, d) +Graphs.has_vertex(g::SRGraphWrap, i) = has_vertex(g.g, i) +Graphs.inneighbors(g::SRGraphWrap{T}, i) where T = inneighbors(g.g, i) +Graphs.outneighbors(g::SRGraphWrap{T}, i) where T = outneighbors(g.g, i) +Graphs.ne(g::SRGraphWrap) = length(g.rateedges) + length(Graphs.edges(g.g)) +Graphs.nv(g::SRGraphWrap) = nv(g.g) +Graphs.vertices(g::SRGraphWrap) = vertices(g.g) +Graphs.is_directed(g::SRGraphWrap) = is_directed(g.g) + +function Graphs.edges(g::SRGraphWrap) + return vcat(collect(Graphs.edges(g.g)), g.rateedges) +end + +""" + PetriNet(rn::ReactionSystem) + + See the documentation for [`SRGraph`](@ref). +""" +function PetriNet(rn::ReactionSystem) + SRGraph(rn) +end + +""" + SRGraph(rn::ReactionSystem) + +Converts a [`ReactionSystem`](@ref) into a GraphMakie plot of the species reaction graph. +Reactions correspond to small green circles, and species to blue circles. + +Notes: +- Black arrows from species to reactions indicate reactants, and are labelled + with their input stoichiometry. +- Black arrows from reactions to species indicate products, and are labelled + with their output stoichiometry. +- Red arrows from species to reactions indicate that species is used within the + rate expression. For example, in the reaction `k*A, B --> C`, there would be a + red arrow from `A` to the reaction node. In `k*A, A+B --> C`, there would be + red and black arrows from `A` to the reaction node. +""" +function SRGraph(rn::ReactionSystem; interactive = true) + srg = SRGraphWrap(rn) + ns = length(species(rn)) + nodecolors = vcat([:skyblue3 for i in 1:ns], + [:green for i in ns+1:nv(srg)]) + ilabels = vcat(map(s -> String(tosymbol(s, escape=false)), species(rn)), + fill("", nv(srg.g) - ns)) + nodesizes = vcat([30 for i in 1:ns], + [10 for i in ns+1:nv(srg)]) + + ssm = substoichmat(rn); psm = prodstoichmat(rn) + # Get stoichiometry of reaction + edgelabels = map(Graphs.edges(srg.g)) do e + string(src(e) > ns ? + psm[dst(e), src(e)-ns] : + ssm[src(e), dst(e)-ns]) + end + edgelabels = vcat(edgelabels, fill("", ne(srg) - ne(srg.g))) + edgecolors = vcat([:black for i in 1:ne(srg.g)], + [:red for i in ne(srg.g)+1:ne(srg)]) + + f, ax, p = graphplot(srg; + edge_color = edgecolors, + elabels = edgelabels, + elabels_rotation = 0, + ilabels = ilabels, + node_color = nodecolors, + node_size = nodesizes, + arrow_shift = :end, + arrow_size = 20, + curve_distance = 0.1 + ) + + interactive && begin + deregister_interaction!(ax, :rectanglezoom) + register_interaction!(ax, :ndrag, NodeDrag(p)) + register_interaction!(ax, :edrag, EdgeDrag(p)) + end + display(f) +end + +# Create the SimpleDiGraph corresponding to the species and reactions +function SRGraphWrap(rn::ReactionSystem) + srg = speciesreactiongraph(rn) + rateedges = Vector{SimpleEdge}() + sm = speciesmap(rn); specs = species(rn) + + for (i, rx) in enumerate(reactions(rn)) + deps = get_variables(rx.rate, specs) + if deps != Any[] + for spec in deps + specidx = sm[spec] + push!(rateedges, SimpleEdge(specidx, i + length(specs))) + end + end + end + SRGraphWrap(srg, rateedges) +end + +""" + ComplexGraph(rn::ReactionSystem) + + Creates a GraphMakie plot of the [`ReactionComplex`](@ref)s in `rn`. Reactions + correspond to arrows and reaction complexes to blue circles. + + Notes: + - Black arrows from complexes to complexes indicate reactions whose rate is a + parameter or a `Number`. i.e. `k, A --> B`. + - Red dashed arrows from complexes to complexes indicate reactions whose rate + depends on species. i.e. `k*C, A --> B` for `C` a species. +""" +function ComplexGraph(rn::ReactionSystem; interactive = true) + img = incidencematgraph(rn) + specs = species(rn); rxs = reactions(rn) + edgecolors = [:black for i in 1:ne(img)] + nodelabels = complexlabels(rn) + edgelabels = [repr(rx.rate) for rx in rxs] + + for (i, rx) in enumerate(rxs) + deps = get_variables(rx.rate, specs) + if deps != Any[] + edgecolors[i] = :red + end + end + + f, ax, p = graphplot(img; + edge_color = edgecolors, + elabels = edgelabels, + ilabels = complexlabels(rn), + node_color = :skyblue3, + elabels_rotation = 0, + arrow_shift = :end, + curve_distance = 0.2 + ) + + interactive && begin + deregister_interaction!(ax, :rectanglezoom) + register_interaction!(ax, :ndrag, NodeDrag(p)) + register_interaction!(ax, :edrag, EdgeDrag(p)) + end + display(f) +end + +function complexelemtostr(e::Catalyst.ReactionComplexElement, specstrs) + if e.speciesstoich == 1 + return "$(specstrs[e.speciesid])" + else + return "$(e.speciesstoich)$(specstrs[e.speciesid])" + end +end + +# Get the strings corresponding to the reaction complexes +function complexlabels(rn::ReactionSystem) + labels = String[] + + specstrs = map(s -> String(tosymbol(s, escape=false)), species(rn)) + complexes, B = reactioncomplexes(rn) + + for complex in complexes + if isempty(complex) + push!(labels, "∅") + elseif length(complex) == 1 + push!(labels, complexelemtostr(complex[1], specstrs)) + else + elems = map(c -> complexelemtostr(c, specstrs), complex) + str = reduce((e1, e2) -> *(e1, " + ", e2), elems[2:end]; init = elems[1]) + push!(labels, str) + end + end + labels +end diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 93bebe1888..074cd5f6d3 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -314,6 +314,41 @@ function incidencematgraph(incidencemat::SparseMatrixCSC{Int, Int}) return graph end + +""" + speciesreactiongraph(rn::ReactionSystem) + +Construct a directed simple graph where there are two types of nodes: species and reactions. +An edge from a species to reaction indicates that it is a reactant, and an edge from a reaction +to a species indicates that it is a product. By default, the species vertices are listed +first, so the first n indices correspond to species nodes. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +speciesreactiongraph(sir) +""" +function speciesreactiongraph(rn::ReactionSystem) + specs = species(rn) + rxs = reactions(rn) + sm = speciesmap(rn) + s = length(specs); r = length(rxs); nv = s + r + + adjmat = zeros(Int64, nv, nv) + for (i, rx) in enumerate(rxs) + for (spec, stoich) in zip(rx.substrates, rx.substoich) + adjmat[sm[spec], s+i] = stoich + end + for (spec, stoich) in zip(rx.products, rx.prodstoich) + adjmat[s+i, sm[spec]] = stoich + end + end + srg = SimpleDiGraph(adjmat) +end + ### Linkage, Deficiency, Reversibility ### """ From 79a20d4f281522256395c63f806a47d7586cdd62 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 14 Nov 2024 15:48:28 -0500 Subject: [PATCH 02/20] up --- .../rn_graph_plot.jl | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl index c9b2864b3c..93de84cb31 100644 --- a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -24,7 +24,18 @@ Graphs.vertices(g::SRGraphWrap) = vertices(g.g) Graphs.is_directed(g::SRGraphWrap) = is_directed(g.g) function Graphs.edges(g::SRGraphWrap) - return vcat(collect(Graphs.edges(g.g)), g.rateedges) + edgelist = vcat(collect(Graphs.edges(g.g)), g.rateedges) + edgeorder = sortperm(edgelist) + edgelist = edgelist[edgeorder] +end + +function gen_distances(g::SRGraphWrap; inc = 0.2) + edgelist = edges(g) + distances = zeros(length(edgelist)) + for i in 2:Base.length(edgelist) + edgelist[i] == edgelist[i-1] && (distances[i] = inc) + end + distances end """ @@ -37,7 +48,7 @@ function PetriNet(rn::ReactionSystem) end """ - SRGraph(rn::ReactionSystem) + SRGraph(rn::ReactionSystem; interactive=false) Converts a [`ReactionSystem`](@ref) into a GraphMakie plot of the species reaction graph. Reactions correspond to small green circles, and species to blue circles. @@ -51,8 +62,10 @@ Notes: rate expression. For example, in the reaction `k*A, B --> C`, there would be a red arrow from `A` to the reaction node. In `k*A, A+B --> C`, there would be red and black arrows from `A` to the reaction node. +- 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. """ -function SRGraph(rn::ReactionSystem; interactive = true) +function SRGraph(rn::ReactionSystem; interactive = false) srg = SRGraphWrap(rn) ns = length(species(rn)) nodecolors = vcat([:skyblue3 for i in 1:ns], @@ -69,9 +82,15 @@ function SRGraph(rn::ReactionSystem; interactive = true) psm[dst(e), src(e)-ns] : ssm[src(e), dst(e)-ns]) end - edgelabels = vcat(edgelabels, fill("", ne(srg) - ne(srg.g))) - edgecolors = vcat([:black for i in 1:ne(srg.g)], - [:red for i in ne(srg.g)+1:ne(srg)]) + edgecolors = [:black for i in 1:ne(srg)] + + elist = Graphs.edges(srg) + for i in 2:length(elist) + elist[i] == elist[i-1] && begin + edgecolors[i] = :red + insert!(edgelabels, i, "") + end + end f, ax, p = graphplot(srg; edge_color = edgecolors, @@ -82,7 +101,8 @@ function SRGraph(rn::ReactionSystem; interactive = true) node_size = nodesizes, arrow_shift = :end, arrow_size = 20, - curve_distance = 0.1 + curve_distance_usage = true, + curve_distance = gen_distances(srg) ) interactive && begin @@ -91,6 +111,7 @@ function SRGraph(rn::ReactionSystem; interactive = true) register_interaction!(ax, :edrag, EdgeDrag(p)) end display(f) + f end # Create the SimpleDiGraph corresponding to the species and reactions @@ -112,7 +133,7 @@ function SRGraphWrap(rn::ReactionSystem) end """ - ComplexGraph(rn::ReactionSystem) + ComplexGraph(rn::ReactionSystem; interactive=false) Creates a GraphMakie plot of the [`ReactionComplex`](@ref)s in `rn`. Reactions correspond to arrows and reaction complexes to blue circles. @@ -120,10 +141,12 @@ end Notes: - Black arrows from complexes to complexes indicate reactions whose rate is a parameter or a `Number`. i.e. `k, A --> B`. - - Red dashed arrows from complexes to complexes indicate reactions whose rate + - Red arrows from complexes to complexes indicate reactions whose rate depends on species. i.e. `k*C, A --> B` for `C` a species. + - 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. """ -function ComplexGraph(rn::ReactionSystem; interactive = true) +function ComplexGraph(rn::ReactionSystem; interactive = false) img = incidencematgraph(rn) specs = species(rn); rxs = reactions(rn) edgecolors = [:black for i in 1:ne(img)] @@ -153,6 +176,7 @@ function ComplexGraph(rn::ReactionSystem; interactive = true) register_interaction!(ax, :edrag, EdgeDrag(p)) end display(f) + f end function complexelemtostr(e::Catalyst.ReactionComplexElement, specstrs) From 95cdf8f89695fd5597ad50776605da09ad973d27 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 14 Nov 2024 14:23:18 -0500 Subject: [PATCH 03/20] graph init --- ext/CatalystGraphMakieExtension.jl | 8 +- .../rn_graph_plot.jl | 185 ++++++++++++++++++ src/network_analysis.jl | 35 ++++ 3 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 ext/CatalystGraphMakieExtension/rn_graph_plot.jl diff --git a/ext/CatalystGraphMakieExtension.jl b/ext/CatalystGraphMakieExtension.jl index ae409b645e..378eb96f3a 100644 --- a/ext/CatalystGraphMakieExtension.jl +++ b/ext/CatalystGraphMakieExtension.jl @@ -1,11 +1,13 @@ module CatalystGraphMakieExtension # Fetch packages. -using Catalyst, GraphMakie +using Catalyst, GraphMakie, Graphs import Catalyst: lattice_plot, lattice_animation, extract_vals -import Graphs: AbstractGraph, SimpleGraph +import Graphs: AbstractGraph, SimpleGraph, SimpleDiGraph, SimpleEdge, src, dst, ne, nv +import Catalyst: speciesreactiongraph, incidencematgraph # Creates and exports hc_steady_states function. include("CatalystGraphMakieExtension/graph_makie_extension_spatial_modelling.jl") - +include("CatalystGraphMakieExtension/rn_graph_plot.jl") +export SRGraph, ComplexGraph end diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl new file mode 100644 index 0000000000..c9b2864b3c --- /dev/null +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -0,0 +1,185 @@ +############# +# Adapted from https://github.com/MakieOrg/GraphMakie.jl/issues/52#issuecomment-1018527479 +############# + +""" + SRGraphWrap{T} + +Wrapper for the species-reaction graph containing edges for rate-dependence on species. Intended to allow plotting of multiple edges. +""" +struct SRGraphWrap{T} <: AbstractGraph{T} + g::SimpleDiGraph{T} + rateedges::Vector{SimpleEdge} +end + +Base.eltype(g::SRGraphWrap) = eltype(g.g) +Graphs.edgetype(g::SRGraphWrap) = edgetype(g.g) +Graphs.has_edge(g::SRGraphWrap, s, d) = has_edge(g.g, s, d) +Graphs.has_vertex(g::SRGraphWrap, i) = has_vertex(g.g, i) +Graphs.inneighbors(g::SRGraphWrap{T}, i) where T = inneighbors(g.g, i) +Graphs.outneighbors(g::SRGraphWrap{T}, i) where T = outneighbors(g.g, i) +Graphs.ne(g::SRGraphWrap) = length(g.rateedges) + length(Graphs.edges(g.g)) +Graphs.nv(g::SRGraphWrap) = nv(g.g) +Graphs.vertices(g::SRGraphWrap) = vertices(g.g) +Graphs.is_directed(g::SRGraphWrap) = is_directed(g.g) + +function Graphs.edges(g::SRGraphWrap) + return vcat(collect(Graphs.edges(g.g)), g.rateedges) +end + +""" + PetriNet(rn::ReactionSystem) + + See the documentation for [`SRGraph`](@ref). +""" +function PetriNet(rn::ReactionSystem) + SRGraph(rn) +end + +""" + SRGraph(rn::ReactionSystem) + +Converts a [`ReactionSystem`](@ref) into a GraphMakie plot of the species reaction graph. +Reactions correspond to small green circles, and species to blue circles. + +Notes: +- Black arrows from species to reactions indicate reactants, and are labelled + with their input stoichiometry. +- Black arrows from reactions to species indicate products, and are labelled + with their output stoichiometry. +- Red arrows from species to reactions indicate that species is used within the + rate expression. For example, in the reaction `k*A, B --> C`, there would be a + red arrow from `A` to the reaction node. In `k*A, A+B --> C`, there would be + red and black arrows from `A` to the reaction node. +""" +function SRGraph(rn::ReactionSystem; interactive = true) + srg = SRGraphWrap(rn) + ns = length(species(rn)) + nodecolors = vcat([:skyblue3 for i in 1:ns], + [:green for i in ns+1:nv(srg)]) + ilabels = vcat(map(s -> String(tosymbol(s, escape=false)), species(rn)), + fill("", nv(srg.g) - ns)) + nodesizes = vcat([30 for i in 1:ns], + [10 for i in ns+1:nv(srg)]) + + ssm = substoichmat(rn); psm = prodstoichmat(rn) + # Get stoichiometry of reaction + edgelabels = map(Graphs.edges(srg.g)) do e + string(src(e) > ns ? + psm[dst(e), src(e)-ns] : + ssm[src(e), dst(e)-ns]) + end + edgelabels = vcat(edgelabels, fill("", ne(srg) - ne(srg.g))) + edgecolors = vcat([:black for i in 1:ne(srg.g)], + [:red for i in ne(srg.g)+1:ne(srg)]) + + f, ax, p = graphplot(srg; + edge_color = edgecolors, + elabels = edgelabels, + elabels_rotation = 0, + ilabels = ilabels, + node_color = nodecolors, + node_size = nodesizes, + arrow_shift = :end, + arrow_size = 20, + curve_distance = 0.1 + ) + + interactive && begin + deregister_interaction!(ax, :rectanglezoom) + register_interaction!(ax, :ndrag, NodeDrag(p)) + register_interaction!(ax, :edrag, EdgeDrag(p)) + end + display(f) +end + +# Create the SimpleDiGraph corresponding to the species and reactions +function SRGraphWrap(rn::ReactionSystem) + srg = speciesreactiongraph(rn) + rateedges = Vector{SimpleEdge}() + sm = speciesmap(rn); specs = species(rn) + + for (i, rx) in enumerate(reactions(rn)) + deps = get_variables(rx.rate, specs) + if deps != Any[] + for spec in deps + specidx = sm[spec] + push!(rateedges, SimpleEdge(specidx, i + length(specs))) + end + end + end + SRGraphWrap(srg, rateedges) +end + +""" + ComplexGraph(rn::ReactionSystem) + + Creates a GraphMakie plot of the [`ReactionComplex`](@ref)s in `rn`. Reactions + correspond to arrows and reaction complexes to blue circles. + + Notes: + - Black arrows from complexes to complexes indicate reactions whose rate is a + parameter or a `Number`. i.e. `k, A --> B`. + - Red dashed arrows from complexes to complexes indicate reactions whose rate + depends on species. i.e. `k*C, A --> B` for `C` a species. +""" +function ComplexGraph(rn::ReactionSystem; interactive = true) + img = incidencematgraph(rn) + specs = species(rn); rxs = reactions(rn) + edgecolors = [:black for i in 1:ne(img)] + nodelabels = complexlabels(rn) + edgelabels = [repr(rx.rate) for rx in rxs] + + for (i, rx) in enumerate(rxs) + deps = get_variables(rx.rate, specs) + if deps != Any[] + edgecolors[i] = :red + end + end + + f, ax, p = graphplot(img; + edge_color = edgecolors, + elabels = edgelabels, + ilabels = complexlabels(rn), + node_color = :skyblue3, + elabels_rotation = 0, + arrow_shift = :end, + curve_distance = 0.2 + ) + + interactive && begin + deregister_interaction!(ax, :rectanglezoom) + register_interaction!(ax, :ndrag, NodeDrag(p)) + register_interaction!(ax, :edrag, EdgeDrag(p)) + end + display(f) +end + +function complexelemtostr(e::Catalyst.ReactionComplexElement, specstrs) + if e.speciesstoich == 1 + return "$(specstrs[e.speciesid])" + else + return "$(e.speciesstoich)$(specstrs[e.speciesid])" + end +end + +# Get the strings corresponding to the reaction complexes +function complexlabels(rn::ReactionSystem) + labels = String[] + + specstrs = map(s -> String(tosymbol(s, escape=false)), species(rn)) + complexes, B = reactioncomplexes(rn) + + for complex in complexes + if isempty(complex) + push!(labels, "∅") + elseif length(complex) == 1 + push!(labels, complexelemtostr(complex[1], specstrs)) + else + elems = map(c -> complexelemtostr(c, specstrs), complex) + str = reduce((e1, e2) -> *(e1, " + ", e2), elems[2:end]; init = elems[1]) + push!(labels, str) + end + end + labels +end diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 93bebe1888..074cd5f6d3 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -314,6 +314,41 @@ function incidencematgraph(incidencemat::SparseMatrixCSC{Int, Int}) return graph end + +""" + speciesreactiongraph(rn::ReactionSystem) + +Construct a directed simple graph where there are two types of nodes: species and reactions. +An edge from a species to reaction indicates that it is a reactant, and an edge from a reaction +to a species indicates that it is a product. By default, the species vertices are listed +first, so the first n indices correspond to species nodes. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +speciesreactiongraph(sir) +""" +function speciesreactiongraph(rn::ReactionSystem) + specs = species(rn) + rxs = reactions(rn) + sm = speciesmap(rn) + s = length(specs); r = length(rxs); nv = s + r + + adjmat = zeros(Int64, nv, nv) + for (i, rx) in enumerate(rxs) + for (spec, stoich) in zip(rx.substrates, rx.substoich) + adjmat[sm[spec], s+i] = stoich + end + for (spec, stoich) in zip(rx.products, rx.prodstoich) + adjmat[s+i, sm[spec]] = stoich + end + end + srg = SimpleDiGraph(adjmat) +end + ### Linkage, Deficiency, Reversibility ### """ From b9e6a27847b56671861e1afb72abd9ae970852f6 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 14 Nov 2024 15:48:28 -0500 Subject: [PATCH 04/20] up --- .../rn_graph_plot.jl | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl index c9b2864b3c..93de84cb31 100644 --- a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -24,7 +24,18 @@ Graphs.vertices(g::SRGraphWrap) = vertices(g.g) Graphs.is_directed(g::SRGraphWrap) = is_directed(g.g) function Graphs.edges(g::SRGraphWrap) - return vcat(collect(Graphs.edges(g.g)), g.rateedges) + edgelist = vcat(collect(Graphs.edges(g.g)), g.rateedges) + edgeorder = sortperm(edgelist) + edgelist = edgelist[edgeorder] +end + +function gen_distances(g::SRGraphWrap; inc = 0.2) + edgelist = edges(g) + distances = zeros(length(edgelist)) + for i in 2:Base.length(edgelist) + edgelist[i] == edgelist[i-1] && (distances[i] = inc) + end + distances end """ @@ -37,7 +48,7 @@ function PetriNet(rn::ReactionSystem) end """ - SRGraph(rn::ReactionSystem) + SRGraph(rn::ReactionSystem; interactive=false) Converts a [`ReactionSystem`](@ref) into a GraphMakie plot of the species reaction graph. Reactions correspond to small green circles, and species to blue circles. @@ -51,8 +62,10 @@ Notes: rate expression. For example, in the reaction `k*A, B --> C`, there would be a red arrow from `A` to the reaction node. In `k*A, A+B --> C`, there would be red and black arrows from `A` to the reaction node. +- 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. """ -function SRGraph(rn::ReactionSystem; interactive = true) +function SRGraph(rn::ReactionSystem; interactive = false) srg = SRGraphWrap(rn) ns = length(species(rn)) nodecolors = vcat([:skyblue3 for i in 1:ns], @@ -69,9 +82,15 @@ function SRGraph(rn::ReactionSystem; interactive = true) psm[dst(e), src(e)-ns] : ssm[src(e), dst(e)-ns]) end - edgelabels = vcat(edgelabels, fill("", ne(srg) - ne(srg.g))) - edgecolors = vcat([:black for i in 1:ne(srg.g)], - [:red for i in ne(srg.g)+1:ne(srg)]) + edgecolors = [:black for i in 1:ne(srg)] + + elist = Graphs.edges(srg) + for i in 2:length(elist) + elist[i] == elist[i-1] && begin + edgecolors[i] = :red + insert!(edgelabels, i, "") + end + end f, ax, p = graphplot(srg; edge_color = edgecolors, @@ -82,7 +101,8 @@ function SRGraph(rn::ReactionSystem; interactive = true) node_size = nodesizes, arrow_shift = :end, arrow_size = 20, - curve_distance = 0.1 + curve_distance_usage = true, + curve_distance = gen_distances(srg) ) interactive && begin @@ -91,6 +111,7 @@ function SRGraph(rn::ReactionSystem; interactive = true) register_interaction!(ax, :edrag, EdgeDrag(p)) end display(f) + f end # Create the SimpleDiGraph corresponding to the species and reactions @@ -112,7 +133,7 @@ function SRGraphWrap(rn::ReactionSystem) end """ - ComplexGraph(rn::ReactionSystem) + ComplexGraph(rn::ReactionSystem; interactive=false) Creates a GraphMakie plot of the [`ReactionComplex`](@ref)s in `rn`. Reactions correspond to arrows and reaction complexes to blue circles. @@ -120,10 +141,12 @@ end Notes: - Black arrows from complexes to complexes indicate reactions whose rate is a parameter or a `Number`. i.e. `k, A --> B`. - - Red dashed arrows from complexes to complexes indicate reactions whose rate + - Red arrows from complexes to complexes indicate reactions whose rate depends on species. i.e. `k*C, A --> B` for `C` a species. + - 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. """ -function ComplexGraph(rn::ReactionSystem; interactive = true) +function ComplexGraph(rn::ReactionSystem; interactive = false) img = incidencematgraph(rn) specs = species(rn); rxs = reactions(rn) edgecolors = [:black for i in 1:ne(img)] @@ -153,6 +176,7 @@ function ComplexGraph(rn::ReactionSystem; interactive = true) register_interaction!(ax, :edrag, EdgeDrag(p)) end display(f) + f end function complexelemtostr(e::Catalyst.ReactionComplexElement, specstrs) From d2223a9a51f74912ab30b92ec165931bf9fdaa99 Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:39:26 -0500 Subject: [PATCH 05/20] Update src/network_analysis.jl Co-authored-by: Sam Isaacson --- src/network_analysis.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 074cd5f6d3..ab0c1f99f9 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -331,7 +331,7 @@ sir = @reaction_network SIR begin end speciesreactiongraph(sir) """ -function speciesreactiongraph(rn::ReactionSystem) +function species_reaction_graph(rn::ReactionSystem) specs = species(rn) rxs = reactions(rn) sm = speciesmap(rn) From 2c36ac39c9c6401e92d84976c6ffb25b651e79b2 Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:39:36 -0500 Subject: [PATCH 06/20] Update ext/CatalystGraphMakieExtension/rn_graph_plot.jl Co-authored-by: Sam Isaacson --- ext/CatalystGraphMakieExtension/rn_graph_plot.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl index 93de84cb31..0214b1a527 100644 --- a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -146,7 +146,7 @@ end - 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. """ -function ComplexGraph(rn::ReactionSystem; interactive = false) +function plot_complex_graph(rn::ReactionSystem; interactive = false) img = incidencematgraph(rn) specs = species(rn); rxs = reactions(rn) edgecolors = [:black for i in 1:ne(img)] From 952fb39d1d27d56ea03fce1c40d7fafb63b96dfd Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:39:47 -0500 Subject: [PATCH 07/20] Update ext/CatalystGraphMakieExtension/rn_graph_plot.jl Co-authored-by: Sam Isaacson --- ext/CatalystGraphMakieExtension/rn_graph_plot.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl index 0214b1a527..9fbc4c8755 100644 --- a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -201,7 +201,7 @@ function complexlabels(rn::ReactionSystem) push!(labels, complexelemtostr(complex[1], specstrs)) else elems = map(c -> complexelemtostr(c, specstrs), complex) - str = reduce((e1, e2) -> *(e1, " + ", e2), elems[2:end]; init = elems[1]) + str = reduce((e1, e2) -> *(e1, " + ", e2), @view elems[2:end]; init = elems[1]) push!(labels, str) end end From 0bf842bb403e7dd93b707880667e57aaea7e9d6d Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:39:59 -0500 Subject: [PATCH 08/20] Update ext/CatalystGraphMakieExtension/rn_graph_plot.jl Co-authored-by: Sam Isaacson --- ext/CatalystGraphMakieExtension/rn_graph_plot.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl index 9fbc4c8755..f6fde86619 100644 --- a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -65,7 +65,7 @@ Notes: - 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. """ -function SRGraph(rn::ReactionSystem; interactive = false) +function plot_speciesreaction_graph(rn::ReactionSystem; interactive = false) srg = SRGraphWrap(rn) ns = length(species(rn)) nodecolors = vcat([:skyblue3 for i in 1:ns], From 241173ce8d58c646128d393f35d9546aee23c881 Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:40:13 -0500 Subject: [PATCH 09/20] Update ext/CatalystGraphMakieExtension/rn_graph_plot.jl Co-authored-by: Sam Isaacson --- ext/CatalystGraphMakieExtension/rn_graph_plot.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl index f6fde86619..57925688aa 100644 --- a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -86,7 +86,7 @@ function plot_speciesreaction_graph(rn::ReactionSystem; interactive = false) elist = Graphs.edges(srg) for i in 2:length(elist) - elist[i] == elist[i-1] && begin + if elist[i] == elist[i-1] edgecolors[i] = :red insert!(edgelabels, i, "") end From 951e16ffd223bc2a8ae8635cf6ec516deab55023 Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:40:24 -0500 Subject: [PATCH 10/20] Update ext/CatalystGraphMakieExtension/rn_graph_plot.jl Co-authored-by: Sam Isaacson --- ext/CatalystGraphMakieExtension/rn_graph_plot.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl index 57925688aa..11e70051f1 100644 --- a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -120,9 +120,11 @@ function SRGraphWrap(rn::ReactionSystem) rateedges = Vector{SimpleEdge}() sm = speciesmap(rn); specs = species(rn) + deps = Set() for (i, rx) in enumerate(reactions(rn)) - deps = get_variables(rx.rate, specs) - if deps != Any[] + empty!(deps) + get_variables!(deps, rx.rate, specs) + if !isempty(deps) for spec in deps specidx = sm[spec] push!(rateedges, SimpleEdge(specidx, i + length(specs))) From 91abcd1aaf22cbb059c2128f779e0d48f124afe2 Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:40:52 -0500 Subject: [PATCH 11/20] Update ext/CatalystGraphMakieExtension/rn_graph_plot.jl Co-authored-by: Sam Isaacson --- ext/CatalystGraphMakieExtension/rn_graph_plot.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl index 11e70051f1..0fb833f909 100644 --- a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -181,7 +181,7 @@ function plot_complex_graph(rn::ReactionSystem; interactive = false) f end -function complexelemtostr(e::Catalyst.ReactionComplexElement, specstrs) +function complexelem_tostr(e::Catalyst.ReactionComplexElement, specstrs) if e.speciesstoich == 1 return "$(specstrs[e.speciesid])" else From f9d01a3f3e7fb3a437ee9acc0621456dfe49da86 Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:41:14 -0500 Subject: [PATCH 12/20] Update ext/CatalystGraphMakieExtension/rn_graph_plot.jl Co-authored-by: Sam Isaacson --- ext/CatalystGraphMakieExtension/rn_graph_plot.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl index 0fb833f909..8acbfc1ef0 100644 --- a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -155,11 +155,11 @@ function plot_complex_graph(rn::ReactionSystem; interactive = false) nodelabels = complexlabels(rn) edgelabels = [repr(rx.rate) for rx in rxs] + deps = Set() for (i, rx) in enumerate(rxs) - deps = get_variables(rx.rate, specs) - if deps != Any[] - edgecolors[i] = :red - end + empty!(deps) + get_variables!(deps, rx.rate, specs) + (!isempty(deps)) && (edgecolors[i] = :red) end f, ax, p = graphplot(img; From 5bb1d10be7f989ecbf391a58d624558aed0bce20 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 15 Nov 2024 12:30:11 -0500 Subject: [PATCH 13/20] up --- .../rn_graph_plot.jl | 43 +++++++------ src/network_analysis.jl | 24 +++---- test/extensions/graphmakie.jl | 63 +++++++++++++++++++ 3 files changed, 96 insertions(+), 34 deletions(-) create mode 100644 test/extensions/graphmakie.jl diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl index 8acbfc1ef0..f7988c5257 100644 --- a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -12,6 +12,26 @@ struct SRGraphWrap{T} <: AbstractGraph{T} rateedges::Vector{SimpleEdge} end +# Create the SimpleDiGraph corresponding to the species and reactions +function SRGraphWrap(rn::ReactionSystem) + srg = speciesreactiongraph(rn) + rateedges = Vector{SimpleEdge}() + sm = speciesmap(rn); specs = species(rn) + + deps = Set() + for (i, rx) in enumerate(reactions(rn)) + empty!(deps) + get_variables!(deps, rx.rate, specs) + if !isempty(deps) + for spec in deps + specidx = sm[spec] + push!(rateedges, SimpleEdge(specidx, i + length(specs))) + end + end + end + SRGraphWrap(srg, rateedges) +end + Base.eltype(g::SRGraphWrap) = eltype(g.g) Graphs.edgetype(g::SRGraphWrap) = edgetype(g.g) Graphs.has_edge(g::SRGraphWrap, s, d) = has_edge(g.g, s, d) @@ -25,8 +45,7 @@ Graphs.is_directed(g::SRGraphWrap) = is_directed(g.g) function Graphs.edges(g::SRGraphWrap) edgelist = vcat(collect(Graphs.edges(g.g)), g.rateedges) - edgeorder = sortperm(edgelist) - edgelist = edgelist[edgeorder] + edgelist = sort!(edgelist) end function gen_distances(g::SRGraphWrap; inc = 0.2) @@ -114,26 +133,6 @@ function plot_speciesreaction_graph(rn::ReactionSystem; interactive = false) f end -# Create the SimpleDiGraph corresponding to the species and reactions -function SRGraphWrap(rn::ReactionSystem) - srg = speciesreactiongraph(rn) - rateedges = Vector{SimpleEdge}() - sm = speciesmap(rn); specs = species(rn) - - deps = Set() - for (i, rx) in enumerate(reactions(rn)) - empty!(deps) - get_variables!(deps, rx.rate, specs) - if !isempty(deps) - for spec in deps - specidx = sm[spec] - push!(rateedges, SimpleEdge(specidx, i + length(specs))) - end - end - end - SRGraphWrap(srg, rateedges) -end - """ ComplexGraph(rn::ReactionSystem; interactive=false) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index ab0c1f99f9..7ffdea0752 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -316,12 +316,12 @@ end """ - speciesreactiongraph(rn::ReactionSystem) + species_reaction_graph(rn::ReactionSystem) Construct a directed simple graph where there are two types of nodes: species and reactions. -An edge from a species to reaction indicates that it is a reactant, and an edge from a reaction -to a species indicates that it is a product. By default, the species vertices are listed -first, so the first n indices correspond to species nodes. +An edge from a species *s* to reaction *r* indicates that *s* is a reactant for *r*, and an edge from a reaction +r to a species s indicates that *s* is a product of *r*. By default, the species vertices are listed +first, so the first *n* indices correspond to species nodes. For example, ```julia @@ -329,24 +329,24 @@ sir = @reaction_network SIR begin β, S + I --> 2I ν, I --> R end -speciesreactiongraph(sir) +species_reaction_graph(sir) """ function species_reaction_graph(rn::ReactionSystem) specs = species(rn) rxs = reactions(rn) sm = speciesmap(rn) - s = length(specs); r = length(rxs); nv = s + r + s = length(specs) - adjmat = zeros(Int64, nv, nv) + edgelist = Graphs.Edge[] for (i, rx) in enumerate(rxs) - for (spec, stoich) in zip(rx.substrates, rx.substoich) - adjmat[sm[spec], s+i] = stoich + for spec in rx.substrates + push!(edgelist, Graphs.Edge(sm[spec], s+i)) end - for (spec, stoich) in zip(rx.products, rx.prodstoich) - adjmat[s+i, sm[spec]] = stoich + for spec in rx.products + push!(edgelist, Graphs.Edge(s+i, sm[spec])) end end - srg = SimpleDiGraph(adjmat) + srg = Graphs.SimpleDiGraphFromIterator(edgelist) end ### Linkage, Deficiency, Reversibility ### diff --git a/test/extensions/graphmakie.jl b/test/extensions/graphmakie.jl new file mode 100644 index 0000000000..659b3051b0 --- /dev/null +++ b/test/extensions/graphmakie.jl @@ -0,0 +1,63 @@ +using Catalyst, GraphMakie, GLMakie +include("../test_networks.jl") +# Test that speciesreactiongraph is generated correctly +let + brusselator = @reaction_network begin + A, ∅ --> X + 1, 2X + Y --> 3X + B, X --> Y + 1, X --> ∅ + end + + srg = Catalyst.species_reaction_graph(brusselator) + s = length(species(brusselator)) + edgel = Edge.([(s+1, 1), + (1, s+2), + (2, s+2), + (s+2, 1), + (s+3, 2), + (1, s+3), + (1, s+4)]) + @test all(∈(collect(Graphs.edges(srg))), edgel) + + MAPK = @reaction_network MAPK begin + (k₁, k₂),KKK + E1 <--> KKKE1 + k₃, KKKE1 --> KKK_ + E1 + (k₄, k₅), KKK_ + E2 <--> KKKE2 + k₆, KKKE2 --> KKK + E2 + (k₇, k₈), KK + KKK_ <--> KK_KKK_ + k₉, KK_KKK_ --> KKP + KKK_ + (k₁₀, k₁₁), KKP + KKK_ <--> KKPKKK_ + k₁₂, KKPKKK_ --> KKPP + KKK_ + (k₁₃, k₁₄), KKP + KKPase <--> KKPKKPase + k₁₅, KKPPKKPase --> KKP + KKPase + k₁₆,KKPKKPase --> KK + KKPase + (k₁₇, k₁₈), KKPP + KKPase <--> KKPPKKPase + (k₁₉, k₂₀), KKPP + K <--> KKPPK + k₂₁, KKPPK --> KKPP + KP + (k₂₂, k₂₃), KKPP + KP <--> KPKKPP + k₂₄, KPKKPP --> KPP + KKPP + (k₂₅, k₂₆), KP + KPase <--> KPKPase + k₂₇, KKPPKPase --> KP + KPase + k₂₈, KPKPase --> K + KPase + (k₂₉, k₃₀), KPP + KPase <--> KKPPKPase + end + srg = Catalyst.species_reaction_graph(MAPK) + @test nv(srg) == length(species(MAPK)) + length(reactions(MAPK)) + @test ne(srg) == 90 +end + +# Test that rate edges are inferred correctly +let + rn = @reaction_network begin + k, A --> B + k * C, A --> C + k * B, B --> C + end + srg = SRGraphWrap(rn) + s = length(species(rn)) + @test Edge(3, s+2) ∈ srg.rateedges + @test Edge(2, s+3) ∈ srg.rateedges + # Since B is both a dep and a reactant + @test count(==(Edge(2, s+3)), edges(srg)) == 2 +end From b2bedf66d48c0f6aa4c07f61228dd14650f8ec3a Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 15 Nov 2024 14:51:33 -0500 Subject: [PATCH 14/20] fixes --- ext/CatalystGraphMakieExtension.jl | 8 +-- .../rn_graph_plot.jl | 71 +++++++------------ src/Catalyst.jl | 6 ++ test/extensions/graphmakie.jl | 57 +++++++++++++-- 4 files changed, 86 insertions(+), 56 deletions(-) diff --git a/ext/CatalystGraphMakieExtension.jl b/ext/CatalystGraphMakieExtension.jl index 378eb96f3a..20c2c1a96f 100644 --- a/ext/CatalystGraphMakieExtension.jl +++ b/ext/CatalystGraphMakieExtension.jl @@ -2,12 +2,10 @@ module CatalystGraphMakieExtension # Fetch packages. using Catalyst, GraphMakie, Graphs -import Catalyst: lattice_plot, lattice_animation, extract_vals -import Graphs: AbstractGraph, SimpleGraph, SimpleDiGraph, SimpleEdge, src, dst, ne, nv -import Catalyst: speciesreactiongraph, incidencematgraph +import Catalyst: lattice_plot, lattice_animation, extract_vals, get_variables! +import Catalyst: species_reaction_graph, incidencematgraph -# Creates and exports hc_steady_states function. +# Creates and exports graph plotting functions. include("CatalystGraphMakieExtension/graph_makie_extension_spatial_modelling.jl") include("CatalystGraphMakieExtension/rn_graph_plot.jl") -export SRGraph, ComplexGraph end diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl index f7988c5257..95a99823ef 100644 --- a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -7,15 +7,16 @@ Wrapper for the species-reaction graph containing edges for rate-dependence on species. Intended to allow plotting of multiple edges. """ -struct SRGraphWrap{T} <: AbstractGraph{T} +struct SRGraphWrap{T} <: Graphs.AbstractGraph{T} g::SimpleDiGraph{T} - rateedges::Vector{SimpleEdge} + rateedges::Vector{Graphs.SimpleEdge{T}} + edgeorder::Vector{Int64} end # Create the SimpleDiGraph corresponding to the species and reactions function SRGraphWrap(rn::ReactionSystem) - srg = speciesreactiongraph(rn) - rateedges = Vector{SimpleEdge}() + srg = species_reaction_graph(rn) + rateedges = Vector{Graphs.SimpleEdge{Int}}() sm = speciesmap(rn); specs = species(rn) deps = Set() @@ -25,11 +26,13 @@ function SRGraphWrap(rn::ReactionSystem) if !isempty(deps) for spec in deps specidx = sm[spec] - push!(rateedges, SimpleEdge(specidx, i + length(specs))) + push!(rateedges, Graphs.SimpleEdge(specidx, i + length(specs))) end end end - SRGraphWrap(srg, rateedges) + edgelist = vcat(collect(Graphs.edges(srg)), rateedges) + edgeorder = sortperm(edgelist) + SRGraphWrap(srg, rateedges, edgeorder) end Base.eltype(g::SRGraphWrap) = eltype(g.g) @@ -44,8 +47,7 @@ Graphs.vertices(g::SRGraphWrap) = vertices(g.g) Graphs.is_directed(g::SRGraphWrap) = is_directed(g.g) function Graphs.edges(g::SRGraphWrap) - edgelist = vcat(collect(Graphs.edges(g.g)), g.rateedges) - edgelist = sort!(edgelist) + edgelist = vcat(collect(Graphs.edges(g.g)), g.rateedges)[g.edgeorder] end function gen_distances(g::SRGraphWrap; inc = 0.2) @@ -60,14 +62,14 @@ end """ PetriNet(rn::ReactionSystem) - See the documentation for [`SRGraph`](@ref). + See the documentation for [`plot_network`](@ref). """ -function PetriNet(rn::ReactionSystem) - SRGraph(rn) +function Catalyst.plot_petrinet(rn::ReactionSystem) + plot_network(rn) end """ - SRGraph(rn::ReactionSystem; interactive=false) + plot_network(rn::ReactionSystem; interactive=false) Converts a [`ReactionSystem`](@ref) into a GraphMakie plot of the species reaction graph. Reactions correspond to small green circles, and species to blue circles. @@ -81,10 +83,9 @@ Notes: rate expression. For example, in the reaction `k*A, B --> C`, there would be a red arrow from `A` to the reaction node. In `k*A, A+B --> C`, there would be red and black arrows from `A` to the reaction node. -- 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. """ -function plot_speciesreaction_graph(rn::ReactionSystem; interactive = false) +# 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. +function Catalyst.plot_network(rn::ReactionSystem) srg = SRGraphWrap(rn) ns = length(species(rn)) nodecolors = vcat([:skyblue3 for i in 1:ns], @@ -103,19 +104,19 @@ function plot_speciesreaction_graph(rn::ReactionSystem; interactive = false) end edgecolors = [:black for i in 1:ne(srg)] - elist = Graphs.edges(srg) - for i in 2:length(elist) - if elist[i] == elist[i-1] + num_e = ne(srg.g) + for i in 1:length(srg.edgeorder) + if srg.edgeorder[i] > num_e edgecolors[i] = :red insert!(edgelabels, i, "") end end - f, ax, p = graphplot(srg; + graphplot(srg; edge_color = edgecolors, - elabels = edgelabels, + elabels = edgelabels, elabels_rotation = 0, - ilabels = ilabels, + ilabels = ilabels, node_color = nodecolors, node_size = nodesizes, arrow_shift = :end, @@ -123,18 +124,10 @@ function plot_speciesreaction_graph(rn::ReactionSystem; interactive = false) curve_distance_usage = true, curve_distance = gen_distances(srg) ) - - interactive && begin - deregister_interaction!(ax, :rectanglezoom) - register_interaction!(ax, :ndrag, NodeDrag(p)) - register_interaction!(ax, :edrag, EdgeDrag(p)) - end - display(f) - f end """ - ComplexGraph(rn::ReactionSystem; interactive=false) + plot_complexes(rn::ReactionSystem; interactive=false) Creates a GraphMakie plot of the [`ReactionComplex`](@ref)s in `rn`. Reactions correspond to arrows and reaction complexes to blue circles. @@ -144,10 +137,8 @@ end parameter or a `Number`. i.e. `k, A --> B`. - Red arrows from complexes to complexes indicate reactions whose rate depends on species. i.e. `k*C, A --> B` for `C` a species. - - 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. """ -function plot_complex_graph(rn::ReactionSystem; interactive = false) +function Catalyst.plot_complexes(rn::ReactionSystem) img = incidencematgraph(rn) specs = species(rn); rxs = reactions(rn) edgecolors = [:black for i in 1:ne(img)] @@ -161,7 +152,7 @@ function plot_complex_graph(rn::ReactionSystem; interactive = false) (!isempty(deps)) && (edgecolors[i] = :red) end - f, ax, p = graphplot(img; + graphplot(img; edge_color = edgecolors, elabels = edgelabels, ilabels = complexlabels(rn), @@ -170,14 +161,6 @@ function plot_complex_graph(rn::ReactionSystem; interactive = false) arrow_shift = :end, curve_distance = 0.2 ) - - interactive && begin - deregister_interaction!(ax, :rectanglezoom) - register_interaction!(ax, :ndrag, NodeDrag(p)) - register_interaction!(ax, :edrag, EdgeDrag(p)) - end - display(f) - f end function complexelem_tostr(e::Catalyst.ReactionComplexElement, specstrs) @@ -199,9 +182,9 @@ function complexlabels(rn::ReactionSystem) if isempty(complex) push!(labels, "∅") elseif length(complex) == 1 - push!(labels, complexelemtostr(complex[1], specstrs)) + push!(labels, complexelem_tostr(complex[1], specstrs)) else - elems = map(c -> complexelemtostr(c, specstrs), complex) + elems = map(c -> complexelem_tostr(c, specstrs), complex) str = reduce((e1, e2) -> *(e1, " + ", e2), @view elems[2:end]; init = elems[1]) push!(labels, str) end diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 047747da71..69da5d8d98 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -167,6 +167,12 @@ export hc_steady_states function make_si_ode end export make_si_ode +# GraphMakie +function plot_network end +function plot_petrinet end +function plot_complexes end +export plot_network, plot_complexes, plot_petrinet + ### Spatial Reaction Networks ### # Spatial reactions. diff --git a/test/extensions/graphmakie.jl b/test/extensions/graphmakie.jl index 659b3051b0..e74b6ce48b 100644 --- a/test/extensions/graphmakie.jl +++ b/test/extensions/graphmakie.jl @@ -1,5 +1,6 @@ -using Catalyst, GraphMakie, GLMakie +using Catalyst, GraphMakie, GLMakie, Graphs include("../test_networks.jl") + # Test that speciesreactiongraph is generated correctly let brusselator = @reaction_network begin @@ -11,7 +12,7 @@ let srg = Catalyst.species_reaction_graph(brusselator) s = length(species(brusselator)) - edgel = Edge.([(s+1, 1), + edgel = Graphs.Edge.([(s+1, 1), (1, s+2), (2, s+2), (s+2, 1), @@ -45,19 +46,61 @@ let srg = Catalyst.species_reaction_graph(MAPK) @test nv(srg) == length(species(MAPK)) + length(reactions(MAPK)) @test ne(srg) == 90 + + # Test that figures are generated properly. + f = plot_network(MAPK) + save("fig.png", f) + @test isfile("fig.png") + rm("fig.png") + f = plot_network(brusselator) + save("fig.png", f) + @test isfile("fig.png") + rm("fig.png") + + f = plot_complexes(MAPK); save("fig.png", f) + @test isfile("fig.png") + rm("fig.png") + f = plot_complexes(brusselator); save("fig.png", f) + @test isfile("fig.png") + rm("fig.png") end -# Test that rate edges are inferred correctly +CGME = Base.get_extension(parentmodule(ReactionSystem), :CatalystGraphMakieExtension) +# Test that rate edges are inferred correctly. We should see two for the following reaction network. let + # Two rate edges, one to species and one to product rn = @reaction_network begin k, A --> B k * C, A --> C k * B, B --> C end - srg = SRGraphWrap(rn) + srg = CGME.SRGraphWrap(rn) s = length(species(rn)) - @test Edge(3, s+2) ∈ srg.rateedges - @test Edge(2, s+3) ∈ srg.rateedges + @test ne(srg) == 8 + @test Graphs.Edge(3, s+2) ∈ srg.rateedges + @test Graphs.Edge(2, s+3) ∈ srg.rateedges # Since B is both a dep and a reactant - @test count(==(Edge(2, s+3)), edges(srg)) == 2 + @test count(==(Graphs.Edge(2, s+3)), edges(srg)) == 2 + + f = plot_network(rn) + save("fig.png", f) + @test isfile("fig.png") + rm("fig.png") + f = plot_complexes(rn); save("fig.png", f) + @test isfile("fig.png") + rm("fig.png") + + # Two rate edges, both to reactants + rn = @reaction_network begin + k, A --> B + k * A, A --> C + k * B, B --> C + end + srg = CGME.SRGraphWrap(rn) + s = length(species(rn)) + @test ne(srg) == 8 + # Since A, B is both a dep and a reactant + @test count(==(Graphs.Edge(1, s+2)), edges(srg)) == 2 + @test count(==(Graphs.Edge(2, s+3)), edges(srg)) == 2 end + From 21237a88ad5c132c0366b2e43394ff1511f7ba44 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 15 Nov 2024 15:00:09 -0500 Subject: [PATCH 15/20] up --- ext/CatalystGraphMakieExtension/rn_graph_plot.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl index 95a99823ef..1792ed039e 100644 --- a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -60,7 +60,7 @@ function gen_distances(g::SRGraphWrap; inc = 0.2) end """ - PetriNet(rn::ReactionSystem) + plot_petrinet(rn::ReactionSystem) See the documentation for [`plot_network`](@ref). """ From 794fb04418c4555e58170a583cb1508c130471da Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Sat, 16 Nov 2024 10:39:08 -0500 Subject: [PATCH 16/20] Update ext/CatalystGraphMakieExtension.jl Co-authored-by: Sam Isaacson --- ext/CatalystGraphMakieExtension.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/CatalystGraphMakieExtension.jl b/ext/CatalystGraphMakieExtension.jl index 20c2c1a96f..d9c20aec14 100644 --- a/ext/CatalystGraphMakieExtension.jl +++ b/ext/CatalystGraphMakieExtension.jl @@ -1,8 +1,8 @@ module CatalystGraphMakieExtension # Fetch packages. -using Catalyst, GraphMakie, Graphs -import Catalyst: lattice_plot, lattice_animation, extract_vals, get_variables! +using Catalyst, GraphMakie, Graphs, Symbolics +using Symbolics: get_variables! import Catalyst: species_reaction_graph, incidencematgraph # Creates and exports graph plotting functions. From 161d1b6ca5738cfa22e41f54cfd4c16b1e41ed4f Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Sat, 16 Nov 2024 10:40:59 -0500 Subject: [PATCH 17/20] Update src/Catalyst.jl Co-authored-by: Sam Isaacson --- src/Catalyst.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 69da5d8d98..4afc8a29a3 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -169,7 +169,6 @@ export make_si_ode # GraphMakie function plot_network end -function plot_petrinet end function plot_complexes end export plot_network, plot_complexes, plot_petrinet From ef0635fae6558aea6112f640f4263c77f8ddba99 Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Sat, 16 Nov 2024 10:41:09 -0500 Subject: [PATCH 18/20] Update src/Catalyst.jl Co-authored-by: Sam Isaacson --- src/Catalyst.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 4afc8a29a3..ad12f0fa4b 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -170,7 +170,7 @@ export make_si_ode # GraphMakie function plot_network end function plot_complexes end -export plot_network, plot_complexes, plot_petrinet +export plot_network, plot_complexes ### Spatial Reaction Networks ### From a46a0c573f3bb3e99c339e260244398b92f75a4e Mon Sep 17 00:00:00 2001 From: vyudu Date: Sat, 16 Nov 2024 10:43:24 -0500 Subject: [PATCH 19/20] up --- ext/CatalystGraphMakieExtension/rn_graph_plot.jl | 14 +++----------- src/network_analysis.jl | 9 ++++++--- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl index 1792ed039e..644a175b32 100644 --- a/ext/CatalystGraphMakieExtension/rn_graph_plot.jl +++ b/ext/CatalystGraphMakieExtension/rn_graph_plot.jl @@ -59,20 +59,12 @@ function gen_distances(g::SRGraphWrap; inc = 0.2) distances end -""" - plot_petrinet(rn::ReactionSystem) - - See the documentation for [`plot_network`](@ref). -""" -function Catalyst.plot_petrinet(rn::ReactionSystem) - plot_network(rn) -end - """ plot_network(rn::ReactionSystem; interactive=false) -Converts a [`ReactionSystem`](@ref) into a GraphMakie plot of the species reaction graph. -Reactions correspond to small green circles, and species to blue circles. +Converts a [`ReactionSystem`](@ref) into a GraphMakie plot of the species reaction graph +(or Petri net representation). Reactions correspond to small green circles, and +species to blue circles. Notes: - Black arrows from species to reactions indicate reactants, and are labelled diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 7ffdea0752..86802fb919 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -319,9 +319,12 @@ end species_reaction_graph(rn::ReactionSystem) Construct a directed simple graph where there are two types of nodes: species and reactions. -An edge from a species *s* to reaction *r* indicates that *s* is a reactant for *r*, and an edge from a reaction -r to a species s indicates that *s* is a product of *r*. By default, the species vertices are listed -first, so the first *n* indices correspond to species nodes. +An edge from a species *s* to reaction *r* indicates that *s* is a reactant for *r*, and +an edge from a reaction *r* to a species *s* indicates that *s* is a product of *r*. By +default, the species vertices are listed first, so the first *n* indices correspond to +species nodes. + +Note: this is equivalent to the Petri net representation of a chemical reaction network. For example, ```julia From 82a575d67938a1df4e8f79cbeb3baa44114fa4dd Mon Sep 17 00:00:00 2001 From: vyudu Date: Sat, 16 Nov 2024 11:09:12 -0500 Subject: [PATCH 20/20] fix --- ext/CatalystGraphMakieExtension.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/CatalystGraphMakieExtension.jl b/ext/CatalystGraphMakieExtension.jl index d9c20aec14..a47ef7f5e5 100644 --- a/ext/CatalystGraphMakieExtension.jl +++ b/ext/CatalystGraphMakieExtension.jl @@ -3,7 +3,7 @@ module CatalystGraphMakieExtension # Fetch packages. using Catalyst, GraphMakie, Graphs, Symbolics using Symbolics: get_variables! -import Catalyst: species_reaction_graph, incidencematgraph +import Catalyst: species_reaction_graph, incidencematgraph, lattice_plot, lattice_animation # Creates and exports graph plotting functions. include("CatalystGraphMakieExtension/graph_makie_extension_spatial_modelling.jl")