Using equations encapsulated in class objects

Description of problem

Hi Brian folk,

I’m working on a class project that utilizes Izhikevich neurons connected by alpha synapses in Brian2. The package works great, but I have a separate script that generates groups of these neurons in a class initializer (e.g. a Bug class that has attributes in the form of neuron groups known as LeftLeg and RightLeg).

I’ve been trying to run a simulation with this design but am running into issues with the Network running in the background. I tried passing in the objects containing the equations to the Network (e.g. Animal1.LeftEye) but this didn’t work. The Network simply can’t see the equations defined.

Is there a way I can get the scope to see these equations?

Minimal code to reproduce problem

My code can be found here, and I am simply trying to create instances of bugs in another script.

What you have aready tried

Above code, and creating the objects in the same script in which the classes are defined

Expected output (if relevant)

Actual output (if relevant)

Full traceback of error (if relevant)

Hi @orbitalhybrid When you go away from the “flat script” structure, you need to manually add all objects to a Network object (see e.g. Running a simulation — Brian 2 2.5.1 documentation). In general, this object is the only thing that you’d need to store, since you can access all objects stored in the Network object like in a dictionary, using their name. Note that this is much more convenient, if you set explicit names for the objects, since otherwise they have default names such as neurongroup, neurongroup_1, etc.:

    group = NeuronGroup(..., name='my_name')
    group2 = NeuronGroup(..., name='other_name')
    self.network = Network(group, group2)
    self.network['my_name'].v = ...   # same effect as group.v = ...

If you prefer, you can of course also keep storing the NeuronGroup objects as attributes of the class in addition to adding them to the Network object. The only thing that is important is that all objects that are part of your simulation (neurons, synapses, monitors, …) have been added to the Network at some point.
You’ll then need to call run on the network object (e.g. self.network.run).

Note that there is a second change that is most likely necessary: the run/Network.run command needs to “see” the external constants that are used in the equations. E.g. the following works:

tau = 10*ms
group = NeuronGroup(1, 'dv/dt = -v/tau : 1')
net = Network(group)
net.run(100*ms)  #  ← can "see" tau

but the following will fail, complaining that it does not know “tau”:

class MySim:
    def __init__(self):
        self.tau = 10*ms
        group = NeuronGroup(1, 'dv/dt = -v/tau : 1')
        self.net = Network(group)

sim = MySim()
sim.net.run(100*ms)  # ← does not "see" tau

One solution is to use the namespace argument, that can be used to explicitly pass a dictionary with the external parameters (Namespaces — Brian 2 2.5.1 documentation). If you already store all constants in attributes of your object, the easiest would be to use it like this:

sim = MySim()
sim.net.run(100*ms, namespace=vars(sim))

Hope that helps!

@mstimberg just out of curiosity, can @orbitalhybrid’s class be a derived class from the Network? For example,

class Bug(Network):
   .....
   def add_brain(self):
        self.neurons = NeuronGroup( .... )
        self.synapses = .....
        self.add(self.neurons, self.synapses)

bug_population = [ Bug() for _ in range(1000) ]
net = Network(bug_population)

Hi @rth. Yes and no: you can derive a class from Network and use this class instead of the original network (even though I would rather store the network in an attribute: Composition over inheritance - Wikipedia), but you cannot combine several networks into a single network. Also, having thousands of NeuronGroup/Synapses objects to represent a population would be rather inefficient. If possible, you should instead use groups of neurons that represent several individuals at once.

1 Like