Warning messages - conflict between internal variable and the name space

Description of problem

Hello, I always appreciate using Brian2.
Recently, I’ve been refining my code to enable the configuration of Brian2 objects through a separate script, in addition to the main script responsible for creating these objects in the lists with for loops.

But when I run the simulation the warning messages pop out and indicate that there is a conflict between internal variables and run namespace values.

The warning message for the first type is as follows (for the list ‘neurongroups’ which contains about 15 neurons) :

WARNING    The name "timestep" refers to different objects in different namespaces used for resolving names in the context of group "neurongroup_1". 
Will use the object from the functions namespace with the value <Function>, 
but the name also refers to a variable in the run namespace with value '100. * usecond'. [brian2.groups.group.Group.resolve.resolution_conflict]

The second type mentions the variable ‘t’ for a poison group and synapses :

WARNING    't' is an internal variable of group 'poissongroup', but also exists in the run namespace with the value 3. The internal variable will be used. [brian2.groups.group.Group.resolve.resolution_conflict]
WARNING    't' is an internal variable of group 'synapses_10', but also exists in the run namespace with the value 3. The internal variable will be used. [brian2.groups.group.Group.resolve.resolution_conflict]

And the last ones are about the variable ‘gmax’ :

WARNING    'gmax' is an internal variable of group 'synapses_20', but also exists in the run namespace with the value 250. * psiemens. The internal variable will be used. [brian2.groups.group.Group.resolve.resolution_conflict]

I found it difficult to grasp the situation accurately.
First of all, what are the functions namespace and the run namespace, and why would an internal variable also exist in there? I looked into the Brian2 documentation about the ‘Namespaces’ but I couldn’t get a clear idea of the namespaces. :
Brian2 docs about Namespaces

Since I haven’t provided to the Network.run I guess this problem has something to do with the lines around the point where the run function is called.

My code around those lines was:

net = Network(collect())
net.add(neurongroups, input_layers, input_synapses, intrinsic_syns_inh, intrinsic_syns_exc, neurongroups_ii, neurongroups_exc, intrinsic_synapses, fw_sources, fw_targets, forward_synapses, bw_sources, bw_targets, backward_synapses, statemonitors, spikemonitors, poprate_mons)
net.run(simulation_time)

In the main script, I’ve already created objects within a list, but around the “run” section, I’m placing those lists inside the net object. Could this be causing a collision, making it seem like they are duplicated?

But referring to the following post, it seems like there was no other option but to use this method.
existing question here

Also, the second question is, what would the ‘t’ variable be?
The warning message says ‘t’ is an internal variable for a synapse (and a poison group) but I cannot find any lines that I set those variables when creating the synapses.

Could you lend me a hand? It’s quite confusing.
Thanks a lot.

Minimal code to reproduce problem

I am attaching only the part creating neuron groups.

Script 1 - main script

from brian2 import *
from config_cmc import *
from equations_cmc import *
from synapses_cmc import *

neurongroups = [] 
for G in range(n_groups) :
    neurongroups.append(NeuronGroup(n_neurons[G], eqs_list[G], method='euler', 
                    threshold='v > Vthreshold', reset='v=Vl',refractory=2*ms))
    neurongroups[G].v = -70*mV
neurongroups = [neurongroups[i : i+len(n_neurons_1area)] for i in range(0, len(neurongroups), len(n_neurons_1area))] 

# inhibitory neurongroups 
neurongroups_ii = []
for a in range(len(neurongroups)) :
    neurongroups_ii.append(neurongroups[a][inh_idx])
    # print('len(neurongroups_ii): ', len(neurongroups_ii))

# excitatory neurongroups
neurongroups_exc = []
for a in range(len(neurongroups)) :
    for i in range(len(neurongroups[0])) :
        if i != inh_idx :
            neurongroups_exc.append(neurongroups[a][i])

Script 2 - config_cmc.py

from brian2 import *
from equations_cmc import *

# basic
simulation_time = (50*2*10)*ms
timestep = 0.1*ms
seeds = 7422

# neurons
areas = 4
eqs_list = [eqs_sp, eqs_ii, eqs_ss, eqs_dp] * areas
neurons = {'sp':24, 'ii':20, 'ss':10, 'dp':46}
neuron_names = list(neurons.keys())
n_neurons_1area =  list(neurons.values())                     
n_groups = len(n_neurons_1area) * areas                        
n_neurons = [n * multiply for n in n_neurons_1area] * areas    
input_idx = neuron_names.index('ss')   

Script 3 - equations_cmc.py

# time constant
tau_ampa = 4.0*ms   	# Excitatory synaptic time constant
tau_gaba = 16.0*ms

# Intrinsic neural properties (spm_fx_cmm_NMDA.m 변수 참고)
Css = 128*pfarad         # Membrane capacitance (memc)
Csp = 128*pfarad
Cii = 256*pfarad
Cdp = 32*pfarad
Cinput = 200*pfarad
gl = 1.0*nsiemens   # Leak conductance
Vl = -70*mV          # Resting potential (el)
Vi = -90*mV          # Inhibitory reversal potential (er)
Ve = 60.*mV              # Excitatory potential
Vthreshold = -40.*mV         # Spiking threshold (vt)
sigma = 2.0*mV 	# sigma of Ornstein-Uhlenbeck noise
tau_noise = 5.0*ms
tau = 5.0*ms

# Parameters for STDP equation
tau_stdp = 20*ms 	# STDP time constant at excitatory synapses
tau_istdp = 20*ms 	# STDP time constant at inhibitory synapses
Apre_exc = 0.005 * nS 	# STDP amplitude (dApre)
Apre_inh = 0.015 * nS 	# Inhibitory STDP amplitude (dApost)
Apost_exc = (-1.05 * 0.005) * nS
Apost_inh = (-1.05 * 0.015) * nS
gmax = 0.25*nS 	# maximum synaptic weight for excitatory synapses       
relbound = 0.1


# --------------------------------------------------------------------------------------------
# LIF Equations
# --------------------------------------------------------------------------------------------

eqs_sp = '''
    dv/dt = (gl*(Vl - v) + Isyn)/Csp + sigma*(2/tau)**0.5 *xi : volt (unless refractory)
    Isyn = IsynE + IsynI : amp
    IsynE = ge * (Ve - v) : amp
    IsynI = gi * (Vi - v) : amp
    dge/dt = -ge/tau_ampa : siemens
    dgi/dt = -gi/tau_gaba : siemens
    '''

eqs_ii = '''
    dv/dt = (gl*(Vl - v) + Isyn)/Cii + sigma*(2/tau)**0.5 *xi : volt (unless refractory)
    Isyn = IsynE + IsynI : amp
    IsynE = ge * (Ve - v) : amp
    IsynI = gi * (Vi - v) : amp
    dge/dt = -ge/tau_ampa : siemens
    dgi/dt = -gi/tau_gaba : siemens
    '''

eqs_ss = '''
    dv/dt = (gl*(Vl - v) + Isyn)/Css + sigma*(2/tau)**0.5 *xi : volt (unless refractory)
    Isyn = IsynE + IsynI : amp
    IsynE = ge * (Ve - v) : amp
    IsynI = gi * (Vi - v) : amp
    dge/dt = -ge/tau_ampa : siemens
    dgi/dt = -gi/tau_gaba : siemens
    '''

eqs_dp = '''
    dv/dt = (gl*(Vl - v) + Isyn)/Cdp + sigma*(2/tau)**0.5 *xi : volt (unless refractory)
    Isyn = IsynE + IsynI : amp
    IsynE = ge * (Ve - v) : amp
    IsynI = gi * (Vi - v) : amp
    dge/dt = -ge/tau_ampa : siemens
    dgi/dt = -gi/tau_gaba : siemens
    '''

For the equations in the synapses objects, I used :

# Excitatory STDP synapses
STDP_E = '''
    w : siemens
    gmax : siemens
    dapre/dt = -apre/tau_stdp : siemens (event-driven)
    dapost/dt = -apost/tau_stdp : siemens (event-driven)
    plastic : boolean (shared)
    '''
# STDP_E weight update
on_pre_STDP_E = '''
    ge += w
    apre += Apre_exc
    w = clip(w + plastic*apost, 0.0*nS, gmax)
    '''

What you have aready tried

I looked up the Brian2 documentation, and I’ve made the main script run without collecting or adding instances in the net object.

Expected output (if relevant)

Actual output (if relevant)

Full traceback of error (if relevant)

Hi @Elina.

The “run namespace” consists of all the names that you define outside the equations (as determined at the point of the run call, hence the name). In your case, these are the variables like tau_ampa, but also the variable timestep that you refer to in config_cmc (more in this later).

The “function namespace” consists of all the function definitions that are built into Brian, e.g. exp, sqrt, clip, etc. And, important for your code, one of the built-in functions is called timestep (see Functions — Brian 2 2.5.4 documentation).

Finally, “internal variables” are all variables that are defined as part of the equations (v, ge, IsynE, …) and also pre-defined variables, such as xi, dt, and t (the current time within your simulation). See Equations — Brian 2 2.5.4 documentation.

This hopefully makes your three warnings clearer:

The model refers to the timestep function, and you also define a constant called timestep – in principle, you could want to refer to this value in your equations, but this wouldn’t work here, since the built-in function timestep takes precedence. Now, I think this warning should probably not be displayed, actually, for two reasons: 1) You do not yourself refer to the timestep function in your equations – the function is used as part of the implementation of the refractoriness, but since you did not write this part manually, there is no room for confusion. 2) the built-in timestep name refers to a function while your external constant is a numerical value, so it is unlikely that there was any confusion between the two.

As mentioned earlier, t is a built-in variable, and equations can refer to it. Similar to the situation with timestep, you do not refer to the variable directly, but it is used as part of the event-driven synaptic equations (which have a term with t - lastupdate in it, see Synapses — Brian 2 2.5.4 documentation). Again, since you are not using it yourself, it might be better not to show a warning in this case. On the other hand, we prefer to be safe, declaring a variable named t yourself can certainly be confusing.

This is probably the only warning that is unambiguously relevant in your case: in your synaptic model (STDP_E), you state that each synapse has its own value for gmax, and the on_pre_STDP_E statement refers to it. The value gmax = 0.25*nS is never used during the simulation. It is not in the code you posted, but maybe you are doing something like syn.gmax = gmax, setting the per-synapse value gmax to 0.25nS? That would work, but if you set the same value for each synapse anyway, you could also remove the definition of gmax : siemens from the equations, and directly refer to the external gmax value.

I hope this makes things clearer? If you prefer a different approach where you don’t run into this kind of warnings, you could consider handing a namespace argument to net.run, with only the necessary external values, i.e. something like:

net.run(simulation_time, namespace={"Css": Css, "Csp": Csp, ...})

(alternatively, you could also pass namespace arguments to the individual NeuronGroup and Synapses objects, and an empty namespace={} to net.run – this would make it clearer which external constants are necessary for which object)

Marcel

PS: Not related to your issue, but from a cursory look at your code, I think it would be more efficient to only have one group of neurons per type, and then use subgroups for the areas. Something along the lines of (unfinished/untested code):

eqs_list = [eqs_sp, eqs_ii, eqs_ss, eqs_dp]
neurons = {'sp':24, 'ii':20, 'ss':10, 'dp':46}
neurongroups = []
for idx, neuron_type in enumerate(neurons):
  # one NeuronGroup per type
  neurongroups.append(NeuronGroup(neurons[neuron_type]*areas, eqs_list[idx], ...))
# The neurongroups list above would  go into the network
# Split up things into subgroups, e.g. this is a list for each area:
subgroups = []
for area_idx in range(areas):
    for neuron_idx, neuron_type in enumerate(neurons.values()):
        n_neurons = neurons[neuron_type]
        subgroups.append(neurongroups[neuron_idx][area_idx*n_neurons:(area_idx+1)*n_neurons])

You’d then use the subgroups to connect the areas with synapses, etc. Another approach would be to add area : integer to your model equations, and then you could connect things referring to area_pre and area_post. Let me know if you are interested in more details on these approaches.

1 Like

Hi Marcel,
Thank you very much for the detailed comment! :slight_smile:
Your explanation helped me a lot in understanding the concept of namespaces clearly.

Based on your reply, to prevent warning messages about variable conflicts, I changed the ‘timestep’ I specified to ‘time_step’.

Regarding ‘gmax’, instead of defining it separately as ‘gmax: siemens’ and using expressions like syn.gmax = gmax to set per-synapse value gmax to 0.25nS, as you mentioned, I opted to directly reference the external variable gmax and used namespaces when creating Synapses, as shown in the code below.

STDP_E = '''
    w : siemens
    dapre/dt = -apre/tau_stdp : siemens (event-driven)
    dapost/dt = -apost/tau_stdp : siemens (event-driven)
    plastic : boolean (shared)
    '''
on_pre_STDP_E = '''
    ge += w
    apre += Apre_exc
    w = clip(w + plastic*apost, 0.0*nS, gmax)
    '''
# and then, used namespace when creating Synapses
Synapses( ..., namespace={'gmax': gmax})

The part regarding the variable ‘t’ still triggers warning messages about conflicts. Is it okay to ignore these?

I’ve also looked into creating the neurongroups and the subgroups you mentioned, and it seems like this approach would make coding much more efficient. It looks like it would be helpful when creating synapses, even as we scale up the network in the future.
Additionally, including area : integer in the model equation and using ‘area_pre’ and ‘area_post’ to connect synapses seems very promising. Could you provide more details on this?

The only thing is, just in case, I plan to run the code on GPU using brian2cuda in the future, so I’m curious if the methods you mentioned are compatible with brian2cuda.

Thanks a lot for your helpful comments!

It is definitely ok to ignore them, the way to avoid them would be to rename the variable t that your code defines somewhere – I did not see a definition in the code you shared, but the warning message mentions that it is defined with the value 3 which might help you find it.

A rather complex example that uses a very similar approach is Example: Wang_2002 — Brian 2 2.5.4.post99 documentation In that example, there are not different layers but subgroups of neurons, defined using the label : integer parameter. But the idea is the same, e.g. you can use synapses.connect("label_pre == label_post") to connect within a population, or you can use something like synapses.w["label_pre == 0 and label_pre!=label_post"] = ... to set a specific synaptic weights for connections from population 0 (but not for connections within the population itself). Another similar example is https://brian-team.github.io/brian-material/2021-07-CNS-showcase/#12 (full code: brian-material/2021-07-CNS-showcase/code/example_3.py at d1d74fc78c04c703a99730c1edaab9f677552a1e · brian-team/brian-material · GitHub), which uses the same approach to connect with a type of neurons. Hope that conveys the general idea, let me know if you’d like a more detailed explanation.

Everything I mentioned should be compatible with brian2cuda as well. Actually, it could be important to reduce the number of NeuronGroup/Synapses objects in your code if you want to use brian2cuda. Its compilation process is currently quite slow, and having many objects can slow it down quite dramatically (i.e. the first step of compiling your code, before the actual code execution begins).

1 Like

Many of my questions have been resolved. I’ve looked through all the examples and codes you provided, and they were exactly what I needed. Setting up synapses according to that method significantly reduced the number of synapse instances, thus increasing the speed.

Additionally, the examples from the CNS showcase you provided were very helpful. Besides specifying connection conditions, I also wanted to display synaptic connection information as lines, and being able to represent it this way is beautiful and impressive. Brian2 seems to be a truly powerful tool.
Thank you! :slight_smile:

1 Like