Source code for brian2.codegen.codeobject

'''
Module providing the base `CodeObject` and related functions.
'''
import copy
import platform
import weakref

from brian2.core.names import Nameable
from brian2.equations.unitcheck import check_units_statements
from brian2.utils.logger import get_logger
from brian2.utils.stringtools import indent, code_representation

from .translation import analyse_identifiers

__all__ = ['CodeObject',
           'CodeObjectUpdater',
           'constant_or_scalar']

logger = get_logger(__name__)


#: Dictionary with basic information about the current system (OS, etc.)
sys_info = {'system': platform.system(),
            'architecture': platform.architecture(),
            'machine': platform.machine()}


[docs]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 brian2.devices.device import get_device # avoid circular import if variable.array: return '%s[0]' % get_device().get_array_name(variable) else: return '%s' % varname
[docs]class CodeObject(Nameable): ''' Executable code object. The ``code`` can either be a string or a `brian2.codegen.templates.MultiTemplate`. After initialisation, the code is compiled with the given namespace using ``code.compile(namespace)``. Calling ``code(key1=val1, key2=val2)`` executes the code with the given variables inserted into the namespace. ''' #: The `CodeGenerator` class used by this `CodeObject` generator_class = None #: A short name for this type of `CodeObject` class_name = None def __init__(self, owner, code, variables, variable_indices, template_name, template_source, name='codeobject*'): Nameable.__init__(self, name=name) try: owner = weakref.proxy(owner) except TypeError: pass # if owner was already a weakproxy then this will be the error raised self.owner = owner self.code = code self.variables = variables self.variable_indices = variable_indices self.template_name = template_name self.template_source = template_source
[docs] @classmethod def is_available(cls): ''' Whether this target for code generation is available. Should use a minimal example to check whether code generation works in general. ''' raise NotImplementedError("CodeObject class %s is missing an " "'is_available' method." % (cls.__name__))
[docs] def update_namespace(self): ''' Update the namespace for this timestep. Should only deal with variables where *the reference* changes every timestep, i.e. where the current reference in `namespace` is not correct. ''' pass
[docs] def compile(self): pass
[docs] def __call__(self, **kwds): self.update_namespace() self.namespace.update(**kwds) return self.run()
[docs] def run(self): ''' Runs the code in the namespace. Returns ------- return_value : dict A dictionary with the keys corresponding to the `output_variables` defined during the call of `CodeGenerator.code_object`. ''' raise NotImplementedError()
def _error_msg(code, name): ''' Little helper function for error messages. ''' error_msg = 'Error generating code for code object %s ' % name code_lines = [l for l in code.split('\n') if len(l.strip())] # If the abstract code is only one line, display it in full if len(code_lines) <= 1: error_msg += 'from this abstract code: "%s"\n' % code_lines[0] else: error_msg += ('from %d lines of abstract code, first line is: ' '"%s"\n') % (len(code_lines), code_lines[0]) return error_msg
[docs]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 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 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.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: 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 + str(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.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, )