# Copyright (c) 2020 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 spinn_utilities.abstract_base import abstractmethod
from spinn_utilities.overrides import overrides
from spinn_utilities.helpful_functions import is_singleton
from pacman.model.graphs.application import ApplicationVertex
from spinn_front_end_common.utilities.exceptions import ConfigurationException
from spinn_front_end_common.abstract_models import HasCustomAtomKeyMap
from pacman.utilities.utility_calls import get_field_based_keys
class PopulationApplicationVertex(ApplicationVertex, HasCustomAtomKeyMap):
"""
A vertex that can be used in a Population.
Provides some default functions that can be overridden if the vertex
supports these.
"""
# No data required here; makes it easy to mix in!
__slots__ = []
@staticmethod
def _as_list(names):
"""
Normalise the input to a list.
:param names: The item or items to normalise
:type names: str or list
:rtype: list(str)
"""
if is_singleton(names):
return [names]
return names
@staticmethod
def _check_names(names, allowed, type_of_thing):
"""
Check the list of names are allowed or not.
:param names: The names to check
:type names: str or list
:param set(str) allowed: The set of allowed names
:param str type_of_thing: The "type of thing" to report in any error
:raises KeyError: If one of the names is not allowed
"""
for name in PopulationApplicationVertex._as_list(names):
if name not in allowed:
raise KeyError(f"{name} is not a recognized {type_of_thing}")
@staticmethod
def _check_parameters(names, allowed):
"""
Check that parameters are allowed.
:param names: The names to check
:type names: str or list
:param set(str) allowed: The set of allowed names
"""
PopulationApplicationVertex._check_names(names, allowed, "parameter")
@staticmethod
def _check_variables(names, allowed):
"""
Check that state variables are allowed.
:param names: The names to check
:type names: str or list
:param set(str) allowed: The set of allowed names
"""
PopulationApplicationVertex._check_names(names, allowed, "variable")
[docs]
def get_parameter_values(self, names, selector=None):
"""
Get the values of a parameter or parameters for the whole
Population or a subset if the selector is used.
:param names: The name or names of the parameter to get
:type names: str or list
:param selector: a description of the subrange to accept, or ``None``
for all. See:
:py:meth:`~spinn_utilities.ranged.AbstractSized.selector_to_ids`
:type selector: None or slice or int or list(bool) or list(int)
:rtype: ParameterHolder
:raise KeyError: if the parameter is not something that can be read
"""
raise KeyError(
"This Population does not support the reading of parameters")
[docs]
def set_parameter_values(self, name, value, selector=None):
"""
Set the values of a parameter for the whole Population or a subset
if the selector is used.
:param str name: The name of the parameter to set
:param selector: a description of the subrange to accept, or ``None``
for all. See:
:py:meth:`~spinn_utilities.ranged.AbstractSized.selector_to_ids`
:type selector: None or slice or int or list(bool) or list(int)
:raise KeyError: if the parameter is not something that can be changed
"""
raise KeyError(
"This Population does not support the setting of parameters")
[docs]
def get_parameters(self):
"""
Get the names of all the parameters that can be obtained
:rtype: list(str)
"""
return []
[docs]
def get_initial_state_values(self, names, selector=None):
"""
Get the initial values of a state variable for the whole Population
or a subset if the selector is used.
:param names: The name or names of the variable to get
:type names: str or list(str)
:param selector: a description of the subrange to accept, or ``None``
for all. See:
:py:meth:`~spinn_utilities.ranged.AbstractSized.selector_to_ids`
:type selector: None or slice or int or list(bool) or list(int)
:rtype: ParameterHolder
:raise KeyError: if the variable is not something that can be read
"""
raise KeyError(
"This Population does not support the reading of state"
" variables")
[docs]
def set_initial_state_values(self, name, value, selector=None):
"""
Set the initial values of a state variable for the whole Population
or a subset if the selector is used.
:param str name: The name of the variable to set
:param selector: a description of the subrange to accept, or ``None``
for all. See:
:py:meth:`~spinn_utilities.ranged.AbstractSized.selector_to_ids`
:type selector: None or slice or int or list(bool) or list(int)
:raise KeyError: if the variable is not something that can be changed
"""
raise KeyError(
"This Population does not support the initialization of state"
" variables")
[docs]
def get_current_state_values(self, names, selector=None):
"""
Get the current values of a state variable for the whole Population
or a subset if the selector is used.
:param names: The name or names of the variable to get
:type names: str or list(str)
:param selector: a description of the subrange to accept, or ``None``
for all. See:
:py:meth:`~spinn_utilities.ranged.AbstractSized.selector_to_ids`
:type selector: None or slice or int or list(bool) or list(int)
:rtype: ParameterHolder
:raise KeyError: if the variable is not something that can be read
"""
raise KeyError(
"This Population does not support the reading of state"
" variables")
[docs]
def set_current_state_values(self, name, value, selector=None):
"""
Set the current values of a state variable for the whole Population
or a subset if the selector is used.
:param str name: The name of the variable to set
:param selector: a description of the subrange to accept, or ``None``
for all. See:
:py:meth:`~spinn_utilities.ranged.AbstractSized.selector_to_ids`
:type selector: None or slice or int or list(bool) or list(int)
:raise KeyError: if the variable is not something that can be changed
"""
raise KeyError(
"This Population does not support the setting of state"
" variables")
[docs]
def get_state_variables(self):
"""
Get a list of supported state variables.
:rtype: list(str)
"""
return []
[docs]
def get_units(self, name):
"""
Get the units of the given parameter or state variable.
:param str name: the name of the parameter to get the units of
:rtype: str
:raise KeyError:
if the name isn't recognised or the units cannot be identified
"""
raise KeyError(f"The units for {name} cannot be found")
@property
def conductance_based(self):
"""
Whether the vertex models post-synaptic inputs as
currents or conductance.
By default this is False; override if the model accepts
conductance based input.
:rtype: bool
"""
return False
# recording methods
# If get_recordable_variables implemented other recording methods must
# be too
[docs]
def get_recordable_variables(self):
"""
Get a list of the names and types of things that can be recorded.
This methods list the variable recorded via the Population.
:rtype: list(str)
"""
return []
[docs]
def set_recording(self, name, sampling_interval=None, indices=None):
"""
Set a variable recording.
:param str name: The name of the variable to set the status of
:param sampling_interval:
How often the variable should be recorded or `None` for every
time step, in milliseconds
:type sampling_interval: float or None
:param indices: The list of neuron indices to record or `None` for all
:type indices: list(int) or None
:raises KeyError: if the variable cannot be recorded
"""
# pylint: disable=unused-argument
if self.get_recordable_variables() == []:
raise KeyError("This Population does not support recording")
raise NotImplementedError(
f"{type(self)} has recording variables so should implement "
f"set_recording")
[docs]
def set_not_recording(self, name, indices=None):
"""
Set a variable not recording.
:param str name: The name of the variable to not record
:param indices:
The list of neuron indices to not record or `None` for all
:type indices: list(int) or None
:raises KeyError: if the variable cannot be stopped from recording
"""
# pylint: disable=unused-argument
if self.get_recordable_variables() == []:
raise KeyError("This Population does not support recording")
raise NotImplementedError(
f"{type(self)} has recording variables so should implement "
f"set_not_recording")
[docs]
def get_recording_variables(self):
"""
Get a list of variables that are currently being recorded.
:rtype: list(str)
"""
if self.get_recordable_variables() == []:
return []
raise NotImplementedError(
f"{type(self)} has recording variables so should implement "
f"get_recording_variables")
[docs]
def get_buffer_data_type(self, name):
"""
Get the type of data recorded by the buffer manager.
The buffer data type controls how data returned by the cores is
handled in NeoBufferDatabase.
:param str name: The name of the variable recorded
:rtype:
~spinn_front_end_common.interface.buffer_management.storage_objects.BufferDatabase
:raises KeyError: if the variable isn't being recorded
"""
if name not in self.get_recordable_variables():
raise KeyError(f"{name} is not being recorded")
raise NotImplementedError(
f"{type(self)} has recording variables so should implement "
f"get_recording_variables")
[docs]
def get_sampling_interval_ms(self, name):
"""
Get the sampling interval of the recording for the given variable.
The values is in ms and unless selective recording is used will be
`SpynnakerDataView.get_simulation_time_step_us()`
:rtype: float
:raises KeyError: If the variable isn't being recorded
"""
if name not in self.get_recordable_variables():
raise KeyError(f"{name} is not being recorded")
raise NotImplementedError(
f"{type(self)} has recording variables so should implement "
f"get_recording_variables")
[docs]
def get_data_type(self, name):
"""
Get the type data returned by a recording of the variable.
This is the type of data the C code is returning.
For instance data such as spikes this will be `None`.
:param str name: The name of the variable to get the type of
:rtype: ~data_specification.enums.DataType or None
:raise KeyError: If the variable isn't recordable
"""
if name not in self.get_recordable_variables():
raise KeyError(f"{name} is not being recorded")
raise NotImplementedError(
f"{type(self)} has recording variables so should implement "
f"get_data_type")
[docs]
def get_recording_region(self, name):
"""
Gets the recording region for the named variable.
:param str name: The name of the variable to get the region of
:rtype: int
:raises KeyError: If the variable isn't being recorded
"""
if name not in self.get_recordable_variables():
raise KeyError(f"{name} is not being recorded")
raise NotImplementedError(
f"{type(self)} has recording variables so should implement "
f"get_recording_region")
[docs]
def get_neurons_recording(self, name, vertex_slice):
"""
Gets the neurons being recorded on the core with this slice.
Typically `vertex_slice.get_raster_ids(atoms_shape)`
but may be a sublist if doing selective recording.
:param str name: The name of the variable to get the region of
:param ~pacman.model.graphs.common.Slice vertex_slice:
:return: A list of the global raster IDs of the atoms in recording
named variable within this slice
:rtype: list(int)
"""
# pylint: disable=unused-argument
if name not in self.get_recordable_variables():
raise KeyError(f"{name} is not being recorded")
raise NotImplementedError(
f"{type(self)} has recording variables so should implement "
f"get_neurons_recording")
# end of recording methods
[docs]
def inject(self, current_source, selector=None):
"""
Inject a current source into this population.
:param AbstractCurrentSource current_source:
the current source to be injected
:param selector: a description of the subrange to accept, or ``None``
for all. See:
:py:meth:`~spinn_utilities.ranged.AbstractSized.selector_to_ids`
:type selector: None or slice or int or list(bool) or list(int)
:raises \
~spinn_front_end_common.utilities.exceptions.ConfigurationException:
if the population doesn't support injection
"""
raise ConfigurationException(
"This Population doesn't support injection")
@property
def n_colour_bits(self):
"""
The number of colour bits sent by this vertex.
Assumed 0 unless overridden
:rtype: int
"""
return 0
[docs]
@overrides(HasCustomAtomKeyMap.get_atom_key_map)
def get_atom_key_map(self, pre_vertex, partition_id, routing_info):
base_key = routing_info.get_first_key_from_pre_vertex(
pre_vertex, partition_id)
# This might happen if there are no edges
if base_key is None:
base_key = 0
vertex_slice = pre_vertex.vertex_slice
keys = get_field_based_keys(base_key, vertex_slice, self.n_colour_bits)
return enumerate(keys, vertex_slice.lo_atom)
@property
@abstractmethod
@overrides(ApplicationVertex.n_atoms)
def n_atoms(self) -> int:
raise NotImplementedError