Where is TimedArray in the Network.objects?

A quick question. When I create a Network object whose NeuronGroup elements use in their equations some TimedArrays, where are these latter, stored within the Network attributes/methods?

Consider, for example, the following sample code:

# Brian module
import os
from brian2 import *

code_dir = os.path.abspath('./code_testing')  # It is better to provide always the absolute path
set_device('cpp_standalone',directory=code_dir,build_on_run=False)
device.delete(force=True)  # Clean codegen directory for safety
defaultclock.dt = 5e-4*second

# Sample code
N_neurons = 10
duration = 1.0*second
pars = { 'vl': 0.0,
            'vr': 10.0,
            'vt': 20.0,
            'taum': 20. * second,
            'trpn': 2. * second,
            'ix': 20.0,
            'sx': 2.0,
            'ICs': 10.*np.ones(N_neurons),
            'ratet': 5000.0}

# Generate TimedArray
ta = np.tile(0.5 * np.arange(0, N_neurons), (int(duration // (0.1 * second)), 1))
stimulus = TimedArray(ta, dt=0.1 * second,name='stim')

def neuron_simulation(N_neurons,pars,stimulus):
    # LIF with parametrized noise input
    eqs = Equations('''
                # LIF equation
                dv/dt = (vl-v + ix*stimulus(t,i))/taum + sx*stimulus(t,i)*xi/(taum**.5) : 1 (unless refractory)
                ix : 1
                sx : 1                        
                ''')
cells = NeuronGroup(N_neurons, eqs,
    threshold='v>=vt',
    reset='v+=vr-vt',
    refractory='trpn',
    method='euler',
    namespace=pars,
    name='Neu', order=0)

    cells.ix = pars['ix']
    cells.sx = pars['sx']

    l = [cells]
    return Network(l)

nnet = neuron_simulation(N_neurons,pars,stimulus=stimulus)

# Add Monitor
mon = StateMonitor(nnet['Neu'],variables=['v'],record=True,dt=1*ms,name='v_mon')
nnet.add(mon)

# Run
nnet.run(duration=duration,report='text')

# Build
device.build(directory=code_dir)

# Show
plt.plot(mon.t_,mon.v_.T)
plt.show()

– First, I tried to access nnet.objects or nnet['Neu'].contained_objects but I could not find my stim Timed Array. Where is it?
– Second, I would like to add a TimedArray or manipulate my stim one after creating nnet. Can I do so just by adding it to the network by nnet.add(<my_timedarray>)?
– Third, is there a way I could specify my timed array stimulus to my NeuronGroup after creating it (rather than passing it as an input argument as it is now), hoping that building everything at the end, Brian is sufficiently smart to resolve dependencies? In this case, do I need to define my TimedArray always in the global scope w.r.t. the method defining my NeuronGroup object (in my case neuron_simulation)?

Thanks.

Hi. The TimedArray is not an object that gets stored in the same way as NeuronGroup, etc., instead it is a Function. You will therefore not find it anywhere stored explicitly, it is resolved using the namespace mechanism. This means that if you do not use explicit namespace arguments for your objects or your run call, it will search for a function with the name “stimulus” at the point of the run call. In your example above, providing the stimulus argument to the neuron_simulation function does not have any effect, it will look for a function (or TimedArray) called stimulus where you call nnet.run

I am not sure how you would “add” a TimedArray, since your equations already have to refer to it. But as I said above, you can certainly create your TimedArray after the NeuronGroup that refers to it. All that matters is that the TimedArray is available at the point of the run call.

You could even switch out the TimedArray between two runs, but note that this does not work in standalone mode if you give it a name via name='stim' as in your current code. I am not sure whether this is a bug or a feature, but names have to be globally unique in standalone mode, so if you force two different TimedArrays to use the same name, the second one will overwrite the first. Without this forced name, changing things will work. E.g. after removing the stimulus argument from your neuron_simulation function, you can do:

# Generate TimedArray
ta = np.tile(0.5 * np.arange(0, N_neurons), (int(duration // (0.1 * second)), 1))
stimulus = TimedArray(ta, dt=0.1 * second)
# Run
nnet.run(duration=duration,report='text')
# Make the stimulus weaker
stimulus = TimedArray(ta*0.1, dt=0.1 * second)
nnet.run(duration=duration,report='text')

And you will get
run

But don’t overuse this, as you know standalone mode is not efficient with multiple runs, it needs to compile all the code again for each run.

1 Like

Thanks, @mstimberg. This solves pretty much my doubts. The fact that the TimedArray must be made available before the run instance is ok. The fact that it is in the namespace of that object is not an issue after, since I can always assign it once I created the object, e.g.

 nnet = neuron_simulation(N_neurons,pars,stimulus=stimulus)
 nnet['Neu'].namespace['stimulus'] = TimedArray(ta, dt=0.1 * second,name='stim') 

This option is not ideal, but it works in my case, where nnet definition is in a class method that is different from that where information on the specific TimedArray stimulus is available.

1 Like