def translate_statement(self, statement): # TODO: optimisation, translate arithmetic to a sequence of inplace # operations like a=b+c -> add(b, c, a) var, op, expr, comment = (statement.var, statement.op, statement.expr, statement.comment) origop = op if op == ':=': op = '=' # For numpy we replace complex expressions involving a single boolean variable into a # where(boolvar, expr_if_true, expr_if_false) if (statement.used_boolean_variables is not None and len(statement.used_boolean_variables) == 1 and brian_dtype_from_dtype(statement.dtype) == 'float' and statement.complexity_std > sum( statement.complexities.values())): used_boolvars = statement.used_boolean_variables bool_simp = statement.boolean_simplified_expressions boolvar = used_boolvars[0] for bool_assigns, simp_expr in bool_simp.items(): _, boolval = bool_assigns[0] if boolval: expr_true = simp_expr else: expr_false = simp_expr code = '{var} {op} _numpy.where({boolvar}, {expr_true}, {expr_false})'.format( var=var, op=op, boolvar=boolvar, expr_true=expr_true, expr_false=expr_false) else: code = var + ' ' + op + ' ' + self.translate_expression(expr) if len(comment): code += ' # ' + comment return code
def translate_statement(self, statement): # TODO: optimisation, translate arithmetic to a sequence of inplace # operations like a=b+c -> add(b, c, a) var, op, expr, comment = (statement.var, statement.op, statement.expr, statement.comment) origop = op if op == ':=': op = '=' # For numpy we replace complex expressions involving a single boolean variable into a # where(boolvar, expr_if_true, expr_if_false) if (statement.used_boolean_variables is not None and len(statement.used_boolean_variables)==1 and brian_dtype_from_dtype(statement.dtype)=='float' and statement.complexity_std>sum(statement.complexities.values())): used_boolvars = statement.used_boolean_variables bool_simp = statement.boolean_simplified_expressions boolvar = used_boolvars[0] for bool_assigns, simp_expr in bool_simp.iteritems(): _, boolval = bool_assigns[0] if boolval: expr_true = simp_expr else: expr_false = simp_expr code = '{var} {op} _numpy.where({boolvar}, {expr_true}, {expr_false})'.format( var=var, op=op, boolvar=boolvar, expr_true=expr_true, expr_false=expr_false) else: code = var + ' ' + op + ' ' + self.translate_expression(expr) if len(comment): code += ' # ' + comment return code
def translate_statement(self, statement): var, op, expr, comment = (statement.var, statement.op, statement.expr, statement.comment) if op == ':=': # make no distinction in Cython (declaration are done elsewhere) op = '=' # For Cython we replace complex expressions involving boolean variables into a sequence of # if/then expressions with simpler expressions. This is provided by the optimise_statements # function. if (statement.used_boolean_variables is not None and len(statement.used_boolean_variables) # todo: improve dtype analysis so that this isn't necessary and brian_dtype_from_dtype(statement.dtype) == 'float'): used_boolvars = statement.used_boolean_variables bool_simp = statement.boolean_simplified_expressions codelines = [] firstline = True # bool assigns is a sequence of (var, value) pairs giving the conditions under # which the simplified expression simp_expr holds for bool_assigns, simp_expr in bool_simp.iteritems(): # generate a boolean expression like ``var1 and var2 and not var3`` atomics = [] for boolvar, boolval in bool_assigns: if boolval: atomics.append(boolvar) else: atomics.append('not ' + boolvar) # use if/else/elif correctly if firstline: line = 'if ' + (' and '.join(atomics)) + ':' else: if len(used_boolvars) > 1: line = 'elif ' + (' and '.join(atomics)) + ':' else: line = 'else:' line += '\n ' line += var + ' ' + op + ' ' + self.translate_expression( simp_expr) codelines.append(line) firstline = False code = '\n'.join(codelines) else: code = var + ' ' + op + ' ' + self.translate_expression(expr) if len(comment): code += ' # ' + comment return code
def translate_statement(self, statement): var, op, expr, comment = (statement.var, statement.op, statement.expr, statement.comment) if op == ':=': # make no distinction in Cython (declaration are done elsewhere) op = '=' # For Cython we replace complex expressions involving boolean variables into a sequence of # if/then expressions with simpler expressions. This is provided by the optimise_statements # function. if (statement.used_boolean_variables is not None and len(statement.used_boolean_variables) # todo: improve dtype analysis so that this isn't necessary and brian_dtype_from_dtype(statement.dtype)=='float'): used_boolvars = statement.used_boolean_variables bool_simp = statement.boolean_simplified_expressions codelines = [] firstline = True # bool assigns is a sequence of (var, value) pairs giving the conditions under # which the simplified expression simp_expr holds for bool_assigns, simp_expr in bool_simp.iteritems(): # generate a boolean expression like ``var1 and var2 and not var3`` atomics = [] for boolvar, boolval in bool_assigns: if boolval: atomics.append(boolvar) else: atomics.append('not '+boolvar) # use if/else/elif correctly if firstline: line = 'if '+(' and '.join(atomics))+':' else: if len(used_boolvars)>1: line = 'elif '+(' and '.join(atomics))+':' else: line = 'else:' line += '\n ' line += var + ' ' + op + ' ' + self.translate_expression(simp_expr) codelines.append(line) firstline = False code = '\n'.join(codelines) else: code = var + ' ' + op + ' ' + self.translate_expression(expr) if len(comment): code += ' # ' + comment return code
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
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