def test_errors(): # No explicit namespace namespace = create_namespace() assert_raises(KeyError, lambda: namespace['nonexisting_variable']) # Empty explicit namespace namespace = create_namespace({}) assert_raises(KeyError, lambda: namespace['nonexisting_variable'])
def test_unit_checking(): # dummy Variable class class S(object): def __init__(self, unit): self.unit = unit # Let's create a namespace with a user-defined namespace that we can # updater later on namespace = create_namespace({}) # inconsistent unit for a differential equation eqs = Equations('dv/dt = -v : volt') assert_raises(DimensionMismatchError, lambda: eqs.check_units(namespace, {'v': S(volt)})) eqs = Equations('dv/dt = -v / tau: volt') namespace['tau'] = 5*mV assert_raises(DimensionMismatchError, lambda: eqs.check_units(namespace, {'v': S(volt)})) namespace['I'] = 3*second eqs = Equations('dv/dt = -(v + I) / (5 * ms): volt') assert_raises(DimensionMismatchError, lambda: eqs.check_units(namespace, {'v': S(volt)})) eqs = Equations('''dv/dt = -(v + I) / (5 * ms): volt I : second''') assert_raises(DimensionMismatchError, lambda: eqs.check_units(namespace, {'v': S(volt), 'I': S(second)})) # inconsistent unit for a static equation eqs = Equations('''dv/dt = -v / (5 * ms) : volt I = 2 * v : amp''') assert_raises(DimensionMismatchError, lambda: eqs.check_units(namespace, {'v': S(volt), 'I': S(amp)}))
def test_resolution(): # implicit namespace tau = 10 * ms namespace = create_namespace() additional_namespace = ('implicit-namespace', {'tau': tau}) resolved = namespace.resolve_all(['tau', 'ms'], additional_namespace) assert len(resolved) == 2 assert type(resolved) == type(dict()) # make sure that the units are stripped assert resolved['tau'] == np.asarray(tau) assert resolved['ms'] == float(ms) # explicit namespace namespace = create_namespace({'tau': 20 * ms}) resolved = namespace.resolve_all(['tau', 'ms'], additional_namespace) assert len(resolved) == 2 assert resolved['tau'] == np.asarray(20 * ms)
def test_resolution(): # implicit namespace tau = 10*ms namespace = create_namespace() additional_namespace = ('implicit-namespace', {'tau': tau}) resolved = namespace.resolve_all(['tau', 'ms'], additional_namespace) assert len(resolved) == 2 assert type(resolved) == type(dict()) # make sure that the units are stripped assert resolved['tau'] == np.asarray(tau) assert resolved['ms'] == float(ms) # explicit namespace namespace = create_namespace({'tau': 20 * ms}) resolved = namespace.resolve_all(['tau', 'ms'], additional_namespace) assert len(resolved) == 2 assert resolved['tau'] == np.asarray(20 * ms)
def test_explicit_namespace(): ''' Test resolution with an explicitly provided namespace ''' explicit_namespace = {'variable': 'explicit_var'} # Explicitly provided namespace = create_namespace( explicit_namespace) with catch_logs() as l: assert namespace['variable'] == 'explicit_var' assert len(l) == 0
def test_default_content(): ''' Test that the default namespace contains standard units and functions. ''' namespace = create_namespace({}) # Units assert namespace['second'] == second assert namespace['volt'] == volt assert namespace['ms'] == ms assert namespace['Hz'] == Hz assert namespace['mV'] == mV # Functions (the namespace contains variables) assert namespace['sin'].pyfunc == sin assert namespace['log'].pyfunc == log assert namespace['exp'].pyfunc == exp
def test_explicit_namespace(): ''' Test resolution with an explicitly provided namespace ''' explicit_namespace = {'variable': 'explicit_var', 'randn': 'explicit_randn'} # Explicitly provided namespace = create_namespace( explicit_namespace) with catch_logs() as l: assert namespace['variable'] == 'explicit_var' assert len(l) == 0 with catch_logs() as l: # The explicitly provided namespace should not overwrite functions assert isinstance(namespace['randn'], RandnFunction) _assert_one_warning(l)
def test_warning(): from brian2.core.functions import DEFAULT_FUNCTIONS from brian2.units.stdunits import cm as brian_cm # Name in external namespace clashes with unit/function name exp = 23 cm = 42 namespace = create_namespace() local_ns = get_local_namespace(level=0) with catch_logs() as l: resolved = namespace.resolve('exp', ('implicit namespace', local_ns)) assert resolved == DEFAULT_FUNCTIONS['exp'] assert len(l) == 1 assert l[0][1].endswith('.resolution_conflict') with catch_logs() as l: resolved = namespace.resolve('cm', ('implicit namespace', local_ns)) assert resolved == brian_cm assert len(l) == 1 assert l[0][1].endswith('.resolution_conflict')
def test_explicit_namespace(): ''' Test resolution with an explicitly provided namespace ''' explicit_namespace = { 'variable': 'explicit_var', 'randn': 'explicit_randn' } # Explicitly provided namespace = create_namespace(explicit_namespace) with catch_logs() as l: assert namespace['variable'] == 'explicit_var' assert len(l) == 0 with catch_logs() as l: # The explicitly provided namespace should not overwrite functions assert isinstance(namespace['randn'], RandnFunction) _assert_one_warning(l)
def test_parse_expression_unit(): default_namespace = create_namespace({}) varunits = dict(default_namespace) varunits.update({'a': volt * amp, 'b': volt, 'c': amp}) EE = [ (volt * amp, 'a+b*c'), (DimensionMismatchError, 'a+b'), (DimensionMismatchError, 'a<b'), (1, 'a<b*c'), (1, 'a or b'), (1, 'not (a >= b*c)'), (DimensionMismatchError, 'a or b<c'), (1, 'a/(b*c)<1'), (1, 'a/(a-a)'), (1, 'a<mV*mA'), (volt**2, 'b**2'), (volt * amp, 'a%(b*c)'), (volt, '-b'), (1, '(a/a)**(a/a)'), # Expressions involving functions (volt, 'rand()*b'), (volt**0.5, 'sqrt(b)'), (volt, 'ceil(b)'), (volt, 'sqrt(randn()*b**2)'), (1, 'sin(b/b)'), (DimensionMismatchError, 'sin(b)'), (DimensionMismatchError, 'sqrt(b) + b') ] for expect, expr in EE: if expect is DimensionMismatchError: assert_raises(DimensionMismatchError, parse_expression_unit, expr, varunits, {}) else: u = parse_expression_unit(expr, varunits, {}) assert have_same_dimensions(u, expect) wrong_expressions = [ 'a**b', 'a << b', 'ot True' # typo ] for expr in wrong_expressions: assert_raises(SyntaxError, parse_expression_unit, expr, varunits, {})
def test_parse_expression_unit(): default_namespace = create_namespace({}) varunits = dict(default_namespace) varunits.update({'a': volt*amp, 'b': volt, 'c': amp}) EE = [ (volt*amp, 'a+b*c'), (DimensionMismatchError, 'a+b'), (DimensionMismatchError, 'a<b'), (1, 'a<b*c'), (1, 'a or b'), (1, 'not (a >= b*c)'), (DimensionMismatchError, 'a or b<c'), (1, 'a/(b*c)<1'), (1, 'a/(a-a)'), (1, 'a<mV*mA'), (volt**2, 'b**2'), (volt*amp, 'a%(b*c)'), (volt, '-b'), (1, '(a/a)**(a/a)'), # Expressions involving functions (volt, 'rand()*b'), (volt**0.5, 'sqrt(b)'), (volt, 'ceil(b)'), (volt, 'sqrt(randn()*b**2)'), (1, 'sin(b/b)'), (DimensionMismatchError, 'sin(b)'), (DimensionMismatchError, 'sqrt(b) + b') ] for expect, expr in EE: if expect is DimensionMismatchError: assert_raises(DimensionMismatchError, parse_expression_unit, expr, varunits, {}) else: u = parse_expression_unit(expr, varunits, {}) assert have_same_dimensions(u, expect) wrong_expressions = ['a**b', 'a << b', 'ot True' # typo ] for expr in wrong_expressions: assert_raises(SyntaxError, parse_expression_unit, expr, varunits, {})
def test_unit_checking(): # dummy Variable class class S(object): def __init__(self, unit): self.unit = unit # Let's create a namespace with a user-defined namespace that we can # updater later on namespace = create_namespace({}) # inconsistent unit for a differential equation eqs = Equations('dv/dt = -v : volt') assert_raises(DimensionMismatchError, lambda: eqs.check_units(namespace, {'v': S(volt)})) eqs = Equations('dv/dt = -v / tau: volt') namespace['tau'] = 5 * mV assert_raises(DimensionMismatchError, lambda: eqs.check_units(namespace, {'v': S(volt)})) namespace['I'] = 3 * second eqs = Equations('dv/dt = -(v + I) / (5 * ms): volt') assert_raises(DimensionMismatchError, lambda: eqs.check_units(namespace, {'v': S(volt)})) eqs = Equations('''dv/dt = -(v + I) / (5 * ms): volt I : second''') assert_raises( DimensionMismatchError, lambda: eqs.check_units(namespace, { 'v': S(volt), 'I': S(second) })) # inconsistent unit for a static equation eqs = Equations('''dv/dt = -v / (5 * ms) : volt I = 2 * v : amp''') assert_raises( DimensionMismatchError, lambda: eqs.check_units(namespace, { 'v': S(volt), 'I': S(amp) }))
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)
def __init__(self, source, target=None, model=None, pre=None, post=None, connect=False, delay=None, namespace=None, dtype=None, codeobj_class=None, clock=None, method=None, name='synapses*'): self._N = 0 Group.__init__(self, when=clock, name=name) self.codeobj_class = codeobj_class self.source = weakref.proxy(source) if target is None: self.target = self.source else: self.target = weakref.proxy(target) ##### Prepare and validate equations if model is None: model = '' 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: ['event-driven'], STATIC_EQUATION: ['summed'], PARAMETER: ['constant']}) # Separate the equations into event-driven and continuously updated # equations event_driven = [] continuous = [] for single_equation in model.itervalues(): if 'event-driven' in single_equation.flags: event_driven.append(single_equation) else: continuous.append(single_equation) # Add the lastupdate variable, used by event-driven equations continuous.append(SingleEquation(PARAMETER, 'lastupdate', second)) if len(event_driven): self.event_driven = Equations(event_driven) else: self.event_driven = None self.equations = Equations(continuous) # Setup the namespace self._given_namespace = namespace self.namespace = create_namespace(namespace) self._queues = {} self._delays = {} # Setup variables self._create_variables() #: Set of `Variable` objects that should be resized when the #: number of synapses changes self._registered_variables = set() for varname, var in self.variables.iteritems(): if isinstance(var, DynamicArrayVariable): # Register the array with the `SynapticItemMapping` object so # it gets automatically resized self.register_variable(var) #: List of names of all updaters, e.g. ['pre', 'post'] self._synaptic_updaters = [] #: List of all `SynapticPathway` objects self._pathways = [] for prepost, argument in zip(('pre', 'post'), (pre, post)): if not argument: continue if isinstance(argument, basestring): self._add_updater(argument, prepost) elif isinstance(argument, collections.Mapping): for key, value in argument.iteritems(): if not isinstance(key, basestring): err_msg = ('Keys for the "{}" argument' 'have to be strings, got ' '{} instead.').format(prepost, type(key)) raise TypeError(err_msg) self._add_updater(value, prepost, objname=key) # If we have a pathway called "pre" (the most common use case), provide # direct access to its delay via a delay attribute (instead of having # to use pre.delay) if 'pre' in self._synaptic_updaters: self.variables.add_reference('delay', self.pre.variables['delay']) if delay is not None: if isinstance(delay, Quantity): if not 'pre' in self._synaptic_updaters: raise ValueError(('Cannot set delay, no "pre" pathway exists.' 'Use a dictionary if you want to set the ' 'delay for a pathway with a different name.')) delay = {'pre': delay} if not isinstance(delay, collections.Mapping): raise TypeError('Delay argument has to be a quantity or a ' 'dictionary, is type %s instead.' % type(delay)) for pathway, pathway_delay in delay.iteritems(): if not pathway in self._synaptic_updaters: raise ValueError(('Cannot set the delay for pathway ' '"%s": unknown pathway.') % pathway) if not isinstance(pathway_delay, Quantity): raise TypeError(('Cannot set the delay for pathway "%s": ' 'expected a quantity, got %s instead.') % (pathway, type(pathway_delay))) if pathway_delay.size != 1: raise TypeError(('Cannot set the delay for pathway "%s": ' 'expected a scalar quantity, got a ' 'quantity with shape %s instead.') % str(pathway_delay.shape)) fail_for_dimension_mismatch(pathway_delay, second, ('Delay has to be ' 'specified in units ' 'of seconds')) updater = getattr(self, pathway) # For simplicity, store the delay as a one-element array # so that for example updater._delays[:] works. updater._delays.resize(1) updater._delays.set_value(float(pathway_delay)) updater._delays.scalar = True # Do not resize the scalar delay variable when adding synapses self.unregister_variable(updater._delays) #: Performs numerical integration step self.state_updater = StateUpdater(self, method) self.contained_objects.append(self.state_updater) #: "Summed variable" mechanism -- sum over all synapses of a #: pre-/postsynaptic target self.summed_updaters = {} # We want to raise an error if the same variable is updated twice # using this mechanism. This could happen if the Synapses object # connected a NeuronGroup to itself since then all variables are # accessible as var_pre and var_post. summed_targets = set() for single_equation in self.equations.itervalues(): if 'summed' in single_equation.flags: varname = single_equation.varname if not (varname.endswith('_pre') or varname.endswith('_post')): raise ValueError(('The summed variable "%s" does not end ' 'in "_pre" or "_post".') % varname) if not varname in self.variables: raise ValueError(('The summed variable "%s" does not refer' 'do any known variable in the ' 'target group.') % varname) if varname.endswith('_pre'): summed_target = self.source orig_varname = varname[:-4] else: summed_target = self.target orig_varname = varname[:-5] target_eq = getattr(summed_target, 'equations', {}).get(orig_varname, None) if target_eq is None or target_eq.type != PARAMETER: raise ValueError(('The summed variable "%s" needs a ' 'corresponding parameter "%s" in the ' 'target group.') % (varname, orig_varname)) fail_for_dimension_mismatch(self.variables['_summed_'+varname].unit, self.variables[varname].unit, ('Summed variables need to have ' 'the same units in Synapses ' 'and the target group')) if self.variables[varname] in summed_targets: raise ValueError(('The target variable "%s" is already ' 'updated by another summed ' 'variable') % orig_varname) summed_targets.add(self.variables[varname]) updater = SummedVariableUpdater(single_equation.expr, varname, self, summed_target) self.summed_updaters[varname] = updater self.contained_objects.append(updater) # Do an initial connect, if requested if not isinstance(connect, (bool, basestring)): raise TypeError(('"connect" keyword has to be a boolean value or a ' 'string, is type %s instead.' % type(connect))) self._initial_connect = connect if not connect is False: self.connect(connect, level=1) # Activate name attribute access self._enable_group_attributes()
def __init__(self, synapses, code, prepost, objname=None): self.code = code self.prepost = prepost if prepost == 'pre': self.source = synapses.source self.target = synapses.target self.synapse_sources = synapses.variables['_synaptic_pre'] elif prepost == 'post': self.source = synapses.target self.target = synapses.source self.synapse_sources = synapses.variables['_synaptic_post'] else: raise ValueError('prepost argument has to be either "pre" or ' '"post"') self.synapses = synapses if objname is None: objname = prepost + '*' GroupCodeRunner.__init__(self, synapses, 'synapses', code=code, when=(synapses.clock, 'synapses'), name=synapses.name + '_' + objname, template_kwds={'pathway': self}) self._pushspikes_codeobj = None self.spikes_start = self.source.start self.spikes_stop = self.source.stop self.spiking_synapses = [] self.variables = Variables(self) self.variables.add_attribute_variable('_spiking_synapses', unit=Unit(1), obj=self, attribute='spiking_synapses', constant=False, scalar=False) self.variables.add_reference('_spikespace', self.source.variables['_spikespace']) self.variables.add_reference('N', synapses.variables['N']) self.variables.add_dynamic_array('delay', unit=second, size=synapses._N, constant=True, constant_size=True) self._delays = self.variables['delay'] # Register the object with the `SynapticIndex` object so it gets # automatically resized synapses.register_variable(self._delays) # Re-extract the last part of the name from the full name self.objname = self.name[len(synapses.name) + 1:] #: The simulation dt (necessary for the delays) self.dt = self.synapses.clock.dt_ #: The `SpikeQueue` self.queue = None #: The `CodeObject` initalising the `SpikeQueue` at the begin of a run self._initialise_queue_codeobj = None self.namespace = create_namespace(None) # Enable access to the delay attribute via the specifier self._enable_group_attributes()
def __init__(self, N, model, method=None, threshold=None, reset=None, refractory=False, namespace=None, dtype=None, clock=None, name='neurongroup*', codeobj_class=None): Group.__init__(self, when=clock, name=name) 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')}) # add refractoriness if refractory is not False: model = add_refractoriness(model) self.equations = model uses_refractoriness = len(model) and any(['unless refractory' in eq.flags for eq in model.itervalues() if eq.type == DIFFERENTIAL_EQUATION]) logger.debug("Creating NeuronGroup of size {self._N}, " "equations {self.equations}.".format(self=self)) # Setup the namespace self.namespace = create_namespace(namespace) # Setup variables self._create_variables(dtype) # All of the following will be created in before_run #: The threshold condition self.threshold = threshold #: The reset statement(s) self.reset = reset #: 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 #: Performs thresholding step, sets the value of `spikes` self.thresholder = None if self.threshold is not None: self.thresholder = Thresholder(self) #: Resets neurons which have spiked (`spikes`) self.resetter = None if self.reset is not None: self.resetter = Resetter(self) # We try to run a before_run already now. This might fail because of an # incomplete namespace but if the namespace is already complete we # can spot unit errors in the equation already here. try: self.before_run(None) except KeyError: pass #: Performs numerical integration step self.state_updater = StateUpdater(self, method) # Creation of contained_objects that do the work self.contained_objects.append(self.state_updater) if self.thresholder is not None: self.contained_objects.append(self.thresholder) if self.resetter is not None: self.contained_objects.append(self.resetter) if refractory is not False: # Set the refractoriness information self.variables['lastspike'].set_value(-np.inf*second) self.variables['not_refractory'].set_value(True) # Activate name attribute access self._enable_group_attributes()
def __init__(self, N, model, method=None, threshold=None, reset=None, refractory=False, namespace=None, dtype=None, clock=None, name='neurongroup*', codeobj_class=None): BrianObject.__init__(self, when=clock, name=name) 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)) ##### 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') }) # add refractoriness model = add_refractoriness(model) self.equations = model uses_refractoriness = len(model) and any([ 'unless-refractory' in eq.flags for eq in model.itervalues() if eq.type == DIFFERENTIAL_EQUATION ]) logger.debug("Creating NeuronGroup of size {self.N}, " "equations {self.equations}.".format(self=self)) ##### Setup the memory self.arrays = self._allocate_memory(dtype=dtype) self._spikespace = np.zeros(N + 1, dtype=np.int32) # Setup the namespace self.namespace = create_namespace(namespace) # Setup variables self.variables = self._create_variables() # All of the following will be created in pre_run #: The threshold condition self.threshold = threshold #: The reset statement(s) self.reset = reset #: 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 #: Performs thresholding step, sets the value of `spikes` self.thresholder = None if self.threshold is not None: self.thresholder = Thresholder(self) #: Resets neurons which have spiked (`spikes`) self.resetter = None if self.reset is not None: self.resetter = Resetter(self) # We try to run a pre_run already now. This might fail because of an # incomplete namespace but if the namespace is already complete we # can spot unit or syntax errors already here, at creation time. try: self.pre_run(None) except KeyError: pass #: Performs numerical integration step self.state_updater = StateUpdater(self, method) # Creation of contained_objects that do the work self.contained_objects.append(self.state_updater) if self.thresholder is not None: self.contained_objects.append(self.thresholder) if self.resetter is not None: self.contained_objects.append(self.resetter) # Activate name attribute access Group.__init__(self) # Set the refractoriness information self.lastspike = -np.inf * second self.not_refractory = True
def __init__(self, source, target=None, model=None, pre=None, post=None, connect=False, delay=None, namespace=None, dtype=None, codeobj_class=None, clock=None, method=None, name='synapses*'): BrianObject.__init__(self, when=clock, name=name) self.codeobj_class = codeobj_class self.source = weakref.proxy(source) if target is None: self.target = self.source else: self.target = weakref.proxy(target) ##### Prepare and validate equations if model is None: model = '' 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: ['event-driven', 'lumped'], STATIC_EQUATION: ['lumped'], PARAMETER: ['constant', 'lumped'] }) # Separate the equations into event-driven and continuously updated # equations event_driven = [] continuous = [] for single_equation in model.itervalues(): if 'event-driven' in single_equation.flags: if 'lumped' in single_equation.flags: raise ValueError( ('Event-driven variable %s cannot be ' 'a lumped variable.') % single_equation.varname) event_driven.append(single_equation) else: continuous.append(single_equation) # Add the lastupdate variable, used by event-driven equations continuous.append(SingleEquation(PARAMETER, 'lastupdate', second)) if len(event_driven): self.event_driven = Equations(event_driven) else: self.event_driven = None self.equations = Equations(continuous) ##### Setup the memory self.arrays = self._allocate_memory(dtype=dtype) # Setup the namespace self._given_namespace = namespace self.namespace = create_namespace(namespace) self._queues = {} self._delays = {} self.item_mapping = SynapticItemMapping(self) self.indices = { '_idx': self.item_mapping, '_presynaptic_idx': self.item_mapping.synaptic_pre, '_postsynaptic_idx': self.item_mapping.synaptic_post } # Allow S.i instead of S.indices.i, etc. self.i = self.item_mapping.i self.j = self.item_mapping.j self.k = self.item_mapping.k # Setup variables self.variables = self._create_variables() #: List of names of all updaters, e.g. ['pre', 'post'] self._updaters = [] for prepost, argument in zip(('pre', 'post'), (pre, post)): if not argument: continue if isinstance(argument, basestring): self._add_updater(argument, prepost) elif isinstance(argument, collections.Mapping): for key, value in argument.iteritems(): if not isinstance(key, basestring): err_msg = ('Keys for the "{}" argument' 'have to be strings, got ' '{} instead.').format(prepost, type(key)) raise TypeError(err_msg) self._add_updater(value, prepost, objname=key) # If we have a pathway called "pre" (the most common use case), provide # direct access to its delay via a delay attribute (instead of having # to use pre.delay) if 'pre' in self._updaters: self.variables['delay'] = self.pre.variables['delay'] if delay is not None: if isinstance(delay, Quantity): if not 'pre' in self._updaters: raise ValueError( ('Cannot set delay, no "pre" pathway exists.' 'Use a dictionary if you want to set the ' 'delay for a pathway with a different name.')) delay = {'pre': delay} if not isinstance(delay, collections.Mapping): raise TypeError('Delay argument has to be a quantity or a ' 'dictionary, is type %s instead.' % type(delay)) for pathway, pathway_delay in delay.iteritems(): if not pathway in self._updaters: raise ValueError(('Cannot set the delay for pathway ' '"%s": unknown pathway.') % pathway) if not isinstance(pathway_delay, Quantity): raise TypeError(('Cannot set the delay for pathway "%s": ' 'expected a quantity, got %s instead.') % (pathway, type(pathway_delay))) if pathway_delay.size != 1: raise TypeError(('Cannot set the delay for pathway "%s": ' 'expected a scalar quantity, got a ' 'quantity with shape %s instead.') % str(pathway_delay.shape)) fail_for_dimension_mismatch(pathway_delay, second, ('Delay has to be ' 'specified in units ' 'of seconds')) updater = getattr(self, pathway) self.item_mapping.unregister_variable(updater._delays) del updater._delays # For simplicity, store the delay as a one-element array # so that for example updater._delays[:] works. updater._delays = np.array([float(pathway_delay)]) variable = ArrayVariable('delay', second, updater._delays, group_name=self.name, scalar=True) updater.variables['delay'] = variable if pathway == 'pre': self.variables['delay'] = variable #: Performs numerical integration step self.state_updater = StateUpdater(self, method) self.contained_objects.append(self.state_updater) #: "Lumped variable" mechanism -- sum over all synapses of a #: postsynaptic target self.lumped_updaters = {} for single_equation in self.equations.itervalues(): if 'lumped' in single_equation.flags: varname = single_equation.varname # For a lumped variable, we need an equivalent parameter in the # target group if not varname in self.target.variables: raise ValueError( ('The lumped variable %s needs a variable ' 'of the same name in the target ' 'group ') % single_equation.varname) fail_for_dimension_mismatch(self.variables[varname].unit, self.target.variables[varname], ('Lumped variables need to have ' 'the same units in Synapses ' 'and the target group')) # TODO: Add some more stringent check about the type of # variable in the target group updater = LumpedUpdater(varname, self, self.target) self.lumped_updaters[varname] = updater self.contained_objects.append(updater) # Do an initial connect, if requested if not isinstance(connect, (bool, basestring)): raise TypeError( ('"connect" keyword has to be a boolean value or a ' 'string, is type %s instead.' % type(connect))) self._initial_connect = connect if not connect is False: self.connect(connect, level=1) # Activate name attribute access Group.__init__(self)
def __init__(self, source, target=None, model=None, pre=None, post=None, connect=False, delay=None, namespace=None, dtype=None, codeobj_class=None, clock=None, method=None, name='synapses*'): BrianObject.__init__(self, when=clock, name=name) self.codeobj_class = codeobj_class self.source = weakref.proxy(source) if target is None: self.target = self.source else: self.target = weakref.proxy(target) ##### Prepare and validate equations if model is None: model = '' 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: ['event-driven', 'lumped'], STATIC_EQUATION: ['lumped'], PARAMETER: ['constant', 'lumped']}) # Separate the equations into event-driven and continuously updated # equations event_driven = [] continuous = [] for single_equation in model.itervalues(): if 'event-driven' in single_equation.flags: if 'lumped' in single_equation.flags: raise ValueError(('Event-driven variable %s cannot be ' 'a lumped variable.') % single_equation.varname) event_driven.append(single_equation) else: continuous.append(single_equation) # Add the lastupdate variable, used by event-driven equations continuous.append(SingleEquation(PARAMETER, 'lastupdate', second)) if len(event_driven): self.event_driven = Equations(event_driven) else: self.event_driven = None self.equations = Equations(continuous) ##### Setup the memory self.arrays = self._allocate_memory(dtype=dtype) # Setup the namespace self._given_namespace = namespace self.namespace = create_namespace(namespace) self._queues = {} self._delays = {} self.item_mapping = SynapticItemMapping(self) self.indices = {'_idx': self.item_mapping, '_presynaptic_idx': self.item_mapping.synaptic_pre, '_postsynaptic_idx': self.item_mapping.synaptic_post} # Allow S.i instead of S.indices.i, etc. self.i = self.item_mapping.i self.j = self.item_mapping.j self.k = self.item_mapping.k # Setup variables self.variables = self._create_variables() #: List of names of all updaters, e.g. ['pre', 'post'] self._updaters = [] for prepost, argument in zip(('pre', 'post'), (pre, post)): if not argument: continue if isinstance(argument, basestring): self._add_updater(argument, prepost) elif isinstance(argument, collections.Mapping): for key, value in argument.iteritems(): if not isinstance(key, basestring): err_msg = ('Keys for the "{}" argument' 'have to be strings, got ' '{} instead.').format(prepost, type(key)) raise TypeError(err_msg) self._add_updater(value, prepost, objname=key) # If we have a pathway called "pre" (the most common use case), provide # direct access to its delay via a delay attribute (instead of having # to use pre.delay) if 'pre' in self._updaters: self.variables['delay'] = self.pre.variables['delay'] if delay is not None: if isinstance(delay, Quantity): if not 'pre' in self._updaters: raise ValueError(('Cannot set delay, no "pre" pathway exists.' 'Use a dictionary if you want to set the ' 'delay for a pathway with a different name.')) delay = {'pre': delay} if not isinstance(delay, collections.Mapping): raise TypeError('Delay argument has to be a quantity or a ' 'dictionary, is type %s instead.' % type(delay)) for pathway, pathway_delay in delay.iteritems(): if not pathway in self._updaters: raise ValueError(('Cannot set the delay for pathway ' '"%s": unknown pathway.') % pathway) if not isinstance(pathway_delay, Quantity): raise TypeError(('Cannot set the delay for pathway "%s": ' 'expected a quantity, got %s instead.') % (pathway, type(pathway_delay))) if pathway_delay.size != 1: raise TypeError(('Cannot set the delay for pathway "%s": ' 'expected a scalar quantity, got a ' 'quantity with shape %s instead.') % str(pathway_delay.shape)) fail_for_dimension_mismatch(pathway_delay, second, ('Delay has to be ' 'specified in units ' 'of seconds')) updater = getattr(self, pathway) self.item_mapping.unregister_variable(updater._delays) del updater._delays # For simplicity, store the delay as a one-element array # so that for example updater._delays[:] works. updater._delays = np.array([float(pathway_delay)]) variable = ArrayVariable('delay', second, updater._delays, group_name=self.name, scalar=True) updater.variables['delay'] = variable if pathway == 'pre': self.variables['delay'] = variable #: Performs numerical integration step self.state_updater = StateUpdater(self, method) self.contained_objects.append(self.state_updater) #: "Lumped variable" mechanism -- sum over all synapses of a #: postsynaptic target self.lumped_updaters = {} for single_equation in self.equations.itervalues(): if 'lumped' in single_equation.flags: varname = single_equation.varname # For a lumped variable, we need an equivalent parameter in the # target group if not varname in self.target.variables: raise ValueError(('The lumped variable %s needs a variable ' 'of the same name in the target ' 'group ') % single_equation.varname) fail_for_dimension_mismatch(self.variables[varname].unit, self.target.variables[varname], ('Lumped variables need to have ' 'the same units in Synapses ' 'and the target group')) # TODO: Add some more stringent check about the type of # variable in the target group updater = LumpedUpdater(varname, self, self.target) self.lumped_updaters[varname] = updater self.contained_objects.append(updater) # Do an initial connect, if requested if not isinstance(connect, (bool, basestring)): raise TypeError(('"connect" keyword has to be a boolean value or a ' 'string, is type %s instead.' % type(connect))) self._initial_connect = connect if not connect is False: self.connect(connect, level=1) # Activate name attribute access Group.__init__(self)