|
1 |
| -# [Basic Chemical Reaction Network Examples](@id basic_CRN_examples) |
| 1 | +# [Library of Basic Chemical Reaction Network Models](@id basic_CRN_library) |
| 2 | +Below we will present various simple and established chemical reaction network (CRN) models. Each model is given some brief background, implemented using the `@reaction_network` DSL, and basic simulations are performed. |
2 | 3 |
|
3 |
| -## Example: Birth-death process |
4 |
| - |
5 |
| -```@example bcrn1 |
6 |
| -using Catalyst, DifferentialEquations, Plots |
| 4 | +## Birth-death process |
| 5 | +The birth-death process is one of the simplest possible CRN models. It consists of a single component ($X$) which is both produced and degraded at linear rates: |
| 6 | +```@example crn_library_birth_death |
| 7 | +using Catalyst |
| 8 | +bd_process = @reaction_network begin |
| 9 | + (p,d), ∅ <--> X |
| 10 | +end |
| 11 | +``` |
| 12 | +Next we define simulation conditions. Note that the initial condition is integer-valued (required to perform jump simulations). |
| 13 | +```@example crn_library_birth_death |
| 14 | +u0 = [:X => 1] |
| 15 | +tspan = (0.0, 10.0) |
| 16 | +ps = [:p => 1, :d => 0.2] |
| 17 | +``` |
| 18 | +We can now simulate our model using all three interpretations. First we perform a reaction rate equation-based ODE simulation: |
| 19 | +```@example crn_library_birth_death |
| 20 | +using OrdinaryDiffEq |
| 21 | +oprob = ODEProblem(bd_process, u0, tspan, ps) |
| 22 | +osol = solve(oprob, Tsit5()) |
| 23 | +nothing # hide |
| 24 | +``` |
| 25 | +Next, a chemical Langevin equation-based SDE simulation: |
| 26 | +```@example crn_library_birth_death |
| 27 | +using StochasticDiffEq |
| 28 | +sprob = SDEProblem(bd_process, u0, tspan, ps) |
| 29 | +ssol = solve(sprob, ImplicitEM()) |
| 30 | +nothing # hide |
| 31 | +``` |
| 32 | +Next, a stochastic chemical kinetics-based jump simulation: |
| 33 | +```@example crn_library_birth_death |
| 34 | +using JumpProcesses |
| 35 | +dprob = DiscreteProblem(bd_process, u0, tspan, ps) |
| 36 | +jprob = JumpProblem(bd_process, dprob, Direct()) |
| 37 | +jsol = solve(jprob, SSAStepper()) |
| 38 | +nothing # hide |
| 39 | +``` |
| 40 | +Finally, we plot the results: |
| 41 | +```@example crn_library_birth_death |
| 42 | +using Plots |
| 43 | +oplt = plot(osol; title = "Reaction rate equation (ODE)") |
| 44 | +splt = plot(ssol; title = "Chemical Langevin equation (SDE)") |
| 45 | +jplt = plot(jsol; title = "Stochastic chemical kinetics (Jump)") |
| 46 | +plot(oplt, splt, jplt; size=(800,700), layout = (3,1)) |
| 47 | +``` |
7 | 48 |
|
8 |
| -rs = @reaction_network begin |
9 |
| - c1, X --> 2X |
10 |
| - c2, X --> 0 |
11 |
| - c3, 0 --> X |
| 49 | +## Michaelis-Menten enzyme kinetics |
| 50 | +[Michaelis-Menten enzyme kinetics](https://en.wikipedia.org/wiki/Michaelis%E2%80%93Menten_kinetics) is a simple description of an enzyme ($E$) transforming a substrate ($S$) into a product ($P$). Under certain assumptions it can be simplified to a singe function (a Michaelis-Menten function) and used as a reaction rate. Here we instead present the full system model: |
| 51 | +```@example crn_library_michaelis_menten |
| 52 | +using Catalyst |
| 53 | +mm_system = @reaction_network begin |
| 54 | + kB, S + E --> SE |
| 55 | + kD, SE --> S + E |
| 56 | + kP, SE --> P + E |
12 | 57 | end
|
13 |
| -p = (:c1 => 1.0, :c2 => 2.0, :c3 => 50.) |
14 |
| -tspan = (0.,4.) |
15 |
| -u0 = [:X => 5.] |
| 58 | +``` |
| 59 | +Next, we perform ODE, SDE, and jump simulations of the model: |
| 60 | +```@example crn_library_michaelis_menten |
| 61 | +u0 = [:S => 301, :E => 100, :SE => 0, :P => 0] |
| 62 | +tspan = (0., 100.) |
| 63 | +ps = [:kB => 0.00166, :kD => 0.0001, :kP => 0.1] |
16 | 64 |
|
17 |
| -# solve ODEs |
18 |
| -oprob = ODEProblem(rs, u0, tspan, p) |
| 65 | +using OrdinaryDiffEq |
| 66 | +oprob = ODEProblem(mm_system, u0, tspan, ps) |
19 | 67 | osol = solve(oprob, Tsit5())
|
20 | 68 |
|
21 |
| -# solve for Steady states |
22 |
| -ssprob = SteadyStateProblem(rs, u0, p) |
23 |
| -sssol = solve(ssprob, SSRootfind()) |
| 69 | +using StochasticDiffEq |
| 70 | +sprob = SDEProblem(mm_system, u0, tspan, ps) |
| 71 | +ssol = solve(sprob, ImplicitEM()) |
24 | 72 |
|
25 |
| -# solve SDEs |
26 |
| -sprob = SDEProblem(rs, u0, tspan, p) |
27 |
| -ssol = solve(sprob, EM(), dt=.01) |
28 |
| -
|
29 |
| -# solve JumpProblem |
30 |
| -u0 = [:X => 5] |
31 |
| -dprob = DiscreteProblem(rs, u0, tspan, p) |
32 |
| -jprob = JumpProblem(rs, dprob, Direct()) |
| 73 | +using JumpProcesses |
| 74 | +dprob = DiscreteProblem(mm_system, u0, tspan, ps) |
| 75 | +jprob = JumpProblem(mm_system, dprob, Direct()) |
33 | 76 | jsol = solve(jprob, SSAStepper())
|
34 | 77 |
|
35 |
| -plot(plot(osol; title = "Reaction Rate Equation ODEs"), |
36 |
| - plot(ssol; title = "Chemical Langevin SDEs"), |
37 |
| - plot(jsol; title = "Stochastic Chemical Kinetics Jump Processes"); |
38 |
| - layout = (3, 1)) |
| 78 | +using Plots |
| 79 | +oplt = plot(osol; title = "Reaction rate equation (ODE)") |
| 80 | +splt = plot(ssol; title = "Chemical Langevin equation (SDE)") |
| 81 | +jplt = plot(jsol; title = "Stochastic chemical kinetics (Jump)") |
| 82 | +plot(oplt, splt, jplt; size=(800,700), layout = (3,1)) |
39 | 83 | ```
|
40 | 84 |
|
41 |
| -## Example: Michaelis-Menten enzyme kinetics |
42 |
| - |
43 |
| -```@example bcrn2 |
44 |
| -using Catalyst, DifferentialEquations, Plots |
45 |
| -
|
46 |
| -rs = @reaction_network begin |
47 |
| - c1, S + E --> SE |
48 |
| - c2, SE --> S + E |
49 |
| - c3, SE --> P + E |
| 85 | +## SIR infection model |
| 86 | +The [SIR model](https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SIR_model) is the simplest model of the spread of an infectious disease. While the real system is very different from the chemical and cellular processes typically modelled with CRNs, it (and several other epidemiological systems) can be modelled using the same CRN formalism. The SIR model consists of three species: susceptible ($S$), infected ($I$), and removed ($R$) individuals, and two reaction events: infection and recovery. |
| 87 | +```@example crn_library_sir |
| 88 | +using Catalyst |
| 89 | +sir_model = @reaction_network begin |
| 90 | + α, S + I --> 2I |
| 91 | + β, I --> R |
50 | 92 | end
|
51 |
| -p = (:c1 => 0.00166, :c2 => 0.0001, :c3 => 0.1) |
52 |
| -tspan = (0., 100.) |
53 |
| -u0 = [:S => 301., :E => 100., :SE => 0., :P => 0.] |
| 93 | +``` |
| 94 | +First we perform a deterministic ODE simulation: |
| 95 | +```@example crn_library_sir |
| 96 | +using OrdinaryDiffEq, Plots |
| 97 | +u0 = [:S => 99, :I => 1, :R => 0] |
| 98 | +tspan = (0.0, 500.0) |
| 99 | +ps = [:α => 0.001, :β => 0.01] |
54 | 100 |
|
55 |
| -# solve ODEs |
56 |
| -oprob = ODEProblem(rs, u0, tspan, p) |
57 |
| -osol = solve(oprob, Tsit5()) |
| 101 | +# Solve ODEs. |
| 102 | +oprob = ODEProblem(sir_model, u0, tspan, ps) |
| 103 | +osol = solve(oprob, Tsit5()) |
| 104 | +plot(osol; title = "Reaction rate equation (ODE)") |
| 105 | +``` |
| 106 | +Next we perform 3 different Jump simulations. Note that for the stochastic model, the occurrence of a outbreak is not certain. Rather, there is a possibility that it fizzles out without a noteworthy peak. |
| 107 | +```@example crn_library_sir |
| 108 | +using JumpProcesses |
| 109 | +dprob = DiscreteProblem(sir_model, u0, tspan, ps) |
| 110 | +jprob = JumpProblem(sir_model, dprob, Direct()) |
58 | 111 |
|
59 |
| -# solve JumpProblem |
60 |
| -u0 = [:S => 301, :E => 100, :SE => 0, :P => 0] |
61 |
| -dprob = DiscreteProblem(rs, u0, tspan, p) |
62 |
| -jprob = JumpProblem(rs, dprob, Direct()) |
63 |
| -jsol = solve(jprob, SSAStepper()) |
| 112 | +jsol1 = solve(jprob, SSAStepper()) |
| 113 | +jsol2 = solve(jprob, SSAStepper()) |
| 114 | +jsol3 = solve(jprob, SSAStepper()) |
| 115 | +jsol1 = solve(jprob, SSAStepper(); seed=1) # hide |
| 116 | +jsol2 = solve(jprob, SSAStepper(); seed=2) # hide |
| 117 | +jsol3 = solve(jprob, SSAStepper(); seed=3) # hide |
64 | 118 |
|
65 |
| -plot(plot(osol; title = "Reaction Rate Equation ODEs"), |
66 |
| - plot(jsol; title = "Stochastic Chemical Kinetics Jump Processes"); |
67 |
| - layout = (2, 1)) |
| 119 | +jplt1 = plot(jsol1; title = "Outbreak") |
| 120 | +jplt2 = plot(jsol2; title = "Outbreak") |
| 121 | +jplt3 = plot(jsol3; title = "No outbreak") |
| 122 | +plot(jplt1, jplt2, jplt3; size=(800,700), layout = (3,1)) |
68 | 123 | ```
|
69 | 124 |
|
70 |
| -## Example: SIR infection model |
71 |
| -```@example bcrn3 |
72 |
| -using Catalyst, DifferentialEquations, Plots |
| 125 | +## The Wilhelm model |
| 126 | +The Wilhelm model was introduced in [*Wilhelm (2009)*](https://bmcsystbiol.biomedcentral.com/articles/10.1186/1752-0509-3-90) as the smallest CRN model (with constant rates) that exhibits bistability. |
| 127 | +```@example crn_library_wilhelm |
| 128 | +wilhelm_model = @reaction_network begin |
| 129 | + k1, Y --> 2X |
| 130 | + k2, 2X --> X + Y |
| 131 | + k3, X + Y --> Y |
| 132 | + k4, X --> 0 |
| 133 | +end |
| 134 | +``` |
| 135 | +We can simulate the model for two different initial conditions, demonstrating the existence of two different stable steady states. |
| 136 | +```@example crn_library_wilhelm |
| 137 | +using OrdinaryDiffEq, Plots |
| 138 | +u0_1 = [:X => 1.5, :Y => 0.5] |
| 139 | +u0_2 = [:X => 2.5, :Y => 0.5] |
| 140 | +tspan = (0., 10.) |
| 141 | +ps = [:k1 => 8.0, :k2 => 2.0, :k3 => 1.0, :k4 => 1.5] |
73 | 142 |
|
74 |
| -rs = @reaction_network begin |
75 |
| - α, S + I --> 2I |
76 |
| - β, I --> R |
| 143 | +oprob1 = ODEProblem(wilhelm_model, u0_1, tspan, ps) |
| 144 | +oprob2 = ODEProblem(wilhelm_model, u0_2, tspan, ps) |
| 145 | +osol1 = solve(oprob1, Tsit5()) |
| 146 | +osol2 = solve(oprob2, Tsit5()) |
| 147 | +oplt1 = plot(osol1; idxs = :X, label = "X(0) = 1.5") |
| 148 | +oplt2 = plot!(osol2; idxs = :X, label = "X(0) = 2.5", yguide = "X", size = (800,700)) |
| 149 | +``` |
| 150 | + |
| 151 | +## Simple self-activation loop |
| 152 | +The simplest self-activation loop consist of a single species (here called $X$) which activates its own production. If its production rate is modelled with a hill function with $n>1$, the system may exhibit bistability. |
| 153 | +```@example crn_library_self_activation |
| 154 | +using Catalyst |
| 155 | +sa_loop = @reaction_network begin |
| 156 | + v₀ + hill(X,v,K,n), ∅ --> X |
| 157 | + d, X --> ∅ |
77 | 158 | end
|
78 |
| -p = [:α => .1/100, :β => .01] |
79 |
| -tspan = (0.0,500.0) |
80 |
| -u0 = [:S => 99.0, :I => 1.0, :R => 0.0] |
| 159 | +``` |
| 160 | +A simple example of such a loop is a transcription factor which activates its own gene. Here, $v₀$ represents a basic transcription rate (leakage) in the absence of the transcription factor. |
81 | 161 |
|
82 |
| -# Solve ODEs. |
83 |
| -oprob = ODEProblem(rs, u0, tspan, p) |
84 |
| -osol = solve(oprob) |
| 162 | +We simulate the self-activation loop from a single initial condition using both deterministic (ODE) and stochastic (jump) simulations. We note that while the deterministic simulation reaches a single steady state, the stochastic one switches between two different states. |
| 163 | +```@example crn_library_self_activation |
| 164 | +using OrdinaryDiffEq, Plots |
| 165 | +u0 = [:X => 4] |
| 166 | +tspan = (0.0, 1000.0) |
| 167 | +ps = [:v₀ => 0.1, :v => 2.0, :K => 10.0, :n => 2, :d => 0.1] |
85 | 168 |
|
86 |
| -# Solve Jumps. |
87 |
| -dprob = DiscreteProblem(rs, u0, tspan, p) |
88 |
| -jprob = JumpProblem(rs, dprob, Direct()) |
| 169 | +oprob = ODEProblem(sa_loop, u0, tspan, ps) |
| 170 | +osol = solve(oprob, Tsit5()) |
| 171 | +
|
| 172 | +dprob = DiscreteProblem(sa_loop, u0, tspan, ps) |
| 173 | +jprob = JumpProblem(sa_loop, dprob, Direct()) |
89 | 174 | jsol = solve(jprob, SSAStepper())
|
| 175 | +jsol = solve(jprob, SSAStepper(); seed = 2091) # hide |
90 | 176 |
|
91 |
| -plot(plot(osol; title = "Reaction Rate Equation ODEs"), |
92 |
| - plot(jsol; title = "Stochastic Chemical Kinetics Jump Processes"); |
93 |
| - layout = (2, 1)) |
| 177 | +plot(osol; label = "Reaction rate equation (ODE)") |
| 178 | +plot!(jsol; label = "Stochastic chemical kinetics (Jump)", yguide = "X", size = (800,600)) |
94 | 179 | ```
|
95 | 180 |
|
96 |
| -## Example: Brusselator chemical reaction network |
97 |
| -```@example bcrn4 |
98 |
| -using Catalyst, DifferentialEquations, Plots |
99 |
| -
|
100 |
| -rs = @reaction_network begin |
101 |
| - @parameters A B |
| 181 | +## The Brusselator |
| 182 | +The [Brusselator](https://en.wikipedia.org/wiki/Brusselator) is a well known (theoretical) CRN model able to produce oscillations (its name is a portmanteau of "Brussels" and "oscillator"). |
| 183 | +```@example crn_library_brusselator |
| 184 | +using Catalyst |
| 185 | +brusselator = @reaction_network begin |
102 | 186 | A, ∅ --> X
|
103 | 187 | 1, 2X + Y --> 3X
|
104 | 188 | B, X --> Y
|
105 | 189 | 1, X --> ∅
|
106 | 190 | end
|
107 |
| -tspan = (0.0,50.0) |
| 191 | +``` |
| 192 | +It is generally known to (for reaction rate equation-based ODE simulations) produce oscillations when $B > 1 + A^2$. However, this results is based on models generated when *combinatorial adjustment of rates is not performed*. Since Catalyst automatically perform these adjustments, and one reaction contain a stoichiometric constant $>1$, the threshold will be different. Here, we trial two different values of $B$. In both cases, $B < 1 + A^2$, however, in he second case the system is able to generate oscillations. |
| 193 | +```@example crn_library_brusselator |
| 194 | +using OrdinaryDiffEq, Plots |
108 | 195 | u0 = [:X => 1.0, :Y => 1.0]
|
| 196 | +tspan = (0., 50.) |
| 197 | +ps1 = [:A => 1.0, :B => 1.0] |
| 198 | +ps2 = [:A => 1.0, :B => 1.8] |
109 | 199 |
|
110 |
| -# Non-oscillation parameter set |
111 |
| -oprob1 = ODEProblem(rs, u0, tspan, [:A => 1.0, :B => 1.0]) |
112 |
| -osol1 = solve(oprob1) |
| 200 | +oprob1 = ODEProblem(brusselator, u0, tspan, ps1) |
| 201 | +oprob2 = ODEProblem(brusselator, u0, tspan, ps2) |
| 202 | +osol1 = solve(oprob1, Rodas5P()) |
| 203 | +osol2 = solve(oprob2, Rodas5P()) |
| 204 | +oplt1 = plot(osol1; title = "No Oscillation") |
| 205 | +oplt2 = plot(osol2; title = "Oscillation") |
113 | 206 |
|
114 |
| -# Oscillation parameter set |
115 |
| -oprob2 = ODEProblem(rs, u0, tspan, [:A => 1.0, :B => 3.0]) |
116 |
| -osol2 = solve(oprob2) |
| 207 | +plot(oplt1, oplt2; layout = (1,2), size(800,700)) |
| 208 | +``` |
| 209 | + |
| 210 | +## The repressilator |
| 211 | +The repressilator was introduced in [*Elowitz & Leibler (2000)*](https://www.nature.com/articles/35002125) as a simple system that is able to generate oscillations (most notably, they demonstrated this both in a model and in a synthetic in vivo implementation in *Escherichia col*). It consists of three genes, repressing each other in a cycle. Here, we will implement it using three species ($X$, $Y$, and $Z$) which production rates are (repressing) [Hill functions](https://en.wikipedia.org/wiki/Hill_equation_(biochemistry)). |
| 212 | +```@example crn_library_brusselator |
| 213 | +using Catalyst |
| 214 | +repressilator = @reaction_network begin |
| 215 | + hillr(Z,v,K,n), ∅ --> X |
| 216 | + hillr(X,v,K,n), ∅ --> Y |
| 217 | + hillr(Y,v,K,n), ∅ --> Z |
| 218 | + d, (X, Y, Z) --> ∅ |
| 219 | +end |
| 220 | +``` |
| 221 | +Whether it oscillates or not depends on its parameter values. Here, we will perform deterministic (ODE) simulations for two different values of $K$, showing that it oscillates for one value and not the other one. Next, we will perform stochastic (SDE) simulations for both $K$ values, showing that the stochastic model is able to sustain oscillations in both cases. This is an example of the phenomena of *noise-induced oscillation*. |
| 222 | +```@example crn_library_brusselator |
| 223 | +using OrdinaryDiffEq, StochasticDiffEq, Plots |
| 224 | +u0 = [:X => 50.0, :Y => 15.0, :Z => 15.0] |
| 225 | +tspan = (0., 200.) |
| 226 | +ps1 = [:v => 10.0, :K => 20.0, :n => 3, :d => 0.1] |
| 227 | +ps2 = [:v => 10.0, :K => 50.0, :n => 3, :d => 0.1] |
| 228 | +
|
| 229 | +oprob1 = ODEProblem(repressilator, u0, tspan, ps1) |
| 230 | +oprob2 = ODEProblem(repressilator, u0, tspan, ps2) |
| 231 | +osol1 = solve(oprob1, Tsit5()) |
| 232 | +osol2 = solve(oprob2, Tsit5()) |
| 233 | +oplt1 = plot(osol1; title = "Oscillation (ODE, K = 20)") |
| 234 | +oplt2 = plot(osol2; title = "No oscillation (ODE, K = 50)") |
| 235 | +
|
| 236 | +sprob1 = SDEProblem(repressilator, u0, tspan, ps1) |
| 237 | +sprob2 = SDEProblem(repressilator, u0, tspan, ps2) |
| 238 | +ssol1 = solve(sprob1, ImplicitEM()) |
| 239 | +ssol2 = solve(sprob2, ImplicitEM()) |
| 240 | +ssol1 = solve(sprob1, ImplicitEM(); seed = 1) # hide |
| 241 | +ssol2 = solve(sprob2, ImplicitEM(); seed = 100) # hide |
| 242 | +splt1 = plot(ssol1; title = "Oscillation (SDE, K = 20)") |
| 243 | +splt2 = plot(ssol2; title = "Oscillation (SDE, K = 50)") |
| 244 | +
|
| 245 | +plot(oplt1, oplt2, splt1, splt2; layout = (2,2), size = (800,600)) |
| 246 | +``` |
| 247 | + |
| 248 | +## The Willamowski–Rössler model |
| 249 | +The Willamowski–Rössler model was introduced in [*Willamowski & Rössler (1979)*](https://www.degruyter.com/document/doi/10.1515/zna-1980-0308/html?lang=en) as an example of a simple CRN model which exhibits [*chaotic behaviours*](https://en.wikipedia.org/wiki/Chaos_theory). This means that small changes in initial conditions can produce relatively large changes in the system's trajectory. |
| 250 | +```@example crn_library_chaos |
| 251 | +using Catalyst |
| 252 | +wr_model = @reaction_network begin |
| 253 | + k1, 2X --> 3X |
| 254 | + k2, X --> 2X |
| 255 | + k3, Z + 2X --> 2Z |
| 256 | + k4, Y + X --> 2Y |
| 257 | + k5, Y --> ∅ |
| 258 | + k6, 2Z --> ∅ |
| 259 | + k7, Z --> ∅ |
| 260 | +end |
| 261 | +``` |
| 262 | +Here we first simulate the model for a single initial conditions, showing in both time-state space and phase space how how it reaches a [*strange attractor*](https://www.dynamicmath.xyz/strange-attractors/). |
| 263 | +```@example crn_library_chaos |
| 264 | +using OrdinaryDiffEq, Plots |
| 265 | +u0 = [:X => 1.5, :Y => 1.5, :Z => 1.5] |
| 266 | +tspan = (0.0, 50.0) |
| 267 | +p = [:k1 => 2.1, :k2 => 0.7, :k3 => 2.9, :k4 => 1.1, :k5 => 1.0, :k6 => 0.5, :k7 => 2.7] |
| 268 | +oprob = ODEProblem(wr_model, u0, tspan, p) |
| 269 | +sol = solve(oprob, Rodas5P()) |
117 | 270 |
|
118 |
| -plot(plot(osol1; title = "No oscillation (B < 1 + A^2)"), |
119 |
| - plot(osol2; title = "Oscillation (B > 1 + A^2)"); |
120 |
| - layout = (2, 1)) |
| 271 | +plt1 = plot(sol; title = "Time-state space") |
| 272 | +plt2 = plot(sol; idxs = (:X, :Y, :Z), title = "Phase space") |
| 273 | +plot(plt1, plt2; layout = (1,2), size = (800,400)) |
121 | 274 | ```
|
0 commit comments