Source code for spynnaker.pyNN

# Copyright (c) 2017 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.

"""
The :py:mod:`spynnaker.pyNN` package contains the front end specifications
and implementation for the PyNN High-level API
(https://neuralensemble.org/trac/PyNN).

This package contains the profile of that code for PyNN 0.9.
"""

# common imports
import logging
import numpy as __numpy
from pyNN import common as pynn_common
from pyNN.common import control as _pynn_control
from pyNN.recording import get_io
from pyNN.random import NumpyRNG
from pyNN.space import (
    Space, Line, Grid2D, Grid3D, Cuboid, Sphere, RandomStructure)
from pyNN.space import distance as _pynn_distance

from spinn_utilities.exceptions import SimulatorNotSetupException
from spinn_utilities.log import FormatAdapter
from spinn_utilities.helpful_functions import is_singleton
from spinn_front_end_common.utilities.exceptions import (
    ConfigurationException)

from spynnaker.pyNN.random_distribution import RandomDistribution
from spynnaker.pyNN.data import SpynnakerDataView
from spynnaker.pyNN.models.abstract_pynn_model import AbstractPyNNModel

# connections
# noinspection PyUnresolvedReferences
from spynnaker.pyNN.models.neural_projections.connectors import (
    AllToAllConnector, ArrayConnector, CSAConnector,
    DistanceDependentProbabilityConnector, FixedNumberPostConnector,
    FixedNumberPreConnector, FixedProbabilityConnector,
    FromFileConnector, FromListConnector, IndexBasedProbabilityConnector,
    KernelConnector, MultapseConnector as FixedTotalNumberConnector,
    OneToOneConnector, SmallWorldConnector, ConvolutionConnector,
    PoolDenseConnector)
# synapse structures
from spynnaker.pyNN.models.neuron.synapse_dynamics import (
    SynapseDynamicsStatic as StaticSynapse)

# plastic stuff
from spynnaker.pyNN.models.neuron.synapse_dynamics import (
    SynapseDynamicsSTDP as
    STDPMechanism, SynapseDynamicsStructuralStatic as
    StructuralMechanismStatic, SynapseDynamicsStructuralSTDP as
    StructuralMechanismSTDP)
from spynnaker.pyNN.models.neuron.plasticity.stdp.weight_dependence import (
    WeightDependenceAdditive as
    AdditiveWeightDependence, WeightDependenceMultiplicative as
    MultiplicativeWeightDependence)
from spynnaker.pyNN.models.neuron.plasticity.stdp.timing_dependence import (
    TimingDependenceSpikePair as
    SpikePairRule)
from spynnaker.pyNN.models.neuron.structural_plasticity.synaptogenesis\
    .partner_selection import (
        LastNeuronSelection, RandomSelection)
from spynnaker.pyNN.models.neuron.structural_plasticity.synaptogenesis\
    .formation import (
        DistanceDependentFormation)
from spynnaker.pyNN.models.neuron.structural_plasticity.synaptogenesis\
    .elimination import (
        RandomByWeightElimination)

# local-only synapses
from spynnaker.pyNN.models.neuron.local_only import (
    LocalOnlyConvolution as Convolution,
    LocalOnlyPoolDense as PoolDense)

# neuron stuff
# noinspection PyUnresolvedReferences
from spynnaker.pyNN.models.neuron.builds.if_cond_exp_base import (
    IFCondExpBase as IF_cond_exp)
# noinspection PyUnresolvedReferences
from spynnaker.pyNN.models.neuron.builds.if_curr_exp_base import (
    IFCurrExpBase as IF_curr_exp)
# noinspection PyUnresolvedReferences
from spynnaker.pyNN.models.neuron.builds.if_curr_alpha import (
    IFCurrAlpha as IF_curr_alpha)
# noinspection PyUnresolvedReferences
from spynnaker.pyNN.models.neuron.builds.if_curr_delta import (
    IFCurrDelta as IF_curr_delta)
# noinspection PyUnresolvedReferences
from spynnaker.pyNN.models.neuron.builds.izk_curr_exp_base import (
    IzkCurrExpBase as Izhikevich)
# noinspection PyUnresolvedReferences
from spynnaker.pyNN.models.spike_source.spike_source_array import (
    SpikeSourceArray)
# noinspection PyUnresolvedReferences
from spynnaker.pyNN.models.spike_source.spike_source_poisson import (
    SpikeSourcePoisson)

# pops
# noinspection PyUnresolvedReferences
from spynnaker.pyNN.models.populations import (
    Assembly, Population, PopulationView)

# projection
# noinspection PyUnresolvedReferences
from spynnaker.pyNN.models.projection import Projection as SpiNNakerProjection

# current sources
# noinspection PyUnresolvedReferences
from spynnaker.pyNN.models.current_sources import (
    DCSource, ACSource, StepCurrentSource, NoisyCurrentSource)

from spynnaker.pyNN import external_devices
from spynnaker.pyNN import extra_models

# big stuff
from spynnaker.pyNN.spinnaker import SpiNNaker

from spynnaker._version import __version__  # NOQA
from spynnaker._version import __version_name__  # NOQA
from spynnaker._version import __version_month__  # NOQA
from spynnaker._version import __version_year__  # NOQA


#: The timestep to use of "auto" is specified as a timestep
SPYNNAKER_AUTO_TIMESTEP = 1.0

logger = FormatAdapter(logging.getLogger(__name__))

__all__ = [
    # PyNN imports
    'Cuboid', 'distance', 'Grid2D', 'Grid3D', 'Line', 'NumpyRNG',
    'RandomDistribution', 'RandomStructure', 'Space', 'Sphere',

    # connections
    'AllToAllConnector', 'ArrayConnector', 'CSAConnector',
    'DistanceDependentProbabilityConnector', 'FixedNumberPostConnector',
    'FixedNumberPreConnector', 'FixedProbabilityConnector',
    'FromFileConnector', 'FromListConnector', 'IndexBasedProbabilityConnector',
    'FixedTotalNumberConnector', 'KernelConnector', 'OneToOneConnector',
    'SmallWorldConnector', 'ConvolutionConnector', 'PoolDenseConnector',
    # Local-only
    'Convolution', 'PoolDense',
    # synapse structures
    'StaticSynapse',
    # plastic stuff
    'STDPMechanism', 'AdditiveWeightDependence',
    'MultiplicativeWeightDependence', 'SpikePairRule',
    # Structural plasticity by Petrut Bogdan
    'StructuralMechanismStatic', 'StructuralMechanismSTDP',
    'LastNeuronSelection', 'RandomSelection',
    'DistanceDependentFormation', 'RandomByWeightElimination',
    # neuron stuff
    'IF_cond_exp', 'IF_curr_exp', "IF_curr_alpha", "IF_curr_delta",
    'Izhikevich', 'SpikeSourceArray', 'SpikeSourcePoisson',
    # pops
    'Assembly', 'Population', 'PopulationView',
    # projection
    'SpiNNakerProjection',
    # External devices and extra models
    'external_devices', 'extra_models',
    # CurrentSources
    'DCSource', 'ACSource', 'StepCurrentSource', 'NoisyCurrentSource',
    # Stuff that we define
    'end', 'setup', 'run', 'run_until', 'run_for', 'num_processes', 'rank',
    'reset', 'set_number_of_neurons_per_core',
    'Projection',
    'get_current_time', 'create', 'connect', 'get_time_step', 'get_min_delay',
    'get_max_delay', 'initialize', 'list_standard_models', 'name',
    'record', "get_machine"]

# Dynamically-extracted operations from PyNN
__pynn = {}
# Cache of the simulator created by setup
__simulator = None


# Patch the bugs in the PyNN documentation... Ugh!
[docs] def distance(src_cell, tgt_cell, mask=None, scale_factor=1.0, offset=0.0, periodic_boundaries=None): """ Return the Euclidean distance between two cells. :param src_cell: Measure from this cell :param tgt_cell: To this cell :param ~numpy.ndarray mask: allows only certain dimensions to be considered, e.g.: * to ignore the z-dimension, use ``mask=array([0,1])`` * to ignore y, ``mask=array([0,2])`` * to just consider z-distance, ``mask=array([2])`` :param float scale_factor: allows for different units in the pre- and post-position (the post-synaptic position is multiplied by this quantity). :param float offset: :param periodic_boundaries: """ return _pynn_distance( src_cell, tgt_cell, mask, scale_factor, offset, periodic_boundaries)
[docs] def setup(timestep=_pynn_control.DEFAULT_TIMESTEP, min_delay=_pynn_control.DEFAULT_MIN_DELAY, max_delay=None, database_socket_addresses=None, time_scale_factor=None, n_chips_required=None, n_boards_required=None, **extra_params): """ The main method needed to be called to make the PyNN 0.8 setup. Needs to be called before any other function :param timestep: the time step of the simulations in microseconds; if `None`, the configuration value is used :type timestep: float or None :param min_delay: the minimum delay of the simulation :type min_delay: float or str :param max_delay: Ignored and logs a warning if provided :type max_delay: float or str or None :param database_socket_addresses: the sockets used by external devices for the database notification protocol :type database_socket_addresses: iterable(~spinn_utilities.socket_address.SocketAddress) :param time_scale_factor: multiplicative factor to the machine time step (does not affect the neuron models accuracy) :type time_scale_factor: int or None :param n_chips_required: Deprecated! Use n_boards_required instead. Must be `None` if n_boards_required specified. :type n_chips_required: int or None :param n_boards_required: if you need to be allocated a machine (for spalloc) before building your graph, then fill this in with a general idea of the number of boards you need so that the spalloc system can allocate you a machine big enough for your needs. :type n_boards_required: int or None :param extra_params: other keyword arguments used to configure PyNN :return: MPI rank (always 0 on SpiNNaker) :rtype: int :raises \ ~spinn_front_end_common.utilities.exceptions.ConfigurationException: if both ``n_chips_required`` and ``n_boards_required`` are used. """ # pylint: disable=global-statement global __simulator # Check for "auto" values if timestep == "auto": timestep = SPYNNAKER_AUTO_TIMESTEP if min_delay == "auto": min_delay = timestep if max_delay: logger.warning( "max_delay is not supported by sPyNNaker so will be ignored") # pylint: disable=too-many-arguments # setup PyNN common stuff pynn_common.setup(timestep, min_delay, **extra_params) # create stuff simulator if SpynnakerDataView.is_setup(): logger.warning("Calling setup a second time causes the previous " "simulator to be stopped and cleared.") # if already exists, kill and rebuild try: __simulator.clear() except Exception: # pylint: disable=broad-except logger.exception("Error forcing previous simulation to clear") # create the main object for all stuff related software __simulator = SpiNNaker( time_scale_factor=time_scale_factor, timestep=timestep, min_delay=min_delay, n_chips_required=n_chips_required, n_boards_required=n_boards_required) # pylint: disable=protected-access external_devices._set_simulator(__simulator) # warn about kwargs arguments if extra_params: logger.warning("Extra params {} have been applied to the setup " "command which we do not consider", extra_params) # get overloaded functions from PyNN in relation of our simulator object _create_overloaded_functions(__simulator) SpynnakerDataView.add_database_socket_addresses(database_socket_addresses) return rank()
[docs] def name(): """ Returns the name of the simulator. :rtype: str """ return SpynnakerDataView.get_sim_name()
[docs] def Projection( presynaptic_population, postsynaptic_population, connector, synapse_type=None, source=None, receptor_type="excitatory", space=None, label=None): """ Used to support PEP 8 spelling correctly. :param presynaptic_population: the source pop :type presynaptic_population: ~spynnaker.pyNN.models.populations.Population :param postsynaptic_population: the destination population :type postsynaptic_population: ~spynnaker.pyNN.models.populations.Population :param AbstractConnector connector: the connector type :param AbstractStaticSynapseDynamics synapse_type: the synapse type :param None source: Unsupported; must be ``None`` :param str receptor_type: the receptor type :param space: the space object :type space: ~pyNN.space.Space or None :param label: the label :type label: str or None :return: a projection object for SpiNNaker :rtype: ~spynnaker.pyNN.models.projection.Projection """ # pylint: disable=too-many-arguments return SpiNNakerProjection( pre_synaptic_population=presynaptic_population, post_synaptic_population=postsynaptic_population, connector=connector, synapse_type=synapse_type, source=source, receptor_type=receptor_type, space=space, label=label)
def _create_overloaded_functions(spinnaker_simulator): """ Creates functions that the main PyNN interface supports (given from PyNN) :param spinnaker_simulator: the simulator object we use underneath """ # overload the failed ones with now valid ones, now that we're in setup # phase. __pynn["run"], __pynn["run_until"] = pynn_common.build_run( spinnaker_simulator) __pynn["get_current_time"], __pynn["get_time_step"], \ __pynn["get_min_delay"], __pynn["get_max_delay"], \ __pynn["num_processes"], __pynn["rank"] = \ pynn_common.build_state_queries(spinnaker_simulator) __pynn["reset"] = pynn_common.build_reset(spinnaker_simulator) __pynn["create"] = pynn_common.build_create(Population) __pynn["connect"] = pynn_common.build_connect( Projection, FixedProbabilityConnector, StaticSynapse) __pynn["record"] = pynn_common.build_record(spinnaker_simulator)
[docs] def end(_=True): """ Cleans up the SpiNNaker machine and software :param _: was named `compatible_output`, which we don't care about, so is a non-existent parameter """ if SpynnakerDataView.is_shutdown(): logger.warning("Second call to end ignored") return try: SpynnakerDataView.check_valid_simulator() except SimulatorNotSetupException: logger.exception("Calling end before setup makes no sense ignoring!") return for (population, variables, filename) in \ __simulator.write_on_end: io = get_io(filename) population.write_data(io, variables) __simulator.write_on_end = [] __simulator.stop()
[docs] def list_standard_models(): """ Return a list of all the StandardCellType classes available for this simulator. :rtype: list(str) """ results = list() for (key, obj) in globals().items(): if isinstance(obj, type) and issubclass(obj, AbstractPyNNModel): results.append(key) return results
[docs] def set_number_of_neurons_per_core(neuron_type, max_permitted): """ Sets a ceiling on the number of neurons of a given model that can be placed on a single core. This can be overridden by the individual Population. The new value can be `None`, meaning that the maximum is the same as the number of atoms, an int, meaning all Populations of this model must have one dimension, or a tuple of n integers, meaning all Populations of this model must have n dimensions. If not all Populations of this model have the same number of dimensions, it is recommended to set this to `None` here and then set the maximum on each Population. :param type(AbstractPopulationVertex) neuron_type: neuron type :param int max_permitted: the number to set to """ if isinstance(neuron_type, str): raise ConfigurationException( "set_number_of_neurons_per_core call now expects " "neuron_type as a class instead of as a str") max_neurons = max_permitted if is_singleton(max_permitted): max_neurons = (max_permitted, ) for m in max_neurons: # Make sure an integer value is passed in here and warn if different m_int = int(m) if (m_int - m) != 0: logger.warning( "The number of neurons per core requested {} is not an " "integer; the value has been set to {}", m, m_int) SpynnakerDataView.set_number_of_neurons_per_dimension_per_core( neuron_type, max_neurons)
# These methods will defer to PyNN methods if a simulator exists
[docs] def connect(pre, post, weight=0.0, delay=None, receptor_type=None, p=1, rng=None): """ Builds a projection. :param ~spynnaker.pyNN.models.populations.Population pre: source pop :param ~spynnaker.pyNN.models.populations.Population post: destination pop :param float weight: weight of the connections :param float delay: the delay of the connections :param str receptor_type: excitatory / inhibitory :param float p: probability :param ~pyNN.random.NumpyRNG rng: random number generator """ # pylint: disable=too-many-arguments SpynnakerDataView.check_user_can_act() __pynn["connect"](pre, post, weight, delay, receptor_type, p, rng)
[docs] def create(cellclass, cellparams=None, n=1): """ Builds a population with certain parameters. :param cellclass: population class :type cellclass: type or AbstractPyNNModel :param cellparams: population parameters. :param int n: number of neurons :rtype: ~spynnaker.pyNN.models.populations.Population """ SpynnakerDataView.check_user_can_act() return __pynn["create"](cellclass, cellparams, n)
def NativeRNG(seed_value): """ Fixes the random number generator's seed. :param seed_value: :type seed_value: int or list(int) or ~numpy.ndarray(int32) """ __numpy.random.seed(seed_value)
[docs] def get_current_time(): """ Gets the time within the simulation. :return: returns the current time """ SpynnakerDataView.check_user_can_act() return __pynn["get_current_time"]()
[docs] def get_min_delay(): """ The minimum allowed synaptic delay; delays will be clamped to be at least this. :return: returns the min delay of the simulation :rtype: int """ SpynnakerDataView.check_user_can_act() return __pynn["get_min_delay"]()
[docs] def get_max_delay(): """ Part of the PyNN API but does not make sense for sPyNNaker as different Projection, Vertex splitter combination could have different delays they can support. Most likely value is timestep * 144 :raises NotImplementedError: As there is no system wide max_delay """ raise NotImplementedError( "sPyNNaker does not have a system wide max_delay")
[docs] def get_time_step(): """ The integration time step. :return: get the time step of the simulation (in ms) :rtype: float """ SpynnakerDataView.check_user_can_act() return float(__pynn["get_time_step"]())
[docs] def initialize(cells, **initial_values): """ Sets cells to be initialised to the given values. :param cells: the cells to change parameters on :type cells: ~spynnaker.pyNN.models.populations.Population or ~spynnaker.pyNN.models.populations.PopulationView :param initial_values: the parameters and their values to change """ SpynnakerDataView.check_user_can_act() pynn_common.initialize(cells, **initial_values)
[docs] def num_processes(): """ The number of MPI processes. .. note:: Always 1 on SpiNNaker, which doesn't use MPI. :return: the number of MPI processes :rtype: int """ SpynnakerDataView.check_user_can_act() return __pynn["num_processes"]()
[docs] def rank(): """ The MPI rank of the current node. .. note:: Always 0 on SpiNNaker, which doesn't use MPI. :return: MPI rank :rtype: int """ SpynnakerDataView.check_user_can_act() return __pynn["rank"]()
[docs] def record(variables, source, filename, sampling_interval=None, annotations=None): """ Sets variables to be recorded. :param variables: may be either a single variable name or a list of variable names. For a given `celltype` class, `celltype.recordable` contains a list of variables that can be recorded for that `celltype`. :type variables: str or list(str) :param source: where to record from :type source: ~spynnaker.pyNN.models.populations.Population or ~spynnaker.pyNN.models.populations.PopulationView :param str filename: file name to write data to :param sampling_interval: how often to sample the recording, not ignored so far :param annotations: the annotations to data writers :type annotations: dict(str, ...) :return: neo object :rtype: ~neo.core.Block """ SpynnakerDataView.check_user_can_act() return __pynn["record"](variables, source, filename, sampling_interval, annotations)
[docs] def reset(annotations=None): """ Resets the simulation to t = 0. :param annotations: the annotations to the data objects :type annotations: dict(str, ...) """ if annotations is None: annotations = {} SpynnakerDataView.check_user_can_act() __pynn["reset"](annotations)
[docs] def run(simtime, callbacks=None): """ The run() function advances the simulation for a given number of milliseconds. :param float simtime: time to run for (in milliseconds) :param callbacks: callbacks to run :return: the actual simulation time that the simulation stopped at :rtype: float """ SpynnakerDataView.check_user_can_act() return __pynn["run"](simtime, callbacks=callbacks)
# left here because needs to be done, and no better place to put it # (ABS don't like it, but will put up with it) run_for = run
[docs] def run_until(tstop): """ Run until a (simulation) time period has completed. :param float tstop: the time to stop at (in milliseconds) :return: the actual simulation time that the simulation stopped at :rtype: float """ SpynnakerDataView.check_user_can_act() return __pynn["run_until"](tstop)
[docs] def get_machine(): """ Get the SpiNNaker machine in use. :return: the machine object :rtype: ~spinn_machine.Machine """ SpynnakerDataView.check_user_can_act() return SpynnakerDataView.get_machine()