Skip to content

Commit 5d53ae2

Browse files
authored
Merge pull request #683 from SciML/hc_extension
Homotopy Continuation extension and remake
2 parents d9189b1 + a03f3a5 commit 5d53ae2

File tree

10 files changed

+307
-100
lines changed

10 files changed

+307
-100
lines changed

HISTORY.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
# Breaking updates and feature summaries across releases
22

33
## Catalyst unreleased (master branch)
4+
- Added a CatalystHomotopyContinuationExtension extension, which exports the `hc_steady_state` function if HomotopyContinuation is exported. `hc_steady_state` finds the steady states of a reactin system using the homotopy continuation method. This feature is only available for julia versions 1.9+. Example:
5+
```julia
6+
wilhelm_2009_model = @reaction_network begin
7+
k1, Y --> 2X
8+
k2, 2X --> X + Y
9+
k3, X + Y --> Y
10+
k4, X --> 0
11+
end
12+
ps = [:k1 => 8.0, :k2 => 2.0, :k3 => 1.0, :k4 => 1.5]
13+
hc_steady_states(wilhelm_2009_model, ps)
14+
```
415

516
## Catalyst 13.4
617
- Added the ability to create species that represent chemical compounds and know

Project.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
2121
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
2222
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
2323

24+
[weakdeps]
25+
HomotopyContinuation = "f213a82b-91d6-5c5d-acf7-10f1c761b327"
26+
27+
[extensions]
28+
CatalystHomotopyContinuationExtension = "HomotopyContinuation"
29+
2430
[compat]
2531
DataStructures = "0.18"
2632
DiffEqBase = "6.83.0"
@@ -42,6 +48,7 @@ julia = "1.6"
4248
[extras]
4349
DomainSets = "5b8099bc-c8ec-5219-889f-1d9e522a28bf"
4450
Graphviz_jll = "3c863552-8265-54e4-a6dc-903eb78fde85"
51+
HomotopyContinuation = "f213a82b-91d6-5c5d-acf7-10f1c761b327"
4552
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
4653
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
4754
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
@@ -57,4 +64,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
5764
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
5865

5966
[targets]
60-
test = ["DomainSets", "Graphviz_jll", "NonlinearSolve", "OrdinaryDiffEq", "Random", "SafeTestsets", "SciMLBase", "SciMLNLSolve", "StableRNGs", "Statistics", "SteadyStateDiffEq", "StochasticDiffEq", "Test", "Unitful"]
67+
test = ["DomainSets", "Graphviz_jll", "HomotopyContinuation", "NonlinearSolve", "OrdinaryDiffEq", "Random", "SafeTestsets", "SciMLBase", "SciMLNLSolve", "StableRNGs", "Statistics", "SteadyStateDiffEq", "StochasticDiffEq", "Test", "Unitful"]

docs/src/catalyst_applications/homotopy_continuation.md

Lines changed: 35 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -8,136 +8,77 @@ example those which are purely mass action or have only have Hill functions with
88
integer Hill exponents). The roots of these can reliably be found through a
99
*homotopy continuation* algorithm. This is implemented in Julia through the
1010
[HomotopyContinuation.jl](https://www.juliahomotopycontinuation.org/) package.
11-
In this tutorial, we will demonstrate how homotopy continuation can be used to
12-
find the steady states of mass action chemical reaction networks implemented in
13-
Catalyst.
11+
12+
Catalyst contains a special homotopy continuation extension that is loaded whenever HomotopyContinuation.jl is. This exports a single function, `hc_steady_states`, that can be used to find the steady states of any `ReactionSystem` structure. If you use this in your research, please [cite the HomotopyContinuation.jl](@ref homotopy_continuation_citation) and [Catalyst.jl]() publications.
1413

1514
## Basic example
1615
For this tutorial, we will use a model from Wilhem (2009)[^1] (which
1716
demonstrates bistability in a small chemical reaction network). We declare the
1817
model and the parameter set for which we want to find the steady states:
1918
```@example hc1
20-
using Catalyst, ModelingToolkit
19+
using Catalyst
2120
import HomotopyContinuation
22-
const MT = ModelingToolkit
23-
const HC = HomotopyContinuation
2421
2522
wilhelm_2009_model = @reaction_network begin
2623
k1, Y --> 2X
2724
k2, 2X --> X + Y
2825
k3, X + Y --> Y
2926
k4, X --> 0
3027
end
31-
32-
# add default parameters values to model
33-
setdefaults!(wilhelm_2009_model, [:k1 => 8.0, :k2 => 2.0, :k3 => 1.0, :k4 => 1.5])
28+
ps = [:k1 => 8.0, :k2 => 2.0, :k3 => 1.0, :k4 => 1.5]
3429
nothing # hide
3530
```
36-
Next, we will need to extract the actual equations from our model. In addition,
37-
we will substitute in our parameter values to these equations.
38-
```@example hc1
39-
ns = convert(NonlinearSystem, wilhelm_2009_model)
40-
41-
# this gets the parameter values ordered consistent with parameters(ns)
42-
pvals = MT.varmap_to_vars([], MT.parameters(ns); defaults = MT.defaults(ns))
31+
Here, we only run `import HomotopyContinuation` as we do not require any of its functions, and just need it to be present in the current scope for the extension to be activated.
4332

44-
subs = Dict(MT.parameters(ns) .=> pvals)
45-
neweqs = map(eq -> substitute(eq.rhs, subs), equations(ns))
46-
```
47-
Finally, we use Catalyst's `to_multivariate_poly` function to reinterpret our
48-
symbolic equations in a polynomial representation that is compatible with
49-
HomotopyContinuation. We can then apply HomotopyContinuation's `solve` command
50-
to find the roots, using `real_solutions` to filter our non-physical complex
51-
steady-states:
33+
Now we can find the steady states using:
5234
```@example hc1
53-
polyeqs = Catalyst.to_multivariate_poly(neweqs)
54-
sols = HC.real_solutions(HC.solve(polyeqs))
35+
hc_steady_states(wilhelm_2009_model, ps)
5536
```
56-
Note that HomotopyContinuation orders variables lexicographically, so this will
57-
be the ordering present in each steady-state solution vector (i.e. `[X1, X2]` is
58-
the ordering here).
37+
The order of the species in the output vectors are the same as in `species(wilhelm_2009_model)`.
38+
39+
It should be noted that the steady state multivariate polynomials produced by reaction systems may have both imaginary and negative roots, which are filtered away by `hc_steady_states`. If you want the negative roots, you can use the `hc_steady_states(wilhelm_2009_model, ps; filter_negative=false)` argument.
5940

60-
While it is not the case for this CRN, we note that solutions with negative
61-
species concentrations can be valid (unphysical) steady-states for certain
62-
systems. These will need to be filtered out as well.
6341

6442
## Systems with conservation laws
65-
Finally, some systems are under-determined, and have an infinite number of
66-
possible steady states. These are typically systems containing a conservation
43+
Some systems are under-determined, and have an infinite number of possible steady states. These are typically systems containing a conservation
6744
law, e.g.
6845
```@example hc3
6946
using Catalyst
7047
import HomotopyContinuation
71-
const MT = ModelingToolkit
72-
const HC = HomotopyContinuation
7348
7449
two_state_model = @reaction_network begin
7550
(k1,k2), X1 <--> X2
7651
end
7752
```
78-
Catalyst allows the conservation laws to be computed using the
79-
`conservationlaws` function. By using these to reduce the dimensionality of the
80-
system, as well specifying the initial amount of each species,
81-
HomotopyContinuation can again be used to find steady states. First, we set the
82-
default values of the system's initial conditions and parameter values. This
83-
will allow the system to automatically find the conserved amounts.
84-
```@example hc3
85-
setdefaults!(two_state_model, [:X1 => 1.0, :X2 => 1.0, :k1 => 2.0, :k2 => 1.0])
86-
```
87-
Next, we create a `NonlinearSystem`, while also removing one species via the
88-
conservation equation.
89-
```@example hc3
90-
ns = convert(NonlinearSystem, two_state_model; remove_conserved = true)
91-
```
92-
Again, we next create a dictionary for parameter values that we substitute in to
93-
give our final equation.
53+
Catalyst allows the conservation laws of such systems to be computed using the `conservationlaws` function. By using these to reduce the dimensionality of the system, as well specifying the initial amount of each species, HomotopyContinuation can again be used to find steady states. To find the steady states using the Catalyst interface to HomotopyContinuation, an initial condition must be provided (which is used to compute the system's conserved quantities, in this case `X1+X2`):
9454
```@example hc3
95-
pvals = MT.varmap_to_vars([], MT.parameters(ns); defaults = MT.defaults(ns))
96-
subs = Dict(MT.parameters(ns) .=> pvals)
97-
neweqs = map(eq -> substitute(eq.rhs, subs), equations(ns))
98-
```
99-
Notice, our equations are just for `X1` as `X2` was eliminated via the conservation law.
55+
ps = [:k1 => 2.0, :k2 => 1.0]
56+
u0 = [:X1 => 1.0, :X2 => 1.0]
57+
hc_steady_states(wilhelm_2009_model, ps; u0)
10058
101-
Finally, we convert to polynomial form and solve for the steady-states
102-
```@example hc3
103-
polyeqs = Catalyst.to_multivariate_poly(neweqs)
104-
sols = HC.real_solutions(HC.solve(polyeqs))
10559
```
10660

107-
If we also want the corresponding value for `X2`, we can substitute
108-
into the equation for it from the conservation laws:
109-
```@example hc3
110-
# get the X2 symbolic variable
111-
@unpack X2 = two_state_model
112-
113-
# get its algebraic formula in terms of X1 and parameters
114-
ceqs = conservedequations(two_state_model)
115-
X2eqidx = findfirst(eq -> isequal(eq.lhs, X2), ceqs)
116-
X2eq = ceqs[X2eqidx].rhs
61+
## Final notes
62+
- `hc_steady_states` supports any systems where all rates are systems of rational polynomials (such as Hill functions with integer Hill coefficients).
63+
- When providing initial conditions to compute conservation laws, values are only required for those species that are part of conserved quantities. If this set of species is unknown, it is recommended to provide initial conditions for all species.
64+
- Additional arguments provided to `hc_steady_states` are automatically passed to HomotopyContinuation's `solve` command. Use e.g. `show_progress=false` to disable the progress bar.
65+
---
11766

118-
# for each SS, set X1's value in the subs map and calculate X2
119-
@unpack X1 = two_state_model
120-
X2 = map(sols) do x
121-
X1val = x[1]
122-
subs[MT.value(X1)] = X1val
123-
substitute(X2eq, subs)
124-
end
67+
## [Citation](@id homotopy_continuation_citation)
68+
If you use this functionality in your research, please cite the following paper to support the authors of the HomotopyContinuation package:
12569
```
126-
giving that the steady-state for `X2` is about `1.33333`.
127-
128-
As an alternative, we could have coupled `neweqs` with the conservation law
129-
relations to have HomotopyContinuation find the steady-states simultaneously:
130-
```@example hc3
131-
# move all the terms in the conserved equations to one side
132-
# and substitute in the parameter values
133-
subs = Dict(MT.parameters(ns) .=> pvals)
134-
conservedrelations = map(eq -> substitute(eq.rhs - eq.lhs, subs), ceqs)
135-
neweqs = vcat(neweqs, conservedrelations)
136-
137-
# calculate the steady-states
138-
polyeqs = Catalyst.to_multivariate_poly(neweqs)
139-
sols = HC.real_solutions(HC.solve(polyeqs))
70+
@inproceedings{HomotopyContinuation.jl,
71+
title={{H}omotopy{C}ontinuation.jl: {A} {P}ackage for {H}omotopy {C}ontinuation in {J}ulia},
72+
author={Breiding, Paul and Timme, Sascha},
73+
booktitle={International Congress on Mathematical Software},
74+
pages={458--465},
75+
year={2018},
76+
organization={Springer}
77+
}
14078
```
141-
---
79+
14280
## References
143-
[^1]: [Wilhelm, T. *The smallest chemical reaction system with bistability*, BMC Systems Biology (2009).](https://bmcsystbiol.biomedcentral.com/articles/10.1186/1752-050Wilhelm-3-90)
81+
[^1]: [Thomas Wilhelm, *The smallest chemical reaction system with bistability*, BMC Systems Biology (2009).](https://bmcsystbiol.biomedcentral.com/articles/10.1186/1752-0509-3-90)
82+
[^2]: [Paul Breiding, Sascha Timme, *HomotopyContinuation.jl: A Package for Homotopy Continuation in Julia*, International Congress on Mathematical Software (2018).](https://link.springer.com/chapter/10.1007/978-3-319-96418-8_54)
83+
[^3:] [Andrew J Sommese, Charles W Wampler *The Numerical Solution of Systems of Polynomials Arising in Engineering and Science*, World Scientific (2005).](https://www.worldscientific.com/worldscibooks/10.1142/5763#t=aboutBook)
84+
[^4:] [Daniel J. Bates, Paul Breiding, Tianran Chen, Jonathan D. Hauenstein, Anton Leykin, Frank Sottile, *Numerical Nonlinear Algebra*, arXiv (2023).](https://arxiv.org/abs/2302.08585)

docs/src/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ Slack's](https://julialang.slack.com) \#sciml-bridged and \#sciml-sysbio channel
130130
For bugs or feature requests [open an
131131
issue](https://github.com/SciML/Catalyst.jl/issues).
132132

133-
## Supporting and Citing Catalyst.jl
133+
## [Supporting and Citing Catalyst.jl](@id catalyst_citation)
134134
The software in this ecosystem was developed as part of academic research. If you would like to help support it,
135135
please star the repository as such metrics may help us secure funding in the future. If you use Catalyst as part
136136
of your research, teaching, or other activities, we would be grateful if you could cite our work:
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module CatalystHomotopyContinuationExtension
2+
3+
# Fetch packages.
4+
using Catalyst
5+
import ModelingToolkit as MT
6+
import HomotopyContinuation as HC
7+
import Symbolics: unwrap, wrap, Rewriters, symtype, issym, istree
8+
9+
# Creates and exports hc_steady_states function.
10+
include("CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl")
11+
12+
end
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
### Homotopy Continuation Based Steady State Finding ###
2+
3+
"""
4+
hc_steady_states(rs::ReactionSystem, ps; filter_negative=true, neg_thres=-1e-20, u0=typeof(ps)(), kwargs...)
5+
6+
Uses homotopy continuation via HomotopyContinuation.jl to find the steady states of the ODE system corresponding to the provided reaction system.
7+
8+
Arguments:
9+
- `rs::ReactionSystem`: The reaction system for which we want to find the steady states.
10+
- `ps`: The parameter values for which we want to find the steady states.
11+
- `filter_negative=true`: If set to true, solutions with any species concentration <neg_thres is removed from the output.
12+
- `neg_thres=-1e-20`: Determine the minimum values for which a species concentration is to be considered non-negative. Species concentrations ``> neg_thres`` but `< 0.0` are set to `0.0`.
13+
- `u0=nothing`: Initial conditions for which we want to find the steady states. For systems with conservation laws this are required to compute conserved quantities. Initial conditions are not required for all species, only those involved in conserved quantities (if this set is unknown, it is recommended to provide initial conditions for all species).
14+
- `kwargs...`: any additional arguments (like `show_progress= true`) are passed into HomotopyContinuation.jl's `solve` call.
15+
16+
Examples
17+
```@repl
18+
rs = @reaction_network begin
19+
k1, Y --> 2X
20+
k2, 2X --> X + Y
21+
k3, X + Y --> Y
22+
k4, X --> 0
23+
end
24+
ps = [:k3 => 1.0, :k2 => 2.0, :k4 => 1.5, :k1=>8.0]
25+
hc_sol = hc_steady_states(rs, ps)
26+
```
27+
gives
28+
```
29+
[0.5000000000000002, 2.0000000000000004]
30+
[0.0, 0.0]
31+
[4.499999999999999, 5.999999999999999]
32+
```
33+
34+
Notes:
35+
```
36+
"""
37+
function Catalyst.hc_steady_states(rs::ReactionSystem, ps; filter_negative=true, neg_thres=-1e-20, u0=[], kwargs...)
38+
ss_poly = steady_state_polynomial(rs, ps, u0)
39+
sols = HC.real_solutions(HC.solve(ss_poly; kwargs...))
40+
reorder_sols!(sols, ss_poly, rs)
41+
return (filter_negative ? filter_negative_f(sols; neg_thres) : sols)
42+
end
43+
44+
# For a given reaction system, parameter values, and initial conditions, find the polynomial that HC solves to find steady states.
45+
function steady_state_polynomial(rs::ReactionSystem, ps, u0)
46+
ns = convert(NonlinearSystem, rs; remove_conserved = true)
47+
pre_varmap = [symmap_to_varmap(rs,u0)..., symmap_to_varmap(rs,ps)...]
48+
conservationlaw_errorcheck(rs, pre_varmap)
49+
p_vals = ModelingToolkit.varmap_to_vars(pre_varmap, parameters(ns); defaults = ModelingToolkit.defaults(ns))
50+
p_dict = Dict(parameters(ns) .=> p_vals)
51+
eqs_pars_funcs = vcat(equations(ns), conservedequations(rs))
52+
eqs_funcs = map(eq -> substitute(eq.rhs - eq.lhs, p_dict), eqs_pars_funcs)
53+
eqs = [deregister([mm, mmr, hill, hillr, hillar], eq) for eq in eqs_funcs]
54+
eqs_intexp = make_int_exps.(eqs)
55+
return Catalyst.to_multivariate_poly(remove_denominators.(eqs_intexp))
56+
end
57+
58+
# If u0s are not given while conservation laws are present, throws an error.
59+
function conservationlaw_errorcheck(rs, pre_varmap)
60+
vars_with_vals = Set(p[1] for p in pre_varmap)
61+
any(s -> s in vars_with_vals, species(rs)) && return
62+
isempty(conservedequations(rs)) ||
63+
error("The system has conservation laws but initial conditions were not provided for some species.")
64+
end
65+
66+
# Unfolds a function (like mm or hill).
67+
function deregister(fs::Vector{T}, expr) where T
68+
for f in fs
69+
expr = deregister(f, expr)
70+
end
71+
return expr
72+
end
73+
# Provided by Shashi Gowda.
74+
deregister(f, expr) = wrap(Rewriters.Postwalk(Rewriters.PassThrough(___deregister(f)))(unwrap(expr)))
75+
function ___deregister(f)
76+
(expr) ->
77+
if istree(expr) && operation(expr) == f
78+
args = arguments(expr)
79+
invoke_with = map(args) do a
80+
t = typeof(a)
81+
issym(a) || istree(a) ? wrap(a) => symtype(a) : a => typeof(a)
82+
end
83+
invoke(f, Tuple{last.(invoke_with)...}, first.(invoke_with)...)
84+
end
85+
end
86+
87+
# Parses and expression and return a version where any exponents that are Float64 (but an int, like 2.0) are turned into Int64s.
88+
make_int_exps(expr) = wrap(Rewriters.Postwalk(Rewriters.PassThrough(___make_int_exps))(unwrap(expr))).val
89+
function ___make_int_exps(expr)
90+
!istree(expr) && return expr
91+
if (operation(expr) == ^)
92+
if isinteger(arguments(expr)[2])
93+
return arguments(expr)[1] ^ Int64(arguments(expr)[2])
94+
else
95+
error("An non integer ($(arguments(expr)[2])) was found as a variable exponent. Non-integer exponents are not supported for homotopy continuation based steady state finding.")
96+
end
97+
end
98+
end
99+
100+
# If the input is a fraction, removes the denominator.
101+
function remove_denominators(expr)
102+
s_expr = simplify_fractions(expr)
103+
!istree(expr) && return expr
104+
if operation(s_expr) == /
105+
return remove_denominators(arguments(s_expr)[1])
106+
end
107+
if operation(s_expr) == +
108+
return sum(remove_denominators(arg) for arg in arguments(s_expr))
109+
end
110+
return s_expr
111+
end
112+
113+
# HC orders the solution vector according to the lexicographic values of the variable names. This reorders the output according to the species index in the reaction system species vector.
114+
function reorder_sols!(sols, ss_poly, rs::ReactionSystem)
115+
var_names_extended = String.(Symbol.(HC.variables(ss_poly)))
116+
var_names = [Symbol(s[1:prevind(s, findlast('_', s))]) for s in var_names_extended]
117+
sort_pattern = indexin(MT.getname.(species(rs)), var_names)
118+
foreach(sol -> permute!(sol, sort_pattern), sols)
119+
end
120+
121+
# Filters away solutions with negative species concentrations (and for neg_thres < val < 0.0, sets val=0.0).
122+
function filter_negative_f(sols; neg_thres=-1e-20)
123+
for sol in sols, idx in 1:length(sol)
124+
(neg_thres < sol[idx] < 0) && (sol[idx] = 0)
125+
end
126+
return filter(sol -> all(>=(0), sol), sols)
127+
end

src/Catalyst.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,10 @@ export @compound
101101
export components, iscompound, coefficients
102102
export balance_reaction
103103

104+
### Extensions ###
105+
106+
# HomotopyContinuation
107+
function hc_steady_states end
108+
export hc_steady_states
109+
104110
end # module

0 commit comments

Comments
 (0)