def test_scc(self): """ Given the following graph it computes SCCs in it. />(2)---------->(8)----------v / | \ /->(10) (1) | v / | ^\ v /---->(9)<-\ v \(3)----->(4) \-(11) \ ^| \ /^ \ / | v / >(5) | (7)----/ ^\ | / \v v (6) """ g = Graph.build(edges=[(1,2), (3,2), (2,3), (3,4), (3,5), (5,4), (4,6), (4,7), (7,6), (6,5), (4,9), (7, 11), (11,9), (9,10), (10,11), (2,8), (8,9), (8,10)], directed=True) connected_components = scc(g) expected = [[8], [1], [2, 3], [9, 10, 11], [4, 5, 6, 7]] self.assertEqual(len(connected_components), len(expected), 'should return the same number of components') for component in connected_components: self.assertIn(component, expected, 'should detect strongly connected components')
def test_max_weighted_independed_set_in_tree(self): tree = Graph.build(directed=True, edges=[ ('r', 'a'), ('r', 'b'), ('a', 'c'), ('a', 'd'), ('a', 'e'), ('d', 'h'), ('d', 'i'), ('b', 'f'), ('b', 'g'), ('g', 'j') ]) values = { 'r': 10, 'a': 5, 'c': 3, 'd': 4, 'e': 9, 'h': 2, 'i': 7, 'b': 8, 'f': 3, 'g': 11, 'j': 8 } for (vertex, value) in values.iteritems(): tree.set_vertex_value(vertex, value) (actual_max_weight, actual_vertex_set) = \ max_weighted_independent_set_in_tree(tree) expected_max_weight = 40 expected_vertex_set = set(['c', 'e', 'g', 'f', 'i', 'h', 'r']) self.assertEqual(actual_max_weight, expected_max_weight, \ 'should compute the correct max weight') self.assertEqual(actual_vertex_set, expected_vertex_set, \ 'should compute the correct included vertex set')
def test_scc(self): """ Given the following graph it computes SCCs in it. />(2)---------->(8)----------v / | \ /->(10) (1) | v / | ^\ v /---->(9)<-\ v \(3)----->(4) \-(11) \ ^| \ /^ \ / | v / >(5) | (7)----/ ^\ | / \v v (6) """ g = Graph.build(edges=[(1, 2), (3, 2), (2, 3), (3, 4), (3, 5), (5, 4), (4, 6), (4, 7), (7, 6), (6, 5), (4, 9), (7, 11), (11, 9), (9, 10), (10, 11), (2, 8), (8, 9), (8, 10)], directed=True) connected_components = scc(g) expected = [[8], [1], [2, 3], [9, 10, 11], [4, 5, 6, 7]] self.assertEqual(len(connected_components), len(expected), 'should return the same number of components') for component in connected_components: self.assertIn(component, expected, 'should detect strongly connected components')
def test_bellman_ford_with_non_negative_cycle(self): """ Given the following graph from the lecture: -2 (a)-------->(b) ^| | | | 4\ -1/ \ / \(c)</ / \ 2/ \-3 v/ \v (x) (y) \^ /^ \ / 1\ /-4 \ / (z) And and a vertex (s) that is connected to all other vertices through edges of cost 0. """ g = Graph.build(edges=[('a', 'b', -2), ('c', 'a', 4), ('b', 'c', -1), ('c', 'x', 2), ('c', 'y', -3), ('z', 'x', 1), ('z', 'y', -4)], directed=True) g.add_vertex('s') for vertex in g.get_vertices(): g.add_edge(('s', vertex, 0)) (costs, __) = shortest_path(g, 's', return_paths=False) expected_costs = {'a': 0, 'c': -3, 'b': -2, 's': 0,'y': -6, 'x': -1,'z': 0} self.assertEqual(costs, expected_costs, 'should produce the correct costs')
def kruskal_union_find_mst(graph): """ Uses Kruskel's greedy algorithm to compute the MST of graph. Running time: O(m*log n) - where m is the number of edges and n is the number of vertices. Params: graph: object, instance of src.graph.Graph Returns: object, src.graph.Graph instance reperesenting the MST. """ mst_edges = [] edges = graph.get_edges() num_vertices = len(graph.get_vertices()) edges = graph.get_edges() edges.sort(key=lambda e: e[2]) union_find = UnionFind() index = 0 while index < num_vertices: edge = edges[index] [tail, head, value] = graph.split_edge(edge) index += 1 if union_find.find(head) == union_find.find(tail): continue else: union_find.union(head, tail) mst_edges.append(edge) mst = Graph.build(edges=mst_edges, directed=False) return mst
def kruskal_union_find_mst(graph): """ Uses Kruskel's greedy algorithm to compute the MST of graph. Running time: O(m*log n) - where m is the number of edges and n is the number of vertices. Params: graph: object, instance of src.graph.Graph Returns: object, src.graph.Graph instance reperesenting the MST. """ mst_edges = [] edges = graph.get_edges() num_vertices = len(graph.get_vertices()) edges = graph.get_edges() edges.sort(key=lambda e: e[2]) union_find = UnionFind() index = 0 while index < num_vertices: edge = edges[index] [tail, head, value] = graph.split_edge(edge) index += 1 if union_find.find(head) == union_find.find(tail): continue else: union_find.union(head, tail) mst_edges.append(edge) mst = Graph.build(edges=mst_edges, directed=False) return mst
def test_johnson(self): """ Given the following graph from the lecture: -2 (a)-------->(b) ^| | | | 4\ -1/ \ / \(c)</ / \ 2/ \-3 v/ \v (x) (y) \^ /^ \ / 1\ /-4 \ / (z) """ g = Graph.build(edges=[('a', 'b', -2), ('c', 'a', 4), ('b', 'c', -1), ('c', 'x', 2), ('c', 'y', -3), ('z', 'x', 1), ('z', 'y', -4)], directed=True) actual = johnson(g) expected = { 'a': {'a': 0, 'c': -3, 'b': -2, 'y': -6, 'x': -1, 'z': INF}, 'c': {'a': 4, 'c': 0, 'b': 2, 'y': -3, 'x': 2, 'z': INF}, 'b': {'a': 3, 'c': -1, 'b': 0, 'y': -4, 'x': 1, 'z': INF}, 'y': {'a': INF, 'c': INF, 'b': INF, 'y': 0, 'x': INF, 'z': INF}, 'x': {'a': INF, 'c': INF, 'b': INF, 'y': INF, 'x': 0, 'z': INF}, 'z': {'a': INF, 'c': INF, 'b': INF, 'y': -4, 'x': 1, 'z': 0} } self.assertEqual(actual, expected, 'should return correct all pairs shortest path distances')
def test_shortest_path_heap_case3(self): """ Compute a shortest path using the heap implementation. """ g = Graph.build(edges=[('a', 'b', 1), ('a', 'c', 4), ('a', 'd', 4), ('b', 'c', 1), ('c', 'd', 1)], directed=True) shortest_path = shortest_path_heap(g, 'a') self.assertEqual(shortest_path['d'], 3)
def test_shortest_path_heap_case3(self): """ Compute a shortest path using the heap implementation. """ g = Graph.build(edges=[ ('a','b',1),('a','c',4),('a','d',4),('b','c',1),('c','d',1)], directed=True) shortest_path = shortest_path_heap(g, 'a') self.assertEqual(shortest_path['d'], 3)
def prims_heap_mst(graph): """ Computes minimal spanning tree using a heap data structure to store unexplored vertices. The difference is that it maintains the frontier of the explored MST in a heap, as such always computing the min vertex in O(logm), where m is the number of edges. The heap is used to store the vertices not the edges as in the previous implementation. The heap maintains two invariants: 1. elements in the heap are vertices not yet explored 2. keys under which each vertex is stored in the heap is the minimum weight of an edge incident on the vertex whose tail is already in the MST. Complexity: O(m*log n), m - number of edges, n - number of vertices Args: graph: object, data structure to hold the graph data. Returns: A Graph instance reperesenting the MST. """ mst_vertices = [] mst_edges = [] INF = float('inf') vertices = graph.get_vertices() num_vertices = len(vertices) frontier = VertexHeap.heapify([(v, INF) for v in vertices]) vertex = random.choice(graph.get_vertices()) frontier.remove(vertex) # This dict stores for each vertex the neighbour with the smallest edge, # and the edge cost. Format {vertex: (incident_vertex, edge_cost)} vertices = {} while vertex: mst_vertices.append(vertex) if len(mst_vertices) == num_vertices: break for edge in graph.egress(vertex): [__, head, cost] = graph.split_edge(edge) if head not in mst_vertices: [__, head_key] = frontier.remove(head) min_cost = min(cost, head_key) frontier.insert((head, min_cost)) if min_cost < head_key: vertices[head] = (vertex, cost) # Extract the vertex with min cost and compute it's associated min edge. (head, __) = frontier.extract_min() (tail, cost) = vertices[head] mst_edges.append((tail, head, cost)) vertex = head mst = Graph.build(edges=mst_edges, directed=False) return mst
def prims_heap_mst(graph): """ Computes minimal spanning tree using a heap data structure to store unexplored vertices. The difference is that it maintains the frontier of the explored MST in a heap, as such always computing the min vertex in O(logm), where m is the number of edges. The heap is used to store the vertices not the edges as in the previous implementation. The heap maintains two invariants: 1. elements in the heap are vertices not yet explored 2. keys under which each vertex is stored in the heap is the minimum weight of an edge incident on the vertex whose tail is already in the MST. Complexity: O(m*log n), m - number of edges, n - number of vertices Args: graph: object, data structure to hold the graph data. Returns: A Graph instance reperesenting the MST. """ mst_vertices = [] mst_edges = [] INF = float('inf') vertices = graph.get_vertices() num_vertices = len(vertices) frontier = VertexHeap.heapify([(v, INF) for v in vertices]) vertex = random.choice(graph.get_vertices()) frontier.remove(vertex) # This dict stores for each vertex the neighbour with the smallest edge, # and the edge cost. Format {vertex: (incident_vertex, edge_cost)} vertices = {} while vertex: mst_vertices.append(vertex) if len(mst_vertices) == num_vertices: break for edge in graph.egress(vertex): [__, head, cost] = graph.split_edge(edge) if head not in mst_vertices: [__, head_key] = frontier.remove(head) min_cost = min(cost, head_key) frontier.insert((head, min_cost)) if min_cost < head_key: vertices[head] = (vertex, cost) # Extract the vertex with min cost and compute it's associated min edge. (head, __) = frontier.extract_min() (tail, cost) = vertices[head] mst_edges.append((tail, head, cost)) vertex = head mst = Graph.build(edges=mst_edges, directed=False) return mst
def test_cluster_graph(self): """ The graph is specified in distances but it should look something like this: | *a | *e | *c | | | | | *d | *f | *b +---------------------------------------------> """ g = Graph.build(edges=[ ('a', 'e', 8), ('a', 'c', 7), ('a', 'd', 9), ('a', 'f', 10), ('a', 'b', 8), ('e', 'f', 6), ('e', 'd', 5), ('e', 'b', 10), ('e', 'c', 1), ('c', 'f', 5), ('c', 'd', 4), ('c', 'b', 9), ('d', 'f', 1), ('d', 'b', 7), ('f', 'b', 8) ], directed=False) (clusters, distances) = cluster_graph(g, 4) expectedClusters = { 'a': ['a'], 'c': ['c', 'e'], 'b': ['b'], 'd': ['d', 'f'] } expectedDistances = { ('b', 'c'): 9, ('c', 'd'): 4, ('a', 'd'): 9, ('a', 'b'): 8, ('a', 'c'): 7, ('b', 'd'): 7 } self.assertEqual(clusters, expectedClusters, 'should return correct clusters') self.assertEqual(distances, expectedDistances, 'should compute distances') (clusters, distances) = cluster_graph(g, 3) expectedClusters = { 'a': ['a'], 'b': ['b'], 'c': ['c', 'e', 'd', 'f'] } expectedDistances = { ('b', 'c'): 7, ('a', 'b'): 8, ('a', 'c'): 7 } self.assertEqual(clusters, expectedClusters, 'should return correct clusters') self.assertEqual(distances, expectedDistances, 'should compute distances')
def test_contract(self): g = Graph.build(edges=[(1,2), (2,3), (3,1)]) g = contract(g, (1,2)) self.assertIn('1_2', g.table, '1 and 2 have fused') self.assertIn(3, g.table['1_2'], '1 and 2 have fused') self.assertIn(3, g.table, '1 and 2 have fused') self.assertIn('1_2', g.table[3], '1 and 2 have fused') self.assertNotIn(2, g.get_vertices(), 'no more 2 vertex')
def test_get_sync_vertices(self): """ Given the graph below it should find 1 and 2 as sync vertices: (1) <- (3) / (2) <-/ """ g = Graph.build(edges=[(3,1), (3,2)], directed=True) actual = get_sync_vertices(g) expected = [1, 2] self.assertEqual(actual, expected, 'found sync vertices')
def test_get_sync_vertices(self): """ Given the graph below it should find 1 and 2 as sync vertices: (1) <- (3) / (2) <-/ """ g = Graph.build(edges=[(3, 1), (3, 2)], directed=True) actual = get_sync_vertices(g) expected = [1, 2] self.assertEqual(actual, expected, 'found sync vertices')
def test_shortest_path_naive_case1(self): """ Compute a shortest path using the heap implementation. """ g = Graph.build(edges=[ ('a','c',3),('c','b',10),('a','b',15),('d','b',9), ('a','d',4),('d','f',7),('d','e',3),('e','g',1), ('e','f',5),('g','f',2),('f','b',1)], directed=True) shortest_path = shortest_path_naive(g, 'a') self.assertEqual(shortest_path['g'], 8) self.assertEqual(shortest_path['b'], 11)
def test_shortest_path_naive_case1(self): """ Compute a shortest path using the heap implementation. """ g = Graph.build(edges=[('a', 'c', 3), ('c', 'b', 10), ('a', 'b', 15), ('d', 'b', 9), ('a', 'd', 4), ('d', 'f', 7), ('d', 'e', 3), ('e', 'g', 1), ('e', 'f', 5), ('g', 'f', 2), ('f', 'b', 1)], directed=True) shortest_path = shortest_path_naive(g, 'a') self.assertEqual(shortest_path['g'], 8) self.assertEqual(shortest_path['b'], 11)
def test_randomized_cut(self): """ Tests that the following graph is split correctly. (a)--(c) | / | (b)--(d) """ g = Graph.build(edges = [('a', 'b'), ('a', 'c'), ('b', 'c'), ('b', 'd'), ('c', 'd')]) compacted = randomized_cut(g) self.assertEqual(len(compacted.get_vertices()), 2, 'should have compacted to only 2 vertices')
def test_topological_ordering(self): """ Given the following graph: /-->(b)-->\ (a) (d) \-->(c)-->/ """ g = Graph.build(edges=[('a','b'), ('a', 'c'), ('b', 'd'), ('c', 'd')], directed=True) ordering = dfs_loop(g) self.assertEqual(ordering['a'], 1) self.assertIn(ordering['b'], [2,3]) self.assertIn(ordering['c'], [2,3]) self.assertEqual(ordering['d'], 4)
def test_kruskal_suboptimal_mst_2(self): g = Graph.build(edges=[('a', 'b', 3), ('a', 'f', 2), ('f', 'g', 7), ('b', 'd', 16), ('d', 'e', 11), ('f', 'e', 1), ('g', 'e', 6), ('g', 'h', 15), ('e', 'h', 5), ('b', 'c', 17), ('c', 'd', 8), ('d', 'i', 4), ('c', 'i', 18), ('e', 'i', 10), ('h', 'i', 12), ('i', 'j', 9), ('h', 'j', 13)], directed=False) mst = kruskal_suboptimal_mst(g) expected = sorted([('a', 'b', 3), ('a', 'f', 2), ('e', 'f', 1), ('e', 'g', 6), ('e', 'h', 5), ('e', 'i', 10), ('d', 'i', 4), ('c', 'd', 8), ('i', 'j', 9)]) actual = sorted(mst.get_edges()) self.assertEqual(actual, expected, 'should compute the correct mst')
def test_topological_ordering(self): """ Given the following graph: /-->(b)-->\ (a) (d) \-->(c)-->/ """ g = Graph.build(edges=[('a', 'b'), ('a', 'c'), ('b', 'd'), ('c', 'd')], directed=True) ordering = dfs_loop(g) self.assertEqual(ordering['a'], 1) self.assertIn(ordering['b'], [2, 3]) self.assertIn(ordering['c'], [2, 3]) self.assertEqual(ordering['d'], 4)
def test_randomized_cut_for_larger_graph(self): """ Test minimum cut for a larger graph. (a)--(b)--(e)--(f) | X | | X | (c)--(d)--(g)--(h) """ g = Graph.build(edges = [('a', 'b'), ('a', 'c'), ('a', 'd'), ('b', 'c'), ('b', 'd'), ('b', 'e'), ('c', 'd'), ('d', 'g'), ('e', 'g'), ('e', 'f'), ('e', 'h'), ('f', 'g'), ('f', 'h'), ('g', 'h')]) compacted = randomized_cut(g) self.assertEqual(len(compacted.get_vertices()), 2, 'should have compacted to only 2 vertices')
def test_get_frontier(self): """ Makes sure frontier edges are correctly picked. (1)--->(2) | | V V (3)<---(4) """ g = Graph.build(edges=[(1,2), (1,3), (2,4), (4,3)], directed=True) explored_vertices = [1,3] actual = get_frontier(g, explored_vertices) expected = set([(1,2,True)]) self.assertEqual(actual, expected, 'should only return the only node ' \ 'reachable that has not yet been visited')
def test_kruskal_suboptimal_mst_2(self): g = Graph.build(edges=[('a', 'b', 3), ('a', 'f', 2), ('f', 'g', 7), ('b', 'd', 16), ('d', 'e', 11), ('f', 'e', 1), ('g', 'e', 6), ('g', 'h', 15), ('e', 'h', 5), ('b', 'c', 17), ('c', 'd', 8), ('d', 'i', 4), ('c', 'i', 18), ('e', 'i', 10), ('h', 'i', 12), ('i', 'j', 9), ('h', 'j', 13)], directed=False) mst = kruskal_suboptimal_mst(g) expected = sorted([('a', 'b', 3), ('a', 'f', 2), ('e', 'f', 1), ('e', 'g', 6), ('e', 'h', 5), ('e', 'i', 10), ('d', 'i', 4), ('c', 'd', 8), ('i', 'j', 9)]) actual = sorted(mst.get_edges()) self.assertEqual(actual, expected, 'should compute the correct mst')
def test_get_frontier(self): """ Makes sure frontier edges are correctly picked. (1)--->(2) | | V V (3)<---(4) """ g = Graph.build(edges=[(1, 2), (1, 3), (2, 4), (4, 3)], directed=True) explored_vertices = [1, 3] actual = get_frontier(g, explored_vertices) expected = set([(1, 2, True)]) self.assertEqual(actual, expected, 'should only return the only node ' \ 'reachable that has not yet been visited')
def test_depth_first_search_returning_paths(self): """ Given this graph to explore: />(a)-->(d)-->(e) / |v |v /^ (s)-->(b)-->(c)-/ """ g = Graph.build(edges=[('s', 'a'), ('a', 'd'), ('d', 'e'), ('s', 'b'), ('b', 'c'), ('c', 'e'), ('a', 'b'), ('d', 'c')], directed=True) paths = dfs_paths(g, 's', 'e') expected_paths = [['s', 'b', 'c', 'e'], ['s', 'a', 'd', 'e'], ['s', 'a', 'd', 'c', 'e'], ['s', 'a', 'b', 'c', 'e']] self.assertEqual(paths, expected_paths, 'should produce the expected paths')
def test_bfs_parses_the_graph_in_order(self): """ Correctly explore the following graph: _(a)--(c)--(e) / | / \ | (s)--(b)-------(d) """ edges = [('s', 'a'), ('s', 'b'), ('a', 'b'), ('a', 'c'), ('b', 'd'), ('c', 'e'), ('c', 'd'), ('e', 'd')] graph = Graph.build(edges=edges) expected = ['s', 'a', 'b', 'c', 'd', 'e'] actual = bfs(graph, 's') self.assertEqual(actual, expected, 'should have visited the graph in correct order')
def test_depth_first_search_for_simple_connected_non_directed_graph(self): """ Given this graph to explore: /-(a)---(d)---(e) / | | / (s)---(b)---(c)-/ """ g = Graph.build(edges=[('s', 'a'), ('s', 'b'), ('a', 'b'), ('a', 'd'), ('b', 'c'), ('d', 'c'), ('d', 'e'), ('c', 'e')], directed=False) path = dfs(g, 's') for vertex in g.get_vertices(): self.assertEqual(g.get_vertex_value(vertex), VISITED, 'should have been visited by the algo')
def test_cluster_graph(self): """ The graph is specified in distances but it should look something like this: | *a | *e | *c | | | | | *d | *f | *b +---------------------------------------------> """ g = Graph.build(edges=[('a', 'e', 8), ('a', 'c', 7), ('a', 'd', 9), ('a', 'f', 10), ('a', 'b', 8), ('e', 'f', 6), ('e', 'd', 5), ('e', 'b', 10), ('e', 'c', 1), ('c', 'f', 5), ('c', 'd', 4), ('c', 'b', 9), ('d', 'f', 1), ('d', 'b', 7), ('f', 'b', 8)], directed=False) (clusters, distances) = cluster_graph(g, 4) expectedClusters = { 'a': ['a'], 'c': ['c', 'e'], 'b': ['b'], 'd': ['d', 'f'] } expectedDistances = { ('b', 'c'): 9, ('c', 'd'): 4, ('a', 'd'): 9, ('a', 'b'): 8, ('a', 'c'): 7, ('b', 'd'): 7 } self.assertEqual(clusters, expectedClusters, 'should return correct clusters') self.assertEqual(distances, expectedDistances, 'should compute distances') (clusters, distances) = cluster_graph(g, 3) expectedClusters = {'a': ['a'], 'b': ['b'], 'c': ['c', 'e', 'd', 'f']} expectedDistances = {('b', 'c'): 7, ('a', 'b'): 8, ('a', 'c'): 7} self.assertEqual(clusters, expectedClusters, 'should return correct clusters') self.assertEqual(distances, expectedDistances, 'should compute distances')
def test_weighted_maximum_cut(self): """ Given the following weighted graph. (u)-3-(v) | \ / | | 5\/1 4 2 /\ | | / \ | (w)-6-(x) """ g = Graph.build(edges=[('u', 'v', 3), ('u', 'w', 2), ('u', 'x', 5), ('v', 'x', 4), ('w', 'x', 6)], directed=False) (left, right) = maximum_cut(g) self.assertEqual(2, len(left), 'left should contain 2 vertices') self.assertEqual(2, len(right), 'right should contain 2 vertices')
def test_prims_suboptimal_mst(self): """ Compute minimal spanning tree given this graph: (a)----1----(b) | \ | 4 \--3--\ 2 | \ | (c)----5----(d) """ g = Graph.build(edges=[('a', 'b', 1), ('a', 'c', 4), ('a', 'd', 3), ('b', 'd', 2), ('c', 'd', 5)], directed=False) expected = [('a', 'b', 1), ('a', 'c', 4), ('b', 'd', 2)] mst = prims_suboptimal_mst(g) actual = sorted(mst.get_edges()) self.assertEqual(actual, expected, 'should have computed correct mst')
def test_bfs_connected_components(self): """ Test the connected graphs algorithm for the folowing setup. (1)--(3) (2)--(4) (6) \ / / \ (5) (8) (10) / \ (7) (9) This should return the nodes in three subgraphs. """ edges = [(1,3), (1,5), (3,5), (5,7), (5,9), (2,4), (6,8), (6,10)] g = Graph.build(edges=edges) expected = [[1,3,5,9,7], [2,4], [6,8,10]] actual = bfs_connected_components(g) self.assertEqual(actual, expected, 'should correctly determine subgraphs')
def test_vertex_cover_for_a_star_graph(self): """ Given this graph: (b)(c)(d) \ | / (i)-(a)-(e) / | \ (h)(g)(f) """ g = Graph.build(edges=[ ('a', 'b'), ('a', 'c'), ('a', 'd'), ('a', 'e'), ('a', 'f'), ('a', 'g'), ('a', 'h'), ('a', 'i') ], directed=False) actual = vertex_cover(g) expected = ['a'] self.assertEqual(actual, expected, 'should compute vertex cover')
def test_bellman_ford_with_negative_cycle(self): """ Given the following graph: /--2-->(v)---2--->(w)--2--\v (s) \^ / (t) \ \1 v/-5 /^ \---4---->(x)------4---/ """ g = Graph.build(edges=[ ('s', 'v', 2), ('v', 'w', 2), ('w', 't', 2), ('s', 'x', 4), ('x', 'v', 1), ('w', 'x', -5), ('x', 't', 4) ], directed=True) no_negative_cycles = shortest_path(g, 's') self.assertFalse(no_negative_cycles, 'should detect no negative cycles')
def test_prims_suboptimal_mst(self): """ Compute minimal spanning tree given this graph: (a)----1----(b) | \ | 4 \--3--\ 2 | \ | (c)----5----(d) """ g = Graph.build(edges=[('a', 'b', 1), ('a', 'c', 4), ('a', 'd', 3), ('b', 'd', 2), ('c', 'd', 5)], directed=False) expected = [('a', 'b', 1), ('a', 'c', 4), ('b', 'd', 2)] mst = prims_suboptimal_mst(g) actual = sorted(mst.get_edges()) self.assertEqual(actual, expected, 'should have computed correct mst')
def x_test_ford_fulkerson_maximum_flow_2(self): # TODO fix this! """ Given the graph: 16/-->(a)--12-->(b)---\20 / | ^ / ^ \v (s) 9| |4 /-9/ |7 (t) \ v | v | /^ 13\--->(c)--14-->(d)---/4 """ g = Graph.build(edges=[('s', 'a', 16), ('s', 'c', 13), ('a', 'c', 9), ('c', 'a', 4), ('a', 'b', 12), ('c', 'd', 14), ('b', 'c', 9), ('d', 'b', 7), ('b', 't', 20), ('d', 't', 4)], directed=True) (max_flow, flow) = ford_fulkerson_maximum_flow(g, 's', 't') expected_max_flow = 23 self.assertEqual(max_flow, expected_max_flow, 'should compute max flow')
def test_ford_fulkerson_maximum_flow_1(self): """ Given the graph: /--1->(a)--1---\v (s) v1 (t) \--1->(b)--1---/^ """ g = Graph.build(edges=[('s', 'a', 1), ('s', 'b', 1), ('a', 'b', 1), ('a', 't', 1), ('b', 't', 1)], directed=True) (max_flow, flow) = ford_fulkerson_maximum_flow(g, 's', 't') expected_max_flow = 2 expected_flow_edges = [('a', 'b', 0), ('a', 't', 1), ('s', 'a', 1), ('s', 'b', 1), ('b', 't', 1)] self.assertEqual(max_flow, expected_max_flow, 'should compute max flow') self.assertListEqual(flow.get_edges(), expected_flow_edges, 'should compute flow through edges correctly')
def test_vertex_cover_for_a_clique_graph(self): """ Given this graph: (a)---(b) | \ /| | X | | / \ | (c)---(d) """ g = Graph.build(edges=[ ('a', 'b'), ('a', 'c'), ('a', 'd'), ('b', 'd'), ('b', 'c'), ('d', 'c') ], directed=False) actual = vertex_cover(g) expected = ['a', 'b', 'c', 'd'] self.assertEqual(set(actual), set(expected), 'should compute vertex cover')
def test_topological_ordering_on_a_cyclic_graph(self): """ Given the following graph: /-->(v)---\ / | \v (s)------+----->(w) ^\ v / \----(t)<---/ """ g = Graph.build(edges=[('s', 'v'), ('v', 'w'), ('w', 't'), ('t', 's'), ('s', 'w'), ('v', 't')], directed=True) ordering = dfs_loop(g) self.assertEqual(ordering['s'], 1) self.assertEqual(ordering['t'], 4) self.assertEqual(ordering['w'], 3) self.assertEqual(ordering['v'], 2)
def test_weighted_maximum_cut(self): """ Given the following weighted graph. (u)-3-(v) | \ / | | 5\/1 4 2 /\ | | / \ | (w)-6-(x) """ g = Graph.build(edges=[ ('u', 'v', 3), ('u', 'w', 2), ('u', 'x', 5), ('v', 'x', 4),('w', 'x', 6)], directed=False) (left, right) = maximum_cut(g) self.assertEqual(2, len(left), 'left should contain 2 vertices') self.assertEqual(2, len(right), 'right should contain 2 vertices')
def test_topological_ordering_on_a_cyclic_graph(self): """ Given the following graph: /-->(v)---\ / | \v (s)------+----->(w) ^\ v / \----(t)<---/ """ g = Graph.build(edges=[('s', 'v'), ('v', 'w'), ('w', 't'), ('t', 's'), ('s', 'w'), ('v', 't')], directed=True) ordering = dfs_loop(g) self.assertEqual(ordering['s'], 1) self.assertEqual(ordering['t'], 4) self.assertEqual(ordering['w'], 3) self.assertEqual(ordering['v'], 2)
def test_bipartite_matching(self): """ Given the graph below: (a)-(b) \_/ / \ (d)-(c) / (e) (h) / (f)--(g) """ g = Graph.build(edges=[('a', 'b'), ('a', 'c'), ('d', 'c') ('d', 'b'), ('e', 'c'), ('f', 'h'), ('f', 'g')], directed=False) pairs = bipartite_matching(g) self.assertEqual(len(pairs), 3, 'should produce three pairs')
def test_vertex_cover_for_a_larger_clique_graph(self): """ Given a clique graph, ie. a complete graph. with vertices: (a), (b), (c), (d), (e), (f), (g), (h) """ g = Graph.build(edges=[ ('a', 'b'), ('a', 'c'), ('a', 'd'), ('a', 'e'), ('a', 'f'), ('a', 'g'), ('a', 'h'), ('b', 'c'), ('b', 'd'), ('b', 'e'), ('b', 'f'), ('b', 'g'), ('b', 'h'), ('c', 'd'), ('c', 'e'), ('c', 'f'), ('c', 'g'), ('c', 'h'), ('d', 'e'), ('d', 'f'), ('d', 'g'), ('d', 'h'), ('e', 'f'), ('e', 'g'), ('e', 'h'), ('f', 'g'), ('f', 'h'), ('g', 'h') ], directed=False) actual = vertex_cover(g) expected = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] self.assertEqual(set(actual), set(expected), 'should compute vertex cover')
def test_bfs_connected_components(self): """ Test the connected graphs algorithm for the folowing setup. (1)--(3) (2)--(4) (6) \ / / \ (5) (8) (10) / \ (7) (9) This should return the nodes in three subgraphs. """ edges = [(1, 3), (1, 5), (3, 5), (5, 7), (5, 9), (2, 4), (6, 8), (6, 10)] g = Graph.build(edges=edges) expected = [[1, 3, 5, 9, 7], [2, 4], [6, 8, 10]] actual = bfs_connected_components(g) self.assertEqual(actual, expected, 'should correctly determine subgraphs')
def test_bipartite_matching(self): """ Given the graph below: (a)-(b) \_/ / \ (d)-(c) / (e) (h) / (f)--(g) """ g = Graph.build(edges=[('a', 'b'), ('a', 'c'), ('d', 'c')('d', 'b'), ('e', 'c'), ('f', 'h'), ('f', 'g')], directed=False) pairs = bipartite_matching(g) self.assertEqual(len(pairs), 3, 'should produce three pairs')
def prims_suboptimal_mst(graph): """ Computes minimum spanning tree using Prim's algorithm. Running time: O(n*m), where n is the num of vertices and m the num of edges. A greedy routine wereby we enlarge the set of explored vertices by always adding the edge on the frontier with the least cost. Discovered in 1957 by Robert Prim, 1959 by Edsger Dijkstra, 1930 by Iarnik. Args: graph: object, undirected graph where each edge has an associated cost (which can be negative). Returns: A subgraph tree of minimal cost. ie. a connected subgraph with no cycles and whose sum of all edges is minimal. """ mst_vertices = [] mst_edges = [] start_vertex = random.choice(graph.get_vertices()) mst_vertices.append(start_vertex) while len(mst_vertices) != len(graph.get_vertices()): min_value = float('inf') min_edge = None min_vertex = None for edge in graph.get_edges(): [tail, head, value] = graph.split_edge(edge) if ((tail in mst_vertices and head not in mst_vertices) or \ (head in mst_vertices and tail not in mst_vertices)) and \ (value < min_value): min_value = value min_edge = edge if tail not in mst_vertices: min_vertex = tail else: min_vertex = head mst_vertices.append(min_vertex) mst_edges.append(min_edge) mst = Graph.build(edges=mst_edges, directed=False) return mst
def prims_suboptimal_mst(graph): """ Computes minimum spanning tree using Prim's algorithm. Running time: O(n*m), where n is the num of vertices and m the num of edges. A greedy routine wereby we enlarge the set of explored vertices by always adding the edge on the frontier with the least cost. Discovered in 1957 by Robert Prim, 1959 by Edsger Dijkstra, 1930 by Iarnik. Args: graph: object, undirected graph where each edge has an associated cost (which can be negative). Returns: A subgraph tree of minimal cost. ie. a connected subgraph with no cycles and whose sum of all edges is minimal. """ mst_vertices = [] mst_edges = [] start_vertex = random.choice(graph.get_vertices()) mst_vertices.append(start_vertex) while len(mst_vertices) != len(graph.get_vertices()): min_value = float('inf') min_edge = None min_vertex = None for edge in graph.get_edges(): [tail, head, value] = graph.split_edge(edge) if ((tail in mst_vertices and head not in mst_vertices) or \ (head in mst_vertices and tail not in mst_vertices)) and \ (value < min_value): min_value = value min_edge = edge if tail not in mst_vertices: min_vertex = tail else: min_vertex = head mst_vertices.append(min_vertex) mst_edges.append(min_edge) mst = Graph.build(edges=mst_edges, directed=False) return mst
def test_bellman_ford_for_positive_edge_lengths(self): """ Given the following graph: /--2-->(v)---2--->(w)--2--\v (s) 1\v (t) \----4--->(x)------4------/^ """ g = Graph.build(edges=[ ('s', 'v', 2), ('s', 'x', 4), ('v', 'w', 2), ('w', 't', 2), ('v', 'x', 1), ('x', 't', 4) ], directed=True) (costs, paths) = shortest_path(g, 's') expected_costs = {'x': 3, 's': 0, 't': 6, 'w': 4, 'v': 2} expected_paths = {'x': ['v'], 's': [], 't': ['v', 'w'], 'w': ['v'], 'v': []} self.assertEqual(costs, expected_costs, 'should return the correct minimum paths costs') self.assertEqual(paths, expected_paths, 'should return the correct minimum paths vertices')
def test_shortest_path_naive(self): """ Compute a shortest path using a naive implementation. Given the following graph: (s)---1-->(v) | /| 4 /-2--/ 6 V v \V (w)---3-->(t) """ g = Graph.build(edges=[('s', 'v', 1), ('s', 'w', 4), ('v', 'w', 2), ('v', 't', 6), ('w', 't', 3)], directed=True) length_to = shortest_path_naive(g, 's') self.assertEqual(length_to['s'], 0, 'shortest path to self is 0') self.assertEqual(length_to['v'], 1) self.assertEqual(length_to['w'], 3) self.assertEqual(length_to['t'], 6)
def test_bellman_ford_without_computing_paths(self): """ Given the following graph: /--2-->(v)---2--->(w)--2--\v (s) 1\v (t) \----4--->(x)------4------/^ """ g = Graph.build(edges=[ ('s', 'v', 2), ('s', 'x', 4), ('v', 'w', 2), ('w', 't', 2), ('v', 'x', 1), ('x', 't', 4) ], directed=True) (costs, paths) = shortest_path(g, 's', return_paths=False) expected_costs = {'x': 3, 's': 0, 't': 6, 'w': 4, 'v': 2} expected_paths = {} # self.assertEqual(costs, expected_costs, 'should return the correct minimum paths costs') self.assertEqual(paths, expected_paths, 'should not return any paths as instructed')