Exemple #1
0
    def generate_code(self,code,level,direct=False,code_namespace=None):
        '''
        Generates pre and post code.
        
        ``code''
            The code as a string.
            
        ``level''
            The namespace level in which the code is executed.
        
        ``direct=False''
            If True, the code is generated assuming that
            postsynaptic variables are not modified. This makes the
            code faster.
            
        ``code_namespace''
            Additional namespace (highest priority)
        
        TODO:
        * include static variables (substitution)
        * have a list of variable names
        '''
        # Handle multi-line pre, post equations and multi-statement equations separated by ;
        # (this should probably be factored)
        if '\n' in code:
            code = flattened_docstring(code)
        elif ';' in code:
            code = '\n'.join([line.strip() for line in code.split(';')])
        
        # Create namespaces
        _namespace = namespace(code, level = level + 1)
        if code_namespace is not None:
            _namespace.update(code_namespace)
        _namespace['target'] = self.target # maybe we could save one indirection here
        _namespace['unique'] = np.unique
        _namespace['nonzero'] = np.nonzero
        _namespace['empty'] = np.empty
        _namespace['logical_not'] = np.logical_not
        _namespace['not_equal'] = np.not_equal
        _namespace['take'] = np.take
        _namespace['extract'] = np.extract
        _namespace['add'] = np.add
        _namespace['hstack'] = np.hstack

        code = re.sub(r'\b' + 'rand\(\)', 'rand(n)', code)
        code = re.sub(r'\b' + 'randn\(\)', 'randn(n)', code)

        # Generate the code
        def update_code(code, indices, postinds):
            res = code
            # given the synapse indices, write the update code,
            # this is here because in the code we generate we need to write this twice (because of the multiple presyn spikes for the same postsyn neuron problem)
                       
            # Replace synaptic variables by their value
            for var in self.var_index: # static variables are not included here
                if isinstance(var, str):
                    res = re.sub(r'\b' + var + r'\b', var + '['+indices+']', res) # synaptic variable, indexed by the synapse number
 
            # Replace postsynaptic variables by their value
            for postsyn_var in self.target.var_index: # static variables are not included here
                if isinstance(postsyn_var, str):
                    #res = re.sub(r'\b' + postsyn_var + r'_post\b', 'target.' + postsyn_var + '['+postinds+']', res)# postsyn variable, indexed by post syn neuron numbers
                    #res = re.sub(r'\b' + postsyn_var + r'\b', 'target.' + postsyn_var + '['+postinds+']', res)# postsyn variable, indexed by post syn neuron numbers
                    res = re.sub(r'\b' + postsyn_var + r'_post\b', '_target_' + postsyn_var + '['+postinds+']', res)# postsyn variable, indexed by post syn neuron numbers
                    res = re.sub(r'\b' + postsyn_var + r'\b', '_target_' + postsyn_var + '['+postinds+']', res)# postsyn variable, indexed by post syn neuron numbers
                    _namespace['_target_' + postsyn_var] = self.target.state_(postsyn_var)
            
            # Replace presynaptic variables by their value
            for presyn_var in self.source.var_index: # static variables are not included here
                if isinstance(presyn_var, str):
                    #res = re.sub(r'\b' + presyn_var + r'_pre\b', 'source.' + presyn_var + '[_pre['+indices+']]', res)# postsyn variable, indexed by post syn neuron numbers
                    res = re.sub(r'\b' + presyn_var + r'_pre\b', '_source_' + presyn_var + '[_pre['+indices+']]', res)# postsyn variable, indexed by post syn neuron numbers
                    _namespace['_source_' + presyn_var] = self.source.state_(presyn_var)
 
            # Replace n by number of synapses being updated
            res = re.sub(r'\bn\b','len('+indices+')', res)
 
            return res
 
        if direct: # direct update code, not caring about multiple accesses to postsynaptic variables
            code_str = '_post_neurons = _post[_synapses]\n'+update_code(code, '_synapses', '_post_neurons') + "\n"            
        else:
            algo = 3
            if algo==0:
                ## Old version using numpy's unique()
                code_str = "_post_neurons = _post[_synapses]\n" # not necessary to do a copy because _synapses is not a slice
                code_str += "_u, _i = unique(_post_neurons, return_index = True)\n"
                #code_str += update_code(code, '_synapses[_i]', '_u') + "\n"
                code_str += update_code(code, '_synapses[_i]', '_post[_synapses[_i]]') + "\n"
                code_str += "if len(_u) < len(_post_neurons):\n"
                code_str += "    _post_neurons[_i] = -1\n"
                code_str += "    while (len(_u) < len(_post_neurons)) & (_post_neurons>-1).any():\n" # !! the any() is time consuming (len(u)>=1??)
                #code_str += "    while (len(_u) < len(_post_neurons)) & (len(_u)>1):\n" # !! the any() is time consuming (len(u)>=1??)
                code_str += "        _u, _i = unique(_post_neurons, return_index = True)\n"
                code_str += indent(update_code(code, '_synapses[_i[1:]]', '_post[_synapses[_i[1:]]]'),2) + "\n"
                code_str += "        _post_neurons[_i[1:]] = -1 \n"
            elif algo==1:
                code_str = "_post_neurons = _post[_synapses]\n" # not necessary to do a copy because _synapses is not a slice
                code_str += "_perm = _post_neurons.argsort()\n"
                code_str += "_aux = _post_neurons[_perm]\n"
                code_str += "_flag = empty(len(_aux) + 1, dtype = bool)\n"
                code_str += "_flag[0] = _flag[-1] = True\n"
                code_str += "not_equal(_aux[1:], _aux[:-1], _flag[1:-1])\n"
                code_str += "_F = _flag.nonzero()[0][:-1]\n"
                code_str += "logical_not(_flag, _flag)\n"
                code_str += "while len(_F):\n"
                code_str += "    _u = _aux[_F]\n"
                code_str += "    _i = _perm[_F]\n"
                code_str += indent(update_code(code, '_synapses[_i]', '_u'), 1) + "\n"
                code_str += "    _F += 1\n"
                code_str += "    _F = _F[_flag[_F]]\n"
            elif algo==2:
                code_str = '''
                _post_neurons = _post.data.take(_synapses)
                _perm = _post_neurons.argsort()
                _aux = _post_neurons.take(_perm)
                _flag = empty(len(_aux)+1, dtype=bool)
                _flag[0] = _flag[-1] = 1
                not_equal(_aux[1:], _aux[:-1], _flag[1:-1])
                if 0:#_flag.sum()==len(_aux)+1:
                %(code1)s
                else:
                    _F = _flag.nonzero()[0][:-1]
                    logical_not(_flag, _flag)
                    while len(_F):
                        _u = _aux.take(_F)
                        _i = _perm.take(_F)
                %(code2)s
                        _F += 1
                        _F = extract(_flag.take(_F), _F)
                '''
                code_str = flattened_docstring(code_str) % {'code1': indent(update_code(code, '_synapses','_post_neurons'), 1),
                                                            'code2': indent(update_code(code, '_synapses[_i]', '_u'), 2)}
            elif algo==3:
                code_str = '''
                _post_neurons = _post.data.take(_synapses)
                _perm = _post_neurons.argsort()
                _aux = _post_neurons.take(_perm)
                _flag = empty(len(_aux)+1, dtype=bool)
                _flag[0] = _flag[-1] = 1
                not_equal(_aux[1:], _aux[:-1], _flag[1:-1])
                _F = _flag.nonzero()[0][:-1]
                logical_not(_flag, _flag)
                while len(_F):
                    _u = _aux.take(_F)
                    _i = _perm.take(_F)
                %(code)s
                    _F += 1
                    _F = extract(_flag.take(_F), _F)
                '''
                code_str = flattened_docstring(code_str) % {'code': indent(update_code(code, '_synapses[_i]', '_u'), 1)}
            elif algo==4:
                code_str = '''
                _post_neurons = _post[_synapses]
                _perm = _post_neurons.argsort()
                _aux = _post_neurons[_perm]
                _flag = empty(len(_aux)+1, dtype=bool)
                _flag[0] = _flag[-1] = 1
                not_equal(_aux[1:], _aux[:-1], _flag[1:-1])
                _F = _flag.nonzero()[0][:-1]
                logical_not(_flag, _flag)
                while len(_F):
                    _u = _aux[_F]
                    _i = _perm[_F]
                %(code)s
                    _F += 1
                    _F = _F[_flag[_F]]
                '''
                code_str = flattened_docstring(code_str) % {'code': indent(update_code(code, '_synapses[_i]', '_u'), 1)}
#        print code_str
            
        log_debug('brian.synapses', '\nCODE:\n'+code_str)
        
        # Compile
        compiled_code = compile(code_str, "Synaptic code", "exec")
        
        _namespace['_original_code_string'] = code_str
        
        return compiled_code,_namespace
Exemple #2
0
    def __init__(self, source, target = None, model = None, pre = None, post = None,
             max_delay = 0*ms,
             level = 0,
             clock = None,code_namespace=None,
             unit_checking = True, method = None, freeze = False, implicit = False, order = 1): # model (state updater) related
        
        target=target or source # default is target=source

        # Check clocks. For the moment we enforce the same clocks for all objects
        clock = clock or source.clock
        if source.clock!=target.clock:
            raise ValueError,"Source and target groups must have the same clock"

        if pre is None:
            pre_list=[]
        elif isSequenceType(pre) and not isinstance(pre,str): # a list of pre codes
            pre_list=pre
        else:
            pre_list=[pre]

        pre_list=[flattened_docstring(pre) for pre in pre_list]
        if post is not None:
            post=flattened_docstring(post)

        # Pre and postsynaptic indexes (synapse -> pre/post)
        self.presynaptic=DynamicArray1D(0,dtype=smallest_inttype(len(source))) # this should depend on number of neurons
        self.postsynaptic=DynamicArray1D(0,dtype=smallest_inttype(len(target))) # this should depend on number of neurons

        if not isinstance(model,SynapticEquations):
            model=SynapticEquations(model,level=level+1)
        # Insert the lastupdate variable if necessary (if it is mentioned in pre/post, or if there is event-driven code)
        expr=re.compile(r'\blastupdate\b')
        if (len(model._eventdriven)>0) or \
           any([expr.search(pre) for pre in pre_list]) or \
           (post is not None and expr.search(post) is not None):
            model+='\nlastupdate : second\n'
            pre_list=[pre+'\nlastupdate=t\n' for pre in pre_list]
            if post is not None:
                post=post+'\nlastupdate=t\n'
        
        # Identify pre and post variables in the model string
        # They are identified by _pre and _post suffixes
        # or no suffix for postsynaptic variables
        ids=set()
        for RHS in model._string.itervalues():
            ids.update(get_identifiers(RHS))
        pre_ids = [id[:-4] for id in ids if id[-4:]=='_pre']
        post_ids = [id[:-5] for id in ids if id[-5:]=='_post']
        post_vars = [var for var in source.var_index if isinstance(var,str)] # postsynaptic variables
        post_ids2 = list(ids.intersection(set(post_vars))) # post variables without the _post suffix

        # remember whether our equations refer to any variables in the pre- or
        # postsynaptic group. This is important for the state-updater, e.g. the
        # equations can no longer be solved as linear equations.
        model.refers_others = (len(pre_ids) + len(post_ids) + len(post_ids2) > 0)

        # Insert static equations for pre and post variables
        S=self
        for name in pre_ids:
            model.add_eq(name+'_pre', 'S.source.'+name+'[S.presynaptic[:]]', source.unit(name),
                         global_namespace={'S':S})
        for name in post_ids:
            model.add_eq(name+'_post', 'S.target.'+name+'[S.postsynaptic[:]]', target.unit(name),
                         global_namespace={'S':S})
        for name in post_ids2: # we have to change the name of the variable to avoid problems with equation processing
            if name not in model._string: # check that it is not already defined
                model.add_eq(name, 'S.target.state_(__'+name+')[S.postsynaptic[:]]', target.unit(name),
                             global_namespace={'S':S,'__'+name:name})

        self.source=source
        self.target=target
        
        NeuronGroup.__init__(self, 0,model=model,clock=clock,level=level+1,unit_checking=unit_checking,method=method,freeze=freeze,implicit=implicit,order=order)
        '''
        At this point we have:
        * a state matrix _S with all variables
        * units, state dictionary with each value being a row of _S + the static equations
        * subgroups of synapses
        * link_var (i.e. we can link two synapses objects)
        * __len__
        * __setattr__: we can write S.w=array of values
        * var_index is a dictionary from names to row index in _S
        * num_states()
        
        Things we have that we don't want:
        * LS structure (but it will not be filled since the object does not spike)
        * (from Group) __getattr_ needs to be rewritten
        * a complete state updater, but we need to extract parameters and event-driven parts
        * The state matrix is not dynamic
        
        Things we may need to add:
        * _pre and _post suffixes
        '''       
        self._iscompressed=False # True if compress() has already been called
        
        # Look for event-driven code in the differential equations
        if use_sympy:
            eqs=self._eqs # an Equations object
            #vars=eqs._diffeq_names_nonzero # Dynamic variables
            vars=eqs._eventdriven.keys()
            var_set=set(vars)
            for var,RHS in eqs._eventdriven.iteritems():
                ids=get_identifiers(RHS)
                if len(set(list(ids)+[var]).intersection(var_set))==1:
                    # no external dynamic variable
                    # Now we test if it is a linear equation
                    _namespace=dict.fromkeys(ids,1.) # there is a possibility of problems here (division by zero)
                    # plus units problems? (maybe not since these are identifiers too)
                    # another option is to use random numbers, but that doesn't solve all problems
                    _namespace[var]=AffineFunction()
                    try:
                        eval(RHS,eqs._namespace[var],_namespace)
                    except: # not linear
                        raise TypeError,"Cannot turn equation for "+var+" into event-driven code"
                    z=symbolic_eval(RHS)
                    symbol_var=sympy.Symbol(var)
                    symbol_t=sympy.Symbol('t')-sympy.Symbol('lastupdate')
                    b=z.subs(symbol_var,0)
                    a=sympy.simplify(z.subs(symbol_var,1)-b)
                    if a==0:
                        expr=symbol_var+b*symbol_t
                    else:
                        expr=-b/a+sympy.exp(a*symbol_t)*(symbol_var+b/a)
                    expr=var+'='+str(expr)
                    # Replace pre and post code
                    # N.B.: the differential equations are kept, we will probably want to remove them!
                    pre_list=[expr+'\n'+pre for pre in pre_list]
                    if post is not None:
                        post=expr+'\n'+post
                else:
                    raise TypeError,"Cannot turn equation for "+var+" into event-driven code"
        elif len(self._eqs._eventdriven)>0:
            raise TypeError,"The Sympy package must be installed to produce event-driven code"

        if len(self._eqs._diffeq_names_nonzero)==0:
            self._state_updater=None
        
        # Set last spike to -infinity
        if 'lastupdate' in self.var_index:
            self.lastupdate=-1e6
        # _S is turned to a dynamic array - OK this is probably not good! we may lose references at this point
        S=self._S
        self._S=DynamicArray(S.shape)
        self._S[:]=S

        # Pre and postsynaptic delays (synapse -> delay_pre/delay_post)
        self._delay_pre=[DynamicArray1D(len(self),dtype=np.int16) for _ in pre_list] # max 32767 delays
        self._delay_post=DynamicArray1D(len(self),dtype=np.int16) # Actually only useful if there is a post code!
        
        # Pre and postsynaptic synapses (i->synapse indexes)
        max_synapses=2147483647 # it could be explicitly reduced by a keyword
        # We use a loop instead of *, otherwise only 1 dynamic array is created
        self.synapses_pre=[DynamicArray1D(0,dtype=smallest_inttype(max_synapses)) for _ in range(len(self.source))]
        self.synapses_post=[DynamicArray1D(0,dtype=smallest_inttype(max_synapses)) for _ in range(len(self.target))]

        # Code generation
        self._binomial = lambda n,p:np.random.binomial(np.array(n,dtype=int),p)

        self.contained_objects = []
        self.codes=[]
        self.namespaces=[]
        self.queues=[]
        for i,pre in enumerate(pre_list):
            code,_namespace=self.generate_code(pre,level+1,code_namespace=code_namespace)
            self.codes.append(code)
            self.namespaces.append(_namespace)
            self.queues.append(SpikeQueue(self.source, self.synapses_pre, self._delay_pre[i], max_delay = max_delay))
        
        if post is not None:
            code,_namespace=self.generate_code(post,level+1,direct=True,code_namespace=code_namespace)
            self.codes.append(code)
            self.namespaces.append(_namespace)
            self.queues.append(SpikeQueue(self.target, self.synapses_post, self._delay_post, max_delay = max_delay))

        self.queues_namespaces_codes = zip(self.queues,
                                           self.namespaces,
                                           self.codes)

        self.contained_objects+=self.queues
Exemple #3
0
    def __init__(self, source, target = None, 
                 clock = None,
                 max_delay = 0,
                 level = 0, model = None,
                 check_units = True, method = None, freeze = False, implicit = False, order = 1, # model (state updater) related
                 pre = '', post = ''):
        
        NetworkOperation.__init__(self, lambda:None, clock=clock)

        # Arguments parsing
        if target is None:
            self.source = self.target = source
        else:
            self.source = source
            self.target = target
        

        ########### CODE PARSING
        # model equations parsing
        if isinstance(model, Equations):
            model_obj = model
        else:
            if '\n' in model:
                model = flattened_docstring(model)
            elif ';' in model:
                model = '\n'.join([line.strip() for line in model.split(';')])
            model_obj = Equations(model, level = level + 1)
            
        # stolen from NeuronGroup
        # !! This will not work with postsynaptic variables in the model eqs !!
        if isinstance(model_obj, StateUpdater):
            self._state_updater = model_obj
            self._all_units = defaultdict() # what is that
        elif isinstance(model_obj, Equations):
            self._eqs = model_obj
            self._state_updater, var_names = magic_state_updater(model_obj, clock=clock, order=order,
                                                                 check_units=check_units, implicit=implicit,
                                                                 compile=compile, freeze=freeze,
                                                                 method=method)
            self._state_updater_varnames = var_names
            
            
        # TODO: 
        # - Maybe stateupdaters should be aware of the dtype of the data (or else my state vectors thing is sort of useless
        # - or ensure fast access to different data types in a vectorized way?
            
        # handle multi-line pre, post equations and multi-statement equations separated by ;
        if '\n' in pre:
            pre = flattened_docstring(pre)
        elif ';' in pre:
            pre = '\n'.join([line.strip() for line in pre.split(';')])
        if '\n' in post:
            post = flattened_docstring(post)
        elif ';' in post:
            post = '\n'.join([line.strip() for line in post.split(';')])

        # Check units
        model_obj.compile_functions()
        #model_obj.check_units()
        # Get variable names
        self.vars = model_obj._diffeq_names # !! there are also static variables and aliases !!
        

        ############# Setting up the data structure
        # Mandatory fields: 3 for pre/post/delay_pre, all int32 TODO: finer pick of dtype!
        
        # REPLACE THAT BY MORE SENSIBLE DATA STRUCTURE
        dtypes = (np.int32, ) * 3
        default_labels = ['_pre', '_post', 'delay']
        # Equation defined fields (float32)
        dtypes += (np.float32, ) * len(self.vars)

        # construction of the structure
        self._statevector = ConstructionSparseStateVector(len(dtypes), dtype = dtypes, labels = default_labels+self.vars)
        

        ############# Code!!!
        # create namespace
        pre_namespace = namespace(pre, level = level + 1)
        pre_namespace['target'] = self.target
        pre_namespace['unique'] = np.unique
        pre_namespace['nonzero'] = np.nonzero
        pre_namespace['_pre'] = self._statevector._pre
        pre_namespace['_post'] = self._statevector._post

        for var in self.vars:
            pre_namespace[var] = self._statevector[var]
        # !! also add postsynaptic variables !!

        def update_code(pre, indices):
            res = pre
            # given the synapse indices, write the update code,
            # this is here because in the code we generate we need to write this twice (because of the multiple presyn spikes for the same postsyn neuron problem)
            for postsyn_var in self.target.var_index:
                if isinstance(postsyn_var, str):
                    res = re.sub(r'\b' + postsyn_var + r'\b', 'target.' + postsyn_var + '[_post['+indices+']]', res)# postsyn variable, indexed by post syn neuron numbers
                
            for var in self.vars:
                res = re.sub(r'\b' + var + r'\b', var + '['+indices+']', res) # synaptic variable, indexed by the synapse number
            return res
 
        # pre code
        pre_code = "_post_neurons = _post[_synapses]\n"
        # which post syn neurons
        pre_code += "_u, _i = unique(_post_neurons, return_index = True)\n"
        pre_code += update_code(pre, '_synapses[_i]') + "\n"
        pre_code += "if len(_u) < len(_post_neurons):\n"
        pre_code += "    _post_neurons[_i] = -1\n"
        pre_code += "    while (len(_u) < len(_post_neurons)) & (_post_neurons>-1).any():\n" # !! the any() is time consuming (len(u)>=1??)
        pre_code += "        _u, _i = unique(_post_neurons, return_index = True)\n"
        pre_code += "        " + update_code(pre, '_synapses[_i[1:]]') + "\n"
        pre_code += "        _post_neurons[_i[1:]] = -1 \n"
        log_debug('brian.synapses', '\nPRE CODE:\n'+pre_code)
        
        pre_code = compile(pre_code, "Presynaptic code", "exec")
        
        
        self.pre_namespace = pre_namespace
        self.pre_code = pre_code
        self.pre_queue = SpikeQueue(self.source, self, max_delay = max_delay)

        self.contained_objects = [self.pre_queue] # wtf is this for: so that pre_queue.propagate is called
        
        
        self._pre_to_synapse = np.zeros(len(self.source), dtype = object)
        self._post_to_synapse = np.zeros(len(self.target), dtype = object)
Exemple #4
0
 def __init__(self, resolved, content, tabs=0):
     self.resolved = resolved
     if isinstance(content, str) and '\n' in content:
         content = [flattened_docstring(content)]
     self.content = content
     self.tabs = tabs
Exemple #5
0
 def __init__(self, resolved, content, tabs=0):
     self.resolved = resolved
     if isinstance(content, str) and '\n' in content:
         content = [flattened_docstring(content)]
     self.content = content
     self.tabs = tabs
Exemple #6
0
    def __init__(
            self,
            source,
            target=None,
            clock=None,
            max_delay=0,
            level=0,
            model=None,
            check_units=True,
            method=None,
            freeze=False,
            implicit=False,
            order=1,  # model (state updater) related
            pre='',
            post=''):

        NetworkOperation.__init__(self, lambda: None, clock=clock)

        # Arguments parsing
        if target is None:
            self.source = self.target = source
        else:
            self.source = source
            self.target = target

        ########### CODE PARSING
        # model equations parsing
        if isinstance(model, Equations):
            model_obj = model
        else:
            if '\n' in model:
                model = flattened_docstring(model)
            elif ';' in model:
                model = '\n'.join([line.strip() for line in model.split(';')])
            model_obj = Equations(model, level=level + 1)

        # stolen from NeuronGroup
        # !! This will not work with postsynaptic variables in the model eqs !!
        if isinstance(model_obj, StateUpdater):
            self._state_updater = model_obj
            self._all_units = defaultdict()  # what is that
        elif isinstance(model_obj, Equations):
            self._eqs = model_obj
            self._state_updater, var_names = magic_state_updater(
                model_obj,
                clock=clock,
                order=order,
                check_units=check_units,
                implicit=implicit,
                compile=compile,
                freeze=freeze,
                method=method)
            self._state_updater_varnames = var_names

        # TODO:
        # - Maybe stateupdaters should be aware of the dtype of the data (or else my state vectors thing is sort of useless
        # - or ensure fast access to different data types in a vectorized way?

        # handle multi-line pre, post equations and multi-statement equations separated by ;
        if '\n' in pre:
            pre = flattened_docstring(pre)
        elif ';' in pre:
            pre = '\n'.join([line.strip() for line in pre.split(';')])
        if '\n' in post:
            post = flattened_docstring(post)
        elif ';' in post:
            post = '\n'.join([line.strip() for line in post.split(';')])

        # Check units
        model_obj.compile_functions()
        #model_obj.check_units()
        # Get variable names
        self.vars = model_obj._diffeq_names  # !! there are also static variables and aliases !!

        ############# Setting up the data structure
        # Mandatory fields: 3 for pre/post/delay_pre, all int32 TODO: finer pick of dtype!

        # REPLACE THAT BY MORE SENSIBLE DATA STRUCTURE
        dtypes = (np.int32, ) * 3
        default_labels = ['_pre', '_post', 'delay']
        # Equation defined fields (float32)
        dtypes += (np.float32, ) * len(self.vars)

        # construction of the structure
        self._statevector = ConstructionSparseStateVector(
            len(dtypes), dtype=dtypes, labels=default_labels + self.vars)

        ############# Code!!!
        # create namespace
        pre_namespace = namespace(pre, level=level + 1)
        pre_namespace['target'] = self.target
        pre_namespace['unique'] = np.unique
        pre_namespace['nonzero'] = np.nonzero
        pre_namespace['_pre'] = self._statevector._pre
        pre_namespace['_post'] = self._statevector._post

        for var in self.vars:
            pre_namespace[var] = self._statevector[var]
        # !! also add postsynaptic variables !!

        def update_code(pre, indices):
            res = pre
            # given the synapse indices, write the update code,
            # this is here because in the code we generate we need to write this twice (because of the multiple presyn spikes for the same postsyn neuron problem)
            for postsyn_var in self.target.var_index:
                if isinstance(postsyn_var, str):
                    res = re.sub(
                        r'\b' + postsyn_var + r'\b',
                        'target.' + postsyn_var + '[_post[' + indices + ']]',
                        res
                    )  # postsyn variable, indexed by post syn neuron numbers

            for var in self.vars:
                res = re.sub(
                    r'\b' + var + r'\b', var + '[' + indices + ']',
                    res)  # synaptic variable, indexed by the synapse number
            return res

        # pre code
        pre_code = "_post_neurons = _post[_synapses]\n"
        # which post syn neurons
        pre_code += "_u, _i = unique(_post_neurons, return_index = True)\n"
        pre_code += update_code(pre, '_synapses[_i]') + "\n"
        pre_code += "if len(_u) < len(_post_neurons):\n"
        pre_code += "    _post_neurons[_i] = -1\n"
        pre_code += "    while (len(_u) < len(_post_neurons)) & (_post_neurons>-1).any():\n"  # !! the any() is time consuming (len(u)>=1??)
        pre_code += "        _u, _i = unique(_post_neurons, return_index = True)\n"
        pre_code += "        " + update_code(pre, '_synapses[_i[1:]]') + "\n"
        pre_code += "        _post_neurons[_i[1:]] = -1 \n"
        log_debug('brian.synapses', '\nPRE CODE:\n' + pre_code)

        pre_code = compile(pre_code, "Presynaptic code", "exec")

        self.pre_namespace = pre_namespace
        self.pre_code = pre_code
        self.pre_queue = SpikeQueue(self.source, self, max_delay=max_delay)

        self.contained_objects = [
            self.pre_queue
        ]  # wtf is this for: so that pre_queue.propagate is called

        self._pre_to_synapse = np.zeros(len(self.source), dtype=object)
        self._post_to_synapse = np.zeros(len(self.target), dtype=object)