Class vs direct?

Description of problem

Dear all,
I am new on Brian, and want to implement my network in a class and test it, but it provides no results.
Did I do something wrong here?

Thanks for your time and help.
BR Binh

Minimal code to reproduce problem

class myNeuronClass :
    def __init__(self):
        eqsM = '''
	    dv/dt = (g+1)/(1*ms): 1 (unless refractory)
	    g : 1
	    '''
        self.G = NeuronGroup(1, eqsM, threshold='v>5', reset='v=0; g = 0', refractory=5*ms, method='Euler')
        # we send spikes at time 2, and 32ms to the neuron group G for changing the value of its variable "g" to 2 and 4 accordingly
        nlayers = 2
        self.clocks = []
        self.clock_synapses = []
        for l in range(nlayers):
	        spike_times = np.array([l*30+2])*ms
	        indices = np.array([0]*len(spike_times))
	        clock = SpikeGeneratorGroup(1, indices, spike_times)
	        self.clocks.append(clock)
	        clock_synapse = Synapses(clock, self.G, on_pre='g =' + str((l+1)*2))
	        clock_synapse.connect()
	        self.clock_synapses.append(clock_synapse)

def main():
	start_scope()

	duration = 100*ms
	net = myNeuronClass()
	
	M = StateMonitor(net.G, 'v', record=True)
	P = StateMonitor(net.G, 'g', record=True)

	run(duration)
	
	subplot(211)
	plot(M.t/ms, M.v[0])
	ylabel('v')
	subplot(212)
	plot(P.t/ms, P.g[0])
	xlabel('Time (ms)')
	ylabel('g')
	show()

What you have already tried

I want to monitor the value “g” and “v”, but they seem to be empty.
Without using class, it works well.

Expected output (if relevant)

For v: I should see a saw-tooth signal over time
For g: it should be 0 until t= 2, then 2 until t=32, and after that 4

Actual output (if relevant)

empty output

Full traceback of error (if relevant)

Hi @Thanh-Binh – welcome to the Brian community :blush:
The issue you are facing is unfortunately quite common, we do not document the “structured program” use case very well, i.e. constructing things in classes and functions as opposed to “flat scripts” as in most of our examples and tutorials.

In your code, the main function calls run(...). At this point, Brian will look for “Brian objects” in the current scope and run them. In your case, it will only find the two StateMonitors. There is a way to make it work with custom classes, but in cases like this, I would rather recommend to explicitly construct a Network object containing everything needed (see Running a simulation — Brian 2 2.7.1 documentation). In your example, you could for example replace your run call by:

network = Network(collect())  # include the monitors
network.add(net.G)  # add the NeuronGroup
network.add(net.clocks)  # add the SpikeGeneratorGroups
network.add(net.clock_synapses)  # add the Synapses
network.run(duration)

(The first line with the collect will basically do what the run(...) call did previously, i.e. collect everything “visible” in the current scope, in your case the two monitors.)
Another option would be to create and store the Network object as part of the class, or have a general .objects attribute containing all the objects that need to be added to the network so that the main function is more independent of the internals of your class.

Hope that gets you going!

PS: I edited your post to display the code more nicely. You can use triple backticks like this for code blocks:

```
# Python code
print("Hello, world!)
```

PPS: A very minor thing, but you can use a single StateMonitor for both variables:

M = StateMonitor(net.G, ['v', 'g'], record=True)

@mstimberg thanks for your comments!
Even in the Case I put all in a flatt script in main()
I found the code

    nlayers = 2
    self.clocks = []
    self.clock_synapses = []
    for l in range(nlayers):
        spike_times = np.array([l*30+2])*ms
        indices = np.array([0]*len(spike_times))
        clock = SpikeGeneratorGroup(1, indices, spike_times)
        self.clocks.append(clock)
        clock_synapse = Synapses(clock, self.G, on_pre='g =' + str((l+1)*2))
        clock_synapse.connect()
        self.clock_synapses.append(clock_synapse)

does not work correctly. Clocks emit only 1 spike at 32ms. No Spike is emitted by the 1st Spike time at 2ms !
Why?

Thanks

Hi again. This is the issue that is briefly discussed in the link I sent earlier : the run call does not “see” the objects that are stored in containers like lists, it only automatically collects objects that are directly stored in variables in the current scope. After the loop, the clock and clock_synapse variables contain the values from the second iteration and are automatically included in the network. The objects from the first iteration are only in self.clocks and self.clock_synapses, and Brian will therefore ignore them.

@mstimberg thank you very much. I understood this concept.

1 Like