Пример #1
0
def cancel_identical_terms(primary, inverted):
    '''
    Cancel terms in a collection, e.g. a+b-a should be cancelled to b

    Simply renders the nodes into expressions and removes whenever there is a common expression
    in primary and inverted.

    Parameters
    ----------
    primary : list of AST nodes
        These are the nodes that are positive with respect to the operator, e.g.
        in x*y/z it would be [x, y].
    inverted : list of AST nodes
        These are the nodes that are inverted with respect to the operator, e.g.
        in x*y/z it would be [z].

    Returns
    -------
    primary : list of AST nodes
        Primary nodes after cancellation
    inverted : list of AST nodes
        Inverted nodes after cancellation
    '''
    nr = NodeRenderer(use_vectorisation_idx=False)
    expressions = dict((node, nr.render_node(node)) for node in primary)
    expressions.update(dict((node, nr.render_node(node)) for node in inverted))
    new_primary = []
    inverted_expressions = [expressions[term] for term in inverted]
    for term in primary:
        expr = expressions[term]
        if expr in inverted_expressions and term.stateless:
            new_inverted = []
            for iterm in inverted:
                if expressions[iterm] == expr:
                   expr = ''  # handled
                else:
                    new_inverted.append(iterm)
            inverted = new_inverted
            inverted_expressions = [expressions[term] for term in inverted]
        else:
            new_primary.append(term)
    return new_primary, inverted
Пример #2
0
def cancel_identical_terms(primary, inverted):
    '''
    Cancel terms in a collection, e.g. a+b-a should be cancelled to b

    Simply renders the nodes into expressions and removes whenever there is a common expression
    in primary and inverted.

    Parameters
    ----------
    primary : list of AST nodes
        These are the nodes that are positive with respect to the operator, e.g.
        in x*y/z it would be [x, y].
    inverted : list of AST nodes
        These are the nodes that are inverted with respect to the operator, e.g.
        in x*y/z it would be [z].

    Returns
    -------
    primary : list of AST nodes
        Primary nodes after cancellation
    inverted : list of AST nodes
        Inverted nodes after cancellation
    '''
    nr = NodeRenderer(use_vectorisation_idx=False)
    expressions = dict((node, nr.render_node(node)) for node in primary)
    expressions.update(dict((node, nr.render_node(node)) for node in inverted))
    new_primary = []
    inverted_expressions = [expressions[term] for term in inverted]
    for term in primary:
        expr = expressions[term]
        if expr in inverted_expressions and term.stateless:
            new_inverted = []
            for iterm in inverted:
                if expressions[iterm] == expr:
                    expr = ''  # handled
                else:
                    new_inverted.append(iterm)
            inverted = new_inverted
            inverted_expressions = [expressions[term] for term in inverted]
        else:
            new_primary.append(term)
    return new_primary, inverted
Пример #3
0
    def render_node(self, node):
        expr = NodeRenderer(use_vectorisation_idx=False).render_node(node)

        # Do not pull out constants or numbers
        if node.__class__.__name__ in ['Name', 'Num', 'NameConstant']:
            return expr

        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:
                self.n += 1
                name = '_lio_const_'+str(self.n)
                self.optimisations[expr] = name
            return name
        else:
            return NodeRenderer.render_node(self, node)
Пример #4
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 sympy.count_ops(sympy_expr, visual=False) < 2:
                    return expr
                self.n += 1
                name = '_lio_const_'+str(self.n)
                self.optimisations[expr] = name
            return name
        else:
            return NodeRenderer.render_node(self, node)
Пример #5
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 sympy.count_ops(sympy_expr, visual=False) < 2:
                    return expr
                self.n += 1
                name = '_lio_const_' + str(self.n)
                self.optimisations[expr] = name
            return name
        else:
            return NodeRenderer.render_node(self, node)
Пример #6
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)
Пример #7
0
class Simplifier(BrianASTRenderer):
    '''
    Carry out arithmetic simplifications (see `ArithmeticSimplifier`) and loop invariants

    Parameters
    ----------
    variables : dict of (str, Variable)
        Usual definition of variables.
    scalar_statements : sequence of Statement
        Predefined scalar statements that can be used as part of simplification

    Notes
    -----

    After calling `render_expr` on a sequence of expressions (coming from vector statements typically),
    this object will have some new attributes:

    ``loop_invariants`` : OrderedDict of (expression, varname)
        varname will be of the form ``_lio_N`` where ``N`` is some integer, and the expressions will be
        strings that correspond to scalar-only expressions that can be evaluated outside of the vector
        block.
    ``loop_invariant_dtypes`` : dict of (varname, dtypename)
        dtypename will be one of ``'boolean'``, ``'integer'``, ``'float'``.
    '''
    def __init__(self, variables, scalar_statements, extra_lio_prefix=''):
        BrianASTRenderer.__init__(self, variables, copy_variables=False)
        self.loop_invariants = OrderedDict()
        self.loop_invariant_dtypes = {}
        self.n = 0
        self.node_renderer = NodeRenderer(use_vectorisation_idx=False)
        self.arithmetic_simplifier = ArithmeticSimplifier(variables)
        self.scalar_statements = scalar_statements
        if extra_lio_prefix is None:
            extra_lio_prefix = ''
        if len(extra_lio_prefix):
            extra_lio_prefix = extra_lio_prefix+'_'
        self.extra_lio_prefix = extra_lio_prefix

    def render_expr(self, expr):
        node = brian_ast(expr, self.variables)
        node = self.arithmetic_simplifier.render_node(node)
        node = self.render_node(node)
        return self.node_renderer.render_node(node)

    def render_node(self, node):
        '''
        Assumes that the node has already been fully processed by BrianASTRenderer
        '''
        # can we pull this out?
        if node.scalar and node.complexity>0:
            expr = self.node_renderer.render_node(self.arithmetic_simplifier.render_node(node))
            if expr in self.loop_invariants:
                name = self.loop_invariants[expr]
            else:
                self.n += 1
                name = '_lio_'+self.extra_lio_prefix+str(self.n)
                self.loop_invariants[expr] = name
                self.loop_invariant_dtypes[name] = node.dtype
                numpy_dtype = {'boolean': bool,
                               'integer': int,
                               'float': float}[node.dtype]
                self.variables[name] = AuxiliaryVariable(name, Unit(1), dtype=numpy_dtype, scalar=True)
            # None is the expression context, we don't use it so we just set to None
            newnode = ast.Name(name, None)
            newnode.scalar = True
            newnode.dtype = node.dtype
            newnode.complexity = 0
            newnode.stateless = node.stateless
            return newnode
        # otherwise, render node as usual
        return super(Simplifier, self).render_node(node)
Пример #8
0
class Simplifier(BrianASTRenderer):
    '''
    Carry out arithmetic simplifications (see `ArithmeticSimplifier`) and loop invariants

    Parameters
    ----------
    variables : dict of (str, Variable)
        Usual definition of variables.
    scalar_statements : sequence of Statement
        Predefined scalar statements that can be used as part of simplification

    Notes
    -----

    After calling `render_expr` on a sequence of expressions (coming from vector statements typically),
    this object will have some new attributes:

    ``loop_invariants`` : OrderedDict of (expression, varname)
        varname will be of the form ``_lio_N`` where ``N`` is some integer, and the expressions will be
        strings that correspond to scalar-only expressions that can be evaluated outside of the vector
        block.
    ``loop_invariant_dtypes`` : dict of (varname, dtypename)
        dtypename will be one of ``'boolean'``, ``'integer'``, ``'float'``.
    '''
    def __init__(self, variables, scalar_statements, extra_lio_prefix=''):
        BrianASTRenderer.__init__(self, variables, copy_variables=False)
        self.loop_invariants = OrderedDict()
        self.loop_invariant_dtypes = {}
        self.n = 0
        self.node_renderer = NodeRenderer(use_vectorisation_idx=False)
        self.arithmetic_simplifier = ArithmeticSimplifier(variables)
        self.scalar_statements = scalar_statements
        if extra_lio_prefix is None:
            extra_lio_prefix = ''
        if len(extra_lio_prefix):
            extra_lio_prefix = extra_lio_prefix + '_'
        self.extra_lio_prefix = extra_lio_prefix

    def render_expr(self, expr):
        node = brian_ast(expr, self.variables)
        node = self.arithmetic_simplifier.render_node(node)
        node = self.render_node(node)
        return self.node_renderer.render_node(node)

    def render_node(self, node):
        '''
        Assumes that the node has already been fully processed by BrianASTRenderer
        '''
        # can we pull this out?
        if node.scalar and node.complexity > 0:
            expr = self.node_renderer.render_node(
                self.arithmetic_simplifier.render_node(node))
            if expr in self.loop_invariants:
                name = self.loop_invariants[expr]
            else:
                self.n += 1
                name = '_lio_' + self.extra_lio_prefix + str(self.n)
                self.loop_invariants[expr] = name
                self.loop_invariant_dtypes[name] = node.dtype
                numpy_dtype = {
                    'boolean': bool,
                    'integer': int,
                    'float': prefs.core.default_float_dtype
                }[node.dtype]
                self.variables[name] = AuxiliaryVariable(name,
                                                         dtype=numpy_dtype,
                                                         scalar=True)
            # None is the expression context, we don't use it so we just set to None
            newnode = ast.Name(name, None)
            newnode.scalar = True
            newnode.dtype = node.dtype
            newnode.complexity = 0
            newnode.stateless = node.stateless
            return newnode
        # otherwise, render node as usual
        return super(Simplifier, self).render_node(node)
def parse_synapse_generator(expr):
    '''
    Returns a parsed form of a synapse generator expression.

    The general form is:

    ``element for iteration_variable in iterator_func(...)``

    or

    ``element for iteration_variable in iterator_func(...) if if_expression``

    Returns a dictionary with keys:

    ``original_expression``
        The original expression as a string.
    ``element``
        As above, a string expression.
    ``iteration_variable``
        A variable name, as above.
    ``iterator_func``
        String. Either ``range`` or ``sample``.
    ``if_expression``
        String expression or ``None``.
    ``iterator_kwds``
        Dictionary of key/value pairs representing the keywords. See
        `handle_range` and `handle_sample`.
    '''
    nr = NodeRenderer(use_vectorisation_idx=False)
    parse_error = ("Error parsing expression '%s'. Expression must have "
                   "generator syntax, for example 'k for k in range(i-10, "
                   "i+10)'." % expr)
    try:
        node = ast.parse('[%s]' % expr, mode='eval').body
    except Exception as e:
        raise SyntaxError(parse_error + " Error encountered was %s" % e)
    if _cname(node) != 'ListComp':
        raise SyntaxError(parse_error + " Expression is not a generator "
                          "expression.")
    element = node.elt
    if len(node.generators) != 1:
        raise SyntaxError(parse_error + " Generator expression must involve "
                          "only one iterator.")
    generator = node.generators[0]
    target = generator.target
    if _cname(target) != 'Name':
        raise SyntaxError(parse_error +
                          " Generator must iterate over a single "
                          "variable (not tuple, etc.).")
    iteration_variable = target.id
    iterator = generator.iter
    if _cname(iterator) != 'Call' or _cname(iterator.func) != 'Name':
        raise SyntaxError(parse_error + " Iterator expression must be one of "
                          "the supported functions: " +
                          str(list(iterator_function_handlers)))
    iterator_funcname = iterator.func.id
    if iterator_funcname not in iterator_function_handlers:
        raise SyntaxError(parse_error + " Iterator expression must be one of "
                          "the supported functions: " +
                          str(list(iterator_function_handlers)))
    if (getattr(iterator, 'starargs', None) is not None
            or getattr(iterator, 'kwargs', None) is not None):
        raise SyntaxError(parse_error + " Star arguments not supported.")
    args = []
    for argnode in iterator.args:
        args.append(nr.render_node(argnode))
    keywords = {}
    for kwdnode in iterator.keywords:
        keywords[kwdnode.arg] = nr.render_node(kwdnode.value)
    try:
        iterator_handler = iterator_function_handlers[iterator_funcname]
        iterator_kwds = iterator_handler(*args, **keywords)
    except SyntaxError as exc:
        raise SyntaxError(parse_error + " " + exc.msg)
    if len(generator.ifs) == 0:
        condition = ast.parse('True', mode='eval').body
    elif len(generator.ifs) > 1:
        raise SyntaxError(parse_error + " Generator must have at most one if "
                          "statement.")
    else:
        condition = generator.ifs[0]
    parsed = {
        'original_expression': expr,
        'element': nr.render_node(element),
        'iteration_variable': iteration_variable,
        'iterator_func': iterator_funcname,
        'iterator_kwds': iterator_kwds,
        'if_expression': nr.render_node(condition),
    }
    return parsed
def parse_synapse_generator(expr):
    '''
    Returns a parsed form of a synapse generator expression.

    The general form is:

    ``element for iteration_variable in iterator_func(...)``

    or

    ``element for iteration_variable in iterator_func(...) if if_expression``

    Returns a dictionary with keys:

    ``original_expression``
        The original expression as a string.
    ``element``
        As above, a string expression.
    ``iteration_variable``
        A variable name, as above.
    ``iterator_func``
        String. Either ``range`` or ``sample``.
    ``if_expression``
        String expression or ``None``.
    ``iterator_kwds``
        Dictionary of key/value pairs representing the keywords. See
        `handle_range` and `handle_sample`.
    '''
    nr = NodeRenderer(use_vectorisation_idx=False)
    parse_error = ("Error parsing expression '%s'. Expression must have "
                   "generator syntax, for example 'k for k in range(i-10, "
                   "i+10)'." % expr)
    try:
        node = ast.parse('[%s]' % expr, mode='eval').body
    except Exception as e:
        raise SyntaxError(parse_error + " Error encountered was %s" % e)
    if _cname(node) != 'ListComp':
        raise SyntaxError(parse_error + " Expression is not a generator "
                                        "expression.")
    element = node.elt
    if len(node.generators) != 1:
        raise SyntaxError(parse_error + " Generator expression must involve "
                                        "only one iterator.")
    generator = node.generators[0]
    target = generator.target
    if _cname(target) != 'Name':
        raise SyntaxError(parse_error + " Generator must iterate over a single "
                                        "variable (not tuple, etc.).")
    iteration_variable = target.id
    iterator = generator.iter
    if _cname(iterator) != 'Call' or _cname(iterator.func) !=  'Name':
        raise SyntaxError(parse_error + " Iterator expression must be one of "
                                        "the supported functions: " +
                          str(iterator_function_handlers.keys()))
    iterator_funcname = iterator.func.id
    if iterator_funcname not in iterator_function_handlers:
        raise SyntaxError(parse_error + " Iterator expression must be one of "
                                        "the supported functions: " +
                          str(iterator_function_handlers.keys()))
    if (getattr(iterator, 'starargs', None) is not None or
                getattr(iterator, 'kwargs', None) is not None):
        raise SyntaxError(parse_error + " Star arguments not supported.")
    args = []
    for argnode in iterator.args:
        args.append(nr.render_node(argnode))
    keywords = {}
    for kwdnode in iterator.keywords:
        keywords[kwdnode.arg] = nr.render_node(kwdnode.value)
    try:
        iterator_handler = iterator_function_handlers[iterator_funcname]
        iterator_kwds = iterator_handler(*args,**keywords)
    except SyntaxError as exc:
        raise SyntaxError(parse_error + " " + exc.msg)
    if len(generator.ifs) == 0:
        condition = ast.parse('True', mode='eval').body
    elif len(generator.ifs) > 1:
        raise SyntaxError(parse_error + " Generator must have at most one if "
                                        "statement.")
    else:
        condition = generator.ifs[0]
    parsed = {
        'original_expression': expr,
        'element': nr.render_node(element),
        'iteration_variable': iteration_variable,
        'iterator_func': iterator_funcname,
        'iterator_kwds': iterator_kwds,
        'if_expression': nr.render_node(condition),
        }
    return parsed