Skip to content

Commit eb03e6d

Browse files
committed
network analysis updates
2 parents 0f47554 + 6eafdb3 commit eb03e6d

File tree

8 files changed

+848
-627
lines changed

8 files changed

+848
-627
lines changed

.github/workflows/DocsCleanup.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Doc Preview Cleanup
2+
3+
on:
4+
pull_request:
5+
types: [closed]
6+
7+
# Ensure that only one "Doc Preview Cleanup" workflow is force pushing at a time
8+
concurrency:
9+
group: doc-preview-cleanup
10+
cancel-in-progress: false
11+
12+
jobs:
13+
doc-preview-cleanup:
14+
runs-on: ubuntu-latest
15+
permissions:
16+
contents: write
17+
steps:
18+
- name: Checkout gh-pages branch
19+
uses: actions/checkout@v4
20+
with:
21+
ref: gh-pages
22+
- name: Delete preview and history + push changes
23+
run: |
24+
if [ -d "${preview_dir}" ]; then
25+
git config user.name "Documenter.jl"
26+
git config user.email "[email protected]"
27+
git rm -rf "${preview_dir}"
28+
git commit -m "delete preview"
29+
git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree})
30+
git push --force origin gh-pages-new:gh-pages
31+
fi
32+
env:
33+
preview_dir: previews/PR${{ github.event.number }}

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ JumpProcesses = "9.13.2"
5858
LaTeXStrings = "1.3.0"
5959
Latexify = "0.16.5"
6060
MacroTools = "0.5.5"
61-
ModelingToolkit = "9.32"
61+
ModelingToolkit = "< 9.60"
6262
NetworkLayout = "0.4.7"
6363
Parameters = "0.12"
6464
Reexport = "0.2, 1.0"
@@ -67,7 +67,7 @@ RuntimeGeneratedFunctions = "0.5.12"
6767
SciMLBase = "2.57.2"
6868
Setfield = "1"
6969
# StructuralIdentifiability = "0.5.8"
70-
SymbolicUtils = "2.1.2, 3.3.0"
70+
SymbolicUtils = "3.8.1"
7171
Symbolics = "6.22"
7272
Unitful = "1.12.4"
7373
julia = "1.10"

docs/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ IncompleteLU = "0.2"
6161
JumpProcesses = "9.13.2"
6262
Latexify = "0.16.5"
6363
LinearSolve = "2.30"
64-
ModelingToolkit = "9.32"
64+
ModelingToolkit = "< 9.60"
6565
NonlinearSolve = "3.12, 4"
6666
Optim = "1.9"
6767
Optimization = "4"

src/Catalyst.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ export @reaction_network, @network_component, @reaction, @species
128128
# Network analysis functionality.
129129
include("network_analysis.jl")
130130
export reactioncomplexmap, reactioncomplexes, incidencemat
131-
export complexstoichmat
132-
export complexoutgoingmat, incidencematgraph, linkageclasses, stronglinkageclasses,
131+
export complexstoichmat, laplacianmat, fluxmat, massactionvector, complexoutgoingmat
132+
export incidencematgraph, linkageclasses, stronglinkageclasses,
133133
terminallinkageclasses, deficiency, subnetworks
134134
export linkagedeficiencies, isreversible, isweaklyreversible
135135
export conservationlaws, conservedquantities, conservedequations, conservationlaw_constants

src/network_analysis.jl

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,143 @@ function complexstoichmat(::Type{Matrix{Int}}, rn::ReactionSystem, rcs)
192192
Z
193193
end
194194

195+
@doc raw"""
196+
laplacianmat(rn::ReactionSystem, pmap=Dict(); sparse=false)
197+
198+
Return the negative of the graph Laplacian of the reaction network. The ODE system of a chemical reaction network can be factorized as ``\frac{dx}{dt} = Y A_k Φ(x)``, where ``Y`` is the [`complexstoichmat`](@ref) and ``A_k`` is the negative of the graph Laplacian, and ``Φ`` is the [`massactionvector`](@ref). ``A_k`` is an n-by-n matrix, where n is the number of complexes, where ``A_{ij} = k_{ij}`` if a reaction exists between the two complexes and 0 otherwise.
199+
Returns a symbolic matrix by default, but will return a numerical matrix if parameter values are specified via pmap.
200+
201+
**Warning**: Unlike other Catalyst functions, the `laplacianmat` function will return a `Matrix{Num}` in the symbolic case. This is to allow easier computation of the matrix decomposition of the ODEs, and to ensure that multiplying the sparse form of the matrix will work.
202+
"""
203+
function laplacianmat(rn::ReactionSystem, pmap::Dict = Dict(); sparse = false)
204+
D = incidencemat(rn; sparse)
205+
K = fluxmat(rn, pmap; sparse)
206+
D * K
207+
end
208+
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+
212+
@doc raw"""
213+
fluxmat(rn::ReactionSystem, pmap = Dict(); sparse=false)
214+
215+
Return an r×c matrix ``K`` such that, if complex ``j`` is the substrate complex of reaction ``i``, then ``K_{ij} = k``, the rate constant for this reaction. Mostly a helper function for the network Laplacian, [`laplacianmat`](@ref). Has the useful property that ``\frac{dx}{dt} = S*K*Φ(x)``, where S is the [`netstoichmat`](@ref) or net stoichiometry matrix and ``Φ(x)`` is the [`massactionvector`](@ref).
216+
Returns a symbolic matrix by default, but will return a numerical matrix if rate constants are specified as a `Tuple`, `Vector`, or `Dict` of symbol-value pairs via `pmap`.
217+
218+
**Warning**: Unlike other Catalyst functions, the `fluxmat` function will return a `Matrix{Num}` in the symbolic case. This is to allow easier computation of the matrix decomposition of the ODEs, and to ensure that multiplying the sparse form of the matrix will work.
219+
"""
220+
function fluxmat(rn::ReactionSystem, pmap::Dict = Dict(); sparse=false)
221+
rates = if isempty(pmap)
222+
reactionrates(rn)
223+
else
224+
substitutevals(rn, pmap, parameters(rn), reactionrates(rn))
225+
end
226+
227+
rcmap = reactioncomplexmap(rn)
228+
nc = length(rcmap)
229+
nr = length(rates)
230+
mtype = eltype(rates) <: Symbolics.BasicSymbolic ? Num : eltype(rates)
231+
if sparse
232+
return fluxmat(SparseMatrixCSC{mtype, Int}, rcmap, rates)
233+
else
234+
return fluxmat(Matrix{mtype}, rcmap, rates)
235+
end
236+
end
237+
238+
function fluxmat(::Type{SparseMatrixCSC{T, Int}}, rcmap, rates) where T
239+
Is = Int[]
240+
Js = Int[]
241+
Vs = T[]
242+
for (i, (complex, rxs)) in enumerate(rcmap)
243+
for (rx, dir) in rxs
244+
dir == -1 && begin
245+
push!(Is, rx)
246+
push!(Js, i)
247+
push!(Vs, rates[rx])
248+
end
249+
end
250+
end
251+
Z = sparse(Is, Js, Vs, length(rates), length(rcmap))
252+
end
253+
254+
function fluxmat(::Type{Matrix{T}}, rcmap, rates) where T
255+
nr = length(rates)
256+
nc = length(rcmap)
257+
K = zeros(T, nr, nc)
258+
for (i, (complex, rxs)) in enumerate(rcmap)
259+
for (rx, dir) in rxs
260+
dir == -1 && (K[rx, i] = rates[rx])
261+
end
262+
end
263+
K
264+
end
265+
266+
function fluxmat(rn::ReactionSystem, pmap::Vector)
267+
pdict = Dict(pmap)
268+
fluxmat(rn, pdict)
269+
end
270+
271+
function fluxmat(rn::ReactionSystem, pmap::Tuple)
272+
pdict = Dict(pmap)
273+
fluxmat(rn, pdict)
274+
end
275+
276+
# Helper to substitute values into a (vector of) symbolic expressions. The syms are the symbols to substitute and the symexprs are the expressions to substitute into.
277+
function substitutevals(rn::ReactionSystem, map::Dict, syms, symexprs)
278+
length(map) != length(syms) && error("Incorrect number of parameter-value pairs were specified.")
279+
map = symmap_to_varmap(rn, map)
280+
map = Dict(ModelingToolkit.value(k) => v for (k, v) in map)
281+
vals = [substitute(expr, map) for expr in symexprs]
282+
end
283+
284+
"""
285+
massactionvector(rn::ReactionSystem, scmap = Dict(); combinatoric_ratelaws = true)
286+
287+
Return the vector whose entries correspond to the "mass action products" of each complex. For example, given the complex A + B, the corresponding entry of the vector would be ``A*B``, and for the complex 2X + Y, the corresponding entry would be ``X^2*Y``. The ODE system of a chemical reaction network can be factorized as ``\frac{dx}{dt} = Y A_k Φ(x)``, where ``Y`` is the [`complexstoichmat`](@ref) and ``A_k`` is the negative of the [`laplacianmat`](@ref). This utility returns ``Φ(x)``.
288+
Returns a symbolic vector by default, but will return a numerical vector if species concentrations are specified as a tuple, vector, or dictionary via scmap.
289+
If the `combinatoric_ratelaws` option is set, will include prefactors for that (see [introduction to Catalyst's rate laws](@ref introduction_to_catalyst_ratelaws). Will default to the default for the system.
290+
291+
**Warning**: Unlike other Catalyst functions, the `massactionvector` function will return a `Vector{Num}` in the symbolic case. This is to allow easier computation of the matrix decomposition of the ODEs.
292+
"""
293+
function massactionvector(rn::ReactionSystem, scmap::Dict = Dict(); combinatoric_ratelaws = Catalyst.get_combinatoric_ratelaws(rn))
294+
r = numreactions(rn)
295+
rxs = reactions(rn)
296+
sm = speciesmap(rn)
297+
298+
specs = if isempty(scmap)
299+
species(rn)
300+
else
301+
substitutevals(rn, scmap, species(rn), species(rn))
302+
end
303+
304+
if !all(r -> ismassaction(r, rn), rxs)
305+
error("The supplied ReactionSystem has reactions that are not ismassaction. The mass action vector is only defined for pure mass action networks.")
306+
end
307+
308+
vtype = eltype(specs) <: Symbolics.BasicSymbolic ? Num : eltype(specs)
309+
Φ = Vector{vtype}()
310+
rcmap = reactioncomplexmap(rn)
311+
for comp in keys(reactioncomplexmap(rn))
312+
subs = map(ce -> getfield(ce, :speciesid), comp)
313+
stoich = map(ce -> getfield(ce, :speciesstoich), comp)
314+
maprod = prod(vtype[specs[s]^α for (s, α) in zip(subs, stoich)])
315+
combinatoric_ratelaws && (maprod /= prod(map(factorial, stoich)))
316+
push!(Φ, maprod)
317+
end
318+
319+
Φ
320+
end
321+
322+
function massactionvector(rn::ReactionSystem, scmap::Tuple; combinatoric_ratelaws = Catalyst.get_combinatoric_ratelaws(rn))
323+
sdict = Dict(scmap)
324+
massactionvector(rn, sdict; combinatoric_ratelaws)
325+
end
326+
327+
function massactionvector(rn::ReactionSystem, scmap::Vector; combinatoric_ratelaws = Catalyst.get_combinatoric_ratelaws(rn))
328+
sdict = Dict(scmap)
329+
massactionvector(rn, sdict; combinatoric_ratelaws)
330+
end
331+
195332
@doc raw"""
196333
complexoutgoingmat(network::ReactionSystem; sparse=false)
197334
@@ -787,7 +924,7 @@ function isdetailedbalanced(rs::ReactionSystem, parametermap::Dict; abstol=0, re
787924
elseif !isreversible(rs)
788925
return false
789926
elseif !all(r -> ismassaction(r, rs), reactions(rs))
790-
error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being complex balanced is currently only supported for pure mass action networks.")
927+
error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being detailed balanced is currently only supported for pure mass action networks.")
791928
end
792929

793930
isforestlike(rs) && deficiency(rs) == 0 && return true

0 commit comments

Comments
 (0)