Ejemplo n.º 1
0
 def test_getNode_getEdge(self):
     print("test_getNode_getEdge")
     graph = DiGraph()
     graph.add_node(0)
     graph.add_node(1)
     graph.add_edge(0, 1, 1)
     e = graph.getEdge(0, 1)
     self.assertIsNotNone(e)
     self.assertEqual(e.getSrc(), 0)
     self.assertEqual(e.getDest(), 1)
     self.assertEqual(e.getWeight(), 1)
     n = graph.getNode(0)
     self.assertIsNotNone(n)
     self.assertEqual(n.getKey(), 0)
Ejemplo n.º 2
0
    def test_get_all_v(self):
        graph = DiGraph()
        for i in range(5):
            graph.add_node(i)
        graph.add_edge(0, 1, 1.2)
        graph.add_edge(1, 2, 1.2)
        graph.add_edge(2, 3, 1.2)
        graph.add_edge(3, 4, 1.2)

        n = graph.getNode(0)
        c = graph.get_all_v()
        z = c.keys()
        self.assertTrue(z.__contains__(n.getKey()))
        c.pop(n.getKey())
        self.assertFalse(z.__contains__(n.getKey()))
Ejemplo n.º 3
0
class GraphAlgo(GraphAlgoInterface):
    """This class represents a Directed (positive) Weighted Graph Theory Algorithms including:
    0. __init__()
    1. get_graph(self)
    2. load_from_json(self, file_name: str)
    3. save_to_json(self, file_name: str)
    4. shortest_path(self, id1: int, id2: int)
    5. connected_component(self, id1: int)
    6. connected_components(self)
    7. plot_graph(self)
    graph-Is an abstract representation of a set of nodes and edge,
    each edge has a weight,
    it is possible to have a route from node to another node."""
    def __init__(self, graph: GraphInterface = None):
        """
        Graph builder
        :param graph: graph
        """
        self.__graph = graph

    def get_graph(self) -> GraphInterface:
        """
        :return: the directed graph on which the algorithm works on
        """

        return self.__graph

    def load_from_json(self, file_name: str) -> bool:
        """
        This method load a graph to this graph.
        if the file was successfully loaded - the underlying graph
        of this class will be changed (to the loaded one), in case the
        graph was not loaded the original graph should remain "as is".
        :param file_name: file
        :return: true or false
        """

        self.__graph = DiGraph()
        try:
            with open(file_name, "r") as file:
                my_dict = json.load(file)
                for item in my_dict['Nodes']:
                    pos = []
                    if "pos" in item:
                        for p in item['pos'].split(','):
                            pos.append(float(p))
                    else:
                        pos = [0, 0, 0]

                    self.__graph.add_node(item['id'], (pos[0], pos[1], pos[2]))

                for item in my_dict['Edges']:
                    self.__graph.add_edge(item['src'], item['dest'], item['w'])

                return True
        except IOError:
            return False

    def save_to_json(self, file_name: str) -> bool:
        """
        Saves this weighted (directed) graph to the given
        file name - in JSON format
        :param file_name: file
        :return: true or false
        """
        try:
            with open(file_name, "w") as file:
                file.write(str(self.__graph))
                return True
        except IOError:
            return False

    def shortest_path(self, id1: int, id2: int) -> (float, list):
        """
        Returns the shortest path from node id1 to node id2 using Dijkstra's Algorithm - as an ordered List of nodes:
        src--> n1-->n2-->...dest.
        By pass the shortest path from the end to the beginning.
        if no such path exists return math.inf, [ ](Maximum route length and empty list).
        The method adds the destination vertex (id2) to the list and accesses the parent vertex by the info value of the
        vertex until it arrives node that its info value is -1 which we set as the end of the trajectory.
        We will then create a list that will contain the weight of the destination node (id2) and the list of vertices
        of the shortest route created
        :param id1: src
        :param id2: dest
        :return: list of shortest path
        """

        if id1 not in self.__graph.vertices or id2 not in self.__graph.vertices:
            pathInf = (math.inf, [])
            return pathInf

        self.Dijkstra(id1)
        distance = self.__graph.vertices.get(id2).getWeight()

        if distance == math.inf:
            pathInf = (distance, [])
            return pathInf

        path_list = []
        if id1 == id2:
            path_list.insert(0, id2)
            distance = 0
            listSp = (distance, path_list)
            return listSp
        else:
            path_list.insert(0, id2)
            parent = self.__graph.getNode(id2)

            while parent.getInfo() != -1:
                path_list.insert(0, parent.getInfo())
                parent = self.__graph.getNode(parent.getInfo())
        listSp = distance, path_list

        return listSp

    def connected_component(self, id1: int) -> list:
        """
        This method gets a vertex Finds the Strongly Connected Component (SCC) that node id1 is a part of.
        If the node does not exist in the graph another blank list will be returned
        A list will be returned that will contain the nodes keys that are part of the bindings component.
        This method creates 3 lists: neiOut- contains the vertices that can be reached from the source node and
        neiIn- which contains the vertices in the graph that can be reached from the source node.
        Another allNode list in which all vertices are transferred and sent to the Valentine list from the previous 2.
        We will first insert the source vertex into allNode and neiOut
        We will reset the tag values of all the vertices of the graph-1 and for the source vertex
        we have a value of tag 0 then we will add all the vertices that can be reached from the source node to
        neiOut by going over the neighbors of each vertex entering allNode and defining each vertex entering
        a tag 0 so we do not repeat the operation From one time. We will perform these operations as long as
        the allNode list is not empty
        We will perform the same operation in the opposite direction, we will add to allNode all the codecs that
        can be reached from the source node by going over the neighbors' neighbors and thus we will fill
        in the neiIn list.
        Once these 2 lists are complete we will create a list that will contain the vertices that belong
        to neiIn and also neiOut which will return to us the Strongly Connected Component.

        :param id1: src
        :return: list of Strongly Connected Component
        """

        if self.__graph.vertices.get(id1) is None:
            list_none = []
            return list_none
        neiIn = []
        neiOut = []
        allNode = [self.__graph.getNode(id1)]
        neiOut.append(self.__graph.getNode(id1))

        for n in self.__graph.get_all_v().keys():
            temp = self.__graph.vertices.get(n)
            temp.setTag(-1)

        while len(allNode) > 0:
            prev = allNode.pop()
            prev.setTag(0)

            for nOut in self.__graph.neighborsOut.get(prev.getKey()).keys():
                temp1 = self.__graph.vertices.get(nOut)
                if temp1.getTag() != 0:
                    allNode.append(temp1)
                    neiOut.append(temp1)
                    temp1.setTag(0)

        for n in self.__graph.get_all_v().keys():
            temp = self.__graph.vertices.get(n)
            temp.setTag(-1)
        allNode.append(self.__graph.getNode(id1))
        neiIn.append(self.__graph.getNode(id1))

        while len(allNode) > 0:
            prev = allNode.pop()
            prev.setTag(0)
            for nIn in self.__graph.neighborsIn.get(prev.getKey()).keys():
                temp2 = self.__graph.vertices.get(nIn)
                if temp2.getTag() != 0:
                    allNode.append(temp2)
                    neiIn.append(temp2)
                    temp2.setTag(0)

        for node in neiOut:
            for node2 in neiIn:
                if node.getKey() == node2.getKey():
                    allNode.append(node.getKey())
                    break
        return allNode

    def connected_components(self) -> List[list]:
        """
          Finds all the Strongly Connected Component(SCC) in the graph.
        If there are no binding elements in the graph, [[]]] will return a list of empty lists.
        In the method we put all the keys in the graph in the allV list.
        We then sent a random vertex to the connected_component method ()
        The list of the linking component we received will be added to the allComponents list
        And the vertices component bindings we got from the method we will remove from the allV list.
        We will do this as long as our list of vertices is not empty.
        And finally we return the allComponents list that contains all the existing binding elements in the graph.
        :return: list of list of Strongly Connected Components
        """

        if self.__graph.v_size() < 1:
            return [[]]

        allComponents = []
        allV = []

        for k in self.__graph.get_all_v().keys():
            allV.append(k)

        while len(allV) > 0:
            temp = allV[0]
            oneComponent = self.connected_component(temp)
            allComponents.append(oneComponent)

            for v in oneComponent:
                allV.remove(v)
        return allComponents

    def plot_graph(self) -> None:
        """
        Plots the graph.
        If the nodes have a position, the nodes will be placed there.
        Otherwise, they will be placed in a random but elegant manner.
        The method draws the graph using the PlotGraph class
        (An explanation of the methods can be found in the imitation)
        Which has methods that create the graph using the "matplotlib" directory
        """

        plot = PlotGraph(self.__graph)
        plot.have_pos()
        if plot.have_pos() is False:
            plot.random_pos()
        plot.paint()

    def Dijkstra(self, node_id: int):
        """
        Algorithm for finding the shortest route with the help of a priority queue
        We will first go through all the nodes in the graph and define their math.inf weight
        Value info -1
        We will insert the given vertex into the queue and as long as the queue is not empty
         we will perform the following steps:
        We will delete the vertex at the top of the queue and pass over all the neighbors of the
        same vertex and define a weight for them using the weight of the parent node and the connecting
        side between them so that we pass over all the vertices in the graph
        (if it is a different link only to some of them)
        And we will mark their weight a method which will help us find the shortest route.
        :param node_id: src
        """

        queue = PriorityQueue()
        for node in self.__graph.vertices.values():
            node.setWeight(math.inf)
            node.setInfo(-1)
        src = self.__graph.vertices.get(node_id)
        src.setWeight(0)
        queue.insert(src)
        while not queue.isEmpty():
            prev = queue.delete()
            for k, w in self.__graph.all_out_edges_of_node(
                    prev.getKey()).items():
                dest = self.__graph.vertices.get(k)
                if dest.getWeight() > prev.getWeight() + w:
                    dest.setWeight(prev.getWeight() + w)
                    queue.insert(dest)
                    dest.setInfo(prev.getKey())
Ejemplo n.º 4
0
class GraphAlgo(GraphAlgoInterface):
    def __init__(self, graph=None):
        if graph is None:
            self.directed_weighted_graph = DiGraph()
        else:
            self.directed_weighted_graph = graph
        self.parent = {}
        self.dis = {}

    def get_graph(self) -> GraphInterface:
        """
        :return: the directed graph on which the algorithm works on.
        """
        return self.directed_weighted_graph

    def parse_file_name(self, file_name):
        file_name = file_name.replace('/', '\\')
        file_name = file_name.replace('\\\\', '\\')
        file_name = file_name.split('\\')
        base_path = Path(__file__).parent.parent
        file_path = ""
        for i in range(len(file_name) - 1):
            if not file_name[i].startswith('..'):
                file_path += file_name[i] + '/'
        file_path = (base_path / file_path /
                     file_name[len(file_name) - 1]).resolve()
        return file_path

    def load_from_json(self, file_name: str) -> bool:
        """
        Loads a graph from a json file.
        @param file_name: The path to the json file
        @returns True if the loading was successful, False o.w.
        """
        file_path = self.parse_file_name(file_name)

        with open(file_path, 'r') as fp:
            data = json.load(fp)

        nodes = data["Nodes"]
        edges = data["Edges"]

        for n in nodes:
            if "pos" in n:
                pos = n['pos']
                if type(pos) == str:
                    pos = tuple(pos.split(','))
                pos = (float(pos[0]), float(pos[1]))
                self.directed_weighted_graph.add_node(n["id"], pos)
            else:
                self.directed_weighted_graph.add_node(n["id"])
        for e in edges:
            self.directed_weighted_graph.add_edge(e["src"], e["dest"], e["w"])
        return True

    def save_to_json(self, file_name: str) -> bool:
        """
        Saves the graph in JSON format to a file
        @param file_name: The path to the out file
        @return: True if the save was successful, False o.w.
        """
        file_name = self.parse_file_name(file_name)
        try:
            os.remove(file_name)
        except OSError:
            pass

        edges = self.directed_weighted_graph.get_edges()
        nodes = self.directed_weighted_graph.get_nodes()
        json_file = {}
        jsonEdges = []
        jsonNodes = []

        for src in edges:
            for dest in edges[src]:
                edge = edges[src][dest]
                parsed_edge = {
                    'src': edge.getSrc(),
                    'dest': edge.getDest(),
                    'w': edge.getWeight()
                }
                jsonEdges.append(parsed_edge)

        for k in nodes:
            if nodes[k].getLocation():
                pos = nodes[k].getLocation()
                parsed_node = {'pos': pos, 'id': k}
            else:
                parsed_node = {'id': k}
            jsonNodes.append(parsed_node)

        json_file["Edges"] = jsonEdges
        json_file["Nodes"] = jsonNodes
        with open(file_name, 'x') as fp:
            json.dump(json_file, fp)
            return True

    def shortest_path_dist(self, src: int, dest: int) -> float:
        if src == dest:
            return 0
        self.dijkstra(self.directed_weighted_graph.getNode(src))
        return self.dis[dest]

    def shortest_path(self, id1: int, id2: int) -> (float, list):
        """
        Returns the shortest path from node id1 to node id2 using Dijkstra's Algorithm
        @param id1: The start node id
        @param id2: The end node id
        @return: The distance of the path, a list of the nodes ids that the path goes through
        Example:
#      >>> from GraphAlgo import GraphAlgo
#       >>> g_algo = GraphAlgo()
#        >>> g_algo.addNode(0)
#        >>> g_algo.addNode(1)
#        >>> g_algo.addNode(2)
#        >>> g_algo.addEdge(0,1,1)
#        >>> g_algo.addEdge(1,2,4)
#        >>> g_algo.shortestPath(0,1)
#        (1, [0, 1])
#        >>> g_algo.shortestPath(0,2)
#        (5, [0, 1, 2])
        Notes:
        If there is no path between id1 and id2, or one of them dose not exist the function returns (float('inf'),[])
        More info:
        https://en.wikipedia.org/wiki/Dijkstra's_algorithm
        """
        if self.directed_weighted_graph.getNode(
                id1) is None or self.directed_weighted_graph.getNode(
                    id2) is None:
            return None

        self.dijkstra(self.directed_weighted_graph.getNode(id1))
        s = self.directed_weighted_graph.getNode(id2)
        path = []
        while s is not None:
            path.append(s.getKey())
            s = self.parent[s.getKey()]
        path.reverse()
        weight = self.dis[id2]
        if weight == float('inf'):
            return weight, []
        return weight, path

    """
    function Dijkstra(Graph, source):

    create vertex set Q

    for each vertex v in Graph:             // Initialization
        dist[v] ← INFINITY                  // Unknown distance from source to v
        prev[v] ← UNDEFINED                 // Previous node in optimal path from source
        add v to Q                          // All nodes initially in Q (unvisited nodes)

    dist[source] ← 0                        // Distance from source to source

    while Q is not empty:
        u ← vertex in Q with min dist[u]    // Node with the least distance will be selected first
        remove u from Q 

        for each neighbor v of u:           // where v is still in Q.
            alt ← dist[u] + length(u, v)
            if alt < dist[v]:               // A shorter path to v has been found
                dist[v] ← alt 
                prev[v] ← u 

    return dist[], prev[]
    """

    def dijkstra(self, src):
        self.dis = {}
        self.parent = {}
        visited = set()
        q = []
        for n in self.directed_weighted_graph.get_all_v():
            self.dis[n.getKey()] = float('inf')
            self.parent[n.getKey()] = None
        self.dis[src.getKey()] = float(0)
        q.append((src.getKey(), 0))
        while len(q) > 0 and len(
                visited) != self.directed_weighted_graph.v_size():
            key = q.pop(0)[0]
            if key not in visited:
                for v in self.directed_weighted_graph.getNode(
                        key).getOutEdges():
                    if v.getDest() not in visited:
                        if self.directed_weighted_graph.getEdge(
                                key, v.getDest()) is not None:
                            tempSum = self.dis[
                                key] + self.directed_weighted_graph.getEdge(
                                    key, v.getDest()).getWeight()
                            if tempSum < self.dis[v.getDest()]:
                                self.dis[v.getDest()] = tempSum
                                self.parent[v.getDest(
                                )] = self.directed_weighted_graph.getNode(key)
                            q.append((v.getDest(), self.dis[v.getDest()]))
                            sorted(q, key=lambda n: n[1])
            visited.add(key)

    def connected_component(self, id1: int) -> list:
        """
        Finds the Strongly Connected Component(SCC) that node id1 is a part of.
        @param id1: The node id
        @return: The list of nodes in the SCC
        Notes:
        If the graph is None or id1 is not in the graph, the function should return an empty list []
        """
        list = []
        if self.directed_weighted_graph.getNode(id1) is None:
            return list

        list.append(id1)
        for n in self.directed_weighted_graph.get_all_v():
            if self.shortest_path(
                    n.getKey(), id1)[0] != float('inf') and self.shortest_path(
                        id1, n.getKey())[0] != float('inf'):
                if n.getKey() is not id1:
                    list.append(n.getKey())
        return list

    def connected_components(self) -> List[list]:
        """
        Finds all the Strongly Connected Component(SCC) in the graph.
        @return: The list all SCC
        Notes:
        If the graph is None the function should return an empty list []
        """
        lists = []
        if self.directed_weighted_graph is None:
            return lists

        for n in self.directed_weighted_graph.get_all_v():
            lists.append(self.connected_component(n.getKey()))

        return lists

    def plot_graph(self) -> None:
        """
        Plots the graph.
        If the nodes have a position, the nodes will be placed there.
        Otherwise, they will be placed in a random but elegant manner.
        @return: None
        """
        ax = plt.axes()
        min_x = float('inf')
        max_x = -float('inf')
        min_y = float('inf')
        max_y = -float('inf')
        for node in self.directed_weighted_graph.get_nodes():
            mynode = self.directed_weighted_graph.getNode(node)
            node_pos = mynode.getLocation()
            if node_pos is not None:
                if node_pos[0] < min_x:
                    min_x = node_pos[0]
                if node_pos[1] < min_y:
                    min_y = node_pos[1]
                if node_pos[0] > max_x:
                    max_x = node_pos[0]
                if node_pos[1] > max_y:
                    max_y = node_pos[1]
            else:
                min_x, max_x, min_y, max_y = 0, 500, 0, 500

        plt.xlim(min_x + (min_x * 0.59), max_x + (max_x * 0.59))
        plt.ylim(min_y + (min_y * 0.59), (max_y * 0.59) + max_y)

        edges = self.directed_weighted_graph.get_edges()
        plotted = []
        circles = []
        for src in edges:
            src_node = self.directed_weighted_graph.getNode(src)
            for dest in edges[src]:
                dest_node = self.directed_weighted_graph.getNode(dest)
                src_pos = src_node.getLocation()
                dest_pos = dest_node.getLocation()
                if src_pos is None:
                    src_pos = [
                        random.uniform(min_x, max_x),
                        random.uniform(min_y, max_y)
                    ]
                    src_node.setLocation(src_pos[0], src_pos[1])
                if dest_pos is None:
                    dest_pos = [
                        random.uniform(min_x, max_x),
                        random.uniform(min_y, max_y)
                    ]
                    dest_node.setLocation(dest_pos[0], dest_pos[1])
                c1 = plt.Circle((src_pos[0], src_pos[1]),
                                max_x / 100 + max_y / 100,
                                color='r')
                c2 = plt.Circle((dest_pos[0], dest_pos[1]),
                                max_x / 100 + max_y / 100,
                                color='r')
                if c1 not in circles:
                    circles.append(c1)
                if c2 not in circles:
                    circles.append(c2)

                if (src_pos[0] == dest_pos[0] and src_pos[1] == dest_pos[1]):
                    dest_pos = (dest_pos[0] + 0.1, dest_pos[1] + 0.1)
                plt.arrow(src_pos[0],
                          src_pos[1],
                          dest_pos[0] - src_pos[0],
                          dest_pos[1] - src_pos[1],
                          head_width=max_x * 0.039,
                          length_includes_head=True,
                          head_length=max_y * 0.039,
                          width=max_y * 0.00002 * max_y,
                          color='black',
                          fc="tan")

                plt.title('|V|=' + str(self.directed_weighted_graph.v_size()) +
                          ',' + '|E|= ' +
                          str(self.directed_weighted_graph.e_size()) + ')',
                          fontdict={
                              'color': 'white',
                              'fontsize': 19,
                              'fontweight': 980
                          })
                if (src_pos[0], src_pos[1]) not in plotted:
                    label = ax.annotate(src_node.getKey(),
                                        xy=(src_pos[0], src_pos[1]),
                                        fontsize=15)
                    plotted.append([src_pos[0], src_pos[1]])
                if (dest_pos[0], dest_pos[1]) not in plotted:
                    label = ax.annotate(dest_node.getKey(),
                                        xy=(dest_pos[0], dest_pos[1]),
                                        fontsize=15)
                    plotted.append([dest_pos[0], dest_pos[1]])

        for i in circles:
            ax.add_artist(i)

        plt.show()