def test_dijkstra(self): distance, previous = dijkstra(self.graph, 'a') self.assertEqual(distance, self.distance) self.assertEqual(previous, self.previous) self.assertRaisesRegexp(ValueError, "dijkstra: 0 is not a vertex of graph.", dijkstra, self.graph, 0) distance, previous = dijkstra(self.graph2, 'd') self.assertEqual(distance, self.distance2) self.assertEqual(previous, self.previous2)
def test_dijkstra_iterator(self): f = lambda g, u: list(dijkstra_iterator(g, u)) self.assertRaisesRegexp(ValueError, "dijkstra_iterator: 0 is not a vertex of graph.", f, self.graph, 0) for graph in (self.graph, self.graph2): paths = list(dijkstra_iterator(graph, 'a')) ddist, dprev = dijkstra(graph, 'a') for (node, dist, path) in paths: self.assertEqual(dist, ddist[node])
def test_dijkstra_bidirectional(self): self.assertRaisesRegexp(ValueError, "dijkstra_bidirectional: 0 is not a vertex of graph.", dijkstra_bidirectional, self.graph, 0, 'a') self.assertRaisesRegexp(ValueError, "dijkstra_bidirectional: 2 is not a vertex of graph.", dijkstra_bidirectional, self.graph, 'a', 2) dist, path = dijkstra_bidirectional(self.graph, 'a', 'e') ddist, dprev = dijkstra(self.graph, 'a') self.assertEqual(dist, ddist['e']) self.graph.add_vertex('f') self.assertEqual(dijkstra_bidirectional(self.graph, 'a', 'f'), float('inf'))
def distance_network_heuristic(graph, terminals): r"""Returns an approximate minimal Steiner tree connecting `terminals` in `graph`. Given a connected, undirected graph `graph` with positive edge weights and a subset of the vertices `terminals`, this method finds a subgraph with near-minimal total edge cost among all connected subgraphs that contain these vertices. Parameters ---------- graph : rosemary.graphs.graphs.Graph terminals : list or set or tuple A collection of vertices of `graph`. Returns ------- (weight, edges) : tuple The number `weight` is the weight of the Steiner tree. The list `edges` is a list of the edges in the Steiner tree. Notes ----- The Steiner problem in graphs asks to find the minimal tree connecting the terminal vertices. This problem is NP-complete and has proven to be extremely difficult even for small problem instances. Given this, it is of practical importance to have heuristics for finding low-cost Steiner trees. This method uses the heuristic algorithm given in [1], which produces a tree spanning the terminals with total cost <= 2*(1 - 1/t)*MST, where t is the number of terminal vertices and MST is the weight of a minimal Steiner tree. We also apply the improvement procedure given in [3]. The implementation given runs in time O(t*|E|*log(|V|)), where |E| is the number of edges of `graph` and |V| is the number of vertices. References ---------- .. [1] L. Kou, G. Markowsky, L. Berman, "A Fast Algorithm for Steiner Trees", Acta Informatica, Volume 15, Issue 2, June 1981, 141-145. .. [2] P. Winter, "Steiner Problem in Networks: A Survey", Networks, Volume 17, Issue 2, Summer 1987, 129-167. .. [3] S. Voss, "Steiner's Problem in Graphs: Heuristic Methods", Discrete Applied Mathematics 40, 1992, 45-72. .. [4] H.J. Proemel, A. Steger, "The Steiner Tree Problem - A Tour through Graphs, Algorithms, and Complexity", Vieweg, 2002. Examples -------- >>> G = Graph() >>> G.add_edges([('u1', 'u2', 1), ('u1', 'v1', 2), ('u1', 'v2', 2), ('u2', 'v3', 4), ('u2', 'v4', 3), ('u2', 'v5', 5), ('u3', 'v1', 2), ('u3', 'v3', 8), ('u4', 'v2', 8), ('u4', 'v5', 8), ('v1', 'v2', 8), ('v1', 'v3', 9), ('v2', 'v5', 5), ('v3', 'v4', 8)]) >>> distance_network_heuristic(G, ['v1', 'v2', 'v3', 'v4', 'v5']) (17, [('u1', 'u2'), ('u1', 'v1'), ('u1', 'v2'), ('u2', 'v3'), ('u2', 'v4'), ('v2', 'v5')]) """ # Create the distance network induced by the terminal set. distance_network = Graph() # shortest_prev[u] holds the predecessor dict for the shortest path # tree rooted at u. shortest_prev = {} shortest_dist = {} for u in terminals: u_dist, u_prev = dijkstra(graph, u) shortest_dist[u] = u_dist shortest_prev[u] = u_prev # For each pair (u, v) of terminal vertices, add an edge with weight # equal to the length of the shortest u, v path. distance_network.add_edges([(u, v, shortest_dist[u][v]) for (u, v) in itertools.combinations(terminals, 2)]) # Determine the minimum spanning tree of the distance network. _, mst_edges = prim(distance_network, edge_list=True) subnetwork = Graph() # Construct a subnetwork of the graph by replacing each edge in the # minimum spanning tree by the corresponding minimum cost path. for (u, v) in mst_edges: a, b = shortest_prev[u][v], v while a is not None: subnetwork.add_edge(a, b, graph[a][b]) a, b = shortest_prev[u][a], a # Determine the minimum spanning tree of the subnetwork. _, subnetwork_mst_edges = prim(subnetwork, edge_list=True) tree_weight, tree_edges = _improve(graph, subnetwork_mst_edges, terminals) return (tree_weight, tree_edges)
def minimum_cost_paths_heuristic(graph, terminals): r"""Returns an approximate minimal Steiner tree connecting `terminals` in `graph`. Given a connected, undirected graph `graph` with positive edge weights and a subset of the vertices `terminals`, this method finds a subgraph with near-minimal total edge cost among all connected subgraphs that contain these vertices. Parameters ---------- graph : rosemary.graphs.graphs.Graph terminals : list or set or tuple A collection of vertices of `graph`. Returns ------- (weight, edges) : tuple The number `weight` is the weight of the Steiner tree. The list `edges` is a list of the edges in the Steiner tree. Notes ----- The Steiner problem in graphs asks to find the minimal tree connecting the terminal vertices. This problem is NP-complete and has proven to be extremely difficult even for small problem instances. Given this, it is of practical importance to have heuristics for finding low-cost Steiner trees. This method uses the heuristic algorithm given in [1], which produces a tree spanning the terminals with total cost <= 2*(1 - 1/t)*MST, where t is the number of terminal vertices and MST is the weight of a minimal Steiner tree. We use the modification given in [2], using each terminal as a root, and keeping the best tree. We also apply the improvement procedure given in [2]. The implementation given runs in time O(t**2*|E|*log(|V|)), where |E| is the number of edges of `graph` and |V| is the number of vertices. References ---------- .. [1] H. Takahashi, A. Matsuyama, "An Approximate Solution for the Steiner Problem in Graphs", Math. Japonica 24, No. 6, 1980, 573-577. .. [2] S. Voss, "Steiner's Problem in Graphs: Heuristic Methods", Discrete Applied Mathematics 40, 1992, 45-72. .. [3] P. Winter, "Steiner Problem in Networks: A Survey", Networks, Volume 17, Issue 2, Summer 1987, 129-167. Examples -------- >>> G = Graph() >>> G.add_edges([('u1', 'u2', 1), ('u1', 'v1', 2), ('u1', 'v2', 2), ('u2', 'v3', 4), ('u2', 'v4', 3), ('u2', 'v5', 5), ('u3', 'v1', 2), ('u3', 'v3', 8), ('u4', 'v2', 8), ('u4', 'v5', 8), ('v1', 'v2', 8), ('v1', 'v3', 9), ('v2', 'v5', 5), ('v3', 'v4', 8)]) >>> distance_network_heuristic(G, ['v1', 'v2', 'v3', 'v4', 'v5']) (17, [('u1', 'u2'), ('u1', 'v1'), ('u1', 'v2'), ('u2', 'v3'), ('u2', 'v4'), ('v2', 'v5')]) """ # shortest_prev[u] holds the predecessor dict for the shortest path # tree rooted at u. shortest_prev = {} shortest_dist = {} for u in terminals: u_dist, u_prev = dijkstra(graph, u) shortest_dist[u] = u_dist shortest_prev[u] = u_prev best_weight = inf for root in terminals: tree_edges = [] tree_vertices = set() terminal_set = set(terminals) terminal_set.discard(root) tree_vertices.add(root) # Repeat until the tree contains all terminals. while terminal_set: min_dist = inf # Find vertices u and v with minimal distance with v in the # tree and u a terminal not in the tree. for u in terminal_set - tree_vertices: for v in tree_vertices: if shortest_dist[u][v] < min_dist: min_dist = shortest_dist[u][v] min_vertices = (u, v) u, v = min_vertices a, b = shortest_prev[u][v], v tree_vertices.add(u) terminal_set.discard(u) # Add the edges (a, b) in the shortest u, v path to the tree. while a is not None: if a < b: tree_edges.append((a, b)) else: tree_edges.append((b, a)) tree_vertices.add(a) a, b = shortest_prev[u][a], a # Apply the improvement procedure to the tree. tree_weight, tree_edges = _improve(graph, tree_edges, terminals) if tree_weight < best_weight: best_weight = tree_weight best_edges = tree_edges return best_weight, best_edges
def test_dijkstra(self): distance, previous = dijkstra(self.graph, 'a') self.assertEqual(distance, self.distance) self.assertEqual(previous, self.previous)