Skip to content

Commit d31e4b6

Browse files
authored
Merge pull request #1252 from SciML/update_constraint_tutorial
Update constraint tutorial
2 parents 2cf99fb + 8468313 commit d31e4b6

File tree

1 file changed

+157
-55
lines changed

1 file changed

+157
-55
lines changed

docs/src/model_creation/constraint_equations.md

Lines changed: 157 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# [Constraint Equations and Events](@id constraint_equations)
1+
# [Coupled ODEs, Algebraic Equations, and Events](@id constraint_equations)
22
In many applications one has additional algebraic or differential equations for
33
non-chemical species that can be coupled to a chemical reaction network model.
44
Catalyst supports coupled differential and algebraic equations, and currently
@@ -8,27 +8,88 @@ condition is reached, such as providing a drug treatment at a specified time, or
88
turning off production of cells once the population reaches a given level.
99
Catalyst supports the event representation provided by ModelingToolkit, see
1010
[here](https://docs.sciml.ai/ModelingToolkit/stable/basics/Events/), allowing
11-
for both continuous and discrete events (though only the latter are supported
12-
when converting to `JumpSystem`s currently).
11+
for both continuous and discrete events.
1312

14-
In this tutorial we'll illustrate how to make use of constraint equations and
15-
events. Let's consider a model of a cell with volume $V(t)$ that grows at a rate
16-
$\lambda$. For now we'll assume the cell can grow indefinitely. We'll also keep
17-
track of one protein $P(t)$, which is produced at a rate proportional $V$ and
18-
can be degraded.
13+
In this tutorial we'll illustrate how to make use of coupled constraint (i.e.
14+
ODE/algebraic) equations and events. Let's consider a model of a cell with
15+
volume $V(t)$ that grows at a rate $\lambda$. For now we'll assume the cell can
16+
grow indefinitely. We'll also keep track of one protein $P(t)$, which is
17+
produced at a rate proportional $V$ and can be degraded.
1918

20-
## [Coupling ODE constraints via extending a system](@id constraint_equations_coupling_constraints)
19+
## [Coupling ODE constraints via the DSL](@id constraint_equations_dsl)
20+
The easiest way to include ODEs and algebraic equations is to just include them
21+
when using the DSL to specify a model. Here we include an ODE for $V(t)$ along
22+
with degradation and production reactions for $P(t)$:
23+
```@example ceq1
24+
using Catalyst, OrdinaryDiffEqTsit5, Plots
25+
26+
rn = @reaction_network growing_cell begin
27+
# the growth rate
28+
@parameters λ = 1.0
2129
22-
There are several ways we can create our Catalyst model with the two reactions
23-
and ODE for $V(t)$. One approach is to use compositional modeling, create
24-
separate `ReactionSystem`s and `ODESystem`s with their respective components,
25-
and then extend the `ReactionSystem` with the `ODESystem`. Let's begin by
26-
creating these two systems.
30+
# assume there is no protein initially
31+
@species P(t) = 0.0
32+
33+
# set the initial volume to 1.0
34+
@variables V(t) = 1.0
2735
28-
Here, to create differentials with respect to time (for our differential equations), we must import the time differential operator from Catalyst. We do this through `D = default_time_deriv()`. Here, `D(V)` denotes the differential of the variable `V` with respect to time.
36+
# the reactions
37+
V, 0 --> P
38+
1.0, P --> 0
2939
40+
# the coupled ODE for V(t)
41+
@equations begin
42+
D(V) ~ λ * V
43+
end
44+
end
45+
```
46+
We can now create an `ODEProblem` from our model and solve it to see how $V(t)$
47+
and $P(t)$ evolve in time:
3048
```@example ceq1
49+
oprob = ODEProblem(rn, [], (0.0, 1.0))
50+
sol = solve(oprob, Tsit5())
51+
plot(sol)
52+
```
53+
54+
## Coupling ODE constraints via directly building a `ReactionSystem`
55+
As an alternative to the previous approach, we could have also constructed our
56+
`ReactionSystem` all at once using the symbolic interface:
57+
```@example ceq2
3158
using Catalyst, OrdinaryDiffEqTsit5, Plots
59+
60+
t = default_t()
61+
D = default_time_deriv()
62+
63+
@parameters λ = 1.0
64+
@variables V(t) = 1.0
65+
eq = D(V) ~ λ * V
66+
rx1 = @reaction $V, 0 --> P
67+
rx2 = @reaction 1.0, P --> 0
68+
@named growing_cell = ReactionSystem([rx1, rx2, eq], t)
69+
setdefaults!(growing_cell, [:P => 0.0])
70+
growing_cell = complete(growing_cell)
71+
72+
oprob = ODEProblem(growing_cell, [], (0.0, 1.0))
73+
sol = solve(oprob, Tsit5())
74+
plot(sol)
75+
```
76+
77+
78+
## [Coupling ODE constraints via extending a system](@id constraint_equations_coupling_constraints)
79+
80+
Finally, we could also construct our model by using compositional modeling. Here
81+
we create separate `ReactionSystem`s and `ODESystem`s with their respective
82+
components, and then extend the `ReactionSystem` with the `ODESystem`. Let's
83+
begin by creating these two systems.
84+
85+
Here, to create differentials with respect to time (for our differential
86+
equations), we must import the time differential operator from Catalyst. We do
87+
this through `D = default_time_deriv()`. Here, `D(V)` denotes the differential
88+
of the variable `V` with respect to time.
89+
90+
```@example ceq2b
91+
using Catalyst, OrdinaryDiffEqTsit5, Plots
92+
3293
t = default_t()
3394
D = default_time_deriv()
3495
@@ -59,40 +120,19 @@ systems together Catalyst requires that the systems have not been marked as
59120

60121
We can now merge the two systems into one complete `ReactionSystem` model using
61122
[`ModelingToolkit.extend`](@ref):
62-
```@example ceq1
123+
```@example ceq2b
63124
@named growing_cell = extend(osys, rn)
64125
growing_cell = complete(growing_cell)
65126
```
66127

67128
We see that the combined model now has both the reactions and ODEs as its
68129
equations. To solve and plot the model we proceed like normal
69-
```@example ceq1
130+
```@example ceq2b
70131
oprob = ODEProblem(growing_cell, [], (0.0, 1.0))
71132
sol = solve(oprob, Tsit5())
72133
plot(sol)
73134
```
74135

75-
## Coupling ODE constraints via directly building a `ReactionSystem`
76-
As an alternative to the previous approach, we could have constructed our
77-
`ReactionSystem` all at once by directly using the symbolic interface:
78-
```@example ceq2
79-
using Catalyst, OrdinaryDiffEqTsit5, Plots
80-
t = default_t()
81-
D = default_time_deriv()
82-
83-
@parameters λ = 1.0
84-
@variables V(t) = 1.0
85-
eq = D(V) ~ λ * V
86-
rx1 = @reaction $V, 0 --> P
87-
rx2 = @reaction 1.0, P --> 0
88-
@named growing_cell = ReactionSystem([rx1, rx2, eq], t)
89-
setdefaults!(growing_cell, [:P => 0.0])
90-
growing_cell = complete(growing_cell)
91-
92-
oprob = ODEProblem(growing_cell, [], (0.0, 1.0))
93-
sol = solve(oprob, Tsit5())
94-
plot(sol)
95-
```
96136

97137
## [Adding events](@id constraint_equations_events)
98138
Our current model is unrealistic in assuming the cell will grow exponentially
@@ -107,7 +147,82 @@ details on the types of events that can be represented symbolically. A
107147
lower-level approach for creating events via the DifferentialEquations.jl
108148
callback interface is illustrated [here](https://docs.sciml.ai/DiffEqDocs/stable/features/callback_functions/) tutorial.
109149

110-
Let's first create our equations and unknowns/species again
150+
```@example ceq3a
151+
using Catalyst, OrdinaryDiffEqTsit5, Plots
152+
153+
rn = @reaction_network growing_cell begin
154+
# the growth rate
155+
@parameters λ = 1.0
156+
157+
# assume there is no protein initially
158+
@species P(t) = 0.0
159+
160+
# set the initial volume to 1.0
161+
@variables V(t) = 1.0
162+
163+
# the reactions
164+
V, 0 --> P
165+
1.0, P --> 0
166+
167+
# the coupled ODE for V(t)
168+
@equations begin
169+
D(V) ~ λ * V
170+
end
171+
172+
# every 1.0 time unit we half the volume of the cell and the number of proteins
173+
@continuous_events begin
174+
[V ~ 2.0] => [V ~ V/2, P ~ P/2]
175+
end
176+
end
177+
```
178+
We can now create and simulate our model
179+
```@example ceq3a
180+
oprob = ODEProblem(rn, [], (0.0, 10.0))
181+
sol = solve(oprob, Tsit5())
182+
plot(sol)
183+
Catalyst.PNG(plot(sol; fmt = :png, dpi = 200)) # hide
184+
```
185+
We can also model discrete events. Here at a time `switch_time` we will set the parameter `k_on` to be
186+
zero:
187+
```@example ceq3a
188+
rn = @reaction_network param_off_ex begin
189+
@parameters switch_time
190+
k_on, A --> B
191+
k_off, B --> A
192+
193+
@discrete_events begin
194+
(t == switch_time) => [k_on ~ 0.0]
195+
end
196+
end
197+
198+
u0 = [:A => 10.0, :B => 0.0]
199+
tspan = (0.0, 4.0)
200+
p = [:k_on => 100.0, :switch_time => 2.0, :k_off => 10.0]
201+
oprob = ODEProblem(rn, u0, tspan, p)
202+
sol = solve(oprob, Tsit5(); tstops = 2.0)
203+
plot(sol)
204+
```
205+
Note that for discrete events we need to set a stop time via `tstops` so that
206+
the ODE solver can step exactly to the specific time of our event. In the
207+
previous example we just manually set the numeric value of the parameter in the
208+
`tstops` kwarg to `solve`, however, it can often be convenient to instead get
209+
the value of the parameter from `oprob` and pass this numeric value. This helps
210+
ensure consistency between the value passed via `p` and/or symbolic defaults and
211+
what we pass as a `tstop` to `solve`. We can do this as
212+
```@example ceq3a
213+
oprob = ODEProblem(rn, u0, tspan, p)
214+
switch_time_val = oprob.ps[:switch_time]
215+
sol = solve(oprob, Tsit5(); tstops = switch_time_val)
216+
plot(sol)
217+
```
218+
For a detailed discussion on how to directly use the lower-level but more
219+
flexible DifferentialEquations.jl event/callback interface, see the
220+
[tutorial](https://docs.sciml.ai/Catalyst/stable/catalyst_applications/advanced_simulations/#Event-handling-using-callbacks)
221+
on event handling using callbacks.
222+
223+
## [Adding events via the symbolic interface](@id constraint_equations_events_symbolic)
224+
Let's repeat the previous two models using the symbolic interface. We first
225+
create our equations and unknowns/species again
111226
```@example ceq3
112227
using Catalyst, OrdinaryDiffEqTsit5, Plots
113228
t = default_t()
@@ -135,7 +250,9 @@ sol = solve(oprob, Tsit5())
135250
plot(sol)
136251
Catalyst.PNG(plot(sol; fmt = :png, dpi = 200)) # hide
137252
```
138-
We can also model discrete events. Similar to our example with continuous events, we start by creating reaction equations, parameters, variables, and unknowns.
253+
We can again also model discrete events. Similar to our example with continuous
254+
events, we start by creating reaction equations, parameters, variables, and
255+
unknowns.
139256
```@example ceq3
140257
t = default_t()
141258
@parameters k_on switch_time k_off
@@ -157,22 +274,7 @@ Simulating our model,
157274
rs2 = complete(rs2)
158275
159276
oprob = ODEProblem(rs2, u0, tspan, p)
160-
sol = solve(oprob, Tsit5(); tstops = 2.0)
161-
plot(sol)
162-
```
163-
Note that for discrete events we need to set a stop time via `tstops` so that
164-
the ODE solver can step exactly to the specific time of our event. In the
165-
previous example we just manually set the numeric value of the parameter in the
166-
`tstops` kwarg to `solve`, however, it can often be convenient to instead get
167-
the value of the parameter from `oprob` and pass this numeric value. This helps
168-
ensure consistency between the value passed via `p` and/or symbolic defaults and
169-
what we pass as a `tstop` to `solve`. We can do this as
170-
```julia
171277
switch_time_val = oprob.ps[:switch_time]
172278
sol = solve(oprob, Tsit5(); tstops = switch_time_val)
173279
plot(sol)
174280
```
175-
For a detailed discussion on how to directly use the lower-level but more
176-
flexible DifferentialEquations.jl event/callback interface, see the
177-
[tutorial](https://docs.sciml.ai/Catalyst/stable/catalyst_applications/advanced_simulations/#Event-handling-using-callbacks)
178-
on event handling using callbacks.

0 commit comments

Comments
 (0)