From 9e927eb1d8f7b82b347753180249b91ff29ade42 Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Fri, 20 Sep 2024 11:01:09 +0100 Subject: [PATCH 01/18] A new connection generator --- .../synapse_expander/connection_generator.c | 8 +- .../connection_generator_wta.h | 162 +++++++++++++++ spynnaker/pyNN/extra_models/__init__.py | 6 +- .../neural_projections/connectors/__init__.py | 4 +- .../abstract_generate_connector_on_machine.py | 1 + .../connectors/wta_connector.py | 195 ++++++++++++++++++ .../test_connectors/test_wta_connector.py | 76 +++++++ 7 files changed, 449 insertions(+), 3 deletions(-) create mode 100644 neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h create mode 100644 spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py create mode 100644 spynnaker_integration_tests/test_connectors/test_wta_connector.py diff --git a/neural_modelling/src/synapse_expander/connection_generator.c b/neural_modelling/src/synapse_expander/connection_generator.c index 4f7632ed97..34cb565053 100644 --- a/neural_modelling/src/synapse_expander/connection_generator.c +++ b/neural_modelling/src/synapse_expander/connection_generator.c @@ -31,6 +31,7 @@ #include "connection_generators/connection_generator_fixed_pre.h" #include "connection_generators/connection_generator_fixed_post.h" #include "connection_generators/connection_generator_kernel.h" +#include "connection_generators/connection_generator_wta.h" //! \brief Known "hashes" of connection generators //! @@ -43,6 +44,7 @@ enum { FIXED_PRE, //!< Fixed pre-size connection generator FIXED_POST, //!< Fixed post-size connection generator KERNEL, //!< Convolution kernel connection generator + WTA, //!< Winner takes all connection generator N_CONNECTION_GENERATORS//!< The number of known generators }; @@ -96,7 +98,11 @@ static const connection_generator_info connection_generators[] = { {KERNEL, connection_generator_kernel_initialise, connection_generator_kernel_generate, - connection_generator_kernel_free} + connection_generator_kernel_free}, + {WTA, + connection_generator_wta_initialise, + connection_generator_wta_generate, + connection_generator_wta_free} }; connection_generator_t connection_generator_init( diff --git a/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h b/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h new file mode 100644 index 0000000000..1a418738c7 --- /dev/null +++ b/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2024 The University of Manchester + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * \dir + * \brief Connection generators + * \file + * \brief Winner Takes All connection generator implementation + */ + +#include +#include + +//! \brief The parameters to be passed around for this connector +struct wta { + // How many values there are in each WTA group + uint32_t n_values; +}; + +/** + * \brief Initialise the wta connection generator + * \param[in,out] region: Region to read parameters from. Should be updated + * to position just after parameters after calling. + * \return A data item to be passed in to other functions later on + */ +static void *connection_generator_wta_initialise(void **region) { + // Allocate the data structure for parameters + struct wta *params = spin1_malloc(sizeof(struct wta)); + struct wta *params_sdram = *region; + + // Copy the parameters into the data structure + *params = *params_sdram; + *region = ¶ms_sdram[1]; + + log_debug("WTA connector, n_values = %u", params->n_values); + + return params; +} + +/** + * \brief Free the wta connection generator + * \param[in] generator: The generator to free + */ +static void connection_generator_wta_free(void *generator) { + sark_free(generator); +} + +static inline bool make_wta_conn(param_generator_t weight_generator, + param_generator_t delay_generator, matrix_generator_t matrix_generator, + uint32_t pre, uint32_t post, unsigned long accum weight_scale, + accum timestep_per_delay) { + accum weight = param_generator_generate(weight_generator); + uint16_t delay = rescale_delay( + param_generator_generate(delay_generator), timestep_per_delay); + if (!matrix_generator_write_synapse(matrix_generator, pre, post, + weight, delay, weight_scale)) { + log_error("Matrix not sized correctly!"); + return false; + } + return true; +} + +static inline void div_mod(uint32_t dividend, uint32_t divisor, uint32_t *div, + uint32_t *mod) { + uint32_t remainder = dividend; + uint32_t count = 0; + while (remainder >= divisor) { + remainder -= divisor; + count++; + } + *div = count; + *mod = remainder; +} + +/** + * \brief Generate connections with the wta connection generator + * \param[in] generator: The generator to use to generate connections + * \param[in] pre_slice_start: The start of the slice of the pre-population + * being generated + * \param[in] pre_slice_count: The number of neurons in the slice of the + * pre-population being generated + * \param[in] post_slice_start: The start of the slice of the post-population + * being generated + * \param[in] post_slice_count: The number of neurons in the slice of the + * post-population being generated + */ +static bool connection_generator_wta_generate( + void *generator, uint32_t pre_lo, uint32_t pre_hi, + uint32_t post_lo, uint32_t post_hi, uint32_t post_index, + uint32_t post_slice_start, uint32_t post_slice_count, + unsigned long accum weight_scale, accum timestep_per_delay, + param_generator_t weight_generator, param_generator_t delay_generator, + matrix_generator_t matrix_generator) { + struct wta *obj = generator; + + // Get the actual ranges to generate within + uint32_t post_start = max(post_slice_start, post_lo); + uint32_t post_end = min(post_slice_start + post_slice_count - 1, post_hi); + + // Work out where we are in the generation + // We need to connect each pre-neuron to each post-neuron in each group + // (but not to itself). We are currently generating a subset of the post + // neurons, so we need to work out which group we are in within that subset, + // and which is the first post-neuron in the group that we are generating + // for now. + uint32_t post_group; + uint32_t post_value; + div_mod(post_start, obj->n_values, &post_group, &post_value); + + // Work out where the pre-neurons start and end for the group that we are + // in at the start of the post-neurons. The group might not have enough + // neurons in it, so we check just in case. + uint32_t pre_start = pre_lo + post_group * obj->n_values; + uint32_t pre_end = min(pre_start + obj->n_values, pre_hi + 1); + uint32_t n_values = pre_end - pre_start; + + // Go through the post neurons in this slice + for (uint32_t post = post_start; post <= post_end; post++) { + uint32_t local_post = post - post_slice_start; + + // Go through each of the "values" in this group that can target this + // post neuron (each of which is a pre-neuron) + for (uint32_t value = 0; value < n_values; value++) { + if (value != post_value) { + uint32_t pre = pre_start + value; + if (!make_wta_conn(weight_generator, delay_generator, + matrix_generator, pre, local_post, weight_scale, + timestep_per_delay)) { + return false; + } + } + } + + // Work out next loop iteration. If we have reached the end of a group + // of values, we need to move onto the next group. + post_value += 1; + if (post_value == obj->n_values) { + post_value = 0; + pre_start += obj->n_values; + pre_end = min(pre_start + obj->n_values, pre_hi + 1); + if (pre_start >= pre_hi) { + break; + } + n_values = pre_end - pre_start; + } + } + + return true; +} diff --git a/spynnaker/pyNN/extra_models/__init__.py b/spynnaker/pyNN/extra_models/__init__.py index 7ddcdbc076..e9ed92c10e 100644 --- a/spynnaker/pyNN/extra_models/__init__.py +++ b/spynnaker/pyNN/extra_models/__init__.py @@ -31,6 +31,7 @@ IFCurrExpSEMDBase as IF_curr_exp_sEMD, IFCurrDeltaCa2Adaptive, StocExp, StocExpStable, StocSigma, IFTruncDelta, IFCurrDeltaFixedProb) +from spynnaker.pyNN.models.neural_projections.connectors import WTAConnector # Variable rate poisson from spynnaker.pyNN.models.spike_source import SpikeSourcePoissonVariable @@ -57,5 +58,8 @@ 'StocExp', 'StocExpStable', 'StocSigma', 'IFCurrDeltaFixedProb', # Special - 'IFTruncDelta' + 'IFTruncDelta', + + # Connectors + 'WTAConnector' ] diff --git a/spynnaker/pyNN/models/neural_projections/connectors/__init__.py b/spynnaker/pyNN/models/neural_projections/connectors/__init__.py index 65d1e33f58..cc64dcd59a 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/__init__.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/__init__.py @@ -34,6 +34,7 @@ from .kernel_connector import KernelConnector from .convolution_connector import ConvolutionConnector from .pool_dense_connector import PoolDenseConnector +from .wta_connector import WTAConnector __all__ = ["AbstractConnector", "AbstractGenerateConnectorOnMachine", "AbstractGenerateConnectorOnHost", "AllToAllConnector", @@ -43,4 +44,5 @@ "FromFileConnector", "FromListConnector", "IndexBasedProbabilityConnector", "KernelConnector", "ConvolutionConnector", "PoolDenseConnector", - "MultapseConnector", "OneToOneConnector", "SmallWorldConnector"] + "MultapseConnector", "OneToOneConnector", "SmallWorldConnector", + "WTAConnector"] diff --git a/spynnaker/pyNN/models/neural_projections/connectors/abstract_generate_connector_on_machine.py b/spynnaker/pyNN/models/neural_projections/connectors/abstract_generate_connector_on_machine.py index 4120f278dc..f98369064f 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/abstract_generate_connector_on_machine.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/abstract_generate_connector_on_machine.py @@ -53,6 +53,7 @@ class ConnectorIDs(Enum): FIXED_NUMBER_PRE_CONNECTOR = 4 FIXED_NUMBER_POST_CONNECTOR = 5 KERNEL_CONNECTOR = 6 + WTA_CONNECTOR = 7 class AbstractGenerateConnectorOnMachine( diff --git a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py new file mode 100644 index 0000000000..96e03c601c --- /dev/null +++ b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py @@ -0,0 +1,195 @@ +# Copyright (c) 2024 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import annotations +from typing import Sequence, Optional, TYPE_CHECKING + +import numpy +from numpy import uint32 +from numpy.typing import NDArray + +from spinn_utilities.overrides import overrides + +from pacman.model.graphs.common import Slice + +from spinn_front_end_common.utilities.constants import BYTES_PER_WORD + +from .abstract_connector import AbstractConnector +from .abstract_generate_connector_on_machine import ( + AbstractGenerateConnectorOnMachine, ConnectorIDs) +from .abstract_generate_connector_on_host import ( + AbstractGenerateConnectorOnHost) + +if TYPE_CHECKING: + from spynnaker.pyNN.models.neural_projections import SynapseInformation + + +class WTAConnector(AbstractGenerateConnectorOnMachine, + AbstractGenerateConnectorOnHost): + """ + Normally used to connect a population to itself, the assumption is that + the population can represent multiple potential WTA groups, where each is + independent. The connector will connect each pre-neuron in a group to each + post-neuron in the same group, except the one with the same index. + """ + + __slots__ = ("__n_values", ) + + def __init__(self, n_values=None, safe=True, + verbose=None, callback=None): + """ + :param int n_values: + The number of values in each WTA group. + :param bool safe: + If ``True``, check that weights and delays have valid values. + If ``False``, this check is skipped. + :param bool verbose: + Whether to output extra information about the connectivity to a + CSV file + :param callable callback: + if given, a callable that display a progress bar on the terminal. + + .. note:: + Not supported by sPyNNaker. + """ + super().__init__(safe, callback, verbose) + self.__n_values = n_values + + def __n_connections(self, synapse_info): + # If not specified, use the smallest of the two populations + if self.__n_values is None: + n_values = min(synapse_info.n_pre_neurons, + synapse_info.n_post_neurons) + return n_values * (n_values - 1) + + # Find out how many groups there are at most + n_groups_pre = synapse_info.n_pre_neurons // self.__n_values + n_groups_post = synapse_info.n_post_neurons // self.__n_values + n_groups = min(n_groups_pre, n_groups_post) + return n_groups * self.__n_values * (self.__n_values - 1) + + @overrides(AbstractConnector.get_delay_maximum) + def get_delay_maximum(self, synapse_info: SynapseInformation) -> float: + return self._get_delay_maximum( + synapse_info.delays, self.__n_connections(synapse_info), + synapse_info) + + @overrides(AbstractConnector.get_delay_minimum) + def get_delay_minimum(self, synapse_info: SynapseInformation) -> float: + return self._get_delay_minimum( + synapse_info.delays, self.__n_connections(synapse_info), + synapse_info) + + @overrides(AbstractConnector.get_n_connections_from_pre_vertex_maximum) + def get_n_connections_from_pre_vertex_maximum( + self, n_post_atoms: int, synapse_info: SynapseInformation, + min_delay: Optional[float] = None, + max_delay: Optional[float] = None) -> int: + + # At most, a pre-neuron will target all post-neurons in the group, + # except the one with the same index. For a given subset of post + # atoms, there might be fewer to target... + n_targets = n_post_atoms + if self.__n_values is not None: + n_targets = min(self.__n_values - 1, n_post_atoms) + if min_delay is None or max_delay is None: + n_targets + + return self._get_n_connections_from_pre_vertex_with_delay_maximum( + synapse_info.delays, self.__n_connections(synapse_info), + n_targets, min_delay, max_delay, synapse_info) + + @overrides(AbstractConnector.get_n_connections_to_post_vertex_maximum) + def get_n_connections_to_post_vertex_maximum( + self, synapse_info: SynapseInformation) -> int: + # At most, each post-neuron will be targeted by all pre-neurons in a + # group, except the one with the same index. + if self.__n_values is None: + return min(synapse_info.n_pre_neurons, + synapse_info.n_post_neurons) - 1 + return self.__n_values - 1 + + @overrides(AbstractConnector.get_weight_maximum) + def get_weight_maximum(self, synapse_info: SynapseInformation) -> float: + return self._get_weight_maximum( + synapse_info.weights, self.__n_connections(synapse_info), + synapse_info) + + @overrides(AbstractGenerateConnectorOnHost.create_synaptic_block) + def create_synaptic_block( + self, post_slices: Sequence[Slice], post_vertex_slice: Slice, + synapse_type: int, synapse_info: SynapseInformation) -> NDArray: + group_size = self.__n_values + if group_size is None: + group_size = min(synapse_info.n_pre_neurons, + synapse_info.n_post_neurons) + post_lo, post_hi = synapse_info.pre_population._view_range + pre_lo, pre_hi = synapse_info.pre_population._view_range + post_start = max(post_vertex_slice.lo_atom, post_lo) + post_end = min(post_vertex_slice.hi_atom + 1, post_hi + 1) + post_group, post_value = divmod(post_start, group_size) + + pre_start = pre_lo + (post_group * group_size) + pre_end = min(pre_start + group_size, pre_hi + 1) + n_values = pre_end - pre_start + + pres = list() + posts = list() + for post in range(post_start, post_end): + for value in range(n_values): + if value != post_value: + pres.append(pre_start + value) + posts.append(post) + + post_value += 1 + if post_value == group_size: + post_value = 0 + pre_start += group_size + pre_end = min(pre_start + group_size, pre_hi + 1) + if pre_start >= pre_hi: + break + n_values = pre_end - pre_start + block = numpy.zeros(len(pres), dtype=self.NUMPY_SYNAPSES_DTYPE) + block["source"] = pres + block["target"] = posts + block["weight"] = self._generate_weights( + block["source"], block["target"], len(pres), post_vertex_slice, + synapse_info) + block["delay"] = self._generate_delays( + block["source"], block["target"], len(pres), post_vertex_slice, + synapse_info) + block["synapse_type"] = synapse_type + return block + + def __repr__(self): + return f"WTAConnector(n_values={self.__n_values})" + + @property + @overrides(AbstractGenerateConnectorOnMachine.gen_connector_id) + def gen_connector_id(self) -> int: + return ConnectorIDs.WTA_CONNECTOR.value + + @overrides(AbstractGenerateConnectorOnMachine.gen_connector_params) + def gen_connector_params( + self, synapse_info: SynapseInformation) -> NDArray[uint32]: + n_values = self.__n_values + if n_values is None: + n_values = min(synapse_info.n_pre_neurons, + synapse_info.n_post_neurons) + return numpy.array([int(n_values)], dtype=uint32) + + @property + @overrides( + AbstractGenerateConnectorOnMachine.gen_connector_params_size_in_bytes) + def gen_connector_params_size_in_bytes(self) -> int: + return BYTES_PER_WORD diff --git a/spynnaker_integration_tests/test_connectors/test_wta_connector.py b/spynnaker_integration_tests/test_connectors/test_wta_connector.py new file mode 100644 index 0000000000..840f37c024 --- /dev/null +++ b/spynnaker_integration_tests/test_connectors/test_wta_connector.py @@ -0,0 +1,76 @@ +# Copyright (c) 2024 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pyNN.spiNNaker as sim +from itertools import permutations +import numpy +from pacman.model.graphs.common.slice import Slice + + +def test_wta(): + sim.setup() + sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 2) + pop = sim.Population(11, sim.IF_curr_exp()) + proj = sim.Projection(pop, pop, sim.extra_models.WTAConnector()) + sim.run(0) + conns = list(proj.get([], format="list")) + sim.end() + groups = list([i, j] for (i, j) in permutations(range(11), 2)) + print(conns) + print(groups) + assert numpy.array_equal(conns, groups) + + +def test_wta_groups(): + sim.setup() + sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 4) + pop = sim.Population(11, sim.IF_curr_exp()) + proj = sim.Projection(pop, pop, sim.extra_models.WTAConnector(n_values=3)) + sim.run(0) + conns = list(proj.get([], format="list")) + sim.end() + groups = list() + for group_start in range(0, 11, 3): + group_end = min(11, group_start + 3) + neurons_in_group = range(group_start, group_end) + groups.extend([i, j] for (i, j) in permutations(neurons_in_group, 2)) + print(conns) + print(groups) + assert numpy.array_equal(conns, groups) + + +def test_wta_offline(): + sim.setup() + pop = sim.Population(11, sim.IF_curr_exp()) + conn = sim.extra_models.WTAConnector() + proj = sim.Projection(pop, pop, conn) + sim.run(0) + conns = list(proj.get([], format="list")) + post_vertex_slice = Slice(0, 11) + post_slices = [post_vertex_slice] + synapse_type = 0 + synapse_info = proj._synapse_information + offline_conns = sorted( + list([i, j] for (i, j, _w, _d, _typ) in conn.create_synaptic_block( + post_slices, post_vertex_slice, synapse_type, synapse_info))) + sim.end() + groups = list([i, j] for (i, j) in permutations(range(11), 2)) + print(conns) + print(groups) + print(offline_conns) + assert numpy.array_equal(conns, groups) + assert numpy.array_equal(conns, offline_conns) + + +if __name__ == "__main__": + test_wta_offline() From 8f944f474857e532bf433f05048c6fbe6a7490a0 Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Fri, 20 Sep 2024 16:32:20 +0100 Subject: [PATCH 02/18] Add a list of weights as an option --- .../connection_generator_wta.h | 79 ++++++++++++++++--- .../src/synapse_expander/type_writers.h | 2 +- .../connectors/wta_connector.py | 40 +++++++++- .../test_connectors/test_wta_connector.py | 20 ++++- 4 files changed, 123 insertions(+), 18 deletions(-) diff --git a/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h b/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h index 1a418738c7..c49b45c63d 100644 --- a/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h +++ b/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h @@ -24,12 +24,30 @@ #include #include +//! \brief The parameters to be passed around for this connector +struct wta_conf { + // How many values there are in each WTA group + uint32_t n_values; + + // Whether there are weight values specified or not + uint32_t has_weights; + + // The weight values if specified. + // If so, there must be (n_values * n_values - 1) weights + accum weights[]; +}; + //! \brief The parameters to be passed around for this connector struct wta { // How many values there are in each WTA group uint32_t n_values; + + // The weight values if specified. + // If so, there must be (n_values * n_values - 1) weights + accum *weights; }; + /** * \brief Initialise the wta connection generator * \param[in,out] region: Region to read parameters from. Should be updated @@ -37,15 +55,31 @@ struct wta { * \return A data item to be passed in to other functions later on */ static void *connection_generator_wta_initialise(void **region) { + // Get the SDRAM params + struct wta_conf *params_sdram = *region; + // Allocate the data structure for parameters struct wta *params = spin1_malloc(sizeof(struct wta)); - struct wta *params_sdram = *region; - // Copy the parameters into the data structure - *params = *params_sdram; - *region = ¶ms_sdram[1]; + // Copy the parameters + params->n_values = params_sdram->n_values; + if (params_sdram->has_weights) { + uint32_t n_values = params->n_values; + uint32_t weight_size = n_values * (n_values - 1) * sizeof(accum); + params->weights = spin1_malloc(weight_size); + if (params->weights == NULL) { + // If we can't copy, just reference the SDRAM + params->weights = ¶ms_sdram->weights[0]; + } else { + spin1_memcpy(¶ms->weights[0], ¶ms_sdram->weights[0], weight_size); + } + *region = ¶ms_sdram->weights[n_values * (n_values - 1)]; + } else { + params->weights = NULL; + *region = ¶ms_sdram->weights[0]; + } - log_debug("WTA connector, n_values = %u", params->n_values); + log_info("WTA connector, n_values = %u, has_weights = %u", params->n_values, params_sdram->has_weights); return params; } @@ -58,11 +92,10 @@ static void connection_generator_wta_free(void *generator) { sark_free(generator); } -static inline bool make_wta_conn(param_generator_t weight_generator, +static inline bool make_wta_conn(accum weight, param_generator_t delay_generator, matrix_generator_t matrix_generator, uint32_t pre, uint32_t post, unsigned long accum weight_scale, accum timestep_per_delay) { - accum weight = param_generator_generate(weight_generator); uint16_t delay = rescale_delay( param_generator_generate(delay_generator), timestep_per_delay); if (!matrix_generator_write_synapse(matrix_generator, pre, post, @@ -85,6 +118,27 @@ static inline void div_mod(uint32_t dividend, uint32_t divisor, uint32_t *div, *mod = remainder; } +/** + * Get the weight for a given pre *value* and post *value*. + */ +static inline accum get_weight(struct wta *obj, param_generator_t weight_generator, + uint32_t pre_value, uint32_t post_value) { + // Get the post position rather than the post value. Because each "row" in + // the table has the diagonal removed, we need to adjust where we get the + // value from depending on the relative pre and post values (which must not + // be the same - this isn't checked here though). + uint32_t post_pos = post_value; + if (post_value >= pre_value) { + post_pos -= 1; + } + if (obj->weights != NULL) { + uint32_t weight_index = (pre_value * (obj->n_values - 1)) + post_pos; + return obj->weights[weight_index]; + } else { + return param_generator_generate(weight_generator); + } +} + /** * \brief Generate connections with the wta connection generator * \param[in] generator: The generator to use to generate connections @@ -99,7 +153,7 @@ static inline void div_mod(uint32_t dividend, uint32_t divisor, uint32_t *div, */ static bool connection_generator_wta_generate( void *generator, uint32_t pre_lo, uint32_t pre_hi, - uint32_t post_lo, uint32_t post_hi, uint32_t post_index, + uint32_t post_lo, uint32_t post_hi, UNUSED uint32_t post_index, uint32_t post_slice_start, uint32_t post_slice_count, unsigned long accum weight_scale, accum timestep_per_delay, param_generator_t weight_generator, param_generator_t delay_generator, @@ -133,10 +187,11 @@ static bool connection_generator_wta_generate( // Go through each of the "values" in this group that can target this // post neuron (each of which is a pre-neuron) - for (uint32_t value = 0; value < n_values; value++) { - if (value != post_value) { - uint32_t pre = pre_start + value; - if (!make_wta_conn(weight_generator, delay_generator, + for (uint32_t pre_value = 0; pre_value < n_values; pre_value++) { + if (pre_value != post_value) { + uint32_t pre = pre_start + pre_value; + accum weight = get_weight(obj, weight_generator, pre_value, post_value); + if (!make_wta_conn(weight, delay_generator, matrix_generator, pre, local_post, weight_scale, timestep_per_delay)) { return false; diff --git a/neural_modelling/src/synapse_expander/type_writers.h b/neural_modelling/src/synapse_expander/type_writers.h index cd46a95714..b7aa02f9cf 100644 --- a/neural_modelling/src/synapse_expander/type_writers.h +++ b/neural_modelling/src/synapse_expander/type_writers.h @@ -65,7 +65,7 @@ static type_info type_writers[] = { }; static type_info *get_type_writer(type t) { - if (t < 0 || t >= sizeof(type_writers) / sizeof(*type_writers)) { + if (t >= sizeof(type_writers) / sizeof(*type_writers)) { // Bogus index is bad! And otherwise hard to debug! log_error("type id=%u is outside sane range", t); rt_error(RTE_SWERR); diff --git a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py index 96e03c601c..6aa3295c24 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py @@ -23,6 +23,8 @@ from pacman.model.graphs.common import Slice from spinn_front_end_common.utilities.constants import BYTES_PER_WORD +from spinn_front_end_common.interface.ds import DataType +from spynnaker.pyNN.types import Weight_Types from .abstract_connector import AbstractConnector from .abstract_generate_connector_on_machine import ( @@ -43,9 +45,9 @@ class WTAConnector(AbstractGenerateConnectorOnMachine, post-neuron in the same group, except the one with the same index. """ - __slots__ = ("__n_values", ) + __slots__ = ("__n_values", "__weights") - def __init__(self, n_values=None, safe=True, + def __init__(self, n_values=None, weights=None, safe=True, verbose=None, callback=None): """ :param int n_values: @@ -64,6 +66,7 @@ def __init__(self, n_values=None, safe=True, """ super().__init__(safe, callback, verbose) self.__n_values = n_values + self.__weights = weights def __n_connections(self, synapse_info): # If not specified, use the smallest of the two populations @@ -121,10 +124,30 @@ def get_n_connections_to_post_vertex_maximum( @overrides(AbstractConnector.get_weight_maximum) def get_weight_maximum(self, synapse_info: SynapseInformation) -> float: + if self.__weights is not None: + return numpy.amax(self.__weights) return self._get_weight_maximum( synapse_info.weights, self.__n_connections(synapse_info), synapse_info) + @overrides(AbstractConnector.get_weight_mean) + def get_weight_mean(self, weights: Weight_Types, + synapse_info: SynapseInformation) -> float: + if self.__weights is None: + return AbstractConnector.get_weight_mean( + self, weights, synapse_info) + else: + return float(numpy.mean(numpy.abs(self.__weights))) + + @overrides(AbstractConnector.get_weight_variance) + def get_weight_variance(self, weights: Weight_Types, + synapse_info: SynapseInformation) -> float: + if self.__weights is None: + return AbstractConnector.get_weight_variance( + self, weights, synapse_info) + else: + return float(numpy.var(numpy.abs(self.__weights))) + @overrides(AbstractGenerateConnectorOnHost.create_synaptic_block) def create_synaptic_block( self, post_slices: Sequence[Slice], post_vertex_slice: Slice, @@ -186,10 +209,19 @@ def gen_connector_params( if n_values is None: n_values = min(synapse_info.n_pre_neurons, synapse_info.n_post_neurons) - return numpy.array([int(n_values)], dtype=uint32) + has_weights = int(self.__weights is not None) + params = numpy.array([n_values, has_weights], dtype=uint32) + if self.__weights is None: + weights = numpy.zeros(0, dtype=uint32) + else: + weights = DataType.S1615.encode_as_numpy_int_array(self.__weights) + return numpy.concatenate((params, weights)) @property @overrides( AbstractGenerateConnectorOnMachine.gen_connector_params_size_in_bytes) def gen_connector_params_size_in_bytes(self) -> int: - return BYTES_PER_WORD + size = BYTES_PER_WORD * 2 + if self.__weights is not None: + size += len(self.__weights) * BYTES_PER_WORD + return size diff --git a/spynnaker_integration_tests/test_connectors/test_wta_connector.py b/spynnaker_integration_tests/test_connectors/test_wta_connector.py index 840f37c024..444aebcdb0 100644 --- a/spynnaker_integration_tests/test_connectors/test_wta_connector.py +++ b/spynnaker_integration_tests/test_connectors/test_wta_connector.py @@ -72,5 +72,23 @@ def test_wta_offline(): assert numpy.array_equal(conns, offline_conns) +def test_wta_weights(): + sim.setup() + sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) + pre = sim.Population(11, sim.IF_curr_exp()) + post = sim.Population(11, sim.IF_curr_exp()) + weights = numpy.arange(0.25, ((11 * 10) + 1) * 0.25, 0.25) + conn = sim.extra_models.WTAConnector(weights=weights) + proj = sim.Projection(pre, post, conn) + sim.run(0) + conns = list(proj.get(["weight"], format="list")) + sim.end() + groups = list([i, j, w] + for ((i, j), w) in zip(permutations(range(11), 2), weights)) + print(conns) + print(groups) + assert numpy.array_equal(conns, groups) + + if __name__ == "__main__": - test_wta_offline() + test_wta_weights() From 72ba9d6e8f131162dfdfa693abdef76ef7439495 Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Mon, 23 Sep 2024 07:31:11 +0100 Subject: [PATCH 03/18] Line length --- .../connection_generators/connection_generator_wta.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h b/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h index c49b45c63d..843501a4f7 100644 --- a/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h +++ b/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h @@ -79,7 +79,8 @@ static void *connection_generator_wta_initialise(void **region) { *region = ¶ms_sdram->weights[0]; } - log_info("WTA connector, n_values = %u, has_weights = %u", params->n_values, params_sdram->has_weights); + log_info("WTA connector, n_values = %u, has_weights = %u", params->n_values, + params_sdram->has_weights); return params; } From f02609f157558b02baec1114d41806115dd1faa1 Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Mon, 23 Sep 2024 07:35:45 +0100 Subject: [PATCH 04/18] De=lint --- .../neural_projections/connectors/wta_connector.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py index 6aa3295c24..37cfac5780 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py @@ -40,9 +40,10 @@ class WTAConnector(AbstractGenerateConnectorOnMachine, AbstractGenerateConnectorOnHost): """ Normally used to connect a population to itself, the assumption is that - the population can represent multiple potential WTA groups, where each is - independent. The connector will connect each pre-neuron in a group to each - post-neuron in the same group, except the one with the same index. + the population can represent multiple potential winner-takes-all groups, + where each is independent. The connector will connect each pre-neuron in a + group to each post-neuron in the same group, except the one with the same + index. """ __slots__ = ("__n_values", "__weights") @@ -51,7 +52,7 @@ def __init__(self, n_values=None, weights=None, safe=True, verbose=None, callback=None): """ :param int n_values: - The number of values in each WTA group. + The number of values in each winner-takes-all group. :param bool safe: If ``True``, check that weights and delays have valid values. If ``False``, this check is skipped. @@ -106,7 +107,7 @@ def get_n_connections_from_pre_vertex_maximum( if self.__n_values is not None: n_targets = min(self.__n_values - 1, n_post_atoms) if min_delay is None or max_delay is None: - n_targets + return n_targets return self._get_n_connections_from_pre_vertex_with_delay_maximum( synapse_info.delays, self.__n_connections(synapse_info), @@ -156,6 +157,7 @@ def create_synaptic_block( if group_size is None: group_size = min(synapse_info.n_pre_neurons, synapse_info.n_post_neurons) + # pylint: disable=protected-access post_lo, post_hi = synapse_info.pre_population._view_range pre_lo, pre_hi = synapse_info.pre_population._view_range post_start = max(post_vertex_slice.lo_atom, post_lo) From 4c96689829164b4a26ffd3d0e382faa2eee4a7e5 Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Mon, 23 Sep 2024 09:12:47 +0100 Subject: [PATCH 05/18] Update names to n_neurons_per_group --- .../connection_generator_wta.h | 28 +++++++-------- .../connectors/wta_connector.py | 34 ++++++++++--------- .../test_connectors/test_wta_connector.py | 3 +- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h b/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h index 843501a4f7..284498399f 100644 --- a/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h +++ b/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h @@ -39,8 +39,8 @@ struct wta_conf { //! \brief The parameters to be passed around for this connector struct wta { - // How many values there are in each WTA group - uint32_t n_values; + // How many neurons there are in each WTA group + uint32_t n_neurons_per_group; // The weight values if specified. // If so, there must be (n_values * n_values - 1) weights @@ -62,10 +62,10 @@ static void *connection_generator_wta_initialise(void **region) { struct wta *params = spin1_malloc(sizeof(struct wta)); // Copy the parameters - params->n_values = params_sdram->n_values; + params->n_neurons_per_group = params_sdram->n_values; if (params_sdram->has_weights) { - uint32_t n_values = params->n_values; - uint32_t weight_size = n_values * (n_values - 1) * sizeof(accum); + uint32_t n_per_group = params->n_neurons_per_group; + uint32_t weight_size = n_per_group * (n_per_group - 1) * sizeof(accum); params->weights = spin1_malloc(weight_size); if (params->weights == NULL) { // If we can't copy, just reference the SDRAM @@ -73,13 +73,13 @@ static void *connection_generator_wta_initialise(void **region) { } else { spin1_memcpy(¶ms->weights[0], ¶ms_sdram->weights[0], weight_size); } - *region = ¶ms_sdram->weights[n_values * (n_values - 1)]; + *region = ¶ms_sdram->weights[n_per_group * (n_per_group - 1)]; } else { params->weights = NULL; *region = ¶ms_sdram->weights[0]; } - log_info("WTA connector, n_values = %u, has_weights = %u", params->n_values, + log_info("WTA connector, n_values = %u, has_weights = %u", params->n_neurons_per_group, params_sdram->has_weights); return params; @@ -133,7 +133,7 @@ static inline accum get_weight(struct wta *obj, param_generator_t weight_generat post_pos -= 1; } if (obj->weights != NULL) { - uint32_t weight_index = (pre_value * (obj->n_values - 1)) + post_pos; + uint32_t weight_index = (pre_value * (obj->n_neurons_per_group - 1)) + post_pos; return obj->weights[weight_index]; } else { return param_generator_generate(weight_generator); @@ -173,13 +173,13 @@ static bool connection_generator_wta_generate( // for now. uint32_t post_group; uint32_t post_value; - div_mod(post_start, obj->n_values, &post_group, &post_value); + div_mod(post_start, obj->n_neurons_per_group, &post_group, &post_value); // Work out where the pre-neurons start and end for the group that we are // in at the start of the post-neurons. The group might not have enough // neurons in it, so we check just in case. - uint32_t pre_start = pre_lo + post_group * obj->n_values; - uint32_t pre_end = min(pre_start + obj->n_values, pre_hi + 1); + uint32_t pre_start = pre_lo + post_group * obj->n_neurons_per_group; + uint32_t pre_end = min(pre_start + obj->n_neurons_per_group, pre_hi + 1); uint32_t n_values = pre_end - pre_start; // Go through the post neurons in this slice @@ -203,10 +203,10 @@ static bool connection_generator_wta_generate( // Work out next loop iteration. If we have reached the end of a group // of values, we need to move onto the next group. post_value += 1; - if (post_value == obj->n_values) { + if (post_value == obj->n_neurons_per_group) { post_value = 0; - pre_start += obj->n_values; - pre_end = min(pre_start + obj->n_values, pre_hi + 1); + pre_start += obj->n_neurons_per_group; + pre_end = min(pre_start + obj->n_neurons_per_group, pre_hi + 1); if (pre_start >= pre_hi) { break; } diff --git a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py index 37cfac5780..22cdd8572e 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py @@ -46,13 +46,13 @@ class WTAConnector(AbstractGenerateConnectorOnMachine, index. """ - __slots__ = ("__n_values", "__weights") + __slots__ = ("__n_neurons_per_group", "__weights") - def __init__(self, n_values=None, weights=None, safe=True, + def __init__(self, n_neurons_per_group=None, weights=None, safe=True, verbose=None, callback=None): """ - :param int n_values: - The number of values in each winner-takes-all group. + :param int n_neurons_per_group: + The number of neurons in each winner-takes-all group. :param bool safe: If ``True``, check that weights and delays have valid values. If ``False``, this check is skipped. @@ -66,21 +66,23 @@ def __init__(self, n_values=None, weights=None, safe=True, Not supported by sPyNNaker. """ super().__init__(safe, callback, verbose) - self.__n_values = n_values + self.__n_neurons_per_group = n_neurons_per_group self.__weights = weights def __n_connections(self, synapse_info): # If not specified, use the smallest of the two populations - if self.__n_values is None: + if self.__n_neurons_per_group is None: n_values = min(synapse_info.n_pre_neurons, synapse_info.n_post_neurons) return n_values * (n_values - 1) # Find out how many groups there are at most - n_groups_pre = synapse_info.n_pre_neurons // self.__n_values - n_groups_post = synapse_info.n_post_neurons // self.__n_values + n_groups_pre = synapse_info.n_pre_neurons // self.__n_neurons_per_group + n_groups_post = (synapse_info.n_post_neurons // + self.__n_neurons_per_group) n_groups = min(n_groups_pre, n_groups_post) - return n_groups * self.__n_values * (self.__n_values - 1) + return (n_groups * self.__n_neurons_per_group * + (self.__n_neurons_per_group - 1)) @overrides(AbstractConnector.get_delay_maximum) def get_delay_maximum(self, synapse_info: SynapseInformation) -> float: @@ -104,8 +106,8 @@ def get_n_connections_from_pre_vertex_maximum( # except the one with the same index. For a given subset of post # atoms, there might be fewer to target... n_targets = n_post_atoms - if self.__n_values is not None: - n_targets = min(self.__n_values - 1, n_post_atoms) + if self.__n_neurons_per_group is not None: + n_targets = min(self.__n_neurons_per_group - 1, n_post_atoms) if min_delay is None or max_delay is None: return n_targets @@ -118,10 +120,10 @@ def get_n_connections_to_post_vertex_maximum( self, synapse_info: SynapseInformation) -> int: # At most, each post-neuron will be targeted by all pre-neurons in a # group, except the one with the same index. - if self.__n_values is None: + if self.__n_neurons_per_group is None: return min(synapse_info.n_pre_neurons, synapse_info.n_post_neurons) - 1 - return self.__n_values - 1 + return self.__n_neurons_per_group - 1 @overrides(AbstractConnector.get_weight_maximum) def get_weight_maximum(self, synapse_info: SynapseInformation) -> float: @@ -153,7 +155,7 @@ def get_weight_variance(self, weights: Weight_Types, def create_synaptic_block( self, post_slices: Sequence[Slice], post_vertex_slice: Slice, synapse_type: int, synapse_info: SynapseInformation) -> NDArray: - group_size = self.__n_values + group_size = self.__n_neurons_per_group if group_size is None: group_size = min(synapse_info.n_pre_neurons, synapse_info.n_post_neurons) @@ -197,7 +199,7 @@ def create_synaptic_block( return block def __repr__(self): - return f"WTAConnector(n_values={self.__n_values})" + return f"WTAConnector(n_neuron_per_group={self.__n_neurons_per_group})" @property @overrides(AbstractGenerateConnectorOnMachine.gen_connector_id) @@ -207,7 +209,7 @@ def gen_connector_id(self) -> int: @overrides(AbstractGenerateConnectorOnMachine.gen_connector_params) def gen_connector_params( self, synapse_info: SynapseInformation) -> NDArray[uint32]: - n_values = self.__n_values + n_values = self.__n_neurons_per_group if n_values is None: n_values = min(synapse_info.n_pre_neurons, synapse_info.n_post_neurons) diff --git a/spynnaker_integration_tests/test_connectors/test_wta_connector.py b/spynnaker_integration_tests/test_connectors/test_wta_connector.py index 444aebcdb0..7435b9efd1 100644 --- a/spynnaker_integration_tests/test_connectors/test_wta_connector.py +++ b/spynnaker_integration_tests/test_connectors/test_wta_connector.py @@ -35,7 +35,8 @@ def test_wta_groups(): sim.setup() sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 4) pop = sim.Population(11, sim.IF_curr_exp()) - proj = sim.Projection(pop, pop, sim.extra_models.WTAConnector(n_values=3)) + proj = sim.Projection(pop, pop, sim.extra_models.WTAConnector( + n_neurons_per_group=3)) sim.run(0) conns = list(proj.get([], format="list")) sim.end() From beda19fbc83be6452f83f4690b2ddf3dcbc8991e Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Mon, 23 Sep 2024 09:52:50 +0100 Subject: [PATCH 06/18] Make more thorough --- .../connectors/wta_connector.py | 40 +++++++++++++++++ .../test_connectors/test_wta_connector.py | 45 ++++++++++++++++--- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py index 22cdd8572e..6d0bbc059c 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py @@ -34,6 +34,8 @@ if TYPE_CHECKING: from spynnaker.pyNN.models.neural_projections import SynapseInformation + from spynnaker.pyNN.models.neural_projections import ( + ProjectionApplicationEdge) class WTAConnector(AbstractGenerateConnectorOnMachine, @@ -68,6 +70,17 @@ def __init__(self, n_neurons_per_group=None, weights=None, safe=True, super().__init__(safe, callback, verbose) self.__n_neurons_per_group = n_neurons_per_group self.__weights = weights + self.__check_weights(weights, n_neurons_per_group) + + def __check_weights(self, weights, n_neurons_per_group): + if weights is not None and n_neurons_per_group is not None: + n_weights = n_neurons_per_group * (n_neurons_per_group - 1) + if len(weights) != n_weights: + raise ValueError( + "The number of weights must be equal to the number of " + f"connections in a group " + f"({n_neurons_per_group} x ({n_neurons_per_group} - 1) = " + f"{n_weights})") def __n_connections(self, synapse_info): # If not specified, use the smallest of the two populations @@ -229,3 +242,30 @@ def gen_connector_params_size_in_bytes(self) -> int: if self.__weights is not None: size += len(self.__weights) * BYTES_PER_WORD return size + + @overrides(AbstractConnector.validate_connection) + def validate_connection( + self, application_edge: ProjectionApplicationEdge, + synapse_info: SynapseInformation): + if (synapse_info.pre_population.size != + synapse_info.post_population.size): + raise ValueError( + "WTAConnector can only be used with populations that are " + "the same size as each other") + if self.__n_neurons_per_group is not None: + if self.__n_neurons_per_group > synapse_info.pre_population.size: + raise ValueError( + "WTAConnector cannot be used with a group size larger " + "than the population size") + if ((synapse_info.post_population.size / + self.__n_neurons_per_group) != + (synapse_info.post_population.size // + self.__n_neurons_per_group)): + raise ValueError( + "The number of neurons in each population must be " + "divisible by the number of neurons per group") + n_neurons_per_group = self.__n_neurons_per_group + if n_neurons_per_group is None: + n_neurons_per_group = min(synapse_info.pre_population.size, + synapse_info.post_population.size) + self.__check_weights(self.__weights, n_neurons_per_group) diff --git a/spynnaker_integration_tests/test_connectors/test_wta_connector.py b/spynnaker_integration_tests/test_connectors/test_wta_connector.py index 7435b9efd1..306cc4ef40 100644 --- a/spynnaker_integration_tests/test_connectors/test_wta_connector.py +++ b/spynnaker_integration_tests/test_connectors/test_wta_connector.py @@ -15,6 +15,7 @@ from itertools import permutations import numpy from pacman.model.graphs.common.slice import Slice +import pytest def test_wta(): @@ -33,16 +34,16 @@ def test_wta(): def test_wta_groups(): sim.setup() - sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 4) - pop = sim.Population(11, sim.IF_curr_exp()) + sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 5) + pop = sim.Population(12, sim.IF_curr_exp()) proj = sim.Projection(pop, pop, sim.extra_models.WTAConnector( n_neurons_per_group=3)) sim.run(0) conns = list(proj.get([], format="list")) sim.end() groups = list() - for group_start in range(0, 11, 3): - group_end = min(11, group_start + 3) + for group_start in range(0, 12, 3): + group_end = min(12, group_start + 3) neurons_in_group = range(group_start, group_end) groups.extend([i, j] for (i, j) in permutations(neurons_in_group, 2)) print(conns) @@ -91,5 +92,39 @@ def test_wta_weights(): assert numpy.array_equal(conns, groups) +def test_wta_wrong_number_of_neurons(): + sim.setup() + sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) + pre = sim.Population(11, sim.IF_curr_exp()) + post = sim.Population(11, sim.IF_curr_exp()) + with pytest.raises(ValueError): + sim.Projection( + pre, post, sim.extra_models.WTAConnector(n_neurons_per_group=3)) + sim.end() + + +def test_wta_diff_number_of_neurons(): + sim.setup() + sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) + pre = sim.Population(12, sim.IF_curr_exp()) + post = sim.Population(9, sim.IF_curr_exp()) + with pytest.raises(ValueError): + sim.Projection( + pre, post, sim.extra_models.WTAConnector(n_neurons_per_group=3)) + sim.end() + + +def test_wta_wrong_number_of_weights(): + sim.setup() + sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) + pre = sim.Population(12, sim.IF_curr_exp()) + post = sim.Population(12, sim.IF_curr_exp()) + with pytest.raises(ValueError): + sim.Projection( + pre, post, sim.extra_models.WTAConnector( + n_neurons_per_group=3, weights=[10])) + sim.end() + + if __name__ == "__main__": - test_wta_weights() + test_wta_wrong_number_of_neurons() From df6791a5bc11e44c7d3112bdf3429d7c8034e6a6 Mon Sep 17 00:00:00 2001 From: "Christian Y. Brenninkmeijer" Date: Mon, 23 Sep 2024 15:36:27 +0100 Subject: [PATCH 07/18] typing and docs fixes --- .../connectors/wta_connector.py | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py index 6d0bbc059c..8859ed2e75 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py @@ -46,22 +46,32 @@ class WTAConnector(AbstractGenerateConnectorOnMachine, where each is independent. The connector will connect each pre-neuron in a group to each post-neuron in the same group, except the one with the same index. + + Can be used for two distinct populations BUT + they must have the same number of neurons + and neuron X of the source will not connect to neuron X of the target. """ __slots__ = ("__n_neurons_per_group", "__weights") - def __init__(self, n_neurons_per_group=None, weights=None, safe=True, - verbose=None, callback=None): + def __init__(self, n_neurons_per_group: Optional[int] = None, + weights: Optional[NDArray[numpy.float64]] = None, + safe: bool = True, verbose: Optional[bool] = None, + callback: None = None): """ - :param int n_neurons_per_group: + :param n_neurons_per_group: The number of neurons in each winner-takes-all group. - :param bool safe: + Must be a positive integer divisor of source.size + :param weights: + The weights for one group of neurons + Single Value, RandomDistribution and string values not supported. + :param safe: If ``True``, check that weights and delays have valid values. If ``False``, this check is skipped. - :param bool verbose: + :param verbose: Whether to output extra information about the connectivity to a CSV file - :param callable callback: + :param callback: if given, a callable that display a progress bar on the terminal. .. note:: @@ -72,7 +82,8 @@ def __init__(self, n_neurons_per_group=None, weights=None, safe=True, self.__weights = weights self.__check_weights(weights, n_neurons_per_group) - def __check_weights(self, weights, n_neurons_per_group): + def __check_weights(self, weights: Optional[NDArray[numpy.float64]], + n_neurons_per_group: Optional[int]): if weights is not None and n_neurons_per_group is not None: n_weights = n_neurons_per_group * (n_neurons_per_group - 1) if len(weights) != n_weights: @@ -82,7 +93,7 @@ def __check_weights(self, weights, n_neurons_per_group): f"({n_neurons_per_group} x ({n_neurons_per_group} - 1) = " f"{n_weights})") - def __n_connections(self, synapse_info): + def __n_connections(self, synapse_info: SynapseInformation): # If not specified, use the smallest of the two populations if self.__n_neurons_per_group is None: n_values = min(synapse_info.n_pre_neurons, From e4a415d01b7d824a4f092d4e3649347f3f2421cc Mon Sep 17 00:00:00 2001 From: "Christian Y. Brenninkmeijer" Date: Mon, 23 Sep 2024 15:43:26 +0100 Subject: [PATCH 08/18] runsafe --- .../test_connectors/test_wta_connector.py | 241 ++++++++++-------- 1 file changed, 128 insertions(+), 113 deletions(-) diff --git a/spynnaker_integration_tests/test_connectors/test_wta_connector.py b/spynnaker_integration_tests/test_connectors/test_wta_connector.py index 306cc4ef40..73def71624 100644 --- a/spynnaker_integration_tests/test_connectors/test_wta_connector.py +++ b/spynnaker_integration_tests/test_connectors/test_wta_connector.py @@ -13,118 +13,133 @@ # limitations under the License. import pyNN.spiNNaker as sim from itertools import permutations -import numpy -from pacman.model.graphs.common.slice import Slice import pytest +import numpy - -def test_wta(): - sim.setup() - sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 2) - pop = sim.Population(11, sim.IF_curr_exp()) - proj = sim.Projection(pop, pop, sim.extra_models.WTAConnector()) - sim.run(0) - conns = list(proj.get([], format="list")) - sim.end() - groups = list([i, j] for (i, j) in permutations(range(11), 2)) - print(conns) - print(groups) - assert numpy.array_equal(conns, groups) - - -def test_wta_groups(): - sim.setup() - sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 5) - pop = sim.Population(12, sim.IF_curr_exp()) - proj = sim.Projection(pop, pop, sim.extra_models.WTAConnector( - n_neurons_per_group=3)) - sim.run(0) - conns = list(proj.get([], format="list")) - sim.end() - groups = list() - for group_start in range(0, 12, 3): - group_end = min(12, group_start + 3) - neurons_in_group = range(group_start, group_end) - groups.extend([i, j] for (i, j) in permutations(neurons_in_group, 2)) - print(conns) - print(groups) - assert numpy.array_equal(conns, groups) - - -def test_wta_offline(): - sim.setup() - pop = sim.Population(11, sim.IF_curr_exp()) - conn = sim.extra_models.WTAConnector() - proj = sim.Projection(pop, pop, conn) - sim.run(0) - conns = list(proj.get([], format="list")) - post_vertex_slice = Slice(0, 11) - post_slices = [post_vertex_slice] - synapse_type = 0 - synapse_info = proj._synapse_information - offline_conns = sorted( - list([i, j] for (i, j, _w, _d, _typ) in conn.create_synaptic_block( - post_slices, post_vertex_slice, synapse_type, synapse_info))) - sim.end() - groups = list([i, j] for (i, j) in permutations(range(11), 2)) - print(conns) - print(groups) - print(offline_conns) - assert numpy.array_equal(conns, groups) - assert numpy.array_equal(conns, offline_conns) - - -def test_wta_weights(): - sim.setup() - sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) - pre = sim.Population(11, sim.IF_curr_exp()) - post = sim.Population(11, sim.IF_curr_exp()) - weights = numpy.arange(0.25, ((11 * 10) + 1) * 0.25, 0.25) - conn = sim.extra_models.WTAConnector(weights=weights) - proj = sim.Projection(pre, post, conn) - sim.run(0) - conns = list(proj.get(["weight"], format="list")) - sim.end() - groups = list([i, j, w] - for ((i, j), w) in zip(permutations(range(11), 2), weights)) - print(conns) - print(groups) - assert numpy.array_equal(conns, groups) - - -def test_wta_wrong_number_of_neurons(): - sim.setup() - sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) - pre = sim.Population(11, sim.IF_curr_exp()) - post = sim.Population(11, sim.IF_curr_exp()) - with pytest.raises(ValueError): - sim.Projection( - pre, post, sim.extra_models.WTAConnector(n_neurons_per_group=3)) - sim.end() - - -def test_wta_diff_number_of_neurons(): - sim.setup() - sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) - pre = sim.Population(12, sim.IF_curr_exp()) - post = sim.Population(9, sim.IF_curr_exp()) - with pytest.raises(ValueError): - sim.Projection( - pre, post, sim.extra_models.WTAConnector(n_neurons_per_group=3)) - sim.end() - - -def test_wta_wrong_number_of_weights(): - sim.setup() - sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) - pre = sim.Population(12, sim.IF_curr_exp()) - post = sim.Population(12, sim.IF_curr_exp()) - with pytest.raises(ValueError): - sim.Projection( - pre, post, sim.extra_models.WTAConnector( - n_neurons_per_group=3, weights=[10])) - sim.end() - - -if __name__ == "__main__": - test_wta_wrong_number_of_neurons() +from pacman.model.graphs.common.slice import Slice +from spinnaker_testbase import BaseTestCase + + +class TestWTAConnector(BaseTestCase): + + def check_wta(self): + sim.setup() + sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 2) + pop = sim.Population(11, sim.IF_curr_exp()) + proj = sim.Projection(pop, pop, sim.extra_models.WTAConnector()) + sim.run(0) + conns = list(proj.get([], format="list")) + sim.end() + groups = list([i, j] for (i, j) in permutations(range(11), 2)) + print(conns) + print(groups) + assert numpy.array_equal(conns, groups) + + def test_wta(self): + self.runsafe(self.check_wta) + + def check_wta_groups(self): + sim.setup() + sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 5) + pop = sim.Population(12, sim.IF_curr_exp()) + proj = sim.Projection(pop, pop, sim.extra_models.WTAConnector( + n_neurons_per_group=3)) + sim.run(0) + conns = list(proj.get([], format="list")) + sim.end() + groups = list() + for group_start in range(0, 12, 3): + group_end = min(12, group_start + 3) + neurons_in_group = range(group_start, group_end) + groups.extend([i, j] for (i, j) in permutations(neurons_in_group, 2)) + print(conns) + print(groups) + assert numpy.array_equal(conns, groups) + + def test_wta_groups(self): + self.runsafe(self.check_wta_groups) + + def check_wta_offline(self): + sim.setup() + pop = sim.Population(11, sim.IF_curr_exp()) + conn = sim.extra_models.WTAConnector() + proj = sim.Projection(pop, pop, conn) + sim.run(0) + conns = list(proj.get([], format="list")) + post_vertex_slice = Slice(0, 11) + post_slices = [post_vertex_slice] + synapse_type = 0 + synapse_info = proj._synapse_information + offline_conns = sorted( + list([i, j] for (i, j, _w, _d, _typ) in conn.create_synaptic_block( + post_slices, post_vertex_slice, synapse_type, synapse_info))) + sim.end() + groups = list([i, j] for (i, j) in permutations(range(11), 2)) + print(conns) + print(groups) + print(offline_conns) + assert numpy.array_equal(conns, groups) + assert numpy.array_equal(conns, offline_conns) + + def test_wta_offline(self): + self.runsafe(self.check_wta_offline) + + def check_wta_weights(self): + sim.setup() + sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) + pre = sim.Population(11, sim.IF_curr_exp()) + post = sim.Population(11, sim.IF_curr_exp()) + weights = numpy.arange(0.25, ((11 * 10) + 1) * 0.25, 0.25) + conn = sim.extra_models.WTAConnector(weights=weights) + proj = sim.Projection(pre, post, conn) + sim.run(0) + conns = list(proj.get(["weight"], format="list")) + sim.end() + groups = list([i, j, w] + for ((i, j), w) in zip(permutations(range(11), 2), weights)) + print(conns) + print(groups) + assert numpy.array_equal(conns, groups) + + def test_wta_weights(self): + self.runsafe(self.check_wta_weights) + + def check_wta_wrong_number_of_neurons(self): + sim.setup() + sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) + pre = sim.Population(11, sim.IF_curr_exp()) + post = sim.Population(11, sim.IF_curr_exp()) + with pytest.raises(ValueError): + sim.Projection( + pre, post, sim.extra_models.WTAConnector(n_neurons_per_group=3)) + sim.end() + + def test_wta_wrong_number_of_neurons(self): + self.runsafe(self.check_wta_wrong_number_of_neurons) + + def check_wta_diff_number_of_neurons(self): + sim.setup() + sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) + pre = sim.Population(12, sim.IF_curr_exp()) + post = sim.Population(9, sim.IF_curr_exp()) + with pytest.raises(ValueError): + sim.Projection( + pre, post, sim.extra_models.WTAConnector(n_neurons_per_group=3)) + sim.end() + + def test_wta_diff_number_of_neurons(self): + self.runsafe(self.check_wta_diff_number_of_neurons) + + def check_wta_wrong_number_of_weights(self): + sim.setup() + sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) + pre = sim.Population(12, sim.IF_curr_exp()) + post = sim.Population(12, sim.IF_curr_exp()) + with pytest.raises(ValueError): + sim.Projection( + pre, post, sim.extra_models.WTAConnector( + n_neurons_per_group=3, weights=[10])) + sim.end() + + def test_wta_wrong_number_of_weights(self): + self.runsafe(self.check_wta_wrong_number_of_weights) \ No newline at end of file From b6d6c9ce27098656013aa3e655bba6198844ef09 Mon Sep 17 00:00:00 2001 From: "Christian Y. Brenninkmeijer" Date: Mon, 23 Sep 2024 15:57:57 +0100 Subject: [PATCH 09/18] flake8 --- .../test_connectors/test_wta_connector.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/spynnaker_integration_tests/test_connectors/test_wta_connector.py b/spynnaker_integration_tests/test_connectors/test_wta_connector.py index 73def71624..37979bc36d 100644 --- a/spynnaker_integration_tests/test_connectors/test_wta_connector.py +++ b/spynnaker_integration_tests/test_connectors/test_wta_connector.py @@ -20,7 +20,7 @@ from spinnaker_testbase import BaseTestCase -class TestWTAConnector(BaseTestCase): +class TestWTAConnector(BaseTestCase): def check_wta(self): sim.setup() @@ -51,7 +51,8 @@ def check_wta_groups(self): for group_start in range(0, 12, 3): group_end = min(12, group_start + 3) neurons_in_group = range(group_start, group_end) - groups.extend([i, j] for (i, j) in permutations(neurons_in_group, 2)) + groups.extend([i, j] + for (i, j) in permutations(neurons_in_group, 2)) print(conns) print(groups) assert numpy.array_equal(conns, groups) @@ -95,8 +96,9 @@ def check_wta_weights(self): sim.run(0) conns = list(proj.get(["weight"], format="list")) sim.end() - groups = list([i, j, w] - for ((i, j), w) in zip(permutations(range(11), 2), weights)) + groups = list( + [i, j, w] for ((i, j), w) in + zip(permutations(range(11), 2), weights)) print(conns) print(groups) assert numpy.array_equal(conns, groups) @@ -111,7 +113,8 @@ def check_wta_wrong_number_of_neurons(self): post = sim.Population(11, sim.IF_curr_exp()) with pytest.raises(ValueError): sim.Projection( - pre, post, sim.extra_models.WTAConnector(n_neurons_per_group=3)) + pre, post, sim.extra_models.WTAConnector( + n_neurons_per_group = 3)) sim.end() def test_wta_wrong_number_of_neurons(self): @@ -124,7 +127,8 @@ def check_wta_diff_number_of_neurons(self): post = sim.Population(9, sim.IF_curr_exp()) with pytest.raises(ValueError): sim.Projection( - pre, post, sim.extra_models.WTAConnector(n_neurons_per_group=3)) + pre, post, sim.extra_models.WTAConnector( + n_neurons_per_group = 3)) sim.end() def test_wta_diff_number_of_neurons(self): @@ -142,4 +146,4 @@ def check_wta_wrong_number_of_weights(self): sim.end() def test_wta_wrong_number_of_weights(self): - self.runsafe(self.check_wta_wrong_number_of_weights) \ No newline at end of file + self.runsafe(self.check_wta_wrong_number_of_weights) From 17709fdcc6583a66dc31587740684319076713fc Mon Sep 17 00:00:00 2001 From: "Christian Y. Brenninkmeijer" Date: Mon, 23 Sep 2024 16:01:40 +0100 Subject: [PATCH 10/18] flake8 second try --- .../test_connectors/test_wta_connector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spynnaker_integration_tests/test_connectors/test_wta_connector.py b/spynnaker_integration_tests/test_connectors/test_wta_connector.py index 37979bc36d..930cbe83aa 100644 --- a/spynnaker_integration_tests/test_connectors/test_wta_connector.py +++ b/spynnaker_integration_tests/test_connectors/test_wta_connector.py @@ -114,7 +114,7 @@ def check_wta_wrong_number_of_neurons(self): with pytest.raises(ValueError): sim.Projection( pre, post, sim.extra_models.WTAConnector( - n_neurons_per_group = 3)) + n_neurons_per_group=3)) sim.end() def test_wta_wrong_number_of_neurons(self): @@ -128,7 +128,7 @@ def check_wta_diff_number_of_neurons(self): with pytest.raises(ValueError): sim.Projection( pre, post, sim.extra_models.WTAConnector( - n_neurons_per_group = 3)) + n_neurons_per_group=3)) sim.end() def test_wta_diff_number_of_neurons(self): From 03bd7061ba00a09c719873f0e33cfdc40dc1678f Mon Sep 17 00:00:00 2001 From: "Christian Y. Brenninkmeijer" Date: Tue, 24 Sep 2024 07:46:45 +0100 Subject: [PATCH 11/18] default verbose like the super class --- .../pyNN/models/neural_projections/connectors/wta_connector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py index 8859ed2e75..254fcb695b 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py @@ -56,7 +56,7 @@ class WTAConnector(AbstractGenerateConnectorOnMachine, def __init__(self, n_neurons_per_group: Optional[int] = None, weights: Optional[NDArray[numpy.float64]] = None, - safe: bool = True, verbose: Optional[bool] = None, + safe: bool = True, verbose: bool = False, callback: None = None): """ :param n_neurons_per_group: From 0b252ff4652340e3ee276b273bca9a4657ae8e0b Mon Sep 17 00:00:00 2001 From: "Christian Y. Brenninkmeijer" Date: Tue, 24 Sep 2024 08:34:52 +0100 Subject: [PATCH 12/18] Abstract wants a float --- .../pyNN/models/neural_projections/connectors/wta_connector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py index 254fcb695b..8239530837 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py @@ -152,7 +152,7 @@ def get_n_connections_to_post_vertex_maximum( @overrides(AbstractConnector.get_weight_maximum) def get_weight_maximum(self, synapse_info: SynapseInformation) -> float: if self.__weights is not None: - return numpy.amax(self.__weights) + return float(numpy.amax(self.__weights)) return self._get_weight_maximum( synapse_info.weights, self.__n_connections(synapse_info), synapse_info) From 7c93dde8b9a64f02665caad1476b6019d1459cc9 Mon Sep 17 00:00:00 2001 From: "Christian Y. Brenninkmeijer" Date: Tue, 24 Sep 2024 10:32:03 +0100 Subject: [PATCH 13/18] Rebrand WTA to AllButMeConnector --- .../synapse_expander/connection_generator.c | 12 ++-- ...ta.h => connection_generator_all_but_me.h} | 35 +++++------ spynnaker/pyNN/extra_models/__init__.py | 5 +- .../neural_projections/connectors/__init__.py | 9 ++- ...a_connector.py => all_but_me_connector.py} | 4 +- ...nector.py => test_all_but_me_connector.py} | 58 +++++++++---------- 6 files changed, 62 insertions(+), 61 deletions(-) rename neural_modelling/src/synapse_expander/connection_generators/{connection_generator_wta.h => connection_generator_all_but_me.h} (87%) rename spynnaker/pyNN/models/neural_projections/connectors/{wta_connector.py => all_but_me_connector.py} (99%) rename spynnaker_integration_tests/test_connectors/{test_wta_connector.py => test_all_but_me_connector.py} (73%) diff --git a/neural_modelling/src/synapse_expander/connection_generator.c b/neural_modelling/src/synapse_expander/connection_generator.c index 34cb565053..a5b8777552 100644 --- a/neural_modelling/src/synapse_expander/connection_generator.c +++ b/neural_modelling/src/synapse_expander/connection_generator.c @@ -31,7 +31,7 @@ #include "connection_generators/connection_generator_fixed_pre.h" #include "connection_generators/connection_generator_fixed_post.h" #include "connection_generators/connection_generator_kernel.h" -#include "connection_generators/connection_generator_wta.h" +#include "connection_generators/connection_generator_all_but_me.h" //! \brief Known "hashes" of connection generators //! @@ -44,7 +44,7 @@ enum { FIXED_PRE, //!< Fixed pre-size connection generator FIXED_POST, //!< Fixed post-size connection generator KERNEL, //!< Convolution kernel connection generator - WTA, //!< Winner takes all connection generator + ALL_BUT_ME, //!< AllButMe connection generator N_CONNECTION_GENERATORS//!< The number of known generators }; @@ -99,10 +99,10 @@ static const connection_generator_info connection_generators[] = { connection_generator_kernel_initialise, connection_generator_kernel_generate, connection_generator_kernel_free}, - {WTA, - connection_generator_wta_initialise, - connection_generator_wta_generate, - connection_generator_wta_free} + {ALL_BUT_ME, + connection_generator_all_but_me_initialise, + connection_generator_all_but_me_generate, + connection_generator_all_but_me_free} }; connection_generator_t connection_generator_init( diff --git a/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h b/neural_modelling/src/synapse_expander/connection_generators/connection_generator_all_but_me.h similarity index 87% rename from neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h rename to neural_modelling/src/synapse_expander/connection_generators/connection_generator_all_but_me.h index 284498399f..603a566377 100644 --- a/neural_modelling/src/synapse_expander/connection_generators/connection_generator_wta.h +++ b/neural_modelling/src/synapse_expander/connection_generators/connection_generator_all_but_me.h @@ -18,14 +18,14 @@ * \dir * \brief Connection generators * \file - * \brief Winner Takes All connection generator implementation + * \brief All But Me connection generator implementation */ #include #include //! \brief The parameters to be passed around for this connector -struct wta_conf { +struct all_but_me_conf { // How many values there are in each WTA group uint32_t n_values; @@ -38,7 +38,7 @@ struct wta_conf { }; //! \brief The parameters to be passed around for this connector -struct wta { +struct all_but_me { // How many neurons there are in each WTA group uint32_t n_neurons_per_group; @@ -49,17 +49,17 @@ struct wta { /** - * \brief Initialise the wta connection generator + * \brief Initialise the all but me connection generator * \param[in,out] region: Region to read parameters from. Should be updated * to position just after parameters after calling. * \return A data item to be passed in to other functions later on */ -static void *connection_generator_wta_initialise(void **region) { +static void *connection_generator_all_but_me_initialise(void **region) { // Get the SDRAM params - struct wta_conf *params_sdram = *region; + struct all_but_me_conf *params_sdram = *region; // Allocate the data structure for parameters - struct wta *params = spin1_malloc(sizeof(struct wta)); + struct all_but_me *params = spin1_malloc(sizeof(struct all_but_me)); // Copy the parameters params->n_neurons_per_group = params_sdram->n_values; @@ -79,21 +79,21 @@ static void *connection_generator_wta_initialise(void **region) { *region = ¶ms_sdram->weights[0]; } - log_info("WTA connector, n_values = %u, has_weights = %u", params->n_neurons_per_group, + log_info("allButMe connector, n_values = %u, has_weights = %u", params->n_neurons_per_group, params_sdram->has_weights); return params; } /** - * \brief Free the wta connection generator + * \brief Free the All But Me connection generator * \param[in] generator: The generator to free */ -static void connection_generator_wta_free(void *generator) { +static void connection_generator_all_but_me_free(void *generator) { sark_free(generator); } -static inline bool make_wta_conn(accum weight, +static inline bool make_all_but_me_conn(accum weight, param_generator_t delay_generator, matrix_generator_t matrix_generator, uint32_t pre, uint32_t post, unsigned long accum weight_scale, accum timestep_per_delay) { @@ -122,8 +122,9 @@ static inline void div_mod(uint32_t dividend, uint32_t divisor, uint32_t *div, /** * Get the weight for a given pre *value* and post *value*. */ -static inline accum get_weight(struct wta *obj, param_generator_t weight_generator, - uint32_t pre_value, uint32_t post_value) { +static inline accum get_weight(struct all_but_me *obj, + param_generator_t weight_generator, uint32_t pre_value, + uint32_t post_value) { // Get the post position rather than the post value. Because each "row" in // the table has the diagonal removed, we need to adjust where we get the // value from depending on the relative pre and post values (which must not @@ -141,7 +142,7 @@ static inline accum get_weight(struct wta *obj, param_generator_t weight_generat } /** - * \brief Generate connections with the wta connection generator + * \brief Generate connections with the all but me connection generator * \param[in] generator: The generator to use to generate connections * \param[in] pre_slice_start: The start of the slice of the pre-population * being generated @@ -152,14 +153,14 @@ static inline accum get_weight(struct wta *obj, param_generator_t weight_generat * \param[in] post_slice_count: The number of neurons in the slice of the * post-population being generated */ -static bool connection_generator_wta_generate( +static bool connection_generator_all_but_me_generate( void *generator, uint32_t pre_lo, uint32_t pre_hi, uint32_t post_lo, uint32_t post_hi, UNUSED uint32_t post_index, uint32_t post_slice_start, uint32_t post_slice_count, unsigned long accum weight_scale, accum timestep_per_delay, param_generator_t weight_generator, param_generator_t delay_generator, matrix_generator_t matrix_generator) { - struct wta *obj = generator; + struct all_but_me *obj = generator; // Get the actual ranges to generate within uint32_t post_start = max(post_slice_start, post_lo); @@ -192,7 +193,7 @@ static bool connection_generator_wta_generate( if (pre_value != post_value) { uint32_t pre = pre_start + pre_value; accum weight = get_weight(obj, weight_generator, pre_value, post_value); - if (!make_wta_conn(weight, delay_generator, + if (!make_all_but_me_conn(weight, delay_generator, matrix_generator, pre, local_post, weight_scale, timestep_per_delay)) { return false; diff --git a/spynnaker/pyNN/extra_models/__init__.py b/spynnaker/pyNN/extra_models/__init__.py index e9ed92c10e..c0ebcb478c 100644 --- a/spynnaker/pyNN/extra_models/__init__.py +++ b/spynnaker/pyNN/extra_models/__init__.py @@ -31,7 +31,8 @@ IFCurrExpSEMDBase as IF_curr_exp_sEMD, IFCurrDeltaCa2Adaptive, StocExp, StocExpStable, StocSigma, IFTruncDelta, IFCurrDeltaFixedProb) -from spynnaker.pyNN.models.neural_projections.connectors import WTAConnector +from spynnaker.pyNN.models.neural_projections.connectors import ( + AllButMeConnector) # Variable rate poisson from spynnaker.pyNN.models.spike_source import SpikeSourcePoissonVariable @@ -61,5 +62,5 @@ 'IFTruncDelta', # Connectors - 'WTAConnector' + 'AllButMeConnector' ] diff --git a/spynnaker/pyNN/models/neural_projections/connectors/__init__.py b/spynnaker/pyNN/models/neural_projections/connectors/__init__.py index cc64dcd59a..f6eecc2b75 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/__init__.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/__init__.py @@ -17,6 +17,7 @@ AbstractGenerateConnectorOnMachine) from .abstract_generate_connector_on_host import ( AbstractGenerateConnectorOnHost) +from .all_but_me_connector import AllButMeConnector from .all_to_all_connector import AllToAllConnector from .array_connector import ArrayConnector from .csa_connector import CSAConnector @@ -34,15 +35,13 @@ from .kernel_connector import KernelConnector from .convolution_connector import ConvolutionConnector from .pool_dense_connector import PoolDenseConnector -from .wta_connector import WTAConnector __all__ = ["AbstractConnector", "AbstractGenerateConnectorOnMachine", - "AbstractGenerateConnectorOnHost", "AllToAllConnector", - "ArrayConnector", "CSAConnector", + "AbstractGenerateConnectorOnHost", "AllButMeConnector", + "AllToAllConnector", "ArrayConnector", "CSAConnector", "DistanceDependentProbabilityConnector", "FixedNumberPostConnector", "FixedNumberPreConnector", "FixedProbabilityConnector", "FromFileConnector", "FromListConnector", "IndexBasedProbabilityConnector", "KernelConnector", "ConvolutionConnector", "PoolDenseConnector", - "MultapseConnector", "OneToOneConnector", "SmallWorldConnector", - "WTAConnector"] + "MultapseConnector", "OneToOneConnector", "SmallWorldConnector"] diff --git a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py similarity index 99% rename from spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py rename to spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py index 8239530837..6e091aabbc 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/wta_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py @@ -38,8 +38,8 @@ ProjectionApplicationEdge) -class WTAConnector(AbstractGenerateConnectorOnMachine, - AbstractGenerateConnectorOnHost): +class AllButMeConnector(AbstractGenerateConnectorOnMachine, + AbstractGenerateConnectorOnHost): """ Normally used to connect a population to itself, the assumption is that the population can represent multiple potential winner-takes-all groups, diff --git a/spynnaker_integration_tests/test_connectors/test_wta_connector.py b/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py similarity index 73% rename from spynnaker_integration_tests/test_connectors/test_wta_connector.py rename to spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py index 930cbe83aa..093f8913f0 100644 --- a/spynnaker_integration_tests/test_connectors/test_wta_connector.py +++ b/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py @@ -20,13 +20,13 @@ from spinnaker_testbase import BaseTestCase -class TestWTAConnector(BaseTestCase): +class TestAllButMeConnector(BaseTestCase): - def check_wta(self): + def check_all_but_me(self): sim.setup() sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 2) pop = sim.Population(11, sim.IF_curr_exp()) - proj = sim.Projection(pop, pop, sim.extra_models.WTAConnector()) + proj = sim.Projection(pop, pop, sim.extra_models.AllButMeConnector()) sim.run(0) conns = list(proj.get([], format="list")) sim.end() @@ -35,14 +35,14 @@ def check_wta(self): print(groups) assert numpy.array_equal(conns, groups) - def test_wta(self): - self.runsafe(self.check_wta) + def test_all_but_me(self): + self.runsafe(self.check_all_but_me) - def check_wta_groups(self): + def check_all_but_me_groups(self): sim.setup() sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 5) pop = sim.Population(12, sim.IF_curr_exp()) - proj = sim.Projection(pop, pop, sim.extra_models.WTAConnector( + proj = sim.Projection(pop, pop, sim.extra_models.AllButMeConnector( n_neurons_per_group=3)) sim.run(0) conns = list(proj.get([], format="list")) @@ -57,13 +57,13 @@ def check_wta_groups(self): print(groups) assert numpy.array_equal(conns, groups) - def test_wta_groups(self): - self.runsafe(self.check_wta_groups) + def test_all_but_me_groups(self): + self.runsafe(self.check_all_but_me_groups) - def check_wta_offline(self): + def check_all_but_me_offline(self): sim.setup() pop = sim.Population(11, sim.IF_curr_exp()) - conn = sim.extra_models.WTAConnector() + conn = sim.extra_models.AllButMeConnector() proj = sim.Projection(pop, pop, conn) sim.run(0) conns = list(proj.get([], format="list")) @@ -82,16 +82,16 @@ def check_wta_offline(self): assert numpy.array_equal(conns, groups) assert numpy.array_equal(conns, offline_conns) - def test_wta_offline(self): - self.runsafe(self.check_wta_offline) + def test_all_but_me_offline(self): + self.runsafe(self.check_all_but_me_offline) - def check_wta_weights(self): + def check_all_but_me_weights(self): sim.setup() sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) pre = sim.Population(11, sim.IF_curr_exp()) post = sim.Population(11, sim.IF_curr_exp()) weights = numpy.arange(0.25, ((11 * 10) + 1) * 0.25, 0.25) - conn = sim.extra_models.WTAConnector(weights=weights) + conn = sim.extra_models.AllButMeConnector(weights=weights) proj = sim.Projection(pre, post, conn) sim.run(0) conns = list(proj.get(["weight"], format="list")) @@ -103,47 +103,47 @@ def check_wta_weights(self): print(groups) assert numpy.array_equal(conns, groups) - def test_wta_weights(self): - self.runsafe(self.check_wta_weights) + def test_all_but_me_weights(self): + self.runsafe(self.check_all_but_me_weights) - def check_wta_wrong_number_of_neurons(self): + def check_all_but_me_wrong_number_of_neurons(self): sim.setup() sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) pre = sim.Population(11, sim.IF_curr_exp()) post = sim.Population(11, sim.IF_curr_exp()) with pytest.raises(ValueError): sim.Projection( - pre, post, sim.extra_models.WTAConnector( + pre, post, sim.extra_models.AllButMeConnector( n_neurons_per_group=3)) sim.end() - def test_wta_wrong_number_of_neurons(self): - self.runsafe(self.check_wta_wrong_number_of_neurons) + def test_all_but_me_wrong_number_of_neurons(self): + self.runsafe(self.check_all_but_me_wrong_number_of_neurons) - def check_wta_diff_number_of_neurons(self): + def check_all_but_me_diff_number_of_neurons(self): sim.setup() sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) pre = sim.Population(12, sim.IF_curr_exp()) post = sim.Population(9, sim.IF_curr_exp()) with pytest.raises(ValueError): sim.Projection( - pre, post, sim.extra_models.WTAConnector( + pre, post, sim.extra_models.AllButMeConnector( n_neurons_per_group=3)) sim.end() - def test_wta_diff_number_of_neurons(self): - self.runsafe(self.check_wta_diff_number_of_neurons) + def test_all_but_me_diff_number_of_neurons(self): + self.runsafe(self.check_all_but_me_diff_number_of_neurons) - def check_wta_wrong_number_of_weights(self): + def check_all_but_me_wrong_number_of_weights(self): sim.setup() sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) pre = sim.Population(12, sim.IF_curr_exp()) post = sim.Population(12, sim.IF_curr_exp()) with pytest.raises(ValueError): sim.Projection( - pre, post, sim.extra_models.WTAConnector( + pre, post, sim.extra_models.AllButMeConnector( n_neurons_per_group=3, weights=[10])) sim.end() - def test_wta_wrong_number_of_weights(self): - self.runsafe(self.check_wta_wrong_number_of_weights) + def test_all_but_me_wrong_number_of_weights(self): + self.runsafe(self.check_all_but_me_wrong_number_of_weights) From aa0b3076d0a03408546ffb6b305adacd8f2e8290 Mon Sep 17 00:00:00 2001 From: "Christian Y. Brenninkmeijer" Date: Tue, 24 Sep 2024 11:26:07 +0100 Subject: [PATCH 14/18] change test to better demonstrate behavior --- .../test_all_but_me_connector.py | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py b/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py index 093f8913f0..581d094e30 100644 --- a/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py +++ b/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py @@ -23,23 +23,28 @@ class TestAllButMeConnector(BaseTestCase): def check_all_but_me(self): - sim.setup() + weight = 5.0 + timestep = 1.0 + sim.setup(timestep=timestep) sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 2) pop = sim.Population(11, sim.IF_curr_exp()) - proj = sim.Projection(pop, pop, sim.extra_models.AllButMeConnector()) + proj = sim.Projection( + pop, pop, sim.extra_models.AllButMeConnector(), + synapse_type=sim.StaticSynapse(weight = weight)) sim.run(0) - conns = list(proj.get([], format="list")) + conns = list(proj.get(["weight", "delay"], format="list")) sim.end() - groups = list([i, j] for (i, j) in permutations(range(11), 2)) print(conns) - print(groups) - assert numpy.array_equal(conns, groups) + # weight if not set in the connector will be the one from the syanpse + #delay if not set in the synapse will be the timestep + for index, (i, j) in enumerate(permutations(range(11), 2)): + assert conns[index] == [i, j, weight, timestep] def test_all_but_me(self): self.runsafe(self.check_all_but_me) def check_all_but_me_groups(self): - sim.setup() + sim.setup(timestep=1) sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 5) pop = sim.Population(12, sim.IF_curr_exp()) proj = sim.Projection(pop, pop, sim.extra_models.AllButMeConnector( @@ -61,7 +66,7 @@ def test_all_but_me_groups(self): self.runsafe(self.check_all_but_me_groups) def check_all_but_me_offline(self): - sim.setup() + sim.setup(timestep=1) pop = sim.Population(11, sim.IF_curr_exp()) conn = sim.extra_models.AllButMeConnector() proj = sim.Projection(pop, pop, conn) @@ -86,13 +91,15 @@ def test_all_but_me_offline(self): self.runsafe(self.check_all_but_me_offline) def check_all_but_me_weights(self): - sim.setup() + sim.setup(timestep=1) sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) pre = sim.Population(11, sim.IF_curr_exp()) post = sim.Population(11, sim.IF_curr_exp()) weights = numpy.arange(0.25, ((11 * 10) + 1) * 0.25, 0.25) conn = sim.extra_models.AllButMeConnector(weights=weights) - proj = sim.Projection(pre, post, conn) + # The weight in the synapse_type is ignored it the connector has one + proj = sim.Projection(pre, post, conn, + synapse_type=sim.StaticSynapse(weight = .3)) sim.run(0) conns = list(proj.get(["weight"], format="list")) sim.end() @@ -107,7 +114,7 @@ def test_all_but_me_weights(self): self.runsafe(self.check_all_but_me_weights) def check_all_but_me_wrong_number_of_neurons(self): - sim.setup() + sim.setup(timestep=1) sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) pre = sim.Population(11, sim.IF_curr_exp()) post = sim.Population(11, sim.IF_curr_exp()) @@ -121,7 +128,7 @@ def test_all_but_me_wrong_number_of_neurons(self): self.runsafe(self.check_all_but_me_wrong_number_of_neurons) def check_all_but_me_diff_number_of_neurons(self): - sim.setup() + sim.setup(timestep=1) sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) pre = sim.Population(12, sim.IF_curr_exp()) post = sim.Population(9, sim.IF_curr_exp()) @@ -135,7 +142,7 @@ def test_all_but_me_diff_number_of_neurons(self): self.runsafe(self.check_all_but_me_diff_number_of_neurons) def check_all_but_me_wrong_number_of_weights(self): - sim.setup() + sim.setup(timestep=1) sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) pre = sim.Population(12, sim.IF_curr_exp()) post = sim.Population(12, sim.IF_curr_exp()) From 156048be43adcccc694902983af772fa18636620 Mon Sep 17 00:00:00 2001 From: "Christian Y. Brenninkmeijer" Date: Tue, 24 Sep 2024 11:37:12 +0100 Subject: [PATCH 15/18] change to NotImplementedError for stuff that may work if we need it --- .../connectors/all_but_me_connector.py | 14 ++++++++------ .../test_connectors/test_all_but_me_connector.py | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py index 6e091aabbc..9ec94867d0 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py @@ -260,19 +260,21 @@ def validate_connection( synapse_info: SynapseInformation): if (synapse_info.pre_population.size != synapse_info.post_population.size): - raise ValueError( - "WTAConnector can only be used with populations that are " - "the same size as each other") + # Probably works by implementation but there is no know need + raise NotImplementedError( + "AllButMeConnector is only designed to be used with " + "populations that are the same size as each other") if self.__n_neurons_per_group is not None: if self.__n_neurons_per_group > synapse_info.pre_population.size: raise ValueError( - "WTAConnector cannot be used with a group size larger " - "than the population size") + "AllButMeConnector cannot be used with a group size " + "larger than the population size") if ((synapse_info.post_population.size / self.__n_neurons_per_group) != (synapse_info.post_population.size // self.__n_neurons_per_group)): - raise ValueError( + # Probably works by implementation but there is no know need + raise NotImplementedError( "The number of neurons in each population must be " "divisible by the number of neurons per group") n_neurons_per_group = self.__n_neurons_per_group diff --git a/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py b/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py index 581d094e30..7f5251a2f8 100644 --- a/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py +++ b/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py @@ -118,7 +118,7 @@ def check_all_but_me_wrong_number_of_neurons(self): sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) pre = sim.Population(11, sim.IF_curr_exp()) post = sim.Population(11, sim.IF_curr_exp()) - with pytest.raises(ValueError): + with pytest.raises(NotImplementedError): sim.Projection( pre, post, sim.extra_models.AllButMeConnector( n_neurons_per_group=3)) @@ -132,7 +132,7 @@ def check_all_but_me_diff_number_of_neurons(self): sim.set_number_of_neurons_per_core(sim.IF_curr_exp, 3) pre = sim.Population(12, sim.IF_curr_exp()) post = sim.Population(9, sim.IF_curr_exp()) - with pytest.raises(ValueError): + with pytest.raises(NotImplementedError): sim.Projection( pre, post, sim.extra_models.AllButMeConnector( n_neurons_per_group=3)) From cc16c55efaaf629dc0cb0c7481c718a1913744e9 Mon Sep 17 00:00:00 2001 From: "Christian Y. Brenninkmeijer" Date: Tue, 24 Sep 2024 11:54:32 +0100 Subject: [PATCH 16/18] changed description --- .../connectors/all_but_me_connector.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py index 9ec94867d0..33c3ad6040 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py @@ -41,15 +41,21 @@ class AllButMeConnector(AbstractGenerateConnectorOnMachine, AbstractGenerateConnectorOnHost): """ - Normally used to connect a population to itself, the assumption is that - the population can represent multiple potential winner-takes-all groups, - where each is independent. The connector will connect each pre-neuron in a - group to each post-neuron in the same group, except the one with the same - index. - - Can be used for two distinct populations BUT - they must have the same number of neurons - and neuron X of the source will not connect to neuron X of the target. + A Connector that connect all the neurons except the one with the same id. + + This will connect each neuron in each group (default a single group for + the whole Population) which all the target neurons in that group except + the one with the same id. + + There is also an option to add weights for each group. + + The know use case is multiple potential winner-takes-all groups where the + connector each time a neurons spikes will inhibit all other neurons + in the group. + + As the only know usecase is source and target population of the same size + (including self connections) whose size is an exact positive integer + multiple of the n_neurons_per_group that is all currently supported. """ __slots__ = ("__n_neurons_per_group", "__weights") From 3fb80e306a239a08eb697f76186d274f05df47c2 Mon Sep 17 00:00:00 2001 From: "Christian Y. Brenninkmeijer" Date: Tue, 24 Sep 2024 11:59:22 +0100 Subject: [PATCH 17/18] flake8 --- .../test_connectors/test_all_but_me_connector.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py b/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py index 7f5251a2f8..4a0d1c1203 100644 --- a/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py +++ b/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py @@ -30,13 +30,13 @@ def check_all_but_me(self): pop = sim.Population(11, sim.IF_curr_exp()) proj = sim.Projection( pop, pop, sim.extra_models.AllButMeConnector(), - synapse_type=sim.StaticSynapse(weight = weight)) + synapse_type=sim.StaticSynapse(weight=weight)) sim.run(0) conns = list(proj.get(["weight", "delay"], format="list")) sim.end() print(conns) # weight if not set in the connector will be the one from the syanpse - #delay if not set in the synapse will be the timestep + # delay if not set in the synapse will be the timestep for index, (i, j) in enumerate(permutations(range(11), 2)): assert conns[index] == [i, j, weight, timestep] @@ -99,7 +99,7 @@ def check_all_but_me_weights(self): conn = sim.extra_models.AllButMeConnector(weights=weights) # The weight in the synapse_type is ignored it the connector has one proj = sim.Projection(pre, post, conn, - synapse_type=sim.StaticSynapse(weight = .3)) + synapse_type=sim.StaticSynapse(weight=.3)) sim.run(0) conns = list(proj.get(["weight"], format="list")) sim.end() From 8852ee20140ecd688f412a4539ed75c78524dd47 Mon Sep 17 00:00:00 2001 From: "Christian Y. Brenninkmeijer" Date: Tue, 24 Sep 2024 12:10:09 +0100 Subject: [PATCH 18/18] docs --- .../connectors/all_but_me_connector.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py index 33c3ad6040..9e629606a7 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py @@ -44,18 +44,19 @@ class AllButMeConnector(AbstractGenerateConnectorOnMachine, A Connector that connect all the neurons except the one with the same id. This will connect each neuron in each group (default a single group for - the whole Population) which all the target neurons in that group except + the whole Population) to the target neurons in that group except the one with the same id. - There is also an option to add weights for each group. + There is also an option to add weights. + These are then used for each group. The know use case is multiple potential winner-takes-all groups where the connector each time a neurons spikes will inhibit all other neurons in the group. - As the only know usecase is source and target population of the same size + As the only know use case is source and target population of the same size (including self connections) whose size is an exact positive integer - multiple of the n_neurons_per_group that is all currently supported. + multiple of the n_neurons_per_group so that is all currently supported. """ __slots__ = ("__n_neurons_per_group", "__weights")