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 list(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
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 list(variables.items()): if isinstance(var, ArrayVariable) and not var.scalar: 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 + ']' # Also index _vectorisation_idx so that e.g. rand() works correctly subs['_vectorisation_idx'] = '_vectorisation_idx' + '[' + repl_string + ']' line = word_substitute(line, subs) line = line.replace(repl_string, index) return line
def translate_expression(self, expr): expr = word_substitute(expr, self.func_name_replacements) return CythonNodeRenderer(auto_vectorise=self.auto_vectorise).render_expr(expr, self.variables).strip()
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( '# namespace for function %s' % 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( "cdef {cpp_dtype} *_namespace{var_name}".format( cpp_dtype=get_cpp_dtype(ns_value.dtype), var_name=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 = '{0} = _namespace["{1}"]'.format(varname, varname) load_namespace.append(line) else: raise TypeError(('Provided function implementation ' 'for function %s is neither a string ' 'nor callable (is type %s instead)') % ( varname, type(func_code))) 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)
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 angela_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
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 = [] 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}) 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(): 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 = self.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 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 = user_function 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)