Changing weights during a run when an event occurred

Description of problem

How to change the weights to other neurons when a neuron fired?
The event-driven example on the Synapses page changes the weight of the neuron that fired, but not the weight to other neurons.

Minimal code to reproduce problem

So far I was able to achieve a similar goal by breaking down the simulation into smaller segments, and update the weight if the spike_mon detected spikes. But this is inaccurate and not event-driven.

from brian2 import *

eqs = '''
dv/dt = (I-v)/tau : 1
I : 1
tau : second
'''

G = NeuronGroup(3, eqs, threshold='v>1', reset='v = 0', method='exact')
G.I = [2, 0, 0]
G.tau = [10]*ms

S = Synapses(G, G, model='w : 1', on_pre='v_post += w')
S.connect(i=0,j=[1,2])
S.w = [0.5,1]
S.delay = [1, 0]*ms  # add a delay to demonstrate the issue

state_mon = StateMonitor(G,'v', record=True)
spike_mon = SpikeMonitor(G)

net = Network(collect())
net.add(S)
net.add(state_mon)
net.add(spike_mon)

#total run=50ms, but break the run into 10ms segment
for seg in range(5):
    net.run(10*ms)
    # check if a neuron fired every 10ms
    if(spike_mon.count[1] != 0 or spike_mon.count[2] != 0):
        # if a neuron fired, set the other neuron's weight to 0
        if(spike_mon.count[1] > spike_mon.count[2]):
            S.w = [1,0]
        else:
            S.w = [0,1]

plt.figure(1)
plt.subplot(311)
plot(state_mon.t/ms, state_mon.v[0], label='Neuron 0')
ylim([0,1.5])
ylabel('Neuron_0')

plt.subplot(312)
plot(state_mon.t/ms, state_mon.v[1], label='Neuron 1')
ylim([0,1.5])
ylabel('Neuron_1')

plt.subplot(313)
plot(state_mon.t/ms, state_mon.v[2], label='Neuron 2')
ylim([0,1.5])
ylabel('Neuron_2')
xlabel('Time (ms)')
show()

What you have already tried

I was thinking to add a backward synapse (blue arrow in the figure) to trigger the neuron in blue to update the weight. But I couldn’t figure out what syntax I should put for the model

STDP

Expected output (if relevant)

02

Actual output (if relevant)

01

Full traceback of error (if relevant)

Hi @RRHHAAWW,
you can implement this by having another “synapse”, that goes from the neurons to the actual synapses. When a neuron spikes, it can change the weight of the targeted synapse. When you set this up you have to be careful, since the target index j, corresponds to the index of the synapse (i.e. 0 is the first synapse that was created, 1 is the second synapse that was created). In your example, this index is very easy to figure out, but for more complex connection patterns it is less straightforward. With this i=[1, 2], j=[1, 0] setup, neuron 1 is connected to the synapse with index 1, i.e. the 0→2 connection, and neuron 2 is connected to the synapse with index 0, i.e. the 0→1 connection. Here’s the full adapted example:

from brian2 import *

eqs = '''
dv/dt = (I-v)/tau : 1
I : 1
tau : second
'''

G = NeuronGroup(3, eqs, threshold='v>1', reset='v = 0', method='exact')
G.I = [2, 0, 0]
G.tau = [10]*ms

S = Synapses(G, G, model='w : 1', on_pre='v_post += w')
S.connect(i=0,j=[1,2])
S.w = [0.5,1]
S.delay = [1, 0]*ms  # add a delay to demonstrate the issue

# Change weights of the synapse to the other neuron if a neuron spikes
S_suppress = Synapses(G, S, on_pre='w_post = 0')
S_suppress.connect(i=[1, 2], j=[1, 0])  # note that the j values are *synapse indices*

state_mon = StateMonitor(G,'v', record=True)
spike_mon = SpikeMonitor(G)

net = Network(collect())

net.run(50*ms)

plt.figure(1)
plt.subplot(311)
plot(state_mon.t/ms, state_mon.v[0], label='Neuron 0')
ylim([0,1.5])
ylabel('Neuron_0')

plt.subplot(312)
plot(state_mon.t/ms, state_mon.v[1], label='Neuron 1')
ylim([0,1.5])
ylabel('Neuron_1')

plt.subplot(313)
plot(state_mon.t/ms, state_mon.v[2], label='Neuron 2')
ylim([0,1.5])
ylabel('Neuron_2')
xlabel('Time (ms)')
show()

This is what I was looking for. Thank you.
In my project, there are around 65000 synapses. This method almost triples the connection and the time it takes to create synapses seems to be O(n^2) or even longer. Right now the synapses are created in loops. I’m wondering if using a flat array to create synapses makes it faster?

It is definitely much faster to make less connect calls with bigger arrays. One main reason is that each of the connect call will generate some Cython/C++ code that needs to get compiled. This is faster than creating the synapses in Python, but is only really worth it for big arrays (and in general probably not worth it at all for connect calls with indices instead of string expressions). A quick workaround could be to set prefs.codegen.target = 'numpy' before creating the synapses and switching it back to prefs.codegen.target = 'cython' before the start of the simulation.