22
33This tutorial shows how to construct a
44[ Catalyst] ( http://docs.sciml.ai/Catalyst/stable/ ) [ ` ReactionSystem ` ] ( @ref ) that
5- is coupled to a constraint ODE, corresponding to the [ Hodgkin–Huxley
5+ includes a coupled ODE, corresponding to the [ Hodgkin–Huxley
66model] ( https://en.wikipedia.org/wiki/Hodgkin%E2%80%93Huxley_model ) for an
77excitable cell. The Hodgkin–Huxley model is a mathematical model that describes
88how action potentials in neurons are initiated and propagated. It is a
@@ -11,20 +11,19 @@ differential equations that model the electrical characteristics of excitable
1111cells such as neurons and muscle cells.
1212
1313We'll present two different approaches for constructing the model. The first
14- will show how it can be built entirely within the DSL, while the second
15- illustrates another work flow, showing how separate models containing the
16- chemistry and the dynamics of the transmembrane potential can be combined.
14+ will show how it can be built entirely within a single DSL model, while the
15+ second illustrates another work flow, showing how separate models containing the
16+ chemistry and the dynamics of the transmembrane potential can be combined into a
17+ complete model.
1718
1819We begin by importing some necessary packages:
1920``` @example hh1
20- using ModelingToolkit, Catalyst, NonlinearSolve
21- using OrdinaryDiffEq, Symbolics
22- using Plots
21+ using ModelingToolkit, Catalyst, NonlinearSolve, Plots, OrdinaryDiffEq
2322```
2423
2524## Building the model via the Catalyst DSL
2625Let's build a simple Hodgkin-Huxley model for a single neuron, with the voltage,
27- $V(t)$, included as a constraint ODE. We first specify the transition rates for
26+ $V(t)$, included as a coupled ODE. We first specify the transition rates for
2827three gating variables, $m(t)$, $n(t)$ and $h(t)$.
2928
3029$$ s' \xleftrightarrow[\beta_s(V(t))]{\alpha_s(V(t))} s, \quad s \in \{m,n,h\} $$
5352nothing # hide
5453```
5554
56- We also declare a function to represent an applied current in our model
55+ We also declare a function to represent an applied current in our model, which we
56+ will use to perturb the system and create action potentials.
5757``` @example hh1
5858Iapp(t,I₀) = I₀ * sin(2*pi*t/30)^2
5959```
@@ -64,7 +64,7 @@ constructing the model to avoid having to specify them later on via parameter
6464maps.
6565
6666``` @example hh1
67- hhrn = @reaction_network hhmodel begin
67+ hhmodel = @reaction_network hhmodel begin
6868 @parameters begin
6969 C = 1.0
7070 ḡNa = 120.0
@@ -83,36 +83,11 @@ hhrn = @reaction_network hhmodel begin
8383 (αₕ(V), βₕ(V)), h′ <--> h
8484
8585 @equations begin
86- D(V) ~ -1/C * (ḡK*n^4*(V-EK) + ḡNa*m^3*h*(V-ENa) + ḡL*(V-EL)) + Iapp/C
87- Iapp ~ I₀ * sin(2*pi*t/30)^2
86+ D(V) ~ -1/C * (ḡK*n^4*(V-EK) + ḡNa*m^3*h*(V-ENa) + ḡL*(V-EL)) + Iapp(t,I₀)
8887 end
8988end
9089```
91-
92- Next we create a ` ModelingToolkit.ODESystem ` to store the equation for ` dV/dt `
93-
94- ``` @example hh1
95- @parameters C=1.0 ḡNa=120.0 ḡK=36.0 ḡL=.3 ENa=45.0 EK=-82.0 EL=-59.0 I₀=0.0
96- I = I₀ * sin(2*pi*t/30)^2
97-
98- # get the gating variables to use in the equation for dV/dt
99- @unpack m,n,h = hhrn
100-
101- Dₜ = default_time_deriv()
102- eqs = [Dₜ(V) ~ -1/C * (ḡK*n^4*(V-EK) + ḡNa*m^3*h*(V-ENa) + ḡL*(V-EL)) + I/C]
103- @named voltageode = ODESystem(eqs, t)
104- nothing # hide
105- ```
106-
107- Notice, we included an applied current, ` I ` , that we will use to perturb the system and create action potentials. For now we turn this off by setting its amplitude, ` I₀ ` , to zero.
108-
109- Finally, we add this ODE into the reaction model as
110-
111- ``` @example hh1
112- @named hhmodel = extend(voltageode, hhrn)
113- hhmodel = complete(hhmodel)
114- nothing # hide
115- ```
90+ For now we turn off the applied current by setting its amplitude, ` I₀ ` , to zero.
11691
11792` hhmodel ` is now a ` ReactionSystem ` that is coupled to an internal constraint
11893ODE for $dV/dt$. Let's now solve to steady-state, as we can then use these
@@ -132,14 +107,16 @@ From the artificial initial condition we specified, the solution approaches the
132107physiological steady-state via firing one action potential:
133108
134109``` @example hh1
135- plot(hhsssol, idxs = V)
110+ plot(hhsssol, idxs = hhmodel. V)
136111```
137112
138113We now save this steady-state to use as the initial condition for simulating how
139- a resting neuron responds to an applied current.
114+ a resting neuron responds to an applied current. We save the steady-state values
115+ as a mapping from the symbolic variables to their steady-states that we can
116+ later use as an initial condition:
140117
141118``` @example hh1
142- u_ss = hhsssol.u[end]
119+ u_ss = unknowns(hhmodel) .=> hhsssol(tspan[2], idxs = unknowns(hhmodel))
143120nothing # hide
144121```
145122
@@ -148,134 +125,70 @@ amplitude of the stimulus is non-zero and see if we get action potentials
148125
149126``` @example hh1
150127tspan = (0.0, 50.0)
151- @unpack I₀ = hhmodel
128+ @unpack V, I₀ = hhmodel
152129oprob = ODEProblem(hhmodel, u_ss, tspan, [I₀ => 10.0])
153130sol = solve(oprob)
154- plot(sol, vars = V, legend = :outerright)
131+ plot(sol, idxs = V, legend = :outerright)
155132```
156133
157134We observe three action potentials due to the steady applied current.
158135
159136## Building the model via composition of separate systems for the ion channel and transmembrane voltage dynamics
160137
161- We first import some symbolic variables we will need in specifying our model
162- ``` @example hh1
163- t = default_t()
164- D = default_time_deriv()
165- ```
166-
167- We'll build a simple Hodgkin-Huxley model for a single neuron, with the voltage,
168- V(t), included as a constraint ODE. We first specify the transition rates for
169- three gating variables, $m(t)$, $n(t)$ and $h(t)$.
170-
171- $$ s' \xleftrightarrow[\beta_s(V(t))]{\alpha_s(V(t))} s, \quad s \in \{m,n,h\} $$
172-
173- Here each gating variable is used in determining the fraction of active (i.e.
174- open) or inactive ($m' = 1 - m$, $n' = 1 -n$, $h' = 1 - h$) sodium ($m$ and $h$)
175- and potassium ($n$) channels.
176-
177- The transition rate functions, which depend on the voltage, $V(t)$, are given by
138+ As an illustration of how one can construct models from individual components,
139+ we now separately construct and compose the model components.
178140
141+ We start by defining systems to model each ionic current:
179142``` @example hh1
180- function αₘ(V)
181- theta = (V + 45) / 10
182- ifelse(theta == 0.0, 1.0, theta/(1 - exp(-theta)))
143+ IKmodel = @reaction_network IKmodel begin
144+ @parameters ḡK = 36.0 EK = -82.0
145+ @variables V(t) Iₖ(t)
146+ (αₙ(V), βₙ(V)), n′ <--> n
147+ @equations Iₖ ~ ḡK*n^4*(V-EK)
183148end
184- βₘ(V) = 4*exp(-(V + 70)/18)
185149
186- αₕ(V) = .07 * exp(-(V + 70)/20)
187- βₕ(V) = 1/(1 + exp(-(V + 40)/10))
188-
189- function αₙ(V)
190- theta = (V + 60) / 10
191- ifelse(theta == 0.0, .1, .1*theta / (1 - exp(-theta)))
150+ INamodel = @reaction_network INamodel begin
151+ @parameters ḡNa = 120.0 ENa = 45.0
152+ @variables V(t) Iₙₐ(t)
153+ (αₘ(V), βₘ(V)), m′ <--> m
154+ (αₕ(V), βₕ(V)), h′ <--> h
155+ @equations Iₙₐ ~ ḡNa*m^3*h*(V-ENa)
192156end
193- βₙ(V) = .125 * exp(-(V + 70)/80)
194- nothing # hide
195- ```
196-
197- We now declare the symbolic variable, ` V(t) ` , that will represent the
198- transmembrane potential
199157
200- ``` @example hh1
201- @variables V(t)
202- nothing # hide
203- ```
204-
205- and a ` ReactionSystem ` that models the opening and closing of receptors
206-
207- ``` @example hh1
208- hhrn = @reaction_network hhmodel begin
209- (αₙ($V), βₙ($V)), n′ <--> n
210- (αₘ($V), βₘ($V)), m′ <--> m
211- (αₕ($V), βₕ($V)), h′ <--> h
158+ ILmodel = @reaction_network ILmodel begin
159+ @parameters ḡL = .3 EL = -59.0
160+ @variables V(t) Iₗ(t)
161+ @equations Iₗ ~ ḡL*(V-EL)
212162end
213- nothing # hide
214- ```
215-
216- Next we create a ` ModelingToolkit.ODESystem ` to store the equation for ` dV/dt `
217-
218- ``` @example hh1
219- @parameters C=1.0 ḡNa=120.0 ḡK=36.0 ḡL=.3 ENa=45.0 EK=-82.0 EL=-59.0 I₀=0.0
220- I = I₀ * sin(2*pi*t/30)^2
221-
222- # get the gating variables to use in the equation for dV/dt
223- @unpack m,n,h = hhrn
224-
225- Dₜ = default_time_deriv()
226- eqs = [Dₜ(V) ~ -1/C * (ḡK*n^4*(V-EK) + ḡNa*m^3*h*(V-ENa) + ḡL*(V-EL)) + I/C]
227- @named voltageode = ODESystem(eqs, t)
228- nothing # hide
229- ```
230-
231- Notice, we included an applied current, ` I ` , that we will use to perturb the system and create action potentials. For now we turn this off by setting its amplitude, ` I₀ ` , to zero.
232-
233- Finally, we add this ODE into the reaction model as
234-
235- ``` @example hh1
236- @named hhmodel = extend(voltageode, hhrn)
237- hhmodel = complete(hhmodel)
238- nothing # hide
239- ```
240-
241- ` hhmodel ` is now a ` ReactionSystem ` that is coupled to an internal constraint
242- ODE for $dV/dt$. Let's now solve to steady-state, as we can then use these
243- resting values as an initial condition before applying a current to create an
244- action potential.
245-
246- ``` @example hh1
247- tspan = (0.0, 50.0)
248- u₀ = [:V => -70, :m => 0.0, :h => 0.0, :n => 0.0,
249- :m′ => 1.0, :n′ => 1.0, :h′ => 1.0]
250- oprob = ODEProblem(hhmodel, u₀, tspan)
251- hhsssol = solve(oprob, Rosenbrock23())
252- nothing # hide
253163```
254164
255- From the artificial initial condition we specified, the solution approaches the
256- physiological steady-state via firing one action potential:
257-
165+ We next define the voltage dynamics with unspecified values for the currents
258166``` @example hh1
259- plot(hhsssol, idxs = V)
167+ hhmodel2 = @reaction_network hhmodel begin
168+ @parameters C = 1.0 I₀ = 0.0
169+ @variables V(t) Iₖ(t) Iₙₐ(t) Iₗ(t)
170+ @equations D(V) ~ -1/C * (Iₖ + Iₙₐ + Iₗ) + Iapp(t,I₀)
171+ end
260172```
261-
262- We now save this steady-state to use as the initial condition for simulating how
263- a resting neuron responds to an applied current.
264-
265- ``` @example hh1
266- u_ss = hhsssol.u[end]
267- nothing # hide
173+ Finally, we extend the ` hhmodel ` with the systems defining the ion channel currents
174+ ``` julia
175+ for sys in (IKmodel, INamodel, ILmodel)
176+ @named hhmodel2 = extend (sys, hhmodel2)
177+ end
178+ hhmodel2 = complete (hhmodel2)
268179```
269-
270- Finally, starting from this resting state let's solve the system when the
271- amplitude of the stimulus is non-zero and see if we get action potentials
180+ Starting from the resting state, let's again solve the system when the amplitude
181+ of the stimulus is non-zero and check we get the same figure as above. Note, we
182+ now run ` structural_simplify ` from ModelingToolkit as part of building the
183+ ` ODEProblem ` to eliminate the algebraic equations for the currents
272184
273185``` @example hh1
274186tspan = (0.0, 50.0)
275- @unpack I₀ = hhmodel
276- oprob = ODEProblem(hhmodel, u_ss, tspan, [I₀ => 10.0])
187+ @unpack I₀,V = hhmodel2
188+ oprob = ODEProblem(hhmodel2, u_ss, tspan, [I₀ => 10.0];
189+ structural_simplify = true)
277190sol = solve(oprob)
278- plot(sol, vars = V, legend = :outerright)
191+ plot(sol, idxs = V, legend = :outerright)
279192```
280193
281- We observe three action potentials due to the steady applied current .
194+ We observe the same solutions as from our original model .
0 commit comments