Пример #1
0
    def __init__(self, tau_rec=100.0, tau_facil=0.01, U=0.5):

        if tau_facil<= 0.0:
            _error('tau_facil must be positive. Choose a very small value if you have to, or derive a new synapse.')
            exit(0)

        parameters = """
    tau_rec = %(tau_rec)s
    tau_facil = %(tau_facil)s
    U = %(U)s
    """ % {'tau_rec': tau_rec, 'tau_facil': tau_facil, 'U': U}
        equations = """
    dx/dt = (1 - x)/tau_rec : init = 1.0, event-driven
    du/dt = (U - u)/tau_facil : init = %(U)s, event-driven   
    """ % {'tau_rec': tau_rec, 'tau_facil': tau_facil, 'U': U}
        pre_spike="""
    g_target += w * u * x
    x *= (1 - u)
    u += U * (1 - u)
    """

        Synapse.__init__(self, parameters=parameters, equations=equations, pre_spike=pre_spike,
            name="Short-term plasticity", description="Synapse exhibiting short-term facilitation and depression, implemented using the model of Tsodyks, Markram et al.")
        # For reporting
        self._instantiated.append(True)
Пример #2
0
def report(filename="./report.tex", standalone=True, gather_subprojections=False, title=None, author=None, date=None, net_id=0):
    """ 
    Generates a report describing the network.

    If the filename ends with ``.tex``, the TeX file is generated according to:

    Nordlie E, Gewaltig M-O, Plesser HE (2009). Towards Reproducible Descriptions of Neuronal Network Models. PLoS Comput Biol 5(8): e1000456.

    If the filename ends with ``.md``, a (more complete) Markdown file is generated, which can be converted to pdf or html by ``pandoc``::

        pandoc report.md  -sSN -V geometry:margin=1in -o report.pdf
        pandoc report.md  -sSN -o report.html

    :param filename: name of the file where the report will be written (default: "./report.tex")
    :param standalone: tells if the generated TeX file should be directly compilable or only includable (default: True). Ignored for Markdown.
    :param gather_subprojections: if a projection between two populations has been implemented as a multiple of projections between sub-populations, this flag allows to group them in the summary (default: False).
    :param title: title of the document (Markdown only)
    :param author: author of the document (Markdown only)
    :param date: date of the document (Markdown only)
    :param net_id: id of the network to be used for reporting (default: 0, everything that was declared)
    """

    if filename.endswith('.tex'):
        from .LatexReport import report_latex
        report_latex(filename, standalone, gather_subprojections, net_id)

    elif filename.endswith('.md'):
        from .MarkdownReport import report_markdown
        report_markdown(filename, standalone, gather_subprojections, title, author, date, net_id)

    else:
        _error('report(): the filename must end with .tex or .md.')
Пример #3
0
    def eventdriven(self, expression):
        # Standardize the equation
        real_tau, stepsize, steadystate = self.standardize_ODE(expression)

        if real_tau == None:  # the equation can not be standardized
            _print(self.expression)
            _error(
                'The equation is not a linear ODE and can not be evaluated exactly.'
            )
            exit(0)

        # Check the steady state is not dependent on other variables
        for var in self.variables:
            if self.local_dict[var] in steadystate.atoms():
                _print(self.expression)
                _error('The equation can not depend on other variables (' +
                       var + ') to be evaluated exactly.')
                exit(0)

        # Obtain C code
        variable_name = self.c_code(self.local_dict[self.name])
        steady = self.c_code(steadystate)
        if steady == '0':
            code = variable_name + '*= exp(dt*(_last_event[i][j] - (t))/(' + self.c_code(
                real_tau) + '));'
        else:
            code = variable_name + ' = ' + steady + ' + (' + variable_name + ' - ' + steady + ')*exp(dt*(_last_event[i][j] - (t))/(' + self.c_code(
                real_tau) + '));'
        return code
Пример #4
0
    def __init__(self, parameters="", equations="", psp=None, operation='sum', pre_spike=None, post_spike=None, functions=None, pruning=None, creating=None, name=None, description=None, extra_values={} ):
        """ 
        *Parameters*:
        
            * **parameters**: parameters of the neuron and their initial value.
            * **equations**: equations defining the temporal evolution of variables.
            * **psp**: influence of a single synapse on the post-synaptic neuron (default for rate-coded: w*pre.r).
            * **operation**: operation (sum, max, min, mean) performed by the post-synaptic neuron on the individual psp (rate-coded only, default=sum).
            * **pre_spike**: updating of variables when a pre-synaptic spike is received (spiking only).
            * **post_spike**: updating of variables when a post-synaptic spike is emitted (spiking only).
            * **functions**: additional functions used in the equations.
            * **name**: name of the synapse type (used for reporting only).
            * **description**: short description of the synapse type (used for reporting).

        """  
        
        # Store the parameters and equations
        self.parameters = parameters
        self.equations = equations
        self.functions = functions
        self.pre_spike = pre_spike
        self.post_spike = post_spike
        self.psp = psp
        self.operation = operation
        self.extra_values = extra_values
        self.pruning = pruning
        self.creating = creating

        # Type of the synapse TODO: smarter
        self.type = 'spike' if pre_spike else 'rate'

        # Check the operation
        if self.type == 'spike' and self.operation != 'sum':
            _error('Spiking synapses can only perform a sum of presynaptic potentials.')
            
        if not self.operation in ['sum', 'min', 'max', 'mean']:
            _error('The only operations permitted are: sum (default), min, max, mean.')
            

        # Description
        self.description = None

        # Reporting
        if not hasattr(self, '_instantiated') : # User-defined
            _objects['synapses'].append(self)
        elif len(self._instantiated) == 0: # First instantiated of the class
            _objects['synapses'].append(self)

        if name:
            self.name = name
        else:
            self.name = self._default_names[self.type]

        if description:
            self.short_description = description
        else:
            if self.type == 'spike':
                self.short_description = "Instantaneous increase of the post-synaptic conductance after a spike is received."
            else:
                self.short_description = "Weighted sum of firing rates."
Пример #5
0
def report(filename="./report.tex", standalone=True, gather_subprojections=False, title=None, author=None, date=None, net_id=0):
    """ 
    Generates a report describing the network.

    If the filename ends with ``.tex``, the TeX file is generated according to:

    Nordlie E, Gewaltig M-O, Plesser HE (2009). Towards Reproducible Descriptions of Neuronal Network Models. PLoS Comput Biol 5(8): e1000456.

    If the filename ends with ``.md``, a (more complete) Markdown file is generated, which can be converted to pdf or html by ``pandoc``::

        pandoc report.md  -sSN -V geometry:margin=1in -o report.pdf
        pandoc report.md  -sSN -o report.html

    *Parameters:*

    * **filename**: name of the file where the report will be written (default: "./report.tex")
    * **standalone**: tells if the generated TeX file should be directly compilable or only includable (default: True). Ignored for Markdown.
    * **gather_subprojections**: if a projection between two populations has been implemented as a multiple of projections between sub-populations, this flag allows to group them in the summary (default: False).
    * **title**: title of the document (Markdown only)
    * **author**: author of the document (Markdown only)
    * **date**: date of the document (Markdown only)
    * **net_id**: id of the network to be used for reporting (default: 0, everything that was declared)
    """

    if filename.endswith('.tex'):
        from .LatexReport import report_latex
        report_latex(filename, standalone, gather_subprojections, net_id)

    elif filename.endswith('.md'):
        from .MarkdownReport import report_markdown
        report_markdown(filename, standalone, gather_subprojections, title, author, date, net_id)

    else:
        _error('report(): the filename must end with .tex or .md.')
Пример #6
0
def extract_randomdist(description):
    " Extracts RandomDistribution objects from all variables"
    rk_rand = 0
    random_objects = []
    for variable in description['variables']:
        eq = variable['eq']
        # Search for all distributions
        for dist in available_distributions:
            matches = re.findall('(?P<pre>[^\w.])'+dist+'\(([^()]+)\)', eq)
            if matches == ' ':
                continue
            for l, v in matches:
                # Check the arguments
                arguments = v.split(',')
                # Check the number of provided arguments
                if len(arguments) < distributions_arguments[dist]:
                    _error(eq)
                    _error('The distribution ' + dist + ' requires ' + str(distributions_arguments[dist]) + 'parameters')
                elif len(arguments) > distributions_arguments[dist]:
                    _error(eq)
                    _error('Too many parameters provided to the distribution ' + dist)
                # Process the arguments
                processed_arguments = ""
                for idx in range(len(arguments)):
                    try:
                        arg = float(arguments[idx])
                    except: # A global parameter
                        if arguments[idx].strip() in description['global']:
                            if description['object'] == 'neuron':
                                arg = arguments[idx].strip() 
                            else:
                                arg = arguments[idx].strip() 
                        else:
                            _error(arguments[idx] + ' is not a global parameter of the neuron/synapse. It can not be used as an argument to the random distribution ' + dist + '(' + v + ')')
                            exit(0)

                    processed_arguments += str(arg)
                    if idx != len(arguments)-1: # not the last one
                        processed_arguments += ', '
                definition = distributions_equivalents[dist] + '(' + processed_arguments + ')'
                # Store its definition
                desc = {'name': 'rand_' + str(rk_rand) ,
                        'dist': dist,
                        'definition': definition,
                        'args' : processed_arguments,
                        'template': distributions_equivalents[dist]}
                rk_rand += 1
                random_objects.append(desc)
                # Replace its definition by its temporary name
                # Problem: when one uses twice the same RD in a single equation (perverse...)
                eq = eq.replace(dist+'('+v+')', desc['name'])
                # Add the new variable to the vocabulary
                description['attributes'].append(desc['name'])
                if variable['name'] in description['local']:
                    description['local'].append(desc['name'])
                else: # Why not on a population-wide variable?
                    description['global'].append(desc['name'])
        variable['transformed_eq'] = eq
        
    return random_objects
Пример #7
0
def extract_spike_variable(description):

    cond = prepare_string(description['raw_spike'])
    if len(cond) > 1:
        _print(description['raw_spike'])
        _error('The spike condition must be a single expression')

    translator = Equation('raw_spike_cond',
                            cond[0].strip(),
                            description)
    raw_spike_code = translator.parse()
    # Also store the variables used in the condition, as it may be needed for CUDA generation
    spike_code_dependencies = translator.dependencies()

    reset_desc = []
    if 'raw_reset' in description.keys() and description['raw_reset']:
        reset_desc = process_equations(description['raw_reset'])
        for var in reset_desc:
            translator = Equation(var['name'], var['eq'],
                                  description)
            var['cpp'] = translator.parse()
            var['dependencies'] = translator.dependencies()

    return { 'spike_cond': raw_spike_code,
             'spike_cond_dependencies': spike_code_dependencies, 
             'spike_reset': reset_desc}
Пример #8
0
 def idx_target(val):
     target = val.group(1).strip()
     if target == '':
         _print(eq)
         _error('pre.sum() requires one argument.')
         exit(0)
     rep = '_pre_sum_' + target.strip()
     dependencies['pre'].append('sum(' + target + ')')
     untouched[rep] = '_sum_' + target + '%(pre_index)s'
     return rep
Пример #9
0
 def idx_target(val):
     target = val.group(1).strip()
     if target == '':
         _print(eq)
         _error('post.sum() requires one argument.')
         exit(0)
     dependencies['post'].append('sum('+target+')')
     rep = '_post_sum_' + target.strip()
     untouched[rep] = '%(post_prefix)s_sum_' + target + '%(post_index)s'
     return rep
Пример #10
0
 def __add__(self, other):
     """Allows to join two neurons if they have the same population."""
     if other.population == self.population:
         if isinstance(other, IndividualNeuron):
             return PopulationView(self.population, list(set([self.rank, other.rank])))
         elif isinstance(other, PopulationView):
             return PopulationView(self.population, list(set([self.rank] + other.ranks)))
     else:
         _error("can only add two PopulationViews of the same population.")
         return None
Пример #11
0
def extract_functions(description, local_global=False):
    """ Extracts all functions from a multiline description."""
    if not description:
        return []
    # Split the multilines into individual lines
    function_list = process_equations(description)
    # Process each function
    functions = []
    for f in function_list:
        eq = f['eq']
        var_name, content = eq.split('=', 1)
        # Extract the name of the function
        func_name = var_name.split('(', 1)[0].strip()
        # Extract the arguments
        arguments = (var_name.split('(', 1)[1].split(')')[0]).split(',')
        arguments = [arg.strip() for arg in arguments]
        # Extract their types
        types = f['constraint']
        if types == '':
            return_type = 'double'
            arg_types = ['double' for a in arguments]
        else:
            types = types.split(',')
            return_type = types[0].strip()
            arg_types = [arg.strip() for arg in types[1:]]
        if not len(arg_types) == len(arguments):
            _error('You must specify exactly the types of return value and arguments in ' + eq)

        arg_line = ""
        for i in range(len(arguments)):
            arg_line += arg_types[i] + " " + arguments[i]
            if not i == len(arguments) -1:
                arg_line += ', '

        # Process the content
        eq2, condition = extract_ite('', content, {'attributes': [], 'local':[], 'global': [], 'variables': [], 'parameters': []}, split=False)
        if condition == []:
            parser = FunctionParser(content, arguments)
            parsed_content = parser.parse()
        else:
            parser = FunctionParser(content, arguments)
            parsed_content = translate_ITE("", eq2, condition, {'attributes': [], 'local':[], 'global': [], 'variables': [], 'parameters': []}, {}, split=False)

        # Create the one-liner
        fdict = {'name': func_name, 'args': arguments, 'content': content, 'return_type': return_type, 'arg_types': arg_types, 'parsed_content': parsed_content, 'arg_line': arg_line}
        if not local_global: # local to a class
            oneliner = """%(return_type)s %(name)s (%(arg_line)s) {return %(parsed_content)s ;};
""" % fdict
        else: # global
            oneliner = """inline %(return_type)s %(name)s (%(arg_line)s) {return %(parsed_content)s ;};
""" % fdict
        fdict['cpp'] = oneliner
        functions.append(fdict)
    return functions
Пример #12
0
 def parse_expression(self, expression, local_dict):
     " Parses a string with respect to the vocabulary defined in local_dict."
     
     try:
         res =  parse_expr(transform_condition(expression),
             local_dict = local_dict,
             transformations = (standard_transformations + (convert_xor,)) 
         )
     except Exception as e:
         _print(e)
         _error('Can not analyse the expression :' +  str(expression))
         exit(0)
     else:
         return res
Пример #13
0
    def parse_expression(self, expression, local_dict):
        " Parses a string with respect to the vocabulary defined in local_dict."

        try:
            res = parse_expr(transform_condition(expression),
                             local_dict=local_dict,
                             transformations=(standard_transformations +
                                              (convert_xor, )))
        except Exception as e:
            _print(e)
            _error('Can not analyse the expression :' + str(expression))
            exit(0)
        else:
            return res
Пример #14
0
    def process_variables(self):

        # Check if the numerical method is the same for all ODEs
        methods = []
        for var in self.variables:
            methods.append(var['method'])
        if len(list(set(methods))) > 1: # mixture of methods
            _error('Can not mix different numerical methods when solving a coupled system of equations.')
            exit(0)
        else:
            method = methods[0]

        if method == 'implicit' or method == 'semiimplicit':
            return self.solve_implicit(self.expression_list)
        elif method == 'midpoint': 
            return self.solve_midpoint(self.expression_list)
Пример #15
0
 def parse(self):
     try:
         if self.type == 'ODE':
             code = self.analyse_ODE(self.expression)
         elif self.type == 'cond':
             code = self.analyse_condition(self.expression)
         elif self.type == 'inc':
             code = self.analyse_increment(self.expression)
         elif self.type == 'return':
             code = self.analyse_return(self.expression)
         elif self.type == 'simple':
             code = self.analyse_assignment(self.expression)
     except Exception as e:
         _print(e)
         _error('can not analyse', self.expression)
     return code
Пример #16
0
def extract_targets(variables):
    targets = []
    for var in variables:
        # Rate-coded neurons
        code = re.findall('(?P<pre>[^\w.])sum\(\s*([^()]+)\s*\)', var['eq'])
        for l, t in code:
            if t.strip() == '':
                _print(var['eq'])
                _error('sum() must have one argument.')
                exit(0)
            targets.append(t.strip())
        # Spiking neurons
        code = re.findall('([^\w.])g_([\w]+)', var['eq'])
        for l, t in code:
            targets.append(t.strip())

    return list(set(targets))
Пример #17
0
def extract_targets(variables):
    targets = []
    for var in variables:
        # Rate-coded neurons
        code = re.findall('(?P<pre>[^\w.])sum\(\s*([^()]+)\s*\)', var['eq'])
        for l, t in code:
            if t.strip() == '':
                _print(var['eq'])
                _error('sum() must have one argument.')
                exit(0)
            targets.append(t.strip())
        # Spiking neurons
        code = re.findall('([^\w.])g_([\w]+)', var['eq'])
        for l, t in code:
            targets.append(t.strip())

    return list(set(targets))
Пример #18
0
    def process_variables(self):

        # Check if the numerical method is the same for all ODEs
        methods = []
        for var in self.variables:
            methods.append(var['method'])
        if len(list(set(methods))) > 1:  # mixture of methods
            _error(
                'Can not mix different numerical methods when solving a coupled system of equations.'
            )
            exit(0)
        else:
            method = methods[0]

        if method == 'implicit' or method == 'semiimplicit':
            return self.solve_implicit(self.expression_list)
        elif method == 'midpoint':
            return self.solve_midpoint(self.expression_list)
Пример #19
0
def extract_spike_variable(description):

    cond = prepare_string(description['raw_spike'])
    if len(cond) > 1:
        _error('The spike condition must be a single expression')
        _print(description['raw_spike'])
        exit(0)

    translator = Equation('raw_spike_cond', cond[0].strip(), description)
    raw_spike_code = translator.parse()

    reset_desc = []
    if 'raw_reset' in description.keys() and description['raw_reset']:
        reset_desc = process_equations(description['raw_reset'])
        for var in reset_desc:
            translator = Equation(var['name'], var['eq'], description)
            var['cpp'] = translator.parse()

    return {'spike_cond': raw_spike_code, 'spike_reset': reset_desc}
Пример #20
0
    def implicit(self, expression):
        "Full implicit method, linearising for example (V - E)^2, but this is not desired."

        # Transform the gradient into a difference TODO: more robust...
        new_expression = expression.replace('d' + self.name, '_t_gradient_')
        new_expression = re.sub(r'([^\w]+)' + self.name + r'([^\w]+)',
                                r'\1_' + self.name + r'\2', new_expression)
        new_expression = new_expression.replace(
            '_t_gradient_', '(_' + self.name + ' - ' + self.name + ')')

        # Add a sympbol for the next value of the variable
        new_var = Symbol('_' + self.name)
        self.local_dict['_' + self.name] = new_var

        # Parse the string
        analysed = parse_expr(new_expression,
                              local_dict=self.local_dict,
                              transformations=(standard_transformations +
                                               (convert_xor, )))
        self.analysed = analysed

        # Solve the equation for delta_mp
        solved = solve(analysed, new_var)
        if len(solved) > 1:
            _print(self.expression)
            _error('the ODE is not linear, can not use the implicit method.')
            exit(0)
        else:
            solved = solved[0]

        equation = simplify(collect(solved, self.local_dict['dt']))

        # Obtain C code
        variable_name = self.c_code(self.local_dict[self.name])


        explicit_code = 'double _' + self.name + ' = '\
                        +  self.c_code(equation) + ';'
        switch = variable_name + ' = _' + self.name + ' ;'

        # Return result
        return [explicit_code, switch]
Пример #21
0
    def __init__(self, tau_rec=100.0, tau_facil=0.01, U=0.5):

        if tau_facil <= 0.0:
            _error(
                'tau_facil must be positive. Choose a very small value if you have to, or derive a new synapse.'
            )
            exit(0)

        parameters = """
    tau_rec = %(tau_rec)s
    tau_facil = %(tau_facil)s
    U = %(U)s
    """ % {
            'tau_rec': tau_rec,
            'tau_facil': tau_facil,
            'U': U
        }
        equations = """
    dx/dt = (1 - x)/tau_rec : init = 1.0, event-driven
    du/dt = (U - u)/tau_facil : init = %(U)s, event-driven   
    """ % {
            'tau_rec': tau_rec,
            'tau_facil': tau_facil,
            'U': U
        }
        pre_spike = """
    g_target += w * u * x
    x *= (1 - u)
    u += U * (1 - u)
    """

        Synapse.__init__(
            self,
            parameters=parameters,
            equations=equations,
            pre_spike=pre_spike,
            name="Short-term plasticity",
            description=
            "Synapse exhibiting short-term facilitation and depression, implemented using the model of Tsodyks, Markram et al."
        )
        # For reporting
        self._instantiated.append(True)
Пример #22
0
def extract_globalops_neuron(name, eq, description):
    """ Replaces global operations (mean(r), etc)  with arbitrary names and 
    returns a dictionary of changes.
    """
    untouched = {}
    globs = []
    # Global ops
    glop_names = ['min', 'max', 'mean', 'norm1', 'norm2']
    for op in glop_names:
        matches = re.findall('([^\w]*)' + op + '\(([\s\w]*)\)', eq)
        for pre, var in matches:
            if var.strip() in description['local']:
                globs.append({'function': op, 'variable': var.strip()})
                oldname = op + '(' + var + ')'
                newname = '_' + op + '_' + var.strip()
                eq = eq.replace(oldname, newname)
                untouched[newname] = '_' + op + '_' + var.strip()
            else:
                _error(eq + '\nThere is no local attribute ' + var + '.')
                exit(0)
    return eq, untouched, globs
Пример #23
0
def extract_globalops_neuron(name, eq, description):
    """ Replaces global operations (mean(r), etc)  with arbitrary names and 
    returns a dictionary of changes.
    """
    untouched = {}    
    globs = []  
    # Global ops
    glop_names = ['min', 'max', 'mean', 'norm1', 'norm2']
    for op in glop_names:
        matches = re.findall('([^\w]*)'+op+'\(([\s\w]*)\)', eq)
        for pre, var in matches:
            if var.strip() in description['local']:
                globs.append({'function': op, 'variable': var.strip()})
                oldname = op + '(' + var + ')'
                newname = '_' + op + '_' + var.strip() 
                eq = eq.replace(oldname, newname)
                untouched[newname] = '_' + op + '_' + var.strip()
            else:
                _error(eq+'\nThere is no local attribute '+var+'.')
                exit(0)
    return eq, untouched, globs
Пример #24
0
def extract_parameters(description, extra_values={}):
    """ Extracts all variable information from a multiline description."""
    parameters = []
    # Split the multilines into individual lines
    parameter_list = prepare_string(description)

    # Analyse all variables
    for definition in parameter_list:
        # Check if there are flags after the : symbol
        equation, constraint = split_equation(definition)
        # Extract the name of the variable
        name = extract_name(equation)
        if name in ['_undefined', ""]:
            _error("Definition can not be analysed: " + equation)
            exit(0)

        # Process constraint
        bounds, flags, ctype, init = extract_boundsflags(
            constraint, equation, extra_values)

        # Determine locality
        for f in ['population', 'postsynaptic']:
            if f in flags:
                locality = 'global'
                break
        else:
            locality = 'local'

        # Store the result
        desc = {
            'name': name,
            'locality': locality,
            'eq': equation,
            'bounds': bounds,
            'flags': flags,
            'ctype': ctype,
            'init': init,
        }
        parameters.append(desc)
    return parameters
Пример #25
0
    def implicit(self, expression):
        "Full implicit method, linearising for example (V - E)^2, but this is not desired."

        # Transform the gradient into a difference TODO: more robust...
        new_expression = expression.replace('d'+self.name, '_t_gradient_')
        new_expression = re.sub(r'([^\w]+)'+self.name+r'([^\w]+)', r'\1_'+self.name+r'\2', new_expression)
        new_expression = new_expression.replace('_t_gradient_', '(_'+self.name+' - '+self.name+')')

        # Add a sympbol for the next value of the variable
        new_var = Symbol('_'+self.name)
        self.local_dict['_'+self.name] = new_var

        # Parse the string
        analysed = parse_expr(new_expression,
            local_dict = self.local_dict,
            transformations = (standard_transformations + (convert_xor,))
        )
        self.analysed = analysed

        # Solve the equation for delta_mp
        solved = solve(analysed, new_var)
        if len(solved) > 1:
            _print(self.expression)
            _error('the ODE is not linear, can not use the implicit method.')
            exit(0)
        else:
            solved = solved[0]

        equation = simplify(collect( solved, self.local_dict['dt']))

        # Obtain C code
        variable_name = self.c_code(self.local_dict[self.name])


        explicit_code = 'double _' + self.name + ' = '\
                        +  self.c_code(equation) + ';'
        switch = variable_name + ' = _' + self.name + ' ;'

        # Return result
        return [explicit_code, switch]
Пример #26
0
def extract_spike_variable(description):

    cond = prepare_string(description['raw_spike'])
    if len(cond) > 1:
        _error('The spike condition must be a single expression')
        _print(description['raw_spike'])
        exit(0)
        
    translator = Equation('raw_spike_cond', 
                            cond[0].strip(), 
                            description)
    raw_spike_code = translator.parse()
    
    reset_desc = []
    if 'raw_reset' in description.keys() and description['raw_reset']:
        reset_desc = process_equations(description['raw_reset'])
        for var in reset_desc:
            translator = Equation(var['name'], var['eq'], 
                                  description)
            var['cpp'] = translator.parse() 
    
    return { 'spike_cond': raw_spike_code, 'spike_reset': reset_desc}
Пример #27
0
def extract_name(equation, left=False):
    " Extracts the name of a parameter/variable by looking the left term of an equation."
    equation = equation.replace(' ','')
    if not left: # there is potentially an equal sign
        try:
            name = equation.split('=')[0]
        except: # No equal sign. Eg: baseline : init=0.0
            return equation.strip()

        # Search for increments
        operators = ['+=', '-=', '*=', '/=', '>=', '<=']
        for op in operators:
            if op in equation:
                return equation.split(op)[0]
    else:
        name = equation.strip()
        # Search for increments
        operators = ['+', '-', '*', '/']
        for op in operators:
            if equation.endswith(op):
                return equation.split(op)[0]
    # Check for error
    if name.strip() == "":
        _error('the variable name can not be extracted from ' + equation)

    # Search for any operation in the left side
    operators = ['+', '-', '*', '/']
    ode = False
    for op in operators:
        if not name.find(op) == -1:
            ode = True
    if not ode: # variable name is alone on the left side
        return name
    # ODE: the variable name is between d and /dt
    name = re.findall("d([\w]+)/dt", name)
    if len(name) == 1:
        return name[0].strip()
    else:
        return '_undefined'
Пример #28
0
def extract_name(equation, left=False):
    " Extracts the name of a parameter/variable by looking the left term of an equation."
    equation = equation.replace(' ', '')
    if not left:  # there is potentially an equal sign
        try:
            name = equation.split('=')[0]
        except:  # No equal sign. Eg: baseline : init=0.0
            return equation.strip()

        # Search for increments
        operators = ['+=', '-=', '*=', '/=', '>=', '<=']
        for op in operators:
            if op in equation:
                return equation.split(op)[0]
    else:
        name = equation.strip()
        # Search for increments
        operators = ['+', '-', '*', '/']
        for op in operators:
            if equation.endswith(op):
                return equation.split(op)[0]
    # Check for error
    if name.strip() == "":
        _error('the variable name can not be extracted from ' + equation)
        exit(0)
    # Search for any operation in the left side
    operators = ['+', '-', '*', '/']
    ode = False
    for op in operators:
        if not name.find(op) == -1:
            ode = True
    if not ode:  # variable name is alone on the left side
        return name
    # ODE: the variable name is between d and /dt
    name = re.findall("d([\w]+)/dt", name)
    if len(name) == 1:
        return name[0].strip()
    else:
        return '_undefined'
Пример #29
0
def extract_parameters(description, extra_values={}):
    """ Extracts all variable information from a multiline description."""
    parameters = []
    # Split the multilines into individual lines
    parameter_list = prepare_string(description)
    
    # Analyse all variables
    for definition in parameter_list:
        # Check if there are flags after the : symbol
        equation, constraint = split_equation(definition)
        # Extract the name of the variable
        name = extract_name(equation)
        if name in ['_undefined', ""]:
            _error("Definition can not be analysed: " + equation)
            exit(0)
            
        # Process constraint
        bounds, flags, ctype, init = extract_boundsflags(constraint, equation, extra_values)

        # Determine locality
        for f in ['population', 'postsynaptic']:
            if f in flags:
                locality = 'global'
                break
        else:
            locality = 'local' 
            
        # Store the result
        desc = {'name': name,
                'locality': locality,
                'eq': equation,
                'bounds': bounds,
                'flags' : flags,
                'ctype' : ctype,
                'init' : init,
                }
        parameters.append(desc)              
    return parameters
Пример #30
0
    def eventdriven(self, expression):
        # Standardize the equation
        real_tau, stepsize, steadystate = self.standardize_ODE(expression)

        if real_tau == None: # the equation can not be standardized
            _print(self.expression)
            _error('The equation is not a linear ODE and can not be evaluated exactly.')
            exit(0)

        # Check the steady state is not dependent on other variables
        for var in self.variables:
            if self.local_dict[var] in steadystate.atoms():
                _print(self.expression)
                _error('The equation can not depend on other variables ('+var+') to be evaluated exactly.')
                exit(0)

        # Obtain C code
        variable_name = self.c_code(self.local_dict[self.name])
        steady = self.c_code(steadystate)
        if steady == '0':
            code = variable_name + '*= exp(dt*(_last_event[i][j] - (t))/(' + self.c_code(real_tau) + '));'
        else:
            code = variable_name + ' = ' + steady + ' + (' + variable_name + ' - ' + steady + ')*exp(dt*(_last_event[i][j] - (t))/(' + self.c_code(real_tau) + '));'
        return code
Пример #31
0
    def standardize_ODE(self, expression):
        """ Transform any 1rst order ODE into the standardized form:
    
        tau * dV/dt + V = S
    
        Non-linear functions of V are left in the steady-state argument.
    
        Returns:
    
            * tau : the time constant associated to the standardized equation.
    
            * stepsize: a simplified version of dt/tau.
    
            * steadystate: the right term of the equation after standardization
        """
        # Replace the gradient with a temporary variable
        expression = expression.replace('d' + self.name +'/dt', '_gradvar_') # TODO: robust to spaces
    
        # Add the gradient sympbol
        grad_var = Symbol('_gradvar_')
    
        # Parse the string
        analysed = self.parse_expression(expression,
            local_dict = self.local_dict
        )
        self.analysed = analysed
    
        # Collect factor on the gradient and main variable A*dV/dt + B*V = C
        expanded = analysed.expand(
            modulus=None, power_base=False, power_exp=False, 
            mul=True, log=False, multinomial=False)

        # Make sure the expansion went well
        collected_var = collect(expanded, self.local_dict[self.name], evaluate=False, exact=False)
        if self.method == 'exponential':
            if not self.local_dict[self.name] in collected_var.keys() or len(collected_var)>2:
                _print(self.expression)
                _error('The exponential method is reserved for linear first-order ODEs of the type tau*d'+ self.name+'/dt + '+self.name+' = f(t). Use the explicit method instead.')
                exit(0)           
    
        factor_var = collected_var[self.local_dict[self.name]]
        
        collected_gradient = collect(expand(analysed, grad_var), grad_var, evaluate=False, exact=True)
        if grad_var in collected_gradient.keys():
            factor_gradient = collected_gradient[grad_var]
        else:
            factor_gradient = S(1.0)
    
        # Real time constant when using the form tau*dV/dt + V = A
        real_tau = factor_gradient / factor_var
    
        # Normalized equation tau*dV/dt + V = A
        normalized = analysed / factor_var
    
        # Steady state A
        steadystate = together(real_tau * grad_var + self.local_dict[self.name] - normalized)

        # Stepsize
        stepsize = together(self.local_dict['dt']/real_tau)
    
        return real_tau, stepsize, steadystate
Пример #32
0
    def standardize_ODE(self, expression):
        """ Transform any 1rst order ODE into the standardized form:
    
        tau * dV/dt + V = S
    
        Non-linear functions of V are left in the steady-state argument.
    
        Returns:
    
            * tau : the time constant associated to the standardized equation.
    
            * stepsize: a simplified version of dt/tau.
    
            * steadystate: the right term of the equation after standardization
        """
        # Replace the gradient with a temporary variable
        expression = expression.replace('d' + self.name + '/dt',
                                        '_gradvar_')  # TODO: robust to spaces

        # Add the gradient sympbol
        grad_var = Symbol('_gradvar_')

        # Parse the string
        analysed = self.parse_expression(expression,
                                         local_dict=self.local_dict)
        self.analysed = analysed

        # Collect factor on the gradient and main variable A*dV/dt + B*V = C
        expanded = analysed.expand(modulus=None,
                                   power_base=False,
                                   power_exp=False,
                                   mul=True,
                                   log=False,
                                   multinomial=False)

        # Make sure the expansion went well
        collected_var = collect(expanded,
                                self.local_dict[self.name],
                                evaluate=False,
                                exact=False)
        if self.method == 'exponential':
            if not self.local_dict[self.name] in collected_var.keys() or len(
                    collected_var) > 2:
                _print(self.expression)
                _error(
                    'The exponential method is reserved for linear first-order ODEs of the type tau*d'
                    + self.name + '/dt + ' + self.name +
                    ' = f(t). Use the explicit method instead.')
                exit(0)

        factor_var = collected_var[self.local_dict[self.name]]

        collected_gradient = collect(expand(analysed, grad_var),
                                     grad_var,
                                     evaluate=False,
                                     exact=True)
        if grad_var in collected_gradient.keys():
            factor_gradient = collected_gradient[grad_var]
        else:
            factor_gradient = S(1.0)

        # Real time constant when using the form tau*dV/dt + V = A
        real_tau = factor_gradient / factor_var

        # Normalized equation tau*dV/dt + V = A
        normalized = analysed / factor_var

        # Steady state A
        steadystate = together(real_tau * grad_var +
                               self.local_dict[self.name] - normalized)

        # Stepsize
        stepsize = together(self.local_dict['dt'] / real_tau)

        return real_tau, stepsize, steadystate
Пример #33
0
    def __init__(self,
                 parameters="",
                 equations="",
                 psp=None,
                 operation='sum',
                 pre_spike=None,
                 post_spike=None,
                 functions=None,
                 pruning=None,
                 creating=None,
                 name=None,
                 description=None,
                 extra_values={}):
        """ 
        *Parameters*:
        
            * **parameters**: parameters of the neuron and their initial value.
            * **equations**: equations defining the temporal evolution of variables.
            * **psp**: influence of a single synapse on the post-synaptic neuron (default for rate-coded: w*pre.r).
            * **operation**: operation (sum, max, min, mean) performed by the post-synaptic neuron on the individual psp (rate-coded only, default=sum).
            * **pre_spike**: updating of variables when a pre-synaptic spike is received (spiking only).
            * **post_spike**: updating of variables when a post-synaptic spike is emitted (spiking only).
            * **functions**: additional functions used in the equations.
            * **name**: name of the synapse type (used for reporting only).
            * **description**: short description of the synapse type (used for reporting).

        """

        # Store the parameters and equations
        self.parameters = parameters
        self.equations = equations
        self.functions = functions
        self.pre_spike = pre_spike
        self.post_spike = post_spike
        self.psp = psp
        self.operation = operation
        self.extra_values = extra_values
        self.pruning = pruning
        self.creating = creating

        # Type of the synapse TODO: smarter
        self.type = 'spike' if pre_spike else 'rate'

        # Check the operation
        if self.type == 'spike' and self.operation != 'sum':
            _error(
                'Spiking synapses can only perform a sum of presynaptic potentials.'
            )
            exit(0)
        if not self.operation in ['sum', 'min', 'max', 'mean']:
            _error(
                'The only operations permitted are: sum (default), min, max, mean.'
            )
            exit(0)

        # Description
        self.description = None

        # Reporting
        if not hasattr(self, '_instantiated'):  # User-defined
            _objects['synapses'].append(self)
        elif len(self._instantiated) == 0:  # First instantiated of the class
            _objects['synapses'].append(self)

        if name:
            self.name = name
        else:
            self.name = self._default_names[self.type]

        if description:
            self.short_description = description
        else:
            if self.type == 'spike':
                self.short_description = "Instantaneous increase of the post-synaptic conductance after a spike is received."
            else:
                self.short_description = "Weighted sum of firing rates."
Пример #34
0
def analyse_synapse(synapse):
    """ Performs the analysis for a single synapse."""
    concurrent_odes = []

    # Store basic information
    description = {
        'object': 'synapse',
        'type': synapse.type,
        'raw_parameters': synapse.parameters,
        'raw_equations': synapse.equations,
        'raw_functions': synapse.functions
    }

    if synapse.psp:
        description['raw_psp'] = synapse.psp
    elif synapse.type == 'rate':
        description['raw_psp'] = "w*pre.r"

    if synapse.type == 'spike': # Additionally store pre_spike and post_spike
        description['raw_pre_spike'] = synapse.pre_spike
        description['raw_post_spike'] = synapse.post_spike

    # Extract parameters and variables names
    parameters = extract_parameters(synapse.parameters, synapse.extra_values)
    variables = extract_variables(synapse.equations)

    # Extract functions
    functions = extract_functions(synapse.functions, False)

    # Check the presence of w
    description['plasticity'] = False
    for var in parameters + variables:
        if var['name'] == 'w':
            break
    else:
        parameters.append(
            #TODO: is this exception really needed? Maybe we could find
            #      a better solution instead of the hard-coded 'w' ... [hdin: 26.05.2015]
            {'name': 'w', 'bounds': {}, 'ctype': 'double', 'init': 0.0, 'flags': [], 'eq': 'w=0.0', 'locality': 'local'}
        )

    # Find out a plasticity rule
    for var in variables:
        if var['name'] == 'w':
            description['plasticity'] = True
            break

    # Build lists of all attributes (param+var), which are local or global
    attributes, local_var, global_var = get_attributes(parameters, variables)

    # Test if attributes are declared only once
    if len(attributes) != len(list(set(attributes))):
        _error('Attributes must be declared only once.', attributes)


    # Add this info to the description
    description['parameters'] = parameters
    description['variables'] = variables
    description['functions'] = functions
    description['attributes'] = attributes
    description['local'] = local_var
    description['global'] = global_var
    description['global_operations'] = []

    description['pre_global_operations'] = []
    description['post_global_operations'] = []

    # Extract RandomDistribution objects
    description['random_distributions'] = extract_randomdist(description)

    # Extract event-driven info
    if description['type'] == 'spike':
        # pre_spike event
        description['pre_spike'] = extract_pre_spike_variable(description)
        for var in description['pre_spike']:
            if var['name'] in ['g_target']: # Already dealt with
                continue
            for avar in description['variables']:
                if var['name'] == avar['name']:
                    break
            else: # not defined already
                description['variables'].append(
                {'name': var['name'], 'bounds': var['bounds'], 'ctype': var['ctype'], 'init': var['init'],
                 'locality': var['locality'],
                 'flags': [], 'transformed_eq': '', 'eq': '',
                 'cpp': '', 'switch': '', 'untouched': '', 'method':'explicit'}
                )
                description['local'].append(var['name'])
                description['attributes'].append(var['name'])

        # post_spike event
        description['post_spike'] = extract_post_spike_variable(description)
        for var in description['post_spike']:
            if var['name'] in ['g_target', 'w']: # Already dealt with
                continue
            for avar in description['variables']:
                if var['name'] == avar['name']:
                    break
            else: # not defined already
                description['variables'].append(
                {'name': var['name'], 'bounds': var['bounds'], 'ctype': var['ctype'], 'init': var['init'],
                 'locality': var['locality'],
                 'flags': [], 'transformed_eq': '', 'eq': '',
                 'cpp': '', 'switch': '', 'untouched': '', 'method':'explicit'}
                )
                description['local'].append(var['name'])
                description['attributes'].append(var['name'])

    # Variables names for the parser which should be left untouched
    untouched = {}
    description['dependencies'] = {'pre': [], 'post': []}

    # Iterate over all variables
    for variable in description['variables']:
        eq = variable['transformed_eq']

        # Event-driven variables
        if eq.strip() == '':
            continue

        # Extract global operations
        eq, untouched_globs, global_ops = extract_globalops_synapse(variable['name'], eq, description)
        description['pre_global_operations'] += global_ops['pre']
        description['post_global_operations'] += global_ops['post']

        # Extract pre- and post_synaptic variables
        eq, untouched_var, dependencies = extract_prepost(variable['name'], eq, description)

        description['dependencies']['pre'] += dependencies['pre']
        description['dependencies']['post'] += dependencies['post']

        # Extract if-then-else statements
        eq, condition = extract_ite(variable['name'], eq, description)

        # Add the untouched variables to the global list
        for name, val in untouched_globs.items():
            if not name in untouched.keys():
                untouched[name] = val
        for name, val in untouched_var.items():
            if not name in untouched.keys():
                untouched[name] = val

        # Save the tranformed equation
        variable['transformed_eq'] = eq

        # Find the numerical method if any
        method = find_method(variable)

        # Process the bounds
        if 'min' in variable['bounds'].keys():
            if isinstance(variable['bounds']['min'], str):
                translator = Equation(variable['name'], variable['bounds']['min'],
                                      description,
                                      type = 'return',
                                      untouched = untouched.keys())
                variable['bounds']['min'] = translator.parse().replace(';', '')

        if 'max' in variable['bounds'].keys():
            if isinstance(variable['bounds']['max'], str):
                translator = Equation(variable['name'], variable['bounds']['max'],
                                      description,
                                      type = 'return',
                                      untouched = untouched.keys())
                variable['bounds']['max'] = translator.parse().replace(';', '')

        # Analyse the equation
        if condition == []: # Call Equation
            translator = Equation(variable['name'], eq,
                                  description,
                                  method = method,
                                  untouched = untouched.keys())
            code = translator.parse()
            dependencies = translator.dependencies()

        else: # An if-then-else statement
            code = translate_ITE(variable['name'], eq, condition, description,
                    untouched)
            dependencies = []

        if isinstance(code, str):
            cpp_eq = code
            switch = None
        else: # ODE
            cpp_eq = code[0]
            switch = code[1]

        # Replace untouched variables with their original name
        # print cpp_eq
        for prev, new in untouched.items():
            cpp_eq = cpp_eq.replace(prev, new)
        # print cpp_eq

        # Replace local functions
        for f in description['functions']:
            cpp_eq = re.sub(r'([^\w]*)'+f['name']+'\(', r'\1'+ f['name'] + '(', ' ' + cpp_eq).strip()

        # Store the result
        variable['cpp'] = cpp_eq # the C++ equation
        variable['switch'] = switch # switch value id ODE
        variable['untouched'] = untouched # may be needed later
        variable['method'] = method # may be needed later
        variable['dependencies'] = dependencies # may be needed later

        # If the method is implicit or midpoint, the equations must be solved concurrently (depend on v[t+1])
        if method in ['implicit', 'midpoint']:
            concurrent_odes.append(variable)

    # After all variables are processed, do it again if they are concurrent
    if len(concurrent_odes) > 1 :
        solver = CoupledEquations(description, concurrent_odes)
        new_eqs = solver.process_variables()
        for idx, variable in enumerate(description['variables']):
            for new_eq in new_eqs:
                if variable['name'] == new_eq['name']:
                    description['variables'][idx] = new_eq

    # Translate the psp code if any
    if 'raw_psp' in description.keys():
        psp = {'eq' : description['raw_psp'].strip() }
        # Extract global operations
        eq, untouched_globs, global_ops = extract_globalops_synapse('psp', " " + psp['eq'] + " ", description)
        description['pre_global_operations'] += global_ops['pre']
        description['post_global_operations'] += global_ops['post']
        # Replace pre- and post_synaptic variables
        eq, untouched, dependencies = extract_prepost('psp', eq, description)
        description['dependencies']['pre'] += dependencies['pre']
        description['dependencies']['post'] += dependencies['post']
        for name, val in untouched_globs.items():
            if not name in untouched.keys():
                untouched[name] = val
        # Extract if-then-else statements
        eq, condition = extract_ite('psp', eq, description, split=False)
        # Analyse the equation
        if condition == []:
            translator = Equation('psp', eq,
                                  description,
                                  method = 'explicit',
                                  untouched = untouched.keys(),
                                  type='return')
            code = translator.parse()
        else:
            code = translate_ITE('psp', eq, condition, description, untouched,
                                  split=False)

        # Replace untouched variables with their original name
        for prev, new in untouched.items():
            code = code.replace(prev, new)

        # Store the result
        psp['cpp'] = code
        description['psp'] = psp

    # Process event-driven info
    if description['type'] == 'spike':
        for variable in description['pre_spike'] + description['post_spike']:
            # Find plasticity
            if variable['name'] == 'w':
                description['plasticity'] = True
            # Retrieve the equation
            eq = variable['eq']
            # Extract if-then-else statements
            eq, condition = extract_ite(variable['name'], eq, description)
            # Analyse the equation
            if condition == []:
                translator = Equation(variable['name'], eq,
                                      description,
                                      method = 'explicit',
                                      untouched = {})
                code = translator.parse()
            else:
                code = translate_ITE(variable['name'], eq, condition, description, {}, split=True)
            # Store the result
            variable['cpp'] = code # the C++ equation

            # Process the bounds
            if 'min' in variable['bounds'].keys():
                if isinstance(variable['bounds']['min'], str):
                    translator = Equation(
                                    variable['name'],
                                    variable['bounds']['min'],
                                    description,
                                    type = 'return',
                                    untouched = untouched )
                    variable['bounds']['min'] = translator.parse().replace(';', '')

            if 'max' in variable['bounds'].keys():
                if isinstance(variable['bounds']['max'], str):
                    translator = Equation(
                                    variable['name'],
                                    variable['bounds']['max'],
                                    description,
                                    type = 'return',
                                    untouched = untouched)
                    variable['bounds']['max'] = translator.parse().replace(';', '')

    # Structural plasticity
    if synapse.pruning:
        description['pruning'] = extract_structural_plasticity(synapse.pruning, description)
    if synapse.creating:
        description['creating'] = extract_structural_plasticity(synapse.creating, description)

    return description
Пример #35
0
def analyse_synapse(synapse):
    """ Performs the analysis for a single synapse."""
    concurrent_odes = []

    # Store basic information
    description = {
        'object': 'synapse',
        'type': synapse.type,
        'raw_parameters': synapse.parameters,
        'raw_equations': synapse.equations,
        'raw_functions': synapse.functions
    }

    if synapse.psp:
        description['raw_psp'] = synapse.psp
    elif synapse.type == 'rate':
        description['raw_psp'] = "w*pre.r"

    if synapse.type == 'spike':  # Additionally store pre_spike and post_spike
        description['raw_pre_spike'] = synapse.pre_spike
        description['raw_post_spike'] = synapse.post_spike

    # Extract parameters and variables names
    parameters = extract_parameters(synapse.parameters, synapse.extra_values)
    variables = extract_variables(synapse.equations)

    # Extract functions
    functions = extract_functions(synapse.functions, False)

    # Check the presence of w
    description['plasticity'] = False
    for var in parameters + variables:
        if var['name'] == 'w':
            break
    else:
        parameters.append(
            #TODO: is this exception really needed? Maybe we could find
            #      a better solution instead of the hard-coded 'w' ... [hdin: 26.05.2015]
            {
                'name': 'w',
                'bounds': {},
                'ctype': 'double',
                'init': 0.0,
                'flags': [],
                'eq': 'w=0.0',
                'locality': 'local'
            })

    # Find out a plasticity rule
    for var in variables:
        if var['name'] == 'w':
            description['plasticity'] = True
            break

    # Build lists of all attributes (param+var), which are local or global
    attributes, local_var, global_var = get_attributes(parameters, variables)

    # Test if attributes are declared only once
    if len(attributes) != len(list(set(attributes))):
        _error('Attributes must be declared only once.', attributes)
        exit(0)

    # Add this info to the description
    description['parameters'] = parameters
    description['variables'] = variables
    description['functions'] = functions
    description['attributes'] = attributes
    description['local'] = local_var
    description['global'] = global_var
    description['global_operations'] = []

    description['pre_global_operations'] = []
    description['post_global_operations'] = []

    # Extract RandomDistribution objects
    description['random_distributions'] = extract_randomdist(description)

    # Extract event-driven info
    if description['type'] == 'spike':
        # pre_spike event
        description['pre_spike'] = extract_pre_spike_variable(description)
        for var in description['pre_spike']:
            if var['name'] in ['g_target']:  # Already dealt with
                continue
            for avar in description['variables']:
                if var['name'] == avar['name']:
                    break
            else:  # not defined already
                description['variables'].append({
                    'name': var['name'],
                    'bounds': var['bounds'],
                    'ctype': var['ctype'],
                    'init': var['init'],
                    'locality': var['locality'],
                    'flags': [],
                    'transformed_eq': '',
                    'eq': '',
                    'cpp': '',
                    'switch': '',
                    'untouched': '',
                    'method': 'explicit'
                })
                description['local'].append(var['name'])
                description['attributes'].append(var['name'])

        # post_spike event
        description['post_spike'] = extract_post_spike_variable(description)
        for var in description['post_spike']:
            if var['name'] in ['g_target', 'w']:  # Already dealt with
                continue
            for avar in description['variables']:
                if var['name'] == avar['name']:
                    break
            else:  # not defined already
                description['variables'].append({
                    'name': var['name'],
                    'bounds': var['bounds'],
                    'ctype': var['ctype'],
                    'init': var['init'],
                    'locality': var['locality'],
                    'flags': [],
                    'transformed_eq': '',
                    'eq': '',
                    'cpp': '',
                    'switch': '',
                    'untouched': '',
                    'method': 'explicit'
                })
                description['local'].append(var['name'])
                description['attributes'].append(var['name'])

    # Variables names for the parser which should be left untouched
    untouched = {}
    description['dependencies'] = {'pre': [], 'post': []}

    # Iterate over all variables
    for variable in description['variables']:
        eq = variable['transformed_eq']

        # Event-driven variables
        if eq.strip() == '':
            continue

        # Extract global operations
        eq, untouched_globs, global_ops = extract_globalops_synapse(
            variable['name'], eq, description)
        description['pre_global_operations'] += global_ops['pre']
        description['post_global_operations'] += global_ops['post']

        # Extract pre- and post_synaptic variables
        eq, untouched_var, dependencies = extract_prepost(
            variable['name'], eq, description)

        description['dependencies']['pre'] += dependencies['pre']
        description['dependencies']['post'] += dependencies['post']

        # Extract if-then-else statements
        eq, condition = extract_ite(variable['name'], eq, description)

        # Add the untouched variables to the global list
        for name, val in untouched_globs.items():
            if not name in untouched.keys():
                untouched[name] = val
        for name, val in untouched_var.items():
            if not name in untouched.keys():
                untouched[name] = val

        # Save the tranformed equation
        variable['transformed_eq'] = eq

        # Find the numerical method if any
        method = find_method(variable)

        # Process the bounds
        if 'min' in variable['bounds'].keys():
            if isinstance(variable['bounds']['min'], str):
                translator = Equation(variable['name'],
                                      variable['bounds']['min'],
                                      description,
                                      type='return',
                                      untouched=untouched.keys())
                variable['bounds']['min'] = translator.parse().replace(';', '')

        if 'max' in variable['bounds'].keys():
            if isinstance(variable['bounds']['max'], str):
                translator = Equation(variable['name'],
                                      variable['bounds']['max'],
                                      description,
                                      type='return',
                                      untouched=untouched.keys())
                variable['bounds']['max'] = translator.parse().replace(';', '')

        # Analyse the equation
        if condition == []:  # Call Equation
            translator = Equation(variable['name'],
                                  eq,
                                  description,
                                  method=method,
                                  untouched=untouched.keys())
            code = translator.parse()
            dependencies = translator.dependencies()

        else:  # An if-then-else statement
            code = translate_ITE(variable['name'], eq, condition, description,
                                 untouched)
            dependencies = []

        if isinstance(code, str):
            cpp_eq = code
            switch = None
        else:  # ODE
            cpp_eq = code[0]
            switch = code[1]

        # Replace untouched variables with their original name
        for prev, new in untouched.items():
            cpp_eq = cpp_eq.replace(prev, new)

        # Replace local functions
        for f in description['functions']:
            cpp_eq = re.sub(r'([^\w]*)' + f['name'] + '\(',
                            r'\1' + f['name'] + '(', ' ' + cpp_eq).strip()

        # Store the result
        variable['cpp'] = cpp_eq  # the C++ equation
        variable['switch'] = switch  # switch value id ODE
        variable['untouched'] = untouched  # may be needed later
        variable['method'] = method  # may be needed later
        variable['dependencies'] = dependencies  # may be needed later

        # If the method is implicit or midpoint, the equations must be solved concurrently (depend on v[t+1])
        if method in ['implicit', 'midpoint']:
            concurrent_odes.append(variable)

    # After all variables are processed, do it again if they are concurrent
    if len(concurrent_odes) > 1:
        solver = CoupledEquations(description, concurrent_odes)
        new_eqs = solver.process_variables()
        for idx, variable in enumerate(description['variables']):
            for new_eq in new_eqs:
                if variable['name'] == new_eq['name']:
                    description['variables'][idx] = new_eq

    # Translate the psp code if any
    if 'raw_psp' in description.keys():
        psp = {'eq': description['raw_psp'].strip()}
        # Extract global operations
        eq, untouched_globs, global_ops = extract_globalops_synapse(
            'psp', " " + psp['eq'] + " ", description)
        description['pre_global_operations'] += global_ops['pre']
        description['post_global_operations'] += global_ops['post']
        # Replace pre- and post_synaptic variables
        eq, untouched, dependencies = extract_prepost('psp', eq, description)
        description['dependencies']['pre'] += dependencies['pre']
        description['dependencies']['post'] += dependencies['post']
        for name, val in untouched_globs.items():
            if not name in untouched.keys():
                untouched[name] = val
        # Extract if-then-else statements
        eq, condition = extract_ite('psp', eq, description, split=False)
        # Analyse the equation
        if condition == []:
            translator = Equation('psp',
                                  eq,
                                  description,
                                  method='explicit',
                                  untouched=untouched.keys(),
                                  type='return')
            code = translator.parse()
        else:
            code = translate_ITE('psp',
                                 eq,
                                 condition,
                                 description,
                                 untouched,
                                 split=False)

        # Replace untouched variables with their original name
        for prev, new in untouched.items():
            code = code.replace(prev, new)

        # Store the result
        psp['cpp'] = code
        description['psp'] = psp

    # Process event-driven info
    if description['type'] == 'spike':
        for variable in description['pre_spike'] + description['post_spike']:
            # Find plasticity
            if variable['name'] == 'w':
                description['plasticity'] = True
            # Retrieve the equation
            eq = variable['eq']
            # Extract if-then-else statements
            eq, condition = extract_ite(variable['name'], eq, description)
            # Analyse the equation
            if condition == []:
                translator = Equation(variable['name'],
                                      eq,
                                      description,
                                      method='explicit',
                                      untouched={})
                code = translator.parse()
            else:
                code = translate_ITE(variable['name'],
                                     eq,
                                     condition,
                                     description, {},
                                     split=True)
            # Store the result
            variable['cpp'] = code  # the C++ equation

            # Process the bounds
            if 'min' in variable['bounds'].keys():
                if isinstance(variable['bounds']['min'], str):
                    translator = Equation(variable['name'],
                                          variable['bounds']['min'],
                                          description,
                                          type='return',
                                          untouched=untouched)
                    variable['bounds']['min'] = translator.parse().replace(
                        ';', '')

            if 'max' in variable['bounds'].keys():
                if isinstance(variable['bounds']['max'], str):
                    translator = Equation(variable['name'],
                                          variable['bounds']['max'],
                                          description,
                                          type='return',
                                          untouched=untouched)
                    variable['bounds']['max'] = translator.parse().replace(
                        ';', '')

    # Structural plasticity
    if synapse.pruning:
        description['pruning'] = extract_structural_plasticity(
            synapse.pruning, description)
    if synapse.creating:
        description['creating'] = extract_structural_plasticity(
            synapse.creating, description)

    return description
Пример #36
0
 def __add__(self, synapse):  
     _error('adding synapse models is not implemented yet.')
Пример #37
0
def extract_functions(description, local_global=False):
    """ Extracts all functions from a multiline description."""
    if not description:
        return []
    # Split the multilines into individual lines
    function_list = process_equations(description)
    # Process each function
    functions = []
    for f in function_list:
        eq = f['eq']
        var_name, content = eq.split('=', 1)
        # Extract the name of the function
        func_name = var_name.split('(', 1)[0].strip()
        # Extract the arguments
        arguments = (var_name.split('(', 1)[1].split(')')[0]).split(',')
        arguments = [arg.strip() for arg in arguments]
        # Extract their types
        types = f['constraint']
        if types == '':
            return_type = 'double'
            arg_types = ['double' for a in arguments]
        else:
            types = types.split(',')
            return_type = types[0].strip()
            arg_types = [arg.strip() for arg in types[1:]]
        if not len(arg_types) == len(arguments):
            _error(
                'You must specify exactly the types of return value and arguments in '
                + eq)
            exit(0)
        arg_line = ""
        for i in range(len(arguments)):
            arg_line += arg_types[i] + " " + arguments[i]
            if not i == len(arguments) - 1:
                arg_line += ', '
        # Process the content
        eq2, condition = extract_ite('', content, None, split=False)
        if condition == []:
            parser = FunctionParser(content, arguments)
            parsed_content = parser.parse()
        else:
            parser = FunctionParser(content, arguments)
            parsed_content = parser.process_ITE(condition)
        # Create the one-liner
        fdict = {
            'name': func_name,
            'args': arguments,
            'content': content,
            'return_type': return_type,
            'arg_types': arg_types,
            'parsed_content': parsed_content,
            'arg_line': arg_line
        }
        if not local_global:  # local to a class
            oneliner = """%(return_type)s %(name)s (%(arg_line)s) {return %(parsed_content)s ;};
""" % fdict
        else:  # global
            oneliner = """inline %(return_type)s %(name)s (%(arg_line)s) {return %(parsed_content)s ;};
""" % fdict
        fdict['cpp'] = oneliner
        functions.append(fdict)
    return functions
Пример #38
0
def extract_structural_plasticity(statement, description):
    # Extract flags
    try:
        eq, constraint = statement.rsplit(':', 1)
        bounds, flags = extract_flags(constraint)
    except:
        eq = statement.strip()
        bounds = {}
        flags = []

    # Extract RD
    rd = None
    for dist in available_distributions:
        matches = re.findall('(?P<pre>[^\w.])' + dist + '\(([^()]+)\)', eq)
        for l, v in matches:
            # Check the arguments
            arguments = v.split(',')
            # Check the number of provided arguments
            if len(arguments) < distributions_arguments[dist]:
                _error(eq)
                _error('The distribution ' + dist + ' requires ' +
                       str(distributions_arguments[dist]) + 'parameters')
            elif len(arguments) > distributions_arguments[dist]:
                _error(eq)
                _error('Too many parameters provided to the distribution ' +
                       dist)
            # Process the arguments
            processed_arguments = ""
            for idx in range(len(arguments)):
                try:
                    arg = float(arguments[idx])
                except:  # A global parameter
                    _error(eq)
                    _error(
                        'Random distributions for creating/pruning synapses must use foxed values.'
                    )
                    exit(0)
                processed_arguments += str(arg)
                if idx != len(arguments) - 1:  # not the last one
                    processed_arguments += ', '
            definition = distributions_equivalents[
                dist] + '(' + processed_arguments + ')'

            # Store its definition
            if rd:
                _error(eq)
                _error('Only one random distribution per equation is allowed.')
                exit(0)

            rd = {
                'name': 'rand_' + str(0),
                'origin': dist + '(' + v + ')',
                'dist': dist,
                'definition': definition,
                'args': processed_arguments,
                'template': distributions_equivalents[dist]
            }

    if rd:
        eq = eq.replace(rd['origin'], 'rd(rng)')

    # Extract pre/post dependencies
    eq, untouched, dependencies = extract_prepost('test', eq, description)

    # Parse code
    translator = Equation('test', eq, description, method='cond', untouched={})

    code = translator.parse()

    # Replace untouched variables with their original name
    for prev, new in untouched.items():
        code = code.replace(prev, new)

    # Add new dependencies
    for dep in dependencies['pre']:
        description['dependencies']['pre'].append(dep)
    for dep in dependencies['post']:
        description['dependencies']['post'].append(dep)

    return {'eq': eq, 'cpp': code, 'bounds': bounds, 'flags': flags, 'rd': rd}
Пример #39
0
def analyse_synapse(synapse):
    """
    Parses the structure and generates code snippets for the synapse type.

    It returns a ``description`` dictionary with the following fields:

    * 'object': 'synapse' by default, to distinguish it from 'neuron'
    * 'type': either 'rate' or 'spiking'
    * 'raw_parameters': provided field
    * 'raw_equations': provided field
    * 'raw_functions': provided field
    * 'raw_psp': provided field
    * 'raw_pre_spike': provided field
    * 'raw_post_spike': provided field
    * 'parameters': list of parameters defined for the synapse type
    * 'variables': list of variables defined for the synapse type
    * 'functions': list of functions defined for the synapse type
    * 'attributes': list of names of all parameters and variables
    * 'local': list of names of parameters and variables which are local to each synapse
    * 'semiglobal': list of names of parameters and variables which are local to each postsynaptic neuron
    * 'global': list of names of parameters and variables which are global to the projection
    * 'random_distributions': list of random number generators used in the neuron equations
    * 'global_operations': list of global operations (min/max/mean...) used in the equations
    * 'pre_global_operations': list of global operations (min/max/mean...) on the pre-synaptic population
    * 'post_global_operations': list of global operations (min/max/mean...) on the post-synaptic population
    * 'pre_spike': list of variables updated after a pre-spike event
    * 'post_spike': list of variables updated after a post-spike event
    * 'dependencies': dictionary ('pre', 'post') of lists of pre (resp. post) variables accessed by the synapse (used for delaying variables)
    * 'psp': dictionary ('eq' and 'psp') for the psp code to be summed
    * 'pruning' and 'creating': statements for structural plasticity


    Each parameter is a dictionary with the following elements:

    * 'bounds': unused
    * 'ctype': 'type of the parameter: 'float', 'double', 'int' or 'bool'
    * 'eq': original equation in text format
    * 'flags': list of flags provided after the :
    * 'init': initial value
    * 'locality': 'local', 'semiglobal' or 'global'
    * 'name': name of the parameter

    Each variable is a dictionary with the following elements:

    * 'bounds': dictionary of bounds ('init', 'min', 'max') provided after the :
    * 'cpp': C++ code snippet updating the variable
    * 'ctype': type of the variable: 'float', 'double', 'int' or 'bool'
    * 'dependencies': list of variable and parameter names on which the equation depends
    * 'eq': original equation in text format
    * 'flags': list of flags provided after the :
    * 'init': initial value
    * 'locality': 'local', 'semiglobal' or 'global'
    * 'method': numericalmethod for ODEs
    * 'name': name of the variable
    * 'pre_loop': ODEs have a pre_loop term for precomputing dt/tau
    * 'switch': ODEs have a switch term
    * 'transformed_eq': same as eq, except special terms (sums, rds) are replaced with a temporary name
    * 'untouched': dictionary of special terms, with their new name as keys and replacement values as values.

    """

    # Store basic information
    description = {
        'object': 'synapse',
        'type': synapse.type,
        'raw_parameters': synapse.parameters,
        'raw_equations': synapse.equations,
        'raw_functions': synapse.functions
    }

    # Psps is what is actually summed over the incoming weights
    if synapse.psp:
        description['raw_psp'] = synapse.psp
    elif synapse.type == 'rate':
        description['raw_psp'] = "w*pre.r"

    # Spiking synapses additionally store pre_spike and post_spike
    if synapse.type == 'spike':
        description['raw_pre_spike'] = synapse.pre_spike
        description['raw_post_spike'] = synapse.post_spike

    # Extract parameters and variables names
    parameters = extract_parameters(synapse.parameters, synapse.extra_values)
    variables = extract_variables(synapse.equations)

    # Extract functions
    functions = extract_functions(synapse.functions, False)

    # Check the presence of w
    description['plasticity'] = False
    for var in parameters + variables:
        if var['name'] == 'w':
            break
    else:
        parameters.append({
            'name': 'w',
            'bounds': {},
            'ctype': config['precision'],
            'init': 0.0,
            'flags': [],
            'eq': 'w=0.0',
            'locality': 'local'
        })

    # Find out a plasticity rule
    for var in variables:
        if var['name'] == 'w':
            description['plasticity'] = True
            break

    # Build lists of all attributes (param+var), which are local or global
    attributes, local_var, global_var, semiglobal_var = get_attributes(
        parameters, variables, neuron=False)

    # Test if attributes are declared only once
    if len(attributes) != len(list(set(attributes))):
        _error('Attributes must be declared only once.', attributes)

    # Add this info to the description
    description['parameters'] = parameters
    description['variables'] = variables
    description['functions'] = functions
    description['attributes'] = attributes
    description['local'] = local_var
    description['semiglobal'] = semiglobal_var
    description['global'] = global_var
    description['global_operations'] = []

    # Lists of global operations needed at the pre and post populations
    description['pre_global_operations'] = []
    description['post_global_operations'] = []

    # Extract RandomDistribution objects
    description['random_distributions'] = extract_randomdist(description)

    # Extract event-driven info
    if description['type'] == 'spike':
        # pre_spike event
        description['pre_spike'] = extract_pre_spike_variable(description)
        for var in description['pre_spike']:
            if var['name'] in ['g_target']:  # Already dealt with
                continue
            for avar in description['variables']:
                if var['name'] == avar['name']:
                    break
            else:  # not defined already
                description['variables'].append({
                    'name': var['name'],
                    'bounds': var['bounds'],
                    'ctype': var['ctype'],
                    'init': var['init'],
                    'locality': var['locality'],
                    'flags': [],
                    'transformed_eq': '',
                    'eq': '',
                    'cpp': '',
                    'switch': '',
                    're_loop': '',
                    'untouched': '',
                    'method': 'explicit'
                })
                description['local'].append(var['name'])
                description['attributes'].append(var['name'])

        # post_spike event
        description['post_spike'] = extract_post_spike_variable(description)
        for var in description['post_spike']:
            if var['name'] in ['g_target', 'w']:  # Already dealt with
                continue
            for avar in description['variables']:
                if var['name'] == avar['name']:
                    break
            else:  # not defined already
                description['variables'].append({
                    'name': var['name'],
                    'bounds': var['bounds'],
                    'ctype': var['ctype'],
                    'init': var['init'],
                    'locality': var['locality'],
                    'flags': [],
                    'transformed_eq': '',
                    'eq': '',
                    'cpp': '',
                    'switch': '',
                    'untouched': '',
                    'method': 'explicit'
                })
                description['local'].append(var['name'])
                description['attributes'].append(var['name'])

    # Variables names for the parser which should be left untouched
    untouched = {}
    description['dependencies'] = {'pre': [], 'post': []}

    # The ODEs may be interdependent (implicit, midpoint), so they need to be passed explicitely to CoupledEquations
    concurrent_odes = []

    # Iterate over all variables
    for variable in description['variables']:
        # Equation
        eq = variable['transformed_eq']
        if eq.strip() == '':
            continue

        # Dependencies must be gathered
        dependencies = []

        # Extract global operations
        eq, untouched_globs, global_ops = extract_globalops_synapse(
            variable['name'], eq, description)
        description['pre_global_operations'] += global_ops['pre']
        description['post_global_operations'] += global_ops['post']
        # Remove doubled entries
        description['pre_global_operations'] = [
            i for n, i in enumerate(description['pre_global_operations'])
            if i not in description['pre_global_operations'][n + 1:]
        ]
        description['post_global_operations'] = [
            i for n, i in enumerate(description['post_global_operations'])
            if i not in description['post_global_operations'][n + 1:]
        ]

        # Extract pre- and post_synaptic variables
        eq, untouched_var, prepost_dependencies = extract_prepost(
            variable['name'], eq, description)

        # Store the pre-post dependencies at the synapse level
        description['dependencies']['pre'] += prepost_dependencies['pre']
        description['dependencies']['post'] += prepost_dependencies['post']
        # and also on the variable for checking
        variable['prepost_dependencies'] = prepost_dependencies

        # Extract if-then-else statements
        eq, condition = extract_ite(variable['name'], eq, description)

        # Add the untouched variables to the global list
        for name, val in untouched_globs.items():
            if not name in untouched.keys():
                untouched[name] = val
        for name, val in untouched_var.items():
            if not name in untouched.keys():
                untouched[name] = val

        # Save the tranformed equation
        variable['transformed_eq'] = eq

        # Find the numerical method if any
        method = find_method(variable)

        # Process the bounds
        if 'min' in variable['bounds'].keys():
            if isinstance(variable['bounds']['min'], str):
                translator = Equation(variable['name'],
                                      variable['bounds']['min'],
                                      description,
                                      type='return',
                                      untouched=untouched.keys())
                variable['bounds']['min'] = translator.parse().replace(';', '')
                dependencies += translator.dependencies()

        if 'max' in variable['bounds'].keys():
            if isinstance(variable['bounds']['max'], str):
                translator = Equation(variable['name'],
                                      variable['bounds']['max'],
                                      description,
                                      type='return',
                                      untouched=untouched.keys())
                variable['bounds']['max'] = translator.parse().replace(';', '')
                dependencies += translator.dependencies()

        # Analyse the equation
        if condition == []:  # Call Equation
            translator = Equation(variable['name'],
                                  eq,
                                  description,
                                  method=method,
                                  untouched=untouched.keys())
            code = translator.parse()
            dependencies += translator.dependencies()

        else:  # An if-then-else statement
            code, deps = translate_ITE(variable['name'], eq, condition,
                                       description, untouched)
            dependencies += deps

        if isinstance(code, str):
            pre_loop = {}
            cpp_eq = code
            switch = None
        else:  # ODE
            pre_loop = code[0]
            cpp_eq = code[1]
            switch = code[2]

        # Replace untouched variables with their original name
        for prev, new in untouched.items():
            cpp_eq = cpp_eq.replace(prev, new)

        # Replace local functions
        for f in description['functions']:
            cpp_eq = re.sub(r'([^\w]*)' + f['name'] + '\(',
                            r'\1' + f['name'] + '(', ' ' + cpp_eq).strip()

        # Store the result
        variable[
            'pre_loop'] = pre_loop  # Things to be declared before the for loop (eg. dt)
        variable['cpp'] = cpp_eq  # the C++ equation
        variable['switch'] = switch  # switch value id ODE
        variable['untouched'] = untouched  # may be needed later
        variable['method'] = method  # may be needed later
        variable['dependencies'] = dependencies

        # If the method is implicit or midpoint, the equations must be solved concurrently (depend on v[t+1])
        if method in ['implicit', 'midpoint'] and switch is not None:
            concurrent_odes.append(variable)

    # After all variables are processed, do it again if they are concurrent
    if len(concurrent_odes) > 1:
        solver = CoupledEquations(description, concurrent_odes)
        new_eqs = solver.parse()
        for idx, variable in enumerate(description['variables']):
            for new_eq in new_eqs:
                if variable['name'] == new_eq['name']:
                    description['variables'][idx] = new_eq

    # Translate the psp code if any
    if 'raw_psp' in description.keys():
        psp = {'eq': description['raw_psp'].strip()}

        # Extract global operations
        eq, untouched_globs, global_ops = extract_globalops_synapse(
            'psp', " " + psp['eq'] + " ", description)
        description['pre_global_operations'] += global_ops['pre']
        description['post_global_operations'] += global_ops['post']

        # Replace pre- and post_synaptic variables
        eq, untouched, prepost_dependencies = extract_prepost(
            'psp', eq, description)
        description['dependencies']['pre'] += prepost_dependencies['pre']
        description['dependencies']['post'] += prepost_dependencies['post']
        for name, val in untouched_globs.items():
            if not name in untouched.keys():
                untouched[name] = val

        # Extract if-then-else statements
        eq, condition = extract_ite('psp', eq, description, split=False)

        # Analyse the equation
        if condition == []:
            translator = Equation('psp',
                                  eq,
                                  description,
                                  method='explicit',
                                  untouched=untouched.keys(),
                                  type='return')
            code = translator.parse()
            deps = translator.dependencies()
        else:
            code, deps = translate_ITE('psp', eq, condition, description,
                                       untouched)

        # Replace untouched variables with their original name
        for prev, new in untouched.items():
            code = code.replace(prev, new)

        # Store the result
        psp['cpp'] = code
        psp['dependencies'] = deps
        description['psp'] = psp

    # Process event-driven info
    if description['type'] == 'spike':
        for variable in description['pre_spike'] + description['post_spike']:
            # Find plasticity
            if variable['name'] == 'w':
                description['plasticity'] = True

            # Retrieve the equation
            eq = variable['eq']

            # Extract if-then-else statements
            eq, condition = extract_ite(variable['name'], eq, description)

            # Extract pre- and post_synaptic variables
            eq, untouched, prepost_dependencies = extract_prepost(
                variable['name'], eq, description)

            # Update dependencies
            description['dependencies']['pre'] += prepost_dependencies['pre']
            description['dependencies']['post'] += prepost_dependencies['post']
            # and also on the variable for checking
            variable['prepost_dependencies'] = prepost_dependencies

            # Analyse the equation
            dependencies = []
            if condition == []:
                translator = Equation(variable['name'],
                                      eq,
                                      description,
                                      method='explicit',
                                      untouched=untouched)
                code = translator.parse()
                dependencies += translator.dependencies()
            else:
                code, deps = translate_ITE(variable['name'], eq, condition,
                                           description, untouched)
                dependencies += deps

            if isinstance(code, list):  # an ode in a pre/post statement
                Global._print(eq)
                if variable in description['pre_spike']:
                    Global._error(
                        'It is forbidden to use ODEs in a pre_spike term.')
                elif variable in description['posz_spike']:
                    Global._error(
                        'It is forbidden to use ODEs in a post_spike term.')
                else:
                    Global._error('It is forbidden to use ODEs here.')

            # Replace untouched variables with their original name
            for prev, new in untouched.items():
                code = code.replace(prev, new)

            # Process the bounds
            if 'min' in variable['bounds'].keys():
                if isinstance(variable['bounds']['min'], str):
                    translator = Equation(variable['name'],
                                          variable['bounds']['min'],
                                          description,
                                          type='return',
                                          untouched=untouched)
                    variable['bounds']['min'] = translator.parse().replace(
                        ';', '')
                    dependencies += translator.dependencies()

            if 'max' in variable['bounds'].keys():
                if isinstance(variable['bounds']['max'], str):
                    translator = Equation(variable['name'],
                                          variable['bounds']['max'],
                                          description,
                                          type='return',
                                          untouched=untouched)
                    variable['bounds']['max'] = translator.parse().replace(
                        ';', '')
                    dependencies += translator.dependencies()

            # Store the result
            variable['cpp'] = code  # the C++ equation
            variable['dependencies'] = dependencies

    # Structural plasticity
    if synapse.pruning:
        description['pruning'] = extract_structural_plasticity(
            synapse.pruning, description)
    if synapse.creating:
        description['creating'] = extract_structural_plasticity(
            synapse.creating, description)

    return description
Пример #40
0
def process_equations(equations):
    """ Takes a multi-string describing equations and returns a list of dictionaries, where:

    * 'name' is the name of the variable

    * 'eq' is the equation

    * 'constraints' is all the constraints given after the last :. _extract_flags() should be called on it.

    Warning: one equation can now be on multiple lines, without needing the ... newline symbol.

    TODO: should this be used for other arguments as equations? pre_event and so on
    """
    def is_constraint(eq):
        " Internal method to determine if a string contains reserved keywords."
        eq = ',' +  eq.replace(' ', '') + ','
        for key in authorized_keywords:
            pattern = '([,]+)' + key + '([=,]+)'
            if re.match(pattern, eq):
                return True
        return False

    # All equations will be stored there, in the order of their definition
    variables = []
    try:
        equations = equations.replace(';', '\n').split('\n')
    except: # equations is empty
        return variables


    # Iterate over all lines
    for line in equations:
        # Skip empty lines
        definition = line.strip()
        if definition == '':
            continue

        # Remove comments
        com = definition.split('#')
        if len(com) > 1:
            definition = com[0]
            if definition.strip() == '':
                continue

        # Process the line
        try:
            equation, constraint = definition.rsplit(':', 1)
        except ValueError: # There is no :, only equation is concerned
            equation = definition
            constraint = ''
        else:   # there is a :
            # Check if the constraint contains the reserved keywords
            has_constraint = is_constraint(constraint)
            # If the right part of : is a constraint, just store it
            # Otherwise, it is an if-then-else statement
            if has_constraint:
                equation = equation.strip()
                constraint = constraint.strip()
            else:
                equation = definition.strip() # there are no constraints
                constraint = ''

        # Split the equation around operators = += -= *= /=, but not ==
        split_operators = re.findall('([\s\w\+\-\*\/\)]+)=([^=])', equation)
        if len(split_operators) == 1: # definition of a new variable
            # Retrieve the name
            eq = split_operators[0][0]
            if eq.strip() == "":
                _print(equation)
                _error('The equation can not be analysed, check the syntax.')

            name = extract_name(eq, left=True)
            if name in ['_undefined', '']:
                _error('No variable name can be found in ' + equation)

            # Append the result
            variables.append({'name': name, 'eq': equation.strip(), 'constraint': constraint.strip()})
        elif len(split_operators) == 0:
            # Continuation of the equation on a new line: append the equation to the previous variable
            variables[-1]['eq'] += ' ' + equation.strip()
            variables[-1]['constraint'] += constraint
        else:
            _error('Only one assignement operator is allowed per equation.')

    return variables
Пример #41
0
    def solve_implicit(self, expression_list):

        equations = {}
        new_vars = {}

        # Pre-processing to replace the gradient
        for name, expression in self.expression_list.items():
            # transform the expression to suppress =
            if '=' in expression:
                expression = expression.replace('=', '- (')
                expression += ')'
            # Suppress spaces to extract dvar/dt
            expression = expression.replace(' ', '')
            # Transform the gradient into a difference TODO: more robust...
            expression = expression.replace('d' + name, '_t_gradient_')
            expression_list[name] = expression

        # replace the variables by their future value
        for name, expression in expression_list.items():
            for n in self.names:
                expression = re.sub(r'([^\w]+)' + n + r'([^\w]+)',
                                    r'\1_' + n + r'\2', expression)
            expression = expression.replace('_t_gradient_',
                                            '(_' + name + ' - ' + name + ')')
            expression_list[name] = expression + '-' + name

            new_var = Symbol('_' + name)
            self.local_dict['_' + name] = new_var
            new_vars[new_var] = name

        for name, expression in expression_list.items():
            analysed = parse_expr(expression,
                                  local_dict=self.local_dict,
                                  transformations=(standard_transformations +
                                                   (convert_xor, )))
            equations[name] = analysed

        try:
            solution = solve(equations.values(), new_vars.keys())
        except:
            _error(
                'The multiple ODEs can not be solved together using the implicit Euler method.'
            )
            exit(0)

        for var, sol in solution.items():
            # simplify the solution
            sol = collect(sol, self.local_dict['dt'])

            # Generate the code
            cpp_eq = 'double _' + new_vars[var] + ' = ' + ccode(sol) + ';'
            switch = ccode(
                self.local_dict[new_vars[var]]) + ' += _' + new_vars[var] + ';'

            # Replace untouched variables with their original name
            for prev, new in self.untouched.items():
                cpp_eq = re.sub(prev, new, cpp_eq)
                switch = re.sub(prev, new, switch)

            # Store the result
            for variable in self.variables:
                if variable['name'] == new_vars[var]:
                    variable['cpp'] = cpp_eq
                    variable['switch'] = switch

        return self.variables
Пример #42
0
def process_equations(equations):
    """ Takes a multi-string describing equations and returns a list of dictionaries, where:
    
    * 'name' is the name of the variable
    
    * 'eq' is the equation
    
    * 'constraints' is all the constraints given after the last :. _extract_flags() should be called on it.
    
    Warning: one equation can now be on multiple lines, without needing the ... newline symbol.
    
    TODO: should this be used for other arguments as equations? pre_event and so on
    """
    def is_constraint(eq):
        " Internal method to determine if a string contains reserved keywords."
        eq = ',' + eq.replace(' ', '') + ','
        for key in authorized_keywords:
            pattern = '([,]+)' + key + '([=,]+)'
            if re.match(pattern, eq):
                return True
        return False

    # All equations will be stored there, in the order of their definition
    variables = []
    try:
        equations = equations.replace(';', '\n').split('\n')
    except:  # euqations is empty
        return variables

    # Iterate over all lines
    for line in equations:
        # Skip empty lines
        definition = line.strip()
        if definition == '':
            continue
        # Remove comments
        com = definition.split('#')
        if len(com) > 1:
            definition = com[0]
            if definition.strip() == '':
                continue
        # Process the line
        try:
            equation, constraint = definition.rsplit(':', 1)
        except ValueError:  # There is no :, only equation is concerned
            equation = line
            constraint = ''
        else:  # there is a :
            # Check if the constraint contains the reserved keywords
            has_constraint = is_constraint(constraint)
            # If the right part of : is a constraint, just store it
            # Otherwise, it is an if-then-else statement
            if has_constraint:
                equation = equation.strip()
                constraint = constraint.strip()
            else:
                equation = definition.strip()  # there are no constraints
                constraint = ''
        # Split the equation around operators = += -= *= /=, but not ==
        split_operators = re.findall('([\s\w\+\-\*\/\)]+)=([^=])', equation)
        if len(split_operators) == 1:  # definition of a new variable
            # Retrieve the name
            eq = split_operators[0][0]
            if eq.strip() == "":
                _error(equation)
                _print('The equation can not be analysed, check the syntax.')
                exit(0)
            name = extract_name(eq, left=True)
            if name in ['_undefined', '']:
                _error('No variable name can be found in ' + equation)
                exit(0)
            # Append the result
            variables.append({
                'name': name,
                'eq': equation.strip(),
                'constraint': constraint.strip()
            })
        elif len(split_operators) == 0:
            # Continuation of the equation on a new line: append the equation to the previous variable
            variables[-1]['eq'] += ' ' + equation.strip()
            variables[-1]['constraint'] += constraint
        else:
            _print(
                'Error: only one assignement operator is allowed per equation.'
            )
            exit(0)
    return variables
Пример #43
0
def extract_ite(name, eq, description, split=True):
    """ Extracts if-then-else statements and processes them.

    If-then-else statements must be of the form:

    .. code-block:: python

        variable = if condition: ...
                       val1 ...
                   else: ...
                       val2

    Conditional statements can be nested, but they should return only one value!
    """

    def transform(code):
        " Transforms the code into a list of lines."
        res = []
        items = []
        for arg in code.split(':'):
            items.append( arg.strip())
        for i in range(len(items)):
            if items[i].startswith('if '):
                res.append( items[i].strip() )
            elif items[i].endswith('else'):
                res.append(items[i].split('else')[0].strip() )
                res.append('else' )
            else: # the last then
                res.append( items[i].strip() )
        return res


    def parse(lines):
        " Recursive analysis of if-else statements"
        result = []
        while lines:
            if lines[0].startswith('if'):
                block = [lines.pop(0).split('if')[1], parse(lines)]
                if lines[0].startswith('else'):
                    lines.pop(0)
                    block.append(parse(lines))
                result.append(block)
            elif not lines[0].startswith(('else')):
                result.append(lines.pop(0))
            else:
                break
        return result[0]

    # If no if, not a conditional
    if not 'if ' in eq:
        return eq, []

    # Process the equation
    condition = []
    # Eventually split around =
    if split:
        left, right =  eq.split('=', 1)
    else:
        left = ''
        right = eq

    nb_then = len(re.findall(':', right))
    nb_else = len(re.findall('else', right))
    # The equation contains a conditional statement
    if nb_then > 0:
        # A if must be right after the equal sign
        if not right.strip().startswith('if'):
            _error(eq, '\nThe right term must directly start with a if statement.')

        # It must have the same number of : and of else
        if not nb_then == 2*nb_else:
            _error(eq, '\nConditional statements must use both : and else.')

        multilined = transform(right)
        condition = parse(multilined)
        right = ' __conditional__0 ' # only one conditional allowed in that case
        if split:
            eq = left + '=' + right
        else:
            eq = right
    else:
        _print(eq)
        _error('Conditional statements must define "then" and "else" values.\n var = if condition: a else: b')

    return eq, [condition]
Пример #44
0
def extract_ite(name, eq, description, split=True):
    """ Extracts if-then-else statements and processes them.

    If-then-else statements must be of the form:

    .. code-block:: python

        variable = if condition: ...
                       val1 ...
                   else: ...
                       val2

    Conditional statements can be nested, but they should return only one value!
    """
    def transform(code):
        " Transforms the code into a list of lines."
        res = []
        items = []
        for arg in code.split(':'):
            items.append(arg.strip())
        for i in range(len(items)):
            if items[i].startswith('if '):
                res.append(items[i].strip())
            elif items[i].endswith('else'):
                res.append(items[i].split('else')[0].strip())
                res.append('else')
            else:  # the last then
                res.append(items[i].strip())
        return res

    def parse(lines):
        " Recursive analysis of if-else statements"
        result = []
        while lines:
            if lines[0].startswith('if'):
                block = [lines.pop(0).split('if')[1], parse(lines)]
                if lines[0].startswith('else'):
                    lines.pop(0)
                    block.append(parse(lines))
                result.append(block)
            elif not lines[0].startswith(('else')):
                result.append(lines.pop(0))
            else:
                break
        return result[0]

    # If no if, not a conditional
    if not 'if ' in eq:
        return eq, []

    # Process the equation
    condition = []
    # Eventually split around =
    if split:
        left, right = eq.split('=', 1)
    else:
        left = ''
        right = eq

    nb_then = len(re.findall(':', right))
    nb_else = len(re.findall('else', right))
    # The equation contains a conditional statement
    if nb_then > 0:
        # A if must be right after the equal sign
        if not right.strip().startswith('if'):
            _error(
                eq,
                '\nThe right term must directly start with a if statement.')

        # It must have the same number of : and of else
        if not nb_then == 2 * nb_else:
            _error(eq, '\nConditional statements must use both : and else.')

        multilined = transform(right)
        condition = parse(multilined)
        right = ' __conditional__0 '  # only one conditional allowed in that case
        if split:
            eq = left + '=' + right
        else:
            eq = right
    else:
        _print(eq)
        _error(
            'Conditional statements must define "then" and "else" values.\n var = if condition: a else: b'
        )

    return eq, [condition]
Пример #45
0
def check_equation(equation):
    "Makes a formal check on the equation (matching parentheses, etc)"
    # Matching parentheses
    if equation.count('(') != equation.count(')'):
        _print(equation)
        _error('The number of parentheses does not match.')
Пример #46
0
def analyse_neuron(neuron):
    """ Performs the initial analysis for a single neuron type."""
    concurrent_odes = []

    # Store basic information
    description = {
        'object': 'neuron',
        'type': neuron.type,
        'raw_parameters': neuron.parameters,
        'raw_equations': neuron.equations,
        'raw_functions': neuron.functions,
    }

    if neuron.type == 'spike':  # Additionally store reset and spike
        description['raw_reset'] = neuron.reset
        description['raw_spike'] = neuron.spike
        description['refractory'] = neuron.refractory

    # Extract parameters and variables names
    parameters = extract_parameters(neuron.parameters, neuron.extra_values)
    variables = extract_variables(neuron.equations)
    description['parameters'] = parameters
    description['variables'] = variables

    # Make sure r is defined for rate-coded networks
    if neuron.type == 'rate':
        for var in description['parameters'] + description['variables']:
            if var['name'] == 'r':
                break
        else:
            _error('Rate-coded neurons must define the variable "r".')
            exit(0)
    else:  # spiking neurons define r by default, it contains the average FR if enabled
        for var in description['parameters'] + description['variables']:
            if var['name'] == 'r':
                _error(
                    'Spiking neurons use the variable "r" for the average FR, use another name.'
                )
                exit(0)
        description['variables'].append({
            'name': 'r',
            'locality': 'local',
            'bounds': {},
            'ctype': 'double',
            'init': 0.0,
            'flags': [],
            'eq': '',
            'cpp': ""
        })

    # Extract functions
    functions = extract_functions(neuron.functions, False)
    description['functions'] = functions

    # Build lists of all attributes (param+var), which are local or global
    attributes, local_var, global_var = get_attributes(parameters, variables)

    # Test if attributes are declared only once
    if len(attributes) != len(list(set(attributes))):
        _error('Attributes must be declared only once.', attributes)
        exit(0)
    description['attributes'] = attributes
    description['local'] = local_var
    description['global'] = global_var

    # Extract all targets
    targets = extract_targets(variables)
    description['targets'] = targets
    if neuron.type == 'spike':  # Add a default reset behaviour for conductances
        for target in targets:
            found = False
            for var in description['variables']:
                if var['name'] == 'g_' + target:
                    found = True
                    break
            if not found:
                description['variables'].append({
                    'name': 'g_' + target,
                    'locality': 'local',
                    'bounds': {},
                    'ctype': 'double',
                    'init': 0.0,
                    'flags': [],
                    'eq': 'g_' + target + ' = 0.0'
                })
                description['attributes'].append('g_' + target)
                description['local'].append('g_' + target)

    # Extract RandomDistribution objects
    random_distributions = extract_randomdist(description)
    description['random_distributions'] = random_distributions

    # Extract the spike condition if any
    if neuron.type == 'spike':
        description['spike'] = extract_spike_variable(description)

    # Global operation TODO
    description['global_operations'] = []

    # Translate the equations to C++
    for variable in description['variables']:
        eq = variable['transformed_eq']
        if eq.strip() == "":
            continue
        untouched = {}

        # Replace sum(target) with pop%(id)s.sum_exc[i]
        for target in description['targets']:
            eq = re.sub('sum\(\s*' + target + '\s*\)',
                        '__sum_' + target + '__', eq)
            untouched['__sum_' + target +
                      '__'] = '_sum_' + target + '%(local_index)s'

        # Extract global operations
        eq, untouched_globs, global_ops = extract_globalops_neuron(
            variable['name'], eq, description)

        # Add the untouched variables to the global list
        for name, val in untouched_globs.items():
            if not name in untouched.keys():
                untouched[name] = val
        description['global_operations'] += global_ops

        # Extract if-then-else statements
        eq, condition = extract_ite(variable['name'], eq, description)

        # Find the numerical method if any
        method = find_method(variable)

        # Process the bounds
        if 'min' in variable['bounds'].keys():
            if isinstance(variable['bounds']['min'], str):
                translator = Equation(variable['name'],
                                      variable['bounds']['min'],
                                      description,
                                      type='return',
                                      untouched=untouched)
                variable['bounds']['min'] = translator.parse().replace(';', '')

        if 'max' in variable['bounds'].keys():
            if isinstance(variable['bounds']['max'], str):
                translator = Equation(variable['name'],
                                      variable['bounds']['max'],
                                      description,
                                      type='return',
                                      untouched=untouched)
                variable['bounds']['max'] = translator.parse().replace(';', '')

        # Analyse the equation
        if condition == []:
            translator = Equation(variable['name'],
                                  eq,
                                  description,
                                  method=method,
                                  untouched=untouched)
            code = translator.parse()
            dependencies = translator.dependencies()
        else:  # An if-then-else statement
            code = translate_ITE(variable['name'], eq, condition, description,
                                 untouched)
            dependencies = []

        if isinstance(code, str):
            cpp_eq = code
            switch = None
        else:  # ODE
            cpp_eq = code[0]
            switch = code[1]

        # Replace untouched variables with their original name
        for prev, new in untouched.items():
            if prev.startswith('g_'):
                cpp_eq = re.sub(r'([^_]+)' + prev, r'\1' + new,
                                ' ' + cpp_eq).strip()
                if switch:
                    switch = re.sub(r'([^_]+)' + prev, new,
                                    ' ' + switch).strip()

            else:
                cpp_eq = re.sub(prev, new, cpp_eq)
                if switch:
                    switch = re.sub(prev, new, switch)

        # Replace local functions
        for f in description['functions']:
            cpp_eq = re.sub(r'([^\w]*)' + f['name'] + '\(',
                            r'\1' + f['name'] + '(', ' ' + cpp_eq).strip()

        # Store the result
        variable['cpp'] = cpp_eq  # the C++ equation
        variable['switch'] = switch  # switch value of ODE
        variable['untouched'] = untouched  # may be needed later
        variable['method'] = method  # may be needed later
        variable['dependencies'] = dependencies  # may be needed later

        # If the method is implicit or midpoint, the equations must be solved concurrently (depend on v[t+1])
        if method in ['implicit', 'midpoint']:
            concurrent_odes.append(variable)

    # After all variables are processed, do it again if they are concurrent
    if len(concurrent_odes) > 1:
        solver = CoupledEquations(description, concurrent_odes)
        new_eqs = solver.process_variables()
        for idx, variable in enumerate(description['variables']):
            for new_eq in new_eqs:
                if variable['name'] == new_eq['name']:
                    description['variables'][idx] = new_eq

    return description
Пример #47
0
def analyse_neuron(neuron):
    """
    Parses the structure and generates code snippets for the neuron type.

    It returns a ``description`` dictionary with the following fields:

    * 'object': 'neuron' by default, to distinguish it from 'synapse'
    * 'type': either 'rate' or 'spiking'
    * 'raw_parameters': provided field
    * 'raw_equations': provided field
    * 'raw_functions': provided field
    * 'raw_reset': provided field
    * 'raw_spike': provided field
    * 'refractory': provided field
    * 'parameters': list of parameters defined for the neuron type
    * 'variables': list of variables defined for the neuron type
    * 'functions': list of functions defined for the neuron type
    * 'attributes': list of names of all parameters and variables
    * 'local': list of names of parameters and variables which are local to each neuron
    * 'global': list of names of parameters and variables which are global to the population
    * 'targets': list of targets used in the equations
    * 'random_distributions': list of random number generators used in the neuron equations
    * 'global_operations': list of global operations (min/max/mean...) used in the equations (unused)
    * 'spike': when defined, contains the equations of the spike conditions and reset.

    Each parameter is a dictionary with the following elements:

    * 'bounds': unused
    * 'ctype': 'type of the parameter: 'float', 'double', 'int' or 'bool'
    * 'eq': original equation in text format
    * 'flags': list of flags provided after the :
    * 'init': initial value
    * 'locality': 'local' or 'global'
    * 'name': name of the parameter

    Each variable is a dictionary with the following elements:

    * 'bounds': dictionary of bounds ('init', 'min', 'max') provided after the :
    * 'cpp': C++ code snippet updating the variable
    * 'ctype': type of the variable: 'float', 'double', 'int' or 'bool'
    * 'dependencies': list of variable and parameter names on which the equation depends
    * 'eq': original equation in text format
    * 'flags': list of flags provided after the :
    * 'init': initial value
    * 'locality': 'local' or 'global'
    * 'method': numericalmethod for ODEs
    * 'name': name of the variable
    * 'pre_loop': ODEs have a pre_loop term for precomputing dt/tau. dict with 'name' and 'value'. type must be inferred.
    * 'switch': ODEs have a switch term
    * 'transformed_eq': same as eq, except special terms (sums, rds) are replaced with a temporary name
    * 'untouched': dictionary of special terms, with their new name as keys and replacement values as values.

    The 'spike' element (when present) is a dictionary containing:

    * 'spike_cond': the C++ code snippet containing the spike condition ("v%(local_index)s > v_T")
    * 'spike_cond_dependencies': list of variables/parameters on which the spike condition depends
    * 'spike_reset': a list of reset statements, each of them composed of :
        * 'constraint': either '' or 'unless_refractory'
        * 'cpp': C++ code snippet
        * 'dependencies': list of variables on which the reset statement depends
        * 'eq': original equation in text format
        * 'name': name of the reset variable

    """

    # Store basic information
    description = {
        'object': 'neuron',
        'type': neuron.type,
        'raw_parameters': neuron.parameters,
        'raw_equations': neuron.equations,
        'raw_functions': neuron.functions,
    }

    # Spiking neurons additionally store the spike condition, the reset statements and a refractory period
    if neuron.type == 'spike':
        description['raw_reset'] = neuron.reset
        description['raw_spike'] = neuron.spike
        description['raw_axon_spike'] = neuron.axon_spike
        description['raw_axon_reset'] = neuron.axon_reset
        description['refractory'] = neuron.refractory

    # Extract parameters and variables names
    parameters = extract_parameters(neuron.parameters, neuron.extra_values)
    variables = extract_variables(neuron.equations)
    description['parameters'] = parameters
    description['variables'] = variables

    # Make sure r is defined for rate-coded networks
    from ANNarchy.extensions.bold.BoldModel import BoldModel
    if isinstance(neuron, BoldModel):
        found = False
        for var in description['parameters'] + description['variables']:
            if var['name'] == 'r':
                found = True
        if not found:
            description['variables'].append({
                'name': 'r',
                'locality': 'local',
                'bounds': {},
                'ctype': config['precision'],
                'init': 0.0,
                'flags': [],
                'eq': '',
                'cpp': ""
            })
    elif neuron.type == 'rate':
        for var in description['parameters'] + description['variables']:
            if var['name'] == 'r':
                break
        else:
            _error('Rate-coded neurons must define the variable "r".')

    else:  # spiking neurons define r by default, it contains the average FR if enabled
        for var in description['parameters'] + description['variables']:
            if var['name'] == 'r':
                _error(
                    'Spiking neurons use the variable "r" for the average FR, use another name.'
                )

        description['variables'].append({
            'name': 'r',
            'locality': 'local',
            'bounds': {},
            'ctype': config['precision'],
            'init': 0.0,
            'flags': [],
            'eq': '',
            'cpp': ""
        })

    # Extract functions
    functions = extract_functions(neuron.functions, False)
    description['functions'] = functions

    # Build lists of all attributes (param + var), which are local or global
    attributes, local_var, global_var, _ = get_attributes(parameters,
                                                          variables,
                                                          neuron=True)

    # Test if attributes are declared only once
    if len(attributes) != len(list(set(attributes))):
        _error('Attributes must be declared only once.', attributes)

    # Store the attributes
    description['attributes'] = attributes
    description['local'] = local_var
    description['semiglobal'] = []  # only for projections
    description['global'] = global_var

    # Extract all targets
    targets = sorted(list(set(extract_targets(variables))))
    description['targets'] = targets
    if neuron.type == 'spike':  # Add a default reset behaviour for conductances
        for target in targets:
            found = False
            for var in description['variables']:
                if var['name'] == 'g_' + target:
                    found = True
                    break
            if not found:
                description['variables'].append({
                    'name': 'g_' + target,
                    'locality': 'local',
                    'bounds': {},
                    'ctype': config['precision'],
                    'init': 0.0,
                    'flags': [],
                    'eq': 'g_' + target + ' = 0.0'
                })
                description['attributes'].append('g_' + target)
                description['local'].append('g_' + target)

    # Extract RandomDistribution objects
    random_distributions = extract_randomdist(description)
    description['random_distributions'] = random_distributions

    # Extract the spike condition if any
    if neuron.type == 'spike':
        description['spike'] = extract_spike_variable(description)
        description['axon_spike'] = extract_axon_spike_condition(description)

    # Global operation TODO
    description['global_operations'] = []

    # The ODEs may be interdependent (implicit, midpoint), so they need to be passed explicitely to CoupledEquations
    concurrent_odes = []

    # Translate the equations to C++
    for variable in description['variables']:
        # Get the equation
        eq = variable['transformed_eq']
        if eq.strip() == "":
            continue

        # Special variables (sums, global operations, rd) are placed in untouched, so that Sympy ignores them
        untouched = {}

        # Dependencies must be gathered
        dependencies = []

        # Replace sum(target) with _sum_exc__[i]
        for target in description['targets']:
            # sum() is valid for all targets
            eq = re.sub(r'(?P<pre>[^\w.])sum\(\)', r'\1sum(__all__)', eq)
            # Replace sum(target) with __sum_target__
            eq = re.sub('sum\(\s*' + target + '\s*\)',
                        '__sum_' + target + '__', eq)
            untouched['__sum_' + target +
                      '__'] = '_sum_' + target + '%(local_index)s'

        # Extract global operations
        eq, untouched_globs, global_ops = extract_globalops_neuron(
            variable['name'], eq, description)

        # Add the untouched variables to the global list
        for name, val in untouched_globs.items():
            if not name in untouched.keys():
                untouched[name] = val
        description['global_operations'] += global_ops

        # Extract if-then-else statements
        eq, condition = extract_ite(variable['name'], eq, description)

        # Find the numerical method if any
        method = find_method(variable)

        # Process the bounds
        if 'min' in variable['bounds'].keys():
            if isinstance(variable['bounds']['min'], str):
                translator = Equation(variable['name'],
                                      variable['bounds']['min'],
                                      description,
                                      type='return',
                                      untouched=untouched)
                variable['bounds']['min'] = translator.parse().replace(';', '')
                dependencies += translator.dependencies()

        if 'max' in variable['bounds'].keys():
            if isinstance(variable['bounds']['max'], str):
                translator = Equation(variable['name'],
                                      variable['bounds']['max'],
                                      description,
                                      type='return',
                                      untouched=untouched)
                variable['bounds']['max'] = translator.parse().replace(';', '')
                dependencies += translator.dependencies()

        # Analyse the equation
        if condition == []:  # No if-then-else
            translator = Equation(variable['name'],
                                  eq,
                                  description,
                                  method=method,
                                  untouched=untouched)
            code = translator.parse()
            dependencies += translator.dependencies()
        else:  # An if-then-else statement
            code, deps = translate_ITE(variable['name'], eq, condition,
                                       description, untouched)
            dependencies += deps

        # ODEs have a switch statement:
        #   double _r = (1.0 - r)/tau;
        #   r[i] += dt* _r;
        # while direct assignments are one-liners:
        #   r[i] = 1.0
        if isinstance(code, str):
            pre_loop = {}
            cpp_eq = code
            switch = None
        else:  # ODE
            pre_loop = code[0]
            cpp_eq = code[1]
            switch = code[2]

        # Replace untouched variables with their original name
        for prev, new in untouched.items():
            if prev.startswith('g_'):
                cpp_eq = re.sub(r'([^_]+)' + prev, r'\1' + new,
                                ' ' + cpp_eq).strip()
                if len(pre_loop) > 0:
                    pre_loop['value'] = re.sub(r'([^_]+)' + prev, new, ' ' +
                                               pre_loop['value']).strip()
                if switch:
                    switch = re.sub(r'([^_]+)' + prev, new,
                                    ' ' + switch).strip()
            else:
                cpp_eq = re.sub(prev, new, cpp_eq)
                if len(pre_loop) > 0:
                    pre_loop['value'] = re.sub(prev, new, pre_loop['value'])
                if switch:
                    switch = re.sub(prev, new, switch)

        # Replace local functions
        for f in description['functions']:
            cpp_eq = re.sub(r'([^\w]*)' + f['name'] + '\(',
                            r'\1' + f['name'] + '(', ' ' + cpp_eq).strip()

        # Store the result
        variable[
            'pre_loop'] = pre_loop  # Things to be declared before the for loop (eg. dt)
        variable['cpp'] = cpp_eq  # the C++ equation
        variable['switch'] = switch  # switch value of ODE
        variable['untouched'] = untouched  # may be needed later
        variable['method'] = method  # may be needed later
        variable['dependencies'] = list(
            set(dependencies))  # may be needed later

        # If the method is implicit or midpoint, the equations must be solved concurrently (depend on v[t+1])
        if method in ['implicit', 'midpoint', 'runge-kutta4'
                      ] and switch is not None:
            concurrent_odes.append(variable)

    # After all variables are processed, do it again if they are concurrent
    if len(concurrent_odes) > 1:
        solver = CoupledEquations(description, concurrent_odes)
        new_eqs = solver.parse()
        for idx, variable in enumerate(description['variables']):
            for new_eq in new_eqs:
                if variable['name'] == new_eq['name']:
                    description['variables'][idx] = new_eq

    return description
Пример #48
0
def extract_randomdist(description):
    " Extracts RandomDistribution objects from all variables"
    rk_rand = 0
    random_objects = []
    for variable in description['variables']:
        eq = variable['eq']
        # Search for all distributions
        for dist in available_distributions:
            matches = re.findall('(?P<pre>[^\w.])' + dist + '\(([^()]+)\)', eq)
            if matches == ' ':
                continue
            for l, v in matches:
                # Check the arguments
                arguments = v.split(',')
                # Check the number of provided arguments
                if len(arguments) < distributions_arguments[dist]:
                    _error(eq)
                    _error('The distribution ' + dist + ' requires ' +
                           str(distributions_arguments[dist]) + 'parameters')
                elif len(arguments) > distributions_arguments[dist]:
                    _error(eq)
                    _error(
                        'Too many parameters provided to the distribution ' +
                        dist)
                # Process the arguments
                processed_arguments = ""
                for idx in range(len(arguments)):
                    try:
                        arg = float(arguments[idx])
                    except:  # A global parameter
                        if arguments[idx].strip() in description['global']:
                            if description['object'] == 'neuron':
                                arg = arguments[idx].strip()
                            else:
                                arg = arguments[idx].strip()
                        else:
                            _error(
                                arguments[idx] +
                                ' is not a global parameter of the neuron/synapse. It can not be used as an argument to the random distribution '
                                + dist + '(' + v + ')')
                            exit(0)

                    processed_arguments += str(arg)
                    if idx != len(arguments) - 1:  # not the last one
                        processed_arguments += ', '
                definition = distributions_equivalents[
                    dist] + '(' + processed_arguments + ')'
                # Store its definition
                desc = {
                    'name': 'rand_' + str(rk_rand),
                    'dist': dist,
                    'definition': definition,
                    'args': processed_arguments,
                    'template': distributions_equivalents[dist]
                }
                rk_rand += 1
                random_objects.append(desc)
                # Replace its definition by its temporary name
                # Problem: when one uses twice the same RD in a single equation (perverse...)
                eq = eq.replace(dist + '(' + v + ')', desc['name'])
                # Add the new variable to the vocabulary
                description['attributes'].append(desc['name'])
                if variable['name'] in description['local']:
                    description['local'].append(desc['name'])
                else:  # Why not on a population-wide variable?
                    description['global'].append(desc['name'])
        variable['transformed_eq'] = eq

    return random_objects
Пример #49
0
def analyse_neuron(neuron):
    """ Performs the initial analysis for a single neuron type."""
    concurrent_odes = []

    # Store basic information
    description = {
        'object': 'neuron',
        'type': neuron.type,
        'raw_parameters': neuron.parameters,
        'raw_equations': neuron.equations,
        'raw_functions': neuron.functions,
    }

    if neuron.type == 'spike': # Additionally store reset and spike
        description['raw_reset'] = neuron.reset
        description['raw_spike'] = neuron.spike
        description['refractory'] = neuron.refractory

    # Extract parameters and variables names
    parameters = extract_parameters(neuron.parameters, neuron.extra_values)
    variables = extract_variables(neuron.equations)
    description['parameters'] = parameters
    description['variables'] = variables

    # Make sure r is defined for rate-coded networks
    if neuron.type == 'rate':
        for var in description['parameters'] + description['variables']:
            if var['name'] == 'r':
                break
        else:
            _error('Rate-coded neurons must define the variable "r".')

    else: # spiking neurons define r by default, it contains the average FR if enabled
        for var in description['parameters'] + description['variables']:
            if var['name'] == 'r':
                _error('Spiking neurons use the variable "r" for the average FR, use another name.')

        description['variables'].append(
                {'name': 'r', 'locality': 'local', 'bounds': {}, 'ctype': 'double', 'init': 0.0,
                 'flags': [], 'eq': '', 'cpp': ""})

    # Extract functions
    functions = extract_functions(neuron.functions, False)
    description['functions'] = functions

    # Build lists of all attributes (param+var), which are local or global
    attributes, local_var, global_var = get_attributes(parameters, variables)

    # Test if attributes are declared only once
    if len(attributes) != len(list(set(attributes))):
        _error('Attributes must be declared only once.', attributes)

    description['attributes'] = attributes
    description['local'] = local_var
    description['global'] = global_var

    # Extract all targets
    targets = extract_targets(variables)
    description['targets'] = targets
    if neuron.type == 'spike': # Add a default reset behaviour for conductances
        for target in targets:
            found = False
            for var in description['variables']:
                if var['name'] == 'g_' + target:
                    found = True
                    break
            if not found:
                description['variables'].append(
                    { 'name': 'g_'+target, 'locality': 'local', 'bounds': {}, 'ctype': 'double',
                        'init': 0.0, 'flags': [], 'eq': 'g_' + target+ ' = 0.0'}
                )
                description['attributes'].append('g_'+target)
                description['local'].append('g_'+target)

    # Extract RandomDistribution objects
    random_distributions = extract_randomdist(description)
    description['random_distributions'] = random_distributions

    # Extract the spike condition if any
    if neuron.type == 'spike':
        description['spike'] = extract_spike_variable(description)

    # Global operation TODO
    description['global_operations'] = []

    # Translate the equations to C++
    for variable in description['variables']:
        eq = variable['transformed_eq']
        if eq.strip() == "":
            continue
        untouched={}

        # Replace sum(target) with pop%(id)s.sum_exc[i]
        for target in description['targets']:
            eq = re.sub('sum\(\s*'+target+'\s*\)', '__sum_'+target+'__', eq)
            untouched['__sum_'+target+'__'] = '_sum_' + target + '%(local_index)s'

        # Extract global operations
        eq, untouched_globs, global_ops = extract_globalops_neuron(variable['name'], eq, description)

        # Add the untouched variables to the global list
        for name, val in untouched_globs.items():
            if not name in untouched.keys():
                untouched[name] = val
        description['global_operations'] += global_ops

        # Extract if-then-else statements
        eq, condition = extract_ite(variable['name'], eq, description)

        # Find the numerical method if any
        method = find_method(variable)

        # Process the bounds
        if 'min' in variable['bounds'].keys():
            if isinstance(variable['bounds']['min'], str):
                translator = Equation(
                                variable['name'],
                                variable['bounds']['min'],
                                description,
                                type = 'return',
                                untouched = untouched
                                )
                variable['bounds']['min'] = translator.parse().replace(';', '')

        if 'max' in variable['bounds'].keys():
            if isinstance(variable['bounds']['max'], str):
                translator = Equation(
                                variable['name'],
                                variable['bounds']['max'],
                                description,
                                type = 'return',
                                untouched = untouched)
                variable['bounds']['max'] = translator.parse().replace(';', '')

        # Analyse the equation
        if condition == []:
            translator = Equation(
                                    variable['name'],
                                    eq,
                                    description,
                                    method = method,
                                    untouched = untouched
                            )
            code = translator.parse()
            dependencies = translator.dependencies()
        else: # An if-then-else statement
            code = translate_ITE(
                        variable['name'],
                        eq,
                        condition,
                        description,
                        untouched )
            dependencies = []


        if isinstance(code, str):
            cpp_eq = code
            switch = None
        else: # ODE
            cpp_eq = code[0]
            switch = code[1]

        # Replace untouched variables with their original name
        for prev, new in untouched.items():
            if prev.startswith('g_'):
                cpp_eq = re.sub(r'([^_]+)'+prev, r'\1'+new, ' ' + cpp_eq).strip()
                if switch:
                    switch = re.sub(r'([^_]+)'+prev, new, ' ' + switch).strip()

            else:
                cpp_eq = re.sub(prev, new, cpp_eq)
                if switch:
                    switch = re.sub(prev, new, switch)

        # Replace local functions
        for f in description['functions']:
            cpp_eq = re.sub(r'([^\w]*)'+f['name']+'\(', r'\1'+f['name'] + '(', ' ' + cpp_eq).strip()

        # Store the result
        variable['cpp'] = cpp_eq # the C++ equation
        variable['switch'] = switch # switch value of ODE
        variable['untouched'] = untouched # may be needed later
        variable['method'] = method # may be needed later
        variable['dependencies'] = dependencies # may be needed later

        # If the method is implicit or midpoint, the equations must be solved concurrently (depend on v[t+1])
        if method in ['implicit', 'midpoint']:
            concurrent_odes.append(variable)

    # After all variables are processed, do it again if they are concurrent
    if len(concurrent_odes) > 1 :
        solver = CoupledEquations(description, concurrent_odes)
        new_eqs = solver.process_variables()
        for idx, variable in enumerate(description['variables']):
            for new_eq in new_eqs:
                if variable['name'] == new_eq['name']:
                    description['variables'][idx] = new_eq

    return description
Пример #50
0
    def solve_implicit(self, expression_list):

        equations = {}
        new_vars = {}

        # Pre-processing to replace the gradient
        for name, expression in self.expression_list.items():
            # transform the expression to suppress =
            if '=' in expression:
                expression = expression.replace('=', '- (')
                expression += ')'
            # Suppress spaces to extract dvar/dt
            expression = expression.replace(' ', '')
            # Transform the gradient into a difference TODO: more robust...
            expression = expression.replace('d'+name, '_t_gradient_')
            expression_list[name] = expression

        # replace the variables by their future value
        for name, expression in expression_list.items():
            for n in self.names:
                expression = re.sub(r'([^\w]+)'+n+r'([^\w]+)', r'\1_'+n+r'\2', expression)
            expression = expression.replace('_t_gradient_', '(_'+name+' - '+name+')')
            expression_list[name] = expression + '-' + name

            new_var = Symbol('_'+name)
            self.local_dict['_'+name] = new_var
            new_vars[new_var] = name


        for name, expression in expression_list.items():
            analysed = parse_expr(expression,
                local_dict = self.local_dict,
                transformations = (standard_transformations + (convert_xor,))
            )
            equations[name] = analysed
          
        try:
            solution = solve(equations.values(), new_vars.keys())
        except:
            _print(expression_list)
            _error('The multiple ODEs can not be solved together using the implicit Euler method.')
            

        for var, sol in solution.items():
            # simplify the solution
            sol  = collect( sol, self.local_dict['dt'])

            # Generate the code
            cpp_eq = 'double _' + new_vars[var] + ' = ' + ccode(sol) + ';'
            switch =  ccode(self.local_dict[new_vars[var]] ) + ' += _' + new_vars[var] + ';'

            # Replace untouched variables with their original name
            for prev, new in self.untouched.items():
                cpp_eq = re.sub(prev, new, cpp_eq)
                switch = re.sub(prev, new, switch)

            # Store the result
            for variable in self.variables:
                if variable['name'] == new_vars[var]:
                    variable['cpp'] = cpp_eq
                    variable['switch'] = switch
            
        return self.variables
Пример #51
0
    def __init__(self,
                 parameters="",
                 equations="",
                 spike=None,
                 axon_spike=None,
                 reset=None,
                 axon_reset=None,
                 refractory=None,
                 functions=None,
                 name="",
                 description="",
                 extra_values={}):
        """
        :param parameters: parameters of the neuron and their initial value.
        :param equations: equations defining the temporal evolution of variables.
        :param functions: additional functions used in the variables' equations.
        :param spike: condition to emit a spike (only for spiking neurons).
        :param axon_spike: condition to emit an axonal spike (only for spiking neurons and optional). The axonal spike can appear additional to the spike and is independent from refractoriness of a neuron.
        :param reset: changes to the variables after a spike (only for spiking neurons).
        :param axon_reset: changes to the variables after an axonal spike (only for spiking neurons).
        :param refractory: refractory period of a neuron after a spike (only for spiking neurons).
        :param name: name of the neuron type (used for reporting only).
        :param description: short description of the neuron type (used for reporting).

        """

        # Store the parameters and equations
        self.parameters = parameters
        self.equations = equations
        self.functions = functions
        self.spike = spike
        self.axon_spike = axon_spike
        self.reset = reset
        self.axon_reset = axon_reset
        self.refractory = refractory
        self.extra_values = extra_values

        # Find the type of the neuron
        self.type = 'spike' if self.spike else 'rate'

        # Not available by now ...
        if axon_spike and config['paradigm'] != "openmp":
            _error(
                "Axonal spike conditions are only available for openMP by now."
            )

        # Reporting
        if not hasattr(self, '_instantiated'):  # User-defined
            _objects['neurons'].append(self)
        elif len(self._instantiated) == 0:  # First instantiated of the class
            _objects['neurons'].append(self)
        self._rk_neurons_type = len(_objects['neurons'])

        if name:
            self.name = name
        else:
            self.name = self._default_names[self.type]

        if description:
            self.short_description = description
        else:
            self.short_description = "User-defined model of a spiking neuron." if self.type == 'spike' else "User-defined model of a rate-coded neuron."

        # Analyse the neuron type
        self.description = None
Пример #52
0
def extract_structural_plasticity(statement, description):
    # Extract flags
    try:
        eq, constraint = statement.rsplit(':', 1)
        bounds, flags = extract_flags(constraint)
    except:
        eq = statement.strip()
        bounds = {}
        flags = []

    # Extract RD
    rd = None
    for dist in available_distributions:
        matches = re.findall('(?P<pre>[^\w.])'+dist+'\(([^()]+)\)', eq)
        for l, v in matches:
            # Check the arguments
            arguments = v.split(',')
            # Check the number of provided arguments
            if len(arguments) < distributions_arguments[dist]:
                _error(eq)
                _error('The distribution ' + dist + ' requires ' + str(distributions_arguments[dist]) + 'parameters')
            elif len(arguments) > distributions_arguments[dist]:
                _error(eq)
                _error('Too many parameters provided to the distribution ' + dist)
            # Process the arguments
            processed_arguments = ""
            for idx in range(len(arguments)):
                try:
                    arg = float(arguments[idx])
                except: # A global parameter
                    _error(eq)
                    _error('Random distributions for creating/pruning synapses must use foxed values.')
                    exit(0)
                processed_arguments += str(arg)
                if idx != len(arguments)-1: # not the last one
                    processed_arguments += ', '
            definition = distributions_equivalents[dist] + '(' + processed_arguments + ')'
            
            # Store its definition
            if rd:
                _error(eq)
                _error('Only one random distribution per equation is allowed.')
                exit(0)

            rd = {'name': 'rand_' + str(0) ,
                    'origin': dist+'('+v+')',
                    'dist': dist,
                    'definition': definition,
                    'args' : processed_arguments,
                    'template': distributions_equivalents[dist]}

    if rd:
        eq = eq.replace(rd['origin'], 'rd(rng)')

    # Extract pre/post dependencies
    eq, untouched, dependencies = extract_prepost('test', eq, description)

    # Parse code
    translator = Equation('test', eq, 
                          description, 
                          method = 'cond', 
                          untouched = {})

    code = translator.parse()

    # Replace untouched variables with their original name
    for prev, new in untouched.items():
        code = code.replace(prev, new) 

    # Add new dependencies
    for dep in dependencies['pre']:
        description['dependencies']['pre'].append(dep)
    for dep in dependencies['post']:
        description['dependencies']['post'].append(dep)

    return {'eq': eq, 'cpp': code, 'bounds': bounds, 'flags': flags, 'rd': rd}
Пример #53
0
 def __add__(self, synapse):
     _error('adding synapse models is not implemented yet.')
     exit(0)
Пример #54
0
def analyse_synapse(synapse):
    """
    Parses the structure and generates code snippets for the synapse type.

    It returns a ``description`` dictionary with the following fields:

    * 'object': 'synapse' by default, to distinguish it from 'neuron'
    * 'type': either 'rate' or 'spiking'
    * 'raw_parameters': provided field
    * 'raw_equations': provided field
    * 'raw_functions': provided field
    * 'raw_psp': provided field
    * 'raw_pre_spike': provided field
    * 'raw_post_spike': provided field
    * 'parameters': list of parameters defined for the synapse type
    * 'variables': list of variables defined for the synapse type
    * 'functions': list of functions defined for the synapse type
    * 'attributes': list of names of all parameters and variables
    * 'local': list of names of parameters and variables which are local to each synapse
    * 'semiglobal': list of names of parameters and variables which are local to each postsynaptic neuron
    * 'global': list of names of parameters and variables which are global to the projection
    * 'random_distributions': list of random number generators used in the neuron equations
    * 'global_operations': list of global operations (min/max/mean...) used in the equations
    * 'pre_global_operations': list of global operations (min/max/mean...) on the pre-synaptic population
    * 'post_global_operations': list of global operations (min/max/mean...) on the post-synaptic population
    * 'pre_spike': list of variables updated after a pre-spike event
    * 'post_spike': list of variables updated after a post-spike event
    * 'dependencies': dictionary ('pre', 'post') of lists of pre (resp. post) variables accessed by the synapse (used for delaying variables)
    * 'psp': dictionary ('eq' and 'psp') for the psp code to be summed
    * 'pruning' and 'creating': statements for structural plasticity


    Each parameter is a dictionary with the following elements:

    * 'bounds': unused
    * 'ctype': 'type of the parameter: 'float', 'double', 'int' or 'bool'
    * 'eq': original equation in text format
    * 'flags': list of flags provided after the :
    * 'init': initial value
    * 'locality': 'local', 'semiglobal' or 'global'
    * 'name': name of the parameter

    Each variable is a dictionary with the following elements:

    * 'bounds': dictionary of bounds ('init', 'min', 'max') provided after the :
    * 'cpp': C++ code snippet updating the variable
    * 'ctype': type of the variable: 'float', 'double', 'int' or 'bool'
    * 'dependencies': list of variable and parameter names on which the equation depends
    * 'eq': original equation in text format
    * 'flags': list of flags provided after the :
    * 'init': initial value
    * 'locality': 'local', 'semiglobal' or 'global'
    * 'method': numericalmethod for ODEs
    * 'name': name of the variable
    * 'pre_loop': ODEs have a pre_loop term for precomputing dt/tau
    * 'switch': ODEs have a switch term
    * 'transformed_eq': same as eq, except special terms (sums, rds) are replaced with a temporary name
    * 'untouched': dictionary of special terms, with their new name as keys and replacement values as values.

    """

    # Store basic information
    description = {
        'object': 'synapse',
        'type': synapse.type,
        'raw_parameters': synapse.parameters,
        'raw_equations': synapse.equations,
        'raw_functions': synapse.functions
    }

    # Psps is what is actually summed over the incoming weights
    if synapse.psp:
        description['raw_psp'] = synapse.psp
    elif synapse.type == 'rate':
        description['raw_psp'] = "w*pre.r"

    # Spiking synapses additionally store pre_spike and post_spike
    if synapse.type == 'spike':
        description['raw_pre_spike'] = synapse.pre_spike
        description['raw_post_spike'] = synapse.post_spike

    # Extract parameters and variables names
    parameters = extract_parameters(synapse.parameters, synapse.extra_values)
    variables = extract_variables(synapse.equations)

    # Extract functions
    functions = extract_functions(synapse.functions, False)

    # Check the presence of w
    description['plasticity'] = False
    for var in parameters + variables:
        if var['name'] == 'w':
            break
    else:
        parameters.append(
            {
                'name': 'w', 'bounds': {}, 'ctype': config['precision'],
                'init': 0.0, 'flags': [], 'eq': 'w=0.0', 'locality': 'local'
            }
        )

    # Find out a plasticity rule
    for var in variables:
        if var['name'] == 'w':
            description['plasticity'] = True
            break

    # Build lists of all attributes (param+var), which are local or global
    attributes, local_var, global_var, semiglobal_var = get_attributes(parameters, variables, neuron=False)

    # Test if attributes are declared only once
    if len(attributes) != len(list(set(attributes))):
        _error('Attributes must be declared only once.', attributes)


    # Add this info to the description
    description['parameters'] = parameters
    description['variables'] = variables
    description['functions'] = functions
    description['attributes'] = attributes
    description['local'] = local_var
    description['semiglobal'] = semiglobal_var
    description['global'] = global_var
    description['global_operations'] = []

    # Lists of global operations needed at the pre and post populations
    description['pre_global_operations'] = []
    description['post_global_operations'] = []

    # Extract RandomDistribution objects
    description['random_distributions'] = extract_randomdist(description)

    # Extract event-driven info
    if description['type'] == 'spike':
        # pre_spike event
        description['pre_spike'] = extract_pre_spike_variable(description)
        for var in description['pre_spike']:
            if var['name'] in ['g_target']: # Already dealt with
                continue
            for avar in description['variables']:
                if var['name'] == avar['name']:
                    break
            else: # not defined already
                description['variables'].append(
                {'name': var['name'], 'bounds': var['bounds'], 'ctype': var['ctype'], 'init': var['init'],
                 'locality': var['locality'],
                 'flags': [], 'transformed_eq': '', 'eq': '',
                 'cpp': '', 'switch': '', 're_loop': '',
                 'untouched': '', 'method':'explicit'}
                )
                description['local'].append(var['name'])
                description['attributes'].append(var['name'])

        # post_spike event
        description['post_spike'] = extract_post_spike_variable(description)
        for var in description['post_spike']:
            if var['name'] in ['g_target', 'w']: # Already dealt with
                continue
            for avar in description['variables']:
                if var['name'] == avar['name']:
                    break
            else: # not defined already
                description['variables'].append(
                {'name': var['name'], 'bounds': var['bounds'], 'ctype': var['ctype'], 'init': var['init'],
                 'locality': var['locality'],
                 'flags': [], 'transformed_eq': '', 'eq': '',
                 'cpp': '', 'switch': '', 'untouched': '', 'method':'explicit'}
                )
                description['local'].append(var['name'])
                description['attributes'].append(var['name'])

    # Variables names for the parser which should be left untouched
    untouched = {}
    description['dependencies'] = {'pre': [], 'post': []}

    # The ODEs may be interdependent (implicit, midpoint), so they need to be passed explicitely to CoupledEquations
    concurrent_odes = []

    # Iterate over all variables
    for variable in description['variables']:
        # Equation
        eq = variable['transformed_eq']
        if eq.strip() == '':
            continue

        # Dependencies must be gathered
        dependencies = []

        # Extract global operations
        eq, untouched_globs, global_ops = extract_globalops_synapse(variable['name'], eq, description)
        description['pre_global_operations'] += global_ops['pre']
        description['post_global_operations'] += global_ops['post']
        # Remove doubled entries
        description['pre_global_operations'] = [i for n, i in enumerate(description['pre_global_operations']) if i not in description['pre_global_operations'][n + 1:]]
        description['post_global_operations'] = [i for n, i in enumerate(description['post_global_operations']) if i not in description['post_global_operations'][n + 1:]]

        # Extract pre- and post_synaptic variables
        eq, untouched_var, prepost_dependencies = extract_prepost(variable['name'], eq, description)

        # Store the pre-post dependencies at the synapse level
        description['dependencies']['pre'] += prepost_dependencies['pre']
        description['dependencies']['post'] += prepost_dependencies['post']
        # and also on the variable for checking
        variable['prepost_dependencies'] = prepost_dependencies

        # Extract if-then-else statements
        eq, condition = extract_ite(variable['name'], eq, description)

        # Add the untouched variables to the global list
        for name, val in untouched_globs.items():
            if not name in untouched.keys():
                untouched[name] = val
        for name, val in untouched_var.items():
            if not name in untouched.keys():
                untouched[name] = val

        # Save the tranformed equation
        variable['transformed_eq'] = eq

        # Find the numerical method if any
        method = find_method(variable)

        # Process the bounds
        if 'min' in variable['bounds'].keys():
            if isinstance(variable['bounds']['min'], str):
                translator = Equation(variable['name'], variable['bounds']['min'],
                                      description,
                                      type = 'return',
                                      untouched = untouched.keys())
                variable['bounds']['min'] = translator.parse().replace(';', '')
                dependencies += translator.dependencies()

        if 'max' in variable['bounds'].keys():
            if isinstance(variable['bounds']['max'], str):
                translator = Equation(variable['name'], variable['bounds']['max'],
                                      description,
                                      type = 'return',
                                      untouched = untouched.keys())
                variable['bounds']['max'] = translator.parse().replace(';', '')
                dependencies += translator.dependencies()

        # Analyse the equation
        if condition == []: # Call Equation
            translator = Equation(variable['name'], eq,
                                  description,
                                  method = method,
                                  untouched = untouched.keys())
            code = translator.parse()
            dependencies += translator.dependencies()

        else: # An if-then-else statement
            code, deps = translate_ITE(variable['name'], eq, condition, description,
                    untouched)
            dependencies += deps

        if isinstance(code, str):
            pre_loop = {}
            cpp_eq = code
            switch = None
        else: # ODE
            pre_loop = code[0]
            cpp_eq = code[1]
            switch = code[2]

        # Replace untouched variables with their original name
        for prev, new in untouched.items():
            cpp_eq = cpp_eq.replace(prev, new)

        # Replace local functions
        for f in description['functions']:
            cpp_eq = re.sub(r'([^\w]*)'+f['name']+'\(', r'\1'+ f['name'] + '(', ' ' + cpp_eq).strip()

        # Store the result
        variable['pre_loop'] = pre_loop # Things to be declared before the for loop (eg. dt)
        variable['cpp'] = cpp_eq # the C++ equation
        variable['switch'] = switch # switch value id ODE
        variable['untouched'] = untouched # may be needed later
        variable['method'] = method # may be needed later
        variable['dependencies'] = dependencies 

        # If the method is implicit or midpoint, the equations must be solved concurrently (depend on v[t+1])
        if method in ['implicit', 'midpoint'] and switch is not None:
            concurrent_odes.append(variable)

    # After all variables are processed, do it again if they are concurrent
    if len(concurrent_odes) > 1 :
        solver = CoupledEquations(description, concurrent_odes)
        new_eqs = solver.parse()
        for idx, variable in enumerate(description['variables']):
            for new_eq in new_eqs:
                if variable['name'] == new_eq['name']:
                    description['variables'][idx] = new_eq

    # Translate the psp code if any
    if 'raw_psp' in description.keys():
        psp = {'eq' : description['raw_psp'].strip() }

        # Extract global operations
        eq, untouched_globs, global_ops = extract_globalops_synapse('psp', " " + psp['eq'] + " ", description)
        description['pre_global_operations'] += global_ops['pre']
        description['post_global_operations'] += global_ops['post']

        # Replace pre- and post_synaptic variables
        eq, untouched, prepost_dependencies = extract_prepost('psp', eq, description)
        description['dependencies']['pre'] += prepost_dependencies['pre']
        description['dependencies']['post'] += prepost_dependencies['post']
        for name, val in untouched_globs.items():
            if not name in untouched.keys():
                untouched[name] = val

        # Extract if-then-else statements
        eq, condition = extract_ite('psp', eq, description, split=False)

        # Analyse the equation
        if condition == []:
            translator = Equation('psp', eq,
                                  description,
                                  method = 'explicit',
                                  untouched = untouched.keys(),
                                  type='return')
            code = translator.parse()
            deps = translator.dependencies()
        else:
            code, deps = translate_ITE('psp', eq, condition, description, untouched)

        # Replace untouched variables with their original name
        for prev, new in untouched.items():
            code = code.replace(prev, new)

        # Store the result
        psp['cpp'] = code
        psp['dependencies'] = deps
        description['psp'] = psp

    # Process event-driven info
    if description['type'] == 'spike':
        for variable in description['pre_spike'] + description['post_spike']:
            # Find plasticity
            if variable['name'] == 'w':
                description['plasticity'] = True

            # Retrieve the equation
            eq = variable['eq']
            
            # Extract if-then-else statements
            eq, condition = extract_ite(variable['name'], eq, description)

            # Extract pre- and post_synaptic variables
            eq, untouched, prepost_dependencies = extract_prepost(variable['name'], eq, description)

            # Update dependencies
            description['dependencies']['pre'] += prepost_dependencies['pre']
            description['dependencies']['post'] += prepost_dependencies['post']
            # and also on the variable for checking
            variable['prepost_dependencies'] = prepost_dependencies

            # Analyse the equation
            dependencies = []
            if condition == []:
                translator = Equation(variable['name'], 
                                      eq,
                                      description,
                                      method = 'explicit',
                                      untouched = untouched)
                code = translator.parse()
                dependencies += translator.dependencies()
            else:
                code, deps = translate_ITE(variable['name'], eq, condition, description, untouched)
                dependencies += deps

            if isinstance(code, list): # an ode in a pre/post statement
                Global._print(eq)
                if variable in description['pre_spike']:
                    Global._error('It is forbidden to use ODEs in a pre_spike term.')
                elif variable in description['posz_spike']:
                    Global._error('It is forbidden to use ODEs in a post_spike term.')
                else:
                    Global._error('It is forbidden to use ODEs here.')

            # Replace untouched variables with their original name
            for prev, new in untouched.items():
                code = code.replace(prev, new)

            # Process the bounds
            if 'min' in variable['bounds'].keys():
                if isinstance(variable['bounds']['min'], str):
                    translator = Equation(
                                    variable['name'],
                                    variable['bounds']['min'],
                                    description,
                                    type = 'return',
                                    untouched = untouched )
                    variable['bounds']['min'] = translator.parse().replace(';', '')
                    dependencies += translator.dependencies()

            if 'max' in variable['bounds'].keys():
                if isinstance(variable['bounds']['max'], str):
                    translator = Equation(
                                    variable['name'],
                                    variable['bounds']['max'],
                                    description,
                                    type = 'return',
                                    untouched = untouched)
                    variable['bounds']['max'] = translator.parse().replace(';', '')
                    dependencies += translator.dependencies()


            # Store the result
            variable['cpp'] = code # the C++ equation
            variable['dependencies'] = dependencies

    # Structural plasticity
    if synapse.pruning:
        description['pruning'] = extract_structural_plasticity(synapse.pruning, description)
    if synapse.creating:
        description['creating'] = extract_structural_plasticity(synapse.creating, description)

    return description