Skip to content

Commit

Permalink
Merge pull request #964 from vyudu/deficiency-one
Browse files Browse the repository at this point in the history
Deficiency one and concnetration robustness
  • Loading branch information
vyudu authored Jul 26, 2024
2 parents e8feb37 + 19309dc commit 87d02ff
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 7 deletions.
99 changes: 94 additions & 5 deletions src/network_analysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -421,11 +421,15 @@ end
"""
function deficiency(rn::ReactionSystem)
nps = get_networkproperties(rn)
conservationlaws(rn)
r = nps.rank
ig = incidencematgraph(rn)
lc = linkageclasses(rn)
nps.deficiency = Graphs.nv(ig) - length(lc) - r

# Check if deficiency has been computed already (initialized to -1)
if nps.deficiency == -1
conservationlaws(rn)
r = nps.rank
ig = incidencematgraph(rn)
lc = linkageclasses(rn)
nps.deficiency = Graphs.nv(ig) - length(lc) - r
end
nps.deficiency
end

Expand Down Expand Up @@ -938,3 +942,88 @@ end
function fluxvectors(rs::ReactionSystem)
cycles(rs)
end

### Deficiency one

"""
satisfiesdeficiencyone(rn::ReactionSystem)
Check if a reaction network obeys the conditions of the deficiency one theorem, which ensures that there is only one equilibrium for every positive stoichiometric compatibility class.
"""

function satisfiesdeficiencyone(rn::ReactionSystem)
all(r -> ismassaction(r, rn), reactions(rn)) ||
error("The deficiency one theorem is only valid for reaction networks that are mass action.")
complexes, D = reactioncomplexes(rn)
δ = deficiency(rn)
δ_l = linkagedeficiencies(rn)

lcs = linkageclasses(rn)
tslcs = terminallinkageclasses(rn)

# Check the conditions for the deficiency one theorem:
# 1) the deficiency of each individual linkage class is at most 1;
# 2) the sum of the linkage deficiencies is the total deficiency, and
# 3) there is only one terminal linkage class per linkage class.
all(<=(1), δ_l) && (sum(δ_l) == δ) && (length(lcs) == length(tslcs))
end

"""
satisfiesdeficiencyzero(rn::ReactionSystem)
Check if a reaction network obeys the conditions of the deficiency zero theorem, which ensures that there is only one equilibrium for every positive stoichiometric compatibility class, this equilibrium is asymptotically stable, and this equilibrium is complex balanced.
"""

function satisfiesdeficiencyzero(rn::ReactionSystem)
all(r -> ismassaction(r, rn), reactions(rn)) ||
error("The deficiency zero theorem is only valid for reaction networks that are mass action.")
δ = deficiency(rn)
δ == 0 && isweaklyreversible(rn, subnetworks(rn))
end

"""
robustspecies(rn::ReactionSystem)
Return a vector of indices corresponding to species that are concentration robust, i.e. for every positive equilbrium, the concentration of species s will be the same.
Note: This function currently only works for networks of deficiency one, and is not currently guaranteed to return *all* the concentration-robust species in the network. Any species returned by the function will be robust, but this may not include all of them. Use with caution. Support for higher deficiency networks and necessary conditions for robustness will be coming in the future.
"""

function robustspecies(rn::ReactionSystem)
complexes, D = reactioncomplexes(rn)
nps = get_networkproperties(rn)

if deficiency(rn) != 1
error("This algorithm currently only checks for robust species in networks with deficiency one.")
end

# A species is concentration robust in a deficiency one network if there are two non-terminal complexes (i.e. complexes
# belonging to a linkage class that is not terminal) that differ only in species s (i.e. their difference is some
# multiple of s. (A + B, A) differ only in B. (A + 2B, B) differ in both A and B, since A + 2B - B = A + B).

if !nps.checkedrobust
tslcs = terminallinkageclasses(rn)
Z = complexstoichmat(rn)

# Find the complexes that do not belong to a terminal linkage class
nonterminal_complexes = deleteat!([1:length(complexes);], vcat(tslcs...))
robust_species = Int64[]

for (c_s, c_p) in Combinatorics.combinations(nonterminal_complexes, 2)
# Check the difference of all the combinations of complexes. The support is the set of indices that are non-zero
suppcnt = 0
supp = 0
for i in 1:size(Z, 1)
(Z[i, c_s] != Z[i, c_p]) && (suppcnt += 1; supp = i)
(suppcnt > 1) && break
end

# If the support has length one, then they differ in only one species, and that species is concentration robust.
(suppcnt == 1) && (supp robust_species) && push!(robust_species, supp)
end
nps.checkedrobust = true
nps.robustspecies = robust_species
end

nps.robustspecies
end
9 changes: 7 additions & 2 deletions src/reactionsystem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ Base.@kwdef mutable struct NetworkProperties{I <: Integer, V <: BasicSymbolic{Re
linkageclasses::Vector{Vector{Int}} = Vector{Vector{Int}}(undef, 0)
stronglinkageclasses::Vector{Vector{Int}} = Vector{Vector{Int}}(undef, 0)
terminallinkageclasses::Vector{Vector{Int}} = Vector{Vector{Int}}(undef, 0)
deficiency::Int = 0

checkedrobust::Bool = false
robustspecies::Vector{Int} = Vector{Int}(undef, 0)
deficiency::Int = -1
end
#! format: on

Expand Down Expand Up @@ -137,7 +140,9 @@ function reset!(nps::NetworkProperties{I, V}) where {I, V}
empty!(nps.linkageclasses)
empty!(nps.stronglinkageclasses)
empty!(nps.terminallinkageclasses)
nps.deficiency = 0
nps.deficiency = -1
empty!(nps.robustspecies)
nps.checkedrobust = false

# this needs to be last due to setproperty! setting it to false
nps.isempty = true
Expand Down
120 changes: 120 additions & 0 deletions test/network_analysis/network_properties.jl
Original file line number Diff line number Diff line change
Expand Up @@ -608,3 +608,123 @@ let
Catalyst.ratematrix(rn, rates_dict) == rate_mat
@test_throws Exception Catalyst.iscomplexbalanced(rn, rates_invalid)
end

### CONCENTRATION ROBUSTNESS TESTS

# Check whether concentration-robust species are correctly identified for two well-known reaction networks: the glyoxylate IDHKP-IDH system, and the EnvZ_OmpR signaling pathway.

let
IDHKP_IDH = @reaction_network begin
(k1, k2), EIp + I <--> EIpI
k3, EIpI --> EIp + Ip
(k4, k5), E + Ip <--> EIp
k6, EIp --> E + I
end

@test Catalyst.robustspecies(IDHKP_IDH) == [2]
end

let
EnvZ_OmpR = @reaction_network begin
(k1, k2), X <--> XT
k3, XT --> Xp
(k4, k5), Xp + Y <--> XpY
k6, XpY --> X + Yp
(k7, k8), XT + Yp <--> XTYp
k9, XTYp --> XT + Y
end

@test Catalyst.robustspecies(EnvZ_OmpR) == [6]
end

### DEFICIENCY ONE TESTS

# Fails because there are two terminal linkage classes in the linkage class
let
rn = @reaction_network begin
k1, A + B --> 2B
k2, A + B --> 2A
end

@test Catalyst.satisfiesdeficiencyone(rn) == false
end

# Fails because linkage deficiencies do not sum to total deficiency
let
rn = @reaction_network begin
(k1, k2), A <--> 2A
(k3, k4), A + B <--> C
(k5, k6), C <--> B
end

@test Catalyst.satisfiesdeficiencyone(rn) == false
end

# Fails because a linkage class has deficiency two
let
rn = @reaction_network begin
k1, 3A --> A + 2B
k2, A + 2B --> 3B
k3, 3B --> 2A + B
k4, 2A + B --> 3A
end

@test Catalyst.satisfiesdeficiencyone(rn) == false
end

let
rn = @reaction_network begin
(k1, k2), 2A <--> D
(k3, k4), D <--> A + B
(k5, k6), A + B <--> C
(k7, k8), C <--> 2B
(k9, k10), C + D <--> E + F
(k11, k12), E + F <--> H
(k13, k14), H <--> C + E
(k15, k16), C + E <--> D + F
(k17, k18), A + D <--> G
(k19, k20), G <--> B + H
end

@test Catalyst.satisfiesdeficiencyone(rn) == true
end

### Some tests for deficiency zero networks.

let
rn = @reaction_network begin
(k1, k2), A <--> 2B
(k3, k4), A + C <--> D
k5, D --> B + E
k6, B + E --> A + C
end

# No longer weakly reversible
rn2 = @reaction_network begin
(k1, k2), A <--> 2B
(k3, k4), A + C <--> D
k5, B + E --> D
k6, B + E --> A + C
end

# No longer weakly reversible
rn3 = @reaction_network begin
k1, A --> 2B
(k3, k4), A + C <--> D
k5, D --> B + E
k6, B + E --> A + C
end

# Weakly reversible but deficiency one
rn4 = @reaction_network begin
(k1, k2), A <--> 2A
(k3, k4), A + B <--> C
(k5, k6), C <--> B
end

@test Catalyst.satisfiesdeficiencyzero(rn) == true
@test Catalyst.satisfiesdeficiencyzero(rn2) == false
@test Catalyst.satisfiesdeficiencyzero(rn3) == false
@test Catalyst.satisfiesdeficiencyzero(rn4) == false
end

0 comments on commit 87d02ff

Please sign in to comment.