Skip to content

Commit 5ef4f1a

Browse files
committed
init
1 parent b06cfee commit 5ef4f1a

File tree

1 file changed

+104
-20
lines changed

1 file changed

+104
-20
lines changed

src/network_analysis.jl

Lines changed: 104 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
function filter_constspecs(specs, stoich::AbstractVector{V}, smap) where {V <: Integer}
55
isempty(specs) && (return Vector{Int}(), Vector{V}())
66

7+
# If any species are constant, go through these manually and add their indices and
8+
# stoichiometries to `ids` and `filtered_stoich`.
79
if any(isconstant, specs)
810
ids = Vector{Int}()
911
filtered_stoich = Vector{V}()
@@ -44,11 +46,15 @@ function reactioncomplexmap(rn::ReactionSystem)
4446
!isempty(nps.complextorxsmap) && return nps.complextorxsmap
4547
complextorxsmap = nps.complextorxsmap
4648

49+
# Retrieves system reactions and a map from species to their index in the species vector.
4750
rxs = reactions(rn)
4851
smap = speciesmap(rn)
4952
numreactions(rn) > 0 ||
5053
error("There must be at least one reaction to find reaction complexes.")
54+
5155
for (i, rx) in enumerate(rxs)
56+
# Create the `ReactionComplex` corresponding to the reaction's substrates. Adds it
57+
# to the reaction complex dictionary (recording it as the substrates of the i'th reaction).
5258
subids, substoich = filter_constspecs(rx.substrates, rx.substoich, smap)
5359
subrc = sort!(ReactionComplex(subids, substoich))
5460
if haskey(complextorxsmap, subrc)
@@ -57,6 +63,8 @@ function reactioncomplexmap(rn::ReactionSystem)
5763
complextorxsmap[subrc] = [i => -1]
5864
end
5965

66+
# Create the `ReactionComplex` corresponding to the reaction's products. Adds it
67+
# to the reaction complex dictionary (recording it as the products of the i'th reaction).
6068
prodids, prodstoich = filter_constspecs(rx.products, rx.prodstoich, smap)
6169
prodrc = sort!(ReactionComplex(prodids, prodstoich))
6270
if haskey(complextorxsmap, prodrc)
@@ -94,7 +102,10 @@ function reactioncomplexes(rn::ReactionSystem; sparse = false)
94102
isempty(get_systems(rn)) ||
95103
error("reactioncomplexes does not currently support subsystems.")
96104
nps = get_networkproperties(rn)
105+
106+
# If the complexes have not been cached, or the cached complexes uses a different sparsity.
97107
if isempty(nps.complexes) || (sparse != issparse(nps.complexes))
108+
# Computes the reaction complex dictionary. Use it to create a sparse/dense matrix.
98109
complextorxsmap = reactioncomplexmap(rn)
99110
nps.complexes, nps.incidencemat = if sparse
100111
reactioncomplexes(SparseMatrixCSC{Int, Int}, rn, complextorxsmap)
@@ -105,8 +116,11 @@ function reactioncomplexes(rn::ReactionSystem; sparse = false)
105116
nps.complexes, nps.incidencemat
106117
end
107118

119+
# Creates a *sparse* reaction complex matrix.
108120
function reactioncomplexes(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem,
109121
complextorxsmap)
122+
# Computes the I, J, and V vectors used for the sparse matrix (read about sparse matrix
123+
# representation for more information).
110124
complexes = collect(keys(complextorxsmap))
111125
Is = Int[]
112126
Js = Int[]
@@ -122,6 +136,7 @@ function reactioncomplexes(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem
122136
complexes, B
123137
end
124138

139+
# Creates a *dense* reaction complex matrix.
125140
function reactioncomplexes(::Type{Matrix{Int}}, rn::ReactionSystem, complextorxsmap)
126141
complexes = collect(keys(complextorxsmap))
127142
B = zeros(Int, length(complexes), numreactions(rn))
@@ -158,6 +173,9 @@ function complexstoichmat(rn::ReactionSystem; sparse = false)
158173
isempty(get_systems(rn)) ||
159174
error("complexstoichmat does not currently support subsystems.")
160175
nps = get_networkproperties(rn)
176+
177+
# If the complexes stoichiometry matrix has not been cached, or the cached one uses a
178+
# different sparsity, computes (and caches) it.
161179
if isempty(nps.complexstoichmat) || (sparse != issparse(nps.complexstoichmat))
162180
nps.complexstoichmat = if sparse
163181
complexstoichmat(SparseMatrixCSC{Int, Int}, rn, keys(reactioncomplexmap(rn)))
@@ -168,7 +186,10 @@ function complexstoichmat(rn::ReactionSystem; sparse = false)
168186
nps.complexstoichmat
169187
end
170188

189+
# Creates a *sparse* reaction complex stoichiometry matrix.
171190
function complexstoichmat(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, rcs)
191+
# Computes the I, J, and V vectors used for the sparse matrix (read about sparse matrix
192+
# representation for more information).
172193
Is = Int[]
173194
Js = Int[]
174195
Vs = Int[]
@@ -182,6 +203,7 @@ function complexstoichmat(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem,
182203
Z = sparse(Is, Js, Vs, numspecies(rn), length(rcs))
183204
end
184205

206+
# Creates a *dense* reaction complex stoichiometry matrix.
185207
function complexstoichmat(::Type{Matrix{Int}}, rn::ReactionSystem, rcs)
186208
Z = zeros(Int, numspecies(rn), length(rcs))
187209
for (i, rc) in enumerate(rcs)
@@ -213,6 +235,9 @@ function complexoutgoingmat(rn::ReactionSystem; sparse = false)
213235
isempty(get_systems(rn)) ||
214236
error("complexoutgoingmat does not currently support subsystems.")
215237
nps = get_networkproperties(rn)
238+
239+
# If the outgoing complexes matrix has not been cached, or the cached one uses a
240+
# different sparsity, computes (and caches) it.
216241
if isempty(nps.complexoutgoingmat) || (sparse != issparse(nps.complexoutgoingmat))
217242
B = reactioncomplexes(rn, sparse = sparse)[2]
218243
nps.complexoutgoingmat = if sparse
@@ -224,16 +249,22 @@ function complexoutgoingmat(rn::ReactionSystem; sparse = false)
224249
nps.complexoutgoingmat
225250
end
226251

252+
# Creates a *sparse* outgoing reaction complex stoichiometry matrix.
227253
function complexoutgoingmat(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, B)
254+
# Computes the I, J, and V vectors used for the sparse matrix (read about sparse matrix
255+
# representation for more information).
228256
n = size(B, 2)
229257
rows = rowvals(B)
230258
vals = nonzeros(B)
231259
Is = Int[]
232260
Js = Int[]
233261
Vs = Int[]
262+
263+
# Allocates space to the vectors (so that it is not done incrementally in the loop).
234264
sizehint!(Is, div(length(vals), 2))
235265
sizehint!(Js, div(length(vals), 2))
236266
sizehint!(Vs, div(length(vals), 2))
267+
237268
for j in 1:n
238269
for i in nzrange(B, j)
239270
if vals[i] != one(eltype(vals))
@@ -246,6 +277,7 @@ function complexoutgoingmat(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSyste
246277
sparse(Is, Js, Vs, size(B, 1), size(B, 2))
247278
end
248279

280+
# Creates a *dense* outgoing reaction complex stoichiometry matrix.
249281
function complexoutgoingmat(::Type{Matrix{Int}}, rn::ReactionSystem, B)
250282
Δ = copy(B)
251283
for (I, b) in pairs(Δ)
@@ -284,10 +316,14 @@ function incidencematgraph(rn::ReactionSystem)
284316
nps.incidencegraph
285317
end
286318

319+
# Computes the incidence graph from an *dense* incidence matrix.
287320
function incidencematgraph(incidencemat::Matrix{Int})
288321
@assert all(([-1, 0, 1]), incidencemat)
289322
n = size(incidencemat, 1) # no. of nodes/complexes
290323
graph = Graphs.DiGraph(n)
324+
325+
# Walks through each column (corresponds to reactions). For each, find the input and output
326+
# complex and add an edge representing these to the incidence graph.
291327
for col in eachcol(incidencemat)
292328
src = 0
293329
dst = 0
@@ -301,12 +337,16 @@ function incidencematgraph(incidencemat::Matrix{Int})
301337
return graph
302338
end
303339

340+
# Computes the incidence graph from an *sparse* incidence matrix.
304341
function incidencematgraph(incidencemat::SparseMatrixCSC{Int, Int})
305342
@assert all(([-1, 0, 1]), incidencemat)
306343
m, n = size(incidencemat)
307344
graph = Graphs.DiGraph(m)
308345
rows = rowvals(incidencemat)
309346
vals = nonzeros(incidencemat)
347+
348+
# Loops through the (n) columns. For each column, directly find the index of the input
349+
# and output complex and add an edge representing these to the incidence graph.
310350
for j in 1:n
311351
inds = nzrange(incidencemat, j)
312352
row = rows[inds]
@@ -387,32 +427,43 @@ rcs,incidencemat = reactioncomplexes(sir)
387427
```
388428
"""
389429
function deficiency(rn::ReactionSystem)
430+
# Precomputes required information. `conservationlaws` caches the conservation laws in `rn`.
390431
nps = get_networkproperties(rn)
391432
conservationlaws(rn)
392433
r = nps.rank
393434
ig = incidencematgraph(rn)
394435
lc = linkageclasses(rn)
436+
437+
# Computes deficiency using its formula. Caches and returns it as output.
395438
nps.deficiency = Graphs.nv(ig) - length(lc) - r
396439
nps.deficiency
397440
end
398441

399-
# Used in the subsequent function.
442+
# For a linkage class (set of reaction complexes that form an isolated sub-graph), and some
443+
# additional information of the full network, find the reactions, species, and parameters
444+
# that constitute the corresponding sub-reaction network.
400445
function subnetworkmapping(linkageclass, allrxs, complextorxsmap, p)
446+
# Finds the reactions that are part of teh sub-reaction network.
401447
rxinds = sort!(collect(Set(rxidx for rcidx in linkageclass
402448
for rxidx in complextorxsmap[rcidx])))
403-
rxs = allrxs[rxinds]
404-
specset = Set(s for rx in rxs for s in rx.substrates if !isconstant(s))
405-
for rx in rxs
449+
newrxs = allrxs[rxinds]
450+
451+
# Find the species that are part of the sub-reaction network.
452+
specset = Set(s for rx in newrxs for s in rx.substrates if !isconstant(s))
453+
for rx in newrxs
406454
for product in rx.products
407455
!isconstant(product) && push!(specset, product)
408456
end
409457
end
410-
specs = collect(specset)
458+
newspecs = collect(specset)
459+
460+
# Find the parameters that are part of the sub-reaction network.
411461
newps = Vector{eltype(p)}()
412-
for rx in rxs
462+
for rx in newrxs
413463
Symbolics.get_variables!(newps, rx.rate, p)
414464
end
415-
rxs, specs, newps # reactions and species involved in reactions of subnetwork
465+
466+
newrxs, newspecs, newps
416467
end
417468

418469
"""
@@ -436,18 +487,23 @@ subnetworks(sir)
436487
"""
437488
function subnetworks(rs::ReactionSystem)
438489
isempty(get_systems(rs)) || error("subnetworks does not currently support subsystems.")
490+
491+
# Retrieves required components. `linkageclasses` caches linkage classes in `rs`.
439492
lcs = linkageclasses(rs)
440493
rxs = reactions(rs)
441494
p = parameters(rs)
442495
t = get_iv(rs)
443496
spatial_ivs = get_sivs(rs)
444497
complextorxsmap = [map(first, rcmap) for rcmap in values(reactioncomplexmap(rs))]
445498
subnetworks = Vector{ReactionSystem}()
499+
500+
# Loops through each sub-graph of connected reaction complexes. For each, create a
501+
# new `ReactionSystem` model and pushes it to the subnetworks vector.
446502
for i in 1:length(lcs)
447-
reacs, specs, newps = subnetworkmapping(lcs[i], rxs, complextorxsmap, p)
503+
newrxs, newspecs, newps = subnetworkmapping(lcs[i], rxs, complextorxsmap, p)
448504
newname = Symbol(nameof(rs), "_", i)
449505
push!(subnetworks,
450-
ReactionSystem(reacs, t, specs, newps; name = newname, spatial_ivs))
506+
ReactionSystem(newrxs, t, newspecs, newps; name = newname, spatial_ivs))
451507
end
452508
subnetworks
453509
end
@@ -475,6 +531,9 @@ function linkagedeficiencies(rs::ReactionSystem)
475531
lcs = linkageclasses(rs)
476532
subnets = subnetworks(rs)
477533
δ = zeros(Int, length(lcs))
534+
535+
# For each sub-reaction network of the reaction network, compute its deficiency. Returns
536+
# the full vector of deficiencies for each sub-reaction network.
478537
for (i, subnet) in enumerate(subnets)
479538
conservationlaws(subnet)
480539
nps = get_networkproperties(subnet)
@@ -531,11 +590,16 @@ function isweaklyreversible(rn::ReactionSystem, subnets)
531590
im = get_networkproperties(rn).incidencemat
532591
isempty(im) &&
533592
error("Error, please call reactioncomplexes(rn::ReactionSystem) to ensure the incidence matrix has been cached.")
593+
594+
# For each sub-reaction network, caches its reaction complexes.
534595
sparseig = issparse(im)
535596
for subnet in subnets
536597
nps = get_networkproperties(subnet)
537598
isempty(nps.incidencemat) && reactioncomplexes(subnet; sparse = sparseig)
538599
end
600+
601+
# the network is weakly reversible if each sub-network's incidenc graph is strongl
602+
# connec (i.e. each node is reachable from each node).
539603
all(Graphs.is_strongly_connected incidencematgraph, subnets)
540604
end
541605

@@ -645,25 +709,43 @@ end
645709

646710
# Used in the subsequent function.
647711
function cache_conservationlaw_eqs!(rn::ReactionSystem, N::AbstractMatrix, col_order)
712+
# Retrieves nullity (the number of conservation laws). `r` is the rank of the netstoichmat.
648713
nullity = size(N, 1)
649-
r = numspecies(rn) - nullity # rank of the netstoichmat
650-
sts = species(rn)
714+
r = numspecies(rn) - nullity
715+
716+
# Creates vectors of all independent and dependent species (those that are not, and are,
717+
# eliminated by the conservation laws). Get vectors both with their indexes and the actual
718+
# species symbolic variables.
719+
sps = species(rn)
651720
indepidxs = col_order[begin:r]
652-
indepspecs = sts[indepidxs]
721+
indepspecs = sps[indepidxs]
653722
depidxs = col_order[(r + 1):end]
654-
depspecs = sts[depidxs]
723+
depspecs = sps[depidxs]
724+
725+
# Declares the conservation law parameters.
655726
constants = MT.unwrap.(MT.scalarize(only(
656727
@parameters $(CONSERVED_CONSTANT_SYMBOL)[1:nullity] [conserved=true])))
657728

729+
# Computes the equations for (examples uses simple two-state system, `X1 <--> X2`):
730+
# - The species eliminated through conservation laws (`conservedeqs`). E.g. `[X2 ~ X1 - Γ[1]]`.
731+
# - The conserved quantity parameters (`constantdefs`). E.g. `[Γ[1] ~ X1 + X2]`.
658732
conservedeqs = Equation[]
659733
constantdefs = Equation[]
734+
735+
# For each conserved quantity.
660736
for (i, depidx) in enumerate(depidxs)
737+
# Finds the coefficient (in the conservation law) of the species that is eliminated
738+
# by this conservation law.
661739
scaleby = (N[i, depidx] != 1) ? N[i, depidx] : one(eltype(N))
662740
(scaleby != 0) || error("Error, found a zero in the conservation law matrix where "
663-
*
664-
"one was not expected.")
741+
* "one was not expected.")
742+
743+
# Creates, for this conservation law, the sum of all independent species (weighted by
744+
# the ratio between the coefficient of the species and the species which is elimianted.
665745
coefs = @view N[i, indepidxs]
666-
terms = sum(p -> p[1] / scaleby * p[2], zip(coefs, indepspecs))
746+
terms = sum((coef, sp) -> coef / scaleby * sp, zip(coefs, indepspecs))
747+
748+
# Computes the two equations corresponding to this conserved quantity.
667749
eq = depspecs[i] ~ constants[i] - terms
668750
push!(conservedeqs, eq)
669751
eq = constants[i] ~ depspecs[i] + terms
@@ -695,6 +777,8 @@ Notes:
695777
function conservationlaws(rs::ReactionSystem)
696778
nps = get_networkproperties(rs)
697779
!isempty(nps.conservationmat) && (return nps.conservationmat)
780+
781+
# If the conservation law matrix is not computed, do so and caches the result.
698782
nsm = netstoichmat(rs)
699783
nps.conservationmat = conservationlaws(nsm; col_order = nps.col_order)
700784
cache_conservationlaw_eqs!(rs, nps.conservationmat, nps.col_order)
@@ -708,13 +792,13 @@ Compute conserved quantities for a system with the given conservation laws.
708792
"""
709793
conservedquantities(state, cons_laws) = cons_laws * state
710794

711-
# If u0s are not given while conservation laws are present, throws an error.
795+
# If u0s are not given while conservation laws are present, throw an error.
712796
# Used in HomotopyContinuation and BifurcationKit extensions.
713-
# Currently only checks if any u0s are given
714-
# (not whether these are enough for computing conserved quantitites, this will yield a less informative error).
797+
# Currently only checks if any u0s are given (not whether these are enough for computing
798+
# conserved quantities, this will yield a less informative error).
715799
function conservationlaw_errorcheck(rs, pre_varmap)
716800
vars_with_vals = Set(p[1] for p in pre_varmap)
717-
any(s -> s in vars_with_vals, species(rs)) && return
801+
any(sp -> sp in vars_with_vals, species(rs)) && return
718802
isempty(conservedequations(Catalyst.flatten(rs))) ||
719803
error("The system has conservation laws but initial conditions were not provided for some species.")
720804
end

0 commit comments

Comments
 (0)