Skip to content

Commit 7dda201

Browse files
committed
Merge remote-tracking branch 'origin/master' into graph-docs
2 parents c2b8654 + 19d9652 commit 7dda201

File tree

4 files changed

+187
-55
lines changed

4 files changed

+187
-55
lines changed

docs/src/steady_state_functionality/nonlinear_solve.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ nl_prob = NonlinearProblem(two_state_model, u_guess, p; remove_conserved = true)
7575
nothing # hide
7676
```
7777
here it is important that the quantities used in `u_guess` correspond to the conserved quantities we wish to use. E.g. here the conserved quantity $X1 + X2 = 3.0 + 1.0 = 4$ holds for the initial condition, and will hence also hold in the computed steady state as well. We can now find the steady states using `solve` like before:
78-
```@example steady_state_solving_claws
78+
<!-- ```@example steady_state_solving_claws
7979
sol = solve(nl_prob)
80-
```
80+
``` -->
8181
We note that the output only provides a single value. The reason is that the actual system solved only contains a single equation (the other being eliminated with the conserved quantity). To find the values of $X1$ and $X2$ we can [directly query the solution object for these species' values, using the species themselves as inputs](@ref simulation_structure_interfacing_solutions):
82-
```@example steady_state_solving_claws
82+
<!--```@example steady_state_solving_claws
8383
sol[[:X1, :X2]]
84-
```
84+
```-->
8585

8686
## [Finding steady states through ODE simulations](@id steady_state_solving_simulation)
8787
The `NonlinearProblem`s generated by Catalyst corresponds to ODEs. A common method of solving these is to simulate the ODE from an initial condition until a steady state is reached. Here we do so for the dimerisation system considered in the previous section. First, we declare our model, initial condition, and parameter values.

ext/CatalystGraphMakieExtension.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module CatalystGraphMakieExtension
22

33
# Fetch packages.
4-
using Catalyst, GraphMakie, Graphs, Symbolics
4+
using Catalyst, GraphMakie, Graphs, Symbolics, SparseArrays
55
using Symbolics: get_variables!
66
import Catalyst: species_reaction_graph, incidencematgraph, lattice_plot, lattice_animation
77

ext/CatalystGraphMakieExtension/rn_graph_plot.jl

Lines changed: 99 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,22 @@
33
#############
44

55
"""
6-
SRGraphWrap{T}
6+
MultiGraphWrap{T}
77
8-
Wrapper for the species-reaction graph containing edges for rate-dependence on species. Intended to allow plotting of multiple edges.
8+
Wrapper intended to allow plotting of multiple edges. This is needed in the following cases:
9+
- For the species-reaction graph, multiple edges can exist when a reaction depends on some species for its rate, and if that species is produced by the reaction.
10+
- For the complex graph, multiple edges can exist between a pair of nodes if there are multiple reactions between the same complexes. This might include a reversible pair of reactions - we might have three total edges if one reaction is reversible, and we have a separate reaction going from one complex to the other.
11+
12+
`gen_distances` sets the distances between the edges that allows multiple to be visible on the plot at the same time.
913
"""
10-
struct SRGraphWrap{T} <: Graphs.AbstractGraph{T}
14+
struct MultiGraphWrap{T} <: Graphs.AbstractGraph{T}
1115
g::SimpleDiGraph{T}
12-
rateedges::Vector{Graphs.SimpleEdge{T}}
16+
multiedges::Vector{Graphs.SimpleEdge{T}}
1317
edgeorder::Vector{Int64}
1418
end
1519

16-
# Create the SimpleDiGraph corresponding to the species and reactions
17-
function SRGraphWrap(rn::ReactionSystem)
20+
# Create the SimpleDiGraph corresponding to the species and reactions, the species-reaction graph
21+
function MultiGraphWrap(rn::ReactionSystem)
1822
srg = species_reaction_graph(rn)
1923
rateedges = Vector{Graphs.SimpleEdge{Int}}()
2024
sm = speciesmap(rn); specs = species(rn)
@@ -32,29 +36,60 @@ function SRGraphWrap(rn::ReactionSystem)
3236
end
3337
edgelist = vcat(collect(Graphs.edges(srg)), rateedges)
3438
edgeorder = sortperm(edgelist)
35-
SRGraphWrap(srg, rateedges, edgeorder)
39+
MultiGraphWrap(srg, rateedges, edgeorder)
40+
end
41+
42+
# Automatically set edge order if not supplied
43+
function MultiGraphWrap(g::SimpleDiGraph{T}, multiedges::Vector{Graphs.SimpleEdge{T}}) where T
44+
edgelist = vcat(collect(Graphs.edges(g)), multiedges)
45+
edgeorder = sortperm(edgelist)
46+
MultiGraphWrap(g, multiedges, edgeorder)
3647
end
3748

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]
49+
Base.eltype(g::MultiGraphWrap) = eltype(g.g)
50+
Graphs.edgetype(g::MultiGraphWrap) = edgetype(g.g)
51+
Graphs.has_edge(g::MultiGraphWrap, s, d) = has_edge(g.g, s, d)
52+
Graphs.has_vertex(g::MultiGraphWrap, i) = has_vertex(g.g, i)
53+
Graphs.inneighbors(g::MultiGraphWrap{T}, i) where T = inneighbors(g.g, i)
54+
Graphs.outneighbors(g::MultiGraphWrap{T}, i) where T = outneighbors(g.g, i)
55+
Graphs.ne(g::MultiGraphWrap) = length(g.multiedges) + length(Graphs.edges(g.g))
56+
Graphs.nv(g::MultiGraphWrap) = nv(g.g)
57+
Graphs.vertices(g::MultiGraphWrap) = vertices(g.g)
58+
Graphs.is_directed(::Type{<:MultiGraphWrap}) = true
59+
Graphs.is_directed(g::MultiGraphWrap) = is_directed(g.g)
60+
61+
function Graphs.edges(g::MultiGraphWrap)
62+
edgelist = vcat(collect(Graphs.edges(g.g)), g.multiedges)[g.edgeorder]
5163
end
5264

53-
function gen_distances(g::SRGraphWrap; inc = 0.2)
65+
function gen_distances(g::MultiGraphWrap; inc = 0.4)
5466
edgelist = edges(g)
5567
distances = zeros(length(edgelist))
56-
for i in 2:Base.length(edgelist)
57-
edgelist[i] == edgelist[i-1] && (distances[i] = inc)
68+
edgedict = Dict(edgelist[1] => [1])
69+
for (i, e) in enumerate(@view edgelist[2:end])
70+
if edgelist[i] != edgelist[i+1]
71+
edgedict[e] = [i+1]
72+
else
73+
push!(edgedict[e], i+1)
74+
end
75+
end
76+
77+
for (edge, inds) in edgedict
78+
if haskey(edgedict, Edge(dst(edge), src(edge)))
79+
distances[inds[1]] != 0. && continue
80+
inds_ = edgedict[Edge(dst(edge), src(edge))]
81+
82+
len = length(inds) + length(inds_)
83+
sp = -inc/2*(len-1)
84+
ep = sp + inc*(len-1)
85+
dists = collect(sp:inc:ep)
86+
distances[inds] = dists[1:length(inds)]
87+
distances[inds_] = -dists[length(inds)+1:end]
88+
else
89+
sp = -inc/2*(length(inds)-1)
90+
ep = sp + inc*(length(inds)-1)
91+
distances[inds] = collect(sp:inc:ep)
92+
end
5893
end
5994
distances
6095
end
@@ -77,7 +112,7 @@ Notes:
77112
red and black arrows from `A` to the reaction node.
78113
"""
79114
function Catalyst.plot_network(rn::ReactionSystem)
80-
srg = SRGraphWrap(rn)
115+
srg = MultiGraphWrap(rn)
81116
ns = length(species(rn))
82117
nodecolors = vcat([:skyblue3 for i in 1:ns],
83118
[:green for i in ns+1:nv(srg)])
@@ -104,16 +139,16 @@ function Catalyst.plot_network(rn::ReactionSystem)
104139
end
105140

106141
graphplot(srg;
107-
edge_color = edgecolors,
108-
elabels = edgelabels,
109-
elabels_rotation = 0,
110-
ilabels = ilabels,
111-
node_color = nodecolors,
112-
node_size = nodesizes,
113-
arrow_shift = :end,
114-
arrow_size = 20,
115-
curve_distance_usage = true,
116-
curve_distance = gen_distances(srg)
142+
edge_color = edgecolors,
143+
elabels = edgelabels,
144+
elabels_rotation = 0,
145+
ilabels = ilabels,
146+
node_color = nodecolors,
147+
node_size = nodesizes,
148+
arrow_shift = :end,
149+
arrow_size = 20,
150+
curve_distance_usage = true,
151+
curve_distance = gen_distances(srg),
117152
)
118153
end
119154

@@ -130,27 +165,46 @@ end
130165
depends on species. i.e. `k*C, A --> B` for `C` a species.
131166
"""
132167
function Catalyst.plot_complexes(rn::ReactionSystem)
133-
img = incidencematgraph(rn)
168+
img = incidencematgraph(rn); D = incidencemat(rn; sparse=true)
134169
specs = species(rn); rxs = reactions(rn)
135-
edgecolors = [:black for i in 1:ne(img)]
136-
nodelabels = complexlabels(rn)
170+
edgecolors = [:black for i in 1:length(rxs)]
137171
edgelabels = [repr(rx.rate) for rx in rxs]
138172

139173
deps = Set()
174+
edgelist = Vector{Graphs.SimpleEdge{Int}}()
175+
rows = rowvals(D)
176+
vals = nonzeros(D)
177+
178+
# Construct the edge order for reactions.
140179
for (i, rx) in enumerate(rxs)
180+
inds = nzrange(D, i)
181+
val = vals[inds]
182+
row = rows[inds]
183+
(sub, prod) = val[1] == -1 ? (row[1], row[2]) : (row[2], row[1])
184+
push!(edgelist, Graphs.SimpleEdge(sub, prod))
185+
141186
empty!(deps)
142187
get_variables!(deps, rx.rate, specs)
143188
(!isempty(deps)) && (edgecolors[i] = :red)
144189
end
145190

146-
graphplot(img;
147-
edge_color = edgecolors,
148-
elabels = edgelabels,
149-
ilabels = nodelabels,
150-
node_color = :skyblue3,
151-
elabels_rotation = 0,
152-
arrow_shift = :end,
153-
curve_distance = 0.2
191+
# Resolve differences between reaction order and edge order in the incidencematgraph.
192+
rxorder = sortperm(edgelist); edgelist = edgelist[rxorder]
193+
multiedges = Vector{Graphs.SimpleEdge{Int}}()
194+
for i in 2:length(edgelist)
195+
isequal(edgelist[i], edgelist[i-1]) && push!(multiedges, edgelist[i])
196+
end
197+
img_ = MultiGraphWrap(img, multiedges)
198+
199+
graphplot(img_;
200+
edge_color = edgecolors[rxorder],
201+
elabels = edgelabels[rxorder],
202+
ilabels = complexlabels(rn),
203+
node_color = :skyblue3,
204+
elabels_rotation = 0,
205+
arrow_shift = :end,
206+
curve_distance_usage = true,
207+
curve_distance = gen_distances(img_)
154208
)
155209
end
156210

test/extensions/graphmakie.jl

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Catalyst, GraphMakie, CairoMakie, Graphs
1+
using Catalyst, GraphMakie, CairoMakie, Graphs, SparseArrays
22
include("../test_networks.jl")
33

44
# Test that speciesreactiongraph is generated correctly
@@ -74,11 +74,11 @@ let
7474
k * C, A --> C
7575
k * B, B --> C
7676
end
77-
srg = CGME.SRGraphWrap(rn)
77+
srg = CGME.MultiGraphWrap(rn)
7878
s = length(species(rn))
7979
@test ne(srg) == 8
80-
@test Graphs.Edge(3, s+2) srg.rateedges
81-
@test Graphs.Edge(2, s+3) srg.rateedges
80+
@test Graphs.Edge(3, s+2) srg.multiedges
81+
@test Graphs.Edge(2, s+3) srg.multiedges
8282
# Since B is both a dep and a reactant
8383
@test count(==(Graphs.Edge(2, s+3)), edges(srg)) == 2
8484

@@ -96,10 +96,88 @@ let
9696
k * A, A --> C
9797
k * B, B --> C
9898
end
99-
srg = CGME.SRGraphWrap(rn)
99+
srg = CGME.MultiGraphWrap(rn)
100100
s = length(species(rn))
101101
@test ne(srg) == 8
102102
# Since A, B is both a dep and a reactant
103103
@test count(==(Graphs.Edge(1, s+2)), edges(srg)) == 2
104104
@test count(==(Graphs.Edge(2, s+3)), edges(srg)) == 2
105105
end
106+
107+
function test_edgeorder(rn)
108+
# The initial edgelabels in `plot_complexes` is given by the order of reactions in reactions(rn).
109+
D = incidencemat(rn; sparse=true); img = incidencematgraph(rn)
110+
rxs = reactions(rn)
111+
edgelist = Vector{Graphs.SimpleEdge{Int}}()
112+
rows = rowvals(D)
113+
vals = nonzeros(D)
114+
115+
for (i, rx) in enumerate(rxs)
116+
inds = nzrange(D, i)
117+
val = vals[inds]
118+
row = rows[inds]
119+
(sub, prod) = val[1] == -1 ? (row[1], row[2]) : (row[2], row[1])
120+
push!(edgelist, Graphs.SimpleEdge(sub, prod))
121+
end
122+
123+
rxorder = sortperm(edgelist); sortededgelist = edgelist[rxorder]
124+
multiedges = Vector{Graphs.SimpleEdge{Int}}()
125+
for i in 2:length(sortededgelist)
126+
isequal(sortededgelist[i], sortededgelist[i-1]) && push!(multiedges, sortededgelist[i])
127+
end
128+
img_ = CGME.MultiGraphWrap(img, multiedges)
129+
130+
# Label iteration order is given by edgelist[rxorder. Actual edge drawing iteration order is given by edges(g)
131+
@test edgelist[rxorder] == Graphs.edges(img_)
132+
return rxorder
133+
end
134+
135+
# Test edge order for complexes.
136+
let
137+
# Multiple edges
138+
rn = @reaction_network begin
139+
k1, A --> B
140+
(k2, k3), C <--> D
141+
k4, A --> B
142+
end
143+
rxorder = test_edgeorder(rn)
144+
edgelabels = [repr(rx.rate) for rx in reactions(rn)]
145+
# Test internal order of labels is preserved
146+
@test edgelabels[rxorder][1] == "k1"
147+
@test edgelabels[rxorder][2] == "k4"
148+
149+
# Multiple edges with species dependencies
150+
rn = @reaction_network begin
151+
k1, A --> B
152+
(k2, k3), C <--> D
153+
k4, A --> B
154+
hillr(D, α, K, n), C --> D
155+
k5*B, A --> B
156+
end
157+
rxorder = test_edgeorder(rn)
158+
edgelabels = [repr(rx.rate) for rx in reactions(rn)]
159+
labels = ["k1", "k4", "k5*B(t)", "k2", "Catalyst.hillr(D(t), α, K, n)", "k3"]
160+
@test edgelabels[rxorder] == labels
161+
162+
rs = @reaction_network begin
163+
ka, Depot --> Central
164+
(k12, k21), Central <--> Peripheral
165+
ke, Central --> 0
166+
end
167+
test_edgeorder(rs)
168+
169+
rn = @reaction_network begin
170+
(k1, k2), A <--> B
171+
k3, C --> B
172+
(α, β), (A, B) --> C
173+
k4, B --> A
174+
(k5, k6), B <--> A
175+
k7, B --> C
176+
(k8, k9), C <--> A
177+
(k10, k11), (A, C) --> B
178+
(k12, k13), (C, B) --> A
179+
end
180+
rxorder = test_edgeorder(rn)
181+
edgelabels = [repr(rx.rate) for rx in reactions(rn)]
182+
@test edgelabels[rxorder][1:3] == ["k1", "k6", "k10"]
183+
end

0 commit comments

Comments
 (0)