Skip to content

Commit b23f5e1

Browse files
committed
Added in the python implementation of the MQIF_neuron, and improved the notebook introducing the neurons
1 parent c8c9468 commit b23f5e1

File tree

2 files changed

+323
-10
lines changed

2 files changed

+323
-10
lines changed
Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
The following Python script runs simulations of the MQIF Neuron model,
4+
with the aims of characterising its bursting patterns.
5+
"""
6+
7+
import numpy as np
8+
import matplotlib.pyplot as plt
9+
from mpl_toolkits import mplot3d
10+
plt.style.use('seaborn')
11+
plt.rcParams['figure.edgecolor'] = 'blue'
12+
plt.rcParams['scatter.marker'] = 'x'
13+
import time as clock
14+
15+
#ODEs governing MQIF model
16+
def Vs_dot(V, Vs, tau_s):
17+
return 250*(V - Vs) / tau_s
18+
19+
def Vus_dot(V, Vus, tau_us):
20+
return 250*(V - Vus) / tau_us
21+
22+
def V_dot(V, Vs, Vus, I_ext, # Current values
23+
V0, Vs0, Vus0, #Intial values
24+
g_f, g_s, g_us, # Conductances
25+
C): #Capacitance
26+
27+
return 250*(I_ext + g_f*((V-V0)**2) - g_s*((Vs-Vs0)**2) - g_us*((Vus-Vus0)**2)) / C
28+
29+
30+
def simulate_MQIF(num_steps, dt, V0, Vs0, Vus0, I_ext,
31+
V_threshold=20, V_reset=-45, Vs_reset=7.5, delta_Vus=1.7,
32+
g_f = 1.0, g_s = 0.5, g_us = 0.015,
33+
tau_s = 4.3, tau_us = 278,
34+
C = 0.82):
35+
"""
36+
Function that generate arrays holding the values of each variable as a time-series.
37+
Integrates the ODEs and fire spikes.
38+
39+
Parameters
40+
----------
41+
num_steps, dt : INTEGER
42+
Governs the timebase of the simulation. num_steps denotes the total
43+
number of updates steps for the whole simulation. dt is the time spacing
44+
between these steps.
45+
46+
V0, Vs0, Vus0 : INTEGER/FLOAT
47+
The initial/equilibrium values in the ODEs. This is passed into the
48+
'v_dot' ODE function. V governs membrane potential. Vs governs the slow
49+
currents within the ODEs. Vus governs the ultraslow currents.
50+
51+
I_ext : ARRAY
52+
Input current waveform.
53+
54+
V_threshold, V_reset, VS_reset, delta_Vus : INTEGER/FLOAT
55+
Paramters governing the reset characteristics. When membrane potential
56+
reaches V_threshold, a spike occurs, and the state variables are reset
57+
using these paramters.
58+
59+
g_f, g_s, g_us : FLOAT
60+
Paramters governing the associated conductances of each of the potential
61+
differences that arise within the 'v_dot' ODE function.
62+
63+
tau_s, tau_us : INTEGER/FLOAT
64+
Time constants governing the slow and ultraslow charactersitics, as set
65+
in the Vs and Vus ODEs.
66+
67+
C : FLOAT
68+
Capactiance used in the V ODE.
69+
70+
Returns
71+
-------
72+
V_values, Vs_values, Vus_values : ARRAY
73+
Timeseries containing the values of each of the state variables at each
74+
time-step. Each element is separated by 'dt' milliseconds of time.
75+
76+
spikes : ARRAY
77+
Array of integers used to relay the number of spikes within a burst.
78+
When a burst begins, the first spike is logged as '1', in an array element
79+
whose index corresponds to the time step at which it occured. The next
80+
spike is logged as '2', then '3', and so on until the burst ends and
81+
the counter is reset.
82+
83+
time : ARRAY
84+
Array to keep track of the time in seconds. Useful for plotting.
85+
86+
"""
87+
time = np.arange(0, num_steps * dt, dt)
88+
89+
# Initialise array to store spike log
90+
spikes = np.zeros(num_steps)
91+
timer = 0
92+
num_in_burst = 0
93+
94+
# Initialise arrays to store results
95+
V_values = np.zeros(num_steps)
96+
V_values[0] = V0
97+
98+
Vs_values = np.zeros(num_steps)
99+
Vs_values[0] = Vs0
100+
101+
Vus_values = np.zeros(num_steps)
102+
Vus_values[0] = Vus0
103+
104+
# Perform forward Euler integration
105+
for i in range(0, num_steps-1):
106+
# Set current value
107+
V_i, Vs_i, Vus_i = V_values[i], Vs_values[i], Vus_values[i]
108+
I_i = I_ext[i]
109+
110+
# Update step
111+
Vs_new = Vs_i + dt * Vs_dot(V_i, Vs_i, tau_s)
112+
Vus_new = Vus_i + dt * Vus_dot(V_i, Vus_i, tau_us)
113+
114+
V_new = V_i + dt * V_dot(V_i, Vs_i, Vus_i, I_i,
115+
V0, Vs0, Vus0,
116+
g_f, g_s, g_us,
117+
C)
118+
119+
# Update array
120+
if V_new > V_threshold:
121+
# Reset after spike
122+
V_values[i+1] = V_reset
123+
Vs_values[i+1] = Vs_reset
124+
Vus_values[i+1] = Vus_new + delta_Vus
125+
126+
# Log spike according to its number in the burst.
127+
num_in_burst += 1
128+
spikes[i] = num_in_burst
129+
timer=0
130+
131+
else:
132+
V_values[i+1] = V_new
133+
Vs_values[i+1] = Vs_new
134+
Vus_values[i+1] = Vus_new
135+
timer += 1
136+
137+
# If the timer exceeds 4% of the total runtime, then the burst has ended.
138+
if timer > 0.04*num_steps:
139+
num_in_burst = 0
140+
141+
return V_values, Vs_values, Vus_values, spikes, time
142+
143+
def plot_MQIF(time, I_ext, V, Vs, Vus, state_variables=False, phase_portrait=False):
144+
"""
145+
Function for plotting a single simulation. Feed this function the generated
146+
arrays from simulate_MQIF to view the dynamics.
147+
148+
Parameters
149+
----------
150+
time : ARRAY
151+
Array to keep track of the time in milliseconds.
152+
153+
I_ext : ARRAY
154+
Input current waveform.
155+
156+
V, Vs, Vus : ARRAY
157+
Contains the values of each state variable at each time-step. V is the
158+
most important here - membrane potential. The others are state variables
159+
used in the ODEs.
160+
161+
state_variables : BOOLEAN
162+
Set to true if you also want to plot the state variables, Vs and Vus,
163+
with respect to time.
164+
165+
phase_portrait : BOOLEAN
166+
Set to true if you also want to plot phase portraits of Vs against V and
167+
Vus against V.
168+
169+
"""
170+
fig1, axes = plt.subplots(1, 2, figsize=(12,4))
171+
172+
axes[0].plot(time, I_ext, color='blue')
173+
axes[0].set_title('Current Input')
174+
axes[0].set_xlabel('time (s)')
175+
axes[0].set_ylabel('I_ext (mA/nF)')
176+
177+
axes[1].plot(time, V, color='crimson')
178+
axes[1].set_title(f'MQIF Membrane Potential, dt={time[1]}')
179+
axes[1].set_xlabel('time (s)')
180+
axes[1].set_ylabel('V (mV)')
181+
182+
if state_variables:
183+
fig2, axes = plt.subplots(1, 2, figsize=(8,3))
184+
185+
axes[0].plot(time, Vs, color='mediumorchid')
186+
axes[0].set_xlabel('time (ms)')
187+
axes[0].set_ylabel('Vs (mV)')
188+
189+
axes[1].plot(time, Vus, color='mediumorchid')
190+
axes[1].set_xlabel('time (ms)')
191+
axes[1].set_ylabel('Vus (mV)')
192+
193+
axes[0].set_title('STATE VARIABLES MQIF', loc='left')
194+
195+
plt.show()
196+
197+
if phase_portrait:
198+
fig3, axes = plt.subplots(1, 2, figsize=(8,3))
199+
200+
axes[0].plot(V, Vs, color='mediumorchid')
201+
axes[0].set_xlabel('V (mV)')
202+
axes[0].set_ylabel('Vs (mV)')
203+
204+
axes[1].plot(V, Vus, color='mediumorchid')
205+
axes[1].set_xlabel('V (mV)')
206+
axes[1].set_ylabel('Vus (mV)')
207+
208+
axes[0].set_title('PHASE PORTRAITS MQIF', loc='left')
209+
210+
plt.show()
211+
212+
def characterise_spiketrain(dt, spike_array):
213+
"""
214+
Function for characterising the bursting behaviour of a generated spike train.
215+
216+
Parameters
217+
----------
218+
dt : FLOAT
219+
Time_step used in simulation
220+
221+
spike_array : ARRAY
222+
Array containing logged spikes, as described in the simulate_MQIF docstring.
223+
224+
Returns
225+
-------
226+
frequency : FLOAT
227+
Describes the burst frequency. i.e. the reciprocal of the
228+
time period of the bursts.
229+
230+
spikes_per_burst : INTEGER
231+
Reflects the number of spikes contatined within a single
232+
burst.
233+
234+
burst_duration : FLOAT
235+
The amount of time each burst lasts for.
236+
237+
duty_cycle : FLOAT
238+
The ratio between the burst duration and the time period of the membrane
239+
potential. i.e. how long for the time period is the neuron bursting for?
240+
"""
241+
# Indexes of when each burst begins
242+
burst_times = np.where(spike_array == 1)[0]
243+
244+
# Initialise
245+
spikes_per_burst = 0
246+
burst_duration = 0
247+
frequency = 0
248+
time_period = 1
249+
250+
if len(burst_times) > 2:
251+
"""
252+
The following script runs if there are more than two "initial" spikes,
253+
i.e. if there are multiple spikes with logged values > 1, then we have
254+
bursting behaviour,not just continous spiking.
255+
256+
We need at least 3 bursts to be able to characterise the pattern, since
257+
the initial burst is generally unrepresentative of the others, which
258+
are uniform copies of one another. Hence, 'len(burst_times) > 2'
259+
260+
'burst_times[0]' is avoided for the same reason - the initial burst is
261+
usually not representative of the other bursts.
262+
"""
263+
264+
# Time between initial spikes = time period of bursts
265+
time_period = (burst_times[2]-burst_times[1])*dt
266+
frequency = 1/time_period
267+
268+
# Extract a short array looking at a singular burst for analysis
269+
single_burst = spike_array[burst_times[1]:burst_times[2]]
270+
271+
# The spikes were counted within spike_array, so the final value
272+
# corresponds to the number of spikes per burst.
273+
spikes_per_burst = max(single_burst)
274+
275+
# We extracted single_burst out of the overall spike train
276+
# to use as a close-up of one burst. Therefore, the index of the
277+
# final spike within single_burst corresponds to the burst duration.
278+
burst_duration = np.argmax(single_burst)*dt
279+
280+
# We have spiking, but not bursting.
281+
elif len(burst_times) == 1:
282+
spikes_per_burst = 1
283+
frequency=0 # Set frequency to zero to indicate there are no bursts.
284+
285+
duty_cycle = burst_duration*100/time_period
286+
287+
return frequency, spikes_per_burst, burst_duration, duty_cycle
288+
289+
290+
# Please note: dt = 0.1 is too big unfortunately. The simulation becomes inaccurate.
291+
dt = 0.0001
292+
runtime =10 # Desired simulation time in s
293+
num_steps = int(runtime/dt)
294+
295+
296+
# Generate input current waveform. Currently set up as a step input of 5 mA
297+
amplitude = 5
298+
I_ext = np.zeros(num_steps)
299+
start_index = num_steps // 6
300+
I_ext[start_index:num_steps] = amplitude
301+
302+
303+
# Parameters to hold constant
304+
V_t, V_r, Vs_r, d_Vus = 20, -45, 7.5, 1.3 #V_threshold, V_reset, Vs_reset, delta_Vus
305+
# g_f, g_s, g_us = 1.0, 0.5, 0.015 # Conductances
306+
g_f, g_s, g_us = 1.0, 0.5, 0.015 # Conductance
307+
C = 0.82 # Capacitance
308+
309+
# Simulate and charactersise
310+
t1 = clock.time()
311+
V_MQIF, Vs, Vus, spikes, time = simulate_MQIF(num_steps, dt, -52, -50, -52, I_ext, V_t, V_r, Vs_r, d_Vus, g_f, g_s, g_us, 4.3, 278, C)
312+
f, s, t, d = characterise_spiketrain(dt, spikes)
313+
elapsed2 = clock.time() - t1
314+
315+
#print(f"A single simulation takes {elapsed1} seconds. A simulation plus characterisation takes {elapsed2} seconds.")
316+
print(f"Frequency: {f} Hz, Spikes per Burst: {s}, Duration: {t} s, Duty Cycle: {d} %")
317+
plot_MQIF(time, I_ext, V_MQIF, Vs, Vus)
318+
f, s, t, d = characterise_spiketrain(dt, spikes)

Ramp Up Guidance/Neuron Guidance/NeuronIntroductions.ipynb renamed to Ramp Up Guidance/Neuron Guidance/Neuron_Introduction.ipynb

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
{
22
"cells": [
3-
{
4-
"cell_type": "markdown",
5-
"source": [],
6-
"metadata": {
7-
"id": "9V3jx4m3fck4"
8-
}
9-
},
103
{
114
"cell_type": "markdown",
125
"source": [
@@ -1100,7 +1093,11 @@
11001093
"\n",
11011094
"Hopefully, this notebook has helped you learn a bit more about the Hodgkin Huxley model. There are many dimensions of information that we would love to use for neuromorphic paradigms - the behaviour of the HH model is weird/selective by design! Unfortunately, it is difficult to mathematically analyse, and also very difficult to control and parametise.\n",
11021095
"\n",
1103-
"Instead, we now come to the MQIF neuron model presented in the thesis written by Tomas Van Pottelbergh. Play around with it here - aim to build intuition and evaluate its efficacy in reflecting the informative behaviours of the HH model."
1096+
"Instead, we now come to the MQIF neuron model presented in the thesis written by Tomas Van Pottelbergh. Play around with it here - aim to build intuition and evaluate its efficacy in reflecting the informative behaviours of the HH model.\n",
1097+
"\n",
1098+
"The rest of the notebook will be less guided, since it stands to serve as a sandbox for your exploration, (plus I am in a rush!).\n",
1099+
"\n",
1100+
"Also check out the 'MQIF_Neuron_Python.py' file! It has been implemented specifically to guide you through the functionised version of this neuron. It also presents one method to track the number of spikes in a burst, which can be useful for characterising the behevaiour of the neuron."
11041101
],
11051102
"metadata": {
11061103
"id": "UTI4L1YDgV9N"
@@ -1151,8 +1148,6 @@
11511148
{
11521149
"cell_type": "code",
11531150
"source": [
1154-
"# Function to generate arrays holding the dynamics of each variable with respect to time.\n",
1155-
"\n",
11561151
"def simulate_MQIF(num_steps, dt, V0, Vs0, Vus0, I_ext,\n",
11571152
" V_threshold=20, V_reset=-45, Vs_reset=7.5, delta_Vus=1.7,\n",
11581153
" g_f = 1.0, g_s = 0.5, g_us = 0.015,\n",

0 commit comments

Comments
 (0)