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
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)
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)
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
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
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
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()
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
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)
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)
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()
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
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()
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
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
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
def translate_expression(self, expr): expr = word_substitute(expr, self.func_name_replacements) return NumpyNodeRenderer().render_expr(expr, self.variables).strip()
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 translate_expression(self, expr): expr = word_substitute(expr, self.func_name_replacements) return CPPNodeRenderer().render_expr(expr).strip()
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
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()
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
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)
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
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)