Source code for spinn_machine.virtual_machine

# Copyright (c) 2016 The University of Manchester
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import math
from collections import defaultdict
import logging
from typing import Dict, List, Optional, Set, Tuple
from spinn_utilities.config_holder import get_config_str_or_none
from spinn_utilities.log import FormatAdapter
from spinn_utilities.typing.coords import XY
from spinn_machine.data import MachineDataView
from spinn_machine.ignores import IgnoreChip, IgnoreCore, IgnoreLink
from .chip import Chip
from .router import Router
from .link import Link
from .machine import Machine

logger = FormatAdapter(logging.getLogger(__name__))


[docs] def virtual_machine(width: int, height: int, validate: bool = True) -> Machine: """ 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 """ factory = _VirtualMachine(width, height, validate) return factory.machine
def virtual_machine_by_min_size( width: int, height: int, validate: bool = True) -> Machine: """ 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) -> Machine: """ 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) -> Machine: """ 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) -> Machine: """ 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 """ __slots__ = ( "_unused_cores", "_unused_links", "_machine", "_with_monitors", "_n_router_entries" ) _4_chip_down_links = { (0, 0, 3), (0, 0, 4), (0, 1, 3), (0, 1, 4), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1) } ORIGIN = "Virtual" def __init__(self, width: int, height: int, validate: bool = True): version = MachineDataView.get_machine_version() 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( width, height, origin=self.ORIGIN) # Store the down items unused_chips = [] for down_chip in IgnoreChip.parse_string(get_config_str_or_none( "Machine", "down_chips")): if down_chip.ip_address is None: unused_chips.append((down_chip.x, down_chip.y)) self._unused_cores: Dict[XY, Set[int]] = defaultdict(set) for down_core in IgnoreCore.parse_string(get_config_str_or_none( "Machine", "down_cores")): if down_core.ip_address is None: self._unused_cores[down_core.x, down_core.y].add( down_core.virtual_p) self._unused_links: Set[Tuple[int, int, int]] = set() for down_link in IgnoreLink.parse_string(get_config_str_or_none( "Machine", "down_links")): if down_link.ip_address is None: self._unused_links.add( (down_link.x, down_link.y, down_link.link)) if width == 2: # Already checked height is now also 2 self._unused_links.update(_VirtualMachine._4_chip_down_links) ethernet_chips = version.get_potential_ethernet_chips(width, height) # Compute list of chips that are possible based on configuration # If there are no wrap arounds, and the the size is not 2 * 2, # the possible chips depend on the 48 chip board's gaps configured_chips: Dict[XY, Tuple[XY, int]] = dict() for eth in ethernet_chips: for (xy, n_cores) in self._machine.get_xy_cores_by_ethernet( *eth): if xy not in unused_chips: configured_chips[xy] = (eth, min(n_cores, max_cores)) # for chip in self._unreachable_outgoing_chips: # configured_chips.remove(chip) # for chip in self._unreachable_incoming_chips: # configured_chips.remove(chip) for xy in configured_chips: if xy in ethernet_chips: x, y = xy new_chip = self._create_chip( xy, configured_chips, f"127.0.{x}.{y}") else: new_chip = self._create_chip(xy, configured_chips) self._machine.add_chip(new_chip) self._machine.add_spinnaker_links() self._machine.add_fpga_links() if validate: self._machine.validate() @property def machine(self) -> Machine: """ The Machine object created by this Factory :rtype: Machine """ return self._machine def _create_chip(self, xy: XY, configured_chips: Dict[XY, Tuple[XY, int]], ip_address: Optional[str] = None) -> Chip: chip_links = self._calculate_links(xy, configured_chips) chip_router = Router(chip_links, self._n_router_entries) ((eth_x, eth_y), n_cores) = configured_chips[xy] x, y = xy sdram = MachineDataView.get_machine_version().max_sdram_per_chip cores = list(range(1, n_cores)) for down_core in self._unused_cores.get(xy, []): if down_core in cores: cores.remove(down_core) return Chip( x, y, [0], cores, chip_router, sdram, eth_x, eth_y, ip_address) def _calculate_links( self, xy: XY, configured_chips: Dict[XY, Tuple[XY, int]] ) -> List[Link]: """ Calculate the links needed for a machine structure """ x, y = xy links = list() for link_id in range(6): if (x, y, link_id) not in self._unused_links: link_x_y = self._machine.xy_over_link(x, y, link_id) if link_x_y in configured_chips: links.append( Link(source_x=x, source_y=y, destination_x=link_x_y[0], destination_y=link_x_y[1], source_link_id=link_id)) return links