diff --git a/neural_modelling/src/synapse_expander/connection_generator.c b/neural_modelling/src/synapse_expander/connection_generator.c index 4f7632ed97..a5b8777552 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_all_but_me.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 + ALL_BUT_ME, //!< AllButMe 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}, + {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_all_but_me.h b/neural_modelling/src/synapse_expander/connection_generators/connection_generator_all_but_me.h new file mode 100644 index 0000000000..603a566377 --- /dev/null +++ b/neural_modelling/src/synapse_expander/connection_generators/connection_generator_all_but_me.h @@ -0,0 +1,219 @@ +/* + * 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 All But Me connection generator implementation + */ + +#include +#include + +//! \brief The parameters to be passed around for this connector +struct all_but_me_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 all_but_me { + // 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 + accum *weights; +}; + + +/** + * \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_all_but_me_initialise(void **region) { + // Get the SDRAM params + struct all_but_me_conf *params_sdram = *region; + + // Allocate the data structure for parameters + struct all_but_me *params = spin1_malloc(sizeof(struct all_but_me)); + + // Copy the parameters + params->n_neurons_per_group = params_sdram->n_values; + if (params_sdram->has_weights) { + 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 + params->weights = ¶ms_sdram->weights[0]; + } else { + spin1_memcpy(¶ms->weights[0], ¶ms_sdram->weights[0], weight_size); + } + *region = ¶ms_sdram->weights[n_per_group * (n_per_group - 1)]; + } else { + params->weights = NULL; + *region = ¶ms_sdram->weights[0]; + } + + log_info("allButMe connector, n_values = %u, has_weights = %u", params->n_neurons_per_group, + params_sdram->has_weights); + + return params; +} + +/** + * \brief Free the All But Me connection generator + * \param[in] generator: The generator to free + */ +static void connection_generator_all_but_me_free(void *generator) { + sark_free(generator); +} + +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) { + 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; +} + +/** + * Get the weight for a given pre *value* and 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 + // 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_neurons_per_group - 1)) + post_pos; + return obj->weights[weight_index]; + } else { + return param_generator_generate(weight_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 + * \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_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 all_but_me *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_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_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 + 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 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_all_but_me_conn(weight, 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_neurons_per_group) { + post_value = 0; + 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; + } + n_values = pre_end - pre_start; + } + } + + return true; +} 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/extra_models/__init__.py b/spynnaker/pyNN/extra_models/__init__.py index 7ddcdbc076..c0ebcb478c 100644 --- a/spynnaker/pyNN/extra_models/__init__.py +++ b/spynnaker/pyNN/extra_models/__init__.py @@ -31,6 +31,8 @@ IFCurrExpSEMDBase as IF_curr_exp_sEMD, IFCurrDeltaCa2Adaptive, StocExp, StocExpStable, StocSigma, IFTruncDelta, IFCurrDeltaFixedProb) +from spynnaker.pyNN.models.neural_projections.connectors import ( + AllButMeConnector) # Variable rate poisson from spynnaker.pyNN.models.spike_source import SpikeSourcePoissonVariable @@ -57,5 +59,8 @@ 'StocExp', 'StocExpStable', 'StocSigma', 'IFCurrDeltaFixedProb', # Special - 'IFTruncDelta' + 'IFTruncDelta', + + # Connectors + 'AllButMeConnector' ] diff --git a/spynnaker/pyNN/models/neural_projections/connectors/__init__.py b/spynnaker/pyNN/models/neural_projections/connectors/__init__.py index 65d1e33f58..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 @@ -36,8 +37,8 @@ from .pool_dense_connector import PoolDenseConnector __all__ = ["AbstractConnector", "AbstractGenerateConnectorOnMachine", - "AbstractGenerateConnectorOnHost", "AllToAllConnector", - "ArrayConnector", "CSAConnector", + "AbstractGenerateConnectorOnHost", "AllButMeConnector", + "AllToAllConnector", "ArrayConnector", "CSAConnector", "DistanceDependentProbabilityConnector", "FixedNumberPostConnector", "FixedNumberPreConnector", "FixedProbabilityConnector", "FromFileConnector", 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/all_but_me_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py new file mode 100644 index 0000000000..9e629606a7 --- /dev/null +++ b/spynnaker/pyNN/models/neural_projections/connectors/all_but_me_connector.py @@ -0,0 +1,291 @@ +# 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 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 ( + AbstractGenerateConnectorOnMachine, ConnectorIDs) +from .abstract_generate_connector_on_host import ( + AbstractGenerateConnectorOnHost) + +if TYPE_CHECKING: + from spynnaker.pyNN.models.neural_projections import SynapseInformation + from spynnaker.pyNN.models.neural_projections import ( + ProjectionApplicationEdge) + + +class AllButMeConnector(AbstractGenerateConnectorOnMachine, + AbstractGenerateConnectorOnHost): + """ + 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) to the target neurons in that group except + the one with the same id. + + 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 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 so that is all currently supported. + """ + + __slots__ = ("__n_neurons_per_group", "__weights") + + def __init__(self, n_neurons_per_group: Optional[int] = None, + weights: Optional[NDArray[numpy.float64]] = None, + safe: bool = True, verbose: bool = False, + callback: None = None): + """ + :param n_neurons_per_group: + The number of neurons in each winner-takes-all group. + 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 verbose: + Whether to output extra information about the connectivity to a + CSV file + :param 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_neurons_per_group = n_neurons_per_group + self.__weights = weights + self.__check_weights(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: + 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: 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, + 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_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_neurons_per_group * + (self.__n_neurons_per_group - 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_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 + + 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_neurons_per_group is None: + return min(synapse_info.n_pre_neurons, + synapse_info.n_post_neurons) - 1 + return self.__n_neurons_per_group - 1 + + @overrides(AbstractConnector.get_weight_maximum) + def get_weight_maximum(self, synapse_info: SynapseInformation) -> float: + if self.__weights is not None: + return float(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, + synapse_type: int, synapse_info: SynapseInformation) -> NDArray: + 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) + # 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) + 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_neuron_per_group={self.__n_neurons_per_group})" + + @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_neurons_per_group + if n_values is None: + n_values = min(synapse_info.n_pre_neurons, + synapse_info.n_post_neurons) + 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: + size = BYTES_PER_WORD * 2 + 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): + # 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( + "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)): + # 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 + 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_all_but_me_connector.py b/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py new file mode 100644 index 0000000000..4a0d1c1203 --- /dev/null +++ b/spynnaker_integration_tests/test_connectors/test_all_but_me_connector.py @@ -0,0 +1,156 @@ +# 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 pytest +import numpy + +from pacman.model.graphs.common.slice import Slice +from spinnaker_testbase import BaseTestCase + + +class TestAllButMeConnector(BaseTestCase): + + def check_all_but_me(self): + 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(), + 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 + 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(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( + 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_all_but_me_groups(self): + self.runsafe(self.check_all_but_me_groups) + + def check_all_but_me_offline(self): + sim.setup(timestep=1) + pop = sim.Population(11, sim.IF_curr_exp()) + conn = sim.extra_models.AllButMeConnector() + 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_all_but_me_offline(self): + self.runsafe(self.check_all_but_me_offline) + + def check_all_but_me_weights(self): + 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) + # 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() + 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_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(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()) + with pytest.raises(NotImplementedError): + sim.Projection( + pre, post, sim.extra_models.AllButMeConnector( + n_neurons_per_group=3)) + sim.end() + + 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(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()) + with pytest.raises(NotImplementedError): + sim.Projection( + pre, post, sim.extra_models.AllButMeConnector( + n_neurons_per_group=3)) + sim.end() + + 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(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()) + with pytest.raises(ValueError): + sim.Projection( + pre, post, sim.extra_models.AllButMeConnector( + n_neurons_per_group=3, weights=[10])) + sim.end() + + def test_all_but_me_wrong_number_of_weights(self): + self.runsafe(self.check_all_but_me_wrong_number_of_weights)