예제 #1
0
    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
예제 #2
0
    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
예제 #3
0
    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
예제 #4
0
    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
예제 #5
0
    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
예제 #6
0
    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
예제 #7
0
    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