| [2945] | 1 | """ |
|---|
| 2 | Spike queues following BEP-21. |
|---|
| 3 | """ |
|---|
| 4 | from brian import * # remove this |
|---|
| 5 | from brian.stdunits import ms |
|---|
| [2959] | 6 | from brian.globalprefs import * |
|---|
| 7 | from scipy import weave |
|---|
| [2945] | 8 | |
|---|
| 9 | INITIAL_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 | |
|---|
| 14 | class 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 | |
|---|
| 265 | if __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 |
|---|