def toDiGraph(self): """ converts the HyGraph instance to its analogous DiGraph, where each hyperedge is replaced by a pair of directed edges between each pair of vertices in the hyperedge. """ ret = DiGraph(); ret._spm = self._spmT.SpMM(self._spm) return ret
def __init__(self, graph: DiGraph = None): if graph: self.graph = graph else: self.graph = DiGraph()
class GraphAlgo(GraphAlgoInterface): def __init__(self, graph: DiGraph = None): self.graph = graph """Return the underlying graph of which this class works.""" def get_graph(self) -> DiGraph: return self.graph """ This method load a graph to this graph algorithm. 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". we used json method. @param file - file name @return true - if the graph was successfully loaded. """ def load_from_json(self, file_name: str) -> bool: try: fp = open(file_name) self.graph = DiGraph() graph_file = json.load(fp) edges = graph_file.get('Edges') nodes = graph_file.get('Nodes') for n in nodes: if n.get('pos') is not None: posarr = str(n.get('pos')).split(",") pos = (float(posarr[0]), float(posarr[1]), 0.0) self.graph.add_node(node_id=n.get('id'), pos=pos) else: self.graph.add_node(node_id=n.get('id')) for x in edges: src = x.get('src') dest = x.get('dest') w = x.get('w') self.graph.add_edge(src, dest, w) fp.close() except FileExistsError: print("Graph was not loaded successfully") return False return True """ Saves this weighted directed graph to the given file name. we used he json method. @param file - the file name (may include a relative path). @return true - if the file was successfully saved """ def save_to_json(self, file_name: str) -> bool: if self.graph is None: return False nodes = [] for n in self.graph.nodes: id = self.graph.nodes.get(n).id pos = self.graph.nodes.get(n).pos if pos is not None: pos_x = self.graph.nodes.get(n).pos[0] pos_y = self.graph.nodes.get(n).pos[1] pos = str(pos_x) + ',' + str(pos_y) + ',' + str(0.0) nodes.append({"pos": pos, "id": id}) edges = [] for n in self.graph.nodes: for dest in self.graph.nodes.get(n).src: w = self.graph.nodes.get(n).src.get(dest) edges.append({"src": int(n), "dest": int(dest), "w": w}) mygraph = {"Edges": edges, "Nodes": nodes} with open(file_name, 'w') as json_file: json.dump(mygraph, json_file) return True """ This method based on Dijkstra Algorithm. The data of this algorithm will save in a temporal Node, contains- 1. The shortest weight from the source vertex to this vertex. 2. The vertex neighbor that connect this vertex and update him this value of weight accepted. (By default the vertex tag contains INF and the vertex parent points as a vertex with key -1). Returns Tuple contain: 1. The length of the shortest path between src to dest If no such path, returns inf and an empty list 2. The shortest path between src to dest - as an ordered List of nodes: src--> n1-->n2-->...dest If no such path --> returns an empty List. The function goes through all the neighbors of the source vertex and does them "weight relief" if necessary- That is, if we have reached a vertex whose weight can be reduced relative to its current weight, we will update the information contained the temporal vertex. The implementation of the algorithm is done by a priority queue, which is implementation by a function of comparing the weights (tag) of the temporal vertexes in the queue. """ def shortest_path(self, id1: int, id2: int) -> (float, list): if self.graph is None: return (math.inf, []) if str(id1) not in self.graph.nodes or str(id2) not in self.graph.nodes: return (math.inf, []) if id1 == id2: return 0, [id1] vertex = {} for n in self.graph.nodes: nodetemp = NodeTemp(n) vertex[str(n)] = nodetemp vertex.get(str(id1)).tag = 0 heap = PriorityQueue() # heap.__init__(self.graph.v_size()) heap.put(vertex.get(str(id1))) # heapq.heappush(heap, vertex.get(str(id1))) flag = True while not heap.empty() and flag: # heapq.heapify(heap) current = heap.get() # current = heapq.heappop(heap) if current.visit != 1: for n in self.graph.get_node(current.idNode).src: b = vertex.get(n) if b.visit != 1: if b.tag > current.tag + self.graph.edges.get(str(current.idNode) + '->' + str(n)): b.tag = current.tag + self.graph.edges.get(str(current.idNode) + '->' + str(n)) b.parentId = current.idNode # heapq.heappush(heap, b) heap.put(b) current.visit = 1 if current.idNode == id2: flag = False dest = vertex.get(str(id2)) # NodeTemp listShort = [] if dest.tag != math.inf: listShort.append(int(dest.idNode)) curr = dest while curr.parentId != -1: parent = curr.parentId curr = vertex.get(str(parent)) listShort.append(int(curr.idNode)) listShort.reverse() tup = (dest.tag, listShort) return tup ''' 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 This method is based on the BFS Algorithm- with little changes. We will send the node we want find his SSC and do BFS algorithm that will return the list of nodes that we can reach from this node. After this we will "reverse" the graph's edges and will send the node to BFS one more time. Finally, we merge the lists and return the union. ''' def connected_component(self, id1: int) -> list: if self.graph is None: return [] if str(id1) not in self.graph.nodes: return [] lists = self.bfs(id1) # resets all the visited nodes to unvisited for n in lists[1]: lists[1][n].tag = 0 return lists[0] """ bfs will pass over the nodes that we can reach from the src node we will send to the function. @Return list of those nodes. The function receives a vertex key from which we will perform the test on the graph connected test. During the test we will mark the vertices in the graph in the "tag" that each vertex holds. """ def bfs(self, id1: int) -> (list, dict): myList = [] list1 = {} node = self.graph.get_node(id1) queue = deque() # ---> first run from src node.tag = 2 list1[node.id] = node myList.append(node) glob[str(id1)] = id1 for n in node.src: temp = self.graph.get_node(n) temp.tag = 1 list1[temp.id] = temp queue.append(temp) while queue: n = queue.popleft() for x in n.src: temp2 = self.graph.get_node(x) if temp2.tag == 0: temp2.tag = 1 list1[temp2.id] = temp2 queue.append(temp2) for n in node.dest: temp = self.graph.get_node(n) if temp.tag == 1: queue.append(temp) # mark src node as visited while queue: n = queue.popleft() if n.tag == 1: n.tag = 2 list1[n.id] = n myList.append(n) glob[str(n.id)] = n.id for x in n.dest: temp2 = self.graph.get_node(x) queue.append(temp2) return myList, list1 """ Finds all the Strongly Connected Component(SCC) in the graph. @return: The list all SCC """ def connected_components(self) -> List[list]: glob.clear() if self.graph is None: return [] listAns = [] for i in self.graph.nodes: if i not in glob: listAns.append(self.connected_component(i)) return listAns """ 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 """ def plot_graph(self) -> None: if self.graph is None: return xlist = [] ylist = [] # paints all the nodes in the graph for x in self.graph.nodes: pos = self.graph.get_node(x).pos if pos is None: self.graph.nodes.get(x).pos = (random.uniform(0.0, 50), random.uniform(0.0, 50), 0.0) pos = self.graph.nodes.get(x).pos xlist.append(pos[0]) ylist.append(pos[1]) label = "{:}".format(int(x)) plt.annotate(label, (pos[0], pos[1]), textcoords="offset points", xytext=(2, 3), ha='center', color=[0, 0.75, 0.75]) plt.plot(xlist, ylist, '.', color='red') # loop over edges in the graph and draw them one by one for e in self.graph.edges: edge = str(e).split('->') src = int(edge[0]) dest = int(edge[1]) w = self.graph.edges.get(e) # draws the arrow pointing in the edge direction --> (dest) pos = self.graph.get_node(dest).pos xy1 = (pos[0], pos[1]) # draw the edge coming out from the src node to dest node pos = self.graph.get_node(src).pos xy2 = (pos[0], pos[1]) plt.annotate(text="", xy=xy1, xytext=xy2, arrowprops=dict(arrowstyle="->")) plt.title("My Graph") for key in self.graph.nodes: ver = self.graph.nodes.get(key) if not ver.info: ver.pos = None plt.show()
def graph_creator() -> DiGraph: graph = DiGraph() for x in range(7): graph.add_node(x) graph.add_edge(1, 2, 2.5) graph.add_edge(1, 3, 1.7) graph.add_edge(2, 3, 0.3) graph.add_edge(3, 2, 5.6) graph.add_edge(5, 1, 3.2) graph.add_edge(5, 0, 9.8) graph.add_edge(0, 6, 3.8) return graph
def setUp(self) -> None: self.graph = DiGraph()
def test_get_graph(self): graph_d = DiGraph() graph_d.add_node(0) graph_d.add_node(1) graph_d.add_node(2) graph_d.add_node(3) graph_d.add_node(4) graph_d.add_edge(0, 1, 6) graph_d.add_edge(0, 2, 9) graph_d.add_edge(1, 2, 2) graph_d.add_edge(1, 3, 7) graph_d.add_edge(1, 4, 5) graph_d.add_edge(2, 0, 3) graph_d.add_edge(2, 3, 1) graph_d.add_edge(3, 4, 1) graph_d.add_edge(4, 1, 3) graph_a = GraphAlgo(graph_d) self.assertEqual(graph_d, graph_a.get_graph())
def test_load_from_json(self): graph_d = DiGraph() graph_d.add_node(0) graph_d.add_node(1) graph_d.add_node(2) graph_d.add_node(3) graph_d.add_node(4) graph_d.add_edge(0, 1, 6) graph_d.add_edge(0, 2, 9) graph_d.add_edge(1, 2, 2) graph_d.add_edge(1, 3, 7) graph_d.add_edge(1, 4, 5) graph_d.add_edge(2, 0, 3) graph_d.add_edge(2, 3, 1) graph_d.add_edge(3, 4, 1) graph_d.add_edge(4, 1, 3) graph_a = GraphAlgo(graph_d) self.assertTrue(graph_a.save_to_json("test1.txt")) self.assertTrue(graph_a.load_from_json("test1.txt")) self.assertFalse( graph_a.load_from_json("test2.txt") ) # Attempt to load the graph from a non-existent file.
def test_plot_graph(self): graph_d = DiGraph() graph_d.add_node(1) graph_d.add_node(2) graph_d.add_node(3) graph_d.add_node(4) graph_d.add_node(5) graph_d.add_edge(1, 2, 3) graph_d.add_edge(2, 1, 4) graph_d.add_edge(1, 3, 4) graph_d.add_edge(3, 1, 6) graph_d.add_edge(3, 5, 1.2) graph_d.add_edge(2, 4, 6) graph_a = GraphAlgo(graph_d) self.assertIsNone(graph_a.plot_graph(graph_a))
def test_v_size(self): # init and set graph graph = DiGraph() self.assertEqual(0, graph.v_size()) graph.add_node(1, (1, 1, 2)) graph.add_node(2, (2, 2, 2)) self.assertEqual(2, graph.v_size()) graph.remove_node(1) self.assertEqual(1, graph.v_size()) graph.remove_node(2) self.assertEqual(0, graph.v_size())
def test_e_size(self): graph = DiGraph() graph.add_node(1, (1, 1, 2)) graph.add_node(2, (2, 2, 2)) graph.add_node(3, (3, 1, 2)) graph.add_node(4, (4, 2, 2)) self.assertEqual(0, graph.e_size()) graph.add_edge(1, 2, 4) graph.add_edge(3, 1, 2.3) self.assertEqual(2, graph.e_size()) graph.remove_edge(1, 2) self.assertEqual(1, graph.e_size()) graph.remove_edge(3, 1) self.assertEqual(0, graph.e_size())
def test_remove_edge(self): graph = DiGraph() graph.add_node(1, (1, 1, 2)) graph.add_node(2, (2, 2, 2)) graph.add_node(3, (3, 1, 2)) graph.add_node(4, (4, 2, 2)) graph.add_edge(1, 2, 4) graph.add_edge(3, 1, 2.3) graph.add_edge(1, 3, 2.3) graph.add_edge(3, 4, 2.3) self.assertFalse(graph.remove_edge(0, 3)) self.assertFalse(graph.remove_edge(3, -1)) self.assertFalse(graph.remove_edge(1, 4)) self.assertTrue(graph.remove_edge(1, 3)) self.assertFalse(graph.remove_edge(1, 3))
def test_add_node(self): graph = DiGraph() graph.add_node(1, (1, 1, 2)) self.assertFalse(graph.add_node(1, (1, 1, 2))) graph.add_node(3, (1, 1, 2)) self.assertFalse(graph.add_node(3, (0, 2, 3)))
def test_add_edge(self): graph = DiGraph() graph.add_node(1, (1, 1, 2)) graph.add_node(2, (2, 2, 2)) graph.add_node(3, (3, 1, 2)) graph.add_node(4, (4, 2, 2)) graph.add_edge(1, 2, 4) self.assertFalse(graph.add_edge(1, 2, 4)) graph.add_edge(3, 1, 2.3) graph.add_edge(1, 3, 2.3) self.assertFalse(graph.add_edge(-1, 2, 1))
def test_get_mc(self): graph = DiGraph() self.assertEqual(0, graph.get_mc()) graph.add_node(1, (1, 1, 2)) graph.add_node(2, (2, 2, 2)) graph.add_node(3, (3, 1, 2)) graph.add_node(4, (4, 2, 2)) self.assertEqual(4, graph.get_mc()) graph.add_edge(1, 2, 4) graph.add_edge(3, 1, 2.3) graph.add_edge(1, 3, 2.3) graph.add_edge(3, 4, 2.3) self.assertEqual(8, graph.get_mc()) graph.remove_edge(1, 2) self.assertEqual(9, graph.get_mc()) graph.remove_node(3) self.assertEqual(13, graph.get_mc())
def test_plot(self): graph = Graph() graph2 = Graph() for number in range(3): graph.add_node(number, (number, number, number)) graph2.add_node(number, (number, number, number)) graph.add_edge(0, 1, 1) graph2.add_edge(0, 1, 1) graph.add_edge(1, 2, 1) graph2.add_edge(1, 2, 1) graph.add_edge(2, 0, 1) graph2.add_edge(2, 0, 1) self.assertEqual(graph, graph2) algo = Algo(graph) algo.plot_graph() x = 5 self.assertEqual(graph, graph2)
def init(): g = DiGraph() for i in range(10): g.add_node(i) return g
def test_all_out_edges_of_node(self): graph = DiGraph() node1 = Node(1, (1, 1, 2)) node2 = Node(2, (2, 2, 2)) node3 = Node(3, (3, 1, 2)) node4 = Node(4, (4, 2, 2)) graph.add_node(node1.getKey(), node1.getLocation()) graph.add_node(node2.getKey(), node2.getLocation()) graph.add_node(node3.getKey(), node3.getLocation()) graph.add_node(node4.getKey(), node4.getLocation()) graph.add_edge(1, 2, 4) graph.add_edge(3, 1, 2.3) graph.add_edge(1, 3, 2.3) graph.add_edge(3, 4, 2.3) res = {} res[2] = 4 res[3] = 2.3 self.assertEqual(res, graph.all_out_edges_of_node(1)) res.pop(2) res.pop(3) res[1] = 2.3 res[4] = 2.3 self.assertEqual(res, graph.all_out_edges_of_node(3))
def test_save_to_json(self): graph_d = DiGraph() graph_d.add_node(0) graph_d.add_node(1) graph_d.add_node(2) graph_d.add_node(3) graph_d.add_node(4) graph_d.add_edge(0, 1, 6) graph_d.add_edge(0, 2, 9) graph_d.add_edge(1, 2, 2) graph_d.add_edge(1, 3, 7) graph_d.add_edge(1, 4, 5) graph_d.add_edge(2, 0, 3) graph_d.add_edge(2, 3, 1) graph_d.add_edge(3, 4, 1) graph_d.add_edge(4, 1, 3) graph_a = GraphAlgo(graph_d) self.assertTrue(graph_a.save_to_json("test1.txt"))
class GraphAlgo(GraphAlgoInterface): """This abstract class represents an interface of a graph.""" def __init__(self, my_graph=None): self.my_graph = my_graph def get_graph(self): """ :return: the directed graph on which the algorithm works on. """ return self.my_graph 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. """ try: import json self.my_graph = DiGraph() with open(file_name) as json_file: data = json.load(json_file) for n in data['Nodes']: self.my_graph.add_node(n['id']) for e in data['Edges']: self.my_graph.add_edge(e['src'], e['dest'], e['w']) except: return False 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, Flase o.w. """ # try: import json data = {'Nodes': [], 'Edges': []} with open(file_name, 'w+') as f: for n in self.my_graph.get_all_v().keys(): data['Nodes'].append({'id': n}) for n, n_data in self.my_graph.get_all_v().items(): for edge in n_data.edge_NI(): data['Edges'].append({ 'src': edge.get_src(), 'dest': edge.get_dest(), 'w': edge.get_weight() }) f.write(json.dumps(data)) # except: return False return True def track_path(self, parent, j, path): if parent[j] == -1: return self.track_path(parent, parent[j], path) path.append(j) def find_path(self, dist, parent, src, dest): for i in range(1, len(dist)): if i == dest: path = list() self.track_path(parent, i, path) path.insert(0, src) return path 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, the path as a list 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]) More info: https://en.wikipedia.org/wiki/Dijkstra's_algorithm """ graph = self.my_graph.get_all_v() distances = {vertex: float('Infinity') for vertex in graph.keys() } ## initial all vertices distances to inf. distances[id1] = 0 ## the first vertex is defined as distance 0 parent = [-1] * len(graph.keys()) pq = [(0, id1)] ## expanding all vertices distance by distance to find the optimal path while len(pq) > 0: current_distance, current_vertex = heapq.heappop(pq) # Nodes can get added to the priority queue multiple times. We only # process a vertex the first time we remove it from the priority queue. if current_distance > distances[current_vertex]: continue for neighbor in graph[current_vertex].key_NI(): distance = current_distance + graph[current_vertex].get_weight( neighbor) ## verify the best path to take if distance < distances[neighbor]: parent[neighbor] = current_vertex distances[neighbor] = distance heapq.heappush(pq, (distance, neighbor)) path = self.find_path(distances, parent, id1, id2) ## return the exact shortest path from id1 to id2 return (distances[id2], path if distances[id2] != float('Infinity') else []) 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 """ v_set = set() sccs = self.connected_components() for scc in sccs: if id1 in scc: for i in scc: v_set.add(i) return list(v_set) # list = [] # for node in self.my_graph.get_all_v().values(): # if self.shortest_path(id1, node.get_key()) != -1 and self.shortest_path(node.get_key(),id1) : # list.append(node.get_key()) # # return list def connected_components(self): # -> List[list]: """ Finds all the Strongly Connected Component(SCC) in the graph. @return: The list all SCC """ # list = [] #this is an eterativ idea # for node in self.my_graph.get_all_v().values(): # list1=self.connected_component(node.get_key()) # list.append(list1) # return list def Vorder(v, visited, stack, g, gn): #this in a recoratin idea visited[v] = True ## mark current vertex as visited ## continue to all this vertex adjacents for n in gn.keys(): if not visited[n]: Vorder(n, visited, stack, g, g[n].get_neighbors()) stack = stack.append(v) def dfs_traversal(v, visited, g, gn, scc): visited[v] = True ## mark current vertex as visited scc.append(v) ## continue to all this vertex adjacents for n in gn.keys(): if not visited[n]: dfs_traversal(n, visited, g, g[n].get_neighbors(), scc) def get_Tgraph(g): gr = DiGraph() for v, v_data in g.items(): for e in v_data.edge_NI(): gr.add_node(e.get_dest()) gr.add_node(e.get_src()) gr.add_edge(e.get_dest(), e.get_src(), e.get_weight()) return gr.get_all_v() stack = list() ## contains the visited vertices g = self.my_graph.get_all_v() visited = [False] * (len(g.keys()) ) ## marking not visited vertices for the DFS ## determining the vertices stack by their finishing time for node_id in g.keys(): if not visited[node_id]: Vorder(node_id, visited, stack, g, g[node_id].get_neighbors()) gr = get_Tgraph(g) ## traspose graph visited = [False] * (len( g.keys())) ## marking not visited vertices for the transpose DFS sccs = list() ## processing all veritces in the order they defined by the stack while stack: i = stack.pop() if not visited[i]: scc = list() if i in gr.keys(): dfs_traversal(i, visited, gr, gr[i].get_neighbors(), scc) else: scc.append(i) if len(scc) > 0: sccs.append(scc) return sccs 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 """ import networkx as nx import matplotlib.pyplot as plt G = nx.Graph().to_directed() for n, n_data in self.my_graph.get_all_v().items(): G.add_node(n, pos=n_data.get_pos()) for n, n_data in self.my_graph.get_all_v().items(): for edge in n_data.edge_NI(): G.add_edge(edge.get_src(), edge.get_dest()) # print("Nodes of graph: ") # print(G.nodes()) # print("Edges of graph: ") # print(G.edges()) nx.draw(G, with_labels=True) # plt.savefig("path/to/some/location/name.png") # save as png plt.show() # display
def test_shortest_path(self): graph_d = DiGraph() graph_d.add_node(0) graph_d.add_node(1) graph_d.add_node(2) graph_d.add_node(3) graph_d.add_node(4) graph_d.add_edge(0, 1, 6) graph_d.add_edge(0, 2, 9) graph_d.add_edge(1, 2, 2) graph_d.add_edge(1, 3, 7) graph_d.add_edge(1, 4, 5) graph_d.add_edge(2, 0, 3) graph_d.add_edge(2, 3, 1) graph_d.add_edge(3, 4, 1) graph_d.add_edge(4, 1, 3) graph_a = GraphAlgo(graph_d) self.assertTupleEqual((5, [1, 2, 0]), graph_a.shortest_path( 1, 0)) # Finds a path between 2 connected nodes. self.assertTupleEqual((10, [0, 1, 2, 3, 4]), graph_a.shortest_path( 0, 4)) # Finds a path between 2 connected nodes. self.assertTupleEqual((3, [1, 2, 3]), graph_a.shortest_path( 1, 3)) # Finds a path between 2 connected nodes. self.assertTrue(graph_a.get_graph().remove_edge(2, 0)) # Removes an edge. self.assertTupleEqual( (float('inf'), []), graph_a.shortest_path(3, 0) ) # Attempt to find a path between 2 nodes which exist in the graph but not connected. self.assertTupleEqual( (float('inf'), []), graph_a.shortest_path(4, 7) ) # Attempt to find a path between a node which exists in the graph and one which not exists in the graph. self.assertTupleEqual( (float('inf'), []), graph_a.shortest_path(8, 1) ) # Attempt to find a path between a node which not exists in the graph and one which exists in the graph. self.assertTupleEqual( (float('inf'), []), graph_a.shortest_path(13, 11) ) # Attempt to find a path between 2 nodes which not exist in the graph. self.assertTupleEqual( (float('inf'), []), graph_a.shortest_path(3, 3) ) # Attempt to find a path between a node in the graph to itself. self.assertTupleEqual( (float('inf'), []), graph_a.shortest_path(3, 3) ) # Attempt to find a path between a node which not exists in the graph to itself.
def __init__(self, g=None): if g: self.graph = g else: self.graph = DiGraph()
class TestDiGraph(TestCase): def setUp(self) -> None: self.graph = DiGraph() def test_add_node(self): self.graph.add_node(2) self.assertEqual(self.graph.v_size(), 1) self.graph.add_node(3) self.graph.add_node(2) #Adding an existing node self.assertEqual(self.graph.v_size(), 2) self.assertFalse(self.graph.add_node(3)) def test_add_edge(self): self.graph.add_node(2) self.graph.add_node(3) self.graph.add_node(4) self.assertEqual(self.graph.e_size(), 0) self.graph.add_edge(2, 3, 5.6) self.assertEqual(self.graph.e_size(), 1) self.graph.add_edge(3, 6, 2.0) self.assertEqual(self.graph.e_size(), 2) self.assertFalse(self.graph.add_edge(3, 6, 2.0)) def test_remove_node(self): self.graph.add_node(1) self.assertEqual(self.graph.v_size(), 1) self.graph.remove_node(1) self.assertEqual(self.graph.v_size(), 0) self.graph.add_node(3) self.graph.remove_node(4) #Removes a not existing node self.assertFalse(self.graph.remove_node(4)) self.assertEqual(self.graph.v_size(), 1) def test_remove_edge(self): self.graph.add_node(0) self.graph.add_node(1) self.graph.add_edge(0, 1, 9) self.assertEqual(self.graph.e_size(), 1) self.graph.remove_edge(0, 1) self.assertEqual(self.graph.e_size(), 0) self.graph.add_edge(1, 0, 3.5) self.assertEqual(self.graph.e_size(), 1) self.graph.remove_edge(2, 3) #Removes a not existing edge self.assertFalse(self.graph.remove_edge(2, 3)) self.assertEqual(self.graph.e_size(), 1) def test_get_node(self): self.graph.add_node(3) self.assertIsNotNone(self.graph.get_node(3)) try: self.graph.get_node(2) except: print("OK") try: self.graph.get_node(0) except: print("OK") def test_v_size(self): self.graph.add_node(3) self.assertEqual(self.graph.v_size(), 1) self.graph.add_node(3) #Adding an existing node self.assertEqual(self.graph.v_size(), 1) self.graph.add_node(2) self.graph.remove_node(2) self.assertEqual(self.graph.v_size(), 1) self.graph.remove_node(7) #Removing a not existing node self.assertEqual(self.graph.v_size(), 1) def test_e_size(self): self.graph.add_node(1) self.graph.add_node(2) self.graph.add_node(3) self.graph.add_edge(1, 2, 10) self.assertEqual(self.graph.e_size(), 1) self.graph.add_edge(1, 3, 10) self.assertEqual(self.graph.e_size(), 2) self.graph.remove_edge(1, 2) self.assertEqual(self.graph.e_size(), 1) self.graph.remove_edge(3, 2) #Removing a not existing edge self.assertEqual(self.graph.e_size(), 1) self.graph.remove_node(1) self.assertEqual(self.graph.e_size(), 1) def test_get_all_v(self): self.graph.add_node(1) self.graph.add_node(2) test_data_1 = self.graph.get_node(2) self.assertIn(test_data_1, self.graph.get_all_v().values()) test_data_2 = self.graph.get_node(1) self.graph.remove_node(1) self.assertNotIn(test_data_2, self.graph.get_all_v().values()) def test_all_in_edges_of_node(self): self.graph.add_node(1) self.graph.add_node(2) self.graph.add_node(3) self.graph.add_edge(1, 2, 8) self.graph.add_edge(1, 3, 5) self.graph.add_edge(3, 2, 2) self.assertEqual(2, len(self.graph.all_in_edges_of_node(2))) self.assertEqual(1, len(self.graph.all_in_edges_of_node(3))) self.graph.remove_edge(1, 2) self.assertEqual(1, len(self.graph.all_in_edges_of_node(2))) def test_all_out_edges_of_node(self): self.graph.add_node(1) self.graph.add_node(2) self.graph.add_node(3) self.graph.add_edge(1, 2, 8) self.graph.add_edge(1, 3, 5) self.graph.add_edge(3, 2, 2) self.assertEqual(2, len(self.graph.all_out_edges_of_node(1))) self.graph.add_edge(2, 1, 8) self.assertEqual(2, len(self.graph.all_out_edges_of_node(1))) self.graph.remove_node(1) self.assertEqual(0, len(self.graph.all_out_edges_of_node(1))) def test_get_mc(self): self.assertEqual(0, self.graph.get_mc()) self.graph.add_node(1) self.graph.add_node(2) self.graph.remove_node(1) self.assertEqual(3, self.graph.get_mc()) self.graph.add_node(1) self.graph.add_edge(1, 2, 9) self.graph.remove_edge(1, 2) self.assertEqual(6, self.graph.get_mc())
class GraphAlgo: def __init__(self, g=None): if g: self.graph = g else: self.graph = DiGraph() 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: """ 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. """ new_graph = DiGraph() try: with open(file_name, 'r') as file: data = json.load(file) for n in data['Nodes']: try: pos = n['pos'].split(",") x, y, z = float(pos[0]), float(pos[1]), float(pos[2]) new_graph.add_node(n['id'], (x, y, z)) except: new_graph.add_node(n['id']) for e in data['Edges']: new_graph.add_edge(e['src'], e['dest'], e['w']) self.graph = new_graph return True except OSError as e: print("OSError {}".format(e)) except: return False 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. """ nodes = self.graph.get_all_v() data = {'Edges': [], 'Nodes': []} for n in nodes: node = nodes[n] if node.pos: data['Nodes'].append({'id': node.key, 'pos': node.pos}) else: data['Nodes'].append({'id': node.key}) for o in node.out_edges: data['Edges'].append({ 'src': node.key, 'dest': o, 'w': node.out_edges[o].weight }) try: with open(file_name, 'w') as file: json.dump(data, file) return True except OSError as e: print("OSError {}".format(e)) 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 @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 """ nodes = self.graph.get_all_v() if id1 in nodes and id2 in nodes: parent = {i: None for i in nodes} distance = {i: float('inf') for i in nodes} distance[id1] = 0 visit = set() # this is a priority queue = (priority, node_id) priority_q = [(distance[id1], id1)] while priority_q: current_distance, index = heapq.heappop(priority_q) if index in visit: continue visit.add(index) for ni in self.graph.all_out_edges_of_node(index): if ni in visit: continue dist = current_distance + self.graph.all_out_edges_of_node( index)[ni].weight if dist < distance[ni]: distance[ni] = dist parent[ni] = index heapq.heappush(priority_q, (dist, ni)) runner = id2 path = [runner] while runner: path.append(parent[runner]) runner = parent[runner] if runner == id1: break if runner is None: return distance[id2], [] # path[::-1] to reverse path return distance[id2], path[::-1] return float('inf'), [] def transpose(self): """ transpose a graph :return: a transposed graph """ transposed = DiGraph() nodes = self.graph.get_all_v() for node in nodes: transposed.add_node(node) for node in nodes: for ni in nodes[node].out_edges: transposed.add_edge(ni, node, nodes[node].out_edges[ni].weight) return transposed def BFS_util(self, node, nodes, stack): q = [node] nodes[node].tag = True while q: index = q.pop() for i in self.graph.all_out_edges_of_node(index): if not nodes[i].tag: nodes[i].tag = True q.append(i) stack.append(index) def BFS(self, node, graph, final): """ this method implement breath-first search :param node: the node :param graph: the graph we want to implement on :param final: this is the list that we will need in the end :return: finds the gcc of the graph using transpose graph and BFS twice """ q = [node] final.append(node) graph.graph_nodes[node].tag = True while q: index = heapq.heappop(q) for i in graph.all_out_edges_of_node(index): if not graph.graph_nodes[i].tag: graph.graph_nodes[i].tag = True q.append(i) final.append(i) return final 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 """ nodes = self.graph.get_all_v() for node in nodes: nodes[node].tag = False stack = [] self.BFS_util(id1, nodes, stack) transpose_graph = self.transpose() sConnected_path = [] self.BFS(id1, transpose_graph, sConnected_path) return list(set(stack).intersection(sConnected_path)) def connected_components(self) -> List[list]: """ BFS(src) transpose_graph BFS(src -> transpose) Finds all the Strongly Connected Component(SCC) in the graph. @return: The list all SCC """ nodes = self.graph.get_all_v() for node in nodes: nodes[node].tag = False result = [] for node in nodes: flag = False for i in result: if node in i: flag = True if flag: continue for x in nodes: nodes[x].tag = False stack = [] self.BFS_util(node, nodes, stack) transpose_graph = self.transpose() sConnected_path = [] nodes = transpose_graph.get_all_v() self.BFS(node, transpose_graph, sConnected_path) result.append(list(set(stack).intersection(sConnected_path))) return result 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 """ nodes = self.graph.get_all_v() for n in nodes: if nodes[n].pos is None: v_size = self.graph.v_size() x, y, z = random.uniform(0, v_size), random.uniform(0, v_size), 0 nodes[n].pos = (x, y, z) X = [] Y = [] Z = [] ax = plt.axes() for n in nodes: node = nodes[n] x, y, z = node.pos for out in node.out_edges: dest_node = self.graph.graph_nodes[out] dest_x, dest_y, dest_z = dest_node.pos ax.quiver(x, y, dest_x - x, dest_y - y, angles='xy', scale_units="xy", scale=1) X.append(x) Y.append(y) Z.append(z) plt.plot(X, Y, 'ro') plt.show() """ isEquals for graphs """ def __eq__(self, other=None): if other is not None: return self.get_graph() == other.get_graph() return False
def __init__(self, graph: DiGraph = DiGraph()): self.graph = graph self.list = []
class GraphAlgo(GraphAlgoInterface): """ * This class represents a set of graph theory algorithms to * apply on a directed, weighted graph data structure, including: * Saving and loading a graph, calculating shortest paths on the graph from * one node to another, checking if the graph is strongly connected, and so on... """ def __init__(self, directed_graph: object = None): # TODO: should be change self._graph = DiGraph() if directed_graph is not None: if isinstance(directed_graph, DiGraph): self._graph = directed_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: """ 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. """ flag = True try: with open(file_name, 'r') as jsonFile: load = json.load(jsonFile) graphJson = DiGraph() for node in load["Nodes"]: if "pos" in node: posJ = tuple(map(float, str(node["pos"]).split(","))) graphJson.add_node(node_id=node["id"], pos=posJ) else: graphJson.add_node(node_id=node["id"]) for edge in load["Edges"]: graphJson.add_edge(id1=edge["src"], id2=edge["dest"], weight=edge["w"]) self._graph = graphJson # print("load successes") except Exception as e: print(e) print("load failed") flag = False finally: return flag 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. """ flag = True with open(file_name, "w") as jsonFile: try: d = {"Edges": [], "Nodes": []} for src in self._graph.out_edges.keys(): for dst, w in self._graph.all_out_edges_of_node(src).items(): d["Edges"].append({"src": src, "w": w.weight, "dest": dst}) for key, value in self._graph.nodes.items(): if value.location is None: d["Nodes"].append({"id": key}) else: d["Nodes"].append({"pos": str(value.location), "id": key}) s = d.__str__() s = s.replace(" ", "") s = s.replace("'", "\"") jsonFile.write(s) # print("Save Json was succeeded ") except Exception as e: print("Save Json was failed ") print(e) flag = False finally: return flag def shortest_path(self, id1: int, id2: int) -> (float, list): """ * returns the the shortest path between src to dest - as an ordered List of nodes: * src--> n1-->n2-->...dest * Logic only was taken from: https://en.wikipedia.org/wiki/Shortest_path_problem * Note if no such path --> returns null; @Runtime: Regular BFS using a priority queue = O(|V|+|E|). @param id1 - start node @param id2 - end (target) node @return - the path between src and dest if there is one. """ # Edge cases # Either one of the nodes does not exist in the graph. if id1 not in self._graph.get_all_v() or id2 not in self._graph.get_all_v(): return float('inf'), [] if id1 == id2: # The path from a node to itself is empty and the total distance is 0 return 0, [] # Initialization src = id1 dest = id2 self.reset_tags() self.set_weights_infinity() prev_node = dict() # A map that stores: {key(int): caller(Node)} (Which node called which) pq = Queue() # A queue to prioritize nodes with lower weight visited = dict() # Keep track of visited nodes total_dist = 0.0 destination_found = False curr = self._graph.get_node(src) curr.weight = total_dist visited[curr.key] = True pq.put(curr) # Traverse while not pq.empty(): curr = pq.get() # Pop the next node with the lowest weight O(log(n)) neighbors = self._graph.all_out_edges_of_node(curr.key) # Neighbors of curr node for i in neighbors: # Iterate over neighbors of curr out_edge = neighbors[i] # out_edge: EdgeData neighbor = self._graph.get_node(out_edge.dest) # neighbor: NodeData if not visited.get(neighbor.key): # Process node if not visited total_dist = curr.weight + out_edge.weight if total_dist < neighbor.weight: neighbor.weight = total_dist prev_node.__setitem__(neighbor.key, curr) if neighbor not in pq.queue: # If not already in the queue, enqueue neighbor. pq.put(neighbor) # Finished processing curr's neighbors if curr.key == dest: destination_found = True visited[curr.key] = True if destination_found: path = self.rebuild_path(prev_node, src, dest) # A list of nodes that represents the path between id1->id2 total_dist = path[len(path) - 1].weight return total_dist, path return float('inf'), [] def rebuild_path(self, node_map: dict = None, src: int = 0, dest: int = 0) -> list: """ * This method back-tracks, takes a map of int keys and NodeData values * inserts all nodes in the path to a list and return the list """ if node_map is None or src == dest: return None ans = [self._graph.get_node(dest)] next_node = node_map.get(dest) ans.append(next_node) while next_node.key is not src: # Backtrack from dest to src ans.append(node_map.get(next_node.key)) next_node = node_map.get(next_node.key) if self._graph.get_node(src) not in ans: ans.append(self._graph.get_node(src)) ans.reverse() # Inserted from return ans def reset_tags(self): for key in self._graph.get_all_v().keys(): node = self.get_graph().get_node(key) node.tag = 0 def set_weights_infinity(self): for key in self._graph.get_all_v().keys(): node = self._graph.get_node(key) node.weight = float('inf') def connected_component(self, id1: int) -> list: """ * Finds the Strongly Connected Component(SCC) that node id1 is a part of. * Notes: If the graph is None or id1 is not in the graph, the function should return an empty list [] @param id1: The node id @return: The list of nodes in the SCC """ if self._graph is None or self._graph.get_node(id1) is None: return [] self.reset_tags() # This method executes a BFS and tag nodes so reset_tags() must be called. # Traverse the original graph, from node id1, and tag all reachable nodes ans = [] src = id1 # alias original_graph = self.get_graph() self.traverse_breadth_first(src, original_graph) # Transpose/Reverse graph's edges transposed_graph = self.reverse_graph() # Traverse the transposed graph, from node id1, and un-tag all reachable nodes self.traverse_breadth_first(src, transposed_graph) # Iterate over nodes in the transposed graph and find the nodes that are tagged twice! for key in transposed_graph.get_all_v(): node = transposed_graph.get_node(key) if node.tag == 2: ans.append(self._graph.get_node(node.key)) # Append original node return ans def traverse_breadth_first(self, src: int = 0, graph: GraphInterface = None): """ * This method is made to traverse any node in the graph and set tag on them using bfs algorithm. """ if not isinstance(graph, DiGraph) or graph is None or self._graph.get_node(src) is None: return curr = graph.get_node(src) q = Queue() q.put(curr) curr.tag += 1 while not q.empty(): curr = q.get() out_edges = graph.all_out_edges_of_node(curr.key) for i in out_edges: out_edge = out_edges[i] neighbor = graph.get_node(out_edge.dest) # Get curr's neighbor if neighbor.tag == curr.tag - 1: neighbor.tag += 1 # If un-tagged -> tag it. q.put(neighbor) # and enqueue it def reverse_graph(self) -> GraphInterface: """ * This method transposes the given graph. * The new graph will have the same set of vertices V = {v1, v2, .. , v(n)}, * And all transposed edges. E = {(v1,v2), (v2,v6), .. }, E(transposed) = {(v2,v1), (v6,v2), ..}. * @param g - the given graph. * @return a transposed directed_weighted_graph. """ ans = DiGraph() nodes = self._graph.get_all_v() # {key: NodeData} for key in nodes: ans.add_node(key) ans.get_node(key).tag = self._graph.get_node(key).tag for key in nodes: out_edges = self._graph.all_out_edges_of_node(key) for edge in out_edges: e = out_edges.get(edge) ans.add_edge(e.dest, e.src, e.weight) return ans def connected_components(self) -> List[list]: """ * This method finds all the Strongly Connected Components(SCC) in the graph. * Notes: If the graph is None the function should return an empty list [] @return: The list all SCC """ self.reset_tags() ans = [] visited = dict() # A dictionary of visited nodes for key in self._graph.get_all_v(): if not visited.get(key): path = self.connected_component(key) for node in path: visited.__setitem__(node.key, True) ans.append(path) return ans def plot_graph(self): """ 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 using get_random_location() function. """ g = self.get_graph() plt.title("Our graph:" + g.__str__()) plt.xlabel("X") plt.ylabel("-<") # I should flip 'Y' letter so I decided to write it by a tricky way. :) for src, node in g.get_all_v().items(): # Print the node point if node.location is None: pos = self.get_random_location() # get a elegant location node.location = GeoLocation(pos) plt.plot(node.location.x, node.location.y, marker='o', markerfacecolor='red', markersize=3, color='yellow') plt.text(node.location.x, node.location.y, str(node.key)) # Print the edge line for dest in g.all_out_edges_of_node(src).keys(): x1 = g.get_all_v()[src].location.x y1 = g.get_all_v()[src].location.y if g.get_all_v()[dest].location is None: pos = self.get_random_location() g.get_all_v()[dest].location = GeoLocation(pos) g.get_all_v()[dest].location = GeoLocation(pos) x2 = g.get_all_v()[dest].location.x y2 = g.get_all_v()[dest].location.y plt.arrow(x1, y1, x2 - x1, y2 - y1, width=0.00001, linewidth=0.05) plt.show() def get_random_location(self): """ * This method was made to return a random location for a node when then node doesn't have any location. * How it work? * We get the max and min of the bounding box and then we set the nodes location on a random range inside it. * if there is no bounding box , which means there is no node location enough to set this bounding box, * so we set the nodes location in a range of x=[32,33],y=[35,36],z=0. """ max_x, max_y, max_z, min_x, min_y, min_z = self.get_max_and_min() if max_x == float('-inf') and min_x == float('inf') and max_y == float('-inf') and min_y == float('inf') and \ max_z == float('-inf') and min_z == float('inf'): x = random.uniform(32, 33) y = random.uniform(35, 36) z = 0 ans = x, y, z return ans counter = 0 for src, node in self._graph.get_all_v().items(): if node.location is not None: counter += 1 x = random.uniform(max_x, min_x) y = random.uniform(max_y, min_y) z = random.uniform(max_z, min_z) if counter == 0: # means all nodes doesn't have any location x = random.uniform(32, 33) y = random.uniform(35, 36) z = 0 ans = x, y, z else: ans = x, y, z return ans def get_max_and_min(self): """ This method get the max and min of the bounding box on current graph. @return max and min of bounding box , o.w -inf&inf """ max_x = float('-inf') min_x = float('inf') max_y = float('-inf') min_y = float('inf') max_z = float('-inf') min_z = float('inf') ans = max_x, max_y, max_z, min_x, min_y, min_z counter = 0 for src, node in self._graph.get_all_v().items(): if node.location is not None: x = node.location.x y = node.location.y z = node.location.z counter += 1 max_x = x if x > max_x else max_x min_x = x if x < min_x else min_x max_y = y if y > max_y else max_y min_y = y if y < min_y else min_y max_z = z if z > max_z else max_z min_z = z if z < min_z else min_z if counter > 4: ans = max_x, max_y, max_z, min_x, min_y, min_z return ans def __repr__(self): return self._graph.__repr__() def __str__(self): return self._graph.__str__()
def createGraph(): g = DiGraph() g.add_node(0, (32, 30, 6)) g.add_node(1, (22, 43)) g.add_node(2, (50, 50)) g.add_node(3, (52, 13)) g.add_node(4, (52, 33)) g.add_node(5, (12, 33)) g.add_node(6, (1, 2)) g.add_edge(0, 1, 0.5) g.add_edge(5, 1, 0.5) g.add_edge(1, 0, 0.5) g.add_edge(5, 4, 0.5) g.add_edge(2, 1, 1.5) g.add_edge(2, 3, 2.5) g.add_edge(4, 1, 1.5) g.add_edge(5, 3, 3.5) g.add_edge(3, 5, 4) g.add_edge(4, 2, 6) g.add_edge(1, 2, 0.5) return g
def __init__(self, directed_graph: object = None): # TODO: should be change self._graph = DiGraph() if directed_graph is not None: if isinstance(directed_graph, DiGraph): self._graph = directed_graph
class GraphAlgo(GraphAlgoInterface): """This class represents the directed weighted graph's algorithms""" visited, unvisited, finish = 1, -1, 0 def __init__(self, graph: DiGraph = None): if graph: self.graph = graph else: self.graph = DiGraph() def get_graph(self) -> GraphInterface: """ @return: the directed graph on which the algorithm works on. """ return self.graph def init_json(self, new_graph: dict) -> None: """ Initializes a new graph from Json format. @param: dict - a new graph """ nodes = new_graph.get("Nodes") edges = new_graph.get("Edges") for node in nodes: k = node.get("id") x, y = uniform(0.0, 100), uniform(0.0, 100) if node.get("pos"): tmp = node.get("pos").split(",") x, y = float(tmp[0]), float(tmp[1]) self.graph.add_node(node_id=k, pos=(x, y)) for edge in edges: self.graph.add_edge(id1=edge.get("src"), id2=edge.get("dest"), weight=edge.get("w")) def init_my_graph_from_json(self, new_graph) -> None: """ Initializes our graph from Json file. @return: None """ g = new_graph.get("graph") for k, v in g.items(): x, y = uniform(0.0, 100), uniform(0.0, 100) if v.get("pos"): tmp = v.get("pos") x, y = float(tmp[0]), float(tmp[1]) self.graph.add_node(node_id=int(k), pos=(x, y)) for k, v in g.items(): tmp = v.get("outside") for ni, w in tmp.items(): self.graph.add_edge(id1=int(k), id2=int(ni), weight=w) 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. """ self.__init__() try: with open(file_name, "r") as f: new_graph = json.load(f) except IOError: return False if new_graph.get("graph"): self.init_my_graph_from_json(new_graph) else: self.init_json(new_graph) return True def encoder(self, o): """ Returns a dictionary """ return o.as_dict() 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. """ try: with open(file_name, "w") as f: json.dump(self.graph, default=self.encoder, indent=4, fp=f) except IOError: return False return True def init_algorithm_graph(self): """ Initializes the GraphAlgo """ for i in self.graph.get_all_v(): node = self.graph.get_node(i) node.set_value(float('inf')) node.set_tag(self.unvisited) node.set_prev(None) def seek_path(self, id2: int, value=float) -> (float, list): """ Returns the shortest path distance (as weight) and its route of vertices as a list @param: int - id1 @param: int - id2 @param: float - value @return: (float, list) """ prev = self.graph.get_node(id2) path = [prev.get_key()] while prev.get_prev() is not None: prev = prev.get_prev() path.append(prev.get_key()) path.reverse() return value, path def shortest_path(self, id1: int, id2: int) -> (float, list): """ Returns the shortest path from node id1 to node id2 using Dijkstra's Algorithm. Adds to every vertex, the previous vertex which the vertex "came from", finally, sends to help function seek_path() which returns a Tuple of the distance and the list of the vertices creating the route. @param id1: The start node id @param id2: The end node id @return: The distance of the path, a list of the vertices IDs which the path goes through If no such path, or one of them doesn't exist the function returns (float('inf'),[]) """ if self.graph.get_node(id1) is None or self.graph.get_node( id2) is None: return float('inf'), [] if id1 is id2: return 0.0, [self.graph.get_node(id1)] self.init_algorithm_graph() self.graph.get_node(id1).set_value(0) queue = PriorityQueue() queue.put(self.graph.get_node(id1)) while not queue.empty(): curr_node = queue.get(0) if curr_node.get_key() == id2 or curr_node.get_value() == float( 'inf'): if curr_node.get_value is float('inf'): return float('inf'), [] return self.seek_path(id2, curr_node.get_value()) for i, w in curr_node.get_outside().items(): weight = curr_node.get_value() + w ni = self.graph.get_node(i) if ni.get_tag() is self.unvisited and ni.get_value() > weight: ni.set_value(weight) queue.put(ni) # sorted(queue.queue) ni.set_prev(curr_node) curr_node.set_tag(self.visited) return float('inf'), [] def connected_component(self, id1: int, reset: int = 0) -> list: """ Finds the Strongly Connected Component (SCC) which node id1 is a part of. Help function sub_graph returns the reversed graph of all the vertices we can get to from vertex id1 and the list of all these vertices using DFS algorithm. Then, help function direction() returns the list of all vertices which we got to from the reverse graph using DFS algorithm. Finally, help function union(), unites both lists nd returns the Strongly Connected Component (SCC) @param id1: The vertex id @param reset: int @return: The list of nodes in the SCC If the graph is None or id1 is not in the graph, the function returns an empty list [] """ if self.graph.get_node(id1) is None: return [] if self.graph.v_size() == 1: return [id1] if reset == 0: self.init_algorithm_graph() reverse_graph, straight_list = self.sub_graph(id1) # straight_list = self.direction(id1, self.graph) reverse_list = self.direction(id1, reverse_graph) return self.union(straight_list, reverse_list) def direction(self, id1: int, g: DiGraph) -> list: """ Returns the list of the neighbors of a vertex @param: int: id1 @param: DiGraph - a graph @return: list """ d = deque() d.append(id1) li = [id1] while len(d) != 0: curr_node = d.popleft() for ni in g.all_out_edges_of_node(curr_node).keys(): if g.get_node(ni).get_tag() is self.unvisited: d.append(ni) g.get_node(ni).set_tag(self.visited) li.append(ni) return li def union(self, straight: list, reverse: list) -> list: """ Returns a union of both lists straight & reverse as another list @return: list """ return list(set(straight) & set(reverse)) def sub_graph(self, id1: int) -> Tuple[DiGraph, List]: """ Returns the graph reverse and the list of neighbors which you can get to, from a vertex @param: int - id1 @return: Tuple[DiGraph, List] """ d = deque() d.append(id1) reverse_graph = DiGraph() reverse_graph.add_node(id1) li = [id1] while len(d) != 0: curr_node = d.popleft() for ni in self.graph.all_out_edges_of_node(curr_node).keys(): reverse_graph.add_node(ni) w = self.graph.get_node(curr_node).get_outside().get(ni) reverse_graph.add_edge(ni, curr_node, w) if self.graph.get_node(ni).get_tag() is self.unvisited: d.append(ni) self.graph.get_node(ni).set_tag(self.visited) li.append(ni) return reverse_graph, li def connected_components(self) -> List[list]: """ Finds all the Strongly Connected Components (SCC) in the graph. Passes through every vertex and sends it to the function connected_component() as long as the vertex is unvisited yet. @return: The list of all SCC If the graph is None, the function returns an empty list [] """ if self.graph is None: return [] scc = [] s = set() for i, node in enumerate(self.graph.get_all_v().keys()): if node not in s: connected = self.connected_component(node, i) s.update(connected) scc.append(connected) return scc def set_pos_for_all(self): """ Sets the position for every vertex in the graph randomly """ for node in self.graph.get_all_v().keys(): if self.graph.get_node(node).get_pos() is None: self.graph.get_node(node).set_pos( (uniform(0.0, 100), uniform(0.0, 100))) else: break def plot_graph(self) -> None: """ Plots the graph. If the vertices have a position, the vertices will be placed there. Otherwise, they will be placed in a random but elegant manner. @return: None """ fig, ax = plt.subplots() self.init_algorithm_graph() self.set_pos_for_all() li_x, li_y, n = [], [], [] for node in self.graph.get_all_v().keys(): xyA = self.graph.get_node(node).get_pos() x, y = self.graph.get_node(node).get_pos() li_x.append(x) li_y.append(y) n.append(node) for ni in self.graph.all_out_edges_of_node(node): xyB = self.graph.get_node(ni).get_pos() con = ConnectionPatch(xyA, xyB, "data", "data", arrowstyle="-|>", shrinkA=5, shrinkB=5, mutation_scale=13, fc="r") ax.plot([xyA[0], xyB[0]], [xyA[1], xyB[1]], "o") ax.add_artist(con) ax.scatter(li_x, li_y) for i, txt in enumerate(n): ax.annotate(n[i], (li_x[i], li_y[i])) plt.xlabel("x coordinates") plt.xlabel("x coordinates") plt.ylabel("y coordinates") plt.title("My Graph") plt.show() def __str__(self) -> str: """ @return: Returns the graph as a String. """ return self.graph.__str__() def __repr__(self) -> str: """ @return: Returns the graph as a String. """ return self.graph.__str__() def __eq__(self, other) -> bool: """ Checks if two GraphAlgo are equals. @return: bool """ if isinstance(other, GraphAlgo): return self.graph.__eq__(other.graph) elif isinstance(other, GraphInterface): return self.graph.__eq__(other) return False
class GraphAlgo(GraphAlgoInterface): def __init__(self, g: tuple = None): if g is None: self.g = DiGraph() else: self.g = g def get_graph(self) -> GraphInterface: return self.g def load_from_json(self, file_name: str): self.g = DiGraph() with open(file_name) as f: data = json.load(f) for n in data['Nodes']: self.g.add_node(n['id']) for e in data['Edges']: self.g.add_edge(e['src'], e['dest'], e['w']) def save_to_json(self, file_name: str): data = {"Edges": [], "Nodes": []} for n in self.g.get_all_v().keys(): data["Nodes"].append({"id": n}) for n in self.g.get_all_v().keys(): for n2, w in self.g.all_out_edges_of_node(n).items(): data["Edges"].append({"src": n, "w": w, "dest": n2}) with open(file_name, 'w+') as f: json.dump(data, f) def shortest_path(self, id1: int, id2: int) -> (float, list): distancedict = {} q = [] list = [] q.append(id1) distancedict.update({id1: 0}) while len(q) != 0: tmp = q.pop(0) for i in self.g.all_out_edges_of_node(tmp): if i not in distancedict: q.append(i) distancedict.update({ i: self.g.all_out_edges_of_node(tmp)[i] + distancedict[tmp] }) elif (self.g.all_out_edges_of_node(tmp)[i] + distancedict[tmp]) < distancedict[i]: q.append(i) distancedict.update({ i: self.g.all_out_edges_of_node(tmp)[i] + distancedict[tmp] }) if not id2 in distancedict: return math.inf, [] tmp2 = id2 while tmp2 != id1: for i in self.g.all_in_edges_of_node(tmp2): distancedict[i] if (self.g.all_out_edges_of_node(i)[tmp2] + distancedict[i] == distancedict[tmp2]): list.append(i) tmp2 = i break list.reverse() return (distancedict[id2], list) def SCCUtil(self, u): next = 0 nextgroup = 0 index = [None] * self.g.v_size() lowlink = [None] * self.g.v_size() onstack = [False] * self.g.v_size() stack = [] groups = [] groupid = {} work = [(u, 0)] while work: v, i = work[-1] del work[-1] if i == 0: index[v] = next lowlink[v] = next next += 1 stack.append(v) onstack[v] = True recurse = False for j in self.g.all_out_edges_of_node(v).keys(): w = j if index[w] == None: work.append((v, j + 1)) work.append((w, 0)) recurse = True break elif onstack[w]: lowlink[v] = min(lowlink[v], index[w]) if recurse: continue if index[v] == lowlink[v]: com = [] while True: w = stack[-1] del stack[-1] onstack[w] = False com.append(w) groupid[w] = nextgroup if w == v: break groups.append(com) nextgroup += 1 if work: w = v v, _ = work[-1] lowlink[v] = min(lowlink[v], lowlink[w]) return groups def connected_component(self, id1: int): for i in self.SCCUtil(id1): for j in i: if (j == id1): return i return list() def Diff(self, li1, li2): return (list(list(set(li1) - set(li2)) + list(set(li2) - set(li1)))) def connected_components(self): check = list(self.g.get_all_v().keys()) ans = list() for i in self.g.get_all_v().keys(): obj = self.SCCUtil(i) for j in obj: if check.__contains__(j[0]): j.reverse() ans.append(j) check = self.Diff(check, j) if not self.g.get_all_v().keys(): ans.append(list()) return ans def plot_graph(self): for key, val in self.g.get_all_v().items(): for node, _ in self.g.all_out_edges_of_node(key).items(): x2, y2 = self.g.get_all_v()[node] x1, y1 = val plt.plot([x1, x2], [y1, y2], marker='o') plt.show()
def check0(): """ This function tests the naming (main methods of the DiGraph class, as defined in GraphInterface. :return: """ g = DiGraph() # creates an empty directed graph for n in range(4): g.add_node(n) g.add_edge(0, 1, 1) g.add_edge(1, 0, 1.1) g.add_edge(1, 2, 1.3) g.add_edge(2, 3, 1.1) g.add_edge(1, 3, 1.9) g.remove_edge(1, 3) g.add_edge(1, 3, 10) print(g) # prints the __repr__ (func output) print(g.get_all_v()) # prints a dict with all the graph's vertices. print(g.all_in_edges_of_node(1)) print(g.all_out_edges_of_node(1))
def __init__(self, g: tuple = None): if g is None: self.g = DiGraph() else: self.g = g