diff --git a/spinn_machine/__init__.py b/spinn_machine/__init__.py index 4dc33a33..b0d624b0 100644 --- a/spinn_machine/__init__.py +++ b/spinn_machine/__init__.py @@ -88,7 +88,6 @@ from .spinnaker_triad_geometry import SpiNNakerTriadGeometry from .virtual_machine import virtual_machine - __all__ = ["Chip", "CoreSubset", "CoreSubsets", "FrozenCoreSubsets", "Link", "Machine", "MulticastRoutingEntry", "Router", "RoutingEntry", "SpiNNakerTriadGeometry", diff --git a/spinn_machine/data/machine_data_view.py b/spinn_machine/data/machine_data_view.py index 6d0ff4fc..b88fad0a 100644 --- a/spinn_machine/data/machine_data_view.py +++ b/spinn_machine/data/machine_data_view.py @@ -43,6 +43,8 @@ class _MachineDataModel(object): __slots__ = [ # Data values cached + "_all_monitor_cores", + "_ethernet_monitor_cores", "_machine", "_machine_generator", "_machine_version", @@ -73,6 +75,8 @@ def _hard_reset(self) -> None: This does NOT clear the machine as it may have been asked for before """ self._soft_reset() + self._all_monitor_cores: int = 0 + self._ethernet_monitor_cores: int = 0 self._machine: Optional[Machine] = None self._user_accessed_machine = False @@ -263,3 +267,32 @@ def get_machine_version(cls) -> AbstractVersion: if cls.__data._machine_version is None: cls.__data._machine_version = version_factory() return cls.__data._machine_version + + @classmethod + def get_all_monitor_cores(cls) -> int: + """ + The number of cores on every chip reported to be used by \ + monitor vertices. + + Ethernet-enabled chips may have more. + + Does not include the system core reserved by the machine/ scamp. + + :rtype: int + """ + return cls.__data._all_monitor_cores + + @classmethod + def get_ethernet_monitor_cores(cls) -> int: + """ + The number of cores on every Ethernet chip reported to be used by \ + monitor vertices. + + This includes the one returned by get_all_monitor_cores unless for + some reason these are not on Ethernet chips. + + Does not include the system core reserved by the machine/ scamp. + + :rtype: int + """ + return cls.__data._ethernet_monitor_cores diff --git a/spinn_machine/data/machine_data_writer.py b/spinn_machine/data/machine_data_writer.py index 08dd6b21..94da3a3d 100644 --- a/spinn_machine/data/machine_data_writer.py +++ b/spinn_machine/data/machine_data_writer.py @@ -17,7 +17,8 @@ from spinn_utilities.data.utils_data_writer import UtilsDataWriter from spinn_utilities.overrides import overrides from spinn_utilities.log import FormatAdapter -from spinn_machine import Machine, virtual_machine +from spinn_machine import Machine +from spinn_machine.virtual_machine import virtual_machine_by_boards from .machine_data_view import MachineDataView, _MachineDataModel logger = FormatAdapter(logging.getLogger(__name__)) __temp_dir = None @@ -46,12 +47,7 @@ def _mock_machine(self) -> None: """ Method to create a virtual machine in mock mode. """ - if self.get_machine_version().number == 3: - self.set_machine(virtual_machine(width=2, height=2)) - elif self.get_machine_version().number == 5: - self.set_machine(virtual_machine(width=8, height=8)) - else: - raise NotImplementedError("Please set machine version") + self.set_machine(virtual_machine_by_boards(1)) @overrides(UtilsDataWriter._setup) def _setup(self) -> None: @@ -109,3 +105,22 @@ def set_machine_generator(self, machine_generator: Callable[[], None]): if not callable(machine_generator): raise TypeError("machine_generator must be callable") self.__data._machine_generator = machine_generator + + def add_monitor_core(self, all_chips: bool): + """ + Accepts a simple of the monitor cores to be added. + + Called by PacmanDataWriter add_sample_monitor_vertex. + + Only affect is to change the numbers reported by the + get_all/ethernet_monitor methods. + + :param bool all_chips: + If True assumes that this Vertex will be placed on all chips + including Ethernet ones. + If False assumes that this Vertex type will only be placed on + Ethernet Vertices + """ + self.__data._ethernet_monitor_cores += 1 + if all_chips: + self.__data._all_monitor_cores += 1 diff --git a/spinn_machine/full_wrap_machine.py b/spinn_machine/full_wrap_machine.py index f8f38baf..d7fef8fa 100644 --- a/spinn_machine/full_wrap_machine.py +++ b/spinn_machine/full_wrap_machine.py @@ -26,10 +26,6 @@ class FullWrapMachine(Machine): This class provides the more complex maths to deal with wraps. """ - @overrides(Machine.multiple_48_chip_boards) - def multiple_48_chip_boards(self) -> bool: - return self._width % 12 == 0 and self._height % 12 == 0 - @overrides(Machine.get_xys_by_ethernet) def get_xys_by_ethernet( self, ethernet_x: int, ethernet_y: int) -> Iterable[XY]: diff --git a/spinn_machine/horizontal_wrap_machine.py b/spinn_machine/horizontal_wrap_machine.py index 6fdb8adf..6ec9dac9 100644 --- a/spinn_machine/horizontal_wrap_machine.py +++ b/spinn_machine/horizontal_wrap_machine.py @@ -27,10 +27,6 @@ class HorizontalWrapMachine(Machine): This class provides the more complex maths to deal with wraps. """ - @overrides(Machine.multiple_48_chip_boards) - def multiple_48_chip_boards(self) -> bool: - return self._width % 12 == 0 and (self._height - 4) % 12 == 0 - @overrides(Machine.get_xys_by_ethernet) def get_xys_by_ethernet( self, ethernet_x: int, ethernet_y: int) -> Iterable[XY]: diff --git a/spinn_machine/json_machine.py b/spinn_machine/json_machine.py index 9bde816b..f841eadd 100644 --- a/spinn_machine/json_machine.py +++ b/spinn_machine/json_machine.py @@ -236,21 +236,7 @@ def to_json() -> JsonObject: :rtype: dict """ machine = MachineDataView.get_machine() - # Find the standard values for any non-Ethernet chip to use by default - std = None - for chip in machine.chips: - if chip.ip_address is None: - std = _Desc( - monitors=chip.n_processors - chip.n_placable_processors, - router_entries=_int_value( - chip.router.n_available_multicast_entries), - sdram=chip.sdram, - tags=list(chip.tag_ids)) - break - else: - raise ValueError("could not compute standard resources") - - # find the nth values to use for Ethernet chips + # find the standard values to use for Ethernet chips chip = machine.boot_chip eth = _Desc( monitors=chip.n_processors - chip.n_placable_processors, @@ -259,6 +245,22 @@ def to_json() -> JsonObject: sdram=chip.sdram, tags=list(chip.tag_ids)) + # Find the standard values for any non-Ethernet chip to use by default + if machine.n_chips > 1: + for chip in machine.chips: + if chip.ip_address is None: + std = _Desc( + monitors=chip.n_processors - chip.n_placable_processors, + router_entries=_int_value( + chip.router.n_available_multicast_entries), + sdram=chip.sdram, + tags=list(chip.tag_ids)) + break + else: + raise ValueError("could not compute standard resources") + else: + std = eth + # write basic stuff return { "height": machine.height, diff --git a/spinn_machine/link_data_objects/spinnaker_link_data.py b/spinn_machine/link_data_objects/spinnaker_link_data.py index f2dcfd64..de932f66 100644 --- a/spinn_machine/link_data_objects/spinnaker_link_data.py +++ b/spinn_machine/link_data_objects/spinnaker_link_data.py @@ -55,3 +55,11 @@ def __hash__(self) -> int: return hash((self._spinnaker_link_id, self.connected_chip_x, self.connected_chip_y, self.connected_link, self.board_address)) + + def __str__(self): + return (f"id:{self._spinnaker_link_id} x:{self.connected_chip_x} " + f"y:{self.connected_chip_y} link:{self._spinnaker_link_id} " + f"{self.board_address}") + + def __repr__(self) -> str: + return self.__str__() diff --git a/spinn_machine/machine.py b/spinn_machine/machine.py index 1e496504..081c497d 100644 --- a/spinn_machine/machine.py +++ b/spinn_machine/machine.py @@ -119,21 +119,6 @@ def __init__(self, width: int, height: int, chip_core_map: Dict[XY, int], self._n_router_entries_counter: Counter[int] = Counter() self._sdram_counter: Counter[int] = Counter() - @abstractmethod - def multiple_48_chip_boards(self) -> bool: - """ - Checks that the width and height correspond to the expected size for a - multi-board machine made up of more than one 48 chip board. - - The assumption is that any size machine can be supported but that - only ones with an expected 48 chip board size can have more than one - Ethernet-enabled chip. - - :return: True if this machine can have multiple 48 chip boards - :rtype: bool - """ - raise NotImplementedError - @abstractmethod def get_xys_by_ethernet( self, ethernet_x: int, ethernet_y: int) -> Iterable[XY]: @@ -501,15 +486,15 @@ def validate(self) -> None: if self._boot_ethernet_address is None: raise SpinnMachineException( "no ethernet chip at 0, 0 found") + version = MachineDataView.get_machine_version() if len(self._ethernet_connected_chips) > 1: - if not self.multiple_48_chip_boards(): + if not version.supports_multiple_boards: raise SpinnMachineException( f"A {self.wrap} machine of size {self._width}, " f"{self._height} can not handle multiple ethernet chips") # The fact that self._boot_ethernet_address is set means there is an # Ethernet chip and it is at 0,0 so no need to check that - version = MachineDataView.get_machine_version() for chip in self.chips: if chip.x < 0: raise SpinnMachineException( @@ -853,22 +838,18 @@ def add_spinnaker_links(self) -> None: Add SpiNNaker links that are on a given machine depending on the version of the board. """ - if self._width == self._height == 2: - chip_0_0 = self[0, 0] - ip_0_0 = chip_0_0.ip_address - assert ip_0_0 is not None - if not chip_0_0.router.is_link(3): - self._add_spinnaker_link(0, 0, 0, 3, ip_0_0) - chip = self.get_chip_at(1, 0) - if chip is not None and not chip.router.is_link(0): - self._add_spinnaker_link(1, 1, 0, 0, ip_0_0) - elif (self._width == self._height == 8) or \ - self.multiple_48_chip_boards(): - for chip in self._ethernet_connected_chips: - if not chip.router.is_link(4): - ip = chip.ip_address - assert ip is not None - self._add_spinnaker_link(0, chip.x, chip.y, 4, ip) + version = MachineDataView.get_machine_version() + for ethernet in self._ethernet_connected_chips: + ip = ethernet.ip_address + assert ip is not None + for (s_id, (local_x, local_y, link)) in enumerate( + version.spinnaker_links()): + global_x, global_y = self.get_global_xy( + local_x, local_y, ethernet.x, ethernet.y) + chip = self.get_chip_at(global_x, global_y) + if chip is not None and not chip.router.is_link(link): + self._add_spinnaker_link( + s_id, global_x, global_y, link, ip) def _add_spinnaker_link( self, spinnaker_link_id: int, x: int, y: int, link: int, @@ -883,72 +864,32 @@ def add_fpga_links(self) -> None: Add FPGA links that are on a given machine depending on the version of the board. """ - if self._width == self._height == 8 or self.multiple_48_chip_boards(): - - for ethernet_connected_chip in self._ethernet_connected_chips: - - # the sides of the hexagonal shape of the board are as follows - # - # - # Top - # #### - # ##### - # Top Left ###### Right - # ####### - # ######## - # ####### - # Left ###### Bottom Right - # ##### - # Bottom - # - - # handle the first chip - (ex, ey) = ethernet_connected_chip - ip = ethernet_connected_chip.ip_address - assert ip is not None - - # List of start x, start y, first link, second link, - # change in x to next, change in y to next - chip_links = [(7, 3, 0, 5, -1, -1), # Bottom Right - (4, 0, 4, 5, -1, 0), # Bottom - (0, 0, 4, 3, 0, 1), # Left - (0, 3, 2, 3, 1, 1), # Top Left - (4, 7, 2, 1, 1, 0), # Top - (7, 7, 0, 1, 0, -1)] # Right - - f = 0 - lk = 0 - for i, (x, y, l1, l2, dx, dy) in enumerate(chip_links): - for _ in range(4): - fx = (x + ex) % (self._width) - fy = (y + ey) % (self._height) - self._add_fpga_link(f, lk, fx, fy, l1, ip, ex, ey) - f, lk = self._next_fpga_link(f, lk) - if i % 2 == 1: - x += dx - y += dy - fx = (x + ex) % (self._width) - fy = (y + ey) % (self._height) - self._add_fpga_link(f, lk, fx, fy, l2, ip, ex, ey) - f, lk = self._next_fpga_link(f, lk) - if i % 2 == 0: - x += dx - y += dy + version = MachineDataView.get_machine_version() + for ethernet in self._ethernet_connected_chips: + ip = ethernet.ip_address + assert ip is not None + for (local_x, local_y, link, fpga_id, fpga_link) in \ + version.fpga_links(): + global_x, global_y = self.get_global_xy( + local_x, local_y, ethernet.x, ethernet.y) + chip = self.get_chip_at(global_x, global_y) + if chip is not None: + self._add_fpga_link(fpga_id, fpga_link, chip.x, chip.y, + link, ip, ethernet.x, ethernet.y) def _add_fpga_link( self, fpga_id: int, fpga_link: int, x: int, y: int, link: int, board_address: str, ex: int, ey: int): # pylint: disable=too-many-arguments - if self.is_chip_at(x, y): - link_data = FPGALinkData( - fpga_link_id=fpga_link, fpga_id=fpga_id, - connected_chip_x=x, connected_chip_y=y, - connected_link=link, board_address=board_address) - self._fpga_links[board_address, fpga_id, fpga_link] = link_data - # Add for the exact chip coordinates - self._fpga_links[(x, y), fpga_id, fpga_link] = link_data - # Add for the Ethernet chip coordinates to allow this to work too - self._fpga_links[(ex, ey), fpga_id, fpga_link] = link_data + link_data = FPGALinkData( + fpga_link_id=fpga_link, fpga_id=fpga_id, + connected_chip_x=x, connected_chip_y=y, + connected_link=link, board_address=board_address) + self._fpga_links[board_address, fpga_id, fpga_link] = link_data + # Add for the exact chip coordinates + self._fpga_links[(x, y), fpga_id, fpga_link] = link_data + # Add for the Ethernet chip coordinates to allow this to work too + self._fpga_links[(ex, ey), fpga_id, fpga_link] = link_data @staticmethod def _next_fpga_link(fpga_id: int, fpga_link: int) -> Tuple[int, int]: diff --git a/spinn_machine/no_wrap_machine.py b/spinn_machine/no_wrap_machine.py index d2fee713..c0e5b8a2 100644 --- a/spinn_machine/no_wrap_machine.py +++ b/spinn_machine/no_wrap_machine.py @@ -27,10 +27,6 @@ class NoWrapMachine(Machine): This class provides the simpler maths that do not deal with wraps. """ - @overrides(Machine.multiple_48_chip_boards) - def multiple_48_chip_boards(self) -> bool: - return (self._width - 4) % 12 == 0 and (self._height - 4) % 12 == 0 - @overrides(Machine.get_xys_by_ethernet) def get_xys_by_ethernet( self, ethernet_x: int, ethernet_y: int) -> Iterable[XY]: diff --git a/spinn_machine/spinn_machine.cfg b/spinn_machine/spinn_machine.cfg index 8a0a6f60..5cc2cee1 100644 --- a/spinn_machine/spinn_machine.cfg +++ b/spinn_machine/spinn_machine.cfg @@ -2,6 +2,8 @@ [Machine] version = None +# Used Instead of version if multiple versions should be tested! +versions = None width = None height = None # Note: if json_path is set all other configs for virtual boards are ignored diff --git a/spinn_machine/version/__init__.py b/spinn_machine/version/__init__.py index 91eaa0c5..83b00d3e 100644 --- a/spinn_machine/version/__init__.py +++ b/spinn_machine/version/__init__.py @@ -11,3 +11,7 @@ # 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 .version_factory import FIVE, SPIN2_1CHIP, THREE, version_factory + +__all__ = ["FIVE", "SPIN2_1CHIP", "THREE", "version_factory"] diff --git a/spinn_machine/version/abstract_version.py b/spinn_machine/version/abstract_version.py index c437c48a..7256dfce 100644 --- a/spinn_machine/version/abstract_version.py +++ b/spinn_machine/version/abstract_version.py @@ -13,11 +13,12 @@ # limitations under the License. from __future__ import annotations import logging -from typing import Mapping, Optional, Sequence, Tuple, TYPE_CHECKING +from typing import List, Mapping, Optional, Sequence, Tuple, TYPE_CHECKING from spinn_utilities.abstract_base import AbstractBase, abstractmethod from spinn_utilities.log import FormatAdapter from spinn_utilities.config_holder import get_config_int_or_none from spinn_utilities.typing.coords import XY +from spinn_machine.data import MachineDataView from spinn_machine.exceptions import SpinnMachineException if TYPE_CHECKING: from spinn_machine.machine import Machine @@ -214,9 +215,9 @@ def chip_core_map(self) -> Mapping[XY, int]: @property @abstractmethod - def clock_speed_hz(self) -> int: + def clock_speeds_hz(self) -> List[int]: """ - The processor clock speed in Hz + The processor clock speeds in Hz this processor can run at :rtype: int """ @@ -351,3 +352,89 @@ def illegal_ethernet_message(self, x: int, y: int) -> Optional[str]: :return: An explanation that the x and y can never be an Ethernet """ raise NotImplementedError + + def size_from_n_cores(self, n_cores: int) -> Tuple[int, int]: + """ + Returns the size needed to support this many cores. + + Takes into consideration scamp and monitor cores. + + Designed for use with virtual boards. + Does not include a safety factor for blacklisted cores or chips. + For real machines a slightly bigger Machine may be needed. + + :param int n_cores: Number of None Scamp and monitor cores needed + :rtype: (int, int) + """ + cores_per_board = sum(self.chip_core_map.values()) + cores_per_board -= MachineDataView.get_ethernet_monitor_cores() + cores_per_board -= ( + (MachineDataView.get_all_monitor_cores() + self.n_scamp_cores) + * self.n_chips_per_board) + # Double minus to round up + return self.size_from_n_boards(-(-n_cores // cores_per_board)) + + def size_from_n_chips(self, n_chips: int) -> Tuple[int, int]: + """ + Returns the size needed to support this many chips. + + Designed for use with virtual boards. + Does not include a safety factor for blacklisted Chips. + For real machines a slightly bigger Machine may be needed. + + :param int n_boards: + :rtype: (int, int) + :raises SpinnMachineException: + If multiple boards are needed but not supported + """ + # Double minus to round up + return self.size_from_n_boards(-(-n_chips // self.n_chips_per_board)) + + def size_from_n_boards(self, n_boards: int) -> Tuple[int, int]: + """ + Returns the size needed to support this many boards. + + :param int n_boards: + :rtype: (int, int) + :raises SpinnMachineException: + If multiple boards are needed but not supported + """ + # Override for versions that support multiple boards + if n_boards == 1: + return self.board_shape + if self.supports_multiple_boards: + raise NotImplementedError + raise SpinnMachineException( + f"Version {self} does not support multiple boards") + + @property + @abstractmethod + def supports_multiple_boards(self) -> bool: + """ + Specifies if this version allows machines of more than one board + + :return: + """ + raise NotImplementedError + + def spinnaker_links(self) -> List[Tuple[int, int, int]]: + """ + The list of Local X, Y and link Id to add spinnaker links to + + These are applied local to each Ethernet Chip and only if the link is + not connected to another board + + :rtype: List((int, int, int)) + """ + raise NotImplementedError + + def fpga_links(self) -> List[Tuple[int, int, int, int, int]]: + """ + The list of Local X, Y, link, fpga_link_id and fpga_id + + These are applied local to each Ethernet Chip and even if the link is + connected to another board + + :rtype: List((int, int, int, int, int)) + """ + raise NotImplementedError diff --git a/spinn_machine/version/version_201.py b/spinn_machine/version/version_201.py new file mode 100644 index 00000000..e7faf0d1 --- /dev/null +++ b/spinn_machine/version/version_201.py @@ -0,0 +1,120 @@ +# Copyright (c) 2024 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 typing import Final, List, Mapping, Optional, Sequence, Tuple + +from spinn_utilities.abstract_base import AbstractBase +from spinn_utilities.typing.coords import XY +from spinn_utilities.overrides import overrides + +from spinn_machine.exceptions import SpinnMachineException +from spinn_machine.no_wrap_machine import NoWrapMachine +from spinn_machine.machine import Machine + +from .abstract_version import AbstractVersion + +CHIPS_PER_BOARD: Final = {(0, 0): 152} + + +class Version201(AbstractVersion, metaclass=AbstractBase): + # pylint: disable=abstract-method + """ + Code for the 1 Chip test Spin2 board versions + """ + + __slots__ = () + + def __init__(self) -> None: + super().__init__(max_cores_per_chip=152, + max_sdram_per_chip=1073741824) + + @property + @overrides(AbstractVersion.n_scamp_cores) + def n_scamp_cores(self) -> int: + return 1 + + @property + @overrides(AbstractVersion.n_router_entries) + def n_router_entries(self) -> int: + return 16384 + + @property + @overrides(AbstractVersion.minimum_cores_expected) + def minimum_cores_expected(self) -> int: + return 100 + + @property + @overrides(AbstractVersion.clock_speeds_hz) + def clock_speeds_hz(self) -> List[int]: + return [150, 300] + + @property + @overrides(AbstractVersion.dtcm_bytes) + def dtcm_bytes(self) -> int: + raise NotImplementedError + + @property + @overrides(AbstractVersion.name) + def name(self) -> str: + return "Spin2 1 Chip" + + @property + @overrides(AbstractVersion.number) + def number(self) -> int: + return 201 + + @property + @overrides(AbstractVersion.board_shape) + def board_shape(self) -> Tuple[int, int]: + return (1, 1) + + @property + @overrides(AbstractVersion.chip_core_map) + def chip_core_map(self) -> Mapping[XY, int]: + return CHIPS_PER_BOARD + + @overrides(AbstractVersion.get_potential_ethernet_chips) + def get_potential_ethernet_chips( + self, width: int, height: int) -> Sequence[XY]: + return [(0, 0)] + + @overrides(AbstractVersion._verify_size) + def _verify_size(self, width: int, height: int): + if width != 1: + raise SpinnMachineException(f"Unexpected {width=}") + if height != 1: + raise SpinnMachineException(f"Unexpected {height=}") + + @overrides(AbstractVersion._create_machine) + def _create_machine(self, width: int, height: int, origin: str) -> Machine: + return NoWrapMachine(width, height, CHIPS_PER_BOARD, origin) + + @overrides(AbstractVersion.illegal_ethernet_message) + def illegal_ethernet_message(self, x: int, y: int) -> Optional[str]: + if x != 0 or y != 0: + return "Only Chip 0, 0 may be an Ethernet Chip" + return None + + @property + @overrides(AbstractVersion.supports_multiple_boards) + def supports_multiple_boards(self) -> bool: + return False + + @overrides(AbstractVersion.spinnaker_links) + def spinnaker_links(self) -> List[Tuple[int, int, int]]: + return [] + + @overrides(AbstractVersion.fpga_links) + def fpga_links(self) -> List[Tuple[int, int, int, int, int]]: + return [] diff --git a/spinn_machine/version/version_3.py b/spinn_machine/version/version_3.py index a186ccb6..2449c285 100644 --- a/spinn_machine/version/version_3.py +++ b/spinn_machine/version/version_3.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Final, Mapping, Optional, Sequence, Tuple +from typing import Final, List, Mapping, Optional, Sequence, Tuple from spinn_utilities.overrides import overrides from spinn_utilities.typing.coords import XY from spinn_machine.exceptions import SpinnMachineException @@ -59,9 +59,9 @@ def get_potential_ethernet_chips( @overrides(VersionSpin1._verify_size) def _verify_size(self, width: int, height: int): if width != 2: - raise SpinnMachineException("Unexpected {width=}") + raise SpinnMachineException(f"Unexpected {width=}") if height != 2: - raise SpinnMachineException("Unexpected {height=}") + raise SpinnMachineException(f"Unexpected {height=}") @overrides(VersionSpin1._create_machine) def _create_machine(self, width: int, height: int, origin: str) -> Machine: @@ -72,3 +72,16 @@ def illegal_ethernet_message(self, x: int, y: int) -> Optional[str]: if x != 0 or y != 0: return "Only Chip 0, 0 may be an Ethernet Chip" return None + + @property + @overrides(VersionSpin1.supports_multiple_boards) + def supports_multiple_boards(self) -> bool: + return False + + @overrides(VersionSpin1.spinnaker_links) + def spinnaker_links(self) -> List[Tuple[int, int, int]]: + return [(0, 0, 3), (1, 0, 0)] + + @overrides(VersionSpin1.fpga_links) + def fpga_links(self) -> List[Tuple[int, int, int, int, int]]: + return [] diff --git a/spinn_machine/version/version_5.py b/spinn_machine/version/version_5.py index 178432dd..30ac9a59 100644 --- a/spinn_machine/version/version_5.py +++ b/spinn_machine/version/version_5.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Final, Mapping, Optional, Sequence, Tuple +import math +from typing import Final, List, Mapping, Optional, Sequence, Tuple from spinn_utilities.overrides import overrides from spinn_utilities.typing.coords import XY from spinn_machine.exceptions import SpinnMachineException @@ -107,3 +108,48 @@ def illegal_ethernet_message(self, x: int, y: int) -> Optional[str]: return "Only Chip with x + y divisible by 12 " \ "may be an Ethernet Chip" return None + + @overrides(VersionSpin1.size_from_n_boards) + def size_from_n_boards(self, n_boards: int) -> Tuple[int, int]: + if n_boards <= 1: + return 8, 8 + # This replicates how spalloc does it + # returning a rectangle of triads + triads = math.ceil(n_boards / 3) + width = math.ceil(math.sqrt(triads)) + height = math.ceil(triads / width) + return width * 12 + 4, height * 12 + 4 + + @property + @overrides(VersionSpin1.supports_multiple_boards) + def supports_multiple_boards(self) -> bool: + return True + + @overrides(VersionSpin1.spinnaker_links) + def spinnaker_links(self) -> List[Tuple[int, int, int]]: + return [(0, 0, 4)] + + @overrides(VersionSpin1.fpga_links) + def fpga_links(self) -> List[Tuple[int, int, int, int, int]]: + return [(0, 0, 3, 1, 1), (0, 0, 4, 1, 0), (0, 0, 5, 0, 15), + (0, 1, 3, 1, 3), (0, 1, 4, 1, 2), + (0, 2, 3, 1, 5), (0, 2, 4, 1, 4), + (0, 3, 2, 1, 8), (0, 3, 3, 1, 7), (0, 3, 4, 1, 6), + (1, 0, 4, 0, 14), (1, 0, 5, 0, 13), + (1, 4, 2, 1, 10), (1, 4, 3, 1, 9), + (2, 0, 4, 0, 12), (2, 0, 5, 0, 11), + (2, 5, 2, 1, 12), (2, 5, 3, 1, 11), + (3, 0, 4, 0, 10), (3, 0, 5, 0, 9), + (3, 6, 2, 1, 14), (3, 6, 3, 1, 13), + (4, 0, 0, 0, 6), (4, 0, 4, 0, 8), + (4, 0, 5, 0, 7), (4, 7, 1, 2, 1), + (4, 7, 2, 2, 0), (4, 7, 3, 1, 15), + (5, 1, 0, 0, 4), (5, 1, 5, 0, 5), + (5, 7, 1, 2, 3), (5, 7, 2, 2, 2), + (6, 2, 0, 0, 2), (6, 2, 5, 0, 3), + (6, 7, 1, 2, 5), (6, 7, 2, 2, 4), + (7, 3, 0, 0, 0), (7, 3, 1, 2, 15), (7, 3, 5, 0, 1), + (7, 4, 0, 2, 14), (7, 4, 1, 2, 13), + (7, 5, 0, 2, 12), (7, 5, 1, 2, 11), + (7, 6, 0, 2, 10), (7, 6, 1, 2, 9), + (7, 7, 0, 2, 8), (7, 7, 1, 2, 7), (7, 7, 2, 2, 6)] diff --git a/spinn_machine/version/version_factory.py b/spinn_machine/version/version_factory.py index 1624624e..fd3dc798 100644 --- a/spinn_machine/version/version_factory.py +++ b/spinn_machine/version/version_factory.py @@ -14,16 +14,24 @@ from __future__ import annotations import logging +import sys from typing import TYPE_CHECKING from spinn_utilities.config_holder import ( get_config_int_or_none, get_config_str_or_none) from spinn_utilities.log import FormatAdapter from spinn_machine.exceptions import SpinnMachineException +from .version_strings import VersionStrings if TYPE_CHECKING: from .abstract_version import AbstractVersion logger = FormatAdapter(logging.getLogger(__name__)) +# Constant when wanting a specific version +THREE = 3 +FIVE = 5 +# New value subject to change +SPIN2_1CHIP = 201 + def version_factory() -> AbstractVersion: """ @@ -36,14 +44,29 @@ def version_factory() -> AbstractVersion: # pylint: disable=import-outside-toplevel from .version_3 import Version3 from .version_5 import Version5 + from .version_201 import Version201 version = get_config_int_or_none("Machine", "version") + versions = get_config_str_or_none("Machine", "versions") + if versions is not None: + if version is not None: + raise SpinnMachineException( + f"Both {version=} and {versions=} found in cfg") + vs = VersionStrings.from_string(versions) + options = vs.options + # Use the fact that we run actions against different python versions + minor = sys.version_info.minor + version = options[minor % len(options)] + if version in [2, 3]: return Version3() if version in [4, 5]: return Version5() + if version == 201: + return Version201() + spalloc_server = get_config_str_or_none("Machine", "spalloc_server") if spalloc_server is not None: return Version5() @@ -56,9 +79,11 @@ def version_factory() -> AbstractVersion: height = get_config_int_or_none("Machine", "height") width = get_config_int_or_none("Machine", "width") if height is not None and width is not None: + logger.info("Your cfg file does not have a version") if height == width == 2: - logger.info("Your cfg file does not have a ") return Version3() + elif height == width == 1: + return Version201() return Version5() if version is None: diff --git a/spinn_machine/version/version_spin1.py b/spinn_machine/version/version_spin1.py index 24a20b47..e71e7794 100644 --- a/spinn_machine/version/version_spin1.py +++ b/spinn_machine/version/version_spin1.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import List from spinn_utilities.abstract_base import AbstractBase from spinn_utilities.overrides import overrides from .abstract_version import AbstractVersion @@ -44,9 +45,9 @@ def minimum_cores_expected(self) -> int: return 5 @property - @overrides(AbstractVersion.clock_speed_hz) - def clock_speed_hz(self) -> int: - return 200 + @overrides(AbstractVersion.clock_speeds_hz) + def clock_speeds_hz(self) -> List[int]: + return [200] @property @overrides(AbstractVersion.dtcm_bytes) diff --git a/spinn_machine/version/version_strings.py b/spinn_machine/version/version_strings.py new file mode 100644 index 00000000..43930aeb --- /dev/null +++ b/spinn_machine/version/version_strings.py @@ -0,0 +1,95 @@ +# Copyright (c) 2024 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 enum import Enum +from typing import List +from spinn_machine.exceptions import SpinnMachineException + + +class VersionStrings(Enum): + """ + A description of strings in cfg settings to say test versions + + As additional Versions are added this should allow easy testing of these + as far as possible. + """ + + ANY = (1, "Any", [3, 5, 201]) + FOUR_PLUS = (2, "Four plus", [3, 5]) + # To test stuff with more than four chips. + BIG = (3, "Big", [5]) + # Specifically to test stuff over multiple boards + MULTIPLE_BOARDS = (4, "Multiple boards", [5]) + # Specifically to test the various wrapping Machine classes + WRAPPABLE = (5, "Wrappable", [5]) + + def __new__(cls, *args, **kwds): + vs = object.__new__(cls) + vs._value_ = args[0] + return vs + + # ignore the first param since it's already set by __new__ + def __init__(self, _: str, text: str, versions: List[int]): + self.text = text + self._versions = versions + + def __str__(self): + return self.text + + @property + def short_str(self) -> str: + """ + The text but in a shortened version + + This makes the text lower case and removes some special characters + + :rtype: str + """ + return self.shorten(self.text) + + # this makes sure that the description is read-only + @property + def options(self) -> List[int]: + """ + The list of the versions covered by this string + + This list can grow as new versions are added + + :rtype: list(int) + """ + return self._versions + + @classmethod + def shorten(cls, value: str) -> str: + """ + Makes the String lower case and removes some special characters + + :param str value: + :rtype: str + """ + return value.lower().replace("_", "").replace("-", "").replace(" ", "") + + @classmethod + def from_string(cls, value: str) -> "VersionStrings": + """ + Gets a VersionString object from a String + + :param str value: + :rtype: VersionStrings + """ + value_short = cls.shorten(value) + for vs in cls: + if value_short == vs.short_str: + return vs + raise SpinnMachineException(f"No version for {value=}") diff --git a/spinn_machine/vertical_wrap_machine.py b/spinn_machine/vertical_wrap_machine.py index c1b16c4e..cbe90102 100644 --- a/spinn_machine/vertical_wrap_machine.py +++ b/spinn_machine/vertical_wrap_machine.py @@ -27,10 +27,6 @@ class VerticalWrapMachine(Machine): This class provides the more complex maths to deal with wraps. """ - @overrides(Machine.multiple_48_chip_boards) - def multiple_48_chip_boards(self) -> bool: - return (self._width - 4) % 12 == 0 and self._height % 12 == 0 - @overrides(Machine.get_xys_by_ethernet) def get_xys_by_ethernet( self, ethernet_x: int, ethernet_y: int) -> Iterable[XY]: diff --git a/spinn_machine/virtual_machine.py b/spinn_machine/virtual_machine.py index 49d15920..3575842c 100644 --- a/spinn_machine/virtual_machine.py +++ b/spinn_machine/virtual_machine.py @@ -11,7 +11,7 @@ # 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 collections import defaultdict import logging from typing import Dict, List, Optional, Set, Tuple @@ -28,14 +28,14 @@ logger = FormatAdapter(logging.getLogger(__name__)) -def virtual_machine( - width: int, height: int, validate: bool = True): +def virtual_machine(width: int, height: int, validate: bool = True): """ Create a virtual SpiNNaker machine, used for planning execution. :param int width: the width of the virtual machine in chips :param int height: the height of the virtual machine in chips :param bool validate: if True will call the machine validate function + :returns: a virtual machine (that cannot execute code) :rtype: ~spinn_machine.Machine """ @@ -43,6 +43,105 @@ def virtual_machine( return factory.machine +def virtual_machine_by_min_size( + width: int, height: int, validate: bool = True): + """ + Create a virtual SpiNNaker machine, used for planning execution. + + :param int width: the minimum width of the virtual machine in chips + :param int height: the minimum height of the virtual machine in chips + :param bool validate: if True will call the machine validate function + + :returns: a virtual machine (that cannot execute code) + :rtype: ~spinn_machine.Machine + """ + version = MachineDataView.get_machine_version() + w_board, h_board = version.board_shape + # check for edge case + if width <= w_board and height > h_board: + width = w_board * 2 + if height <= h_board and width > w_board: + height = h_board * 2 + width = w_board * math.ceil(width / w_board) + height = h_board * math.ceil(height / h_board) + return virtual_machine(width, height, validate) + + +def virtual_machine_by_cores(n_cores: int, validate: bool = True): + """ + Create a virtual SpiNNaker machine, used for planning execution. + + Semantic sugar for + + MachineDataView.get_machine_version() + + width, height = version.size_from_n_cores(n_cores) + + return virtual_machine(width, height, validate) + + :param n_cores: Minimum number of user cores + :param bool validate: if True will call the machine validate function + + :returns: a virtual machine (that cannot execute code) + :rtype: ~spinn_machine.Machine + :raises SpinnMachineException: + If multiple boards are needed but not supported + """ + version = MachineDataView.get_machine_version() + width, height = version.size_from_n_cores(n_cores) + return virtual_machine(width, height, validate) + + +def virtual_machine_by_chips(n_chips: int, validate: bool = True): + """ + Create a virtual SpiNNaker machine, used for planning execution. + + Semantic sugar for + + MachineDataView.get_machine_version() + + width, height = version.size_from_n_cchips(n_cores) + + return virtual_machine(width, height, validate) + + :param n_chips: Minimum number of chips + :param bool validate: if True will call the machine validate function + + :returns: a virtual machine (that cannot execute code) + :rtype: ~spinn_machine.Machine + :raises SpinnMachineException: + If multiple boards are needed but not supported + """ + version = MachineDataView.get_machine_version() + width, height = version.size_from_n_chips(n_chips) + return virtual_machine(width, height, validate) + + +def virtual_machine_by_boards(n_boards: int, validate: bool = True): + """ + Create a virtual SpiNNaker machine, used for planning execution. + + semantic sugar for: + + version = MachineDataView.get_machine_version() + + width, height = version.size_from_n_boards(n_boards) + + return virtual_machine(width, height, validate) + + :param n_boards: Minimum number of boards + :param bool validate: if True will call the machine validate function + + :returns: a virtual machine (that cannot execute code) + :rtype: ~spinn_machine.Machine + :raises SpinnMachineException: + If multiple boards are needed but not supported + """ + version = MachineDataView.get_machine_version() + width, height = version.size_from_n_boards(n_boards) + return virtual_machine(width, height, validate) + + class _VirtualMachine(object): """ A Virtual SpiNNaker machine factory @@ -63,10 +162,9 @@ class _VirtualMachine(object): ORIGIN = "Virtual" - def __init__( - self, width: int, height: int, validate: bool = True): + def __init__(self, width: int, height: int, validate: bool = True): version = MachineDataView.get_machine_version() - version.verify_size(height, width) + version.verify_size(width, height) max_cores = version.max_cores_per_chip self._n_router_entries = version.n_router_entries self._machine = version.create_machine( diff --git a/unittests/data/test_data.py b/unittests/data/test_data.py index f0647bde..d9447d0b 100644 --- a/unittests/data/test_data.py +++ b/unittests/data/test_data.py @@ -19,13 +19,14 @@ from spinn_machine.config_setup import unittest_setup from spinn_machine.data import MachineDataView from spinn_machine.data.machine_data_writer import MachineDataWriter +from spinn_machine.version import FIVE, THREE +from spinn_machine.version.version_strings import VersionStrings class TestSimulatorData(unittest.TestCase): def setUp(self): unittest_setup() - set_config("Machine", "version", 5) def test_setup(self): # What happens before setup depends on the previous test @@ -37,12 +38,12 @@ def test_setup(self): MachineDataView.get_chip_at(1, 1) def test_mock(self): - MachineDataWriter.mock() + set_config("Machine", "version", FIVE) self.assertEqual(3, MachineDataView.get_chip_at(3, 5).x) self.assertEqual(48, MachineDataView.get_machine().n_chips) def test_machine(self): - set_config("Machine", "version", 3) + set_config("Machine", "version", THREE) writer = MachineDataWriter.setup() with self.assertRaises(DataNotYetAvialable): @@ -58,6 +59,7 @@ def test_machine(self): self.assertTrue(MachineDataView.has_machine()) def test_where_is_mocked(self): + set_config("Machine", "versions", VersionStrings.MULTIPLE_BOARDS.text) writer = MachineDataWriter.mock() self.assertEqual( "No Machine created yet", @@ -75,6 +77,7 @@ def test_where_is_mocked(self): MachineDataView.where_is_chip(machine.get_chip_at(2, 8))) def test_where_is_setup(self): + set_config("Machine", "versions", VersionStrings.MULTIPLE_BOARDS.text) writer = MachineDataWriter.setup() self.assertEqual( "No Machine created yet", @@ -94,3 +97,52 @@ def test_where_is_setup(self): "None", MachineDataView.where_is_chip(None) ) + + def test_mock_any(self): + # Should work with any version + set_config("Machine", "versions", VersionStrings.ANY.text) + # check there is a value not what it is + machine = MachineDataView.get_machine() + self.assertGreaterEqual(machine.n_chips, 1) + + def test_mock_4_or_more(self): + # Should work with any version + set_config("Machine", "versions", VersionStrings.FOUR_PLUS.text) + # check there is a value not what it is + machine = MachineDataView.get_machine() + self.assertGreaterEqual(machine.n_chips, 4) + + def test_mock_big(self): + # Should work with any version + set_config("Machine", "versions", VersionStrings.BIG.text) + # check there is a value not what it is + machine = MachineDataView.get_machine() + self.assertGreaterEqual(machine.n_chips, 48) + + def test_mock3(self): + # Should work with any version + set_config("Machine", "version", 3) + # check there is a value not what it is + MachineDataView.get_machine() + + def test_mock5(self): + set_config("Machine", "version", 5) + # check there is a value not what it is + MachineDataView.get_machine() + + def test_mock201(self): + set_config("Machine", "version", 201) + # check there is a value not what it is + MachineDataView.get_machine() + + def test_get_monitors(self): + writer = MachineDataWriter.setup() + self.assertEqual(0, MachineDataView.get_all_monitor_cores()) + self.assertEqual(0, MachineDataView.get_ethernet_monitor_cores()) + writer.add_monitor_core(True) + self.assertEqual(1, MachineDataView.get_all_monitor_cores()) + self.assertEqual(1, MachineDataView.get_ethernet_monitor_cores()) + writer.add_monitor_core(False) + writer.add_monitor_core(True) + self.assertEqual(2, MachineDataView.get_all_monitor_cores()) + self.assertEqual(3, MachineDataView.get_ethernet_monitor_cores()) diff --git a/unittests/test_chip.py b/unittests/test_chip.py index e7d73cb8..f80749f7 100644 --- a/unittests/test_chip.py +++ b/unittests/test_chip.py @@ -17,13 +17,14 @@ from spinn_utilities.ordered_set import OrderedSet from spinn_machine import Link, Router, Chip from spinn_machine.config_setup import unittest_setup +from spinn_machine.version.version_strings import VersionStrings class TestingChip(unittest.TestCase): def setUp(self): unittest_setup() - set_config("Machine", "version", 5) + set_config("Machine", "versions", VersionStrings.ANY.text) self._x = 0 self._y = 1 diff --git a/unittests/test_json_machine.py b/unittests/test_json_machine.py index 65a5a06a..372f0f72 100644 --- a/unittests/test_json_machine.py +++ b/unittests/test_json_machine.py @@ -15,21 +15,23 @@ from tempfile import mktemp import unittest from spinn_utilities.config_holder import set_config -from spinn_machine import virtual_machine +from spinn_machine.virtual_machine import ( + virtual_machine, virtual_machine_by_boards, virtual_machine_by_min_size) from spinn_machine.data.machine_data_writer import MachineDataWriter from spinn_machine.config_setup import unittest_setup from spinn_machine.json_machine import (machine_from_json, to_json_path) +from spinn_machine.version import (FIVE, SPIN2_1CHIP, THREE) +from spinn_machine.version.version_strings import VersionStrings class TestJsonMachine(unittest.TestCase): def setUp(self): unittest_setup() - set_config("Machine", "version", 5) - def test_json_version_5_hole(self): - set_config("Machine", "down_chips", "3,3") - vm = virtual_machine(width=8, height=8) + def test_json_version_3(self): + set_config("Machine", "version", THREE) + vm = virtual_machine(width=2, height=2) MachineDataWriter.mock().set_machine(vm) jpath = mktemp("json") to_json_path(jpath) @@ -40,31 +42,77 @@ def test_json_version_5_hole(self): for vchip, jchip in zip(vm, jm): self.assertEqual(str(vchip), str(jchip)) - def test_exceptions(self): + def test_json_version_5(self): + set_config("Machine", "version", FIVE) vm = virtual_machine(width=8, height=8) MachineDataWriter.mock().set_machine(vm) - chip22 = vm[2, 2] - router22 = chip22.router - router22._n_available_multicast_entries = \ - router22._n_available_multicast_entries - 20 - chip33 = vm[3, 3] - chip33._sdram = 50000000 - chip33._tag_ids = [2, 3] jpath = mktemp("json") to_json_path(jpath) jm = machine_from_json(jpath) + vstr = str(vm).replace("Virtual", "") + jstr = str(jm).replace("Json", "") + self.assertEqual(vstr, jstr) + for vchip, jchip in zip(vm, jm): + self.assertEqual(str(vchip), str(jchip)) + + def test_json_version_201(self): + set_config("Machine", "version", SPIN2_1CHIP) + vm = virtual_machine(width=1, height=1) + MachineDataWriter.mock().set_machine(vm) + jpath = mktemp("json") + to_json_path(jpath) + jm = machine_from_json(jpath) + vstr = str(vm).replace("Virtual", "") + jstr = str(jm).replace("Json", "") + self.assertEqual(vstr, jstr) + for vchip, jchip in zip(vm, jm): + self.assertEqual(str(vchip), str(jchip)) + + def test_json_hole(self): + set_config("Machine", "versions", VersionStrings.BIG.text) + set_config("Machine", "down_chips", "3,3") + writer = MachineDataWriter.mock() + vm = virtual_machine_by_min_size(5, 5) + writer.set_machine(vm) + jpath = mktemp("json") + to_json_path(jpath) + jm = machine_from_json(jpath) + vstr = str(vm).replace("Virtual", "") + jstr = str(jm).replace("Json", "") + self.assertEqual(vstr, jstr) for vchip, jchip in zip(vm, jm): self.assertEqual(str(vchip), str(jchip)) - vchip33 = jm[3, 3] - self.assertEqual(vchip33.tag_ids, chip33.tag_ids) + + def test_exceptions(self): + set_config("Machine", "versions", VersionStrings.FOUR_PLUS.text) + writer = MachineDataWriter.mock() + vm = virtual_machine_by_boards(1) + writer.set_machine(vm) + chip01 = vm[0, 1] + router01 = chip01.router + router01._n_available_multicast_entries = \ + router01._n_available_multicast_entries - 20 + chip10 = vm[1, 0] + chip10._sdram = 50000000 + chip10._tag_ids = [2, 3] + jpath = mktemp("json") + to_json_path(jpath) + jm = machine_from_json(jpath) + for vchip, jchip in zip(vm, jm): + self.assertEqual(str(vchip), str(jchip)) + vchip10 = jm[1, 0] + self.assertEqual(vchip10.tag_ids, chip10.tag_ids) def test_monitor_exceptions(self): - vm = virtual_machine(width=8, height=8) + set_config("Machine", "versions", VersionStrings.FOUR_PLUS.text) + vm = virtual_machine_by_boards(1) MachineDataWriter.mock().set_machine(vm) - chip02 = vm[0, 2] + for chip in vm.chips: + if chip.ip_address is None: + break # Hack in an extra monitor - chip02._scamp_processors = tuple([0, 1]) - chip02._placable_processors = tuple([2, 3, 4, 5, 6, 7, 8, 9]) + chip._scamp_processors = tuple([0, 1]) + chip._placable_processors = tuple([2, 3, 4, 5, 6, 7, 8, 9]) jpath = mktemp("json") # Should still be able to write json even with more than one monitor to_json_path(jpath) @@ -73,21 +121,22 @@ def test_monitor_exceptions(self): machine_from_json(jpath) def test_ethernet_exceptions(self): - vm = virtual_machine(width=16, height=16) + set_config("Machine", "versions", VersionStrings.MULTIPLE_BOARDS.text) + vm = virtual_machine_by_boards(2) MachineDataWriter.mock().set_machine(vm) - chip48 = vm[4, 8] - router48 = chip48.router - router48._n_available_multicast_entries = \ - router48._n_available_multicast_entries - 20 - chip48._sdram = 50000000 - chip48._tag_ids = [2, 3] + eth2 = vm.ethernet_connected_chips[1] + router2 = eth2.router + router2._n_available_multicast_entries = \ + router2._n_available_multicast_entries - 20 + eth2._sdram = 50000000 + eth2._tag_ids = [2, 3] jpath = mktemp("json") to_json_path(jpath) jm = machine_from_json(jpath) for vchip, jchip in zip(vm, jm): self.assertEqual(str(vchip), str(jchip)) - vchip48 = jm[4, 8] - self.assertEqual(vchip48.tag_ids, chip48.tag_ids) + jeth2 = jm.ethernet_connected_chips[1] + self.assertEqual(jeth2.tag_ids, eth2.tag_ids) if __name__ == '__main__': diff --git a/unittests/test_machine.py b/unittests/test_machine.py index 42679a44..835c2e8c 100644 --- a/unittests/test_machine.py +++ b/unittests/test_machine.py @@ -20,7 +20,10 @@ from spinn_utilities.config_holder import set_config from spinn_utilities.testing import log_checker from spinn_machine import Link, Router, Chip -from spinn_machine import virtual_machine +from spinn_machine.version import FIVE +from spinn_machine.version.version_strings import VersionStrings +from spinn_machine.virtual_machine import ( + virtual_machine_by_boards, virtual_machine_by_min_size) from spinn_machine.config_setup import unittest_setup from spinn_machine.data import MachineDataView from spinn_machine.exceptions import ( @@ -34,7 +37,6 @@ class SpinnMachineTestCase(unittest.TestCase): def setUp(self): unittest_setup() - set_config("Machine", "version", 5) self._sdram = 123469792 @@ -50,19 +52,22 @@ def setUp(self): self._ip = "192.162.240.253" def _create_chip(self, x, y): + n_cores = MachineDataView.get_machine_version().max_cores_per_chip if x == y == 0: - return Chip(x, y, 18, self._router, self._sdram, + return Chip(x, y, n_cores, self._router, self._sdram, self._nearest_ethernet_chip[0], self._nearest_ethernet_chip[1], self._ip) - return Chip(x, y, 18, self._router, self._sdram, + return Chip(x, y, n_cores, self._router, self._sdram, self._nearest_ethernet_chip[0], self._nearest_ethernet_chip[1], None) - def test_create_new_machine(self): + def test_create_new_machine_version5(self): """ test creating a new machine """ - new_machine = virtual_machine(8, 8) + # Tests the version 5 values specifically + set_config("Machine", "version", FIVE) + new_machine = virtual_machine_by_boards(1) self.assertEqual(new_machine.width, 8) self.assertEqual(new_machine.height, 8) @@ -90,7 +95,9 @@ def test_create_new_machine(self): self.assertEqual(1023, new_machine.min_n_router_enteries) def test_summary(self): - machine = virtual_machine(8, 8) + # Strings hard coded to version 5 + set_config("Machine", "version", FIVE) + machine = virtual_machine_by_boards(1) self.assertEqual( "Machine on 127.0.0.0 with 48 Chips, 856 cores and 120.0 links. " "Chips have sdram of 123469792 bytes, router table of size 1023, " @@ -137,56 +144,32 @@ def test_summary(self): "Not all Chips had the same n_router_tables. " "The counts where Counter({456: 1, 321: 1}).") - def test_create_new_machine_with_invalid_chips(self): + def test_chip_already_exists(self): """ - check that building a machine with invalid chips causes errors + check that adding a chip that already exists causes an error :rtype: None """ - machine = virtual_machine(8, 8) + set_config("Machine", "versions", VersionStrings.ANY.text) + machine = virtual_machine_by_boards(1) with self.assertRaises(SpinnMachineAlreadyExistsException): machine.add_chip(Chip( 0, 0, 18, self._router, self._sdram, self._nearest_ethernet_chip[0], self._nearest_ethernet_chip[1], self._ip)) - def test_machine_add_chip(self): - """ - test the add_chip method of the machine object - - :rtype: None - """ - new_machine = virtual_machine(8, 8) - extra_chip = self._create_chip(5, 0) - new_machine.add_chip(extra_chip) - - for c in new_machine.chips: - if (c.x == c.y == 0): - self.assertEqual(c.ip_address, "127.0.0.0") - else: - self.assertIsNone(c.ip_address) - self.assertEqual(c.sdram, self._sdram) - self.assertIsNotNone(c.router) - - def test_machine_add_duplicate_chip(self): - """ - test if adding the same chip twice causes an error - - :rtype: None - """ - new_machine = virtual_machine(8, 8) - with self.assertRaises(SpinnMachineAlreadyExistsException): - new_machine.add_chip(new_machine.get_chip_at(1, 1)) - def test_machine_get_chip_at(self): """ test the get_chip_at function from the machine with a valid request :rtype: None """ - new_machine = virtual_machine(8, 8) - self.assertEqual(2, new_machine.get_chip_at(2, 3).x) - self.assertEqual(3, new_machine.get_chip_at(2, 3).y) + set_config("Machine", "versions", VersionStrings.FOUR_PLUS.text) + new_machine = virtual_machine_by_min_size(2, 2) + self.assertEqual(1, new_machine.get_chip_at(1, 0).x) + self.assertEqual(0, new_machine.get_chip_at(1, 0).y) + self.assertEqual(0, new_machine.get_chip_at(0, 1).x) + self.assertEqual(1, new_machine.get_chip_at(0, 1).y) def test_machine_big_x(self): """ @@ -195,15 +178,19 @@ def test_machine_big_x(self): :rtype: None """ - machine = MachineDataView.get_machine_version().create_machine(8, 8) + set_config("Machine", "versions", VersionStrings.ANY.text) + version = MachineDataView.get_machine_version() + width, height = version.board_shape + # create an empty Machine + machine = version.create_machine(width, height) machine.add_chip(self._create_chip(0, 0)) # the add does not have the safety code - machine.add_chip(self._create_chip(10, 2)) + machine.add_chip(self._create_chip(width + 2, height // 2)) # however the validate does try: machine.validate() except SpinnMachineException as ex: - self.assertIn("has an x larger than width 8", str(ex)) + self.assertIn(f"has an x larger than width {width}", str(ex)) def test_machine_big_y(self): """ @@ -212,16 +199,19 @@ def test_machine_big_y(self): :rtype: None """ + set_config("Machine", "versions", VersionStrings.ANY.text) version = MachineDataView.get_machine_version() - new_machine = version.create_machine(8, 8) - new_machine.add_chip(self._create_chip(0, 0)) + width, height = version.board_shape + # create an empty Machine + machine = version.create_machine(width, height) + machine.add_chip(self._create_chip(0, 0)) # the add does not have the safety code - new_machine.add_chip(self._create_chip(2, 10)) + machine.add_chip(self._create_chip(width // 2, height + 2)) # however the validate does try: - new_machine.validate() + machine.validate() except SpinnMachineException as ex: - self.assertIn("has a y larger than height 8", str(ex)) + self.assertIn(f"has a y larger than height {height}", str(ex)) def test_machine_get_chip_at_invalid_location(self): """ @@ -230,8 +220,11 @@ def test_machine_get_chip_at_invalid_location(self): :rtype: None """ - new_machine = virtual_machine(8, 8) - self.assertEqual(None, new_machine.get_chip_at(10, 0)) + set_config("Machine", "versions", VersionStrings.ANY.text) + version = MachineDataView.get_machine_version() + new_machine = virtual_machine_by_boards(1) + width, height = version.board_shape + self.assertEqual(None, new_machine.get_chip_at(width + 2, height // 2)) def test_machine_is_chip_at_true(self): """ @@ -240,8 +233,11 @@ def test_machine_is_chip_at_true(self): :rtype: None """ - new_machine = virtual_machine(8, 8) - self.assertTrue(new_machine.is_chip_at(3, 0)) + set_config("Machine", "versions", VersionStrings.ANY.text) + version = MachineDataView.get_machine_version() + new_machine = virtual_machine_by_boards(1) + width, height = version.board_shape + self.assertTrue(new_machine.is_chip_at(width // 2, height // 2)) def test_machine_is_chip_at_false(self): """ @@ -250,17 +246,21 @@ def test_machine_is_chip_at_false(self): :rtype: None """ - new_machine = virtual_machine(8, 8) - self.assertFalse(new_machine.is_chip_at(10, 0)) + set_config("Machine", "versions", VersionStrings.ANY.text) + version = MachineDataView.get_machine_version() + new_machine = virtual_machine_by_boards(1) + width, height = version.board_shape + self.assertFalse(new_machine.is_chip_at(width + 2, height // 2)) def test_machine_get_chips_on_board(self): - new_machine = virtual_machine(8, 8) + set_config("Machine", "versions", VersionStrings.MULTIPLE_BOARDS.text) + new_machine = virtual_machine_by_boards(3) + version = MachineDataView.get_machine_version() for eth_chip in new_machine._ethernet_connected_chips: chips_in_machine = list( new_machine.get_existing_xys_on_board(eth_chip)) - # _create_chips made a 5*5 grid of 25 chips, - # but (0,4) is not on a standard 48-node board - self.assertEqual(len(chips_in_machine), 48) + self.assertEqual(len(chips_in_machine), version.n_chips_per_board) + # TODO use version info from other PR with self.assertRaises(KeyError): new_machine.get_spinnaker_link_with_id(1) with self.assertRaises(KeyError): @@ -273,6 +273,7 @@ def test_x_y_over_link(self): Notice that the function only does the math not validate the values. :return: """ + set_config("Machine", "versions", VersionStrings.WRAPPABLE.text) # full wrap around machine = MachineDataView.get_machine_version().create_machine(24, 24) self.assertEqual(machine.xy_over_link(0, 0, 4), (23, 23)) @@ -301,6 +302,7 @@ def test_get_global_xy(self): Notice that the function only does the math not validate the values. :return: """ + set_config("Machine", "versions", VersionStrings.WRAPPABLE.text) # full wrap around machine = MachineDataView.get_machine_version().create_machine(24, 24) self.assertEqual(machine.get_global_xy(1, 4, 4, 20), (5, 0)) @@ -319,53 +321,68 @@ def test_get_global_xy(self): self.assertEqual(machine.get_global_xy(5, 0, 20, 4), (25, 4)) def test_no_boot(self): - machine = MachineDataView.get_machine_version().create_machine(8, 8) + set_config("Machine", "versions", VersionStrings.ANY.text) + version = MachineDataView.get_machine_version() + width, height = version.board_shape + # create an empty Machine + machine = version.create_machine(width, height) with self.assertRaises(SpinnMachineException): machine.validate() def test_negative_x(self): - machine = MachineDataView.get_machine_version().create_machine(8, 8) + set_config("Machine", "versions", VersionStrings.ANY.text) + version = MachineDataView.get_machine_version() + width, height = version.board_shape + # create an empty Machine + machine = version.create_machine(width, height) chip = self._create_chip(2, -1) machine.add_chip(chip) with self.assertRaises(SpinnMachineException): machine.validate() def test_negative_y(self): - machine = MachineDataView.get_machine_version().create_machine(8, 8) + set_config("Machine", "versions", VersionStrings.ANY.text) + version = MachineDataView.get_machine_version() + width, height = version.board_shape + # create an empty Machine + machine = version.create_machine(width, height) chip = self._create_chip(-1, 3) machine.add_chip(chip) with self.assertRaises(SpinnMachineException): machine.validate() + def _non_ethernet_chip(self, machine): + for chip in machine.chips: + if chip.ip_address is None: + return chip + raise SpinnMachineException("No none Ethernet Chip") + def test_weird_ethernet1(self): - machine = virtual_machine(8, 8) - machine.get_chip_at(1, 3)._ip_address = "1.2.3.4" + set_config("Machine", "versions", VersionStrings.FOUR_PLUS.text) + machine = virtual_machine_by_boards(1) + self._non_ethernet_chip(machine)._ip_address = "1.2.3.4" with self.assertRaises(SpinnMachineException): machine.validate() def test_bad_ethernet_chip_x(self): - machine = virtual_machine(8, 8) - machine.get_chip_at(0, 1)._nearest_ethernet_x = 1 + set_config("Machine", "versions", VersionStrings.FOUR_PLUS.text) + machine = virtual_machine_by_boards(1) + width, _ = MachineDataView.get_machine_version().board_shape + self._non_ethernet_chip(machine)._nearest_ethernet_x = width + 1 with self.assertRaises(SpinnMachineException): machine.validate() def test_bad_ethernet_chip_no_chip(self): - machine = virtual_machine(8, 8) - machine.get_chip_at(0, 1)._nearest_ethernet_x = 12 + set_config("Machine", "versions", VersionStrings.FOUR_PLUS.text) + machine = virtual_machine_by_boards(1) + _, height = MachineDataView.get_machine_version().board_shape + self._non_ethernet_chip(machine)._nearest_ethernet_x = height + 1 with self.assertRaises(SpinnMachineException): machine.validate() - def test_getitem(self): - machine = virtual_machine(8, 8) - chip12 = machine[(1, 2)] - self.assertEqual(chip12.x, 1) - self.assertEqual(chip12.y, 2) - self.assertTrue((1, 2) in machine) - self.assertFalse((1, 9) in machine) - def test_concentric_xys(self): - machine = virtual_machine(8, 8) - machine.get_chip_at(1, 3) + set_config("Machine", "versions", VersionStrings.BIG.text) + machine = virtual_machine_by_min_size(5, 5) found = list(machine.concentric_xys(2, (2, 2))) expected = [ (2, 2), @@ -375,10 +392,11 @@ def test_concentric_xys(self): self.assertListEqual(expected, found) def test_too_few_cores(self): - machine = virtual_machine(8, 8) + set_config("Machine", "versions", VersionStrings.ANY.text) + machine = virtual_machine_by_boards(1) # Hack to get n_processors return a low number - chip01 = machine.get_chip_at(0, 1) - chip01._placable_processors = tuple([1, 2]) + chip = next(machine.chips) + chip._placable_processors = tuple([1, 2]) with self.assertRaises(SpinnMachineException): machine.validate() diff --git a/unittests/test_using_virtual_machine.py b/unittests/test_using_virtual_machine.py new file mode 100644 index 00000000..6a0c0111 --- /dev/null +++ b/unittests/test_using_virtual_machine.py @@ -0,0 +1,367 @@ +# Copyright (c) 2014 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 unittest +from spinn_utilities.config_holder import set_config +from spinn_machine.config_setup import unittest_setup +from spinn_machine import Chip, Link, Router, virtual_machine +from spinn_machine.virtual_machine import ( + virtual_machine_by_boards, virtual_machine_by_min_size) +from spinn_machine.data import MachineDataView +from spinn_machine.exceptions import (SpinnMachineException) +from spinn_machine.ignores import IgnoreChip, IgnoreCore, IgnoreLink +from spinn_machine.machine_factory import machine_repair +from spinn_machine.version.version_strings import VersionStrings +from spinn_machine.version.version_5 import CHIPS_PER_BOARD +from .geometry import (to_xyz, shortest_mesh_path_length, + shortest_torus_path_length, minimise_xyz) + + +class TestUsingVirtualMachine(unittest.TestCase): + + VERSION_5_N_CORES_PER_BOARD = sum(CHIPS_PER_BOARD.values()) + + def setUp(self): + unittest_setup() + + def _create_chip(self, x, y): + # Create a list of processors. + + n_processors = 18 + + links = list() + links.append(Link(0, 0, 0, 1, 1)) + links.append(Link(0, 1, 1, 1, 0)) + links.append(Link(1, 1, 2, 0, 0)) + links.append(Link(1, 0, 3, 0, 1)) + _router = Router(links, 1024) + + _sdram = 128 + nearest_ethernet_chip = (0, 0) + _ip = "192.162.240.253" + + if (x == y == 0): + return Chip(x, y, n_processors, _router, _sdram, + nearest_ethernet_chip[0], + nearest_ethernet_chip[1], _ip) + else: + return Chip(x, y, n_processors, _router, _sdram, + nearest_ethernet_chip[0], + nearest_ethernet_chip[1], None) + + def test_new_vm_with_max_cores(self): + set_config("Machine", "versions", VersionStrings.ANY.text) + version = MachineDataView.get_machine_version() + n_cpus = version.max_cores_per_chip - 5 + set_config("Machine", "max_machine_core", n_cpus) + # HACK Unsupported! Need new the version again after the cfg changed + MachineDataView._MachineDataView__data._machine_version = None + version = MachineDataView.get_machine_version() + vm = virtual_machine_by_boards(1, validate=True) + for chip in vm.chips: + self.assertEqual(n_cpus, chip.n_processors) + self.assertEqual(n_cpus - version.n_scamp_cores, + chip.n_placable_processors) + self.assertEqual(version.n_scamp_cores, chip.n_scamp_processors) + self.assertEqual(n_cpus - version.n_scamp_cores, + len(list(chip.placable_processors_ids))) + self.assertEqual(version.n_scamp_cores, + len(list(chip.scamp_processors_ids))) + + def test_iter_chips(self): + set_config("Machine", "versions", VersionStrings.ANY.text) + vm = virtual_machine_by_boards(1) + n_chips = MachineDataView.get_machine_version().n_chips_per_board + self.assertEqual(n_chips, vm.n_chips) + count = 0 + for _chip in vm.chips: + count += 1 + self.assertEqual(n_chips, count) + + def test_down_chip(self): + set_config("Machine", "versions", VersionStrings.FOUR_PLUS.text) + down_chips = set() + down_chips.add((1, 1)) + set_config("Machine", "down_chips", "1,1") + vm = virtual_machine_by_boards(1) + n_chips = MachineDataView.get_machine_version().n_chips_per_board + self.assertEqual(n_chips - 1, vm.n_chips) + count = 0 + for _chip in vm.chip_coordinates: + count += 1 + self.assertNotIn(_chip, down_chips) + self.assertEqual(n_chips - 1, count) + + def _check_path(self, source, target, path, width, height): + new_target = ((source[0] + path[0] - path[2]) % width, + (source[1] + path[1] - path[2]) % height) + self.assertEqual(target, new_target, "{}{}".format(source, path)) + + def test_nowrap_shortest_path(self): + set_config("Machine", "versions", VersionStrings.WRAPPABLE.text) + machine = virtual_machine(16, 28, validate=True) + for source in machine.chip_coordinates: + for target in machine.chip_coordinates: + rig_len = shortest_mesh_path_length( + to_xyz(source), to_xyz(target)) + mac_len = machine.get_vector_length(source, target) + self.assertEqual(rig_len, mac_len) + path = machine.get_vector(source, target) + self.assertEqual( + mac_len, abs(path[0]) + abs(path[1]) + abs(path[2])) + self._check_path(source, target, path, 1000000, 1000000) + + def test_fullwrap_shortest_path(self): + set_config("Machine", "versions", VersionStrings.WRAPPABLE.text) + width = 12 + height = 24 + machine = virtual_machine(width, height, validate=True) + for source in machine.chip_coordinates: + for target in machine.chip_coordinates: + rig_len = shortest_torus_path_length( + to_xyz(source), to_xyz(target), width, height) + mac_len = machine.get_vector_length(source, target) + self.assertEqual(rig_len, mac_len) + path = machine.get_vector(source, target) + self.assertEqual( + mac_len, abs(path[0]) + abs(path[1]) + abs(path[2]), + "{}{}{}".format(source, target, path)) + self._check_path(source, target, path, width, height) + + def test_hoizontal_wrap_shortest_path(self): + set_config("Machine", "versions", VersionStrings.WRAPPABLE.text) + width = 12 + height = 16 + machine = virtual_machine(width, height, validate=False) + for source in machine.chip_coordinates: + for target in machine.chip_coordinates: + rig_no = shortest_mesh_path_length( + to_xyz(source), to_xyz(target)) + if source[0] < target[0]: + fake = (target[0] - width, target[1]) + else: + fake = (target[0] + width, target[1]) + rig_with = shortest_mesh_path_length( + to_xyz(source), to_xyz(fake)) + rig_len = min(rig_no, rig_with) + mac_len = machine.get_vector_length(source, target) + self.assertEqual(rig_len, mac_len, "{} {}".format( + source, target)) + path = machine.get_vector(source, target) + self.assertEqual( + mac_len, abs(path[0]) + abs(path[1]) + abs(path[2]), + "{}{}{}".format(source, target, path)) + self._check_path(source, target, path, width, height) + + def test_vertical_wrap_shortest_path(self): + set_config("Machine", "versions", VersionStrings.WRAPPABLE.text) + width = 16 + height = 12 + machine = virtual_machine(width, height, validate=False) + for source in machine.chip_coordinates: + for target in machine.chip_coordinates: + rig_no = shortest_mesh_path_length( + to_xyz(source), to_xyz(target)) + if source[1] < target[1]: + fake = (target[0], target[1] - height) + else: + fake = (target[0], target[1] + height) + rig_with = shortest_mesh_path_length( + to_xyz(source), to_xyz(fake)) + rig_len = min(rig_no, rig_with) + mac_len = machine.get_vector_length(source, target) + self.assertEqual(rig_len, mac_len, "{} {}".format( + source, target)) + path = machine.get_vector(source, target) + self.assertEqual( + mac_len, abs(path[0]) + abs(path[1]) + abs(path[2]), + "{}{}{}".format(source, target, path)) + self._check_path(source, target, path, width, height) + + def test_minimize(self): + set_config("Machine", "versions", VersionStrings.ANY.text) + machine = virtual_machine_by_boards(1) + for x in range(-3, 3): + for y in range(-3, 3): + min1 = minimise_xyz((x, y, 0)) + min2 = machine._minimize_vector(x, y) + self.assertEqual(min1, min2) + + def test_unreachable_incoming_chips(self): + set_config("Machine", "versions", VersionStrings.BIG.text) + machine = virtual_machine_by_min_size(6, 6) + + # Delete links incoming to 3, 3 + down_links = [ + (2, 2, 1), (2, 3, 0), (3, 4, 5), (4, 4, 4), (4, 3, 3), (3, 2, 2)] + for (x, y, link) in down_links: + if machine.is_link_at(x, y, link): + del machine._chips[x, y].router._links[link] + unreachable = machine.unreachable_incoming_chips() + self.assertListEqual([(3, 3)], unreachable) + + def test_unreachable_outgoing_chips(self): + set_config("Machine", "versions", VersionStrings.BIG.text) + machine = virtual_machine_by_min_size(6, 6) + + # Delete links outgoing from 3, 3 + for link in range(6): + if machine.is_link_at(3, 3, link): + del machine._chips[3, 3].router._links[link] + unreachable = machine.unreachable_outgoing_chips() + self.assertListEqual([(3, 3)], unreachable) + + def test_unreachable_incoming_local_chips(self): + set_config("Machine", "versions", VersionStrings.WRAPPABLE.text) + # Assumes boards of exactly size 8,8 + down_chips = [(8, 6), (9, 7), (9, 8)] + down_str = ":".join([f"{x},{y}" for x, y in down_chips]) + set_config("Machine", "down_chips", down_str) + machine = virtual_machine(16, 16) + unreachable = machine.unreachable_incoming_local_chips() + self.assertListEqual([(8, 7)], unreachable) + + def test_unreachable_outgoing_local_chips(self): + set_config("Machine", "versions", VersionStrings.WRAPPABLE.text) + # Assumes boards of exactly size 8,8 + down_chips = [(8, 6), (9, 7), (9, 8)] + down_str = ":".join([f"{x},{y}" for x, y in down_chips]) + set_config("Machine", "down_chips", down_str) + machine = virtual_machine(16, 16) + unreachable = machine.unreachable_outgoing_local_chips() + self.assertListEqual([(8, 7)], unreachable) + + def test_repair_with_local_orphan(self): + set_config("Machine", "versions", VersionStrings.WRAPPABLE.text) + # Assumes boards of exactly size 8,8 + down_chips = [(8, 6), (9, 7), (9, 8)] + down_str = ":".join([f"{x},{y}" for x, y in down_chips]) + set_config("Machine", "down_chips", down_str) + machine = virtual_machine(16, 16) + with self.assertRaises(SpinnMachineException): + set_config("Machine", "repair_machine", False) + repaired = machine_repair(machine) + set_config("Machine", "repair_machine", True) + repaired = machine_repair(machine) + self.assertTrue(machine.is_chip_at(8, 7)) + self.assertFalse(repaired.is_chip_at(8, 7)) + + def test_repair_with_one_way_links_different_boards(self): + set_config("Machine", "versions", VersionStrings.WRAPPABLE.text) + machine = virtual_machine(12, 12) + # Assumes boards of exactly size 8,8 + # Delete some links between boards + down_links = [ + (7, 7, 0), (7, 3, 1), (6, 7, 2), (4, 7, 3), (8, 6, 4), (8, 4, 5)] + for (x, y, link) in down_links: + del machine._chips[x, y].router._links[link] + with self.assertRaises(SpinnMachineException): + set_config("Machine", "repair_machine", False) + new_machine = machine_repair(machine) + set_config("Machine", "repair_machine", True) + new_machine = machine_repair(machine) + self.assertIsNotNone(new_machine) + + def test_oneway_link_no_repair(self): + set_config("Machine", "versions", VersionStrings.WRAPPABLE.text) + machine = virtual_machine(8, 8) + + # Delete some random links + down_links = [ + (3, 6, 0), (5, 4, 1), (3, 2, 5), (1, 3, 3)] + for (x, y, link) in down_links: + if machine.is_link_at(x, y, link): + del machine._chips[x, y].router._links[link] + with self.assertRaises(SpinnMachineException): + set_config("Machine", "repair_machine", False) + new_machine = machine_repair(machine) + set_config("Machine", "repair_machine", True) + new_machine = machine_repair(machine) + self.assertIsNotNone(new_machine) + + def test_removed_chip_repair(self): + set_config("Machine", "versions", VersionStrings.BIG.text) + machine = virtual_machine_by_boards(1) + + del machine._chips[(3, 3)] + set_config("Machine", "repair_machine", False) + new_machine = machine_repair(machine, [(3, 3)]) + self.assertIsNotNone(new_machine) + self.assertFalse(new_machine.is_link_at(2, 2, 1)) + + def test_ignores(self): + set_config("Machine", "versions", VersionStrings.BIG.text) + set_config("Machine", "down_chips", "2,2:4,4:6,6,ignored_ip") + set_config("Machine", "down_cores", + "1,1,1:3,3,3: 5,5,-5:7,7,7,ignored_ip:0,0,5-10") + set_config("Machine", "down_links", "1,3,3:3,5,3:5,3,3,ignored_ip") + + machine = virtual_machine_by_min_size(8, 8) + + self.assertFalse(machine.is_chip_at(4, 4)) + self.assertFalse(machine.is_chip_at(2, 2)) + self.assertTrue(machine.is_chip_at(6, 6)) + self.assertTrue(machine.is_chip_at(0, 0)) + + chip = machine[3, 3] + self.assertFalse(chip.is_processor_with_id(3)) + + chip = machine[5, 5] + self.assertFalse(chip.is_processor_with_id(6)) + + chip = machine[7, 7] + self.assertTrue(chip.is_processor_with_id(6)) + + chip = machine[1, 1] + self.assertFalse(chip.is_processor_with_id(1)) + + router = machine[1, 3].router + self.assertFalse(router.is_link(3)) + + router = machine[3, 5].router + self.assertFalse(router.is_link(3)) + + router = machine[5, 3].router + self.assertTrue(router.is_link(3)) + + chip = machine[0, 0] + for i in range(0, 5): + self.assertTrue(chip.is_processor_with_id(i)) + for i in range(5, 11): + self.assertFalse(chip.is_processor_with_id(i)) + for i in range(12, 18): + self.assertTrue(chip.is_processor_with_id(i)) + + def test_bad_ignores(self): + try: + IgnoreChip.parse_string("4,4,3,4:6,6,ignored_ip") + except Exception as ex: + self.assertTrue("downed_chip" in str(ex)) + + try: + IgnoreCore.parse_string("3,3,3,4: 5,5,-5:7,7,7,ignored_ip") + except Exception as ex: + self.assertTrue("downed_core" in str(ex)) + + empty = IgnoreCore.parse_string(None) + self.assertEqual(len(empty), 0) + + try: + IgnoreLink.parse_string("1,3:5,3,3,ignored_ip") + except Exception as ex: + self.assertTrue("downed_link" in str(ex)) + + +if __name__ == '__main__': + unittest.main() diff --git a/unittests/test_virtual_machine201.py b/unittests/test_virtual_machine201.py new file mode 100644 index 00000000..6c550c51 --- /dev/null +++ b/unittests/test_virtual_machine201.py @@ -0,0 +1,259 @@ +# Copyright (c) 2014 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 unittest +from spinn_utilities.config_holder import set_config +from spinn_machine.config_setup import unittest_setup +from spinn_machine import Chip, Link, Router, virtual_machine +from spinn_machine.exceptions import ( + SpinnMachineException, SpinnMachineAlreadyExistsException) +from spinn_machine.version import SPIN2_1CHIP +from spinn_machine.virtual_machine import ( + virtual_machine_by_boards, virtual_machine_by_chips, + virtual_machine_by_cores) + + +class TestVirtualMachine201(unittest.TestCase): + + def setUp(self): + unittest_setup() + + def _create_chip(self, x, y): + # Create a list of processors. + + n_processors = 18 + + links = list() + links.append(Link(0, 0, 0, 1, 1)) + links.append(Link(0, 1, 1, 1, 0)) + links.append(Link(1, 1, 2, 0, 0)) + links.append(Link(1, 0, 3, 0, 1)) + _router = Router(links, 1024) + + _sdram = 128 + nearest_ethernet_chip = (0, 0) + _ip = "192.162.240.253" + + if (x == y == 0): + return Chip(x, y, n_processors, _router, _sdram, + nearest_ethernet_chip[0], + nearest_ethernet_chip[1], _ip) + else: + return Chip(x, y, n_processors, _router, _sdram, + nearest_ethernet_chip[0], + nearest_ethernet_chip[1], None) + + def test_illegal_vms(self): + set_config("Machine", "version", SPIN2_1CHIP) + with self.assertRaises(SpinnMachineException): + virtual_machine(width=-1, height=2) + with self.assertRaises(SpinnMachineException): + virtual_machine(width=2, height=-1) + with self.assertRaises(SpinnMachineException): + virtual_machine(width=15, height=15) + with self.assertRaises(SpinnMachineException): + virtual_machine(width=8, height=8) + with self.assertRaises(SpinnMachineException): + virtual_machine(width=2, height=2) + + def test_version_SPIN2_1CHIP(self): + set_config("Machine", "version", SPIN2_1CHIP) + vm = virtual_machine(width=1, height=1) + self.assertEqual(1, vm.n_chips) + self.assertTrue(vm.is_chip_at(0, 0)) + self.assertFalse(vm.is_chip_at(0, 1)) + self.assertEqual(1, len(vm.ethernet_connected_chips)) + self.assertFalse(vm.is_link_at(0, 0, 0)) + self.assertFalse(vm.is_link_at(0, 0, 1)) + self.assertFalse(vm.is_link_at(0, 0, 2)) + self.assertFalse(vm.is_link_at(0, 0, 3)) + self.assertFalse(vm.is_link_at(0, 0, 4)) + self.assertFalse(vm.is_link_at(0, 0, 5)) + + count = 0 + for _chip in vm.chips: + for _link in _chip.router.links: + count += 1 + self.assertEqual(0, count) + self.assertEqual(152, vm.get_cores_count()) + self.assertEqual(0, vm.get_links_count()) + count = 0 + for _chip in vm.get_existing_xys_on_board(vm[0, 0]): + count += 1 + self.assertEqual(1, count) + self.assertEqual((1, 0), vm.get_unused_xy()) + self.assertEqual(0, len(list(vm.spinnaker_links))) + self.assertEqual(0, len(vm._fpga_links)) + + def test_new_vm_with_max_cores(self): + set_config("Machine", "version", SPIN2_1CHIP) + n_cpus = 105 + set_config("Machine", "max_machine_core", n_cpus) + vm = virtual_machine(1, 1, validate=True) + _chip = vm[0, 0] + self.assertEqual(n_cpus, _chip.n_processors) + self.assertEqual(n_cpus - 1, _chip.n_placable_processors) + self.assertEqual(1, _chip.n_scamp_processors) + self.assertEqual(n_cpus - 1, len(list(_chip.placable_processors_ids))) + self.assertEqual(1, len(list(_chip.scamp_processors_ids))) + count = sum(_chip.n_processors for _chip in vm.chips) + self.assertEqual(count, 1 * n_cpus) + + def test_iter_chips(self): + set_config("Machine", "version", SPIN2_1CHIP) + vm = virtual_machine(1, 1) + self.assertEqual(1, vm.n_chips) + count = 0 + for _chip in vm.chips: + count += 1 + self.assertEqual(1, count) + + def test_add_existing_chip(self): + set_config("Machine", "version", SPIN2_1CHIP) + vm = virtual_machine(1, 1) + _chip = self._create_chip(0, 0) + with self.assertRaises(SpinnMachineAlreadyExistsException): + vm.add_chip(_chip) + + def test_chips(self): + set_config("Machine", "version", SPIN2_1CHIP) + vm = virtual_machine(1, 1) + count = 0 + for _chip in vm.chips: + count += 1 + self.assertEqual(count, 1) + + def test_ethernet_chips_exist(self): + set_config("Machine", "version", SPIN2_1CHIP) + vm = virtual_machine(width=1, height=1) + for eth_chip in vm._ethernet_connected_chips: + self.assertTrue(vm.get_chip_at(eth_chip.x, eth_chip.y), + "Eth chip location x={}, y={} not in " + "_configured_chips" + .format(eth_chip.x, eth_chip.y)) + + def test_boot_chip(self): + set_config("Machine", "version", SPIN2_1CHIP) + vm = virtual_machine(1, 1) + # as Chip == its XY + self.assertEqual(vm.boot_chip, (0, 0)) + + def test_get_chips_on_boards(self): + set_config("Machine", "version", SPIN2_1CHIP) + vm = virtual_machine(width=1, height=1) + # check each chip appears only once on the entire board + count00 = 0 + count01 = 0 + count04 = 0 + for eth_chip in vm._ethernet_connected_chips: + list_of_chips = list(vm.get_existing_xys_on_board(eth_chip)) + self.assertEqual(len(list_of_chips), 1) + if (0, 0) in list_of_chips: + count00 += 1 + if (0, 1) in list_of_chips: + count01 += 1 + if (0, 4) in list_of_chips: + count04 += 1 + # (0,0) is on this virtual machine + self.assertEqual(count00, 1) + + # (0, 1), (24,36) is not on this virtual machine + self.assertEqual(count01, 0) + self.assertEqual(count04, 0) + + def test_fpga_links_single_board(self): + set_config("Machine", "version", 3) + machine = virtual_machine(width=2, height=2) + machine.add_fpga_links() + self.assertEqual(0, len(machine._fpga_links)) + + def test_size_2_2(self): + set_config("Machine", "version", 2) + machine = virtual_machine(2, 2, validate=True) + ethernet = machine[0, 0] + chips = set(machine.get_existing_xys_on_board(ethernet)) + self.assertEqual(len(chips), 4) + chips = set(machine.get_existing_xys_by_ethernet(0, 0)) + self.assertEqual(len(chips), 4) + global_xys = set() + for chip in machine.chips: + local_x, local_y = machine.get_local_xy(chip) + global_x, global_y = machine.get_global_xy( + local_x, local_y, + chip.nearest_ethernet_x, chip.nearest_ethernet_y) + self.assertEqual(global_x, chip.x) + self.assertEqual(global_y, chip.y) + global_xys.add((global_x, global_y)) + self.assertEqual(len(global_xys), 4) + self.assertEqual(4, len(list(machine.local_xys))) + + def test_size_2_2_hole(self): + set_config("Machine", "version", 2) + hole = [(1, 1)] + set_config("Machine", "down_chips", "1,1") + machine = virtual_machine(2, 2, validate=True) + self.assertEqual(4, len(list(machine.get_xys_by_ethernet(0, 0)))) + count = 0 + for chip in machine.get_chips_by_ethernet(0, 0): + count += 1 + xy = (chip.x, chip.y) + assert xy not in hole + self.assertEqual(3, count) + count = 0 + for xy in machine.get_existing_xys_by_ethernet(0, 0): + count += 1 + assert xy not in hole + self.assertEqual(3, count) + + def _check_path(self, source, target, path, width, height): + new_target = ((source[0] + path[0] - path[2]) % width, + (source[1] + path[1] - path[2]) % height) + self.assertEqual(target, new_target, "{}{}".format(source, path)) + + def test_n_cores_2_2(self): + set_config("Machine", "version", SPIN2_1CHIP) + machine = virtual_machine(1, 1) + n_cores = sum( + cores for (_, cores) in machine.get_xy_cores_by_ethernet(0, 0)) + self.assertEqual(n_cores, 152) + n_cores = sum(chip.n_processors for chip in machine.chips) + self.assertEqual(n_cores, 152) + + def test_by(self): + set_config("Machine", "version", SPIN2_1CHIP) + n_cores = 40 + machine = virtual_machine_by_cores(n_cores) + self.assertEqual(1, machine.n_chips) + self.assertEqual(1, machine.width) + self.assertEqual(1, machine.height) + self.assertGreaterEqual(machine.total_available_user_cores, n_cores) + machine2 = virtual_machine_by_boards(1) + self.assertEqual(1, machine2.n_chips) + self.assertEqual(1, machine2.width) + self.assertEqual(1, machine2.height) + machine = virtual_machine_by_chips(1) + self.assertEqual(1, machine.n_chips) + self.assertEqual(1, machine.width) + self.assertEqual(1, machine.height) + + def test_by_cores_too_many(self): + set_config("Machine", "version", SPIN2_1CHIP) + with self.assertRaises(SpinnMachineException): + virtual_machine_by_cores(200) + with self.assertRaises(SpinnMachineException): + virtual_machine_by_boards(2) + + +if __name__ == '__main__': + unittest.main() diff --git a/unittests/test_virtual_machine3.py b/unittests/test_virtual_machine3.py new file mode 100644 index 00000000..389ee30a --- /dev/null +++ b/unittests/test_virtual_machine3.py @@ -0,0 +1,303 @@ +# Copyright (c) 2014 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 unittest +from spinn_utilities.config_holder import set_config +from spinn_machine import Chip, Link, Router, virtual_machine +from spinn_machine.config_setup import unittest_setup +from spinn_machine.link_data_objects import SpinnakerLinkData +from spinn_machine.exceptions import ( + SpinnMachineException, SpinnMachineAlreadyExistsException) +from spinn_machine.virtual_machine import ( + virtual_machine_by_boards, virtual_machine_by_chips, + virtual_machine_by_cores) + + +class TestVirtualMachine3(unittest.TestCase): + + def setUp(self): + unittest_setup() + + def _create_chip(self, x, y): + # Create a list of processors. + + n_processors = 18 + + links = list() + links.append(Link(0, 0, 0, 1, 1)) + links.append(Link(0, 1, 1, 1, 0)) + links.append(Link(1, 1, 2, 0, 0)) + links.append(Link(1, 0, 3, 0, 1)) + _router = Router(links, 1024) + + _sdram = 128 + nearest_ethernet_chip = (0, 0) + _ip = "192.162.240.253" + + if (x == y == 0): + return Chip(x, y, n_processors, _router, _sdram, + nearest_ethernet_chip[0], + nearest_ethernet_chip[1], _ip) + else: + return Chip(x, y, n_processors, _router, _sdram, + nearest_ethernet_chip[0], + nearest_ethernet_chip[1], None) + + def test_illegal_vms(self): + set_config("Machine", "version", 3) + with self.assertRaises(SpinnMachineException): + virtual_machine(width=-1, height=2) + with self.assertRaises(SpinnMachineException): + virtual_machine(width=2, height=-1) + with self.assertRaises(SpinnMachineException): + virtual_machine(width=15, height=15) + with self.assertRaises(SpinnMachineException): + virtual_machine(width=8, height=8) + with self.assertRaises(SpinnMachineException): + virtual_machine(width=1, height=1) + + def test_version_2(self): + set_config("Machine", "version", 2) + vm = virtual_machine(width=2, height=2) + self.assertEqual(4, vm.n_chips) + self.assertTrue(vm.is_chip_at(0, 0)) + self.assertTrue(vm.is_chip_at(0, 1)) + self.assertTrue(vm.is_chip_at(1, 0)) + self.assertTrue(vm.is_chip_at(1, 1)) + self.assertEqual(1, len(vm.ethernet_connected_chips)) + self.assertTrue(vm.is_link_at(0, 0, 0)) + self.assertTrue(vm.is_link_at(0, 0, 1)) + self.assertTrue(vm.is_link_at(0, 0, 2)) + self.assertFalse(vm.is_link_at(0, 0, 3)) + self.assertFalse(vm.is_link_at(0, 0, 4)) + self.assertTrue(vm.is_link_at(0, 0, 5)) + self.assertTrue(vm.is_link_at(0, 1, 0)) + self.assertTrue(vm.is_link_at(0, 1, 1)) + self.assertTrue(vm.is_link_at(0, 1, 2)) + self.assertFalse(vm.is_link_at(0, 1, 3)) + self.assertFalse(vm.is_link_at(0, 1, 4)) + self.assertTrue(vm.is_link_at(0, 1, 5)) + + self.assertFalse(vm.is_link_at(1, 0, 0)) + self.assertFalse(vm.is_link_at(1, 0, 1)) + self.assertTrue(vm.is_link_at(1, 0, 2)) + self.assertTrue(vm.is_link_at(1, 0, 3)) + self.assertTrue(vm.is_link_at(1, 0, 4)) + self.assertTrue(vm.is_link_at(1, 0, 5)) + self.assertFalse(vm.is_link_at(1, 1, 0)) + self.assertFalse(vm.is_link_at(1, 1, 1)) + self.assertTrue(vm.is_link_at(1, 1, 2)) + self.assertTrue(vm.is_link_at(1, 1, 3)) + self.assertTrue(vm.is_link_at(1, 1, 4)) + self.assertTrue(vm.is_link_at(1, 1, 5)) + + count = 0 + for _chip in vm.chips: + for _link in _chip.router.links: + count += 1 + self.assertEqual(16, count) +# self.assertEqual(str(vm), +# "[VirtualMachine: max_x=1, max_y=1, n_chips=4]") + self.assertEqual(72, vm.get_cores_count()) + self.assertEqual(8, vm.get_links_count()) + count = 0 + for _chip in vm.get_existing_xys_on_board(vm[1, 1]): + count += 1 + self.assertEqual(4, count) + self.assertEqual((2, 0), vm.get_unused_xy()) + spinnaker_links = (list(vm.spinnaker_links)) + expected = [] + sp = SpinnakerLinkData(0, 0, 0, 3, '127.0.0.0') + expected.append((('127.0.0.0', 0), sp)) + expected.append((((0, 0), 0), sp)) + sp = SpinnakerLinkData(1, 1, 0, 0, '127.0.0.0') + expected.append((('127.0.0.0', 1), sp)) + expected.append((((1, 0), 1), sp)) + self.assertEqual(expected, spinnaker_links) + self.assertEqual(0, len(vm._fpga_links)) + + def test_new_vm_with_max_cores(self): + set_config("Machine", "version", 2) + n_cpus = 13 + set_config("Machine", "max_machine_core", n_cpus) + vm = virtual_machine(2, 2, validate=True) + _chip = vm[1, 1] + self.assertEqual(n_cpus, _chip.n_processors) + self.assertEqual(n_cpus - 1, _chip.n_placable_processors) + self.assertEqual(1, _chip.n_scamp_processors) + self.assertEqual(n_cpus - 1, len(list(_chip.placable_processors_ids))) + self.assertEqual(1, len(list(_chip.scamp_processors_ids))) + count = sum(_chip.n_processors for _chip in vm.chips) + self.assertEqual(count, 4 * n_cpus) + + def test_iter_chips(self): + set_config("Machine", "version", 2) + vm = virtual_machine(2, 2) + self.assertEqual(4, vm.n_chips) + count = 0 + for _chip in vm.chips: + count += 1 + self.assertEqual(4, count) + + def test_down_chip(self): + set_config("Machine", "version", 2) + down_chips = set() + down_chips.add((1, 1)) + set_config("Machine", "down_chips", "1,1") + vm = virtual_machine(2, 2) + self.assertEqual(3, vm.n_chips) + count = 0 + for _chip in vm.chip_coordinates: + count += 1 + self.assertNotIn(_chip, down_chips) + self.assertEqual(3, count) + + def test_add_existing_chip(self): + set_config("Machine", "version", 2) + vm = virtual_machine(2, 2) + _chip = self._create_chip(1, 1) + with self.assertRaises(SpinnMachineAlreadyExistsException): + vm.add_chip(_chip) + + def test_chips(self): + set_config("Machine", "version", 2) + vm = virtual_machine(2, 2) + count = 0 + for _chip in vm.chips: + count += 1 + self.assertEqual(count, 4) + + def test_ethernet_chips_exist(self): + set_config("Machine", "version", 2) + vm = virtual_machine(width=2, height=2) + for eth_chip in vm._ethernet_connected_chips: + self.assertTrue(vm.get_chip_at(eth_chip.x, eth_chip.y), + "Eth chip location x={}, y={} not in " + "_configured_chips" + .format(eth_chip.x, eth_chip.y)) + + def test_boot_chip(self): + set_config("Machine", "version", 2) + vm = virtual_machine(2, 2) + # as Chip == its XY + self.assertEqual(vm.boot_chip, (0, 0)) + + def test_get_chips_on_boards(self): + set_config("Machine", "version", 2) + vm = virtual_machine(width=2, height=2) + # check each chip appears only once on the entire board + count00 = 0 + count01 = 0 + count04 = 0 + for eth_chip in vm._ethernet_connected_chips: + list_of_chips = list(vm.get_existing_xys_on_board(eth_chip)) + self.assertEqual(len(list_of_chips), 4) + if (0, 0) in list_of_chips: + count00 += 1 + if (0, 1) in list_of_chips: + count01 += 1 + if (0, 4) in list_of_chips: + count04 += 1 + # (0,0), (0,1) are all on this virtual machine + self.assertEqual(count00, 1) + self.assertEqual(count01, 1) + + # (24,36) is not on this virtual machine + self.assertEqual(count04, 0) + + def test_fpga_links_single_board(self): + set_config("Machine", "version", 3) + machine = virtual_machine(width=2, height=2) + machine.add_fpga_links() + self.assertEqual(0, len(machine._fpga_links)) + + def test_size_2_2(self): + set_config("Machine", "version", 2) + machine = virtual_machine(2, 2, validate=True) + ethernet = machine[0, 0] + chips = set(machine.get_existing_xys_on_board(ethernet)) + self.assertEqual(len(chips), 4) + chips = set(machine.get_existing_xys_by_ethernet(0, 0)) + self.assertEqual(len(chips), 4) + global_xys = set() + for chip in machine.chips: + local_x, local_y = machine.get_local_xy(chip) + global_x, global_y = machine.get_global_xy( + local_x, local_y, + chip.nearest_ethernet_x, chip.nearest_ethernet_y) + self.assertEqual(global_x, chip.x) + self.assertEqual(global_y, chip.y) + global_xys.add((global_x, global_y)) + self.assertEqual(len(global_xys), 4) + self.assertEqual(4, len(list(machine.local_xys))) + + def test_size_2_2_hole(self): + set_config("Machine", "version", 2) + hole = [(1, 1)] + set_config("Machine", "down_chips", "1,1") + machine = virtual_machine(2, 2, validate=True) + self.assertEqual(4, len(list(machine.get_xys_by_ethernet(0, 0)))) + count = 0 + for chip in machine.get_chips_by_ethernet(0, 0): + count += 1 + xy = (chip.x, chip.y) + assert xy not in hole + self.assertEqual(3, count) + count = 0 + for xy in machine.get_existing_xys_by_ethernet(0, 0): + count += 1 + assert xy not in hole + self.assertEqual(3, count) + + def _check_path(self, source, target, path, width, height): + new_target = ((source[0] + path[0] - path[2]) % width, + (source[1] + path[1] - path[2]) % height) + self.assertEqual(target, new_target, "{}{}".format(source, path)) + + def test_n_cores_2_2(self): + set_config("Machine", "version", 2) + machine = virtual_machine(2, 2) + n_cores = sum( + cores for (_, cores) in machine.get_xy_cores_by_ethernet(0, 0)) + self.assertEqual(n_cores, 4 * 18) + n_cores = sum(chip.n_processors for chip in machine.chips) + self.assertEqual(n_cores, 4 * 18) + + def test_2_2_by(self): + set_config("Machine", "version", 2) + n_cores = 40 + machine = virtual_machine_by_cores(n_cores) + self.assertEqual(4, machine.n_chips) + self.assertEqual(2, machine.width) + self.assertEqual(2, machine.height) + self.assertGreaterEqual(machine.total_available_user_cores, n_cores) + machine2 = virtual_machine_by_boards(1) + self.assertEqual(4, machine2.n_chips) + self.assertEqual(2, machine2.width) + self.assertEqual(2, machine2.height) + machine = virtual_machine_by_chips(3) + self.assertEqual(4, machine.n_chips) + self.assertEqual(2, machine.width) + self.assertEqual(2, machine.height) + + def test_2_2_by_cores_too_many(self): + set_config("Machine", "version", 2) + with self.assertRaises(SpinnMachineException): + virtual_machine_by_cores(100) + with self.assertRaises(SpinnMachineException): + virtual_machine_by_boards(2) + + +if __name__ == '__main__': + unittest.main() diff --git a/unittests/test_virtual_machine.py b/unittests/test_virtual_machine5.py similarity index 56% rename from unittests/test_virtual_machine.py rename to unittests/test_virtual_machine5.py index ac405269..89196e52 100644 --- a/unittests/test_virtual_machine.py +++ b/unittests/test_virtual_machine5.py @@ -14,49 +14,25 @@ import unittest from spinn_utilities.config_holder import set_config + +from spinn_machine import virtual_machine from spinn_machine.config_setup import unittest_setup -from spinn_machine import Chip, Link, Router, virtual_machine -from spinn_machine.exceptions import ( - SpinnMachineException, SpinnMachineAlreadyExistsException) -from spinn_machine.ignores import IgnoreChip, IgnoreCore, IgnoreLink -from spinn_machine.machine_factory import machine_repair +from spinn_machine.data import MachineDataView +from spinn_machine.exceptions import SpinnMachineException +from spinn_machine.link_data_objects import SpinnakerLinkData from spinn_machine.version.version_5 import CHIPS_PER_BOARD -from .geometry import (to_xyz, shortest_mesh_path_length, - shortest_torus_path_length, minimise_xyz) +from spinn_machine.virtual_machine import ( + virtual_machine_by_boards, virtual_machine_by_chips, + virtual_machine_by_cores, virtual_machine_by_min_size) -class TestVirtualMachine(unittest.TestCase): +class TestVirtualMachine5(unittest.TestCase): VERSION_5_N_CORES_PER_BOARD = sum(CHIPS_PER_BOARD.values()) def setUp(self): unittest_setup() - def _create_chip(self, x, y): - # Create a list of processors. - - n_processors = 18 - - links = list() - links.append(Link(0, 0, 0, 1, 1)) - links.append(Link(0, 1, 1, 1, 0)) - links.append(Link(1, 1, 2, 0, 0)) - links.append(Link(1, 0, 3, 0, 1)) - _router = Router(links, 1024) - - _sdram = 128 - nearest_ethernet_chip = (0, 0) - _ip = "192.162.240.253" - - if (x == y == 0): - return Chip(x, y, n_processors, _router, _sdram, - nearest_ethernet_chip[0], - nearest_ethernet_chip[1], _ip) - else: - return Chip(x, y, n_processors, _router, _sdram, - nearest_ethernet_chip[0], - nearest_ethernet_chip[1], None) - def test_illegal_vms(self): set_config("Machine", "version", 5) with self.assertRaises(SpinnMachineException): @@ -65,56 +41,37 @@ def test_illegal_vms(self): virtual_machine(width=2, height=-1) with self.assertRaises(SpinnMachineException): virtual_machine(width=15, height=15) + with self.assertRaises(SpinnMachineException): + virtual_machine(5, 7) + size_x = 12 * 5 + size_y = 12 * 7 + with self.assertRaises(SpinnMachineException): + virtual_machine(size_x + 1, size_y, validate=True) + size_x = 12 * 5 + size_y = None + with self.assertRaises(SpinnMachineException): + virtual_machine(size_x, size_y, validate=True) - def test_version_2(self): - set_config("Machine", "version", 2) - vm = virtual_machine(width=2, height=2) - self.assertEqual(4, vm.n_chips) - self.assertTrue(vm.is_chip_at(0, 0)) - self.assertTrue(vm.is_chip_at(0, 1)) - self.assertTrue(vm.is_chip_at(1, 0)) - self.assertTrue(vm.is_chip_at(1, 1)) + def test_version_5(self): + set_config("Machine", "version", 5) + vm = virtual_machine(width=8, height=8) + self.assertEqual(48, vm.n_chips) self.assertEqual(1, len(vm.ethernet_connected_chips)) - self.assertTrue(vm.is_link_at(0, 0, 0)) - self.assertTrue(vm.is_link_at(0, 0, 1)) - self.assertTrue(vm.is_link_at(0, 0, 2)) - self.assertFalse(vm.is_link_at(0, 0, 3)) - self.assertFalse(vm.is_link_at(0, 0, 4)) - self.assertTrue(vm.is_link_at(0, 0, 5)) - self.assertTrue(vm.is_link_at(0, 1, 0)) - self.assertTrue(vm.is_link_at(0, 1, 1)) - self.assertTrue(vm.is_link_at(0, 1, 2)) - self.assertFalse(vm.is_link_at(0, 1, 3)) - self.assertFalse(vm.is_link_at(0, 1, 4)) - self.assertTrue(vm.is_link_at(0, 1, 5)) - - self.assertFalse(vm.is_link_at(1, 0, 0)) - self.assertFalse(vm.is_link_at(1, 0, 1)) - self.assertTrue(vm.is_link_at(1, 0, 2)) - self.assertTrue(vm.is_link_at(1, 0, 3)) - self.assertTrue(vm.is_link_at(1, 0, 4)) - self.assertTrue(vm.is_link_at(1, 0, 5)) - self.assertFalse(vm.is_link_at(1, 1, 0)) - self.assertFalse(vm.is_link_at(1, 1, 1)) - self.assertTrue(vm.is_link_at(1, 1, 2)) - self.assertTrue(vm.is_link_at(1, 1, 3)) - self.assertTrue(vm.is_link_at(1, 1, 4)) - self.assertTrue(vm.is_link_at(1, 1, 5)) count = 0 for _chip in vm.chips: for _link in _chip.router.links: count += 1 - self.assertEqual(16, count) + self.assertEqual(240, count) + self.assertEqual(120, vm.get_links_count()) # self.assertEqual(str(vm), # "[VirtualMachine: max_x=1, max_y=1, n_chips=4]") - self.assertEqual(72, vm.get_cores_count()) - self.assertEqual(8, vm.get_links_count()) + self.assertEqual(856, vm.get_cores_count()) count = 0 for _chip in vm.get_existing_xys_on_board(vm[1, 1]): count += 1 - self.assertEqual(4, count) - self.assertEqual((2, 0), vm.get_unused_xy()) + self.assertEqual(48, count) + self.assertEqual((0, 4), vm.get_unused_xy()) def test_version_5_8_by_8(self): set_config("Machine", "version", 5) @@ -128,6 +85,88 @@ def test_version_5_8_by_8(self): self.assertEqual(240, count) count = sum(_chip.n_processors for _chip in vm.chips) self.assertEqual(count, 856) + spinnaker_links = (list(vm.spinnaker_links)) + expected = [] + sp = SpinnakerLinkData(0, 0, 0, 4, '127.0.0.0') + expected.append((('127.0.0.0', 0), sp)) + expected.append((((0, 0), 0), sp)) + self.assertEqual(expected, spinnaker_links) + # 8 links on each of the 6 sides recorded 3 times + # Except for the Ethernet Chip's 3 links which are only recorded twice + expected_fpgas = 8 * 6 * 3 - 3 + self.assertEqual(expected_fpgas, len(vm._fpga_links)) + keys = set([('127.0.0.0', 0, 0), ((7, 3), 0, 0), ((0, 0), 0, 0), + ('127.0.0.0', 0, 1), ((7, 3), 0, 1), ((0, 0), 0, 1), + ('127.0.0.0', 0, 2), ((6, 2), 0, 2), ((0, 0), 0, 2), + ('127.0.0.0', 0, 3), ((6, 2), 0, 3), ((0, 0), 0, 3), + ('127.0.0.0', 0, 4), ((5, 1), 0, 4), ((0, 0), 0, 4), + ('127.0.0.0', 0, 5), ((5, 1), 0, 5), ((0, 0), 0, 5), + ('127.0.0.0', 0, 6), ((4, 0), 0, 6), ((0, 0), 0, 6), + ('127.0.0.0', 0, 7), ((4, 0), 0, 7), ((0, 0), 0, 7), + ('127.0.0.0', 0, 8), ((4, 0), 0, 8), ((0, 0), 0, 8), + ('127.0.0.0', 0, 9), ((3, 0), 0, 9), ((0, 0), 0, 9), + ('127.0.0.0', 0, 10), ((3, 0), 0, 10), ((0, 0), 0, 10), + ('127.0.0.0', 0, 11), ((2, 0), 0, 11), ((0, 0), 0, 11), + ('127.0.0.0', 0, 12), ((2, 0), 0, 12), ((0, 0), 0, 12), + ('127.0.0.0', 0, 13), ((1, 0), 0, 13), ((0, 0), 0, 13), + ('127.0.0.0', 0, 14), ((1, 0), 0, 14), ((0, 0), 0, 14), + ('127.0.0.0', 0, 15), ((0, 0), 0, 15), + ('127.0.0.0', 1, 0), ((0, 0), 1, 0), + ('127.0.0.0', 1, 1), ((0, 0), 1, 1), + ('127.0.0.0', 1, 2), ((0, 1), 1, 2), ((0, 0), 1, 2), + ('127.0.0.0', 1, 3), ((0, 1), 1, 3), ((0, 0), 1, 3), + ('127.0.0.0', 1, 4), ((0, 2), 1, 4), ((0, 0), 1, 4), + ('127.0.0.0', 1, 5), ((0, 2), 1, 5), ((0, 0), 1, 5), + ('127.0.0.0', 1, 6), ((0, 3), 1, 6), ((0, 0), 1, 6), + ('127.0.0.0', 1, 7), ((0, 3), 1, 7), ((0, 0), 1, 7), + ('127.0.0.0', 1, 8), ((0, 3), 1, 8), ((0, 0), 1, 8), + ('127.0.0.0', 1, 9), ((1, 4), 1, 9), ((0, 0), 1, 9), + ('127.0.0.0', 1, 10), ((1, 4), 1, 10), ((0, 0), 1, 10), + ('127.0.0.0', 1, 11), ((2, 5), 1, 11), ((0, 0), 1, 11), + ('127.0.0.0', 1, 12), ((2, 5), 1, 12), ((0, 0), 1, 12), + ('127.0.0.0', 1, 13), ((3, 6), 1, 13), ((0, 0), 1, 13), + ('127.0.0.0', 1, 14), ((3, 6), 1, 14), ((0, 0), 1, 14), + ('127.0.0.0', 1, 15), ((4, 7), 1, 15), ((0, 0), 1, 15), + ('127.0.0.0', 2, 0), ((4, 7), 2, 0), ((0, 0), 2, 0), + ('127.0.0.0', 2, 1), ((4, 7), 2, 1), ((0, 0), 2, 1), + ('127.0.0.0', 2, 2), ((5, 7), 2, 2), ((0, 0), 2, 2), + ('127.0.0.0', 2, 3), ((5, 7), 2, 3), ((0, 0), 2, 3), + ('127.0.0.0', 2, 4), ((6, 7), 2, 4), ((0, 0), 2, 4), + ('127.0.0.0', 2, 5), ((6, 7), 2, 5), ((0, 0), 2, 5), + ('127.0.0.0', 2, 6), ((7, 7), 2, 6), ((0, 0), 2, 6), + ('127.0.0.0', 2, 7), ((7, 7), 2, 7), ((0, 0), 2, 7), + ('127.0.0.0', 2, 8), ((7, 7), 2, 8), ((0, 0), 2, 8), + ('127.0.0.0', 2, 9), ((7, 6), 2, 9), ((0, 0), 2, 9), + ('127.0.0.0', 2, 10), ((7, 6), 2, 10), ((0, 0), 2, 10), + ('127.0.0.0', 2, 11), ((7, 5), 2, 11), ((0, 0), 2, 11), + ('127.0.0.0', 2, 12), ((7, 5), 2, 12), ((0, 0), 2, 12), + ('127.0.0.0', 2, 13), ((7, 4), 2, 13), ((0, 0), 2, 13), + ('127.0.0.0', 2, 14), ((7, 4), 2, 14), ((0, 0), 2, 14), + ('127.0.0.0', 2, 15), ((7, 3), 2, 15), ((0, 0), 2, 15)]) + self.assertEqual(keys, set(vm._fpga_links.keys())) + data = set() + for (_, fpga_id, fpga_link), link in vm._fpga_links.items(): + data.add((link.connected_chip_x, link.connected_chip_y, + link.connected_link, fpga_id, fpga_link)) + expected = set([(7, 3, 0, 0, 0), (7, 3, 5, 0, 1), (6, 2, 0, 0, 2), + (6, 2, 5, 0, 3), (5, 1, 0, 0, 4), (5, 1, 5, 0, 5), + (4, 0, 0, 0, 6), (4, 0, 5, 0, 7), (4, 0, 4, 0, 8), + (3, 0, 5, 0, 9), (3, 0, 4, 0, 10), (2, 0, 5, 0, 11), + (2, 0, 4, 0, 12), (1, 0, 5, 0, 13), (1, 0, 4, 0, 14), + (0, 0, 5, 0, 15), + (0, 0, 4, 1, 0), (0, 0, 3, 1, 1), (0, 1, 4, 1, 2), + (0, 1, 3, 1, 3), (0, 2, 4, 1, 4), (0, 2, 3, 1, 5), + (0, 3, 4, 1, 6), (0, 3, 3, 1, 7), (0, 3, 2, 1, 8), + (1, 4, 3, 1, 9), (1, 4, 2, 1, 10), (2, 5, 3, 1, 11), + (2, 5, 2, 1, 12), (3, 6, 3, 1, 13), (3, 6, 2, 1, 14), + (4, 7, 3, 1, 15), + (4, 7, 2, 2, 0), (4, 7, 1, 2, 1), (5, 7, 2, 2, 2), + (5, 7, 1, 2, 3), (6, 7, 2, 2, 4), (6, 7, 1, 2, 5), + (7, 7, 2, 2, 6), (7, 7, 1, 2, 7), (7, 7, 0, 2, 8), + (7, 6, 1, 2, 9), (7, 6, 0, 2, 10), (7, 5, 1, 2, 11), + (7, 5, 0, 2, 12), (7, 4, 1, 2, 13), (7, 4, 0, 2, 14), + (7, 3, 1, 2, 15)]) + self.assertEqual(data, expected) def test_version_5_12_by_12(self): set_config("Machine", "version", 5) @@ -141,6 +180,39 @@ def test_version_5_12_by_12(self): count += 1 self.assertEqual(48, count) self.assertEqual((12, 0), vm.get_unused_xy()) + spinnaker_links = (list(vm.spinnaker_links)) + expected = [] + self.assertEqual(expected, spinnaker_links) + # 8 links on each of the 6 sides recorded 3 times + # Except for the Ethernet Chip's 3 links which are only recorded twice + expected_fpgas = (8 * 6 * 3 - 3) * 3 + self.assertEqual(expected_fpgas, len(vm._fpga_links)) + + def test_version_5_16_by_16(self): + set_config("Machine", "version", 5) + vm = virtual_machine(height=16, width=16, validate=True) + self.assertEqual(144, vm.n_chips) + self.assertEqual(3, len(vm.ethernet_connected_chips)) + count = sum(1 for _chip in vm.chips for _link in _chip.router.links) + self.assertEqual(768, count) + count = 0 + for _chip in vm.get_existing_xys_on_board(vm[1, 1]): + count += 1 + self.assertEqual(48, count) + self.assertEqual((0, 4), vm.get_unused_xy()) + spinnaker_links = (list(vm.spinnaker_links)) + expected = [] + sp = SpinnakerLinkData(0, 0, 0, 4, '127.0.0.0') + expected.append((('127.0.0.0', 0), sp)) + expected.append((((0, 0), 0), sp)) + sp = SpinnakerLinkData(0, 4, 8, 4, '127.0.4.8') + expected.append((('127.0.4.8', 0), sp)) + expected.append((((4, 8), 0), sp)) + self.assertEqual(expected, spinnaker_links) + # 8 links on each of the 6 sides recorded 3 times + # Except for the Ethernet Chip's 3 links which are only recorded twice + expected_fpgas = (8 * 6 * 3 - 3) * 3 + self.assertEqual(expected_fpgas, len(vm._fpga_links)) def test_version_5_hole(self): set_config("Machine", "version", 5) @@ -169,53 +241,6 @@ def test_version_5_hole2(self): self.assertEqual(48, len(list(vm.local_xys))) self.assertEqual((0, 4), vm.get_unused_xy()) - def test_new_vm_with_max_cores(self): - set_config("Machine", "version", 2) - n_cpus = 13 - set_config("Machine", "max_machine_core", n_cpus) - vm = virtual_machine(2, 2, validate=True) - _chip = vm[1, 1] - self.assertEqual(n_cpus, _chip.n_processors) - self.assertEqual(n_cpus - 1, _chip.n_placable_processors) - self.assertEqual(1, _chip.n_scamp_processors) - self.assertEqual(n_cpus - 1, len(list(_chip.placable_processors_ids))) - self.assertEqual(1, len(list(_chip.scamp_processors_ids))) - count = sum(_chip.n_processors for _chip in vm.chips) - self.assertEqual(count, 4 * n_cpus) - - def test_iter_chips(self): - set_config("Machine", "version", 2) - vm = virtual_machine(2, 2) - self.assertEqual(4, vm.n_chips) - count = 0 - for _chip in vm.chips: - count += 1 - self.assertEqual(4, count) - - def test_down_chip(self): - set_config("Machine", "version", 2) - down_chips = set() - down_chips.add((1, 1)) - set_config("Machine", "down_chips", "1,1") - vm = virtual_machine(2, 2) - self.assertEqual(3, vm.n_chips) - count = 0 - for _chip in vm.chip_coordinates: - count += 1 - self.assertNotIn(_chip, down_chips) - self.assertEqual(3, count) - - def test_add_existing_chip(self): - set_config("Machine", "version", 2) - vm = virtual_machine(2, 2) - _chip = self._create_chip(1, 1) - with self.assertRaises(SpinnMachineAlreadyExistsException): - vm.add_chip(_chip) - - def test_weird_size(self): - with self.assertRaises(SpinnMachineException): - virtual_machine(5, 7) - def test_12_n_plus4_12_m_4(self): set_config("Machine", "version", 5) size_x = 12 * 5 @@ -230,94 +255,13 @@ def test_12_n_12_m(self): vm = virtual_machine(size_x, size_y, validate=True) self.assertEqual(size_x * size_y, vm.n_chips) - def test_bad_size(self): - set_config("Machine", "version", 5) - size_x = 12 * 5 - size_y = 12 * 7 - with self.assertRaises(SpinnMachineException): - virtual_machine(size_x + 1, size_y, validate=True) - - def test_none_size(self): - set_config("Machine", "version", 5) - size_x = 12 * 5 - size_y = None - with self.assertRaises(SpinnMachineException): - virtual_machine(size_x, size_y, validate=True) - - def test_add__chip(self): - set_config("Machine", "version", 2) - vm = virtual_machine(2, 2) - _chip = self._create_chip(2, 2) - vm.add_chip(_chip) - self.assertEqual(5, vm.n_chips) - - self.assertTrue(vm.is_chip_at(2, 2)) - _good = vm.get_chip_at(2, 2) - self.assertEqual(_chip, _good) - - _bad = vm.get_chip_at(2, 1) - self.assertIsNone(_bad) - - count = 0 - for _chip in vm.chips: - count += 1 - self.assertEqual(5, count) - - def test_add_high_chip_with_down(self): - set_config("Machine", "version", 2) - set_config("Machine", "down_chips", "1,1") - vm = virtual_machine(2, 2) - self.assertEqual(3, vm.n_chips) - - _chip = self._create_chip(2, 2) - vm.add_chip(_chip) - self.assertEqual(4, vm.n_chips) - - self.assertTrue(vm.is_chip_at(2, 2)) - _good = vm.get_chip_at(2, 2) - self.assertEqual(_chip, _good) - - _bad = vm.get_chip_at(2, 1) - self.assertIsNone(_bad) - - _down = vm.get_chip_at(1, 1) - self.assertIsNone(_down) - - count = 0 - for _chip in vm.chips: - count += 1 - self.assertEqual(4, count) - - def test_add_low_chip_with_down(self): - set_config("Machine", "version", 2) - set_config("Machine", "down_chips", "1,1") - vm = virtual_machine(2, 2) - self.assertEqual(3, vm.n_chips) - self.assertFalse(vm.is_chip_at(1, 1)) - - _chip = self._create_chip(1, 1) - vm.add_chip(_chip) - self.assertEqual(4, vm.n_chips) - - self.assertTrue(vm.is_chip_at(1, 1)) - _good = vm.get_chip_at(1, 1) - self.assertEqual(_chip, _good) - - _bad = vm.get_chip_at(2, 1) - self.assertIsNone(_bad) - - count = 0 - for _chip in vm.chips: - count += 1 - self.assertEqual(4, count) - def test_chips(self): - set_config("Machine", "version", 2) - vm = virtual_machine(2, 2) + set_config("Machine", "version", 5) + vm = virtual_machine(8, 8) count = 0 for _chip in vm.chips: count += 1 - self.assertEqual(count, 4) + self.assertEqual(count, 48) def test_ethernet_chips_exist(self): set_config("Machine", "version", 5) @@ -329,9 +273,10 @@ def test_ethernet_chips_exist(self): .format(eth_chip.x, eth_chip.y)) def test_boot_chip(self): - set_config("Machine", "version", 2) - vm = virtual_machine(2, 2) - self.assertNotEqual(vm.boot_chip, None) + set_config("Machine", "version", 5) + vm = virtual_machine(12, 12) + # based on Chip equaling its XY + self.assertEqual(vm.boot_chip, (0, 0)) def test_get_chips_on_boards(self): set_config("Machine", "version", 5) @@ -460,26 +405,6 @@ def test_big(self): virtual_machine( width=240, height=240, validate=True) - def test_size_2_2(self): - set_config("Machine", "version", 2) - machine = virtual_machine(2, 2, validate=True) - ethernet = machine[0, 0] - chips = set(machine.get_existing_xys_on_board(ethernet)) - self.assertEqual(len(chips), 4) - chips = set(machine.get_existing_xys_by_ethernet(0, 0)) - self.assertEqual(len(chips), 4) - global_xys = set() - for chip in machine.chips: - local_x, local_y = machine.get_local_xy(chip) - global_x, global_y = machine.get_global_xy( - local_x, local_y, - chip.nearest_ethernet_x, chip.nearest_ethernet_y) - self.assertEqual(global_x, chip.x) - self.assertEqual(global_y, chip.y) - global_xys.add((global_x, global_y)) - self.assertEqual(len(global_xys), 4) - self.assertEqual(4, len(list(machine.local_xys))) - def test_48_28(self): set_config("Machine", "version", 5) machine = virtual_machine(48, 24, validate=True) @@ -540,24 +465,6 @@ def test_52_24(self): self.assertEqual(len(global_xys), 48 * 24) self.assertEqual(48, len(list(machine.local_xys))) - def test_size_2_2_hole(self): - set_config("Machine", "version", 2) - hole = [(1, 1)] - set_config("Machine", "down_chips", "1,1") - machine = virtual_machine(2, 2, validate=True) - self.assertEqual(4, len(list(machine.get_xys_by_ethernet(0, 0)))) - count = 0 - for chip in machine.get_chips_by_ethernet(0, 0): - count += 1 - xy = (chip.x, chip.y) - assert xy not in hole - self.assertEqual(3, count) - count = 0 - for xy in machine.get_existing_xys_by_ethernet(0, 0): - count += 1 - assert xy not in hole - self.assertEqual(3, count) - def test_fullwrap_holes(self): set_config("Machine", "version", 5) hole = [(1, 1), (7, 7), (8, 1), (8, 10), (1, 8), (9, 6)] @@ -762,261 +669,6 @@ def test_no_wrap_holes(self): assert xy not in hole self.assertEqual(46, count) - def _check_path(self, source, target, path, width, height): - new_target = ((source[0] + path[0] - path[2]) % width, - (source[1] + path[1] - path[2]) % height) - self.assertEqual(target, new_target, "{}{}".format(source, path)) - - def test_nowrap_shortest_path(self): - set_config("Machine", "version", 5) - machine = virtual_machine(16, 28, validate=True) - for source in machine.chip_coordinates: - for target in machine.chip_coordinates: - rig_len = shortest_mesh_path_length( - to_xyz(source), to_xyz(target)) - mac_len = machine.get_vector_length(source, target) - self.assertEqual(rig_len, mac_len) - path = machine.get_vector(source, target) - self.assertEqual( - mac_len, abs(path[0]) + abs(path[1]) + abs(path[2])) - self._check_path(source, target, path, 1000000, 1000000) - - def test_fullwrap_shortest_path(self): - set_config("Machine", "version", 5) - width = 12 - height = 24 - machine = virtual_machine(width, height, validate=True) - for source in machine.chip_coordinates: - for target in machine.chip_coordinates: - rig_len = shortest_torus_path_length( - to_xyz(source), to_xyz(target), width, height) - mac_len = machine.get_vector_length(source, target) - self.assertEqual(rig_len, mac_len) - path = machine.get_vector(source, target) - self.assertEqual( - mac_len, abs(path[0]) + abs(path[1]) + abs(path[2]), - "{}{}{}".format(source, target, path)) - self._check_path(source, target, path, width, height) - - def test_hoizontal_wrap_shortest_path(self): - set_config("Machine", "version", 5) - width = 12 - height = 16 - machine = virtual_machine(width, height, validate=False) - for source in machine.chip_coordinates: - for target in machine.chip_coordinates: - rig_no = shortest_mesh_path_length( - to_xyz(source), to_xyz(target)) - if source[0] < target[0]: - fake = (target[0] - width, target[1]) - else: - fake = (target[0] + width, target[1]) - rig_with = shortest_mesh_path_length( - to_xyz(source), to_xyz(fake)) - rig_len = min(rig_no, rig_with) - mac_len = machine.get_vector_length(source, target) - self.assertEqual(rig_len, mac_len, "{} {}".format( - source, target)) - path = machine.get_vector(source, target) - self.assertEqual( - mac_len, abs(path[0]) + abs(path[1]) + abs(path[2]), - "{}{}{}".format(source, target, path)) - self._check_path(source, target, path, width, height) - - def test_vertical_wrap_shortest_path(self): - set_config("Machine", "version", 5) - width = 16 - height = 12 - machine = virtual_machine(width, height, validate=False) - for source in machine.chip_coordinates: - for target in machine.chip_coordinates: - rig_no = shortest_mesh_path_length( - to_xyz(source), to_xyz(target)) - if source[1] < target[1]: - fake = (target[0], target[1] - height) - else: - fake = (target[0], target[1] + height) - rig_with = shortest_mesh_path_length( - to_xyz(source), to_xyz(fake)) - rig_len = min(rig_no, rig_with) - mac_len = machine.get_vector_length(source, target) - self.assertEqual(rig_len, mac_len, "{} {}".format( - source, target)) - path = machine.get_vector(source, target) - self.assertEqual( - mac_len, abs(path[0]) + abs(path[1]) + abs(path[2]), - "{}{}{}".format(source, target, path)) - self._check_path(source, target, path, width, height) - - def test_minimize(self): - set_config("Machine", "version", 3) - machine = virtual_machine(2, 2, validate=False) - for x in range(-3, 3): - for y in range(-3, 3): - min1 = minimise_xyz((x, y, 0)) - min2 = machine._minimize_vector(x, y) - self.assertEqual(min1, min2) - - def test_unreachable_incoming_chips(self): - set_config("Machine", "version", 5) - machine = virtual_machine(8, 8) - - # Delete links incoming to 3, 3 - down_links = [ - (2, 2, 1), (2, 3, 0), (3, 4, 5), (4, 4, 4), (4, 3, 3), (3, 2, 2)] - for (x, y, link) in down_links: - if machine.is_link_at(x, y, link): - del machine._chips[x, y].router._links[link] - unreachable = machine.unreachable_incoming_chips() - self.assertListEqual([(3, 3)], unreachable) - - def test_unreachable_outgoing_chips(self): - set_config("Machine", "version", 5) - machine = virtual_machine(8, 8) - - # Delete links outgoing from 3, 3 - for link in range(6): - if machine.is_link_at(3, 3, link): - del machine._chips[3, 3].router._links[link] - unreachable = machine.unreachable_outgoing_chips() - self.assertListEqual([(3, 3)], unreachable) - - def test_unreachable_incoming_local_chips(self): - set_config("Machine", "version", 5) - down_chips = [(8, 6), (9, 7), (9, 8)] - down_str = ":".join([f"{x},{y}" for x, y in down_chips]) - set_config("Machine", "down_chips", down_str) - machine = virtual_machine(16, 16) - unreachable = machine.unreachable_incoming_local_chips() - self.assertListEqual([(8, 7)], unreachable) - - def test_unreachable_outgoing_local_chips(self): - set_config("Machine", "version", 5) - down_chips = [(8, 6), (9, 7), (9, 8)] - down_str = ":".join([f"{x},{y}" for x, y in down_chips]) - set_config("Machine", "down_chips", down_str) - machine = virtual_machine(16, 16) - unreachable = machine.unreachable_outgoing_local_chips() - self.assertListEqual([(8, 7)], unreachable) - - def test_repair_with_local_orphan(self): - set_config("Machine", "version", 5) - down_chips = [(8, 6), (9, 7), (9, 8)] - down_str = ":".join([f"{x},{y}" for x, y in down_chips]) - set_config("Machine", "down_chips", down_str) - machine = virtual_machine(16, 16) - with self.assertRaises(SpinnMachineException): - set_config("Machine", "repair_machine", False) - repaired = machine_repair(machine) - set_config("Machine", "repair_machine", True) - repaired = machine_repair(machine) - self.assertTrue(machine.is_chip_at(8, 7)) - self.assertFalse(repaired.is_chip_at(8, 7)) - - def test_repair_with_one_way_links_different_boards(self): - set_config("Machine", "version", 5) - machine = virtual_machine(12, 12) - # Delete some links between boards - down_links = [ - (7, 7, 0), (7, 3, 1), (6, 7, 2), (4, 7, 3), (8, 6, 4), (8, 4, 5)] - for (x, y, link) in down_links: - del machine._chips[x, y].router._links[link] - with self.assertRaises(SpinnMachineException): - set_config("Machine", "repair_machine", False) - new_machine = machine_repair(machine) - set_config("Machine", "repair_machine", True) - new_machine = machine_repair(machine) - self.assertIsNotNone(new_machine) - - def test_oneway_link_no_repair(self): - set_config("Machine", "version", 5) - machine = virtual_machine(8, 8) - - # Delete some random links - down_links = [ - (3, 6, 0), (5, 4, 1), (3, 2, 5), (1, 3, 3)] - for (x, y, link) in down_links: - if machine.is_link_at(x, y, link): - del machine._chips[x, y].router._links[link] - with self.assertRaises(SpinnMachineException): - set_config("Machine", "repair_machine", False) - new_machine = machine_repair(machine) - set_config("Machine", "repair_machine", True) - new_machine = machine_repair(machine) - self.assertIsNotNone(new_machine) - - def test_removed_chip_repair(self): - set_config("Machine", "version", 5) - machine = virtual_machine(8, 8) - - del machine._chips[(3, 3)] - set_config("Machine", "repair_machine", False) - new_machine = machine_repair(machine, [(3, 3)]) - self.assertIsNotNone(new_machine) - self.assertFalse(new_machine.is_link_at(2, 2, 1)) - - def test_ignores(self): - set_config("Machine", "version", 5) - set_config("Machine", "down_chips", "2,2:4,4:6,6,ignored_ip") - set_config("Machine", "down_cores", - "1,1,1:3,3,3: 5,5,-5:7,7,7,ignored_ip:0,0,5-10") - set_config("Machine", "down_links", "1,3,3:3,5,3:5,3,3,ignored_ip") - - machine = virtual_machine(8, 8) - - self.assertFalse(machine.is_chip_at(4, 4)) - self.assertFalse(machine.is_chip_at(2, 2)) - self.assertTrue(machine.is_chip_at(6, 6)) - self.assertTrue(machine.is_chip_at(0, 0)) - - chip = machine[3, 3] - self.assertFalse(chip.is_processor_with_id(3)) - - chip = machine[5, 5] - self.assertFalse(chip.is_processor_with_id(6)) - - chip = machine[7, 7] - self.assertTrue(chip.is_processor_with_id(6)) - - chip = machine[1, 1] - self.assertFalse(chip.is_processor_with_id(1)) - - router = machine[1, 3].router - self.assertFalse(router.is_link(3)) - - router = machine[3, 5].router - self.assertFalse(router.is_link(3)) - - router = machine[5, 3].router - self.assertTrue(router.is_link(3)) - - chip = machine[0, 0] - for i in range(0, 5): - self.assertTrue(chip.is_processor_with_id(i)) - for i in range(5, 11): - self.assertFalse(chip.is_processor_with_id(i)) - for i in range(12, 18): - self.assertTrue(chip.is_processor_with_id(i)) - - def test_bad_ignores(self): - try: - IgnoreChip.parse_string("4,4,3,4:6,6,ignored_ip") - except Exception as ex: - self.assertTrue("downed_chip" in str(ex)) - - try: - IgnoreCore.parse_string("3,3,3,4: 5,5,-5:7,7,7,ignored_ip") - except Exception as ex: - self.assertTrue("downed_core" in str(ex)) - - empty = IgnoreCore.parse_string(None) - self.assertEqual(len(empty), 0) - - try: - IgnoreLink.parse_string("1,3:5,3,3,ignored_ip") - except Exception as ex: - self.assertTrue("downed_link" in str(ex)) - def test_n_cores_full_wrap(self): set_config("Machine", "version", 5) machine = virtual_machine(12, 12) @@ -1077,14 +729,96 @@ def test_n_cores_8_8(self): n_cores = sum(chip.n_processors for chip in machine.chips) self.assertEqual(n_cores, self.VERSION_5_N_CORES_PER_BOARD) - def test_n_cores_2_2(self): - set_config("Machine", "version", 2) - machine = virtual_machine(2, 2) - n_cores = sum( - cores for (_, cores) in machine.get_xy_cores_by_ethernet(0, 0)) - self.assertEqual(n_cores, 4 * 18) - n_cores = sum(chip.n_processors for chip in machine.chips) - self.assertEqual(n_cores, 4 * 18) + def test_8_8_by_cores_1_board(self): + set_config("Machine", "version", 5) + version = MachineDataView.get_machine_version() + n_cores = sum(version.chip_core_map.values()) + n_cores -= version.n_chips_per_board + machine = virtual_machine_by_cores(n_cores) + self.assertEqual(8, machine.width) + self.assertEqual(8, machine.height) + self.assertEqual(n_cores, machine.total_available_user_cores) + machine = virtual_machine_by_boards(1) + self.assertEqual(8, machine.width) + self.assertEqual(8, machine.height) + self.assertEqual(n_cores, machine.total_available_user_cores) + + def test_8_8_by_cores_3_boards(self): + set_config("Machine", "version", 5) + version = MachineDataView.get_machine_version() + n_cores = sum(version.chip_core_map.values()) + n_cores -= version.n_chips_per_board + machine = virtual_machine_by_cores(n_cores * 2) + # despite asking for two boards you get a triad + self.assertEqual(16, machine.width) + self.assertEqual(16, machine.height) + self.assertEqual(n_cores*3, machine.total_available_user_cores) + machine = virtual_machine_by_boards(2) + # despite asking for two boards you get a triad + self.assertEqual(16, machine.width) + self.assertEqual(16, machine.height) + self.assertEqual(n_cores*3, machine.total_available_user_cores) + machine = virtual_machine_by_chips(100) + # despite asking for two boards you get a triad + self.assertEqual(16, machine.width) + self.assertEqual(16, machine.height) + self.assertEqual(n_cores*3, machine.total_available_user_cores) + + def test_8_8_by_cores_6_boards(self): + set_config("Machine", "version", 5) + version = MachineDataView.get_machine_version() + n_cores = sum(version.chip_core_map.values()) + n_cores -= version.n_chips_per_board + machine = virtual_machine_by_cores(n_cores * 5) + self.assertEqual(28, machine.width) + self.assertEqual(16, machine.height) + self.assertEqual(n_cores * 6, machine.total_available_user_cores) + machine = virtual_machine_by_boards(4) + self.assertEqual(28, machine.width) + self.assertEqual(16, machine.height) + self.assertEqual(n_cores * 6, machine.total_available_user_cores) + + def test_8_8_by_cores_12_boards(self): + set_config("Machine", "version", 5) + version = MachineDataView.get_machine_version() + n_cores = sum(version.chip_core_map.values()) + n_cores -= version.n_chips_per_board + machine = virtual_machine_by_cores(n_cores * 9) + self.assertEqual(28, machine.width) + self.assertEqual(28, machine.height) + self.assertEqual(n_cores * 12, machine.total_available_user_cores) + machine = virtual_machine_by_boards(10) + self.assertEqual(28, machine.width) + self.assertEqual(28, machine.height) + self.assertEqual(n_cores * 12, machine.total_available_user_cores) + + def test_8_8_by_cores_18_boards(self): + set_config("Machine", "version", 5) + version = MachineDataView.get_machine_version() + n_cores = sum(version.chip_core_map.values()) + n_cores -= version.n_chips_per_board + machine = virtual_machine_by_cores(n_cores * 12 + 1) + self.assertEqual(40, machine.width) + self.assertEqual(28, machine.height) + self.assertEqual(n_cores * 18, machine.total_available_user_cores) + machine = virtual_machine_by_boards(15) + self.assertEqual(40, machine.width) + self.assertEqual(28, machine.height) + self.assertEqual(n_cores * 18, machine.total_available_user_cores) + + def test_by_min_size(self): + set_config("Machine", "version", 5) + machine = virtual_machine_by_min_size(15, 21) + self.assertGreaterEqual(machine.width, 15) + self.assertGreaterEqual(machine.height, 21) + + def test_by_min_size_edge_case(self): + set_config("Machine", "version", 5) + version = MachineDataView.get_machine_version() + width, height = version.board_shape + machine = virtual_machine_by_min_size(width, height + 1) + self.assertGreaterEqual(machine.width, width) + self.assertGreaterEqual(machine.height, height + 1) if __name__ == '__main__': diff --git a/unittests/version/test_version201.py b/unittests/version/test_version201.py new file mode 100644 index 00000000..c30f4638 --- /dev/null +++ b/unittests/version/test_version201.py @@ -0,0 +1,146 @@ +# Copyright (c) 2024 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. + +""" +Testing Version5 +""" +import unittest +from spinn_utilities.config_holder import set_config +from spinn_machine.no_wrap_machine import NoWrapMachine +from spinn_machine.version.version_201 import Version201 +from spinn_machine.config_setup import unittest_setup +from spinn_machine.exceptions import SpinnMachineException + + +class TestVersion201(unittest.TestCase): + + def setUp(self): + unittest_setup() + + def test_attributes(self): + version = Version201() + self.assertEqual(152, version.max_cores_per_chip) + self.assertEqual(2**30, version.max_sdram_per_chip) + self.assertEqual(1, version.n_scamp_cores) + self.assertEqual("Spin2 1 Chip", version.name) + self.assertEqual(201, version.number) + self.assertEqual((1, 1), version.board_shape) + self.assertEqual(1, version.n_chips_per_board) + self.assertEqual(16384, version.n_router_entries) + + def test_verify_config_width_height(self): + set_config("Machine", "width", "None") + set_config("Machine", "height", "None") + Version201() + + set_config("Machine", "width", 1) + with self.assertRaises(SpinnMachineException): + Version201() + + set_config("Machine", "height", 1) + Version201() + + set_config("Machine", "width", "None") + with self.assertRaises(SpinnMachineException): + Version201() + + def test_set_max_lower(self): + set_config("Machine", "max_sdram_allowed_per_chip", 1000) + set_config("Machine", "max_machine_core", 100) + version = Version201() + self.assertEqual(100, version.max_cores_per_chip) + self.assertEqual(1000, version.max_sdram_per_chip) + + def test_expected_xys(self): + version = Version201() + xys = version.expected_xys + self.assertEqual(1, len(xys)) + self.assertEqual(1, len(set(xys))) + for (x, y) in xys: + self.assertGreaterEqual(x, 0) + self.assertGreaterEqual(y, 0) + self.assertLess(x, 1) + self.assertLess(y, 1) + + def test_expected_chip_core_map(self): + version = Version201() + chip_core_map = version.chip_core_map + self.assertEqual(1, len(chip_core_map)) + for (x, y) in chip_core_map: + self.assertGreaterEqual(x, 0) + self.assertGreaterEqual(y, 0) + self.assertLess(x, 1) + self.assertLess(y, 1) + cores = chip_core_map[(x, y)] + self.assertGreaterEqual(cores, 100) + self.assertLessEqual(cores, 152) + + def test_get_potential_ethernet_chips(self): + version = Version201() + eths = version.get_potential_ethernet_chips(0, 0) + self.assertListEqual([(0, 0)], eths) + + # if size is wromg GIGO + eths = version.get_potential_ethernet_chips(8, 8) + self.assertListEqual([(0, 0)], eths) + eths = version.get_potential_ethernet_chips(12, 12) + self.assertListEqual([(0, 0)], eths) + eths = version.get_potential_ethernet_chips(16, 16) + self.assertListEqual([(0, 0)], eths) + + def test_verify_size(self): + version = Version201() + + with self.assertRaises(SpinnMachineException): + version.verify_size(12, -12) + with self.assertRaises(SpinnMachineException): + version.verify_size(-12, 12) + with self.assertRaises(SpinnMachineException): + version.verify_size(12, None) + with self.assertRaises(SpinnMachineException): + version.verify_size(None, 12) + with self.assertRaises(SpinnMachineException): + version.verify_size(12, 8) + with self.assertRaises(SpinnMachineException): + version.verify_size(8, 12) + with self.assertRaises(SpinnMachineException): + version.verify_size(2, 2) + version.verify_size(1, 1) + with self.assertRaises(SpinnMachineException): + version.verify_size(8, 8) + with self.assertRaises(SpinnMachineException): + version.verify_size(12, 8) + with self.assertRaises(SpinnMachineException): + version.verify_size(12, 12) + with self.assertRaises(SpinnMachineException): + version.verify_size(12, 16) + with self.assertRaises(SpinnMachineException): + version.verify_size(16, 12) + with self.assertRaises(SpinnMachineException): + version.verify_size(16, 16) + + def test_create_machine(self): + version = Version201() + + machine = version.create_machine(width=1, height=1) + self.assertIsInstance(machine, NoWrapMachine) + + def test_processor_info(self): + version = Version201() + self.assertEqual([150, 300], version.clock_speeds_hz) + # self.assertEqual(65536, version.dtcm_bytes) + + +if __name__ == '__main__': + unittest.main() diff --git a/unittests/version/test_version3.py b/unittests/version/test_version3.py index 5d5795ed..c837392e 100644 --- a/unittests/version/test_version3.py +++ b/unittests/version/test_version3.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015 The University of Manchester +# Copyright (c) 2023 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. @@ -24,8 +24,7 @@ class TestVersion3(unittest.TestCase): - """ Tests of IPTag - """ + def setUp(self): unittest_setup() @@ -137,9 +136,23 @@ def test_create_machin(self): def test_processor_info(self): version = Version3() - self.assertEqual(200, version.clock_speed_hz) + self.assertEqual([200], version.clock_speeds_hz) self.assertEqual(65536, version.dtcm_bytes) + def test_size_from_n_cores(self): + version = Version3() + self.assertEqual((2, 2), version.size_from_n_cores(10)) + self.assertEqual((2, 2), version.size_from_n_cores(17 * 4)) + with self.assertRaises(SpinnMachineException): + version.size_from_n_cores(17 * 4 + 1) + + def test_size_from_n_chips(self): + version = Version3() + self.assertEqual((2, 2), version.size_from_n_chips(1)) + self.assertEqual((2, 2), version.size_from_n_chips(4)) + with self.assertRaises(SpinnMachineException): + version.size_from_n_chips(5) + if __name__ == '__main__': unittest.main() diff --git a/unittests/version/test_version5.py b/unittests/version/test_version5.py index a4ec4387..8faf84ac 100644 --- a/unittests/version/test_version5.py +++ b/unittests/version/test_version5.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015 The University of Manchester +# Copyright (c) 2023 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. @@ -27,8 +27,7 @@ class TestVersion5(unittest.TestCase): - """ Tests of IPTag - """ + def setUp(self): unittest_setup() @@ -147,9 +146,31 @@ def test_create_machin(self): def test_processor_info(self): version = Version5() - self.assertEqual(200, version.clock_speed_hz) + self.assertEqual([200], version.clock_speeds_hz) self.assertEqual(65536, version.dtcm_bytes) + def test_size_from_n_cores(self): + version = Version5() + self.assertEqual((8, 8), version.size_from_n_cores(10)) + # standard for there to be 8 17 core Chips and each has 1 scamp core + n_cores = 17 * 48 - 8 + self.assertEqual((8, 8), version.size_from_n_cores(n_cores)) + self.assertEqual((16, 16), version.size_from_n_cores(n_cores + 1)) + self.assertEqual((16, 16), version.size_from_n_cores(n_cores * 3)) + self.assertEqual((28, 16), version.size_from_n_cores(n_cores * 4)) + self.assertEqual((28, 16), version.size_from_n_cores(n_cores * 6)) + self.assertEqual((28, 28), version.size_from_n_cores(n_cores * 7)) + self.assertEqual((28, 28), version.size_from_n_cores(n_cores * 12)) + self.assertEqual((40, 28), version.size_from_n_cores(n_cores * 13)) + self.assertEqual((40, 28), version.size_from_n_cores(n_cores * 18)) + self.assertEqual((40, 40), version.size_from_n_cores(n_cores * 18 + 1)) + + def test_size_from_n_chips(self): + version = Version5() + self.assertEqual((8, 8), version.size_from_n_chips(1)) + self.assertEqual((8, 8), version.size_from_n_chips(48)) + self.assertEqual((16, 16), version.size_from_n_chips(49)) + if __name__ == '__main__': unittest.main() diff --git a/unittests/version/test_version_string.py b/unittests/version/test_version_string.py new file mode 100644 index 00000000..eeda4db1 --- /dev/null +++ b/unittests/version/test_version_string.py @@ -0,0 +1,39 @@ +# Copyright (c) 2024 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. + +""" +Testing Versions +""" +import unittest +from spinn_machine.config_setup import unittest_setup +from spinn_machine.exceptions import SpinnMachineException +from spinn_machine.version.version_strings import VersionStrings + + +class TestVersionString(unittest.TestCase): + + def setUp(self): + unittest_setup() + + def test_names(self): + vs = VersionStrings.from_string("any") + self.assertEqual(vs.text, "Any") + vs = VersionStrings.from_string("FourPlus") + self.assertEqual(vs.text, "Four plus") + with self.assertRaises(SpinnMachineException): + vs = VersionStrings.from_string("Foo") + + +if __name__ == '__main__': + unittest.main()