Skip to content

Commit 5457842

Browse files
authored
Merge pull request #495 from isaacsas/update_stoich_tutorial
Update stoich tutorial
2 parents f52b3a9 + 35a37e5 commit 5457842

File tree

3 files changed

+91
-54
lines changed

3 files changed

+91
-54
lines changed

HISTORY.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
# Breaking updates and feature summaries across releases
22

33
## Catalyst unreleased (master branch)
4+
5+
## Catalyst 10.8
46
- Added the ability to use symbolic stoichiometry expressions via the DSL. This should now work
57
```julia
68
rn = @reaction_network rs begin
79
t*k, (α+k+B)*A --> B
810
1.0, α*A + 2*B --> k*C + α*D
911
end k α
1012
```
11-
Here Catalyst will try to preserve the order of symbols within an expression, taking the leftmost as the species and
12-
everything multiplying that species as stoichiometry. For example, we can interpret the above reaction as `S1 A --> S2 b`
13-
where `S1 = (α+k+B)` is the stoichiometry of the reactant `A` and `1` is the stoichiometry of the reactant `B`. For
13+
Here Catalyst will try to preserve the order of symbols within an expression,
14+
taking the rightmost as the species and everything multiplying that species as
15+
stoichiometry. For example, we can interpret the above reaction as `S1 A -->
16+
S2 b` where `S1 = (α+k+B)` is the stoichiometry of the reactant `A` and `1` is
17+
the stoichiometry of the reactant `B`. For
1418
```julia
1519
rn = @reaction_network rs begin
1620
1.0, 2X*(Y + Z) --> XYZ
@@ -28,7 +32,8 @@
2832
rx = @reaction 1.0, 2X*(Y + Z) --> XYZ
2933
```
3034
will make `X` a parameter and `Y`, `Z` and `XYZ` species.
31-
- Symbolic stoichiometry supports interpolation of expressions.
35+
- Symbolic stoichiometry supports interpolation of expressions in
36+
`@reaction_network` and `@reaction`.
3237

3338
## Catalyst 10.7
3439
- Added the ability to use symbolic variables, parameters and expressions for

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "Catalyst"
22
uuid = "479239e8-5488-4da2-87a7-35f2df7eef83"
3-
version = "10.7.0"
3+
version = "10.8.0"
44

55
[deps]
66
AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d"

docs/src/tutorials/symbolic_stoich.md

Lines changed: 81 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,127 @@
11
# Parametric Stoichiometry
2-
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.*
37

48
## 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.
611
```@example s1
712
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
840
@parameters k₊,k₋,m,n
941
@variables t, A(t), B(t)
1042
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
1453
```
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:
1660
```@example s1
1761
osys = convert(ODESystem, revsys)
1862
equations(osys)
1963
show(stdout, MIME"text/plain"(), equations(osys)) # hide
2064
```
21-
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*
2269
```@example s1
2370
p = (k₊ => 1.0, k₋ => 1.0, m => 2, n => 2)
2471
u₀ = [A => 1.0, B => 1.0]
2572
oprob = ODEProblem(osys, u₀, (0.0,1.0), p)
2673
```
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
4875
```@example s1
4976
sol = solve(oprob, Tsit5())
5077
plot(sol)
5178
```
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.*
5380

5481
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
5582
```@example s1
5683
osys = convert(ODESystem, revsys; combinatoric_ratelaws=false)
5784
equations(osys)
5885
show(stdout, MIME"text/plain"(), equations(osys)) # hide
5986
```
60-
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`:
6189
```@example s1
90+
p = (k₊ => 1.0, k₋ => 1.0, m => 2.0, n => 2.0)
6291
oprob = ODEProblem(osys, u₀, (0.0,1.0), p)
6392
sol = solve(oprob, Tsit5())
6493
plot(sol)
6594
```
6695

6796
## 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:
75100
```@example s1
76101
using Distributions: Geometric
77102
@register Geometric(b)
103+
@parameters b
78104
m = rand(Geometric(1/b)) + 1
79105
nothing # hide
80106
```
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).
82108

83109
We can now define our model
84110
```@example s1
85-
rxs = [Reaction(k₊, [G₋], [G₊]),
86-
Reaction(k₋*P^2, [G₊], [G₋]),
87-
Reaction(kₚ, [G₊], [G₊,P], [1], [1,m]),
88-
Reaction(γₚ, [P], nothing)]
89-
@named burstyrn = ReactionSystem(rxs, t)
111+
burstyrn = @reaction_network burstyrn begin
112+
k₊, G₋ --> G₊
113+
k₋*P^2, G₊ --> G₋
114+
kₚ, G₊ --> G₊ + $m*P
115+
γₚ, P --> ∅
116+
end k₊ k₋ kₚ γₚ
90117
reactions(burstyrn)
91118
show(stdout, MIME"text/plain"(), reactions(burstyrn)) # hide
92119
```
93-
and convert it to a jump process representation
120+
The parameter `b` does not need to be explicitly declared in the
121+
`@reaction_network` macro as it is detected when the expression
122+
`rand(Geometric(1/b)) + 1` is substituted for `m`.
123+
124+
We next convert our network to a jump process representation
94125
```@example s1
95126
jsys = convert(JumpSystem, burstyrn; combinatoric_ratelaws=false)
96127
equations(jsys)
@@ -113,15 +144,15 @@ bval = 70
113144
k₋val = 0.001
114145
k₊val = 0.05
115146
kₚval = pmean * γₚval * (k₋val * pmean^2 + k₊val) / (k₊val * bval)
116-
p = (k₊ => k₊val, k₋ => k₋val, kₚ => kₚval, γₚ => γₚval, b => bval)
117-
u₀ = [G₊ => 1, G₋ => 0, P => 1]
147+
p = symmap_to_varmap(jsys, (:k₊ => k₊val, :k₋ => k₋val, :kₚ => kₚval, :γₚ => γₚval, :b => bval))
148+
u₀ = symmap_to_varmap(jsys, [:G₊ => 1, :G₋ => 0, :P => 1])
118149
tspan = (0., 6.0) # time interval to solve over
119150
dprob = DiscreteProblem(jsys, u₀, tspan, p)
120151
jprob = JumpProblem(jsys, dprob, Direct())
121152
sol = solve(jprob, SSAStepper())
122-
plot(sol.t, sol[P], legend=false, xlabel="time", ylabel="P(t)")
153+
plot(sol.t, sol[jsys.P], legend=false, xlabel="time", ylabel="P(t)")
123154
```
124-
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/)).
125156
```@example s1
126157
function getmean(jprob, Nsims, tv)
127158
Pmean = zeros(length(tv))
@@ -135,4 +166,5 @@ end
135166
tv = range(tspan[1],tspan[2],step=.1)
136167
psim_mean = getmean(jprob, 20000, tv)
137168
plot(tv, psim_mean, ylabel="average of P(t)", xlabel="time", xlim=(0.0,6.0), legend=false)
138-
```
169+
```
170+
Comparing, we see similar averages for `P(t)`.

0 commit comments

Comments
 (0)