Esempio n. 1
0
def freeze(code, ns):
    # this is a bit of a hack, it should be passed to the template somehow
    for k, v in ns.items():
        if isinstance(v, (int, float)): # for the namespace provided for functions
            code = word_substitute(code, {k: str(v)})
        elif (isinstance(v, Variable) and not isinstance(v, AttributeVariable) and
              v.scalar and v.constant and v.read_only):
            code = word_substitute(code, {k: repr(v.get_value())})
    return code
Esempio n. 2
0
def ufunc_at_vectorisation(statements, variables, indices, index):
    '''
    '''
    # We assume that the code has passed the test for synapse order independence

    main_index_variables = [v for v in variables if indices[v] == index]

    lines = []
    need_unique_indices = set()

    for statement in statements:
        vars_in_expr = get_identifiers(statement.expr).intersection(variables)
        subs = {}
        for var in vars_in_expr:
            subs[var] = '{var}[{idx}]'.format(var=var, idx=indices[var])
        expr = word_substitute(statement.expr, subs)
        if statement.var in main_index_variables:
            line = '{var}[{idx}] {op} {expr}'.format(var=statement.var,
                                                     op=statement.op,
                                                     expr=expr,
                                                     idx=index)
            lines.append(line)
        else:
            if statement.inplace:
                if statement.op == '+=':
                    ufunc_name = '_numpy.add'
                elif statement.op == '*=':
                    ufunc_name = '_numpy.multiply'
                else:
                    raise SynapseVectorisationError()
                line = '{ufunc_name}.at({var}, {idx}, {expr})'.format(
                    ufunc_name=ufunc_name,
                    var=statement.var,
                    idx=indices[statement.var],
                    expr=expr)
                lines.append(line)
            else:
                # if statement is not in-place then we assume the expr has no synaptic
                # variables in it otherwise it would have failed the order independence
                # check. In this case, we only need to work with the unique indices
                need_unique_indices.add(indices[statement.var])
                idx = '_unique_' + indices[statement.var]
                expr = word_substitute(expr, {indices[statement.var]: idx})
                line = '{var}[{idx}] = {expr}'.format(var=statement.var,
                                                      idx=idx,
                                                      expr=expr)
                lines.append(line)

    for unique_idx in need_unique_indices:
        lines.insert(
            0, '_unique_{idx} = _numpy.unique({idx})'.format(idx=unique_idx))

    return '\n'.join(lines)
Esempio n. 3
0
def ufunc_at_vectorisation(statements, variables, indices, index):
    '''
    '''
    # We assume that the code has passed the test for synapse order independence

    main_index_variables = [v for v in variables if indices[v] == index]
    
    lines = []
    need_unique_indices = set()
    
    for statement in statements:
        vars_in_expr = get_identifiers(statement.expr).intersection(variables)
        subs = {}
        for var in vars_in_expr:
            subs[var] = '{var}[{idx}]'.format(var=var, idx=indices[var])
        expr = word_substitute(statement.expr, subs)
        if statement.var in main_index_variables:
            line = '{var}[{idx}] {op} {expr}'.format(var=statement.var,
                                                     op=statement.op,
                                                     expr=expr,
                                                     idx=index)
            lines.append(line)
        else:
            if statement.inplace:
                if statement.op=='+=':
                    ufunc_name = '_numpy.add'
                elif statement.op=='*=':
                    ufunc_name = '_numpy.multiply'
                else:
                    raise SynapseVectorisationError()
                line = '{ufunc_name}.at({var}, {idx}, {expr})'.format(ufunc_name=ufunc_name,
                                                                      var=statement.var,
                                                                      idx=indices[statement.var],
                                                                      expr=expr)
                lines.append(line)
            else:
                # if statement is not in-place then we assume the expr has no synaptic
                # variables in it otherwise it would have failed the order independence
                # check. In this case, we only need to work with the unique indices
                need_unique_indices.add(indices[statement.var])
                idx = '_unique_' + indices[statement.var]
                expr = word_substitute(expr, {indices[statement.var]: idx})
                line = '{var}[{idx}] = {expr}'.format(var=statement.var,
                                                      idx=idx, expr=expr)
                lines.append(line)

    for unique_idx in need_unique_indices:
        lines.insert(0, '_unique_{idx} = _numpy.unique({idx})'.format(idx=unique_idx))
        
    return '\n'.join(lines)
Esempio n. 4
0
def freeze(code, ns):
    # this is a bit of a hack, it should be passed to the template somehow
    for k, v in ns.items():
        if isinstance(v, (int, float)): # for the namespace provided for functions
            code = word_substitute(code, {k: str(v)})
        elif (isinstance(v, Variable) and not isinstance(v, AttributeVariable) and
              v.scalar and v.constant and v.read_only):
            value = v.get_value()
            if value < 0:
                string_value = '(%r)' % value
            else:
                string_value = '%r' % value
            code = word_substitute(code, {k: string_value})
    return code
Esempio n. 5
0
def translate_subexpression(subexpr, variables):
    substitutions = {}
    for name in get_identifiers(subexpr.expr):
        if name not in subexpr.owner.variables:
            # Seems to be a name referring to an external variable,
            # nothing to do
            continue
        subexpr_var = subexpr.owner.variables[name]
        if name in variables and variables[name] is subexpr_var:
            # Variable is available under the same name, nothing to do
            continue

        # The variable is not available under the same name, but maybe
        # under a different name (e.g. x_post instead of x)
        found_variable = False
        for varname, variable in variables.iteritems():
            if variable is subexpr_var:
                # We found it
                substitutions[name] = varname
                found_variable = True
                break
        if not found_variable:
            raise KeyError(('Variable %s, referred to by the subexpression '
                            '%s, is not available in this '
                            'context.') % (name, subexpr.name))
    new_expr = word_substitute(subexpr.expr, substitutions)
    return new_expr
Esempio n. 6
0
 def _get_substituted_expressions(self):
     '''
     Return a list of ``(varname, expr)`` tuples, containing all
     differential equations with all the static equation variables
     substituted with the respective expressions.
     
     Returns
     -------
     expr_tuples : list of (str, `CodeString`)
         A list of ``(varname, expr)`` tuples, where ``expr`` is a
         `CodeString` object with all static equation variables substituted
         with the respective expression.
     '''
     subst_exprs = []
     substitutions = {}        
     for eq in self.equations_ordered:
         # Skip parameters
         if eq.expr is None:
             continue
         
         expr = eq.expr.replace_code(word_substitute(eq.expr.code, substitutions))
         
         if eq.eq_type == STATIC_EQUATION:
             substitutions.update({eq.varname: '(%s)' % expr.code})
         elif eq.eq_type == DIFFERENTIAL_EQUATION:
             #  a differential equation that we have to check                                
             expr.resolve(self.names)
             subst_exprs.append((eq.varname, expr))
         else:
             raise AssertionError('Unknown equation type %s' % eq.eq_type)
     
     return subst_exprs        
Esempio n. 7
0
 def translate_expression(self, expr):
     for varname, var in self.variables.iteritems():
         if isinstance(var, Function):
             impl_name = var.implementations[self.codeobj_class].name
             if impl_name is not None:
                 expr = word_substitute(expr, {varname: impl_name})
     return NumpyNodeRenderer().render_expr(expr, self.variables).strip()
Esempio n. 8
0
 def add_referred_subexpression(self, name, subexpr, index):
     identifiers = subexpr.identifiers
     substitutions = {}
     for identifier in identifiers:
         if not identifier in subexpr.owner.variables:
             # external variable --> nothing to do
             continue
         subexpr_var = subexpr.owner.variables[identifier]
         if hasattr(subexpr_var, 'owner'):
             new_name = '_%s_%s_%s' % (name, subexpr.owner.name, identifier)
         else:
             new_name = '_%s_%s' % (name, identifier)
         substitutions[identifier] = new_name
         self.indices[new_name] = index
         if isinstance(subexpr_var, Subexpression):
             self.add_referred_subexpression(new_name, subexpr_var, index)
         else:
             self.add_reference(new_name, subexpr_var, index)
     new_expr = word_substitute(subexpr.expr, substitutions)
     new_subexpr = Subexpression(name,
                                 subexpr.unit,
                                 self.owner,
                                 new_expr,
                                 device=subexpr.device,
                                 dtype=subexpr.dtype,
                                 scalar=subexpr.scalar)
     self._variables[name] = new_subexpr
Esempio n. 9
0
 def translate_expression(self, expr):
     for varname, var in self.variables.iteritems():
         if isinstance(var, Function):
             impl_name = var.implementations[self.codeobj_class].name
             if impl_name is not None:
                 expr = word_substitute(expr, {varname: impl_name})
     return NumpyNodeRenderer().render_expr(expr, self.variables).strip()
Esempio n. 10
0
    def _add_user_function(self, varname, variable):
        impl = variable.implementations[self.codeobj_class]
        support_code = []
        hash_defines = []
        pointers = []
        user_functions = [(varname, variable)]
        funccode = impl.get_code(self.owner)
        if isinstance(funccode, str):
            # Rename references to any dependencies if necessary
            for dep_name, dep in iteritems(impl.dependencies):
                dep_impl = dep.implementations[self.codeobj_class]
                dep_impl_name = dep_impl.name
                if dep_impl_name is None:
                    dep_impl_name = dep.pyfunc.__name__
                if dep_name != dep_impl_name:
                    funccode = word_substitute(funccode,
                                               {dep_name: dep_impl_name})
            funccode = {'support_code': funccode}
        if funccode is not None:
            # To make namespace variables available to functions, we
            # create global variables and assign to them in the main
            # code
            func_namespace = impl.get_namespace(self.owner) or {}
            for ns_key, ns_value in iteritems(func_namespace):
                if hasattr(ns_value, 'dtype'):
                    if ns_value.shape == ():
                        raise NotImplementedError((
                        'Directly replace scalar values in the function '
                        'instead of providing them via the namespace'))
                    type_str = c_data_type(ns_value.dtype) + '*'
                else:  # e.g. a function
                    type_str = 'py::object'
                support_code.append('static {0} _namespace{1};'.format(type_str,
                                                                       ns_key))
                pointers.append('_namespace{0} = {1};'.format(ns_key, ns_key))
            support_code.append(deindent(funccode.get('support_code', '')))
            hash_defines.append(deindent(funccode.get('hashdefine_code', '')))

        dep_hash_defines = []
        dep_pointers = []
        dep_support_code = []
        if impl.dependencies is not None:
            for dep_name, dep in iteritems(impl.dependencies):
                if dep_name not in self.variables:  # do not add a dependency twice
                    self.variables[dep_name] = dep
                    dep_impl = dep.implementations[self.codeobj_class]
                    if dep_name != dep_impl.name:
                        self.func_name_replacements[dep_name] = dep_impl.name
                    hd, ps, sc, uf = self._add_user_function(dep_name, dep)
                    dep_hash_defines.extend(hd)
                    dep_pointers.extend(ps)
                    dep_support_code.extend(sc)
                    user_functions.extend(uf)

        return (dep_hash_defines + hash_defines,
                dep_pointers + pointers,
                dep_support_code + support_code,
                user_functions)
Esempio n. 11
0
    def render_node(self, node):
        expr = NodeRenderer(use_vectorisation_idx=False).render_node(node)

        if is_scalar_expression(expr, self.variables) and not has_non_float(expr,
                                                                            self.variables):
            if expr in self.optimisations:
                name = self.optimisations[expr]
            else:
                # Do not pull out very simple expressions (including constants
                # and numbers)
                sympy_expr = str_to_sympy(expr)
                if expression_complexity(sympy_expr) < 2:
                    return expr
                self.n += 1
                name = '_lio_const_'+str(self.n)
                self.optimisations[expr] = name
            return name
        else:
            for varname, var in self.boolvars.iteritems():
                expr_0 = word_substitute(expr, {varname: '0.0'})
                expr_1 = '(%s)-(%s)' % (word_substitute(expr, {varname: '1.0'}), expr_0)
                if (is_scalar_expression(expr_0, self.variables) and is_scalar_expression(expr_1, self.variables) and
                        not has_non_float(expr, self.variables)):
                    # we do this check here because we don't want to apply it to statements, only expressions
                    if expression_complexity(expr)<=4:
                        break
                    if expr_0 not in self.optimisations:
                        self.n += 1
                        name_0 = '_lio_const_'+str(self.n)
                        self.optimisations[expr_0] = name_0
                    else:
                        name_0 = self.optimisations[expr_0]
                    if expr_1 not in self.optimisations:
                        self.n += 1
                        name_1 = '_lio_const_'+str(self.n)
                        self.optimisations[expr_1] = name_1
                    else:
                        name_1 = self.optimisations[expr_1]
                    newexpr = '({name_0}+{name_1}*int({varname}))'.format(name_0=name_0, name_1=name_1,
                                                                     varname=varname)
                    return newexpr
            return NodeRenderer.render_node(self, node)
Esempio n. 12
0
def numerically_check_permutation_code(code):
    # numerically checks that a code block used in the test below is permutation-independent by creating a
    # presynaptic and postsynaptic group of 3 neurons each, and a full connectivity matrix between them, then
    # repeatedly filling in random values for each of the variables, and checking for several random shuffles of
    # the synapse order that the result doesn't depend on it. This is a sort of test of the test itself, to make
    # sure we didn't accidentally assign a good/bad example to the wrong class.
    code = deindent(code)
    from collections import defaultdict
    vars = get_identifiers(code)
    indices = defaultdict(lambda: '_idx')
    vals = {}
    for var in vars:
        if var.endswith('_syn'):
            indices[var] = '_idx'
            vals[var] = zeros(9)
        elif var.endswith('_pre'):
            indices[var] ='_presynaptic_idx'
            vals[var] = zeros(3)
        elif var.endswith('_post'):
            indices[var] = '_postsynaptic_idx'
            vals[var] = zeros(3)
    subs = dict((var, var+'['+idx+']') for var, idx in indices.iteritems())
    code = word_substitute(code, subs)
    code = '''
from numpy import *
from numpy.random import rand, randn
for _idx in shuffled_indices:
    _presynaptic_idx = presyn[_idx]
    _postsynaptic_idx = postsyn[_idx]
{code}
    '''.format(code=indent(code))
    ns = vals.copy()
    ns['shuffled_indices'] = arange(9)
    ns['presyn'] = arange(9)%3
    ns['postsyn'] = arange(9)/3
    for _ in xrange(10):
        origvals = {}
        for k, v in vals.iteritems():
            v[:] = randn(len(v))
            origvals[k] = v.copy()
        exec code in ns
        endvals = {}
        for k, v in vals.iteritems():
            endvals[k] = v.copy()
        for _ in xrange(10):
            for k, v in vals.iteritems():
                v[:] = origvals[k]
            shuffle(ns['shuffled_indices'])
            exec code in ns
            for k, v in vals.iteritems():
                try:
                    assert_allclose(v, endvals[k])
                except AssertionError:
                    raise OrderDependenceError()
Esempio n. 13
0
def freeze(code, ns):
    # this is a bit of a hack, it should be passed to the template somehow
    for k, v in ns.items():

        if (isinstance(v, Variable) and not isinstance(v, AttributeVariable) and
              v.scalar and v.constant and v.read_only):
            v = v.get_value()

        if isinstance(v, basestring):
            code = word_substitute(code, {k: v})
        elif isinstance(v, numbers.Number):
            # Use a renderer to correctly transform constants such as True or inf
            renderer = CPPNodeRenderer()
            string_value = renderer.render_expr(repr(v))
            if v < 0:
                string_value = '(%s)' % string_value
            code = word_substitute(code, {k: string_value})
        else:
            pass  # don't deal with this object
    return code
Esempio n. 14
0
def freeze(code, ns):
    # this is a bit of a hack, it should be passed to the template somehow
    for k, v in ns.items():

        if (isinstance(v, Variable) and not isinstance(v, AttributeVariable)
                and v.scalar and v.constant and v.read_only):
            v = v.get_value()

        if isinstance(v, basestring):
            code = word_substitute(code, {k: v})
        elif isinstance(v, numbers.Number):
            # Use a renderer to correctly transform constants such as True or inf
            renderer = CPPNodeRenderer()
            string_value = renderer.render_expr(repr(v))
            if v < 0:
                string_value = '(%s)' % string_value
            code = word_substitute(code, {k: string_value})
        else:
            pass  # don't deal with this object
    return code
Esempio n. 15
0
 def translate_expression(self, expr):
     for varname, var in self.variables.iteritems():
         if isinstance(var, Function):
             try:
                 impl_name = var.implementations[self.codeobj_class].name
             except KeyError:
                 raise NotImplementedError('Function {} is not available '
                                           'for use with '
                                           'GeNN.'.format(var.name))
             if impl_name is not None:
                 expr = word_substitute(expr, {varname: impl_name})
     return CPPNodeRenderer().render_expr(expr).strip()
Esempio n. 16
0
 def conditional_write(self, line, stmt, variables, conditional_write_vars,
                       created_vars):
     if stmt.var in conditional_write_vars:
         subs = {}
         index = conditional_write_vars[stmt.var]
         # we replace all var with var[index], but actually we use this repl_string first because
         # we don't want to end up with lines like x[not_refractory[not_refractory]] when
         # multiple substitution passes are invoked
         repl_string = '#$(@#&$@$*U#@)$@(#'  # this string shouldn't occur anywhere I hope! :)
         for varname, var in variables.items():
             if isinstance(var, ArrayVariable):
                 subs[varname] = varname + '[' + repl_string + ']'
         # all newly created vars are arrays and will need indexing
         for varname in created_vars:
             subs[varname] = varname + '[' + repl_string + ']'
         line = word_substitute(line, subs)
         line = line.replace(repl_string, index)
     return line
Esempio n. 17
0
 def conditional_write(self, line, stmt, variables, conditional_write_vars,
                       created_vars):
     if stmt.var in conditional_write_vars:
         subs = {}
         index = conditional_write_vars[stmt.var]
         # we replace all var with var[index], but actually we use this repl_string first because
         # we don't want to end up with lines like x[not_refractory[not_refractory]] when
         # multiple substitution passes are invoked
         repl_string = '#$(@#&$@$*U#@)$@(#'  # this string shouldn't occur anywhere I hope! :)
         for varname, var in variables.items():
             if isinstance(var, ArrayVariable):
                 subs[varname] = varname + '[' + repl_string + ']'
         # all newly created vars are arrays and will need indexing
         for varname in created_vars:
             subs[varname] = varname + '[' + repl_string + ']'
         line = word_substitute(line, subs)
         line = line.replace(repl_string, index)
     return line
Esempio n. 18
0
    def translate_vector_code(self, code_lines, to_replace):
        '''
        Translate vector code to GSL compatible code by substituting fragments
        of code.

        Parameters
        ----------
        code_lines : list
            list of strings describing the vector_code
        to_replace: dict
            dictionary with to be replaced strings (see to_replace_vector_vars
            and to_replace_diff_vars)

        Returns
        -------
        vector_code : str
            New code that is now to be added to the function that is sent to the
            GSL integrator
        '''
        code = []
        for expr_set in code_lines:
            for line in expr_set.split(
                    '\n'):  # every line seperate to make tabbing correct
                code += ['\t' + line]
        code = ('\n').join(code)
        code = word_substitute(code, to_replace)

        # special substitute because of limitations of regex word boundaries with
        # variable[_idx]
        for from_sub, to_sub in to_replace.items():
            m = re.search('\[(\w+)\];?$', from_sub)
            if m:
                code = re.sub(re.sub('\[', '\[', from_sub), to_sub, code)

        if '_gsl' in code:
            raise AssertionError(
                ('Translation failed, _gsl still in code (should only '
                 'be tag, and should be replaced.\n'
                 'Code:\n%s' % code))

        return code
Esempio n. 19
0
    def translate_vector_code(self, code_lines, to_replace):
        '''
        Translate vector code to GSL compatible code by substituting fragments
        of code.

        Parameters
        ----------
        code_lines : list
            list of strings describing the vector_code
        to_replace: dict
            dictionary with to be replaced strings (see to_replace_vector_vars
            and to_replace_diff_vars)

        Returns
        -------
        vector_code : str
            New code that is now to be added to the function that is sent to the
            GSL integrator
        '''
        code = []
        for expr_set in code_lines:
            for line in expr_set.split('\n'): # every line seperate to make tabbing correct
                code += ['\t' + line]
        code = ('\n').join(code)
        code = word_substitute(code, to_replace)

        # special substitute because of limitations of regex word boundaries with
        # variable[_idx]
        for from_sub, to_sub in to_replace.items():
            m = re.search('\[(\w+)\];?$', from_sub)
            if m:
                code = re.sub(re.sub('\[','\[', from_sub), to_sub, code)

        if '_gsl' in code:
            raise AssertionError(('Translation failed, _gsl still in code (should only '
                             'be tag, and should be replaced.\n'
                             'Code:\n%s' % code))

        return code
Esempio n. 20
0
 def translate_expression(self, expr):
     expr = word_substitute(expr, self.func_name_replacements)
     return NumpyNodeRenderer().render_expr(expr, self.variables).strip()
Esempio n. 21
0
def optimise_statements(scalar_statements, vector_statements, variables, blockname=''):
    '''
    Optimise a sequence of scalar and vector statements

    Performs the following optimisations:

    1. Constant evaluations (e.g. exp(0) to 1). See `evaluate_expr`.
    2. Arithmetic simplifications (e.g. 0*x to 0). See `ArithmeticSimplifier`, `collect`.
    3. Pulling out loop invariants (e.g. v*exp(-dt/tau) to a=exp(-dt/tau) outside the loop and v*a inside).
       See `Simplifier`.
    4. Boolean simplifications (allowing the replacement of expressions with booleans with a sequence of if/thens).
       See `Simplifier`.

    Parameters
    ----------
    scalar_statements : sequence of Statement
        Statements that only involve scalar values and should be evaluated in the scalar block.
    vector_statements : sequence of Statement
        Statements that involve vector values and should be evaluated in the vector block.
    variables : dict of (str, Variable)
        Definition of the types of the variables.
    blockname : str, optional
        Name of the block (used for LIO constant prefixes to avoid name clashes)

    Returns
    -------
    new_scalar_statements : sequence of Statement
        As above but with loop invariants pulled out from vector statements
    new_vector_statements : sequence of Statement
        Simplified/optimised versions of statements
    '''
    boolvars = dict((k, v) for k, v in variables.iteritems()
                    if hasattr(v, 'dtype') and brian_dtype_from_dtype(v.dtype)=='boolean')
    # We use the Simplifier class by rendering each expression, which generates new scalar statements
    # stored in the Simplifier object, and these are then added to the scalar statements.
    simplifier = Simplifier(variables, scalar_statements, extra_lio_prefix=blockname)
    new_vector_statements = []
    for stmt in vector_statements:
        # Carry out constant evaluation, arithmetic simplification and loop invariants
        new_expr = simplifier.render_expr(stmt.expr)
        new_stmt = Statement(stmt.var, stmt.op, new_expr, stmt.comment,
                             dtype=stmt.dtype,
                             constant=stmt.constant,
                             subexpression=stmt.subexpression,
                             scalar=stmt.scalar)
        # Now check if boolean simplification can be carried out
        complexity_std = expression_complexity(new_expr, simplifier.variables)
        idents = get_identifiers(new_expr)
        used_boolvars = [var for var in boolvars.iterkeys() if var in idents]
        if len(used_boolvars):
            # We want to iterate over all the possible assignments of boolean variables to values in (True, False)
            bool_space = [[False, True] for var in used_boolvars]
            expanded_expressions = {}
            complexities = {}
            for bool_vals in itertools.product(*bool_space):
                # substitute those values into the expr and simplify (including potentially pulling out new
                # loop invariants)
                subs = dict((var, str(val)) for var, val in zip(used_boolvars, bool_vals))
                curexpr = word_substitute(new_expr, subs)
                curexpr = simplifier.render_expr(curexpr)
                key = tuple((var, val) for var, val in zip(used_boolvars, bool_vals))
                expanded_expressions[key] = curexpr
                complexities[key] = expression_complexity(curexpr, simplifier.variables)
            # See Statement for details on these
            new_stmt.used_boolean_variables = used_boolvars
            new_stmt.boolean_simplified_expressions = expanded_expressions
            new_stmt.complexity_std = complexity_std
            new_stmt.complexities = complexities
        new_vector_statements.append(new_stmt)
    # Generate additional scalar statements for the loop invariants
    new_scalar_statements = copy.copy(scalar_statements)
    for expr, name in simplifier.loop_invariants.iteritems():
        dtype_name = simplifier.loop_invariant_dtypes[name]
        if dtype_name=='boolean':
            dtype = bool
        elif dtype_name=='integer':
            dtype = int
        else:
            dtype = float
        new_stmt = Statement(name, ':=', expr, '',
                             dtype=dtype,
                             constant=True,
                             subexpression=False,
                             scalar=True)
        new_scalar_statements.append(new_stmt)
    return new_scalar_statements, new_vector_statements
Esempio n. 22
0
 def translate_expression(self, expr):
     expr = word_substitute(expr, self.func_name_replacements)
     return CPPNodeRenderer().render_expr(expr).strip()
Esempio n. 23
0
def optimise_statements(scalar_statements,
                        vector_statements,
                        variables,
                        blockname=''):
    '''
    Optimise a sequence of scalar and vector statements

    Performs the following optimisations:

    1. Constant evaluations (e.g. exp(0) to 1). See `evaluate_expr`.
    2. Arithmetic simplifications (e.g. 0*x to 0). See `ArithmeticSimplifier`, `collect`.
    3. Pulling out loop invariants (e.g. v*exp(-dt/tau) to a=exp(-dt/tau) outside the loop and v*a inside).
       See `Simplifier`.
    4. Boolean simplifications (allowing the replacement of expressions with booleans with a sequence of if/thens).
       See `Simplifier`.

    Parameters
    ----------
    scalar_statements : sequence of Statement
        Statements that only involve scalar values and should be evaluated in the scalar block.
    vector_statements : sequence of Statement
        Statements that involve vector values and should be evaluated in the vector block.
    variables : dict of (str, Variable)
        Definition of the types of the variables.
    blockname : str, optional
        Name of the block (used for LIO constant prefixes to avoid name clashes)

    Returns
    -------
    new_scalar_statements : sequence of Statement
        As above but with loop invariants pulled out from vector statements
    new_vector_statements : sequence of Statement
        Simplified/optimised versions of statements
    '''
    boolvars = dict((k, v) for k, v in variables.items()
                    if hasattr(v, 'dtype')
                    and brian_dtype_from_dtype(v.dtype) == 'boolean')
    # We use the Simplifier class by rendering each expression, which generates new scalar statements
    # stored in the Simplifier object, and these are then added to the scalar statements.
    simplifier = Simplifier(variables,
                            scalar_statements,
                            extra_lio_prefix=blockname)
    new_vector_statements = []
    for stmt in vector_statements:
        # Carry out constant evaluation, arithmetic simplification and loop invariants
        new_expr = simplifier.render_expr(stmt.expr)
        new_stmt = Statement(stmt.var,
                             stmt.op,
                             new_expr,
                             stmt.comment,
                             dtype=stmt.dtype,
                             constant=stmt.constant,
                             subexpression=stmt.subexpression,
                             scalar=stmt.scalar)
        # Now check if boolean simplification can be carried out
        complexity_std = expression_complexity(new_expr, simplifier.variables)
        idents = get_identifiers(new_expr)
        used_boolvars = [var for var in boolvars if var in idents]
        if len(used_boolvars):
            # We want to iterate over all the possible assignments of boolean variables to values in (True, False)
            bool_space = [[False, True] for var in used_boolvars]
            expanded_expressions = {}
            complexities = {}
            for bool_vals in itertools.product(*bool_space):
                # substitute those values into the expr and simplify (including potentially pulling out new
                # loop invariants)
                subs = dict((var, str(val))
                            for var, val in zip(used_boolvars, bool_vals))
                curexpr = word_substitute(new_expr, subs)
                curexpr = simplifier.render_expr(curexpr)
                key = tuple(
                    (var, val) for var, val in zip(used_boolvars, bool_vals))
                expanded_expressions[key] = curexpr
                complexities[key] = expression_complexity(
                    curexpr, simplifier.variables)
            # See Statement for details on these
            new_stmt.used_boolean_variables = used_boolvars
            new_stmt.boolean_simplified_expressions = expanded_expressions
            new_stmt.complexity_std = complexity_std
            new_stmt.complexities = complexities
        new_vector_statements.append(new_stmt)
    # Generate additional scalar statements for the loop invariants
    new_scalar_statements = copy.copy(scalar_statements)
    for expr, name in simplifier.loop_invariants.items():
        dtype_name = simplifier.loop_invariant_dtypes[name]
        if dtype_name == 'boolean':
            dtype = bool
        elif dtype_name == 'integer':
            dtype = int
        else:
            dtype = prefs.core.default_float_dtype
        new_stmt = Statement(name,
                             ':=',
                             expr,
                             '',
                             dtype=dtype,
                             constant=True,
                             subexpression=False,
                             scalar=True)
        new_scalar_statements.append(new_stmt)
    return new_scalar_statements, new_vector_statements
Esempio n. 24
0
 def translate_expression(self, expr):
     expr = word_substitute(expr, self.func_name_replacements)
     return NumpyNodeRenderer(
         auto_vectorise=self.auto_vectorise).render_expr(
             expr, self.variables).strip()
Esempio n. 25
0
    def translate_one_statement_sequence(self, statements):
        variables = self.variables
        variable_indices = self.variable_indices
        read, write, indices, conditional_write_vars = self.arrays_helper(
            statements)
        lines = []
        # index and read arrays (index arrays first)
        for varname in itertools.chain(indices, read):
            var = variables[varname]
            index = variable_indices[varname]
            #            if index in iterate_all:
            #                line = '{varname} = {array_name}'
            #            else:
            #                line = '{varname} = {array_name}.take({index})'
            #            line = line.format(varname=varname, array_name=self.get_array_name(var), index=index)
            line = varname + ' = ' + self.get_array_name(var)
            if not index in self.iterate_all:
                line = line + '[' + index + ']'
            lines.append(line)
        # the actual code
        created_vars = set([])
        for stmt in statements:
            if stmt.op == ':=':
                created_vars.add(stmt.var)
            line = self.translate_statement(stmt)
            if stmt.var in conditional_write_vars:
                subs = {}
                index = conditional_write_vars[stmt.var]
                # we replace all var with var[index], but actually we use this repl_string first because
                # we don't want to end up with lines like x[not_refractory[not_refractory]] when
                # multiple substitution passes are invoked
                repl_string = '#$(@#&$@$*U#@)$@(#'  # this string shouldn't occur anywhere I hope! :)
                for varname, var in variables.items():
                    if isinstance(var, ArrayVariable):
                        subs[varname] = varname + '[' + repl_string + ']'
                # all newly created vars are arrays and will need indexing
                for varname in created_vars:
                    subs[varname] = varname + '[' + repl_string + ']'
                line = word_substitute(line, subs)
                line = line.replace(repl_string, index)
            lines.append(line)
        # write arrays
        for varname in write:
            var = variables[varname]
            index_var = variable_indices[varname]
            # check if all operations were inplace and we're operating on the
            # whole vector, if so we don't need to write the array back
            if not index_var in self.iterate_all:
                all_inplace = False
            else:
                all_inplace = True
                for stmt in statements:
                    if stmt.var == varname and not stmt.inplace:
                        all_inplace = False
                        break
            if not all_inplace:
                line = self.get_array_name(var)
                if index_var in self.iterate_all:
                    line = line + '[:]'
                else:
                    line = line + '[' + index_var + ']'
                line = line + ' = ' + varname
                lines.append(line)
#                if index_var in iterate_all:
#                    line = '{array_name}[:] = {varname}'
#                else:
#                    line = '''
#if isinstance(_idx, slice):
#    {array_name}[:] = {varname}
#else:
#    {array_name}.put({index_var}, {varname})
#                    '''
#                    line = '\n'.join([l for l in line.split('\n') if l.strip()])
#                line = line.format(array_name=self.get_array_name(var), index_var=index_var, varname=varname)
#                if index_var in iterate_all:
#                    lines.append(line)
#                else:
#                    lines.extend(line.split('\n'))

# Make sure we do not use the __call__ function of Function objects but
# rather the Python function stored internally. The __call__ function
# would otherwise return values with units
        for varname, var in variables.iteritems():
            if isinstance(var, Function):
                variables[varname] = var.implementations[
                    self.codeobj_class].get_code(self.owner)

        return lines
Esempio n. 26
0
    def _add_user_function(self, varname, variable, added):
        impl = variable.implementations[self.codeobj_class]
        if (impl.name, variable) in added:
            return  # nothing to do
        else:
            added.add((impl.name, variable))
        support_code = []
        hash_defines = []
        pointers = []
        kernel_lines = []
        user_functions = [(varname, variable)]
        funccode = impl.get_code(self.owner)
        if isinstance(funccode, str):
            # Rename references to any dependencies if necessary
            for dep_name, dep in impl.dependencies.items():
                dep_impl = dep.implementations[self.codeobj_class]
                dep_impl_name = dep_impl.name
                if dep_impl_name is None:
                    dep_impl_name = dep.pyfunc.__name__
                if dep_name != dep_impl_name:
                    funccode = word_substitute(funccode, {dep_name: dep_impl_name})

        ### Different from CPPCodeGenerator: We format the funccode dtypes here
        from brian2.devices.device import get_device
        device = get_device()
        if varname in functions_C99:
            funccode = funccode.format(default_type=self.default_func_type,
                                       other_type=self.other_func_type)
        elif varname in ['clip', 'exprel']:
            funccode = funccode.format(float_dtype=self.float_dtype)
        ###

        if isinstance(funccode, str):
            funccode = {'support_code': funccode}
        if funccode is not None:
            # To make namespace variables available to functions, we
            # create global variables and assign to them in the main
            # code
            func_namespace = impl.get_namespace(self.owner) or {}
            for ns_key, ns_value in func_namespace.items():
                # This section is adapted from CPPCodeGenerator such that file
                # global namespace pointers can be used in both host and device
                # code.
                assert hasattr(ns_value, 'dtype'), \
                    'This should not have happened. Please report at ' \
                    'https://github.com/brian-team/brian2cuda/issues/new'
                if ns_value.shape == ():
                    raise NotImplementedError((
                    'Directly replace scalar values in the function '
                    'instead of providing them via the namespace'))
                type_str = self.c_data_type(ns_value.dtype) + '*'
                namespace_ptr = '''
                    #if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ > 0))
                    __device__ {dtype} _namespace{name};
                    #else
                    {dtype} _namespace{name};
                    #endif
                    '''.format(dtype=type_str, name=ns_key)
                support_code.append(namespace_ptr)
                # pointer lines will be used in codeobjects running on the host
                pointers.append('_namespace{name} = {name};'.format(name=ns_key))
                # kernel lines will be used in codeobjects running on the device
                kernel_lines.append('''
                    #if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ > 0))
                    _namespace{name} = d{name};
                    #else
                    _namespace{name} = {name};
                    #endif
                    '''.format(name=ns_key))
            support_code.append(deindent(funccode.get('support_code', '')))
            hash_defines.append(deindent(funccode.get('hashdefine_code', '')))

        dep_hash_defines = []
        dep_pointers = []
        dep_support_code = []
        dep_kernel_lines = []
        if impl.dependencies is not None:
            for dep_name, dep in impl.dependencies.items():
                if dep_name not in self.variables:
                    self.variables[dep_name] = dep
                    dep_impl = dep.implementations[self.codeobj_class]
                    if dep_name != dep_impl.name:
                        self.func_name_replacements[dep_name] = dep_impl.name
                    user_function = self._add_user_function(dep_name, dep, added)
                    if user_function is not None:
                        hd, ps, sc, uf, kl = user_function
                        dep_hash_defines.extend(hd)
                        dep_pointers.extend(ps)
                        dep_support_code.extend(sc)
                        user_functions.extend(uf)
                        dep_kernel_lines.extend(kl)

        return (dep_hash_defines + hash_defines,
                dep_pointers + pointers,
                dep_support_code + support_code,
                user_functions,
                dep_kernel_lines + kernel_lines)
Esempio n. 27
0
    def translate_one_statement_sequence(self, statements):
        variables = self.variables
        variable_indices = self.variable_indices
        read, write, indices, conditional_write_vars = self.arrays_helper(statements)
        lines = []
        # index and read arrays (index arrays first)
        for varname in itertools.chain(indices, read):
            var = variables[varname]
            index = variable_indices[varname]
#            if index in iterate_all:
#                line = '{varname} = {array_name}'
#            else:
#                line = '{varname} = {array_name}.take({index})'
#            line = line.format(varname=varname, array_name=self.get_array_name(var), index=index)
            line = varname + ' = ' + self.get_array_name(var)
            if not index in self.iterate_all:
                line += '[' + index + ']'
            elif varname in write:
                # avoid potential issues with aliased variables, see github #259
                line += '.copy()'
            lines.append(line)
        # the actual code
        created_vars = set([])
        for stmt in statements:
            if stmt.op==':=':
                created_vars.add(stmt.var)
            line = self.translate_statement(stmt)
            if stmt.var in conditional_write_vars:
                subs = {}
                index = conditional_write_vars[stmt.var]
                # we replace all var with var[index], but actually we use this repl_string first because
                # we don't want to end up with lines like x[not_refractory[not_refractory]] when
                # multiple substitution passes are invoked
                repl_string = '#$(@#&$@$*U#@)$@(#' # this string shouldn't occur anywhere I hope! :)
                for varname, var in variables.items():
                    if isinstance(var, ArrayVariable):
                        subs[varname] = varname+'['+repl_string+']'
                # all newly created vars are arrays and will need indexing
                for varname in created_vars:
                    subs[varname] = varname+'['+repl_string+']'
                line = word_substitute(line, subs)
                line = line.replace(repl_string, index)
            lines.append(line)
        # write arrays
        for varname in write:
            var = variables[varname]
            index_var = variable_indices[varname]
            line = self.get_array_name(var)
            if index_var in self.iterate_all:
                line = line + '[:]'
            else:
                line = line + '[' + index_var + ']'
            line = line + ' = ' + varname
            lines.append(line)
#                if index_var in iterate_all:
#                    line = '{array_name}[:] = {varname}'
#                else:
#                    line = '''
#if isinstance(_idx, slice):
#    {array_name}[:] = {varname}
#else:
#    {array_name}.put({index_var}, {varname})
#                    '''
#                    line = '\n'.join([l for l in line.split('\n') if l.strip()])
#                line = line.format(array_name=self.get_array_name(var), index_var=index_var, varname=varname)
#                if index_var in iterate_all:
#                    lines.append(line)
#                else:
#                    lines.extend(line.split('\n'))

        # Make sure we do not use the __call__ function of Function objects but
        # rather the Python function stored internally. The __call__ function
        # would otherwise return values with units
        for varname, var in variables.iteritems():
            if isinstance(var, Function):
                variables[varname] = var.implementations[self.codeobj_class].get_code(self.owner)

        return lines
Esempio n. 28
0
    def _add_user_function(self, varname, var, added):
        user_functions = []
        load_namespace = []
        support_code = []
        impl = var.implementations[self.codeobj_class]
        if (impl.name, var) in added:
            return  # nothing to do
        else:
            added.add((impl.name, var))
        func_code = impl.get_code(self.owner)
        # Implementation can be None if the function is already
        # available in Cython (possibly under a different name)
        if func_code is not None:
            if isinstance(func_code, str):
                # Function is provided as Cython code
                # To make namespace variables available to functions, we
                # create global variables and assign to them in the main
                # code
                user_functions.append((varname, var))
                func_namespace = impl.get_namespace(self.owner) or {}
                for ns_key, ns_value in func_namespace.items():
                    load_namespace.append(
                        f'# namespace for function {varname}')
                    if hasattr(ns_value, 'dtype'):
                        if ns_value.shape == ():
                            raise NotImplementedError((
                                'Directly replace scalar values in the function '
                                'instead of providing them via the namespace'))
                        newlines = [
                            "global _namespace{var_name}",
                            "global _namespace_num{var_name}",
                            "cdef _numpy.ndarray[{cpp_dtype}, ndim=1, mode='c'] _buf_{var_name} = _namespace['{var_name}']",
                            "_namespace{var_name} = <{cpp_dtype} *> _buf_{var_name}.data",
                            "_namespace_num{var_name} = len(_namespace['{var_name}'])"
                        ]
                        support_code.append(
                            f"cdef {get_cpp_dtype(ns_value.dtype)} *_namespace{ns_key}"
                        )

                    else:  # e.g. a function
                        newlines = [
                            "_namespace{var_name} = namespace['{var_name}']"
                        ]
                    for line in newlines:
                        load_namespace.append(
                            line.format(
                                cpp_dtype=get_cpp_dtype(ns_value.dtype),
                                numpy_dtype=get_numpy_dtype(ns_value.dtype),
                                var_name=ns_key))
                # Rename references to any dependencies if necessary
                for dep_name, dep in impl.dependencies.items():
                    dep_impl = dep.implementations[self.codeobj_class]
                    dep_impl_name = dep_impl.name
                    if dep_impl_name is None:
                        dep_impl_name = dep.pyfunc.__name__
                    if dep_name != dep_impl_name:
                        func_code = word_substitute(func_code,
                                                    {dep_name: dep_impl_name})
                support_code.append(deindent(func_code))
            elif callable(func_code):
                self.variables[varname] = func_code
                line = f'{varname} = _namespace["{varname}"]'
                load_namespace.append(line)
            else:
                raise TypeError(
                    f"Provided function implementation for function "
                    f"'{varname}' is neither a string nor callable (is "
                    f"type {type(func_code)} instead).")

        dep_support_code = []
        dep_load_namespace = []
        dep_user_functions = []
        if impl.dependencies is not None:
            for dep_name, dep in impl.dependencies.items():
                if dep_name not in self.variables:
                    self.variables[dep_name] = dep
                    user_func = self._add_user_function(dep_name, dep, added)
                    if user_func is not None:
                        sc, ln, uf = user_func
                        dep_support_code.extend(sc)
                        dep_load_namespace.extend(ln)
                        dep_user_functions.extend(uf)

        return (support_code + dep_support_code,
                dep_load_namespace + load_namespace,
                dep_user_functions + user_functions)