root/trunk/brian/experimental/synapses/spikequeue.py @ 2959

Revision 2959, 11.5 KB (checked in by romainbrette, 16 months ago)

Docstrings in SpikeQueue?

RevLine 
[2945]1"""
2Spike queues following BEP-21.
3"""
4from brian import * # remove this
5from brian.stdunits import ms
[2959]6from brian.globalprefs import *
7from scipy import weave
[2945]8
9INITIAL_MAXSPIKESPER_DT = 1 # I guess it could be larger no?
10# This is a 2D circular array, but also a SpikeMonitor
11
12__all__=['SpikeQueue']
13
14class SpikeQueue(SpikeMonitor):
[2959]15    '''Spike queue
[2945]16   
[2959]17    Initialised with arguments:
[2945]18
[2959]19    ``source''
20        The neuron group that sends spikes.
21    ``synapses''
22        A list of synapses (synapses[i]=array of synapse indexes for neuron i).
23    ``delays''
24        An array of delays (delays[k]=delay of synapse k). 
25    ``max_delay=0*ms''
26        The maximum delay (in second) of synaptic events. At run time, the
27        structure is resized to the maximum delay in ``delays'', and thus
28        the ``max_delay'' should only be specified if delays can change
29        during the simulation (in which case offsets should not be
30        precomputed).
31    ``maxevents = INITIAL_MAXSPIKESPER_DT''
32        The initial size of the queue for each timestep. Note that the data
33        structure automatically grows to the required size, and therefore this
34        option is generally not useful.
35    ``precompute_offsets = True''
36        A flag to precompute offsets. By default, offsets (an internal array
37        derived from ``delays'', used to insert events in the data structure)
38        are precomputed for all neurons, the first time the object is run.
39        This usually results in a speed up but takes memory, which is why it
40        can be disabled.
[2945]41
[2959]42    **Data structure**
[2945]43   
[2959]44    A spike queue is implemented as a 2D array, that is circular in the time
45    direction (rows) and dynamic in the events direction (columns).
[2945]46   
47    * At the beginning or end of each timestep: queue.next()
48    * To get all spikes: events=queue.peek()
49      It returns the indexes of all synapses receiving an event.
50    * When a presynaptic spike is emitted, the following is executed:
[2959]51      queue.insert(delay,target,offset)
[2945]52      where delay is the array of synaptic delays of targets in timesteps,
53      offset is the array of offsets within each timestep,
54      target is the array of synapse indexes of targets.
55      The offset is used to solve the problem of multiple synapses with the
56      same delay. For example, if there are two target synapses 7 and 9 with delay
57      2 timesteps: queue.insert([2,2],[0,1],[7,9])
58   
59    Thus, offsets are determined by delays. They could be either precalculated
60    (faster), or determined at run time (saves memory). Note that if they
[2959]61    are determined at run time, then it is possible to also vectorise over
[2945]62    presynaptic spikes.
63   
[2959]64    The class is implemented as a SpikeMonitor, so that the propagate() method
65    is called at each timestep (of the monitored group).
[2945]66    '''
67    def __init__(self, source, synapses, delays,
68                 max_delay = 0*ms, maxevents = INITIAL_MAXSPIKESPER_DT,
69                 precompute_offsets = True):
70        # SpikeMonitor structure
71        self.source = source #NeuronGroup
72        self.delays = delays
73        self.synapses = synapses
74        self._precompute_offsets=precompute_offsets
75       
76        # number of time steps, maximum number of spikes per time step
77        nsteps = int(np.floor((max_delay)/(self.source.clock.dt)))+1
78        self.X = zeros((nsteps, maxevents), dtype = self.synapses[0].dtype) # target synapses
79        self.X_flat = self.X.reshape(nsteps*maxevents,)
80        self.currenttime = 0
81        self.n = zeros(nsteps, dtype = int) # number of events in each time step
82       
83        self._offsets = None # precalculated offsets
84       
[2959]85        # Compiled version
86        self._useweave = get_global_preference('useweave')
87        self._cpp_compiler = get_global_preference('weavecompiler')
88        self._extra_compile_args = ['-O3']
89        if self._cpp_compiler == 'gcc':
90            self._extra_compile_args += get_global_preference('gcc_options') # ['-march=native', '-ffast-math']
91
[2945]92        super(SpikeQueue, self).__init__(source, 
93                                         record = False)
94       
95        #useweave=get_global_preference('useweave')
96        #compiler=get_global_preference('weavecompiler')
97
98    def compress(self):
99        '''
100        Prepare the structure:
101        * calculate maximum delay
102        * calculate offsets
103        * check if delays are homogeneous
104        '''
105        # Adjust the maximum delay and number of events per timestep if necessary
106        nsteps=max(self.delays)+1
107        maxevents=self.X.shape[1]
108        if maxevents==INITIAL_MAXSPIKESPER_DT:
109            maxevents=max(INITIAL_MAXSPIKESPER_DT,max([len(targets) for targets in self.synapses]))
110        # Check if homogeneous delays
111        if (nsteps>self.X.shape[0]): 
112            self._homogeneous=(nsteps==min(self.delays)+1)
113        else: # this means that the user has set a larger delay than necessary, which means the delays are not fixed
114            self._homogeneous=False
115        if (nsteps>self.X.shape[0]) or (maxevents>self.X.shape[1]):
116            self.X = zeros((nsteps, maxevents), dtype = self.synapses[0].dtype) # target synapses
117            self.X_flat = self.X.reshape(nsteps*maxevents,)
118            self.n = zeros(nsteps, dtype = int) # number of events in each time step
119
120        # Precompute offsets
121        if (self._offsets is None) and self._precompute_offsets:
122            self.precompute_offsets()
123
124    ################################ SPIKE QUEUE DATASTRUCTURE ######################
125    def next(self):
126        # Advance by one timestep
127        self.n[self.currenttime]=0 # erase
128        self.currenttime=(self.currenttime+1) % len(self.n)
129       
130    def peek(self):
131        # Events in the current timestep     
132        return self.X[self.currenttime,:self.n[self.currenttime]]
133   
134    def precompute_offsets(self):
135        #t0 = time.time()
136        self._offsets=[]
137        for i in range(len(self.synapses)):
138            delays=self.delays[self.synapses[i].data]
139            self._offsets.append(self.offsets(delays))
140        #log_debug('spikequeue.offsets', 'Offsets computed in '+str(time.time()-t0))
141   
142    def offsets(self, delay):
143        '''
144        Calculates offsets corresponding to a delay array
145        '''
146        # We use merge sort because it preserves the input order of equal
147        # elements in the sorted output
148        I = argsort(delay,kind='mergesort')
149        xs = delay[I]
150        J = xs[1:]!=xs[:-1]
151        #K = xs[1:]==xs[:-1]
152        A = hstack((0, cumsum(J)))
153        #B = hstack((0, cumsum(K)))
154        B = hstack((0, cumsum(-J)))
155        BJ = hstack((0, B[J]))
156        ei = B-BJ[A]
157        ofs = zeros_like(delay)
158        ofs[I] = array(ei,dtype=ofs.dtype) # maybe types should be signed?
159        return ofs
160       
161    def insert(self, delay, target, offset=None):
[2959]162        '''
163        Vectorized insertion of spike events
[2945]164        # delay = delay in timesteps
[2959]165        # target = target synaptic index
[2945]166        # offset = offset within timestep
[2959]167        '''
[2945]168       
169        if offset is None:
170            offset=self.offsets(delay)
171       
172        timesteps = (self.currenttime + delay) % len(self.n)
173               
174        # Compute new stack sizes:
175        old_nevents = self.n[timesteps].copy() # because we need this for the final assignment, but we need to precompute the  new one to check for overflow
176        self.n[timesteps] += offset+1 # that's a trick (to update stack size), plus we pre-compute it to check for overflow
177        # Note: the trick can only work if offsets are ordered in the right way
178       
179        m = max(self.n[timesteps])+1 # If overflow, then at least one self.n is bigger than the size
180        if (m >= self.X.shape[1]):
181            self.resize(m+1) # was m previously (not enough)
182       
183        self.X_flat[timesteps*self.X.shape[1]+offset+old_nevents]=target
184        # Old code seemed wrong:
185        #self.X_flat[(self.currenttime*self.X.shape[1]+offset+\
186        #             old_nevents)\
187        #             % len(self.X)]=target
188       
[2959]189    def insert_C(self,delay,target):
190        '''
191        Insertion of events using weave
192        # delay = delay in timesteps
193        # target = target synaptic index
194       
195        UNFINISHED
196        Difficult bit: check whether we need to resize
197        '''
198        nevents=len(target)
199        code='''
200        for(int i=0;i<nevents;i++) {
201            ();
202        }
203        '''
204        weave.inline(code, ['nevents'], \
205             compiler=self._cpp_compiler,
206             type_converters=weave.converters.blitz,
207             extra_compile_args=self._extra_compile_args)
208       
[2945]209    def insert_homogeneous(self,delay,target):
210        '''
211        Inserts events at a fixed delay
212        '''
213        timestep = (self.currenttime + delay) % len(self.n)
214        nevents=len(target)
215        m = max(self.n[timestep])+nevents+1 # If overflow, then at least one self.n is bigger than the size
216        if (m >= self.X.shape[1]):
217            self.resize(m+1) # was m previously (not enough)
218        k=timestep*self.X.shape[1]+self.n[timestep]
219        self.X_flat[k:k+nevents]=target
220        self.n[timestep]+=nevents
221       
222    def resize(self, maxevents):
223        '''
224        Resizes the underlying data structure (number of columns = spikes per dt).
225        max events will be rounded to the closest power of 2.
226        '''
227        # old and new sizes
228        old_maxevents = self.X.shape[1]
229        new_maxevents = 2**ceil(log2(maxevents)) # maybe 2 is too large
230        # new array
231        newX = zeros((self.X.shape[0], new_maxevents), dtype = self.X.dtype)
232        newX[:, :old_maxevents] = self.X[:, :old_maxevents] # copy old data
233       
234        self.X = newX
235        self.X_flat = self.X.reshape(self.X.shape[0]*new_maxevents,)
236        #log_debug('spikequeue', 'Resizing SpikeQueue')
237       
238    def propagate(self, spikes):
239        if len(spikes):
240            if self._homogeneous: # homogeneous delays
241                synaptic_events=hstack([self.synapses[i].data for i in spikes]) # could be not efficient
242                self.insert_homogeneous(self.delays[0],synaptic_events)
243            elif self._offsets is None: # vectorise over synaptic events
244                synaptic_events=hstack([self.synapses[i].data for i in spikes])
245                if len(synaptic_events):
246                    delay = self.delays[synaptic_events]
247                    self.insert(delay, synaptic_events)
248            else: # offsets are precomputed
249                for i in spikes:
250                    synaptic_events=self.synapses[i].data # assuming a dynamic array: could change at run time?   
251                    if len(synaptic_events):
252                        delay = self.delays[synaptic_events]
253                        offsets = self._offsets[i]
254                        self.insert(delay, synaptic_events, offsets)
255
256    ######################################## UTILS   
257    def plot(self, display = True):
258        for i in range(self.X.shape[0]):
259            idx = (i + self.currenttime ) % self.X.shape[0]
260            data = self.X[idx, :self.n[idx]]
261            plot(idx * ones(len(data)), data, '.')
262        if display:
263            show()
264
265if __name__=='__main__':
266    from synapses import *
267    P=NeuronGroup(1,model='v:1')
268    S=Synapses(P,model='w:1')
269    queue=S.pre_queue
270    #delays=array([4,2,2,1,6,2,5,9,6,9],dtype=int)
271    s="9 6 6 5 1 7 8 2 6 0 9 6 8 3 6 6 1 1 2 6 6 8 6 4 4 1 4 9 4 7 1 3 4 4 8 4 7\
272 1 3 0 4 4 2 5 7 2 5 6 0 6 8 5 7 1 7 0 9 2 1 9 5 9 4 3 5 7 2 5 8 8 7 9 9 8\
273 8 9 1 5 8 3 7 8 4 3 7 4 7 6 2 5 5 3 8 6 1 2 7 5 9 7".split()
274    delays=array([int(x) for x in s])
275    offsets=queue.offsets(delays)
276    n=zeros(max(delays)+1,dtype=int)
277    print offsets
278    n[delays]+=offsets+1
279    print n
Note: See TracBrowser for help on using the browser.