Realization the model of network in Brain 2



the above are from my run of the code presented so far, with some modified plotting.
I added a 40Hz sine to compare to the gamma rhythm mentioned in the original paper:

visually inspecting the rasters, the oscillation seems less pronounced (and less synchronous), but looking at the rates, there does appear to be a 40Hz oscillation in the inhibitory firing rate.

here is my refactored code. I moved some constants around, and added some plotting:

expand for code
from brian2 import *
import numpy as np

start_scope()
# seed(11922)
n = 1000
num_ex = int(0.8*n)
num_inh = n - num_ex

defaultclock.dt = 0.5*ms
refractory_period = 0.1*ms # I don't think the original paper uses a refractory period
duration = 1.0*second

# The model
neuron_eqs = Equations('''
dv/dt = (0.04*v**2+5.*v+140- u + g_exc - g_inh + noise * randn())/ms : 1 (unless refractory)
du/dt = a*(b*v-u)/ms                             : 1 (unless refractory)
g_exc :1
g_inh :1
noise :1
a :1
b :1
c :1
d :1
''')

reset_eq = 'v = c; u += d; g_exc=0; g_inh=0'

N = NeuronGroup(n, model=neuron_eqs, threshold='v>=30', reset = reset_eq, refractory=refractory_period,
method='euler')

Ne = N[:num_ex]
Ni = N[num_ex:]

re = np.random.random(num_ex)  ; ri = np.random.random(num_inh)
Ne.noise = 5.0                 ; Ni.noise = 2.0
Ne.a = 0.02                    ; Ni.a = 0.02 + 0.08 * ri
Ne.b = 0.2                     ; Ni.b = 0.25 - 0.05 * ri
Ne.c = -65.0 + 15.0 * re**2    ; Ni.c = -65.0
Ne.d = 8.0 - 6.0 * re**2       ; Ni.d = 2.0
Ne.v = -65.0                   ; Ni.v = -65.0
Ne.u = Ne.v * Ne.b           ; Ni.u = Ni.v * Ni.b

Se = Synapses(Ne, N, model= "w:1", on_pre= 'g_exc_post += w')
Si = Synapses(Ni, N, model= "w:1", on_pre= 'g_inh_post += w')


prob_connect_e = 0.1
prob_connect_i = prob_connect_e

w_e = 0.5
w_i = 1.0
Se.connect(p=prob_connect_e) 
Si.connect(p=prob_connect_i)

# Initialization
Se.w = 'rand() * w_e'
Si.w = 'rand() * w_i'

M = SpikeMonitor(N)
Ex_rate = PopulationRateMonitor(Ne)
Inh_rate = PopulationRateMonitor(Ni)
#%%
run(duration)
#%%
figure(figsize=(8,7))

plot(M.t/ms, M.i,'k_',ms=2)
plot([0,duration/ms],[num_ex,num_ex],'r--')
xlim(0, duration/ms)
ylim(0, n)
xlabel('Time (ms)')
ylabel('Neuron index');

show()
#%%
t = Ex_rate.t
f = 40
gamma = sin(2*pi*f*t/second)*5
figure(figsize=(12,2))
plot(Ex_rate.t/ms, Ex_rate.smooth_rate(width=2*ms),'r')
plot(Inh_rate.t/ms, Inh_rate.smooth_rate(width=2*ms),'b')
plot(t/ms, gamma,'g')
ylim(-20,70)
legend(['excitatory','inhibitory','40Hz sine'])
xlabel('Time (ms)')
ylabel('Firing Rate (spikes/sec)')

now for some remaining issues / discrepancies:

1.) the paper sets the I→E weights to be uniformly distributed between -1.0 and 0.0:

S=[0.5*rand(Ne+Ni,Ne), -rand(Ne+Ni,Ni)];

the brian code we have seems to use positive weights for the “inhibitory” population:

Se.w = 'rand() * 0.5'
Si.w = 'rand() * 1.0'

maybe you’ve encoded that negative influence somewhere else but I don’t see it.
If I modify that line to Si.w = rand() * -1.0 the raster looks very different, arguably less like the original paper.

2.) the resultant network is NOT scale invariant. Synchrony seems to strongly depend on the number of total neurons

  • EDIT: this CAN be corrected by scaling the synaptic weights relative to the population size
  • i.e. w_e = 0.5 * (1000 / n)

3.) I don’t see anywhere in the original Izhikevich paper where they have a probability of connection, I think it’s essentially “all-to-all” whereas the code above uses a 10% probability of connection.

4.) The brian implementation is NOT invariant to changing the size of the timestep

  • the timing of the initial transient bump in firing rate depends strongly on what the timestep (dt) is
1 Like