diff --git a/brian2/codegen/runtime/cython_rt/templates/synapses_create_generator.pyx b/brian2/codegen/runtime/cython_rt/templates/synapses_create_generator.pyx index 6e5690e8a..d58827247 100644 --- a/brian2/codegen/runtime/cython_rt/templates/synapses_create_generator.pyx +++ b/brian2/codegen/runtime/cython_rt/templates/synapses_create_generator.pyx @@ -54,17 +54,23 @@ cdef void _flush_buffer(buf, dynarr, int buf_len): cdef size_t _jump {% endif %} {% endif %} + + {# For a connect call j='k+i for k in range(0, N_post, 2) if k+i < N_post' + "j" is called the "target variable" (and "_post_idx" the "target array", etc.) + "i" is called the "base variable" (and "_pre_idx" the "base array", etc.) + "k" is called the iteration variable #} + # scalar code _vectorisation_idx = 1 {{scalar_code['setup_iterator']|autoindent}} - {{scalar_code['create_j']|autoindent}} + {{scalar_code['generator_expr']|autoindent}} {{scalar_code['create_cond']|autoindent}} - {{scalar_code['update_post']|autoindent}} + {{scalar_code['update']|autoindent}} - for _i in range(N_pre): - _raw_pre_idx = _i + _source_offset + for _{{base_var}} in range({{base_var_size}}): + _raw{{base_array}} = _{{base_var}} + {{base_offset}} - {% if not postsynaptic_condition %} + {% if not target_condition %} {{vector_code['create_cond']|autoindent}} if not _cond: continue @@ -155,35 +161,35 @@ cdef void _flush_buffer(buf, dynarr, int buf_len): {% endif %} {% endif %} - {{vector_code['create_j']|autoindent}} - _raw_post_idx = _j + _target_offset + {{vector_code['generator_expr']|autoindent}} + _raw{{target_array}} = _{{target_var}} + {{target_offset}} - {% if postsynaptic_condition %} - {% if postsynaptic_variable_used %} + {% if target_condition %} + {% if target_variable_used %} {# The condition could index outside of array range #} - if _j<0 or _j>=N_post: + if _{{target_var}}<0 or _{{target_var}}>={{target_var_size}}: {% if skip_if_invalid %} continue {% else %} - raise IndexError("index j=%d outside allowed range from 0 to %d" % (_j, N_post-1)) + raise IndexError("index {{target_var}}=%d outside allowed range from 0 to %d" % (_{{target_var}}, {{target_var_size}}-1)) {% endif %} {% endif %} {{vector_code['create_cond']|autoindent}} {% endif %} - {% if if_expression!='True' and postsynaptic_condition %} + {% if if_expression!='True' and target_condition %} if not _cond: continue {% endif %} - {% if not postsynaptic_variable_used %} + {% if not target_variable_used %} {# Otherwise, we already checked before #} - if _j<0 or _j>=N_post: + if _{{target_var}}<0 or _{{target_var}}>={{target_var_size}}: {% if skip_if_invalid %} continue {% else %} - raise IndexError("index j=%d outside allowed range from 0 to %d" % (_j, N_post-1)) + raise IndexError("index j=%d outside allowed range from 0 to %d" % (_{{target_var}}, {{target_var_size}}-1)) {% endif %} {% endif %} - {{vector_code['update_post']|autoindent}} + {{vector_code['update']|autoindent}} for _repetition in range(_n): _prebuf_ptr[_curbuf] = _pre_idx diff --git a/brian2/codegen/runtime/numpy_rt/templates/synapses_create_generator.py_ b/brian2/codegen/runtime/numpy_rt/templates/synapses_create_generator.py_ index b922f655f..5952d00f1 100644 --- a/brian2/codegen/runtime/numpy_rt/templates/synapses_create_generator.py_ +++ b/brian2/codegen/runtime/numpy_rt/templates/synapses_create_generator.py_ @@ -96,13 +96,18 @@ _cur_num_synapses = _old_num_synapses # scalar code _vectorisation_idx = 1 {{scalar_code['setup_iterator']|autoindent}} -{{scalar_code['create_j']|autoindent}} +{{scalar_code['generator_expr']|autoindent}} {{scalar_code['create_cond']|autoindent}} -{{scalar_code['update_post']|autoindent}} +{{scalar_code['update']|autoindent}} -for _i in range(N_pre): - _raw_pre_idx = _i + _source_offset - {% if not postsynaptic_condition %} +{# For a connect call j='k+i for k in range(0, N_post, 2) if k+i < N_post' +"j" is called the "target variable" (and "_post_idx" the "target array", etc.) +"i" is called the "base variable" (and "_pre_idx" the "base array", etc.) +"k" is called the iteration variable #} + +for _{{base_var}} in range({{base_var_size}}): + _raw{{base_array}} = _{{base_var}} + {{base_offset}} + {% if not target_condition %} {{vector_code['create_cond']|autoindent}} if not _cond: continue @@ -119,76 +124,76 @@ for _i in range(N_pre): {% endif %} _vectorisation_idx = {{iteration_variable}} - {{vector_code['create_j']|autoindent}} - _vectorisation_idx = _j - _raw_post_idx = _j + _target_offset - {% if postsynaptic_condition %} - {% if postsynaptic_variable_used %} + {{vector_code['generator_expr']|autoindent}} + _vectorisation_idx = _{{target_var}} + _raw{{target_array}} = _{{target_var}} + {{target_offset}} + {% if target_condition %} + {% if target_variable_used %} {# The condition could index outside of array range #} {% if skip_if_invalid %} - if _numpy.isscalar(_j): - if _j<0 or _j>=N_post: + if _numpy.isscalar(_{{target_var}}): + if _{{target_var}}<0 or _{{target_var}}>={{target_var_size}}: continue else: - _in_range = _numpy.logical_and(_j>=0, _j=0, _{{target_var}}<{{target_var_size}}) + _{{target_var}} = _{{target_var}}[_in_range] {% else %} - if _numpy.isscalar(_j): - _min_j = _max_j = _j - elif _j.shape == (0, ): + if _numpy.isscalar(_{{target_var}}): + _min_val = _max_val = _{{target_var}} + elif _{{target_var}}.shape == (0, ): continue else: - _min_j = _numpy.min(_j) - _max_j = _numpy.max(_j) - if _min_j < 0: - raise IndexError("index j=%d outside allowed range from 0 to %d, and the condition refers to a post-synaptic variable" % (_min_j, N_post-1)) - elif _max_j >= N_post: - raise IndexError("index j=%d outside allowed range from 0 to %d, and the condition refers to a post-synaptic variable" % (_max_j, N_post-1)) + _min_val = _numpy.min(_{{target_var}}) + _max_val = _numpy.max(_{{target_var}}) + if _min_val < 0: + raise IndexError("index {{target_var}}=%d outside allowed range from 0 to %d, and the condition refers to a post-synaptic variable" % (_min_val, {{target_var_size}}-1)) + elif _max_val >= {{target_var_size}}: + raise IndexError("index {{target_var}}=%d outside allowed range from 0 to %d, and the condition refers to a post-synaptic variable" % (_max_val, {{target_var_size}}-1)) {% endif %} {% endif %} {{vector_code['create_cond']|autoindent}} {% endif %} - {% if if_expression!='True' and postsynaptic_condition %} - _j, _cond = _numpy.broadcast_arrays(_j, _cond) - _j = _j[_cond] + {% if if_expression!='True' and target_condition %} + _{{target_var}}, _cond = _numpy.broadcast_arrays(_{{target_var}}, _cond) + _{{target_var}} = _{{target_var}}[_cond] {% else %} - _j, {{iteration_variable}} = _numpy.broadcast_arrays(_j, {{iteration_variable}}) + _{{target_var}}, {{iteration_variable}} = _numpy.broadcast_arrays(_{{target_var}}, {{iteration_variable}}) {% endif %} {% if skip_if_invalid %} - if _numpy.isscalar(_j): - if _j<0 or _j>=N_post: + if _numpy.isscalar(_{{target_var}}): + if _{{target_var}}<0 or _{{target_var}}>={{target_var_size}}: continue else: - _in_range = _numpy.logical_and(_j>=0, _j=0, _{{target_var}}<{{target_var_size}}) + _{{target_var}} = _{{target_var}}[_in_range] + {% elif not target_variable_used %} {# Otherwise, we already checked before #} - if _numpy.isscalar(_j): - _min_j = _max_j = _j - elif _j.shape == (0, ): + if _numpy.isscalar(_{{target_var}}): + _min_val = _max_val = _{{target_var}} + elif _{{target_var}}.shape == (0, ): continue else: - _min_j = _numpy.min(_j) - _max_j = _numpy.max(_j) - if _min_j < 0: - raise IndexError("index j=%d outside allowed range from 0 to %d" % (_min_j, N_post-1)) - elif _max_j >= N_post: - raise IndexError("index j=%d outside allowed range from 0 to %d" % (_max_j, N_post-1)) + _min_val = _numpy.min(_{{target_var}}) + _max_val = _numpy.max(_{{target_var}}) + if _min_val < 0: + raise IndexError("index _{{target_var}}=%d outside allowed range from 0 to %d" % (_min_val, {{target_var_size}}-1)) + elif _max_val >= {{target_var_size}}: + raise IndexError("index _{{target_var}}=%d outside allowed range from 0 to %d" % (_max_val, {{target_var_size}}-1)) {% endif %} - _vectorisation_idx = _j - _raw_post_idx = _j + _target_offset - {{vector_code['update_post']|autoindent}} + _vectorisation_idx = _{{target_var}} + _raw{{target_array}} = _{{target_var}} + {{target_offset}} + {{vector_code['update']|autoindent}} if not _numpy.isscalar(_n): - # The "n" expression involved j - _post_idx = _post_idx.repeat(_n[_j]) + # The "n" expression involved the target variable + {{target_array}} = {{target_array}}.repeat(_n[_j]) elif _n != 1: - # We have a j-independent number - _post_idx = _post_idx.repeat(_n) + # We have a target-independent number + {{target_array}} = {{target_array}}.repeat(_n) + _numnew = len({{target_array}}) - _numnew = len(_post_idx) _new_num_synapses = _cur_num_synapses + _numnew {{_dynamic__synaptic_pre}}.resize(_new_num_synapses) {{_dynamic__synaptic_post}}.resize(_new_num_synapses) diff --git a/brian2/devices/cpp_standalone/templates/synapses_create_generator.cpp b/brian2/devices/cpp_standalone/templates/synapses_create_generator.cpp index 047208a4d..503dbf5d5 100644 --- a/brian2/devices/cpp_standalone/templates/synapses_create_generator.cpp +++ b/brian2/devices/cpp_standalone/templates/synapses_create_generator.cpp @@ -17,17 +17,22 @@ {{_dynamic_N_incoming}}.resize(_N_post + _target_offset); {{_dynamic_N_outgoing}}.resize(_N_pre + _source_offset); size_t _raw_pre_idx, _raw_post_idx; + {# For a connect call j='k+i for k in range(0, N_post, 2) if k+i < N_post' + "j" is called the "target variable" (and "_post_idx" the "target array", etc.) + "i" is called the "base variable" (and "_pre_idx" the "base array", etc.) + "k" is called the iteration variable #} + // scalar code const size_t _vectorisation_idx = -1; {{scalar_code['setup_iterator']|autoindent}} - {{scalar_code['create_j']|autoindent}} + {{scalar_code['generator_expr']|autoindent}} {{scalar_code['create_cond']|autoindent}} - {{scalar_code['update_post']|autoindent}} - for(size_t _i=0; _i<_N_pre; _i++) + {{scalar_code['update']|autoindent}} + for(size_t _{{base_var}}=0; _{{base_var}}<_{{base_var_size}}; _{{base_var}}++) { bool __cond, _cond; - _raw_pre_idx = _i + _source_offset; - {% if not postsynaptic_condition %} + _raw{{base_array}} = _{{base_var}} + {{base_offset}}; + {% if not target_condition %} { {{vector_code['create_cond']|autoindent}} __cond = _cond; @@ -164,26 +169,26 @@ } {% endif %} {% endif %} - long __j, _j, _pre_idx, __pre_idx; + long __{{target_var}}, _{{target_var}}, {{base_array}}, _{{base_array}}; { - {{vector_code['create_j']|autoindent}} - __j = _j; // pick up the locally scoped _j and store in __j - __pre_idx = _pre_idx; + {{vector_code['generator_expr']|autoindent}} + __{{target_var}} = _{{target_var}}; // pick up the locally scoped var and store in outer var + _{{base_array}} = {{base_array}}; } - _j = __j; // make the previously locally scoped _j available - _pre_idx = __pre_idx; - _raw_post_idx = _j + _target_offset; - {% if postsynaptic_condition %} + _{{target_var}} = __{{target_var}}; // make the previously locally scoped var available + {{base_array}} = _{{base_array}}; + _raw{{target_array}} = _{{target_var}} + {{target_offset}}; + {% if target_condition %} { - {% if postsynaptic_variable_used %} + {% if target_variable_used %} {# The condition could index outside of array range #} - if(_j<0 || _j>=_N_post) + if(_{{target_var}}<0 || _{{target_var}}>=_{{target_var_size}}) { {% if skip_if_invalid %} continue; {% else %} - cout << "Error: tried to create synapse to neuron j=" << _j << " outside range 0 to " << - _N_post-1 << endl; + cout << "Error: tried to create synapse to neuron {{target_var}}=" << _{{target_var}} << " outside range 0 to " << + _{{target_var_size}}-1 << endl; exit(1); {% endif %} } @@ -197,20 +202,20 @@ {% if if_expression!='True' %} if(!_cond) continue; {% endif %} - {% if not postsynaptic_variable_used %} + {% if not target_variable_used %} {# Otherwise, we already checked before #} - if(_j<0 || _j>=_N_post) + if(_{{target_var}}<0 || _{{target_var}}>=_{{target_var_size}}) { {% if skip_if_invalid %} continue; {% else %} - cout << "Error: tried to create synapse to neuron j=" << _j << " outside range 0 to " << - _N_post-1 << endl; + cout << "Error: tried to create synapse to neuron {{target_var}}=" << _{{target_var}} << + " outside range 0 to " << _{{target_var_size}}-1 << endl; exit(1); {% endif %} } {% endif %} - {{vector_code['update_post']|autoindent}} + {{vector_code['update']|autoindent}} for (size_t _repetition=0; _repetition<_n; _repetition++) { {{_dynamic_N_outgoing}}[_pre_idx] += 1; diff --git a/brian2/synapses/synapses.py b/brian2/synapses/synapses.py index 95d252278..d1bc78be0 100644 --- a/brian2/synapses/synapses.py +++ b/brian2/synapses/synapses.py @@ -1307,9 +1307,10 @@ def connect(self, condition=None, i=None, j=None, p=1., n=1, The expression can depend on indices ``i`` and ``j`` and on pre- and post-synaptic variables. Can be combined with arguments ``n``, and ``p`` but not ``i`` or ``j``. - i : int, ndarray of int, optional - The presynaptic neuron indices (in the form of an index or an array - of indices). Must be combined with ``j`` argument. + i : int, ndarray of int, str, optional + The presynaptic neuron indices It can be an index or array of + indices if combined with the ``j`` argument, or it can be a string + generator expression. j : int, ndarray of int, str, optional The postsynaptic neuron indices. It can be an index or array of indices if combined with the ``i`` argument, or it can be a string @@ -1350,32 +1351,7 @@ def connect(self, condition=None, i=None, j=None, p=1., n=1, >>> S.connect(j='k for k in sample(N_post, size=i//2)') # Each neuron connects to i//2 other neurons (chosen randomly) ''' # check types - if condition is not None and not isinstance(condition, (bool, - str)): - raise TypeError("condition argument must be bool or string. If you " - "want to connect based on indices, use " - "connect(i=..., j=...).") - if i is not None and (not (isinstance(i, (numbers.Integral, - np.ndarray, - Sequence)) or - hasattr(i, '_indices')) or - isinstance(i, str)): - raise TypeError("i argument must be int or array") - if j is not None and not (isinstance(j, (numbers.Integral, - np.ndarray, - Sequence)) or - hasattr(j, '_indices')): - raise TypeError("j argument must be int, array or string") - # TODO: eliminate these restrictions - if not isinstance(p, (int, float, str)): - raise TypeError("p must be float or string") - if not isinstance(n, (int, str)): - raise TypeError("n must be int or string") - if isinstance(condition, str) and re.search(r'\bfor\b', - condition): - raise ValueError("Generator expression given for condition, write " - "connect(j='{condition}'...) instead of " - "connect('{condition}'...).".format(condition=condition)) + self._verify_connect_argument_types(condition, i, j, n, p) self._connect_called = True @@ -1383,113 +1359,137 @@ def connect(self, condition=None, i=None, j=None, p=1., n=1, if namespace is None: namespace = get_local_namespace(level=level + 2) - # which connection case are we in? - if condition is None and i is None and j is None: - condition = True - try: + try: # wrap everything to catch IndexError + # which connection case are we in? + # 1: Connection condition + if condition is None and i is None and j is None: + condition = True if condition is not None: if i is not None or j is not None: raise ValueError("Cannot combine condition with i or j " "arguments") - # convert to generator syntax - if condition is False: + if condition is False or condition == 'False': + # Nothing to do return - if condition is True: - condition = 'True' - # Check that the condition is a boolean expresion - identifiers = get_identifiers(condition) - variables = self.resolve_all(identifiers, namespace) - if not is_boolean_expression(condition, variables): - raise TypeError(f'Condition \'{condition}\' is not a ' - f'boolean condition') - - # Check the units (mostly to check for unit consistency within the condition) - dims = parse_expression_dimensions(condition, variables) - if dims is not DIMENSIONLESS: - # We should not get here normally - raise TypeError(f'Condition \'{condition}\' is not a ' - f'boolean condition') - - condition = word_substitute(condition, {'j': '_k'}) - if not isinstance(p, str) and p == 1: - j = ('_k for _k in range(N_post) ' - 'if {expr}').format(expr=condition) - else: - j = None - if isinstance(p, str): - identifiers = get_identifiers(p) - variables = self.resolve_all(identifiers, namespace) - dim = parse_expression_dimensions(p, variables) - if dim is not DIMENSIONLESS: - raise DimensionMismatchError('Expression for p should be dimensionless.') - p_dep = self._expression_index_dependence(p, namespace=namespace) - if '_postsynaptic_idx' in p_dep or '_iterator_idx' in p_dep: - j = ('_k for _k in range(N_post) ' - 'if ({expr}) and ' - 'rand()<{p}').format(expr=condition, p=p) - if j is None: - j = ('_k for _k in sample(N_post, p={p}) ' - 'if {expr}').format(expr=condition, p=p) - # will now call standard generator syntax (see below) - elif i is not None: - if j is None: - raise ValueError("i argument must be combined with j " - "argument") + j = self._condition_to_generator_expression(condition, p, namespace) + self._add_synapses_generator(j, n, skip_if_invalid=skip_if_invalid, + namespace=namespace, level=level + 2, + over_presynaptic=True) + # 2: connection indices + elif (i is not None and j is not None) and not (isinstance(i, str) or isinstance(j, str)): if skip_if_invalid: raise ValueError("Can only use skip_if_invalid with string " "syntax") - if hasattr(i, '_indices'): - i = i._indices() - i = np.asarray(i) - if not np.issubdtype(i.dtype, np.signedinteger): - raise TypeError(('Presynaptic indices have to be given as ' - 'integers, are type %s ' - 'instead.') % i.dtype) - - if hasattr(j, '_indices'): - j = j._indices() - j = np.asarray(j) - if not np.issubdtype(j.dtype, np.signedinteger): - raise TypeError(('Presynaptic indices can only be combined ' - 'with postsynaptic integer indices))')) - if isinstance(n, str): - raise TypeError(('Indices cannot be combined with a string' - 'expression for n. Either use an ' - 'array/scalar for n, or a string ' - 'expression for the connections')) - i, j, n = np.broadcast_arrays(i, j, n) - if i.ndim > 1: - raise ValueError('Can only use 1-dimensional indices') + i, j, n = self._verify_connect_array_arguments(i, j, n) self._add_synapses_from_arrays(i, j, n, p, namespace=namespace) - return - elif j is not None: - if isinstance(p, str) or p != 1: - raise ValueError("Generator syntax cannot be combined with " - "p argument") - if not re.search(r'\bfor\b', j): - if_split = j.split(' if ') - if len(if_split) == 1: - j = '{j} for _ in range(1)'.format(j=j) - elif len(if_split) == 2: - j = '{target} for _ in range(1) if {cond}'.format(target=if_split[0], - cond=if_split[1]) - else: - raise SyntaxError("Error parsing expression '{j}'. " - "Expression must have generator " - "syntax, for example 'k for k in " - "range(i-10, i+10)'".format(j=j)) - # will now call standard generator syntax (see below) + # 3: Generator expression over post-synaptic cells (i='...') + elif isinstance(i, str): + i = self._finalize_generator_expression(i, j, p, 'i', 'j') + self._add_synapses_generator(i, n, skip_if_invalid=skip_if_invalid, + namespace=namespace, level=level + 2, + over_presynaptic=False) + # 4: Generator expression over pre-synaptic cells (i='...') + elif isinstance(j, str): + j = self._finalize_generator_expression(j, i, p, 'j', 'i') + self._add_synapses_generator(j, n, skip_if_invalid=skip_if_invalid, + namespace=namespace, level=level + 2, + over_presynaptic=True) else: raise ValueError("Must specify at least one of condition, i or " "j arguments") - - # standard generator syntax - self._add_synapses_generator(j, n, skip_if_invalid=skip_if_invalid, - namespace=namespace, level=level+2) except IndexError as e: raise IndexError("Tried to create synapse indices outside valid " "range. Original error message: " + str(e)) + # Helper functions for Synapses.connect ↑ + def _verify_connect_array_arguments(self, i, j, n): + if hasattr(i, '_indices'): + i = i._indices() + i = np.asarray(i) + if not np.issubdtype(i.dtype, np.signedinteger): + raise TypeError(('Presynaptic indices have to be given as ' + 'integers, are type %s ' + 'instead.') % i.dtype) + if hasattr(j, '_indices'): + j = j._indices() + j = np.asarray(j) + if not np.issubdtype(j.dtype, np.signedinteger): + raise TypeError(('Presynaptic indices can only be combined ' + 'with postsynaptic integer indices))')) + if isinstance(n, str): + raise TypeError(('Indices cannot be combined with a string' + 'expression for n. Either use an ' + 'array/scalar for n, or a string ' + 'expression for the connections')) + i, j, n = np.broadcast_arrays(i, j, n) + if i.ndim > 1: + raise ValueError('Can only use 1-dimensional indices') + return i, j, n + + def _condition_to_generator_expression(self, condition, p, namespace): + if condition is True: + condition = 'True' + # Check that the condition is a boolean expresion + identifiers = get_identifiers(condition) + variables = self.resolve_all(identifiers, namespace) + if not is_boolean_expression(condition, variables): + raise TypeError(f'Condition \'{condition}\' is not a ' + f'boolean condition') + # Check the units (mostly to check for unit consistency within the condition) + dims = parse_expression_dimensions(condition, variables) + if dims is not DIMENSIONLESS: + # We should not get here normally + raise TypeError(f'Condition \'{condition}\' is not a ' + f'boolean condition') + condition = word_substitute(condition, {'j': '_k'}) + if not isinstance(p, str) and p == 1: + j = ('_k for _k in range(N_post) ' + 'if {expr}').format(expr=condition) + else: + j = None + if isinstance(p, str): + identifiers = get_identifiers(p) + variables = self.resolve_all(identifiers, namespace) + dim = parse_expression_dimensions(p, variables) + if dim is not DIMENSIONLESS: + raise DimensionMismatchError('Expression for p should be dimensionless.') + p_dep = self._expression_index_dependence(p, namespace=namespace) + if '_postsynaptic_idx' in p_dep or '_iterator_idx' in p_dep: + j = ('_k for _k in range(N_post) ' + 'if ({expr}) and ' + 'rand()<{p}').format(expr=condition, p=p) + if j is None: + j = ('_k for _k in sample(N_post, p={p}) ' + 'if {expr}').format(expr=condition, p=p) + return j + + def _verify_connect_argument_types(self, condition, i, j, n, p): + if condition is not None and not isinstance(condition, (bool, + str)): + raise TypeError("condition argument must be bool or string. If you " + "want to connect based on indices, use " + "connect(i=..., j=...).") + if i is not None and not (isinstance(i, (numbers.Integral, + np.ndarray, + Sequence)) or + hasattr(i, '_indices')): + raise TypeError("i argument must be int, array or string") + if j is not None and not (isinstance(j, (numbers.Integral, + np.ndarray, + Sequence)) or + hasattr(j, '_indices')): + raise TypeError("j argument must be int, array or string") + # TODO: eliminate these restrictions + if not isinstance(p, (int, float, str)): + raise TypeError("p must be float or string") + if not isinstance(n, (int, str)): + raise TypeError("n must be int or string") + if isinstance(condition, str) and re.search(r'\bfor\b', + condition): + raise ValueError("Generator expression given for condition, write " + "connect(j='{condition}'...) instead of " + "connect('{condition}'...).".format(condition=condition)) + def check_variable_write(self, variable): ''' Checks that `Synapses.connect` has been called before setting a @@ -1677,17 +1677,18 @@ def _expression_index_dependence(self, expr, namespace, deps.remove('0') return deps - def _add_synapses_generator(self, j, n, skip_if_invalid=False, namespace=None, level=0): + def _add_synapses_generator(self, gen, n, skip_if_invalid=False, + over_presynaptic=True, namespace=None, level=0): # Get the local namespace if namespace is None: namespace = get_local_namespace(level=level+1) - parsed = parse_synapse_generator(j) + parsed = parse_synapse_generator(gen) self._check_parsed_synapses_generator(parsed, namespace) # Referring to N_incoming/N_outgoing in the connect statement is # ill-defined (see github issue #1227) - identifiers = get_identifiers_recursively([j], self.variables) + identifiers = get_identifiers_recursively([gen], self.variables) for var in ['N_incoming', 'N_outgoing']: if var in identifiers: raise ValueError(f'The connect statement cannot refer to ' @@ -1696,52 +1697,74 @@ def _add_synapses_generator(self, j, n, skip_if_invalid=False, namespace=None, l template_kwds, needed_variables = self._get_multisynaptic_indices() template_kwds.update(parsed) template_kwds['skip_if_invalid'] = skip_if_invalid - + # To support both i='...' and j='...' syntax, we provide additional keywords + # to the template + base_var = 'i' if over_presynaptic else 'j' + base_var_size = 'N_pre' if over_presynaptic else 'N_post' + base_array = '_pre_idx' if over_presynaptic else '_post_idx' + base_offset = '_source_offset' if over_presynaptic else '_target_offset' + target_var = 'j' if over_presynaptic else 'i' + target_var_size = 'N_post' if over_presynaptic else 'N_pre' + target_idx = '_postsynaptic_idx' if over_presynaptic else '_presynaptic_idx' + target_array = '_post_idx' if over_presynaptic else '_pre_idx' + target_offset = '_target_offset' if over_presynaptic else '_source_offset' + # for error messages: + target_idx_name = 'postsynaptic' if over_presynaptic else 'presynaptic' + template_kwds.update({'base_var': base_var, + 'base_var_size': base_var_size, + 'base_array': base_array, + 'base_offset': base_offset, + 'target_var': target_var, + 'target_var_size': target_var_size, + 'target_idx': target_idx, + 'target_array': target_array, + 'target_offset': target_offset}) abstract_code = {'setup_iterator': '', - 'create_j': '', + 'generator_expr': '', 'create_cond': '', - 'update_post': ''} + 'update': ''} additional_indices = {parsed['iteration_variable']: '_iterator_idx'} setupiter = '' for k, v in parsed['iterator_kwds'].items(): - if v is not None and k!='sample_size': + if v is not None and k != 'sample_size': deps = self._expression_index_dependence(v, namespace=namespace, additional_indices=additional_indices) - if '_postsynaptic_idx' in deps or '_iterator_idx' in deps: - raise ValueError('Expression "{}" depends on postsynaptic ' - 'index or iterator'.format(v)) - setupiter += '_iter_'+k+' = '+v+'\n' + if target_idx in deps or '_iterator_idx' in deps: + raise ValueError(f'Expression "{v}" depends on {target_idx_name} ' + f'index or iterator') + setupiter += f'_iter_{k} = {v}\n' # rand() in the if condition depends on _vectorisation_idx, but not if # its in the range expression (handled above) additional_indices['_vectorisation_idx'] = '_iterator_idx' - postsynaptic_condition = False - postsynaptic_variable_used = False + target_idx_condition = False + target_variable_used = False if parsed['if_expression'] is not None: deps = self._expression_index_dependence(parsed['if_expression'], namespace=namespace, additional_indices=additional_indices) - if '_postsynaptic_idx' in deps: - postsynaptic_condition = True - postsynaptic_variable_used = True + if target_idx in deps: + target_idx_condition = True + target_variable_used = True elif '_iterator_idx' in deps: - postsynaptic_condition = True - template_kwds['postsynaptic_condition'] = postsynaptic_condition - template_kwds['postsynaptic_variable_used'] = postsynaptic_variable_used + target_idx_condition = True + template_kwds['target_condition'] = target_idx_condition + template_kwds['target_variable_used'] = target_variable_used abstract_code['setup_iterator'] += setupiter - abstract_code['create_j'] += '_pre_idx = _raw_pre_idx \n' - abstract_code['create_j'] += '_j = '+parsed['element']+'\n' - if postsynaptic_condition: - abstract_code['create_cond'] += '_post_idx = _raw_post_idx \n' + abstract_code['generator_expr'] += f'{base_array} = _raw{base_array} \n' + abstract_code['generator_expr'] += f'_{target_var} = {parsed["element"]}\n' + + if target_idx_condition: + abstract_code['create_cond'] += f'{target_array} = _raw{target_array} \n' if parsed['if_expression'] is not None: abstract_code['create_cond'] += ('_cond = ' + parsed['if_expression'] + '\n') - abstract_code['update_post'] += '_post_idx = _raw_post_idx \n' - abstract_code['update_post'] += '_n = ' + str(n) + '\n' + abstract_code['update'] += f'{target_array} = _raw{target_array} \n' + abstract_code['update'] += '_n = ' + str(n) + '\n' # This overwrites 'i' and 'j' in the synapses' variables dictionary # This is necessary because in the context of synapse creation, i @@ -1822,5 +1845,26 @@ def _check_parsed_synapses_generator(self, parsed, namespace): annotated = brian_ast(arg, variables) if annotated.dtype != 'integer': raise TypeError('The "%s" argument of the range function was ' - '"%s", but it needs to be an ' - 'integer.' % (argname, arg)) + '"%s", but it needs to be an ' + 'integer.' % (argname, arg)) + + def _finalize_generator_expression(self, generator_expression, iteration_index, p, + target_index_name, iteration_index_name): + if iteration_index is not None: + raise TypeError(f'Generator syntax for {target_index_name} cannot be combined with ' + f'{iteration_index_name} argument') + if isinstance(p, str) or p != 1: + raise ValueError("Generator syntax cannot be combined with " + "p argument") + if not re.search(r'\bfor\b', generator_expression): + if_split = generator_expression.split(' if ') + if len(if_split) == 1: + generator_expression = f'{generator_expression} for _ in range(1)' + elif len(if_split) == 2: + generator_expression = f'{if_split[0]} for _ in range(1) if {if_split[1]}' + else: + raise SyntaxError(f"Error parsing expression '{generator_expression}'. " + f"Expression must have generator " + f"syntax, for example 'k for k in " + f"range({iteration_index_name}-10, {iteration_index_name}+10)'") + return generator_expression diff --git a/brian2/tests/test_subgroup.py b/brian2/tests/test_subgroup.py index 627c405c1..032850a4e 100644 --- a/brian2/tests/test_subgroup.py +++ b/brian2/tests/test_subgroup.py @@ -156,13 +156,13 @@ def test_shared_variable(): @pytest.mark.standalone_compatible def test_synapse_creation(): - G1 = NeuronGroup(10, '', threshold='False') - G2 = NeuronGroup(20, '', threshold='False') + G1 = NeuronGroup(10, '') + G2 = NeuronGroup(20, '') SG1 = G1[:5] SG2 = G2[10:] - S = Synapses(SG1, SG2, 'w:1') + S = Synapses(SG1, SG2) S.connect(i=2, j=2) # Should correspond to (2, 12) - S.connect('i==2 and j==5') # Should correspond to (2, 15) + S.connect('i==2 and j==5') # Should correspond to (2, 15) run(0*ms) # for standalone @@ -177,26 +177,27 @@ def test_synapse_creation(): assert all(S.N_incoming[:, 2] == 1) assert all(S.N_incoming[:, 5] == 1) + @pytest.mark.standalone_compatible def test_synapse_creation_state_vars(): - G1 = NeuronGroup(10, 'v : 1', threshold='False') - G2 = NeuronGroup(20, 'v : 1', threshold='False') + G1 = NeuronGroup(10, 'v : 1') + G2 = NeuronGroup(20, 'v : 1') G1.v = 'i' G2.v = '10 + i' SG1 = G1[:5] SG2 = G2[10:] # connect based on pre-/postsynaptic state variables - S2 = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S2 = Synapses(SG1, SG2, 'w:1') S2.connect('v_pre > 2') - S3 = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S3 = Synapses(SG1, SG2, 'w:1') S3.connect('v_post < 25') - S4 = Synapses(SG2, SG1, 'w:1', on_pre='v+=w') + S4 = Synapses(SG2, SG1, 'w:1') S4.connect('v_post > 2') - S5 = Synapses(SG2, SG1, 'w:1', on_pre='v+=w') + S5 = Synapses(SG2, SG1, 'w:1') S5.connect('v_pre < 25') run(0*ms) # for standalone @@ -214,26 +215,26 @@ def test_synapse_creation_state_vars(): @pytest.mark.standalone_compatible def test_synapse_creation_generator(): - G1 = NeuronGroup(10, 'v:1', threshold='False') - G2 = NeuronGroup(20, 'v:1', threshold='False') + G1 = NeuronGroup(10, 'v:1') + G2 = NeuronGroup(20, 'v:1') G1.v = 'i' G2.v = '10 + i' SG1 = G1[:5] SG2 = G2[10:] - S = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S = Synapses(SG1, SG2, 'w:1') S.connect(j='i*2 + k for k in range(2)') # diverging connections # connect based on pre-/postsynaptic state variables - S2 = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S2 = Synapses(SG1, SG2, 'w:1') S2.connect(j='k for k in range(N_post) if v_pre > 2') - S3 = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S3 = Synapses(SG1, SG2, 'w:1') S3.connect(j='k for k in range(N_post) if v_post < 25') - S4 = Synapses(SG2, SG1, 'w:1', on_pre='v+=w') + S4 = Synapses(SG2, SG1, 'w:1') S4.connect(j='k for k in range(N_post) if v_post > 2') - S5 = Synapses(SG2, SG1, 'w:1', on_pre='v+=w') + S5 = Synapses(SG2, SG1, 'w:1') S5.connect(j='k for k in range(N_post) if v_pre < 25') run(0*ms) # for standalone @@ -262,40 +263,40 @@ def test_synapse_creation_generator(): @pytest.mark.standalone_compatible def test_synapse_creation_generator_multiple_synapses(): - G1 = NeuronGroup(10, 'v:1', threshold='False') - G2 = NeuronGroup(20, 'v:1', threshold='False') + G1 = NeuronGroup(10, 'v:1') + G2 = NeuronGroup(20, 'v:1') G1.v = 'i' G2.v = '10 + i' SG1 = G1[:5] SG2 = G2[10:] - S1 = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S1 = Synapses(SG1, SG2) S1.connect(j='k for k in range(N_post)', n='i') - S2 = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S2 = Synapses(SG1, SG2) S2.connect(j='k for k in range(N_post)', n='j') - S3 = Synapses(SG2, SG1, 'w:1', on_pre='v+=w') + S3 = Synapses(SG2, SG1) S3.connect(j='k for k in range(N_post)', n='i') - S4 = Synapses(SG2, SG1, 'w:1', on_pre='v+=w') + S4 = Synapses(SG2, SG1) S4.connect(j='k for k in range(N_post)', n='j') - S5 = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S5 = Synapses(SG1, SG2) S5.connect(j='k for k in range(N_post)', n='i+j') - S6 = Synapses(SG2, SG1, 'w:1', on_pre='v+=w') + S6 = Synapses(SG2, SG1) S6.connect(j='k for k in range(N_post)', n='i+j') - S7 = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S7 = Synapses(SG1, SG2) S7.connect(j='k for k in range(N_post)', n='int(v_pre>2)*2') - S8 = Synapses(SG2, SG1, 'w:1', on_pre='v+=w') + S8 = Synapses(SG2, SG1) S8.connect(j='k for k in range(N_post)', n='int(v_post>2)*2') - S9 = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S9 = Synapses(SG1, SG2) S9.connect(j='k for k in range(N_post)', n='int(v_post>22)*2') - S10 = Synapses(SG2, SG1, 'w:1', on_pre='v+=w') + S10 = Synapses(SG2, SG1) S10.connect(j='k for k in range(N_post)', n='int(v_pre>22)*2') run(0*ms) # for standalone @@ -320,21 +321,21 @@ def test_synapse_creation_generator_multiple_synapses(): @pytest.mark.standalone_compatible def test_synapse_creation_generator_complex_ranges(): - G1 = NeuronGroup(10, 'v:1', threshold='False') - G2 = NeuronGroup(20, 'v:1', threshold='False') + G1 = NeuronGroup(10, 'v:1') + G2 = NeuronGroup(20, 'v:1') G1.v = 'i' G2.v = '10 + i' SG1 = G1[:5] SG2 = G2[10:] - S = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S = Synapses(SG1, SG2) S.connect(j='i+k for k in range(N_post-i)') # Connect to all j>i # connect based on pre-/postsynaptic state variables - S2 = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S2 = Synapses(SG1, SG2) S2.connect(j='k for k in range(N_post * int(v_pre > 2))') # connect based on pre-/postsynaptic state variables - S3 = Synapses(SG2, SG1, 'w:1', on_pre='v+=w') + S3 = Synapses(SG2, SG1) S3.connect(j='k for k in range(N_post * int(v_pre > 22))') run(0*ms) # for standalone @@ -354,18 +355,18 @@ def test_synapse_creation_generator_complex_ranges(): @pytest.mark.standalone_compatible def test_synapse_creation_generator_random(): - G1 = NeuronGroup(10, 'v:1', threshold='False') - G2 = NeuronGroup(20, 'v:1', threshold='False') + G1 = NeuronGroup(10, 'v:1') + G2 = NeuronGroup(20, 'v:1') G1.v = 'i' G2.v = '10 + i' SG1 = G1[:5] SG2 = G2[10:] # connect based on pre-/postsynaptic state variables - S2 = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S2 = Synapses(SG1, SG2) S2.connect(j='k for k in sample(N_post, p=1.0*int(v_pre > 2))') - S3 = Synapses(SG2, SG1, 'w:1', on_pre='v+=w') + S3 = Synapses(SG2, SG1) S3.connect(j='k for k in sample(N_post, p=1.0*int(v_pre > 22))') run(0*ms) # for standalone @@ -377,13 +378,13 @@ def test_synapse_creation_generator_random(): def test_synapse_access(): - G1 = NeuronGroup(10, 'v:1', threshold='False') + G1 = NeuronGroup(10, 'v:1') G1.v = 'i' - G2 = NeuronGroup(20, 'v:1', threshold='False') + G2 = NeuronGroup(20, 'v:1') G2.v = 'i' SG1 = G1[:5] SG2 = G2[10:] - S = Synapses(SG1, SG2, 'w:1', on_pre='v+=w') + S = Synapses(SG1, SG2, 'w:1') S.connect(True) S.w['j == 0'] = 5 assert all(S.w['j==0'] == 5) @@ -467,6 +468,7 @@ def test_synapses_access_subgroups_problematic(): assert all([entry[1].endswith('ambiguous_string_expression') for entry in l]) + @pytest.mark.standalone_compatible def test_subgroup_summed_variable(): # Check in particular that only neurons targeted are reset to 0 (see github issue #925) diff --git a/brian2/tests/test_synapses.py b/brian2/tests/test_synapses.py index 4443d06e2..4c1a0daa7 100644 --- a/brian2/tests/test_synapses.py +++ b/brian2/tests/test_synapses.py @@ -141,6 +141,7 @@ def test_name_clashes(): Synapses(G1, G2, 'a_syn : 1') Synapses(G1, G2, 'b_syn : 1') + @pytest.mark.standalone_compatible def test_incoming_outgoing(): ''' @@ -148,9 +149,9 @@ def test_incoming_outgoing(): (It will be also automatically tested for all connection patterns that use the above _compare function for testing) ''' - G1 = NeuronGroup(5, 'v: 1', threshold='False') - G2 = NeuronGroup(5, 'v: 1', threshold='False') - S = Synapses(G1, G2, 'w:1', on_pre='v+=w') + G1 = NeuronGroup(5, '') + G2 = NeuronGroup(5, '') + S = Synapses(G1, G2, '') S.connect(i=[0, 0, 0, 1, 1, 2], j=[0, 1, 2, 1, 2, 3]) run(0*ms) # to make this work for standalone @@ -174,8 +175,8 @@ def test_connection_arrays(): ''' Test connecting synapses with explictly given arrays ''' - G = NeuronGroup(42, 'v : 1') - G2 = NeuronGroup(17, 'v : 1') + G = NeuronGroup(42, '') + G2 = NeuronGroup(17, '') # one-to-one expected1 = np.eye(len(G2)) @@ -232,20 +233,19 @@ def test_connection_arrays(): with pytest.raises(TypeError): S.connect(object()) + @pytest.mark.standalone_compatible def test_connection_string_deterministic_full(): - G = NeuronGroup(17, 'v : 1', threshold='False') - G.v = 'i' - G2 = NeuronGroup(4, 'v : 1', threshold='False') - G2.v = '17 + i' + G = NeuronGroup(17, '') + G2 = NeuronGroup(4, '') # Full connection expected_full = np.ones((len(G), len(G2))) - S1 = Synapses(G, G2, 'w:1', 'v+=w') + S1 = Synapses(G, G2, '') S1.connect(True) - S2 = Synapses(G, G2, 'w:1', 'v+=w') + S2 = Synapses(G, G2, '') S2.connect('True') run(0 * ms) # for standalone @@ -253,23 +253,24 @@ def test_connection_string_deterministic_full(): _compare(S1, expected_full) _compare(S2, expected_full) + @pytest.mark.standalone_compatible def test_connection_string_deterministic_full_no_self(): - G = NeuronGroup(17, 'v : 1', threshold='False') + G = NeuronGroup(17, 'v : 1') G.v = 'i' - G2 = NeuronGroup(4, 'v : 1', threshold='False') + G2 = NeuronGroup(4, 'v : 1') G2.v = '17 + i' # Full connection without self-connections expected_no_self = np.ones((len(G), len(G))) - np.eye(len(G)) - S1 = Synapses(G, G, 'w:1', 'v+=w') + S1 = Synapses(G, G) S1.connect('i != j') - S2 = Synapses(G, G, 'w:1', 'v+=w') + S2 = Synapses(G, G) S2.connect('v_pre != v_post') - S3 = Synapses(G, G, 'w:1', 'v+=w') + S3 = Synapses(G, G) S3.connect(condition='i != j') run(0*ms) # for standalone @@ -278,29 +279,30 @@ def test_connection_string_deterministic_full_no_self(): _compare(S2, expected_no_self) _compare(S3, expected_no_self) + @pytest.mark.standalone_compatible def test_connection_string_deterministic_full_one_to_one(): - G = NeuronGroup(17, 'v : 1', threshold='False') + G = NeuronGroup(17, 'v : 1') G.v = 'i' - G2 = NeuronGroup(4, 'v : 1', threshold='False') + G2 = NeuronGroup(4, 'v : 1') G2.v = '17 + i' # One-to-one connectivity expected_one_to_one = np.eye(len(G)) - S1 = Synapses(G, G, 'w:1', 'v+=w') + S1 = Synapses(G, G) S1.connect('i == j') - S2 = Synapses(G, G, 'w:1', 'v+=w') + S2 = Synapses(G, G) S2.connect('v_pre == v_post') S3 = Synapses(G, G, ''' sub_1 = v_pre : 1 sub_2 = v_post : 1 - w:1''', 'v+=w') + w:1''') S3.connect('sub_1 == sub_2') - S4 = Synapses(G, G, 'w:1', 'v+=w') + S4 = Synapses(G, G) S4.connect(j='i') run(0*ms) # for standalone @@ -310,20 +312,21 @@ def test_connection_string_deterministic_full_one_to_one(): _compare(S3, expected_one_to_one) _compare(S4, expected_one_to_one) + @pytest.mark.standalone_compatible def test_connection_string_deterministic_full_custom(): - G = NeuronGroup(17, 'v : 1', threshold='False') - G2 = NeuronGroup(4, 'v : 1', threshold='False') + G = NeuronGroup(17, '') + G2 = NeuronGroup(4, '') # Everything except for the upper [2, 2] quadrant number = 2 expected_custom = np.ones((len(G), len(G))) expected_custom[:number, :number] = 0 - S1 = Synapses(G, G, 'w:1', 'v+=w') + S1 = Synapses(G, G) S1.connect('(i >= number) or (j >= number)') - S2 = Synapses(G, G, 'w:1', 'v+=w') + S2 = Synapses(G, G) S2.connect('(i >= explicit_number) or (j >= explicit_number)', - namespace={'explicit_number': number}) + namespace={'explicit_number': number}) # check that this mistaken syntax raises an error with pytest.raises(ValueError): @@ -340,6 +343,7 @@ def test_connection_string_deterministic_full_custom(): _compare(S1, expected_custom) _compare(S2, expected_custom) + @pytest.mark.standalone_compatible def test_connection_string_deterministic_multiple_and(): # In Brian versions 2.1.0-2.1.2, this fails on the numpy target @@ -353,27 +357,27 @@ def test_connection_string_deterministic_multiple_and(): @pytest.mark.standalone_compatible def test_connection_random_with_condition(): - G = NeuronGroup(4, 'v: 1', threshold='False') + G = NeuronGroup(4, '') - S1 = Synapses(G, G, 'w:1', 'v+=w') + S1 = Synapses(G, G) S1.connect('i!=j', p=0.0) - S2 = Synapses(G, G, 'w:1', 'v+=w') + S2 = Synapses(G, G) S2.connect('i!=j', p=1.0) expected2 = np.ones((len(G), len(G))) - np.eye(len(G)) - S3 = Synapses(G, G, 'w:1', 'v+=w') + S3 = Synapses(G, G) S3.connect('i>=2', p=0.0) - S4 = Synapses(G, G, 'w:1', 'v+=w') + S4 = Synapses(G, G) S4.connect('i>=2', p=1.0) expected4 = np.zeros((len(G), len(G))) expected4[2, :] = 1 expected4[3, :] = 1 - S5 = Synapses(G, G, 'w:1', 'v+=w') + S5 = Synapses(G, G) S5.connect('j<2', p=0.0) - S6 = Synapses(G, G, 'w:1', 'v+=w') + S6 = Synapses(G, G) S6.connect('j<2', p=1.0) expected6 = np.zeros((len(G), len(G))) expected6[:, 0] = 1 @@ -389,56 +393,56 @@ def test_connection_random_with_condition(): assert len(S5) == 0 _compare(S6, expected6) + @pytest.mark.standalone_compatible @pytest.mark.long def test_connection_random_with_condition_2(): - G = NeuronGroup(4, 'v: 1', threshold='False') + G = NeuronGroup(4) # Just checking that everything works in principle (we can't check the # actual connections) - S7 = Synapses(G, G, 'w:1', 'v+=w') + S7 = Synapses(G, G) S7.connect('i!=j', p=0.01) - S8 = Synapses(G, G, 'w:1', 'v+=w') + S8 = Synapses(G, G) S8.connect('i!=j', p=0.03) - S9 = Synapses(G, G, 'w:1', 'v+=w') + S9 = Synapses(G, G) S9.connect('i!=j', p=0.3) - S10 = Synapses(G, G, 'w:1', 'v+=w') + S10 = Synapses(G, G) S10.connect('i>=2', p=0.01) - S11 = Synapses(G, G, 'w:1', 'v+=w') + S11 = Synapses(G, G) S11.connect('i>=2', p=0.03) - S12 = Synapses(G, G, 'w:1', 'v+=w') + S12 = Synapses(G, G) S12.connect('i>=2', p=0.3) - S13 = Synapses(G, G, 'w:1', 'v+=w') + S13 = Synapses(G, G) S13.connect('j>=2', p=0.01) - S14 = Synapses(G, G, 'w:1', 'v+=w') + S14 = Synapses(G, G) S14.connect('j>=2', p=0.03) - S15 = Synapses(G, G, 'w:1', 'v+=w') + S15 = Synapses(G, G) S15.connect('j>=2', p=0.3) - S16 = Synapses(G, G, 'w:1', 'v+=w') + S16 = Synapses(G, G) S16.connect('i!=j', p='i*0.1') - S17 = Synapses(G, G, 'w:1', 'v+=w') + S17 = Synapses(G, G) S17.connect('i!=j', p='j*0.1') # Forces the use of the "jump algorithm" - big_group = NeuronGroup(10000, 'v: 1', threshold='False') - S18 = Synapses(big_group, big_group, 'w:1', 'v+=w') + big_group = NeuronGroup(10000, '') + S18 = Synapses(big_group, big_group) S18.connect('i != j', p=0.001) # See github issue #835 -- this failed when using numpy - S19 = Synapses(big_group, big_group, 'w:1', 'v+=w') + S19 = Synapses(big_group, big_group) S19.connect('i < int(N_post*0.5)', p=0.001) - with catch_logs() as _: # Ignore warnings about empty synapses run(0*ms) # for standalone @@ -460,40 +464,40 @@ def test_connection_random_with_indices(): ''' Test random connections. ''' - G = NeuronGroup(4, 'v: 1', threshold='False') - G2 = NeuronGroup(7, 'v: 1', threshold='False') + G = NeuronGroup(4, '') + G2 = NeuronGroup(7, '') - S1 = Synapses(G, G2, 'w:1', 'v+=w') + S1 = Synapses(G, G2) S1.connect(i=0, j=0, p=0.) expected1 = np.zeros((len(G), len(G2))) - S2 = Synapses(G, G2, 'w:1', 'v+=w') + S2 = Synapses(G, G2) S2.connect(i=0, j=0, p=1.) expected2 = np.zeros((len(G), len(G2))) expected2[0, 0] = 1 - S3 = Synapses(G, G2, 'w:1', 'v+=w') + S3 = Synapses(G, G2) S3.connect(i=[0, 1], j=[0, 2], p=1.) expected3 = np.zeros((len(G), len(G2))) expected3[0, 0] = 1 expected3[1, 2] = 1 # Just checking that it works in principle - S4 = Synapses(G, G, 'w:1', 'v+=w') + S4 = Synapses(G, G) S4.connect(i=0, j=0, p=0.01) - S5 = Synapses(G, G, 'w:1', 'v+=w') + S5 = Synapses(G, G) S5.connect(i=[0, 1], j=[0, 2], p=0.01) - S6 = Synapses(G, G, 'w:1', 'v+=w') + S6 = Synapses(G, G) S6.connect(i=0, j=0, p=0.03) - S7 = Synapses(G, G, 'w:1', 'v+=w') + S7 = Synapses(G, G) S7.connect(i=[0, 1], j=[0, 2], p=0.03) - S8 = Synapses(G, G, 'w:1', 'v+=w') + S8 = Synapses(G, G) S8.connect(i=0, j=0, p=0.3) - S9 = Synapses(G, G, 'w:1', 'v+=w') + S9 = Synapses(G, G) S9.connect(i=[0, 1], j=[0, 2], p=0.3) with catch_logs() as _: # Ignore warnings about empty synapses @@ -509,33 +513,34 @@ def test_connection_random_with_indices(): assert 0 <= len(S8) <= 1 assert 0 <= len(S9) <= 2 + @pytest.mark.standalone_compatible def test_connection_random_without_condition(): G = NeuronGroup(4, '''v: 1 - x : integer''', threshold='False') + x : integer''') G.x = 'i' G2 = NeuronGroup(7, '''v: 1 - y : 1''', threshold='False') + y : 1''') G2.y = '1.0*i/N' - S1 = Synapses(G, G2, 'w:1', 'v+=w') + S1 = Synapses(G, G2) S1.connect(True, p=0.0) - S2 = Synapses(G, G2, 'w:1', 'v+=w') + S2 = Synapses(G, G2) S2.connect(True, p=1.0) # Just make sure using values between 0 and 1 work in principle - S3 = Synapses(G, G2, 'w:1', 'v+=w') + S3 = Synapses(G, G2) S3.connect(True, p=0.3) # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic - S4 = Synapses(G, G2, 'w:1', on_pre='v+=w') + S4 = Synapses(G, G2) S4.connect(True, p='int(x_pre==2)*1.0') # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic - S5 = Synapses(G, G2, 'w:1', on_pre='v+=w') + S5 = Synapses(G, G2) S5.connect(True, p='int(x_pre==2 and y_post > 0.5)*1.0') with catch_logs() as _: # Ignore warnings about empty synapses @@ -557,27 +562,27 @@ def test_connection_multiple_synapses(): ''' Test multiple synapses per connection. ''' - G = NeuronGroup(42, 'v: 1', threshold='False') + G = NeuronGroup(42, 'v: 1') G.v = 'i' - G2 = NeuronGroup(17, 'v: 1', threshold='False') + G2 = NeuronGroup(17, 'v: 1') G2.v = 'i' - S1 = Synapses(G, G2, 'w:1', 'v+=w') + S1 = Synapses(G, G2) S1.connect(True, n=0) - S2 = Synapses(G, G2, 'w:1', 'v+=w') + S2 = Synapses(G, G2) S2.connect(True, n=2) - S3 = Synapses(G, G2, 'w:1', 'v+=w') + S3 = Synapses(G, G2) S3.connect(True, n='j') - S4 = Synapses(G, G2, 'w:1', 'v+=w') + S4 = Synapses(G, G2) S4.connect(True, n='i') - S5 = Synapses(G, G2, 'w:1', 'v+=w') + S5 = Synapses(G, G2) S5.connect(True, n='int(i>j)*2') - S6 = Synapses(G, G2, 'w:1', 'v+=w') + S6 = Synapses(G, G2) S6.connect(True, n='int(v_pre>v_post)*2') with catch_logs() as _: # Ignore warnings about empty synapses @@ -602,7 +607,7 @@ def test_state_variable_assignment(): Assign values to state variables in various ways ''' - G = NeuronGroup(10, 'v: volt', threshold='False') + G = NeuronGroup(10, 'v: volt') G.v = 'i*mV' S = Synapses(G, G, 'w:volt') S.connect(True) @@ -889,8 +894,9 @@ def test_delays_pathways_subgroups(): G = NeuronGroup(10, 'x: meter', threshold='False') G.x = 'i*mmeter' # Array delay - S = Synapses(G[:5], G[5:], 'w:1', on_pre={'pre1': 'v+=w', - 'pre2': 'v+=w'}, + S = Synapses(G[:5], G[5:], 'w:1', + on_pre={'pre1': 'v+=w', + 'pre2': 'v+=w'}, on_post='v-=w') S.connect(j='i') assert len(S.pre1.delay[:]) == 5 @@ -910,6 +916,7 @@ def test_delays_pathways_subgroups(): assert_allclose(S.pre2.delay[:], np.ones(5) * 10*ms) assert_allclose(S.post.delay[:], np.ones(5) * 1*ms) + @pytest.mark.codegen_independent def test_pre_before_post(): # The pre pathway should be executed before the post pathway @@ -959,6 +966,7 @@ def test_transmission_simple(): assert_allclose(mon[1].v[mon.t<1*ms+offset], 0.) assert_allclose(mon[1].v[mon.t>=1*ms+offset], 1.) + @pytest.mark.standalone_compatible def test_transmission_custom_event(): source = NeuronGroup(2, '', @@ -975,6 +983,7 @@ def test_transmission_custom_event(): assert_allclose(mon[1].v[mon.t<1*ms], 0.) assert_allclose(mon[1].v[mon.t>=1*ms], 1.) + @pytest.mark.codegen_independent def test_invalid_custom_event(): group1 = NeuronGroup(2, 'v : 1', @@ -1097,6 +1106,7 @@ def test_transmission_scalar_delay_different_clocks(): assert_allclose(mon[1].v[mon.t<1.5*ms], 0) assert_allclose(mon[1].v[mon.t>=1.5*ms], 1) + @pytest.mark.standalone_compatible def test_transmission_boolean_variable(): source = SpikeGeneratorGroup(4, [0, 1, 2, 3], [2, 1, 2, 1] * ms) @@ -1135,6 +1145,7 @@ def test_clocks(): assert synapse.post._clock.dt == target_dt assert synapse._clock.dt == synapse_dt + def test_equations_with_clocks(): ''' Make sure that dt of a `Synapse` object is correctly resolved. @@ -1149,6 +1160,7 @@ def test_equations_with_clocks(): assert synapse.w[0] == 1 + def test_changed_dt_spikes_in_queue(): defaultclock.dt = .5*ms G1 = NeuronGroup(1, 'v:1', threshold='v>1', reset='v=0') @@ -1339,6 +1351,7 @@ def test_multiple_summed_variables(): with pytest.raises(NotImplementedError): net.run(0*ms) + @pytest.mark.standalone_compatible def test_summed_variables_subgroups(): source = NeuronGroup(1, '') @@ -1353,6 +1366,7 @@ def test_summed_variables_subgroups(): assert_allclose(target.v[:6], 2*np.ones(6)) assert_allclose(target.v[6:], 1 * np.ones(4)) + @pytest.mark.codegen_independent def test_summed_variables_overlapping_subgroups(): # See github issue #766 @@ -1369,6 +1383,7 @@ def test_summed_variables_overlapping_subgroups(): with pytest.raises(NotImplementedError): net.run(0*ms) + @pytest.mark.codegen_independent def test_summed_variables_linked_variables(): source = NeuronGroup(1, '') @@ -1449,6 +1464,7 @@ def test_scalar_subexpression(): sub = v_post + s : 1 (shared)''', on_pre='v+=s') + @pytest.mark.standalone_compatible def test_sim_with_scalar_variable(): inp = SpikeGeneratorGroup(2, [0, 1], [0, 0]*ms) @@ -1462,6 +1478,7 @@ def test_sim_with_scalar_variable(): run(2*defaultclock.dt) assert_allclose(out.v[:], [6, 7]) + @pytest.mark.standalone_compatible def test_sim_with_scalar_subexpression(): inp = SpikeGeneratorGroup(2, [0, 1], [0, 0]*ms) @@ -1474,6 +1491,7 @@ def test_sim_with_scalar_subexpression(): run(2*defaultclock.dt) assert_allclose(out.v[:], [6, 7]) + @pytest.mark.standalone_compatible def test_sim_with_constant_subexpression(): inp = SpikeGeneratorGroup(2, [0, 1], [0, 0]*ms) @@ -2138,6 +2156,7 @@ def test_ufunc_at_vectorisation(): finally: NumpyCodeGenerator._use_ufunc_at_vectorisation = True # restore it + def test_fallback_loop_and_stateless_func(): # See github issue #1024 if prefs.codegen.target != 'numpy': @@ -2154,7 +2173,7 @@ def test_fallback_loop_and_stateless_func(): @pytest.mark.standalone_compatible def test_synapses_to_synapses_summed_variable(): - source = NeuronGroup(5, '', threshold='False') + source = NeuronGroup(5, '') target = NeuronGroup(5, '') conn = Synapses(source, target, 'w : integer') conn.connect(j='i') @@ -2224,9 +2243,8 @@ def test_synapse_generator_syntax(): def test_synapse_generator_out_of_range(): - G = NeuronGroup(16, 'v : 1', threshold='False') - G.v = 'i' - G2 = NeuronGroup(4, 'v : 1', threshold='False') + G = NeuronGroup(16, 'v : 1') + G2 = NeuronGroup(4, 'v : 1') G2.v = '16 + i' S1 = Synapses(G, G2, '') @@ -2263,47 +2281,47 @@ def test_synapse_generator_out_of_range(): def test_synapse_generator_deterministic(): # Same as "test_connection_string_deterministic" but using the generator # syntax - G = NeuronGroup(16, 'v : 1', threshold='False') + G = NeuronGroup(16, 'v : 1') G.v = 'i' - G2 = NeuronGroup(4, 'v : 1', threshold='False') + G2 = NeuronGroup(4, 'v : 1') G2.v = '16 + i' # Full connection expected_full = np.ones((len(G), len(G2))) - S1 = Synapses(G, G2, 'w:1', 'v+=w') + S1 = Synapses(G, G2) S1.connect(j='k for k in range(N_post)') # Full connection without self-connections expected_no_self = np.ones((len(G), len(G))) - np.eye(len(G)) - S2 = Synapses(G, G, 'w:1', 'v+=w') + S2 = Synapses(G, G) S2.connect(j='k for k in range(N_post) if k != i') - S3 = Synapses(G, G, 'w:1', 'v+=w') + S3 = Synapses(G, G) # slightly confusing with j on the RHS, but it should work... S3.connect(j='k for k in range(N_post) if j != i') - S4 = Synapses(G, G, 'w:1', 'v+=w') + S4 = Synapses(G, G) S4.connect(j='k for k in range(N_post) if v_post != v_pre') # One-to-one connectivity expected_one_to_one = np.eye(len(G)) - S5 = Synapses(G, G, 'w:1', 'v+=w') + S5 = Synapses(G, G) S5.connect(j='k for k in range(N_post) if k == i') # inefficient - S6 = Synapses(G, G, 'w:1', 'v+=w') + S6 = Synapses(G, G) # slightly confusing with j on the RHS, but it should work... S6.connect(j='k for k in range(N_post) if j == i') # inefficient - S7 = Synapses(G, G, 'w:1', 'v+=w') + S7 = Synapses(G, G) S7.connect(j='k for k in range(N_post) if v_pre == v_post') # inefficient - S8 = Synapses(G, G, 'w:1', 'v+=w') + S8 = Synapses(G, G) S8.connect(j='i for _ in range(1)') # efficient - S9 = Synapses(G, G, 'w:1', 'v+=w') + S9 = Synapses(G, G) S9.connect(j='i') # short form of the above with catch_logs() as _: # Ignore warnings about empty synapses @@ -2321,20 +2339,78 @@ def test_synapse_generator_deterministic(): @pytest.mark.standalone_compatible -@pytest.mark.long -def test_synapse_generator_deterministic_2(): +def test_synapse_generator_deterministic_over_postsynaptic(): # Same as "test_connection_string_deterministic" but using the generator - # syntax - G = NeuronGroup(16, 'v : 1', threshold='False') + # syntax and iterating over post-synaptic variables + G = NeuronGroup(16, 'v : 1') G.v = 'i' - G2 = NeuronGroup(4, 'v : 1', threshold='False') + G2 = NeuronGroup(4, 'v : 1') G2.v = '16 + i' + # Full connection + expected_full = np.ones((len(G), len(G2))) + + S1 = Synapses(G, G2) + S1.connect(i='k for k in range(N_pre)') + + # Full connection without self-connections + expected_no_self = np.ones((len(G), len(G))) - np.eye(len(G)) + + S2 = Synapses(G, G) + S2.connect(i='k for k in range(N_pre) if k != j') + + S3 = Synapses(G, G) + # slightly confusing with i on the RHS, but it should work... + S3.connect(i='k for k in range(N_pre) if i != j') + + S4 = Synapses(G, G) + S4.connect(j='k for k in range(N_pre) if v_pre != v_post') + + # One-to-one connectivity + expected_one_to_one = np.eye(len(G)) + + S5 = Synapses(G, G) + S5.connect(i='k for k in range(N_pre) if k == j') # inefficient + + S6 = Synapses(G, G) + # slightly confusing with j on the RHS, but it should work... + S6.connect(i='k for k in range(N_pre) if i == j') # inefficient + + S7 = Synapses(G, G) + S7.connect(i='k for k in range(N_pre) if v_pre == v_post') # inefficient + + S8 = Synapses(G, G) + S8.connect(i='j for _ in range(1)') # efficient + + S9 = Synapses(G, G) + S9.connect(i='j') # short form of the above + + with catch_logs() as _: # Ignore warnings about empty synapses + run(0*ms) # for standalone + + _compare(S1, expected_full) + _compare(S2, expected_no_self) + _compare(S3, expected_no_self) + _compare(S4, expected_no_self) + _compare(S5, expected_one_to_one) + _compare(S6, expected_one_to_one) + _compare(S7, expected_one_to_one) + _compare(S8, expected_one_to_one) + _compare(S9, expected_one_to_one) + + +@pytest.mark.standalone_compatible +@pytest.mark.long +def test_synapse_generator_deterministic_2(): + # Same as "test_connection_string_deterministic" but using the generator + # syntax + G = NeuronGroup(16) + G2 = NeuronGroup(4) # A few more tests of deterministic connections where the generator syntax # is particularly useful # Ring structure - S10 = Synapses(G, G, 'w:1', 'v+=w') + S10 = Synapses(G, G) S10.connect(j='(i + (-1)**k) % N_post for k in range(2)') expected_ring = np.zeros((len(G), len(G)), dtype=np.int32) expected_ring[np.arange(15), np.arange(15)+1] = 1 # Next cell @@ -2342,14 +2418,14 @@ def test_synapse_generator_deterministic_2(): expected_ring[[0, 15], [15, 0]] = 1 # wrap around the ring # Diverging connection pattern - S11 = Synapses(G2, G, 'w:1', 'v+=w') + S11 = Synapses(G2, G) S11.connect(j='i*4 + k for k in range(4)') expected_diverging = np.zeros((len(G2), len(G)), dtype=np.int32) for source in range(4): expected_diverging[source, np.arange(4) + source*4] = 1 # Diverging connection pattern within population (no self-connections) - S11b = Synapses(G2, G2, 'w:1', 'v+=w') + S11b = Synapses(G2, G2) S11b.connect(j='k for k in range(i-3, i+4) if i!=k', skip_if_invalid=True) expected_diverging_b = np.zeros((len(G2), len(G2)), dtype=np.int32) for source in range(len(G2)): @@ -2357,21 +2433,21 @@ def test_synapse_generator_deterministic_2(): expected_diverging_b[source, source] = 0 # Converging connection pattern - S12 = Synapses(G, G2, 'w:1', 'v+=w') + S12 = Synapses(G, G2) S12.connect(j='int(i/4)') expected_converging = np.zeros((len(G), len(G2)), dtype=np.int32) for target in range(4): expected_converging[np.arange(4) + target*4, target] = 1 # skip if invalid - S13 = Synapses(G2, G2, 'w:1', 'v+=w') + S13 = Synapses(G2, G2) S13.connect(j='i+(-1)**k for k in range(2)', skip_if_invalid=True) expected_offdiagonal = np.zeros((len(G2), len(G2)), dtype=np.int32) expected_offdiagonal[np.arange(len(G2)-1), np.arange(len(G2)-1)+1] = 1 expected_offdiagonal[np.arange(len(G2)-1)+1, np.arange(len(G2)-1)] = 1 # Converging connection pattern with restriction - S14 = Synapses(G, G2, 'w:1', 'v+=w') + S14 = Synapses(G, G2) S14.connect(j='int(i/4) if i % 2 == 0') expected_converging_restricted = np.zeros((len(G), len(G2)), dtype=np.int32) for target in range(4): @@ -2411,26 +2487,23 @@ def test_synapse_generator_deterministic_2(): def test_synapse_generator_random(): # The same tests as test_connection_random_without_condition, but using # the generator syntax - G = NeuronGroup(4, '''v: 1 - x : integer''', threshold='False') + G = NeuronGroup(4, 'x : integer') G.x = 'i' - G2 = NeuronGroup(7, '''v: 1 - y : 1''', threshold='False') - G2.y = '1.0*i/N' + G2 = NeuronGroup(7, '') - S1 = Synapses(G, G2, 'w:1', 'v+=w') + S1 = Synapses(G, G2) S1.connect(j='k for k in sample(N_post, p=0)') - S2 = Synapses(G, G2, 'w:1', 'v+=w') + S2 = Synapses(G, G2) S2.connect(j='k for k in sample(N_post, p=1)') # Just make sure using values between 0 and 1 work in principle - S3 = Synapses(G, G2, 'w:1', 'v+=w') + S3 = Synapses(G, G2) S3.connect(j='k for k in sample(N_post, p=0.3)') # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic - S4 = Synapses(G, G2, 'w:1', on_pre='v+=w') + S4 = Synapses(G, G2) S4.connect(j='k for k in sample(N_post, p=int(x_pre==2)*1.0)') with catch_logs() as _: # Ignore warnings about empty synapses @@ -2444,34 +2517,65 @@ def test_synapse_generator_random(): assert_equal(S4.j, np.arange(7)) +@pytest.mark.standalone_compatible +def test_synapse_generator_random_over_postsynaptic(): + # The same tests as test_connection_random_without_condition, but using + # the generator syntax and iterating over post-synaptic neurons + G = NeuronGroup(4, '') + G2 = NeuronGroup(7, 'y : 1') + G2.y = 'i' + + S1 = Synapses(G, G2) + S1.connect(i='k for k in sample(N_pre, p=0)') + + S2 = Synapses(G, G2) + S2.connect(i='k for k in sample(N_pre, p=1)') + + # Just make sure using values between 0 and 1 work in principle + S3 = Synapses(G, G2) + S3.connect(i='k for k in sample(N_pre, p=0.3)') + + # Use pre-/post-synaptic variables for "stochastic" connections that are + # actually deterministic + S4 = Synapses(G, G2) + S4.connect(i='k for k in sample(N_pre, p=int(y_post==2)*1.0)') + + with catch_logs() as _: # Ignore warnings about empty synapses + run(0*ms) # for standalone + + assert len(S1) == 0 + _compare(S2, np.ones((len(G), len(G2)))) + assert 0 <= len(S3) <= len(G) * len(G2) + assert len(S4) == 4 + assert_equal(S4.i, np.arange(4)) + assert_equal(S4.j, np.ones(4)*2) + + @pytest.mark.standalone_compatible def test_synapse_generator_random_positive_steps(): # Test generator with sampling from stepped ranges (e.g. all even numbers) - G = NeuronGroup(4, '''v: 1 - x : integer''', threshold='False') + G = NeuronGroup(4, 'x : integer') G.x = 'i' - G2 = NeuronGroup(7, '''v: 1 - y : 1''', threshold='False') - G2.y = '1.0*i/N' + G2 = NeuronGroup(7, '') - S1 = Synapses(G, G2, 'w:1', 'v+=w') + S1 = Synapses(G, G2) S1.connect(j='k for k in sample(2, N_post, 2, p=0)') - S2 = Synapses(G, G2, 'w:1', 'v+=w') + S2 = Synapses(G, G2) S2.connect(j='k for k in sample(2, N_post, 2, p=1)') # Just make sure using values between 0 and 1 work in principle (note that # 0.25 is the cutoff between the general method and the "jump method", so # we test a value above and below - S3 = Synapses(G, G2, 'w:1', 'v+=w') + S3 = Synapses(G, G2) S3.connect(j='k for k in sample(2, N_post, 2, p=0.2)') - S3b = Synapses(G, G2, 'w:1', 'v+=w') + S3b = Synapses(G, G2) S3b.connect(j='k for k in sample(2, N_post, 2, p=0.3)') # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic - S4 = Synapses(G, G2, 'w:1', on_pre='v+=w') + S4 = Synapses(G, G2) S4.connect(j='k for k in sample(2, N_post, 2, p=int(x_pre==2)*1.0)') with catch_logs() as _: # Ignore warnings about empty synapses @@ -2496,31 +2600,28 @@ def test_synapse_generator_random_positive_steps(): def test_synapse_generator_random_negative_steps(): # Test generator with sampling from stepped ranges (e.g. all even numbers) # going backwards - G = NeuronGroup(4, '''v: 1 - x : integer''', threshold='False') + G = NeuronGroup(4, 'x : integer') G.x = 'i' - G2 = NeuronGroup(7, '''v: 1 - y : 1''', threshold='False') - G2.y = '1.0*i/N' + G2 = NeuronGroup(7, '') - S1 = Synapses(G, G2, 'w:1', 'v+=w') + S1 = Synapses(G, G2) S1.connect(j='k for k in sample(N_post-1, 0, -2, p=0)') - S2 = Synapses(G, G2, 'w:1', 'v+=w') + S2 = Synapses(G, G2) S2.connect(j='k for k in sample(N_post-1, 0, -2, p=1)') # Just make sure using values between 0 and 1 work in principle (note that # 0.25 is the cutoff between the general method and the "jump method", so # we test a value above and below - S3 = Synapses(G, G2, 'w:1', 'v+=w') + S3 = Synapses(G, G2) S3.connect(j='k for k in sample(N_post-1, 0, -2, p=0.2)') - S3b = Synapses(G, G2, 'w:1', 'v+=w') + S3b = Synapses(G, G2) S3b.connect(j='k for k in sample(N_post-1, 0, -2, p=0.3)') # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic - S4 = Synapses(G, G2, 'w:1', on_pre='v+=w') + S4 = Synapses(G, G2) S4.connect(j='k for k in sample(N_post-1, 0, -2, p=int(x_pre==2)*1.0)') with catch_logs() as _: # Ignore warnings about empty synapses @@ -2544,25 +2645,22 @@ def test_synapse_generator_random_negative_steps(): @pytest.mark.standalone_compatible def test_synapse_generator_fixed_random(): # Random samples with fixed size - G = NeuronGroup(4, '''v: 1 - x : integer''', threshold='False') + G = NeuronGroup(4, 'x : integer') G.x = 'i' - G2 = NeuronGroup(7, '''v: 1 - y : 1''', threshold='False') - G2.y = '1.0*i/N' + G2 = NeuronGroup(7, '') - S1 = Synapses(G, G2, 'w:1', 'v+=w') + S1 = Synapses(G, G2) S1.connect(j='k for k in sample(N_post, size=0)') - S2 = Synapses(G, G2, 'w:1', 'v+=w') + S2 = Synapses(G, G2) S2.connect(j='k for k in sample(N_post, size=N_post)') - S3 = Synapses(G, G2, 'w:1', 'v+=w') + S3 = Synapses(G, G2) S3.connect(j='k for k in sample(N_post, size=3)') # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic - S4 = Synapses(G, G2, 'w:1', on_pre='v+=w') + S4 = Synapses(G, G2) S4.connect(j='k for k in sample(N_post, size=int(x_pre==2)*N_post)') with catch_logs() as _: # Ignore warnings about empty synapses @@ -2585,26 +2683,23 @@ def test_synapse_generator_fixed_random(): def test_synapse_generator_fixed_random_positive_steps(): # Test generator with fixed-size sampling from stepped ranges (e.g. all # even numbers) - G = NeuronGroup(4, '''v: 1 - x : integer''', threshold='False') + G = NeuronGroup(4, 'x : integer') G.x = 'i' - G2 = NeuronGroup(7, '''v: 1 - y : 1''', threshold='False') - G2.y = '1.0*i/N' + G2 = NeuronGroup(7, '') - S1 = Synapses(G, G2, 'w:1', 'v+=w') + S1 = Synapses(G, G2) S1.connect(j='k for k in sample(2, N_post, 2, size=0)') - S2 = Synapses(G, G2, 'w:1', 'v+=w') + S2 = Synapses(G, G2) S2.connect(j='k for k in sample(2, N_post, 2, size=3)') # Just make sure using values between 0 and 1 work in principle - S3 = Synapses(G, G2, 'w:1', 'v+=w') + S3 = Synapses(G, G2) S3.connect(j='k for k in sample(2, N_post, 2, size=2)') # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic - S4 = Synapses(G, G2, 'w:1', on_pre='v+=w') + S4 = Synapses(G, G2) S4.connect(j='k for k in sample(2, N_post, 2, size=int(x_pre==2)*3)') with catch_logs() as _: # Ignore warnings about empty synapses @@ -2629,26 +2724,23 @@ def test_synapse_generator_fixed_random_positive_steps(): def test_synapse_generator_fixed_random_negative_steps(): # Test generator with fixed-size sampling from stepped ranges (e.g. all # even numbers) going backwards - G = NeuronGroup(4, '''v: 1 - x : integer''', threshold='False') + G = NeuronGroup(4, 'x : integer') G.x = 'i' - G2 = NeuronGroup(7, '''v: 1 - y : 1''', threshold='False') - G2.y = '1.0*i/N' + G2 = NeuronGroup(7, '') - S1 = Synapses(G, G2, 'w:1', 'v+=w') + S1 = Synapses(G, G2) S1.connect(j='k for k in sample(N_post-1, 0, -2, size=0)') - S2 = Synapses(G, G2, 'w:1', 'v+=w') + S2 = Synapses(G, G2) S2.connect(j='k for k in sample(N_post-1, 0, -2, size=3)') # Just make sure using intermediate values between 0 and 1 work in principle - S3 = Synapses(G, G2, 'w:1', 'v+=w') + S3 = Synapses(G, G2) S3.connect(j='k for k in sample(N_post-1, 0, -2, size=2)') # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic - S4 = Synapses(G, G2, 'w:1', on_pre='v+=w') + S4 = Synapses(G, G2, 'w:1') S4.connect(j='k for k in sample(N_post-1, 0, -2, size=int(x_pre==2)*3)') with catch_logs() as _: # Ignore warnings about empty synapses @@ -2668,9 +2760,9 @@ def test_synapse_generator_fixed_random_negative_steps(): assert_equal(S4.i, np.ones(3) * 2) assert_equal(S4.j, np.arange(6, 0, -2)) + @pytest.mark.standalone_compatible def test_synapse_generator_fixed_random_error1(): - set_device('cpp_standalone') G = NeuronGroup(5, '') G2 = NeuronGroup(7, '') S = Synapses(G, G2) @@ -2708,37 +2800,37 @@ def test_synapse_generator_fixed_random_skip_if_invalid(): @pytest.mark.standalone_compatible def test_synapse_generator_random_with_condition(): - G = NeuronGroup(4, 'v: 1', threshold='False') + G = NeuronGroup(4, '') - S1 = Synapses(G, G, 'w:1', 'v+=w') + S1 = Synapses(G, G) S1.connect(j='k for k in sample(N_post, p=0) if i != k') - S2 = Synapses(G, G, 'w:1', 'v+=w') + S2 = Synapses(G, G) S2.connect(j='k for k in sample(N_post, p=1) if i != k') expected2 = np.ones((len(G), len(G))) - np.eye(len(G)) - S3 = Synapses(G, G, 'w:1', 'v+=w') + S3 = Synapses(G, G) S3.connect(j='k for k in sample(N_post, p=0) if i >= 2') - S4 = Synapses(G, G, 'w:1', 'v+=w') + S4 = Synapses(G, G) S4.connect(j='k for k in sample(N_post, p=1.0) if i >= 2') expected4 = np.zeros((len(G), len(G))) expected4[2, :] = 1 expected4[3, :] = 1 - S5 = Synapses(G, G, 'w:1', 'v+=w') + S5 = Synapses(G, G) S5.connect(j='k for k in sample(N_post, p=0) if j < 2') # inefficient - S6 = Synapses(G, G, 'w:1', 'v+=w') + S6 = Synapses(G, G) S6.connect(j='k for k in sample(2, p=0)') # better - S7 = Synapses(G, G, 'w:1', 'v+=w') + S7 = Synapses(G, G) expected7 = np.zeros((len(G), len(G))) expected7[:, 0] = 1 expected7[:, 1] = 1 S7.connect(j='k for k in sample(N_post, p=1.0) if j < 2') # inefficient - S8 = Synapses(G, G, 'w:1', 'v+=w') + S8 = Synapses(G, G) S8.connect(j='k for k in sample(2, p=1.0)') # better with catch_logs() as _: # Ignore warnings about empty synapses @@ -2757,66 +2849,65 @@ def test_synapse_generator_random_with_condition(): @pytest.mark.standalone_compatible @pytest.mark.long def test_synapse_generator_random_with_condition_2(): - G = NeuronGroup(4, 'v: 1', threshold='False') + G = NeuronGroup(4, '') # Just checking that everything works in principle (we can't check the # actual connections) - S9 = Synapses(G, G, 'w:1', 'v+=w') + S9 = Synapses(G, G) S9.connect(j='k for k in sample(N_post, p=0.001) if i != k') - S10 = Synapses(G, G, 'w:1', 'v+=w') + S10 = Synapses(G, G) S10.connect(j='k for k in sample(N_post, p=0.03) if i != k') - S11 = Synapses(G, G, 'w:1', 'v+=w') + S11 = Synapses(G, G) S11.connect(j='k for k in sample(N_post, p=0.1) if i != k') - S12 = Synapses(G, G, 'w:1', 'v+=w') + S12 = Synapses(G, G) S12.connect(j='k for k in sample(N_post, p=0.9) if i != k') - S13 = Synapses(G, G, 'w:1', 'v+=w') + S13 = Synapses(G, G) S13.connect(j='k for k in sample(N_post, p=0.001) if i >= 2') - S14 = Synapses(G, G, 'w:1', 'v+=w') + S14 = Synapses(G, G) S14.connect(j='k for k in sample(N_post, p=0.03) if i >= 2') - S15 = Synapses(G, G, 'w:1', 'v+=w') + S15 = Synapses(G, G) S15.connect(j='k for k in sample(N_post, p=0.1) if i >= 2') - S16 = Synapses(G, G, 'w:1', 'v+=w') + S16 = Synapses(G, G) S16.connect(j='k for k in sample(N_post, p=0.9) if i >= 2') - S17 = Synapses(G, G, 'w:1', 'v+=w') + S17 = Synapses(G, G) S17.connect(j='k for k in sample(N_post, p=0.001) if j < 2') - S18 = Synapses(G, G, 'w:1', 'v+=w') + S18 = Synapses(G, G) S18.connect(j='k for k in sample(N_post, p=0.03) if j < 2') - S19 = Synapses(G, G, 'w:1', 'v+=w') + S19 = Synapses(G, G) S19.connect(j='k for k in sample(N_post, p=0.1) if j < 2') - S20 = Synapses(G, G, 'w:1', 'v+=w') + S20 = Synapses(G, G) S20.connect(j='k for k in sample(N_post, p=0.9) if j < 2') - S21 = Synapses(G, G, 'w:1', 'v+=w') + S21 = Synapses(G, G) S21.connect(j='k for k in sample(2, p=0.001)') - S22 = Synapses(G, G, 'w:1', 'v+=w') + S22 = Synapses(G, G) S22.connect(j='k for k in sample(2, p=0.03)') - S23 = Synapses(G, G, 'w:1', 'v+=w') + S23 = Synapses(G, G) S23.connect(j='k for k in sample(2, p=0.1)') - S24 = Synapses(G, G, 'w:1', 'v+=w') + S24 = Synapses(G, G) S24.connect(j='k for k in sample(2, p=0.9)') # Some more tests specific to the generator syntax - S25 = Synapses(G, G, 'w:1', on_pre='v+=w') + S25 = Synapses(G, G) S25.connect(j='i+1 for _ in sample(1, p=0.5) if i < N_post-1') - S26 = Synapses(G, G, 'w:1', on_pre='v+=w') + S26 = Synapses(G, G) S26.connect(j='i+k for k in sample(N_post-i, p=0.5)') - with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone @@ -2869,6 +2960,7 @@ def test_synapses_refractory(): assert_allclose(target.v[:5], 1) assert_allclose(target.v[5:], 0) + @pytest.mark.standalone_compatible def test_synapses_refractory_rand(): source = NeuronGroup(10, '', threshold='True') @@ -2889,8 +2981,8 @@ def test_synapses_refractory_rand(): @pytest.mark.codegen_independent def test_synapse_generator_range_noint(): # arguments to `range` should only be integers (issue #781) - G = NeuronGroup(42, 'v: 1', threshold='False') - S = Synapses(G, G, 'w:1', 'v+=w') + G = NeuronGroup(42, '') + S = Synapses(G, G) msg = r'The "{}" argument of the range function was .+, but it needs to be an integer\.' with pytest.raises(TypeError, match=msg.format('high')): S.connect(j='k for k in range(42.0)') @@ -2907,6 +2999,7 @@ def test_synapse_generator_range_noint(): with pytest.raises(TypeError, match=msg.format('step')): S.connect(j='k for k in range(0, 42, True)') + @pytest.mark.codegen_independent def test_missing_lastupdate_error_syn_pathway(): G = NeuronGroup(1, 'v : 1', threshold='False') @@ -2921,7 +3014,7 @@ def test_missing_lastupdate_error_syn_pathway(): @pytest.mark.codegen_independent def test_missing_lastupdate_error_run_regularly(): - G = NeuronGroup(1, 'v : 1', threshold='False') + G = NeuronGroup(1, 'v : 1') S = Synapses(G, G) S.connect() S.run_regularly('v += exp(-lastupdate/dt')