diff --git a/brian2/codegen/codeobject.py b/brian2/codegen/codeobject.py index 5c5132311..321c55f99 100644 --- a/brian2/codegen/codeobject.py +++ b/brian2/codegen/codeobject.py @@ -1,6 +1,6 @@ import functools -from brian2.core.specifiers import (ArrayVariable, Variable, +from brian2.core.variables import (ArrayVariable, Variable, AttributeVariable, Subexpression, StochasticVariable) from .functions.base import Function @@ -31,11 +31,11 @@ def get_default_codeobject_class(): return codeobj_class -def prepare_namespace(namespace, specifiers): +def prepare_namespace(namespace, variables): namespace = dict(namespace) # Add variables referring to the arrays arrays = [] - for value in specifiers.itervalues(): + for value in variables.itervalues(): if isinstance(value, ArrayVariable): arrays.append((value.arrayname, value.get_value())) namespace.update(arrays) @@ -43,7 +43,7 @@ def prepare_namespace(namespace, specifiers): return namespace -def create_codeobject(name, abstract_code, namespace, specifiers, template_name, +def create_codeobject(name, abstract_code, namespace, variables, template_name, indices, variable_indices, iterate_all, codeobj_class=None, template_kwds=None): @@ -69,10 +69,10 @@ def create_codeobject(name, abstract_code, namespace, specifiers, template_name, template = get_codeobject_template(template_name, codeobj_class=codeobj_class) - namespace = prepare_namespace(namespace, specifiers) + namespace = prepare_namespace(namespace, variables) logger.debug(name + " abstract code:\n" + abstract_code) - innercode, kwds = translate(abstract_code, specifiers, namespace, + innercode, kwds = translate(abstract_code, variables, namespace, dtype=brian_prefs['core.default_scalar_dtype'], language=codeobj_class.language, variable_indices=variable_indices, @@ -82,8 +82,8 @@ def create_codeobject(name, abstract_code, namespace, specifiers, template_name, code = template(innercode, **template_kwds) logger.debug(name + " code:\n" + str(code)) - specifiers.update(indices) - codeobj = codeobj_class(code, namespace, specifiers) + variables.update(indices) + codeobj = codeobj_class(code, namespace, variables) codeobj.compile() return codeobj @@ -114,40 +114,42 @@ class CodeObject(object): #: The `Language` used by this `CodeObject` language = None - def __init__(self, code, namespace, specifiers): + def __init__(self, code, namespace, variables): self.code = code - self.compile_methods = self.get_compile_methods(specifiers) + self.compile_methods = self.get_compile_methods(variables) self.namespace = namespace - self.specifiers = specifiers + self.variables = variables # Specifiers can refer to values that are either constant (e.g. dt) # or change every timestep (e.g. t). We add the values of the - # constant specifiers here and add the names of non-constant specifiers + # constant variables here and add the names of non-constant variables # to a list # A list containing tuples of name and a function giving the value self.nonconstant_values = [] - for name, spec in self.specifiers.iteritems(): - if isinstance(spec, Variable): - if not spec.constant: - self.nonconstant_values.append((name, spec.get_value)) - if not spec.scalar: - self.nonconstant_values.append(('_num' + name, - spec.get_len)) - else: - value = spec.get_value() - self.namespace[name] = value - # if it is a type that has a length, add a variable called - # '_num'+name with its length - if not spec.scalar: - self.namespace['_num' + name] = spec.get_len() - - def get_compile_methods(self, specifiers): + for name, var in self.variables.iteritems(): + if not var.constant: + self.nonconstant_values.append((name, var.get_value)) + if not var.scalar: + self.nonconstant_values.append(('_num' + name, + var.get_len)) + else: + try: + value = var.get_value() + except TypeError: # A dummy Variable without unit + continue + self.namespace[name] = value + # if it is a type that has a length, add a variable called + # '_num'+name with its length + if not var.scalar: + self.namespace['_num' + name] = var.get_len() + + def get_compile_methods(self, variables): meths = [] - for var, spec in specifiers.items(): - if isinstance(spec, Function): - meths.append(functools.partial(spec.on_compile, + for var, var in variables.items(): + if isinstance(var, Function): + meths.append(functools.partial(var.on_compile, language=self.language, var=var)) return meths diff --git a/brian2/codegen/languages/base.py b/brian2/codegen/languages/base.py index fef274176..70470e200 100644 --- a/brian2/codegen/languages/base.py +++ b/brian2/codegen/languages/base.py @@ -2,7 +2,7 @@ Base class for languages, gives the methods which should be overridden to implement a new language. ''' -from brian2.core.specifiers import (ArrayVariable, AttributeVariable, +from brian2.core.variables import (ArrayVariable, AttributeVariable, Subexpression) from brian2.utils.stringtools import get_identifiers @@ -34,7 +34,7 @@ def translate_statement(self, statement): ''' raise NotImplementedError - def translate_statement_sequence(self, statements, specifiers, namespace, indices): + def translate_statement_sequence(self, statements, variables, namespace, indices): ''' Translate a sequence of `Statement` into the target language, taking care to declare variables, etc. if necessary. @@ -45,7 +45,7 @@ def translate_statement_sequence(self, statements, specifiers, namespace, indice ''' raise NotImplementedError - def array_read_write(self, statements, specifiers): + def array_read_write(self, statements, variables): ''' Helper function, gives the set of ArrayVariables that are read from and written to in the series of statements. Returns the pair read, write @@ -60,6 +60,8 @@ def array_read_write(self, statements, specifiers): ids.add(stmt.var) read = read.union(ids) write.add(stmt.var) - read = set(var for var, spec in specifiers.items() if isinstance(spec, ArrayVariable) and var in read) - write = set(var for var, spec in specifiers.items() if isinstance(spec, ArrayVariable) and var in write) + read = set(varname for varname, var in variables.items() + if isinstance(var, ArrayVariable) and varname in read) + write = set(varname for varname, var in variables.items() + if isinstance(var, ArrayVariable) and varname in write) return read, write diff --git a/brian2/codegen/languages/cpp_lang.py b/brian2/codegen/languages/cpp_lang.py index 5b8d05606..8dd57d0f4 100644 --- a/brian2/codegen/languages/cpp_lang.py +++ b/brian2/codegen/languages/cpp_lang.py @@ -10,7 +10,7 @@ from brian2.utils.logger import get_logger from brian2.parsing.rendering import CPPNodeRenderer from brian2.core.preferences import brian_prefs, BrianPreference -from brian2.core.specifiers import ArrayVariable +from brian2.core.variables import ArrayVariable from .base import Language @@ -125,38 +125,38 @@ def translate_statement(self, statement): decl = '' return decl + var + ' ' + op + ' ' + self.translate_expression(expr) + ';' - def translate_statement_sequence(self, statements, specifiers, namespace, + def translate_statement_sequence(self, statements, variables, namespace, variable_indices, iterate_all): # Note that C++ code does not care about the iterate_all argument -- it # always has to loop over the elements - read, write = self.array_read_write(statements, specifiers) + read, write = self.array_read_write(statements, variables) lines = [] # read arrays - for var in read: - index_var = variable_indices[specifiers[var]] + '_idx' - spec = specifiers[var] - if var not in write: + for varname in read: + index_var = variable_indices[variables[varname]] + '_idx' + var = variables[varname] + if varname not in write: line = 'const ' else: line = '' - line = line + c_data_type(spec.dtype) + ' ' + var + ' = ' - line = line + '_ptr' + spec.arrayname + '[' + index_var + '];' + line = line + c_data_type(var.dtype) + ' ' + varname + ' = ' + line = line + '_ptr' + var.arrayname + '[' + index_var + '];' lines.append(line) # simply declare variables that will be written but not read - for var in write: - if var not in read: - spec = specifiers[var] - line = c_data_type(spec.dtype) + ' ' + var + ';' + for varname in write: + if varname not in read: + var = variables[varname] + line = c_data_type(var.dtype) + ' ' + varname + ';' lines.append(line) # the actual code lines.extend([self.translate_statement(stmt) for stmt in statements]) # write arrays - for var in write: - index_var = variable_indices[specifiers[var]] + '_idx' - spec = specifiers[var] - line = '_ptr' + spec.arrayname + '[' + index_var + '] = ' + var + ';' + for varname in write: + index_var = variable_indices[variables[varname]] + '_idx' + var = variables[varname] + line = '_ptr' + var.arrayname + '[' + index_var + '] = ' + varname + ';' lines.append(line) code = '\n'.join(lines) # set up the restricted pointers, these are used so that the compiler @@ -166,11 +166,11 @@ def translate_statement_sequence(self, statements, specifiers, namespace, # same array. E.g. in gapjunction code, v_pre and v_post refer to the # same array if a group is connected to itself arraynames = set() - for var, spec in specifiers.iteritems(): - if isinstance(spec, ArrayVariable): - arrayname = spec.arrayname + for varname, var in variables.iteritems(): + if isinstance(var, ArrayVariable): + arrayname = var.arrayname if not arrayname in arraynames: - line = c_data_type(spec.dtype) + ' * ' + self.restrict + '_ptr' + arrayname + ' = ' + arrayname + ';' + line = c_data_type(var.dtype) + ' * ' + self.restrict + '_ptr' + arrayname + ' = ' + arrayname + ';' lines.append(line) arraynames.add(arrayname) pointers = '\n'.join(lines) @@ -179,23 +179,22 @@ def translate_statement_sequence(self, statements, specifiers, namespace, user_functions = [] support_code = '' hash_defines = '' - for var, spec in itertools.chain(namespace.items(), - specifiers.items()): - if isinstance(spec, Function): - user_functions.append(var) - speccode = spec.code(self, var) + for varname, variable in namespace.items(): + if isinstance(variable, Function): + user_functions.append(varname) + speccode = variable.code(self, varname) support_code += '\n' + deindent(speccode['support_code']) hash_defines += deindent(speccode['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 spec.pyfunc is None: - pyfunc_name = '_python_' + var - if pyfunc_name in namespace: + if not variable.pyfunc is None: + pyfunc_name = '_python_' + varname + if pyfunc_name in namespace: logger.warn(('Namespace already contains function %s, ' 'not replacing it') % pyfunc_name) else: - namespace[pyfunc_name] = spec.pyfunc + namespace[pyfunc_name] = variable.pyfunc # delete the user-defined functions from the namespace for func in user_functions: diff --git a/brian2/codegen/languages/numpy_lang.py b/brian2/codegen/languages/numpy_lang.py index b9c5fae95..a48575f82 100644 --- a/brian2/codegen/languages/numpy_lang.py +++ b/brian2/codegen/languages/numpy_lang.py @@ -26,13 +26,13 @@ def translate_statement(self, statement): op = '=' return var + ' ' + op + ' ' + self.translate_expression(expr) - def translate_statement_sequence(self, statements, specifiers, namespace, + def translate_statement_sequence(self, statements, variables, namespace, variable_indices, iterate_all): - read, write = self.array_read_write(statements, specifiers) + read, write = self.array_read_write(statements, variables) lines = [] # read arrays for var in read: - spec = specifiers[var] + spec = variables[var] index = variable_indices[spec] line = var + ' = ' + spec.arrayname if not index in iterate_all: @@ -42,7 +42,7 @@ def translate_statement_sequence(self, statements, specifiers, namespace, lines.extend([self.translate_statement(stmt) for stmt in statements]) # write arrays for var in write: - index_var = variable_indices[specifiers[var]] + index_var = variable_indices[variables[var]] # check if all operations were inplace and we're operating on the # whole vector, if so we don't need to write the array back if not index_var in iterate_all: @@ -54,7 +54,7 @@ def translate_statement_sequence(self, statements, specifiers, namespace, all_inplace = False break if not all_inplace: - line = specifiers[var].arrayname + line = variables[var].arrayname if index_var in iterate_all: line = line + '[:]' else: diff --git a/brian2/codegen/runtime/numpy_rt/numpy_rt.py b/brian2/codegen/runtime/numpy_rt/numpy_rt.py index e89a3352c..23f375d0e 100644 --- a/brian2/codegen/runtime/numpy_rt/numpy_rt.py +++ b/brian2/codegen/runtime/numpy_rt/numpy_rt.py @@ -18,10 +18,10 @@ class NumpyCodeObject(CodeObject): 'templates')) language = NumpyLanguage() - def __init__(self, code, namespace, specifiers): + def __init__(self, code, namespace, variables): # TODO: This should maybe go somewhere else namespace['logical_not'] = np.logical_not - CodeObject.__init__(self, code, namespace, specifiers) + CodeObject.__init__(self, code, namespace, variables) def compile(self): super(NumpyCodeObject, self).compile() diff --git a/brian2/codegen/runtime/weave_rt/weave_rt.py b/brian2/codegen/runtime/weave_rt/weave_rt.py index 6287c554d..7f22ddf2b 100644 --- a/brian2/codegen/runtime/weave_rt/weave_rt.py +++ b/brian2/codegen/runtime/weave_rt/weave_rt.py @@ -47,8 +47,8 @@ class WeaveCodeObject(CodeObject): 'templates')) language = CPPLanguage() - def __init__(self, code, namespace, specifiers): - super(WeaveCodeObject, self).__init__(code, namespace, specifiers) + def __init__(self, code, namespace, variables): + super(WeaveCodeObject, self).__init__(code, namespace, variables) self.compiler = brian_prefs['codegen.runtime.weave.compiler'] self.extra_compile_args = brian_prefs['codegen.runtime.weave.extra_compile_args'] diff --git a/brian2/codegen/templates.py b/brian2/codegen/templates.py index a2210ad88..0f48efb47 100644 --- a/brian2/codegen/templates.py +++ b/brian2/codegen/templates.py @@ -36,14 +36,14 @@ def __init__(self, template): self.words = set([]) for v in temps: self.words.update(get_identifiers(v)) - #: The set of specifiers in this template - self.specifiers = set([]) + #: The set of variables in this template + self.variables = set([]) for v in temps: # This is the bit inside {} for USE_SPECIFIERS { list of words } specifier_blocks = re.findall(r'\bUSE_SPECIFIERS\b\s*\{(.*?)\}', v, re.M|re.S) for block in specifier_blocks: - self.specifiers.update(get_identifiers(block)) + self.variables.update(get_identifiers(block)) def __call__(self, code_lines, **kwds): kwds['code_lines'] = code_lines diff --git a/brian2/codegen/translation.py b/brian2/codegen/translation.py index 78956fae2..66f37f794 100644 --- a/brian2/codegen/translation.py +++ b/brian2/codegen/translation.py @@ -19,7 +19,7 @@ from numpy import float64 -from brian2.core.specifiers import Variable, Subexpression +from brian2.core.variables import Variable, Subexpression from brian2.utils.stringtools import (deindent, strip_empty_lines, get_identifiers) @@ -44,7 +44,7 @@ def __init__(self, **kwds): STANDARD_IDENTIFIERS = set(['and', 'or', 'not', 'True', 'False']) -def analyse_identifiers(code, specifiers, recursive=False): +def analyse_identifiers(code, variables, recursive=False): ''' Analyses a code string (sequence of statements) to find all identifiers by type. @@ -60,7 +60,7 @@ def analyse_identifiers(code, specifiers, recursive=False): ---------- code : str The code string, a sequence of statements one per line. - specifiers : dict of `Specifier`, set of names + variables : dict of `Variable`, set of names Specifiers for the model variables or a set of known names recursive : bool, optional Whether to recurse down into subexpressions (defaults to ``False``). @@ -77,20 +77,19 @@ def analyse_identifiers(code, specifiers, recursive=False): it and not previously known. Should correspond to variables in the external namespace. ''' - if isinstance(specifiers, collections.Mapping): - known = set(specifiers.keys()) + if isinstance(variables, collections.Mapping): + known = set(variables.keys()) else: - known = set(specifiers) - specifiers = dict((k, Variable(k, 1.0, dtype=float64, - is_bool=False, scalar=False)) for k in known) + known = set(variables) + variables = dict((k, Variable(unit=None)) for k in known) known |= STANDARD_IDENTIFIERS - stmts = make_statements(code, specifiers, float64) + stmts = make_statements(code, variables, float64) defined = set(stmt.var for stmt in stmts if stmt.op==':=') if recursive: - if not isinstance(specifiers, collections.Mapping): - raise TypeError('Have to specify a specifiers dictionary.') - allids = get_identifiers_recursively(code, specifiers) + if not isinstance(variables, collections.Mapping): + raise TypeError('Have to specify a variables dictionary.') + allids = get_identifiers_recursively(code, variables) else: allids = get_identifiers(code) dependent = allids.difference(defined, known) @@ -99,20 +98,20 @@ def analyse_identifiers(code, specifiers, recursive=False): return defined, used_known, dependent -def get_identifiers_recursively(expr, specifiers): +def get_identifiers_recursively(expr, variables): ''' Gets all the identifiers in a code, recursing down into subexpressions. ''' identifiers = get_identifiers(expr) for name in set(identifiers): - if name in specifiers and isinstance(specifiers[name], Subexpression): - s_identifiers = get_identifiers_recursively(specifiers[name].expr, - specifiers) + if name in variables and isinstance(variables[name], Subexpression): + s_identifiers = get_identifiers_recursively(variables[name].expr, + variables) identifiers |= s_identifiers return identifiers -def make_statements(code, specifiers, dtype): +def make_statements(code, variables, dtype): ''' Turn a series of abstract code statements into Statement objects, inferring whether each line is a set/declare operation, whether the variables are @@ -126,11 +125,10 @@ def make_statements(code, specifiers, dtype): if DEBUG: print 'INPUT CODE:' print code - dtypes = dict((name, value.dtype) for name, value in specifiers.items() if hasattr(value, 'dtype')) + dtypes = dict((name, var.dtype) for name, var in variables.iteritems()) # we will do inference to work out which lines are := and which are = - #defined = set(specifiers.keys()) # variables which are already defined - defined = set(var for var, spec in specifiers.items() - if hasattr(spec, 'get_value')) + defined = set(variables.keys()) + for line in lines: # parse statement into "var op expr" var, op, expr = parse_statement(line.code) @@ -144,7 +142,7 @@ def make_statements(code, specifiers, dtype): # for each line will give the variable being written to line.write = var # each line will give a set of variables which are read - line.read = get_identifiers_recursively(expr, specifiers) + line.read = get_identifiers_recursively(expr, variables) if DEBUG: print 'PARSED STATEMENTS:' @@ -182,7 +180,7 @@ def make_statements(code, specifiers, dtype): # as invalid, and are invalidated whenever one of the variables appearing # in the RHS changes value. #subexpressions = get_all_subexpressions() - subexpressions = dict((name, val) for name, val in specifiers.items() if isinstance(val, Subexpression)) + subexpressions = dict((name, val) for name, val in variables.items() if isinstance(val, Subexpression)) if DEBUG: print 'SUBEXPRESSIONS:', subexpressions.keys() statements = [] @@ -240,16 +238,16 @@ def make_statements(code, specifiers, dtype): return statements -def translate(code, specifiers, namespace, dtype, language, +def translate(code, variables, namespace, dtype, language, variable_indices, iterate_all): ''' Translates an abstract code block into the target language. ``code`` The abstract code block, a series of one-line statements. - ``specifiers`` + ``variables`` A dict of ``(var, spec)`` where ``var`` is a variable name whose type - is specified by ``spec``, a `Specifier` object. These include + is specified by ``spec``, a `Variable` object. These include `Value` for a single (non-vector) value that will be inserted into the namespace at runtime, `Function` for a function, `ArrayVariable` for a value coming from an array of values, @@ -257,7 +255,7 @@ def translate(code, specifiers, namespace, dtype, language, `Subexpression` for a common subexpression used in the code. There should only be a single `Index` specifier, and the name should correspond to that given in the `ArrayVariable` - specifiers. + variables. ``dtype`` The default dtype for newly created variables (usually float64). ``language`` @@ -265,7 +263,7 @@ def translate(code, specifiers, namespace, dtype, language, Returns a multi-line string. ''' - statements = make_statements(code, specifiers, dtype) - return language.translate_statement_sequence(statements, specifiers, + statements = make_statements(code, variables, dtype) + return language.translate_statement_sequence(statements, variables, namespace, variable_indices, iterate_all) diff --git a/brian2/core/namespace.py b/brian2/core/namespace.py index ceb11afa8..b84324f4b 100644 --- a/brian2/core/namespace.py +++ b/brian2/core/namespace.py @@ -99,7 +99,7 @@ def _same_function(func1, func2): ''' Helper function, used during namespace resolution for comparing wether to functions are the same. This takes care of treating a function and a - `Function` specifiers whose `Function.pyfunc` attribute matches as the + `Function` variables whose `Function.pyfunc` attribute matches as the same. This prevents the user from getting spurious warnings when having for example a numpy function such as :np:func:`~random.randn` in the local namespace, while the ``randn`` symbol in the numpy namespace used for the diff --git a/brian2/core/specifiers.py b/brian2/core/variables.py similarity index 85% rename from brian2/core/specifiers.py rename to brian2/core/variables.py index 71e244c51..0fdd155f2 100644 --- a/brian2/core/specifiers.py +++ b/brian2/core/variables.py @@ -1,484 +1,466 @@ -''' -Classes used to specify the type of a function, variable or common sub-expression - -TODO: have a single global dtype rather than specify for each variable? -''' -import numpy as np - -from brian2.units.allunits import second - -from brian2.utils.stringtools import get_identifiers -from brian2.units.fundamentalunits import (Quantity, Unit, is_scalar_type, - fail_for_dimension_mismatch, - have_same_dimensions) - -__all__ = ['Specifier', - 'Variable', - 'StochasticVariable', - 'AttributeVariable', - 'ArrayVariable', - 'DynamicArrayVariable', - 'Subexpression', - 'Index', - ] - - -def get_dtype(obj): - if hasattr(obj, 'dtype'): - return obj.dtype - else: - return np.dtype(type(obj)) - - -############################################################################### -# Parent classes -############################################################################### -class Specifier(object): - ''' - An object providing information about parts of a model (e.g. variables). - `Specifier` objects are used both to store the information within the model - (and allow for things like unit checking) and are passed on to code - generation to specify properties like the dtype. - - This class is only used as a parent class for more concrete specifiers. - - ''' - - def __init__(self): - pass - - -class Variable(Specifier): - ''' - An object providing information about model variables (including implicit - variables such as ``t`` or ``xi``). - - Parameters - ---------- - unit : `Unit` - The unit of the variable. Note that the variable itself (as referenced - by value) should never have units attached. - value: reference to the variable value, optional - Some variables (e.g. stochastic variables) don't have their value - stored anywhere, they'd pass ``None`` as a value. - dtype: `numpy.dtype`, optional - The dtype used for storing the variable. If a - scalar : bool, optional - Whether the variable is a scalar value (``True``) or vector-valued, e.g. - defined for every neuron (``False``). Defaults to ``True``. - constant: bool, optional - Whether the value of this variable can change during a run. Defaults - to ``False``. - is_bool: bool, optional - Whether this is a boolean variable (also implies it is dimensionless). - If no value is given, checks the value itself. - See Also - -------- - Value - ''' - def __init__(self, unit, value=None, dtype=None, scalar=None, - constant=False, is_bool=None): - Specifier.__init__(self) - - #: The variable's unit. - self.unit = unit - - #: reference to a value of type `dtype` - self.value = value - - if dtype is None: - self.dtype = get_dtype(value) - else: - value_dtype = get_dtype(value) - if value is not None and value_dtype != dtype: - raise TypeError(('Conflicting dtype information: ' - 'referred value has dtype %r, not ' - '%r.') % (value_dtype, dtype)) - #: The dtype used for storing the variable. - self.dtype = dtype - - if is_bool is None: - if value is None: - raise TypeError('is_bool needs to be specified if no value is given') - self.is_bool = value is True or value is False - else: - #: Whether this variable is a boolean - self.is_bool = is_bool - - if is_bool: - if not have_same_dimensions(unit, 1): - raise ValueError('Boolean variables can only be dimensionless') - - if scalar is None: - if value is None: - raise TypeError('scalar needs to be specified if no value is given') - self.scalar = is_scalar_type(value) - else: - #: Whether the variable is a scalar - self.scalar = scalar - - #: Whether the variable is constant during a run - self.constant = constant - - def get_value(self): - ''' - Return the value associated with the variable. - ''' - if self.value is None: - raise TypeError('Variable does not have a value') - else: - return self.value - - def set_value(self): - ''' - Set the value associated with the variable. - ''' - raise NotImplementedError() - - def get_value_with_unit(self): - return Quantity(self.get_value(), self.unit.dimensions) - - def get_addressable_value(self, level=0): - return self.get_value() - - def get_addressable_value_with_unit(self, level=0): - return self.get_value_with_unit() - - def get_len(self): - if self.scalar: - return 0 - else: - return len(self.get_value()) - - def __repr__(self): - description = ('<{classname}(unit={unit}, value={value}, ' - 'dtype={dtype}, scalar={scalar}, constant={constant})>') - return description.format(classname=self.__class__.__name__, - unit=repr(self.unit), - value='' % type(self.value), - dtype=repr(self.dtype), - scalar=repr(self.scalar), - constant=repr(self.constant)) - -############################################################################### -# Concrete classes that are used as specifiers in practice. -############################################################################### - - -class StochasticVariable(Variable): - ''' - An object providing information about a stochastic variable. Automatically - sets the unit to ``second**-.5``. - - ''' - def __init__(self): - # The units of stochastic variables is fixed - Variable.__init__(self, second**(-.5), dtype=np.float64, - scalar=False, constant=False, is_bool=False) - - -class AttributeVariable(Variable): - ''' - An object providing information about a value saved as an attribute of an - object. Instead of saving a reference to the value itself, we save the - name of the attribute. This way, we get the correct value if the attribute - is overwritten with a new value (e.g. in the case of ``clock.t_``) - - The object value has to be accessible by doing ``getattr(obj, attribute)``. - - Parameters - ---------- - unit : `Unit` - The unit of the variable - obj : object - The object storing the variable's value (e.g. a `NeuronGroup`). - attribute : str - The name of the attribute storing the variable's value. `attribute` has - to be an attribute of `obj`. - constant : bool, optional - Whether the attribute's value is constant during a run. Defaults to - ``False``. - Raises - ------ - AttributeError - If `obj` does not have an attribute `attribute`. - - ''' - def __init__(self, unit, obj, attribute, constant=False): - if not hasattr(obj, attribute): - raise AttributeError('Object %r does not have an attribute %r' % - (obj, attribute)) - - value = getattr(obj, attribute) - - Variable.__init__(self, unit, value, constant=constant) - #: A reference to the object storing the variable's value - self.obj = obj - #: The name of the attribute storing the variable's value - self.attribute = attribute - - def get_value(self): - return getattr(self.obj, self.attribute) - - def __repr__(self): - description = ('{classname}(unit={unit}, obj={obj}, ' - 'attribute={attribute}, constant={constant})') - return description.format(classname=self.__class__.__name__, - unit=repr(self.unit), - obj=repr(self.obj), - attribute=repr(self.attribute), - constant=repr(self.constant)) - - -class VariableView(object): - - def __init__(self, specifier, group, unit=None, level=0): - self.specifier = specifier - self.group = group - self.unit = unit - self.level = level - - def __getitem__(self, i): - spec = self.specifier - if spec.scalar: - if not (i == slice(None) or i == 0 or (hasattr(i, '__len__') and len(i) == 0)): - raise IndexError('Variable is a scalar variable.') - indices = 0 - else: - indices = self.group.indices[self.group.variable_indices[spec]][i] - if self.unit is None or have_same_dimensions(self.unit, Unit(1)): - return spec.get_value()[indices] - else: - return Quantity(spec.get_value()[indices], self.unit.dimensions) - - def __setitem__(self, i, value): - spec = self.specifier - if spec.scalar: - if not (i == slice(None) or i == 0 or (hasattr(i, '__len__') and len(i) == 0)): - raise IndexError('Variable is a scalar variable.') - indices = np.array([0]) - else: - indices = self.group.indices[self.group.variable_indices[spec]][i] - if isinstance(value, basestring): - check_units = self.unit is not None - self.group._set_with_code(spec, indices, value, - check_units, level=self.level + 1) - else: - if not self.unit is None: - fail_for_dimension_mismatch(value, self.unit) - self.specifier.value[indices] = value - - def __array__(self, dtype=None): - if dtype is not None and dtype != self.specifier.dtype: - raise NotImplementedError('Changing dtype not supported') - return self[:] - - def __add__(self, other): - return self[:] + other - - def __sub__(self, other): - return self[:] - other - - def __mul__(self, other): - return self[:] * other - - def __div__(self, other): - return self[:] / other - - def __iadd__(self, other): - if isinstance(other, basestring): - raise TypeError(('In-place modification with strings not ' - 'supported. Use group.var = "var + expression" ' - 'instead of group.var += "expression".')) - else: - rhs = self[:] + other - self[:] = rhs - return self - - def __isub__(self, other): - if isinstance(other, basestring): - raise TypeError(('In-place modification with strings not ' - 'supported. Use group.var = "var - expression" ' - 'instead of group.var -= "expression".')) - else: - rhs = self[:] - other - self[:] = rhs - return self - - def __imul__(self, other): - if isinstance(other, basestring): - raise TypeError(('In-place modification with strings not ' - 'supported. Use group.var = "var * expression" ' - 'instead of group.var *= "expression".')) - else: - rhs = self[:] * other - self[:] = rhs - return self - - def __idiv__(self, other): - if isinstance(other, basestring): - raise TypeError(('In-place modification with strings not ' - 'supported. Use group.var = "var / expression" ' - 'instead of group.var /= "expression".')) - else: - rhs = self[:] / other - self[:] = rhs - return self - - def __repr__(self): - if self.unit is None or have_same_dimensions(self.unit, Unit(1)): - return '<%s.%s_: %r>' % (self.group.name, self.specifier.name, - self.specifier.get_value()) - else: - return '<%s.%s: %r>' % (self.group.name, self.specifier.name, - Quantity(self.specifier.get_value(), - self.unit.dimensions)) - - -class ArrayVariable(Variable): - ''' - An object providing information about a model variable stored in an array - (for example, all state variables). - - TODO - - Parameters - ---------- - name : str - The name of the variable. - unit : `Unit` - The unit of the variable - value : `numpy.array` - A reference to the array storing the data for the variable. - group : `Group`, optional - The group to which this variable belongs, this is necessary to - interpret strings in the context of this group. - constant : bool, optional - Whether the variable's value is constant during a run. - Defaults to ``False``. - scalar : bool, optional - Whether this array is a 1-element array that should be treated like a - scalar (e.g. for a single delay value across synapses). Defaults to - ``False``. - is_bool: bool, optional - Whether this is a boolean variable (also implies it is dimensionless). - Defaults to ``False`` - ''' - def __init__(self, name, unit, value, group=None, constant=False, - scalar=False, is_bool=False): - - self.group = group - self.name = name - - Variable.__init__(self, unit, value, scalar=scalar, - constant=constant, is_bool=is_bool) - #: The reference to the array storing the data for the variable. - self.value = value - - group_name = '_'+group.name+'_' if group is not None else '_' - #: The name for the array used in generated code - self.arrayname = '_array' + group_name + name - - def get_value(self): - return self.value - - def set_value(self, value): - self.value[:] = value - - def get_addressable_value(self, level=0): - return VariableView(self, self.group, None, level) - - def get_addressable_value_with_unit(self, level=0): - return VariableView(self, self.group, self.unit, level) - - -class DynamicArrayVariable(ArrayVariable): - ''' - An object providing information about a model variable stored in a dynamic - array (used in synapses). - ''' - - def get_value(self): - # The actual numpy array is accesible via DynamicArray1D.data - return self.value.data - - -class Subexpression(Variable): - ''' - An object providing information about a static equation in a model - definition, used as a hint in optimising. Can test if a variable is used - via ``var in spec``. The specifier is also able to return the result of - the expression (used in a `StateMonitor`, for example). - - Parameters - ---------- - unit : `Unit` - The unit of the static equation - dtype : `numpy.dtype` - The dtype used for the expression. - expr : str - The expression defining the static equation. - specifiers : dict - The specifiers dictionary, containing specifiers for the - model variables used in the expression - namespace : dict - The namespace dictionary, containing identifiers for all the external - variables/functions used in the expression - is_bool: bool, optional - Whether this is a boolean variable (also implies it is dimensionless). - Defaults to ``False`` - ''' - def __init__(self, unit, dtype, expr, specifiers, namespace, - is_bool=False): - Variable.__init__(self, unit, value=None, dtype=dtype, - constant=False, scalar=False, is_bool=is_bool) - - #: The expression defining the static equation. - self.expr = expr.strip() - #: The identifiers used in the expression - self.identifiers = get_identifiers(expr) - #: Specifiers for the identifiers used in the expression - self.specifiers = specifiers - - #: The NeuronGroup's namespace for the identifiers used in the - #: expression - self.namespace = namespace - - #: An additional namespace provided by the run function (and updated - #: in `NeuronGroup.pre_run`) that is used if the NeuronGroup does not - #: have an explicitly defined namespace. - self.additional_namespace = None - - def get_value(self): - variable_values = {} - for identifier in self.identifiers: - if identifier in self.specifiers: - variable_values[identifier] = self.specifiers[identifier].get_value() - else: - variable_values[identifier] = self.namespace.resolve(identifier, - self.additional_namespace, - strip_units=True) - return eval(self.expr, variable_values) - - def __contains__(self, var): - return var in self.identifiers - - def __repr__(self): - description = ('<{classname}(unit={unit}, dtype={dtype}, ' - 'expr={expr}, specifiers=<...>, namespace=<....>)>') - return description.format(classname=self.__class__.__name__, - unit=repr(self.unit), - dtype=repr(self.dtype), - expr=repr(self.expr)) - - -class Index(Variable): - ''' - An object describing an index variable. - ''' - def __init__(self): - Variable.__init__(self, Unit(1), None, dtype=np.int32, - scalar=False, is_bool=False, constant=True) - - def get_value(self): - return self[:] +''' +Classes used to specify the type of a function, variable or common sub-expression + +TODO: have a single global dtype rather than specify for each variable? +''' +import numpy as np + +from brian2.units.allunits import second + +from brian2.utils.stringtools import get_identifiers +from brian2.units.fundamentalunits import (Quantity, Unit, is_scalar_type, + fail_for_dimension_mismatch, + have_same_dimensions) + +__all__ = ['Variable', + 'Variable', + 'StochasticVariable', + 'AttributeVariable', + 'ArrayVariable', + 'DynamicArrayVariable', + 'Subexpression', + 'Index', + ] + + +def get_dtype(obj): + if hasattr(obj, 'dtype'): + return obj.dtype + else: + return np.obj2sctype(obj) + + +class Variable(object): + ''' + An object providing information about model variables (including implicit + variables such as ``t`` or ``xi``). + + Parameters + ---------- + unit : `Unit` + The unit of the variable. Note that the variable itself (as referenced + by value) should never have units attached. + value: reference to the variable value, optional + Some variables (e.g. stochastic variables) don't have their value + stored anywhere, they'd pass ``None`` as a value. + dtype: `numpy.dtype`, optional + The dtype used for storing the variable. If a + scalar : bool, optional + Whether the variable is a scalar value (``True``) or vector-valued, e.g. + defined for every neuron (``False``). Defaults to ``True``. + constant: bool, optional + Whether the value of this variable can change during a run. Defaults + to ``False``. + is_bool: bool, optional + Whether this is a boolean variable (also implies it is dimensionless). + If specified as ``None`` and a `value` is given, checks the value + itself. If no `value` is given, defaults to ``False``. + See Also + -------- + Value + ''' + def __init__(self, unit, value=None, dtype=None, scalar=None, + constant=False, is_bool=None): + + #: The variable's unit. + self.unit = unit + + #: reference to a value of type `dtype` + self.value = value + + if dtype is None: + self.dtype = get_dtype(value) + else: + value_dtype = get_dtype(value) + if value is not None and value_dtype != dtype: + raise TypeError(('Conflicting dtype information: ' + 'referred value has dtype %r, not ' + '%r.') % (value_dtype, dtype)) + #: The dtype used for storing the variable. + self.dtype = dtype + + if is_bool is None: + if value is None: + self.is_bool = False + self.is_bool = value is True or value is False + else: + #: Whether this variable is a boolean + self.is_bool = is_bool + + if is_bool: + if not have_same_dimensions(unit, 1): + raise ValueError('Boolean variables can only be dimensionless') + + if scalar is None: + if value is None: + self.scalar = True + self.scalar = is_scalar_type(value) + else: + #: Whether the variable is a scalar + self.scalar = scalar + + #: Whether the variable is constant during a run + self.constant = constant + + def get_value(self): + ''' + Return the value associated with the variable. + ''' + if self.value is None: + raise TypeError('Variable does not have a value') + else: + return self.value + + def set_value(self): + ''' + Set the value associated with the variable. + ''' + raise NotImplementedError() + + def get_value_with_unit(self): + return Quantity(self.get_value(), self.unit.dimensions) + + def get_addressable_value(self, level=0): + return self.get_value() + + def get_addressable_value_with_unit(self, level=0): + return self.get_value_with_unit() + + def get_len(self): + if self.scalar: + return 0 + else: + return len(self.get_value()) + + def __repr__(self): + description = ('<{classname}(unit={unit}, value={value}, ' + 'dtype={dtype}, scalar={scalar}, constant={constant})>') + return description.format(classname=self.__class__.__name__, + unit=repr(self.unit), + value='' % type(self.value), + dtype=repr(self.dtype), + scalar=repr(self.scalar), + constant=repr(self.constant)) + +############################################################################### +# Concrete classes that are used as variables in practice. +############################################################################### + + +class StochasticVariable(Variable): + ''' + An object providing information about a stochastic variable. Automatically + sets the unit to ``second**-.5``. + + ''' + def __init__(self): + # The units of stochastic variables is fixed + Variable.__init__(self, second**(-.5), dtype=np.float64, + scalar=False, constant=False, is_bool=False) + + +class AttributeVariable(Variable): + ''' + An object providing information about a value saved as an attribute of an + object. Instead of saving a reference to the value itself, we save the + name of the attribute. This way, we get the correct value if the attribute + is overwritten with a new value (e.g. in the case of ``clock.t_``) + + The object value has to be accessible by doing ``getattr(obj, attribute)``. + + Parameters + ---------- + unit : `Unit` + The unit of the variable + obj : object + The object storing the variable's value (e.g. a `NeuronGroup`). + attribute : str + The name of the attribute storing the variable's value. `attribute` has + to be an attribute of `obj`. + constant : bool, optional + Whether the attribute's value is constant during a run. Defaults to + ``False``. + Raises + ------ + AttributeError + If `obj` does not have an attribute `attribute`. + + ''' + def __init__(self, unit, obj, attribute, constant=False): + if not hasattr(obj, attribute): + raise AttributeError('Object %r does not have an attribute %r' % + (obj, attribute)) + + value = getattr(obj, attribute) + + Variable.__init__(self, unit, value, constant=constant) + #: A reference to the object storing the variable's value + self.obj = obj + #: The name of the attribute storing the variable's value + self.attribute = attribute + + def get_value(self): + return getattr(self.obj, self.attribute) + + def __repr__(self): + description = ('{classname}(unit={unit}, obj={obj}, ' + 'attribute={attribute}, constant={constant})') + return description.format(classname=self.__class__.__name__, + unit=repr(self.unit), + obj=repr(self.obj), + attribute=repr(self.attribute), + constant=repr(self.constant)) + + +class VariableView(object): + + def __init__(self, variable, group, unit=None, level=0): + self.variable = variable + self.group = group + self.unit = unit + self.level = level + + def __getitem__(self, i): + variable = self.variable + if variable.scalar: + if not (i == slice(None) or i == 0 or (hasattr(i, '__len__') and len(i) == 0)): + raise IndexError('Variable is a scalar variable.') + indices = 0 + else: + indices = self.group.indices[self.group.variable_indices[variable]][i] + if self.unit is None or have_same_dimensions(self.unit, Unit(1)): + return variable.get_value()[indices] + else: + return Quantity(variable.get_value()[indices], self.unit.dimensions) + + def __setitem__(self, i, value): + variable = self.variable + if variable.scalar: + if not (i == slice(None) or i == 0 or (hasattr(i, '__len__') and len(i) == 0)): + raise IndexError('Variable is a scalar variable.') + indices = np.array([0]) + else: + indices = self.group.indices[self.group.variable_indices[variable]][i] + if isinstance(value, basestring): + check_units = self.unit is not None + self.group._set_with_code(variable, indices, value, + check_units, level=self.level + 1) + else: + if not self.unit is None: + fail_for_dimension_mismatch(value, self.unit) + variable.value[indices] = value + + def __array__(self, dtype=None): + if dtype is not None and dtype != self.variable.dtype: + raise NotImplementedError('Changing dtype not supported') + return self[:] + + def __add__(self, other): + return self[:] + other + + def __sub__(self, other): + return self[:] - other + + def __mul__(self, other): + return self[:] * other + + def __div__(self, other): + return self[:] / other + + def __iadd__(self, other): + if isinstance(other, basestring): + raise TypeError(('In-place modification with strings not ' + 'supported. Use group.var = "var + expression" ' + 'instead of group.var += "expression".')) + else: + rhs = self[:] + other + self[:] = rhs + return self + + def __isub__(self, other): + if isinstance(other, basestring): + raise TypeError(('In-place modification with strings not ' + 'supported. Use group.var = "var - expression" ' + 'instead of group.var -= "expression".')) + else: + rhs = self[:] - other + self[:] = rhs + return self + + def __imul__(self, other): + if isinstance(other, basestring): + raise TypeError(('In-place modification with strings not ' + 'supported. Use group.var = "var * expression" ' + 'instead of group.var *= "expression".')) + else: + rhs = self[:] * other + self[:] = rhs + return self + + def __idiv__(self, other): + if isinstance(other, basestring): + raise TypeError(('In-place modification with strings not ' + 'supported. Use group.var = "var / expression" ' + 'instead of group.var /= "expression".')) + else: + rhs = self[:] / other + self[:] = rhs + return self + + def __repr__(self): + if self.unit is None or have_same_dimensions(self.unit, Unit(1)): + return '<%s.%s_: %r>' % (self.group.name, self.variable.name, + self.specifier.get_value()) + else: + return '<%s.%s: %r>' % (self.group.name, self.variable.name, + Quantity(self.specifier.get_value(), + self.unit.dimensions)) + + +class ArrayVariable(Variable): + ''' + An object providing information about a model variable stored in an array + (for example, all state variables). + + TODO + + Parameters + ---------- + name : str + The name of the variable. + unit : `Unit` + The unit of the variable + value : `numpy.array` + A reference to the array storing the data for the variable. + group : `Group`, optional + The group to which this variable belongs, this is necessary to + interpret strings in the context of this group. + constant : bool, optional + Whether the variable's value is constant during a run. + Defaults to ``False``. + scalar : bool, optional + Whether this array is a 1-element array that should be treated like a + scalar (e.g. for a single delay value across synapses). Defaults to + ``False``. + is_bool: bool, optional + Whether this is a boolean variable (also implies it is dimensionless). + Defaults to ``False`` + ''' + def __init__(self, name, unit, value, group=None, constant=False, + scalar=False, is_bool=False): + + self.group = group + self.name = name + + Variable.__init__(self, unit, value, scalar=scalar, + constant=constant, is_bool=is_bool) + #: The reference to the array storing the data for the variable. + self.value = value + + group_name = '_'+group.name+'_' if group is not None else '_' + #: The name for the array used in generated code + self.arrayname = '_array' + group_name + name + + def get_value(self): + return self.value + + def set_value(self, value): + self.value[:] = value + + def get_addressable_value(self, level=0): + return VariableView(self, self.group, None, level) + + def get_addressable_value_with_unit(self, level=0): + return VariableView(self, self.group, self.unit, level) + + +class DynamicArrayVariable(ArrayVariable): + ''' + An object providing information about a model variable stored in a dynamic + array (used in synapses). + ''' + + def get_value(self): + # The actual numpy array is accesible via DynamicArray1D.data + return self.value.data + + +class Subexpression(Variable): + ''' + An object providing information about a static equation in a model + definition, used as a hint in optimising. Can test if a variable is used + via ``var in spec``. The specifier is also able to return the result of + the expression (used in a `StateMonitor`, for example). + + Parameters + ---------- + unit : `Unit` + The unit of the static equation + dtype : `numpy.dtype` + The dtype used for the expression. + expr : str + The expression defining the static equation. + variables : dict + The variables dictionary, containing variables for the + model variables used in the expression + namespace : dict + The namespace dictionary, containing identifiers for all the external + variables/functions used in the expression + is_bool: bool, optional + Whether this is a boolean variable (also implies it is dimensionless). + Defaults to ``False`` + ''' + def __init__(self, unit, dtype, expr, variables, namespace, + is_bool=False): + Variable.__init__(self, unit, value=None, dtype=dtype, + constant=False, scalar=False, is_bool=is_bool) + + #: The expression defining the static equation. + self.expr = expr.strip() + #: The identifiers used in the expression + self.identifiers = get_identifiers(expr) + #: Specifiers for the identifiers used in the expression + self.variables = variables + + #: The NeuronGroup's namespace for the identifiers used in the + #: expression + self.namespace = namespace + + #: An additional namespace provided by the run function (and updated + #: in `NeuronGroup.pre_run`) that is used if the NeuronGroup does not + #: have an explicitly defined namespace. + self.additional_namespace = None + + def get_value(self): + variable_values = {} + for identifier in self.identifiers: + if identifier in self.variables: + variable_values[identifier] = self.variables[identifier].get_value() + else: + variable_values[identifier] = self.namespace.resolve(identifier, + self.additional_namespace, + strip_units=True) + return eval(self.expr, variable_values) + + def __contains__(self, var): + return var in self.identifiers + + def __repr__(self): + description = ('<{classname}(unit={unit}, dtype={dtype}, ' + 'expr={expr}, variables=<...>, namespace=<....>)>') + return description.format(classname=self.__class__.__name__, + unit=repr(self.unit), + dtype=repr(self.dtype), + expr=repr(self.expr)) + + +class Index(Variable): + ''' + An object describing an index variable. + ''' + def __init__(self): + Variable.__init__(self, Unit(1), None, dtype=np.int32, + scalar=False, is_bool=False, constant=True) + + def get_value(self): + return self[:] diff --git a/brian2/equations/equations.py b/brian2/equations/equations.py index 565082f67..a40dd7939 100644 --- a/brian2/equations/equations.py +++ b/brian2/equations/equations.py @@ -699,7 +699,7 @@ def _sort_static_equations(self): elif eq.type == PARAMETER: eq.update_order = len(sorted_eqs) + 1 - def check_units(self, namespace, specifiers, additional_namespace=None): + def check_units(self, namespace, variables, additional_namespace=None): ''' Check all the units for consistency. @@ -708,8 +708,8 @@ def check_units(self, namespace, specifiers, additional_namespace=None): namespace : `CompoundNamespace` The namespace for resolving external identifiers, should be provided by the `NeuronGroup` or `Synapses`. - specifiers : dict of `Specifier` objects - The specifiers of the state variables and internal variables + variables : dict of `Variable` objects + The variables of the state variables and internal variables (e.g. t and dt) additional_namespace = (str, dict-like) A namespace tuple (name and dictionary), describing the additional @@ -724,7 +724,7 @@ def check_units(self, namespace, specifiers, additional_namespace=None): ''' external = frozenset().union(*[expr.identifiers for _, expr in self.eq_expressions]) - external -= set(specifiers.keys()) + external -= set(variables.keys()) resolved_namespace = namespace.resolve_all(external, additional_namespace, @@ -737,10 +737,10 @@ def check_units(self, namespace, specifiers, additional_namespace=None): if eq.type == DIFFERENTIAL_EQUATION: check_unit(str(eq.expr), self.units[var] / second, - resolved_namespace, specifiers) + resolved_namespace, variables) elif eq.type == STATIC_EQUATION: check_unit(str(eq.expr), self.units[var], - resolved_namespace, specifiers) + resolved_namespace, variables) else: raise AssertionError('Unknown equation type: "%s"' % eq.type) diff --git a/brian2/equations/unitcheck.py b/brian2/equations/unitcheck.py index 60c969b9d..15ee604d6 100644 --- a/brian2/equations/unitcheck.py +++ b/brian2/equations/unitcheck.py @@ -16,7 +16,7 @@ from brian2.codegen.translation import analyse_identifiers from brian2.parsing.expressions import parse_expression_unit from brian2.codegen.parsing import parse_statement -from brian2.core.specifiers import Variable +from brian2.core.variables import Variable __all__ = ['unit_from_string', 'unit_from_expression', 'check_unit', 'check_units_statements'] @@ -88,7 +88,7 @@ def unit_from_string(unit_string): return evaluated_unit -def check_unit(expression, unit, namespace, specifiers): +def check_unit(expression, unit, namespace, variables): ''' Evaluates the unit for an expression in a given namespace. @@ -98,7 +98,7 @@ def check_unit(expression, unit, namespace, specifiers): The expression to evaluate. namespace : dict-like The namespace of external variables. - specifiers : dict of `Specifier` objects + variables : dict of `Variable` objects The information about the internal variables Raises @@ -112,13 +112,13 @@ def check_unit(expression, unit, namespace, specifiers): -------- unit_from_expression ''' - expr_unit = parse_expression_unit(expression, namespace, specifiers) + expr_unit = parse_expression_unit(expression, namespace, variables) fail_for_dimension_mismatch(expr_unit, unit, ('Expression %s does not ' 'have the expected units' % expression)) -def check_units_statements(code, namespace, specifiers): +def check_units_statements(code, namespace, variables): ''' Check the units for a series of statements. Setting a model variable has to use the correct unit. For newly introduced temporary variables, the unit @@ -131,7 +131,7 @@ def check_units_statements(code, namespace, specifiers): The expression to evaluate. namespace : dict-like The namespace of external variables. - specifiers : dict of `Specifier` objects + variables : dict of `Variable` objects The information about the internal variables Raises @@ -141,7 +141,7 @@ def check_units_statements(code, namespace, specifiers): DimensionMismatchError If an unit mismatch occurs during the evaluation. ''' - known = set(specifiers.keys()) | set(namespace.keys()) + known = set(variables.keys()) | set(namespace.keys()) newly_defined, _, unknown = analyse_identifiers(code, known) if len(unknown): @@ -149,9 +149,9 @@ def check_units_statements(code, namespace, specifiers): 'not happen at this stage. Unkown identifiers: %s' % unknown)) - # We want to add newly defined variables to the specifiers dictionary so we + # We want to add newly defined variables to the variables dictionary so we # make a copy now - specs = dict(specifiers) + variables = dict(variables) code = re.split(r'[;\n]', code) for line in code: @@ -159,10 +159,10 @@ def check_units_statements(code, namespace, specifiers): if not len(line): continue # skip empty lines - var, op, expr = parse_statement(line) + varname, op, expr = parse_statement(line) if op in ('+=', '-=', '*=', '/=', '%='): # Replace statements such as "w *=2" by "w = w * 2" - expr = '{var} {op_first} {expr}'.format(var=var, + expr = '{var} {op_first} {expr}'.format(var=varname, op_first=op[0], expr=expr) op = '=' @@ -170,19 +170,19 @@ def check_units_statements(code, namespace, specifiers): pass else: raise AssertionError('Unknown operator "%s"' % op) - - expr_unit = parse_expression_unit(expr, namespace, specs) - if var in specifiers: - fail_for_dimension_mismatch(specifiers[var].unit, + expr_unit = parse_expression_unit(expr, namespace, variables) + + if varname in variables: + fail_for_dimension_mismatch(variables[varname].unit, expr_unit, ('Code statement "%s" does not use ' 'correct units' % line)) - elif var in newly_defined: + elif varname in newly_defined: # note the unit for later - specs[var] = Variable(var, expr_unit, is_bool=False, - scalar=False) + variables[varname] = Variable(expr_unit, is_bool=False, + scalar=False) else: - raise AssertionError(('Variable "%s" is neither in the specifiers ' + raise AssertionError(('Variable "%s" is neither in the variables ' 'dictionary nor in the list of undefined ' - 'variables.' % var)) \ No newline at end of file + 'variables.' % varname)) \ No newline at end of file diff --git a/brian2/groups/group.py b/brian2/groups/group.py index 16e5eea9b..3939fef38 100644 --- a/brian2/groups/group.py +++ b/brian2/groups/group.py @@ -8,7 +8,7 @@ import numpy as np from brian2.core.base import BrianObject -from brian2.core.specifiers import (ArrayVariable, Index, +from brian2.core.variables import (ArrayVariable, Index, StochasticVariable, AttributeVariable) from brian2.core.namespace import get_local_namespace from brian2.units.fundamentalunits import fail_for_dimension_mismatch, Unit @@ -28,7 +28,7 @@ class GroupItemMapping(Index): def __init__(self, N): self.N = N self._indices = np.arange(self.N) - self.specifiers = {'i': ArrayVariable('i', + self.variables = {'i': ArrayVariable('i', Unit(1), self._indices)} @@ -64,8 +64,8 @@ class Group(object): # (should make autocompletion work) ''' def __init__(self): - if not hasattr(self, 'specifiers'): - raise ValueError('Classes derived from Group need specifiers attribute.') + if not hasattr(self, 'variables'): + raise ValueError('Classes derived from Group need variables attribute.') if not hasattr(self, 'item_mapping'): try: N = len(self) @@ -83,7 +83,7 @@ def __init__(self): self._group_attribute_access_active = True - def _create_specifiers(self): + def _create_variables(self): return {'t': AttributeVariable(second, self.clock, 't_', constant=False), 'dt': AttributeVariable(second, self.clock, 'dt_', @@ -95,7 +95,7 @@ def state_(self, name): Gets the unitless array. ''' try: - return self.specifiers[name].get_addressable_value() + return self.variables[name].get_addressable_value() except KeyError: raise KeyError("Array named "+name+" not found.") @@ -104,8 +104,8 @@ def state(self, name): Gets the array with units. ''' try: - spec = self.specifiers[name] - return spec.get_addressable_value_with_unit() + var = self.variables[name] + return var.get_addressable_value_with_unit() except KeyError: raise KeyError("Array named "+name+" not found.") @@ -140,22 +140,22 @@ def __setattr__(self, name, val): # Group.__init__ if not hasattr(self, '_group_attribute_access_active'): object.__setattr__(self, name, val) - elif name in self.specifiers: - spec = self.specifiers[name] + elif name in self.variables: + var = self.variables[name] if not isinstance(val, basestring): - fail_for_dimension_mismatch(val, spec.unit, + fail_for_dimension_mismatch(val, var.unit, 'Incorrect units for setting %s' % name) # Make the call X.var = ... equivalent to X.var[:] = ... - spec.get_addressable_value_with_unit(level=1)[:] = val - elif len(name) and name[-1]=='_' and name[:-1] in self.specifiers: + var.get_addressable_value_with_unit(level=1)[:] = val + elif len(name) and name[-1]=='_' and name[:-1] in self.variables: # no unit checking - spec = self.specifiers[name[:-1]] + var = self.variables[name[:-1]] # Make the call X.var = ... equivalent to X.var[:] = ... - spec.get_addressable_value(level=1)[:] = val + var.get_addressable_value(level=1)[:] = val else: object.__setattr__(self, name, val) - def _set_with_code(self, specifier, group_indices, code, + def _set_with_code(self, variable, group_indices, code, check_units=True, level=0): ''' Sets a variable using a string expression. Is called by @@ -164,8 +164,8 @@ def _set_with_code(self, specifier, group_indices, code, Parameters ---------- - specifier : `ArrayVariable` - The `Specifier` for the variable to be set + variable : `ArrayVariable` + The `Variable` for the variable to be set group_indices : ndarray of int The indices of the elements that are to be set. code : str @@ -178,13 +178,13 @@ def _set_with_code(self, specifier, group_indices, code, Necessary so that both `X.var = ` and `X.var[:] = ` have access to the surrounding namespace. ''' - abstract_code = specifier.name + ' = ' + code + abstract_code = variable.name + ' = ' + code namespace = get_local_namespace(level + 1) additional_namespace = ('implicit-namespace', namespace) # TODO: Find a name that makes sense for reset and variable setting # with code - additional_specifiers = self.item_mapping.specifiers - additional_specifiers['_spikes'] = ArrayVariable('_spikes', + additional_variables = self.item_mapping.variables + additional_variables['_spikes'] = ArrayVariable('_spikes', Unit(1), group_indices.astype(np.int32), group=self) @@ -196,7 +196,7 @@ def _set_with_code(self, specifier, group_indices, code, self.indices, variable_indices=self.variable_indices, iterate_all=[], - additional_specifiers=additional_specifiers, + additional_variables=additional_variables, additional_namespace=additional_namespace, check_units=check_units, codeobj_class=self.codeobj_class) @@ -206,7 +206,7 @@ def _set_with_code(self, specifier, group_indices, code, def create_runner_codeobj(group, code, template_name, indices, variable_indices, iterate_all, - name=None, check_units=True, additional_specifiers=None, + name=None, check_units=True, additional_variables=None, additional_namespace=None, template_kwds=None, codeobj_class=None): @@ -225,7 +225,7 @@ def create_runner_codeobj(group, code, template_name, indices, A mapping from index name to `Index` objects, describing the indices used for the variables in the code. variable_indices : dict-like - A mapping from `Specifier` objects to index names (strings). + A mapping from `Variable` objects to index names (strings). iterate_all : list of str A list of index names for which the code should iterate over all indices. In numpy code, this allows to not use the indices and use @@ -236,9 +236,9 @@ def create_runner_codeobj(group, code, template_name, indices, none is given. check_units : bool, optional Whether to check units in the statement. Defaults to ``True``. - additional_specifiers : dict-like, optional - A mapping of names to `Specifier` objects, used in addition to the - specifiers saved in `group`. + 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`. @@ -250,12 +250,12 @@ def create_runner_codeobj(group, code, template_name, indices, logger.debug('Creating code object for abstract code:\n' + str(code)) if group is not None: - all_specifiers = dict(group.specifiers) + all_variables = dict(group.variables) else: - all_specifiers = {} - # If the GroupCodeRunner has specifiers, add them - if additional_specifiers is not None: - all_specifiers.update(additional_specifiers) + all_variables = {} + # If the GroupCodeRunner has variables, add them + if additional_variables is not None: + all_variables.update(additional_variables) template = get_codeobject_template(template_name, codeobj_class=codeobj_class) @@ -268,39 +268,38 @@ def create_runner_codeobj(group, code, template_name, indices, # not need to recursively descend into subexpressions. For unit # checking, we only need to know the units of the subexpressions, # not what variables they refer to - _, _, unknown = analyse_identifiers(code, all_specifiers) + _, _, unknown = analyse_identifiers(code, all_variables) resolved_namespace = group.namespace.resolve_all(unknown, additional_namespace, strip_units=False) - - check_units_statements(code, resolved_namespace, all_specifiers) + check_units_statements(code, resolved_namespace, all_variables) # Determine the identifiers that were used - _, used_known, unknown = analyse_identifiers(code, all_specifiers, + _, used_known, unknown = analyse_identifiers(code, all_variables, recursive=True) logger.debug('Unknown identifiers in the abstract code: ' + str(unknown)) resolved_namespace = group.namespace.resolve_all(unknown, additional_namespace) - # Only pass the specifiers that are actually used - specifiers = {} + # Only pass the variables that are actually used + variables = {} for var in used_known: - if not isinstance(all_specifiers[var], StochasticVariable): - specifiers[var] = all_specifiers[var] + if not isinstance(all_variables[var], StochasticVariable): + variables[var] = all_variables[var] - # Also add the specifiers that the template needs - for spec in template.specifiers: + # Also add the variables that the template needs + for var in template.variables: try: - specifiers[spec] = all_specifiers[spec] + variables[var] = all_variables[var] except KeyError as ex: - # We abuse template.specifiers here to also store names of things + # 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 - resolved_namespace[spec] = group.namespace.resolve(spec, - additional_namespace) + resolved_namespace[var] = group.namespace.resolve(var, + additional_namespace) else: raise ex @@ -313,7 +312,7 @@ def create_runner_codeobj(group, code, template_name, indices, return create_codeobject(name, code, resolved_namespace, - specifiers, + variables, template_name, indices=indices, variable_indices=variable_indices, @@ -394,15 +393,15 @@ def update_abstract_code(self): def _create_codeobj(self, additional_namespace=None): ''' A little helper function to reduce the amount of repetition when - calling the language's _create_codeobj (always pass self.specifiers and + calling the language's _create_codeobj (always pass self.variables and self.namespace + additional namespace). ''' - # If the GroupCodeRunner has specifiers, add them - if hasattr(self, 'specifiers'): - additional_specifiers = self.specifiers + # If the GroupCodeRunner has variables, add them + if hasattr(self, 'variables'): + additional_variables = self.variables else: - additional_specifiers = None + additional_variables = None return create_runner_codeobj(self.group, self.abstract_code, self.template, variable_indices=self.group.variable_indices, @@ -410,7 +409,7 @@ def _create_codeobj(self, additional_namespace=None): iterate_all=self.iterate_all, name=self.name, check_units=self.check_units, - additional_specifiers=additional_specifiers, + additional_variables=additional_variables, additional_namespace=additional_namespace, template_kwds=self.template_kwds, codeobj_class=self.group.codeobj_class) diff --git a/brian2/groups/neurongroup.py b/brian2/groups/neurongroup.py index c52887aad..0e284955b 100644 --- a/brian2/groups/neurongroup.py +++ b/brian2/groups/neurongroup.py @@ -13,7 +13,7 @@ from brian2.core.preferences import brian_prefs from brian2.core.base import BrianObject from brian2.core.namespace import create_namespace -from brian2.core.specifiers import (Variable, AttributeVariable, ArrayVariable, +from brian2.core.variables import (Variable, AttributeVariable, ArrayVariable, StochasticVariable, Subexpression) from brian2.core.spikesource import SpikeSource from brian2.core.scheduler import Scheduler @@ -46,13 +46,13 @@ def __init__(self, group, method): iterate_all=['_element']) self.method = StateUpdateMethod.determine_stateupdater(self.group.equations, - self.group.specifiers, + self.group.variables, method) def update_abstract_code(self): self.method = StateUpdateMethod.determine_stateupdater(self.group.equations, - self.group.specifiers, + self.group.variables, self.method_choice) # Update the not_refractory variable for the refractory period mechanism @@ -64,12 +64,12 @@ def update_abstract_code(self): self.abstract_code = 'not_refractory = 1*((t - lastspike) > %f)\n' % ref else: namespace = self.group.namespace - unit = parse_expression_unit(str(ref), namespace, self.group.specifiers) + unit = parse_expression_unit(str(ref), namespace, self.group.variables) if have_same_dimensions(unit, second): self.abstract_code = 'not_refractory = 1*((t - lastspike) > %s)\n' % ref elif have_same_dimensions(unit, Unit(1)): if not is_boolean_expression(str(ref), namespace, - self.group.specifiers): + self.group.variables): raise TypeError(('Refractory expression is dimensionless ' 'but not a boolean value. It needs to ' 'either evaluate to a timespan or to a ' @@ -85,7 +85,7 @@ def update_abstract_code(self): '"%s" has units %s instead') % (ref, unit)) self.abstract_code += self.method(self.group.equations, - self.group.specifiers) + self.group.variables) class Thresholder(GroupCodeRunner): @@ -99,8 +99,8 @@ def __init__(self, group): # and lastspike might also be used in the threshold condition -- the # names will then refer to single (constant) values and cannot be used # for assigning new values - template_kwds = {'_array_not_refractory': group.specifiers['not_refractory'].arrayname, - '_array_lastspike': group.specifiers['lastspike'].arrayname} + template_kwds = {'_array_not_refractory': group.variables['not_refractory'].arrayname, + '_array_lastspike': group.variables['lastspike'].arrayname} GroupCodeRunner.__init__(self, group, 'threshold', when=(group.clock, 'thresholds'), @@ -233,8 +233,8 @@ def __init__(self, N, model, method=None, # Setup the namespace self.namespace = create_namespace(namespace) - # Setup specifiers - self.specifiers = self._create_specifiers() + # Setup variables + self.variables = self._create_variables() # All of the following will be created in pre_run @@ -347,15 +347,15 @@ def runner(self, code, when=None, name=None): iterate_all=['_element']) return runner - def _create_specifiers(self): + def _create_variables(self): ''' - Create the specifiers dictionary for this `NeuronGroup`, containing + Create the variables dictionary for this `NeuronGroup`, containing entries for the equation variables and some standard entries. ''' - # Get the standard specifiers for all groups - s = Group._create_specifiers(self) + # Get the standard variables for all groups + s = Group._create_variables(self) - # Standard specifiers always present + # Standard variables always present s.update({'_num_elements': Variable(Unit(1), self.N, constant=True), '_spikes': AttributeVariable(Unit(1), self, 'spikes', constant=False)}) @@ -375,7 +375,7 @@ def _create_specifiers(self): s.update({eq.varname: Subexpression(eq.unit, brian_prefs['core.default_scalar_dtype'], str(eq.expr), - specifiers=s, + variables=s, namespace=self.namespace, is_bool=eq.is_bool)}) else: @@ -389,18 +389,18 @@ def _create_specifiers(self): def pre_run(self, namespace): - # Update the namespace information in the specifiers in case the + # Update the namespace information in the variables in case the # namespace was not specified explicitly defined at creation time # Note that values in the explicit namespace might still change # between runs, but the Subexpression stores a reference to # self.namespace so these changes are taken into account automatically if not self.namespace.is_explicit: - for spec in self.specifiers.itervalues(): - if isinstance(spec, Subexpression): - spec.additional_namespace = namespace + for var in self.variables.itervalues(): + if isinstance(var, Subexpression): + var.additional_namespace = namespace # Check units - self.equations.check_units(self.namespace, self.specifiers, + self.equations.check_units(self.namespace, self.variables, namespace) def _repr_html_(self): diff --git a/brian2/monitors/ratemonitor.py b/brian2/monitors/ratemonitor.py index b880303bf..93b731c2e 100644 --- a/brian2/monitors/ratemonitor.py +++ b/brian2/monitors/ratemonitor.py @@ -7,7 +7,7 @@ from brian2.core.base import BrianObject from brian2.core.preferences import brian_prefs from brian2.core.scheduler import Scheduler -from brian2.core.specifiers import Variable, AttributeVariable +from brian2.core.variables import Variable, AttributeVariable from brian2.memory.dynamicarray import DynamicArray1D from brian2.units.allunits import second, hertz from brian2.units.fundamentalunits import Unit, Quantity @@ -50,7 +50,7 @@ def __init__(self, source, when=None, name='ratemonitor*', # create data structures self.reinit() - self.specifiers = {'t': AttributeVariable(second, self.clock, 't'), + self.variables = {'t': AttributeVariable(second, self.clock, 't'), 'dt': AttributeVariable(second, self.clock, 'dt', constant=True), '_spikes': AttributeVariable(Unit(1), @@ -78,7 +78,7 @@ def pre_run(self, namespace): self.codeobj = create_codeobject(self.name, '', # No model-specific code {}, # no namespace - self.specifiers, + self.variables, template_name='ratemonitor', indices={}, variable_indices=defaultdict(lambda: '_element'), diff --git a/brian2/monitors/spikemonitor.py b/brian2/monitors/spikemonitor.py index 283c16f6c..bf7290f62 100644 --- a/brian2/monitors/spikemonitor.py +++ b/brian2/monitors/spikemonitor.py @@ -7,7 +7,7 @@ from brian2.core.base import BrianObject from brian2.core.preferences import brian_prefs from brian2.core.scheduler import Scheduler -from brian2.core.specifiers import ArrayVariable, AttributeVariable, Variable +from brian2.core.variables import ArrayVariable, AttributeVariable, Variable from brian2.memory.dynamicarray import DynamicArray1D from brian2.units.allunits import second from brian2.units.fundamentalunits import Unit @@ -53,7 +53,7 @@ def __init__(self, source, record=True, when=None, name='spikemonitor*', # create data structures self.reinit() - self.specifiers = {'t': AttributeVariable(second, self.clock, 't'), + self.variables = {'t': AttributeVariable(second, self.clock, 't'), '_spikes': AttributeVariable(Unit(1), self.source, 'spikes'), # The template needs to have access to the @@ -82,7 +82,7 @@ def pre_run(self, namespace): self.codeobj = create_codeobject(self.name, '', # No model-specific code {}, # no namespace - self.specifiers, + self.variables, template_name='spikemonitor', indices={}, variable_indices=defaultdict(lambda: '_element'), diff --git a/brian2/monitors/statemonitor.py b/brian2/monitors/statemonitor.py index 5689df9fb..043043a66 100644 --- a/brian2/monitors/statemonitor.py +++ b/brian2/monitors/statemonitor.py @@ -2,7 +2,7 @@ import numpy as np -from brian2.core.specifiers import Variable, AttributeVariable, ArrayVariable +from brian2.core.variables import Variable, AttributeVariable, ArrayVariable from brian2.core.base import BrianObject from brian2.core.scheduler import Scheduler from brian2.core.preferences import brian_prefs @@ -80,7 +80,8 @@ def __init__(self, source, variables, record=None, when=None, variables = source.equations.names elif isinstance(variables, str): variables = [variables] - self.variables = variables + #: The variables to record + self.record_variables = variables # record should always be an array of ints self.record_all = False @@ -98,33 +99,33 @@ def __init__(self, source, variables, record=None, when=None, # create data structures self.reinit() - # Setup specifiers - self.specifiers = {} - for variable in variables: - spec = source.specifiers[variable] - if spec.dtype != np.float64: + # Setup variables + self.variables = {} + for varname in variables: + var = source.variables[varname] + if var.dtype != np.float64: raise NotImplementedError(('Cannot record %s with data type ' '%s, currently only values stored as ' 'doubles can be recorded.') % - (variable, spec.dtype)) - self.specifiers[variable] = spec - self.specifiers['_recorded_'+variable] = Variable(Unit(1), - self._values[variable]) - - self.specifiers['_t'] = Variable(Unit(1), self._t) - self.specifiers['_clock_t'] = AttributeVariable(second, self.clock, 't_') - self.specifiers['_indices'] = ArrayVariable('_indices', Unit(1), - self.indices, - group=None, - constant=True) + (varname, var.dtype)) + self.variables[varname] = var + self.variables['_recorded_'+varname] = Variable(Unit(1), + self._values[varname]) + + self.variables['_t'] = Variable(Unit(1), self._t) + self.variables['_clock_t'] = AttributeVariable(second, self.clock, 't_') + self.variables['_indices'] = ArrayVariable('_indices', Unit(1), + self.indices, + group=None, + constant=True) self._group_attribute_access_active = True def reinit(self): self._values = dict((v, DynamicArray((0, len(self.indices)), use_numpy_resize=True, - dtype=self.source.specifiers[v].dtype)) - for v in self.variables) + dtype=self.source.variables[v].dtype)) + for v in self.record_variables) self._t = DynamicArray1D(0, use_numpy_resize=True, dtype=brian_prefs['core.default_scalar_dtype']) @@ -132,20 +133,22 @@ def pre_run(self, namespace): # Some dummy code so that code generation takes care of the indexing # and subexpressions code = ['_to_record_%s = %s' % (v, v) - for v in self.variables] + for v in self.record_variables] code += ['_recorded_%s = _recorded_%s' % (v, v) - for v in self.variables] + for v in self.record_variables] code = '\n'.join(code) self.codeobj = create_runner_codeobj(self.source, code, name=self.name, - additional_specifiers=self.specifiers, + additional_variables=self.variables, additional_namespace=namespace, template_name='statemonitor', indices=self.source.indices, variable_indices=self.source.variable_indices, iterate_all=[], - template_kwds={'_variable_names': self.variables}, + template_kwds={'_variable_names': + self.record_variables}, + #check_units=False, codeobj_class=self.codeobj_class) def update(self): @@ -168,14 +171,14 @@ def __getattr__(self, item): return Quantity(self._t.data.copy(), dim=second.dim) elif item == 't_': return self._t.data.copy() - elif item in self.variables: - unit = self.specifiers[item].unit + elif item in self.record_variables: + unit = self.variables[item].unit if have_same_dimensions(unit, 1): return self._values[item].data.copy() else: return Quantity(self._values[item].data.copy(), dim=unit.dim) - elif item.endswith('_') and item[:-1] in self.variables: + elif item.endswith('_') and item[:-1] in self.record_variables: return self._values[item[:-1]].data.copy() else: raise AttributeError('Unknown attribute %s' % item) @@ -183,5 +186,5 @@ def __getattr__(self, item): def __repr__(self): description = '<{classname}, recording {variables} from {source}>' return description.format(classname=self.__class__.__name__, - variables=repr(self.variables), + variables=repr(self.record_variables), source=self.source.name) diff --git a/brian2/parsing/expressions.py b/brian2/parsing/expressions.py index fde42fe79..b69ca3668 100644 --- a/brian2/parsing/expressions.py +++ b/brian2/parsing/expressions.py @@ -13,7 +13,7 @@ 'parse_expression_unit',] -def is_boolean_expression(expr, namespace, specifiers): +def is_boolean_expression(expr, namespace, variables): ''' Determines if an expression is of boolean type or not @@ -24,7 +24,7 @@ def is_boolean_expression(expr, namespace, specifiers): The expression to test namespace : dict-like The namespace of external variables. - specifiers : dict of `Specifier` objects + variables : dict of `Variable` objects The information about the internal variables Returns @@ -64,7 +64,7 @@ def is_boolean_expression(expr, namespace, specifiers): expr = mod.body if expr.__class__ is ast.BoolOp: - if all(is_boolean_expression(node, namespace, specifiers) + if all(is_boolean_expression(node, namespace, variables) for node in expr.values): return True else: @@ -74,16 +74,16 @@ def is_boolean_expression(expr, namespace, specifiers): if name in namespace: value = namespace[name] return value is True or value is False - elif name in specifiers: - return getattr(specifiers[name], 'is_bool', False) + elif name in variables: + return getattr(variables[name], 'is_bool', False) else: return name == 'True' or name == 'False' elif expr.__class__ is ast.Call: name = expr.func.id if name in namespace: return getattr(namespace[name], '_returns_bool', False) - elif name in specifiers: - return getattr(specifiers[name], '_returns_bool', False) + elif name in variables: + return getattr(variables[name], '_returns_bool', False) else: raise SyntaxError('Unknown function %s' % name) elif expr.__class__ is ast.Compare: @@ -94,7 +94,7 @@ def is_boolean_expression(expr, namespace, specifiers): return False -def _get_value_from_expression(expr, namespace, specifiers): +def _get_value_from_expression(expr, namespace, variables): ''' Returns the scalar value of an expression, and checks its validity. @@ -104,7 +104,7 @@ def _get_value_from_expression(expr, namespace, specifiers): The expression to check. namespace : dict-like The namespace of external variables. - specifiers : dict of `Specifier` objects + variables : dict of `Variable` objects The information about the internal variables Returns @@ -126,12 +126,12 @@ def _get_value_from_expression(expr, namespace, specifiers): if expr.__class__ is ast.Name: name = expr.id - if name in specifiers: - if not getattr(specifiers[name], 'constant', False): + if name in variables: + if not getattr(variables[name], 'constant', False): raise SyntaxError('Value %s is not constant' % name) - if not getattr(specifiers[name], 'scalar', False): + if not getattr(variables[name], 'scalar', False): raise SyntaxError('Value %s is not scalar' % name) - return specifiers[name].get_value() + return variables[name].get_value() elif name in namespace: if not have_same_dimensions(namespace[name], 1): raise SyntaxError('Variable %s is not dimensionless' % name) @@ -154,8 +154,8 @@ def _get_value_from_expression(expr, namespace, specifiers): raise SyntaxError('Cannot determine the numerical value for a function call.') elif expr.__class__ is ast.BinOp: op = expr.op.__class__.__name__ - left = _get_value_from_expression(expr.left, namespace, specifiers) - right = _get_value_from_expression(expr.right, namespace, specifiers) + left = _get_value_from_expression(expr.left, namespace, variables) + right = _get_value_from_expression(expr.right, namespace, variables) if op=='Add' or op=='Sub': v = left + right elif op=='Mult': @@ -172,7 +172,7 @@ def _get_value_from_expression(expr, namespace, specifiers): elif expr.__class__ is ast.UnaryOp: op = expr.op.__class__.__name__ # check validity of operand and get its unit - v = _get_value_from_expression(expr.operand, namespace, specifiers) + v = _get_value_from_expression(expr.operand, namespace, variables) if op=='Not': raise SyntaxError(('Cannot determine the numerical value ' 'for a boolean operation.')) @@ -184,7 +184,7 @@ def _get_value_from_expression(expr, namespace, specifiers): raise SyntaxError('Unsupported operation ' + str(expr.__class__)) -def parse_expression_unit(expr, namespace, specifiers): +def parse_expression_unit(expr, namespace, variables): ''' Returns the unit value of an expression, and checks its validity @@ -194,7 +194,7 @@ def parse_expression_unit(expr, namespace, specifiers): The expression to check. namespace : dict-like The namespace of external variables. - specifiers : dict of `Specifier` objects + variables : dict of `Variable` objects The information about the internal variables Returns @@ -221,8 +221,8 @@ def parse_expression_unit(expr, namespace, specifiers): expr = mod.body if expr.__class__ is ast.Name: name = expr.id - if name in specifiers: - return specifiers[name].unit + if name in variables: + return variables[name].unit elif name in namespace: return get_unit_fast(namespace[name]) elif name in ['True', 'False']: @@ -234,7 +234,7 @@ def parse_expression_unit(expr, namespace, specifiers): elif expr.__class__ is ast.BoolOp: # check that the units are valid in each subexpression for node in expr.values: - parse_expression_unit(node, namespace, specifiers) + parse_expression_unit(node, namespace, variables) # but the result is a bool, so we just return 1 as the unit return get_unit_fast(1) elif expr.__class__ is ast.Compare: @@ -242,7 +242,7 @@ def parse_expression_unit(expr, namespace, specifiers): subexprs = [expr.left]+expr.comparators subunits = [] for node in subexprs: - subunits.append(parse_expression_unit(node, namespace, specifiers)) + subunits.append(parse_expression_unit(node, namespace, variables)) for left, right in zip(subunits[:-1], subunits[1:]): if not have_same_dimensions(left, right): raise DimensionMismatchError("Comparison of expressions with different units", @@ -257,10 +257,10 @@ def parse_expression_unit(expr, namespace, specifiers): elif expr.kwargs is not None: raise ValueError("Keyword arguments not supported") - arg_units = [parse_expression_unit(arg, namespace, specifiers) + arg_units = [parse_expression_unit(arg, namespace, variables) for arg in expr.args] - func = namespace.get(expr.func.id, specifiers.get(expr.func, None)) + func = namespace.get(expr.func.id, variables.get(expr.func, None)) if func is None: raise SyntaxError('Unknown function %s' % expr.func.id) if not hasattr(func, '_arg_units') or not hasattr(func, '_return_unit'): @@ -285,8 +285,8 @@ def parse_expression_unit(expr, namespace, specifiers): elif expr.__class__ is ast.BinOp: op = expr.op.__class__.__name__ - left = parse_expression_unit(expr.left, namespace, specifiers) - right = parse_expression_unit(expr.right, namespace, specifiers) + left = parse_expression_unit(expr.left, namespace, variables) + right = parse_expression_unit(expr.right, namespace, variables) if op=='Add' or op=='Sub': u = left+right elif op=='Mult': @@ -296,7 +296,7 @@ def parse_expression_unit(expr, namespace, specifiers): elif op=='Pow': if have_same_dimensions(left, 1) and have_same_dimensions(right, 1): return get_unit_fast(1) - n = _get_value_from_expression(expr.right, namespace, specifiers) + n = _get_value_from_expression(expr.right, namespace, variables) u = left**n elif op=='Mod': u = left % right @@ -306,7 +306,7 @@ def parse_expression_unit(expr, namespace, specifiers): elif expr.__class__ is ast.UnaryOp: op = expr.op.__class__.__name__ # check validity of operand and get its unit - u = parse_expression_unit(expr.operand, namespace, specifiers) + u = parse_expression_unit(expr.operand, namespace, variables) if op=='Not': return get_unit_fast(1) else: diff --git a/brian2/stateupdaters/base.py b/brian2/stateupdaters/base.py index ef529160a..46542f4a2 100644 --- a/brian2/stateupdaters/base.py +++ b/brian2/stateupdaters/base.py @@ -19,7 +19,7 @@ class StateUpdateMethod(object): stateupdaters = [] @abstractmethod - def can_integrate(self, equations, specifiers): + def can_integrate(self, equations, variables): ''' Determine whether the state updater is a suitable choice. Should return ``False`` if it is not appropriate (e.g. non-linear equations for a @@ -29,8 +29,8 @@ def can_integrate(self, equations, specifiers): ---------- equations : `Equations` The model equations. - specifiers : dict - The `Specifier` objects for the model variables. + variables : dict + The `Variable` objects for the model variables. Returns ------- @@ -41,10 +41,10 @@ def can_integrate(self, equations, specifiers): pass @abstractmethod - def __call__(self, equations, specifiers=None): + def __call__(self, equations, variables=None): ''' Generate abstract code from equations. The method also gets the - the specifiers because some state updaters have to check whether + the variables because some state updaters have to check whether variable names reflect other state variables (which can change from timestep to timestep) or are external values (which stay constant during a run) For convenience, this arguments are optional -- this allows to @@ -55,8 +55,8 @@ def __call__(self, equations, specifiers=None): ---------- equations : `Equations` The model equations. - specifiers : dict, optional - The `Specifier` objects for the model variables. + variables : dict, optional + The `Variable` objects for the model variables. Returns ------- @@ -108,7 +108,7 @@ def register(name, stateupdater, index=None): StateUpdateMethod.stateupdaters.append((name, stateupdater)) @staticmethod - def determine_stateupdater(equations, specifiers, method=None): + def determine_stateupdater(equations, variables, method=None): ''' Determine a suitable state updater. If a `method` is given, the state updater with the given name is used. In case it is a callable, it @@ -122,8 +122,8 @@ def determine_stateupdater(equations, specifiers, method=None): ---------- equations : `Equations` The model equations. - specifiers : `dict` - The dictionary of `Specifier` objects, describing the internal + variables : `dict` + The dictionary of `Variable` objects, describing the internal model variables. method : {callable, str, ``None``}, optional A callable usable as a state updater, the name of a registered @@ -134,7 +134,7 @@ def determine_stateupdater(equations, specifiers, method=None): # can_integrate method, check this method and raise a warning if it # claims not to be applicable. try: - priority = method.can_integrate(equations, specifiers) + priority = method.can_integrate(equations, variables) if priority == 0: logger.warn(('The manually specified state updater ' 'claims that it does not support the given ' @@ -156,7 +156,7 @@ def determine_stateupdater(equations, specifiers, method=None): if stateupdater is None: raise ValueError('No state updater with the name "%s" ' 'is known' % method) - if not stateupdater.can_integrate(equations, specifiers): + if not stateupdater.can_integrate(equations, variables): raise ValueError(('The state updater "%s" cannot be used for ' 'the given equations' % method)) return stateupdater @@ -165,7 +165,7 @@ def determine_stateupdater(equations, specifiers, method=None): best_stateupdater = None for name, stateupdater in StateUpdateMethod.stateupdaters: try: - if stateupdater.can_integrate(equations, specifiers): + if stateupdater.can_integrate(equations, variables): best_stateupdater = (name, stateupdater) break except KeyError: diff --git a/brian2/stateupdaters/exact.py b/brian2/stateupdaters/exact.py index a9e55f9d8..83b46ba6f 100644 --- a/brian2/stateupdaters/exact.py +++ b/brian2/stateupdaters/exact.py @@ -7,7 +7,7 @@ from sympy import Wild, Symbol, Float import sympy as sp -from brian2.core.specifiers import Variable +from brian2.core.variables import Variable from brian2.codegen.parsing import sympy_to_str from brian2.utils.stringtools import get_identifiers from brian2.utils.logger import get_logger @@ -71,10 +71,10 @@ def get_linear_system(eqs): return (diff_eq_names, coefficients, constants) -def _non_constant_symbols(symbols, specifiers): +def _non_constant_symbols(symbols, variables): ''' Determine whether the given `sympy.Matrix` only refers to constant - variables. Note that variables that are not present in the `specifiers` + variables. Note that variables that are not present in the `variables` dictionary are considered to be external variables and therefore constant. Parameters @@ -82,8 +82,8 @@ def _non_constant_symbols(symbols, specifiers): symbols : set of `Symbol` The symbols to check, e.g. resulting from expression.atoms() - specifiers : dict - The dictionary of `Specifier` objects. + variables : dict + The dictionary of `Variable` objects. Returns ------- @@ -91,8 +91,8 @@ def _non_constant_symbols(symbols, specifiers): A set of non-constant symbols. ''' # As every symbol in the matrix should be either in the namespace or - # the specifiers dictionary, it should be sufficient to just check for - # the presence of any non-constant specifiers. + # the variables dictionary, it should be sufficient to just check for + # the presence of any non-constant variables. # Only check true symbols, not numbers symbols = set([str(symbol) for symbol in symbols @@ -101,7 +101,7 @@ def _non_constant_symbols(symbols, specifiers): non_constant = set() for symbol in symbols: - if symbol in specifiers and not getattr(specifiers[symbol], + if symbol in variables and not getattr(variables[symbol], 'constant', False): non_constant |= set([symbol]) @@ -114,14 +114,14 @@ class IndependentStateUpdater(StateUpdateMethod): i.e. 1-dimensional differential equations. The individual equations are solved by sympy. ''' - def can_integrate(self, equations, specifiers): + def can_integrate(self, equations, variables): if equations.is_stochastic: return False # Not very efficient but guaranteed to give the correct answer: # Just try to apply the integration method try: - self.__call__(equations, specifiers) + self.__call__(equations, variables) except (ValueError, NotImplementedError, TypeError) as ex: logger.debug('Cannot use independent integration: %s' % ex) return False @@ -129,9 +129,9 @@ def can_integrate(self, equations, specifiers): # It worked return True - def __call__(self, equations, specifiers=None): - if specifiers is None: - specifiers = {} + def __call__(self, equations, variables=None): + if variables is None: + variables = {} if equations.is_stochastic: raise ValueError('Cannot solve stochastic equations with this state updater') @@ -149,7 +149,7 @@ def __call__(self, equations, specifiers=None): for name, expression in diff_eqs: rhs = expression.sympy_expr non_constant = _non_constant_symbols(rhs.atoms(), - specifiers) - set([name]) + variables) - set([name]) if len(non_constant): raise ValueError(('Equation for %s referred to non-constant ' 'variables %s') % (name, str(non_constant))) @@ -196,14 +196,14 @@ class LinearStateUpdater(StateUpdateMethod): analytical solution given by sympy. Uses the matrix exponential (which is only implemented for diagonalizable matrices in sympy). ''' - def can_integrate(self, equations, specifiers): + def can_integrate(self, equations, variables): if equations.is_stochastic: return False # Not very efficient but guaranteed to give the correct answer: # Just try to apply the integration method try: - self.__call__(equations, specifiers) + self.__call__(equations, variables) except (ValueError, NotImplementedError, TypeError) as ex: logger.debug('Cannot use linear integration: %s' % ex) return False @@ -211,25 +211,25 @@ def can_integrate(self, equations, specifiers): # It worked return True - def __call__(self, equations, specifiers=None): + def __call__(self, equations, variables=None): - if specifiers is None: - specifiers = {} + if variables is None: + variables = {} # Get a representation of the ODE system in the form of # dX/dt = M*X + B - variables, matrix, constants = get_linear_system(equations) + varnames, matrix, constants = get_linear_system(equations) # Make sure that the matrix M is constant, i.e. it only contains - # external variables or constant specifiers - symbols = symbols = set.union(*(el.atoms() for el in matrix)) - non_constant = _non_constant_symbols(symbols, specifiers) + # external variables or constant variables + symbols = set.union(*(el.atoms() for el in matrix)) + non_constant = _non_constant_symbols(symbols, variables) if len(non_constant): raise ValueError(('The coefficient matrix for the equations ' 'contains the symbols %s, which are not ' 'constant.') % str(non_constant)) - symbols = [Symbol(variable, real=True) for variable in variables] + symbols = [Symbol(variable, real=True) for variable in varnames] solution = sp.solve_linear_system(matrix.row_join(constants), *symbols) b = sp.ImmutableMatrix([solution[symbol] for symbol in symbols]).transpose() @@ -237,7 +237,7 @@ def __call__(self, equations, specifiers=None): dt = Symbol('dt', real=True, positive=True) A = (matrix * dt).exp() C = sp.ImmutableMatrix([A.dot(b)]) - b - _S = sp.MatrixSymbol('_S', len(variables), 1) + _S = sp.MatrixSymbol('_S', len(varnames), 1) updates = A * _S + C.transpose() try: # In sympy 0.7.3, we have to explicitly convert it to a single matrix @@ -250,14 +250,16 @@ def __call__(self, equations, specifiers=None): # The solution contains _S[0, 0], _S[1, 0] etc. for the state variables, # replace them with the state variable names abstract_code = [] - for idx, (variable, update) in enumerate(zip(variables, updates)): + for idx, (variable, update) in enumerate(zip(varnames, updates)): rhs = update.subs(_S[idx, 0], variable) identifiers = get_identifiers(sympy_to_str(rhs)) for identifier in identifiers: - if identifier in specifiers: - spec = specifiers[identifier] - if isinstance(spec, Variable) and spec.scalar and spec.constant: - float_val = spec.get_value() + if identifier in variables: + var = variables[identifier] + if var is None: + print identifier, variables + if var.scalar and var.constant: + float_val = var.get_value() rhs = rhs.xreplace({Symbol(identifier, real=True): Float(float_val)}) # Do not overwrite the real state variables yet, the update step @@ -265,7 +267,7 @@ def __call__(self, equations, specifiers=None): abstract_code.append('_' + variable + ' = ' + sympy_to_str(rhs)) # Update the state variables - for variable in variables: + for variable in varnames: abstract_code.append('{variable} = _{variable}'.format(variable=variable)) return '\n'.join(abstract_code) diff --git a/brian2/stateupdaters/explicit.py b/brian2/stateupdaters/explicit.py index 51fe50278..f4a4e1d40 100644 --- a/brian2/stateupdaters/explicit.py +++ b/brian2/stateupdaters/explicit.py @@ -247,7 +247,7 @@ def __init__(self, description, stochastic=None): raise AssertionError('Unknown element name: %s' % element.getName()) - def can_integrate(self, equations, specifiers): + def can_integrate(self, equations, variables): # Non-stochastic numerical integrators should work for all equations, # except for stochastic equations if equations.is_stochastic and self.stochastic is None: @@ -435,7 +435,7 @@ def replace_func(x, t, expr, temp_vars): return RHS - def __call__(self, eqs, specifiers=None): + def __call__(self, eqs, variables=None): ''' Apply a state updater description to model equations. @@ -445,8 +445,8 @@ def __call__(self, eqs, specifiers=None): The equations describing the model - specifiers: dict-like, optional - The `Specifier` objects for the model. Ignored by the explicit + variables: dict-like, optional + The `Variable` objects for the model. Ignored by the explicit state updater. Examples diff --git a/brian2/stateupdaters/exponential_euler.py b/brian2/stateupdaters/exponential_euler.py index fad537748..7483c878b 100644 --- a/brian2/stateupdaters/exponential_euler.py +++ b/brian2/stateupdaters/exponential_euler.py @@ -76,7 +76,7 @@ class ExponentialEulerStateUpdater(StateUpdateMethod): category, it is therefore the default integration method used in the GENESIS simulator, for example. ''' - def can_integrate(self, equations, specifiers): + def can_integrate(self, equations, variables): ''' Return whether the given equations can be integrated using this state updater. This method tests for conditional linearity by @@ -94,7 +94,7 @@ def can_integrate(self, equations, specifiers): return True - def __call__(self, equations, specifiers=None): + def __call__(self, equations, variables=None): system = get_conditionally_linear_system(equations) code = [] diff --git a/brian2/synapses/synapses.py b/brian2/synapses/synapses.py index 7276a1b33..a2edb8971 100644 --- a/brian2/synapses/synapses.py +++ b/brian2/synapses/synapses.py @@ -10,9 +10,9 @@ from brian2.core.base import BrianObject from brian2.core.namespace import create_namespace from brian2.core.preferences import brian_prefs -from brian2.core.specifiers import (ArrayVariable, Index, DynamicArrayVariable, +from brian2.core.variables import (ArrayVariable, Index, DynamicArrayVariable, Variable, Subexpression, AttributeVariable, - StochasticVariable, Specifier) + StochasticVariable, Variable) from brian2.equations.equations import (Equations, SingleEquation, DIFFERENTIAL_EQUATION, STATIC_EQUATION, PARAMETER) @@ -50,17 +50,17 @@ def __init__(self, group, method): check_units=False) self.method = StateUpdateMethod.determine_stateupdater(self.group.equations, - self.group.specifiers, + self.group.variables, method) def update_abstract_code(self): self.method = StateUpdateMethod.determine_stateupdater(self.group.equations, - self.group.specifiers, + self.group.variables, self.method_choice) self.abstract_code = self.method(self.group.equations, - self.group.specifiers) + self.group.variables) class LumpedUpdater(GroupCodeRunner): @@ -80,7 +80,7 @@ def __init__(self, varname, synapses, target): {varname}_post = {varname}_post '''.format(varname=varname) - template_kwds = {'_target_var_array': synapses.specifiers[varname+'_post'].arrayname} + template_kwds = {'_target_var_array': synapses.variables[varname+'_post'].arrayname} GroupCodeRunner.__init__(self, group=synapses, template='lumped_variable', @@ -134,7 +134,7 @@ def __init__(self, synapses, code, prepost, objname=None): synapses.item_mapping.register_variable(self._delays) self.queue = SpikeQueue() self.spiking_synapses = [] - self.specifiers = {'_spiking_synapses': AttributeVariable(Unit(1), + self.variables = {'_spiking_synapses': AttributeVariable(Unit(1), self, 'spiking_synapses', constant=False), @@ -171,7 +171,7 @@ def __init__(self, synapses, code, prepost, objname=None): def update_abstract_code(self): if self.synapses.event_driven is not None: event_driven_update = independent(self.synapses.event_driven, - self.group.specifiers) + self.group.variables) # TODO: Any way to do this more elegantly? event_driven_update = re.sub(r'\bdt\b', '(t - lastupdate)', event_driven_update) @@ -326,7 +326,7 @@ def __init__(self, synapses): self.j = IndexView(self, self.synaptic_post) self.k = SynapseIndexView(self) - self.specifiers = {'i': DynamicArrayVariable('i', + self.variables = {'i': DynamicArrayVariable('i', Unit(1), self.synaptic_pre), 'j': DynamicArrayVariable('j', @@ -411,7 +411,7 @@ def _add_synapses(self, sources, targets, n, p, condition=None, abstract_code += '_p = ' + str(p) namespace = get_local_namespace(level + 1) additional_namespace = ('implicit-namespace', namespace) - specifiers = { + variables = { '_source_neurons': ArrayVariable('_source_neurons', Unit(1), self.source.item_mapping[:], constant=True), @@ -429,16 +429,17 @@ def _add_synapses(self, sources, targets, n, p, condition=None, self.pre_synaptic, constant=True), '_post_synaptic': Variable(Unit(1), self.post_synaptic, constant=True), - # Will be set in the template - 'i': Specifier(), - 'j': Specifier() + # Will be set in the template (they are set to constant to avoid + # the get_value() function call at every time step) + 'i': Variable(unit=Unit(1), constant=True), + 'j': Variable(unit=Unit(1), constant=True) } codeobj = create_runner_codeobj(self.synapses, abstract_code, 'synapses_create', indices={}, iterate_all=[], - additional_specifiers=specifiers, + additional_variables=variables, additional_namespace=additional_namespace, variable_indices=defaultdict(lambda: '_element'), check_units=False, @@ -496,11 +497,11 @@ def __getitem__(self, index): elif isinstance(index, basestring): # interpret the string expression identifiers = get_identifiers(index) - specifiers = dict(self.specifiers) + variables = dict(self.variables) if 'k' in identifiers: synapse_numbers = _synapse_numbers(self.synaptic_pre[:], self.synaptic_post[:]) - specifiers['k'] = ArrayVariable('k', Unit(1), + variables['k'] = ArrayVariable('k', Unit(1), synapse_numbers) # Get the locals and globals from the stack frame frame = inspect.stack()[2][0] @@ -515,7 +516,7 @@ def __getitem__(self, index): indices=self.synapses.indices, variable_indices=defaultdict(lambda: '_element'), iterate_all=['_element'], - additional_specifiers=specifiers, + additional_variables=variables, additional_namespace=additional_namespace, check_units=False, codeobj_class=self.synapses.codeobj_class, @@ -658,8 +659,8 @@ def __init__(self, source, target=None, model=None, pre=None, post=None, self.j = self.item_mapping.j self.k = self.item_mapping.k - # Setup specifiers - self.specifiers = self._create_specifiers() + # Setup variables + self.variables = self._create_variables() #: List of names of all updaters, e.g. ['pre', 'post'] self._updaters = [] @@ -681,7 +682,7 @@ def __init__(self, source, target=None, model=None, pre=None, post=None, # direct access to its delay via a delay attribute (instead of having # to use pre.delay) if 'pre' in self._updaters: - self.specifiers['delay'] = self.pre.specifiers['delay'] + self.variables['delay'] = self.pre.variables['delay'] if delay is not None: if isinstance(delay, Quantity): @@ -715,11 +716,11 @@ def __init__(self, source, target=None, model=None, pre=None, post=None, # For simplicity, store the delay as a one-element array # so that for example updater._delays[:] works. updater._delays = np.array([float(pathway_delay)]) - specifier = ArrayVariable('delay', second, updater._delays, + variable = ArrayVariable('delay', second, updater._delays, group=self, scalar=True) - updater.specifiers['delay'] = specifier + updater.variables['delay'] = variable if pathway == 'pre': - self.specifiers['delay'] = specifier + self.variables['delay'] = variable #: Performs numerical integration step self.state_updater = StateUpdater(self, method) @@ -733,12 +734,12 @@ def __init__(self, source, target=None, model=None, pre=None, post=None, varname = single_equation.varname # For a lumped variable, we need an equivalent parameter in the # target group - if not varname in self.target.specifiers: + if not varname in self.target.variables: raise ValueError(('The lumped variable %s needs a variable ' 'of the same name in the target ' 'group ') % single_equation.varname) - fail_for_dimension_mismatch(self.specifiers[varname].unit, - self.target.specifiers[varname], + fail_for_dimension_mismatch(self.variables[varname].unit, + self.target.variables[varname], ('Lumped variables need to have ' 'the same units in Synapses ' 'and the target group')) @@ -816,30 +817,30 @@ def _add_updater(self, code, prepost, objname=None): self.contained_objects.append(updater) return objname - def _create_specifiers(self): + def _create_variables(self): ''' - Create the specifiers dictionary for this `NeuronGroup`, containing + Create the variables dictionary for this `NeuronGroup`, containing entries for the equation variables and some standard entries. ''' - # Add all the pre and post specifiers with _pre and _post suffixes - s = {} + # Add all the pre and post variables with _pre and _post suffixes + v = {} self.variable_indices = defaultdict(lambda: '_element') - for name, spec in getattr(self.source, 'specifiers', {}).iteritems(): - if isinstance(spec, (ArrayVariable, Subexpression)): - s[name + '_pre'] = spec - self.variable_indices[spec] = '_presynaptic' - for name, spec in getattr(self.target, 'specifiers', {}).iteritems(): - if isinstance(spec, (ArrayVariable, Subexpression)): - s[name + '_post'] = spec - self.variable_indices[spec] = '_postsynaptic' - # Also add all the post specifiers without a suffix -- if this + for name, var in getattr(self.source, 'variables', {}).iteritems(): + if isinstance(var, (ArrayVariable, Subexpression)): + v[name + '_pre'] = var + self.variable_indices[var] = '_presynaptic' + for name, var in getattr(self.target, 'variables', {}).iteritems(): + if isinstance(var, (ArrayVariable, Subexpression)): + v[name + '_post'] = var + self.variable_indices[var] = '_postsynaptic' + # Also add all the post variables without a suffix -- if this # clashes with the name of a state variable defined in this # Synapses group, the latter will overwrite the entry later and # take precedence - s[name] = spec + v[name] = var - # Standard specifiers always present - s.update({'t': AttributeVariable(second, self.clock, 't_', + # Standard variables always present + v.update({'t': AttributeVariable(second, self.clock, 't_', constant=False), 'dt': AttributeVariable(second, self.clock, 'dt_', constant=True), @@ -862,7 +863,8 @@ def _create_specifiers(self): self.item_mapping.post_synaptic)}) for eq in itertools.chain(self.equations.itervalues(), - self.event_driven.itervalues() if self.event_driven is not None else []): + self.event_driven.itervalues() + if self.event_driven is not None else []): if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER): array = self.arrays[eq.varname] constant = ('constant' in eq.flags) @@ -870,7 +872,7 @@ def _create_specifiers(self): # shouldn't directly access the specifier.array attribute but # use specifier.get_value() to get a reference to the underlying # array - s.update({eq.varname: DynamicArrayVariable(eq.varname, + v.update({eq.varname: DynamicArrayVariable(eq.varname, eq.unit, array, group=self, @@ -880,10 +882,10 @@ def _create_specifiers(self): # automatically resized self.item_mapping.register_variable(array) elif eq.type == STATIC_EQUATION: - s.update({eq.varname: Subexpression(eq.unit, + v.update({eq.varname: Subexpression(eq.unit, brian_prefs['core.default_scalar_dtype'], str(eq.expr), - specifiers=s, + variables=v, namespace=self.namespace, is_bool=eq.is_bool)}) else: @@ -891,9 +893,9 @@ def _create_specifiers(self): # Stochastic variables for xi in self.equations.stochastic_variables: - s.update({xi: StochasticVariable()}) + v.update({xi: StochasticVariable()}) - return s + return v def _allocate_memory(self, dtype=None): # Allocate memory (TODO: this should be refactored somewhere at some point) diff --git a/brian2/tests/test_codegen.py b/brian2/tests/test_codegen.py index 54db14769..b76c596cf 100644 --- a/brian2/tests/test_codegen.py +++ b/brian2/tests/test_codegen.py @@ -1,7 +1,7 @@ import numpy as np from brian2.codegen.translation import analyse_identifiers, get_identifiers_recursively -from brian2.core.specifiers import Subexpression, Specifier +from brian2.core.variables import Subexpression, Variable from brian2.units.fundamentalunits import Unit @@ -26,13 +26,13 @@ def test_get_identifiers_recursively(): ''' Test finding identifiers including subexpressions. ''' - specifiers = {} - specifiers['sub1'] = Subexpression(Unit(1), np.float32, 'sub2 * z', - specifiers, {}) - specifiers['sub2'] = Subexpression(Unit(1), np.float32, '5 + y', - specifiers, {}) - specifiers['x'] = Specifier() - identifiers = get_identifiers_recursively('_x = sub1 + x', specifiers) + variables = {} + variables['sub1'] = Subexpression(Unit(1), np.float32, 'sub2 * z', + variables, {}) + variables['sub2'] = Subexpression(Unit(1), np.float32, '5 + y', + variables, {}) + variables['x'] = Variable(unit=None) + identifiers = get_identifiers_recursively('_x = sub1 + x', variables) assert identifiers == set(['x', '_x', 'y', 'z', 'sub1', 'sub2']) if __name__ == '__main__': diff --git a/brian2/tests/test_equations.py b/brian2/tests/test_equations.py index a89c80c93..9bf0b94f8 100644 --- a/brian2/tests/test_equations.py +++ b/brian2/tests/test_equations.py @@ -249,7 +249,7 @@ def test_construction_errors(): x = 3 * w : 1''')) def test_unit_checking(): - # dummy Specifier class + # dummy Variable class class S(object): def __init__(self, unit): self.unit = unit diff --git a/brian2/tests/test_namespaces.py b/brian2/tests/test_namespaces.py index 3e338a96c..c92d55c7b 100644 --- a/brian2/tests/test_namespaces.py +++ b/brian2/tests/test_namespaces.py @@ -24,7 +24,7 @@ def test_default_content(): assert namespace['ms'] == ms assert namespace['Hz'] == Hz assert namespace['mV'] == mV - # Functions (the namespace contains specifiers) + # Functions (the namespace contains variables) assert namespace['sin'].pyfunc == sin assert namespace['log'].pyfunc == log assert namespace['exp'].pyfunc == exp diff --git a/brian2/tests/test_neurongroup.py b/brian2/tests/test_neurongroup.py index 28436ab7c..0379462f3 100644 --- a/brian2/tests/test_neurongroup.py +++ b/brian2/tests/test_neurongroup.py @@ -43,15 +43,15 @@ def test_creation(): assert_raises(TypeError, lambda: NeuronGroup(1, object())) -def test_specifiers(): +def test_variables(): ''' - Test the correct creation of the specifiers dictionary. + Test the correct creation of the variables dictionary. ''' G = NeuronGroup(1, 'dv/dt = -v/(10*ms) : 1') - assert 'v' in G.specifiers and 't' in G.specifiers and 'dt' in G.specifiers + assert 'v' in G.variables and 't' in G.variables and 'dt' in G.variables G = NeuronGroup(1, 'dv/dt = -v/tau + xi*tau**-0.5: 1') - assert not 'tau' in G.specifiers and 'xi' in G.specifiers + assert not 'tau' in G.variables and 'xi' in G.variables def test_stochastic_variable(): @@ -239,7 +239,7 @@ def test_state_variables(): if __name__ == '__main__': test_creation() - test_specifiers() + test_variables() test_stochastic_variable() test_unit_errors() test_threshold_reset() diff --git a/brian2/tests/test_parsing.py b/brian2/tests/test_parsing.py index 7cfde3fe2..74dfc58fc 100644 --- a/brian2/tests/test_parsing.py +++ b/brian2/tests/test_parsing.py @@ -170,8 +170,8 @@ def test_abstract_code_dependencies(): def test_is_boolean_expression(): - # dummy "specifier" class - Spec = namedtuple("Spec", ['is_bool']) + # dummy "Variable" class + Var = namedtuple("Var", ['is_bool']) # dummy function object class Func(object): @@ -185,12 +185,12 @@ def __init__(self, returns_bool=False): f = Func(returns_bool=True) g = Func(returns_bool=False) - # specifier - s1 = Spec(is_bool=True) - s2 = Spec(is_bool=False) + # variables + s1 = Var(is_bool=True) + s2 = Var(is_bool=False) namespace = {'a': a, 'b': b, 'c': c, 'f': f, 'g': g} - specifiers = {'s1': s1, 's2': s2} + variables = {'s1': s1, 's2': s2} EVF = [ (True, 'a or b'), @@ -208,20 +208,20 @@ def __init__(self, returns_bool=False): (True, 'f(c) or a automatic choice no longer works StateUpdateMethod.stateupdaters = {} - assert_raises(ValueError, lambda: determine_stateupdater(eqs, specifiers)) + assert_raises(ValueError, lambda: determine_stateupdater(eqs, variables)) # reset to state before the test StateUpdateMethod.stateupdaters = before diff --git a/docs_sphinx/developer/codegen.txt b/docs_sphinx/developer/codegen.txt index 54974503c..4d24539a7 100644 --- a/docs_sphinx/developer/codegen.txt +++ b/docs_sphinx/developer/codegen.txt @@ -13,7 +13,7 @@ a single `NeuronGroup` object: - Allocate memory for the state variables. - Create a namespace object. - Create `Thresholder`, `Resetter` and `StateUpdater` objects. - - Collect `Specifier` objects from the group and code template. + - Collect `Variable` objects from the group and code template. - Resolve the namespace, i.e. for hierarchical namespace choose just one value for each variable name. - Create a `CodeObject`. diff --git a/docs_sphinx/developer/equations_namespaces.rst b/docs_sphinx/developer/equations_namespaces.rst index c9e7abde6..b5759a7f2 100644 --- a/docs_sphinx/developer/equations_namespaces.rst +++ b/docs_sphinx/developer/equations_namespaces.rst @@ -14,7 +14,7 @@ Specifiers Each Brian object that saves state variables (e.g. `NeuronGroup`, `Synapses`, `StateMonitor`) has a ``specifiers`` attribute, a dictionary mapping variable -names to `Specifier` objects. `Specifier` objects contain information *about* +names to `Variable` objects. `Variable` objects contain information *about* the variable (name, dtype, units) as well as access to the variable's value via a ``get_value`` method. Some will also allow setting the values via a corresponding ``set_value`` method. These objects can therefore act as proxies diff --git a/docs_sphinx/developer/overview.rst b/docs_sphinx/developer/overview.rst index 42ee4b6ef..e138b6604 100644 --- a/docs_sphinx/developer/overview.rst +++ b/docs_sphinx/developer/overview.rst @@ -64,7 +64,7 @@ explicitly, the namespace used for running code will be filled at the time of a run with either the namespace provided to the run function or the locals/globals surrounding the run call. *Specifiers* on the other hand, define everything *internal* to the model, the objects in this dictionary -inherit from `Specifier` and -- in addition to specifying things like the units +inherit from `Variable` and -- in addition to specifying things like the units -- act as proxy objects, connecting for example state variable names to the numpy arrays where the values are actually stored. @@ -72,11 +72,11 @@ This indirection will be useful when dealing with memory on devices. The specifiers also offer an explicit and simple way to implement linked variables or the access to pre- and postsynaptic variables in `Synapses`: To link the symbol ``v_post`` to the postsynaptic membrane potentials, the specifier -dictionary just has to contain a reference to the respective `Specifier` object +dictionary just has to contain a reference to the respective `Variable` object of the target group under the key ``v_post``. Another parent class of `NeuronGroup` is `Group`, which also relies on the -`Specifier` objects and exposes access to the state variables as attributes. +`Variable` objects and exposes access to the state variables as attributes. This is also used in classes such as `StateMonitor`. State Updaters