Ejemplo n.º 1
0
    def compile_functions(self, freeze=False):
        """
        Compile all functions defined as strings.
        If freeze is True, all external parameters and units are replaced by their value.
        ALL FUNCTIONS MUST HAVE STRINGS.
        """
        all_variables = self._eq_names + self._diffeq_names + self._alias.keys(
        ) + ['t']
        # Check if freezable
        freeze = freeze and all([optimiser.freeze(expr, all_variables, self._namespace[name])\
                               for name, expr in self._string.iteritems()])
        self._frozen = freeze

        # Compile strings to functions
        for name, expr in self._string.iteritems():
            namespace = self._namespace[name]  # name space of the function
            # Find variables
            vars = [
                var for var in get_identifiers(expr) if var in all_variables
            ]
            if freeze:
                expr = optimiser.freeze(expr, all_variables, namespace)
                #self._string[name]=expr # should we?
                #namespace={}
            s = "lambda " + ','.join(vars) + ":" + expr
            self._function[name] = eval(s, namespace)
Ejemplo n.º 2
0
 def exponential_euler_code(self):
     '''
     Generates Python code for an exponential Euler step.
     Not efficient for the moment!
     '''
     all_variables = self._eq_names + self._diffeq_names + self._alias.keys() + ['t']
     vars_tmp = [name + '__tmp' for name in self._diffeq_names]
     lines = ','.join(self._diffeq_names) + '=P._S\n'
     lines += ','.join(vars_tmp) + '=P._dS\n'
     for name in self._diffeq_names:
         # Freeze
         namespace = self._namespace[name]
         expr = optimiser.freeze(self._string[name], all_variables, namespace)
         # Find a and b in dx/dt=a*x+b
         sym_expr = symbolic_eval(expr)
         if isinstance(sym_expr, float):
             lines += name + '__tmp[:]=' + name + '+(' + str(expr) + ')*dt\n'
         else:
             sym_expr = sym_expr.expand()
             sname = sympy.Symbol(name)
             terms = sympy.collect(sym_expr, name, evaluate=False)
             if sname ** 0 in terms:
                 b = terms[sname ** 0]
             else:
                 b = 0
             if sname in terms:
                 a = terms[sname]
             else:
                 a = 0
             lines += name + '__tmp[:]=' + str(-b / a + (sname + b / a) * sympy.exp(a * sympy.Symbol('dt'))) + '\n'
     lines += 'P._S[:]=P._dS'
     #print lines
     return compile(lines, 'Exponential Euler update code', 'exec')
Ejemplo n.º 3
0
 def exponential_euler_code(self):
     '''
     Generates Python code for an exponential Euler step.
     Not efficient for the moment!
     '''
     all_variables = self._eq_names + self._diffeq_names + self._alias.keys() + ['t']
     vars_tmp = [name + '__tmp' for name in self._diffeq_names]
     lines = ','.join(self._diffeq_names) + '=P._S\n'
     lines += ','.join(vars_tmp) + '=P._dS\n'
     for name in self._diffeq_names:
         # Freeze
         namespace = self._namespace[name]
         expr = optimiser.freeze(self._string[name], all_variables, namespace)
         # Find a and b in dx/dt=a*x+b
         sym_expr = symbolic_eval(expr)
         if isinstance(sym_expr, float):
             lines += name + '__tmp[:]=' + name + '+(' + str(expr) + ')*dt\n'
         else:
             sym_expr = sym_expr.expand()
             sname = sympy.Symbol(name)
             terms = sympy.collect(sym_expr, name, evaluate=False)
             if sname ** 0 in terms:
                 b = terms[sname ** 0]
             else:
                 b = 0
             if sname in terms:
                 a = terms[sname]
             else:
                 a = 0
             lines += name + '__tmp[:]=' + str(-b / a + (sname + b / a) * sympy.exp(a * sympy.Symbol('dt'))) + '\n'
     lines += 'P._S[:]=P._dS'
     #print lines
     return compile(lines, 'Exponential Euler update code', 'exec')
Ejemplo n.º 4
0
    def compile_functions(self, freeze=False):
        """
        Compile all functions defined as strings.
        If freeze is True, all external parameters and units are replaced by their value.
        ALL FUNCTIONS MUST HAVE STRINGS.
        """
        all_variables = self._eq_names + self._diffeq_names + self._alias.keys() + ['t']
        # Check if freezable
        freeze = freeze and all([optimiser.freeze(expr, all_variables, self._namespace[name])\
                               for name, expr in self._string.iteritems()])
        self._frozen = freeze

        # Compile strings to functions
        for name, expr in self._string.iteritems():
            namespace = self._namespace[name] # name space of the function
            # Find variables
            vars = [var for var in get_identifiers(expr) if var in all_variables]
            if freeze:
                expr = optimiser.freeze(expr, all_variables, namespace)
                #self._string[name]=expr # should we?
                #namespace={}
            s = "lambda " + ','.join(vars) + ":" + expr
            self._function[name] = eval(s, namespace)
Ejemplo n.º 5
0
 def forward_euler_code(self):
     '''
     Generates Python code for a forward Euler step.
     '''
     # TODO: check if it can really be frozen
     # TODO: change /a to *(1/a) with precalculation (use parser)
     all_variables = self._eq_names + self._diffeq_names + self._alias.keys() + ['t']
     # nonzero? insert dt?
     vars_tmp = [name + '__tmp' for name in self._diffeq_names]
     lines = ','.join(self._diffeq_names) + '=P._S\n'
     lines += ','.join(vars_tmp) + '=P._dS\n'
     for name in self._diffeq_names_nonzero:
         namespace = self._namespace[name]
         expr = optimiser.freeze(self._string[name], all_variables, namespace)
         lines += name + '__tmp[:]=' + expr + '\n'
     lines += 'P._S+=dt*P._dS\n'
     #print lines
     return compile(lines, 'Euler update code', 'exec')
Ejemplo n.º 6
0
 def forward_euler_code(self):
     '''
     Generates Python code for a forward Euler step.
     '''
     # TODO: check if it can really be frozen
     # TODO: change /a to *(1/a) with precalculation (use parser)
     all_variables = self._eq_names + self._diffeq_names + self._alias.keys() + ['t']
     # nonzero? insert dt?
     vars_tmp = [name + '__tmp' for name in self._diffeq_names]
     lines = ','.join(self._diffeq_names) + '=P._S\n'
     lines += ','.join(vars_tmp) + '=P._dS\n'
     for name in self._diffeq_names_nonzero:
         namespace = self._namespace[name]
         expr = optimiser.freeze(self._string[name], all_variables, namespace)
         lines += name + '__tmp[:]=' + expr + '\n'
     lines += 'P._S+=dt*P._dS\n'
     #print lines
     return compile(lines, 'Euler update code', 'exec')
Ejemplo n.º 7
0
    def __init__(self,
                 C,
                 eqs,
                 pre,
                 post,
                 wmin=0,
                 wmax=Inf,
                 level=0,
                 clock=None,
                 delay_pre=None,
                 delay_post=None):
        '''
        C: connection object
        eqs: differential equations (with units)
        pre: Python code for presynaptic spikes
        post: Python code for postsynaptic spikes
        wmax: maximum weight (default unlimited)
        delay_pre: presynaptic delay
        delay_post: postsynaptic delay (backward propagating spike)
        '''
        if get_global_preference('usecstdp') and get_global_preference(
                'useweave'):
            from experimental.c_stdp import CSTDP
            log_warn('brian.stdp', 'Using experimental C STDP class.')
            self.__class__ = CSTDP
            CSTDP.__init__(self,
                           C,
                           eqs,
                           pre,
                           post,
                           wmin=wmin,
                           wmax=wmax,
                           level=level + 1,
                           clock=clock,
                           delay_pre=delay_pre,
                           delay_post=delay_post)
            return
        NetworkOperation.__init__(self, lambda: None, clock=clock)
        # Convert to equations object
        if isinstance(eqs, Equations):
            eqs_obj = eqs
        else:
            eqs_obj = Equations(eqs, level=level + 1)
        # 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
        eqs_obj.compile_functions()
        eqs_obj.check_units()
        # Get variable names
        vars = eqs_obj._diffeq_names
        # Find which ones are directly modified (e.g. regular expression matching; careful with comments)
        vars_pre = [var for var in vars if var in modified_variables(pre)]
        vars_post = [var for var in vars if var in modified_variables(post)]

        # additional dependencies are used to ensure that if there are multiple
        # pre/post separated equations they are grouped together as one
        #
        # We should replace with this code here, in case there are no pre/post synaptic variables
        #additional_deps =[]
        #if len(vars_pre)>0:
        #    additional_deps.append('__pre_deps='+'+'.join(vars_pre))
        #if len(vars_post)>0:
        #    additional_deps.append('__post_deps='+'+'.join(vars_post))
        additional_deps = [
            '__pre_deps=' + '+'.join(vars_pre),
            '__post_deps=' + '+'.join(vars_post)
        ]
        separated_equations = separate_equations(eqs_obj, additional_deps)
        if not len(separated_equations) == 2:
            raise ValueError(
                'Equations should separate into pre and postsynaptic variables.'
            )
        sep_pre, sep_post = separated_equations
        for v in vars_pre:
            if v in sep_post._diffeq_names:
                sep_pre, sep_post = sep_post, sep_pre
                break
        index_pre = [
            i for i in range(len(vars))
            if vars[i] in vars_pre or vars[i] in sep_pre._diffeq_names
        ]
        index_post = [
            i for i in range(len(vars))
            if vars[i] in vars_post or vars[i] in sep_post._diffeq_names
        ]

        vars_pre = array(vars)[index_pre]
        vars_post = array(vars)[index_post]

        # Check pre/post consistency
        shared_vars = set(vars_pre).intersection(vars_post)
        if shared_vars != set([]):
            raise Exception, str(
                list(shared_vars)) + " are both presynaptic and postsynaptic!"

        # Substitute equations/aliases into pre/post code
        def substitute_eqs(code):
            for name in sep_pre._eq_names[-1::-1] + sep_post._eq_names[
                    -1::-1]:  # reverse order, as in equations.py
                if name in sep_pre._eq_names:
                    expr = sep_pre._string[name]
                else:
                    expr = sep_post._string[name]
                code = re.sub("\\b" + name + "\\b", '(' + expr + ')', code)
            return code

        pre = substitute_eqs(pre)
        post = substitute_eqs(post)

        # Create namespaces for pre and post codes
        pre_namespace = namespace(pre, level=level + 1)
        post_namespace = namespace(post, level=level + 1)
        pre_namespace['clip'] = clip
        post_namespace['clip'] = clip
        pre_namespace['Inf'] = Inf
        post_namespace['Inf'] = Inf
        pre_namespace['enumerate'] = enumerate
        post_namespace['enumerate'] = enumerate

        # freeze pre and post (otherwise units will cause problems)
        all_vars = list(vars_pre) + list(vars_post) + ['w']
        pre = '\n'.join(
            freeze(line.strip(), all_vars, pre_namespace)
            for line in pre.split('\n'))
        post = '\n'.join(
            freeze(line.strip(), all_vars, post_namespace)
            for line in post.split('\n'))

        # Neuron groups
        G_pre = NeuronGroup(len(C.source), model=sep_pre, clock=self.clock)
        G_post = NeuronGroup(len(C.target), model=sep_post, clock=self.clock)
        G_pre._S[:] = 0
        G_post._S[:] = 0
        self.pre_group = G_pre
        self.post_group = G_post
        var_group = {}  # maps variable name to group
        for v in vars_pre:
            var_group[v] = G_pre
        for v in vars_post:
            var_group[v] = G_post
        self.var_group = var_group

        # Create updaters and monitors
        if isinstance(C, DelayConnection):
            G_pre_monitors = {}  # these get values put in them later
            G_post_monitors = {}
            max_delay = C._max_delay * C.target.clock.dt

            def gencode(incode, vars, other_vars, wreplacement):
                num_immediate = num_delayed = 0
                reordering_warning = False
                incode_lines = [line.strip() for line in incode.split('\n')]
                outcode_immediate = 'for _i in spikes:\n'
                # delayed variables
                outcode_delayed = 'for _j, _i in enumerate(spikes):\n'
                for var in other_vars:
                    outcode_delayed += '    ' + var + '__delayed = ' + var + '__delayed_values_seq[_j]\n'
                for line in incode_lines:
                    if not line.strip(): continue
                    m = re.search(
                        r'\bw\b\s*[^><=]?=',
                        line)  # lines of the form w = ..., w *= ..., etc.
                    for var in vars:
                        line = re.sub(r'\b' + var + r'\b', var + '[_i]', line)
                    for var in other_vars:
                        line = re.sub(r'\b' + var + r'\b', var + '__delayed',
                                      line)
                    if m:
                        num_delayed += 1
                        outcode_delayed += '    ' + line + '\n'
                    else:
                        if num_delayed != 0 and not reordering_warning:
                            log_warn(
                                'brian.stdp',
                                'STDP operations are being re-ordered for delay connection, results may be wrong.'
                            )
                            reordering_warning = True
                        num_immediate += 1
                        outcode_immediate += '    ' + line + '\n'
                outcode_delayed = re.sub(r'\bw\b', wreplacement,
                                         outcode_delayed)
                outcode_delayed += '\n    %(w)s = clip(%(w)s, %(min)e, %(max)e)' % {
                    'min': wmin,
                    'max': wmax,
                    'w': wreplacement
                }
                return (outcode_immediate, outcode_delayed)

            pre_immediate, pre_delayed = gencode(pre, vars_pre, vars_post,
                                                 'w[_i,:]')
            post_immediate, post_delayed = gencode(post, vars_post, vars_pre,
                                                   'w[:,_i]')
            log_debug('brian.stdp', 'PRE CODE IMMEDIATE:\n' + pre_immediate)
            log_debug('brian.stdp', 'PRE CODE DELAYED:\n' + pre_delayed)
            log_debug('brian.stdp',
                      'POST CODE:\n' + post_immediate + post_delayed)
            pre_delay_expr = 'max_delay-d'
            post_delay_expr = 'd'
            pre_code_immediate = compile(pre_immediate,
                                         "Presynaptic code immediate", "exec")
            pre_code_delayed = compile(pre_delayed, "Presynaptic code delayed",
                                       "exec")
            post_code = compile(post_immediate + post_delayed,
                                "Postsynaptic code", "exec")

            if delay_pre is not None or delay_post is not None:
                raise ValueError(
                    "Must use delay_pre=delay_post=None for the moment.")
            max_delay = C._max_delay * C.target.clock.dt
            # Ensure that the source and target neuron spikes are kept for at least the
            # DelayConnection's maximum delay
            C.source.set_max_delay(max_delay)
            C.target.set_max_delay(max_delay)
            # create forward and backward Connection objects or SpikeMonitor objects
            pre_updater_immediate = STDPUpdater(C.source,
                                                C,
                                                vars=vars_pre,
                                                code=pre_code_immediate,
                                                namespace=pre_namespace,
                                                delay=0 * ms)
            pre_updater_delayed = DelayedSTDPUpdater(
                C,
                reverse=False,
                delay_expr=pre_delay_expr,
                max_delay=max_delay,
                vars=vars_pre,
                other_vars=vars_post,
                varmon=G_pre_monitors,
                othervarmon=G_post_monitors,
                code=pre_code_delayed,
                namespace=pre_namespace,
                delay=max_delay)
            post_updater = DelayedSTDPUpdater(C,
                                              reverse=True,
                                              delay_expr=post_delay_expr,
                                              max_delay=max_delay,
                                              vars=vars_post,
                                              other_vars=vars_pre,
                                              varmon=G_post_monitors,
                                              othervarmon=G_pre_monitors,
                                              code=post_code,
                                              namespace=post_namespace,
                                              delay=0 * ms)
            updaters = [
                pre_updater_immediate, pre_updater_delayed, post_updater
            ]
            self.contained_objects += updaters
            vars_pre_ind = dict((var, i) for i, var in enumerate(vars_pre))
            vars_post_ind = dict((var, i) for i, var in enumerate(vars_post))
            self.G_pre_monitors = G_pre_monitors
            self.G_post_monitors = G_post_monitors
            self.G_pre_monitors.update(
                ((var,
                  RecentStateMonitor(G_pre,
                                     vars_pre_ind[var],
                                     duration=(C._max_delay + 1) *
                                     C.target.clock.dt,
                                     clock=G_pre.clock)) for var in vars_pre))
            self.G_post_monitors.update(
                ((var,
                  RecentStateMonitor(
                      G_post,
                      vars_post_ind[var],
                      duration=(C._max_delay + 1) * C.target.clock.dt,
                      clock=G_post.clock)) for var in vars_post))
            self.contained_objects += self.G_pre_monitors.values()
            self.contained_objects += self.G_post_monitors.values()

        else:
            # Indent and loop
            pre = re.compile('^', re.M).sub('    ', pre)
            post = re.compile('^', re.M).sub('    ', post)
            pre = 'for _i in spikes:\n' + pre
            post = 'for _i in spikes:\n' + post

            # Pre code
            for var in vars_pre:  # presynaptic variables (vectorisation)
                pre = re.sub(r'\b' + var + r'\b', var + '[_i]', pre)
            pre = re.sub(r'\bw\b', 'w[_i,:]', pre)  # synaptic weight
            # Post code
            for var in vars_post:  # postsynaptic variables (vectorisation)
                post = re.sub(r'\b' + var + r'\b', var + '[_i]', post)
            post = re.sub(r'\bw\b', 'w[:,_i]', post)  # synaptic weight

            # Bounds: add one line to pre/post code (clip(w,min,max,w))
            # or actual code? (rather than compiled string)
            if wmax == Inf:
                pre += '\n    w[_i,:]=clip(w[_i,:],%(min)e,Inf)' % {
                    'min': wmin
                }
                post += '\n    w[:,_i]=clip(w[:,_i],%(min)e,Inf)' % {
                    'min': wmin
                }
            else:
                pre += '\n    w[_i,:]=clip(w[_i,:],%(min)e,%(max)e)' % {
                    'min': wmin,
                    'max': wmax
                }
                post += '\n    w[:,_i]=clip(w[:,_i],%(min)e,%(max)e)' % {
                    'min': wmin,
                    'max': wmax
                }
            log_debug('brian.stdp', 'PRE CODE:\n' + pre)
            log_debug('brian.stdp', 'POST CODE:\n' + post)
            # Compile code
            pre_code = compile(pre, "Presynaptic code", "exec")
            post_code = compile(post, "Postsynaptic code", "exec")

            connection_delay = C.delay * C.source.clock.dt
            if (delay_pre is None) and (
                    delay_post is None):  # same delays as the Connnection C
                delay_pre = connection_delay
                delay_post = 0 * ms
            elif delay_pre is None:
                delay_pre = connection_delay - delay_post
                if delay_pre < 0 * ms:
                    raise AttributeError, "Presynaptic delay is too large"
            elif delay_post is None:
                delay_post = connection_delay - delay_pre
                if delay_post < 0 * ms:
                    raise AttributeError, "Postsynaptic delay is too large"
            # create forward and backward Connection objects or SpikeMonitor objects
            pre_updater = STDPUpdater(C.source,
                                      C,
                                      vars=vars_pre,
                                      code=pre_code,
                                      namespace=pre_namespace,
                                      delay=delay_pre)
            post_updater = STDPUpdater(C.target,
                                       C,
                                       vars=vars_post,
                                       code=post_code,
                                       namespace=post_namespace,
                                       delay=delay_post)
            updaters = [pre_updater, post_updater]
            self.contained_objects += [pre_updater, post_updater]

        # Put variables in namespaces
        for i, var in enumerate(vars_pre):
            for updater in updaters:
                updater._namespace[var] = G_pre._S[i]
        for i, var in enumerate(vars_post):
            for updater in updaters:
                updater._namespace[var] = G_post._S[i]

        self.contained_objects += [G_pre, G_post]
Ejemplo n.º 8
0
    def __init__(self, C, eqs, pre, post, wmin=0, wmax=Inf, level=0, clock=None, delay_pre=None, delay_post=None):
        '''
        C: connection object
        eqs: differential equations (with units)
        pre: Python code for presynaptic spikes
        post: Python code for postsynaptic spikes
        wmax: maximum weight (default unlimited)
        delay_pre: presynaptic delay
        delay_post: postsynaptic delay (backward propagating spike)
        '''
        if get_global_preference('usecstdp') and get_global_preference('useweave'):
            from experimental.c_stdp import CSTDP
            log_warn('brian.stdp', 'Using experimental C STDP class.')
            self.__class__ = CSTDP
            CSTDP.__init__(self, C, eqs, pre, post, wmin=wmin, wmax=wmax,
                           level=level + 1, clock=clock, delay_pre=delay_pre,
                           delay_post=delay_post)
            return
        NetworkOperation.__init__(self, lambda:None, clock=clock)
        # Convert to equations object
        if isinstance(eqs, Equations):
            eqs_obj = eqs
        else:
            eqs_obj = Equations(eqs, level=level + 1)
        # 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
        eqs_obj.compile_functions()
        eqs_obj.check_units()
        # Get variable names
        vars = eqs_obj._diffeq_names
        # Find which ones are directly modified (e.g. regular expression matching; careful with comments)
        vars_pre = [var for var in vars if var in modified_variables(pre)]
        vars_post = [var for var in vars if var in modified_variables(post)]

        # additional dependencies are used to ensure that if there are multiple
        # pre/post separated equations they are grouped together as one
        #
        # We should replace with this code here, in case there are no pre/post synaptic variables
        #additional_deps =[]
        #if len(vars_pre)>0:
        #    additional_deps.append('__pre_deps='+'+'.join(vars_pre))
        #if len(vars_post)>0:
        #    additional_deps.append('__post_deps='+'+'.join(vars_post))
        additional_deps = ['__pre_deps='+'+'.join(vars_pre),
                           '__post_deps='+'+'.join(vars_post)]
        separated_equations = separate_equations(eqs_obj, additional_deps)
        if not len(separated_equations) == 2:
            raise ValueError('Equations should separate into pre and postsynaptic variables.')
        sep_pre, sep_post = separated_equations
        for v in vars_pre:
            if v in sep_post._diffeq_names:
                sep_pre, sep_post = sep_post, sep_pre
                break
        index_pre = [i for i in range(len(vars)) if vars[i] in vars_pre or vars[i] in sep_pre._diffeq_names]
        index_post = [i for i in range(len(vars)) if vars[i] in vars_post or vars[i] in sep_post._diffeq_names]

        vars_pre = array(vars)[index_pre]
        vars_post = array(vars)[index_post]

        # Check pre/post consistency
        shared_vars = set(vars_pre).intersection(vars_post)
        if shared_vars != set([]):
            raise Exception, str(list(shared_vars)) + " are both presynaptic and postsynaptic!"

        # Substitute equations/aliases into pre/post code
        def substitute_eqs(code):
            for name in sep_pre._eq_names[-1::-1]+sep_post._eq_names[-1::-1]: # reverse order, as in equations.py
                if name in sep_pre._eq_names:
                    expr = sep_pre._string[name]
                else:
                    expr = sep_post._string[name]
                code = re.sub("\\b" + name + "\\b", '(' + expr + ')', code)
            return code
        pre = substitute_eqs(pre)
        post = substitute_eqs(post)

        # Create namespaces for pre and post codes
        pre_namespace = namespace(pre, level=level + 1)
        post_namespace = namespace(post, level=level + 1)
        pre_namespace['clip'] = clip
        post_namespace['clip'] = clip
        pre_namespace['Inf'] = Inf
        post_namespace['Inf'] = Inf
        pre_namespace['enumerate'] = enumerate
        post_namespace['enumerate'] = enumerate

        # freeze pre and post (otherwise units will cause problems)
        all_vars = list(vars_pre) + list(vars_post) + ['w']
        pre = '\n'.join(freeze(line.strip(), all_vars, pre_namespace) for line in pre.split('\n'))
        post = '\n'.join(freeze(line.strip(), all_vars, post_namespace) for line in post.split('\n'))

        # Neuron groups
        G_pre = NeuronGroup(len(C.source), model=sep_pre, clock=self.clock)
        G_post = NeuronGroup(len(C.target), model=sep_post, clock=self.clock)
        G_pre._S[:] = 0
        G_post._S[:] = 0
        self.pre_group = G_pre
        self.post_group = G_post
        var_group = {} # maps variable name to group
        for v in vars_pre:
            var_group[v] = G_pre
        for v in vars_post:
            var_group[v] = G_post
        self.var_group = var_group

        # Create updaters and monitors
        if isinstance(C, DelayConnection):
            G_pre_monitors = {} # these get values put in them later
            G_post_monitors = {}
            max_delay = C._max_delay * C.target.clock.dt

            def gencode(incode, vars, other_vars, wreplacement):
                num_immediate = num_delayed = 0
                reordering_warning = False
                incode_lines = [line.strip() for line in incode.split('\n')]
                outcode_immediate = 'for _i in spikes:\n'
                # delayed variables
                outcode_delayed = 'for _j, _i in enumerate(spikes):\n'
                for var in other_vars:
                    outcode_delayed += '    ' + var + '__delayed = ' + var + '__delayed_values_seq[_j]\n'
                for line in incode_lines:
                    if not line.strip(): continue
                    m = re.search(r'\bw\b\s*[^><=]?=', line) # lines of the form w = ..., w *= ..., etc.
                    for var in vars:
                        line = re.sub(r'\b' + var + r'\b', var + '[_i]', line)
                    for var in other_vars:
                        line = re.sub(r'\b' + var + r'\b', var + '__delayed', line)
                    if m:
                        num_delayed += 1
                        outcode_delayed += '    ' + line + '\n'
                    else:
                        if num_delayed!=0 and not reordering_warning:
                            log_warn('brian.stdp', 'STDP operations are being re-ordered for delay connection, results may be wrong.')
                            reordering_warning = True
                        num_immediate += 1
                        outcode_immediate += '    ' + line + '\n'
                outcode_delayed = re.sub(r'\bw\b', wreplacement, outcode_delayed)
                outcode_delayed += '\n    %(w)s = clip(%(w)s, %(min)e, %(max)e)' % {'min':wmin, 'max':wmax, 'w':wreplacement}
                return (outcode_immediate, outcode_delayed)

            pre_immediate, pre_delayed = gencode(pre, vars_pre, vars_post, 'w[_i,:]')
            post_immediate, post_delayed = gencode(post, vars_post, vars_pre, 'w[:,_i]')
            log_debug('brian.stdp', 'PRE CODE IMMEDIATE:\n'+pre_immediate)
            log_debug('brian.stdp', 'PRE CODE DELAYED:\n'+pre_delayed)
            log_debug('brian.stdp', 'POST CODE:\n'+post_immediate+post_delayed)
            pre_delay_expr = 'max_delay-d'
            post_delay_expr = 'd'
            pre_code_immediate = compile(pre_immediate, "Presynaptic code immediate", "exec")
            pre_code_delayed = compile(pre_delayed, "Presynaptic code delayed", "exec")
            post_code = compile(post_immediate + post_delayed, "Postsynaptic code", "exec")

            if delay_pre is not None or delay_post is not None:
                raise ValueError("Must use delay_pre=delay_post=None for the moment.")
            max_delay = C._max_delay * C.target.clock.dt
            # Ensure that the source and target neuron spikes are kept for at least the
            # DelayConnection's maximum delay
            C.source.set_max_delay(max_delay)
            C.target.set_max_delay(max_delay)
            # create forward and backward Connection objects or SpikeMonitor objects
            pre_updater_immediate = STDPUpdater(C.source, C, vars=vars_pre,
                                           code=pre_code_immediate, namespace=pre_namespace, delay=0 * ms)
            pre_updater_delayed = DelayedSTDPUpdater(C, reverse=False, delay_expr=pre_delay_expr, max_delay=max_delay,
                                            vars=vars_pre, other_vars=vars_post,
                                            varmon=G_pre_monitors, othervarmon=G_post_monitors,
                                            code=pre_code_delayed, namespace=pre_namespace, delay=max_delay)
            post_updater = DelayedSTDPUpdater(C, reverse=True, delay_expr=post_delay_expr, max_delay=max_delay,
                                            vars=vars_post, other_vars=vars_pre,
                                            varmon=G_post_monitors, othervarmon=G_pre_monitors,
                                            code=post_code, namespace=post_namespace, delay=0 * ms)
            updaters = [pre_updater_immediate, pre_updater_delayed, post_updater]
            self.contained_objects += updaters
            vars_pre_ind = dict((var, i) for i, var in enumerate(vars_pre))
            vars_post_ind = dict((var, i) for i, var in enumerate(vars_post))
            self.G_pre_monitors = G_pre_monitors
            self.G_post_monitors = G_post_monitors
            self.G_pre_monitors.update(((var, RecentStateMonitor(G_pre, vars_pre_ind[var], duration=(C._max_delay + 1) * C.target.clock.dt, clock=G_pre.clock)) for var in vars_pre))
            self.G_post_monitors.update(((var, RecentStateMonitor(G_post, vars_post_ind[var], duration=(C._max_delay + 1) * C.target.clock.dt, clock=G_post.clock)) for var in vars_post))
            self.contained_objects += self.G_pre_monitors.values()
            self.contained_objects += self.G_post_monitors.values()

        else:
            # Indent and loop
            pre = re.compile('^', re.M).sub('    ', pre)
            post = re.compile('^', re.M).sub('    ', post)
            pre = 'for _i in spikes:\n' + pre
            post = 'for _i in spikes:\n' + post

            # Pre code
            for var in vars_pre: # presynaptic variables (vectorisation)
                pre = re.sub(r'\b' + var + r'\b', var + '[_i]', pre)
            pre = re.sub(r'\bw\b', 'w[_i,:]', pre) # synaptic weight
            # Post code
            for var in vars_post: # postsynaptic variables (vectorisation)
                post = re.sub(r'\b' + var + r'\b', var + '[_i]', post)
            post = re.sub(r'\bw\b', 'w[:,_i]', post) # synaptic weight

            # Bounds: add one line to pre/post code (clip(w,min,max,w))
            # or actual code? (rather than compiled string)
            if wmax==Inf:
                pre += '\n    w[_i,:]=clip(w[_i,:],%(min)e,Inf)' % {'min':wmin}
                post += '\n    w[:,_i]=clip(w[:,_i],%(min)e,Inf)' % {'min':wmin}
            else:
                pre += '\n    w[_i,:]=clip(w[_i,:],%(min)e,%(max)e)' % {'min':wmin, 'max':wmax}
                post += '\n    w[:,_i]=clip(w[:,_i],%(min)e,%(max)e)' % {'min':wmin, 'max':wmax}
            log_debug('brian.stdp', 'PRE CODE:\n'+pre)
            log_debug('brian.stdp', 'POST CODE:\n'+post)
            # Compile code
            pre_code = compile(pre, "Presynaptic code", "exec")
            post_code = compile(post, "Postsynaptic code", "exec")

            connection_delay = C.delay * C.source.clock.dt
            if (delay_pre is None) and (delay_post is None): # same delays as the Connnection C
                delay_pre = connection_delay
                delay_post = 0 * ms
            elif delay_pre is None:
                delay_pre = connection_delay - delay_post
                if delay_pre < 0 * ms: raise AttributeError, "Presynaptic delay is too large"
            elif delay_post is None:
                delay_post = connection_delay - delay_pre
                if delay_post < 0 * ms: raise AttributeError, "Postsynaptic delay is too large"
            # create forward and backward Connection objects or SpikeMonitor objects
            pre_updater = STDPUpdater(C.source, C, vars=vars_pre, code=pre_code, namespace=pre_namespace, delay=delay_pre)
            post_updater = STDPUpdater(C.target, C, vars=vars_post, code=post_code, namespace=post_namespace, delay=delay_post)
            updaters = [pre_updater, post_updater]
            self.contained_objects += [pre_updater, post_updater]

        # Put variables in namespaces
        for i, var in enumerate(vars_pre):
            for updater in updaters:
                updater._namespace[var] = G_pre._S[i]
        for i, var in enumerate(vars_post):
            for updater in updaters:
                updater._namespace[var] = G_post._S[i]

        self.contained_objects += [G_pre, G_post]