You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Catalyst supports stoichiometric coefficients that involve parameters, species, or even general expressions. In this tutorial we show several examples of how to use parametric stoichiometry, and discuss several caveats to be aware of.
2
+
Catalyst supports stoichiometric coefficients that involve parameters, species,
3
+
or even general expressions. In this tutorial we show several examples of how to
4
+
use parametric stoichiometry, and discuss several caveats to be aware of.
5
+
6
+
*Note, this tutorial requires ModelingToolkit v8.5.4 or greater to work properly.*
3
7
4
8
## Using Symbolic Stoichiometry
5
-
Let's first consider a simple reversible reaction where the number of reactants is a parameter, and the number of products is the product of two parameters. Note, currently Catalyst's `@reaction_network` macro does not support symbolic stoichiometry, so the model needs to be specified through the symbolic API:
9
+
Let's first consider a simple reversible reaction where the number of reactants
10
+
is a parameter, and the number of products is the product of two parameters.
6
11
```@example s1
7
12
using Catalyst, Latexify, DifferentialEquations, ModelingToolkit, Plots
13
+
revsys = @reaction_network revsys begin
14
+
k₊, m*A --> (m*n)*B
15
+
k₋, B --> A
16
+
end k₊ k₋ m n
17
+
reactions(revsys)
18
+
```
19
+
Note, as always the `@reaction_network` macro sets all symbols not declared to
20
+
be parameters to be species, so that in this example we have two species, `A`
21
+
and `B`, and four parameters. In addition, the stoichiometry is applied to the
22
+
right most symbol in a given term, i.e. in the first equation the substrate `A`
23
+
has stoichiometry `m` and the product `B` has stoichiometry `m*n`. For example,
24
+
in
25
+
```@example s1
26
+
rn = @reaction_network begin
27
+
k, A*C --> 2B
28
+
end k
29
+
reactions(rn)
30
+
```
31
+
we see three species, `(A,B,C)`, however, `A` is treated as the stoichiometric
32
+
coefficient of `C`, i.e.
33
+
```@example s1
34
+
rx = reactions(rn)[1]
35
+
rx.substrates[1],rx.substoich[1]
36
+
```
37
+
We could have equivalently specified our systems directly via the Catalyst
38
+
API. For example, for `revsys` we would could use
39
+
```@example s1
8
40
@parameters k₊,k₋,m,n
9
41
@variables t, A(t), B(t)
10
42
rxs = [Reaction(k₊,[A],[B],[m],[m*n]),
11
-
Reaction(k₋,[B],[A])]
12
-
@named revsys = ReactionSystem(rxs,t)
13
-
reactions(revsys)
43
+
Reaction(k₋,[B],[A])]
44
+
revsys2 = ReactionSystem(rxs,t; name=:revsys)
45
+
revsys2 == revsys
46
+
```
47
+
which can be simplified using the `@reaction` macro to
48
+
```@example s1
49
+
rxs2 = [(@reaction k₊, m*A --> (m*n)*B),
50
+
(@reaction k₋, B --> A)]
51
+
revsys3 = ReactionSystem(rxs2,t; name=:revsys)
52
+
revsys3 == revsys
14
53
```
15
-
Let's now convert the system to ODEs and look at the resulting equations:
54
+
Note, the `@reaction` macro assumes all symbols are parameters except the right
55
+
most symbols in the reaction line (i.e. `A` and `B`). For example, in
56
+
`@reaction k, F*A + 2(H*G+B) --> D`, the substrates are `(A,G,B)` with
57
+
stoichiometries `(F,2*H,2)`.
58
+
59
+
Let's now convert `revsys` to ODEs and look at the resulting equations:
Notice, as described in the [Reaction rate laws used in simulations](@ref) section, the default rate laws involve factorials in the stoichiometric coefficients. As ModelingToolkit currently converts numeric parameters to a common type, this can lead to difficulties since the `factorial` function only accepts integer input, i.e. the integer parameters in `p`
65
+
Notice, as described in the [Reaction rate laws used in simulations](@ref)
66
+
section, the default rate laws involve factorials in the stoichiometric
67
+
coefficients. For this reason we must specify `m` and `n` as integers, and hence
68
+
*use a tuple for the parameter mapping*
22
69
```@example s1
23
70
p = (k₊ => 1.0, k₋ => 1.0, m => 2, n => 2)
24
71
u₀ = [A => 1.0, B => 1.0]
25
72
oprob = ODEProblem(osys, u₀, (0.0,1.0), p)
26
73
```
27
-
are converted to floating point:
28
-
```@example s1
29
-
oprob.p
30
-
```
31
-
Calling
32
-
```julia
33
-
sol =solve(oprob, Tsit5())
34
-
```
35
-
will now give an error that
36
-
```julia
37
-
MethodError: no method matching factorial(::Float64)
38
-
```
39
-
40
-
There are two ways around this problem. First, we can rebuild `oprob` to use a parameter tuple of the correct type. This is complicated slightly as we need to know the parameter ordering used internally by ModelingToolkit. A robust way to do this when the parameter ordering is not known is the following:
41
-
```@example s1
42
-
pmap = Dict(p)
43
-
pcorrect = Tuple(pmap[psym] for psym in parameters(osys))
44
-
oprob = remake(oprob, p=pcorrect)
45
-
oprob.p
46
-
```
47
-
now `oprob.p` has the correct type to use in solving the system
74
+
We can now solve and plot the system
48
75
```@example s1
49
76
sol = solve(oprob, Tsit5())
50
77
plot(sol)
51
78
```
52
-
*Note, to allow for mixed parameter types (i.e. integers and floats in this example), it is necessary to use a tuple to store parameters.*
79
+
*If we had used a vector to store parameters, `m`and `n` would be converted to floating point giving an error when solving the system.*
53
80
54
81
An alternative approach to avoid the issues of using mixed floating point and integer variables is to disable the rescaling of rate laws as described in [Reaction rate laws used in simulations](@ref) section. This requires passing the `combinatoric_ratelaws=false` keyword to `convert` or to `ODEProblem` (if directly building the problem from a `ReactionSystem` instead of first converting to an `ODESystem`). For the previous example this gives the following (different) system of ODEs
Since we no longer have factorial functions appearing, our example will now run when ModelingToolkit converts `m` and `n` to be floating point:
87
+
Since we no longer have factorial functions appearing, our example will now run
88
+
even with floating point values for `m` and `n`:
61
89
```@example s1
90
+
p = (k₊ => 1.0, k₋ => 1.0, m => 2.0, n => 2.0)
62
91
oprob = ODEProblem(osys, u₀, (0.0,1.0), p)
63
92
sol = solve(oprob, Tsit5())
64
93
plot(sol)
65
94
```
66
95
67
96
## Gene expression with randomly produced amounts of protein
68
-
As a second example, let's build the negative feedback model from [MomentClosure.jl](https://augustinas1.github.io/MomentClosure.jl/dev/tutorials/geometric_reactions+conditional_closures/) that involves a bursty reaction that produces a random amount of protein. First, let's define our chemical species and parameters
69
-
```@example s1
70
-
@parameters k₊, k₋, kₚ, γₚ, b
71
-
@variables t, G₋(t), G₊(t), P(t)
72
-
nothing # hide
73
-
```
74
-
Here `G₋` denotes the repressed state, and `G₊` the active state where the gene can transcribe. `P` denotes the protein product of the gene. We will assume that proteins are produced in bursts that produce `m` proteins, where `m` is a (shifted) geometric random variable with mean `b`. To define `m` we must register the `Distributions.Geometric` distribution from Distributions.jl with Symbolics.jl, after which we can use it in symbolic expressions:
97
+
As a second example, let's build the negative feedback model from [MomentClosure.jl](https://augustinas1.github.io/MomentClosure.jl/dev/tutorials/geometric_reactions+conditional_closures/) that involves a bursty reaction that produces a random amount of protein.
98
+
99
+
In our model `G₋` will denote the repressed state, and `G₊` the active state where the gene can transcribe. `P` will denote the protein product of the gene. We will assume that proteins are produced in bursts that produce `m` proteins, where `m` is a (shifted) geometric random variable with mean `b`. To define `m` we must register the `Distributions.Geometric` distribution from Distributions.jl with Symbolics.jl, after which we can use it in symbolic expressions:
75
100
```@example s1
76
101
using Distributions: Geometric
77
102
@register Geometric(b)
103
+
@parameters b
78
104
m = rand(Geometric(1/b)) + 1
79
105
nothing # hide
80
106
```
81
-
Note, as we require the shifted geometric distribution, we add one to Distributions.jl's `Geometric` random variable (which includes zero).
107
+
Note, as we require the shifted geometric distribution, we add one to Distributions.jl's `Geometric` random variable (which includes zero).
To double check our results are consistent with MomentClosure.jl, let's calculate and plot the average amount of protein
155
+
To double check our results are consistent with MomentClosure.jl, let's calculate and plot the average amount of protein (which is also plotted in the MomentClosure.jl [tutorial](https://augustinas1.github.io/MomentClosure.jl/dev/tutorials/geometric_reactions+conditional_closures/)).
125
156
```@example s1
126
157
function getmean(jprob, Nsims, tv)
127
158
Pmean = zeros(length(tv))
@@ -135,4 +166,5 @@ end
135
166
tv = range(tspan[1],tspan[2],step=.1)
136
167
psim_mean = getmean(jprob, 20000, tv)
137
168
plot(tv, psim_mean, ylabel="average of P(t)", xlabel="time", xlim=(0.0,6.0), legend=false)
0 commit comments