Source code for spynnaker.pyNN.models.neuron.structural_plasticity.synaptogenesis.formation.distance_dependent_formation

# Copyright (c) 2017-2019 The University of Manchester
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import numpy
from spinn_utilities.overrides import overrides
from .abstract_formation import AbstractFormation
from data_specification.enums.data_type import DataType
from spinn_front_end_common.utilities.constants import BYTES_PER_WORD

# 6 32-bit words (grid_x, grid_y, grid_x_recip, grid_y_recep, ff_prob_size,
#                 lat_prob_size)
_PARAMS_SIZE_IN_BYTES = 6 * BYTES_PER_WORD


[docs]class DistanceDependentFormation(AbstractFormation): """ Formation rule that depends on the physical distance between neurons """ __slots__ = [ "__grid", "__p_form_forward", "__sigma_form_forward", "__p_form_lateral", "__sigma_form_lateral", "__ff_distance_probabilities", "__lat_distance_probabilities" ] def __init__( self, grid=(16, 16), p_form_forward=0.16, sigma_form_forward=2.5, p_form_lateral=1.0, sigma_form_lateral=1.0): """ :param grid: (x, y) dimensions of the grid of distance :type grid: tuple(int,int) or list(int) or ~numpy.ndarray(int) :param float p_form_forward: The peak probability of formation on feed-forward connections :param float sigma_form_forward: The spread of probability with distance of formation on\ feed-forward connections :param float p_form_lateral: The peak probability of formation on lateral connections :param float sigma_form_lateral: The spread of probability with distance of formation on\ lateral connections """ self.__grid = numpy.asarray(grid, dtype=int) self.__p_form_forward = p_form_forward self.__sigma_form_forward = sigma_form_forward self.__p_form_lateral = p_form_lateral self.__sigma_form_lateral = sigma_form_lateral self.__ff_distance_probabilities = \ self.generate_distance_probability_array( self.__p_form_forward, self.__sigma_form_forward) self.__lat_distance_probabilities = \ self.generate_distance_probability_array( self.__p_form_lateral, self.__sigma_form_lateral) @property @overrides(AbstractFormation.vertex_executable_suffix) def vertex_executable_suffix(self): return "_distance"
[docs] @overrides(AbstractFormation.get_parameters_sdram_usage_in_bytes) def get_parameters_sdram_usage_in_bytes(self): return (_PARAMS_SIZE_IN_BYTES + len(self.__ff_distance_probabilities) * 2 + len(self.__lat_distance_probabilities) * 2)
[docs] def generate_distance_probability_array(self, probability, sigma): """ Generate the exponentially decaying probability LUTs. :param float probability: peak probability :param float sigma: spread :return: distance-dependent probabilities :rtype: ~numpy.ndarray(float) """ euclidian_distances = numpy.ones(self.__grid ** 2) * numpy.nan for row in range(euclidian_distances.shape[0]): for column in range(euclidian_distances.shape[1]): if self.__grid[0] > 1: pre = (row // self.__grid[0], row % self.__grid[1]) post = (column // self.__grid[0], column % self.__grid[1]) else: pre = (0, row % self.__grid[1]) post = (0, column % self.__grid[1]) # TODO Make distance metric "type" controllable euclidian_distances[row, column] = self.distance( pre, post, metric='euclidian') largest_squared_distance = numpy.max(euclidian_distances ** 2) squared_distances = numpy.arange(largest_squared_distance + 1) raw_probabilities = probability * ( numpy.exp(-squared_distances / (2 * sigma ** 2))) quantised_probabilities = raw_probabilities * ((2 ** 16) - 1) # Quantize probabilities and cast as uint16 / short unfiltered_probabilities = quantised_probabilities.astype( dtype="uint16") # Only return probabilities which are non-zero filtered_probabilities = unfiltered_probabilities[ unfiltered_probabilities > 0] if filtered_probabilities.size % 2 != 0: filtered_probabilities = numpy.concatenate( (filtered_probabilities, numpy.zeros(filtered_probabilities.size % 2, dtype="uint16"))) return filtered_probabilities
[docs] def distance(self, x0, x1, metric): """ Compute the distance between points x0 and x1 place on the grid\ using periodic boundary conditions. :param ~numpy.ndarray(int) x0: first point in space :param ~numpy.ndarray(int) x1: second point in space :param ~numpy.ndarray(int) grid: shape of grid :param str metric: distance metric, i.e. ``euclidian`` or ``manhattan`` or ``equidistant`` :return: the distance :rtype: float """ # pylint: disable=assignment-from-no-return x0 = numpy.asarray(x0) x1 = numpy.asarray(x1) delta = numpy.abs(x0 - x1) if (delta[0] > self.__grid[0] * .5) and self.__grid[0] > 0: delta[0] -= self.__grid[0] if (delta[1] > self.__grid[1] * .5) and self.__grid[1] > 0: delta[1] -= self.__grid[1] if metric == 'manhattan': return numpy.abs(delta).sum(axis=-1) elif metric == 'equidistant': p = 4 exponents = numpy.power(delta, [p] * delta.size) return numpy.floor(numpy.power(exponents.sum(axis=-1), [1. / p])) return numpy.sqrt((delta ** 2).sum(axis=-1))
[docs] @overrides(AbstractFormation.write_parameters) def write_parameters(self, spec): spec.write_array(self.__grid) # Work out the reciprocal, but zero them if >= 1 as they are not # representable as S031 in that case, and not used anyway recip = 1 / self.__grid recip[recip >= 1.0] = 0 spec.write_array(DataType.S031.encode_as_numpy_int_array(recip)) spec.write_value(len(self.__ff_distance_probabilities)) spec.write_value(len(self.__lat_distance_probabilities)) spec.write_array(self.__ff_distance_probabilities.view("<u4")) spec.write_array(self.__lat_distance_probabilities.view("<u4"))
[docs] @overrides(AbstractFormation.get_parameter_names) def get_parameter_names(self): return ["grid", "p_form_forward", "sigma_form_forward", "p_form_lateral", "sigma_form_lateral"]