Delays as variables

I’d like to modify synaptic conduction delays, initially between runs e.g. with net.set_states({"Synapses0": {"delay": np.array([5, 10, 15, 20]) * br.mV}}) and perhaps eventually during runs as a form of plasticity. I found the following discussion from 6 1/2 years ago (!):

Does anyone know if this has been implemented or is possible in some other way? If not, is the advice about how to start in the GitHub issue still applicable? Are there any other tips on how to proceed?

Hi Ben. Changing the delay in between runs should definitely work, even though I am not sure whether we are testing this behaviour as part of our test suite. Note that if a spike is already on its way, it will use the delay that was set when it was emitted (if I remember correctly). If this does not work correctly, please file an issue!
Delays that can vary during a run are more complicated though (as discussed in the issue, there are a number of syntax and implementation issues to figure out). I don’t think we ever did proper benchmarking of the offset pre-calculation – if this turns out to be not meaningfully faster than just calculating the offset “on-the-fly”, then supporting variable delays would be almost trivial.
Of course even without this feature, you could have some delta_delay variable in your model that keeps track of whether delays should change,. You could then run your simulation for a little while and execute something along the lines of

synapses.delay = 'clip(delay + delta_delay, 0*ms, inf*ms)'

in between runs.

Hi Marcel, thanks for the quick response!
Ok, considering only changing between runs for now: so I can’t do it with the set_states() method of a Network object (as in my example above) but I can set the property directly on a Synapses object (and Brian will take care of any spikes in transit)? I checked and setting delays on the Synapses objects seems ok so far.

Ah, sorry I forgot to reply to this part. You can also use set_states(), but you have to be aware that the delay is a property of the SynapticPathway and not of the Synapses object. The assignment synapses.delay is actually a shorthand for synapses.pre.delay. So in your example from above, the name should be something like Synapses0_pre.

Thanks Marcel. So if I access the Synapses object directly, I can set the delays with either S0.delay = ... or S0.pre.delay = ... (I’ve changed Synapses0 to S0 for brevity). However, if I do:
net.set_states({“S0_pre”: {“delay”: np.arange(4) *}})
(where S0 has been previously added to the Network object, net), then I get:
KeyError: “Network does not include a network with name ‘S0_pre’.”
I can’t see anything in the relevant entry in the states dictionary (from net.get_states()) that would give me access to the SynapticPathway (below). Do I need to explicitly add something else to the Network?

     'S0': {'N': array(4, dtype=int32),
        'N_incoming': array([2, 2, 2, 2], dtype=int32),
        'N_outgoing': array([2, 2, 2, 2], dtype=int32),
        'N_post': array(2),
        'N_pre': array(2),
        'dt': 100. * usecond,
        'dt_post': 100. * usecond,
        'dt_pre': 100. * usecond,
        'i': array([0, 0, 1, 1], dtype=int32),
        'i_post': array([0, 1, 0, 1], dtype=int32),
        'i_pre': array([0, 0, 1, 1], dtype=int32),
        'j': array([0, 1, 0, 1], dtype=int32),
        'lastspike': array([-10., -10., -10., -10.]) * ksecond,
        'lastspike_post': array([-10., -10., -10., -10.]) * ksecond,
        'not_refractory': array([ True,  True,  True,  True]),
        'not_refractory_post': array([ True,  True,  True,  True]),
        'period_pre': 0. * second,
        't': 0. * second,
        't_in_timesteps': array(0),
        't_in_timesteps_post': array(0),
        't_in_timesteps_pre': array(0),
        't_post': 0. * second,
        't_pre': 0. * second,
        'tau_m': array([30., 30., 30., 30.]) * msecond,
        'tau_m_post': array([30., 30., 30., 30.]) * msecond,
        'v': array([-65.8616107 , -66.10371261, -65.8616107 , -66.10371261]) * mvolt,
        'v_post': array([-65.8616107 , -66.10371261, -65.8616107 , -66.10371261]) * mvolt,
        'w': array([ 5.42988123, -7.22922798, -1.82890738,  0.35918114]) * mvolt},

Oops, sorry, I only tested it with the “magic” network. This should work, but I think we broke it with the changes in this PR: The fix should be quite straightforward, though. In the mean time, can you try to add S0.pre to the Network manually? All the objects end up in a set, so there is no danger of executing things twice.

Thanks Marcel, adding S0.pre to the network works! I’ll keep an eye on "contained objects" should be accessible via Network.get_states/set_states by mstimberg · Pull Request #1292 · brian-team/brian2 · GitHub to see when the fix is merged into a release.
Until then, could I just add S0.per to net instead of S0 (rather than in addition to) to tidy up the dict returned by net.get_states() (it seems to contain all the same values plus delays)?

Hmm, I think it depends on whether your synapse is doing anything else except for using an on_pre, i.e. whether if it has differential equations. If not, it might work :slight_smile:

Ok thanks. I’ll leave the clutter to avoid any tortuous bugs further down the road!