Source code for pacman.model.graphs.application.abstract.abstract_2d_device_vertex

# Copyright (c) 2021 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 math
from typing import Tuple
from spinn_utilities.abstract_base import AbstractBase, abstractmethod
from spinn_utilities.overrides import overrides
from spinn_utilities.typing.coords import XY
from pacman.exceptions import PacmanConfigurationException
from pacman.utilities.utility_calls import get_n_bits, is_power_of_2
from pacman.utilities.constants import BITS_IN_KEY
from pacman.model.routing_info.base_key_and_mask import BaseKeyAndMask
from pacman.model.graphs.application import ApplicationVertex
from pacman.model.graphs.common import MDSlice


[docs] class Abstract2DDeviceVertex(object, metaclass=AbstractBase): """ A helper for 2D input devices. .. note:: This assumes that the input keys will contain a field for each of the X and Y dimensions with X field in the LSBs and the Y field in the next adjacent bits. If the fields are in different places, override the methods: `_source_x_shift`, `_source_y_shift`, `_source_x_mask` and `_source_y_mask`. If the key has bits in addition to the X and Y values, you can also override `_key_shift`. """ @property @abstractmethod def width(self) -> int: """ The width of the device. """ raise NotImplementedError @property @abstractmethod def height(self) -> int: """ The height of the device. """ raise NotImplementedError @property @abstractmethod def sub_width(self) -> int: """ The width of the sub-rectangles to divide the input into. """ raise NotImplementedError @property @abstractmethod def sub_height(self) -> int: """ The height of the sub-rectangles to divide the input into. """ raise NotImplementedError @property @abstractmethod @overrides(ApplicationVertex.atoms_shape) def atoms_shape(self) -> Tuple[int, ...]: """ 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. """ raise NotImplementedError def _verify_sub_size(self) -> None: """ Ensure the sub width and height are within restrictions. """ if not is_power_of_2(self.sub_width): raise PacmanConfigurationException( f"sub_width ({self.sub_width}) must be a power of 2") if not is_power_of_2(self.sub_height): raise PacmanConfigurationException( f"sub_height ({self.sub_height}) must be a power of 2") if self.sub_width > self.width: raise PacmanConfigurationException( f"sub_width ({self.sub_width}) must not be greater than " f"width ({self.width})") if self.sub_height > self.height: raise PacmanConfigurationException( f"sub_height ({self.sub_height}) must not be greater than " f"height ({self.height})") @property def _n_sub_rectangles(self) -> int: """ The number of sub-rectangles the device is made up of. """ return (int(math.ceil(self.width / self.sub_width)) * int(math.ceil(self.height / self.sub_height))) def _sub_square_from_index(self, index: int) -> XY: """ Work out the x and y components of the index. :param index: The index of the sub square """ n_squares_per_row = int(math.ceil( self.width / self.sub_width)) x_index = index % n_squares_per_row y_index = index // n_squares_per_row # Return the information return x_index, y_index def _get_slice(self, index: int) -> MDSlice: """ Get the slice for the given machine vertex index. :param index: The machine vertex index """ x_index, y_index = self._sub_square_from_index(index) lo_atom_x = x_index * self.sub_width lo_atom_y = y_index * self.sub_height n_atoms_per_subsquare = self.sub_width * self.sub_height lo_atom = index * n_atoms_per_subsquare hi_atom = (lo_atom + n_atoms_per_subsquare) - 1 return MDSlice( lo_atom, hi_atom, (self.sub_width, self.sub_height), (lo_atom_x, lo_atom_y), self.atoms_shape) def _get_key_and_mask(self, base_key: int, index: int) -> BaseKeyAndMask: """ Get the key and mask of the given machine vertex index. :param base_key: The key to use (not shifted) :param index: The machine vertex index """ x_index, y_index = self._sub_square_from_index(index) key_bits = base_key << self._key_shift key = (key_bits + (y_index << self._y_index_shift) + (x_index << self._x_index_shift)) return BaseKeyAndMask(key, self._mask) @property def _mask(self) -> int: """ The mask to be used for the key. """ n_key_bits = BITS_IN_KEY - self._key_shift key_mask = (1 << n_key_bits) - 1 sub_x_mask = (1 << self._sub_x_bits) - 1 sub_y_mask = (1 << self._sub_y_bits) - 1 return ((key_mask << self._key_shift) + (sub_y_mask << self._y_index_shift) + (sub_x_mask << self._x_index_shift)) @property def _x_bits(self) -> int: """ The number of bits to use for X. """ return get_n_bits(self.width) @property def _y_bits(self) -> int: """ The number of bits to use for Y. """ return get_n_bits(self.height) @property def _sub_x_bits(self) -> int: """ The number of bits to use for the X coordinate of a sub-rectangle. """ n_per_row = int(math.ceil(self.width / self.sub_width)) return get_n_bits(n_per_row) @property def _sub_y_bits(self) -> int: """ The number of bits to use for the Y coordinate of a sub-rectangle. """ n_per_col = int(math.ceil(self.height / self.sub_height)) return get_n_bits(n_per_col) @property def _x_index_shift(self) -> int: """ The shift to apply to the key to get the sub-X coordinate. """ return self._source_x_shift + (self._x_bits - self._sub_x_bits) @property def _y_index_shift(self) -> int: """ The shift to apply to the key to get the sub-Y coordinate. """ return self._source_y_shift + (self._y_bits - self._sub_y_bits) @property def _source_x_mask(self) -> int: """ The mask to apply to the key *before* shifting to get the X coordinate. """ return (1 << self._x_bits) - 1 @property def _source_x_shift(self) -> int: """ The shift to apply to the key *after* masking to get the X coordinate. """ return 0 @property def _source_y_mask(self) -> int: """ The mask to apply to the key *before* shifting to get the Y coordinate. """ return ((1 << self._y_bits) - 1) << self._x_bits @property def _source_y_shift(self) -> int: """ The shift to apply to the key *after* masking to get the Y coordinate. """ return self._x_bits @property def _key_shift(self) -> int: """ The shift to apply to the key to get the base key. """ return self._y_bits + self._x_bits