# Copyright (c) 2016 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.
import logging
import numpy
from spinn_utilities.abstract_base import AbstractBase, abstractproperty
from spinn_utilities.ordered_set import OrderedSet
from spinn_utilities.log import FormatAdapter
from pacman.exceptions import (
PacmanConfigurationException, PacmanInvalidParameterException)
from pacman.model.graphs import AbstractVertex
logger = FormatAdapter(logging.getLogger(__file__))
[docs]class ApplicationVertex(AbstractVertex, metaclass=AbstractBase):
"""
A vertex that can be broken down into a number of smaller vertices
based on the resources that the vertex requires.
"""
__slots__ = [
# List of machine verts associated with this app vertex
"_machine_vertices",
# The splitter object associated with this app vertex
"_splitter",
# The maximum number of atoms for each dimension for each core.
# For example, a 2D vertex might have a shape of 640 by 480 with
# rectangles on each core or 32 by 16.
# Typically all but possibly the last core will have this number. If
# the vertex has multiple dimensions, one or more of the dimensions
# might have fewer atoms on the last core (e.g. the rectangle on the
# last core of a 2D vertex might be smaller).
"_max_atoms_per_dimension_per_core"]
def __init__(self, label=None, max_atoms_per_core=None, splitter=None):
"""
:param str label: The optional name of the vertex.
:param max_atoms_per_core: The max number of atoms that can be
placed on a core for each dimension, used in partitioning.
If the vertex is n-dimensional, with n > 1, the value must be a
tuple with a value for each dimension. If it is single-dimensional
the value can be a 1-tuple or an int.
:type max_atoms_per_core: None or int or tuple(int,...)
:param splitter: The splitter object needed for this vertex.
Leave as `None` to delegate the choice of splitter to the selector.
:type splitter: None or
~pacman.model.partitioner_interfaces.AbstractSplitterPartitioner
"""
# Need to set to None temporarily as add_constraint checks splitter
self._splitter = None
super().__init__(label)
self._machine_vertices = OrderedSet()
# Use setter as there is extra work to do
self.splitter = splitter
# Keep the name for simplicity but move to new internal representation
self._max_atoms_per_dimension_per_core = max_atoms_per_core
if isinstance(max_atoms_per_core, int):
self._max_atoms_per_dimension_per_core = (max_atoms_per_core, )
def __str__(self):
return self.label
def __repr__(self):
if self.get_fixed_location():
return (f"ApplicationVertex({self.label},"
f" at{self.get_fixed_location()})")
else:
return f"ApplicationVertex({self.label})"
@property
def splitter(self):
"""
:rtype:
~pacman.model.partitioner_interfaces.AbstractSplitterPartitioner
"""
return self._splitter
@splitter.setter
def splitter(self, new_value):
"""
Sets the splitter object. Does not allow repeated settings.
:param new_value: The new splitter object
:type new_value:
~pacman.model.partitioner_interfaces.AbstractSplitterPartitioner
"""
if self._splitter == new_value:
return
if self._splitter is not None:
raise PacmanConfigurationException(
f"The splitter object on {self._label} has already been set, "
"it cannot be reset. Please fix and try again.")
self._splitter = new_value
self._splitter.set_governed_app_vertex(self)
[docs] def remember_machine_vertex(self, machine_vertex):
"""
Adds the machine vertex to the iterable returned by machine_vertices
:param ~pacman.model.graphs.machine.MachineVertex machine_vertex:
A pointer to a machine_vertex
"""
machine_vertex.index = len(self._machine_vertices)
self._machine_vertices.add(machine_vertex)
@property
def atoms_shape(self):
"""
The "shape" of the atoms in the vertex i.e. how the atoms are split
between the dimensions of the vertex. By default everything is
1-dimensional, so the value will be a 1-tuple but can be
overridden by a vertex that supports multiple dimensions.
:rtype: tuple(int, ...)
"""
return (self.n_atoms,)
@abstractproperty
def n_atoms(self):
"""
The number of atoms in the vertex.
:rtype: int
"""
[docs] def round_n_atoms(self, n_atoms, label="n_atoms"):
"""
Utility function to allow suoer-classes to make sure `n_atoms` is an
integer.
:param n_atoms: Value convertible to int to be used for `n_atoms`
:type n_atoms: int or float or numpy.
:return: Number of atoms.
:rtype: int
"""
if isinstance(n_atoms, int):
return n_atoms
# Allow a float which has a near int value
temp = int(round(n_atoms))
if abs(temp - n_atoms) < 0.001:
if temp != n_atoms:
logger.warning(
"Size of the {} rounded from {} to {}. "
"Please use int values for n_atoms",
label, n_atoms, temp)
return temp
raise PacmanInvalidParameterException(
label, n_atoms, f"int value expected for {label}")
@property
def machine_vertices(self):
"""
The machine vertices that this application vertex maps to.
:rtype: iterable(~pacman.model.graphs.machine.MachineVertex)
"""
return self._machine_vertices
[docs] def get_max_atoms_per_core(self):
"""
Gets the maximum number of atoms per core, which is either the
number of atoms required across the whole application vertex,
or a lower value set.
:rtype: int
"""
if self._max_atoms_per_dimension_per_core is None:
return self.n_atoms
return int(numpy.prod(self._max_atoms_per_dimension_per_core))
[docs] def get_max_atoms_per_dimension_per_core(self):
"""
Gets the maximum number of atoms per dimension per core. This
will return a tuple with a number for each dimension of the vertex,
which might be one if this is a single-dimension vertex.
:rtype: tuple(int,...)
"""
if self._max_atoms_per_dimension_per_core is None:
return self.atoms_shape
return self._max_atoms_per_dimension_per_core
[docs] def set_max_atoms_per_dimension_per_core(self, new_value):
"""
Set the maximum number of atoms per dimension per core.
Can be used to raise or lower the maximum number of atoms per core
or per dimension per core.
:param new_value:
Value to set. If the vertex is n-dimensional where n > 1, a tuple
of n values must be given. If the vertex is 1 dimensional,
a 1-tuple or integer can be given. If this is set to `None` the
vertex will have atoms_shape as the maximum.
:type new_value: None or int or tuple(int,...)
"""
self._max_atoms_per_dimension_per_core = new_value
if isinstance(new_value, int):
self._max_atoms_per_dimension_per_core = (new_value, )
if (len(self._max_atoms_per_dimension_per_core) !=
len(self.atoms_shape)):
raise ValueError(
"The number of dimensions of new_value must be the same as the"
" number of dimensions of atoms_shape")
[docs] def reset(self):
"""
Forget all machine vertices in the application vertex, and reset
the splitter (if any).
"""
self._machine_vertices = OrderedSet()
if self._splitter is not None:
self._splitter.reset_called()
[docs] def get_machine_fixed_key_and_mask(self, machine_vertex, partition_id):
"""
Get a fixed key and mask for the given machine vertex and partition
identifier, or `None` if not fixed (the default). If this doesn't
return `None`, get_fixed_key_and_mask must also not return `None`,
and the keys returned here must align with those such that for each
key:mask returned here, key & app_mask == app_key. It is OK for
this to return `None` and get_fixed_key_and_mask to return not `None`
iff there is only one machine vertex.
:param ~pacman.model.graphs.machine.MachineVertex machine_vertex:
A source machine vertex of this application vertex
:param str partition_id:
The identifier of the partition to get the key for
:rtype: ~pacman.model.routing_info.BaseKeyAndMask or None
"""
# pylint: disable=unused-argument
return None
[docs] def get_fixed_key_and_mask(self, partition_id):
"""
Get a fixed key and mask for the application vertex or `None` if not
fixed (the default). See :py:meth:`get_machine_gixed_key_and_mask` for
the conditions.
:param str partition_id:
The identifier of the partition to get the key for
:rtype: ~pacman.model.routing_info.BaseKeyAndMask or None
"""
# pylint: disable=unused-argument
return None
[docs] def add_incoming_edge(self, edge, partition):
"""
Add an edge incoming to this vertex. This is ignored by default,
but could be used to track incoming edges, and/or report faults.
:param ~pacman.model.graphs.application.ApplicationEdge edge:
The edge to add.
:param partition: The partition to add the edge to.
:type partition:
~pacman.model.graphs.application.ApplicationEdgePartition
"""