Source code for spynnaker.pyNN.models.abstract_pynn_model
# 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
from collections import defaultdict
import sys
from typing import (
Any, cast, Dict, Optional, Sequence, Tuple, TYPE_CHECKING, Union)
import numpy
from pyNN import descriptions
from spinn_utilities.classproperty import classproperty
from spinn_utilities.abstract_base import (
AbstractBase, abstractmethod)
from spynnaker.pyNN.models.defaults import AbstractProvidesDefaults
from spynnaker.pyNN.exceptions import SpynnakerException
if TYPE_CHECKING:
from spynnaker.pyNN.models.common.population_application_vertex import (
PopulationApplicationVertex)
[docs]
class AbstractPyNNModel(AbstractProvidesDefaults, metaclass=AbstractBase):
"""
A Model that can be passed in to a Population object in PyNN.
"""
__slots__ = ()
_max_atoms_per_core: Dict[type, Optional[Tuple[int, ...]]] = defaultdict(
lambda: None)
[docs]
@classmethod
def set_model_max_atoms_per_dimension_per_core(
cls, n_atoms: Union[None, int, Tuple[int, ...]] = None) -> None:
"""
Set the default maximum number of atoms per dimension per core for
this model. 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 n_atoms: The new maximum, or `None` for the largest possible
"""
abs_max = cls.absolute_max_atoms_per_core
if n_atoms is None:
AbstractPyNNModel._max_atoms_per_core[cls] = None
elif numpy.isscalar(n_atoms):
if n_atoms > abs_max:
raise SpynnakerException(
"The absolute maximum neurons per core for this"
f" model is {abs_max}")
max_atoms_int: int = int(cast(int, n_atoms))
AbstractPyNNModel._max_atoms_per_core[cls] = (max_atoms_int, )
else:
if numpy.prod(n_atoms) > abs_max:
raise SpynnakerException(
"The absolute maximum sum of neurons per core for this"
f" model is {abs_max}")
max_atoms_tuple: Tuple[int, ...] = cast(
Tuple[int, ...], n_atoms)
AbstractPyNNModel._max_atoms_per_core[cls] = max_atoms_tuple
[docs]
@classmethod
def get_model_max_atoms_per_dimension_per_core(cls) -> Tuple[int, ...]:
"""
Get the maximum number of atoms per dimension per core for this model.
"""
# If there is a stored value, use it
max_stored = AbstractPyNNModel._max_atoms_per_core.get(cls)
if max_stored is not None:
return max_stored
# Otherwise return the absolute maximum assuming 1D
return (cls.absolute_max_atoms_per_core, )
@classproperty
def absolute_max_atoms_per_core( # pylint: disable=no-self-argument
cls) -> int:
"""
The absolute maximum number of atoms per core.
This is an integer regardless of the number of dimensions
in any vertex.
:rtype: int
"""
return sys.maxsize
[docs]
@classmethod
def get_parameter_names(cls) -> Sequence[str]:
"""
Get the names of the parameters of the model.
:rtype: list(str)
"""
return cls.default_parameters.keys() # pylint: disable=no-member
[docs]
@classmethod
def has_parameter(cls, name: str) -> bool:
"""
Determine if the model has a parameter with the given name.
:param str name: The name of the parameter to check for
:rtype: bool
"""
return name in cls.default_parameters
#: The default values for the parameters at the population level.
#: These are parameters that can be passed in to the Population
#: constructor in addition to the standard PyNN options.
default_population_parameters: Dict[str, Any] = {}
@classmethod
def _get_default_population_parameters(cls) -> Dict[str, Any]:
"""
Get the default population parameters.
Slightly contorted to allow for overriding class variables.
"""
return dict(cls.default_population_parameters)
[docs]
@abstractmethod
def create_vertex(
self, n_neurons: int, label: str) -> PopulationApplicationVertex:
"""
Create a vertex for a population of the model.
:param int n_neurons: The number of neurons in the population
:param str label: The label to give to the vertex
:return: An application vertex for the population
:rtype: PopulationApplicationVertex
"""
raise NotImplementedError
@property
def name(self) -> str:
"""
The name of this model.
:rtype: str
"""
return self.__class__.__name__
[docs]
def describe(self, template: Optional[str] = 'modeltype_default.txt',
engine: str = 'default') -> str:
"""
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 = {
"name": self.name
}
return descriptions.render(engine, template, context)