Skip to content

Commit 53eee37

Browse files
committed
update HH tutorial
1 parent 13a7420 commit 53eee37

File tree

1 file changed

+58
-145
lines changed

1 file changed

+58
-145
lines changed

docs/src/model_creation/examples/hodgkin_huxley_equation.md

Lines changed: 58 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This 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
66
model](https://en.wikipedia.org/wiki/Hodgkin%E2%80%93Huxley_model) for an
77
excitable cell. The Hodgkin–Huxley model is a mathematical model that describes
88
how action potentials in neurons are initiated and propagated. It is a
@@ -11,20 +11,19 @@ differential equations that model the electrical characteristics of excitable
1111
cells such as neurons and muscle cells.
1212

1313
We'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

1819
We 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
2625
Let'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
2827
three 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\}$$
@@ -53,7 +52,8 @@ end
5352
nothing # 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
5858
Iapp(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
6464
maps.
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
8988
end
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
11893
ODE 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
132107
physiological steady-state via firing one action potential:
133108

134109
```@example hh1
135-
plot(hhsssol, idxs = V)
110+
plot(hhsssol, idxs = hhmodel.V)
136111
```
137112

138113
We 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))
143120
nothing # 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
150127
tspan = (0.0, 50.0)
151-
@unpack I₀ = hhmodel
128+
@unpack V,I₀ = hhmodel
152129
oprob = ODEProblem(hhmodel, u_ss, tspan, [I₀ => 10.0])
153130
sol = solve(oprob)
154-
plot(sol, vars = V, legend = :outerright)
131+
plot(sol, idxs = V, legend = :outerright)
155132
```
156133

157134
We 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)
183148
end
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)
192156
end
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)
212162
end
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
274186
tspan = (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)
277190
sol = 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

Comments
 (0)