Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/neuroscience/neural_network.tex
75 views
unlisted
1
\documentclass[a4paper, 11pt]{report}
2
\usepackage[utf8]{inputenc}
3
\usepackage[T1]{fontenc}
4
\usepackage{amsmath, amssymb}
5
\usepackage{graphicx}
6
\usepackage{siunitx}
7
\usepackage{booktabs}
8
\usepackage{xcolor}
9
\usepackage[makestderr]{pythontex}
10
11
\definecolor{excitatory}{RGB}{52, 152, 219}
12
\definecolor{inhibitory}{RGB}{231, 76, 60}
13
\definecolor{activity}{RGB}{46, 204, 113}
14
15
\title{Spiking Neural Networks:\\
16
Population Dynamics and Synchronization}
17
\author{Department of Neuroscience\\Technical Report NS-2024-003}
18
\date{\today}
19
20
\begin{document}
21
\maketitle
22
23
\begin{abstract}
24
This report presents a comprehensive analysis of spiking neural network dynamics. We implement leaky integrate-and-fire neurons, analyze population synchronization, compute spike train statistics, examine balanced excitation-inhibition, and investigate network oscillations. All simulations use PythonTeX for reproducibility.
25
\end{abstract}
26
27
\tableofcontents
28
29
\chapter{Introduction}
30
31
The leaky integrate-and-fire (LIF) neuron model:
32
\begin{equation}
33
\tau_m \frac{dV}{dt} = -(V - V_{rest}) + R_m I_{syn}
34
\end{equation}
35
36
When $V \geq V_{th}$: emit spike and reset to $V_{reset}$.
37
38
\section{Network Connectivity}
39
Synaptic current:
40
\begin{equation}
41
I_{syn}(t) = \sum_j w_j \sum_k \delta(t - t_j^k - d)
42
\end{equation}
43
44
\begin{pycode}
45
import numpy as np
46
import matplotlib.pyplot as plt
47
from scipy import signal
48
plt.rc('text', usetex=True)
49
plt.rc('font', family='serif')
50
51
np.random.seed(42)
52
53
# LIF parameters
54
tau_m = 20.0 # Membrane time constant (ms)
55
V_rest = -65.0 # Resting potential (mV)
56
V_th = -50.0 # Threshold (mV)
57
V_reset = -70.0 # Reset potential (mV)
58
R_m = 10.0 # Membrane resistance (MOhm)
59
tau_syn = 5.0 # Synaptic time constant (ms)
60
61
# Network parameters
62
N = 200 # Total neurons
63
N_exc = int(0.8 * N)
64
N_inh = N - N_exc
65
p_conn = 0.1 # Connection probability
66
w_exc = 0.3 # Excitatory weight
67
w_inh = -1.2 # Inhibitory weight
68
69
# Simulation parameters
70
dt = 0.1 # Time step (ms)
71
T = 1000 # Total time (ms)
72
n_steps = int(T / dt)
73
74
# Initialize
75
V = V_rest + 5 * np.random.randn(N)
76
is_exc = np.arange(N) < N_exc
77
78
# Create connectivity
79
W = np.zeros((N, N))
80
for i in range(N):
81
for j in range(N):
82
if i != j and np.random.rand() < p_conn:
83
W[i, j] = w_exc if is_exc[j] else w_inh
84
85
# External input
86
I_ext = 15.0
87
88
# Recording
89
spike_times = [[] for _ in range(N)]
90
V_record = np.zeros((N, n_steps))
91
I_syn = np.zeros(N)
92
93
# Simulation
94
for step in range(n_steps):
95
t = step * dt
96
I_syn *= np.exp(-dt / tau_syn)
97
98
dV = dt / tau_m * (-(V - V_rest) + R_m * (I_ext + I_syn))
99
V += dV
100
101
spiked = V >= V_th
102
for n in np.where(spiked)[0]:
103
spike_times[n].append(t)
104
I_syn += W[:, n]
105
106
V[spiked] = V_reset
107
V_record[:, step] = V
108
\end{pycode}
109
110
\chapter{Network Activity}
111
112
\begin{pycode}
113
fig, axes = plt.subplots(2, 3, figsize=(14, 8))
114
115
# Raster plot
116
ax = axes[0, 0]
117
for i in range(N):
118
if len(spike_times[i]) > 0:
119
color = '#3498db' if is_exc[i] else '#e74c3c'
120
ax.scatter(spike_times[i], [i]*len(spike_times[i]), c=color, s=0.5, marker='|')
121
122
ax.set_xlabel('Time (ms)')
123
ax.set_ylabel('Neuron Index')
124
ax.set_title('Spike Raster')
125
ax.set_xlim([0, T])
126
127
# Population rate
128
bin_size = 5
129
n_bins = int(T / bin_size)
130
pop_rate = np.zeros(n_bins)
131
exc_rate = np.zeros(n_bins)
132
inh_rate = np.zeros(n_bins)
133
134
for i, st in enumerate(spike_times):
135
for t in st:
136
bin_idx = min(int(t / bin_size), n_bins - 1)
137
pop_rate[bin_idx] += 1
138
if is_exc[i]:
139
exc_rate[bin_idx] += 1
140
else:
141
inh_rate[bin_idx] += 1
142
143
pop_rate = pop_rate / N * 1000 / bin_size
144
exc_rate = exc_rate / N_exc * 1000 / bin_size
145
inh_rate = inh_rate / N_inh * 1000 / bin_size
146
147
t_bins = np.arange(0, T, bin_size)
148
ax = axes[0, 1]
149
ax.plot(t_bins, pop_rate, 'k-', linewidth=1, label='Total')
150
ax.plot(t_bins, exc_rate, 'b-', linewidth=1, alpha=0.7, label='Exc')
151
ax.plot(t_bins, inh_rate, 'r-', linewidth=1, alpha=0.7, label='Inh')
152
ax.set_xlabel('Time (ms)')
153
ax.set_ylabel('Rate (Hz)')
154
ax.set_title('Population Activity')
155
ax.legend(fontsize=8)
156
ax.grid(True, alpha=0.3)
157
158
# Firing rate distribution
159
firing_rates = [len(st) / (T / 1000) for st in spike_times]
160
mean_rate = np.mean(firing_rates)
161
162
ax = axes[0, 2]
163
ax.hist(firing_rates, bins=30, alpha=0.7, color='green', edgecolor='black')
164
ax.axvline(mean_rate, color='r', linewidth=2, label=f'Mean: {mean_rate:.1f} Hz')
165
ax.set_xlabel('Firing Rate (Hz)')
166
ax.set_ylabel('Count')
167
ax.set_title('Rate Distribution')
168
ax.legend()
169
ax.grid(True, alpha=0.3)
170
171
# ISI distribution
172
all_isis = []
173
for st in spike_times:
174
if len(st) > 1:
175
all_isis.extend(np.diff(st))
176
177
ax = axes[1, 0]
178
if len(all_isis) > 0:
179
ax.hist(all_isis, bins=50, alpha=0.7, color='purple', edgecolor='black')
180
cv_isi = np.std(all_isis) / np.mean(all_isis)
181
ax.axvline(np.mean(all_isis), color='r', linewidth=2)
182
ax.set_xlabel('ISI (ms)')
183
ax.set_ylabel('Count')
184
ax.set_title(f'ISI Distribution (CV={cv_isi:.2f})')
185
ax.grid(True, alpha=0.3)
186
187
# Cross-correlogram
188
ax = axes[1, 1]
189
# Select two excitatory neurons with spikes
190
active_exc = [i for i in range(N_exc) if len(spike_times[i]) > 10]
191
if len(active_exc) >= 2:
192
st1 = np.array(spike_times[active_exc[0]])
193
st2 = np.array(spike_times[active_exc[1]])
194
195
lags = np.arange(-50, 51, 1)
196
cc = np.zeros(len(lags))
197
for i, lag in enumerate(lags):
198
for t1 in st1:
199
cc[i] += np.sum(np.abs(st2 - t1 - lag) < 0.5)
200
201
ax.bar(lags, cc, width=1, color='#3498db', alpha=0.7)
202
ax.set_xlabel('Lag (ms)')
203
ax.set_ylabel('Coincidences')
204
ax.set_title('Cross-Correlogram')
205
ax.grid(True, alpha=0.3)
206
207
# Power spectrum of population activity
208
ax = axes[1, 2]
209
f_pop, psd_pop = signal.welch(pop_rate, fs=1000/bin_size, nperseg=len(pop_rate)//2)
210
ax.semilogy(f_pop, psd_pop, 'k-', linewidth=1.5)
211
ax.set_xlabel('Frequency (Hz)')
212
ax.set_ylabel('Power')
213
ax.set_title('Population Power Spectrum')
214
ax.grid(True, alpha=0.3, which='both')
215
ax.set_xlim([0, 100])
216
217
plt.tight_layout()
218
plt.savefig('snn_activity.pdf', dpi=150, bbox_inches='tight')
219
plt.close()
220
221
rate_exc = np.mean([firing_rates[i] for i in range(N_exc)])
222
rate_inh = np.mean([firing_rates[i] for i in range(N_exc, N)])
223
\end{pycode}
224
225
\begin{figure}[htbp]
226
\centering
227
\includegraphics[width=0.95\textwidth]{snn_activity.pdf}
228
\caption{Network activity: (a) raster, (b) population rate, (c) rate distribution, (d) ISI, (e) cross-correlogram, (f) power spectrum.}
229
\end{figure}
230
231
\chapter{Synchronization Analysis}
232
233
\begin{pycode}
234
fig, axes = plt.subplots(2, 3, figsize=(14, 8))
235
236
# Synchrony measure (population spike count variance)
237
sync_window = 5 # ms
238
n_sync_bins = int(T / sync_window)
239
spike_counts = np.zeros(n_sync_bins)
240
241
for st in spike_times:
242
for t in st:
243
bin_idx = min(int(t / sync_window), n_sync_bins - 1)
244
spike_counts[bin_idx] += 1
245
246
fano_factor = np.var(spike_counts) / np.mean(spike_counts) if np.mean(spike_counts) > 0 else 0
247
248
ax = axes[0, 0]
249
ax.hist(spike_counts, bins=30, alpha=0.7, color='orange', edgecolor='black')
250
ax.axvline(np.mean(spike_counts), color='r', linewidth=2, label=f'FF={fano_factor:.2f}')
251
ax.set_xlabel('Spikes per bin')
252
ax.set_ylabel('Count')
253
ax.set_title('Spike Count Distribution')
254
ax.legend()
255
ax.grid(True, alpha=0.3)
256
257
# Membrane potential traces
258
ax = axes[0, 1]
259
sample_neurons = [0, N_exc//2, N_exc, N_exc + N_inh//2]
260
t_plot = np.arange(0, T, dt)
261
for i, n in enumerate(sample_neurons[:3]):
262
label = 'Exc' if n < N_exc else 'Inh'
263
color = '#3498db' if n < N_exc else '#e74c3c'
264
ax.plot(t_plot[:1000], V_record[n, :1000] - i*30, color=color, linewidth=0.5, label=label)
265
266
ax.set_xlabel('Time (ms)')
267
ax.set_ylabel('$V_m$ (offset)')
268
ax.set_title('Membrane Potentials')
269
ax.set_xlim([0, 100])
270
ax.grid(True, alpha=0.3)
271
272
# E/I balance
273
ax = axes[0, 2]
274
t_balance = t_bins[10:]
275
ei_ratio = exc_rate[10:] / (inh_rate[10:] + 1)
276
ax.plot(t_balance, ei_ratio, 'g-', linewidth=1)
277
ax.axhline(N_exc/N_inh, color='r', linestyle='--', alpha=0.5, label='Expected')
278
ax.set_xlabel('Time (ms)')
279
ax.set_ylabel('E/I Rate Ratio')
280
ax.set_title('Excitation-Inhibition Balance')
281
ax.legend()
282
ax.grid(True, alpha=0.3)
283
284
# Input-output curves
285
ax = axes[1, 0]
286
I_test = np.linspace(5, 30, 10)
287
rates_test = []
288
289
for I in I_test:
290
V_test = V_rest * np.ones(20)
291
spikes_test = 0
292
for _ in range(int(500/dt)):
293
dV = dt / tau_m * (-(V_test - V_rest) + R_m * I)
294
V_test += dV
295
spiked = V_test >= V_th
296
spikes_test += np.sum(spiked)
297
V_test[spiked] = V_reset
298
rates_test.append(spikes_test / 20 / 0.5)
299
300
ax.plot(I_test, rates_test, 'ko-', markersize=6)
301
ax.set_xlabel('Input Current ($\\mu$A/cm$^2$)')
302
ax.set_ylabel('Firing Rate (Hz)')
303
ax.set_title('Single Neuron F-I Curve')
304
ax.grid(True, alpha=0.3)
305
306
# Pairwise correlations
307
ax = axes[1, 1]
308
n_pairs = 50
309
correlations = []
310
for _ in range(n_pairs):
311
i, j = np.random.choice(N_exc, 2, replace=False)
312
if len(spike_times[i]) > 5 and len(spike_times[j]) > 5:
313
# Simple correlation measure
314
bins_ij = np.arange(0, T, 10)
315
counts_i = np.histogram(spike_times[i], bins=bins_ij)[0]
316
counts_j = np.histogram(spike_times[j], bins=bins_ij)[0]
317
if np.std(counts_i) > 0 and np.std(counts_j) > 0:
318
corr = np.corrcoef(counts_i, counts_j)[0, 1]
319
correlations.append(corr)
320
321
if len(correlations) > 0:
322
ax.hist(correlations, bins=20, alpha=0.7, color='#9b59b6', edgecolor='black')
323
ax.axvline(np.mean(correlations), color='r', linewidth=2)
324
ax.set_xlabel('Correlation')
325
ax.set_ylabel('Count')
326
ax.set_title('Pairwise Spike Correlations')
327
ax.grid(True, alpha=0.3)
328
329
mean_corr = np.mean(correlations) if len(correlations) > 0 else 0
330
331
# Connectivity matrix visualization
332
ax = axes[1, 2]
333
im = ax.imshow(W[:50, :50], cmap='RdBu', aspect='auto', vmin=-2, vmax=2)
334
ax.set_xlabel('Presynaptic')
335
ax.set_ylabel('Postsynaptic')
336
ax.set_title('Weight Matrix (50x50)')
337
plt.colorbar(im, ax=ax)
338
339
plt.tight_layout()
340
plt.savefig('snn_sync.pdf', dpi=150, bbox_inches='tight')
341
plt.close()
342
\end{pycode}
343
344
\begin{figure}[htbp]
345
\centering
346
\includegraphics[width=0.95\textwidth]{snn_sync.pdf}
347
\caption{Synchronization: (a) spike counts, (b) membrane traces, (c) E/I balance, (d) F-I curve, (e) correlations, (f) connectivity.}
348
\end{figure}
349
350
\chapter{Numerical Results}
351
352
\begin{pycode}
353
results_table = [
354
('Network size', f'{N}', 'neurons'),
355
('Mean firing rate', f'{mean_rate:.1f}', 'Hz'),
356
('Excitatory rate', f'{rate_exc:.1f}', 'Hz'),
357
('Inhibitory rate', f'{rate_inh:.1f}', 'Hz'),
358
('ISI CV', f'{cv_isi:.2f}', ''),
359
('Mean pairwise correlation', f'{mean_corr:.3f}', ''),
360
]
361
\end{pycode}
362
363
\begin{table}[htbp]
364
\centering
365
\caption{Spiking network results}
366
\begin{tabular}{@{}lcc@{}}
367
\toprule
368
Parameter & Value & Units \\
369
\midrule
370
\py{results_table[0][0]} & \py{results_table[0][1]} & \py{results_table[0][2]} \\
371
\py{results_table[1][0]} & \py{results_table[1][1]} & \py{results_table[1][2]} \\
372
\py{results_table[2][0]} & \py{results_table[2][1]} & \py{results_table[2][2]} \\
373
\py{results_table[3][0]} & \py{results_table[3][1]} & \py{results_table[3][2]} \\
374
\py{results_table[4][0]} & \py{results_table[4][1]} & \py{results_table[4][2]} \\
375
\py{results_table[5][0]} & \py{results_table[5][1]} & \py{results_table[5][2]} \\
376
\bottomrule
377
\end{tabular}
378
\end{table}
379
380
\chapter{Conclusions}
381
382
\begin{enumerate}
383
\item Balanced E/I maintains stable asynchronous activity
384
\item ISI CV near 1 indicates irregular Poisson-like firing
385
\item Sparse connectivity produces weak correlations
386
\item Population oscillations emerge from network interactions
387
\item Inhibition shapes temporal precision of excitation
388
\item LIF networks capture essential cortical dynamics
389
\end{enumerate}
390
391
\end{document}
392
393