Hi. I’m trying to define many groups of neurons and need to define them in a loop. My problem is that when I append them in a list, it seems that I cannot monitor the variables that I want. I would really appreciate it if someone could help me in this regard. Thank you.
Hi LBENE,
i often use python’s list comprehension to solve this:
voltage_monitors = [StateMonitor(pop,'v',record=True) for pop in pop_list]
spike_monitors = [SpikeMonitor(pop) for pop in pop_list]
rate_monitors = [PopulationRateMonitor(pop) for pop in pop_list]
where pop_list is just a list of neuron groups
Thank you very much adam. Are the synapses between each two neuron groups defined the same way?
I haven’t tried monitoring synapses yet, but yeah I think if you collected synapses like:
a_syn = Synapses(...)
b_syn = Synapses(...)
c_syn = Synapses(...)
synapse_list = [a_syn, b_syn, c_syn]
or
synapse_list = []
for i in range(3):
synapse_list.append( Synapses(...) )
then you could do
synpase_w_monitors = [StateMonitor(S, 'w', record=True) for S in synapse_list]
I tried expanding the code to a larger network and the same thing happened again. StateMonitor records nothing. This is the related part of the code (e: excitatory, ffi: feedforward inhibitory, fbi: feedfback inhibitory):
...
G_e = []
G_ffi = []
G_fbi = []
S_e_e = []
S_e_ffi = []
S_e_fbi = []
S_ffi_e = []
S_ffi_ffi = []
S_fbi_e = []
S_fbi_fbi = []
for rc1 in range(number_of_regions):
...
G_e.append(NeuronGroup(net1_attr[rc1].N_e, eqs_e, threshold="v > 0*mV", refractory="v > 0*mV", method="exponential_euler"))
...
G_ffi.append(NeuronGroup(net1_attr[rc1].N_ffi, eqs_ffi, threshold="v > 0*mV", refractory="v > 0*mV", method="exponential_euler"))
G_fbi.append(NeuronGroup(net1_attr[rc1].N_fbi, eqs_fbi, threshold="v > 0*mV", refractory="v > 0*mV", method="exponential_euler"))
S_e_e.append(Synapses(G_e[rc1], G_e[rc1], "w : volt", on_pre="v_post += w"))
S_e_ffi.append(Synapses(G_e[rc1], G_ffi[rc1], "w : volt", on_pre="v_post += w"))
S_e_fbi.append(Synapses(G_e[rc1], G_fbi[rc1], "w : volt", on_pre="v_post += w"))
S_ffi_e.append(Synapses(G_ffi[rc1], G_e[rc1], "w : volt", on_pre="v_post -= w"))
S_ffi_ffi.append(Synapses(G_ffi[rc1], G_ffi[rc1], "w : volt", on_pre="v_post -= w"))
S_fbi_e.append(Synapses(G_fbi[rc1], G_e[rc1], "w : volt", on_pre="v_post -= w"))
S_fbi_fbi.append(Synapses(G_fbi[rc1], G_fbi[rc1], "w : volt", on_pre="v_post -= w"))
...
S_e_e[rc1].connect(i=i1, j=post1)
...
S_e_ffi[rc1].connect(i=i1, j=post1)
...
S_e_fbi[rc1].connect(i=i1, j=post1)
...
S_e_e[rc1].w = scale1*np.random.normal(PP_m, STD1)*mV
S_e_ffi[rc1].w = scale1*np.random.normal(PPV_m, STD1)*mV
S_e_fbi[rc1].w = scale1*np.random.normal(PS_m, STD1)*mV
S_e_e[rc1].delay = delay_e_e*ms
S_e_ffi[rc1].delay = delay_e_ffi*ms
S_e_fbi[rc1].delay = delay_e_fbi*ms
>>>same approach for ffi and fbi<<<
...
S_e_e_o = []
S_e_ffi_o = []
S_ffi_e_o = []
S_ffi_ffi_o = []
for rc1 in range(number_of_regions):
S_e_e_o.append([])
S_e_ffi_o.append([])
S_ffi_e_o.append([])
S_ffi_ffi_o.append([])
for rc2 in range(number_of_regions):
if rc1 != rc2:
S_e_e_o[rc1].append(Synapses(G_e[rc1], G_e[rc2], "w : volt", on_pre="v_post += w"))
S_e_ffi_o[rc1].append(Synapses(G_e[rc1], G_ffi[rc2], "w : volt", on_pre="v_post += w"))
S_ffi_e_o[rc1].append(Synapses(G_ffi[rc1], G_e[rc2], "w : volt", on_pre="v_post -= w"))
S_ffi_ffi_o[rc1].append(Synapses(G_ffi[rc1], G_ffi[rc2], "w : volt", on_pre="v_post -= w"))
S_e_e_o[rc1][rc2].connect(p=prob_t[0])
S_e_ffi_o[rc1][rc2].connect(p=prob_t[1])
S_ffi_e_o[rc1][rc2].connect(p=prob_t[2])
S_ffi_ffi_o[rc1][rc2].connect(p=prob_t[3])
S_e_e_o[rc1][rc2].w = scale1*np.random.normal(PP_m, STD1)*mV
S_e_ffi_o[rc1][rc2].w = scale1*np.random.normal(PPV_m, STD1)*mV
S_ffi_e_o[rc1][rc2].w = scale1*np.random.normal(PVP_m, STD1)*mV
S_ffi_ffi_o[rc1][rc2].w = scale1*np.random.normal(PVPV_m, STD1)*mV
S_e_e_o[rc1][rc2].delay = 20.*ms
S_e_ffi_o[rc1][rc2].delay = 13.*ms
S_ffi_e_o[rc1][rc2].delay = 17.*ms
S_ffi_ffi_o[rc1][rc2].delay = 17.*ms
else:
S_e_e_o[rc1].append([])
S_e_ffi_o[rc1].append([])
S_ffi_e_o[rc1].append([])
S_ffi_ffi_o[rc1].append([])
M_v_e = [StateMonitor(g_e,'v',record=True) for g_e in G_e]
M_v_ffi = [StateMonitor(g_ffi,'v',record=True) for g_ffi in G_ffi]
M_v_fbi = [StateMonitor(g_fbi,'v',record=True) for g_fbi in G_fbi]
run(60*second)
Hi @LBENE , please see the remarks in the documentation here: Running a simulation — Brian 2 2.5.0.1 documentation If your objects (e.g. monitors) are in a list, you have to manually add them to a Network, a simple run will not include them. See also my reply here: Unable to create proper synapses connections using a loop - #2 by mstimberg
Oh, and one more thing: it is much more efficient to create a minimal number of NeuronGroup and Synapses objects. In particular, this will drastically reduce the preparation/compilation time. From your code example, it seems that all the neurons stored in G_e share the same equations, they could therefore all go into a single NeuronGroup (same for G_ffi and G_fbi). You can always create subgroups to divide them up into smaller units. Similarly, you could then have considerable fewer Synapses objects, since you only need one per type of connection.
A technique that is not widely used but can lead to very readable code is to make use of “labels” in the neuron description, e.g. add region : integer (constant) to the equations. Then, assuming you have a single NeuronGroup G_e storing all layers of excitatory neurons, you’ll set them with something like (inspired from the code you posted):
region_sizes = [net1_attr[rc1].N_e for rc1 in range(number_regions)]
indices = np.cumsum(np.hstack([0, region_sizes]))
for r in range(number_of_regions):
G_e.region[indices[r]:indices[r+1]] = r
You can then refer to the region of cells when you create synapses. For example, you could create connections between the excitatory neurons in each region with a single connect command:
S_e_e = Synapses(G_e, G_e, ...)
S_e_e.connect("region_pre == region_post", p=0.1)
The only downside of this approach is that it is not the most efficient way to connect the synapses: it will check for all possible pairs of neurons whether they are in the same region. If you have a very big network, this will be quite slow.
Wow! Thank you very much for all the insights. The network is not that big right now: five regions with 250 neurons each. The number of neurons in each region will be increased later on to something like 1000.
Also, I previously overlooked the part about subgroups. After this, I wanted to define like 75 groups for each region representing 25 cortical columns. This subgroup really avoids going on that path.
Hi again. I tried breaking the groups in smaller pieces and it apparently got very slow. I tried printing some variables and it seems for some reason synapses are defined very slow. This is the related part of the code:
for rc1 in range(number_of_regions):
...
for i1 in range(20):
G1 = G_py[rc1][14*i1:14*(i1+1)]
for i2 in range(20):
G2 = G_py[rc1][14*i2:14*(i2+1)]
syn_temp = Synapses(G1, G2, "w : volt", on_pre="v_post += w")
if i1 == i2:
syn_temp.connect(p=.65)
syn_temp.w = scale1*np.random.normal(PYPY_m, STD1)*mV
syn_temp.delay = synaptic_time_constant_py_py*ms
else:
syn_temp.connect(p=.1)
syn_temp.w = scale1*np.random.normal(PYPY_m, STD1)*mV
syn_temp.delay = (neurons_distance(i1, i2) / conduction_velocity_py_py + synaptic_time_constant_py_py)*ms
S_all_intra.append(syn_temp)
if i2%2 == 0:
G3 = G_pv[rc1][5*int(np.floor(i2/2)):5*int(np.floor(i2/2))+3]
else:
G3 = G_pv[rc1][5*int(np.floor(i2/2))+3:5*int(np.floor(i2/2))+5]
syn_temp = Synapses(G1, G3, "w : volt", on_pre="v_post += w")
if i1 == i2:
syn_temp.connect(p=.65)
syn_temp.w = scale1*np.random.normal(PYPV_m, STD1)*mV
syn_temp.delay = synaptic_time_constant_py_pv*ms
else:
syn_temp.connect(p=.1)
syn_temp.w = scale1*np.random.normal(PYPV_m, STD1)*mV
syn_temp.delay = (neurons_distance(i1, i2) / conduction_velocity_py_pv + synaptic_time_constant_py_pv)*ms
S_all_intra.append(syn_temp)
G4 = G_sst[rc1][i2]
syn_temp = Synapses(G1, G4, "w : volt", on_pre="v_post += w")
if i1 == i2:
syn_temp.connect(p=.65)
syn_temp.w = scale1*np.random.normal(PYSST_m, STD1)*mV
syn_temp.delay = synaptic_time_constant_py_sst*ms
else:
syn_temp.connect(p=.1)
syn_temp.w = scale1*np.random.normal(PYSST_m, STD1)*mV
syn_temp.delay = (neurons_distance(i1, i2) / conduction_velocity_py_sst + synaptic_time_constant_py_sst)*ms
S_all_intra.append(syn_temp)
if i1%2 == 0:
G1 = G_pv[rc1][5*int(np.floor(i1/2)):5*int(np.floor(i1/2))+3]
else:
G1 = G_pv[rc1][5*int(np.floor(i1/2))+3:5*int(np.floor(i1/2))+5]
for i2 in range(20):
G2 = G_py[rc1][14*i2:14*(i2+1)]
syn_temp = Synapses(G1, G2, "w : volt", on_pre="v_post -= w")
if i1 == i2:
syn_temp.connect(p=.62)
syn_temp.w = scale1*np.random.normal(PVPY_m, STD1)*mV
syn_temp.delay = synaptic_time_constant_pv_py*ms
else:
syn_temp.connect(p=.07)
syn_temp.w = scale1*np.random.normal(PVPY_m, STD1)*mV
syn_temp.delay = (neurons_distance(i1, i2) / conduction_velocity_pv_py + synaptic_time_constant_pv_py)*ms
S_all_intra.append(syn_temp)
if i2%2 == 0:
G3 = G_pv[rc1][5*int(np.floor(i2/2)):5*int(np.floor(i2/2))+3]
else:
G3 = G_pv[rc1][5*int(np.floor(i2/2))+3:5*int(np.floor(i2/2))+5]
syn_temp = Synapses(G1, G3, "w : volt", on_pre="v_post -= w")
if i1 == i2:
syn_temp.connect(p=.8)
syn_temp.w = scale1*np.random.normal(PVPV_m, STD1)*mV
syn_temp.delay = synaptic_time_constant_pv_pv*ms
else:
syn_temp.connect(p=.07)
syn_temp.w = scale1*np.random.normal(PVPV_m, STD1)*mV
syn_temp.delay = (neurons_distance(i1, i2) / conduction_velocity_pv_pv + synaptic_time_constant_pv_pv)*ms
S_all_intra.append(syn_temp)
G4 = G_sst[rc1][i2]
syn_temp = Synapses(G1, G4, "w : volt", on_pre="v_post -= w")
if i1 == i2:
syn_temp.connect(p=.8)
syn_temp.w = scale1*np.random.normal(PVSST_m, STD1)*mV
syn_temp.delay = synaptic_time_constant_pv_sst*ms
else:
syn_temp.connect(p=.07)
syn_temp.w = scale1*np.random.normal(PVSST_m, STD1)*mV
syn_temp.delay = (neurons_distance(i1, i2) / conduction_velocity_pv_sst + synaptic_time_constant_pv_sst)*ms
S_all_intra.append(syn_temp)
G1 = G_sst[rc1][i1]
for i2 in range(20):
G2 = G_py[rc1][14*i2:14*(i2+1)]
syn_temp = Synapses(G1, G2, "w : volt", on_pre="v_post -= w")
if i1 == i2:
syn_temp.connect(p=.71)
syn_temp.w = scale1*np.random.normal(SSTPY_m, STD1)*mV
syn_temp.delay = synaptic_time_constant_sst_py*ms
else:
syn_temp.connect(p=0.)
syn_temp.w = scale1*np.random.normal(SSTPY_m, STD1)*mV
syn_temp.delay = (neurons_distance(i1, i2) / conduction_velocity_sst_py + synaptic_time_constant_sst_py)*ms
S_all_intra.append(syn_temp)
if i2%2 == 0:
G3 = G_pv[rc1][5*int(np.floor(i2/2)):5*int(np.floor(i2/2))+3]
else:
G3 = G_pv[rc1][5*int(np.floor(i2/2))+3:5*int(np.floor(i2/2))+5]
syn_temp = Synapses(G1, G3, "w : volt", on_pre="v_post -= w")
if i1 == i2:
syn_temp.connect(p=.8)
syn_temp.w = scale1*np.random.normal(SSTPV_m, STD1)*mV
syn_temp.delay = synaptic_time_constant_sst_pv*ms
else:
syn_temp.connect(p=0.)
syn_temp.w = scale1*np.random.normal(SSTPV_m, STD1)*mV
syn_temp.delay = (neurons_distance(i1, i2) / conduction_velocity_sst_pv + synaptic_time_constant_sst_pv)*ms
S_all_intra.append(syn_temp)
I would really appreciate it if you could guide me in this regard.
I rewrote the code like:
for rc1 in range(number_of_regions):
S_py_py.append(Synapses(G_py[rc1], G_py[rc1], "w : volt", on_pre="v_post += w"))
...
for i1 in range(20):
g1_0 = range(14*i1,14*(i1+1))
for i2 in range(20):
g2_0 = range(14*i2,14*(i2+1))
G1_0 = np.repeat(g1_0, np.size(g2_0))
G2_0 = np.tile(g2_0, np.size(g1_0))
if i1 == i2:
S_py_py[rc1].connect(i=G1_0,j=G2_0,p=.65)
S_py_py[rc1].w[G1_0,G2_0] = scale1*np.random.normal(PYPY_m, STD1)*mV
S_py_py[rc1].delay[G1_0,G2_0] = synaptic_time_constant_py_py*ms
else:
S_py_py[rc1].connect(i=G1_0,j=G2_0,p=.1)
S_py_py[rc1].w[G1_0,G2_0] = scale1*np.random.normal(PYPY_m, STD1)*mV
S_py_py[rc1].delay[G1_0,G2_0] = (neurons_distance(i1, i2) / conduction_velocity_py_py + synaptic_time_constant_py_py)*ms
And it seems fast now. My only problem is that I don’t know if the way I defined connections, weights, and delays works fine. In other words, can this setup be used for having various weights and delays inside a synaptic object?
Hi. Thanks for the example code, that is really helpful. I’ll have a closer look, but I think whether this works correctly depends on whether the S_py_py[rc1].w = ... and ...delay = ... lines are setting single values, or an array of values. In other words should all connections within a column, or all connections between two specific columns, share the same random weight? This seems to be the case in your current code (assuming that PYPY_m and STD1 are scalar values which means that np.random.normal returns a single random value).