From 2c31f1edbd517da98007fee3aa00c2dc5670053e Mon Sep 17 00:00:00 2001 From: thesamovar Date: Fri, 14 Jun 2013 22:41:38 -0400 Subject: [PATCH 1/7] Some progress on syntax translation --- brian2/codegen/syntax/__init__.py | 0 brian2/codegen/syntax/cpp.py | 64 +++++++ brian2/codegen/syntax/parser.py | 230 ++++++++++++++++++++++++ brian2/tests/test_syntax_translation.py | 59 ++++++ brian2/utils/logger.py | 4 +- 5 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 brian2/codegen/syntax/__init__.py create mode 100644 brian2/codegen/syntax/cpp.py create mode 100644 brian2/codegen/syntax/parser.py create mode 100644 brian2/tests/test_syntax_translation.py diff --git a/brian2/codegen/syntax/__init__.py b/brian2/codegen/syntax/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/brian2/codegen/syntax/cpp.py b/brian2/codegen/syntax/cpp.py new file mode 100644 index 000000000..b5d171290 --- /dev/null +++ b/brian2/codegen/syntax/cpp.py @@ -0,0 +1,64 @@ +''' +Convert from Python syntax to C++ syntax + +TODO: handle constants properly +TODO: handle pow/fpow abs/fabs correctly +''' + +from brian2.utils.stringtools import word_substitute +from brian2.codegen.syntax.parser import (Var, PowVar, FuncVar, + parse_expr, parse_statement, + AndVar, OrVar, XorVar, Statement, + ) + +__all__ = ['cpp_expr', 'cpp_statement'] + +cpp_function_substitutions = { + } + +def replace(var): + newitems = [] + if isinstance(var, PowVar): + var = FuncVar(Var('pow'), *var.items) + elif isinstance(var, AndVar): + var.op = '&&' + elif isinstance(var, OrVar): + var.op = '||' + elif isinstance(var, XorVar): + var.op = '^^' + else: + var.items = [replace(item) for item in var.items] + return var + +def cpp_statement(expr, use_float_math=False): + var = parse_statement(expr) + expr = replace(var.expr) + op = var.op + if op=='&=': + op = '&&=' + elif op=='|=': + op = ' ||=' + elif op=='^=': + op = '^^=' + elif op=='**=': + expr = FuncVar('pow', Var(var.var), expr) + op = '=' + var = Statement(var.var, op, expr) + returnval = str(var)+';' + subs = cpp_function_substitutions.copy() + return word_substitute(returnval, cpp_function_substitutions) + + +def cpp_expr(expr, use_float_math=False): + var = parse_expr(expr) + returnval = str(replace(var)) + subs = cpp_function_substitutions.copy() + return word_substitute(returnval, cpp_function_substitutions) + + +if __name__=='__main__': + expr = 'a+b*c+d(e)+f**g+(d&c)+(a=v)' + print cpp_expr(expr) + print cpp_statement('a += 3*a') + print cpp_statement('a = 0') + print cpp_statement('a **= 5') diff --git a/brian2/codegen/syntax/parser.py b/brian2/codegen/syntax/parser.py new file mode 100644 index 000000000..eb82e6478 --- /dev/null +++ b/brian2/codegen/syntax/parser.py @@ -0,0 +1,230 @@ +''' +Parses string representations into a tree structure + +TODO: how to handle logical operators and/or/not? +TODO: how to handle e.g. '1/2' (always convert to float? provide an option?) +''' + +from brian2.utils.stringtools import get_identifiers +from brian2.codegen.parsing import parse_statement as parse_string_statement + +__all__ = ['parse_statement', 'parse_expr', + 'OperatorVar', 'AddVar', 'SubVar', 'MulVar', 'DivVar', 'PowVar', + 'AndVar', 'OrVar', 'XorVar', + 'ModVar', + 'NegVar', + 'LeVar', 'LtVar', 'GeVar', 'GtVar', 'EqVar', 'NeVar', + 'FuncVar', + 'ConstVar', + ] + +class Var(object): + atom = True + def __init__(self, name): + self.name = name + self.items = [] + # Arithemetical operators + def __mul__(self, other): + return MulVar(self, other) + def __rmul__(self, other): + return MulVar(other, self) + def __div__(self, other): + return DivVar(self, other) + def __rdiv__(self, other): + return DivVar(other, self) + def __add__(self, other): + return AddVar(self, other) + def __radd__(self, other): + return AddVar(other, self) + def __sub__(self, other): + return SubVar(self, other) + def __rsub__(self, other): + return SubVar(other, self) + def __pow__(self, other): + return PowVar(self, other) + def __rpow__(self, other): + return PowVar(other, self) + # Logical + def __and__(self, other): + return AndVar(self, other) + def __rand__(self, other): + return AndVar(other, self) + def __or__(self, other): + return OrVar(self, other) + def __ror__(self, other): + return OrVar(other, self) + def __xor__(self, other): + return XorVar(self, other) + def __rxor__(self, other): + return XorVar(other, self) + def __mod__(self, other): + return ModVar(self, other) + def __rmod__(self, other): + return ModVar(other, self) + # Comparison + def __lt__(self, other): + return LtVar(self, other) + def __le__(self, other): + return LeVar(self, other) + def __gt__(self, other): + return GtVar(self, other) + def __ge__(self, other): + return GeVar(self, other) + def __eq__(self, other): + return EqVar(self, other) + def __ne__(self, other): + return NeVar(self, other) + # Unary operators + def __neg__(self): + return NegVar(self) + # Function call + def __call__(self, *args): + return FuncVar(self, *args) + # Invalid + def __nonzero__(self): + raise SyntaxError("Cannot use 'and' and 'or' in expressions") + def __getitem__(self, key): + raise SyntaxError("Cannot use array syntax in expressions") + def __setitem__(self, key, val): + raise SyntaxError("Cannot use array syntax in expressions") + def __contains__(self, item): + raise SyntaxError("Cannot use 'in' in expressions") + def __invert__(self): + raise SyntaxError("Cannot use '~' in expressions") + # Convert to string + def __str__(self): + return self.name + +# TODO: this function and the if len(args)==2 in OperatorVar allow some +# simplification of expressions such as a*b*c instead of (a*b)*c. This +# doesn't cover all cases, but can be improved later. +def saferep(var): + if var.atom: + return str(var) + else: + return '('+str(var)+')' + +class ConstVar(Var): + atom = True + def __init__(self, val): + self.val = val + self.items = [] + def __str__(self): + if self.val<0: + return '(%s)'%repr(self.val) + else: + return repr(self.val) + +def parse_for_constants(args): + newargs = [] + for arg in args: + if not isinstance(arg, Var): + arg = ConstVar(arg) + newargs.append(arg) + return newargs + +class OperatorVar(Var): + atom = False + def __init__(self, *args): + args = parse_for_constants(args) + self.items = list(args) + if len(args)==2: + left, right = args + if right.atom and left.__class__ is self.__class__: + self.items = left.items+[right] + def __str__(self): + return self.op.join(saferep(item) for item in self.items) + +class AddVar(OperatorVar): + op = '+' + +class SubVar(OperatorVar): + op = '-' + +class MulVar(OperatorVar): + op = '*' + +class DivVar(OperatorVar): + op = '/' + +class PowVar(OperatorVar): + op = '**' + +class AndVar(OperatorVar): + op = '&' + +class OrVar(OperatorVar): + op = '|' + +class XorVar(OperatorVar): + op = '^' + +class ModVar(OperatorVar): + op = '%' + +class LtVar(OperatorVar): + op = '<' + +class LeVar(OperatorVar): + op = '<=' + +class GtVar(OperatorVar): + op = '>' + +class GeVar(OperatorVar): + op = '>=' + +class EqVar(OperatorVar): + op = '==' + +class NeVar(OperatorVar): + op = '!=' + +class NegVar(Var): + atom = False + def __init__(self, var): + self.items = [var] + def __str__(self): + return '-'+saferep(self.items[0]) + +class FuncVar(Var): + atom = True + def __init__(self, func, *args): + args = parse_for_constants(args) + self.items = [func]+list(args) + func = property(fget=lambda self: self.items[0]) + args = property(fget=lambda self: self.items[1:]) + def __str__(self): + argslist = ', '.join(str(arg) for arg in self.args) + return '%s(%s)'%(str(self.func), argslist) + +class Statement(object): + def __init__(self, var, op, expr): + self.var = var + self.op = op + self.expr = expr + def __str__(self): + return '%s %s %s'%(str(self.var), self.op, str(self.expr)) + +def parse_expr(expr): + varnames = get_identifiers(expr) + ns = dict((varname, Var(varname)) for varname in varnames) + expr = eval(expr, ns) + if not isinstance(expr, Var): + expr = ConstVar(expr) + return expr + +def parse_statement(code): + var, op, expr = parse_string_statement(code) + return Statement(var, op, parse_expr(expr)) + +if __name__=='__main__': + print parse_expr('a+1') + print parse_expr('1.0+a') + print parse_expr('1+2+x**2') + x = parse_expr('a+b+c+abs(d)+(-a)**b+2') + print x + x = parse_expr('a+b*c+d(e)+f**g+(x==y)') + print x + print parse_statement('a+=b+c') + \ No newline at end of file diff --git a/brian2/tests/test_syntax_translation.py b/brian2/tests/test_syntax_translation.py new file mode 100644 index 000000000..1e18aafd5 --- /dev/null +++ b/brian2/tests/test_syntax_translation.py @@ -0,0 +1,59 @@ +''' +Tests the brian2.codegen.syntax package +''' +from brian2.utils.stringtools import get_identifiers +from brian2.codegen.syntax.parser import parse_expr, parse_statement +from numpy.testing import assert_raises, assert_equal +from numpy.random import rand, randint + +def generate_expressions(N=100, numvars=5, numfloats=1, numints=1, complexity=5, depth=3): + ops = ['+', '*', '-', '/', '**'] + vars = [chr(ord('a')+i) for i in xrange(numvars)] + consts = [rand() for _ in xrange(numfloats)]+range(1, 1+numints) + varsconsts = [str(x) for x in vars+consts] + for _ in xrange(N): + expr = 'a' + for _ in xrange(depth): + s = 'a' + for _ in xrange(complexity): + op = ops[randint(len(ops))] + var = vars[randint(numvars)] + s = s+op+var + op = ops[randint(len(ops))] + expr = '(%s)%s(%s)'%(expr, op, s) + yield (vars, [], expr) + +def test_parse_expressions(numvalues=10): + exprs = list(generate_expressions()) + additional_exprs = ''' + ab + a>=b + a==b + a!=b + a+1 + 1+a + a%2 + a%2.0 + 1+3 + ''' + exprs = exprs+[('abc', [], l.strip()) for l in additional_exprs.split('\n') if l.strip()] + for varids, funcids, expr in exprs: + pexpr = str(parse_expr(expr)) + n = 0 + for _ in xrange(numvalues): + # assign some random values + ns = dict((v, rand()) for v in varids) + try: + r1 = eval(expr, ns) + except (ZeroDivisionError, ValueError, OverflowError): + continue + n += 1 + r2 = eval(pexpr, ns) + assert_equal(r1, r2) +# print n + +if __name__=='__main__': + test_parse_expressions() + \ No newline at end of file diff --git a/brian2/utils/logger.py b/brian2/utils/logger.py index f13302ff8..67e0a667c 100644 --- a/brian2/utils/logger.py +++ b/brian2/utils/logger.py @@ -201,8 +201,8 @@ def clean_up_logging(): except IOError as exc: warn('Could not delete copy of script file: %s' % exc) -sys.excepthook = brian_excepthook -atexit.register(clean_up_logging) +#sys.excepthook = brian_excepthook +#atexit.register(clean_up_logging) class HierarchyFilter(object): From e0502115903677e19db30550cbaa6a9142bfc5df Mon Sep 17 00:00:00 2001 From: thesamovar Date: Fri, 21 Jun 2013 18:43:48 -0400 Subject: [PATCH 2/7] Added AST based parser --- brian2/codegen/syntax/ast_parser.py | 131 ++++++++++++++++++++++++ brian2/codegen/syntax/parser.py | 11 +- brian2/tests/test_syntax_translation.py | 1 + 3 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 brian2/codegen/syntax/ast_parser.py diff --git a/brian2/codegen/syntax/ast_parser.py b/brian2/codegen/syntax/ast_parser.py new file mode 100644 index 000000000..d6d36c291 --- /dev/null +++ b/brian2/codegen/syntax/ast_parser.py @@ -0,0 +1,131 @@ +import ast + +class NodeRenderer(object): + expression_ops = { + # BinOp + 'Add': '+', + 'Sub': '-', + 'Mult': '*', + 'Div': '/', + 'Pow': '**', + # Compare + 'Lt': '<', + 'LtE': '<=', + 'Gt': '>', + 'GtE': '>=', + 'Eq': '==', + 'NotEq': '!=', + # Unary ops + 'Not': 'not', + 'Invert': '~', + 'UAdd': '+', + 'USub': '-', + # Bool ops + 'And': 'and', + 'Or': 'or', + } + + def render_expr(self, expr): + node = ast.parse(expr, mode='eval') + return self.render_node(node.body) + + def render_code(self, code): + lines = [] + for node in ast.parse(code).body: + lines.append(self.render_node(node)) + return '\n'.join(lines) + + def render_node(self, node): + nodename = node.__class__.__name__ + methname = 'render_'+nodename + if not hasattr(self, methname): + raise SyntaxError("Unknown syntax: "+nodename) + return getattr(self, methname)(node) + + def render_Name(self, node): + return node.id + + def render_Num(self, node): + return repr(node.n) + + def render_Call(self, node): + if len(node.keywords): + raise ValueError("Keyword arguments not supported.") + elif node.starargs is not None: + raise ValueError("*args not supported") + elif node.kwargs is not None: + raise ValueError("**kwds not supported") + return '%s(%s)' % (self.render_node(node.func), + ', '.join(self.render_node(arg) for arg in node.args)) + + def render_BinOp(self, node): + return '(%s) %s (%s)'%(self.render_node(node.left), + self.expression_ops[node.op.__class__.__name__], + self.render_node(node.right)) + + def render_BoolOp(self, node): + op = self.expression_ops[node.op.__class__.__name__] + return (' '+op+' ').join('(%s)' % self.render_node(v) for v in node.values) + + def render_Compare(self, node): + if len(node.comparators)>1: + raise SyntaxError("Can only handle single comparisons like a1: + raise SyntaxError("Only support syntax like a=b not a=b=c") + return '%s = %s' % (self.render_node(node.targets[0]), + self.render_node(node.value)) + + +class NumpyNodeRenderer(NodeRenderer): + expression_ops = NodeRenderer.expression_ops.copy() + expression_ops.update({ + # Unary ops + 'Not': 'logical_not', + 'Invert': 'logical_not', + # Bool ops + 'And': '*', + 'Or': '+', + }) + + +class CPPNodeRenderer(NodeRenderer): + expression_ops = NodeRenderer.expression_ops.copy() + expression_ops.update({ + # Unary ops + 'Not': '!', + 'Invert': '!', + # Bool ops + 'And': '&&', + 'Or': '||', + }) + + def render_BinOp(self, node): + if node.op.__class__.__name__=='Pow': + return 'pow(%s, %s)' % (self.render_node(node.left), + self.render_node(node.right)) + else: + return NodeRenderer.render_BinOp(self, node) + + def render_Assign(self, node): + return NodeRenderer.render_Assign(self, node)+';' + + +if __name__=='__main__': + for renderer in [NodeRenderer(), NumpyNodeRenderer(), CPPNodeRenderer()]: + name = renderer.__class__.__name__ + print name+'\n'+'='*len(name) + print renderer.render_expr('a+b*c(d, e)+e**f') + print renderer.render_expr('a and -b and c and 1.2') + print renderer.render_code('a=b\nc=d+e') diff --git a/brian2/codegen/syntax/parser.py b/brian2/codegen/syntax/parser.py index eb82e6478..184064d1b 100644 --- a/brian2/codegen/syntax/parser.py +++ b/brian2/codegen/syntax/parser.py @@ -5,7 +5,7 @@ TODO: how to handle e.g. '1/2' (always convert to float? provide an option?) ''' -from brian2.utils.stringtools import get_identifiers +from brian2.utils.stringtools import get_identifiers, word_substitute from brian2.codegen.parsing import parse_statement as parse_string_statement __all__ = ['parse_statement', 'parse_expr', @@ -82,7 +82,7 @@ def __call__(self, *args): return FuncVar(self, *args) # Invalid def __nonzero__(self): - raise SyntaxError("Cannot use 'and' and 'or' in expressions") + raise SyntaxError("Cannot use 'and' and 'or' in expressions: "+str(self)) def __getitem__(self, key): raise SyntaxError("Cannot use array syntax in expressions") def __setitem__(self, key, val): @@ -207,6 +207,13 @@ def __str__(self): return '%s %s %s'%(str(self.var), self.op, str(self.expr)) def parse_expr(expr): + expr = word_substitute(expr, { + 'and': '&', + 'or': '|', + 'not': '~', + }) + expr = expr.replace('^', '**') + print expr varnames = get_identifiers(expr) ns = dict((varname, Var(varname)) for varname in varnames) expr = eval(expr, ns) diff --git a/brian2/tests/test_syntax_translation.py b/brian2/tests/test_syntax_translation.py index 1e18aafd5..9ae71b6dd 100644 --- a/brian2/tests/test_syntax_translation.py +++ b/brian2/tests/test_syntax_translation.py @@ -37,6 +37,7 @@ def test_parse_expressions(numvalues=10): a%2 a%2.0 1+3 + a>1 and b>1 ''' exprs = exprs+[('abc', [], l.strip()) for l in additional_exprs.split('\n') if l.strip()] for varids, funcids, expr in exprs: From 6f9a83f50a4f25fd2d562e4bfaf4e0a055931318 Mon Sep 17 00:00:00 2001 From: thesamovar Date: Fri, 21 Jun 2013 20:19:16 -0400 Subject: [PATCH 3/7] Improved printing using precedence for binary operators (but not unary or boolean) --- brian2/codegen/syntax/ast_parser.py | 45 ++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/brian2/codegen/syntax/ast_parser.py b/brian2/codegen/syntax/ast_parser.py index d6d36c291..b8f7f6db5 100644 --- a/brian2/codegen/syntax/ast_parser.py +++ b/brian2/codegen/syntax/ast_parser.py @@ -41,7 +41,7 @@ def render_node(self, node): if not hasattr(self, methname): raise SyntaxError("Unknown syntax: "+nodename) return getattr(self, methname)(node) - + def render_Name(self, node): return node.id @@ -58,24 +58,46 @@ def render_Call(self, node): return '%s(%s)' % (self.render_node(node.func), ', '.join(self.render_node(arg) for arg in node.args)) + def render_BinOp_parentheses(self, left, right, op): + # This function checks whether or not you can ommit parentheses assuming Python + # precedence relations, hopefully this is the same in C++ and Java, but we'll need + # to check it + exprs = ['%s %s %s', '(%s) %s %s', '%s %s (%s)', '(%s) %s (%s)'] + nr = NodeRenderer() + L = nr.render_node(left) + R = nr.render_node(right) + O = NodeRenderer.expression_ops[op.__class__.__name__] + refexpr = '(%s) %s (%s)' % (L, O, R) + refexprdump = ast.dump(ast.parse(refexpr)) + for expr in exprs: + e = expr % (L, O, R) + if ast.dump(ast.parse(e))==refexprdump: + return expr % (self.render_node(left), + self.expression_ops[op.__class__.__name__], + self.render_node(right), + ) + def render_BinOp(self, node): - return '(%s) %s (%s)'%(self.render_node(node.left), - self.expression_ops[node.op.__class__.__name__], - self.render_node(node.right)) + return self.render_BinOp_parentheses(node.left, node.right, node.op) def render_BoolOp(self, node): + # TODO: for the moment we always parenthesise boolean ops because precedence + # might be different in different languages and it's safer - also because it's + # a bit more complicated to write the parenthesis rule + op = node.op + left = node.values[0] + remaining = node.values[1:] + while len(remaining): + right = remaining[0] + remaining = remaining[1:] + s = self.render_BinOp_parentheses(left, right, op) op = self.expression_ops[node.op.__class__.__name__] return (' '+op+' ').join('(%s)' % self.render_node(v) for v in node.values) def render_Compare(self, node): if len(node.comparators)>1: raise SyntaxError("Can only handle single comparisons like a Date: Fri, 21 Jun 2013 22:21:44 -0400 Subject: [PATCH 5/7] Replaced excepthook which I'd removed by mistake --- brian2/utils/logger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/brian2/utils/logger.py b/brian2/utils/logger.py index 67e0a667c..f13302ff8 100644 --- a/brian2/utils/logger.py +++ b/brian2/utils/logger.py @@ -201,8 +201,8 @@ def clean_up_logging(): except IOError as exc: warn('Could not delete copy of script file: %s' % exc) -#sys.excepthook = brian_excepthook -#atexit.register(clean_up_logging) +sys.excepthook = brian_excepthook +atexit.register(clean_up_logging) class HierarchyFilter(object): From 2af91f7f6cffe74f8232ee1ae3da22bd25d1d2a7 Mon Sep 17 00:00:00 2001 From: thesamovar Date: Fri, 21 Jun 2013 23:07:59 -0400 Subject: [PATCH 6/7] Removed unused languages and replaced sympy with ast_parser --- brian2/codegen/languages/__init__.py | 2 - brian2/codegen/languages/cpp.py | 10 +- brian2/codegen/languages/cuda.py | 51 ----------- brian2/codegen/languages/cython.py | 1 - brian2/codegen/languages/python.py | 3 +- brian2/codegen/languages/python_numexpr.py | 102 --------------------- brian2/codegen/languages/theano.py | 1 - dev/tools/run_nose_tests.py | 2 +- 8 files changed, 5 insertions(+), 167 deletions(-) delete mode 100644 brian2/codegen/languages/cuda.py delete mode 100644 brian2/codegen/languages/cython.py delete mode 100644 brian2/codegen/languages/python_numexpr.py delete mode 100644 brian2/codegen/languages/theano.py diff --git a/brian2/codegen/languages/__init__.py b/brian2/codegen/languages/__init__.py index f5f61db98..431f4fa21 100644 --- a/brian2/codegen/languages/__init__.py +++ b/brian2/codegen/languages/__init__.py @@ -1,5 +1,3 @@ from .base import * from .cpp import * -from .cuda import * from .python import * -from .python_numexpr import * diff --git a/brian2/codegen/languages/cpp.py b/brian2/codegen/languages/cpp.py index 4ce2dbcba..f59eefc98 100644 --- a/brian2/codegen/languages/cpp.py +++ b/brian2/codegen/languages/cpp.py @@ -3,15 +3,14 @@ ''' import re -from sympy.printing.ccode import CCodePrinter import numpy from brian2.utils.stringtools import deindent -from brian2.codegen.parsing import parse_to_sympy from brian2.codegen.functions.base import Function from brian2.utils.logger import get_logger from .base import Language, CodeObject +from ..ast_parser import CPPNodeRenderer logger = get_logger(__name__) try: @@ -111,12 +110,7 @@ def __init__(self, compiler='gcc', extra_compile_args=['-O3', '-ffast-math'], self.flush_denormals = flush_denormals def translate_expression(self, expr): - # temporary hack to make randn() pass through sympy - expr = re.sub(r'\brandn\b\s*\(\s*\)', '_temporary_randn_symbol', expr) - expr = parse_to_sympy(expr) - expr = CCodePrinter().doprint(expr) - expr = re.sub(r'\b_temporary_randn_symbol\b', 'randn()', expr) - return expr + return CPPNodeRenderer().render_expr(expr).strip() def translate_statement(self, statement): var, op, expr = statement.var, statement.op, statement.expr diff --git a/brian2/codegen/languages/cuda.py b/brian2/codegen/languages/cuda.py deleted file mode 100644 index 37b6db7c8..000000000 --- a/brian2/codegen/languages/cuda.py +++ /dev/null @@ -1,51 +0,0 @@ -from .cpp import CPPLanguage - -__all__ = ['CUDALanguage'] - -class CUDALanguage(CPPLanguage): - - language_id = 'cuda' - - def code_object(self, code, specifiers): - raise NotImplementedError - - def template_iterate_all(self, index, size): - return ''' - __global__ stateupdate(int _num_neurons, double t, double dt) - {{ - const int {index} = threadIdx.x+blockIdx.x*blockDim.x; - if({index}>={size}) return; - %POINTERS% - %CODE% - }} - '''.format(index=index, size=size) - - def template_iterate_index_array(self, index, array, size): - return ''' - __global__ stateupdate(int _num_neurons, double t, double dt) - {{ - const int _index_{array} = threadIdx.x+blockIdx.x*blockDim.x; - if(_index_{array}>={size}) return; - const int {index} = {array}[_index_{array}]; - %POINTERS% - %CODE% - }} - '''.format(index=index, array=array, size=size) - - def template_threshold(self): - return ''' - __global__ threshold(int _num_neurons, double t, double dt) - { - const int _neuron_idx = threadIdx.x+blockIdx.x*blockDim.x; - if(_neuron_idx>=_num_neurons) return; - %POINTERS% - %CODE% - _array_cond[_neuron_idx] = _cond; - } - ''' - - def template_synapses(self): - raise NotImplementedError - - # TODO: optimisation of translate_statement_sequence, interleave read/write - # accesses with computations diff --git a/brian2/codegen/languages/cython.py b/brian2/codegen/languages/cython.py deleted file mode 100644 index a4faa8e56..000000000 --- a/brian2/codegen/languages/cython.py +++ /dev/null @@ -1 +0,0 @@ -# TODO: cython \ No newline at end of file diff --git a/brian2/codegen/languages/python.py b/brian2/codegen/languages/python.py index 3c4ee893a..e6c31f748 100644 --- a/brian2/codegen/languages/python.py +++ b/brian2/codegen/languages/python.py @@ -1,4 +1,5 @@ from .base import Language, CodeObject +from ..ast_parser import NumpyNodeRenderer __all__ = ['PythonLanguage', 'PythonCodeObject'] @@ -8,7 +9,7 @@ class PythonLanguage(Language): language_id = 'python' def translate_expression(self, expr): - return expr.strip() + return NumpyNodeRenderer().render_expr(expr).strip() def translate_statement(self, statement): # TODO: optimisation, translate arithmetic to a sequence of inplace diff --git a/brian2/codegen/languages/python_numexpr.py b/brian2/codegen/languages/python_numexpr.py deleted file mode 100644 index 35cdf5657..000000000 --- a/brian2/codegen/languages/python_numexpr.py +++ /dev/null @@ -1,102 +0,0 @@ -from brian2.codegen.parsing import parse_to_sympy - -try: - import numexpr - numexpr_ver = tuple(map(int, numexpr.__version__.split('.'))) - if len(numexpr_ver)<3: - numexpr_ver = numexpr_ver+(0,)*(3-len(numexpr_ver)) - if numexpr_ver<(2, 0, 0): - numexpr = None -except ImportError: - numexpr = None - numexpr_ver = None - -from .python import PythonLanguage, PythonCodeObject - -__all__ = ['NumexprPythonLanguage', 'NumexprPythonCodeObject'] - -class NumexprPythonLanguage(PythonLanguage): - ''' - ``complexity_threshold`` - The minimum complexity (as defined in - :func:`expression_complexity`) of an expression for numexpr to be used. - This stops numexpr being used for simple things like ``x = y``. The - default is to use numexpr whenever it is more complicated than this, - but you can set the threshold higher so that e.g. ``x=y+z`` would - be excluded as well, which will improve performance when - operating over small vectors. - - ``multicore`` - Whether or not to use multiple cores in numexpr evaluation, set - to True, False or number of cores, including negative numbers - for number_of_cores()-1, etc. - ''' - def __init__(self, complexity_threshold=2, multicore=True): - if numexpr_ver is None or numexpr_ver<(2, 0, 0): - raise ImportError("numexpr version 2.0.0 or better required.") - self.complexity_threshold = complexity_threshold - nc = numexpr.detect_number_of_cores() - if multicore is True: - multicore = nc - elif multicore is False: - multicore = 1 - elif multicore<=0: - multicore += nc - numexpr.set_num_threads(multicore) - - # TODO: there is now an out argument in numexpr, so we can do: - # numexpr.evaluate('y+z', {'y':y, 'z':z}, out=x) - # for maximum efficiency. Once this is done, we also need to consider - # whether we do all operations in-place or rebind, and handle correctly - # the other parts of code generation. - def translate_expression(self, expr): - if expression_complexity(expr)>=self.complexity_threshold: - return '_numexpr.evaluate("'+expr.strip()+'")' - else: - return expr.strip() - - def translate_statement(self, statement): - if expression_complexity(statement.expr)1 therefore I - # suspect a bug with numexpr? - return PythonLanguage.translate_statement(self, statement) -# # other statement.op is [?]=, e.g. +=, *=, **=, /= -# opfirst = statement.op[:-1] -# return '_numexpr.evaluate("{var}{opfirst}({expr})", out={var})'.format( -# var=statement.var, opfirst=opfirst, expr=statement.expr) - - def code_object(self, code, specifiers): - return NumexprPythonCodeObject(code, self.compile_methods(specifiers)) - - -def expression_complexity(expr): - ''' - Returns the complexity of the expression defined as the sum of the number - of leaves and nodes of the tree. TODO: better definition? - ''' - if isinstance(expr, str): - expr = parse_to_sympy(expr) - if len(expr.args)==0: - return 1 - else: - return 1+sum(map(expression_complexity, expr.args)) - - -class NumexprPythonCodeObject(PythonCodeObject): - def compile(self, namespace): - PythonCodeObject.compile(self, namespace) - exec 'import numexpr as _numexpr' in self.namespace - - -if __name__=='__main__': - print expression_complexity('x+y+z') - print expression_complexity('x+y*(z+1)+2*x**3') diff --git a/brian2/codegen/languages/theano.py b/brian2/codegen/languages/theano.py deleted file mode 100644 index 583c639ed..000000000 --- a/brian2/codegen/languages/theano.py +++ /dev/null @@ -1 +0,0 @@ -# TODO: theano \ No newline at end of file diff --git a/dev/tools/run_nose_tests.py b/dev/tools/run_nose_tests.py index ebd79a17f..43366e8a9 100644 --- a/dev/tools/run_nose_tests.py +++ b/dev/tools/run_nose_tests.py @@ -2,4 +2,4 @@ Run all the tests using nose. ''' import brian2 -brian2.test() \ No newline at end of file +brian2.test() From a76c38b3f8e899643bd96554aec8b742e6c6a8e0 Mon Sep 17 00:00:00 2001 From: mstimberg Date: Sun, 23 Jun 2013 22:08:52 +0200 Subject: [PATCH 7/7] Do not make test fail if weave is not available (weave does not support Python 3) --- brian2/tests/test_syntax_translation.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/brian2/tests/test_syntax_translation.py b/brian2/tests/test_syntax_translation.py index 0b692275a..4d5dd0be3 100644 --- a/brian2/tests/test_syntax_translation.py +++ b/brian2/tests/test_syntax_translation.py @@ -8,7 +8,10 @@ from numpy.testing import assert_raises, assert_equal from numpy.random import rand, randint import numpy as np -from scipy import weave +try: + from scipy import weave +except ImportError: + weave = None import nose def generate_expressions(N=100, numvars=5, numfloats=1, numints=1, complexity=5, depth=3): @@ -74,8 +77,11 @@ def numpy_evaluator(expr, ns): def cpp_evaluator(expr, ns): - return weave.inline('return_val = %s;' % expr, ns.keys(), local_dict=ns, - compiler='gcc') + if weave is not None: + return weave.inline('return_val = %s;' % expr, ns.keys(), local_dict=ns, + compiler='gcc') + else: + raise nose.SkipTest('No weave support.') def test_parse_expressions_python():