Skip to content

Commit b179191

Browse files
authored
Merge pull request #713 from SciML/custom_function_expansion
Create function for expanding e.g. `mm` expressions, and use for e.g. HomotopyContinuation
2 parents f624106 + dd302b2 commit b179191

File tree

8 files changed

+113
-28
lines changed

8 files changed

+113
-28
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"
1717
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
1818
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
1919
RuntimeGeneratedFunctions = "7e49a35a-f44a-4d26-94aa-eba1b4ca6b47"
20+
Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46"
2021
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
2122
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
2223
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
@@ -42,6 +43,7 @@ Parameters = "0.12"
4243
Reexport = "0.2, 1.0"
4344
Requires = "1.0"
4445
RuntimeGeneratedFunctions = "0.5.12"
46+
Setfield = "1"
4547
SymbolicUtils = "1.0.3"
4648
Symbolics = "5.0.3"
4749
Unitful = "1.12.4"

ext/CatalystHomotopyContinuationExtension.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module CatalystHomotopyContinuationExtension
44
using Catalyst
55
import ModelingToolkit as MT
66
import HomotopyContinuation as HC
7+
import Setfield: @set
78
import Symbolics: unwrap, wrap, Rewriters, symtype, issym, istree
89

910
# Creates and exports hc_steady_states function.

ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ gives
3232
```
3333
3434
Notes:
35+
- Homotopy-based steady state finding only works when all rates are rational polynomials (e.g. constant, linear, mm, or hill functions).
3536
```
3637
"""
3738
function Catalyst.hc_steady_states(rs::ReactionSystem, ps; filter_negative=true, neg_thres=-1e-20, u0=[], kwargs...)
@@ -43,14 +44,14 @@ end
4344

4445
# For a given reaction system, parameter values, and initial conditions, find the polynomial that HC solves to find steady states.
4546
function steady_state_polynomial(rs::ReactionSystem, ps, u0)
47+
rs = Catalyst.expand_registered_functions(rs)
4648
ns = convert(NonlinearSystem, rs; remove_conserved = true)
4749
pre_varmap = [symmap_to_varmap(rs,u0)..., symmap_to_varmap(rs,ps)...]
4850
conservationlaw_errorcheck(rs, pre_varmap)
4951
p_vals = ModelingToolkit.varmap_to_vars(pre_varmap, parameters(ns); defaults = ModelingToolkit.defaults(ns))
5052
p_dict = Dict(parameters(ns) .=> p_vals)
5153
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 = map(eq -> substitute(eq.rhs - eq.lhs, p_dict), eqs_pars_funcs)
5455
eqs_intexp = make_int_exps.(eqs)
5556
return Catalyst.to_multivariate_poly(remove_denominators.(eqs_intexp))
5657
end
@@ -63,27 +64,6 @@ function conservationlaw_errorcheck(rs, pre_varmap)
6364
error("The system has conservation laws but initial conditions were not provided for some species.")
6465
end
6566

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-
8767
# Parses and expression and return a version where any exponents that are Float64 (but an int, like 2.0) are turned into Int64s.
8868
make_int_exps(expr) = wrap(Rewriters.Postwalk(Rewriters.PassThrough(___make_int_exps))(unwrap(expr))).val
8969
function ___make_int_exps(expr)

src/Catalyst.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ $(DocStringExtensions.README)
44
module Catalyst
55

66
using DocStringExtensions
7-
using SparseArrays, DiffEqBase, Reexport
7+
using SparseArrays, DiffEqBase, Reexport, Setfield
88
using LaTeXStrings, Latexify, Requires
99
using JumpProcesses: JumpProcesses,
1010
JumpProblem, MassActionJump, ConstantRateJump,

src/reactionsystem.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,7 @@ function Base.convert(::Type{<:NonlinearSystem}, rs::ReactionSystem; name = name
13351335
as_odes = false, include_zero_odes)
13361336
error_if_constraint_odes(NonlinearSystem, fullrs)
13371337
eqs, sts, ps, obs, defs = addconstraints!(eqs, fullrs, ists, ispcs; remove_conserved)
1338+
13381339
NonlinearSystem(eqs, sts, ps;
13391340
name,
13401341
observed = obs,

src/registered_functions.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,41 @@ function Symbolics.derivative(::typeof(hillar), args::NTuple{5, Any}, ::Val{5})
107107
(args[4]^args[5]) * log(args[4])) /
108108
(args[1]^args[5] + args[2]^args[5] + args[4]^args[5])^2
109109
end
110+
111+
"""
112+
expand_registered_functions(expr)
113+
114+
Takes an expression, and expands registered function expressions. E.g. `mm(X,v,K)` is replaced with v*X/(X+K). Currently supported functions: `mm`, `mmr`, `hill`, `hillr`, and `hill`.
115+
"""
116+
function expand_registered_functions(expr)
117+
istree(expr) || return expr
118+
args = arguments(expr)
119+
if operation(expr) == Catalyst.mm
120+
return args[2]*args[1]/(args[1] + args[3])
121+
elseif operation(expr) == Catalyst.mmr
122+
return args[2]*args[3]/(args[1] + args[3])
123+
elseif operation(expr) == Catalyst.hill
124+
return args[2]*(args[1]^args[4])/((args[1])^args[4] + (args[3])^args[4])
125+
elseif operation(expr) == Catalyst.hillr
126+
return args[2]*(args[3]^args[4])/((args[1])^args[4] + (args[3])^args[4])
127+
elseif operation(expr) == Catalyst.hillar
128+
return args[3]*(args[1]^args[5])/((args[1])^args[5] + (args[2])^args[5] + (args[4])^args[5])
129+
end
130+
for i = 1:length(args)
131+
args[i] = expand_registered_functions(args[i])
132+
end
133+
return expr
134+
end
135+
# If applied to a Reaction, return a reaction with its rate modified.
136+
function expand_registered_functions(rx::Reaction)
137+
Reaction(expand_registered_functions(rx.rate), rx.substrates, rx.products, rx.substoich, rx.prodstoich, rx.netstoich, rx.only_use_rate)
138+
end
139+
# If applied to a Equation, returns it with it applied to lhs and rhs
140+
function expand_registered_functions(eq::Equation)
141+
return expand_registered_functions(eq.lhs) ~ expand_registered_functions(eq.rhs)
142+
end
143+
# If applied to a ReactionSystem, applied function to all Reactions and other Equations, and return updated system.
144+
function expand_registered_functions(rs::ReactionSystem)
145+
rs = @set rs.eqs = [Catalyst.expand_registered_functions(eq) for eq in rs.eqs]
146+
return @set rs.rxs = [Catalyst.expand_registered_functions(rx) for rx in rs.rxs]
147+
end

test/dsl/custom_functions.jl

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ using ModelingToolkit: get_states, get_ps
66
using StableRNGs
77
rng = StableRNG(12345)
88

9-
### Tests Cutom Functions ###
9+
### Tests Custom Functions ###
1010
let
1111
new_hill(x, v, k, n) = v * x^n / (k^n + x^n)
1212
new_poly(x, p1, p2) = 0.5 * p1 * x^2
@@ -204,3 +204,65 @@ let
204204
v * (X^n) * ((K^n + Y^n) * log(X) - (K^n) * log(K) - (Y^n) * log(Y)) /
205205
(K^n + X^n + Y^n)^2)
206206
end
207+
208+
### Tests Current Function Expansion ###
209+
210+
# Tests `ReactionSystem`s.
211+
let
212+
@variables t
213+
@species x(t) y(t)
214+
@parameters k v n
215+
rs1 = @reaction_network rs begin
216+
mm(x, v, k), 0 --> x
217+
mmr(x, v, k), 0 --> x
218+
hill(x, v, k, n), 0 --> x
219+
hillr(x, v, k, n), 0 --> x
220+
hillar(x, y, v, k, n), 0 --> x
221+
end
222+
rs2 = @reaction_network rs begin
223+
v * x / (x + k), 0 --> x
224+
v * k / (x + k), 0 --> x
225+
v * (x^n) / (x^n + k^n), 0 --> x
226+
v * (k^n) / (x^n + k^n), 0 --> x
227+
v * (x^n) / (x^n + y^n + k^n), 0 --> x
228+
end
229+
230+
@test Catalyst.expand_registered_functions(rs1) == rs2
231+
end
232+
233+
# Tests `Reaction`s.
234+
let
235+
@variables t
236+
@species x(t) y(t)
237+
@parameters k v n
238+
239+
r1 = @reaction mm(x, v, k), 0 --> x
240+
r2 = @reaction mmr(x, v, k), 0 --> x
241+
r3 = @reaction hill(x, v, k, n), 0 --> x
242+
r4 = @reaction hillr(x, v, k, n), 0 --> x
243+
r5 = @reaction hillar(x, y, v, k, n), 0 --> x + y
244+
245+
@test isequal(Catalyst.expand_registered_functions(r1).rate, v * x / (x + k))
246+
@test isequal(Catalyst.expand_registered_functions(r2).rate, v * k / (x + k))
247+
@test isequal(Catalyst.expand_registered_functions(r3).rate, v * (x^n) / (x^n + k^n))
248+
@test isequal(Catalyst.expand_registered_functions(r4).rate, v * (k^n) / (x^n + k^n))
249+
@test isequal(Catalyst.expand_registered_functions(r5).rate, v * (x^n) / (x^n + y^n + k^n))
250+
end
251+
252+
# Tests `Equation`s.
253+
let
254+
@variables T X(T) Y(T)
255+
@parameters K V N
256+
257+
eq1 = 0 ~ mm(X, V, K)
258+
eq2 = 0 ~ mmr(X, V, K)
259+
eq3 = 0 ~ hill(X, V, K, N)
260+
eq4 = 0 ~ hillr(X, V, K, N)
261+
eq5 = 0 ~ hillar(X, Y, V, K, N)
262+
263+
@test isequal(Catalyst.expand_registered_functions(eq1), 0 ~ V * X / (X + K))
264+
@test isequal(Catalyst.expand_registered_functions(eq2), 0 ~ V * K / (X + K))
265+
@test isequal(Catalyst.expand_registered_functions(eq3), 0 ~ V * (X^N) / (X^N + K^N))
266+
@test isequal(Catalyst.expand_registered_functions(eq4), 0 ~ V * (K^N) / (X^N + K^N))
267+
@test isequal(Catalyst.expand_registered_functions(eq5), 0 ~ V * (X^N) / (X^N + Y^N + K^N))
268+
end

test/extensions/homotopy_continuation.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,12 @@ end
7979

8080
# Tests that non-scalar reaction rates work.
8181
# Tests that rational polynomial steady state systems work.
82+
# Tests that Hill function is correctly expanded even if nested.
8283
# Test filter_negative=false works.
83-
# tests than non-integer exponents throws an error.
84+
# Tests than non-integer exponents throws an error.
8485
let
8586
rs = @reaction_network begin
86-
0.1 + hill(X,v,K,n), 0 --> X
87+
v*(0.1/v + hill(X,1,K,n)), 0 --> X
8788
d, X --> 0
8889
end
8990
ps = [:v => 5.0, :K => 2.5, :n => 3, :d => 1.0]
@@ -92,7 +93,7 @@ let
9293
f = ODEFunction(convert(ODESystem, rs))
9394
@test length(sss) == 4
9495
for ss in sss
95-
@test f(sss[1], last.(ps), 0.0)[1] == 0.0
96+
@test isapprox(f(sss[1], last.(ps), 0.0)[1], 0.0; atol=1e-12)
9697
end
9798

9899
@test_throws Exception hc_steady_states(rs, [:v => 5.0, :K => 2.5, :n => 2.7, :d => 1.0]; show_progress=false)

0 commit comments

Comments
 (0)