Brian2GeNN: multiple synaptic output variables not supported

I attempted this but I get the same error as above (that it cannot find -lcuda)
Did I do it correctly (I did it a couple times by accident :sweat_smile:)?

(brianenv) [Nina@utwks62681 bin]$ echo $PATH
/home/Nina/.conda/envs/brianenv/bin:/usr/condabin:/usr/local/cuda-7.5/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/home/puttenmjam/.local/bin:/home/puttenmjam/bin:/opt/scripts:/var/lib/snapd/snap/bin:/Nina/Downloads/genn-4.3.3/bin:/home/Nina/Downloads/genn-4.3.3/bin:/usr/local/cuda-7.5/lib64:/usr/local/cuda-7.5/lib64:/usr/local/cuda-7.5/lib64

The /usr/local/cuda-7.5/lib64 path needs to be added to the LD_LIBRARY_PATH variable (or most likely you do not have any LD_LIBRARY_PATH variable at the moment, so you can set it to this value instead of adding it to it). In your output, you seem to have added it to PATH instead.

After making much progress in building my own model in brian2, I would like to get back to this. After playing around with different delays and synaptic weights, I found that it is not necessary to make these heterogeneous. I would really like to speed up my simulation, so; If all synapses are the same (same equations and same parameters (delays and weights and stuff)), but I have a sparse connectivity (I have some connection matrix), is it possible to restructure the model to make simulations much faster like you mentioned here? It should be possible to use some type of matrix multiplication right?

Hi again. The “extreme” solution would indeed be to do a calculation of the synaptic propagation yourself, but this would either mean to write a network_operation (rather straight-forward, but you could no longer use C++ standalone more or Brian2GeNN) or a function implementation in C++ (considerably less straightforward). But there is one technique that is very general and should help considerably: move computation from the synapses into the (pre-synaptic) neuron. Except for extremely sparse connectivity, you will have an order of magnitude more synapses than neurons. So every computation that is done “per synapse” and “per timestep” is very costly, and should be avoided if possible. Let’s have a look at some equations (more or less copied from your earlier posts):

eqs_synapsmodel = '''
s_nmda_tot_post = w *u_f * x_d * s_nmda :1 (summed)
ds_nmda/dt = -s_nmda/(taus_nmda)+alpha_nmda*x*(1-s_nmda) : 1 (clock-driven)
dx/dt = -x/(taux_nmda) :1 (clock-driven)
dx_d/dt = (1-x_d)/tau_d :1 (clock-driven)
du_f/dt = (U-u_f)/tau_f :1 (clock-driven)
w : 1
'''

eqs_onpre = '''
x += 1
u_f += U*(1-u_f)
x_d *= (1-u_f)
'''

These equations are solved for every synapse individually, but you can see that since tau_d etc. are constants, almost all of these equations will only depend on the pre-synaptic neuron, i.e. they will have exactly the same value for all synapses that are coming from the same neuron. Simulating them individually is therefore quite a waste of resources. In the above equations this means that you can move x, x_d, u_f, and s_nmda to the equations of the pre-synaptic neuron. The related statements in on_pre would go into the reset of the pre-synaptic neuron. The summed variable has to stay in the Synapses and then needs to refer to x_pre, etc. Now, there is one complication: all this only works if there are no synaptic delays, since a neuron’s reset statement will update the variables immediately. There is a workaround to this limitation, at least if all synapses coming from the same neuron have the same delay: you can introduce a “fake synapse” from each neuron onto itself, which triggers the update instead of using the reset statement. This way, you can set a delay on this synapse to have it trigger the modifications with a delay.
Finally, it might make sense to have a look at the output of profiling_summary first (note that using the name argument for objects will make this output more readable), to make sure you are not optimizing in the wrong place.

Thank you for the complete answer!

I’ve changed this and it does speed it up considerably :slight_smile: However, the result is not the same as before (before being with no delays). I’ve set the devices.device.seed the same both times but the noise component of the neurons is different for simulations before and after the change (normally setting the seed keeps all random number the same so why not now?). Did I do something wrong or am I making a reasoning mistake? This is what I did, I had:

1. eqs_synapsmodel = '''
2. s_nmda_tot1_post = w * S * U * 0.5 * s_nmda  :1 (summed)
3. ds_nmda/dt = -s_nmda/(taus_nmda)+alpha_nmda*x_nmda*(1-s_nmda) : 1 (clock-driven)
4. dx_nmda/dt = -x_nmda/(taux_nmda) :1 (clock-driven)
5. dx_d/dt = (1-x_d)/tau_d :1 (clock-driven)
6. w : 1
7. '''

8. eqs_onpre = '''
9. x_nmda += 1
10. x_d *= (1-U)
11. s_ampa += w * S * U * x_d 
12. ''' 

I’ve moved line 3 up to and including 6 to the neuron equations without changing anything (only deleting the ‘clock-driven’). I’ve moved line 9 and 10 to the reset statement. The remaining synapse equations are:

eqs_synapsmodel = '''
s_nmda_tot1_post = w_pre * S * U * 0.5 * s_nmda_pre  :1 (summed)
'''

eqs_onpre = '''
s_ampa += w * S * U * x_d
'''

So this does work and speeds it up somewhat. With profiling summary, I can see that synapses_stateupdater had reduced in consumed time by a factor of 6, but the neuron_stateupdated now takes a fourth time more. But the noise realisation is different and this makes it hard for me to check if anything else is wrong.

I hope the question is clear.

Yes, setting the seed should keep the seed realization the same. Could you verify with scheduling_summary that your objects are executed in the same order? E.g. if you have two neuron groups with noise, the order in which they will be executed is undefined – it will order them by their name but if you did not give them a name manually their name will be neurongroup and neurongroup_2, etc., which might change in subtle ways depending on how you set up your simulation.

The changes you made look good to me, that’s what I had in mind.

I am sorry, I made a mistake somewhere, the seed realization is the same now. s_ampa however, has changes slightly at every timestep (not shifted in time, just changed a tiny fraction). The scheduling summary looks exactly the same, except that the synapses_stateupdater has disappeared. But I was thinking, the reset takes place at a later time than the synapse stateupdates right? Could this cause the small difference?

Ah, indeed. In your original formulation, you update x_d first, and then s_ampa. In the current formulation, s_ampa is updated first based on the previous x_d value, and only then is x_d updated. You can either move the scheduling of the synaptic pathway (the on_pre statements) to a later point, or the reset scheduling to an earlier point to exactly reproduce the previous calculation. To move the synaptic pathway you can use something like

synapses.pre.when = 'after_resets'

To move the reset you can use

neurons.resetter['spike'].when = 'before_synapses'

(one of the two will be enough).

1 Like