diff --git a/spinn_machine/__init__.py b/spinn_machine/__init__.py index 4dc33a33..e4c08d43 100644 --- a/spinn_machine/__init__.py +++ b/spinn_machine/__init__.py @@ -86,10 +86,12 @@ from .router import Router from .routing_entry import RoutingEntry from .spinnaker_triad_geometry import SpiNNakerTriadGeometry -from .virtual_machine import virtual_machine +from .virtual_machine import ( + virtual_machine, virtual_machine_by_boards, virtual_machine_by_cores) __all__ = ["Chip", "CoreSubset", "CoreSubsets", "FrozenCoreSubsets", "Link", "Machine", "MulticastRoutingEntry", "Router", "RoutingEntry", "SpiNNakerTriadGeometry", - "virtual_machine"] + "virtual_machine", + "virtual_machine_by_boards", "virtual_machine_by_cores"] 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..080111e1 100644 --- a/spinn_machine/data/machine_data_writer.py +++ b/spinn_machine/data/machine_data_writer.py @@ -17,7 +17,7 @@ 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, virtual_machine_by_boards from .machine_data_view import MachineDataView, _MachineDataModel logger = FormatAdapter(logging.getLogger(__name__)) __temp_dir = None @@ -46,12 +46,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 +104,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/version/abstract_version.py b/spinn_machine/version/abstract_version.py index c437c48a..d1c8efc9 100644 --- a/spinn_machine/version/abstract_version.py +++ b/spinn_machine/version/abstract_version.py @@ -18,6 +18,7 @@ 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 @@ -351,3 +352,55 @@ 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 + raise SpinnMachineException( + f"Version {self} does not support multiple boards") diff --git a/spinn_machine/version/version_3.py b/spinn_machine/version/version_3.py index a186ccb6..1b819a75 100644 --- a/spinn_machine/version/version_3.py +++ b/spinn_machine/version/version_3.py @@ -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: diff --git a/spinn_machine/version/version_5.py b/spinn_machine/version/version_5.py index 178432dd..6f3216fa 100644 --- a/spinn_machine/version/version_5.py +++ b/spinn_machine/version/version_5.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import math from typing import Final, Mapping, Optional, Sequence, Tuple from spinn_utilities.overrides import overrides from spinn_utilities.typing.coords import XY @@ -107,3 +108,14 @@ 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 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..3c606ef7 100644 --- a/unittests/data/test_data.py +++ b/unittests/data/test_data.py @@ -94,3 +94,15 @@ def test_where_is_setup(self): "None", MachineDataView.where_is_chip(None) ) + + 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_json_machine.py b/unittests/test_json_machine.py index 65a5a06a..c320ebe9 100644 --- a/unittests/test_json_machine.py +++ b/unittests/test_json_machine.py @@ -15,7 +15,8 @@ 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_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) @@ -29,7 +30,7 @@ def setUp(self): def test_json_version_5_hole(self): set_config("Machine", "down_chips", "3,3") - vm = virtual_machine(width=8, height=8) + vm = virtual_machine_by_min_size(width=8, height=8) MachineDataWriter.mock().set_machine(vm) jpath = mktemp("json") to_json_path(jpath) @@ -41,7 +42,7 @@ def test_json_version_5_hole(self): self.assertEqual(str(vchip), str(jchip)) def test_exceptions(self): - vm = virtual_machine(width=8, height=8) + vm = virtual_machine_by_min_size(width=8, height=8) MachineDataWriter.mock().set_machine(vm) chip22 = vm[2, 2] router22 = chip22.router @@ -59,12 +60,14 @@ def test_exceptions(self): self.assertEqual(vchip33.tag_ids, chip33.tag_ids) def test_monitor_exceptions(self): - vm = virtual_machine(width=8, height=8) + 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 +76,21 @@ def test_monitor_exceptions(self): machine_from_json(jpath) def test_ethernet_exceptions(self): - vm = virtual_machine(width=16, height=16) + 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..ddc9d322 100644 --- a/unittests/test_machine.py +++ b/unittests/test_machine.py @@ -20,7 +20,8 @@ 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.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 ( @@ -58,11 +59,13 @@ def _create_chip(self, x, y): 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", 5) + new_machine = virtual_machine_by_boards(1) self.assertEqual(new_machine.width, 8) self.assertEqual(new_machine.height, 8) @@ -90,7 +93,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", 5) + 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, " @@ -143,38 +148,20 @@ def test_create_new_machine_with_invalid_chips(self): :rtype: None """ - machine = virtual_machine(8, 8) + 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) + new_machine = virtual_machine_by_boards(1) with self.assertRaises(SpinnMachineAlreadyExistsException): new_machine.add_chip(new_machine.get_chip_at(1, 1)) @@ -184,9 +171,11 @@ def test_machine_get_chip_at(self): :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) + 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 +184,18 @@ def test_machine_big_x(self): :rtype: None """ - machine = MachineDataView.get_machine_version().create_machine(8, 8) + 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): """ @@ -213,15 +205,17 @@ def test_machine_big_y(self): :rtype: None """ 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 +224,10 @@ 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)) + 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 +236,10 @@ 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)) + 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 +248,19 @@ 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)) + 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) + 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,8 @@ def test_x_y_over_link(self): Notice that the function only does the math not validate the values. :return: """ + # TODO wrap arrounds in Spin2 + set_config("Machine", "version", 5) # 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 +303,8 @@ def test_get_global_xy(self): Notice that the function only does the math not validate the values. :return: """ + # TODO wrap arounds in Spin2 + set_config("Machine", "version", 5) # 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 +323,59 @@ 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) + 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) + 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) + 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" + 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 + machine = virtual_machine_by_boards(1) + self._non_ethernet_chip(machine)._nearest_ethernet_x = 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 + machine = virtual_machine_by_boards(1) + self._non_ethernet_chip(machine)._nearest_ethernet_x = 12 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) + machine = virtual_machine_by_min_size(5, 5) found = list(machine.concentric_xys(2, (2, 2))) expected = [ (2, 2), @@ -375,10 +385,10 @@ def test_concentric_xys(self): self.assertListEqual(expected, found) def test_too_few_cores(self): - machine = virtual_machine(8, 8) + 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 = self._non_ethernet_chip(machine) + chip._placable_processors = tuple([1, 2]) with self.assertRaises(SpinnMachineException): machine.validate() diff --git a/unittests/test_virtual_machine.py b/unittests/test_virtual_machine.py index ac405269..b9140003 100644 --- a/unittests/test_virtual_machine.py +++ b/unittests/test_virtual_machine.py @@ -15,7 +15,11 @@ 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 import (Chip, Link, Router, virtual_machine) +from spinn_machine.virtual_machine import ( + virtual_machine_by_boards, virtual_machine_by_chips, + virtual_machine_by_cores, virtual_machine_by_min_size) +from spinn_machine.data import MachineDataView from spinn_machine.exceptions import ( SpinnMachineException, SpinnMachineAlreadyExistsException) from spinn_machine.ignores import IgnoreChip, IgnoreCore, IgnoreLink @@ -1086,6 +1090,121 @@ def test_n_cores_2_2(self): n_cores = sum(chip.n_processors for chip in machine.chips) self.assertEqual(n_cores, 4 * 18) + def test_2_2_by_cores(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) + + 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__': unittest.main() diff --git a/unittests/version/test_version3.py b/unittests/version/test_version3.py index 5d5795ed..f0dff481 100644 --- a/unittests/version/test_version3.py +++ b/unittests/version/test_version3.py @@ -140,6 +140,20 @@ def test_processor_info(self): self.assertEqual(200, version.clock_speed_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..363393d9 100644 --- a/unittests/version/test_version5.py +++ b/unittests/version/test_version5.py @@ -150,6 +150,28 @@ def test_processor_info(self): self.assertEqual(200, version.clock_speed_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()