Skip to content

Commit 30cf870

Browse files
committed
simplify iscomplexbalanced, rename adjacencymat, adjust implementation to be more similar
1 parent 05fbd4f commit 30cf870

File tree

2 files changed

+66
-71
lines changed

2 files changed

+66
-71
lines changed

src/network_analysis.jl

Lines changed: 59 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,6 @@ function laplacianmat(rn::ReactionSystem, pmap::Dict = Dict(); sparse = false)
206206
D * K
207207
end
208208

209-
Base.zero(::Type{Union{R, Symbolics.BasicSymbolic{Real}}}) where R <: Real = zero(R)
210-
Base.one(::Type{Union{R, Symbolics.BasicSymbolic{Real}}}) where R <: Real = one(R)
211-
212209
@doc raw"""
213210
fluxmat(rn::ReactionSystem, pmap = Dict(); sparse=false)
214211
@@ -225,8 +222,6 @@ function fluxmat(rn::ReactionSystem, pmap::Dict = Dict(); sparse=false)
225222
end
226223

227224
rcmap = reactioncomplexmap(rn)
228-
nc = length(rcmap)
229-
nr = length(rates)
230225
mtype = eltype(rates) <: Symbolics.BasicSymbolic ? Num : eltype(rates)
231226
if sparse
232227
return fluxmat(SparseMatrixCSC{mtype, Int}, rcmap, rates)
@@ -936,7 +931,7 @@ function isdetailedbalanced(rs::ReactionSystem, parametermap::Dict; abstol=0, re
936931
complexes, D = reactioncomplexes(rs)
937932
img = incidencematgraph(rs)
938933
undir_img = SimpleGraph(incidencematgraph(rs))
939-
K = ratematrix(rs, pmap)
934+
K = adjacencymat(rs, pmap)
940935

941936
spanning_forest = Graphs.kruskal_mst(undir_img)
942937
outofforest_edges = setdiff(collect(edges(undir_img)), spanning_forest)
@@ -993,52 +988,26 @@ function isdetailedbalanced(rs::ReactionSystem, parametermap)
993988
end
994989

995990
"""
996-
iscomplexbalanced(rs::ReactionSystem, parametermap)
991+
iscomplexbalanced(rs::ReactionSystem, parametermap; sparse = false)
997992
998993
Constructively compute whether a network will have complex-balanced equilibrium
999994
solutions, following the method in van der Schaft et al., [2015](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3).
1000-
Accepts a dictionary, vector, or tuple of variable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...].
1001-
"""
1002-
function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict)
1003-
if length(parametermap) != numparams(rs)
1004-
error("Incorrect number of parameters specified.")
1005-
end
1006995
1007-
pmap = symmap_to_varmap(rs, parametermap)
1008-
pmap = Dict(ModelingToolkit.value(k) => v for (k, v) in pmap)
1009-
1010-
sm = speciesmap(rs)
1011-
cm = reactioncomplexmap(rs)
1012-
complexes, D = reactioncomplexes(rs)
996+
Requires the specification of values for the parameters/rate constants. Accepts a dictionary, vector, or tuple of parameter-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...].
997+
"""
998+
function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict; sparse = false)
1013999
rxns = reactions(rs)
1014-
nc = length(complexes)
1015-
nr = numreactions(rs)
1016-
nm = numspecies(rs)
1017-
10181000
if !all(r -> ismassaction(r, rs), rxns)
10191001
error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being complex balanced is currently only supported for pure mass action networks.")
10201002
end
10211003

1022-
rates = [substitute(rate, pmap) for rate in reactionrates(rs)]
1023-
1024-
# Construct kinetic matrix, K
1025-
K = zeros(nr, nc)
1026-
for c in 1:nc
1027-
complex = complexes[c]
1028-
for (r, dir) in cm[complex]
1029-
rxn = rxns[r]
1030-
if dir == -1
1031-
K[r, c] = rates[r]
1032-
end
1033-
end
1034-
end
1035-
1036-
L = -D * K
1037-
S = netstoichmat(rs)
1004+
L = laplacianmat(rs, parametermap; sparse)
1005+
D = incidencemat(rs; sparse)
1006+
S = netstoichmat(rs; sparse)
10381007

10391008
# Compute ρ using the matrix-tree theorem
10401009
g = incidencematgraph(rs)
1041-
R = ratematrix(rs, rates)
1010+
R = adjacencymat(rs, parametermap; sparse)
10421011
ρ = matrixtree(g, R)
10431012

10441013
# Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T
@@ -1069,50 +1038,74 @@ function iscomplexbalanced(rs::ReactionSystem, parametermap)
10691038
end
10701039

10711040
"""
1072-
ratematrix(rs::ReactionSystem, parametermap)
1041+
adjacencymat(rs::ReactionSystem, pmap = Dict(); sparse = false)
10731042
10741043
Given a reaction system with n complexes, outputs an n-by-n matrix where R_{ij} is the rate
10751044
constant of the reaction between complex i and complex j. Accepts a dictionary, vector, or tuple
10761045
of variable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...].
1046+
1047+
Equivalent to the adjacency matrix of the weighted graph whose weights are the
1048+
reaction rates.
1049+
Returns a symbolic matrix by default, but will return a numerical matrix if parameter values are specified via pmap.
1050+
1051+
The difference between this matrix and [`fluxmat`](@ref) is quite subtle. The non-zero entries to both matrices are the rate constants. However:
1052+
- In `fluxmat`, the rows represent complexes and columns represent reactions, and an entry (c, r) is non-zero if reaction r's source complex is c.
1053+
- In `adjacencymat`, the rows and columns both represent complexes, and an entry (c1, c2) is non-zero if there is a reaction c1 --> c2.
10771054
"""
1078-
function ratematrix(rs::ReactionSystem, rates::Vector{Float64})
1079-
complexes, D = reactioncomplexes(rs)
1080-
n = length(complexes)
1081-
rxns = reactions(rs)
1082-
ratematrix = zeros(n, n)
1055+
function adjacencymat(rn::ReactionSystem, pmap::Dict = Dict(); sparse = false)
1056+
rates = if isempty(pmap)
1057+
reactionrates(rn)
1058+
else
1059+
substitutevals(rn, pmap, parameters(rn), reactionrates(rn))
1060+
end
1061+
mtype = eltype(rates) <: Symbolics.BasicSymbolic ? Num : eltype(rates)
10831062

1084-
for r in 1:length(rxns)
1085-
rxn = rxns[r]
1086-
s = findfirst(==(-1), @view D[:, r])
1087-
p = findfirst(==(1), @view D[:, r])
1088-
ratematrix[s, p] = rates[r]
1063+
if sparse
1064+
return adjacencymat(SparseMatrixCSC{mtype, Int}, incidencemat(rn), rates)
1065+
else
1066+
return adjacencymat(Matrix{mtype}, incidencemat(rn), rates)
10891067
end
1090-
ratematrix
10911068
end
10921069

1093-
function ratematrix(rs::ReactionSystem, parametermap::Dict)
1094-
if length(parametermap) != numparams(rs)
1095-
error("Incorrect number of parameters specified.")
1070+
function adjacencymat(::Type{SparseMatrixCSC{T, Int}}, D, rates) where T
1071+
Is = Int[]
1072+
Js = Int[]
1073+
Vs = T[]
1074+
nc = size(D, 1)
1075+
1076+
for r in 1:length(rates)
1077+
s = findfirst(==(-1), @view D[:, r])
1078+
p = findfirst(==(1), @view D[:, r])
1079+
push!(Is, s)
1080+
push!(Js, p)
1081+
push!(Vs, rates[r])
10961082
end
1083+
A = sparse(Is, Js, Vs, nc, nc)
1084+
end
10971085

1098-
pmap = symmap_to_varmap(rs, parametermap)
1099-
pmap = Dict(ModelingToolkit.value(k) => v for (k, v) in pmap)
1086+
function adjacencymat(::Type{Matrix{T}}, D, rates) where T
1087+
nc = size(D, 1)
1088+
A = zeros(T, nc, nc)
11001089

1101-
rates = [substitute(rate, pmap) for rate in reactionrates(rs)]
1102-
ratematrix(rs, rates)
1090+
for r in 1:length(rates)
1091+
s = findfirst(==(-1), @view D[:, r])
1092+
p = findfirst(==(1), @view D[:, r])
1093+
A[s, p] = rates[r]
1094+
end
1095+
A
11031096
end
11041097

1105-
function ratematrix(rs::ReactionSystem, parametermap::Vector{<:Pair})
1106-
pdict = Dict(parametermap)
1107-
ratematrix(rs, pdict)
1098+
function adjacencymat(rn::ReactionSystem, pmap::Vector{<:Pair}; sparse=false)
1099+
pdict = Dict(pmap)
1100+
adjacencymat(rn, pdict; sparse)
11081101
end
11091102

1110-
function ratematrix(rs::ReactionSystem, parametermap::Tuple)
1111-
pdict = Dict(parametermap)
1112-
ratematrix(rs, pdict)
1103+
function adjacencymat(rn::ReactionSystem, pmap::Tuple; sparse=false)
1104+
pdict = Dict(pmap)
1105+
adjacencymat(rn, pdict; sparse)
11131106
end
11141107

1115-
function ratematrix(rs::ReactionSystem, parametermap)
1108+
function adjacencymat(rn::ReactionSystem, pmap)
11161109
error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.")
11171110
end
11181111

test/network_analysis/crn_theory.jl

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Tests for properties from chemical reaction network theory: deficiency theorems, complex/detailed balance, etc.
22
using Catalyst, StableRNGs, LinearAlgebra, Test
33
rng = StableRNG(514)
4-
54
# Tests that `iscomplexbalanced` works for different rate inputs.
65
# Tests that non-valid rate input yields and error
76
let
@@ -57,11 +56,14 @@ let
5756
rates_invalid = reshape(rate_vals, 1, 8)
5857

5958
# Tests that all input types generates the correct rate matrix.
60-
Catalyst.ratematrix(rn, rate_vals) == rate_mat
61-
Catalyst.ratematrix(rn, rates_vec) == rate_mat
62-
Catalyst.ratematrix(rn, rates_tup) == rate_mat
63-
Catalyst.ratematrix(rn, rates_dict) == rate_mat
59+
@test Catalyst.adjacencymat(rn, rates_vec) == rate_mat
60+
@test Catalyst.adjacencymat(rn, rates_tup) == rate_mat
61+
@test Catalyst.adjacencymat(rn, rates_dict) == rate_mat
62+
@test_throws Exception Catalyst.adjacencymat(rn, rate_vals)
6463
@test_throws Exception Catalyst.iscomplexbalanced(rn, rates_invalid)
64+
65+
# Test sparse matrix
66+
@test Catalyst.adjacencymat(rn, rates_vec; sparse = true) == rate_mat
6567
end
6668

6769
### CONCENTRATION ROBUSTNESS TESTS

0 commit comments

Comments
 (0)