Skip to content

Commit 1f72c7e

Browse files
committed
rebase
1 parent 0d50188 commit 1f72c7e

File tree

12 files changed

+251
-86
lines changed

12 files changed

+251
-86
lines changed

docs/pages.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pages = Any[
3737
"Steady state analysis" => Any[
3838
"steady_state_functionality/homotopy_continuation.md",
3939
"steady_state_functionality/nonlinear_solve.md",
40-
# Stability analysis.
40+
"steady_state_functionality/steady_state_stability_computation.md",
4141
"steady_state_functionality/bifurcation_diagrams.md"
4242
# Dynamical systems analysis.
4343
],

docs/src/catalyst_applications/steady_state_stability_computation.md renamed to docs/src/steady_state_functionality/steady_state_stability_computation.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Steady state stability computation
22
After system steady states have been found using [HomotopyContinuation.jl](@ref homotopy_continuation), [NonlinearSolve.jl](@ref nonlinear_solve), or other means, their stability can be computed using Catalyst's `steady_state_stability` function. Systems with conservation laws will automatically have these removed, permitting stability computation on systems with singular Jacobian.
33

4+
!!! warn
5+
Catalyst currently computes steady state stabilities using the naive approach of checking whether a system's largest eigenvalue is negative. While more advanced stability computation methods exist (and would be a welcome addition to Catalyst), there is no direct plans to implement these.
6+
47
## Basic examples
58
Let us consider the following basic example:
69
```@example stability_1
@@ -51,4 +54,10 @@ ps_2 = [:v => 4.0, :K => 1.5, :n => 2, :d => 1.0]
5154
steady_states_2 = hc_steady_states(sa_loop, ps)
5255
stability_2 = steady_state_stability(steady_states_2, sa_loop, ps_2; ss_jac=ss_jac)
5356
nothing # hide
57+
```
58+
59+
It is possible to designate that a [sparse Jacobian](@ref ref) should be used using the `sparse = true` option (either to `steady_state_jac` or directly to `steady_state_stability`):
60+
```@example stability_1
61+
ss_jac = steady_state_jac(sa_loop; sparse = true)
62+
nothing # hide
5463
```

ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
# Creates a BifurcationProblem, using a ReactionSystem as an input.
44
function BK.BifurcationProblem(rs::ReactionSystem, u0_bif, ps, bif_par, args...;
55
plot_var=nothing, record_from_solution=BK.record_sol_default, jac=true, u0=[], kwargs...)
6-
is_autonomous(rs) || error("Attempting to compute steady state for non-autonomous system (e.g. does some rate depend on $(rs.iv)?), this is not possible.")
6+
if !is_autonomous(rs)
7+
error("Attempting to create a `BifurcationProblem` for a non-autonomous system (e.g. where some rate depend on $(rs.iv)). This is not possible.")
8+
end
79

810
# Converts symbols to symbolics.
911
(bif_par isa Symbol) && (bif_par = ModelingToolkit.get_var_to_name(rs)[bif_par])

ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ Notes:
3636
```
3737
"""
3838
function Catalyst.hc_steady_states(rs::ReactionSystem, ps; filter_negative=true, neg_thres=-1e-20, u0=[], kwargs...)
39-
is_autonomous(rs) || error("Attempting to compute steady state for non-autonomous system (e.g. does some rate depend on $(rs.iv)?), this is not possible.")
39+
if !is_autonomous(rs)
40+
error("Attempting to compute steady state for a non-autonomous system (e.g. where some rate depend on $(rs.iv)). This is not possible.")
41+
end
4042
ss_poly = steady_state_polynomial(rs, ps, u0)
4143
sols = HC.real_solutions(HC.solve(ss_poly; kwargs...))
4244
reorder_sols!(sols, ss_poly, rs)

src/Catalyst.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export species, nonspecies, reactionparams, reactions, nonreactions, speciesmap,
103103
export numspecies, numreactions, numreactionparams, setdefaults!
104104
export make_empty_network, reactionparamsmap
105105
export dependants, dependents, substoichmat, prodstoichmat, netstoichmat
106+
export is_autonomous
106107
export reactionrates
107108
export isequivalent
108109
export set_default_noise_scaling
@@ -150,7 +151,7 @@ export @compound, @compounds
150151
export iscompound, components, coefficients, component_coefficients
151152
export balance_reaction
152153

153-
# for functions I am unsure where to best place them.
154+
# Functionality for computing the stability of system steady states.
154155
include("steady_state_stability.jl")
155156
export steady_state_stability, steady_state_jac
156157

src/reactionsystem.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,35 @@ function dependants(rx, network)
12031203
dependents(rx, network)
12041204
end
12051205

1206+
"""
1207+
is_autonomous(rs::ReactionSystem)
1208+
1209+
Checks if a system is autonomous (i.e. no rate or equation depend on the independent variable(s)).
1210+
Example:
1211+
```julia
1212+
rs1 = @reaction_system
1213+
(p,d), 0 <--> X
1214+
end
1215+
is_autonomous(rs1) # Returns `true`.
1216+
1217+
rs2 = @reaction_system
1218+
(p/t,d), 0 <--> X
1219+
end
1220+
is_autonomous(rs2) # Returns `false`.
1221+
```
1222+
"""
1223+
function is_autonomous(rs::ReactionSystem)
1224+
# Get all variables occuring in reactions and then other equations.
1225+
dep_var_param_rxs = [ModelingToolkit.get_variables(rate) for rate in reactionrates(rs)]
1226+
dep_var_param_eqs = [ModelingToolkit.get_variables(eq) for eq in filter(eq -> !(eq isa Reaction), equations(rs))]
1227+
dep_var_param = reduce(vcat,[dep_var_param_rxs; dep_var_param_eqs])
1228+
1229+
# Checks for iv and spatial ivs
1230+
any(isequal(get_iv(rs), var) for var in dep_var_param) && (return false)
1231+
any(isequal(siv, var) for siv in get_sivs(rs) for var in dep_var_param) && (return false)
1232+
return true
1233+
end
1234+
12061235

12071236
### `ReactionSystem` Remaking ###
12081237

src/reactionsystem_conversions.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,8 +518,14 @@ function Base.convert(::Type{<:NonlinearSystem}, rs::ReactionSystem; name = name
518518
include_zero_odes = true, remove_conserved = false, checks = false,
519519
default_u0 = Dict(), default_p = Dict(), defaults = _merge(Dict(default_u0), Dict(default_p)),
520520
all_differentials_permitted = false, kwargs...)
521+
# Error checks.
521522
iscomplete(rs) || error(COMPLETENESS_ERROR)
522523
spatial_convert_err(rs::ReactionSystem, NonlinearSystem)
524+
if !is_autonomous(rs)
525+
error("Attempting to convert a non-autonomous `ReactionSystem` (e.g. where some rate depend on $(rs.iv)) to a `NonlinearSystem`. This is not possible. if you are intending to compute system steady states, consider creating and solving a `SteadyStateProblem.")
526+
end
527+
528+
# Generates system equations.
523529
fullrs = Catalyst.flatten(rs)
524530
remove_conserved && conservationlaws(fullrs)
525531
ists, ispcs = get_indep_sts(fullrs, remove_conserved)

src/steady_state_stability.jl

Lines changed: 59 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
### Stability Analysis ###
22

33
"""
4-
stability(u::Vector{T}, rs::ReactionSystem, p; sparse=false, ss_jac = steady_state_jac(u, rs, p; sparse=sparse), t = Inf, non_autonomous_warn = true)
4+
stability(u::Vector{T}, rs::ReactionSystem, p; sparse = false,
5+
ss_jac = steady_state_jac(u, rs, p; sparse=sparse))
56
67
Compute the stability of a steady state (Returned as a `Bool`, with `true` indicating stability).
78
89
Arguments:
910
- `u`: The steady state for which we want to compute stability.
10-
- `rs`: The reaction system model for which we want to compute stability.
11+
- `rs`: The `ReactionSystem` model for which we want to compute stability.
1112
- `p`: The parameter set for which we want to compute stability.
12-
- `sparse=false`: If we wish to create a sparse Jacobian for the stability computation.
13-
- `ss_jac = steady_state_jac(u, rs; sparse=sparse)`: It is possible to pre-compute the Jacobian used for stability computation using `steady_state_jac`. If stability is computed for many states, precomputing the Jacobian may speed up evaluation.
14-
- `t = Inf`: The time point at which stability is computed.
15-
- `non_autonomous_warn = true`: If the system is non-autonomous (e.g. a rate depends on t), a warning will be given. Set this to false to remove that. Alternatively, specify a nonInf value for `t`.
13+
- `sparse = false`: If we wish to create a sparse Jacobian for the stability computation.
14+
- `ss_jac = steady_state_jac(u, rs; sparse = sparse)`: It is possible to pre-compute the
15+
Jacobian used for stability computation using `steady_state_jac`. If stability is computed
16+
for many states, precomputing the Jacobian may speed up evaluation.
1617
1718
Example:
1819
```julia
@@ -22,45 +23,49 @@ rn = @reaction_network begin
2223
end
2324
p = [:p => 1.0, :d => 0.5]
2425
25-
# Finds (the trivial) steady state, and computes stability.
26+
# Finds (the trivial) steady state, and computes its stability.
2627
steady_state = [2.0]
2728
steady_state_stability(steady_state, rn, p)
28-
```
2929
3030
Notes:
31-
y states (each being a `Vector`) is provided as `u`, stability is computed for each state separately.
31+
- The input `u` can (currently) be both a vector of values (like `[1.0, 2.0]`) and a vector map
32+
(like `[X => 1.0, Y => 2.0]`). The reason is that most methods for computing stability only
33+
produces vectors of values. However, if possible, it is recommended to work with values on the
34+
form of maps.
35+
```
3236
"""
33-
function steady_state_stability(u::Vector{T}, rs::ReactionSystem, p;
34-
sparse=false, ss_jac = steady_state_jac(rs; u0=u, sparse=sparse),
35-
t = Inf, non_autonomous_warn =true) where T
37+
function steady_state_stability(u::Vector, rs::ReactionSystem, ps; sparse = false,
38+
ss_jac = steady_state_jac(rs; u0 = u, sparse = sparse))
3639
# Warning checks.
37-
!is_autonomous(rs) && non_autonomous_warn && @warn "Attempting to compute stability for a non-autonomous system. Set `non_autonomous_warn = false` to disable this warning."
38-
39-
# Because Jacobian currently requires u and p to be a normal vector.
40-
# Can be removed once this get fixed in MTK.
41-
if (u isa Vector{<:Pair}) || (u isa Dict)
42-
u_dict = Dict(symmap_to_varmap(rs, u))
43-
u = [u_dict[var] for var in states(rs)]
40+
if !is_autonomous(rs)
41+
error("Attempting to compute stability for a non-autonomous system (e.g. where some rate depend on $(rs.iv)). This is not possible.")
4442
end
45-
if (p isa Vector{<:Pair}) || (p isa Dict)
46-
p_dict = Dict(symmap_to_varmap(rs, p))
47-
p = [p_dict[var] for var in parameters(rs)]
43+
44+
# If `u` is a vector of values, we convert it to a map. Also, if there are conservation laws,
45+
# corresponding values must be eliminated from `u`.
46+
u = steady_state_u_conversion(u, rs)
47+
if length(u) > length(unknowns(ss_jac.f.sys))
48+
u = filter(u_v -> any(isequal(u_v[1]), unknowns(ss_jac.f.sys)), u)
4849
end
4950

5051
# Computes stability (by checking that the real part of all eigenvalues are negative).
51-
jac = ss_jac(u, p, Inf)
52-
return maximum(real.(eigvals(jac))) < 0
53-
end
54-
# Computes the stability for a vector of steady states.
55-
function steady_state_stability(us::Vector{Vector{T}}, rs::ReactionSystem, p;
56-
sparse=false, ss_jac = steady_state_jac(rs; u0=us[1], sparse=sparse)) where T
57-
return [steady_state_stability(u, rs, p) for u in us]
52+
# Here, `ss_jac` is a `ODEProblem` with dummy values for `u0` and `p`.
53+
J = zeros(length(u), length(u))
54+
ss_jac = remake(ss_jac; u0 = u, p = ps)
55+
ss_jac.f.jac(J, ss_jac.u0, ss_jac.p, Inf)
56+
return maximum(real(ev) for ev in (eigvals(J))) < 0
5857
end
5958

6059
"""
61-
steady_state_jac(rs::ReactionSystem; u0=[], sparse=false)
60+
steady_state_jac(rs::ReactionSystem; u0 = [], sparse = false)
61+
62+
Creates the Jacobian function which can be used as input to `steady_state_stability`. Useful when
63+
a large number of stability computation has to be carried out in a performant manner.
6264
63-
Creates the Jacobian function which can be used as input to `steady_state_stability`. Useful when a large number of stability computation has to be carried out in a performant manner.
65+
Arguments:
66+
- `rs`: The reaction system model for which we want to compute stability.
67+
- `u0 = []`: For systems with conservation laws, a `u` is required to compute the conserved quantities.
68+
- `sparse = false`: If we wish to create a sparse Jacobian for the stability computation.
6469
6570
Example:
6671
```julia
@@ -76,28 +81,34 @@ ss_jac = steady_state_jacobian(rn)
7681
# Finds (the trivial) steady state, and computes stability.
7782
steady_state = [2.0]
7883
steady_state_stability(steady_state, rn, p; ss_jac)
79-
```
8084
81-
Arguments:
82-
- `rs`: The reaction system model for which we want to compute stability.
83-
- `u0=[]`: For systems with conservation laws, a `u` is required to compute the conserved quantities.
84-
- `sparse=false`: If we wish to create a sparse Jacobian for the stability computation.
85+
Notes:
86+
- In practise, this function returns an `ODEProblem` (with a computed Jacobian) set up in
87+
such a way that it can be used by the `steady_state_stability` function.
88+
```
8589
"""
86-
function steady_state_jac(rs::ReactionSystem; u0=[], sparse=false) where T
90+
function steady_state_jac(rs::ReactionSystem; u0 = [sp => 0.0 for sp in unknowns(rs)],
91+
sparse = false, combinatoric_ratelaws = get_combinatoric_ratelaws(rs))
8792
# If u0 is a vector of values, must be converted to something MTK understands.
88-
if (u0 isa Vector{<:Number})
89-
if length(u0) == length(states(rs))
90-
u0 = Pair.(states(rs), u0)
91-
elseif !isempty(u0)
92-
error("You are trying to compute a stability matrix, providing u0 to compute conservation laws. Your provided u0 vector has length < the number of system states. If you provide a u0 vector, these have to be identical.")
93-
end
94-
end
9593

9694
# Converts u0 to values MTK understands, and checks that potential conservation laws are accounted for.
97-
u0 = symmap_to_varmap(rs, u0)
95+
u0 = steady_state_u_conversion(u0, rs)
9896
conservationlaw_errorcheck(rs, u0)
9997

100-
# Creates a ODESystem and jacobian.
101-
osys = convert(ODESystem, rs; remove_conserved = true, defaults=Dict(u0))
102-
return ODEFunction(osys; jac=true, sparse=sparse).jac
98+
# Creates an `ODEProblem` with a Jacobian. Dummy values for `u0` and `ps` must be provided.
99+
ps = [p => 0.0 for p in parameters(rs)]
100+
return ODEProblem(rs, u0, 0, ps; jac = true, remove_conserved = true,
101+
combinatoric_ratelaws = get_combinatoric_ratelaws(rs))
102+
end
103+
104+
# Converts a `u` vector from a vector of values to a map.
105+
function steady_state_u_conversion(u, rs::ReactionSystem)
106+
if (u isa Vector{<:Number})
107+
if length(u) == length(unknowns(rs))
108+
u = [sp => v for (sp,v) in zip(unknowns(rs), u)]
109+
else
110+
error("You are trying to generate a stability Jacobian, providing u0 to compute conservation laws. Your provided u0 vector has length < the number of system states. If you provide a u0 vector, these have to be identical.")
111+
end
112+
end
113+
return symmap_to_varmap(rs, u)
103114
end

test/miscellaneous_tests/api.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,6 @@ let
462462
(p + X*(p1+p2),d), 0 <--> X
463463
(kB,kD), 2X <--> X
464464
end
465-
466465
@test !is_autonomous(rn1)
467466
@test !is_autonomous(rn2)
468467
@test is_autonomous(rn3)
@@ -488,9 +487,15 @@ let
488487
(p + X*(p1+p2),d), 0 <--> X
489488
(kB,kD), 2X <--> X
490489
end
491-
492490
@test !is_autonomous(rn4)
493491
@test !is_autonomous(rn5)
494492
@test !is_autonomous(rn6)
495493
@test is_autonomous(rn7)
494+
495+
# Using a coupled CRN/equation model.
496+
rn7 = @reaction_network begin
497+
@equations D(V) ~ X/(1+t) - V
498+
(p,d), 0 <--> X
499+
end
500+
@test !is_autonomous(rn7)
496501
end

0 commit comments

Comments
 (0)