From a7abbee7b132a4148084aa0f9e12bedced6d42c2 Mon Sep 17 00:00:00 2001 From: chanokin Date: Thu, 9 Jul 2020 22:19:12 +0100 Subject: [PATCH 1/9] starting procedural on pynn --- pynn_genn/__init__.py | 1 + pynn_genn/connectors.py | 52 ++++++++++++-------- pynn_genn/projections.py | 100 ++++++++++++++++++++++++++++++++++----- pynn_genn/simulator.py | 1 + 4 files changed, 121 insertions(+), 33 deletions(-) diff --git a/pynn_genn/__init__.py b/pynn_genn/__init__.py index c5ebefbb..77cee0ba 100644 --- a/pynn_genn/__init__.py +++ b/pynn_genn/__init__.py @@ -47,6 +47,7 @@ def setup(timestep=DEFAULT_TIMESTEP, min_delay=DEFAULT_MIN_DELAY, simulator.state.max_delay = max_delay simulator.state.mpi_rank = extra_params.get("rank", 0) simulator.state.num_processes = extra_params.get("num_processes", 1) + simulator.state.num_threads_per_spike = extra_params.get("num_threads_per_spike", 8) # If backend is specified, use that if "backend" in extra_params: diff --git a/pynn_genn/connectors.py b/pynn_genn/connectors.py index d8b5561f..0920a2fc 100644 --- a/pynn_genn/connectors.py +++ b/pynn_genn/connectors.py @@ -58,11 +58,12 @@ def __str__(self): class GeNNConnectorMixin(object): - def __init__(self, use_sparse=True): + def __init__(self, use_sparse=True, use_procedural=False): self.use_sparse = use_sparse self.on_device_init_params = {} self.connectivity_init_possible = False self._builtin_name = "" + self.use_procedural = use_procedural def _parameters_from_synapse_type(self, projection, distance_map=None): """ @@ -128,8 +129,8 @@ def _conn_init_params(self): class OneToOneConnector(GeNNConnectorMixin, OneToOnePyNN): __doc__ = OneToOnePyNN.__doc__ - def __init__(self, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) + def __init__(self, safe=True, callback=None, use_procedural=False): + GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) OneToOnePyNN.__init__( self, safe=safe, callback=callback) @@ -140,8 +141,10 @@ def __init__(self, safe=True, callback=None): class AllToAllConnector(GeNNConnectorMixin, AllToAllPyNN): __doc__ = AllToAllPyNN.__doc__ - def __init__(self, allow_self_connections=True, safe=True, callback=None,): - GeNNConnectorMixin.__init__(self, use_sparse=False) + def __init__(self, allow_self_connections=True, safe=True, callback=None, + use_procedural=False): + GeNNConnectorMixin.__init__(self, use_sparse=False, + use_procedural=use_procedural) AllToAllPyNN.__init__( self, allow_self_connections=allow_self_connections, safe=safe, callback=callback) @@ -151,8 +154,9 @@ class FixedProbabilityConnector(GeNNConnectorMixin, FixProbPyNN): __doc__ = FixProbPyNN.__doc__ def __init__(self, p_connect, allow_self_connections=True, - rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) + rng=None, safe=True, callback=None, use_procedural=False): + + GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) FixProbPyNN.__init__(self, p_connect, allow_self_connections, rng, safe=safe, callback=callback) @@ -172,8 +176,9 @@ class FixedTotalNumberConnector(GeNNConnectorMixin, FixTotalPyNN): __doc__ = FixTotalPyNN.__doc__ def __init__(self, n, allow_self_connections=True, with_replacement=True, - rng=None, safe=True, callback=None,): - GeNNConnectorMixin.__init__(self) + rng=None, safe=True, callback=None, use_procedural=False): + + GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) FixTotalPyNN.__init__(self, n, allow_self_connections, with_replacement, rng, safe=safe, callback=callback) @@ -195,8 +200,9 @@ class FixedNumberPreConnector(GeNNConnectorMixin, FixNumPrePyNN): __doc__ = FixNumPrePyNN.__doc__ def __init__(self, n, allow_self_connections=True, with_replacement=False, - rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) + rng=None, safe=True, callback=None, use_procedural=False): + + GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) FixNumPrePyNN.__init__(self, n, allow_self_connections, with_replacement, rng, safe=safe, callback=callback) @@ -205,8 +211,9 @@ class FixedNumberPostConnector(GeNNConnectorMixin, FixNumPostPyNN): __doc__ = FixNumPostPyNN.__doc__ def __init__(self, n, allow_self_connections=True, with_replacement=False, - rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) + rng=None, safe=True, callback=None, use_procedural=False): + + GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) FixNumPostPyNN.__init__(self, n, allow_self_connections, with_replacement, rng, safe=safe, callback=callback) @@ -228,8 +235,9 @@ class DistanceDependentProbabilityConnector(GeNNConnectorMixin, DistProbPyNN): __doc__ = DistProbPyNN.__doc__ def __init__(self, d_expression, allow_self_connections=True, - rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) + rng=None, safe=True, callback=None, use_procedural=False): + + GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) DistProbPyNN.__init__(self, d_expression, allow_self_connections, rng, safe=safe, callback=callback) @@ -239,8 +247,9 @@ class DisplacementDependentProbabilityConnector( __doc__ = DisplaceProbPyNN.__doc__ def __init__(self, disp_function, allow_self_connections=True, - rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) + rng=None, safe=True, callback=None, use_procedural=False): + + GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) DisplaceProbPyNN.__init__(self, disp_function, allow_self_connections, rng, safe=safe, callback=callback) @@ -249,8 +258,8 @@ class IndexBasedProbabilityConnector(GeNNConnectorMixin, IndexProbPyNN): __doc__ = IndexProbPyNN.__doc__ def __init__(self, index_expression, allow_self_connections=True, - rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) + rng=None, safe=True, callback=None, use_procedural=False): + GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) IndexProbPyNN.__init__(self, index_expression, allow_self_connections, rng, safe=safe, callback=callback) @@ -259,8 +268,9 @@ class SmallWorldConnector(GeNNConnectorMixin, SmallWorldPyNN): __doc__ = SmallWorldPyNN.__doc__ def __init__(self, degree, rewiring, allow_self_connections=True, - n_connections=None, rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) + n_connections=None, rng=None, safe=True, callback=None, + use_procedural=False): + GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) SmallWorldPyNN.__init__(self, degree, rewiring, allow_self_connections, n_connections, rng, safe=safe, callback=callback) diff --git a/pynn_genn/projections.py b/pynn_genn/projections.py index d22f21fe..1bb313e1 100644 --- a/pynn_genn/projections.py +++ b/pynn_genn/projections.py @@ -21,11 +21,24 @@ from pyNN.space import Space from pyNN.parameters import LazyArray +from pygenn import genn_wrapper + from . import simulator from .standardmodels.synapses import StaticSynapse from .model import sanitize_label from .contexts import ContextMixin -from .random import NativeRNG +from .random import NativeRNG, RandomDistribution + + +class RetrieveProceduralWeights(Exception): + def __str__(self): + return ("Downloading weights from device when using procedural " + "connectivity is not supported") + + +class RetrieveProceduralConnectivity(Exception): + def __str__(self): + return ("Downloading procedural connectivity from device is not supported") # Tuple type used to store details of GeNN sub-projections SubProjection = namedtuple("SubProjection", @@ -137,6 +150,11 @@ def __init__(self, presynaptic_population, postsynaptic_population, self._sub_projections = [] self.use_sparse = connector.use_sparse + + # iniatlize this as False and later on we can actually asses if it + # is possible to use procedural synapses or not + self.use_procedural = False + # Generate name stem for sub-projections created from this projection # **NOTE** superclass will always populate label PROPERTY # with something moderately useful i.e. at least unique @@ -177,7 +195,11 @@ def _get_attributes_as_arrays(self, names, multiple_synapses="sum"): for sub_pop in self._sub_projections: # Loop through names and pull variables for n in names: - if n != "presynaptic_index" and n != "postsynaptic_index" and n in sub_pop.syn_pop.vars: + if n == "g" and self.use_procedural: + raise RetrieveProceduralWeights() + + if (n != "presynaptic_index" and n != "postsynaptic_index" and + n in sub_pop.syn_pop.vars): genn_model.pull_var_from_device(sub_pop.genn_label, n) # If projection is sparse @@ -194,6 +216,8 @@ def _get_attributes_as_arrays(self, names, multiple_synapses="sum"): # if we were able to initialize connectivity on device # we need to get it before examining variables if self._connector.connectivity_init_possible: + if self.use_procedural: + raise RetrieveProceduralConnectivity() sub.syn_pop.pull_connectivity_from_device() # Get connection indices in @@ -215,6 +239,9 @@ def _get_attributes_as_arrays(self, names, multiple_synapses="sum"): else: # Loop through variables for n in names[0]: + if n == "g" and self.use_procedural: + raise RetrieveProceduralWeights() + # Create empty array to hold variable var = np.empty((self.pre.size, self.post.size)) @@ -247,6 +274,9 @@ def _get_attributes_as_list(self, names): for sub_pop in self._sub_projections: # Loop through names and pull variables for n in names: + if n == "g" and self.use_procedural: + raise RetrieveProceduralWeights() + if n != "presynaptic_index" and n != "postsynaptic_index": genn_model.pull_var_from_device(sub_pop.genn_label, n) @@ -338,17 +368,39 @@ def _get_sub_pops(self, pop, neuron_slice, conn_inds, conn_mask): else: return [(pop, neuron_slice, conn_mask)] + def _use_procedural(self, params): + + if not self._connector.use_procedural: + return False + + if not isinstance(self.synapse_type, StaticSynapse): + # todo: warn about procedural and plastic + return False + + weights_ok = False + if 'g' in params: + g = params['g'] + # if weights were not expanded and weights are homogeneous (constant) + # or are to be generated on device + if (isinstance(g, LazyArray) and + (g.is_homogeneous or + (isinstance(g.base_value, RandomDistribution) and + isinstance(g.base_value.rng, NativeRNG)))): + weights_ok = True + # if weights were expanded but are homegeneous + elif not np.allclose(g, g[0]): + weights_ok = True + + connect_ok = (self._connector.connectivity_init_possible or + isinstance(self._connector, AllToAllConnector)) + + return (weights_ok and connect_ok) def _create_native_projection(self): """Create GeNN projections (aka synaptic populatiosn) This function is supposed to be called by the simulator """ - if self.use_sparse: - matrix_type = "SPARSE_INDIVIDUALG" - else: - matrix_type = "DENSE_INDIVIDUALG" - # Set prefix based on receptor type # **NOTE** this is used to translate the right set of # neuron parameters into postsynaptic model parameters @@ -376,7 +428,6 @@ def _create_native_projection(self): conn_params=params): self._connector.connect(self) - # Convert pre and postsynaptic indices to numpy arrays pre_indices = np.asarray(pre_indices, dtype=np.uint32) post_indices = np.asarray(post_indices, dtype=np.uint32) @@ -390,6 +441,15 @@ def _create_native_projection(self): for c in self._connector.on_device_init_params: params[c] = self._connector.on_device_init_params[c] + self.use_procedural = self._use_procedural(params) + if self.use_procedural: + matrix_type = "PROCEDURAL_PROCEDURALG" + elif self.use_sparse: + matrix_type = "SPARSE_INDIVIDUALG" + else: + matrix_type = "DENSE_INDIVIDUALG" + + # Extract delays # If the delays were not expanded on host, check if homogeneous and # evaluate through the LazyArray method @@ -427,10 +487,12 @@ def _create_native_projection(self): matrix_type, prefix, params, delay_steps) else: self._on_host_init_native_projection( - pre_indices, post_indices, matrix_type, prefix, params, delay_steps) + pre_indices, post_indices, matrix_type, prefix, params, + delay_steps) - def _on_device_init_native_projection(self, matrix_type, prefix, params, delay_steps): + def _on_device_init_native_projection( + self, matrix_type, prefix, params, delay_steps): """ Create an on-device connectivity initializer based projection, this removes the need to compute things on host so we can use less memory and faster network initialization @@ -467,9 +529,16 @@ def _on_device_init_native_projection(self, matrix_type, prefix, params, delay_s wum_model, wum_params, wum_init, wum_pre_init, wum_post_init, psm_model, psm_params, psm_ini, conn_init) + if self.use_procedural: + syn_pop.pop.set_span_type( + genn_wrapper.SynapseGroup.SpanType_PRESYNAPTIC) + syn_pop.pop.set_num_threads_per_spike( + simulator.state.num_threads_per_spike) + # todo: warn about performance and tweaking num_threads_per_spike + self._sub_projections.append( - SubProjection(genn_label, self.pre, self.post, - slice(0, self.pre.size), slice(0, self.post.size), syn_pop, wum_params)) + SubProjection(genn_label, self.pre, self.post, slice(0, self.pre.size), + slice(0, self.post.size), syn_pop, wum_params)) def _on_host_init_native_projection(self, pre_indices, post_indices, matrix_type, prefix, params, delay_steps): @@ -555,6 +624,13 @@ def _on_host_init_native_projection(self, pre_indices, post_indices, if self.use_sparse: syn_pop.set_sparse_connections(conn_pre_inds, conn_post_inds) + if self.use_procedural: + syn_pop.pop.set_span_type( + genn_wrapper.SynapseGroup.SpanType_PRESYNAPTIC) + syn_pop.pop.set_num_threads_per_spike( + simulator.state.num_threads_per_spike) + # todo: warn about performance and tweaking num_threads_per_spike + self._sub_projections.append( SubProjection(genn_label, pre_pop, post_pop, pre_slice, post_slice, syn_pop, wum_params)) diff --git a/pynn_genn/simulator.py b/pynn_genn/simulator.py index 728b0bf2..37467181 100644 --- a/pynn_genn/simulator.py +++ b/pynn_genn/simulator.py @@ -23,6 +23,7 @@ def __init__(self): self.clear() self.dt = 0.1 self.t = 0.0 + self.num_threads_per_spike = 8 self.num_current_sources = 0 self.native_rng = None From e17e583140713fd067875e8cf75792ec242f3e8b Mon Sep 17 00:00:00 2001 From: Garibaldi Pineda Garcia Date: Mon, 13 Jul 2020 18:06:05 +0100 Subject: [PATCH 2/9] set 1 thread per spike for 1-to-1 conn --- pynn_genn/__init__.py | 2 +- pynn_genn/projections.py | 20 +++++++++----------- setup.py | 4 ++-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/pynn_genn/__init__.py b/pynn_genn/__init__.py index 77cee0ba..e9a6a906 100644 --- a/pynn_genn/__init__.py +++ b/pynn_genn/__init__.py @@ -47,7 +47,7 @@ def setup(timestep=DEFAULT_TIMESTEP, min_delay=DEFAULT_MIN_DELAY, simulator.state.max_delay = max_delay simulator.state.mpi_rank = extra_params.get("rank", 0) simulator.state.num_processes = extra_params.get("num_processes", 1) - simulator.state.num_threads_per_spike = extra_params.get("num_threads_per_spike", 8) + simulator.state.num_threads_per_spike = extra_params.get("num_threads_per_spike", 16) # If backend is specified, use that if "backend" in extra_params: diff --git a/pynn_genn/projections.py b/pynn_genn/projections.py index 1bb313e1..7c56d1cc 100644 --- a/pynn_genn/projections.py +++ b/pynn_genn/projections.py @@ -16,7 +16,7 @@ from pyNN import common from pyNN.connectors import AllToAllConnector, FromListConnector, \ - FromFileConnector + FromFileConnector, OneToOneConnector from pyNN.core import ezip from pyNN.space import Space from pyNN.parameters import LazyArray @@ -532,9 +532,14 @@ def _on_device_init_native_projection( if self.use_procedural: syn_pop.pop.set_span_type( genn_wrapper.SynapseGroup.SpanType_PRESYNAPTIC) - syn_pop.pop.set_num_threads_per_spike( - simulator.state.num_threads_per_spike) - # todo: warn about performance and tweaking num_threads_per_spike + print(simulator.state.num_threads_per_spike) + if isinstance(self._connector, OneToOneConnector): + syn_pop.pop.set_num_threads_per_spike(1) + else: + syn_pop.pop.set_num_threads_per_spike( + simulator.state.num_threads_per_spike) + # todo: warn about performance and tweaking num_threads_per_spike + self._sub_projections.append( SubProjection(genn_label, self.pre, self.post, slice(0, self.pre.size), @@ -624,13 +629,6 @@ def _on_host_init_native_projection(self, pre_indices, post_indices, if self.use_sparse: syn_pop.set_sparse_connections(conn_pre_inds, conn_post_inds) - if self.use_procedural: - syn_pop.pop.set_span_type( - genn_wrapper.SynapseGroup.SpanType_PRESYNAPTIC) - syn_pop.pop.set_num_threads_per_spike( - simulator.state.num_threads_per_spike) - # todo: warn about performance and tweaking num_threads_per_spike - self._sub_projections.append( SubProjection(genn_label, pre_pop, post_pop, pre_slice, post_slice, syn_pop, wum_params)) diff --git a/setup.py b/setup.py index 93431440..3e991cbb 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ # Requirements # **NOTE** PyNN really should be requiring lazyarray itself but it (0.9.2) doesn't seem to - install_requires=["pynn>=0.9, <0.9.3", "pygenn >= 0.4.1", "lazyarray>=0.3, < 0.4", + install_requires=["pynn>=0.9, <0.9.3", "pygenn >= 0.4.0", "lazyarray>=0.3, < 0.4", "sentinel", "neo>=0.6, <0.7", "numpy>=1.10.0,!=1.16.*", "six"], zip_safe=False, # Partly for performance reasons -) \ No newline at end of file +) From 1037fecc916f7ef20048194c322673ecbf3cc986 Mon Sep 17 00:00:00 2001 From: chanokin Date: Mon, 13 Jul 2020 21:32:43 +0100 Subject: [PATCH 3/9] set num_thr_per_spike in proj --- pynn_genn/__init__.py | 1 - pynn_genn/projections.py | 65 ++++++++++++++++++++++++++++++---------- pynn_genn/simulator.py | 1 - 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/pynn_genn/__init__.py b/pynn_genn/__init__.py index e9a6a906..c5ebefbb 100644 --- a/pynn_genn/__init__.py +++ b/pynn_genn/__init__.py @@ -47,7 +47,6 @@ def setup(timestep=DEFAULT_TIMESTEP, min_delay=DEFAULT_MIN_DELAY, simulator.state.max_delay = max_delay simulator.state.mpi_rank = extra_params.get("rank", 0) simulator.state.num_processes = extra_params.get("num_processes", 1) - simulator.state.num_threads_per_spike = extra_params.get("num_threads_per_spike", 16) # If backend is specified, use that if "backend" in extra_params: diff --git a/pynn_genn/projections.py b/pynn_genn/projections.py index 7c56d1cc..32e44ddb 100644 --- a/pynn_genn/projections.py +++ b/pynn_genn/projections.py @@ -30,16 +30,30 @@ from .random import NativeRNG, RandomDistribution -class RetrieveProceduralWeights(Exception): +class RetrieveProceduralWeightsException(Exception): def __str__(self): return ("Downloading weights from device when using procedural " "connectivity is not supported") -class RetrieveProceduralConnectivity(Exception): +class RetrieveProceduralConnectivityException(Exception): def __str__(self): return ("Downloading procedural connectivity from device is not supported") + +class TuneThreadsPerSpikeWarning(Warning): + def __str__(self): + return ("Performance of network may vary if num_threads_per_spike is not " + "appropriately selected") + + +class OneToOneThreadsPerSpikeWarning(Warning): + def __str__(self): + return ("A OneToOneConnector can only really use a single thread per spike. " + "Setting Projection parameter num_threads_per_spike to 1") + +DEFAULT_NUM_THREADS_PER_SPIKE = 8 + # Tuple type used to store details of GeNN sub-projections SubProjection = namedtuple("SubProjection", ["genn_label", "pre_pop", "post_pop", @@ -134,7 +148,8 @@ class Projection(common.Projection, ContextMixin): def __init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type=None, source=None, - receptor_type=None, space=Space(), label=None): + receptor_type=None, space=Space(), label=None, + num_threads_per_spike=None): # Make a deep copy of synapse type # so projection can independently change parameters synapse_type = deepcopy(synapse_type) @@ -154,6 +169,7 @@ def __init__(self, presynaptic_population, postsynaptic_population, # iniatlize this as False and later on we can actually asses if it # is possible to use procedural synapses or not self.use_procedural = False + self.num_threads_per_spike = num_threads_per_spike # Generate name stem for sub-projections created from this projection # **NOTE** superclass will always populate label PROPERTY @@ -196,7 +212,7 @@ def _get_attributes_as_arrays(self, names, multiple_synapses="sum"): # Loop through names and pull variables for n in names: if n == "g" and self.use_procedural: - raise RetrieveProceduralWeights() + raise RetrieveProceduralWeightsException() if (n != "presynaptic_index" and n != "postsynaptic_index" and n in sub_pop.syn_pop.vars): @@ -217,7 +233,7 @@ def _get_attributes_as_arrays(self, names, multiple_synapses="sum"): # we need to get it before examining variables if self._connector.connectivity_init_possible: if self.use_procedural: - raise RetrieveProceduralConnectivity() + raise RetrieveProceduralConnectivityException() sub.syn_pop.pull_connectivity_from_device() # Get connection indices in @@ -240,7 +256,7 @@ def _get_attributes_as_arrays(self, names, multiple_synapses="sum"): # Loop through variables for n in names[0]: if n == "g" and self.use_procedural: - raise RetrieveProceduralWeights() + raise RetrieveProceduralWeightsException() # Create empty array to hold variable var = np.empty((self.pre.size, self.post.size)) @@ -275,7 +291,7 @@ def _get_attributes_as_list(self, names): # Loop through names and pull variables for n in names: if n == "g" and self.use_procedural: - raise RetrieveProceduralWeights() + raise RetrieveProceduralWeightsException() if n != "presynaptic_index" and n != "postsynaptic_index": genn_model.pull_var_from_device(sub_pop.genn_label, n) @@ -368,7 +384,7 @@ def _get_sub_pops(self, pop, neuron_slice, conn_inds, conn_mask): else: return [(pop, neuron_slice, conn_mask)] - def _use_procedural(self, params): + def can_use_procedural(self, params): if not self._connector.use_procedural: return False @@ -441,7 +457,7 @@ def _create_native_projection(self): for c in self._connector.on_device_init_params: params[c] = self._connector.on_device_init_params[c] - self.use_procedural = self._use_procedural(params) + self.use_procedural = self.can_use_procedural(params) if self.use_procedural: matrix_type = "PROCEDURAL_PROCEDURALG" elif self.use_sparse: @@ -530,15 +546,32 @@ def _on_device_init_native_projection( psm_model, psm_params, psm_ini, conn_init) if self.use_procedural: + # if we can use a procedural connection set the apropriate span type syn_pop.pop.set_span_type( genn_wrapper.SynapseGroup.SpanType_PRESYNAPTIC) - print(simulator.state.num_threads_per_spike) - if isinstance(self._connector, OneToOneConnector): - syn_pop.pop.set_num_threads_per_spike(1) - else: - syn_pop.pop.set_num_threads_per_spike( - simulator.state.num_threads_per_spike) - # todo: warn about performance and tweaking num_threads_per_spike + + # if we want and can use a procedural connection, check for + # the number of threads selected by teh user + n_thr = self.num_threads_per_spike + # if it wasn't set + if self.num_threads_per_spike is None: + # and it's a 1-to-1 just set it to 1 + if isinstance(self._connector, OneToOneConnector): + n_thr = 1 + # if it is another connector, just set it to the default + # and warn the user that they may be able to do better + else: + n_thr = DEFAULT_NUM_THREADS_PER_SPIKE + warnings.warn(TuneThreadsPerSpikeWarning()) + # if it was set and it's a 1-to-1 and the number of threads is not 1 + # warn the user about this and set it to 1 + elif (isinstance(self._connector, OneToOneConnector) and + self.num_threads_per_spike != 1) : + n_thr = 1 + warnings.warn(OneToOneThreadsPerSpikeWarning()) + + # finally pass the value to GeNN + syn_pop.pop.set_num_threads_per_spike(n_thr) self._sub_projections.append( diff --git a/pynn_genn/simulator.py b/pynn_genn/simulator.py index 37467181..728b0bf2 100644 --- a/pynn_genn/simulator.py +++ b/pynn_genn/simulator.py @@ -23,7 +23,6 @@ def __init__(self): self.clear() self.dt = 0.1 self.t = 0.0 - self.num_threads_per_spike = 8 self.num_current_sources = 0 self.native_rng = None From d95424c55fdc9e5b2f6899ee3029f84787941a7a Mon Sep 17 00:00:00 2001 From: chanokin Date: Mon, 13 Jul 2020 21:45:34 +0100 Subject: [PATCH 4/9] warnings and exceptions --- pynn_genn/projections.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pynn_genn/projections.py b/pynn_genn/projections.py index 32e44ddb..61a32749 100644 --- a/pynn_genn/projections.py +++ b/pynn_genn/projections.py @@ -3,6 +3,8 @@ except NameError: # Python 3 xrange = range +import warnings + from collections import defaultdict, namedtuple, Iterable from itertools import product, repeat import logging @@ -35,18 +37,19 @@ def __str__(self): return ("Downloading weights from device when using procedural " "connectivity is not supported") - class RetrieveProceduralConnectivityException(Exception): def __str__(self): return ("Downloading procedural connectivity from device is not supported") +class PositiveNumThreadsException(Exception): + def __str__(self): + return ("The parameter num_threads_per_spike has to be greater than 0") class TuneThreadsPerSpikeWarning(Warning): def __str__(self): return ("Performance of network may vary if num_threads_per_spike is not " "appropriately selected") - class OneToOneThreadsPerSpikeWarning(Warning): def __str__(self): return ("A OneToOneConnector can only really use a single thread per spike. " @@ -169,6 +172,8 @@ def __init__(self, presynaptic_population, postsynaptic_population, # iniatlize this as False and later on we can actually asses if it # is possible to use procedural synapses or not self.use_procedural = False + if num_threads_per_spike <= 0: + raise PositiveNumThreadsException() self.num_threads_per_spike = num_threads_per_spike # Generate name stem for sub-projections created from this projection From d6cc694c9dc92bbf4c6665df84cc1df4155abfff Mon Sep 17 00:00:00 2001 From: chanokin Date: Mon, 13 Jul 2020 22:20:22 +0100 Subject: [PATCH 5/9] change choice of procedural from connector to projection --- pynn_genn/connectors.py | 37 +++++++++-------- pynn_genn/projections.py | 85 +++++++++++++++++++++------------------- 2 files changed, 63 insertions(+), 59 deletions(-) diff --git a/pynn_genn/connectors.py b/pynn_genn/connectors.py index 0920a2fc..74226168 100644 --- a/pynn_genn/connectors.py +++ b/pynn_genn/connectors.py @@ -58,12 +58,11 @@ def __str__(self): class GeNNConnectorMixin(object): - def __init__(self, use_sparse=True, use_procedural=False): + def __init__(self, use_sparse=True): self.use_sparse = use_sparse self.on_device_init_params = {} self.connectivity_init_possible = False self._builtin_name = "" - self.use_procedural = use_procedural def _parameters_from_synapse_type(self, projection, distance_map=None): """ @@ -129,8 +128,8 @@ def _conn_init_params(self): class OneToOneConnector(GeNNConnectorMixin, OneToOnePyNN): __doc__ = OneToOnePyNN.__doc__ - def __init__(self, safe=True, callback=None, use_procedural=False): - GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) + def __init__(self, safe=True, callback=None): + GeNNConnectorMixin.__init__(self) OneToOnePyNN.__init__( self, safe=safe, callback=callback) @@ -154,9 +153,9 @@ class FixedProbabilityConnector(GeNNConnectorMixin, FixProbPyNN): __doc__ = FixProbPyNN.__doc__ def __init__(self, p_connect, allow_self_connections=True, - rng=None, safe=True, callback=None, use_procedural=False): + rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) + GeNNConnectorMixin.__init__(self) FixProbPyNN.__init__(self, p_connect, allow_self_connections, rng, safe=safe, callback=callback) @@ -176,9 +175,9 @@ class FixedTotalNumberConnector(GeNNConnectorMixin, FixTotalPyNN): __doc__ = FixTotalPyNN.__doc__ def __init__(self, n, allow_self_connections=True, with_replacement=True, - rng=None, safe=True, callback=None, use_procedural=False): + rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) + GeNNConnectorMixin.__init__(self) FixTotalPyNN.__init__(self, n, allow_self_connections, with_replacement, rng, safe=safe, callback=callback) @@ -200,9 +199,9 @@ class FixedNumberPreConnector(GeNNConnectorMixin, FixNumPrePyNN): __doc__ = FixNumPrePyNN.__doc__ def __init__(self, n, allow_self_connections=True, with_replacement=False, - rng=None, safe=True, callback=None, use_procedural=False): + rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) + GeNNConnectorMixin.__init__(self) FixNumPrePyNN.__init__(self, n, allow_self_connections, with_replacement, rng, safe=safe, callback=callback) @@ -211,9 +210,9 @@ class FixedNumberPostConnector(GeNNConnectorMixin, FixNumPostPyNN): __doc__ = FixNumPostPyNN.__doc__ def __init__(self, n, allow_self_connections=True, with_replacement=False, - rng=None, safe=True, callback=None, use_procedural=False): + rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) + GeNNConnectorMixin.__init__(self) FixNumPostPyNN.__init__(self, n, allow_self_connections, with_replacement, rng, safe=safe, callback=callback) @@ -235,9 +234,9 @@ class DistanceDependentProbabilityConnector(GeNNConnectorMixin, DistProbPyNN): __doc__ = DistProbPyNN.__doc__ def __init__(self, d_expression, allow_self_connections=True, - rng=None, safe=True, callback=None, use_procedural=False): + rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) + GeNNConnectorMixin.__init__(self) DistProbPyNN.__init__(self, d_expression, allow_self_connections, rng, safe=safe, callback=callback) @@ -247,9 +246,9 @@ class DisplacementDependentProbabilityConnector( __doc__ = DisplaceProbPyNN.__doc__ def __init__(self, disp_function, allow_self_connections=True, - rng=None, safe=True, callback=None, use_procedural=False): + rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) + GeNNConnectorMixin.__init__(self) DisplaceProbPyNN.__init__(self, disp_function, allow_self_connections, rng, safe=safe, callback=callback) @@ -258,8 +257,8 @@ class IndexBasedProbabilityConnector(GeNNConnectorMixin, IndexProbPyNN): __doc__ = IndexProbPyNN.__doc__ def __init__(self, index_expression, allow_self_connections=True, - rng=None, safe=True, callback=None, use_procedural=False): - GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) + rng=None, safe=True, callback=None): + GeNNConnectorMixin.__init__(self) IndexProbPyNN.__init__(self, index_expression, allow_self_connections, rng, safe=safe, callback=callback) @@ -270,7 +269,7 @@ class SmallWorldConnector(GeNNConnectorMixin, SmallWorldPyNN): def __init__(self, degree, rewiring, allow_self_connections=True, n_connections=None, rng=None, safe=True, callback=None, use_procedural=False): - GeNNConnectorMixin.__init__(self, use_procedural=use_procedural) + GeNNConnectorMixin.__init__(self) SmallWorldPyNN.__init__(self, degree, rewiring, allow_self_connections, n_connections, rng, safe=safe, callback=callback) diff --git a/pynn_genn/projections.py b/pynn_genn/projections.py index 61a32749..232f3ab7 100644 --- a/pynn_genn/projections.py +++ b/pynn_genn/projections.py @@ -152,7 +152,7 @@ class Projection(common.Projection, ContextMixin): def __init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type=None, source=None, receptor_type=None, space=Space(), label=None, - num_threads_per_spike=None): + use_procedural=False, num_threads_per_spike=None): # Make a deep copy of synapse type # so projection can independently change parameters synapse_type = deepcopy(synapse_type) @@ -169,10 +169,10 @@ def __init__(self, presynaptic_population, postsynaptic_population, self.use_sparse = connector.use_sparse - # iniatlize this as False and later on we can actually asses if it + # later on we can actually asses if it # is possible to use procedural synapses or not - self.use_procedural = False - if num_threads_per_spike <= 0: + self.use_procedural = use_procedural + if not num_threads_per_spike is None and num_threads_per_spike <= 0: raise PositiveNumThreadsException() self.num_threads_per_spike = num_threads_per_spike @@ -391,7 +391,7 @@ def _get_sub_pops(self, pop, neuron_slice, conn_inds, conn_mask): def can_use_procedural(self, params): - if not self._connector.use_procedural: + if not self.use_procedural: return False if not isinstance(self.synapse_type, StaticSynapse): @@ -462,8 +462,8 @@ def _create_native_projection(self): for c in self._connector.on_device_init_params: params[c] = self._connector.on_device_init_params[c] - self.use_procedural = self.can_use_procedural(params) - if self.use_procedural: + use_procedural = self.can_use_procedural(params) + if use_procedural: matrix_type = "PROCEDURAL_PROCEDURALG" elif self.use_sparse: matrix_type = "SPARSE_INDIVIDUALG" @@ -505,15 +505,43 @@ def _create_native_projection(self): # prevented PyNN from expanding indices if self._connector.connectivity_init_possible: self._on_device_init_native_projection( - matrix_type, prefix, params, delay_steps) + matrix_type, prefix, params, delay_steps, use_procedural) else: self._on_host_init_native_projection( pre_indices, post_indices, matrix_type, prefix, params, - delay_steps) + delay_steps, use_procedural) + + def _setup_procedural(self, synaptic_population): + # if we can use a procedural connection set the apropriate span type + synaptic_population.pop.set_span_type( + genn_wrapper.SynapseGroup.SpanType_PRESYNAPTIC) + + # if we want and can use a procedural connection, check for + # the number of threads selected by teh user + n_thr = self.num_threads_per_spike + # if it wasn't set + if self.num_threads_per_spike is None: + # and it's a 1-to-1 just set it to 1 + if isinstance(self._connector, OneToOneConnector): + n_thr = 1 + # if it is another connector, just set it to the default + # and warn the user that they may be able to do better + else: + n_thr = DEFAULT_NUM_THREADS_PER_SPIKE + warnings.warn(TuneThreadsPerSpikeWarning()) + # if it was set and it's a 1-to-1 and the number of threads is not 1 + # warn the user about this and set it to 1 + elif (isinstance(self._connector, OneToOneConnector) and + self.num_threads_per_spike != 1) : + n_thr = 1 + warnings.warn(OneToOneThreadsPerSpikeWarning()) + + # finally pass the value to GeNN + synaptic_population.pop.set_num_threads_per_spike(n_thr) def _on_device_init_native_projection( - self, matrix_type, prefix, params, delay_steps): + self, matrix_type, prefix, params, delay_steps, use_procedural): """ Create an on-device connectivity initializer based projection, this removes the need to compute things on host so we can use less memory and faster network initialization @@ -550,41 +578,15 @@ def _on_device_init_native_projection( wum_model, wum_params, wum_init, wum_pre_init, wum_post_init, psm_model, psm_params, psm_ini, conn_init) - if self.use_procedural: - # if we can use a procedural connection set the apropriate span type - syn_pop.pop.set_span_type( - genn_wrapper.SynapseGroup.SpanType_PRESYNAPTIC) - - # if we want and can use a procedural connection, check for - # the number of threads selected by teh user - n_thr = self.num_threads_per_spike - # if it wasn't set - if self.num_threads_per_spike is None: - # and it's a 1-to-1 just set it to 1 - if isinstance(self._connector, OneToOneConnector): - n_thr = 1 - # if it is another connector, just set it to the default - # and warn the user that they may be able to do better - else: - n_thr = DEFAULT_NUM_THREADS_PER_SPIKE - warnings.warn(TuneThreadsPerSpikeWarning()) - # if it was set and it's a 1-to-1 and the number of threads is not 1 - # warn the user about this and set it to 1 - elif (isinstance(self._connector, OneToOneConnector) and - self.num_threads_per_spike != 1) : - n_thr = 1 - warnings.warn(OneToOneThreadsPerSpikeWarning()) - - # finally pass the value to GeNN - syn_pop.pop.set_num_threads_per_spike(n_thr) - + if use_procedural: + self._setup_procedural(syn_pop) self._sub_projections.append( SubProjection(genn_label, self.pre, self.post, slice(0, self.pre.size), slice(0, self.post.size), syn_pop, wum_params)) - def _on_host_init_native_projection(self, pre_indices, post_indices, - matrix_type, prefix, params, delay_steps): + def _on_host_init_native_projection(self, pre_indices, post_indices, matrix_type, + prefix, params, delay_steps, use_procedural): """If the projection HAS to be generated on host (i.e. using a FromListConnector) then go through the standard connectivity path :param pre_indices: indices for the pre-synaptic population neurons @@ -667,6 +669,9 @@ def _on_host_init_native_projection(self, pre_indices, post_indices, if self.use_sparse: syn_pop.set_sparse_connections(conn_pre_inds, conn_post_inds) + if use_procedural: + self._setup_procedural(syn_pop) + self._sub_projections.append( SubProjection(genn_label, pre_pop, post_pop, pre_slice, post_slice, syn_pop, wum_params)) From 083fa17e533b8bfc0f053f0a3a2f169e790683b0 Mon Sep 17 00:00:00 2001 From: chanokin Date: Mon, 13 Jul 2020 22:24:11 +0100 Subject: [PATCH 6/9] failed to remove a procedural arg in connectors.py --- pynn_genn/connectors.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pynn_genn/connectors.py b/pynn_genn/connectors.py index 74226168..bfee5adb 100644 --- a/pynn_genn/connectors.py +++ b/pynn_genn/connectors.py @@ -142,8 +142,7 @@ class AllToAllConnector(GeNNConnectorMixin, AllToAllPyNN): def __init__(self, allow_self_connections=True, safe=True, callback=None, use_procedural=False): - GeNNConnectorMixin.__init__(self, use_sparse=False, - use_procedural=use_procedural) + GeNNConnectorMixin.__init__(self, use_sparse=False) AllToAllPyNN.__init__( self, allow_self_connections=allow_self_connections, safe=safe, callback=callback) From a4316e3d80be84c00e5b5df7e0d2124353b4008a Mon Sep 17 00:00:00 2001 From: Garibaldi Pineda Garcia Date: Tue, 14 Jul 2020 17:42:08 +0100 Subject: [PATCH 7/9] added support for all-to-all --- pynn_genn/projections.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pynn_genn/projections.py b/pynn_genn/projections.py index 232f3ab7..761067cf 100644 --- a/pynn_genn/projections.py +++ b/pynn_genn/projections.py @@ -415,7 +415,12 @@ def can_use_procedural(self, params): connect_ok = (self._connector.connectivity_init_possible or isinstance(self._connector, AllToAllConnector)) - return (weights_ok and connect_ok) + if isinstance(self._connector, AllToAllConnector): + mtx_type = "DENSE_PROCEDURALG" + else: + mtx_type = "PROCEDURAL_PROCEDURALG" + + return (weights_ok and connect_ok), mtx_type def _create_native_projection(self): @@ -462,9 +467,9 @@ def _create_native_projection(self): for c in self._connector.on_device_init_params: params[c] = self._connector.on_device_init_params[c] - use_procedural = self.can_use_procedural(params) + use_procedural, mtx_type = self.can_use_procedural(params) if use_procedural: - matrix_type = "PROCEDURAL_PROCEDURALG" + matrix_type = mtx_type elif self.use_sparse: matrix_type = "SPARSE_INDIVIDUALG" else: @@ -512,6 +517,9 @@ def _create_native_projection(self): delay_steps, use_procedural) def _setup_procedural(self, synaptic_population): + if isinstance(self._connector, AllToAllConnector): + return + # if we can use a procedural connection set the apropriate span type synaptic_population.pop.set_span_type( genn_wrapper.SynapseGroup.SpanType_PRESYNAPTIC) From 466c12587939bfd63cf8617c9044e5dede1b943e Mon Sep 17 00:00:00 2001 From: chanokin Date: Tue, 14 Jul 2020 18:23:17 +0100 Subject: [PATCH 8/9] tidy and comments --- pynn_genn/connectors.py | 12 ++--------- pynn_genn/projections.py | 44 ++++++++++++++++++++++++++++++---------- setup.py | 2 +- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/pynn_genn/connectors.py b/pynn_genn/connectors.py index bfee5adb..06b3e34e 100644 --- a/pynn_genn/connectors.py +++ b/pynn_genn/connectors.py @@ -140,8 +140,7 @@ def __init__(self, safe=True, callback=None): class AllToAllConnector(GeNNConnectorMixin, AllToAllPyNN): __doc__ = AllToAllPyNN.__doc__ - def __init__(self, allow_self_connections=True, safe=True, callback=None, - use_procedural=False): + def __init__(self, allow_self_connections=True, safe=True, callback=None): GeNNConnectorMixin.__init__(self, use_sparse=False) AllToAllPyNN.__init__( self, allow_self_connections=allow_self_connections, @@ -153,7 +152,6 @@ class FixedProbabilityConnector(GeNNConnectorMixin, FixProbPyNN): def __init__(self, p_connect, allow_self_connections=True, rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) FixProbPyNN.__init__(self, p_connect, allow_self_connections, rng, safe=safe, callback=callback) @@ -175,7 +173,6 @@ class FixedTotalNumberConnector(GeNNConnectorMixin, FixTotalPyNN): def __init__(self, n, allow_self_connections=True, with_replacement=True, rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) FixTotalPyNN.__init__(self, n, allow_self_connections, with_replacement, rng, safe=safe, callback=callback) @@ -199,7 +196,6 @@ class FixedNumberPreConnector(GeNNConnectorMixin, FixNumPrePyNN): def __init__(self, n, allow_self_connections=True, with_replacement=False, rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) FixNumPrePyNN.__init__(self, n, allow_self_connections, with_replacement, rng, safe=safe, callback=callback) @@ -210,7 +206,6 @@ class FixedNumberPostConnector(GeNNConnectorMixin, FixNumPostPyNN): def __init__(self, n, allow_self_connections=True, with_replacement=False, rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) FixNumPostPyNN.__init__(self, n, allow_self_connections, with_replacement, rng, safe=safe, callback=callback) @@ -234,7 +229,6 @@ class DistanceDependentProbabilityConnector(GeNNConnectorMixin, DistProbPyNN): def __init__(self, d_expression, allow_self_connections=True, rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) DistProbPyNN.__init__(self, d_expression, allow_self_connections, rng, safe=safe, callback=callback) @@ -246,7 +240,6 @@ class DisplacementDependentProbabilityConnector( def __init__(self, disp_function, allow_self_connections=True, rng=None, safe=True, callback=None): - GeNNConnectorMixin.__init__(self) DisplaceProbPyNN.__init__(self, disp_function, allow_self_connections, rng, safe=safe, callback=callback) @@ -266,8 +259,7 @@ class SmallWorldConnector(GeNNConnectorMixin, SmallWorldPyNN): __doc__ = SmallWorldPyNN.__doc__ def __init__(self, degree, rewiring, allow_self_connections=True, - n_connections=None, rng=None, safe=True, callback=None, - use_procedural=False): + n_connections=None, rng=None, safe=True, callback=None): GeNNConnectorMixin.__init__(self) SmallWorldPyNN.__init__(self, degree, rewiring, allow_self_connections, n_connections, rng, safe=safe, callback=callback) diff --git a/pynn_genn/projections.py b/pynn_genn/projections.py index 761067cf..2cd58e61 100644 --- a/pynn_genn/projections.py +++ b/pynn_genn/projections.py @@ -45,6 +45,10 @@ class PositiveNumThreadsException(Exception): def __str__(self): return ("The parameter num_threads_per_spike has to be greater than 0") +class OnlyStaticWeightsProceduralException(Exception): + def __str__(self): + return ("Only static projections can be generated as procedural.") + class TuneThreadsPerSpikeWarning(Warning): def __str__(self): return ("Performance of network may vary if num_threads_per_spike is not " @@ -216,6 +220,8 @@ def _get_attributes_as_arrays(self, names, multiple_synapses="sum"): for sub_pop in self._sub_projections: # Loop through names and pull variables for n in names: + # grabbing weights when using procedurally generated projections + # is not possible if n == "g" and self.use_procedural: raise RetrieveProceduralWeightsException() @@ -237,6 +243,8 @@ def _get_attributes_as_arrays(self, names, multiple_synapses="sum"): # if we were able to initialize connectivity on device # we need to get it before examining variables if self._connector.connectivity_init_possible: + # grabbing connectivity when using procedurally generated + # projections is not possible if self.use_procedural: raise RetrieveProceduralConnectivityException() sub.syn_pop.pull_connectivity_from_device() @@ -261,6 +269,8 @@ def _get_attributes_as_arrays(self, names, multiple_synapses="sum"): # Loop through variables for n in names[0]: if n == "g" and self.use_procedural: + # grabbing weights when using procedurally generated projections + # is not possible raise RetrieveProceduralWeightsException() # Create empty array to hold variable @@ -296,6 +306,8 @@ def _get_attributes_as_list(self, names): # Loop through names and pull variables for n in names: if n == "g" and self.use_procedural: + # grabbing weights when using procedurally generated projections + # is not possible raise RetrieveProceduralWeightsException() if n != "presynaptic_index" and n != "postsynaptic_index": @@ -390,36 +402,45 @@ def _get_sub_pops(self, pop, neuron_slice, conn_inds, conn_mask): return [(pop, neuron_slice, conn_mask)] def can_use_procedural(self, params): + # do a series of checks to see if the projection can be computed + # on the fly by the GPU + + # select the apropriate matrix type given the connector type + if isinstance(self._connector, AllToAllConnector): + mtx_type = "DENSE_PROCEDURALG" + else: + mtx_type = "PROCEDURAL_PROCEDURALG" + # did the user ask for a procedural projection? if not self.use_procedural: - return False + return False, mtx_type + # did the user set the projection as plastic? not valid! if not isinstance(self.synapse_type, StaticSynapse): - # todo: warn about procedural and plastic - return False + # todo: should this be a warning? + raise OnlyStaticWeightsProceduralException() + # return False weights_ok = False if 'g' in params: g = params['g'] - # if weights were not expanded and weights are homogeneous (constant) - # or are to be generated on device + # if weights were not expanded and are either homogeneous (constant) + # or to be generated on device if (isinstance(g, LazyArray) and (g.is_homogeneous or (isinstance(g.base_value, RandomDistribution) and isinstance(g.base_value.rng, NativeRNG)))): weights_ok = True # if weights were expanded but are homegeneous + # todo: not sure if this case can happen? elif not np.allclose(g, g[0]): weights_ok = True + # can we generate the connectivity on device or + # is easily assumed as in the All-to-All connect_ok = (self._connector.connectivity_init_possible or isinstance(self._connector, AllToAllConnector)) - if isinstance(self._connector, AllToAllConnector): - mtx_type = "DENSE_PROCEDURALG" - else: - mtx_type = "PROCEDURAL_PROCEDURALG" - return (weights_ok and connect_ok), mtx_type @@ -475,7 +496,6 @@ def _create_native_projection(self): else: matrix_type = "DENSE_INDIVIDUALG" - # Extract delays # If the delays were not expanded on host, check if homogeneous and # evaluate through the LazyArray method @@ -557,6 +577,7 @@ def _on_device_init_native_projection( :param prefix: for the connection type (excitatory or inhibitory) :param params: connection parameters, required synapse_type.build_genn_wum :param delay_steps: delay used by connections + :param use_procedural: whether this projection can be generated on the fly :return: """ # Build GeNN postsynaptic model @@ -603,6 +624,7 @@ def _on_host_init_native_projection(self, pre_indices, post_indices, matrix_type :param prefix: for the connection type (excitatory or inhibitory) :param params: connection parameters, required synapse_type.build_genn_wum :param delay_steps: delay used by connections + :param use_procedural: whether this projection can be generated on the fly :return: """ num_synapses = len(pre_indices) diff --git a/setup.py b/setup.py index 3e991cbb..d6a58dab 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ # Requirements # **NOTE** PyNN really should be requiring lazyarray itself but it (0.9.2) doesn't seem to - install_requires=["pynn>=0.9, <0.9.3", "pygenn >= 0.4.0", "lazyarray>=0.3, < 0.4", + install_requires=["pynn>=0.9, <0.9.3", "pygenn >= 0.4.1", "lazyarray>=0.3, < 0.4", "sentinel", "neo>=0.6, <0.7", "numpy>=1.10.0,!=1.16.*", "six"], zip_safe=False, # Partly for performance reasons ) From 3a73961952752b5389a57f861da5fe9a37ac2506 Mon Sep 17 00:00:00 2001 From: chanokin Date: Tue, 14 Jul 2020 18:35:13 +0100 Subject: [PATCH 9/9] added tests --- test/system/test_genn.py | 131 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/test/system/test_genn.py b/test/system/test_genn.py index 4ceb1dac..1818e36f 100644 --- a/test/system/test_genn.py +++ b/test/system/test_genn.py @@ -313,13 +313,137 @@ def conn_init_fix_post(): abs_diff = numpy.abs(n - numpy.mean(n_cols)) epsilon = 0.01 - assert abs_diff <= epsilon + assert_less_equal(abs_diff, epsilon) scale = dist_params['high'] - dist_params['low'] s, p = stats.kstest((comp_var - dist_params['low']) / scale, 'uniform') min_p = 0.05 assert_greater(p, min_p) +def conn_proc_o2o(): + import numpy as np + import pynn_genn as sim + import copy + timestep = 1. + sim.setup(timestep) + + n_neurons = 100 + params = copy.copy(sim.IF_curr_exp.default_parameters) + pre = sim.Population(n_neurons, sim.SpikeSourceArray, + {'spike_times': [[1 + i] for i in range(n_neurons)]}, + label='pre') + params['tau_syn_E'] = 5. + post = sim.Population(n_neurons, sim.IF_curr_exp, params, + label='post') + post.record('spikes') + + conn = sim.OneToOneConnector() + syn = sim.StaticSynapse(weight=5, delay=1) + proj = sim.Projection(pre, post, conn, synapse_type=syn, + use_procedural=bool(1), num_threads_per_spike=1) + + sim.run(2 * n_neurons) + data = post.get_data() + spikes = np.asarray(data.segments[0].spiketrains) + sim.end() + + all_at_appr_time = 0 + sum_spikes = 0 + for i, times in enumerate(spikes): + sum_spikes += len(times) + if int(times[0]) == (i + 9): + all_at_appr_time += 1 + + assert_equal(sum_spikes, n_neurons) + assert_equal(all_at_appr_time, n_neurons) + +def conn_proc_a2a(): + import numpy as np + import pynn_genn as sim + import copy + timestep = 1. + sim.setup(timestep) + + n_neurons = 100 + params = copy.copy(sim.IF_curr_exp.default_parameters) + pre = sim.Population(n_neurons, sim.SpikeSourceArray, + {'spike_times': [[1] for _ in range(n_neurons)]}, + label='pre') + params['tau_syn_E'] = 5. + post = sim.Population(n_neurons, sim.IF_curr_exp, params, + label='post') + post.record('spikes') + + conn = sim.AllToAllConnector() + syn = sim.StaticSynapse(weight=5. / n_neurons, delay=1) # rand_dist) + proj = sim.Projection(pre, post, conn, synapse_type=syn, + use_procedural=bool(1)) + + sim.run(2 * n_neurons) + data = post.get_data() + spikes = np.asarray(data.segments[0].spiketrains) + + sim.end() + + all_at_appr_time = 0 + sum_spikes = 0 + for i, times in enumerate(spikes): + sum_spikes += len(times) + if int(times[0]) == 9: + all_at_appr_time += 1 + + assert_equal(sum_spikes, n_neurons) + assert_equal(all_at_appr_time, n_neurons) + +def conn_proc_fix_post(): + import numpy as np + import pynn_genn as sim + import copy + from pynn_genn.random import NativeRNG, NumpyRNG, RandomDistribution + + np_rng = NumpyRNG() + rng = NativeRNG(np_rng) + + timestep = 1. + sim.setup(timestep) + + n_pre = 100 + n_post = 50000 + params = copy.copy(sim.IF_curr_exp.default_parameters) + times = [[1] for _ in range(n_pre)] + pre = sim.Population(n_pre, sim.SpikeSourceArray, + {'spike_times': times}, + label='pre') + post = sim.Population(n_post, sim.IF_curr_exp, params, + label='post') + post.record('spikes') + + n = 2 + dist_params = {'low': 4.99, 'high': 5.01} + dist = 'uniform' + rand_dist = RandomDistribution(dist, rng=rng, **dist_params) + conn = sim.FixedNumberPostConnector(n, with_replacement=True, rng=rng) + syn = sim.StaticSynapse(weight=rand_dist, delay=1) # rand_dist) + # needed to use 1 thread per spike to get correct results, + # this is because the number of connections? + proj = sim.Projection(pre, post, conn, synapse_type=syn, + use_procedural=bool(1), num_threads_per_spike=1) + + sim.run(100) + data = post.get_data() + spikes = np.asarray(data.segments[0].spiketrains) + sim.end() + + all_at_appr_time = 0 + sum_spikes = 0 + for i, times in enumerate(spikes): + sum_spikes += (1 if len(times) else 0) + if len(times) == 1 and times[0] == 9: + all_at_appr_time += 1 + + assert_less_equal(np.abs(sum_spikes - (n_pre * n)), 2) + assert_less_equal(np.abs(all_at_appr_time - (n_pre * n)), 2) + if __name__ == '__main__': test_scenarios() @@ -329,3 +453,8 @@ def conn_init_fix_post(): conn_init_fix_prob() conn_init_fix_total() conn_init_fix_post() + # todo: these tests are not super good, need to think a better way + conn_proc_o2o() + conn_proc_a2a() + conn_proc_fix_post() +