Esempio n. 1
0
    def get_array_name(var, access_data=True, prefix=None):
        '''
        Return a globally unique name for `var`.
        See CUDAStandaloneDevice.get_array_name for parameters.

        Here, `prefix` defaults to `'_ptr'` when `access_data=True`.

        `prefix='_ptr'` is used since the CUDACodeGenerator generates the
        `scalar_code` and `vector_code` snippets.
        '''
        # We have to do the import here to avoid circular import dependencies.
        from brian2.devices.device import get_device
        device = get_device()
        if access_data:
            if prefix is None:
                prefix = '_ptr'
            return device.get_array_name(var, access_data=True, prefix=prefix)
        else:
            return device.get_array_name(var, access_data=False, prefix=prefix)
Esempio n. 2
0
def pytest_runtest_makereport(item, call):
    if hasattr(item.config, 'workerinput'):
        fail_for_not_implemented = item.config.workerinput['fail_for_not_implemented']
    else:
        fail_for_not_implemented = item.config.fail_for_not_implemented
    outcome = yield
    rep = outcome.get_result()
    if rep.outcome == 'failed':
        project_dir = getattr(get_device(), 'project_dir', None)
        if project_dir is not None:
            rep.sections.append(('Standalone project directory', f'{project_dir}'))
        reinit_devices()
        if not fail_for_not_implemented:
            exc_cause = getattr(call.excinfo.value, '__cause__', None)
            if (call.excinfo.errisinstance(NotImplementedError) or
                isinstance(exc_cause, NotImplementedError)):
                rep.outcome = 'skipped'
                r = call.excinfo._getreprcrash()
                rep.longrepr = (str(r.path), r.lineno, r.message)
    else:
        # clean up after the test (delete directory for standalone)
        reinit_and_delete()
Esempio n. 3
0
def create_runner_codeobj(group, code, template_name,
                          variable_indices=None,
                          name=None, check_units=True,
                          needed_variables=None,
                          additional_variables=None,
                          additional_namespace=None,
                          template_kwds=None):
    ''' Create a `CodeObject` for the execution of code in the context of a
    `Group`.

    Parameters
    ----------
    group : `Group`
        The group where the code is to be run
    code : str
        The code to be executed.
    template_name : str
        The name of the template to use for the code.
    variable_indices : dict-like, optional
        A mapping from `Variable` objects to index names (strings).  If none is
        given, uses the corresponding attribute of `group`.
    name : str, optional
        A name for this code object, will use ``group + '_codeobject*'`` if
        none is given.
    check_units : bool, optional
        Whether to check units in the statement. Defaults to ``True``.
    needed_variables: list of str, optional
        A list of variables that are neither present in the abstract code, nor
        in the ``USES_VARIABLES`` statement in the template. This is only
        rarely necessary, an example being a `StateMonitor` where the
        names of the variables are neither known to the template nor included
        in the abstract code statements.
    additional_variables : dict-like, optional
        A mapping of names to `Variable` objects, used in addition to the
        variables saved in `group`.
    additional_namespace : dict-like, optional
        A mapping from names to objects, used in addition to the namespace
        saved in `group`.
    template_kwds : dict, optional
        A dictionary of additional information that is passed to the template.
    '''
    logger.debug('Creating code object for abstract code:\n' + str(code))
    from brian2.devices import get_device
    device = get_device()

    if check_units:
        if isinstance(code, dict):
            for c in code.values():
                check_code_units(c, group,
                                 additional_variables=additional_variables,
                                 additional_namespace=additional_namespace)
        else:
            check_code_units(code, group,
                             additional_variables=additional_variables,
                             additional_namespace=additional_namespace)

    codeobj_class = device.code_object_class(group.codeobj_class)
    template = getattr(codeobj_class.templater, template_name)

    all_variables = dict(group.variables)
    if additional_variables is not None:
        all_variables.update(additional_variables)

    # Determine the identifiers that were used
    if isinstance(code, dict):
        used_known = set()
        unknown = set()
        for v in code.values():
            _, uk, u = analyse_identifiers(v, all_variables, recursive=True)
            used_known |= uk
            unknown |= u
    else:
        _, used_known, unknown = analyse_identifiers(code, all_variables,
                                                     recursive=True)

    logger.debug('Unknown identifiers in the abstract code: ' + str(unknown))

    # Only pass the variables that are actually used
    variables = {}
    for var in used_known:
        # Emit a warning if a variable is also present in the namespace
        if (var in group.namespace or (additional_namespace is not None and
                                       var in additional_namespace[1])):
            message = ('Variable {var} is present in the namespace but is also an'
                       ' internal variable of {name}, the internal variable will'
                       ' be used.'.format(var=var, name=group.name))
            logger.warn(message, 'create_runner_codeobj.resolution_conflict',
                        once=True)
        variables[var] = all_variables[var]

    resolved_namespace = group.namespace.resolve_all(unknown,
                                                     additional_namespace)

    for varname, value in resolved_namespace.iteritems():
        if isinstance(value, Function):
            variables[varname] = value
        else:
            unit = get_unit(value)
            # For the moment, only allow the use of scalar values
            array_value = np.asarray(value)
            if array_value.shape != ():
                raise TypeError('Name "%s" does not refer to a scalar value' % varname)
            variables[varname] = Constant(name=varname, unit=unit, value=value)

    # Add variables that are not in the abstract code, nor specified in the
    # template but nevertheless necessary
    if needed_variables is None:
        needed_variables = []
    for var in needed_variables:
        variables[var] = all_variables[var]

    # Also add the variables that the template needs
    for var in template.variables:
        try:
            variables[var] = all_variables[var]
        except KeyError as ex:
            # We abuse template.variables here to also store names of things
            # from the namespace (e.g. rand) that are needed
            # TODO: Improve all of this namespace/specifier handling
            if group is not None:
                # Try to find the name in the group's namespace
                variables[var] = group.namespace.resolve(var,
                                                         additional_namespace)
            else:
                raise ex

    # always add N, the number of neurons or synapses
    variables['N'] = all_variables['N']

    if name is None:
        if group is not None:
            name = '%s_%s_codeobject*' % (group.name, template_name)
        else:
            name = '%s_codeobject*' % template_name

    all_variable_indices = copy.copy(group.variables.indices)
    if additional_variables is not None:
        all_variable_indices.update(additional_variables.indices)
    if variable_indices is not None:
        all_variable_indices.update(variable_indices)

    # Add the indices needed by the variables
    varnames = variables.keys()
    for varname in varnames:
        var_index = all_variable_indices[varname]
        if var_index != '_idx':
            variables[var_index] = all_variables[var_index]

    return device.code_object(owner=group,
                              name=name,
                              abstract_code=code,
                              variables=variables,
                              template_name=template_name,
                              variable_indices=all_variable_indices,
                              template_kwds=template_kwds,
                              codeobj_class=group.codeobj_class)
Esempio n. 4
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
Esempio n. 5
0
    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)
Esempio n. 6
0
def _generate_atomic_support_code():
    #            (arg_dtype, int_dtype, name in type cast function)
    overloads = [('int', 'int', 'int'),
                 ('float', 'int', 'int'),
                 ('double', 'unsigned long long int', 'longlong')]

    cuda_runtime_version = get_cuda_runtime_version()

    # Note: There are atomic functions that are supported only for compute capability >=
    # 3.5. We don't check for those as we require at least 3.5. If we ever support
    # smaller CC, we need to adapt the code here (see Atomic Functions in CUDA
    # Programming Guide)
    device = get_device()
    assert device.minimal_compute_capability >= 3.5, "Need to adapt atomic support code"

    software_implementation_float_dtype = '''
        // software implementation
        {int_dtype}* address_as_int = ({int_dtype}*)address;
        {int_dtype} old = *address_as_int, assumed;

        do {{
            assumed = old;
            old = atomicCAS(address_as_int, assumed,
                            __{arg_dtype}_as_{val_type_cast}(val {op}
                                   __{val_type_cast}_as_{arg_dtype}(assumed)));

        // Note: uses integer comparison to avoid hang in case of NaN (since NaN != NaN)
        }} while (assumed != old);

        return __{val_type_cast}_as_{arg_dtype}(old);
        '''

    hardware_implementation = '''
        // hardware implementation
        return atomic{op_name}(address, val);
        '''

    atomic_support_code = ''
    for op_name, op in [('Add', '+'), ('Mul', '*'), ('Div', '/')]:
        for arg_dtype, int_dtype, val_type_cast in overloads:
            # atomicAdd had hardware implementations, depending on dtype, compute
            # capability and CUDA runtime version. This section uses them when possible.
            if op_name == 'Add':
                format_sw = True
                code = '''
                inline __device__ {arg_dtype} _brian_atomic{op_name}({arg_dtype}* address, {arg_dtype} val)
                {{
                '''
                if arg_dtype in ['int', 'float']:
                    code += f'''
                    {hardware_implementation}
                    '''
                elif arg_dtype == 'double':
                    if cuda_runtime_version >= 8.0:
                        # Check for CC in at runtime to use software or hardware
                        # implementation.
                        # Don't need to check for defined __CUDA__ARCH__, it is always defined
                        # in __device__ and __global__ functions (no host code path).
                        code += '''
                        #if (__CUDA_ARCH__ >= 600)
                        {hardware_implementation}
                        #else
                        {software_implementation}
                        #endif
                        '''.format(
                            hardware_implementation=hardware_implementation,
                            software_implementation=software_implementation_float_dtype
                        )
                    else:
                        # For runtime < 8.0, there is no atomicAdd hardware implementation
                        # support independent of CC.
                        code += '''
                        {software_implementation}
                        '''.format(
                            software_implementation=software_implementation_float_dtype
                        )
                        format_sw = True
                code += '''
                }}
                '''
                if format_sw:
                    code = code.format(
                        arg_dtype=arg_dtype, int_dtype=int_dtype,
                        val_type_cast=val_type_cast, op_name=op_name, op=op
                    )
                else:
                    code = code.format(arg_dtype=arg_dtype, op_name=op_name)

                atomic_support_code += code
            # For other atomic operations on int types, we can use the same template.
            elif arg_dtype == 'int':
                atomic_support_code += '''
                inline __device__ int _brian_atomic{op_name}(int* address, int val)
                {{
                    // software implementation
                    int old = *address, assumed;

                    do {{
                        assumed = old;
                        old = atomicCAS(address, assumed, val {op} assumed);
                    }} while (assumed != old);

                    return old;
                }}
                '''.format(op_name=op_name, op=op)
            # Else (not atomicAdd and not int types) use this template.
            else:
                # in the software implementation, we treat all data as integer types
                # (since atomicCAS is only define
                # and use atomicCAS to swap the memory with our our desired value
                code = '''
                inline __device__ {{arg_dtype}} _brian_atomic{{op_name}}({{arg_dtype}}* address, {{arg_dtype}} val)
                {{{{
                    {software_implementation}
                }}}}
                '''.format(software_implementation=software_implementation_float_dtype)
                # above, just add the software_implementation code, below add the other
                # format variables (also present in software_implementation)
                code = code.format(
                    arg_dtype=arg_dtype, int_dtype=int_dtype,
                    val_type_cast=val_type_cast, op_name=op_name, op=op,
                )
                atomic_support_code += code

    return atomic_support_code
Esempio n. 7
0
def create_runner_codeobj(
    group,
    code,
    template_name,
    variable_indices=None,
    name=None,
    check_units=True,
    needed_variables=None,
    additional_variables=None,
    level=0,
    run_namespace=None,
    template_kwds=None,
    override_conditional_write=None,
):
    ''' Create a `CodeObject` for the execution of code in the context of a
    `Group`.

    Parameters
    ----------
    group : `Group`
        The group where the code is to be run
    code : str or dict of str
        The code to be executed.
    template_name : str
        The name of the template to use for the code.
    variable_indices : dict-like, optional
        A mapping from `Variable` objects to index names (strings).  If none is
        given, uses the corresponding attribute of `group`.
    name : str, optional
        A name for this code object, will use ``group + '_codeobject*'`` if
        none is given.
    check_units : bool, optional
        Whether to check units in the statement. Defaults to ``True``.
    needed_variables: list of str, optional
        A list of variables that are neither present in the abstract code, nor
        in the ``USES_VARIABLES`` statement in the template. This is only
        rarely necessary, an example being a `StateMonitor` where the
        names of the variables are neither known to the template nor included
        in the abstract code statements.
    additional_variables : dict-like, optional
        A mapping of names to `Variable` objects, used in addition to the
        variables saved in `group`.
    level : int, optional
        How far to go up in the stack to find the call frame.
    run_namespace : dict-like, optional
        An additional namespace that is used for variable lookup (if not
        defined, the implicit namespace of local variables is used).
    template_kwds : dict, optional
        A dictionary of additional information that is passed to the template.
    override_conditional_write: list of str, optional
        A list of variable names which are used as conditions (e.g. for
        refractoriness) which should be ignored.
    '''

    if isinstance(code, str):
        code = {None: code}
    msg = 'Creating code object (group=%s, template name=%s) for abstract code:\n' % (
        group.name, template_name)
    msg += indent(code_representation(code))
    logger.debug(msg)
    from brian2.devices import get_device
    device = get_device()

    if override_conditional_write is None:
        override_conditional_write = set([])
    else:
        override_conditional_write = set(override_conditional_write)

    if check_units:
        for c in code.values():
            check_code_units(c,
                             group,
                             additional_variables=additional_variables,
                             level=level + 1,
                             run_namespace=run_namespace)

    codeobj_class = device.code_object_class(group.codeobj_class)
    template = getattr(codeobj_class.templater, template_name)

    all_variables = dict(group.variables)
    if additional_variables is not None:
        all_variables.update(additional_variables)

    # Determine the identifiers that were used
    used_known = set()
    unknown = set()
    for v in code.values():
        _, uk, u = analyse_identifiers(v, all_variables, recursive=True)
        used_known |= uk
        unknown |= u

    logger.debug('Unknown identifiers in the abstract code: ' +
                 ', '.join(unknown))

    # Only pass the variables that are actually used
    variables = group.resolve_all(used_known | unknown,
                                  additional_variables=additional_variables,
                                  run_namespace=run_namespace,
                                  level=level + 1)

    conditional_write_variables = {}
    # Add all the "conditional write" variables
    for var in variables.itervalues():
        cond_write_var = getattr(var, 'conditional_write', None)
        if cond_write_var in override_conditional_write:
            continue
        if cond_write_var is not None and cond_write_var not in variables.values(
        ):
            if cond_write_var.name in variables:
                raise AssertionError(
                    ('Variable "%s" is needed for the '
                     'conditional write mechanism of variable '
                     '"%s". Its name is already used for %r.') %
                    (cond_write_var.name, var.name,
                     variables[cond_write_var.name]))
            conditional_write_variables[cond_write_var.name] = cond_write_var

    variables.update(conditional_write_variables)

    # Add variables that are not in the abstract code, nor specified in the
    # template but nevertheless necessary
    if needed_variables is None:
        needed_variables = []
    # Also add the variables that the template needs
    variables.update(
        group.resolve_all(
            set(needed_variables) | set(template.variables),
            additional_variables=additional_variables,
            run_namespace=run_namespace,
            level=level + 1,
            do_warn=False))  # no warnings for internally used variables

    if name is None:
        if group is not None:
            name = '%s_%s_codeobject*' % (group.name, template_name)
        else:
            name = '%s_codeobject*' % template_name

    all_variable_indices = copy.copy(group.variables.indices)
    if additional_variables is not None:
        all_variable_indices.update(additional_variables.indices)
    if variable_indices is not None:
        all_variable_indices.update(variable_indices)

    # Make "conditional write" variables use the same index as the variable
    # that depends on them
    for varname, var in variables.iteritems():
        cond_write_var = getattr(var, 'conditional_write', None)
        if cond_write_var is not None:
            all_variable_indices[
                cond_write_var.name] = all_variable_indices[varname]

    # Add the indices needed by the variables
    varnames = variables.keys()
    for varname in varnames:
        var_index = all_variable_indices[varname]
        if not var_index in ('_idx', '0'):
            variables[var_index] = all_variables[var_index]

    return device.code_object(
        owner=group,
        name=name,
        abstract_code=code,
        variables=variables,
        template_name=template_name,
        variable_indices=all_variable_indices,
        template_kwds=template_kwds,
        codeobj_class=group.codeobj_class,
        override_conditional_write=override_conditional_write,
    )
Esempio n. 8
0
def create_runner_codeobj(group, code, template_name,
                          user_code=None,
                          variable_indices=None,
                          name=None, check_units=True,
                          needed_variables=None,
                          additional_variables=None,
                          level=0,
                          run_namespace=None,
                          template_kwds=None,
                          override_conditional_write=None,
                          codeobj_class=None
                          ):
    ''' Create a `CodeObject` for the execution of code in the context of a
    `Group`.

    Parameters
    ----------
    group : `Group`
        The group where the code is to be run
    code : str or dict of str
        The code to be executed.
    template_name : str
        The name of the template to use for the code.
    user_code : str, optional
        The code that had been specified by the user before other code was
        added automatically. If not specified, will be assumed to be identical
        to ``code``.
    variable_indices : dict-like, optional
        A mapping from `Variable` objects to index names (strings).  If none is
        given, uses the corresponding attribute of `group`.
    name : str, optional
        A name for this code object, will use ``group + '_codeobject*'`` if
        none is given.
    check_units : bool, optional
        Whether to check units in the statement. Defaults to ``True``.
    needed_variables: list of str, optional
        A list of variables that are neither present in the abstract code, nor
        in the ``USES_VARIABLES`` statement in the template. This is only
        rarely necessary, an example being a `StateMonitor` where the
        names of the variables are neither known to the template nor included
        in the abstract code statements.
    additional_variables : dict-like, optional
        A mapping of names to `Variable` objects, used in addition to the
        variables saved in `group`.
    level : int, optional
        How far to go up in the stack to find the call frame.
    run_namespace : dict-like, optional
        An additional namespace that is used for variable lookup (if not
        defined, the implicit namespace of local variables is used).
    template_kwds : dict, optional
        A dictionary of additional information that is passed to the template.
    override_conditional_write: list of str, optional
        A list of variable names which are used as conditions (e.g. for
        refractoriness) which should be ignored.
    codeobj_class : class, optional
        The `CodeObject` class to run code with. If not specified, defaults to
        the `group`'s ``codeobj_class`` attribute.
    '''

    if name is None:
        if group is not None:
            name = '%s_%s_codeobject*' % (group.name, template_name)
        else:
            name = '%s_codeobject*' % template_name

    if user_code is None:
        user_code = code

    if isinstance(code, str):
        code = {None: code}
        user_code = {None: user_code}

    msg = 'Creating code object (group=%s, template name=%s) for abstract code:\n' % (group.name, template_name)
    msg += indent(code_representation(code))
    logger.debug(msg)
    from brian2.devices import get_device
    device = get_device()
    
    if override_conditional_write is None:
        override_conditional_write = set([])
    else:
        override_conditional_write = set(override_conditional_write)

    if check_units:
        for c in code.values():
            # This is the first time that the code is parsed, catch errors
            try:
                check_code_units(c, group,
                                 additional_variables=additional_variables,
                                 level=level+1,
                                 run_namespace=run_namespace)
            except (SyntaxError, KeyError, ValueError) as ex:
                error_msg = _error_msg(c, name)
                raise ValueError(error_msg + str(ex))

    if codeobj_class is None:
        codeobj_class = device.code_object_class(group.codeobj_class)
    else:
        codeobj_class = device.code_object_class(codeobj_class)
    template = getattr(codeobj_class.templater, template_name)

    all_variables = dict(group.variables)
    if additional_variables is not None:
        all_variables.update(additional_variables)

    # Determine the identifiers that were used
    identifiers = set()
    user_identifiers = set()
    for v, u_v in zip(code.values(), user_code.values()):
        _, uk, u = analyse_identifiers(v, all_variables, recursive=True)
        identifiers |= uk | u
        _, uk, u = analyse_identifiers(u_v, all_variables, recursive=True)
        user_identifiers |= uk | u
    # Only pass the variables that are actually used
    variables = group.resolve_all(identifiers,
                                  user_identifiers,
                                  additional_variables=additional_variables,
                                  run_namespace=run_namespace,
                                  level=level+1)

    conditional_write_variables = {}
    # Add all the "conditional write" variables
    for var in variables.itervalues():
        cond_write_var = getattr(var, 'conditional_write', None)
        if cond_write_var in override_conditional_write:
            continue
        if cond_write_var is not None and cond_write_var not in variables.values():
            if cond_write_var.name in variables:
                raise AssertionError(('Variable "%s" is needed for the '
                                      'conditional write mechanism of variable '
                                      '"%s". Its name is already used for %r.') % (cond_write_var.name,
                                                                                   var.name,
                                                                                   variables[cond_write_var.name]))
            conditional_write_variables[cond_write_var.name] = cond_write_var

    variables.update(conditional_write_variables)

    # Add variables that are not in the abstract code, nor specified in the
    # template but nevertheless necessary
    if needed_variables is None:
        needed_variables = []
    # Also add the variables that the template needs
    variables.update(group.resolve_all(set(needed_variables) | set(template.variables),
                                       # template variables are not known to the user:
                                       user_identifiers=set(),
                                       additional_variables=additional_variables,
                                       run_namespace=run_namespace,
                                       level=level+1))

    all_variable_indices = copy.copy(group.variables.indices)
    if additional_variables is not None:
        all_variable_indices.update(additional_variables.indices)
    if variable_indices is not None:
        all_variable_indices.update(variable_indices)

    # Make "conditional write" variables use the same index as the variable
    # that depends on them
    for varname, var in variables.iteritems():
        cond_write_var = getattr(var, 'conditional_write', None)
        if cond_write_var is not None:
            all_variable_indices[cond_write_var.name] = all_variable_indices[varname]

    # Add the indices needed by the variables
    varnames = variables.keys()
    for varname in varnames:
        var_index = all_variable_indices[varname]
        if not var_index in ('_idx', '0'):
            variables[var_index] = all_variables[var_index]

    return device.code_object(owner=group,
                              name=name,
                              abstract_code=code,
                              variables=variables,
                              template_name=template_name,
                              variable_indices=all_variable_indices,
                              template_kwds=template_kwds,
                              codeobj_class=codeobj_class,
                              override_conditional_write=override_conditional_write,
                              )