def test_add_remove(self): """ This test contains the basic methods implemented in DiGraph @:param: mc -> measured by the method "get_mc" @:param: nodes -> measured by the method "v_size" @:param: edges -> measured by the method "e_size" :return: True if it works """ print("test add remove") graph = DiGraph() graph.add_node(1) self.assertEqual(graph.v_size(), 1, "You have added a node to your Graph") self.assertEqual(graph.get_mc(), 1, "You have added a node to your Graph") self.assertEqual(graph.e_size(), 0, "The graph is Edges free") graph.add_node(2) self.assertEqual(graph.v_size(), 2, "You have added a node to your Graph") graph.add_edge(1, 2, 2.0) self.assertEqual(graph.get_mc(), 3, "You have a 2 node Graph") self.assertEqual(graph.e_size(), 1, "The graph has 1 Edge") graph.remove_node(2) self.assertFalse(2 in graph.get_all_v()) print("Completed")
def test_shortest_path_list(self): """Creating a graph and checking the shortestPathDist on it""" print("Checking shortest_path_list... ") g1 = DiGraph() for i in range(0, 5): g1.add_node(i) g1.add_edge(0, 1, 2.5) g1.add_edge(0, 1, 20) g1.add_edge(1, 2, 6.7) g1.add_edge(2, 3, 40) g1.add_edge(3, 4, 10) g1.add_edge(4, 3, 1) g1.add_edge(3, 2, 16) g1.add_edge(2, 1, 0.5) g1.add_edge(1, 0, 20) g1.add_edge(4, 2, 70) g1.add_edge(2, 4, 70) g1.add_edge(1, 2, 100) g1.add_edge(3, 1, 80) g1.add_edge(1, 3, 80) operator = GraphAlgo(g1) self.assertEqual([1], operator.shortest_path(1, 1)[1]) self.assertEqual([0, 1, 2], operator.shortest_path(0, 2)[1]) self.assertEqual([0, 1, 2, 3], operator.shortest_path(0, 3)[1]) self.assertEqual([0, 1, 2, 3, 4], operator.shortest_path(0, 4)[1]) self.assertEqual([4, 3], operator.shortest_path(4, 3)[1]) self.assertEqual([4, 3, 2], operator.shortest_path(4, 2)[1]) self.assertEqual([4, 3, 2, 1], operator.shortest_path(4, 1)[1]) self.assertEqual([4, 3, 2, 1, 0], operator.shortest_path(4, 0)[1])
def test_shortest_path_dist(self): """Creating a graph and checking the shortestPath on it""" print("Checking shortest_path _dist... ") g1 = DiGraph() for i in range(0, 5): g1.add_node(i) g1.add_edge(0, 1, 2.5) g1.add_edge(0, 1, 20) g1.add_edge(1, 2, 6.7) g1.add_edge(2, 3, 40) g1.add_edge(3, 4, 10) g1.add_edge(4, 3, 1) g1.add_edge(3, 2, 16) g1.add_edge(2, 1, 0.5) g1.add_edge(1, 0, 20) g1.add_edge(4, 2, 70) g1.add_edge(2, 4, 70) g1.add_edge(1, 2, 100) g1.add_edge(3, 1, 80) g1.add_edge(1, 3, 80) operator = GraphAlgo(g1) self.assertEqual(float('inf'), operator.shortest_path(9, 1)[0]) self.assertEqual(0, operator.shortest_path(1, 1)[0]) self.assertEqual(9.2, operator.shortest_path(0, 2)[0]) self.assertEqual(49.2, operator.shortest_path(0, 3)[0]) self.assertEqual(59.2, operator.shortest_path(0, 4)[0]) self.assertEqual(1, operator.shortest_path(4, 3)[0]) self.assertEqual(17, operator.shortest_path(4, 2)[0]) self.assertEqual(17.5, operator.shortest_path(4, 1)[0]) self.assertEqual(37.5, operator.shortest_path(4, 0)[0])
def test_add_edge_and_remove_edge(self): g = DiGraph() # adding nodes to the graph for i in range(10): g.add_node(i) # creating edges for i in range(4): g.add_edge(i, i + 3, i * 5 / 2) my_dict = g.get_all_v() # checking if the edges successfully added in the node for i in range(4): current_node = my_dict[i] edge_dic = current_node.edges_towards self.assertEqual(edge_dic[i + 3], i * 5 / 2) # removing the edges for i in range(4): g.remove_edge(i, i + 3) my_dict = g.get_all_v() flag = True # checking if the edges were removed successfully for i in range(4): current_node = my_dict[i] empty_dict = {} if empty_dict != current_node.edges_towards: flag = False break self.assertEqual(True, flag)
def test_get_all_v(self): g = DiGraph() my_dict = {} for i in range(10000): g.add_node(i) current_node = g.get_all_v()[i] my_dict[i] = current_node self.assertEqual(my_dict, g.get_all_v())
def single_node_graph(self) -> DiGraph: """ This method creates a new graph with only one node :return: DiGraph """ graph = DiGraph() graph.add_node(100) return graph
def test_add_node(self): g = DiGraph() flag = True for i in range(10): g.add_node(i) if not flag: break self.assertEqual(flag, True)
def test_all_out_edges_of_node(self): g = DiGraph() my_dict = dict() for i in range(15): g.add_node(i) for i in range(9): g.add_edge(0, i + 2, i * 5) my_dict[i + 2] = i * 5 self.assertEqual(my_dict, g.all_out_edges_of_node(0))
def test_v_size(self): counter = 0 g = DiGraph() for i in range(10000): g.add_node(i) counter += 1 for i in range(500): g.remove_node(i) counter -= 1 self.assertEqual(counter, g.v_size())
def liniar_graph(self) -> DiGraph: """ This method create a linear 10 node graph :return: DiGraph """ graph = DiGraph() for i in range(0, 10): graph.add_node(i) for i in range(0, 10): graph.add_edge(i, i + 1, 1.0) return graph
def test_remove_node(self): g = DiGraph() flag = True g.add_node(11) for i in range(10): g.add_node(i) g.add_edge(i, 11, 1) for i in range(10): flag = g.remove_node(i) if not flag: break self.assertEqual(flag, True)
def connected_graph(self) -> DiGraph: """ This method create a linear 10 node graph :return: DiGraph """ graph = DiGraph() for i in range(0, 10): graph.add_node(i) for i in range(0, 10): for j in range(0,10): if i is not j: graph.add_edge(i, j, 1.0) return graph
def test_e_size(self): counter = 0 g = DiGraph() for i in range(10000): g.add_node(i) for i in range(500): g.add_edge(i, (i + 1) * 10, i * 10 / 5) counter += 1 self.assertEqual(counter, g.e_size()) for i in range(100): g.remove_edge(i, (i + 1) * 10) counter -= 1 self.assertEqual(counter, g.e_size())
def testa_shortest_path(self): """ This method tests the shortest path between 2 nodes :return: True if the graphs are the same """ print("third test") graph = DiGraph() graph.add_node(0) graph.add_node(1) graph.add_edge(0, 1, 0.0) algo = GraphAlgo(graph) algo.save_to_json("../tests/third_algo_test.json") s_path_algo = algo.shortest_path(0, 1) g_algo = GraphAlgo() file = '../tests/third_algo_test.json' g_algo.load_from_json(file) s_path_g_algo = g_algo.shortest_path(0, 1) self.assertEqual(s_path_algo, s_path_g_algo) print("Completed")
def test_ccg_graph(self): """ This test checks if the graph is strongly connected :return: True if it does """ print("eighth test") graph = DiGraph() graph.add_node(100) algo = GraphAlgo() algo.graph = graph ans = algo.connected_components() self.assertEqual([[100]], ans) g_algo = GraphAlgo(self.connected_graph()) res = g_algo.connected_components() exp = [] for i in g_algo.get_graph().graph_nodes: exp.append(i) self.assertEqual([exp], res) print("Completed")
def create_graph() -> DiGraph: """ Creates a new graph :return: DiGraph """ graph = DiGraph() # add 10 nodes to the graph for i in range(1, 11): graph.add_node(i) # connects = 10 graph.add_edge(1, 2, 20) graph.add_edge(2, 3, 5) graph.add_edge(2, 4, 90) graph.add_edge(2, 6, 7) graph.add_edge(3, 7, 21) graph.add_edge(4, 2, 1) graph.add_edge(5, 10, 1) graph.add_edge(7, 9, 12) graph.add_edge(8, 9, 1) graph.add_edge(10, 3, 75) # mc = 20 return graph
def setUp(self): """The method creates 3 graphs for this unittest. connected graph and the other one isn't connected and the last one is scc_graph. algo initialized on connected""" self.startTime = time.time() g = DiGraph() for n in range(15): g.add_node(n) g.add_edge(0, 9, 2.5) g.add_edge(1, 9, 6.7) g.add_edge(4, 9, 40) g.add_edge(3, 10, 7) g.add_edge(3, 13, 27) g.add_edge(3, 2, 4.5) g.add_edge(14, 5, 21) g.add_edge(14, 8, 41) g.add_edge(11, 2, 17) self.is_not_connected = g graph = DiGraph() for n in range(10): graph.add_node(n) graph.add_edge(0, 1, 12) graph.add_edge(1, 2, 67) graph.add_edge(2, 3, 43) graph.add_edge(3, 4, 17) graph.add_edge(4, 5, 27) graph.add_edge(5, 6, 4.5) graph.add_edge(6, 7, 21) graph.add_edge(7, 8, 41) graph.add_edge(8, 9, 11.23) graph.add_edge(9, 0, 13.765649959) self.connected = graph di_graph = DiGraph() for n in range(10): di_graph.add_node(n) di_graph.add_edge(0, 1, 13) di_graph.add_edge(1, 2, 32) di_graph.add_edge(1, 4, 11) di_graph.add_edge(2, 3, 12) di_graph.add_edge(2, 6, 35) di_graph.add_edge(3, 2, 7) di_graph.add_edge(3, 7, 21) di_graph.add_edge(4, 0, 10) di_graph.add_edge(4, 5, 47) di_graph.add_edge(5, 6, 31) di_graph.add_edge(5, 8, 91) di_graph.add_edge(6, 5, 0.2) di_graph.add_edge(7, 3, 29) di_graph.add_edge(7, 6, 61) di_graph.add_edge(8, 5, 22) di_graph.add_edge(9, 8, 75) self.scc_graph = di_graph self.algo = GraphAlgo(self.connected)
class TestDiGraph(unittest.TestCase): def setUp(self) -> None: """ build the central graph for all the tests """ self.graph = DiGraph() self.graph.add_node(1) self.graph.add_node(2) self.graph.add_node(3) self.graph.add_node(4) self.graph.add_node(5) self.graph.add_edge(1, 2, 600) self.graph.add_edge(2, 3, 600) self.graph.add_edge(3, 1, 600) self.graph.add_edge(3, 4, 600) self.graph.add_edge(4, 5, 600) self.graph.add_edge(5, 4, 600) def test_v_size(self): self.assertEqual(5, self.graph.v_size()) def test_e_size(self): self.assertEqual(6, self.graph.e_size()) def test_get_mc(self): self.assertEqual(11, self.graph.get_mc()) def test_removeEdge(self): self.graph.remove_edge(3, 1) self.graph.remove_edge(2, 3) self.graph.remove_edge(5, 4) self.assertEqual(3, self.graph.e_size()) self.graph.add_edge(3, 1, 1.67) self.assertEqual(4, self.graph.e_size()) def test_removeNode(self): self.graph.remove_node(1) self.graph.remove_node(3) self.graph.remove_node(1) self.assertEqual(3, self.graph.v_size()) def test_add_node(self): self.graph.add_node(6) self.graph.add_node(7) self.graph.add_node(8) self.graph.add_node(8) self.assertEqual(8, self.graph.v_size()) def test_all_in_edges_of_node(self): self.assertTrue(len(self.graph.all_in_edges_of_node(1)) == 1) self.assertTrue(len(self.graph.all_in_edges_of_node(2)) == 1) self.graph.add_node(6) self.graph.add_edge(6, 3, 5.5) self.assertTrue(len(self.graph.all_in_edges_of_node(3)) == 2) def test_all_out_edges_of_node(self): self.assertTrue(len(self.graph.all_out_edges_of_node(2)) == 1) self.assertTrue(len(self.graph.all_out_edges_of_node(4)) == 1) self.assertTrue(len(self.graph.all_out_edges_of_node(3)) == 2)
class GraphAlgo(GraphAlgoInterface): """This abstract class represents an interface of a graph algotirhms class.""" myGraph = DiGraph() tropologicalSort = [] sccList = [] def __init__(self, graph=myGraph): # , graph: DiGraph): self.myGraph = DiGraph() self.myGraph = graph self.tropologicalSort = [] # have the list in tropoligical sort self.sccList = [] """Init the graph on which this set of algorithms operates on.""" def get_graph(self) -> DiGraph: return self.myGraph def get_graph_in_class(self) -> DiGraph: return self.myGraph """ :return: the directed graph on which the algorithm works on. """ def load_from_json(self, file_name: str) -> bool: try: with open(file_name, "r") as file: data = json.load(file) self.__init__() self.myGraph = DiGraph() for n in data.get("Nodes"): self.myGraph.add_node(n.get("id"), n.get("pos")) for e in data.get("Edges"): self.myGraph.add_edge(e.get("src"), e.get("dest"), e.get("w")) return True except Exception as e: return False """ 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. """ def save_to_json(self, file_name: str) -> bool: try: with open(file_name, "w") as file: json.dump(self.myGraph.as_dict(), indent=4, fp=file) return True except Exception as e: return False """ 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. """ def shortest_path_list(self, src, dest): groupNodes = self.myGraph._nodes # get and store the graphs' collection of vertices. parents = { } # for (space I guess.. wanted to achieve time) complexity reason- # a hashmap of parents, to restore the parent of each node in the shortest path myListg = [] # create a list for returning the shortest path. # groupNodes[i.getKey()] = i # for every key in the nodes list put it in the hashmap with its key. desty = dest # for further use if (dest == src): # the path of node from itself to itself is itself. myListg.append(groupNodes.get(src)) return myListg if src not in groupNodes.keys() or dest not in groupNodes.keys(): return myListg groupNodesKeys = groupNodes.keys() for key in groupNodesKeys: # initializing- prepearing the nodes for the proccess. (groupNodes.get(key)).setTagB(float('inf')) groupNodes.get(key).setInfo("") # Shortest distance- we set it to infinity cuz we didn't check it yet parents[ key] = None # None because initially, we don't have any path to reach (groupNodes.get(src)).setTagB(0) # distance a node to itelf is 0, and it's the distance of src from itself q = deque() # deque groupNodesKeys = groupNodes.keys() for key in groupNodesKeys: # all nodes in the graph are unoptimized - thus are in Q # key is Integer q.append(groupNodes.get(key)) minDist = float('inf') # double minKey = -1 # int dist = 0 # double minNode = None # node_data while q: # while it's not empty for node in q: # getting the smallest dist node # node is type NodeData if node.getTagB() <= minDist: minDist = node.getTagB() minKey = node.getKey() minNode = node q.remove(minNode) # for every neighbor of minKey= neinode if (self.myGraph.all_out_edges_of_node(minKey)): for key in self.myGraph.all_out_edges_of_node(minKey).keys( ): # where v has not yet been removed from Q. # edge is type edge_data # get node data from edge_data neinode = self.myGraph._nodes.get( key) # (NodeData) #typed NodeData # we're taking a node from myGraph, which is the neighbor of MINKEY node, # which is the DEST in the edge between MINKEY and DEST. if ( neinode in q ): # where v has not yet been removed from Q.#if it contains the neighbor node dist = minDist + self.myGraph.all_out_edges_of_node( minKey).get(key) if (dist < neinode.getTagB()): (neinode).setTagB( dist ) # it's the path's weight sum, why is it double? #(NodeData) neinode.setInfo(str(minKey)) minDist = float('inf') # done with while loop. desty = dest while (groupNodes.get(desty) ).getTagB() != 0 and groupNodes.get(desty).getInfo() != "": # typed NodeData # means you haven't yet reached the src because only the src has tag=0 (dist) # of 0 from itself. # myListg.append(groupNodes.get(desty)) myListg.append(groupNodes.get(desty)) desty = int(groupNodes.get(desty).getInfo()) # get the "father" # myListg.append(groupNodes.get(src)) <<list of nodes myListg.append(groupNodes.get(src)) # list of nodes id myListg.reverse( ) # reverse the list, 'cuz it came reversed, as we got from the dest to src by parents. if myListg[len(myListg) - 1].getKey() != dest: myListg.clear() return myListg """ returns the shortest path between src to dest - as an ordered List of nodes""" def shortest_path_dist(self, src, dest): if (src == dest): # the distance from a node to itself is 0. return 0 aj = self.shortest_path_list(src, dest) # aj is type List if not (aj): return float( 'inf') # EMPTY aj MEANS THERE'S NO PATH FROM SRC TO DEST weight = (aj.pop(len(aj) - 1)).getTagB() return weight # with -1 cuz of the edges num, if there are 2 nodes, theres only one edge connecting them """returns the length of the shortest path between src to dest""" def shortest_path(self, id1: int, id2: int) -> (float, list): nodesList = self.shortest_path_list(id1, id2) # print("nodes",nodesList) nodesKeys = [] for node in nodesList: nodesKeys.append(node.key) # print("keys ",nodesKeys) dist = self.shortest_path_dist(id1, id2) return dist, nodesKeys """ 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 """ def connected_component(self, id1: int) -> list: if self.get_graph() is None or id1 not in self.get_graph().get_all_v( ).keys(): return [] return self.bfs(self.get_graph(), id1, {}) """ 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 """ def connected_components(self) -> List[list]: if self.get_graph() is None or self.get_graph().v_size() == 0: return [] all_the_SCC = [] has_family = {} for key in self.get_graph().get_all_v(): if key not in has_family.keys(): all_the_SCC.append(self.bfs(self.get_graph(), key, has_family)) return all_the_SCC """ Finds all the Strongly Connected Component(SCC) in the graph. @return: The list all SCC """ def bfs(self, g: DiGraph(), id1: int, has_family: dict) -> list: scc = [] queue = deque() v_list = g.get_all_v() u = v_list.get(id1) scanned, scanned_reverse = {}, {} queue.append(u) scc.append(u.key) has_family[u.key] = True scanned[u] = True while queue: u = queue.popleft() if u.key in g._edges.keys(): for key in g.all_out_edges_of_node(u.key).keys(): v = g.get_all_v().get(key) if v not in scanned: scanned[v] = True queue.append(v) queue.append(g.get_all_v().get(id1)) while queue: u = queue.popleft() if g.all_in_edges_of_node(u.key) is not None: for key in g.all_in_edges_of_node(u.key).keys(): v = g.get_all_v().get(key) if v not in scanned_reverse: scanned_reverse[v] = True queue.append(v) if v in scanned.keys() and key not in has_family.keys( ): scc.append(key) has_family[key] = True return sorted(list(dict.fromkeys(scc))) """BFS algorithm: this method operates BFS twice: one for the original graph, and the second time for reverse graph. After this time we get SCC for specific node""" def plot_graph(self) -> None: fig, axes = plt.subplots(figsize=(8, 6)) csfont = {'fontname': 'Comic Sans MS'} axes.set_title("Graph Plot", **csfont, fontsize=25) for node in self.myGraph._nodes.values(): plt.scatter(node.pos[0], node.pos[1], s=25, color="red") plt.text(node.pos[0], node.pos[1] + 0.0001, str(node.key), color="green", fontsize=8) for src in self.myGraph._edges.keys(): for dest in self.myGraph._edges.get(src).keys(): x1 = self.myGraph._nodes.get(src).pos[0] y1 = self.myGraph._nodes.get(src).pos[1] x2 = self.myGraph._nodes.get(dest).pos[0] y2 = self.myGraph._nodes.get(dest).pos[1] if (self.myGraph._edges.get(dest) ): # if there's a chance for a two directional edge.. if (self.myGraph._edges.get(dest).get(src) ): # if there's a two- directional edge srcNode = self.myGraph._nodes.get(src) destNode = self.myGraph._nodes.get(dest) plt.annotate("", xy=(destNode.pos[0], destNode.pos[1]), xytext=(srcNode.pos[0], srcNode.pos[1]), arrowprops=dict(arrowstyle='<->', color='black')) else: # There's only a one directed edge plt.annotate("", xy=(x2, y2), xytext=(x1, y1), arrowprops=dict(arrowstyle='->', color='blue')) else: # there's only a one directed edge plt.annotate("", xy=(x2, y2), xytext=(x1, y1), arrowprops=dict(arrowstyle='->', color='blue')) white_patch = mpatches.Patch(color='blue', label='(one)Directed edge') black_patch = mpatches.Patch(color='black', label='Bidirected edge') magenta_patch = mpatches.Patch(color='red', label='Node') plt.legend(handles=[black_patch, white_patch, magenta_patch]) # axes.relim() # axes.autoscale_view() plt.show() """