From 215fcb822fe5e1c8c6893c846c91df5f7b810334 Mon Sep 17 00:00:00 2001 From: thesamovar Date: Sat, 14 Sep 2013 16:33:34 -0400 Subject: [PATCH 1/9] Added CodeObject.owner and owner name to template keywords --- brian2/codegen/codeobject.py | 15 +++++++++++---- brian2/codegen/runtime/numpy_rt/numpy_rt.py | 4 ++-- brian2/codegen/runtime/weave_rt/weave_rt.py | 4 ++-- brian2/devices/cpp_standalone/device.py | 4 ++-- brian2/devices/device.py | 4 ++-- brian2/groups/group.py | 1 + brian2/monitors/ratemonitor.py | 1 + brian2/monitors/spikemonitor.py | 4 +++- 8 files changed, 24 insertions(+), 13 deletions(-) diff --git a/brian2/codegen/codeobject.py b/brian2/codegen/codeobject.py index d3ce2d12a..b5685f535 100644 --- a/brian2/codegen/codeobject.py +++ b/brian2/codegen/codeobject.py @@ -32,7 +32,7 @@ def prepare_namespace(namespace, variables): return namespace -def create_codeobject(name, abstract_code, namespace, variables, template_name, +def create_codeobject(owner, name, abstract_code, namespace, variables, template_name, indices, variable_indices, codeobj_class, template_kwds=None): ''' @@ -83,10 +83,12 @@ def create_codeobject(name, abstract_code, namespace, variables, template_name, variables.update(indices) - code = template(snippet, variables=variables, codeobj_name=name, namespace=namespace, **template_kwds) + code = template(snippet, + owner=owner, variables=variables, codeobj_name=name, namespace=namespace, + **template_kwds) logger.debug(name + " code:\n" + str(code)) - codeobj = codeobj_class(code, namespace, variables, name=name) + codeobj = codeobj_class(owner, code, namespace, variables, name=name) codeobj.compile() return codeobj @@ -108,8 +110,13 @@ class CodeObject(Nameable): #: The `Language` used by this `CodeObject` language = None - def __init__(self, code, namespace, variables, name='codeobject*'): + def __init__(self, owner, code, namespace, variables, name='codeobject*'): Nameable.__init__(self, name=name) + try: + owner = weakref.proxy(owner) + except TypeError: + pass # if owner was already a weakproxy then this will be the error raised + self.owner = owner self.code = code self.compile_methods = self.get_compile_methods(variables) self.namespace = namespace diff --git a/brian2/codegen/runtime/numpy_rt/numpy_rt.py b/brian2/codegen/runtime/numpy_rt/numpy_rt.py index e3319e7c1..afee5078b 100644 --- a/brian2/codegen/runtime/numpy_rt/numpy_rt.py +++ b/brian2/codegen/runtime/numpy_rt/numpy_rt.py @@ -21,10 +21,10 @@ class NumpyCodeObject(CodeObject): 'templates')) language = NumpyLanguage() - def __init__(self, code, namespace, variables, name='numpy_code_object*'): + def __init__(self, owner, code, namespace, variables, name='numpy_code_object*'): # TODO: This should maybe go somewhere else namespace['logical_not'] = np.logical_not - CodeObject.__init__(self, code, namespace, variables, name=name) + CodeObject.__init__(self, owner, code, namespace, variables, name=name) def variables_to_namespace(self): # Variables can refer to values that are either constant (e.g. dt) diff --git a/brian2/codegen/runtime/weave_rt/weave_rt.py b/brian2/codegen/runtime/weave_rt/weave_rt.py index 43244167f..fd90c8ee9 100644 --- a/brian2/codegen/runtime/weave_rt/weave_rt.py +++ b/brian2/codegen/runtime/weave_rt/weave_rt.py @@ -68,8 +68,8 @@ class WeaveCodeObject(CodeObject): 'templates')) language = CPPLanguage(c_data_type=weave_data_type) - def __init__(self, code, namespace, variables, name='weave_code_object*'): - super(WeaveCodeObject, self).__init__(code, namespace, variables, name=name) + def __init__(self, owner, code, namespace, variables, name='weave_code_object*'): + super(WeaveCodeObject, self).__init__(owner, code, namespace, variables, name=name) self.compiler = brian_prefs['codegen.runtime.weave.compiler'] self.extra_compile_args = brian_prefs['codegen.runtime.weave.extra_compile_args'] diff --git a/brian2/devices/cpp_standalone/device.py b/brian2/devices/cpp_standalone/device.py index 57f6c5358..b0fe7d4f2 100644 --- a/brian2/devices/cpp_standalone/device.py +++ b/brian2/devices/cpp_standalone/device.py @@ -56,10 +56,10 @@ def code_object_class(self, codeobj_class=None): raise ValueError("Cannot specify codeobj_class for C++ standalone device.") return CPPStandaloneCodeObject - def code_object(self, name, abstract_code, namespace, variables, template_name, + def code_object(self, owner, name, abstract_code, namespace, variables, template_name, indices, variable_indices, codeobj_class=None, template_kwds=None): - codeobj = super(CPPStandaloneDevice, self).code_object(name, abstract_code, namespace, variables, + codeobj = super(CPPStandaloneDevice, self).code_object(owner, name, abstract_code, namespace, variables, template_name, indices, variable_indices, codeobj_class=codeobj_class, template_kwds=template_kwds, diff --git a/brian2/devices/device.py b/brian2/devices/device.py index 03b592c11..5ba109682 100644 --- a/brian2/devices/device.py +++ b/brian2/devices/device.py @@ -47,11 +47,11 @@ def code_object_class(self, codeobj_class=None): codeobj_class = get_default_codeobject_class() return codeobj_class - def code_object(self, name, abstract_code, namespace, variables, template_name, + def code_object(self, owner, name, abstract_code, namespace, variables, template_name, indices, variable_indices, codeobj_class=None, template_kwds=None): codeobj_class = self.code_object_class(codeobj_class) - return create_codeobject(name, abstract_code, namespace, variables, template_name, + return create_codeobject(owner, name, abstract_code, namespace, variables, template_name, indices, variable_indices, codeobj_class=codeobj_class, template_kwds=template_kwds) diff --git a/brian2/groups/group.py b/brian2/groups/group.py index 63179cdc8..9cc66bb70 100644 --- a/brian2/groups/group.py +++ b/brian2/groups/group.py @@ -384,6 +384,7 @@ def create_runner_codeobj(group, code, template_name, indices=None, variable_indices = group.variable_indices return get_device().code_object( + group, name, code, resolved_namespace, diff --git a/brian2/monitors/ratemonitor.py b/brian2/monitors/ratemonitor.py index 11c331cab..ee3d4fd4e 100644 --- a/brian2/monitors/ratemonitor.py +++ b/brian2/monitors/ratemonitor.py @@ -75,6 +75,7 @@ def reinit(self): def pre_run(self, namespace): self.codeobj = get_device().code_object( + self, self.name+'_codeobject*', '', # No model-specific code {}, # no namespace diff --git a/brian2/monitors/spikemonitor.py b/brian2/monitors/spikemonitor.py index 2ce9b1bae..c3bb3ae70 100644 --- a/brian2/monitors/spikemonitor.py +++ b/brian2/monitors/spikemonitor.py @@ -79,7 +79,9 @@ def reinit(self): self.count = get_device().array(self, '_count', len(self.source), 1, dtype=np.int32) def pre_run(self, namespace): - self.codeobj = get_device().code_object(self.name+'_codeobject*', + self.codeobj = get_device().code_object( + self, + self.name+'_codeobject*', '', # No model-specific code {}, # no namespace self.variables, From d5bd5a989ff81581c6da7c8aeac3594fa85fbc84 Mon Sep 17 00:00:00 2001 From: thesamovar Date: Sat, 14 Sep 2013 18:52:39 -0400 Subject: [PATCH 2/9] SynapticPathway now uses a CodeObject --- brian2/codegen/runtime/numpy_rt/numpy_rt.py | 1 + .../numpy_rt/templates/synapses_push_spikes | 1 + .../templates/synapses_push_spikes.cpp | 9 +++++ brian2/codegen/runtime/weave_rt/weave_rt.py | 12 ++++++ brian2/synapses/synapses.py | 38 +++++++++++-------- 5 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 brian2/codegen/runtime/numpy_rt/templates/synapses_push_spikes create mode 100644 brian2/codegen/runtime/weave_rt/templates/synapses_push_spikes.cpp diff --git a/brian2/codegen/runtime/numpy_rt/numpy_rt.py b/brian2/codegen/runtime/numpy_rt/numpy_rt.py index afee5078b..7d03ddb04 100644 --- a/brian2/codegen/runtime/numpy_rt/numpy_rt.py +++ b/brian2/codegen/runtime/numpy_rt/numpy_rt.py @@ -25,6 +25,7 @@ def __init__(self, owner, code, namespace, variables, name='numpy_code_object*') # TODO: This should maybe go somewhere else namespace['logical_not'] = np.logical_not CodeObject.__init__(self, owner, code, namespace, variables, name=name) + namespace['_owner'] = self.owner def variables_to_namespace(self): # Variables can refer to values that are either constant (e.g. dt) diff --git a/brian2/codegen/runtime/numpy_rt/templates/synapses_push_spikes b/brian2/codegen/runtime/numpy_rt/templates/synapses_push_spikes new file mode 100644 index 000000000..a574c5818 --- /dev/null +++ b/brian2/codegen/runtime/numpy_rt/templates/synapses_push_spikes @@ -0,0 +1 @@ +_owner.push_spikes() diff --git a/brian2/codegen/runtime/weave_rt/templates/synapses_push_spikes.cpp b/brian2/codegen/runtime/weave_rt/templates/synapses_push_spikes.cpp new file mode 100644 index 000000000..e75b133d1 --- /dev/null +++ b/brian2/codegen/runtime/weave_rt/templates/synapses_push_spikes.cpp @@ -0,0 +1,9 @@ +{% macro main() %} +{% endmacro %} + +{% macro support_code() %} +{% endmacro %} + +{% macro python_pre() %} +_owner.push_spikes() +{% endmacro %} diff --git a/brian2/codegen/runtime/weave_rt/weave_rt.py b/brian2/codegen/runtime/weave_rt/weave_rt.py index fd90c8ee9..4246167c6 100644 --- a/brian2/codegen/runtime/weave_rt/weave_rt.py +++ b/brian2/codegen/runtime/weave_rt/weave_rt.py @@ -72,6 +72,7 @@ def __init__(self, owner, code, namespace, variables, name='weave_code_object*') super(WeaveCodeObject, self).__init__(owner, code, namespace, variables, name=name) self.compiler = brian_prefs['codegen.runtime.weave.compiler'] self.extra_compile_args = brian_prefs['codegen.runtime.weave.extra_compile_args'] + self.python_code_namespace = {'_owner': owner} def variables_to_namespace(self): @@ -111,12 +112,23 @@ def update_namespace(self): # update the values of the non-constant values in the namespace for name, func in self.nonconstant_values: self.namespace[name] = func() + + def compile(self): + CodeObject.compile(self) + if hasattr(self.code, 'python_pre'): + self.compiled_python_pre = compile(self.code.python_pre, '(string)', 'exec') + if hasattr(self.code, 'python_post'): + self.compiled_python_post = compile(self.code.python_post, '(string)', 'exec') def run(self): + if hasattr(self, 'compiled_python_pre'): + exec self.compiled_python_pre in self.python_code_namespace return weave.inline(self.code.main, self.namespace.keys(), local_dict=self.namespace, support_code=self.code.support_code, compiler=self.compiler, extra_compile_args=self.extra_compile_args) + if hasattr(self, 'compiled_python_post'): + exec self.compiled_python_post in self.python_code_namespace runtime_targets['weave'] = WeaveCodeObject diff --git a/brian2/synapses/synapses.py b/brian2/synapses/synapses.py index 66bf91ca1..36acffb77 100644 --- a/brian2/synapses/synapses.py +++ b/brian2/synapses/synapses.py @@ -12,6 +12,7 @@ from brian2.core.variables import (ArrayVariable, DynamicArrayVariable, Variable, Subexpression, AttributeVariable, StochasticVariable) +from brian2.devices.device import get_device from brian2.equations.equations import (Equations, SingleEquation, DIFFERENTIAL_EQUATION, STATIC_EQUATION, PARAMETER) @@ -187,33 +188,40 @@ def pre_run(self, namespace): self.dt = self.synapses.clock.dt_ GroupCodeRunner.pre_run(self, namespace) # we insert rather than replace because GroupCodeRunner puts a CodeObject in updaters already - self.updaters.insert(0, SynapticPathwayUpdater(self)) + self.pushspikes_codeobj = get_device().code_object(self, + self.name+'_push_spikes_codeobject*', + '', + {}, + self.group.variables, + 'synapses_push_spikes', + self.group.indices, + self.group.variable_indices, + ) + self.updaters.insert(0, self.pushspikes_codeobj.get_updater()) + #self.updaters.insert(0, SynapticPathwayUpdater(self)) self.queue.compress(np.round(self._delays[:] / self.dt).astype(np.int), self.synapse_indices, len(self.synapses)) - -class SynapticPathwayUpdater(Updater): - def run(self): - path = self.owner + def push_spikes(self): # Push new spikes into the queue - spikes = path.source.spikes - offset = path.source.offset + spikes = self.source.spikes + offset = self.source.offset if len(spikes): # This check is necessary for subgroups - max_index = len(path.synapse_indices) + offset - indices = np.concatenate([path.synapse_indices[spike - offset] + max_index = len(self.synapse_indices) + offset + indices = np.concatenate([self.synapse_indices[spike - offset] for spike in spikes if offset <= spike < max_index]).astype(np.int32) if len(indices): - if len(path._delays) > 1: - delays = np.round(path._delays[indices] / path.dt).astype(int) + if len(self._delays) > 1: + delays = np.round(self._delays[indices] / self.dt).astype(int) else: - delays = np.round(path._delays[:] / path.dt).astype(int) - path.queue.push(indices, delays) + delays = np.round(self._delays[:] / self.dt).astype(int) + self.queue.push(indices, delays) # Get the spikes - path.spiking_synapses = path.queue.peek() + self.spiking_synapses = self.queue.peek() # Advance the spike queue - path.queue.next() + self.queue.next() class IndexView(object): From cf04975db435afa5fbf915d5538fadc27712af90 Mon Sep 17 00:00:00 2001 From: thesamovar Date: Thu, 19 Sep 2013 12:57:55 -0400 Subject: [PATCH 3/9] Ignore egg-info --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ff12bb7fa..3d227ec5e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ nosetests.xml # pycharm project files .idea +/Brian2.egg-info From 5af9d3ffe9777fe613a24d18ee98ea4b9397c608 Mon Sep 17 00:00:00 2001 From: Marcel Stimberg Date: Thu, 19 Sep 2013 18:11:59 +0200 Subject: [PATCH 4/9] Subexpression doesn't need information about variables and namespace --- brian2/core/variables.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/brian2/core/variables.py b/brian2/core/variables.py index 704c8e92f..e94d7b020 100644 --- a/brian2/core/variables.py +++ b/brian2/core/variables.py @@ -469,26 +469,14 @@ class Subexpression(Variable): 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): + def __init__(self, unit, dtype, expr, 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.before_run`) that is used if the NeuronGroup does not - #: have an explicitly defined namespace. - self.additional_namespace = None + self.identifiers = get_identifiers(expr) def get_value(self): raise AssertionError('get_value should never be called for a Subexpression') From 4d4faa63c439d277e679996e287b78995e494869 Mon Sep 17 00:00:00 2001 From: Marcel Stimberg Date: Thu, 19 Sep 2013 19:35:54 +0200 Subject: [PATCH 5/9] Merge array and ArrayVariable creation, add new ArrayVariable objects for standalone. Closes #122 --- brian2/codegen/codeobject.py | 8 +- brian2/codegen/runtime/numpy_rt/numpy_rt.py | 38 +++-- brian2/codegen/runtime/weave_rt/weave_rt.py | 7 +- brian2/core/variables.py | 20 ++- brian2/devices/cpp_standalone/device.py | 101 +++++++++--- brian2/devices/device.py | 56 ++++--- brian2/groups/group.py | 4 +- brian2/groups/neurongroup.py | 82 ++++------ brian2/groups/poissongroup.py | 36 ++--- brian2/monitors/ratemonitor.py | 38 ++--- brian2/monitors/spikemonitor.py | 44 +++--- brian2/monitors/statemonitor.py | 62 ++++---- brian2/synapses/synapses.py | 160 +++++++++----------- brian2/tests/test_codegen.py | 9 +- brian2/tests/test_refractory.py | 1 + dev/ideas/devices/cpp_standalone.py | 1 - 16 files changed, 352 insertions(+), 315 deletions(-) diff --git a/brian2/codegen/codeobject.py b/brian2/codegen/codeobject.py index 6a9f8d7e6..241b8ec80 100644 --- a/brian2/codegen/codeobject.py +++ b/brian2/codegen/codeobject.py @@ -22,12 +22,7 @@ def prepare_namespace(namespace, variables, codeobj_class): # We do the import here to avoid import problems from .runtime.numpy_rt.numpy_rt import NumpyCodeObject - namespace = dict(namespace) - # Add variables referring to the arrays - arrays = [] - for value in variables.itervalues(): - if isinstance(value, ArrayVariable): - arrays.append((value.arrayname, value.get_value())) + # Check that all functions are available for name, value in namespace.iteritems(): if isinstance(value, Function): @@ -40,7 +35,6 @@ def prepare_namespace(namespace, variables, codeobj_class): else: raise NotImplementedError(('Cannot use function ' '%s: %s') % (name, ex)) - namespace.update(arrays) return namespace diff --git a/brian2/codegen/runtime/numpy_rt/numpy_rt.py b/brian2/codegen/runtime/numpy_rt/numpy_rt.py index 84b217972..e3ab1468e 100644 --- a/brian2/codegen/runtime/numpy_rt/numpy_rt.py +++ b/brian2/codegen/runtime/numpy_rt/numpy_rt.py @@ -2,7 +2,8 @@ import numpy as np from brian2.core.preferences import brian_prefs, BrianPreference -from brian2.core.variables import Variable, Subexpression, DynamicArrayVariable +from brian2.core.variables import (Variable, Subexpression, + DynamicArrayVariable, ArrayVariable) from ...codeobject import CodeObject from ...templates import Templater @@ -50,20 +51,27 @@ def variables_to_namespace(self): self.nonconstant_values = [] for name, var in self.variables.iteritems(): - if isinstance(var, Variable) and not isinstance(var, Subexpression): - if not var.constant: - self.nonconstant_values.append((name, var.get_value)) - if isinstance(var, DynamicArrayVariable): - self.nonconstant_values.append((name+'_object', - var.get_object)) - else: - try: - value = var.get_value() - except TypeError: # A dummy Variable without value - continue - self.namespace[name] = value - if isinstance(var, DynamicArrayVariable): - self.namespace[name+'_object'] = var.get_object() + if isinstance(var, Subexpression): + continue + + if not var.constant: + self.nonconstant_values.append((name, var.get_value)) + if isinstance(var, ArrayVariable): + self.nonconstant_values.append((var.arrayname, + var.get_value)) + if isinstance(var, DynamicArrayVariable): + self.nonconstant_values.append((name+'_object', + var.get_object)) + else: + try: + value = var.get_value() + except TypeError: # A dummy Variable without value + continue + if isinstance(var, ArrayVariable): + self.namespace[var.arrayname] = var.get_value() + self.namespace[name] = value + if isinstance(var, DynamicArrayVariable): + self.namespace[var.name+'_object'] = var.get_object() def update_namespace(self): # update the values of the non-constant values in the namespace diff --git a/brian2/codegen/runtime/weave_rt/weave_rt.py b/brian2/codegen/runtime/weave_rt/weave_rt.py index 0ccac5e71..110f23fd8 100644 --- a/brian2/codegen/runtime/weave_rt/weave_rt.py +++ b/brian2/codegen/runtime/weave_rt/weave_rt.py @@ -8,7 +8,8 @@ # No weave for Python 3 weave = None -from brian2.core.variables import Variable, Subexpression, DynamicArrayVariable +from brian2.core.variables import (Variable, Subexpression, + DynamicArrayVariable, ArrayVariable) from brian2.core.preferences import brian_prefs, BrianPreference from brian2.core.functions import DEFAULT_FUNCTIONS, FunctionImplementation @@ -87,6 +88,8 @@ def variables_to_namespace(self): for name, var in self.variables.iteritems(): if isinstance(var, Variable) and not isinstance(var, Subexpression): if not var.constant: + if isinstance(var, ArrayVariable): + self.nonconstant_values.append((var.arrayname, var.get_value)) self.nonconstant_values.append((name, var.get_value)) if not var.scalar: self.nonconstant_values.append(('_num' + name, @@ -99,6 +102,8 @@ def variables_to_namespace(self): value = var.get_value() except TypeError: # A dummy Variable without value continue + if isinstance(var, ArrayVariable): + self.namespace[var.arrayname] = value self.namespace[name] = value # if it is a type that has a length, add a variable called # '_num'+name with its length diff --git a/brian2/core/variables.py b/brian2/core/variables.py index e94d7b020..5c7c93643 100644 --- a/brian2/core/variables.py +++ b/brian2/core/variables.py @@ -109,7 +109,7 @@ def get_value(self): else: return self.value - def set_value(self): + def set_value(self, value, index=None): ''' Set the value associated with the variable. ''' @@ -145,6 +145,9 @@ def get_len(self): else: return len(self.get_value()) + def __len__(self): + return self.get_len() + def __repr__(self): description = ('<{classname}(unit={unit}, value={value}, ' 'dtype={dtype}, scalar={scalar}, constant={constant})>') @@ -415,8 +418,16 @@ def __init__(self, name, unit, value, group_name=None, constant=False, def get_value(self): return self.value - def set_value(self, value): - self.value[:] = value + def __getitem__(self, item): + return self.get_value()[item] + + def set_value(self, value, index=None): + if index is None: + index = slice(None) + self.value[index] = value + + def __setitem__(self, item, value): + self.set_value(value, item) def get_addressable_value(self, group, level=0): template = getattr(group, '_set_with_code_template', @@ -443,6 +454,9 @@ def get_value(self): def get_object(self): return self.value + def resize(self, new_size): + self.value.resize(new_size) + class Subexpression(Variable): ''' diff --git a/brian2/devices/cpp_standalone/device.py b/brian2/devices/cpp_standalone/device.py index b0fe7d4f2..ebcdee915 100644 --- a/brian2/devices/cpp_standalone/device.py +++ b/brian2/devices/cpp_standalone/device.py @@ -25,31 +25,92 @@ def freeze(code, ns): code = word_substitute(code, {k: repr(v)}) return code +class StandaloneVariableView(): + ''' + Will store information about how the variable was set in the original + `ArrayVariable` object. + ''' + def __init__(self, variable): + self.variable = variable + + def __setitem__(self, key, value): + self.variable.assignments.append((key, value)) + + def __getitem__(self, item): + raise NotImplementedError() + +class StandaloneArrayVariable(ArrayVariable): + + def __init__(self, name, unit, size, dtype, group_name=None, constant=False, + is_bool=False): + self.assignments = [] + self.size = size + super(StandaloneArrayVariable, self).__init__(name, unit, value=None, + group_name=group_name, + constant=constant, + is_bool=is_bool) + self.dtype = dtype + + def get_len(self): + return self.size + + def get_value(self): + raise NotImplementedError() + + def set_value(self, value, index=None): + if index is None: + index = slice(None) + self.assignments.append((index, value)) + + def get_addressable_value(self, group, level=0): + return StandaloneVariableView(self) + + def get_addressable_value_with_unit(self, group, level=0): + return StandaloneVariableView(self) + + +class StandaloneDynamicArrayVariable(StandaloneArrayVariable): + + def resize(self, new_size): + self.assignments.append(('resize', new_size)) + class CPPStandaloneDevice(Device): ''' ''' def __init__(self): - self.arrays = {} - self.dynamic_arrays = {} + self.array_specs = [] + self.dynamic_array_specs = [] self.code_objects = {} - def array(self, owner, name, size, unit, dtype=None): - if dtype is None: + def array(self, owner, name, size, unit, dtype=None, constant=False, + is_bool=False): + if is_bool: + dtype = numpy.bool + elif dtype is None: dtype = brian_prefs['core.default_scalar_dtype'] - arr = numpy.zeros(size, dtype=dtype) - self.arrays['_array_%s_%s' % (owner.name, name)] = arr - return arr - - def dynamic_array_1d(self, owner, name, size, unit, dtype): - if dtype is None: + self.array_specs.append(('_array_%s_%s' % (owner.name, name), + c_data_type(dtype), size)) + return StandaloneArrayVariable(name, unit, size=size, dtype=dtype, + group_name=owner.name, + constant=constant, is_bool=is_bool) + + def dynamic_array_1d(self, owner, name, size, unit, dtype=None, + constant=False, is_bool=False): + if is_bool: + dtype = numpy.bool + elif dtype is None: dtype = brian_prefs['core.default_scalar_dtype'] - arr = DynamicArray1D(size, dtype=dtype) - self.dynamic_arrays['_dynamic_array_%s_%s' % (owner.name, name)] = arr - return arr + self.dynamic_array_specs.append(('_dynamic_array_%s_%s' % (owner.name, name), + c_data_type(dtype))) + return StandaloneDynamicArrayVariable(name, unit, size=size, + dtype=dtype, + group_name=owner.name, + constant=constant, is_bool=is_bool) - def dynamic_array(self): - raise NotImplentedError + def dynamic_array(self, owner, name, size, unit, dtype=None, + constant=False, is_bool=False): + raise NotImplementedError() def code_object_class(self, codeobj_class=None): if codeobj_class is not None: @@ -86,11 +147,9 @@ def build(self, net): if not os.path.exists('output'): os.mkdir('output') - # Write the arrays - array_specs = [(k, c_data_type(v.dtype), len(v)) for k, v in self.arrays.iteritems()] - dynamic_array_specs = [(k, c_data_type(v.dtype)) for k, v in self.dynamic_arrays.iteritems()] - arr_tmp = CPPStandaloneCodeObject.templater.arrays(None, array_specs=array_specs, - dynamic_array_specs=dynamic_array_specs) + # Write the arrays + arr_tmp = CPPStandaloneCodeObject.templater.arrays(None, array_specs=self.array_specs, + dynamic_array_specs=self.dynamic_array_specs) open('output/arrays.cpp', 'w').write(arr_tmp.cpp_file) open('output/arrays.h', 'w').write(arr_tmp.h_file) @@ -105,7 +164,7 @@ def build(self, net): elif not v.scalar: N = v.get_len() code_object_defs[codeobj.name].append('const int _num%s = %s;' % (k, N)) - if isinstance(v, DynamicArrayVariable): + if isinstance(v, StandaloneDynamicArrayVariable): c_type = c_data_type(v.dtype) # Create an alias name for the underlying array code = ('{c_type}* {arrayname} = ' diff --git a/brian2/devices/device.py b/brian2/devices/device.py index 67dc52806..26aab1458 100644 --- a/brian2/devices/device.py +++ b/brian2/devices/device.py @@ -1,8 +1,10 @@ -import numpy +import numpy as np + from brian2.memory.dynamicarray import DynamicArray, DynamicArray1D from brian2.codegen.codeobject import create_codeobject from brian2.codegen.targets import codegen_targets from brian2.core.preferences import brian_prefs +from brian2.core.variables import ArrayVariable, DynamicArrayVariable __all__ = ['Device', 'RuntimeDevice', 'get_device', 'set_device', @@ -36,15 +38,18 @@ class Device(object): def __init__(self): pass - def create_array(self, owner, name, size, unit, dtype=None): - pass + def array(self, owner, name, size, unit, dtype=None, constant=False, + is_bool=False): + raise NotImplementedError() - def create_dynamic_array_1d(self, owner, name, size, unit, dtype=None): - pass + def dynamic_array_1d(self, owner, name, size, unit, dtype=None, + constant=False, is_bool=False): + raise NotImplementedError() + + def dynamic_array(self, owner, name, size, unit, dtype=None, + constant=False, is_bool=False): + raise NotImplementedError() - def create_dynamic_array(self, owner, name, size, unit, dtype=None): - pass - def code_object_class(self, codeobj_class=None): if codeobj_class is None: codeobj_class = get_default_codeobject_class() @@ -69,22 +74,37 @@ class RuntimeDevice(Device): ''' ''' def __init__(self): - pass + super(Device, self).__init__() - def array(self, owner, name, size, unit, dtype=None): - if dtype is None: + def array(self, owner, name, size, unit, dtype=None, + constant=False, is_bool=False): + if is_bool: + dtype = np.bool + elif dtype is None: dtype = brian_prefs['core.default_scalar_dtype'] - return numpy.zeros(size, dtype=dtype) - - def dynamic_array_1d(self, owner, name, size, unit, dtype): + array = np.zeros(size, dtype=dtype) + return ArrayVariable(name, unit, array, group_name=owner.name, + constant=constant, is_bool=is_bool) + + def dynamic_array_1d(self, owner, name, size, unit, dtype=None, + constant=False, is_bool=False): + if is_bool: + dtype = np.bool if dtype is None: dtype = brian_prefs['core.default_scalar_dtype'] - return DynamicArray1D(size, dtype=dtype) - - def dynamic_array(self, owner, name, size, unit, dtype): + array = DynamicArray1D(size, dtype=dtype) + return DynamicArrayVariable(name, unit, array, group_name=owner.name, + constant=constant, is_bool=is_bool) + + def dynamic_array(self, owner, name, size, unit, dtype=None, + constant=False, is_bool=False): + if is_bool: + dtype = np.bool if dtype is None: dtype = brian_prefs['core.default_scalar_dtype'] - return DynamicArray(size, dtype=dtype) + array = DynamicArray(size, dtype=dtype) + return DynamicArrayVariable(name, unit, array, group_name=owner.name, + constant=constant, is_bool=is_bool) runtime_device = RuntimeDevice() diff --git a/brian2/groups/group.py b/brian2/groups/group.py index 2e6aead3b..193d1aea0 100644 --- a/brian2/groups/group.py +++ b/brian2/groups/group.py @@ -168,12 +168,12 @@ def __setattr__(self, name, val): fail_for_dimension_mismatch(val, var.unit, 'Incorrect units for setting %s' % name) # Make the call X.var = ... equivalent to X.var[:] = ... - var.get_addressable_value_with_unit(self, level=1)[:] = val + var.get_addressable_value_with_unit(self, level=1)[slice(None)] = val elif len(name) and name[-1]=='_' and name[:-1] in self.variables: # no unit checking var = self.variables[name[:-1]] # Make the call X.var = ... equivalent to X.var[:] = ... - var.get_addressable_value(self, level=1)[:] = val + var.get_addressable_value(self, level=1)[slice(None)] = val else: object.__setattr__(self, name, val) diff --git a/brian2/groups/neurongroup.py b/brian2/groups/neurongroup.py index 91ba5c805..e26700787 100644 --- a/brian2/groups/neurongroup.py +++ b/brian2/groups/neurongroup.py @@ -1,6 +1,8 @@ ''' This model defines the `NeuronGroup`, the core of most simulations. ''' +from collections import defaultdict + import numpy as np import sympy @@ -12,8 +14,7 @@ from brian2.core.preferences import brian_prefs from brian2.core.base import BrianObject from brian2.core.namespace import create_namespace -from brian2.core.variables import (ArrayVariable, StochasticVariable, - Subexpression) +from brian2.core.variables import (StochasticVariable, Subexpression) from brian2.core.spikesource import SpikeSource from brian2.core.scheduler import Scheduler from brian2.parsing.expressions import (parse_expression_unit, @@ -178,9 +179,10 @@ class NeuronGroup(BrianObject, Group, SpikeSource): the local and global namespace surrounding the creation of the class, is used. dtype : (`dtype`, `dict`), optional - The `numpy.dtype` that will be used to store the values, or - `core.default_scalar_dtype` if not specified (`numpy.float64` by - default). + The `numpy.dtype` that will be used to store the values, or a + dictionary specifying the type for variable names. If a value is not + provided for a variable (or no value is provided at all), the preference + setting `core.default_scalar_dtype` is used. codeobj_class : class, optional The `CodeObject` class to run code with. clock : Clock, optional @@ -238,16 +240,11 @@ def __init__(self, N, model, method=None, logger.debug("Creating NeuronGroup of size {self.N}, " "equations {self.equations}.".format(self=self)) - ##### Setup the memory - self.arrays = self._allocate_memory(dtype=dtype) - - self._spikespace = get_device().array(self, '_spikespace', N+1, 1, dtype=np.int32) - # Setup the namespace self.namespace = create_namespace(namespace) # Setup variables - self.variables = self._create_variables() + self.variables = self._create_variables(dtype) # All of the following will be created in before_run @@ -313,7 +310,7 @@ def spikes(self): ''' The spikes returned by the most recent thresholding operation. ''' - return self._spikespace[:self._spikespace[-1]] + return self.variables['_spikespace'].get_value()[:self.variables['_spikespace'].get_value()[-1]] def __getitem__(self, item): if not isinstance(item, slice): @@ -327,29 +324,6 @@ def __getitem__(self, item): return Subgroup(self, start, stop) - def _allocate_memory(self, dtype=None): - # Allocate memory (TODO: this should be refactored somewhere at some point) - - arrays = {} - for eq in self.equations.itervalues(): - if eq.type == STATIC_EQUATION: - # nothing to do - continue - name = eq.varname - if isinstance(dtype, dict): - curdtype = dtype[name] - else: - curdtype = dtype - if curdtype is None: - curdtype = brian_prefs['core.default_scalar_dtype'] - if eq.is_bool: - arrays[name] = get_device().array(self, name, self.N, 1, dtype=np.bool) - else: - # TODO: specify unit here - arrays[name] = get_device().array(self, name, self.N, 1, dtype=curdtype) - logger.debug("NeuronGroup memory allocated successfully.") - return arrays - def runner(self, code, when=None, name=None): ''' Returns a `CodeRunner` that runs abstract code in the groups namespace @@ -379,37 +353,41 @@ def runner(self, code, when=None, name=None): code=code, name=name, when=when) return runner - def _create_variables(self): + def _create_variables(self, dtype=None): ''' Create the variables dictionary for this `NeuronGroup`, containing entries for the equation variables and some standard entries. ''' + device = get_device() # Get the standard variables for all groups s = Group._create_variables(self) + if dtype is None: + dtype = defaultdict(lambda: brian_prefs['core.default_scalar_dtype']) + elif isinstance(dtype, np.dtype): + dtype = defaultdict(lambda: dtype) + elif not hasattr(dtype, '__getitem__'): + raise TypeError(('Cannot use type %s as dtype ' + 'specification') % type(dtype)) + # Standard variables always present - s.update({'_spikespace': ArrayVariable('_spikespace', Unit(1), - self._spikespace, - group_name=self.name)}) + s['_spikespace'] = device.array(self, '_spikespace', self.N+1, + Unit(1), dtype=np.int32, + constant=False) for eq in self.equations.itervalues(): if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER): - array = self.arrays[eq.varname] constant = ('constant' in eq.flags) - s.update({eq.varname: ArrayVariable(eq.varname, - eq.unit, - array, - group_name=self.name, - constant=constant, - is_bool=eq.is_bool)}) + s[eq.varname] = device.array(self, eq.varname, self.N, eq.unit, + dtype=dtype[eq.varname], + constant=constant, + is_bool=eq.is_bool) elif eq.type == STATIC_EQUATION: - s.update({eq.varname: Subexpression(eq.unit, - brian_prefs['core.default_scalar_dtype'], - str(eq.expr), - variables=s, - namespace=self.namespace, - is_bool=eq.is_bool)}) + s[eq.varname] = Subexpression(eq.unit, + brian_prefs['core.default_scalar_dtype'], + str(eq.expr), + is_bool=eq.is_bool) else: raise AssertionError('Unknown type of equation: ' + eq.eq_type) diff --git a/brian2/groups/poissongroup.py b/brian2/groups/poissongroup.py index 10f9c323c..db5e9091c 100644 --- a/brian2/groups/poissongroup.py +++ b/brian2/groups/poissongroup.py @@ -6,8 +6,7 @@ from brian2.core.variables import ArrayVariable from brian2.devices.device import get_device from brian2.equations import Equations -from brian2.units.fundamentalunits import check_units, Unit -from brian2.units.allunits import second +from brian2.units.fundamentalunits import check_units from brian2.units.stdunits import Hz from .group import Group @@ -46,8 +45,6 @@ def __init__(self, N, rates, clock=None, name='poissongroup*', self.codeobj_class = codeobj_class self.N = N = int(N) - #: The array holding the spikes - self._spikespace = get_device().array(self, '_spikespace', N+1, 1, dtype=np.int32) #: The array holding the rates self._rates = np.asarray(rates) @@ -61,27 +58,22 @@ def __init__(self, N, rates, clock=None, name='poissongroup*', # users write their own NeuronGroup (with threshold rand() < rates*dt) # for more complex use cases. - #: The array storing the refractoriness information (not used, currently) - self._not_refractory = get_device().array(self, '_not_refractory', N, 1, - dtype=np.bool) - self._lastspike = get_device().array(self, '_lastspike', N, 1) - self.variables = Group._create_variables(self) self.variables.update({'rates': ArrayVariable('rates', Hz, self._rates, group_name=self.name, constant=True), - '_spikespace': ArrayVariable('_spikespace', Unit(1), - self._spikespace, - group_name=self.name), - 'not_refractory': ArrayVariable('not_refractory', - Unit(1), - self._not_refractory, - group_name=self.name, - is_bool=True), - 'lastspike': ArrayVariable('lastspike', - second, - self._lastspike, - group_name=self.name)}) + '_spikespace': get_device().array(self, + '_spikespace', + N+1, + 1, + dtype=np.int32), + 'not_refractory': get_device().array(self, + '_not_refractory', + N, 1, + dtype=np.bool), + 'lastspike': get_device().array(self, + '_lastspike', + N, 1)}) self.namespace = create_namespace(None) @@ -102,7 +94,7 @@ def spikes(self): ''' The spikes returned by the most recent thresholding operation. ''' - return self._spikespace[:self._spikespace[-1]] + return self.variables['_spikespace'].get_value()[:self.variables['_spikespace'].get_value()[-1]] def __len__(self): return self.N diff --git a/brian2/monitors/ratemonitor.py b/brian2/monitors/ratemonitor.py index 4e789b40a..13d3f7b62 100644 --- a/brian2/monitors/ratemonitor.py +++ b/brian2/monitors/ratemonitor.py @@ -47,31 +47,23 @@ def __init__(self, source, when=None, name='ratemonitor*', self.codeobj_class = codeobj_class BrianObject.__init__(self, when=scheduler, name=name) - # create data structures - self.reinit() - + dev = get_device() self.variables = {'t': AttributeVariable(second, self.clock, 't_'), - 'dt': AttributeVariable(second, self.clock, - 'dt_', constant=True), + 'dt': AttributeVariable(second, self.clock, + 'dt_', constant=True), '_spikespace': self.source.variables['_spikespace'], - '_rate': DynamicArrayVariable('_rate', Unit(1), - self._rate, - group_name=self.name), - '_t': DynamicArrayVariable('_t', Unit(1), - self._t, - group_name=self.name), - '_num_source_neurons': Variable(Unit(1), - len(self.source))} + '_rate': dev.dynamic_array_1d(self, '_rate', 0, 1), + '_t': dev.dynamic_array_1d(self, '_t', 0, second, + dtype=getattr(self.clock.t, 'dtype', + np.dtype(type(self.clock.t)))), + '_num_source_neurons': Variable(Unit(1), + len(self.source))} def reinit(self): ''' Clears all recorded rates ''' - dev = get_device() - self._rate = dev.dynamic_array_1d(self, '_rate', 0, 1, dtype=brian_prefs['core.default_scalar_dtype']) - self._t = dev.dynamic_array_1d(self, '_t', 0, second, - dtype=getattr(self.clock.t, 'dtype', - np.dtype(type(self.clock.t)))) + raise NotImplementedError() def before_run(self, namespace): self.codeobj = get_device().code_object( @@ -91,28 +83,30 @@ def rate(self): ''' Array of recorded rates (in units of Hz). ''' - return Quantity(self._rate.data.copy(), dim=hertz.dim) + return Quantity(self.variables['_rate'].get_value().copy(), + dim=hertz.dim) @property def rate_(self): ''' Array of recorded rates (unitless). ''' - return self._rate.data.copy() + return self.variables['_rate'].get_value().copy() @property def t(self): ''' Array of recorded time points (in units of second). ''' - return Quantity(self._t.data.copy(), dim=second.dim) + return Quantity(self.variables['_t'].get_value().copy(), + dim=second.dim) @property def t_(self): ''' Array of recorded time points (unitless). ''' - return self._t.data.copy() + return self.variables['_t'].get_value().copy() def __repr__(self): description = '<{classname}, recording {source}>' diff --git a/brian2/monitors/spikemonitor.py b/brian2/monitors/spikemonitor.py index 5042be5dc..14ebcb12a 100644 --- a/brian2/monitors/spikemonitor.py +++ b/brian2/monitors/spikemonitor.py @@ -3,13 +3,11 @@ import numpy as np -from brian2.codegen.codeobject import create_codeobject from brian2.core.base import BrianObject -from brian2.core.preferences import brian_prefs from brian2.core.scheduler import Scheduler -from brian2.core.variables import ArrayVariable, AttributeVariable, Variable, DynamicArrayVariable +from brian2.core.variables import AttributeVariable, Variable from brian2.units.allunits import second -from brian2.units.fundamentalunits import Unit +from brian2.units.fundamentalunits import Unit, Quantity from brian2.devices.device import get_device __all__ = ['SpikeMonitor'] @@ -49,19 +47,22 @@ def __init__(self, source, record=True, when=None, name='spikemonitor*', self.codeobj_class = codeobj_class BrianObject.__init__(self, when=scheduler, name=name) - - # create data structures - self.reinit() # Handle subgroups correctly start = getattr(self.source, 'start', 0) end = getattr(self.source, 'end', len(self.source)) + device = get_device() self.variables = {'t': AttributeVariable(second, self.clock, 't_'), '_spikespace': self.source.variables['_spikespace'], - '_i': DynamicArrayVariable('_i', Unit(1), self._i, group_name=self.name), - '_t': DynamicArrayVariable('_t', Unit(1), self._t, group_name=self.name), - '_count': ArrayVariable('_count', Unit(1), self.count, group_name=self.name), + '_i': device.dynamic_array_1d(self, '_i', 0, Unit(1), + dtype=np.int32), + '_t': device.dynamic_array_1d(self, '_t', 0, + Unit(1)), + '_count': device.array(self, '_count', + len(self.source), + Unit(1), + dtype=np.int32), '_source_start': Variable(Unit(1), start, constant=True), '_source_end': Variable(Unit(1), end, @@ -71,12 +72,7 @@ def reinit(self): ''' Clears all recorded spikes ''' - dev = get_device() - self._i = dev.dynamic_array_1d(self, '_i', 0, 1, dtype=np.int32) - self._t = dev.dynamic_array_1d(self, '_t', 0, 1, dtype=brian_prefs['core.default_scalar_dtype']) - - #: Array of the number of times each source neuron has spiked - self.count = get_device().array(self, '_count', len(self.source), 1, dtype=np.int32) + raise NotImplementedError() def before_run(self, namespace): self.codeobj = get_device().code_object( @@ -97,21 +93,22 @@ def i(self): ''' Array of recorded spike indices, with corresponding times `t`. ''' - return self._i.data.copy() + return self.variables['_i'].get_value().copy() @property def t(self): ''' Array of recorded spike times, with corresponding indices `i`. ''' - return self._t.data.copy()*second + return Quantity(self.variables['_t'].get_value().copy(), + dim=second.dim) @property def t_(self): ''' Array of recorded spike times without units, with corresponding indices `i`. ''' - return self._t.data.copy() + return self.variables['_t'].get_value().copy() @property def it(self): @@ -126,7 +123,14 @@ def it_(self): Returns the pair (`i`, `t_`). ''' return self.i, self.t_ - + + @property + def count(self): + ''' + Return the total spike count for each neuron. + ''' + return self.variables['_count'].get_value().copy() + @property def num_spikes(self): ''' diff --git a/brian2/monitors/statemonitor.py b/brian2/monitors/statemonitor.py index 001be2f7e..ad2d21c0b 100644 --- a/brian2/monitors/statemonitor.py +++ b/brian2/monitors/statemonitor.py @@ -3,11 +3,9 @@ import numpy as np -from brian2.core.variables import (Variable, AttributeVariable, ArrayVariable, - DynamicArrayVariable) +from brian2.core.variables import (AttributeVariable, ArrayVariable) from brian2.core.base import BrianObject from brian2.core.scheduler import Scheduler -from brian2.core.preferences import brian_prefs from brian2.devices.device import get_device from brian2.units.fundamentalunits import Unit, Quantity, have_same_dimensions from brian2.units.allunits import second @@ -35,16 +33,18 @@ def __getattr__(self, item): if not hasattr(self, '_group_attribute_access_active'): raise AttributeError + mon = self.monitor if item == 't': - return Quantity(self.monitor._t.data.copy(), dim=second.dim) + return Quantity(mon.variables['_t'].get_value().copy(), + dim=second.dim) elif item == 't_': - return self.monitor._t.data.copy() - elif item in self.monitor.record_variables: - unit = self.monitor.variables[item].unit - return Quantity(self.monitor._values[item].data.T[self.indices].copy(), + return mon._t.data.copy() + elif item in mon.record_variables: + unit = mon.variables[item].unit + return Quantity(mon.variables['_recorded_'+item].get_value().T[self.indices].copy(), dim=unit.dim) - elif item.endswith('_') and item[:-1] in self.monitor.record_variables: - return self.monitor._values[item[:-1]].data.T[self.indices].copy() + elif item.endswith('_') and item[:-1] in mon.record_variables: + return mon.variables['_recorded_'+item[:-1]].get_value().T[self.indices].copy() else: raise AttributeError('Unknown attribute %s' % item) @@ -168,10 +168,9 @@ def __init__(self, source, variables, record=None, when=None, #: The array of recorded indices self.indices = record - # create data structures - self.reinit() # Setup variables + device = get_device() self.variables = {} for varname in variables: var = source.variables[varname] @@ -182,13 +181,15 @@ def __init__(self, source, variables, record=None, when=None, 'doubles can be recorded.') % (varname, var.dtype)) self.variables[varname] = var - self.variables['_recorded_'+varname] = DynamicArrayVariable('_recorded_'+varname, - Unit(1), - self._values[varname], - group_name=self.name) + self.variables['_recorded_'+varname] = device.dynamic_array(self, + '_recorded_'+varname, + (0, len(self.indices)), + var.unit, + dtype=var.dtype, + constant=False) - self.variables['_t'] = DynamicArrayVariable('_t', Unit(1), self._t, - group_name=self.name) + self.variables['_t'] = device.dynamic_array_1d(self, '_t', 0, Unit(1), + constant=False) self.variables['_clock_t'] = AttributeVariable(second, self.clock, 't_') self.variables['_indices'] = ArrayVariable('_indices', Unit(1), self.indices, @@ -197,16 +198,7 @@ def __init__(self, source, variables, record=None, when=None, self._group_attribute_access_active = True def reinit(self): - dev = get_device() - vars = self.source.variables - self._values = dict((v, dev.dynamic_array(self, '_values', (0, len(self.indices)), - vars[v].unit, - dtype=vars[v].dtype)) - for v in self.record_variables) - self._t = dev.dynamic_array_1d(self, '_t', 0, second, - dtype=brian_prefs['core.default_scalar_dtype']) - # FIXME: This does not update the variables dictionary with the new - # references + raise NotImplementedError() def before_run(self, namespace): # Some dummy code so that code generation takes care of the indexing @@ -254,18 +246,16 @@ def __getattr__(self, item): # TODO: Decide about the interface if item == 't': - return Quantity(self._t.data.copy(), dim=second.dim) + return Quantity(self.variables['_t'].get_value().copy(), + dim=second.dim) elif item == 't_': - return self._t.data.copy() + return self.variables['_t'].get_value().copy() elif item in self.record_variables: unit = self.variables[item].unit - if have_same_dimensions(unit, 1): - return self._values[item].data.T.copy() - else: - return Quantity(self._values[item].data.T.copy(), - dim=unit.dim) + return Quantity(self.variables['_recorded_'+item].get_value().T, + dim=unit.dim) elif item.endswith('_') and item[:-1] in self.record_variables: - return self._values[item[:-1]].data.T.copy() + return self.variables['_recorded_'+item[:-1]].get_value().T else: raise AttributeError('Unknown attribute %s' % item) diff --git a/brian2/synapses/synapses.py b/brian2/synapses/synapses.py index cd16374a4..59ad409cd 100644 --- a/brian2/synapses/synapses.py +++ b/brian2/synapses/synapses.py @@ -139,21 +139,20 @@ def __init__(self, synapses, code, prepost, objname=None): when=(synapses.clock, 'synapses'), name=synapses.name + '_' + objname) - self._delays = DynamicArray1D(synapses.N, dtype=np.float64) - # Register the object with the `SynapticIndex` object so it gets - # automatically resized - synapses.item_mapping.register_variable(self._delays) self.queue = SpikeQueue() self.spiking_synapses = [] self.variables = {'_spiking_synapses': AttributeVariable(Unit(1), self, 'spiking_synapses', constant=False), - 'delay': DynamicArrayVariable('delay', second, - self._delays, - group_name=self.name, - constant=True)} - + 'delay': get_device().dynamic_array_1d(self, 'delay', + synapses.N, + second, + constant=True)} + self._delays = self.variables['delay'] + # Register the object with the `SynapticIndex` object so it gets + # automatically resized + synapses.item_mapping.register_variable(self._delays) # Re-extract the last part of the name from the full name self.objname = self.name[len(synapses.name) + 1:] @@ -199,7 +198,7 @@ def before_run(self, namespace): ) self.updaters.insert(0, self.pushspikes_codeobj.get_updater()) #self.updaters.insert(0, SynapticPathwayUpdater(self)) - self.queue.compress(np.round(self._delays[:] / self.dt).astype(np.int), + self.queue.compress(np.round(self._delays.get_value() / self.dt).astype(np.int), self.synapse_indices, len(self.synapses)) def push_spikes(self): @@ -212,11 +211,12 @@ def push_spikes(self): indices = np.concatenate([self.synapse_indices[spike - offset] for spike in spikes if offset <= spike < max_index]).astype(np.int32) + if len(indices): if len(self._delays) > 1: - delays = np.round(self._delays[indices] / self.dt).astype(int) + delays = np.round(self._delays.get_value()[indices] / self.dt).astype(int) else: - delays = np.round(self._delays[:] / self.dt).astype(int) + delays = np.round(self._delays.get_value() / self.dt).astype(int) self.queue.push(indices, delays) # Get the spikes self.spiking_synapses = self.queue.peek() @@ -231,7 +231,11 @@ def __init__(self, indices, mapping): self.mapping = mapping def __getitem__(self, item): - synaptic_indices = self.index[self.mapping[item]] + try: + synaptic_indices = self.index.get_value()[self.mapping.get_value()[item]] + except TypeError as ex: + print 'index', self.index + raise ex return synaptic_indices @@ -328,30 +332,21 @@ def __init__(self, synapses): Variable.__init__(self, Unit(1), value=self, constant=True) self.source = synapses.source self.target = synapses.target - source_len = len(synapses.source) - target_len = len(synapses.target) self.synapses = weakref.proxy(synapses) - dtype = smallest_inttype(MAX_SYNAPSES) - self.synaptic_pre = DynamicArray1D(0, dtype=dtype) - self.synaptic_post = DynamicArray1D(0, dtype=dtype) - self.pre_synaptic = [DynamicArray1D(0, dtype=dtype) - for _ in xrange(source_len)] - self.post_synaptic = [DynamicArray1D(0, dtype=dtype) - for _ in xrange(target_len)] + self.synaptic_pre = self.synapses.variables['_synaptic_pre'] + self.synaptic_post = self.synapses.variables['_synaptic_post'] + self.pre_synaptic = self.synapses.pre_synaptic + self.post_synaptic = self.synapses.post_synaptic self._registered_variables = [] - self.variables = {'i': DynamicArrayVariable('i', - Unit(1), - self.synaptic_pre), - 'j': DynamicArrayVariable('j', - Unit(1), - self.synaptic_post)} - self.i = IndexView(self.synaptic_pre, self) - self.j = IndexView(self.synaptic_post, self) + self.variables = {'i': self.synapses.variables['_synaptic_pre'], + 'j': self.synapses.variables['_synaptic_post']} + self.i = IndexView(self.variables['i'], self) + self.j = IndexView(self.variables['j'], self) self.k = SynapseIndexView(self) - N = property(fget=lambda self: len(self.synaptic_pre), + N = property(fget=lambda self: self.variables['i'].get_len(), doc='Total number of synapses') def _resize(self, number): @@ -362,8 +357,8 @@ def _resize(self, number): raise ValueError(('Cannot reduce number of synapses, ' '{} < {}').format(number, self.N)) - self.synaptic_pre.resize(number) - self.synaptic_post.resize(number) + self.variables['i'].resize(number) + self.variables['j'].resize(number) for variable in self._registered_variables: variable.resize(number) @@ -448,7 +443,7 @@ def _add_synapses(self, sources, targets, n, p, condition=None, check_units=False ) codeobj() - number = len(self.synaptic_pre) + number = self.variables['i'].get_len() for variable in self._registered_variables: variable.resize(number) @@ -474,9 +469,9 @@ def __getitem__(self, index): I, J, K = index pre_synapses = find_synapses(I, self.pre_synaptic, - self.synaptic_pre) + self.variables['i'].get_value()) post_synapses = find_synapses(J, self.post_synaptic, - self.synaptic_post) + self.variables['j'].get_value()) matching_synapses = np.intersect1d(pre_synapses, post_synapses, assume_unique=True) @@ -638,9 +633,6 @@ def __init__(self, source, target=None, model=None, pre=None, post=None, self.equations = Equations(continuous) - ##### Setup the memory - self.arrays = self._allocate_memory(dtype=dtype) - # Setup the namespace self._given_namespace = namespace self.namespace = create_namespace(namespace) @@ -648,15 +640,6 @@ def __init__(self, source, target=None, model=None, pre=None, post=None, self._queues = {} self._delays = {} - self.item_mapping = SynapticItemMapping(self) - self.indices = {'_idx': self.item_mapping, - '_presynaptic_idx': self.item_mapping.synaptic_pre, - '_postsynaptic_idx': self.item_mapping.synaptic_post} - # Allow S.i instead of S.indices.i, etc. - self.i = self.item_mapping.i - self.j = self.item_mapping.j - self.k = self.item_mapping.k - # Make use of a special template when setting/indexing variables with # code in order to allow references to pre- and postsynaptic variables self._set_with_code_template = 'synaptic_variable_set' @@ -665,6 +648,21 @@ def __init__(self, source, target=None, model=None, pre=None, post=None, # Setup variables self.variables = self._create_variables() + self.item_mapping = SynapticItemMapping(self) + for var in self.variables.itervalues(): + if isinstance(var, DynamicArrayVariable): + # Register the array with the `SynapticItemMapping` object so + # it gets automatically resized + self.item_mapping.register_variable(var) + + self.indices = {'_idx': self.item_mapping, + '_presynaptic_idx': self.variables['_synaptic_pre'], + '_postsynaptic_idx': self.variables['_synaptic_post']} + # Allow S.i instead of S.indices.i, etc. + self.i = self.item_mapping.i + self.j = self.item_mapping.j + self.k = self.item_mapping.k + #: List of names of all updaters, e.g. ['pre', 'post'] self._synaptic_updaters = [] for prepost, argument in zip(('pre', 'post'), (pre, post)): @@ -820,11 +818,19 @@ def _add_updater(self, code, prepost, objname=None): self.contained_objects.append(updater) return objname - def _create_variables(self): + def _create_variables(self, dtype=None): ''' Create the variables dictionary for this `Synapses`, containing entries for the equation variables and some standard entries. ''' + if dtype is None: + dtype = defaultdict(lambda: brian_prefs['core.default_scalar_dtype']) + elif isinstance(dtype, np.dtype): + dtype = defaultdict(lambda: dtype) + elif not hasattr(dtype, '__getitem__'): + raise TypeError(('Cannot use type %s as dtype ' + 'specification') % type(dtype)) + # Add all the pre and post variables with _pre and _post suffixes v = {} self.variable_indices = defaultdict(lambda: '_idx') @@ -843,6 +849,12 @@ def _create_variables(self): v[name] = var self.variable_indices[name] = '_postsynaptic_idx' + self.pre_synaptic = [DynamicArray1D(0, dtype=np.int32) + for _ in xrange(len(self.source))] + self.post_synaptic = [DynamicArray1D(0, dtype=np.int32) + for _ in xrange(len(self.target))] + + dev = get_device() # Standard variables always present v.update({'t': AttributeVariable(second, self.clock, 't_', constant=False), @@ -856,47 +868,39 @@ def _create_variables(self): constant=True), '_target_offset': Variable(Unit(1), self.target.offset, constant=True), - '_synaptic_pre': DynamicArrayVariable('_synaptic_pre', - Unit(1), - self.item_mapping.synaptic_pre), - '_synaptic_post': DynamicArrayVariable('_synaptic_pre', - Unit(1), - self.item_mapping.synaptic_post), + '_synaptic_pre': dev.dynamic_array_1d(self, '_synaptic_pre', + 0, Unit(1), dtype=np.int32), + '_synaptic_post': dev.dynamic_array_1d(self, '_synaptic_post', + 0, Unit(1), dtype=np.int32), # We don't need "proper" specifier for these -- they go # back to Python code currently - '_pre_synaptic': Variable(Unit(1), self.item_mapping.pre_synaptic), - '_post_synaptic': Variable(Unit(1), - self.item_mapping.post_synaptic)}) + '_pre_synaptic': Variable(Unit(1), self.pre_synaptic), + '_post_synaptic': Variable(Unit(1), self.post_synaptic)}) for eq in itertools.chain(self.equations.itervalues(), 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) # We are dealing with dynamic arrays here, code generation # shouldn't directly access the specifier.array attribute but # use specifier.get_value() to get a reference to the underlying # array - v[eq.varname] = DynamicArrayVariable(eq.varname, + v[eq.varname] = dev.dynamic_array_1d(self, + eq.varname, + 0, eq.unit, - array, - group_name=self.name, + dtype=dtype[eq.varname], constant=constant, is_bool=eq.is_bool) if eq.varname in self.variable_indices: # we are overwriting a postsynaptic variable of the same # name, delete the reference to the postsynaptic index del self.variable_indices[eq.varname] - # Register the array with the `SynapticItemMapping` object so - # it gets automatically resized - self.item_mapping.register_variable(array) elif eq.type == STATIC_EQUATION: v.update({eq.varname: Subexpression(eq.unit, brian_prefs['core.default_scalar_dtype'], str(eq.expr), - variables=v, - namespace=self.namespace, is_bool=eq.is_bool)}) else: raise AssertionError('Unknown type of equation: ' + eq.eq_type) @@ -907,28 +911,6 @@ def _create_variables(self): return v - def _allocate_memory(self, dtype=None): - # Allocate memory (TODO: this should be refactored somewhere at some point) - arrayvarnames = set(eq.varname for eq in self.equations.itervalues() if - eq.type in (DIFFERENTIAL_EQUATION, - PARAMETER)) - if self.event_driven is not None: - # Only differential equations are event-driven - arrayvarnames |= set(eq.varname - for eq in self.event_driven.itervalues()) - - arrays = {} - for name in arrayvarnames: - if isinstance(dtype, dict): - curdtype = dtype[name] - else: - curdtype = dtype - if curdtype is None: - curdtype = brian_prefs['core.default_scalar_dtype'] - arrays[name] = DynamicArray1D(0) - logger.debug("NeuronGroup memory allocated successfully.") - return arrays - def connect(self, pre_or_cond, post=None, p=1., n=1, level=0): ''' Add synapses. The first argument can be either a presynaptic index diff --git a/brian2/tests/test_codegen.py b/brian2/tests/test_codegen.py index b76c596cf..3e2c35bda 100644 --- a/brian2/tests/test_codegen.py +++ b/brian2/tests/test_codegen.py @@ -26,12 +26,9 @@ def test_get_identifiers_recursively(): ''' Test finding identifiers including subexpressions. ''' - 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) + variables = {'sub1': Subexpression(Unit(1), np.float32, 'sub2 * z'), + 'sub2': Subexpression(Unit(1), np.float32, '5 + y'), + 'x': Variable(unit=None)} identifiers = get_identifiers_recursively('_x = sub1 + x', variables) assert identifiers == set(['x', '_x', 'y', 'z', 'sub1', 'sub2']) diff --git a/brian2/tests/test_refractory.py b/brian2/tests/test_refractory.py index 1d30bb5c9..51b11c01a 100644 --- a/brian2/tests/test_refractory.py +++ b/brian2/tests/test_refractory.py @@ -51,6 +51,7 @@ def test_refractoriness_variables(): mon = StateMonitor(G, ['v', 'w'], record=True) net = Network(G, mon) net.run(20*ms) + print 'mon.t', mon.t # No difference before the spike assert_equal(mon[0].v[mon.t < 10*ms], mon[0].w[mon.t < 10*ms]) # v is not updated during refractoriness diff --git a/dev/ideas/devices/cpp_standalone.py b/dev/ideas/devices/cpp_standalone.py index 3ee03655a..7f1f51ab3 100644 --- a/dev/ideas/devices/cpp_standalone.py +++ b/dev/ideas/devices/cpp_standalone.py @@ -34,7 +34,6 @@ M, #G2, ) - net.run(0*second) build(net) From e962720201c921b97e0b46b1b053761d2bb2d68c Mon Sep 17 00:00:00 2001 From: mstimberg Date: Thu, 19 Sep 2013 23:20:21 +0200 Subject: [PATCH 6/9] Some simplifications in the code --- brian2/devices/cpp_standalone/device.py | 6 ++---- brian2/groups/neurongroup.py | 5 ++++- brian2/groups/poissongroup.py | 5 ++++- brian2/monitors/ratemonitor.py | 8 ++++---- brian2/monitors/spikemonitor.py | 4 ++-- brian2/monitors/statemonitor.py | 14 +++++++------- brian2/synapses/synapses.py | 8 ++++---- 7 files changed, 27 insertions(+), 23 deletions(-) diff --git a/brian2/devices/cpp_standalone/device.py b/brian2/devices/cpp_standalone/device.py index ebcdee915..70e43fa03 100644 --- a/brian2/devices/cpp_standalone/device.py +++ b/brian2/devices/cpp_standalone/device.py @@ -3,14 +3,12 @@ import inspect from collections import defaultdict -from brian2.units import second from brian2.core.clocks import defaultclock -from brian2.devices.device import Device, set_device, all_devices +from brian2.devices.device import Device, all_devices from brian2.core.preferences import brian_prefs from brian2.core.variables import * from brian2.utils.filetools import copy_directory from brian2.utils.stringtools import word_substitute -from brian2.memory.dynamicarray import DynamicArray, DynamicArray1D from brian2.codegen.languages.cpp_lang import c_data_type from brian2.codegen.codeobject import CodeObjectUpdater @@ -162,7 +160,7 @@ def build(self, net): elif isinstance(v, Subexpression): pass elif not v.scalar: - N = v.get_len() + N = len(v) code_object_defs[codeobj.name].append('const int _num%s = %s;' % (k, N)) if isinstance(v, StandaloneDynamicArrayVariable): c_type = c_data_type(v.dtype) diff --git a/brian2/groups/neurongroup.py b/brian2/groups/neurongroup.py index e26700787..53db5066e 100644 --- a/brian2/groups/neurongroup.py +++ b/brian2/groups/neurongroup.py @@ -310,7 +310,10 @@ def spikes(self): ''' The spikes returned by the most recent thresholding operation. ''' - return self.variables['_spikespace'].get_value()[:self.variables['_spikespace'].get_value()[-1]] + # Note that we have to directly access the ArrayVariable object here + # instead of using the Group mechanism by accessing self._spikespace + # Using the latter would cut _spikespace to the length of the group + return self.variables['_spikespace'][:self.variables['_spikespace'][-1]] def __getitem__(self, item): if not isinstance(item, slice): diff --git a/brian2/groups/poissongroup.py b/brian2/groups/poissongroup.py index db5e9091c..5aa3dc1bc 100644 --- a/brian2/groups/poissongroup.py +++ b/brian2/groups/poissongroup.py @@ -94,7 +94,10 @@ def spikes(self): ''' The spikes returned by the most recent thresholding operation. ''' - return self.variables['_spikespace'].get_value()[:self.variables['_spikespace'].get_value()[-1]] + # Note that we have to directly access the ArrayVariable object here + # instead of using the Group mechanism by accessing self._spikespace + # Using the latter would cut _spikespace to the length of the group + return self.variables['_spikespace'][:self.variables['_spikespace'][-1]] def __len__(self): return self.N diff --git a/brian2/monitors/ratemonitor.py b/brian2/monitors/ratemonitor.py index 13d3f7b62..3ec6e4f25 100644 --- a/brian2/monitors/ratemonitor.py +++ b/brian2/monitors/ratemonitor.py @@ -83,8 +83,8 @@ def rate(self): ''' Array of recorded rates (in units of Hz). ''' - return Quantity(self.variables['_rate'].get_value().copy(), - dim=hertz.dim) + return Quantity(self.variables['_rate'].get_value(), dim=hertz.dim, + copy=True) @property def rate_(self): @@ -98,8 +98,8 @@ def t(self): ''' Array of recorded time points (in units of second). ''' - return Quantity(self.variables['_t'].get_value().copy(), - dim=second.dim) + return Quantity(self.variables['_t'].get_value(), dim=second.dim, + copy=True) @property def t_(self): diff --git a/brian2/monitors/spikemonitor.py b/brian2/monitors/spikemonitor.py index 14ebcb12a..1fbca0abf 100644 --- a/brian2/monitors/spikemonitor.py +++ b/brian2/monitors/spikemonitor.py @@ -100,8 +100,8 @@ def t(self): ''' Array of recorded spike times, with corresponding indices `i`. ''' - return Quantity(self.variables['_t'].get_value().copy(), - dim=second.dim) + return Quantity(self.variables['_t'].get_value(), dim=second.dim, + copy=True) @property def t_(self): diff --git a/brian2/monitors/statemonitor.py b/brian2/monitors/statemonitor.py index ad2d21c0b..5c7d12cdb 100644 --- a/brian2/monitors/statemonitor.py +++ b/brian2/monitors/statemonitor.py @@ -35,14 +35,14 @@ def __getattr__(self, item): mon = self.monitor if item == 't': - return Quantity(mon.variables['_t'].get_value().copy(), - dim=second.dim) + return Quantity(mon.variables['_t'].get_value(), dim=second.dim, + copy=True) elif item == 't_': return mon._t.data.copy() elif item in mon.record_variables: unit = mon.variables[item].unit - return Quantity(mon.variables['_recorded_'+item].get_value().T[self.indices].copy(), - dim=unit.dim) + return Quantity(mon.variables['_recorded_'+item].get_value().T[self.indices], + dim=unit.dim, copy=True) elif item.endswith('_') and item[:-1] in mon.record_variables: return mon.variables['_recorded_'+item[:-1]].get_value().T[self.indices].copy() else: @@ -246,14 +246,14 @@ def __getattr__(self, item): # TODO: Decide about the interface if item == 't': - return Quantity(self.variables['_t'].get_value().copy(), - dim=second.dim) + return Quantity(self.variables['_t'].get_value(), + dim=second.dim, copy=True) elif item == 't_': return self.variables['_t'].get_value().copy() elif item in self.record_variables: unit = self.variables[item].unit return Quantity(self.variables['_recorded_'+item].get_value().T, - dim=unit.dim) + dim=unit.dim, copy=True) elif item.endswith('_') and item[:-1] in self.record_variables: return self.variables['_recorded_'+item[:-1]].get_value().T else: diff --git a/brian2/synapses/synapses.py b/brian2/synapses/synapses.py index 59ad409cd..a1c0a7f9e 100644 --- a/brian2/synapses/synapses.py +++ b/brian2/synapses/synapses.py @@ -214,7 +214,7 @@ def push_spikes(self): if len(indices): if len(self._delays) > 1: - delays = np.round(self._delays.get_value()[indices] / self.dt).astype(int) + delays = np.round(self._delays[indices] / self.dt).astype(int) else: delays = np.round(self._delays.get_value() / self.dt).astype(int) self.queue.push(indices, delays) @@ -232,7 +232,7 @@ def __init__(self, indices, mapping): def __getitem__(self, item): try: - synaptic_indices = self.index.get_value()[self.mapping.get_value()[item]] + synaptic_indices = self.index[self.mapping.get_value()[item]] except TypeError as ex: print 'index', self.index raise ex @@ -346,7 +346,7 @@ def __init__(self, synapses): self.j = IndexView(self.variables['j'], self) self.k = SynapseIndexView(self) - N = property(fget=lambda self: self.variables['i'].get_len(), + N = property(fget=lambda self: len(self.variables['i']), doc='Total number of synapses') def _resize(self, number): @@ -443,7 +443,7 @@ def _add_synapses(self, sources, targets, n, p, condition=None, check_units=False ) codeobj() - number = self.variables['i'].get_len() + number = len(self.variables['i']) for variable in self._registered_variables: variable.resize(number) From 399479e4b773b5cab8ca3b0e6ef57ed889d0519f Mon Sep 17 00:00:00 2001 From: Marcel Stimberg Date: Mon, 23 Sep 2013 15:42:10 +0200 Subject: [PATCH 7/9] Rename synapses_push_spikes template to end in .py_ as the other templates --- .../{synapses_push_spikes => synapses_push_spikes.py_} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename brian2/codegen/runtime/numpy_rt/templates/{synapses_push_spikes => synapses_push_spikes.py_} (95%) diff --git a/brian2/codegen/runtime/numpy_rt/templates/synapses_push_spikes b/brian2/codegen/runtime/numpy_rt/templates/synapses_push_spikes.py_ similarity index 95% rename from brian2/codegen/runtime/numpy_rt/templates/synapses_push_spikes rename to brian2/codegen/runtime/numpy_rt/templates/synapses_push_spikes.py_ index a574c5818..34e992d13 100644 --- a/brian2/codegen/runtime/numpy_rt/templates/synapses_push_spikes +++ b/brian2/codegen/runtime/numpy_rt/templates/synapses_push_spikes.py_ @@ -1 +1 @@ -_owner.push_spikes() +_owner.push_spikes() From e7577288c6c8c1c2077ad8b3f83dffa1709971c8 Mon Sep 17 00:00:00 2001 From: Marcel Stimberg Date: Mon, 23 Sep 2013 15:55:41 +0200 Subject: [PATCH 8/9] Derive Group from BrianObject (no object derived from Group without also deriving from BrianObject). Rename Group.__init__ to Group._enable_group_attributes to avoid confusion between the two constructors --- brian2/groups/group.py | 7 +++---- brian2/groups/neurongroup.py | 7 +++---- brian2/groups/poissongroup.py | 7 +++---- brian2/groups/subgroup.py | 9 +++------ brian2/monitors/ratemonitor.py | 4 +--- brian2/monitors/statemonitor.py | 2 +- brian2/synapses/synapses.py | 8 ++++---- 7 files changed, 18 insertions(+), 26 deletions(-) diff --git a/brian2/groups/group.py b/brian2/groups/group.py index 193d1aea0..ee52674b7 100644 --- a/brian2/groups/group.py +++ b/brian2/groups/group.py @@ -13,7 +13,6 @@ from brian2.core.namespace import get_local_namespace from brian2.units.fundamentalunits import fail_for_dimension_mismatch, Unit from brian2.units.allunits import second -from brian2.codegen.codeobject import create_codeobject from brian2.codegen.translation import analyse_identifiers from brian2.equations.unitcheck import check_units_statements from brian2.utils.logger import get_logger @@ -77,14 +76,14 @@ def __getitem__(self, index): return self._indices[index_array + self.offset] -class Group(object): +class Group(BrianObject): ''' Mix-in class for accessing arrays by attribute. # TODO: Overwrite the __dir__ method to return the state variables # (should make autocompletion work) ''' - def __init__(self): + def _enable_group_attributes(self): if not hasattr(self, 'offset'): self.offset = 0 if not hasattr(self, 'variables'): @@ -159,7 +158,7 @@ def __getattr__(self, name): def __setattr__(self, name, val): # attribute access is switched off until this attribute is created by - # Group.__init__ + # _enable_group_attributes if not hasattr(self, '_group_attribute_access_active'): object.__setattr__(self, name, val) elif name in self.variables: diff --git a/brian2/groups/neurongroup.py b/brian2/groups/neurongroup.py index 53db5066e..4a3a1cb82 100644 --- a/brian2/groups/neurongroup.py +++ b/brian2/groups/neurongroup.py @@ -12,7 +12,6 @@ from brian2.stateupdaters.base import StateUpdateMethod from brian2.devices.device import get_device from brian2.core.preferences import brian_prefs -from brian2.core.base import BrianObject from brian2.core.namespace import create_namespace from brian2.core.variables import (StochasticVariable, Subexpression) from brian2.core.spikesource import SpikeSource @@ -146,7 +145,7 @@ def update_abstract_code(self): self.abstract_code = self.group.reset -class NeuronGroup(BrianObject, Group, SpikeSource): +class NeuronGroup(Group, SpikeSource): ''' A group of neurons. @@ -206,7 +205,7 @@ def __init__(self, N, model, method=None, dtype=None, clock=None, name='neurongroup*', codeobj_class=None): - BrianObject.__init__(self, when=clock, name=name) + Group.__init__(self, when=clock, name=name) self.codeobj_class = codeobj_class @@ -293,7 +292,7 @@ def __init__(self, N, model, method=None, self.contained_objects.append(self.resetter) # Activate name attribute access - Group.__init__(self) + self._enable_group_attributes() # Set the refractoriness information self.lastspike = -np.inf*second diff --git a/brian2/groups/poissongroup.py b/brian2/groups/poissongroup.py index 5aa3dc1bc..82a7d40b9 100644 --- a/brian2/groups/poissongroup.py +++ b/brian2/groups/poissongroup.py @@ -1,6 +1,5 @@ import numpy as np -from brian2.core.base import BrianObject from brian2.core.namespace import create_namespace from brian2.core.spikesource import SpikeSource from brian2.core.variables import ArrayVariable @@ -15,7 +14,7 @@ __all__ = ['PoissonGroup'] -class PoissonGroup(Group, BrianObject, SpikeSource): +class PoissonGroup(Group, SpikeSource): ''' Poisson spike source @@ -40,7 +39,7 @@ class PoissonGroup(Group, BrianObject, SpikeSource): def __init__(self, N, rates, clock=None, name='poissongroup*', codeobj_class=None): - BrianObject.__init__(self, when=clock, name=name) + Group.__init__(self, when=clock, name=name) self.codeobj_class = codeobj_class @@ -87,7 +86,7 @@ def __init__(self, N, rates, clock=None, name='poissongroup*', self._refractory = False self.state_updater = StateUpdater(self, method='independent') self.contained_objects.append(self.state_updater) - Group.__init__(self) + self._enable_group_attributes() @property def spikes(self): diff --git a/brian2/groups/subgroup.py b/brian2/groups/subgroup.py index 906dcf978..be5b8971d 100644 --- a/brian2/groups/subgroup.py +++ b/brian2/groups/subgroup.py @@ -1,8 +1,5 @@ import weakref -import numpy as np - -from brian2.core.base import BrianObject from brian2.core.spikesource import SpikeSource from brian2.core.scheduler import Scheduler from brian2.groups.group import Group @@ -10,7 +7,7 @@ __all__ = ['Subgroup'] -class Subgroup(Group, BrianObject, SpikeSource): +class Subgroup(Group, SpikeSource): ''' Subgroup of any `Group` @@ -45,7 +42,7 @@ def __init__(self, source, start, end, name=None): # parent threshold operation schedule = Scheduler(clock=source.clock, when='thresholds', order=source.order+1) - BrianObject.__init__(self, when=schedule, name=name) + Group.__init__(self, when=schedule, name=name) self.N = end-start self.start = start self.end = end @@ -56,7 +53,7 @@ def __init__(self, source, start, end, name=None): self.namespace = self.source.namespace self.codeobj_class = self.source.codeobj_class - Group.__init__(self) + self._enable_group_attributes() # Make the spikes from the source group accessible spikes = property(lambda self: self.source.spikes) diff --git a/brian2/monitors/ratemonitor.py b/brian2/monitors/ratemonitor.py index 3ec6e4f25..807da39c3 100644 --- a/brian2/monitors/ratemonitor.py +++ b/brian2/monitors/ratemonitor.py @@ -4,10 +4,8 @@ import numpy as np from brian2.core.base import BrianObject -from brian2.core.preferences import brian_prefs from brian2.core.scheduler import Scheduler -from brian2.core.variables import (Variable, AttributeVariable, - DynamicArrayVariable) +from brian2.core.variables import (Variable, AttributeVariable) from brian2.units.allunits import second, hertz from brian2.units.fundamentalunits import Unit, Quantity from brian2.devices.device import get_device diff --git a/brian2/monitors/statemonitor.py b/brian2/monitors/statemonitor.py index 5c7d12cdb..7e620f225 100644 --- a/brian2/monitors/statemonitor.py +++ b/brian2/monitors/statemonitor.py @@ -7,7 +7,7 @@ from brian2.core.base import BrianObject from brian2.core.scheduler import Scheduler from brian2.devices.device import get_device -from brian2.units.fundamentalunits import Unit, Quantity, have_same_dimensions +from brian2.units.fundamentalunits import Unit, Quantity from brian2.units.allunits import second from brian2.groups.group import create_runner_codeobj diff --git a/brian2/synapses/synapses.py b/brian2/synapses/synapses.py index a1c0a7f9e..de23af409 100644 --- a/brian2/synapses/synapses.py +++ b/brian2/synapses/synapses.py @@ -165,7 +165,7 @@ def __init__(self, synapses, code, prepost, objname=None): self.indices = self.synapses.indices # Enable access to the delay attribute via the specifier - Group.__init__(self) + self._enable_group_attributes() def update_abstract_code(self): if self.synapses.event_driven is not None: @@ -518,7 +518,7 @@ def __getitem__(self, index): raise IndexError('Unsupported index type {itype}'.format(itype=type(index))) -class Synapses(BrianObject, Group): +class Synapses(Group): ''' Class representing synaptic connections. Creating a new `Synapses` object does by default not create any synapses -- you either have to provide @@ -586,7 +586,7 @@ def __init__(self, source, target=None, model=None, pre=None, post=None, codeobj_class=None, clock=None, method=None, name='synapses*'): - BrianObject.__init__(self, when=clock, name=name) + Group.__init__(self, when=clock, name=name) self.codeobj_class = codeobj_class @@ -759,7 +759,7 @@ def __init__(self, source, target=None, model=None, pre=None, post=None, self.connect(connect, level=1) # Activate name attribute access - Group.__init__(self) + self._enable_group_attributes() N = property(fget=lambda self: self.item_mapping.N, doc='Total number of synapses') From 81356e967e6732a828ecad6ae7f3e4ba907b34b8 Mon Sep 17 00:00:00 2001 From: Marcel Stimberg Date: Mon, 23 Sep 2013 16:11:41 +0200 Subject: [PATCH 9/9] Remove the restriction that Clock.dt can only be set once -- state updaters etc. all work fine with changing dt. Closes #84 --- brian2/core/clocks.py | 25 ++++++------------- brian2/tests/test_clocks.py | 4 +-- .../developer/new_magic_and_clocks.rst | 17 +++++-------- 3 files changed, 14 insertions(+), 32 deletions(-) diff --git a/brian2/core/clocks.py b/brian2/core/clocks.py index 30aa1217b..65580b1b3 100644 --- a/brian2/core/clocks.py +++ b/brian2/core/clocks.py @@ -8,7 +8,7 @@ from brian2.utils.logger import get_logger from brian2.core.names import Nameable -from brian2.units.fundamentalunits import check_units +from brian2.units.fundamentalunits import check_units, Quantity from brian2.units.allunits import second, msecond __all__ = ['Clock', 'defaultclock'] @@ -40,12 +40,12 @@ class Clock(Nameable): ''' @check_units(dt=second) - def __init__(self, dt=None, name='clock*'): - self._dt_spec = dt + def __init__(self, dt=0.1*msecond, name='clock*'): + self._dt = float(dt) self.i = 0 #: The time step of the simulation as an integer. self.i_end = 0 #: The time step the simulation will end as an integer Nameable.__init__(self, name=name) - logger.debug("Created clock {self.name} with dt={self._dt_spec}".format(self=self)) + logger.debug("Created clock {self.name} with dt={self._dt}".format(self=self)) def reinit(self): ''' @@ -77,18 +77,9 @@ def _set_t_end(self, end): self.i_end = int(float(end) / self.dt_) def _get_dt_(self): - if hasattr(self, '_dt'): - return self._dt - else: - dtspec = self._dt_spec - if dtspec is None: - dtspec = 0.1*msecond - self._dt = float(dtspec) - return self._dt + return self._dt def _set_dt_(self, dt_): - if hasattr(self, '_dt'): - raise RuntimeError("Cannot change dt, it has already been set to "+str(self.dt)) self._dt = dt_ logger.debug("Set dt for clock {self.name} to {self.dt}".format(self=self)) @@ -96,11 +87,9 @@ def _set_dt_(self, dt_): def _set_dt(self, dt): self.dt_ = float(dt) - dt = property(fget=lambda self: self.dt_*second, + dt = property(fget=lambda self: Quantity(self.dt_, dim=second.dim), fset=_set_dt, - doc='''The time step of the simulation in seconds - Returns a `Quantity`, and can only - be set once. Defaults to ``0.1*ms``.''', + doc='''The time step of the simulation in seconds.''', ) dt_ = property(fget=_get_dt_, fset=_set_dt_, diff --git a/brian2/tests/test_clocks.py b/brian2/tests/test_clocks.py index eaa3b6648..d0432566f 100644 --- a/brian2/tests/test_clocks.py +++ b/brian2/tests/test_clocks.py @@ -17,7 +17,6 @@ def test_clocks(): assert_equal(clock.i_end, 100) clock.i_end = 200 assert_equal(clock.t_end, 200*ms) - assert_raises(RuntimeError, lambda: setattr(clock, 'dt', 2*ms)) clock.t_ = float(8*ms) assert_equal(clock.i, 8) clock.t = 0*ms @@ -31,15 +30,14 @@ def test_clocks(): assert_equal(clock.t, 0*ms) clock = Clock() + assert_equal(clock.dt, 0.1*ms) clock.dt = 1*ms assert_equal(clock.dt, 1*ms) - assert_raises(RuntimeError, lambda: setattr(clock, 'dt', 2*ms)) @with_setup(teardown=restore_initial_state) def test_defaultclock(): defaultclock.dt = 1*ms assert_equal(defaultclock.dt, 1*ms) - assert_raises(RuntimeError, lambda: setattr(defaultclock, 'dt', 2*ms)) if __name__=='__main__': test_clocks() diff --git a/docs_sphinx/developer/new_magic_and_clocks.rst b/docs_sphinx/developer/new_magic_and_clocks.rst index 3c99d3894..439e137a2 100644 --- a/docs_sphinx/developer/new_magic_and_clocks.rst +++ b/docs_sphinx/developer/new_magic_and_clocks.rst @@ -4,7 +4,7 @@ New magic and clock behaviour Clocks ------ -The rule for clocks was that +The rule for clocks in Brian 1 was that you would either specify a clock explicitly, or it would guess it based on the following rule: if there is no clock defined in the execution frame of the object being defined, use the default clock; if there is a single clock @@ -21,16 +21,11 @@ introduce. Incidentally, you could also change the dt of a clock after it had been defined, which would invalidate any state updaters that -were based on a fixed dt. Consequently, in the new design you cannot change the -dt of a clock once it has been specified. This has one huge problem, it would -mean that you couldn't change the dt of the defaultclock, which would be too -annoying in practice. So, you can now leave the dt of a clock unspecified when -you create the clock, and it can be left unspecified until the .dt attribute -is accessed. If no value is specified, it uses ``dt=0.1*ms``, and the .dt -attribute can be set precisely once (if it was left unspecified when created). -This means you can now set ``defaultclock.dt`` precisely once. Again, this is -slightly less flexible than the old system, but avoids many potentially -confusing bugs. +were based on a fixed dt. This is no longer a problem in Brian 2, since state +updaters are re-built at every run so they work fine with a changed dt. It is +important to note that the dt of the respective clock (i.e. in many cases, +``defaultclock.dt``) at the time of the `run` call, not the dt during +the `NeuronGroup` creation, for example, is relevant for the simulation. Magic -----