def ufunc_at_vectorisation(self, statement, variables, indices, conditional_write_vars, created_vars, used_variables): if not self._use_ufunc_at_vectorisation: raise VectorisationError() # Avoids circular import from brian2.devices.device import device # See https://github.com/brian-team/brian2/pull/531 for explanation used = set(get_identifiers(statement.expr)) used = used.intersection(k for k in list(variables.keys()) if k in indices and indices[k] != '_idx') used_variables.update(used) if statement.var in used_variables: raise VectorisationError() expr = NumpyNodeRenderer( auto_vectorise=self.auto_vectorise).render_expr(statement.expr) if statement.op == ':=' or indices[ statement.var] == '_idx' or not statement.inplace: if statement.op == ':=': op = '=' else: op = statement.op line = '{var} {op} {expr}'.format(var=statement.var, op=op, expr=expr) elif statement.inplace: if statement.op == '+=': ufunc_name = '_numpy.add' elif statement.op == '*=': ufunc_name = '_numpy.multiply' elif statement.op == '/=': ufunc_name = '_numpy.divide' elif statement.op == '-=': ufunc_name = '_numpy.subtract' else: raise VectorisationError() line = '{ufunc_name}.at({array_name}, {idx}, {expr})'.format( ufunc_name=ufunc_name, array_name=device.get_array_name(variables[statement.var]), idx=indices[statement.var], expr=expr) line = self.conditional_write( line, statement, variables, conditional_write_vars=conditional_write_vars, created_vars=created_vars) else: raise VectorisationError() if len(statement.comment): line += ' # ' + statement.comment return line
def ufunc_at_vectorisation(self, statement, variables, indices, conditional_write_vars, created_vars, index): ''' ''' # Avoids circular import from brian2.devices.device import device # We assume that the code has passed the test for synapse order independence expr = NumpyNodeRenderer().render_expr(statement.expr) if statement.op == ':=' or indices[ statement.var] == '_idx' or not statement.inplace: if statement.op == ':=': op = '=' else: op = statement.op line = '{var} {op} {expr}'.format(var=statement.var, op=op, expr=expr) elif statement.inplace: if statement.op == '+=': ufunc_name = '_numpy.add' elif statement.op == '*=': ufunc_name = '_numpy.multiply' elif statement.op == '/=': ufunc_name = '_numpy.divide' elif statement.op == '-=': ufunc_name = '_numpy.subtract' else: raise VectorisationError() line = '{ufunc_name}.at({array_name}, {idx}, {expr})'.format( ufunc_name=ufunc_name, array_name=device.get_array_name(variables[statement.var]), idx=indices[statement.var], expr=expr) line = self.conditional_write( line, statement, variables, conditional_write_vars=conditional_write_vars, created_vars=created_vars) else: raise VectorisationError() if len(statement.comment): line += ' # ' + statement.comment return line
def ufunc_at_vectorisation(self, statement, variables, indices, conditional_write_vars, created_vars, used_variables): if not self._use_ufunc_at_vectorisation: raise VectorisationError() # Avoids circular import from brian2.devices.device import device # See https://github.com/brian-team/brian2/pull/531 for explanation used = set(get_identifiers(statement.expr)) used = used.intersection(k for k in variables.keys() if k in indices and indices[k]!='_idx') used_variables.update(used) if statement.var in used_variables: raise VectorisationError() expr = NumpyNodeRenderer().render_expr(statement.expr) if statement.op == ':=' or indices[statement.var] == '_idx' or not statement.inplace: if statement.op == ':=': op = '=' else: op = statement.op line = '{var} {op} {expr}'.format(var=statement.var, op=op, expr=expr) elif statement.inplace: if statement.op == '+=': ufunc_name = '_numpy.add' elif statement.op == '*=': ufunc_name = '_numpy.multiply' elif statement.op == '/=': ufunc_name = '_numpy.divide' elif statement.op == '-=': ufunc_name = '_numpy.subtract' else: raise VectorisationError() line = '{ufunc_name}.at({array_name}, {idx}, {expr})'.format( ufunc_name=ufunc_name, array_name=device.get_array_name(variables[statement.var]), idx=indices[statement.var], expr=expr) line = self.conditional_write(line, statement, variables, conditional_write_vars=conditional_write_vars, created_vars=created_vars) else: raise VectorisationError() if len(statement.comment): line += ' # ' + statement.comment return line
def ufunc_at_vectorisation(self, statement, variables, indices, conditional_write_vars, created_vars, index): ''' ''' # Avoids circular import from brian2.devices.device import device # We assume that the code has passed the test for synapse order independence expr = NumpyNodeRenderer().render_expr(statement.expr) if statement.op == ':=' or indices[statement.var] == '_idx' or not statement.inplace: if statement.op == ':=': op = '=' else: op = statement.op line = '{var} {op} {expr}'.format(var=statement.var, op=op, expr=expr) elif statement.inplace: if statement.op == '+=': ufunc_name = '_numpy.add' elif statement.op == '*=': ufunc_name = '_numpy.multiply' elif statement.op == '/=': ufunc_name = '_numpy.divide' elif statement.op == '-=': ufunc_name = '_numpy.subtract' else: raise VectorisationError() line = '{ufunc_name}.at({array_name}, {idx}, {expr})'.format(ufunc_name=ufunc_name, array_name=device.get_array_name(variables[statement.var]), idx=indices[statement.var], expr=expr) line = self.conditional_write(line, statement, variables, conditional_write_vars=conditional_write_vars, created_vars=created_vars) else: raise VectorisationError() if len(statement.comment): line += ' # ' + statement.comment return line
def determine_keywords(self): # set up the restricted pointers, these are used so that the compiler # knows there is no aliasing in the pointers, for optimisation lines = [] # it is possible that several different variable names refer to the # same array. E.g. in gapjunction code, v_pre and v_post refer to the # same array if a group is connected to itself handled_pointers = set() template_kwds = {} # again, do the import here to avoid a circular dependency. from brian2.devices.device import get_device device = get_device() for varname, var in self.variables.iteritems(): if isinstance(var, ArrayVariable): # This is the "true" array name, not the restricted pointer. array_name = device.get_array_name(var) pointer_name = self.get_array_name(var) if pointer_name in handled_pointers: continue if getattr(var, 'ndim', 1) > 1: continue # multidimensional (dynamic) arrays have to be treated differently line = self.c_data_type(var.dtype) + ' * ' + self.restrict + pointer_name + ' = ' + array_name + ';' lines.append(line) handled_pointers.add(pointer_name) pointers = '\n'.join(lines) # set up the functions user_functions = [] support_code = '' hash_defines = '' # set convertion types for standard C99 functions in device code if prefs.codegen.generators.cuda.default_functions_integral_convertion == np.float64: default_func_type = 'double' other_func_type = 'float' else: # np.float32 default_func_type = 'float' other_func_type = 'double' # set clip function to either use all float or all double arguments # see #51 for details if prefs['core.default_float_dtype'] == np.float64: float_dtype = 'float' else: # np.float32 float_dtype = 'double' for varname, variable in self.variables.items(): if isinstance(variable, Function): user_functions.append((varname, variable)) funccode = variable.implementations[self.codeobj_class].get_code(self.owner) if varname in functions_C99: funccode = funccode.format(default_type=default_func_type, other_type=other_func_type) if varname == 'clip': funccode = funccode.format(float_dtype=float_dtype) if isinstance(funccode, basestring): funccode = {'support_code': funccode} if funccode is not None: support_code += '\n' + deindent(funccode.get('support_code', '')) hash_defines += '\n' + deindent(funccode.get('hashdefine_code', '')) # add the Python function with a leading '_python', if it # exists. This allows the function to make use of the Python # function via weave if necessary (e.g. in the case of randn) if not variable.pyfunc is None: pyfunc_name = '_python_' + varname if pyfunc_name in self.variables: logger.warn(('Namespace already contains function %s, ' 'not replacing it') % pyfunc_name) else: self.variables[pyfunc_name] = variable.pyfunc # delete the user-defined functions from the namespace and add the # function namespaces (if any) for funcname, func in user_functions: del self.variables[funcname] func_namespace = func.implementations[self.codeobj_class].get_namespace(self.owner) if func_namespace is not None: self.variables.update(func_namespace) support_code += '\n' + deindent(self.universal_support_code) keywords = {'pointers_lines': stripped_deindented_lines(pointers), 'support_code_lines': stripped_deindented_lines(support_code), 'hashdefine_lines': stripped_deindented_lines(hash_defines), 'denormals_code_lines': stripped_deindented_lines(self.denormals_to_zero_code()), 'uses_atomics': self.uses_atomics } keywords.update(template_kwds) return keywords
def determine_keywords(self): # set up the restricted pointers, these are used so that the compiler # knows there is no aliasing in the pointers, for optimisation pointers = [] # Add additional lines inside the kernel functions kernel_lines = [] # It is possible that several different variable names refer to the # same array. E.g. in gapjunction code, v_pre and v_post refer to the # same array if a group is connected to itself handled_pointers = set() template_kwds = {} # Again, do the import here to avoid a circular dependency. from brian2.devices.device import get_device device = get_device() for varname, var in self.variables.iteritems(): if isinstance(var, ArrayVariable): # This is the "true" array name, not the restricted pointer. array_name = device.get_array_name(var) pointer_name = self.get_array_name(var) if pointer_name in handled_pointers: continue if getattr(var, 'ndim', 1) > 1: continue # multidimensional (dynamic) arrays have to be treated differently restrict = self.restrict # turn off restricted pointers for scalars for safety if var.scalar: restrict = ' ' line = '{0}* {1} {2} = {3};'.format( self.c_data_type(var.dtype), restrict, pointer_name, array_name) pointers.append(line) handled_pointers.add(pointer_name) # set up the functions user_functions = [] support_code = [] hash_defines = [] for varname, variable in self.variables.items(): if isinstance(variable, Function): hd, ps, sc, uf, kl = self._add_user_function(varname, variable) user_functions.extend(uf) support_code.extend(sc) pointers.extend(ps) hash_defines.extend(hd) kernel_lines.extend(kl) support_code.append(self.universal_support_code) # Clock variables (t, dt, timestep) are passed by value to kernels and # need to be translated back into pointers for scalar/vector code. for varname, variable in self.variables.iteritems(): if hasattr(variable, 'owner') and isinstance( variable.owner, Clock): # get arrayname without _ptr suffix (e.g. _array_defaultclock_dt) arrayname = self.get_array_name(variable, pointer=False) line = "const {dtype}* _ptr{arrayname} = &_value{arrayname};" line = line.format(dtype=c_data_type(variable.dtype), arrayname=arrayname) if line not in kernel_lines: kernel_lines.append(line) keywords = { 'pointers_lines': stripped_deindented_lines('\n'.join(pointers)), 'support_code_lines': stripped_deindented_lines('\n'.join(support_code)), 'hashdefine_lines': stripped_deindented_lines('\n'.join(hash_defines)), 'denormals_code_lines': stripped_deindented_lines('\n'.join( self.denormals_to_zero_code())), 'kernel_lines': stripped_deindented_lines('\n'.join(kernel_lines)), 'uses_atomics': self.uses_atomics } keywords.update(template_kwds) return keywords
def determine_keywords(self): # set up the restricted pointers, these are used so that the compiler # knows there is no aliasing in the pointers, for optimisation pointers = [] # Add additional lines inside the kernel functions kernel_lines = [] # It is possible that several different variable names refer to the # same array. E.g. in gapjunction code, v_pre and v_post refer to the # same array if a group is connected to itself handled_pointers = set() template_kwds = {} # Again, do the import here to avoid a circular dependency. from brian2.devices.device import get_device device = get_device() for varname, var in self.variables.items(): if isinstance(var, ArrayVariable): # This is the "true" array name, not the restricted pointer. array_name = device.get_array_name(var) pointer_name = self.get_array_name(var) if pointer_name in handled_pointers: continue if getattr(var, 'ndim', 1) > 1: continue # multidimensional (dynamic) arrays have to be treated differently restrict = self.restrict # turn off restricted pointers for scalars for safety if var.scalar: restrict = ' ' # Need to use correct dt type in pointers_lines for single precision, # see #148 if varname == "dt" and prefs.core.default_float_dtype == np.float32: # c_data_type(variable.dtype) is float, but we need double dtype = "double" else: dtype = self.c_data_type(var.dtype) line = '{0}* {1} {2} = {3};'.format(dtype, restrict, pointer_name, array_name) pointers.append(line) handled_pointers.add(pointer_name) # set up the functions user_functions = [] support_code = [] hash_defines = [] added = set() # keep track of functions that were added for varname, variable in list(self.variables.items()): if isinstance(variable, Function): user_func = self._add_user_function(varname, variable, added) if user_func is not None: hd, ps, sc, uf, kl = user_func user_functions.extend(uf) support_code.extend(sc) pointers.extend(ps) hash_defines.extend(hd) kernel_lines.extend(kl) # Generate universal_support_code once when the first codeobject is created. # Can't do it at import time since need access to user preferences # This is a class attribute (not instance attribute). if CUDACodeGenerator.universal_support_code is None: _atomic_support_code = _generate_atomic_support_code() CUDACodeGenerator.universal_support_code = ( _hightype_support_code + _mod_support_code + _floordiv_support_code + _pow_support_code + _atomic_support_code ) support_code.append(CUDACodeGenerator.universal_support_code) # Clock variables (t, dt, timestep) are passed by value to kernels and # need to be translated back into pointers for scalar/vector code. for varname, variable in self.variables.items(): if hasattr(variable, 'owner') and isinstance(variable.owner, Clock): # get arrayname without _ptr suffix (e.g. _array_defaultclock_dt) arrayname = self.get_array_name(variable, prefix='') # kernel_lines appear before dt is cast to float (in scalar_code), hence # we need to still use double (used in kernel parameters), see #148 if varname == "dt" and prefs.core.default_float_dtype == np.float32: # c_data_type(variable.dtype) is float, but we need double dtype = "double" else: dtype = dtype=c_data_type(variable.dtype) line = f"const {dtype}* _ptr{arrayname} = &_value{arrayname};" if line not in kernel_lines: kernel_lines.append(line) keywords = {'pointers_lines': stripped_deindented_lines('\n'.join(pointers)), 'support_code_lines': stripped_deindented_lines('\n'.join(support_code)), 'hashdefine_lines': stripped_deindented_lines('\n'.join(hash_defines)), 'denormals_code_lines': stripped_deindented_lines('\n'.join(self.denormals_to_zero_code())), 'kernel_lines': stripped_deindented_lines('\n'.join(kernel_lines)), 'uses_atomics': self.uses_atomics } keywords.update(template_kwds) return keywords