def run_block(self, block): if block == 'run': get_device().main_queue.append((block + '_code_object', (self, ))) else: # Check the C++ code whether there is anything to run cpp_code = getattr(self.code, block + '_cpp_file') if len(cpp_code) and 'EMPTY_CODE_BLOCK' not in cpp_code: get_device().main_queue.append( (block + '_code_object', (self, ))) self.before_after_blocks.append(block)
def test_set_reset_device_implicit(): from angela2.devices import device_module old_prev_devices = list(device_module.previous_devices) device_module.previous_devices = [] test_device1 = ATestDevice() all_devices['test1'] = test_device1 test_device2 = ATestDevice() all_devices['test2'] = test_device2 set_device('test1', build_on_run=False, my_opt=1) set_device('test2', build_on_run=True, my_opt=2) assert get_device() is test_device2 assert get_device()._options['my_opt'] == 2 assert get_device().build_on_run reset_device() assert get_device() is test_device1 assert get_device()._options['my_opt'] == 1 assert not get_device().build_on_run reset_device() assert get_device() is runtime_device reset_device( ) # If there is no previous device, will reset to runtime device assert get_device() is runtime_device del all_devices['test1'] del all_devices['test2'] device_module.previous_devices = old_prev_devices
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 = [] # 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 angela2.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 or var.size == 1: 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 = [] 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 = user_func user_functions.extend(uf) support_code.extend(sc) pointers.extend(ps) hash_defines.extend(hd) support_code.append(self.universal_support_code) 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())), } keywords.update(template_kwds) return keywords
def __init__(self, owner, code, variables, variable_indices, template_name, template_source, compiler_kwds, name='numpy_code_object*'): check_compiler_kwds(compiler_kwds, [], 'numpy') from angela2.devices.device import get_device self.device = get_device() self.namespace = { '_owner': owner, # TODO: This should maybe go somewhere else 'logical_not': np.logical_not } CodeObject.__init__(self, owner, code, variables, variable_indices, template_name, template_source, compiler_kwds=compiler_kwds, name=name) self.variables_to_namespace()
def device_override_decorated_function(*args, **kwds): from angela2.devices.device import get_device curdev = get_device() if hasattr(curdev, name): return getattr(curdev, name)(*args, **kwds) else: return func(*args, **kwds)
def variables_to_array_names(variables, access_data=True): from angela2.devices.device import get_device device = get_device() names = [ device.get_array_name(var, access_data=access_data) for var in variables ] return names
def test_get_set_random_generator_state(): group = NeuronGroup(10, 'dv/dt = -v/(10*ms) + (10*ms)**-0.5*xi : 1', method='euler') group.v = 'rand()' run(10 * ms) assert np.var(group.v) > 0 # very basic test for randomness ;) old_v = np.array(group.v) random_state = get_device().get_random_state() group.v = 'rand()' run(10 * ms) assert np.var(group.v - old_v) > 0 # just checking for *some* difference old_v = np.array(group.v) get_device().set_random_state(random_state) group.v = 'rand()' run(10 * ms) assert_equal(group.v, old_v)
def get_array_name(var, access_data=True): # We have to do the import here to avoid circular import dependencies. from angela2.devices.device import get_device device = get_device() if access_data: return '_ptr' + device.get_array_name(var) else: return device.get_array_name(var, access_data=False)
def constant_or_scalar(varname, variable): ''' Convenience function to generate code to access the value of a variable. Will return ``'varname'`` if the ``variable`` is a constant, and ``array_name[0]`` if it is a scalar array. ''' from angela2.devices.device import get_device # avoid circular import if variable.array: return '%s[0]' % get_device().get_array_name(variable) else: return '%s' % varname
def test_set_reset_device_explicit(): original_device = get_device() test_device1 = ATestDevice() all_devices['test1'] = test_device1 test_device2 = ATestDevice() all_devices['test2'] = test_device2 test_device3 = ATestDevice() all_devices['test3'] = test_device3 set_device('test1', build_on_run=False, my_opt=1) set_device('test2', build_on_run=True, my_opt=2) set_device('test3', build_on_run=False, my_opt=3) reset_device('test1') # Directly jump back to the first device assert get_device() is test_device1 assert get_device()._options['my_opt'] == 1 assert not get_device().build_on_run del all_devices['test1'] del all_devices['test2'] del all_devices['test3'] reset_device(original_device)
def is_cpp_standalone(self): ''' Check whether we're running with cpp_standalone. Test if `get_device()` is instance `CPPStandaloneDevice`. Returns ------- is_cpp_standalone : bool whether currently using cpp_standalone device See Also -------- is_constant_and_cpp_standalone : uses the returned value ''' # imports here to avoid circular imports from angela2.devices.device import get_device from angela2.devices.cpp_standalone.device import CPPStandaloneDevice device = get_device() return isinstance(device, CPPStandaloneDevice)
def __init__(self, variables, variable_indices, owner, iterate_all, codeobj_class, name, template_name, override_conditional_write=None, allows_scalar_write=False): # We have to do the import here to avoid circular import dependencies. from angela2.devices.device import get_device self.device = get_device() self.variables = variables self.variable_indices = variable_indices self.func_name_replacements = {} for varname, var in variables.items(): if isinstance(var, Function): if codeobj_class in var.implementations: impl_name = var.implementations[codeobj_class].name if impl_name is not None: self.func_name_replacements[varname] = impl_name self.iterate_all = iterate_all self.codeobj_class = codeobj_class self.owner = owner if override_conditional_write is None: self.override_conditional_write = set() else: self.override_conditional_write = set(override_conditional_write) self.allows_scalar_write = allows_scalar_write self.name = name self.template_name = template_name # Gather the names of functions that should get an additional # "_vectorisation_idx" argument in the generated code. Take care # of storing their translated name (e.g. "_rand" instead of "rand") # if necessary self.auto_vectorise = { self.func_name_replacements.get(name, name) for name in self.variables if getattr(self.variables[name], 'auto_vectorise', False) }
def get_array_name(var, access_data=True): ''' Get a globally unique name for a `ArrayVariable`. Parameters ---------- var : `ArrayVariable` The variable for which a name should be found. access_data : bool, optional For `DynamicArrayVariable` objects, specifying `True` here means the name for the underlying data is returned. If specifying `False`, the name of object itself is returned (e.g. to allow resizing). Returns ------- name : str A uniqe name for `var`. ''' # We have to do the import here to avoid circular import dependencies. from angela2.devices.device import get_device device = get_device() return device.get_array_name(var, access_data=access_data)
def get_codeobj_class(self): ''' Return codeobject class based on target language and device. Choose which version of the GSL `CodeObject` to use. If ```isinstance(device, CPPStandaloneDevice)```, then we want the `GSLCPPStandaloneCodeObject`. Otherwise the return value is based on prefs.codegen.target. Returns ------- code_object : class The respective `CodeObject` class (i.e. either `GSLCythonCodeObject` or `GSLCPPStandaloneCodeObject`). ''' # imports in this function to avoid circular imports from angela2.devices.cpp_standalone.device import CPPStandaloneDevice from angela2.devices.device import get_device from ..codegen.runtime.GSLcython_rt import GSLCythonCodeObject device = get_device() if device.__class__ is CPPStandaloneDevice: # We do not want to accept subclasses here from ..devices.cpp_standalone.GSLcodeobject import GSLCPPStandaloneCodeObject # In runtime mode (i.e. Cython), the compiler settings are # added for each `CodeObject` (only the files that use the GSL are # linked to the GSL). However, in C++ standalone mode, there are global # compiler settings that are used for all files (stored in the # `CPPStandaloneDevice`). Furthermore, header file includes are directly # inserted into the template instead of added during the compilation # phase. Therefore, we have to add the options here # instead of in `GSLCPPStandaloneCodeObject` # Add the GSL library if it has not yet been added if 'gsl' not in device.libraries: device.libraries += ['gsl', 'gslcblas'] device.headers += [ '<stdio.h>', '<stdlib.h>', '<gsl/gsl_odeiv2.h>', '<gsl/gsl_errno.h>', '<gsl/gsl_matrix.h>' ] if sys.platform == 'win32': device.define_macros += [('WIN32', '1'), ('GSL_DLL', '1')] if prefs.GSL.directory is not None: device.include_dirs += [prefs.GSL.directory] return GSLCPPStandaloneCodeObject elif isinstance(device, RuntimeDevice): if prefs.codegen.target == 'auto': target_name = auto_target().class_name else: target_name = prefs.codegen.target if target_name == 'cython': return GSLCythonCodeObject raise NotImplementedError( ("GSL integration has not been implemented for " "for the '{target_name}' code generation target." "\nUse the 'cython' code generation target, " "or switch to the 'cpp_standalone' device.").format( target_name=target_name)) else: device_name = [ name for name, dev in all_devices.items() if dev is device ] assert len(device_name) == 1 raise NotImplementedError( ("GSL integration has not been implemented for " "for the '{device}' device." "\nUse either the 'cpp_standalone' device, " "or the runtime device with target language " "'cython'.").format(device=device_name[0]))
def __init__(self, dt=None, clock=None, when='start', order=0, name='angelaobject*'): # Setup traceback information for this object creation_stack = [] bases = [] for modulename in ['angela2']: if modulename in sys.modules: base, _ = os.path.split(sys.modules[modulename].__file__) bases.append(base) for fname, linenum, funcname, line in traceback.extract_stack(): if all(base not in fname for base in bases): s = ' File "{fname}", line {linenum}, in {funcname}\n {line}'.format( fname=fname, linenum=linenum, funcname=funcname, line=line) creation_stack.append(s) creation_stack = [''] + creation_stack #: A string indicating where this object was created (traceback with any parts of angela code removed) self._creation_stack = ( 'Object was created here (most recent call only, full details in ' 'debug log):\n' + creation_stack[-1]) self._full_creation_stack = 'Object was created here:\n' + '\n'.join( creation_stack) if dt is not None and clock is not None: raise ValueError( 'Can only specify either a dt or a clock, not both.') if not isinstance(when, str): from angela2.core.clocks import Clock # Give some helpful error messages for users coming from the alpha # version if isinstance(when, Clock): raise TypeError(("Do not use the 'when' argument for " "specifying a clock, either provide a " "timestep for the 'dt' argument or a Clock " "object for 'clock'.")) if isinstance(when, tuple): raise TypeError("Use the separate keyword arguments, 'dt' (or " "'clock'), 'when', and 'order' instead of " "providing a tuple for 'when'. Only use the " "'when' argument for the scheduling slot.") # General error raise TypeError("The 'when' argument has to be a string " "specifying the scheduling slot (e.g. 'start').") Nameable.__init__(self, name) #: The clock used for simulating this object self._clock = clock if clock is None: from angela2.core.clocks import Clock, defaultclock if dt is not None: self._clock = Clock(dt=dt, name=self.name + '_clock*') else: self._clock = defaultclock if getattr(self._clock, '_is_proxy', False): from angela2.devices.device import get_device self._clock = get_device().defaultclock #: Used to remember the `Network` in which this object has been included #: before, to raise an error if it is included in a new `Network` self._network = None #: The ID string determining when the object should be updated in `Network.run`. self.when = when #: The order in which objects with the same clock and ``when`` should be updated self.order = order self._dependencies = set() self._contained_objects = [] self._code_objects = [] self._active = True #: The scope key is used to determine which objects are collected by magic self._scope_key = self._scope_current_key logger.diagnostic( "Created angelaObject with name {self.name}, " "clock={self._clock}, " "when={self.when}, order={self.order}".format(self=self))
def create_runner_codeobj(group, code, template_name, run_namespace, user_code=None, variable_indices=None, name=None, check_units=True, needed_variables=None, additional_variables=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. run_namespace : dict-like An additional namespace that is used for variable lookup (either an explicitly defined namespace or one taken from the local context). 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`. 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.diagnostic(msg) from angela2.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 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) template_variables = getattr(template, 'variables', None) 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 # Add variables that are not in the abstract code, nor specified in the # template but nevertheless necessary if needed_variables is None: needed_variables = [] # Resolve all variables (variables used in the code and variables needed by # the template) variables = group.resolve_all( identifiers | set(needed_variables) | set(template_variables), # template variables are not known to the user: user_identifiers=user_identifiers, additional_variables=additional_variables, run_namespace=run_namespace) # We raise this error only now, because there is some non-obvious code path # where Jinja tries to get a Synapse's "name" attribute via syn['name'], # which then triggers the use of the `group_get_indices` template which does # not exist for standalone. Putting the check for template == None here # means we will first raise an error about the unknown identifier which will # then make Jinja try syn.name if template is None: codeobj_class_name = codeobj_class.class_name or codeobj_class.__name__ raise AttributeError( ('"%s" does not provide a code generation ' 'template "%s"') % (codeobj_class_name, template_name)) conditional_write_variables = {} # Add all the "conditional write" variables for var in variables.values(): cond_write_var = getattr(var, 'conditional_write', None) if cond_write_var in override_conditional_write: continue if cond_write_var is not None: if (cond_write_var.name in variables and not variables[cond_write_var.name] is cond_write_var): logger.diagnostic(('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])) else: conditional_write_variables[ cond_write_var.name] = cond_write_var variables.update(conditional_write_variables) if check_units: for c in code.values(): # This is the first time that the code is parsed, catch errors try: check_units_statements(c, variables) except (SyntaxError, ValueError) as ex: error_msg = _error_msg(c, name) raise ValueError(error_msg) from ex 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.items(): 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] # Check that all functions are available for varname, value in variables.items(): if isinstance(value, Function): try: value.implementations[codeobj_class] except KeyError as ex: # if we are dealing with numpy, add the default implementation from angela2.codegen.runtime.numpy_rt import NumpyCodeObject if codeobj_class is NumpyCodeObject: value.implementations.add_numpy_implementation( value.pyfunc) else: raise NotImplementedError( ('Cannot use function ' '%s: %s') % (varname, ex)) from ex # Gather the additional compiler arguments declared by function # implementations all_keywords = [ _gather_compiler_kwds(var, codeobj_class) for var in variables.values() if isinstance(var, Function) ] compiler_kwds = _merge_compiler_kwds(all_keywords) # Add the indices needed by the variables for varname in list(variables): 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, compiler_kwds=compiler_kwds)
def determine_keywords(self): from angela2.devices.device import get_device device = get_device() # load variables from namespace load_namespace = [] support_code = [] handled_pointers = set() user_functions = [] added = set() for varname, var in sorted(self.variables.items()): if isinstance(var, Variable) and not isinstance(var, (Subexpression, AuxiliaryVariable)): load_namespace.append('_var_{0} = _namespace["_var_{1}"]'.format(varname, varname)) if isinstance(var, AuxiliaryVariable): line = "cdef {dtype} {varname}".format( dtype=get_cpp_dtype(var.dtype), varname=varname) load_namespace.append(line) elif isinstance(var, Subexpression): dtype = get_cpp_dtype(var.dtype) line = "cdef {dtype} {varname}".format(dtype=dtype, varname=varname) load_namespace.append(line) elif isinstance(var, Constant): dtype_name = get_cpp_dtype(var.value) line = 'cdef {dtype} {varname} = _namespace["{varname}"]'.format(dtype=dtype_name, varname=varname) load_namespace.append(line) elif isinstance(var, Variable): if var.dynamic: load_namespace.append('{0} = _namespace["{1}"]'.format(self.get_array_name(var, False), self.get_array_name(var, False))) # 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 if get_dtype_str(var.dtype) == 'bool': newlines = ["cdef _numpy.ndarray[char, ndim=1, mode='c', cast=True] _buf_{array_name} = _namespace['{array_name}']", "cdef {cpp_dtype} * {array_name} = <{cpp_dtype} *> _buf_{array_name}.data"] else: newlines = ["cdef _numpy.ndarray[{cpp_dtype}, ndim=1, mode='c'] _buf_{array_name} = _namespace['{array_name}']", "cdef {cpp_dtype} * {array_name} = <{cpp_dtype} *> _buf_{array_name}.data"] if not var.scalar: newlines += ["cdef size_t _num{array_name} = len(_namespace['{array_name}'])"] if var.scalar and var.constant: newlines += ['cdef {cpp_dtype} {varname} = _namespace["{varname}"]'] else: newlines += ["cdef {cpp_dtype} {varname}"] for line in newlines: line = line.format(cpp_dtype=get_cpp_dtype(var.dtype), numpy_dtype=get_numpy_dtype(var.dtype), pointer_name=pointer_name, array_name=array_name, varname=varname ) load_namespace.append(line) handled_pointers.add(pointer_name) elif isinstance(var, Function): user_func = self._add_user_function(varname, var, added) if user_func is not None: sc, ln, uf = user_func support_code.extend(sc) load_namespace.extend(ln) user_functions.extend(uf) else: # fallback to Python object load_namespace.append('{0} = _namespace["{1}"]'.format(varname, varname)) for varname, dtype in sorted(self.temporary_vars): cpp_dtype = get_cpp_dtype(dtype) line = "cdef {cpp_dtype} {varname}".format(cpp_dtype=cpp_dtype, varname=varname) load_namespace.append(line) return {'load_namespace': '\n'.join(load_namespace), 'support_code_lines': support_code}
set_device('test2', build_on_run=True, my_opt=2) set_device('test3', build_on_run=False, my_opt=3) reset_device('test1') # Directly jump back to the first device assert get_device() is test_device1 assert get_device()._options['my_opt'] == 1 assert not get_device().build_on_run del all_devices['test1'] del all_devices['test2'] del all_devices['test3'] reset_device(original_device) @pytest.mark.skipif( not isinstance(get_device(), RuntimeDevice), reason='Getting/setting random number state only supported ' 'for runtime device.') def test_get_set_random_generator_state(): group = NeuronGroup(10, 'dv/dt = -v/(10*ms) + (10*ms)**-0.5*xi : 1', method='euler') group.v = 'rand()' run(10 * ms) assert np.var(group.v) > 0 # very basic test for randomness ;) old_v = np.array(group.v) random_state = get_device().get_random_state() group.v = 'rand()' run(10 * ms) assert np.var(group.v - old_v) > 0 # just checking for *some* difference old_v = np.array(group.v)