Issues with creating a neuron population inside a custom function

Description of problem

My overall goal is to create a network of multiple neuron groups, each representing different neuron types within different brain regions. Therefore, I am creating custom functions inside which I am creating the neuron groups. However, I am having issues transferring variables through my own function into the NeuronGroup() function, and when I run the group I keep getting errors like '‘The identifier “tau” could not be resolved.’, even though I am sure I am defining the variables correctly.

Sometimes it will throw this error for one variable, and after re-starting everything it might throw the error for a different variable (it doesn’t seem to be consistent).

Is this something that can be done (or maybe should not be done and hence why it doesn’t work)?

Apologies if this has already been answered somewhere, I am fairly new to this!

Minimal code to reproduce problem

Simplified example

from brian2 import *

variables={'N':1, 'tau':10*ms, 'tau_2':10*ms}

def create_neuron_population(variables):
    # Parameters
    start_scope()
    
    N = variables['N']
    tau = variables['tau']
    tau_2 = variables['tau_2']
    eqs = '''
    dv/dt = (1.1-v)/tau : 1
    du/dt = (1.1 - u)/tau_2 : 1
    '''
    
    # Create a neuron group
    G = NeuronGroup(N, eqs, threshold='v>1', reset='v=0', method='exact')
    
    # Initial condition
    G.v = 'rand()'
    G.u = 0
    
    return G

G = create_neuron_population(variables)

spike_monitor = SpikeMonitor(G)
state_monitor = StateMonitor(G, 'v', record=True)

run(100*ms)

What you have aready tried

Taking all the same functionality outside the custom function removes any errors.

Hi @rebeccanclarke. This is a quite common issue, and we clearly need to document it better – most of our examples/tutorials/etc. are based on simple “flat” scripts where this issue does not appear. You can find some details here: Namespaces — Brian 2 2.7.1 documentation
But shortly: the variables need to be accessible where you call the run function, not where you define the group. So your example would work if N, tau, and tau_2 would be defined before run, not in the function. If you have the constants already in a dictionary, then the cleanest solution would be to not define them as normal Python variables at all, but instead use the namespace mechanism. If the constants are shared across groups, then you would provide this dictionary to the run function:

run(100*ms, namespace=variables)

This would work in your above example, but I guess in your real use case, different neuron types have different values for e.g. tau. In that case, you can give the namespace argument to NeuronGroup itself. I.e., in your example above:

G = NeuronGroup(N, eqs, threshold='v>1', reset='v=0', method='exact', namespace=variables)

(without needing to “unpack” the dictionary into variables before).

Both mechanisms can also be combined, i.e. each NeuronGroup has a few parameters that are specific to that group, while some parameters might be shared across all groups.

Finally, please note that for efficiency reasons, you should ideally only create separate NeuronGroups if they need different equations, not just different parameters.

E.g. instead of

   eqs = '''
    dv/dt = (1.1-v)/tau : 1
    du/dt = (1.1 - u)/tau_2 : 1
    '''
    G1 = NeuronGroup(N1, eqs, threshold='v>1', reset='v=0', method='exact', namespace={'tau': 10*ms, 'tau_2': 5*ms})
    G2 = NeuronGroup(N2, eqs, threshold='v>1', reset='v=0', method='exact', namespace={'tau': 20*ms, 'tau_2': 10*ms})

you could write:

   eqs = '''
    dv/dt = (1.1-v)/tau : 1
    du/dt = (1.1 - u)/tau_2 : 1
    tau : second (constant)
    tau_2 : second (constant)
    '''
    G = NeuronGroup(N1 + N2, eqs, threshold='v>1', reset='v=0', method='exact')
    G1 = G[:N1]
    G1.tau = 10*ms
    G1.tau_2 = 5*ms
    G2 = G[N1:]
    G2.tau = 20*ms
    G2.tau_2 = 10*ms

Of course this won’t change anything noticeable in a simple example, but if you have a complex network, then each separate NeuronGroup also requires that you use separate Synapses, so the total number of objects that need to be generated quickly explodes.

Hope that helps a bit!

Thank you @mstimberg that is so helpful!

I was planning on using separate neuron groups for different regions because I am including different receptor types for each group. So although the basic equations are the same, the synaptic current being added to dv/dt is slightly different for each one.

Thanks again, and for the speedy response!

1 Like