Skip to content

Commit f3fd160

Browse files
committed
Merge remote-tracking branch 'origin/master' into BK
2 parents a4a4620 + 87cb28d commit f3fd160

File tree

3 files changed

+298
-10
lines changed

3 files changed

+298
-10
lines changed

docs/Project.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
3535
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
3636
SteadyStateDiffEq = "9672c7b4-1e72-59bd-8a11-6ac3964bc41f"
3737
StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0"
38-
#StructuralIdentifiability = "220ca800-aa68-49bb-acd8-6037fa93a544"
3938
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
4039

4140
[compat]
@@ -58,7 +57,7 @@ JumpProcesses = "9.13.2"
5857
Latexify = "0.16.5"
5958
LinearSolve = "2.30"
6059
ModelingToolkit = "9.32"
61-
NonlinearSolve = "3.12"
60+
NonlinearSolve = "3.12, 4"
6261
Optim = "1.9"
6362
Optimization = "4"
6463
OptimizationBBO = "0.4"
@@ -74,5 +73,4 @@ SpecialFunctions = "2.4"
7473
StaticArrays = "1.9"
7574
SteadyStateDiffEq = "2.2"
7675
StochasticDiffEq = "6.65"
77-
#StructuralIdentifiability = "0.5.8"
7876
Symbolics = "5.30.1, 6"

src/network_analysis.jl

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,19 @@ function isterminal(lc::Vector, rn::ReactionSystem)
398398
true
399399
end
400400

401+
function isforestlike(rn::ReactionSystem)
402+
subnets = subnetworks(rn)
403+
nps = get_networkproperties(rn)
404+
405+
isempty(nps.incidencemat) && reactioncomplexes(rn)
406+
sparseig = issparse(nps.incidencemat)
407+
for subnet in subnets
408+
nps = get_networkproperties(subnet)
409+
isempty(nps.incidencemat) && reactioncomplexes(subnet; sparse = sparseig)
410+
end
411+
all(Graphs.is_tree SimpleGraph incidencematgraph, subnets)
412+
end
413+
401414
@doc raw"""
402415
deficiency(rn::ReactionSystem)
403416
@@ -724,14 +737,93 @@ function conservationlaw_errorcheck(rs, pre_varmap)
724737
error("The system has conservation laws but initial conditions were not provided for some species.")
725738
end
726739

740+
"""
741+
isdetailedbalanced(rs::ReactionSystem, parametermap; reltol=1e-9, abstol)
742+
743+
Constructively compute whether a kinetic system (a reaction network with a set of rate constants) will admit detailed-balanced equilibrium
744+
solutions, using the Wegscheider conditions, [Feinberg, 1989](https://www.sciencedirect.com/science/article/pii/0009250989851243). A detailed-balanced solution is one for which the rate of every forward reaction exactly equals its reverse reaction. Accepts a dictionary, vector, or tuple of variable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...].
745+
"""
746+
function isdetailedbalanced(rs::ReactionSystem, parametermap::Dict; abstol=0, reltol=1e-9)
747+
if length(parametermap) != numparams(rs)
748+
error("Incorrect number of parameters specified.")
749+
elseif !isreversible(rs)
750+
return false
751+
elseif !all(r -> ismassaction(r, rs), reactions(rs))
752+
error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being complex balanced is currently only supported for pure mass action networks.")
753+
end
754+
755+
isforestlike(rs) && deficiency(rs) == 0 && return true
756+
757+
pmap = symmap_to_varmap(rs, parametermap)
758+
pmap = Dict(ModelingToolkit.value(k) => v for (k, v) in pmap)
759+
760+
# Construct reaction-complex graph
761+
complexes, D = reactioncomplexes(rs)
762+
img = incidencematgraph(rs)
763+
undir_img = SimpleGraph(incidencematgraph(rs))
764+
K = ratematrix(rs, pmap)
765+
766+
spanning_forest = Graphs.kruskal_mst(undir_img)
767+
outofforest_edges = setdiff(collect(edges(undir_img)), spanning_forest)
768+
769+
# Independent Cycle Conditions: for any cycle we create by adding in an out-of-forest reaction, the product of forward reaction rates over the cycle must equal the product of reverse reaction rates over the cycle.
770+
for edge in outofforest_edges
771+
g = SimpleGraph([spanning_forest..., edge])
772+
ic = Graphs.cycle_basis(g)[1]
773+
fwd = prod([K[ic[r], ic[r + 1]] for r in 1:(length(ic) - 1)]) * K[ic[end], ic[1]]
774+
rev = prod([K[ic[r + 1], ic[r]] for r in 1:(length(ic) - 1)]) * K[ic[1], ic[end]]
775+
isapprox(fwd, rev; atol = abstol, rtol = reltol) ? continue : return false
776+
end
777+
778+
# Spanning Forest Conditions: for non-deficiency 0 networks, we get an additional δ equations. Choose an orientation for each reaction pair in the spanning forest (we will take the one given by default from kruskal_mst).
779+
780+
if deficiency(rs) > 0
781+
rxn_idxs = [edgeindex(D, Graphs.src(e), Graphs.dst(e)) for e in spanning_forest]
782+
S_F = netstoichmat(rs)[:, rxn_idxs]
783+
sols = positive_nullspace(S_F)
784+
785+
for i in 1:size(sols, 2)
786+
α = sols[:, i]
787+
fwd = prod([K[Graphs.src(e), Graphs.dst(e)]^α[i]
788+
for (e, i) in zip(spanning_forest, 1:length(α))])
789+
rev = prod([K[Graphs.dst(e), Graphs.src(e)]^α[i]
790+
for (e, i) in zip(spanning_forest, 1:length(α))])
791+
isapprox(fwd, rev; atol = abstol, rtol = reltol) ? continue : return false
792+
end
793+
end
794+
795+
true
796+
end
797+
798+
# Helper to find the index of the reaction with a given reactant and product complex.
799+
function edgeindex(imat, src::T, dst::T) where T <: Int
800+
for i in 1:size(imat, 2)
801+
(imat[src, i] == -1) && (imat[dst, i] == 1) && return i
802+
end
803+
error("This edge does not exist in this reaction graph.")
804+
end
805+
806+
function isdetailedbalanced(rs::ReactionSystem, parametermap::Vector{<:Pair})
807+
pdict = Dict(parametermap)
808+
isdetailedbalanced(rs, pdict)
809+
end
810+
811+
function isdetailedbalanced(rs::ReactionSystem, parametermap::Tuple{<:Pair})
812+
pdict = Dict(parametermap)
813+
isdetailedbalanced(rs, pdict)
814+
end
815+
816+
function isdetailedbalanced(rs::ReactionSystem, parametermap)
817+
error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.")
818+
end
819+
727820
"""
728821
iscomplexbalanced(rs::ReactionSystem, parametermap)
729822
730823
Constructively compute whether a network will have complex-balanced equilibrium
731824
solutions, following the method in van der Schaft et al., [2015](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3).
732825
Accepts a dictionary, vector, or tuple of variable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...].
733826
"""
734-
735827
function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict)
736828
if length(parametermap) != numparams(rs)
737829
error("Incorrect number of parameters specified.")
@@ -808,7 +900,6 @@ end
808900
constant of the reaction between complex i and complex j. Accepts a dictionary, vector, or tuple
809901
of variable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...].
810902
"""
811-
812903
function ratematrix(rs::ReactionSystem, rates::Vector{Float64})
813904
complexes, D = reactioncomplexes(rs)
814905
n = length(complexes)
@@ -903,7 +994,6 @@ end
903994
Returns the matrix of a basis of cycles (or flux vectors), or a basis for reaction fluxes for which the system is at steady state.
904995
These correspond to right eigenvectors of the stoichiometric matrix. Equivalent to [`fluxmodebasis`](@ref).
905996
"""
906-
907997
function cycles(rs::ReactionSystem)
908998
nps = get_networkproperties(rs)
909999
nsm = netstoichmat(rs)
@@ -938,7 +1028,6 @@ end
9381028
9391029
See documentation for [`cycles`](@ref).
9401030
"""
941-
9421031
function fluxvectors(rs::ReactionSystem)
9431032
cycles(rs)
9441033
end
@@ -950,7 +1039,6 @@ end
9501039
9511040
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.
9521041
"""
953-
9541042
function satisfiesdeficiencyone(rn::ReactionSystem)
9551043
all(r -> ismassaction(r, rn), reactions(rn)) ||
9561044
error("The deficiency one theorem is only valid for reaction networks that are mass action.")
@@ -973,7 +1061,6 @@ end
9731061
9741062
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.
9751063
"""
976-
9771064
function satisfiesdeficiencyzero(rn::ReactionSystem)
9781065
all(r -> ismassaction(r, rn), reactions(rn)) ||
9791066
error("The deficiency zero theorem is only valid for reaction networks that are mass action.")
@@ -988,7 +1075,6 @@ end
9881075
9891076
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.
9901077
"""
991-
9921078
function robustspecies(rn::ReactionSystem)
9931079
complexes, D = reactioncomplexes(rn)
9941080
nps = get_networkproperties(rn)

0 commit comments

Comments
 (0)