Source code for pacman.operations.router_compressors.ranged_compressor
# Copyright (c) 2017 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 logging
import sys
from typing import cast
from spinn_utilities.config_holder import get_config_bool
from spinn_utilities.log import FormatAdapter
from spinn_utilities.progress_bar import ProgressBar
from spinn_machine import MulticastRoutingEntry, RoutingEntry
from pacman.data import PacmanDataView
from pacman.model.routing_tables import (
CompressedMulticastRoutingTable, MulticastRoutingTables,
AbstractMulticastRoutingTable, UnCompressedMulticastRoutingTable)
from pacman.exceptions import MinimisationFailedError
logger = FormatAdapter(logging.getLogger(__name__))
def range_compressor(accept_overflow: bool = True) -> MulticastRoutingTables:
"""
:param bool accept_overflow:
A flag which should only be used in testing to stop raising an
exception if result is too big
:rtype: MulticastRoutingTables
"""
if accept_overflow:
message = "Precompressing tables using Range Compressor"
else:
message = "Compressing tables using Range Compressor"
router_tables = PacmanDataView.get_uncompressed()
assert router_tables is not None
progress = ProgressBar(len(router_tables.routing_tables), message)
compressor = RangeCompressor()
compressed_tables = MulticastRoutingTables()
for table in progress.over(router_tables.routing_tables):
new_table = compressor.compress_table(cast(
UnCompressedMulticastRoutingTable, table))
chip = PacmanDataView.get_chip_at(table.x, table.y)
target = chip.router.n_available_multicast_entries
if new_table.number_of_entries > target and not accept_overflow:
raise MinimisationFailedError(
f"The routing table {table.x} {table.y} with "
f"{table.number_of_entries} entries after compression "
f"still has {new_table.number_of_entries} so will not fit")
compressed_tables.add_routing_table(new_table)
logger.info(f"Ranged compressor resulted with the largest table of size "
f"{compressed_tables.get_max_number_of_entries()}")
return compressed_tables
class RangeCompressor(object):
"""
A compressor based on ranges.
Use via :py:func:`range_compressor`.
"""
# pylint: disable=attribute-defined-outside-init
__slots__ = (
# Router table to add merged routes into
"_compressed",
# List of entries to be merged
"_entries")
[docs]
def compress_table(
self, uncompressed: UnCompressedMulticastRoutingTable
) -> AbstractMulticastRoutingTable:
"""
Compresses all the entries for a single table.
Compressed the entries for this unordered table
returning a new table with possibly fewer entries
:param UnCompressedMulticastRoutingTable uncompressed:
Original Routing table for a single chip
:return: Compressed routing table for the same chip
:rtype: AbstractMulticastRoutingTable
"""
# Check you need to compress
if not get_config_bool(
"Mapping", "router_table_compress_as_far_as_possible"):
chip = PacmanDataView.get_chip_at(uncompressed.x, uncompressed.y)
target = chip.router.n_available_multicast_entries
if uncompressed.number_of_entries < target:
return uncompressed
# Step 1 get the entries and make sure they are sorted by key
self._entries = list(uncompressed.multicast_routing_entries)
self._entries.sort(key=lambda x: x.key)
if not self._validate():
return uncompressed
# Step 2 Create the results Table
self._compressed = CompressedMulticastRoutingTable(
uncompressed.x, uncompressed.y)
# Step 3 Find ranges of entries with the same route and merge them
# Start the first range
route = self._entries[0].spinnaker_route
first = 0
for i, entry in enumerate(self._entries):
# Keep building the range until the route changes
if entry.spinnaker_route != route:
# Merge all the entries in the range
self._merge_range(first, i-1)
# Start the next range with this entry
first = i
route = entry.spinnaker_route
# Merge the last range
self._merge_range(first, i) # pylint:disable=undefined-loop-variable
# return the results as a list
return self._compressed
def _validate(self) -> bool:
for i in range(len(self._entries)):
if self._get_endpoint(i) >= self._get_key(i+1):
logger.warning(
"Unable to run range compressor because entries overlap")
return False
return True
def _get_key(self, index: int) -> int:
"""
Gets routing_entry_key for entry index with support for index overflow
if index == len(self._entries):
return sys.maxsize
:param int index:
"""
if index == len(self._entries):
return sys.maxsize
entry = self._entries[index]
return entry.key & entry.mask
def _get_endpoint(self, index: int) -> int:
"""
Get the end of the range covered by entry index's key and mask
With support for index underflow
:param index:
"""
if index < 0:
return 0
entry = self._entries[index]
# return the key plus the mask flipping ones and zeros
return (entry.key | ~entry.mask) & 0xFFFFFFFF
def _merge_range(self, first: int, last: int) -> None:
while True:
# With a range of 1 just use the existing
if first == last:
self._compressed.add_multicast_routing_entry(
self._entries[first])
return
# Find the points the range must cover
first_point = self._get_key(first)
last_point = self._get_endpoint(last)
# Find the points the range may NOT go into
pre_point = self._get_endpoint(first - 1)
post_point = self._get_key(last + 1)
# find the power big enough to include the first and last entry
dif = last_point - first_point
power = 1 if dif < 1 else 1 << ((dif - 1).bit_length())
# Find the start range cut-off
low_cut = first_point // power * power
high_cut = low_cut + power
# If that does not cover all try one power higher
if high_cut < last_point:
power <<= 1
low_cut = first_point // power * power
high_cut = low_cut + power
# The power is too big if it touches the entry before or after
while power > 1 and (low_cut < pre_point or high_cut > post_point):
power >>= 1
low_cut = first_point // power * power
high_cut = low_cut + power
# The range may now not cover all the index so reduce the indexes
full_last = last
while high_cut <= last_point:
last -= 1
last_point = self._get_endpoint(last)
# make the new router entry
new_mask = 2 ** 32 - power
route = self._entries[first].spinnaker_route
entry = RoutingEntry(spinnaker_route=route)
self._compressed.add_multicast_routing_entry(MulticastRoutingEntry(
low_cut, new_mask, entry))
# Check if we're done
if full_last == last:
return
# Loop to do any indexes skip from before
first, last = last + 1, full_last