Ejemplo n.º 1
0
 def __init__(self, group, when='thresholds', event='spike'):
     self.event = event
     if group._refractory is False or event != 'spike':
         template_kwds = {'_uses_refractory': False}
         needed_variables = []
     else:
         template_kwds = {'_uses_refractory': True}
         needed_variables = ['t', 'not_refractory', 'lastspike']
     # Since this now works for general events not only spikes, we have to
     # pass the information about which variable to use to the template,
     # it can not longer simply refer to "_spikespace"
     eventspace_name = '_{}space'.format(event)
     template_kwds['eventspace_variable'] = group.variables[eventspace_name]
     needed_variables.append(eventspace_name)
     self.variables = Variables(self)
     self.variables.add_auxiliary_variable('_cond', dtype=np.bool)
     CodeRunner.__init__(
         self,
         group,
         'threshold',
         code='',  # will be set in update_abstract_code
         clock=group.clock,
         when=when,
         order=group.order,
         name=group.name + '_thresholder*',
         needed_variables=needed_variables,
         template_kwds=template_kwds)
Ejemplo n.º 2
0
 def __init__(self, dt, name='clock*'):
     # We need a name right away because some devices (e.g. cpp_standalone)
     # need a name for the object when creating the variables
     Nameable.__init__(self, name=name)
     self._old_dt = None
     self.variables = Variables(self)
     self.variables.add_array('timestep',
                              size=1,
                              dtype=np.int64,
                              read_only=True,
                              scalar=True)
     self.variables.add_array('t',
                              dimensions=second.dim,
                              size=1,
                              dtype=np.double,
                              read_only=True,
                              scalar=True)
     self.variables.add_array('dt',
                              dimensions=second.dim,
                              size=1,
                              values=float(dt),
                              dtype=np.float,
                              read_only=True,
                              constant=True,
                              scalar=True)
     self.variables.add_constant('N', value=1)
     self._enable_group_attributes()
     self.dt = dt
     logger.diagnostic("Created clock {name} with dt={dt}".format(
         name=self.name, dt=self.dt))
Ejemplo n.º 3
0
    def __init__(self, source, name='ratemonitor*', codeobj_class=None,
                 dtype=np.float64):

        #: The group we are recording from
        self.source = source

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self, group=self, code='', template='ratemonitor',
                            clock=source.clock, when='end', order=0, name=name)

        self.add_dependency(source)

        self.variables = Variables(self)
        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))
        self.variables.add_constant('_source_start', start)
        self.variables.add_constant('_source_stop', stop)
        self.variables.add_reference('_spikespace', source)
        self.variables.add_dynamic_array('rate', size=0, dimensions=hertz.dim,
                                         read_only=True, dtype=dtype)
        self.variables.add_dynamic_array('t', size=0, dimensions=second.dim,
                                         read_only=True,
                                         dtype=self._clock.variables['t'].dtype)
        self.variables.add_reference('_num_source_neurons', source, 'N')
        self.variables.add_array('N', dtype=np.int32, size=1,
                                 scalar=True, read_only=True)
        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')
        self._enable_group_attributes()
Ejemplo n.º 4
0
 def __init__(self, dt, name='clock*'):
     # We need a name right away because some devices (e.g. cpp_standalone)
     # need a name for the object when creating the variables
     Nameable.__init__(self, name=name)
     #: Note that right after a change of dt, this
     #: will not equal the new dt (which is stored in `Clock._new_dt`). Call
     #: `Clock._set_t_update_t` to update the internal clock representation.
     self._new_dt = None
     self.variables = Variables(self)
     self.variables.add_array('timestep',
                              unit=Unit(1),
                              size=1,
                              dtype=np.uint64,
                              read_only=True,
                              scalar=True)
     self.variables.add_array('t',
                              unit=second,
                              size=1,
                              dtype=np.double,
                              read_only=True,
                              scalar=True)
     self.variables.add_array('dt',
                              unit=second,
                              size=1,
                              values=float(dt),
                              dtype=np.float,
                              read_only=True,
                              constant=True,
                              scalar=True)
     self.variables.add_constant('N', unit=Unit(1), value=1)
     self._enable_group_attributes()
     self.dt = dt
     logger.diagnostic("Created clock {name} with dt={dt}".format(
         name=self.name, dt=self.dt))
Ejemplo n.º 5
0
    def __init__(self,
                 N,
                 rates,
                 dt=None,
                 clock=None,
                 when='thresholds',
                 order=0,
                 namespace=None,
                 name='poissongroup*',
                 codeobj_class=None):

        Group.__init__(self,
                       dt=dt,
                       clock=clock,
                       when=when,
                       order=order,
                       namespace=namespace,
                       name=name)

        self.codeobj_class = codeobj_class

        self._N = N = int(N)

        # TODO: In principle, it would be nice to support Poisson groups with
        # refactoriness, but we can't currently, since the refractoriness
        # information is reset in the state updater which we are not using
        # We could either use a specific template or simply not bother and make
        # users write their own NeuronGroup (with threshold rand() < rates*dt)
        # for more complex use cases.

        self.variables = Variables(self)
        # standard variables
        self.variables.add_constant('N', value=self._N)
        self.variables.add_arange('i', self._N, constant=True, read_only=True)
        self.variables.add_array('_spikespace', size=N + 1, dtype=np.int32)
        self.variables.create_clock_variables(self._clock)

        # The firing rates
        if isinstance(rates, str):
            self.variables.add_subexpression('rates',
                                             dimensions=Hz.dim,
                                             expr=rates)
        else:
            self.variables.add_array('rates', size=N, dimensions=Hz.dim)
        self._rates = rates

        self.start = 0
        self.stop = N

        self._refractory = False

        self.events = {'spike': 'rand() < rates * dt'}
        self.thresholder = {'spike': Thresholder(self)}
        self.contained_objects.append(self.thresholder['spike'])

        self._enable_group_attributes()

        if not isinstance(rates, str):
            self.rates = rates
Ejemplo n.º 6
0
    def __init__(self,
                 N,
                 rates,
                 dt=None,
                 clock=None,
                 when='thresholds',
                 order=0,
                 name='poissongroup*',
                 codeobj_class=None):

        Group.__init__(self,
                       dt=dt,
                       clock=clock,
                       when=when,
                       order=order,
                       name=name)

        self.codeobj_class = codeobj_class

        self._N = N = int(N)

        # TODO: In principle, it would be nice to support Poisson groups with
        # refactoriness, but we can't currently, since the refractoriness
        # information is reset in the state updater which we are not using
        # We could either use a specific template or simply not bother and make
        # users write their own NeuronGroup (with threshold rand() < rates*dt)
        # for more complex use cases.

        self.variables = Variables(self)
        # standard variables
        self.variables.add_constant('N', unit=Unit(1), value=self._N)
        self.variables.add_arange('i', self._N, constant=True, read_only=True)
        self.variables.add_array('_spikespace',
                                 size=N + 1,
                                 unit=Unit(1),
                                 dtype=np.int32)
        self.variables.create_clock_variables(self._clock)

        # The firing rates
        self.variables.add_array('rates', size=N, unit=Hz)

        self.start = 0
        self.stop = N

        self._refractory = False

        # To avoid a warning about the local variable rates, we set the real
        # threshold condition only after creating the object
        self.events = {'spike': 'False'}
        self.thresholder = {'spike': Thresholder(self)}
        self.events = {'spike': 'rand() < rates * dt'}
        self.contained_objects.append(self.thresholder['spike'])

        self._enable_group_attributes()

        # Here we want to use the local namespace, but at the level where the
        # constructor was called
        self.rates.set_item(slice(None), rates, level=2)
Ejemplo n.º 7
0
    def __init__(self,
                 N,
                 indices,
                 times,
                 when=None,
                 name='spikegeneratorgroup*',
                 codeobj_class=None):
        if when is None:
            when = Scheduler(when='thresholds')
        Group.__init__(self, when=when, name=name)

        self.codeobj_class = codeobj_class

        if N < 1 or int(N) != N:
            raise ValueError('N has to be an integer >=1.')

        if len(indices) != len(times):
            raise ValueError(
                ('Length of the indices and times array must '
                 'match, but %d != %d') % (len(indices), len(times)))

        self.start = 0
        self.stop = N

        # sort times and indices first by time, then by indices
        rec = np.rec.fromarrays([times, indices], names=['t', 'i'])
        rec.sort()
        times = np.ascontiguousarray(rec.t)
        indices = np.ascontiguousarray(rec.i)

        self.variables = Variables(self)

        # standard variables
        self.variables.add_clock_variables(self.clock)
        self.variables.add_constant('N', unit=Unit(1), value=N)
        self.variables.add_arange('i', N)
        self.variables.add_arange('spike_number', len(indices))
        self.variables.add_array('neuron_index',
                                 values=indices,
                                 size=len(indices),
                                 unit=Unit(1),
                                 dtype=np.int32,
                                 index='spike_number',
                                 read_only=True)
        self.variables.add_array('spike_time',
                                 values=times,
                                 size=len(times),
                                 unit=second,
                                 index='spike_number',
                                 read_only=True)
        self.variables.add_array('_spikespace',
                                 size=N + 1,
                                 unit=Unit(1),
                                 dtype=np.int32)
        # Activate name attribute access
        self._enable_group_attributes()

        CodeRunner.__init__(self, self, 'spikegenerator', when=when, name=None)
Ejemplo n.º 8
0
    def __init__(self, source, start, stop, name=None):
        # First check if the source is itself a Subgroup
        # If so, then make this a Subgroup of the original Group
        if isinstance(source, Subgroup):
            source = source.source
            start = start + source.start
            stop = stop + source.start
            self.source = source
        else:
            self.source = weakproxy_with_fallback(source)

        if name is None:
            name = source.name + '_subgroup*'
        # We want to update the spikes attribute after it has been updated
        # by the parent, we do this in slot 'thresholds' with an order
        # one higher than the parent order to ensure it takes place after the
        # parent threshold operation
        schedule = Scheduler(clock=source.clock,
                             when='thresholds',
                             order=source.order + 1)
        Group.__init__(self, when=schedule, name=name)
        self._N = stop - start
        self.start = start
        self.stop = stop

        # All the variables have to go via the _sub_idx to refer to the
        # appropriate values in the source group
        self.variables = Variables(self, default_index='_sub_idx')

        # overwrite the meaning of N and i
        self.variables.add_constant('_offset', unit=Unit(1), value=self.start)
        self.variables.add_reference('_source_i', source, 'i')
        self.variables.add_subexpression('i',
                                         unit=Unit(1),
                                         dtype=source.variables['i'].dtype,
                                         expr='_source_i - _offset')
        self.variables.add_constant('N', unit=Unit(1), value=self._N)
        # add references for all variables in the original group
        self.variables.add_references(source, source.variables.keys())

        # Only the variable _sub_idx itself is stored in the subgroup
        # and needs the normal index for this group
        self.variables.add_arange('_sub_idx',
                                  size=self._N,
                                  start=self.start,
                                  index='_idx')

        for key, value in self.source.variables.indices.iteritems():
            if value not in ('_idx', '0'):
                raise ValueError(
                    ('Do not know how to deal with variable %s '
                     'using  index %s in a subgroup') % (key, value))

        self.namespace = self.source.namespace
        self.codeobj_class = self.source.codeobj_class

        self._enable_group_attributes()
Ejemplo n.º 9
0
    def __init__(self,
                 target,
                 target_var,
                 N,
                 rate,
                 weight,
                 when='synapses',
                 order=0):
        if target_var not in target.variables:
            raise KeyError('%s is not a variable of %s' %
                           (target_var, target.name))

        self._weight = weight
        self._target_var = target_var

        if isinstance(weight, str):
            weight = '(%s)' % weight
        else:
            weight_dims = get_dimensions(weight)
            target_dims = target.variables[target_var].dim
            # This will be checked automatically in the abstract code as well
            # but doing an explicit check here allows for a clearer error
            # message
            if not have_same_dimensions(weight_dims, target_dims):
                raise DimensionMismatchError(
                    ('The provided weight does not '
                     'have the same unit as the '
                     'target variable "%s"') % target_var, weight_dims,
                    target_dims)
            weight = repr(weight)
        self._N = N
        self._rate = rate
        binomial_sampling = BinomialFunction(N,
                                             rate * target.clock.dt,
                                             name='poissoninput_binomial*')

        code = '{targetvar} += {binomial}()*{weight}'.format(
            targetvar=target_var,
            binomial=binomial_sampling.name,
            weight=weight)
        self._stored_dt = target.dt_[:]  # make a copy
        # FIXME: we need an explicit reference here for on-the-fly subgroups
        # For example: PoissonInput(group[:N], ...)
        self._group = target
        CodeRunner.__init__(self,
                            group=target,
                            template='stateupdate',
                            code=code,
                            user_code='',
                            when=when,
                            order=order,
                            name='poissoninput*',
                            clock=target.clock)
        self.variables = Variables(self)
        self.variables._add_variable(binomial_sampling.name, binomial_sampling)
Ejemplo n.º 10
0
    def __init__(self,
                 source,
                 record=True,
                 when=None,
                 name='spikemonitor*',
                 codeobj_class=None):
        self.record = bool(record)
        #: The source we are recording from
        self.source = source

        # run by default on source clock at the end
        scheduler = Scheduler(when)
        if not scheduler.defined_clock:
            scheduler.clock = source.clock
        if not scheduler.defined_when:
            scheduler.when = 'end'

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self,
                            group=self,
                            template='spikemonitor',
                            name=name,
                            when=scheduler)

        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))

        self.variables = Variables(self)
        self.variables.add_clock_variables(scheduler.clock, prefix='_clock_')
        self.variables.add_reference('_spikespace',
                                     source.variables['_spikespace'])
        self.variables.add_dynamic_array('i',
                                         size=0,
                                         unit=Unit(1),
                                         dtype=np.int32,
                                         constant_size=False)
        self.variables.add_dynamic_array('t',
                                         size=0,
                                         unit=second,
                                         constant_size=False)
        self.variables.add_array('_count',
                                 size=len(source),
                                 unit=Unit(1),
                                 dtype=np.int32)
        self.variables.add_constant('_source_start', Unit(1), start)
        self.variables.add_constant('_source_stop', Unit(1), stop)
        self.variables.add_attribute_variable('N',
                                              unit=Unit(1),
                                              obj=self,
                                              attribute='_N',
                                              dtype=np.int32)
        self._N = 0

        self._enable_group_attributes()
Ejemplo n.º 11
0
    def __init__(self,
                 source,
                 record=True,
                 when='end',
                 order=0,
                 name='spikemonitor*',
                 codeobj_class=None):
        self.record = bool(record)
        #: The source we are recording from
        self.source = source

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self,
                            group=self,
                            code='',
                            template='spikemonitor',
                            name=name,
                            clock=source.clock,
                            when=when,
                            order=order)

        self.add_dependency(source)

        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))

        self.variables = Variables(self)
        self.variables.add_reference('_spikespace', source)
        self.variables.add_dynamic_array('i',
                                         size=0,
                                         unit=Unit(1),
                                         dtype=np.int32,
                                         constant_size=False)
        self.variables.add_dynamic_array('t',
                                         size=0,
                                         unit=second,
                                         constant_size=False)
        self.variables.add_arange('_source_i', size=len(source))
        self.variables.add_array('_count',
                                 size=len(source),
                                 unit=Unit(1),
                                 dtype=np.int32,
                                 read_only=True,
                                 index='_source_i')
        self.variables.add_constant('_source_start', Unit(1), start)
        self.variables.add_constant('_source_stop', Unit(1), stop)
        self.variables.add_attribute_variable('N',
                                              unit=Unit(1),
                                              obj=self,
                                              attribute='_N',
                                              dtype=np.int32)
        self.variables.create_clock_variables(self._clock, prefix='_clock_')
        self._enable_group_attributes()
Ejemplo n.º 12
0
    def set_with_expression_conditional(self, varname, variable, cond,
                                        code, check_units=True, level=0,
                                        run_namespace=None):
        '''
        Sets a variable using a string expression and string condition. Is
        called by `VariableView.set_item` for statements such as
        ``S.var['i!=j'] = 'exp(-abs(i-j)/space_constant)*nS'``

        Parameters
        ----------
        varname : str
            The name of the variable to be set.
        variable : `ArrayVariable`
            The `ArrayVariable` object for the variable to be set.
        cond : str
            The string condition for which the variables should be set.
        code : str
            The code that should be executed to set the variable values.
        check_units : bool, optional
            Whether to check the units of the expression.
        level : int, optional
            How much farther to go up in the stack to find the implicit
            namespace (if used, see `run_namespace`).
        run_namespace : dict-like, optional
            An additional namespace that is used for variable lookup (if not
            defined, the implicit namespace of local variables is used).
        '''
        if variable.scalar and cond != 'True':
            raise IndexError(('Cannot conditionally set the scalar variable '
                              '%s.') % varname)
        abstract_code_cond = '_cond = '+cond
        abstract_code = varname + ' = ' + code
        variables = Variables(None)
        variables.add_auxiliary_variable('_cond', unit=Unit(1), dtype=np.bool)
        check_code_units(abstract_code_cond, self,
                         additional_variables=variables,
                         level=level+2,
                         run_namespace=run_namespace)
        # TODO: Have an additional argument to avoid going through the index
        # array for situations where iterate_all could be used
        codeobj = create_runner_codeobj(self,
                                        {'condition': abstract_code_cond,
                                         'statement': abstract_code},
                                        'group_variable_set_conditional',
                                        additional_variables=variables,
                                        check_units=check_units,
                                        level=level+2,
                                        run_namespace=run_namespace)
        codeobj()
Ejemplo n.º 13
0
    def get_with_expression(self, group, variable_name, variable, code, level=0):
        '''
        Gets a variable using a string expression. Is called by
        `VariableView.get_item` for statements such as
        ``print G.v['g_syn > 0']``.

        Parameters
        ----------
        group : `Group`
            The group providing the context for the indexing.
        variable_name : str
            The name of the variable in its context (e.g. ``'g_post'`` for a
            variable with name ``'g'``)
        variable : `ArrayVariable`
            The `ArrayVariable` object for the variable to be set
        code : str
            An expression that states a condition for elements that should be
            selected. Can contain references to indices, such as ``i`` or ``j``
            and to state variables. For example: ``'i>3 and v>0*mV'``.
        level : int, optional
            How much farther to go up in the stack to find the namespace.
        '''
        # interpret the string expression
        namespace = get_local_namespace(level+1)
        additional_namespace = ('implicit-namespace', namespace)
        # Add the recorded variable under a known name to the variables
        # dictionary. Important to deal correctly with
        # the type of the variable in C++
        variables = Variables(None)
        variables.add_auxiliary_variable('_variable', unit=variable.unit,
                                         dtype=variable.dtype,
                                         scalar=variable.scalar,
                                         is_bool=variable.is_bool)
        variables.add_auxiliary_variable('_cond', unit=Unit(1), dtype=np.bool,
                                         is_bool=True)

        abstract_code = '_variable = ' + variable_name + '\n'
        abstract_code += '_cond = ' + code
        check_code_units(abstract_code, group,
                         additional_namespace=additional_namespace,
                         additional_variables=variables)
        codeobj = create_runner_codeobj(group,
                                        abstract_code,
                                        'group_variable_get_conditional',
                                        additional_variables=variables,
                                        additional_namespace=additional_namespace,
                                        )
        return codeobj()
Ejemplo n.º 14
0
 def __init__(self, group, when='thresholds', event='spike'):
     self.event = event
     if group._refractory is False or event != 'spike':
         template_kwds = {'_uses_refractory': False}
         needed_variables = []
     else:
         template_kwds = {'_uses_refractory': True}
         needed_variables=['t', 'not_refractory', 'lastspike']
     # Since this now works for general events not only spikes, we have to
     # pass the information about which variable to use to the template,
     # it can not longer simply refer to "_spikespace"
     eventspace_name = '_{}space'.format(event)
     template_kwds['eventspace_variable'] = group.variables[eventspace_name]
     needed_variables.append(eventspace_name)
     self.variables = Variables(self)
     self.variables.add_auxiliary_variable('_cond', dtype=np.bool)
     CodeRunner.__init__(self, group,
                         'threshold',
                         code='',  # will be set in update_abstract_code
                         clock=group.clock,
                         when=when,
                         order=group.order,
                         name=group.name+'_thresholder*',
                         needed_variables=needed_variables,
                         template_kwds=template_kwds)
Ejemplo n.º 15
0
    def __init__(self, source, name='ratemonitor*',
                 codeobj_class=None):

        #: The group we are recording from
        self.source = source

        scheduler = Scheduler(clock=source.clock, when='end')

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self, group=self, template='ratemonitor',
                            when=scheduler, name=name)

        self.variables = Variables(self)
        self.variables.add_reference('_spikespace',
                                     source.variables['_spikespace'])
        self.variables.add_reference('_clock_t', source.variables['t'])
        self.variables.add_reference('_clock_dt', source.variables['dt'])
        self.variables.add_dynamic_array('rate', size=0, unit=hertz,
                                         constant_size=False)
        self.variables.add_dynamic_array('t', size=0, unit=second,
                                         constant_size=False)
        self.variables.add_reference('_num_source_neurons',
                                     source.variables['N'])
        self.variables.add_attribute_variable('N', unit=Unit(1), obj=self,
                                              attribute='_N', dtype=np.int32)
        self._N = 0

        self._enable_group_attributes()
Ejemplo n.º 16
0
 def __init__(self, group, when="thresholds", event="spike"):
     self.event = event
     if group._refractory is False or event != "spike":
         template_kwds = {"_uses_refractory": False}
         needed_variables = []
     else:
         template_kwds = {"_uses_refractory": True}
         needed_variables = ["t", "not_refractory", "lastspike"]
     # Since this now works for general events not only spikes, we have to
     # pass the information about which variable to use to the template,
     # it can not longer simply refer to "_spikespace"
     eventspace_name = "_{}space".format(event)
     template_kwds["eventspace_variable"] = group.variables[eventspace_name]
     needed_variables.append(eventspace_name)
     self.variables = Variables(self)
     self.variables.add_auxiliary_variable("_cond", unit=Unit(1), dtype=np.bool)
     CodeRunner.__init__(
         self,
         group,
         "threshold",
         code="",  # will be set in update_abstract_code
         clock=group.clock,
         when=when,
         order=group.order,
         name=group.name + "_thresholder*",
         needed_variables=needed_variables,
         template_kwds=template_kwds,
     )
Ejemplo n.º 17
0
    def __init__(self, source, variables, record=None, when=None,
                 name='statemonitor*', codeobj_class=None):
        self.source = weakref.proxy(source)
        self.codeobj_class = codeobj_class

        # run by default on source clock at the end
        scheduler = Scheduler(when)
        if not scheduler.defined_clock:
            scheduler.clock = source.clock
        if not scheduler.defined_when:
            scheduler.when = 'end'

        BrianObject.__init__(self, when=scheduler, name=name)

        # variables should always be a list of strings
        if variables is True:
            variables = source.equations.names
        elif isinstance(variables, str):
            variables = [variables]
        #: The variables to record
        self.record_variables = variables

        # record should always be an array of ints
        self.record_all = False
        if record is True:
            self.record_all = True
            record = np.arange(len(source), dtype=np.int32)
        elif record is None or record is False:
            record = np.array([], dtype=np.int32)
        elif isinstance(record, int):
            record = np.array([record], dtype=np.int32)
        else:
            record = np.asarray(record, dtype=np.int32)
            
        #: The array of recorded indices
        self.indices = record
        
        # Setup variables
        self.variables = Variables(self)
        for varname in variables:
            var = source.variables[varname]
            self.variables.add_reference(varname, var,
                                         index=source.variables.indices[varname])
            self.variables.add_dynamic_array('_recorded_' + varname,
                                             size=(0, len(self.indices)),
                                             unit=var.unit,
                                             dtype=var.dtype,
                                             constant=False,
                                             constant_size=False,
                                             is_bool=var.is_bool)

        self.variables.add_dynamic_array('_t', size=0, unit=Unit(1),
                                         constant=False, constant_size=False)
        self.variables.add_attribute_variable('_clock_t', second, self.clock, 't_')
        self.variables.add_array('_indices', size=len(self.indices),
                                 unit=Unit(1), dtype=self.indices.dtype,
                                 constant=True, read_only=True)
        self.variables['_indices'].set_value(self.indices)

        self._group_attribute_access_active = True
Ejemplo n.º 18
0
    def __init__(self, source, name='ratemonitor*',
                 codeobj_class=None):

        #: The group we are recording from
        self.source = source

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self, group=self, code='', template='ratemonitor',
                            clock=source.clock, when='end', order=0, name=name)

        self.add_dependency(source)

        self.variables = Variables(self)
        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))
        self.variables.add_constant('_source_start', Unit(1), start)
        self.variables.add_constant('_source_stop', Unit(1), stop)
        self.variables.add_reference('_spikespace', source)
        self.variables.add_dynamic_array('rate', size=0, unit=hertz,
                                         constant_size=False)
        self.variables.add_dynamic_array('t', size=0, unit=second,
                                         constant_size=False)
        self.variables.add_reference('_num_source_neurons', source, 'N')
        self.variables.add_array('N', unit=Unit(1), dtype=np.int32, size=1,
                                 scalar=True, read_only=True)
        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')
        self._enable_group_attributes()
Ejemplo n.º 19
0
    def __init__(self, source, record=True, when='end', order=0,
                 name='spikemonitor*', codeobj_class=None):
        self.record = bool(record)
        #: The source we are recording from
        self.source =source

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self, group=self, code='', template='spikemonitor',
                            name=name, clock=source.clock, when=when,
                            order=order)

        self.add_dependency(source)

        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))

        self.variables = Variables(self)
        self.variables.add_reference('_spikespace', source)
        self.variables.add_dynamic_array('i', size=0, unit=Unit(1),
                                         dtype=np.int32, constant_size=False)
        self.variables.add_dynamic_array('t', size=0, unit=second,
                                         constant_size=False)
        self.variables.add_arange('_source_i', size=len(source))
        self.variables.add_array('_count', size=len(source), unit=Unit(1),
                                 dtype=np.int32, read_only=True,
                                 index='_source_i')
        self.variables.add_constant('_source_start', Unit(1), start)
        self.variables.add_constant('_source_stop', Unit(1), stop)
        self.variables.add_attribute_variable('N', unit=Unit(1), obj=self,
                                              attribute='_N', dtype=np.int32)
        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')
        self._enable_group_attributes()
Ejemplo n.º 20
0
    def _create_variables(self, user_dtype=None):
        '''
        Create the variables dictionary for this `NeuronGroup`, containing
        entries for the equation variables and some standard entries.
        '''
        self.variables = Variables(self)
        self.variables.add_clock_variables(self.clock)
        self.variables.add_constant('N', Unit(1), self._N)

        # Standard variables always present
        self.variables.add_array('_spikespace', unit=Unit(1), size=self._N+1,
                                 dtype=np.int32, constant=False)
        # Add the special variable "i" which can be used to refer to the neuron index
        self.variables.add_arange('i', size=self._N, constant=True,
                                  read_only=True)

        for eq in self.equations.itervalues():
            dtype = get_dtype(eq, user_dtype)

            if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER):
                constant = 'constant' in eq.flags
                scalar = 'scalar' in eq.flags
                size = 1 if scalar else self._N
                index = '0' if scalar else None
                self.variables.add_array(eq.varname, size=size,
                                         unit=eq.unit, dtype=dtype,
                                         constant=constant,
                                         scalar=scalar,
                                         index=index)
            elif eq.type == SUBEXPRESSION:
                self.variables.add_subexpression(eq.varname, unit=eq.unit,
                                                 expr=str(eq.expr),
                                                 dtype=dtype,
                                                 scalar='scalar' in eq.flags)
            else:
                raise AssertionError('Unknown type of equation: ' + eq.eq_type)

        # Add the conditional-write attribute for variables with the
        # "unless refractory" flag
        for eq in self.equations.itervalues():
            if eq.type == DIFFERENTIAL_EQUATION and 'unless refractory' in eq.flags:
                not_refractory_var = self.variables['not_refractory']
                self.variables[eq.varname].set_conditional_write(not_refractory_var)

        # Stochastic variables
        for xi in self.equations.stochastic_variables:
            self.variables.add_auxiliary_variable(xi, unit=second**-0.5)

        # Check scalar subexpressions
        for eq in self.equations.itervalues():
            if eq.type == SUBEXPRESSION and 'scalar' in eq.flags:
                var = self.variables[eq.varname]
                for identifier in var.identifiers:
                    if identifier in self.variables:
                        if not self.variables[identifier].scalar:
                            raise SyntaxError(('Scalar subexpression %s refers '
                                               'to non-scalar variable %s.')
                                              % (eq.varname, identifier))
Ejemplo n.º 21
0
    def set_with_expression_conditional(self, group, varname, variable, cond,
                                        code, check_units=True, level=0):
        '''
        Sets a variable using a string expression and string condition. Is
        called by `VariableView.set_item` for statements such as
        ``S.var['i!=j'] = 'exp(-abs(i-j)/space_constant)*nS'``

        Parameters
        ----------
        group : `Group`
            The group providing the context for the indexing.
        varname : str
            The name of the variable to be set.
        variable : `ArrayVariable`
            The `ArrayVariable` object for the variable to be set.
        cond : str
            The string condition for which the variables should be set.
        code : str
            The code that should be executed to set the variable values.
        check_units : bool, optional
            Whether to check the units of the expression.
        level : int, optional
            How much farther to go up in the stack to find the namespace.
        '''

        abstract_code_cond = '_cond = '+cond
        abstract_code = varname + ' = ' + code
        namespace = get_local_namespace(level + 1)
        additional_namespace = ('implicit-namespace', namespace)
        variables = Variables(None)
        variables.add_auxiliary_variable('_cond', unit=Unit(1), dtype=np.bool,
                                         is_bool=True)
        check_code_units(abstract_code_cond, group,
                         additional_variables=variables,
                         additional_namespace=additional_namespace)
        # TODO: Have an additional argument to avoid going through the index
        # array for situations where iterate_all could be used
        codeobj = create_runner_codeobj(group,
                                 {'condition': abstract_code_cond,
                                  'statement': abstract_code},
                                 'group_variable_set_conditional',
                                 additional_variables=variables,
                                 additional_namespace=additional_namespace,
                                 check_units=check_units)
        codeobj()
Ejemplo n.º 22
0
    def set_with_expression(self, varname, variable, item, code,
                            check_units=True, level=0, run_namespace=None):
        '''
        Sets a variable using a string expression. Is called by
        `VariableView.set_item` for statements such as
        ``S.var[:, :] = 'exp(-abs(i-j)/space_constant)*nS'``

        Parameters
        ----------
        varname : str
            The name of the variable to be set
        variable : `ArrayVariable`
            The `ArrayVariable` object for the variable to be set.
        item : `ndarray`
            The indices for the variable (in the context of this `group`).
        code : str
            The code that should be executed to set the variable values.
            Can contain references to indices, such as `i` or `j`
        check_units : bool, optional
            Whether to check the units of the expression.
        level : int, optional
            How much farther to go up in the stack to find the implicit
            namespace (if used, see `run_namespace`).
        run_namespace : dict-like, optional
            An additional namespace that is used for variable lookup (if not
            defined, the implicit namespace of local variables is used).
        '''
        indices = self.calc_indices(item)
        abstract_code = varname + ' = ' + code
        variables = Variables(None)
        variables.add_array('_group_idx', unit=Unit(1),
                            size=len(indices), dtype=np.int32)
        variables['_group_idx'].set_value(indices)

        # TODO: Have an additional argument to avoid going through the index
        # array for situations where iterate_all could be used
        codeobj = create_runner_codeobj(self,
                                        abstract_code,
                                        'group_variable_set',
                                        additional_variables=variables,
                                        check_units=check_units,
                                        level=level+2,
                                        run_namespace=run_namespace)
        codeobj()
Ejemplo n.º 23
0
    def set_with_expression(self, group, varname, variable, item, code,
                            check_units=True, level=0):
        '''
        Sets a variable using a string expression. Is called by
        `VariableView.set_item` for statements such as
        ``S.var[:, :] = 'exp(-abs(i-j)/space_constant)*nS'``

        Parameters
        ----------
        group : `Group`
            The group providing the context for the indexing.
        varname : str
            The name of the variable to be set
        variable : `ArrayVariable`
            The `ArrayVariable` object for the variable to be set.
        item : `ndarray`
            The indices for the variable (in the context of this `group`).
        code : str
            The code that should be executed to set the variable values.
            Can contain references to indices, such as `i` or `j`
        check_units : bool, optional
            Whether to check the units of the expression.
        level : int, optional
            How much farther to go up in the stack to find the namespace.
        '''
        indices = group.calc_indices(item)
        abstract_code = varname + ' = ' + code
        namespace = get_local_namespace(level + 1)
        additional_namespace = ('implicit-namespace', namespace)
        variables = Variables(None)
        variables.add_array('_group_idx', unit=Unit(1),
                            size=len(indices), dtype=np.int32)
        variables['_group_idx'].set_value(indices)

        # TODO: Have an additional argument to avoid going through the index
        # array for situations where iterate_all could be used
        codeobj = create_runner_codeobj(group,
                                 abstract_code,
                                 'group_variable_set',
                                 additional_variables=variables,
                                 additional_namespace=additional_namespace,
                                 check_units=check_units)
        codeobj()
Ejemplo n.º 24
0
    def __init__(self, group, method, clock, order=0):
        # group is the neuron (a group of compartments) 
        self.method_choice = method
        self.group = weakref.proxy(group)
        CodeRunner.__init__(self, group,
                            'spatialstateupdate',
                            code='''_gtot = gtot__private
                                    _I0 = I0__private''',
                            clock=clock,
                            when='groups',
                            order=order,
                            name=group.name + '_spatialstateupdater*',
                            check_units=False)
        n = len(group) # total number of compartments
        segments = self.number_branches(group.morphology)
        self.variables = Variables(self, default_index='_segment_idx')
        self.variables.add_reference('N', group)
        self.variables.add_arange('_compartment_idx', size=n)
        self.variables.add_arange('_segment_idx', size=segments)
        self.variables.add_arange('_segment_root_idx', size=segments+1)
        self.variables.add_arange('_P_idx', size=(segments+1)**2)

        self.variables.add_array('_invr', unit=siemens, size=n, constant=True,
                                 index='_compartment_idx')
        self.variables.add_array('_P', unit=Unit(1), size=(segments+1)**2,
                                 constant=True, index='_P_idx')
        self.variables.add_array('_B', unit=Unit(1), size=segments+1,
                                 constant=True, index='_segment_root_idx')
        self.variables.add_array('_morph_i', unit=Unit(1), size=segments,
                                 dtype=np.int32, constant=True)
        self.variables.add_array('_morph_parent_i', unit=Unit(1), size=segments,
                                 dtype=np.int32, constant=True)
        self.variables.add_array('_starts', unit=Unit(1), size=segments,
                                 dtype=np.int32, constant=True)
        self.variables.add_array('_ends', unit=Unit(1), size=segments,
                                 dtype=np.int32, constant=True)
        self.variables.add_array('_invr0', unit=siemens, size=segments,
                                 constant=True)
        self.variables.add_array('_invrn', unit=siemens, size=segments,
                                 constant=True)
        self._enable_group_attributes()

        # The morphology is considered fixed (length etc. can still be changed,
        # though)
        # Traverse it once to get a flattened representation
        self._temp_morph_i = np.zeros(segments, dtype=np.int32)
        self._temp_morph_parent_i = np.zeros(segments, dtype=np.int32)
        self._temp_starts = np.zeros(segments, dtype=np.int32)
        self._temp_ends = np.zeros(segments, dtype=np.int32)
        self._pre_calc_iteration(self.group.morphology)
        self._morph_i = self._temp_morph_i
        self._morph_parent_i = self._temp_morph_parent_i
        self._starts = self._temp_starts
        self._ends = self._temp_ends
        self._prepare_codeobj = None
Ejemplo n.º 25
0
    def __getitem__(self, item):
        if isinstance(item, basestring):
            variables = Variables(None)
            variables.add_auxiliary_variable('_indices', dtype=np.int32)
            variables.add_auxiliary_variable('_cond', dtype=np.bool)

            abstract_code = '_cond = ' + item
            namespace = get_local_namespace(level=1)
            from brian2.devices.device import get_device
            device = get_device()
            codeobj = create_runner_codeobj(self.group,
                                            abstract_code,
                                            'group_get_indices',
                                            run_namespace=namespace,
                                            additional_variables=variables,
                                            codeobj_class=device.code_object_class(fallback_pref='codegen.string_expression_target')
                                            )
            return codeobj()
        else:
            return self.indices(item)
Ejemplo n.º 26
0
    def __getitem__(self, item):
        if isinstance(item, basestring):
            variables = Variables(None)
            variables.add_auxiliary_variable('_indices', unit=Unit(1),
                                             dtype=np.int32)
            variables.add_auxiliary_variable('_cond', unit=Unit(1),
                                             dtype=np.bool)

            abstract_code = '_cond = ' + item
            from brian2.devices.device import get_default_codeobject_class
            codeobj = create_runner_codeobj(self.group,
                                            abstract_code,
                                            'group_get_indices',
                                            additional_variables=variables,
                                            level=1,
                                            codeobj_class=get_default_codeobject_class('codegen.string_expression_target')
                                            )
            return codeobj()
        else:
            return self.indices(item)
Ejemplo n.º 27
0
    def __init__(self, source, name='ratemonitor*', codeobj_class=None):

        #: The group we are recording from
        self.source = source

        scheduler = Scheduler(clock=source.clock, when='end')

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self,
                            group=self,
                            template='ratemonitor',
                            when=scheduler,
                            name=name)

        self.add_dependency(source)

        self.variables = Variables(self)
        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))
        self.variables.add_constant('_source_start', Unit(1), start)
        self.variables.add_constant('_source_stop', Unit(1), stop)
        self.variables.add_reference('_spikespace', source)
        self.variables.add_reference('_clock_t', source, 't')
        self.variables.add_reference('_clock_dt', source, 'dt')
        self.variables.add_dynamic_array('rate',
                                         size=0,
                                         unit=hertz,
                                         constant_size=False)
        self.variables.add_dynamic_array('t',
                                         size=0,
                                         unit=second,
                                         constant_size=False)
        self.variables.add_reference('_num_source_neurons', source, 'N')
        self.variables.add_attribute_variable('N',
                                              unit=Unit(1),
                                              obj=self,
                                              attribute='_N',
                                              dtype=np.int32)

        self._enable_group_attributes()
Ejemplo n.º 28
0
    def __init__(self, source, start, stop, name=None):
        # First check if the source is itself a Subgroup
        # If so, then make this a Subgroup of the original Group
        if isinstance(source, Subgroup):
            source = source.source
            start = start + source.start
            stop = stop + source.start
            self.source = source
        else:
            self.source = weakproxy_with_fallback(source)

        if name is None:
            name = source.name + '_subgroup*'
        # We want to update the spikes attribute after it has been updated
        # by the parent, we do this in slot 'thresholds' with an order
        # one higher than the parent order to ensure it takes place after the
        # parent threshold operation
        schedule = Scheduler(clock=source.clock, when='thresholds',
                             order=source.order+1)
        Group.__init__(self, when=schedule, name=name)
        self._N = stop-start
        self.start = start
        self.stop = stop

        # All the variables have to go via the _sub_idx to refer to the
        # appropriate values in the source group
        self.variables = Variables(self, default_index='_sub_idx')

        # overwrite the meaning of N and i
        self.variables.add_constant('_offset', unit=Unit(1), value=self.start)
        self.variables.add_reference('_source_i', source, 'i')
        self.variables.add_subexpression('i', unit=Unit(1),
                                         dtype=source.variables['i'].dtype,
                                         expr='_source_i - _offset')
        self.variables.add_constant('N', unit=Unit(1), value=self._N)
        # add references for all variables in the original group
        self.variables.add_references(source, source.variables.keys())

        # Only the variable _sub_idx itself is stored in the subgroup
        # and needs the normal index for this group
        self.variables.add_arange('_sub_idx', size=self._N, start=self.start,
                                  index='_idx')

        for key, value in self.source.variables.indices.iteritems():
            if value not in ('_idx', '0'):
                raise ValueError(('Do not know how to deal with variable %s '
                                  'using  index %s in a subgroup') % (key,
                                                                      value))

        self.namespace = self.source.namespace
        self.codeobj_class = self.source.codeobj_class

        self._enable_group_attributes()
Ejemplo n.º 29
0
    def __getitem__(self, item):
        if isinstance(item, basestring):
            variables = Variables(None)
            variables.add_auxiliary_variable("_indices", unit=Unit(1), dtype=np.int32)
            variables.add_auxiliary_variable("_cond", unit=Unit(1), dtype=np.bool)

            abstract_code = "_cond = " + item
            check_code_units(abstract_code, self.group, additional_variables=variables, level=1)
            from brian2.devices.device import get_default_codeobject_class

            codeobj = create_runner_codeobj(
                self.group,
                abstract_code,
                "group_get_indices",
                additional_variables=variables,
                level=1,
                codeobj_class=get_default_codeobject_class("codegen.string_expression_target"),
            )
            return codeobj()
        else:
            return self.indices(item)
Ejemplo n.º 30
0
    def __getitem__(self, item):
        if isinstance(item, basestring):
            variables = Variables(None)
            variables.add_auxiliary_variable('_indices',
                                             unit=Unit(1),
                                             dtype=np.int32)
            variables.add_auxiliary_variable('_cond',
                                             unit=Unit(1),
                                             dtype=np.bool)

            abstract_code = '_cond = ' + item
            check_code_units(abstract_code,
                             self.group,
                             additional_variables=variables,
                             level=1)
            from brian2.devices.device import get_default_codeobject_class
            codeobj = create_runner_codeobj(
                self.group,
                abstract_code,
                'group_get_indices',
                additional_variables=variables,
                level=1,
                codeobj_class=get_default_codeobject_class(
                    'codegen.string_expression_target'))
            return codeobj()
        else:
            return self.indices(item)
Ejemplo n.º 31
0
    def __init__(self, N, rates, dt=None, clock=None, when='thresholds',
                 order=0, namespace=None, name='poissongroup*',
                 codeobj_class=None):

        if namespace is None:
            namespace = {}
        #: The group-specific namespace
        self.namespace = namespace

        Group.__init__(self, dt=dt, clock=clock, when=when, order=order,
                       name=name)

        self.codeobj_class = codeobj_class

        self._N = N = int(N)

        # TODO: In principle, it would be nice to support Poisson groups with
        # refactoriness, but we can't currently, since the refractoriness
        # information is reset in the state updater which we are not using
        # We could either use a specific template or simply not bother and make
        # users write their own NeuronGroup (with threshold rand() < rates*dt)
        # for more complex use cases.

        self.variables = Variables(self)
        # standard variables
        self.variables.add_constant('N', unit=Unit(1), value=self._N)
        self.variables.add_arange('i', self._N, constant=True, read_only=True)
        self.variables.add_array('_spikespace', size=N+1, unit=Unit(1),
                                 dtype=np.int32)
        self.variables.create_clock_variables(self._clock)

        # The firing rates
        if isinstance(rates, basestring):
            self.variables.add_subexpression('rates', unit=Hz,
                                             expr=rates)
        else:
            self.variables.add_array('rates', size=N, unit=Hz)
        self._rates = rates

        self.start = 0
        self.stop = N

        self._refractory = False

        self.events = {'spike': 'rand() < rates * dt'}
        self.thresholder = {'spike': Thresholder(self)}
        self.contained_objects.append(self.thresholder['spike'])

        self._enable_group_attributes()

        if not isinstance(rates, basestring):
            self.rates = rates
Ejemplo n.º 32
0
    def __init__(self, source, name='ratemonitor*', codeobj_class=None):

        #: The group we are recording from
        self.source = source

        scheduler = Scheduler(clock=source.clock, when='end')

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self,
                            group=self,
                            template='ratemonitor',
                            when=scheduler,
                            name=name)

        self.variables = Variables(self)
        self.variables.add_reference('_spikespace',
                                     source.variables['_spikespace'])
        self.variables.add_reference('_clock_t', source.variables['t'])
        self.variables.add_reference('_clock_dt', source.variables['dt'])
        self.variables.add_dynamic_array('rate',
                                         size=0,
                                         unit=hertz,
                                         constant_size=False)
        self.variables.add_dynamic_array('t',
                                         size=0,
                                         unit=second,
                                         constant_size=False)
        self.variables.add_reference('_num_source_neurons',
                                     source.variables['N'])
        self.variables.add_attribute_variable('N',
                                              unit=Unit(1),
                                              obj=self,
                                              attribute='_N',
                                              dtype=np.int32)
        self._N = 0

        self._enable_group_attributes()
Ejemplo n.º 33
0
Archivo: group.py Proyecto: yger/brian2
    def __getitem__(self, item):
        if isinstance(item, basestring):
            namespace = get_local_namespace(1)
            additional_namespace = ('implicit-namespace', namespace)
            variables = Variables(None)
            variables.add_auxiliary_variable('_indices', unit=Unit(1),
                                             dtype=np.int32)
            variables.add_auxiliary_variable('_cond', unit=Unit(1),
                                             dtype=np.bool,
                                             is_bool=True)

            abstract_code = '_cond = ' + item
            check_code_units(abstract_code, self.group,
                             additional_namespace=additional_namespace,
                             additional_variables=variables)
            codeobj = create_runner_codeobj(self.group,
                                            abstract_code,
                                            'group_get_indices',
                                            additional_variables=variables,
                                            additional_namespace=additional_namespace,
                                            )
            return codeobj()
        else:
            return self.group.calc_indices(item)
Ejemplo n.º 34
0
    def get_with_index_array(self, group, variable_name, variable, item):
        if variable.scalar:
            if not ((isinstance(item, slice) and item == slice(None)) or item == 0 or (hasattr(item, '__len__')
                                                                                           and len(item) == 0)):
                raise IndexError('Variable is a scalar variable.')
            indices = np.array([0])
        else:
            indices = group.calc_indices(item)

        # For "normal" variables, we can directly access the underlying data
        # and use the usual slicing syntax. For subexpressions, however, we
        # have to evaluate code for the given indices
        if isinstance(variable, Subexpression):
            variables = Variables(None)
            variables.add_auxiliary_variable('_variable', unit=variable.unit,
                                             dtype=variable.dtype,
                                             scalar=variable.scalar,
                                             is_bool=variable.is_bool)
            variables.add_array('_group_idx', unit=Unit(1),
                                size=len(indices), dtype=np.int32)
            variables['_group_idx'].set_value(indices)

            abstract_code = '_variable = ' + variable_name + '\n'
            codeobj = create_runner_codeobj(group,
                                            abstract_code,
                                            'group_variable_get',
                                            additional_variables=variables
            )
            return codeobj()
        else:
            # We are not going via code generation so we have to take care
            # of correct indexing (in particular for subgroups) explicitly
            var_index = group.variables.indices[variable_name]
            if var_index != '_idx':
                indices = group.variables[var_index].get_value()[indices]
            return variable.get_value()[indices]
Ejemplo n.º 35
0
    def __init__(self, N, rates, dt=None, clock=None, when='thresholds',
                 order=0, name='poissongroup*', codeobj_class=None):

        Group.__init__(self, dt=dt, clock=clock, when=when, order=order,
                       name=name)

        self.codeobj_class = codeobj_class

        self._N = N = int(N)

        # TODO: In principle, it would be nice to support Poisson groups with
        # refactoriness, but we can't currently, since the refractoriness
        # information is reset in the state updater which we are not using
        # We could either use a specific template or simply not bother and make
        # users write their own NeuronGroup (with threshold rand() < rates*dt)
        # for more complex use cases.

        self.variables = Variables(self)
        # standard variables
        self.variables.add_constant('N', unit=Unit(1), value=self._N)
        self.variables.add_arange('i', self._N, constant=True, read_only=True)
        self.variables.add_array('_spikespace', size=N+1, unit=Unit(1),
                                 dtype=np.int32)
        self.variables.create_clock_variables(self._clock)

        # The firing rates
        self.variables.add_array('rates', size=N, unit=Hz)

        self.start = 0
        self.stop = N

        self._refractory = False

        # To avoid a warning about the local variable rates, we set the real
        # threshold condition only after creating the object
        self.events = {'spike': 'False'}
        self.thresholder = {'spike': Thresholder(self)}
        self.events = {'spike': 'rand() < rates * dt'}
        self.contained_objects.append(self.thresholder['spike'])

        self._enable_group_attributes()

        # Here we want to use the local namespace, but at the level where the
        # constructor was called
        self.rates.set_item(slice(None), rates, level=2)
Ejemplo n.º 36
0
    def get_with_expression(self, variable_name, variable, code,
                            level=0, run_namespace=None):
        '''
        Gets a variable using a string expression. Is called by
        `VariableView.get_item` for statements such as
        ``print G.v['g_syn > 0']``.

        Parameters
        ----------
        variable_name : str
            The name of the variable in its context (e.g. ``'g_post'`` for a
            variable with name ``'g'``)
        variable : `ArrayVariable`
            The `ArrayVariable` object for the variable to be set
        code : str
            An expression that states a condition for elements that should be
            selected. Can contain references to indices, such as ``i`` or ``j``
            and to state variables. For example: ``'i>3 and v>0*mV'``.
        level : int, optional
            How much farther to go up in the stack to find the implicit
            namespace (if used, see `run_namespace`).
        run_namespace : dict-like, optional
            An additional namespace that is used for variable lookup (if not
            defined, the implicit namespace of local variables is used).
        '''
        if variable.scalar:
            raise IndexError(('Cannot access the variable %s with a '
                              'string expression, it is a scalar '
                              'variable.') % variable_name)
        # Add the recorded variable under a known name to the variables
        # dictionary. Important to deal correctly with
        # the type of the variable in C++
        variables = Variables(None)
        variables.add_auxiliary_variable('_variable', unit=variable.unit,
                                         dtype=variable.dtype,
                                         scalar=variable.scalar)
        variables.add_auxiliary_variable('_cond', unit=Unit(1), dtype=np.bool)

        abstract_code = '_variable = ' + variable_name + '\n'
        abstract_code += '_cond = ' + code
        check_code_units(abstract_code, self,
                         additional_variables=variables,
                         level=level+2,
                         run_namespace=run_namespace)
        codeobj = create_runner_codeobj(self,
                                        abstract_code,
                                        'group_variable_get_conditional',
                                        additional_variables=variables,
                                        level=level+2,
                                        run_namespace=run_namespace,
                                        )
        return codeobj()
Ejemplo n.º 37
0
 def __init__(self, dt, name='clock*'):
     # We need a name right away because some devices (e.g. cpp_standalone)
     # need a name for the object when creating the variables
     Nameable.__init__(self, name=name)
     self._old_dt = None
     self.variables = Variables(self)
     self.variables.add_array('timestep', unit=Unit(1), size=1,
                              dtype=np.uint64, read_only=True, scalar=True)
     self.variables.add_array('t', unit=second, size=1,
                              dtype=np.double, read_only=True, scalar=True)
     self.variables.add_array('dt', unit=second, size=1, values=float(dt),
                              dtype=np.float, read_only=True, constant=True,
                              scalar=True)
     self.variables.add_constant('N', unit=Unit(1), value=1)
     self._enable_group_attributes()
     self.dt = dt
     logger.diagnostic("Created clock {name} with dt={dt}".format(name=self.name,
                                                                  dt=self.dt))
Ejemplo n.º 38
0
    def __init__(self, target, target_var, N, rate, weight, when='synapses',
                 order=0):
        if target_var not in target.variables:
            raise KeyError('%s is not a variable of %s' % (target_var, target.name))

        if isinstance(weight, basestring):
            weight = '(%s)' % weight
        else:
            weight_unit = get_unit(weight)
            weight = repr(weight)
            target_unit = target.variables[target_var].unit
            # This will be checked automatically in the abstract code as well
            # but doing an explicit check here allows for a clearer error
            # message
            if not have_same_dimensions(weight_unit, target_unit):
                raise DimensionMismatchError(('The provided weight does not '
                                              'have the same unit as the '
                                              'target variable "%s"') % target_var,
                                             weight_unit.dim,
                                             target_unit.dim)


        binomial_sampling = BinomialFunction(N, rate*target.clock.dt,
                                             name='poissoninput_binomial*')

        code = '{targetvar} += {binomial}()*{weight}'.format(targetvar=target_var,
                                                             binomial=binomial_sampling.name,
                                                             weight=weight)
        self._stored_dt = target.dt_[:]  # make a copy
        # FIXME: we need an explicit reference here for on-the-fly subgroups
        # For example: PoissonInput(group[:N], ...)
        self._group = target
        CodeRunner.__init__(self,
                            group=target,
                            template='stateupdate',
                            code=code,
                            user_code='',
                            when=when,
                            order=order,
                            name='poissoninput*',
                            clock=target.clock
                            )
        self.variables = Variables(self)
        self.variables._add_variable(binomial_sampling.name, binomial_sampling)
Ejemplo n.º 39
0
 def __init__(self, group, method):
     # group is the neuron (a group of compartments) 
     self.method_choice = method
     self._isprepared = False
     CodeRunner.__init__(self, group,
                         'spatialstateupdate',
                         when=(group.clock, 'groups', 1),
                         name=group.name + '_spatialstateupdater*',
                         check_units=False)
     self.abstract_code = '''
     _gtot = gtot__private
     _I0 = I0__private
     '''
     N = len(self.group)
     self.ab_star = zeros((3, N))
     self.ab_plus = zeros((3, N))
     self.ab_minus = zeros((3, N))
     self.b_plus = zeros(N)
     self.b_minus = zeros(N)
     self.v_star = zeros(N)
     self.u_plus = zeros(N)
     self.u_minus = zeros(N)
     self.variables = Variables(self)
     # These 5 variables are constant after prepare()
     self.variables.add_array('ab_star', Unit(1), 3 * N,
                              values=self.ab_star.flatten(),
                              dtype=self.ab_star.dtype)
     self.variables.add_array('ab_plus', Unit(1), 3 * N,
                              values=self.ab_plus.flatten(),
                              dtype=self.ab_plus.dtype)
     self.variables.add_array('ab_minus', Unit(1), 3 * N,
                              values=self.ab_minus.flatten(),
                              dtype=self.ab_minus.dtype)
     self.variables.add_array('b_plus', Unit(1), N, values=self.b_plus,
                              dtype=self.b_plus.dtype)
     self.variables.add_array('b_minus', Unit(1), N, values=self.b_minus,
                              dtype=self.b_minus.dtype)
     # These 3 variables change every time step
     self.variables.add_array('v_star', Unit(1), N, values=self.v_star,
                              dtype=self.v_star.dtype)
     self.variables.add_array('u_plus', Unit(1), N, values=self.u_plus,
                              dtype=self.u_plus.dtype)
     self.variables.add_array('u_minus', Unit(1), N, values=self.u_minus,
                              dtype=self.u_minus.dtype)
Ejemplo n.º 40
0
 def __init__(self, dt, name='clock*'):
     # We need a name right away because some devices (e.g. cpp_standalone)
     # need a name for the object when creating the variables
     Nameable.__init__(self, name=name)
     #: Note that right after a change of dt, this
     #: will not equal the new dt (which is stored in `Clock._new_dt`). Call
     #: `Clock._set_t_update_t` to update the internal clock representation.
     self._new_dt = None
     self.variables = Variables(self)
     self.variables.add_array('timestep', unit=Unit(1), size=1,
                              dtype=np.uint64, read_only=True, scalar=True)
     self.variables.add_array('t', unit=second, size=1,
                              dtype=np.double, read_only=True, scalar=True)
     self.variables.add_array('dt', unit=second, size=1, values=float(dt),
                              dtype=np.float, read_only=True, constant=True,
                              scalar=True)
     self.variables.add_constant('N', unit=Unit(1), value=1)
     self._enable_group_attributes()
     self.dt = dt
     logger.diagnostic("Created clock {name} with dt={dt}".format(name=self.name,
                                                                  dt=self.dt))
Ejemplo n.º 41
0
    def __init__(self, N, rates, clock=None, name='poissongroup*',
                 codeobj_class=None):

        Group.__init__(self, when=clock, name=name)

        self.codeobj_class = codeobj_class

        self._N = N = int(N)

        # TODO: In principle, it would be nice to support Poisson groups with
        # refactoriness, but we can't currently, since the refractoriness
        # information is reset in the state updater which we are not using
        # We could either use a specific template or simply not bother and make
        # users write their own NeuronGroup (with threshold rand() < rates*dt)
        # for more complex use cases.

        self.variables = Variables(self)
        # standard variables
        self.variables.add_clock_variables(self.clock)
        self.variables.add_constant('N', unit=Unit(1), value=self._N)
        self.variables.add_arange('i', self._N, constant=True, read_only=True)
        self.variables.add_array('_spikespace', size=N+1, unit=Unit(1),
                                 dtype=np.int32)

        # The firing rates
        self.variables.add_array('rates', size=N, unit=Hz)

        self.start = 0
        self.stop = N
        self.namespace = create_namespace(None)

        self.threshold = 'rand() < rates * dt'
        self._refractory = False
        self.thresholder = Thresholder(self)
        self.contained_objects.append(self.thresholder)

        self._enable_group_attributes()
        # Set the rates according to the argument (make sure to use the correct
        # namespace)
        self.rates.set_item(slice(None), rates, level=2)
Ejemplo n.º 42
0
    def _create_variables(self, dtype=None):
        '''
        Create the variables dictionary for this `NeuronGroup`, containing
        entries for the equation variables and some standard entries.
        '''
        self.variables = Variables(self)
        self.variables.add_clock_variables(self.clock)
        self.variables.add_constant('N', Unit(1), self._N)

        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
        self.variables.add_array('_spikespace', unit=Unit(1), size=self._N+1,
                                 dtype=np.int32, constant=False)
        # Add the special variable "i" which can be used to refer to the neuron index
        self.variables.add_arange('i', size=self._N, constant=True,
                                  read_only=True)

        for eq in self.equations.itervalues():
            if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER):
                constant = ('constant' in eq.flags)
                self.variables.add_array(eq.varname, size=self._N,
                                         unit=eq.unit, dtype=dtype[eq.varname],
                                         constant=constant, is_bool=eq.is_bool)
            elif eq.type == STATIC_EQUATION:
                self.variables.add_subexpression(eq.varname, unit=eq.unit,
                                                 expr=str(eq.expr),
                                                 is_bool=eq.is_bool)
            else:
                raise AssertionError('Unknown type of equation: ' + eq.eq_type)

        # Stochastic variables
        for xi in self.equations.stochastic_variables:
            self.variables.add_auxiliary_variable(xi, unit=second**-0.5)
Ejemplo n.º 43
0
    def get_with_index_array(self, variable_name, variable, item):
        if variable.scalar:
            if not (isinstance(item, slice) and item == slice(None)):
                raise IndexError(('Illegal index for variable %s, it is a '
                                  'scalar variable.') % variable_name)
            indices = np.array(0)
        else:
            indices = self.calc_indices(item)

        # For "normal" variables, we can directly access the underlying data
        # and use the usual slicing syntax. For subexpressions, however, we
        # have to evaluate code for the given indices
        if isinstance(variable, Subexpression):
            variables = Variables(None)
            variables.add_auxiliary_variable('_variable', unit=variable.unit,
                                             dtype=variable.dtype,
                                             scalar=variable.scalar)
            if indices.shape ==  ():
                single_index = True
                indices = np.array([indices])
            else:
                single_index = False
            variables.add_array('_group_idx', unit=Unit(1),
                                size=len(indices), dtype=np.int32)
            variables['_group_idx'].set_value(indices)

            abstract_code = '_variable = ' + variable_name + '\n'
            codeobj = create_runner_codeobj(self,
                                            abstract_code,
                                            'group_variable_get',
                                            additional_variables=variables
            )
            result = codeobj()
            if single_index and not variable.scalar:
                return result[0]
            else:
                return result
        else:
            if variable.scalar:
                return variable.get_value()[0]
            else:
                # We are not going via code generation so we have to take care
                # of correct indexing (in particular for subgroups) explicitly
                var_index = self.variables.indices[variable_name]
                if var_index != '_idx':
                    indices = self.variables[var_index].get_value()[indices]
                return variable.get_value()[indices]
Ejemplo n.º 44
0
    def __getitem__(self, item):
        if isinstance(item, str):
            variables = Variables(None)
            variables.add_auxiliary_variable('_indices', dtype=np.int32)
            variables.add_auxiliary_variable('_cond', dtype=bool)

            abstract_code = '_cond = ' + item
            namespace = get_local_namespace(level=1)
            from brian2.devices.device import get_device
            device = get_device()
            codeobj = create_runner_codeobj(self.group,
                                            abstract_code,
                                            'group_get_indices',
                                            run_namespace=namespace,
                                            additional_variables=variables,
                                            codeobj_class=device.code_object_class(fallback_pref='codegen.string_expression_target')
                                            )
            return codeobj()
        else:
            return self.indices(item)
Ejemplo n.º 45
0
    def __getitem__(self, item):
        if isinstance(item, basestring):
            variables = Variables(None)
            variables.add_auxiliary_variable('_indices', unit=Unit(1),
                                             dtype=np.int32)
            variables.add_auxiliary_variable('_cond', unit=Unit(1),
                                             dtype=np.bool)

            abstract_code = '_cond = ' + item
            check_code_units(abstract_code, self.group,
                             additional_variables=variables,
                             level=1)
            codeobj = create_runner_codeobj(self.group,
                                            abstract_code,
                                            'group_get_indices',
                                            additional_variables=variables,
                                            level=1
                                            )
            return codeobj()
        else:
            return self.group.calc_indices(item)
Ejemplo n.º 46
0
class SpikeGeneratorGroup(Group, CodeRunner, SpikeSource):
    '''
    SpikeGeneratorGroup(N, indices, times, dt=None, clock=None,
                        period=1e100*second, when='thresholds', order=0,
                        sorted=False, name='spikegeneratorgroup*',
                        codeobj_class=None)

    A group emitting spikes at given times.

    Parameters
    ----------
    N : int
        The number of "neurons" in this group
    indices : array of integers
        The indices of the spiking cells
    times : `Quantity`
        The spike times for the cells given in ``indices``. Has to have the
        same length as ``indices``.
    period : `Quantity`, optional
        If this is specified, it will repeat spikes with this period.
    dt : `Quantity`, optional
        The time step to be used for the simulation. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the `defaultclock` will be used.
    when : str, optional
        When to run within a time step, defaults to the ``'thresholds'`` slot.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    sorted : bool, optional
        Whether the given indices and times are already sorted. Set to ``True``
        if your events are already sorted (first by spike time, then by index),
        this can save significant time at construction if your arrays contain
        large numbers of spikes. Defaults to ``False``.

    Notes
    -----
    * In a time step, `SpikeGeneratorGroup` emits all spikes that happened
      at :math:`t-dt < t_{spike} \leq t`. This might lead to unexpected
      or missing spikes if you change the time step dt between runs.
    * `SpikeGeneratorGroup` does not currently raise any warning if a neuron
      spikes more that once during a time step, but other code (e.g. for
      synaptic propagation) might assume that neurons only spike once per
      time step and will therefore not work properly.
    * If `sorted` is set to ``True``, the given arrays will not be copied
      (only affects runtime mode)..
    '''
    @check_units(N=1, indices=1, times=second, period=second)
    def __init__(self,
                 N,
                 indices,
                 times,
                 dt=None,
                 clock=None,
                 period=1e100 * second,
                 when='thresholds',
                 order=0,
                 sorted=False,
                 name='spikegeneratorgroup*',
                 codeobj_class=None):

        Group.__init__(self,
                       dt=dt,
                       clock=clock,
                       when=when,
                       order=order,
                       name=name)

        # Let other objects know that we emit spikes events
        self.events = {'spike': None}

        self.codeobj_class = codeobj_class

        times = Quantity(times)
        if N < 1 or int(N) != N:
            raise TypeError('N has to be an integer >=1.')
        N = int(
            N)  # Make sure that it is an integer, values such as 10.0 would
        # otherwise make weave compilation fail
        if len(indices) != len(times):
            raise ValueError(
                ('Length of the indices and times array must '
                 'match, but %d != %d') % (len(indices), len(times)))
        if period < 0 * second:
            raise ValueError('The period cannot be negative.')
        elif len(times) and period <= np.max(times):
            raise ValueError(
                'The period has to be greater than the maximum of '
                'the spike times')
        if len(times) and np.min(times) < 0 * second:
            raise ValueError('Spike times cannot be negative')
        if len(indices) and (np.min(indices) < 0 or np.max(indices) >= N):
            raise ValueError('Indices have to lie in the interval [0, %d[' % N)

        self.start = 0
        self.stop = N

        if not sorted:
            # sort times and indices first by time, then by indices
            rec = np.rec.fromarrays([times, indices], names=['t', 'i'])
            rec.sort()
            times = np.ascontiguousarray(rec.t)
            indices = np.ascontiguousarray(rec.i)

        self.variables = Variables(self)

        # We store the indices and times also directly in the Python object,
        # this way we can use them for checks in `before_run` even in standalone
        # TODO: Remove this when the checks in `before_run` have been moved to the template
        self._spike_time = times
        self._neuron_index = indices

        # standard variables
        self.variables.add_constant('N', unit=Unit(1), value=N)
        self.variables.add_array('period',
                                 unit=second,
                                 size=1,
                                 constant=True,
                                 read_only=True,
                                 scalar=True)
        self.variables.add_arange('i', N)
        self.variables.add_dynamic_array('spike_number',
                                         values=np.arange(len(indices)),
                                         size=len(indices),
                                         unit=Unit(1),
                                         dtype=np.int32,
                                         read_only=True,
                                         constant=True,
                                         index='spike_number',
                                         unique=True)
        self.variables.add_dynamic_array('neuron_index',
                                         values=indices,
                                         size=len(indices),
                                         unit=Unit(1),
                                         dtype=np.int32,
                                         index='spike_number',
                                         read_only=True,
                                         constant=True)
        self.variables.add_dynamic_array('spike_time',
                                         values=times,
                                         size=len(times),
                                         unit=second,
                                         index='spike_number',
                                         read_only=True,
                                         constant=True)
        self.variables.add_array('_spikespace',
                                 size=N + 1,
                                 unit=Unit(1),
                                 dtype=np.int32)
        self.variables.add_array('_lastindex',
                                 size=1,
                                 values=0,
                                 unit=Unit(1),
                                 dtype=np.int32,
                                 read_only=True,
                                 scalar=True)
        self.variables.create_clock_variables(self._clock)

        #: Remember the dt we used the last time when we checked the spike bins
        #: to not repeat the work for multiple runs with the same dt
        self._previous_dt = None

        #: "Dirty flag" that will be set when spikes are changed after the
        #: `before_run` check
        self._spikes_changed = True

        CodeRunner.__init__(self,
                            self,
                            code='',
                            template='spikegenerator',
                            clock=self._clock,
                            when=when,
                            order=order,
                            name=None)

        # Activate name attribute access
        self._enable_group_attributes()

        self.variables['period'].set_value(period)

    def before_run(self, run_namespace):
        # Do some checks on the period vs. dt
        dt = self.dt_[:]  # make a copy
        period = self.period_
        if period < np.inf:
            if period < dt:
                raise ValueError('The period of %s is %s, which is smaller '
                                 'than its dt of %s.' %
                                 (self.name, self.period, dt))
            if (abs(int(period / dt) * dt - period) >
                    period * np.finfo(dt.dtype).eps):
                raise NotImplementedError('The period of %s is %s, which is '
                                          'not an integer multiple of its dt '
                                          'of %s.' %
                                          (self.name, self.period, dt))

        # Check that we don't have more than one spike per neuron in a time bin
        if self._previous_dt is None or dt != self._previous_dt or self._spikes_changed:
            # We shift all the spikes by a tiny amount to make sure that spikes
            # at exact multiples of dt do not end up in the previous time bin
            # This shift has to be quite significant relative to machine
            # epsilon, we use 1e-3 of the dt here
            shift = 1e-3 * dt
            timebins = np.asarray(np.asarray(self._spike_time + shift) / dt,
                                  dtype=np.int32)
            index_timebins = np.rec.fromarrays([self._neuron_index, timebins],
                                               names=['i', 't'])
            if not len(np.unique(index_timebins)) == len(timebins):
                raise ValueError('Using a dt of %s, some neurons of '
                                 'SpikeGeneratorGroup "%s" spike more than '
                                 'once during a time step.' %
                                 (str(self.dt), self.name))
            self._previous_dt = dt
            self._spikes_changed = False

        super(SpikeGeneratorGroup,
              self).before_run(run_namespace=run_namespace)

    @check_units(indices=1, times=second, period=second)
    def set_spikes(self, indices, times, period=1e100 * second, sorted=False):
        '''
        set_spikes(indices, times, period=1e100*second, sorted=False)

        Change the spikes that this group will generate.

        This can be used to set the input for a second run of a model based on
        the output of a first run (if the input for the second run is already
        known before the first run, then all the information should simply be
        included in the initial `SpikeGeneratorGroup` initializer call,
        instead).

        Parameters
        ----------
        indices : array of integers
            The indices of the spiking cells
        times : `Quantity`
            The spike times for the cells given in ``indices``. Has to have the
            same length as ``indices``.
        period : `Quantity`, optional
            If this is specified, it will repeat spikes with this period.
        sorted : bool, optional
            Whether the given indices and times are already sorted. Set to
            ``True`` if your events are already sorted (first by spike time,
            then by index), this can save significant time at construction if
            your arrays contain large numbers of spikes. Defaults to ``False``.
        '''
        times = Quantity(times)
        if len(indices) != len(times):
            raise ValueError(
                ('Length of the indices and times array must '
                 'match, but %d != %d') % (len(indices), len(times)))

        if period < 0 * second:
            raise ValueError('The period cannot be negative.')
        elif len(times) and period <= np.max(times):
            raise ValueError(
                'The period has to be greater than the maximum of '
                'the spike times')

        if not sorted:
            # sort times and indices first by time, then by indices
            rec = np.rec.fromarrays([times, indices], names=['t', 'i'])
            rec.sort()
            times = np.ascontiguousarray(rec.t)
            indices = np.ascontiguousarray(rec.i)

        self.variables['period'].set_value(period)
        self.variables['neuron_index'].resize(len(indices))
        self.variables['spike_time'].resize(len(indices))
        self.variables['spike_number'].resize(len(indices))
        self.variables['spike_number'].set_value(np.arange(len(indices)))
        self.variables['neuron_index'].set_value(indices)
        self.variables['spike_time'].set_value(times)
        self.variables['_lastindex'].set_value(0)

        # Update the internal variables used in `SpikeGeneratorGroup.before_run`
        self._neuron_index = indices
        self._spike_time = times
        self._spikes_changed = True

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # 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
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __repr__(self):
        return ('{cls}({N}, indices=<length {l} array>, '
                'times=<length {l} array>').format(
                    cls=self.__class__.__name__,
                    N=self.N,
                    l=self.variables['neuron_index'].size)
Ejemplo n.º 47
0
    def __init__(self,
                 N,
                 indices,
                 times,
                 dt=None,
                 clock=None,
                 period=1e100 * second,
                 when='thresholds',
                 order=0,
                 sorted=False,
                 name='spikegeneratorgroup*',
                 codeobj_class=None):

        Group.__init__(self,
                       dt=dt,
                       clock=clock,
                       when=when,
                       order=order,
                       name=name)

        # Let other objects know that we emit spikes events
        self.events = {'spike': None}

        self.codeobj_class = codeobj_class

        times = Quantity(times)
        if N < 1 or int(N) != N:
            raise TypeError('N has to be an integer >=1.')
        N = int(
            N)  # Make sure that it is an integer, values such as 10.0 would
        # otherwise make weave compilation fail
        if len(indices) != len(times):
            raise ValueError(
                ('Length of the indices and times array must '
                 'match, but %d != %d') % (len(indices), len(times)))
        if period < 0 * second:
            raise ValueError('The period cannot be negative.')
        elif len(times) and period <= np.max(times):
            raise ValueError(
                'The period has to be greater than the maximum of '
                'the spike times')
        if len(times) and np.min(times) < 0 * second:
            raise ValueError('Spike times cannot be negative')
        if len(indices) and (np.min(indices) < 0 or np.max(indices) >= N):
            raise ValueError('Indices have to lie in the interval [0, %d[' % N)

        self.start = 0
        self.stop = N

        if not sorted:
            # sort times and indices first by time, then by indices
            rec = np.rec.fromarrays([times, indices], names=['t', 'i'])
            rec.sort()
            times = np.ascontiguousarray(rec.t)
            indices = np.ascontiguousarray(rec.i)

        self.variables = Variables(self)

        # We store the indices and times also directly in the Python object,
        # this way we can use them for checks in `before_run` even in standalone
        # TODO: Remove this when the checks in `before_run` have been moved to the template
        self._spike_time = times
        self._neuron_index = indices

        # standard variables
        self.variables.add_constant('N', unit=Unit(1), value=N)
        self.variables.add_array('period',
                                 unit=second,
                                 size=1,
                                 constant=True,
                                 read_only=True,
                                 scalar=True)
        self.variables.add_arange('i', N)
        self.variables.add_dynamic_array('spike_number',
                                         values=np.arange(len(indices)),
                                         size=len(indices),
                                         unit=Unit(1),
                                         dtype=np.int32,
                                         read_only=True,
                                         constant=True,
                                         index='spike_number',
                                         unique=True)
        self.variables.add_dynamic_array('neuron_index',
                                         values=indices,
                                         size=len(indices),
                                         unit=Unit(1),
                                         dtype=np.int32,
                                         index='spike_number',
                                         read_only=True,
                                         constant=True)
        self.variables.add_dynamic_array('spike_time',
                                         values=times,
                                         size=len(times),
                                         unit=second,
                                         index='spike_number',
                                         read_only=True,
                                         constant=True)
        self.variables.add_array('_spikespace',
                                 size=N + 1,
                                 unit=Unit(1),
                                 dtype=np.int32)
        self.variables.add_array('_lastindex',
                                 size=1,
                                 values=0,
                                 unit=Unit(1),
                                 dtype=np.int32,
                                 read_only=True,
                                 scalar=True)
        self.variables.create_clock_variables(self._clock)

        #: Remember the dt we used the last time when we checked the spike bins
        #: to not repeat the work for multiple runs with the same dt
        self._previous_dt = None

        #: "Dirty flag" that will be set when spikes are changed after the
        #: `before_run` check
        self._spikes_changed = True

        CodeRunner.__init__(self,
                            self,
                            code='',
                            template='spikegenerator',
                            clock=self._clock,
                            when=when,
                            order=order,
                            name=None)

        # Activate name attribute access
        self._enable_group_attributes()

        self.variables['period'].set_value(period)
Ejemplo n.º 48
0
class Subgroup(Group, SpikeSource):
    '''
    Subgroup of any `Group`
    
    Parameters
    ----------
    source : SpikeSource
        The source object to subgroup.
    start, stop : int
        Select only spikes with indices from ``start`` to ``stop-1``.
    name : str, optional
        A unique name for the group, or use ``source.name+'_subgroup_0'``, etc.
    '''
    def __init__(self, source, start, stop, name=None):
        # First check if the source is itself a Subgroup
        # If so, then make this a Subgroup of the original Group
        if isinstance(source, Subgroup):
            source = source.source
            start = start + source.start
            stop = stop + source.start
            self.source = source
        else:
            self.source = weakproxy_with_fallback(source)

        # Store a reference to the source's equations (if any)
        self.equations = None
        if hasattr(self.source, 'equations'):
            self.equations = weakproxy_with_fallback(self.source.equations)

        if name is None:
            name = source.name + '_subgroup*'
        # We want to update the spikes attribute after it has been updated
        # by the parent, we do this in slot 'thresholds' with an order
        # one higher than the parent order to ensure it takes place after the
        # parent threshold operation
        Group.__init__(self,
                       clock=source._clock,
                       when='thresholds',
                       order=source.order+1, name=name)
        self._N = stop-start
        self.start = start
        self.stop = stop

        self.events = self.source.events

        # All the variables have to go via the _sub_idx to refer to the
        # appropriate values in the source group
        self.variables = Variables(self, default_index='_sub_idx')

        # overwrite the meaning of N and i
        if self.start > 0:
            self.variables.add_constant('_offset', value=self.start)
            self.variables.add_reference('_source_i', source, 'i')
            self.variables.add_subexpression('i',
                                             dtype=source.variables['i'].dtype,
                                             expr='_source_i - _offset',
                                             index='_idx')
        else:
            # no need to calculate anything if this is a subgroup starting at 0
            self.variables.add_reference('i', source)

        self.variables.add_constant('N', value=self._N)
        self.variables.add_constant('_source_N', value=len(source))
        # add references for all variables in the original group
        self.variables.add_references(source, source.variables.keys())

        # Only the variable _sub_idx itself is stored in the subgroup
        # and needs the normal index for this group
        self.variables.add_arange('_sub_idx', size=self._N, start=self.start,
                                  index='_idx')

        # special indexing for subgroups
        self._indices = Indexing(self, self.variables['_sub_idx'])

        for key, value in self.source.variables.indices.iteritems():
            if value not in ('_idx', '0'):
                raise ValueError(('Do not know how to deal with variable %s '
                                  'using  index %s in a subgroup') % (key,
                                                                      value))

        self.namespace = self.source.namespace
        self.codeobj_class = self.source.codeobj_class

        self._enable_group_attributes()

    spikes = property(lambda self: self.source.spikes)

    def __getitem__(self, item):
        if not isinstance(item, slice):
            raise TypeError('Subgroups can only be constructed using slicing syntax')
        start, stop, step = item.indices(self._N)
        if step != 1:
            raise IndexError('Subgroups have to be contiguous')
        if start >= stop:
            raise IndexError('Illegal start/end values for subgroup, %d>=%d' %
                             (start, stop))
        return Subgroup(self.source, self.start + start, self.start + stop)

    def __repr__(self):
        description = '<{classname} {name} of {source} from {start} to {end}>'
        return description.format(classname=self.__class__.__name__,
                                  name=repr(self.name),
                                  source=repr(self.source.name),
                                  start=self.start,
                                  end=self.stop)
Ejemplo n.º 49
0
class NeuronGroup(Group, SpikeSource):
    '''
    A group of neurons.

    
    Parameters
    ----------
    N : int
        Number of neurons in the group.
    model : (str, `Equations`)
        The differential equations defining the group
    method : (str, function), optional
        The numerical integration method. Either a string with the name of a
        registered method (e.g. "euler") or a function that receives an
        `Equations` object and returns the corresponding abstract code. If no
        method is specified, a suitable method will be chosen automatically.
    threshold : str, optional
        The condition which produces spikes. Should be a single line boolean
        expression.
    reset : str, optional
        The (possibly multi-line) string with the code to execute on reset.
    refractory : {str, `Quantity`}, optional
        Either the length of the refractory period (e.g. ``2*ms``), a string
        expression that evaluates to the length of the refractory period
        after each spike (e.g. ``'(1 + rand())*ms'``), or a string expression
        evaluating to a boolean value, given the condition under which the
        neuron stays refractory after a spike (e.g. ``'v > -20*mV'``)
    events : dict, optional
        User-defined events in addition to the "spike" event defined by the
        ``threshold``. Has to be a mapping of strings (the event name) to
        strings (the condition) that will be checked.
    namespace: dict, optional
        A dictionary mapping identifier names to objects. If not given, the
        namespace will be filled in at the time of the call of `Network.run`,
        with either the values from the ``namespace`` argument of the
        `Network.run` method or from the local context, if no such argument is
        given.
    dtype : (`dtype`, `dict`), optional
        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_float_dtype` is used.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.
    dt : `Quantity`, optional
        The time step to be used for the simulation. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the `defaultclock` will be used.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        A unique name for the group, otherwise use ``neurongroup_0``, etc.
        
    Notes
    -----
    `NeuronGroup` contains a `StateUpdater`, `Thresholder` and `Resetter`, and
    these are run at the 'groups', 'thresholds' and 'resets' slots (i.e. the
    values of their `when` attribute take these values). The `order`
    attribute will be passed down to the contained objects but can be set
    individually by setting the `order` attribute of the `state_updater`,
    `thresholder` and `resetter` attributes, respectively.
    '''
    add_to_magic_network = True

    def __init__(self,
                 N,
                 model,
                 method=('exact', 'euler', 'heun'),
                 method_options=None,
                 threshold=None,
                 reset=None,
                 refractory=False,
                 events=None,
                 namespace=None,
                 dtype=None,
                 dt=None,
                 clock=None,
                 order=0,
                 name='neurongroup*',
                 codeobj_class=None):
        Group.__init__(self,
                       dt=dt,
                       clock=clock,
                       when='start',
                       order=order,
                       name=name)
        if dtype is None:
            dtype = {}
        if isinstance(dtype, collections.MutableMapping):
            dtype['lastspike'] = self._clock.variables['t'].dtype

        self.codeobj_class = codeobj_class

        try:
            self._N = N = int(N)
        except ValueError:
            if isinstance(N, str):
                raise TypeError(
                    "First NeuronGroup argument should be size, not equations."
                )
            raise
        if N < 1:
            raise ValueError("NeuronGroup size should be at least 1, was " +
                             str(N))

        self.start = 0
        self.stop = self._N

        ##### Prepare and validate equations
        if isinstance(model, basestring):
            model = Equations(model)
        if not isinstance(model, Equations):
            raise TypeError(('model has to be a string or an Equations '
                             'object, is "%s" instead.') % type(model))

        # Check flags
        model.check_flags({
            DIFFERENTIAL_EQUATION: ('unless refractory', ),
            PARAMETER: ('constant', 'shared', 'linked'),
            SUBEXPRESSION: ('shared', 'constant over dt')
        })

        # add refractoriness
        #: The original equations as specified by the user (i.e. without
        #: the multiplied `int(not_refractory)` term for equations marked as
        #: `(unless refractory)`)
        self.user_equations = model
        if refractory is not False:
            model = add_refractoriness(model)
        uses_refractoriness = len(model) and any([
            'unless refractory' in eq.flags
            for eq in model.values() if eq.type == DIFFERENTIAL_EQUATION
        ])

        # Separate subexpressions depending whether they are considered to be
        # constant over a time step or not
        model, constant_over_dt = extract_constant_subexpressions(model)
        self.equations = model

        self._linked_variables = set()
        logger.diagnostic("Creating NeuronGroup of size {self._N}, "
                          "equations {self.equations}.".format(self=self))

        if namespace is None:
            namespace = {}
        #: The group-specific namespace
        self.namespace = namespace

        # All of the following will be created in before_run

        #: The refractory condition or timespan
        self._refractory = refractory
        if uses_refractoriness and refractory is False:
            logger.warn(
                'Model equations use the "unless refractory" flag but '
                'no refractory keyword was given.', 'no_refractory')

        #: The state update method selected by the user
        self.method_choice = method

        if events is None:
            events = {}

        if threshold is not None:
            if 'spike' in events:
                raise ValueError(("The NeuronGroup defines both a threshold "
                                  "and a 'spike' event"))
            events['spike'] = threshold

        # Setup variables
        # Since we have to create _spikespace and possibly other "eventspace"
        # variables, we pass the supported events
        self._create_variables(dtype, events=list(events.keys()))

        #: Events supported by this group
        self.events = events

        #: Code that is triggered on events (e.g. reset)
        self.event_codes = {}

        #: Checks the spike threshold (or abitrary user-defined events)
        self.thresholder = {}

        #: Reset neurons which have spiked (or perform arbitrary actions for
        #: user-defined events)
        self.resetter = {}

        for event_name in events.keys():
            if not isinstance(event_name, basestring):
                raise TypeError(('Keys in the "events" dictionary have to be '
                                 'strings, not type %s.') % type(event_name))
            if not _valid_event_name(event_name):
                raise TypeError(("The name '%s' cannot be used as an event "
                                 "name.") % event_name)
            # By default, user-defined events are checked after the threshold
            when = 'thresholds' if event_name == 'spike' else 'after_thresholds'
            # creating a Thresholder will take care of checking the validity
            # of the condition
            thresholder = Thresholder(self, event=event_name, when=when)
            self.thresholder[event_name] = thresholder
            self.contained_objects.append(thresholder)

        if reset is not None:
            self.run_on_event('spike', reset, when='resets')

        #: Performs numerical integration step
        self.state_updater = StateUpdater(self, method, method_options)
        self.contained_objects.append(self.state_updater)

        #: Update the "constant over a time step" subexpressions
        self.subexpression_updater = None
        if len(constant_over_dt):
            self.subexpression_updater = SubexpressionUpdater(
                self, constant_over_dt)
            self.contained_objects.append(self.subexpression_updater)

        if refractory is not False:
            # Set the refractoriness information
            self.variables['lastspike'].set_value(-1e4 * second)
            self.variables['not_refractory'].set_value(True)

        # Activate name attribute access
        self._enable_group_attributes()

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # 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
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def state(self, name, use_units=True, level=0):
        try:
            return Group.state(self,
                               name,
                               use_units=use_units,
                               level=level + 1)
        except KeyError as ex:
            if name in self._linked_variables:
                raise TypeError(('Link target for variable %s has not been '
                                 'set.') % name)
            else:
                raise ex

    def run_on_event(self, event, code, when='after_resets', order=None):
        '''
        Run code triggered by a custom-defined event (see `NeuronGroup`
        documentation for the specification of events).The created `Resetter`
        object will be automatically added to the group, it therefore does not
        need to be added to the network manually. However, a reference to the
        object will be returned, which can be used to later remove it from the
        group or to set it to inactive.

        Parameters
        ----------
        event : str
            The name of the event that should trigger the code
        code : str
            The code that should be executed
        when : str, optional
            The scheduling slot that should be used to execute the code.
            Defaults to `'after_resets'`.
        order : int, optional
            The order for operations in the same scheduling slot. Defaults to
            the order of the `NeuronGroup`.

        Returns
        -------
        obj : `Resetter`
            A reference to the object that will be run.
        '''
        if event not in self.events:
            error_message = "Unknown event '%s'." % event
            if event == 'spike':
                error_message += ' Did you forget to define a threshold?'
            raise ValueError(error_message)
        if event in self.resetter:
            raise ValueError(("Cannot add code for event '%s', code for this "
                              "event has already been added.") % event)
        self.event_codes[event] = code
        resetter = Resetter(self, when=when, order=order, event=event)
        self.resetter[event] = resetter
        self.contained_objects.append(resetter)

        return resetter

    def set_event_schedule(self, event, when='after_thresholds', order=None):
        '''
        Change the scheduling slot for checking the condition of an event.

        Parameters
        ----------
        event : str
            The name of the event for which the scheduling should be changed
        when : str, optional
            The scheduling slot that should be used to check the condition.
            Defaults to `'after_thresholds'`.
        order : int, optional
            The order for operations in the same scheduling slot. Defaults to
            the order of the `NeuronGroup`.
        '''
        if event not in self.thresholder:
            raise ValueError("Unknown event '%s'." % event)
        order = order if order is not None else self.order
        self.thresholder[event].when = when
        self.thresholder[event].order = order

    def __setattr__(self, key, value):
        # attribute access is switched off until this attribute is created by
        # _enable_group_attributes
        if not hasattr(
                self,
                '_group_attribute_access_active') or key in self.__dict__:
            object.__setattr__(self, key, value)
        elif key in self._linked_variables:
            if not isinstance(value, LinkedVariable):
                raise ValueError(
                    ('Cannot set a linked variable directly, link '
                     'it to another variable using "linked_var".'))
            linked_var = value.variable

            if isinstance(linked_var, DynamicArrayVariable):
                raise NotImplementedError(('Linking to variable %s is not '
                                           'supported, can only link to '
                                           'state variables of fixed '
                                           'size.') % linked_var.name)

            eq = self.equations[key]
            if eq.dim is not linked_var.dim:
                raise DimensionMismatchError(
                    ('Unit of variable %s does not '
                     'match its link target %s') % (key, linked_var.name))

            if not isinstance(linked_var, Subexpression):
                var_length = len(linked_var)
            else:
                var_length = len(linked_var.owner)

            if value.index is not None:
                try:
                    index_array = np.asarray(value.index)
                    if not np.issubsctype(index_array.dtype, np.int):
                        raise TypeError()
                except TypeError:
                    raise TypeError(('The index for a linked variable has '
                                     'to be an integer array'))
                size = len(index_array)
                source_index = value.group.variables.indices[value.name]
                if source_index not in ('_idx', '0'):
                    # we are indexing into an already indexed variable,
                    # calculate the indexing into the target variable
                    index_array = value.group.variables[
                        source_index].get_value()[index_array]

                if not index_array.ndim == 1 or size != len(self):
                    raise TypeError(
                        ('Index array for linked variable %s '
                         'has to be a one-dimensional array of '
                         'length %d, but has shape '
                         '%s') % (key, len(self), str(index_array.shape)))
                if min(index_array) < 0 or max(index_array) >= var_length:
                    raise ValueError('Index array for linked variable %s '
                                     'contains values outside of the valid '
                                     'range [0, %d[' % (key, var_length))
                self.variables.add_array('_%s_indices' % key,
                                         size=size,
                                         dtype=index_array.dtype,
                                         constant=True,
                                         read_only=True,
                                         values=index_array)
                index = '_%s_indices' % key
            else:
                if linked_var.scalar or (var_length == 1 and self._N != 1):
                    index = '0'
                else:
                    index = value.group.variables.indices[value.name]
                    if index == '_idx':
                        target_length = var_length
                    else:
                        target_length = len(value.group.variables[index])
                        # we need a name for the index that does not clash with
                        # other names and a reference to the index
                        new_index = '_' + value.name + '_index_' + index
                        self.variables.add_reference(new_index, value.group,
                                                     index)
                        index = new_index

                    if len(self) != target_length:
                        raise ValueError(
                            ('Cannot link variable %s to %s, the size of '
                             'the target group does not match '
                             '(%d != %d). You can provide an indexing '
                             'scheme with the "index" keyword to link '
                             'groups with different sizes') %
                            (key, linked_var.name, len(self), target_length))

            self.variables.add_reference(key,
                                         value.group,
                                         value.name,
                                         index=index)
            log_msg = ('Setting {target}.{targetvar} as a link to '
                       '{source}.{sourcevar}').format(
                           target=self.name,
                           targetvar=key,
                           source=value.variable.owner.name,
                           sourcevar=value.variable.name)
            if index is not None:
                log_msg += '(using "{index}" as index variable)'.format(
                    index=index)
            logger.diagnostic(log_msg)
        else:
            if isinstance(value, LinkedVariable):
                raise TypeError(
                    ('Cannot link variable %s, it has to be marked '
                     'as a linked variable with "(linked)" in the '
                     'model equations.') % key)
            else:
                Group.__setattr__(self, key, value, level=1)

    def __getitem__(self, item):
        start, stop = to_start_stop(item, self._N)

        return Subgroup(self, start, stop)

    def _create_variables(self, user_dtype, events):
        '''
        Create the variables dictionary for this `NeuronGroup`, containing
        entries for the equation variables and some standard entries.
        '''
        self.variables = Variables(self)
        self.variables.add_constant('N', self._N)

        # Standard variables always present
        for event in events:
            self.variables.add_array('_{}space'.format(event),
                                     size=self._N + 1,
                                     dtype=np.int32,
                                     constant=False)
        # Add the special variable "i" which can be used to refer to the neuron index
        self.variables.add_arange('i',
                                  size=self._N,
                                  constant=True,
                                  read_only=True)
        # Add the clock variables
        self.variables.create_clock_variables(self._clock)

        for eq in self.equations.values():
            dtype = get_dtype(eq, user_dtype)
            check_identifier_pre_post(eq.varname)
            if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER):
                if 'linked' in eq.flags:
                    # 'linked' cannot be combined with other flags
                    if not len(eq.flags) == 1:
                        raise SyntaxError(('The "linked" flag cannot be '
                                           'combined with other flags'))
                    self._linked_variables.add(eq.varname)
                else:
                    constant = 'constant' in eq.flags
                    shared = 'shared' in eq.flags
                    size = 1 if shared else self._N
                    self.variables.add_array(eq.varname,
                                             size=size,
                                             dimensions=eq.dim,
                                             dtype=dtype,
                                             constant=constant,
                                             scalar=shared)
            elif eq.type == SUBEXPRESSION:
                self.variables.add_subexpression(eq.varname,
                                                 dimensions=eq.dim,
                                                 expr=str(eq.expr),
                                                 dtype=dtype,
                                                 scalar='shared' in eq.flags)
            else:
                raise AssertionError('Unknown type of equation: ' + eq.eq_type)

        # Add the conditional-write attribute for variables with the
        # "unless refractory" flag
        if self._refractory is not False:
            for eq in self.equations.values():
                if (eq.type == DIFFERENTIAL_EQUATION
                        and 'unless refractory' in eq.flags):
                    not_refractory_var = self.variables['not_refractory']
                    var = self.variables[eq.varname]
                    var.set_conditional_write(not_refractory_var)

        # Stochastic variables
        for xi in self.equations.stochastic_variables:
            self.variables.add_auxiliary_variable(
                xi, dimensions=(second**-0.5).dim)

        # Check scalar subexpressions
        for eq in self.equations.values():
            if eq.type == SUBEXPRESSION and 'shared' in eq.flags:
                var = self.variables[eq.varname]
                for identifier in var.identifiers:
                    if identifier in self.variables:
                        if not self.variables[identifier].scalar:
                            raise SyntaxError(
                                ('Shared subexpression %s refers '
                                 'to non-shared variable %s.') %
                                (eq.varname, identifier))

    def before_run(self, run_namespace=None):
        # Check units
        self.equations.check_units(self, run_namespace=run_namespace)
        # Check that subexpressions that refer to stateful functions are labeled
        # as "constant over dt"
        check_subexpressions(self, self.equations, run_namespace)
        super(NeuronGroup, self).before_run(run_namespace=run_namespace)

    def _repr_html_(self):
        text = [
            r'NeuronGroup "%s" with %d neurons.<br>' % (self.name, self._N)
        ]
        text.append(r'<b>Model:</b><nr>')
        text.append(sympy.latex(self.equations))

        def add_event_to_text(event):
            if event == 'spike':
                event_header = 'Spiking behaviour'
                event_condition = 'Threshold condition'
                event_code = 'Reset statement(s)'
            else:
                event_header = 'Event "%s"' % event
                event_condition = 'Event condition'
                event_code = 'Executed statement(s)'
            condition = self.events[event]
            text.append(
                r'<b>%s:</b><ul style="list-style-type: none; margin-top: 0px;">'
                % event_header)
            text.append(r'<li><i>%s: </i>' % event_condition)
            text.append('<code>%s</code></li>' % str(condition))
            statements = self.event_codes.get(event, None)
            if statements is not None:
                text.append(r'<li><i>%s:</i>' % event_code)
                if '\n' in str(statements):
                    text.append('</br>')
                text.append(r'<code>%s</code></li>' % str(statements))
            text.append('</ul>')

        if 'spike' in self.events:
            add_event_to_text('spike')
        for event in self.events:
            if event != 'spike':
                add_event_to_text(event)

        return '\n'.join(text)
Ejemplo n.º 50
0
    def _create_variables(self, user_dtype, events):
        '''
        Create the variables dictionary for this `NeuronGroup`, containing
        entries for the equation variables and some standard entries.
        '''
        self.variables = Variables(self)
        self.variables.add_constant('N', self._N)

        # Standard variables always present
        for event in events:
            self.variables.add_array('_{}space'.format(event),
                                     size=self._N + 1,
                                     dtype=np.int32,
                                     constant=False)
        # Add the special variable "i" which can be used to refer to the neuron index
        self.variables.add_arange('i',
                                  size=self._N,
                                  constant=True,
                                  read_only=True)
        # Add the clock variables
        self.variables.create_clock_variables(self._clock)

        for eq in self.equations.values():
            dtype = get_dtype(eq, user_dtype)
            check_identifier_pre_post(eq.varname)
            if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER):
                if 'linked' in eq.flags:
                    # 'linked' cannot be combined with other flags
                    if not len(eq.flags) == 1:
                        raise SyntaxError(('The "linked" flag cannot be '
                                           'combined with other flags'))
                    self._linked_variables.add(eq.varname)
                else:
                    constant = 'constant' in eq.flags
                    shared = 'shared' in eq.flags
                    size = 1 if shared else self._N
                    self.variables.add_array(eq.varname,
                                             size=size,
                                             dimensions=eq.dim,
                                             dtype=dtype,
                                             constant=constant,
                                             scalar=shared)
            elif eq.type == SUBEXPRESSION:
                self.variables.add_subexpression(eq.varname,
                                                 dimensions=eq.dim,
                                                 expr=str(eq.expr),
                                                 dtype=dtype,
                                                 scalar='shared' in eq.flags)
            else:
                raise AssertionError('Unknown type of equation: ' + eq.eq_type)

        # Add the conditional-write attribute for variables with the
        # "unless refractory" flag
        if self._refractory is not False:
            for eq in self.equations.values():
                if (eq.type == DIFFERENTIAL_EQUATION
                        and 'unless refractory' in eq.flags):
                    not_refractory_var = self.variables['not_refractory']
                    var = self.variables[eq.varname]
                    var.set_conditional_write(not_refractory_var)

        # Stochastic variables
        for xi in self.equations.stochastic_variables:
            self.variables.add_auxiliary_variable(
                xi, dimensions=(second**-0.5).dim)

        # Check scalar subexpressions
        for eq in self.equations.values():
            if eq.type == SUBEXPRESSION and 'shared' in eq.flags:
                var = self.variables[eq.varname]
                for identifier in var.identifiers:
                    if identifier in self.variables:
                        if not self.variables[identifier].scalar:
                            raise SyntaxError(
                                ('Shared subexpression %s refers '
                                 'to non-shared variable %s.') %
                                (eq.varname, identifier))
Ejemplo n.º 51
0
class Subgroup(Group, SpikeSource):
    '''
    Subgroup of any `Group`
    
    Parameters
    ----------
    source : SpikeSource
        The source object to subgroup.
    start, stop : int
        Select only spikes with indices from ``start`` to ``stop-1``.
    name : str, optional
        A unique name for the group, or use ``source.name+'_subgroup_0'``, etc.
    
    Notes
    -----
    
    Functions differently to Brian 1.x subgroup in that:
    
    * It works for any spike source
    * You need to keep a reference to it
    * It makes a copy of the spikes, and there is no direct support for
      subgroups in `Connection` (or rather `Synapses`)
    
    TODO: Group state variable access
    '''
    def __init__(self, source, start, stop, name=None):
        self.source = weakref.proxy(source)
        if name is None:
            name = source.name + '_subgroup*'
        # We want to update the spikes attribute after it has been updated
        # by the parent, we do this in slot 'thresholds' with an order
        # one higher than the parent order to ensure it takes place after the
        # parent threshold operation
        schedule = Scheduler(clock=source.clock,
                             when='thresholds',
                             order=source.order + 1)
        Group.__init__(self, when=schedule, name=name)
        self._N = stop - start
        self.start = start
        self.stop = stop

        # All the variables have to go via the _sub_idx to refer to the
        # appropriate values in the source group
        self.variables = Variables(self, default_index='_sub_idx')

        # overwrite the meaning of N and i
        self.variables.add_constant('_offset', unit=Unit(1), value=self.start)
        self.variables.add_reference('_source_i', source.variables['i'])
        self.variables.add_subexpression('i',
                                         unit=Unit(1),
                                         dtype=source.variables['i'].dtype,
                                         expr='_source_i - _offset')
        self.variables.add_constant('N', unit=Unit(1), value=self._N)
        # add references for all variables in the original group
        self.variables.add_references(source.variables)

        # Only the variable _sub_idx itself is stored in the subgroup
        # and needs the normal index for this group
        self.variables.add_arange('_sub_idx',
                                  size=self._N,
                                  start=self.start,
                                  index='_idx')

        for key, value in self.source.variables.indices.iteritems():
            if value != '_idx':
                raise ValueError(('Do not how to deal with variable %s using '
                                  'index %s in a subgroup') % (key, value))

        self.namespace = self.source.namespace
        self.codeobj_class = self.source.codeobj_class

        self._enable_group_attributes()

    spikes = property(lambda self: self.source.spikes)

    def __len__(self):
        return self._N

    def __repr__(self):
        description = '<{classname} {name} of {source} from {start} to {end}>'
        return description.format(classname=self.__class__.__name__,
                                  name=repr(self.name),
                                  source=repr(self.source.name),
                                  start=self.start,
                                  end=self.stop)
Ejemplo n.º 52
0
class Thresholder(CodeRunner):
    '''
    The `CodeRunner` that applies the threshold condition to the state
    variables of a `NeuronGroup` at every timestep and sets its ``spikes``
    and ``refractory_until`` attributes.
    '''
    def __init__(self, group, when='thresholds', event='spike'):
        self.event = event
        if group._refractory is False or event != 'spike':
            template_kwds = {'_uses_refractory': False}
            needed_variables = []
        else:
            template_kwds = {'_uses_refractory': True}
            needed_variables = ['t', 'not_refractory', 'lastspike']
        # Since this now works for general events not only spikes, we have to
        # pass the information about which variable to use to the template,
        # it can not longer simply refer to "_spikespace"
        eventspace_name = '_{}space'.format(event)
        template_kwds['eventspace_variable'] = group.variables[eventspace_name]
        needed_variables.append(eventspace_name)
        self.variables = Variables(self)
        self.variables.add_auxiliary_variable('_cond', dtype=np.bool)
        CodeRunner.__init__(
            self,
            group,
            'threshold',
            code='',  # will be set in update_abstract_code
            clock=group.clock,
            when=when,
            order=group.order,
            name=group.name + '_thresholder*',
            needed_variables=needed_variables,
            template_kwds=template_kwds)

    def update_abstract_code(self, run_namespace):
        code = self.group.events[self.event]
        # Raise a useful error message when the user used a Brian1 syntax
        if not isinstance(code, basestring):
            if isinstance(code, Quantity):
                t = 'a quantity'
            else:
                t = '%s' % type(code)
            error_msg = 'Threshold condition has to be a string, not %s.' % t
            if self.event == 'spike':
                try:
                    vm_var = _guess_membrane_potential(self.group.equations)
                except AttributeError:  # not a group with equations...
                    vm_var = None
                if vm_var is not None:
                    error_msg += " Probably you intended to use '%s > ...'?" % vm_var
            raise TypeError(error_msg)

        self.user_code = '_cond = ' + code

        identifiers = get_identifiers(code)
        variables = self.group.resolve_all(identifiers,
                                           run_namespace,
                                           user_identifiers=identifiers)
        if not is_boolean_expression(code, variables):
            raise TypeError(('Threshold condition "%s" is not a boolean '
                             'expression') % code)
        if self.group._refractory is False or self.event != 'spike':
            self.abstract_code = '_cond = %s' % code
        else:
            self.abstract_code = '_cond = (%s) and not_refractory' % code
Ejemplo n.º 53
0
    def __init__(self, source, variables, record=None, dt=None, clock=None,
                 when='start', order=0, name='statemonitor*', codeobj_class=None):
        self.source = source
        # Make the monitor use the explicitly defined namespace of its source
        # group (if it exists)
        self.namespace = getattr(source, 'namespace', None)
        self.codeobj_class = codeobj_class

        # run by default on source clock at the end
        if dt is None and clock is None:
            clock = source.clock

        # variables should always be a list of strings
        if variables is True:
            variables = source.equations.names
        elif isinstance(variables, str):
            variables = [variables]
        #: The variables to record
        self.record_variables = variables

        # record should always be an array of ints
        self.record_all = False
        if hasattr(record, '_indices'):
            # The ._indices method always returns absolute indices
            # If the source is already a subgroup of another group, we therefore
            # have to shift the indices to become relative to the subgroup
            record = record._indices() - getattr(source, '_offset', 0)
        if record is True:
            self.record_all = True
            record = np.arange(len(source), dtype=np.int32)
        elif record is None or record is False:
            logger.warn(('The StateMonitor set up to record the variable(s) '
                         '{vars} of "{source}" is not recording any value. '
                         'Did you forget to provide the record '
                         'argument?').format(vars=', '.join('"%s"' % var
                                                            for var in variables),
                                             source=self.source.name),
                        once=True)
            record = np.array([], dtype=np.int32)
        elif isinstance(record, numbers.Number):
            record = np.array([record], dtype=np.int32)
        else:
            record = np.asarray(record, dtype=np.int32)
            
        #: The array of recorded indices
        self.record = record
        self.n_indices = len(record)

        # Some dummy code so that code generation takes care of the indexing
        # and subexpressions
        code = ['_to_record_%s = _source_%s' % (v, v)
                for v in variables]
        code = '\n'.join(code)

        CodeRunner.__init__(self, group=self, template='statemonitor',
                            code=code, name=name,
                            clock=clock,
                            dt=dt,
                            when=when,
                            order=order,
                            check_units=False)

        self.add_dependency(source)

        # Setup variables
        self.variables = Variables(self)

        self.variables.add_dynamic_array('t', size=0, unit=second,
                                         constant=False, constant_size=False)
        self.variables.add_attribute_variable('N', unit=Unit(1),
                                              dtype=np.int32,
                                              obj=self, attribute='_N')
        self.variables.add_array('_indices', size=len(self.record),
                                 unit=Unit(1), dtype=self.record.dtype,
                                 constant=True, read_only=True,
                                 values=self.record)
        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')
        for varname in variables:
            var = source.variables[varname]
            if var.scalar and len(self.record) > 1:
                logger.warn(('Variable %s is a shared variable but it will be '
                             'recorded once for every target.' % varname),
                            once=True)
            index = source.variables.indices[varname]
            self.variables.add_reference('_source_%s' % varname,
                                         source, varname, index=index)
            if not index in ('_idx', '0') and index not in variables:
                self.variables.add_reference(index, source)
            self.variables.add_dynamic_array(varname,
                                             size=(0, len(self.record)),
                                             resize_along_first=True,
                                             unit=var.unit,
                                             dtype=var.dtype,
                                             constant=False,
                                             constant_size=False)

        for varname in variables:
            var = self.source.variables[varname]
            self.variables.add_auxiliary_variable('_to_record_' + varname,
                                                  unit=var.unit,
                                                  dtype=var.dtype,
                                                  scalar=var.scalar)

        self.recorded_variables = dict([(varname, self.variables[varname])
                                        for varname in variables])
        recorded_names = [varname for varname in variables]

        self.needed_variables = recorded_names
        self.template_kwds = {'_recorded_variables': self.recorded_variables}
        self._enable_group_attributes()
Ejemplo n.º 54
0
class PopulationRateMonitor(Group, CodeRunner):
    """
    Record instantaneous firing rates, averaged across neurons from a
    `NeuronGroup` or other spike source.

    Parameters
    ----------
    source : (`NeuronGroup`, `SpikeSource`)
        The source of spikes to record.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'_ratemonitor_0'``, etc.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.
    dtype : dtype, optional
        The dtype to use to store the ``rate`` variable. Defaults to
        `~numpy.float64`, i.e. double precision.
    Notes
    -----
    Currently, this monitor can only monitor the instantaneous firing rates at
    each time step of the source clock. Any binning/smoothing of the firing
    rates has to be done manually afterwards.
    """
    invalidates_magic_network = False
    add_to_magic_network = True

    def __init__(self,
                 source,
                 name='ratemonitor*',
                 codeobj_class=None,
                 dtype=np.float64):

        #: The group we are recording from
        self.source = source

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self,
                            group=self,
                            code='',
                            template='ratemonitor',
                            clock=source.clock,
                            when='end',
                            order=0,
                            name=name)

        self.add_dependency(source)

        self.variables = Variables(self)
        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))
        self.variables.add_constant('_source_start', start)
        self.variables.add_constant('_source_stop', stop)
        self.variables.add_reference('_spikespace', source)
        self.variables.add_dynamic_array('rate',
                                         size=0,
                                         dimensions=hertz.dim,
                                         read_only=True,
                                         dtype=dtype)
        self.variables.add_dynamic_array(
            't',
            size=0,
            dimensions=second.dim,
            read_only=True,
            dtype=self._clock.variables['t'].dtype)
        self.variables.add_reference('_num_source_neurons', source, 'N')
        self.variables.add_array('N',
                                 dtype=np.int32,
                                 size=1,
                                 scalar=True,
                                 read_only=True)
        self.variables.create_clock_variables(self._clock, prefix='_clock_')
        self._enable_group_attributes()

    def resize(self, new_size):
        # Note that this does not set N, this has to be done in the template
        # since we use a restricted pointer to access it (which promises that
        # we only change the value through this pointer)
        self.variables['rate'].resize(new_size)
        self.variables['t'].resize(new_size)

    def reinit(self):
        """
        Clears all recorded rates
        """
        raise NotImplementedError()

    @check_units(width=second)
    def smooth_rate(self, window='gaussian', width=None):
        """
        smooth_rate(self, window='gaussian', width=None)

        Return a smooth version of the population rate.

        Parameters
        ----------
        window : str, ndarray
            The window to use for smoothing. Can be a string to chose a
            predefined window(``'flat'`` for a rectangular, and ``'gaussian'``
            for a Gaussian-shaped window). In this case the width of the window
            is determined by the ``width`` argument. Note that for the Gaussian
            window, the ``width`` parameter specifies the standard deviation of
            the Gaussian, the width of the actual window is ``4*width + dt``
            (rounded to the nearest dt). For the flat window, the width is
            rounded to the nearest odd multiple of dt to avoid shifting the rate
            in time.
            Alternatively, an arbitrary window can be given as a numpy array
            (with an odd number of elements). In this case, the width in units
            of time depends on the ``dt`` of the simulation, and no ``width``
            argument can be specified. The given window will be automatically
            normalized to a sum of 1.
        width : `Quantity`, optional
            The width of the ``window`` in seconds (for a predefined window).

        Returns
        -------
        rate : `Quantity`
            The population rate in Hz, smoothed with the given window. Note that
            the rates are smoothed and not re-binned, i.e. the length of the
            returned array is the same as the length of the ``rate`` attribute
            and can be plotted against the `PopulationRateMonitor` 's ``t``
            attribute.
        """
        if width is None and isinstance(window, str):
            raise TypeError("Need a width when using a predefined window.")
        if width is not None and not isinstance(window, str):
            raise TypeError("Can only specify a width for a predefined window")

        if isinstance(window, str):
            if window == 'gaussian':
                width_dt = int(np.round(2 * width / self.clock.dt))
                # Rounding only for the size of the window, not for the standard
                # deviation of the Gaussian
                window = np.exp(-np.arange(-width_dt, width_dt + 1)**2 * 1. /
                                (2 * (width / self.clock.dt)**2))
            elif window == 'flat':
                width_dt = int(width / 2 / self.clock.dt) * 2 + 1
                used_width = width_dt * self.clock.dt
                if abs(used_width - width) > 1e-6 * self.clock.dt:
                    logger.info(f'width adjusted from {width} to {used_width}',
                                'adjusted_width',
                                once=True)
                window = np.ones(width_dt)
            else:
                raise NotImplementedError(
                    f'Unknown pre-defined window "{window}"')
        else:
            try:
                window = np.asarray(window)
            except TypeError:
                raise TypeError(f"Cannot use a window of type {type(window)}")
            if window.ndim != 1:
                raise TypeError("The provided window has to be "
                                "one-dimensional.")
            if len(window) % 2 != 1:
                raise TypeError("The window has to have an odd number of "
                                "values.")
        return Quantity(np.convolve(self.rate_,
                                    window * 1. / sum(window),
                                    mode='same'),
                        dim=hertz.dim)

    def __repr__(self):
        classname = self.__class__.__name__
        return f"<{classname}, recording {self.source.name}>"
Ejemplo n.º 55
0
class StateMonitor(Group, CodeRunner):
    '''
    Record values of state variables during a run
    
    To extract recorded values after a run, use the ``t`` attribute for the
    array of times at which values were recorded, and variable name attribute
    for the values. The values will have shape ``(len(indices), len(t))``,
    where ``indices`` are the array indices which were recorded. When indexing
    the `StateMonitor` directly, the returned object can be used to get the
    recorded values for the specified indices, i.e. the indexing semantic
    refers to the indices in ``source``, not to the relative indices of the
    recorded values. For example, when recording only neurons with even numbers,
    `mon[[0, 2]].v` will return the values for neurons 0 and 2, whereas
    `mon.v[[0, 2]]` will return the values for the first and third *recorded*
    neurons, i.e. for neurons 0 and 4.

    Parameters
    ----------
    source : `Group`
        Which object to record values from.
    variables : str, sequence of str, True
        Which variables to record, or ``True`` to record all variables
        (note that this may use a great deal of memory).
    record : None, False, True, sequence of ints, optional
        Which indices to record, nothing is recorded for ``None`` or ``False``,
        everything is recorded for ``True`` (warning: may use a great deal of
        memory), or a specified subset of indices. Defaults to ``None``.
    dt : `Quantity`, optional
        The time step to be used for the monitor. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the ``dt`` argument
        is specified, the clock of the `source` will be used.
    when : str, optional
        At which point during a time step the values should be recorded.
        Defaults to ``'start'``.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'statemonitor_0'``, etc.
    codeobj_class : `CodeObject`, optional
        The `CodeObject` class to create.

    Examples
    --------
    
    Record all variables, first 5 indices::
    
        eqs = """
        dV/dt = (2-V)/(10*ms) : 1
        """
        threshold = 'V>1'
        reset = 'V = 0'
        G = NeuronGroup(100, eqs, threshold=threshold, reset=reset)
        G.V = rand(len(G))
        M = StateMonitor(G, True, record=range(5))
        run(100*ms)
        plot(M.t, M.V.T)
        show()

    Notes
    -----

    Since this monitor by default records in the ``'start'`` time slot,
    recordings of the membrane potential in integrate-and-fire models may look
    unexpected: the recorded membrane potential trace will never be above
    threshold in an integrate-and-fire model, because the reset statement will
    have been applied already. Set the ``when`` keyword to a different value if
    this is not what you want.
    '''
    invalidates_magic_network = False
    add_to_magic_network = True
    def __init__(self, source, variables, record=None, dt=None, clock=None,
                 when='start', order=0, name='statemonitor*', codeobj_class=None):
        self.source = source
        # Make the monitor use the explicitly defined namespace of its source
        # group (if it exists)
        self.namespace = getattr(source, 'namespace', None)
        self.codeobj_class = codeobj_class

        # run by default on source clock at the end
        if dt is None and clock is None:
            clock = source.clock

        # variables should always be a list of strings
        if variables is True:
            variables = source.equations.names
        elif isinstance(variables, str):
            variables = [variables]
        #: The variables to record
        self.record_variables = variables

        # record should always be an array of ints
        self.record_all = False
        if hasattr(record, '_indices'):
            # The ._indices method always returns absolute indices
            # If the source is already a subgroup of another group, we therefore
            # have to shift the indices to become relative to the subgroup
            record = record._indices() - getattr(source, '_offset', 0)
        if record is True:
            self.record_all = True
            record = np.arange(len(source), dtype=np.int32)
        elif record is None or record is False:
            logger.warn(('The StateMonitor set up to record the variable(s) '
                         '{vars} of "{source}" is not recording any value. '
                         'Did you forget to provide the record '
                         'argument?').format(vars=', '.join('"%s"' % var
                                                            for var in variables),
                                             source=self.source.name),
                        once=True)
            record = np.array([], dtype=np.int32)
        elif isinstance(record, numbers.Number):
            record = np.array([record], dtype=np.int32)
        else:
            record = np.asarray(record, dtype=np.int32)
            
        #: The array of recorded indices
        self.record = record
        self.n_indices = len(record)

        # Some dummy code so that code generation takes care of the indexing
        # and subexpressions
        code = ['_to_record_%s = _source_%s' % (v, v)
                for v in variables]
        code = '\n'.join(code)

        CodeRunner.__init__(self, group=self, template='statemonitor',
                            code=code, name=name,
                            clock=clock,
                            dt=dt,
                            when=when,
                            order=order,
                            check_units=False)

        self.add_dependency(source)

        # Setup variables
        self.variables = Variables(self)

        self.variables.add_dynamic_array('t', size=0, unit=second,
                                         constant=False, constant_size=False)
        self.variables.add_attribute_variable('N', unit=Unit(1),
                                              dtype=np.int32,
                                              obj=self, attribute='_N')
        self.variables.add_array('_indices', size=len(self.record),
                                 unit=Unit(1), dtype=self.record.dtype,
                                 constant=True, read_only=True,
                                 values=self.record)
        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')
        for varname in variables:
            var = source.variables[varname]
            if var.scalar and len(self.record) > 1:
                logger.warn(('Variable %s is a shared variable but it will be '
                             'recorded once for every target.' % varname),
                            once=True)
            index = source.variables.indices[varname]
            self.variables.add_reference('_source_%s' % varname,
                                         source, varname, index=index)
            if not index in ('_idx', '0') and index not in variables:
                self.variables.add_reference(index, source)
            self.variables.add_dynamic_array(varname,
                                             size=(0, len(self.record)),
                                             resize_along_first=True,
                                             unit=var.unit,
                                             dtype=var.dtype,
                                             constant=False,
                                             constant_size=False)

        for varname in variables:
            var = self.source.variables[varname]
            self.variables.add_auxiliary_variable('_to_record_' + varname,
                                                  unit=var.unit,
                                                  dtype=var.dtype,
                                                  scalar=var.scalar)

        self.recorded_variables = dict([(varname, self.variables[varname])
                                        for varname in variables])
        recorded_names = [varname for varname in variables]

        self.needed_variables = recorded_names
        self.template_kwds = {'_recorded_variables': self.recorded_variables}
        self._enable_group_attributes()

    @property
    def _N(self):
        return self.variables['t'].get_value().shape[0]

    def __len__(self):
        return self._N

    def resize(self, new_size):
        self.variables['t'].resize(new_size)

        for var in self.recorded_variables.values():
            var.resize((new_size, self.n_indices))

    def reinit(self):
        raise NotImplementedError()

    def __getitem__(self, item):
        dtype = get_dtype(item)
        if np.issubdtype(dtype, np.int):
            return StateMonitorView(self, item)
        elif isinstance(item, collections.Sequence):
            index_array = np.array(item)
            if not np.issubdtype(index_array.dtype, np.int):
                raise TypeError('Index has to be an integer or a sequence '
                                'of integers')
            return StateMonitorView(self, item)
        elif hasattr(item, '_indices'):
            # objects that support the indexing interface will return absolute
            # indices but here we need relative ones
            # TODO: How to we prevent the use of completely unrelated objects here?
            source_offset = getattr(self.source, '_offset', 0)
            return StateMonitorView(self, item._indices() - source_offset)
        else:
            raise TypeError('Cannot use object of type %s as an index'
                            % type(item))

    def __getattr__(self, item):
        # We do this because __setattr__ and __getattr__ are not active until
        # _group_attribute_access_active attribute is set, and if it is set,
        # then __getattr__ will not be called. Therefore, if getattr is called
        # with this name, it is because it hasn't been set yet and so this
        # method should raise an AttributeError to agree that it hasn't been
        # called yet.
        if item == '_group_attribute_access_active':
            raise AttributeError
        if not hasattr(self, '_group_attribute_access_active'):
            raise AttributeError
        if item in self.record_variables:
            unit = self.variables[item].unit
            return Quantity(self.variables[item].get_value().T,
                            dim=unit.dim, copy=True)
        elif item.endswith('_') and item[:-1] in self.record_variables:
            return self.variables[item[:-1]].get_value().T
        else:
            return Group.__getattr__(self, item)

    def __repr__(self):
        description = '<{classname}, recording {variables} from {source}>'
        return description.format(classname=self.__class__.__name__,
                                  variables=repr(self.record_variables),
                                  source=self.source.name)

    def record_single_timestep(self):
        '''
        Records a single time step. Useful for recording the values at the end
        of the simulation -- otherwise a `StateMonitor` will not record the
        last simulated values since its ``when`` attribute defaults to
        ``'start'``, i.e. the last recording is at the *beginning* of the last
        time step.

        Notes
        -----
        This function will only work if the `StateMonitor` has been already run,
        but a run with a length of ``0*ms`` does suffice.

        Examples
        --------
        >>> from brian2 import *
        >>> G = NeuronGroup(1, 'dv/dt = -v/(5*ms) : 1')
        >>> G.v = 1
        >>> mon = StateMonitor(G, 'v', record=True)
        >>> run(0.5*ms)
        >>> mon.v
        array([[ 1.        ,  0.98019867,  0.96078944,  0.94176453,  0.92311635]])
        >>> mon.t[:]
        array([   0.,  100.,  200.,  300.,  400.]) * usecond
        >>> G.v[:]  # last value had not been recorded
        array([ 0.90483742])
        >>> mon.record_single_timestep()
        >>> mon.t[:]
        array([   0.,  100.,  200.,  300.,  400.,  500.]) * usecond
        >>> mon.v[:]
        array([[ 1.        ,  0.98019867,  0.96078944,  0.94176453,  0.92311635,
                 0.90483742]])
        '''
        if self.codeobj is None:
            raise TypeError('Can only record a single time step after the '
                            'network has been run once.')
        self.codeobj()