from pacman import exceptions
from pacman.model.constraints.placer_constraints\
import RadialPlacementFromChipConstraint, BoardConstraint
from pacman.model.constraints.placer_constraints\
import ChipAndCoreConstraint, AbstractPlacerConstraint
from pacman.model.resources import ResourceContainer, DTCMResource, \
SDRAMResource, CPUCyclesPerTickResource
from pacman.utilities import utility_calls
from pacman.exceptions import PacmanInvalidParameterException
from spinn_utilities.ordered_set import OrderedSet
from collections import defaultdict
[docs]class ResourceTracker(object):
""" Tracks the usage of resources of a machine
"""
__slots__ = [
# The amount of SDRAM used by each chip,
# indexed by the (x, y) tuple of coordinates of the chip
# Note that entries are only added when the SDRAM is first used
"_sdram_tracker",
# The set of processor ids available on each chip,
# indexed by the (x, y) tuple of coordinates of the chip
# Note that entries are only added when a core is first used
"_core_tracker",
# The machine object
"_machine",
# Set of tags available indexed by board address
# Note that entries are only added when a board is first used
"_tags_by_board",
# Set of boards with available ip tags
"_boards_with_ip_tags",
# Set of (board_address, tag) assigned to an ip tag indexed by
# (ip address, traffic identifier) - Note not reverse ip tags
"_ip_tags_address_traffic",
# The (ip address, traffic identifier) assigned to an ip tag indexed by
# (board address, tag)
"_address_and_traffic_ip_tag",
# The (strip_sdp, port) assigned to an ip tag indexed by
# (board address, tag)
"_ip_tags_strip_sdp_and_port",
# The (board address, port) combinations already assigned to a
# reverse ip tag - Note not ip tags
"_reverse_ip_tag_listen_port",
# The port assigned to a reverse ip tag, indexed by
# (board address, tag) - Note not ip tags
"_listen_port_reverse_ip_tag",
# A count of how many allocations are sharing the same ip tag -
# Note not reverse ip tags
"_n_ip_tag_allocations",
# (x, y) tuple of coordinates of Ethernet connected chip indexed by
# board address
"_ethernet_chips",
# Set of (x, y) tuples of coordinates of chips which have available
# processors
"_chips_available",
# Number of cores preallocated on each chip (by x, y coordinates)
"_n_cores_preallocated",
# counter of chips that have had processors allocated to them
"_chips_used"
]
def __init__(self, machine, chips=None, preallocated_resources=None):
"""
:param machine: The machine to track the usage of
:type machine: :py:class:`spinn_machine.Machine`
:param chips: If specified, this list of chips will be used\
instead of the list from the machine. Note that the order\
will be maintained, so this can be used either to reduce\
the set of chips used, or to re-order the chips. Note\
also that on deallocation, the order is no longer\
guaranteed.
:type chips: iterable of (int, int) tuples of coordinates of chips
"""
# The amount of SDRAM used by each chip,
# indexed by the (x, y) tuple of coordinates of the chip
# Note that entries are only added when the SDRAM is first used
self._sdram_tracker = dict()
# The set of processor ids available on each chip,
# indexed by the (x, y) tuple of coordinates of the chip
# Note that entries are only added when a core is first used
self._core_tracker = dict()
# set of resources that have been pre allocated and therefore need to
# be taken account of when allocating resources
self._n_cores_preallocated = self._convert_pre_allocated_resources(
preallocated_resources)
# The machine object
self._machine = machine
# tracker for chips used
self._chips_used = set()
# Set of tags available indexed by board address
# Note that entries are only added when a board is first used
self._tags_by_board = dict()
# Set of boards with available ip tags
self._boards_with_ip_tags = OrderedSet()
# Set of (board_address, tag) assigned
# to any ip tag, indexed by (ip address, traffic_identifier)
# - Note not reverse ip tags
self._ip_tags_address_traffic = defaultdict(set)
# The (ip address, traffic identifier) assigned to an ip tag indexed by
# (board address, tag)
self._address_and_traffic_ip_tag = dict()
# The (strip_sdp, port) assigned to an ip tag indexed by
# (board address, tag)
self._ip_tags_strip_sdp_and_port = dict()
# The (board address, port) combinations already assigned to a
# reverse ip tag - Note not ip tags
self._reverse_ip_tag_listen_port = set()
# The port assigned to a reverse ip tag, indexed by
# (board address, tag) - Note not ip tags
self._listen_port_reverse_ip_tag = dict()
# A count of how many allocations are sharing the same ip tag -
# Note not reverse ip tags
self._n_ip_tag_allocations = dict()
# (x, y) tuple of coordinates of Ethernet connected chip indexed by
# board address
self._ethernet_chips = dict()
for chip in self._machine.ethernet_connected_chips:
self._ethernet_chips[chip.ip_address] = (chip.x, chip.y)
self._boards_with_ip_tags.add(chip.ip_address)
# Set of (x, y) tuples of coordinates of chips which have available
# processors
self._chips_available = OrderedSet()
if chips is None:
for x, y in machine.chip_coordinates:
self._chips_available.add((x, y))
else:
for x, y in chips:
self._chips_available.add((x, y))
def _convert_pre_allocated_resources(self, pre_allocated_resources):
""" Allocates pre allocated sdram and specific cores to the trackers.
Also builds an arbitrary core map for usage throughout\
resource tracker
:param pre_allocated_resources:\
the pre allocated resources from the tools
:type pre_allocated_resources: PreAllocatedResourceContainer
:return: a mapping of chip to arbitrary core demands
:rtype: dict of key (int, int) to int
"""
# If there are no resources, return an empty dict which returns 0
if pre_allocated_resources is None:
return defaultdict(lambda: 0)
# remove sdram by adding the cost into the sdram tracker already
sdram_allocations = pre_allocated_resources.specific_sdram_usage
for sdram_pre_allocated in sdram_allocations:
chip = sdram_pre_allocated.chip
sdram = sdram_pre_allocated.sdram_usage
self._sdram_tracker[(chip.x, chip.y)] = sdram
# remove specific cores from the tracker
specific_cores = pre_allocated_resources.specific_core_resources
for specific_core in specific_cores:
chip = specific_core.chip
processor_ids = specific_core.cores
self._fill_in_core_tracker_for_chip((chip.x, chip.y), chip)
for processor_id in processor_ids:
self._core_tracker[(chip.x, chip.y)].remove(processor_id)
# create random_core_map
chip_to_arbitrary_core_requirement = defaultdict(lambda: 0)
for arbitrary_core in pre_allocated_resources.core_resources:
chip = arbitrary_core.chip
n_cores = arbitrary_core.n_cores
chip_to_arbitrary_core_requirement[(chip.x, chip.y)] = n_cores
return chip_to_arbitrary_core_requirement
[docs] @staticmethod
def check_constraints(
vertices, additional_placement_constraints=None):
""" Check that the constraints on the given vertices are supported\
by the resource tracker
:param vertices: The vertices to check the constraints of
:param additional_placement_constraints:\
Additional placement constraints supported by the algorithm doing\
this check
"""
# These placement constraints are supported by the resource tracker
placement_constraints = {
ChipAndCoreConstraint, BoardConstraint,
RadialPlacementFromChipConstraint
}
if additional_placement_constraints is not None:
placement_constraints.update(additional_placement_constraints)
# Check the placement constraints
utility_calls.check_algorithm_can_support_constraints(
constrained_vertices=vertices,
supported_constraints=placement_constraints,
abstract_constraint_type=AbstractPlacerConstraint)
[docs] @staticmethod
def get_ip_tag_info(resources, constraints):
""" Get the ip tag resource information
:param resources: The resources to get the values from
:type resources:\
:py:class:`pacman.model.resources.ResourceContainer`
:param constraints: A list of constraints
:type constraints:\
list of\
:py:class:`pacman.model.constraints.AbstractConstraint`
:return:\
A tuple of board address, iterable of ip tag resources and \
iterable of reverse ip tag resources
:rtype: (str, iterable of\
:py:class:`pacman.model.resources.IptagResource`,
iterable of\
:py:class:`pacman.model.resources.ReverseIPtabResource`)
"""
board_address = None
ip_tags = resources.iptags
reverse_ip_tags = resources.reverse_iptags
for constraint in constraints:
if isinstance(constraint, BoardConstraint):
board_address = utility_calls.check_constrained_value(
constraint.board_address, board_address)
return board_address, ip_tags, reverse_ip_tags
[docs] @staticmethod
def get_chip_and_core(constraints, chips=None):
""" Get an assigned chip and core from a set of constraints
:param constraints: The set of constraints to get the values from.\
Note that any type of constraint can be in the list but only those\
relevant will be used
:type constraints: iterable of\
:py:class:`pacman.model.constraints.AbstractConstraint`
:param chips: Optional list of tuples of (x, y) coordinates of chips,\
restricting the allowed chips
:type chips: iterable of (int, int)
:return: tuple of a chip x and y coordinates, and processor id, any of\
which might be None
:rtype: (tuple of (int, int, int)
"""
x = None
y = None
p = None
for constraint in constraints:
if isinstance(constraint, ChipAndCoreConstraint):
x = utility_calls.check_constrained_value(constraint.x, x)
y = utility_calls.check_constrained_value(constraint.y, y)
p = utility_calls.check_constrained_value(constraint.p, p)
if chips is not None and x is not None and y is not None:
if (x, y) not in chips:
raise PacmanInvalidParameterException(
"x, y and chips",
"{}, {} and {}".format(x, y, chips),
"The constraint cannot be met with the given chips")
return x, y, p
def _chip_available(self, x, y):
pre_allocated_cores = self._n_cores_preallocated[(x, y)]
return (
self._machine.is_chip_at(x, y) and
(((x, y) not in self._core_tracker and
self._machine.get_chip_at(x, y).n_user_processors >
pre_allocated_cores) or
((x, y) in self._core_tracker and
len(self._core_tracker[x, y]) > pre_allocated_cores))
)
def _get_usable_chips(
self, chips, board_address, ip_tags, reverse_ip_tags):
""" Get all chips that are available on a board given the constraints
:param chips: iterable of tuples of (x, y) coordinates of chips to \
look though for usable chips, or None to use all available\
chips
:type chips: iterable of (int, int)
:param board_address: the board address to check for usable chips on
:type board_address: str or None
:param ip_tags: list of ip tag resources
:type ip_tags: list of\
:py:class:`pacman.model.resources.IptagResource`
:param reverse_ip_tags: list of reverse ip tag resources
:type reverse_ip_tags: list of\
:py:class:`pacman.model.resources.ReverseIptagResource`
:return: iterable of tuples of (x, y) coordinates of usable chips
:rtype: iterable of tuple of (x, y)
:raise PacmanInvalidParameterException:
* If the board address is unknown
* When either or both chip coordinates of any chip are none
* When a non-existent chip is specified
* When all the chips in the specified board have been used
"""
eth_chip = None
if board_address is not None:
if board_address not in self._ethernet_chips:
raise exceptions.PacmanInvalidParameterException(
"board_address", str(board_address),
"Unrecognised board address")
eth_x, eth_y = self._ethernet_chips[board_address]
eth_chip = self._machine.get_chip_at(eth_x, eth_y)
if chips is not None:
area_code = None
if eth_chip is not None:
area_code = set(self._machine.get_chips_on_board(eth_chip))
chip_found = False
for (chip_x, chip_y) in chips:
if ((area_code is None or (chip_x, chip_y) in area_code) and
self._chip_available(chip_x, chip_y)):
chip_found = True
yield (chip_x, chip_y)
if not chip_found:
raise exceptions.PacmanInvalidParameterException(
"chips and board_address",
"{} and {}".format(chips, board_address),
"No valid chips found on the specified board")
elif board_address is not None:
for (x, y) in self._machine.get_chips_on_board(eth_chip):
if self._chip_available(x, y):
yield (x, y)
else:
for (x, y) in self._chips_available:
if self._chip_available(x, y):
yield (x, y)
@property
def chips_available(self):
""" The chips currently available
"""
return self._chips_available
def _is_sdram_available(self, chip, key, resources):
""" Check if the SDRAM available on a given chip is enough for the\
given resources.
:param chip: The chip to check the resources of
:type chip: :py:class:`spinn_machine.Chip`
:param key: The (x, y) coordinates of the chip
:type key: tuple of (int, int)
:param resources: the resources containing the SDRAM required
:type resources:\
:py:class:`pacman.model.resources.ResourceContainer`
:return: True if there is enough SDRAM available, or False otherwise
:rtype: bool
"""
if key in self._sdram_tracker:
return ((chip.sdram.size - self._sdram_tracker[key]) >=
resources.sdram.get_value())
else:
return chip.sdram.size >= resources.sdram.get_value()
def _sdram_available(self, chip, key):
""" Return the amount of SDRAM available on a chip
:param chip: The chip to check the resources of
:type chip: :py:class:`spinn_machine.Chip`
:param key: The (x, y) coordinates of the chip
:type key: tuple of (int, int)
:return: the SDRAM available
:rtype: int
"""
if key not in self._sdram_tracker:
return chip.sdram.size
return chip.sdram.size - self._sdram_tracker[key]
[docs] def sdram_avilable_on_chip(self, chip_x, chip_y):
""" Get the available SDRAM on the chip at coordinates chip_x, chip_y
:param chip_x: x coord of the chip in question
:param chip_y: y coord of the chip in question
:return: the SDRAM remaining
"""
key = (chip_x, chip_y)
chip = self._machine.get_chip_at(chip_x, chip_y)
return self._sdram_available(chip, key)
def _best_core_available(self, chip, key, processor_id):
""" Locate the best core available on a chip
:param chip: The chip to check the resources of
:type chip: :py:class:`spinn_machine.Chip`
:param key: The (x, y) coordinates of the chip
:type key: tuple of (int, int)
:param processor_id: A fixed processor id
:type processor_id: int
:return: The processor id selected as the best on this chip
"""
if processor_id is not None:
return processor_id
# TODO: Check for the best core; currently assumes all are the same
if key not in self._core_tracker:
for processor in chip.processors:
if not processor.is_monitor:
return processor.processor_id
return iter(self._core_tracker[key]).next()
def _is_core_available(self, chip, key, processor_id):
""" Check if there is a core available on a given chip given the\
constraints
:param chip: The chip to check the resources of
:type chip: :py:class:`spinn_machine.Chip`
:param key: The (x, y) coordinates of the chip
:type key: tuple of (int, int)
:param processor_id: A constraining fixed processor id
:type processor_id: int or None
:return: True if there is a core available given the constraints, or\
False otherwise
:rtype: bool
"""
return self._n_cores_available(chip, key, processor_id) > 0
def _n_cores_available(self, chip, key, processor_id):
""" Get the number of cores available on the given chip given the\
constraints
:param chip: The chip to check the resources of
:type chip: :py:class:`spinn_machine.Chip`
:param key: The (x, y) coordinates of the chip
:type key: (int, int)
:param processor_id: A constraining fixed processor id
:type processor_id: int or None
:return: The number of cores that meet the given constraints
:rtype: int
"""
# If a specific processor has been requested, perform special checks
if processor_id is not None:
# If the chip has already had cores allocated...
if key in self._core_tracker:
# Check if there is enough space for preallocated cores,
# and that the processor specified is available
if (len(self._core_tracker[key]) -
self._n_cores_preallocated[key] > 0 and
processor_id in self._core_tracker[key]):
return 1
# If here, the chip has no cores allocated, so check that there
# are enough cores on the chip for preallocated cores
elif chip.n_user_processors - self._n_cores_preallocated[key] > 0:
# Check that the processor is not a monitor core
processor = chip.get_processor_with_id(processor_id)
if processor is not None and not processor.is_monitor:
return 1
# If we get here, the core has been allocated
return 0
# Check how many cores are available
# TODO: Check the resources can be met with the processor
# Currently assumes all processors are equal
n_cores = 0
if key in self._core_tracker:
n_cores = (len(self._core_tracker[key]) -
self._n_cores_preallocated[key])
else:
n_cores = len([
proc for proc in chip.processors if not proc.is_monitor])
n_cores -= self._n_cores_preallocated[key]
return n_cores
def _get_matching_ip_tag(
self, chip, board_address, tag_id, ip_address, port, strip_sdp,
traffic_identifier):
""" Attempt to locate a matching tag for the given details
:param chip: The chip which is the source of the data for the tag
:type chip: :py:class:`spinn_machine.Chip` or None
:param board_address: the board address to locate the chip on
:type board_address: str or None
:param tag_id: the tag id to locate
:type tag_id: int or None
:param ip_address: The ip address of the tag
:type ip_address: str
:param port: The port of the tag or None if not assigned
:type port: int or None
:param strip_sdp: True if the tag is to strip SDP header
:type strip_sdp: bool
:param traffic_identifier: The identifier of the traffic to pass over\
this tag
:type traffic_identifier: str
:return: A board address, tag id, and port or None, None, None if none
:rtype: tuple of (str, int, (int or None)) or (None, None, None)
"""
# If there is no tag for the given ip address - traffic identifier
# combination, return
if ((ip_address, traffic_identifier) not in
self._ip_tags_address_traffic):
return None, None, None
# If no board address is specified, try to allow use of the closest
# board
eth_chip = None
if board_address is None and chip is not None:
eth_chip = self._machine.get_chip_at(
chip.nearest_ethernet_x, chip.nearest_ethernet_y)
# Scan the existing allocated tags and see if any match the details
found_board = None
found_tag = None
found_port = None
existing_tags = self._ip_tags_address_traffic[
(ip_address, traffic_identifier)]
for (other_board_address, other_tag) in existing_tags:
(other_strip_sdp, other_port) = self._ip_tags_strip_sdp_and_port[
(other_board_address, other_tag)]
if (utility_calls.is_equal_or_None(
other_board_address, board_address) and
utility_calls.is_equal_or_None(other_tag, tag_id) and
other_strip_sdp == strip_sdp and
utility_calls.is_equal_or_None(other_port, port)):
# If the existing tag is on the same board, return immediately
if (eth_chip is not None and
other_board_address == eth_chip.ip_address):
return other_board_address, other_tag, other_port
# Otherwise store the tag for possible later use
found_board = other_board_address
found_tag = other_tag
found_port = other_port
# If we got here, we didn't find an existing tag on the same board
# so check if the tag *could* be assigned to the current board
if self._is_tag_available_on_ethernet_chip(eth_chip, tag_id):
# If the tag is available, allow it to be used
return None, None, None
# Otherwise, return any matching existing tag
return found_board, found_tag, found_port
def _is_tag_available(self, board_address, tag):
""" Check if a tag is available given the constraints
:param board_address: the board address to locate the chip on
:type board_address: str or None
:param tag: the tag id to locate
:type tag: int or None
:return: True if the tag is available, False otherwise
:rtype: bool
"""
if board_address is None and tag is not None:
for board_address in self._boards_with_ip_tags:
if (board_address not in self._tags_by_board or
tag in self._tags_by_board[board_address]):
return True
return False
elif board_address is not None and tag is None:
return board_address in self._boards_with_ip_tags
elif board_address is None and tag is None:
return len(self._boards_with_ip_tags) > 0
return board_address not in self._tags_by_board \
or tag in self._tags_by_board[board_address]
def _is_tag_available_on_ethernet_chip(self, ethernet_chip, tag_id):
if ethernet_chip is None:
return False
# Having found the board address, it can only be used if a
# tag is available
addr = ethernet_chip.ip_address
return (addr in self._boards_with_ip_tags and
(tag_id is None or addr not in self._tags_by_board or
tag_id in self._tags_by_board[addr]))
def _is_ip_tag_available(self, board_address, tag, ip_address, port,
strip_sdp, traffic_identifier):
""" Check if an iptag is available given the constraints
:param board_address: the board address to locate the chip on
:type board_address: str or None
:param tag: the tag id to locate
:type tag: int or None
:param ip_address: the ip address of the tag to be assigned
:type ip_address: str
:param port: the port number of the tag to be assigned
:type port: int or None
:param strip_sdp: if the iptag has to be able to strip the SDP header
:type strip_sdp: bool
:param traffic_identifier: The type of traffic for the tag
:type traffic_identifier: str
:return: True if a matching iptag is available, False otherwise
:rtype: bool
"""
# If equivalent traffic is being used by another ip tag, re-use it
(b_address, _, _) = self._get_matching_ip_tag(
None, board_address, tag, ip_address, port, strip_sdp,
traffic_identifier)
if b_address is not None:
return True
# Otherwise determine if another tag is available
return self._is_tag_available(board_address, tag)
def _are_ip_tags_available(self, board_address, ip_tags):
""" Check if the set of tags are available using the given chip,\
given the constraints
:param board_address: the board to allocate ip tags on
:type board_address: str or None
:param ip_tags: The ip tag resource
:type ip_tags: iterable of\
:py:class:`pacman.model.resource.IptagResource`
:return: True if the tags can be allocated, False otherwise
:rtype: bool
"""
# If there are no tags to assign, declare that they are available
if ip_tags is None or len(ip_tags) == 0:
return True
# Check if each of the tags is available
for ip_tag in ip_tags:
if not self._is_ip_tag_available(
board_address, ip_tag.tag, ip_tag.ip_address, ip_tag.port,
ip_tag.strip_sdp, ip_tag.traffic_identifier):
return False
return True
def _is_reverse_ip_tag_available(self, board_address, tag, port):
""" Check if the reverse ip tag is available given the constraints
:param board_address: The board address to use
:type board_address: str or None
:param tag: The tag to be used
:type tag: int or None
:param port: The port that the tag will listen on on the board
:type port: int or None
:return: True if the tag is available, false otherwise
:rtype: int
"""
if board_address is not None:
# If the board address is not None, and the port is already
# assigned, the tag is not available
if (port is not None and
(board_address, port) in self._reverse_ip_tag_listen_port):
return False
# If the port is available, return true if the tag is available
return self._is_tag_available(board_address, tag)
# If the board address is not None but the port is already used
# everywhere that the tag is available, the tag is not available.
# Note that if port is None, any tag just has to be available
port_available = False
for b_address in self._boards_with_ip_tags:
if ((port is None or
(b_address, port) not in self._reverse_ip_tag_listen_port) and
self._is_tag_available(b_address, tag)):
port_available = True
break
return port_available
def _are_reverse_ip_tags_available(
self, board_address, reverse_ip_tags):
""" Check if this chip can be used given the reverse ip tag resources
:param board_address: the board to allocate ip tags on
:type board_address: str or None
:param reverse_ip_tags: The reverse ip tag resource to be met
:type reverse_ip_tags: iterable of \
:py:class:`pacman.model.resources.ReverseIptagResource`
:return: True if the chip can be used, False otherwise
:rtype: bool
"""
# If there are no tags, declare they are available
if reverse_ip_tags is None or len(reverse_ip_tags) == 0:
return True
for ip_tag in reverse_ip_tags:
if not self._is_reverse_ip_tag_available(
board_address, ip_tag.tag, ip_tag.port):
return False
return True
def _allocate_sdram(self, key, resources):
""" Allocates the SDRAM on the given chip
:param key: The (x, y) coordinates of the chip
:type key: tuple of (int, int)
:param resources: the resources containing the SDRAM required
:type resources:\
:py:class:`pacman.model.resources.ResourceContainer`
"""
if key not in self._sdram_tracker:
self._sdram_tracker[key] = resources.sdram.get_value()
else:
self._sdram_tracker[key] += resources.sdram.get_value()
def _allocate_core(self, chip, key, processor_id):
""" Allocates a core on the given chip
:param chip: The chip to allocate the resources of
:type chip: :py:class:`spinn_machine.Chip`
:param key: The (x, y) coordinates of the chip
:type key: (int, int)
:param processor_id: The id of the processor to allocate
:type processor_id: int
"""
if key not in self._core_tracker:
self._fill_in_core_tracker_for_chip(key, chip)
if processor_id is not None:
self._core_tracker[key].remove(processor_id)
else:
# TODO: Find a core that meets the resource requirements
processor_id = self._core_tracker[key].pop()
if len(self._core_tracker[key]) == self._n_cores_preallocated[key]:
self._chips_available.remove(key)
# update chip tracker
self._chips_used.add(key)
# return processor id
return processor_id
def _fill_in_core_tracker_for_chip(self, key, chip):
self._core_tracker[key] = set()
for processor in chip.processors:
if not processor.is_monitor:
self._core_tracker[key].add(processor.processor_id)
def _allocate_tag(self, chip, board_address, tag_id):
""" Allocate a tag given the constraints
:param chip: The chip containing the source of data for this tag
:type chip: :py:class:`spinn_machine.Chip`
:param board_address: the board address to allocate to
:type board_address: str or None
:param tag_id: the tag id to allocate on this board address
:type tag_id: int or None
:return: a tuple of (board_address and tag)
:rtype: (str, int)
"""
# First try to find a tag on the board closest to the chip
if board_address is None:
eth_chip = self._machine.get_chip_at(
chip.nearest_ethernet_x, chip.nearest_ethernet_y)
# verify if the Ethernet chip has the available tag id
if self._is_tag_available_on_ethernet_chip(eth_chip, tag_id):
board_address = eth_chip.ip_address
if board_address is None and tag_id is not None:
for b_address in self._boards_with_ip_tags:
if (b_address not in self._tags_by_board or
tag_id in self._tags_by_board[b_address]):
board_address = b_address
break
elif board_address is None and tag_id is None:
board_address = iter(self._boards_with_ip_tags).next()
if board_address not in self._tags_by_board:
(e_chip_x, e_chip_y) = self._ethernet_chips[board_address]
e_chip = self._machine.get_chip_at(e_chip_x, e_chip_y)
self._tags_by_board[board_address] = set(e_chip.tag_ids)
if tag_id is None:
tag_id = self._tags_by_board[board_address].pop()
else:
self._tags_by_board[board_address].remove(tag_id)
if len(self._tags_by_board[board_address]) == 0:
self._boards_with_ip_tags.remove(board_address)
return board_address, tag_id
def _allocate_ip_tags(self, chip, board_address, ip_tags):
""" Allocate the given set of ip tag resources
:param chip: The chip to allocate the tags for
:type chip: :py:class:`spinn_machine.Chip`
:param board_address: The board address to allocate on
:type board_address: str or None
:param ip_tags: The ip tag resources to allocate
:type ip_tags: iterable of\
:py:class:`pacman.model.resources.IptagResource`
:return: iterable of tuples of (board address, tag) assigned
:rtype: iterable of (str, int)
"""
if ip_tags is None or len(ip_tags) == 0:
return None
allocations = list()
for ip_tag in ip_tags:
# Find a tag that matches the one required
(b_address, a_tag, a_port) = self._get_matching_ip_tag(
chip, board_address, ip_tag.tag, ip_tag.ip_address,
ip_tag.port, ip_tag.strip_sdp, ip_tag.traffic_identifier)
if b_address is not None:
# Get the chip with the Ethernet
(e_chip_x, e_chip_y) = self._ethernet_chips[b_address]
# If there is already an allocation that matches the current
# tag, return this as the allocated tag
allocations.append((b_address, a_tag, e_chip_x, e_chip_y))
# Add to the number of things allocated to the tag
self._n_ip_tag_allocations[(b_address, a_tag)] += 1
# If the port is None and the requested port is not None,
# update the port number
if a_port is None and ip_tag.port is not None:
self._ip_tags_strip_sdp_and_port[(b_address, a_tag)] =\
(ip_tag.strip_sdp, a_port)
else:
# Allocate an ip tag
(board_address, tag) = self._allocate_tag(
chip, board_address, ip_tag.tag)
tag_key = (board_address, tag)
existing_tags = self._ip_tags_address_traffic[
(ip_tag.ip_address, ip_tag.traffic_identifier)]
existing_tags.add(tag_key)
self._ip_tags_strip_sdp_and_port[tag_key] = \
(ip_tag.strip_sdp, ip_tag.port)
self._address_and_traffic_ip_tag[tag_key] = \
(ip_tag.ip_address, ip_tag.traffic_identifier)
# Remember how many allocations are sharing this tag
# in case an deallocation is requested
self._n_ip_tag_allocations[tag_key] = 1
# Get the chip with the Ethernet
(e_chip_x, e_chip_y) = self._ethernet_chips[board_address]
allocations.append((board_address, tag, e_chip_x, e_chip_y))
if len(allocations) == 0:
return None
return allocations
def _allocate_reverse_ip_tags(self, chip, board_address, reverse_ip_tags):
""" Allocate reverse ip tags with the given constraints
:param chip: The chip to allocate the tags for
:type chip: :py:class:`spinn_machine.Chip`
:param board_address: the board address to allocate on
:type board_address: str or None
:param reverse_ip_tags: The reverse ip tag resources
:type reverse_ip_tags: iterable of\
:py:class:`pacman.model.resources.ReverseIptagResource`
:return: iterable of tuples of (board address, tag) assigned
:rtype: iterable of (str, int)
"""
if reverse_ip_tags is None or len(reverse_ip_tags) == 0:
return None
allocations = list()
for reverse_ip_tag in reverse_ip_tags:
(board_address, tag) = self._allocate_tag(
chip, board_address, reverse_ip_tag.tag)
allocations.append((board_address, tag))
if reverse_ip_tag.port is not None:
self._reverse_ip_tag_listen_port.add(
(board_address, reverse_ip_tag.port))
self._listen_port_reverse_ip_tag[
(board_address, reverse_ip_tag.tag)] = reverse_ip_tag.port
if len(allocations) == 0:
return None
return allocations
[docs] def allocate_constrained_resources(
self, resources, constraints, chips=None):
""" Attempts to use the given resources of the machine, constrained\
by the given placement constraints.
:param resources: The resources to be allocated
:type resources:\
:py:class:`pacman.model.resources.ResourceContainer`
:param constraints: the constraints to consider
:type constraints: \
list of \
:py:class:`pacman.model.constraints.AbstractConstraint`
:param chips: \
The optional list of (x, y) tuples of chip coordinates\
of chips that can be used. Note that any chips passed in\
previously will be ignored
:type chips: iterable of (int, int)
:return:\
The x and y coordinates of the used chip, the processor_id,\
and the ip tag and reverse ip tag allocation tuples
:rtype: (int, int, int, list((int, int)), list((int, int)))
:raise PacmanValueError: \
If the constraints cannot be met given the\
current allocation of resources
"""
(x, y, p) = self.get_chip_and_core(constraints, chips)
(board_address, ip_tags, reverse_ip_tags) = \
self.get_ip_tag_info(resources, constraints)
chips = None
if x is not None and y is not None:
chips = [(x, y)]
return self.allocate_resources(resources, chips, p, board_address,
ip_tags, reverse_ip_tags)
[docs] def allocate_constrained_group_resources(
self, resource_and_constraint_list, chips=None):
""" Allocates a group of cores on the same chip for these resources
:param resource_and_constraint_list:\
A list of tuples of (resources, list of constraints) to allocate
:param chips: a list of chips that can be used
:return: list of The x and y coordinates of the used chip,
the processor_id, and the ip tag and reverse ip tag
allocation tuples
:rtype: iterable of (int, int, int, list((int, int)), list((int, int)))
"""
x = None
y = None
processor_ids = list()
board_address = None
group_ip_tags = list()
group_reverse_ip_tags = list()
for (resources, constraints) in resource_and_constraint_list:
this_board_address, this_ip_tags, this_reverse_ip_tags = \
self.get_ip_tag_info(resources, constraints)
this_x, this_y, this_p = self.get_chip_and_core(
constraints, chips)
if ((x is not None and this_x is not None and this_x != x) or
(y is not None and this_y is not None and this_y != y) or
(this_p is not None and this_p in processor_ids) or
(board_address is not None and
this_board_address is not None and
board_address != this_board_address)):
raise exceptions.PacmanException(
"Cannot merge conflicting constraints")
if this_x is not None:
x = this_x
if this_y is not None:
y = this_y
if this_board_address is not None:
board_address = this_board_address
processor_ids.append(this_p)
group_ip_tags.append(this_ip_tags)
group_reverse_ip_tags.append(this_reverse_ip_tags)
chips = None
if x is not None and y is not None:
chips = [(x, y)]
# try to allocate in one block
group_resources = [item[0] for item in resource_and_constraint_list]
return self.allocate_group_resources(
group_resources, chips, processor_ids, board_address,
group_ip_tags, group_reverse_ip_tags)
[docs] def allocate_group_resources(
self, group_resources, chips=None, processor_ids=None,
board_address=None, group_ip_tags=None,
group_reverse_ip_tags=None):
""" Attempts to use the given group of resources on a single chip of\
the machine. Can be given specific place to use the resources,\
or else it will allocate them on the first place that the\
resources of the group fit together.
:param group_resources: The resources to be allocated
:type group_resources: list of\
:py:class:`pacman.model.resources.ResourceContainer`
:param chips: An iterable of (x, y) tuples of chips that are to be used
:type chips: iterable of (int, int)
:param processor_ids: The specific processor to use on any chip for\
each resource of the group
:type processor_ids: list of (int or None)
:param board_address: the board address to allocate resources of a chip
:type board_address: str
:param group_ip_tags: list of lists of ip tag resources
:type group_ip_tags: list of lists of\
:py:class:`pacman.model.resources.IptagResource`
:param group_reverse_ip_tags: list of lists of reverse ip tag \
resources
:type group_reverse_ip_tags: list of lists of\
:py:class:`pacman.model.resources.ReverseIptagResource`
:return: An iterable of tuples of the x and y coordinates of the used\
chip, the processor_id, and the ip tag and reverse ip tag\
allocation tuples
:rtype: iterable of (int, int, int, list((int, int)), list((int, int)))
"""
usable_chips = chips
for ip_tags, reverse_ip_tags in zip(
group_ip_tags, group_reverse_ip_tags):
usable_chips = self._get_usable_chips(
usable_chips, board_address, ip_tags, reverse_ip_tags)
total_sdram = 0
for resources in group_resources:
total_sdram += resources.sdram.get_value()
# Find the first usable chip which fits all the group resources
tried_chips = list()
for (chip_x, chip_y) in usable_chips:
tried_chips.append((chip_x, chip_y))
chip = self._machine.get_chip_at(chip_x, chip_y)
key = (chip_x, chip_y)
# No point in continuing if the chip doesn't have space for
# everything
if (self._n_cores_available(chip, key, None) >=
len(group_resources) and
self._sdram_available(chip, key) >= total_sdram):
# Check that the constraints of all the resources can be met
is_available = True
for resources, processor_id, ip_tags, reverse_ip_tags in zip(
group_resources, processor_ids, group_ip_tags,
group_reverse_ip_tags):
if (not self._is_core_available(
chip, key, processor_id) or
not self._are_ip_tags_available(
board_address, ip_tags) or
not self._are_reverse_ip_tags_available(
board_address, reverse_ip_tags)):
is_available = False
break
# If everything is good, do the allocation
if is_available:
results = list()
for resources, proc_id, ip_tags, reverse_ip_tags in zip(
group_resources, processor_ids, group_ip_tags,
group_reverse_ip_tags):
processor_id = self._allocate_core(chip, key, proc_id)
self._allocate_sdram(key, resources)
ip_tags_allocated = self._allocate_ip_tags(
chip, board_address, ip_tags)
reverse_ip_tags_allocated = \
self._allocate_reverse_ip_tags(
chip, board_address, reverse_ip_tags)
results.append((
chip.x, chip.y, processor_id, ip_tags_allocated,
reverse_ip_tags_allocated))
return results
# If no chip is available, raise an exception
n_cores, n_chips, max_sdram, n_tags = self._available_resources(
tried_chips)
raise exceptions.PacmanValueError(
"No resources available to allocate the given group resources"
" within the given constraints:\n"
" Request for {} cores on a single chip with SDRAM: {}\n"
" Resources available which meet constraints:"
" {} Cores and {} tags on {} chips,"
" largest SDRAM space: {}".format(
len(group_resources), total_sdram, n_cores, n_tags, n_chips,
max_sdram))
[docs] def allocate_resources(self, resources, chips=None,
processor_id=None, board_address=None,
ip_tags=None, reverse_ip_tags=None):
""" Attempts to use the given resources of the machine. Can be given\
specific place to use the resources, or else it will allocate them\
on the first place that the resources fit.
:param resources: The resources to be allocated
:type resources:\
:py:class:`pacman.model.resources.ResourceContainer`
:param chips: An iterable of (x, y) tuples of chips that are to be used
:type chips: iterable of (int, int)
:param processor_id: The specific processor to use on any chip.
:type processor_id: int
:param board_address: the board address to allocate resources of a chip
:type board_address: str
:param ip_tags: iterable of ip tag resources
:type ip_tags: iterable of\
:py:class:`pacman.model.resources.IptagResource`
:param reverse_ip_tags: iterable of reverse ip tag resources
:type reverse_ip_tags: iterable of\
:py:class:`pacman.model.resources.ReverseIPtagResource`
:return: The x and y coordinates of the used chip, the processor_id,\
and the ip tag and reverse ip tag allocation tuples
:rtype: (int, int, int, list((int, int, int, int)), list((int, int)))
"""
usable_chips = self._get_usable_chips(chips, board_address,
ip_tags, reverse_ip_tags)
# Find the first usable chip which fits the resources
tried_chips = list()
for (chip_x, chip_y) in usable_chips:
tried_chips.append((chip_x, chip_y))
chip = self._machine.get_chip_at(chip_x, chip_y)
key = (chip_x, chip_y)
if (self._is_core_available(chip, key, processor_id) and
self._is_sdram_available(chip, key, resources) and
self._are_ip_tags_available(board_address, ip_tags) and
self._are_reverse_ip_tags_available(board_address,
reverse_ip_tags)):
processor_id = self._allocate_core(chip, key, processor_id)
self._allocate_sdram(key, resources)
ip_tags_allocated = self._allocate_ip_tags(
chip, board_address, ip_tags)
reverse_ip_tags_allocated = self._allocate_reverse_ip_tags(
chip, board_address, reverse_ip_tags)
return (chip.x, chip.y, processor_id, ip_tags_allocated,
reverse_ip_tags_allocated)
# If no chip is available, raise an exception
n_cores, n_chips, max_sdram, n_tags = \
self._available_resources(tried_chips)
all_chips = self._get_usable_chips(None, None, None, None)
all_n_cores, all_n_chips, all_max_sdram, all_n_tags = \
self._available_resources(all_chips)
raise exceptions.PacmanValueError(
"No resources available to allocate the given resources"
" within the given constraints:\n"
" Request for CPU: {}, DTCM: {}, SDRAM: {}, IP TAGS: {}, {}\n"
" Resources available which meet constraints:\n"
" {} Cores and {} tags on {} chips, largest SDRAM space: {}\n"
" All resources available:\n"
" {} Cores and {} tags on {} chips, largest SDRAM space: {}\n"
.format(
resources.cpu_cycles.get_value(), resources.dtcm.get_value(),
resources.sdram.get_value(), resources.iptags,
resources.reverse_iptags, n_cores, n_tags, n_chips, max_sdram,
all_n_cores, all_n_tags, all_n_chips, all_max_sdram))
def _available_resources(self, usable_chips):
"""Describe how much of the various resource types are available.
:param usable_chips: Coordinates of usable chips
:type usable_chips: iterable of pair(int,int)
:return: returns #cores, #chips, amount of SDRAM, #tags
:rtype: 4-tuple of int
"""
n_cores = 0
max_sdram = 0
n_chips = 0
n_tags = 0
for x, y in usable_chips:
chip = self._machine.get_chip_at(x, y)
if (x, y) in self._core_tracker:
n_cores += (len(self._core_tracker[x, y]) -
self._n_cores_preallocated[(x, y)])
else:
n_cores += (chip.n_user_processors -
self._n_cores_preallocated[(x, y)])
sdram_available = self._sdram_available(chip, (x, y))
if sdram_available > max_sdram:
max_sdram = sdram_available
n_chips += 1
for board_address in self._boards_with_ip_tags:
if board_address in self._tags_by_board:
n_tags += len(self._tags_by_board)
else:
eth_x, eth_y = self._ethernet_chips[board_address]
n_tags += len(self._machine.get_chip_at(eth_x, eth_y).tag_ids)
return n_cores, n_chips, max_sdram, n_tags
[docs] def get_maximum_constrained_resources_available(self, resources,
constraints, chips=None):
""" Get the maximum resources available given the constraints
:param resources: The resources of the item to check
:type resources:\
:py:class:`pacman.model.resources.AbstractResource`
:type constraints: \
iterable of\
:py:class:`pacman.model.constraints.AbstractConstraint`
:param chips: the chips to locate the max available resources of
:type chips: iterable of spinn_machine.Chip
"""
(x, y, p) = self.get_chip_and_core(constraints, chips)
(board_address, ip_tags, reverse_ip_tags) = self.get_ip_tag_info(
resources, constraints)
chips = None
if x is not None and y is not None:
chips = [(x, y)]
return self.get_maximum_resources_available(
chips, p, board_address, ip_tags, reverse_ip_tags)
[docs] def get_maximum_resources_available(self, chips=None, processor_id=None,
board_address=None, ip_tags=None,
reverse_ip_tags=None):
""" Get the maximum resources available
:param chips: An iterable of (x, y) tuples of chips that are to be used
:type chips: iterable of (int, int)
:param processor_id: the processor id
:type processor_id: int
:param board_address: the board address for locating max resources from
:type board_address: str
:param ip_tags: iterable of ip tag resources
:type ip_tags: iterable of\
:py:class:`pacman.model.resources.IptagResource`
:param reverse_ip_tags: iterable of reverse ip tag resources
:type reverse_ip_tags: iterable of\
:py:class:`pacman.model.resources.ReverseIptagResource`
:return: a resource which shows max resources available
:rtype: pacman.model.resources.ResourceContainer
"""
usable_chips = self._get_usable_chips(chips, board_address,
ip_tags, reverse_ip_tags)
# If the chip is not fixed, find the maximum SDRAM
# TODO: Also check for the best core
max_sdram_available = 0
max_dtcm_available = 0
max_cpu_available = 0
for (chip_x, chip_y) in usable_chips:
key = (chip_x, chip_y)
chip = self._machine.get_chip_at(chip_x, chip_y)
sdram_available = self._sdram_available(chip, key)
ip_tags_available = self._are_ip_tags_available(
board_address, ip_tags)
reverse_ip_tags_available = self._are_reverse_ip_tags_available(
board_address, reverse_ip_tags)
if (sdram_available > max_sdram_available and
ip_tags_available and reverse_ip_tags_available):
max_sdram_available = sdram_available
best_processor_id = self._best_core_available(chip, key,
processor_id)
processor = chip.get_processor_with_id(best_processor_id)
max_dtcm_available = processor.dtcm_available
max_cpu_available = processor.cpu_cycles_available
# If all the SDRAM on the chip is available,
# this chip is unallocated, so the max must be the max
# TODO: This assumes that the chips are all the same
if sdram_available == chip.sdram.size:
break
# Send the maximums
return ResourceContainer(
DTCMResource(max_dtcm_available),
SDRAMResource(max_sdram_available),
CPUCyclesPerTickResource(max_cpu_available))
[docs] def unallocate_resources(self, chip_x, chip_y, processor_id, resources,
ip_tags, reverse_ip_tags):
""" Undo the allocation of resources
:param chip_x: the x coord of the chip allocated
:param chip_y: the y coord of the chip allocated
:type chip_x: int
:type chip_y: int
:param processor_id: the processor id
:type processor_id: int
:param resources: The resources to be unallocated
:type resources:\
:py:class:`pacman.model.resources.ResourceContainer`
:param ip_tags: the details of the ip tags allocated
:type ip_tags: iterable of (str, int) or None
:param reverse_ip_tags: the details of the reverse ip tags allocated
:type reverse_ip_tags: iterable of (str, int) or None
:rtype: None
"""
self._chips_available.add((chip_x, chip_y))
self._sdram_tracker[chip_x, chip_y] -= resources.sdram.get_value()
self._core_tracker[(chip_x, chip_y)].add(processor_id)
# check if chip used needs updating
if (len(self._core_tracker[(chip_x, chip_y)]) ==
self._machine.get_chip_at(chip_x, chip_y).n_user_processors):
self._chips_used.remove((chip_x, chip_y))
# Deallocate the ip tags
if ip_tags is not None:
for (board_address, tag, _, _) in ip_tags:
self._boards_with_ip_tags.add(board_address)
tag_key = (board_address, tag)
self._n_ip_tag_allocations[tag_key] -= 1
if self._n_ip_tag_allocations[tag_key] == 0:
key = self._address_and_traffic_ip_tag[tag_key]
del self._address_and_traffic_ip_tag[tag_key]
self._ip_tags_address_traffic[key].remove(tag_key)
if len(self._ip_tags_address_traffic[key]) == 0:
del self._ip_tags_address_traffic[key]
self._tags_by_board[board_address].add(tag)
del self._ip_tags_strip_sdp_and_port[tag_key]
# Deallocate the reverse ip tags
if reverse_ip_tags is not None:
for (board_address, tag) in reverse_ip_tags:
self._boards_with_ip_tags.add(board_address)
self._tags_by_board[board_address].add(tag)
port = self._listen_port_reverse_ip_tag.get(
(board_address, tag), None)
if port is not None:
del self._listen_port_reverse_ip_tag[(board_address, tag)]
self._reverse_ip_tag_listen_port.remove(
(board_address, port))
[docs] def is_chip_available(self, chip_x, chip_y):
""" Check if a given chip is available
:param chip_x: the x coord of the chip
:type chip_x: int
:param chip_y: the y coord of the chip
:type chip_y: int
:return: True if the chip is available, False otherwise
:rtype: bool
"""
return (chip_x, chip_y) in self._chips_available
@property
def keys(self):
""" The chip coordinates assigned
"""
return self._sdram_tracker.keys()
@property
def chips_used(self):
""" deduce the number of chips used in this allocation
:return: the number of chips used during the allocation.
"""
return len(self._chips_used)