Source code for spynnaker.pyNN.models.populations.population

# 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.
from __future__ import annotations
import inspect
import logging
import os
from typing import (
    Any, Callable, Dict, Iterable, Iterator, List, Optional, Sequence, Tuple,
    Type, Union, final, overload, TYPE_CHECKING)

import numpy
from numpy import floating
from numpy.typing import NDArray
from typing_extensions import TypeAlias

import neo
from neo.io.baseio import BaseIO  # type: ignore[import]
from pyNN.descriptions import TemplateEngine
from pyNN import descriptions
from pyNN.random import NumpyRNG
from pyNN.space import BaseStructure

from spinn_utilities.log import FormatAdapter
from spinn_utilities.logger_utils import warn_once
from spinn_utilities.overrides import overrides

from spinn_front_end_common.utilities.exceptions import ConfigurationException

from spynnaker.pyNN.data import SpynnakerDataView
from spynnaker.pyNN.exceptions import SpynnakerException
from spynnaker.pyNN.models.abstract_models import SupportsStructure
from spynnaker.pyNN.models.abstract_pynn_model import AbstractPyNNModel
from spynnaker.pyNN.models.common import PopulationApplicationVertex
from spynnaker.pyNN.models.recorder import Recorder
from spynnaker.pyNN.utilities.neo_buffer_database import NeoBufferDatabase
from spynnaker.pyNN.utilities.utility_calls import get_neo_io

from .population_base import PopulationBase
from .population_view import PopulationView, IDMixin

if TYPE_CHECKING:
    from pyNN.neuron.standardmodels.electrodes import NeuronCurrentSource
    from spynnaker.pyNN.models.common.types import Names, Values
    from spynnaker.pyNN.models.common.parameter_holder import ParameterHolder
    __Values: TypeAlias = Values  # Stupid PyDev!

logger = FormatAdapter(logging.getLogger(__file__))
_CellType: TypeAlias = Union[AbstractPyNNModel, PopulationApplicationVertex]
_CellTypeArg: TypeAlias = Union[Type[AbstractPyNNModel], _CellType]
_ParamDict: TypeAlias = Dict[str, Any]


# Not in the class so pylint doesn't get confused about abstractness of methods
def _we_dont_do_this_now(*args):  # pylint: disable=unused-argument
    # pragma: no cover
    raise NotImplementedError("sPyNNaker does not currently do this")


class Population(PopulationBase):
    """
    PyNN population object.
    """
    # pylint: disable=redefined-builtin
    __slots__ = (
        "__annotations",
        "__celltype",
        "__first_id",
        "__last_id",
        "__positions",
        "__recorder",
        "__size",
        "__structure",
        "__vertex")

    def __init__(
            self, size: Union[int, float, None], cellclass: _CellTypeArg,
            cellparams: Optional[_ParamDict] = None,
            structure: Optional[BaseStructure] = None,
            initial_values: Optional[Dict[str, float]] = None,
            label: Optional[str] = None,
            additional_parameters: Optional[_ParamDict] = None,
            **additional_kwargs):
        """
        :param int size: The number of neurons in the population
        :param cellclass: The implementation of the individual neurons.
        :type cellclass: type or AbstractPyNNModel
        :param cellparams: Parameters to pass to ``cellclass`` if it
            is a class to instantiate. Must be ``None`` if ``cellclass`` is an
            instantiated object.
        :type cellparams: dict(str,object) or None
        :param ~pyNN.space.BaseStructure structure:
        :param dict(str,float) initial_values:
            Initial values of state variables
        :param str label: A label for the population
        :param additional_parameters:
            Additional parameters to pass to the vertex creation function.
        :type additional_parameters: dict(str, ...)
        :param additional_kwargs:
            A nicer way of allowing additional things
        :type additional_kwargs: dict(str, ...)
        """
        # pylint: disable=too-many-arguments

        # Deal with the kwargs!
        additional: _ParamDict = dict()
        if additional_parameters is not None:
            additional.update(additional_parameters)
        if additional_kwargs:
            additional.update(additional_kwargs)

        # build our initial objects
        self.__celltype: AbstractPyNNModel
        self.__vertex: PopulationApplicationVertex
        model = self.__create_model(cellclass, cellparams)
        realsize = self.__roundsize(size, label)
        self.__create_vertex(model, realsize, label, additional)
        self.__recorder = Recorder(population=self, vertex=self.__vertex)

        # structure should be a valid Space.py structure type.
        # generation of positions is deferred until needed.
        self.__structure = structure
        self.__positions: Optional[numpy.ndarray] = None
        if isinstance(self.__vertex, SupportsStructure):
            self.__vertex.set_structure(structure)

        # add objects to the SpiNNaker control class
        SpynnakerDataView.add_vertex(self.__vertex)

        # initialise common stuff
        if realsize is None:
            realsize = self.__vertex.n_atoms
        self.__size = realsize
        self.__annotations: Dict[str, Any] = dict()

        # things for pynn demands
        self.__first_id, self.__last_id = SpynnakerDataView.add_population(
            self)

        # set up initial values if given
        if initial_values:
            for variable, value in initial_values.items():
                self.__vertex.set_initial_state_values(variable, value)

    def __iter__(self) -> Iterator[PopulationView]:
        """
        Iterate over local cells.
        """
        for _id in range(self.__size):
            yield IDMixin(self, _id)

    def __getitem__(self, index_or_slice) -> PopulationView:
        if isinstance(index_or_slice, int):
            return IDMixin(self, index_or_slice)
        else:
            return PopulationView(self, index_or_slice)

[docs] def all(self) -> Iterable[PopulationView]: """ Iterator over cell IDs on all MPI nodes. :rtype: iterable(IDMixin) """ for _id in range(self.__size): yield IDMixin(self, _id)
@property def annotations(self) -> Dict[str, Any]: """ The annotations given by the end user. :rtype: dict(str, ...) """ return self.__annotations @property def celltype(self) -> AbstractPyNNModel: """ Implements the PyNN expected `celltype` property. :return: The cell type this property has been set to, or the vertex if it was directly instantiated. :rtype: AbstractPyNNModel """ return self.__celltype
[docs] def can_record(self, variable: str) -> bool: """ Determine whether `variable` can be recorded from this population. :param str variable: The variable to answer the question about :rtype: bool """ return variable in self.__vertex.get_recordable_variables()
[docs] @overrides(PopulationBase.record, extend_doc=False) def record(self, variables: Names, to_file: Optional[str] = None, sampling_interval: Optional[int] = None): """ Record the specified variable or variables for all cells in the Population or view. :param variables: 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 to_file: a file to automatically record to (optional). :py:meth:`write_data` will be automatically called when `sim.end()` is called. :type to_file: ~neo.io or ~neo.rawio or str :param int sampling_interval: a value in milliseconds, and an integer multiple of the simulation timestep. """ self.__recorder.record( variables, to_file, sampling_interval, indexes=None)
[docs] def sample(self, n: int, rng: Optional[NumpyRNG] = None) -> PopulationView: """ Randomly sample `n` cells from the Population, and return a PopulationView object. :param int n: The number of cells to put in the view. :param rng: The random number generator to use :type rng: ~pyNN.random.NumpyRNG :rtype: ~spynnaker.pyNN.models.populations.PopulationView """ if not rng: rng = NumpyRNG() indices = rng.permutation( numpy.arange(len(self), dtype=numpy.int32))[0:n] return PopulationView( self, indices, label=f"Random sample size {n} from {self.label}")
[docs] @overrides(PopulationBase.write_data, extend_doc=False) def write_data(self, io: Union[str, BaseIO], variables: Names = 'all', gather: bool = True, clear: bool = False, annotations: Optional[Dict[str, Any]] = None): """ Write recorded data to file, using one of the file formats supported by Neo. :param io: a Neo IO instance, or a string for where to put a neo instance :type io: neo.io.baseio.BaseIO or str :param variables: either a single variable name or a list of variable names. Variables must have been previously recorded, otherwise an Exception will be raised. :type variables: str or list(str) :param bool gather: Whether to bring all relevant data together. .. note:: SpiNNaker always gathers. :param bool clear: clears the storage data if set to true after reading it back :param annotations: annotations to put on the neo block :type annotations: dict(str, ...) :raises \ ~spinn_front_end_common.utilities.exceptions.ConfigurationException: If the variable or variables have not been previously set to record. """ self._check_params(gather, annotations) if isinstance(io, str): extension = os.path.splitext(io)[1][1:] if extension == "csv": self.__recorder.csv_neo_block( io, variables, annotations=annotations) return io = get_neo_io(io) data = self.__recorder.extract_neo_block( variables, None, clear, annotations) # write the neo block to the file io.write(data)
[docs] def describe(self, template: str = 'population_default.txt', engine: Optional[Union[str, TemplateEngine]] = 'default' ) -> Union[str, Dict[str, Any]]: """ Returns a human-readable description of the population. The output may be customised by specifying a different template together with an associated template engine (see :mod:`pyNN.descriptions`). If ``template`` is ``None``, then a dictionary containing the template context will be returned. :param str template: Template filename :param engine: Template substitution engine :type engine: str or ~pyNN.descriptions.TemplateEngine or None :rtype: str or dict """ context = { "label": self.label, "celltype": self.celltype.describe(template=None), "structure": None, "size": self.size, "size_local": self.size, "first_id": self.first_id, "last_id": self.last_id, } context.update(self.annotations) if self.size > 0: parameters = self.__vertex.get_parameters() cell_parameters: Union[str, ParameterHolder] if parameters: cell_parameters = self.__vertex.get_parameter_values( parameters, 0) else: cell_parameters = "No cell parameters" context.update({ "local_first_id": 0, "cell_parameters": cell_parameters }) if self.structure: context["structure"] = self.structure.describe(template=None) return descriptions.render(engine, template, context)
def _end(self) -> None: """ Do final steps at the end of the simulation. """ for variable in self.__recorder.write_to_files_indicators: if self.__recorder.write_to_files_indicators[variable]: self.write_data( io=self.__recorder.write_to_files_indicators[variable], variables=[variable])
[docs] @overrides(PopulationBase.get_data, extend_doc=False) def get_data( self, variables: Names = 'all', gather: bool = True, clear: bool = False, *, annotations: Optional[Dict[str, Any]] = None) -> neo.Block: """ Return a Neo Block containing the data (spikes, state variables) recorded from the Assembly. :param variables: either a single variable name or a list of variable names. Variables must have been previously recorded, otherwise an Exception will be raised. :type variables: str or list(str) :param bool gather: Whether to collect data from all MPI nodes or just the current node. .. note:: This is irrelevant on sPyNNaker, which always behaves as if this parameter is True. :param bool clear: Whether recorded data will be deleted from the ``Assembly``. :param annotations: annotations to put on the neo block :type annotations: dict(str, ...) :rtype: ~neo.core.Block :raises \ ~spinn_front_end_common.utilities.exceptions.ConfigurationException: If the variable or variables have not been previously set to record. """ self._check_params(gather, annotations) return self.__recorder.extract_neo_block( variables, None, clear, annotations)
[docs] def spinnaker_get_data( self, variable: str, as_matrix: bool = False, view_indexes: Optional[Sequence[int]] = None) -> NDArray[floating]: """ SsPyNNaker specific method for getting data as a numpy array, instead of the Neo-based object :param str variable: a single variable name. :type variable: str or list(str) :param bool as_matrix: If set True the data is returned as a 2d matrix :param view_indexes: The indexes for which data should be returned. If ``None``, all data (view_index = data_indexes) :return: array of the data :rtype: ~numpy.ndarray """ warn_once( logger, "spinnaker_get_data is non-standard PyNN and therefore " "will not be portable to other simulators.") with NeoBufferDatabase() as db: return db.spinnaker_get_data(self.__recorder.recording_label, variable, as_matrix, view_indexes)
[docs] @overrides(PopulationBase.get_spike_counts, extend_doc=False) def get_spike_counts(self, gather: bool = True) -> Dict[int, int]: """ Return the number of spikes for each neuron. :rtype: ~numpy.ndarray """ self._check_params(gather) with NeoBufferDatabase() as db: return db.get_spike_counts(self.__recorder.recording_label)
[docs] def find_units(self, variable: str) -> str: """ Get the units of a variable. :param str variable: The name of the variable :return: The units of the variable :rtype: str """ return self.__vertex.get_units(variable)
[docs] def set(self, **parameters: Values): """ Set one or more parameters for every cell in the population. For example:: p.set(tau_m=20.0). :param parameters: The parameters to set and the values to set them to. The type of each parameter depends on the parameter; it's often a float, but not always. :raises SimulatorRunningException: If `sim.run` is currently running :raises SimulatorNotSetupException: If called before `sim.setup` :raises SimulatorShutdownException: If called after `sim.end` """ SpynnakerDataView.check_user_can_act() for parameter, value in parameters.items(): self.__vertex.set_parameter_values(parameter, value)
[docs] def initialize(self, **kwargs: Values): """ Set initial values of state variables, e.g. the membrane potential. Values passed to ``initialize()`` may be: * single numeric values (all neurons set to the same value), or * :py:class:`~spynnaker.pyNN.RandomDistribution` objects, or * lists / arrays of numbers of the same size as the population mapping functions, where a mapping function accepts a single argument (the cell index) and returns a single number. Values should be expressed in the standard PyNN units (i.e. millivolts, nanoamps, milliseconds, microsiemens, nanofarads, event per second). Examples:: p.initialize(v=-70.0) p.initialize(v=rand_distr, gsyn_exc=0.0) p.initialize(v=lambda i: -65 + i / 10.0) """ SpynnakerDataView.check_user_can_act() if SpynnakerDataView.is_ran_last(): warn_once( logger, "Calling initialize without reset will have no effect." " If you want to set the current value of a state variable" " consider calling the non-PyNN function set_state instead.") for variable, value in kwargs.items(): self.__vertex.set_initial_state_values(variable, value)
@property def initial_values(self) -> ParameterHolder: """ The initial values of the state variables. .. note:: These values will be the same as the values set with the last call to initialize rather than the actual initial values if this call has been made. :rtype: ParameterHolder """ SpynnakerDataView.check_user_can_act() return self.__vertex.get_initial_state_values( self.__vertex.get_state_variables())
[docs] def set_state(self, **kwargs: Values): """ Set current values of state variables, e.g. the membrane potential. Values passed to ``set_state()`` may be: * single numeric values (all neurons set to the same value), or * :py:class:`~spynnaker.pyNN.RandomDistribution` objects, or * lists / arrays of numbers of the same size as the population mapping functions, where a mapping function accepts a single argument (the cell index) and returns a single number. Values should be expressed in the standard PyNN units (i.e. millivolts, nanoamps, milliseconds, microsiemens, nanofarads, event per second). Examples:: p.set_state(v=-70.0) p.set_state(v=rand_distr, gsyn_exc=0.0) p.set_state(v=lambda i: -65 + i / 10.0) """ SpynnakerDataView.check_user_can_act() warn_once( logger, "set_state is non-standard PyNN and therefore " "will not be portable to other simulators.") for variable, value in kwargs.items(): self.__vertex.set_current_state_values(variable, value)
@property def current_values(self) -> ParameterHolder: """ Get the current values of the state variables. :rtype: ParameterHolder """ SpynnakerDataView.check_user_can_act() warn_once( logger, "current_values is non-standard PyNN and therefore " "will not be portable to other simulators.") return self.__vertex.get_current_state_values( self.__vertex.get_state_variables()) @property def positions(self) -> NDArray[numpy.floating]: """ The position array for structured populations. :return: a 3xN array :rtype: ~numpy.ndarray """ if self.__positions is None: if self.__structure is None: raise ValueError("attempted to retrieve positions " "for an unstructured population") self.__positions = self.__structure.generate_positions( self.__vertex.n_atoms) return self.__positions.T @positions.setter def positions(self, positions: NDArray[numpy.floating]): """ Sets all the positions in the population. """ self.__positions = positions @property def all_cells(self) -> List[IDMixin]: """ :rtype: list(IDMixin) """ return [IDMixin(self, _id) for _id in range(self.__size)] @property def position_generator(self) -> Callable[[int], NDArray[numpy.floating]]: """ :rtype: callable((int), ~numpy.ndarray) """ return lambda i: self.positions[:, i] @property def first_id(self) -> int: """ The ID of the first member of the population. :rtype: int """ return self.__first_id @property def last_id(self) -> int: """ The ID of the last member of the population. :rtype: int """ return self.__last_id @property @overrides(PopulationBase._vertex) def _vertex(self) -> PopulationApplicationVertex: return self.__vertex @property def _recorder(self) -> Recorder: """ :rtype: Recorder """ return self.__recorder @property @overrides(PopulationBase._view_range) def _view_range(self) -> Tuple[int, int]: return 0, self.size - 1 @property def conductance_based(self) -> bool: """ Whether the population uses conductance inputs :rtype: bool """ return self.__vertex.conductance_based
[docs] def get(self, parameter_names: Names, gather: bool = True, simplify=True) -> ParameterHolder: """ Get the values of a parameter for every local cell in the population. :param parameter_names: Name of parameter. This is either a single string or a list of strings :type parameter_names: str or iterable(str) :param bool gather: pointless on sPyNNaker :param bool simplify: ignored :return: A single list of values (or possibly a single value) if paramter_names is a string, or a dict of these if parameter names is a list. :rtype: ParameterHolder """ self._check_params(gather) if simplify is not True: warn_once( logger, "The simplify value is ignored if not set to true") return self.__vertex.get_parameter_values(parameter_names)
@overload def id_to_index(self, id: int) -> int: # @ReservedAssignment ... @overload def id_to_index( self, id: Iterable[int]) -> Sequence[int]: # @ReservedAssignment ...
[docs] def id_to_index(self, id: Union[int, Iterable[int]] ) -> Union[int, Sequence[int]]: # @ReservedAssignment """ Given the ID(s) of cell(s) in the Population, return its (their) index (order in the Population). Defined by https://neuralensemble.org/docs/PyNN/reference/populations.html :param id: :type id: int or iterable(int) :rtype: int or iterable(int) """ # pylint: disable=redefined-builtin if not numpy.iterable(id): if not self.__first_id <= id <= self.__last_id: raise ValueError( f"id should be in the range [{self.__first_id}," f"{self.__last_id}], actually {id}") return int(id - self.__first_id) # assume IDs are consecutive return numpy.array(id) - self.__first_id
@overload def index_to_id(self, index: int) -> int: ... @overload def index_to_id(self, index: Iterable[int]) -> Sequence[int]: ...
[docs] def index_to_id(self, index: Union[int, Iterable[int]] ) -> Union[int, Sequence[int]]: """ Given the index (order in the Population) of cell(s) in the Population, return their ID(s) :param index: :type index: int or iterable(int) :rtype: int or iterable(int) """ if not numpy.iterable(index): if index > self.__last_id - self.__first_id: raise ValueError( "indexes should be in the range [0," f"{self.__last_id - self.__first_id}], actually {index}") return int(index + self.__first_id) # this assumes IDs are consecutive return numpy.array(index) + self.__first_id
[docs] def id_to_local_index(self, cell_id): """ Given the ID(s) of cell(s) in the Population, return its (their) index (order in the Population), counting only cells on the local MPI node. Defined by https://neuralensemble.org/docs/PyNN/reference/populations.html :param cell_id: :type cell_id: int or iterable(int) :rtype: int or iterable(int) """ # TODO: Need __getitem__ _we_dont_do_this_now(cell_id)
[docs] def inject(self, current_source: NeuronCurrentSource): """ Connect a current source to all cells in the Population. Defined by https://neuralensemble.org/docs/PyNN/reference/populations.html """ # Pass this into the vertex self.__vertex.inject(current_source, [n for n in range(self.__size)])
def __len__(self) -> int: """ Get the total number of cells in the population. """ return self.__size @property def label(self) -> str: """ The label of the population. :rtype: str """ return self.__vertex.label or "" # Should never be empty @label.setter def label(self, label: str): raise NotImplementedError( "As label is used as an ID it can not be changed") @property def structure(self) -> Optional[BaseStructure]: """ The structure for the population. :rtype: ~pyNN.space.BaseStructure or None """ return self.__structure # NON-PYNN API CALL
[docs] def add_placement_constraint( self, x: int, y: int, p: Optional[int] = None): """ Add a placement constraint. :param int x: The x-coordinate of the placement constraint :param int y: The y-coordinate of the placement constraint :param int p: The processor ID of the placement constraint (optional) :raises SimulatorRunningException: If `sim.run` is currently running :raises SimulatorNotSetupException: If called before `sim.setup` :raises SimulatorShutdownException: If called after `sim.end` """ self.__vertex.set_fixed_location(x, y, p)
# NON-PYNN API CALL
[docs] def set_max_atoms_per_core(self, max_atoms_per_core: int): """ Supports the setting of this population's max atoms per dimension per core. :param int max_atoms_per_core: the new value for the max atoms per dimension per core. :raises SimulatorRunningException: If `sim.run` is currently running :raises SimulatorNotSetupException: If called before `sim.setup` :raises SimulatorShutdownException: If called after `sim.end` """ SpynnakerDataView.check_user_can_act() ct = self.celltype if isinstance(ct, AbstractPyNNModel): cap = ct.absolute_max_atoms_per_core if numpy.prod(max_atoms_per_core) > cap: raise SpynnakerException( f"Set the max_atoms_per_core to {max_atoms_per_core} " f"blocked as the current limit for the model is {cap}") self.__vertex.set_max_atoms_per_dimension_per_core(max_atoms_per_core) # state that something has changed in the population SpynnakerDataView.set_requires_mapping()
@property def size(self) -> int: """ The number of neurons in the population. :rtype: int """ return self.__vertex.n_atoms @staticmethod def __create_model( cell_class: _CellTypeArg, cell_params: Optional[_ParamDict]) -> _CellType: """ :param cell_class: The implementation of the individual neurons. :type cell_class: type or AbstractPyNNModel or ApplicationVertex :param cell_params: Parameters to pass to ``cell_class`` if it is a class to instantiate. Must be ``None`` if ``cell_class`` is an instantiated object. :type cell_params: dict(str,object) or None :rtype: ApplicationVertex or AbstractPyNNModel """ if inspect.isclass(cell_class): if cell_params is None: model = cell_class() else: model = cell_class(**cell_params) assert isinstance(model, ( AbstractPyNNModel, PopulationApplicationVertex)) elif cell_params: raise ConfigurationException( "cell_class is an instance which includes params so " "cell_params must be None") else: model = cell_class return model def __create_vertex_from_model( self, model: AbstractPyNNModel, size: Optional[int], label: Optional[str], additional_parameters: _ParamDict): """ Worker for :meth:`__create_vertex` to handle the case where we really have a model. """ self.__celltype = model # pylint: disable=protected-access if size is not None and size <= 0: raise ConfigurationException( "A population cannot have a negative or zero size.") parameters = model._get_default_population_parameters() if additional_parameters: # check that the additions are suitable. report wrong ones # and ignore parameters = self.__process_additional_params( additional_parameters, parameters) self.__vertex = model.create_vertex( size or 1, label or f"{model.name} vertex", **parameters) def __init_with_supplied_vertex( self, model: PopulationApplicationVertex, size: Optional[int], label: Optional[str], additional_parameters: _ParamDict): """ Worker for :meth:`__create_vertex` to handle the case where we have a user-supplied vertex. """ if additional_parameters: raise ConfigurationException( "Cannot accept additional parameters " f"{additional_parameters} when the cell is a vertex") # Use a synthetic model self.__celltype = _VertexHolder(model) self.__vertex = model if size is not None and size != self.__vertex.n_atoms: raise ConfigurationException( "Vertex size does not match Population size") if label is not None: self.__vertex.set_label(label) def __create_vertex( self, model: _CellType, size: Optional[int], label: Optional[str], additional_parameters: _ParamDict): """ :param model: The implementation of the individual neurons. :type model: ApplicationVertex or AbstractPyNNModel :param int size: :param label: :type label: str or None :param additional_parameters: Additional parameters to pass to the vertex creation function. :type additional_parameters: dict(str, ...) """ # Use a provided model to create a vertex if isinstance(model, AbstractPyNNModel): self.__create_vertex_from_model( model, size, label, additional_parameters) # Use a provided application vertex directly elif isinstance(model, PopulationApplicationVertex): self.__init_with_supplied_vertex( model, size, label, additional_parameters) # Fail on anything else else: raise ConfigurationException( "Model must be either an AbstractPyNNModel or a" " PopulationApplicationVertex")
[docs] @staticmethod def create( cellclass: _CellTypeArg, cellparams: Optional[_ParamDict] = None, n: int = 1) -> 'Population': """ Pass through method to the constructor defined by PyNN. Create ``n`` cells all of the same type. :param cellclass: see :py:meth:`~.Population.__init__` :type cellclass: type or AbstractPyNNModel :param cellparams: see :py:meth:`~.Population.__init__` :type cellparams: dict(str, object) or None :param int n: see :py:meth:`~.Population.__init__` (``size`` parameter) :return: A New Population :rtype: ~spynnaker.pyNN.models.populations.Population """ return Population(size=n, cellclass=cellclass, cellparams=cellparams)
@staticmethod def __process_additional_params( additional_parameters: _ParamDict, population_parameters: _ParamDict) -> _ParamDict: """ Essential method for allowing things like splitter objects at population level. :param additional_parameters: the additional parameters handed down from user :param population_parameters: the additional parameters the vertex can support. :return: the list of parameters that are accepted. """ for key in additional_parameters.keys(): if key in population_parameters: population_parameters[key] = additional_parameters[key] else: logger.warning("additional_parameter {} will be ignored", key) return population_parameters @staticmethod def __roundsize( size: Union[int, float, None], label: Optional[str]) -> Optional[int]: # External device population can have a size of None so accept for now if size is None or isinstance(size, int): return size # Allow a float which has a near int value temp = int(round(size)) if abs(temp - size) < 0.001: logger.warning("Size of the population {} rounded " "from {} to {}. Please use int values for size", label, size, temp) return temp raise ConfigurationException( f"Size of a population with label {label} must be an int," f" received {size}") @final class _VertexHolder(AbstractPyNNModel): """ A simplistic model that just holds its supplied vertex. It has nothing to configure. """ __slots__ = ("__vertex", ) default_population_parameters = {} def __init__(self, vertex: PopulationApplicationVertex): self.__vertex = vertex @property def vertex(self): """ The vertex passed into the init. :return: """ return self.__vertex @overrides(AbstractPyNNModel.create_vertex) def create_vertex( self, n_neurons: int, label: str) -> PopulationApplicationVertex: # The parameters are ignored; the vertex already exists return self.__vertex