def __init__(self):
     self._multi_cast_routing_paths = MulticastRoutingPaths()
     self._direction_translation = {
         "CORE_0": (True, 0),
         "CORE_1": (True, 1),
         "CORE_2": (True, 2),
         "CORE_3": (True, 3),
         "CORE_4": (True, 4),
         "CORE_5": (True, 5),
         "CORE_6": (True, 6),
         "CORE_7": (True, 7),
         "CORE_8": (True, 8),
         "CORE_9": (True, 9),
         "CORE_10": (True, 10),
         "CORE_11": (True, 11),
         "CORE_12": (True, 12),
         "CORE_13": (True, 13),
         "CORE_14": (True, 14),
         "CORE_15": (True, 15),
         "CORE_16": (True, 16),
         "CORE_17": (True, 17),
         "EAST": (False, 0),
         "NORTH_EAST": (False, 1),
         "NORTH": (False, 2),
         "WEST": (False, 3),
         "SOUTH_WEST": (False, 4),
         "SOUTH": (False, 5)
     }
Exemplo n.º 2
0
    def __call__(self, placements, machine, partitioned_graph, k=1, l=0, m=0,
                 bw_per_route_entry=BW_PER_ROUTE_ENTRY, max_bw=MAX_BW):
        """ Find routes between the subedges with the allocated information,
            placed in the given places

        :param placements: The placements of the subedges
        :type placements:\
                    :py:class:`pacman.model.placements.placements.Placements`
        :param machine: The machine through which the routes are to be found
        :type machine: :py:class:`spinn_machine.machine.Machine`
        :param partitioned_graph: the partitioned_graph object
        :type partitioned_graph:\
                    :py:class:`pacman.partitioned_graph.partitioned_graph.PartitionedGraph`
        :return: The discovered routes
        :rtype:\
                    :py:class:`pacman.model.routing_tables.multicast_routing_tables.MulticastRoutingTables`
        :raise pacman.exceptions.PacmanRoutingException: If something\
                   goes wrong with the routing
        """

        # set up basic data structures
        self._routing_paths = MulticastRoutingPaths()
        self._k = k
        self._l = l
        self._m = m
        self._bw_per_route_entry = bw_per_route_entry
        self._max_bw = max_bw
        self._machine = machine

        nodes_info = self._initiate_node_info(machine)
        dijkstra_tables = self._initiate_dijkstra_tables(machine)
        self._update_all_weights(nodes_info, machine)

        # each subvertex represents a core in the board
        progress = ProgressBar(len(list(placements.placements)),
                               "Creating routing entries")

        for placement in placements.placements:
            subvert = placement.subvertex
            out_going_sub_edges = \
                partitioned_graph.outgoing_subedges_from_subvertex(subvert)
            out_going_sub_edges = filter(
                lambda edge: isinstance(edge, MultiCastPartitionedEdge),
                out_going_sub_edges)

            dest_chips = set()
            subedges_to_route = list()

            for subedge in out_going_sub_edges:
                destination_subvetex = subedge.post_subvertex
                destination_placement = \
                    placements.get_placement_of_subvertex(destination_subvetex)

                chip = machine.get_chip_at(destination_placement.x,
                                           destination_placement.y)
                dest_chips.add((chip.x, chip.y))
                subedges_to_route.append(subedge)

            if len(dest_chips) != 0:
                self._update_all_weights(nodes_info, machine)
                self._reset_tables(dijkstra_tables)
                dijkstra_tables[
                    (placement.x, placement.y)]["activated?"] = True
                dijkstra_tables[(placement.x, placement.y)]["lowest cost"] = 0
                self._propagate_costs_until_reached_destinations(
                    dijkstra_tables, nodes_info, dest_chips, placement.x,
                    placement.y)

            for subedge in subedges_to_route:
                dest = subedge.post_subvertex
                dest_placement = placements.get_placement_of_subvertex(dest)
                self._retrace_back_to_source(
                    dest_placement.x, dest_placement.y, dijkstra_tables,
                    dest_placement.p, subedge, nodes_info, placement.p)
            progress.update()
        progress.end()
        return {'routing_paths': self._routing_paths}
Exemplo n.º 3
0
class BasicDijkstraRouting(object):
    """ An routing algorithm that can find routes for subedges between\
        subvertices in a partitioned_graph that have been placed on a
        machine by the use of a dijkstra shortest path algorithm
    """

    BW_PER_ROUTE_ENTRY = 0.01
    MAX_BW = 250

    def __call__(self, placements, machine, partitioned_graph, k=1, l=0, m=0,
                 bw_per_route_entry=BW_PER_ROUTE_ENTRY, max_bw=MAX_BW):
        """ Find routes between the subedges with the allocated information,
            placed in the given places

        :param placements: The placements of the subedges
        :type placements:\
                    :py:class:`pacman.model.placements.placements.Placements`
        :param machine: The machine through which the routes are to be found
        :type machine: :py:class:`spinn_machine.machine.Machine`
        :param partitioned_graph: the partitioned_graph object
        :type partitioned_graph:\
                    :py:class:`pacman.partitioned_graph.partitioned_graph.PartitionedGraph`
        :return: The discovered routes
        :rtype:\
                    :py:class:`pacman.model.routing_tables.multicast_routing_tables.MulticastRoutingTables`
        :raise pacman.exceptions.PacmanRoutingException: If something\
                   goes wrong with the routing
        """

        # set up basic data structures
        self._routing_paths = MulticastRoutingPaths()
        self._k = k
        self._l = l
        self._m = m
        self._bw_per_route_entry = bw_per_route_entry
        self._max_bw = max_bw
        self._machine = machine

        nodes_info = self._initiate_node_info(machine)
        dijkstra_tables = self._initiate_dijkstra_tables(machine)
        self._update_all_weights(nodes_info, machine)

        # each subvertex represents a core in the board
        progress = ProgressBar(len(list(placements.placements)),
                               "Creating routing entries")

        for placement in placements.placements:
            subvert = placement.subvertex
            out_going_sub_edges = \
                partitioned_graph.outgoing_subedges_from_subvertex(subvert)
            out_going_sub_edges = filter(
                lambda edge: isinstance(edge, MultiCastPartitionedEdge),
                out_going_sub_edges)

            dest_chips = set()
            subedges_to_route = list()

            for subedge in out_going_sub_edges:
                destination_subvetex = subedge.post_subvertex
                destination_placement = \
                    placements.get_placement_of_subvertex(destination_subvetex)

                chip = machine.get_chip_at(destination_placement.x,
                                           destination_placement.y)
                dest_chips.add((chip.x, chip.y))
                subedges_to_route.append(subedge)

            if len(dest_chips) != 0:
                self._update_all_weights(nodes_info, machine)
                self._reset_tables(dijkstra_tables)
                dijkstra_tables[
                    (placement.x, placement.y)]["activated?"] = True
                dijkstra_tables[(placement.x, placement.y)]["lowest cost"] = 0
                self._propagate_costs_until_reached_destinations(
                    dijkstra_tables, nodes_info, dest_chips, placement.x,
                    placement.y)

            for subedge in subedges_to_route:
                dest = subedge.post_subvertex
                dest_placement = placements.get_placement_of_subvertex(dest)
                self._retrace_back_to_source(
                    dest_placement.x, dest_placement.y, dijkstra_tables,
                    dest_placement.p, subedge, nodes_info, placement.p)
            progress.update()
        progress.end()
        return {'routing_paths': self._routing_paths}

    def _initiate_node_info(self, machine):
        """ private method DO NOT CALL FROM OUTSIDE BASIC DIJKSTRA ROUTING. \
        used for setting up a dictionary which contains data for each chip in \
        the machine

        :param machine: the machine object
        :type machine: spinn_machine.machine.Machine
        :return: nodes_info dictionary
        :rtype: dict
        :raise None: this method does not raise any known exceptions
        """
        nodes_info = dict()
        for chip in machine.chips:
            x, y = chip.x, chip.y

            # get_neighbours should return a list of
            # dictionaries of 'x' and 'y' values
            nodes_info[(x, y)] = dict()

            nodes_info[(x, y)]["neighbours"] = list()
            for source_id in range(6):
                nodes_info[(x, y)]["neighbours"].append(
                    chip.router.get_link(source_id))

            nodes_info[(x, y)]["bws"] = []

            nodes_info[(x, y)]["weights"] = []

            for i in range(len(nodes_info[(x, y)]["neighbours"])):

                nodes_info[(x, y)]["weights"].append(None)

                if nodes_info[(x, y)]["neighbours"][i] is None:

                    nodes_info[(x, y)]["bws"].append(None)

                else:

                    nodes_info[(x, y)]["bws"].append(self._max_bw)
        return nodes_info

    @staticmethod
    def _initiate_dijkstra_tables(machine):
        """ Set up the Dijkstra's table which includes if you've reached a \
            given node

        :param machine: the machine object
        :type machine: the spinn_machine.machine.Machine object
        :return the  Dijkstra's table dictionary
        :rtype: dict
        :raise None: this method does not raise any known exception
        """
        # Holds all the information about nodes within one full run of
        # Dijkstra's algorithm
        dijkstra_tables = dict()

        for chip in machine.chips:
            x, y = chip.x, chip.y

            # Each node has a dictionary, or 'table'
            dijkstra_tables[(x, y)] = dict()

            dijkstra_tables[(x, y)]["lowest cost"] = None
            dijkstra_tables[(x, y)]["activated?"] = False
        return dijkstra_tables

    def _update_all_weights(self, nodes_info, machine):
        """ private method DO NOT CALL FROM OUTSIDE BASIC DIJKSTRA ROUTING. \
            used by the routing algorithm to change the weights of the\
            neighbouring nodes

        :param nodes_info: the node info dictionary
        :param machine: the machine python object that represents the\
                    structure of the machine
        :type nodes_info: dict
        :type machine 'py:class':spinn_machine.machine.Machine
        :return None
        :rtype: None
        :raise None: this method does not raise any known exception
        """
        for key in nodes_info:
            if nodes_info[key] is not None:
                self._update_neighbour_weights(nodes_info, machine, key)

    def _update_neighbour_weights(self, nodes_info, machine, key):
        """ Change the weights of the neighbouring nodes

        :param nodes_info: the node info dictionary
        :param machine: the machine python object that represents the\
                    structure of the machine
        :param key: the identifier to the object in nodes_info
        :type key: str
        :type nodes_info: dict
        :type machine 'py:class':spinn_machine.machine.Machine
        :return None
        :rtype: None
        :raise None: this method does not raise any known exception
        """
        for n in range(len(nodes_info[key]["neighbours"])):
            if nodes_info[key]["neighbours"][n] is not None:
                neighbour = nodes_info[key]["neighbours"][n]
                xn, yn = neighbour.destination_x, neighbour.destination_y
                entries = self._routing_paths.get_entries_for_router(xn, yn)
                nodes_info[key]["weights"][n] = self._get_weight(
                    machine.get_chip_at(xn, yn).router,
                    nodes_info[key]["bws"][n],
                    len(entries))

    def _get_weight(self, router, bws, no_routing_table_entries):
        """ Get the weight based on basic heuristics

        :param router: the router to assess the weight of
        :param bws: the basic weight of the source node
        :param no_routing_table_entries: the number of entries going though
         this router
        :type router: spinn_machine.router.Router
        :type bws: int
        :type no_routing_table_entries: int
        :return weight of this router
        :rtype: int
        :raise None: does not raise any known exception
        """
        free_entries = (router.ROUTER_DEFAULT_AVAILABLE_ENTRIES -
                        no_routing_table_entries)

        q = 0
        if self._l > 0:
            q = float(self._l *
                      (1 / float(free_entries) - 1 /
                       float(router.ROUTER_DEFAULT_AVAILABLE_ENTRIES)))

        t = 0
        if self._m > 0:
            t = self._m * (1 / float(bws) - 1 / float(self._max_bw))

        weight = self._k + q + t
        return weight

    @staticmethod
    def _reset_tables(dijkstra_tables):
        """ Reset the dijsktra tables for a new path search

        :param dijkstra_tables: the dictionary object for the dijkstra-tables
        :type dijkstra_tables: dict
        :return: None
        :rtype: None
        :raise None: this method does not raise any known exception
        """
        for key in dijkstra_tables:
            dijkstra_tables[key]["lowest cost"] = None
            dijkstra_tables[key]["activated?"] = False

    def _propagate_costs_until_reached_destinations(
            self, dijkstra_tables, nodes_info, dest_chips,
            x_source, y_source):
        """ Propagate the weights till the destination nodes of the source\
            nodes are retraced

         :param dijkstra_tables: the dictionary object for the dijkstra-tables
         :param nodes_info: the dictionary object for the nodes inside a route\
                    scope
         :param dest_chips:
         :param x_source:
         :param y_source:
         :type dijkstra_tables: dict
         :type nodes_info: dict
         :type dest_chips:
         :type x_source: int
         :type y_source: int
         :return: None
         :rtype: None
         :raise PacmanRoutingException: when the destination node could not be\
                    reached from this source node.
        """

        dest_chips_to_find = set(dest_chips)
        try:
            dest_chips_to_find.remove((x_source, y_source))
        except KeyError:
            # Ignore this - it just isn't in the set of destinations
            pass

        x_current = x_source
        y_current = y_source

        # Iterate only if the destination node hasn't been activated
        while len(dest_chips_to_find) > 0:

            # PROPAGATE!
            for i in range(len(
                    nodes_info[(x_current, y_current)]["neighbours"])):
                neighbour = nodes_info[(x_current, y_current)]["neighbours"][i]
                weight = nodes_info[(x_current, y_current)]["weights"][i]

                # "neighbours" is a list of 6 links or None objects.
                # There is a None object where there is no connection to
                # that neighbour.
                if ((neighbour is not None) and
                        not (neighbour.destination_x == x_source and
                             neighbour.destination_y == y_source)):

                    # These variables change with every look at a new neighbour
                    self._update_neighbour(
                        dijkstra_tables, neighbour.destination_x,
                        neighbour.destination_y, x_current, y_current,
                        x_source, y_source, weight)

            # This cannot be done in the above loop, since when a node
            # becomes activated the rest of the costs cannot be retrieved, and
            #  a new partitionable_graph lowest cost cannot be found

            # This is the lowest cost across ALL
            # un-activated nodes in the partitionable_graph.
            graph_lowest_cost = None

            # Find the next node to be activated
            for key in dijkstra_tables:

                # Don't continue if the node hasn't even been touched yet
                if (dijkstra_tables[key]["lowest cost"] is not None and
                        not dijkstra_tables[key]["activated?"] and
                        (graph_lowest_cost is not None and
                         (dijkstra_tables[key]["lowest cost"] <
                          graph_lowest_cost) or graph_lowest_cost is None)):
                    graph_lowest_cost = dijkstra_tables[key]["lowest cost"]
                    x_current, y_current = int(key[0]), int(key[1])

            # If there were no un-activated nodes with costs,
            # but the destination was not reached this iteration,
            # raise an exception
            if graph_lowest_cost is None:
                raise exceptions.PacmanRoutingException(
                    "Destination could not be activated, ending run")

            # Set the next activated node as the un-activated node with the
            #  lowest current cost
            dijkstra_tables[(x_current, y_current)]["activated?"] = True
            try:
                dest_chips_to_find.remove((x_current, y_current))
            except KeyError:
                # Ignore the error - it just isn't a destination chip
                pass

    @staticmethod
    def _update_neighbour(
            dijkstra_tables, x_neighbour, y_neighbour, x_current, y_current,
            x_source, y_source, weight):
        """private method DO NOT CALL FROM OUTSIDE BASIC DIJKSTRA ROUTING. \
        used to update the lowest cost for each neighbour of a node

        :param dijkstra_tables:
        :param x_current:
        :param y_current:
        :param x_source:
        :param y_source:
        :param x_neighbour:
        :param y_neighbour:
        :param weight:
        :type dijkstra_tables:
        :type x_current:
        :type y_current:
        :type x_source:
        :type y_source:
        :type x_neighbour:
        :type y_neighbour:
        :type weight:
        :return:
        :rtype:
        :raise PacmanRoutingException: when the algorithm goes to a node that\
                    doesn't exist in the machine or the node's cost was set\
                    too low.
        """
        neighbour_exists = (x_neighbour, y_neighbour) in dijkstra_tables
        if not neighbour_exists:
            raise exceptions.PacmanRoutingException(
                "Tried to propagate to ({}, {}), which is not in the"
                " partitionable_graph: remove non-existent neighbours"
                .format(x_neighbour, y_neighbour))

        neighbour_activated =\
            dijkstra_tables[(x_neighbour, y_neighbour)]["activated?"]
        chip_lowest_cost =\
            dijkstra_tables[(x_current, y_current)]["lowest cost"]
        neighbour_lowest_cost =\
            dijkstra_tables[(x_neighbour, y_neighbour)]["lowest cost"]

        # Only try to update if the neighbour is within the partitionable_graph
        #  and the cost if the node hasn't already been activated
        # and the lowest cost if the new cost is less, or if
        # there is no current cost
        new_weight = float(chip_lowest_cost + weight)
        if (not neighbour_activated and
                (neighbour_lowest_cost is None or
                 new_weight < neighbour_lowest_cost)):

            # update dijkstra table
            dijkstra_tables[(x_neighbour, y_neighbour)]["lowest cost"] =\
                new_weight

        if (dijkstra_tables[(x_neighbour, y_neighbour)]["lowest cost"] == 0) \
                and (x_neighbour != x_source or y_neighbour != y_source):
            raise exceptions.PacmanRoutingException(
                "!!!Cost of non-source node ({}, {}) was set to zero!!!"
                .format(x_neighbour, y_neighbour))

    def _retrace_back_to_source(
            self, x_destination, y_destination, dijkstra_tables,
            processor_dest, subedge, nodes_info, source_processor):
        """private method DO NOT CALL FROM OUTSIDE BASIC DIJKSTRA ROUTING. \

        :param x_destination:
        :param y_destination:
        :param dijkstra_tables:
        :param processor_dest:
        :param subedge:
        :param nodes_info:
        :type nodes_info:
        :type subedge:
        :type x_destination:
        :type y_destination:
        :type dijkstra_tables:
        :type processor_dest:
        :return: the next coords to look into
        :rtype: int int
        :raise PacmanRoutingException: when the algorithm doesn't find a next\
                    point to search from. AKA, the neighbours of a chip do not\
                    have a cheaper cost than the node itself, but the node is\
                    not the destination or when the algorithm goes to a node\
                    that's not considered in the weighted search
        """
        # Set the tracking node to the destination to begin with
        x_current, y_current = x_destination, y_destination
        routing_entry_route_processors = []

        # if the processor is None, don't add to router path entry
        if processor_dest is not None:
            routing_entry_route_processors.append(processor_dest)
        routing_entry_route_links = None

        entry = MulticastRoutingPathEntry(
            router_x=x_destination, router_y=y_destination,
            edge=subedge, out_going_links=routing_entry_route_links,
            outgoing_processors=routing_entry_route_processors)
        self._routing_paths.add_path_entry(entry)
        previous_routing_entry = entry

        while dijkstra_tables[(x_current, y_current)]["lowest cost"] != 0:

            xcheck, ycheck = x_current, y_current

            neighbours = nodes_info[(x_current, y_current)]["neighbours"]
            neighbour_index = 0
            added_an_entry = False
            while not added_an_entry and neighbour_index < len(neighbours):
                neighbour = neighbours[neighbour_index]
                if neighbour is not None:

                    x_neighbour, y_neighbour = (neighbour.destination_x,
                                                neighbour.destination_y)

                    # Only check if it can be a preceding node if it actually
                    # exists
                    if (x_neighbour, y_neighbour) in dijkstra_tables:
                        dijkstra_table_key = (x_neighbour, y_neighbour)
                        lowest_cost = \
                            dijkstra_tables[dijkstra_table_key]["lowest cost"]
                        if lowest_cost is not None:
                            (x_current, y_current, previous_routing_entry,
                                added_an_entry) = self._create_routing_entry(
                                    x_neighbour, y_neighbour, dijkstra_tables,
                                    neighbour_index, nodes_info,
                                    x_current, y_current,
                                    previous_routing_entry, subedge)
                    else:
                        raise exceptions.PacmanRoutingException(
                            "Tried to trace back to node not in "
                            "partitionable_graph: remove non-existent"
                            " neighbours")
                neighbour_index += 1

            if x_current == xcheck and y_current == ycheck:
                raise exceptions.PacmanRoutingException(
                    "Iterated through all neighbours of tracking node but"
                    " did not find a preceding node! Consider increasing "
                    "acceptable discrepancy between sought traceback cost"
                    " and actual cost at node. Terminating...")
        previous_routing_entry.add_in_coming_processor_direction(
            source_processor)
        return x_current, y_current

    def _create_routing_entry(self, x_neighbour, y_neighbour, dijkstra_tables,
                              neighbour_index, nodes_info,
                              x_current, y_current, previous_routing_entry,
                              subedge):
        """ Create a new routing entry

        :param x_neighbour:
        :param y_neighbour:
        :param dijkstra_tables:
        :param neighbour_index
        :param nodes_info:
        :param x_current:
        :param y_current:
        :param previous_routing_entry:
        :param subedge:
        :type subedge:
        :type x_neighbour:
        :type y_neighbour:
        :type dijkstra_tables:
        :type neighbour_index
        :type nodes_info:
        :type x_current:
        :type y_current:
        :type previous_routing_entry:
        :return x_current, y_current, previous_routing_entry, made_an_entry
        :rtype: int, int, spinn_machine.multicast_routing_entry, bool
        :raise PacmanRoutingException: when the bandwidth of a router is\
                beyond expected parameters
        """

        # Set the direction of the routing other_entry as that which
        # is from the preceding node to the current tracking node
        # x_neighbour, y_neighbour are the 'old' coordinates since they are
        # from the preceding node. x_current and y_current are the 'new'
        # coordinates since they are where the router should send the packet to
        dec_direction = self._get_reverse_direction(neighbour_index)
        made_an_entry = False

        neighbour_weight = \
            nodes_info[(x_neighbour, y_neighbour)]["weights"][dec_direction]
        chip_sought_cost = \
            (dijkstra_tables[(x_current, y_current)]["lowest cost"] -
             neighbour_weight)
        neighbours_lowest_cost = \
            dijkstra_tables[(x_neighbour, y_neighbour)]["lowest cost"]

        if (neighbours_lowest_cost is not None and
                abs(neighbours_lowest_cost - chip_sought_cost) <
                0.00000000001):

            # create entry for next hop going backwards
            entry = MulticastRoutingPathEntry(
                router_x=x_neighbour, router_y=y_neighbour, edge=subedge,
                incoming_link=None, out_going_links=dec_direction,
                outgoing_processors=None)
            previous_routing_entry.add_in_coming_processor_direction(
                neighbour_index)
            # add entry for next hop going backwards into path
            self._routing_paths.add_path_entry(entry)
            previous_routing_entry = entry
            made_an_entry = True

            # Finally move the tracking node
            x_current, y_current = x_neighbour, y_neighbour

            nodes_info[(x_neighbour, y_neighbour)]["bws"][dec_direction] -= \
                self._bw_per_route_entry  # TODO arbitrary

            if (nodes_info[(x_neighbour, y_neighbour)]["bws"][dec_direction] <
                    0):
                print ("Bandwidth over-used from ({}, {}) in direction {}! to "
                       "({}, {})".format(x_neighbour, y_neighbour,
                                         dec_direction, x_current, y_current))

                raise exceptions.PacmanRoutingException(
                    "Bandwidth over-used as described above! Terminating...")
        return x_current, y_current, previous_routing_entry, made_an_entry

    @staticmethod
    def _get_reverse_direction(neighbour_position):
        """private method, do not call from outside dijskra routing\

        used to determine the direction of a link to go down

        :param neighbour_position: the position the neighbour is at
        :type neighbour_position: int
        :return: The position of the opposite link
        :rtype: int
        :raise None: this method does not raise any known exceptions
        """

        if neighbour_position == 0:
            return 3
        elif neighbour_position == 1:
            return 4
        elif neighbour_position == 2:
            return 5
        elif neighbour_position == 3:
            return 0
        elif neighbour_position == 4:
            return 1
        elif neighbour_position == 5:
            return 2
        return None
class ConvertToMemoryMultiCastRoutingPaths(object):
    """ Converts between file routing paths and the pacman representation of\
        the routing paths
    """

    def __init__(self):
        self._multi_cast_routing_paths = MulticastRoutingPaths()
        self._direction_translation = {
            "CORE_0": (True, 0),
            "CORE_1": (True, 1),
            "CORE_2": (True, 2),
            "CORE_3": (True, 3),
            "CORE_4": (True, 4),
            "CORE_5": (True, 5),
            "CORE_6": (True, 6),
            "CORE_7": (True, 7),
            "CORE_8": (True, 8),
            "CORE_9": (True, 9),
            "CORE_10": (True, 10),
            "CORE_11": (True, 11),
            "CORE_12": (True, 12),
            "CORE_13": (True, 13),
            "CORE_14": (True, 14),
            "CORE_15": (True, 15),
            "CORE_16": (True, 16),
            "CORE_17": (True, 17),
            "EAST": (False, 0),
            "NORTH_EAST": (False, 1),
            "NORTH": (False, 2),
            "WEST": (False, 3),
            "SOUTH_WEST": (False, 4),
            "SOUTH": (False, 5)
        }

    def __call__(self, file_routing_paths, partitioned_graph, placements,
                 machine):

        # load the json files
        file_routing_paths = self._handle_json_files(file_routing_paths)
        progress_bar = ProgressBar(len(file_routing_paths),
                                   "Converting to PACMAN routing paths")

        # iterate though the path for each edge and create entries
        for edge_id in file_routing_paths:
            edge = partitioned_graph.get_subedge_with_label(edge_id)

            # if the vertex is none, its a vertex with the special skills of
            # needing no cores. therefore ignore
            if edge is not None:
                placement = placements.get_placement_of_subvertex(
                    edge.pre_subvertex)
                self._create_entries_for_path(
                    edge_id, file_routing_paths[edge_id], None, placement.p,
                    partitioned_graph, machine, placements)
            progress_bar.update()
        progress_bar.end()

        return {'routing_paths': self._multi_cast_routing_paths}

    def _create_entries_for_path(
            self, edge_id, edge_path, source_link_id, source_p,
            partitioned_graph, machine, placements):
        """

        :param edge_id:
        :param edge_path:
        :param partitioned_graph:
        :param source_x:
        :param source_y:
        :param placements:
        :return:
        """
        chip_coords = edge_path['chip']
        memory_edges = []
        for child in edge_path['children']:
            direction_data = self._direction_translation[
                child['route'].upper()]
            next_hop = child['next_hop']
            if source_p is None:
                self._handle_none_core_level_entries(
                    direction_data, edge_id, next_hop, partitioned_graph,
                    machine, placements, chip_coords, source_link_id,
                    memory_edges)
            else:
                memory_edges += self._handle_core_level_entry(
                    placements, chip_coords, source_p, source_link_id,
                    partitioned_graph, edge_id, direction_data, next_hop,
                    machine)
        return memory_edges

    def _handle_none_core_level_entries(
            self, direction_data, edge_id, next_hop, partitioned_graph,
            machine, placements, chip_coords, source_link_id, memory_edges):
        if direction_data[0]:

            # has a core level here. focus on core level entries
            local_memory_edges = self._handle_core_level_entry(
                placements, chip_coords, None, source_link_id,
                partitioned_graph, edge_id, direction_data, next_hop, machine)
            memory_edges += local_memory_edges
        else:

            # none core level. keep searching
            local_memory_edges = self._create_entries_for_path(
                edge_id, next_hop, direction_data[1], None,
                partitioned_graph, machine, placements)
            memory_edges += local_memory_edges
            for memory_edge in local_memory_edges:
                entry = MulticastRoutingPathEntry(
                    router_x=chip_coords[0], router_y=chip_coords[1],
                    edge=memory_edge,
                    out_going_links=direction_data[1],
                    outgoing_processors=None, incoming_processor=None,
                    incoming_link=source_link_id)

                # add entry to system
                self._multi_cast_routing_paths.add_path_entry(entry)
        return memory_edges

    def _handle_core_level_entry(
            self, placements, chip_coords, source_p, source_link,
            partitioned_graph, edge_id, direction_data, next_hop, machine):
        """

        :param placements:
        :param chip_coords:
        :param source_p:
        :param partitioned_graph:
        :param edge_id:
        :param direction_data:
        :return:
        """

        # create list holder
        memory_edges = []

        # create correct entries
        if direction_data[0]:

            # locate edge this core is associated with
            subvertex = placements.get_subvertex_on_processor(
                chip_coords[0], chip_coords[1], direction_data[1])
            memory_edge = \
                partitioned_graph.get_subedge_with_label(edge_id, subvertex)
            entry = MulticastRoutingPathEntry(
                router_x=chip_coords[0], router_y=chip_coords[1],
                edge=memory_edge, outgoing_processors=direction_data[1],
                out_going_links=None, incoming_processor=source_p,
                incoming_link=source_link)
            memory_edges.append(memory_edge)

            # add entry to system
            self._multi_cast_routing_paths.add_path_entry(entry)
        else:
            local_memory_edges = self._create_entries_for_path(
                edge_id, next_hop, direction_data[1], None,
                partitioned_graph, machine, placements)
            memory_edges += local_memory_edges
            for memory_edge in local_memory_edges:
                entry = MulticastRoutingPathEntry(
                    router_x=chip_coords[0], router_y=chip_coords[1],
                    edge=memory_edge, outgoing_processors=None,
                    out_going_links=direction_data[1],
                    incoming_processor=source_p,
                    incoming_link=source_link)

                # add entry to system
                self._multi_cast_routing_paths.add_path_entry(entry)

        # search for new hops if needed
        if isinstance(next_hop, dict):
            local_memory_edges = self._create_entries_for_path(
                edge_id, next_hop, direction_data[1], None,
                partitioned_graph, machine, placements)
            memory_edges += local_memory_edges
            return memory_edges
        else:
            return memory_edges

    @staticmethod
    def _handle_json_files(file_routing_paths):
        """

        :param file_routing_paths:
        :return:
        """
        file_routing_paths_file = open(file_routing_paths, "r")
        file_routing_paths = json.load(file_routing_paths_file)

        # TODO: Routing Path validation is currently not possible due to
        #       recursion
        # validate the json files against the schemas
        # verify that the files meet the schema.
        # locate schemas
        # file_routing_paths_schema_file_path = os.path.join(
        #     os.path.dirname(file_format_schemas.__file__), "routes.json"
        # )
        # open readers for schemas and read in schema
        # file_to_read = open(file_routing_paths_schema_file_path, "r")
        # routing_paths_schema = json.load(file_to_read)

        # validate json file from json schema
        # jsonschema.validate(
        #    file_routing_paths, routing_paths_schema)

        return file_routing_paths