class Gravitation: def __init__(self, graph, terminals, poi, max_level_attraction=2, contract_graph=True, contracted_graph=None, within_convex_hull=False, dist_paths_suitable_nodes=None): if not graph.is_node_weighted(): raise ( ValueError, "Gravitation algorithm only works with node-weighted graphs.") # Store class variables for future references. self.__original_graph = graph self.__terminals = terminals self.__poi = poi self.__contract_graph = contract_graph # terminals_poi = list(terminals) terminals_poi.append(poi) generator = SuitableNodeWeightGenerator() # Contracted graph... if contract_graph: if contracted_graph is not None: self.__graph = contracted_graph.copy() else: self.__graph = SuitabilityGraph() self.__graph.append_graph(graph) self.__graph.contract_suitable_regions( generator, excluded_nodes=terminals_poi) else: self.__graph = SuitabilityGraph() self.__graph.append_graph(graph) # # # ngh = NetworkXGraphHelper(self.__original_graph) # ngh.draw_graph(nodes_1=terminals, # nodes_2=[poi], # subgraphs_1=[r for _, (r, _, _) in self.__graph.contracted_regions.iteritems()], # node_weight_generator=generator, # node_size=25) # # # ngh = NetworkXGraphHelper(self.__graph) # ngh.draw_graph(node_weight_generator=generator, node_size=25, node_labels=True) # Copy distances and paths dictionary since it will be changed. dist_paths = None if dist_paths_suitable_nodes is not None: dist_paths = dict(dist_paths_suitable_nodes) for e in terminals_poi: dist_paths[e] = dijkstra(self.__graph, e) # Get the suitable nodes. if within_convex_hull: self.__suitable_nodes = self.__graph.get_suitable_nodes_within_convex_set( terminals_poi, generator, dist_paths) else: self.__suitable_nodes = self.__graph.get_suitable_nodes( generator, excluded_nodes=terminals_poi) # print(self.__suitable_nodes) # self.__dist_paths_node_node = {} if dist_paths is not None: self.__dist_paths_node_node = { n: dist_paths[n] for n in self.__suitable_nodes } else: self.__dist_paths_node_node = \ {n: dijkstra(self.__graph, n) for n in self.__suitable_nodes} for e in terminals_poi: if e not in self.__dist_paths_node_node: self.__dist_paths_node_node[e] = dijkstra(self.__graph, e) # max_distances = [ max(self.__dist_paths_node_node[n][0].values()) for n in self.__suitable_nodes if len(self.__dist_paths_node_node[n][0].values()) > 0 ] if len(max_distances) > 0: self.__max_dist = max(max_distances) else: self.__max_dist = 0 # # # max_level_attraction_poi = 0 # for t in terminals: # max_level_attraction_poi = max(max_level_attraction_poi, len(self.__dist_paths_node_node[poi][1][t])) # mass = self.__calculate_mass_suitable_node(poi) # self.__attract_nodes_to(poi, mass, poi, max_level_attraction_poi, 0, []) # dist_to_poi = {} for n in self.__suitable_nodes: try: dist_to_poi[n] = self.__dist_paths_node_node[n][0][poi] except KeyError: dist_to_poi[n] = sys.maxint # dist_to_poi = {n: self.__dist_paths_node_node[n][0][poi] for n in self.__suitable_nodes} # ord_suit_nodes = sorted(dist_to_poi.iteritems(), key=operator.itemgetter(1), reverse=True) ord_suit_nodes = sorted(dist_to_poi.iteritems(), key=operator.itemgetter(1)) for n, _ in ord_suit_nodes: mass = self.__calculate_mass_suitable_node(n) self.__attract_nodes_to(n, mass, n, max_level_attraction, 0, []) # # # ngh = NetworkXGraphHelper(self.__graph) # ngh.draw_graph(node_weight_generator=generator, node_size=25, node_labels=False) ''' ''' def __calculate_mass_suitable_node(self, node): hits = 0 for t in self.__terminals: p = self.__dist_paths_node_node[self.__poi][1][t] if node in p: hits += 10 mass = 0 if node in self.__graph.contracted_regions: region = self.__graph.contracted_regions[node][0] for n in region: mass += 1. / region[n][0] * 100 else: mass = 1. / self.__graph[node][0] * 100 return mass + hits ''' ''' def __calculate_adjusted_edge_cost(self, edge_cost, mass_attracting_node, distance_from_attracting_node): if self.__max_dist > 0: scaled_dist = float( distance_from_attracting_node) / self.__max_dist else: scaled_dist = 1 return edge_cost * scaled_dist / mass_attracting_node ''' ''' def __attract_nodes_to(self, attracting_node, mass_attracting_node, current_node, level_attraction, distance_so_far, already_affected_edges): if level_attraction < 1: return for neighbour in self.__graph[current_node][1]: edge = tuple(sorted([current_node, neighbour])) distance_from_attracting_node = self.__dist_paths_node_node[ attracting_node][0][neighbour] edge_cost = self.__graph[current_node][1][neighbour] if distance_so_far + edge_cost == distance_from_attracting_node and edge not in already_affected_edges: adjusted_edge_cost = \ self.__calculate_adjusted_edge_cost(edge_cost, mass_attracting_node, distance_from_attracting_node) self.__graph[current_node][1][neighbour] = adjusted_edge_cost self.__graph[neighbour][1][current_node] = adjusted_edge_cost already_affected_edges.append(edge) self.__attract_nodes_to(attracting_node, mass_attracting_node, neighbour, level_attraction - 1, distance_so_far + edge_cost, already_affected_edges) def __find_closest_node_to_node_within_region(self, node, region_id): region = self.__graph.contracted_regions[region_id][0] min_dist = sys.maxint closest_node = None distances, paths = dijkstra(self.__original_graph, node, region.keys()) for n in region: if distances[n] < min_dist: closest_node = n min_dist = distances[n] return closest_node, paths[closest_node] def __decontract_steiner_tree(self, steiner_tree): regions = [] paths = [] trees = [] for r in steiner_tree: if r in self.__graph.contracted_regions: regions.append(r) neighbors = steiner_tree[r][1].keys() new_terminals = [] for n in neighbors: closest_node_to_n, path = self.__find_closest_node_to_node_within_region( n, r) paths.append(path) new_terminals.append(closest_node_to_n) del steiner_tree[n][1][r] if len(new_terminals) > 1: region = self.__graph.contracted_regions[r][0] g = Gravitation(region, new_terminals[1:], new_terminals[0], contract_graph=False) st = g.steiner_tree() trees.append(st) for r in regions: del steiner_tree[r] for p in paths: steiner_tree.append_path(p, self.__original_graph) for st in trees: steiner_tree.append_graph(st) # Fix the edge costs. for v in steiner_tree: for w in steiner_tree[v][1]: steiner_tree[v][1][w] = self.__original_graph[v][1][w] @staticmethod def __merge_subtrees(subtrees): result = SuitabilityGraph() for subtree in subtrees: result.append_graph(subtree) return result def __prune_steiner_tree(self, steiner_tree): while True: keys_to_prune = [] for n in steiner_tree: if n not in self.__terminals and n != self.__poi: neighbours = steiner_tree[n][1].keys() if len(neighbours) == 1: neighbour = neighbours[0] del steiner_tree[neighbour][1][n] keys_to_prune.append(n) if len(keys_to_prune) == 0: break for n in keys_to_prune: del steiner_tree[n] def steiner_tree(self): subtrees = [] _, paths = dijkstra(self.__graph, self.__poi, self.__terminals) for t in self.__terminals: path = paths[t] subtree = SuitabilityGraph() # j = 0 # for i in range(len(path_to_poi)): # if path_to_poi[i] in self.__graph.contracted_regions: # region_id = path_to_poi[i] # subtree.append_from_path(path_to_poi[j:i], self.__original_graph) # closest_node = self.__find_closest_node_to_poi_within_region(region_id) # path_endpoint_1 = self.__dist_paths_node_within_region_node[closest_node][1][path_to_poi[i - 1]] # subtree.append_from_path(path_endpoint_1, self.__original_graph) # path_endpoint_2 = self.__dist_paths_node_within_region_node[closest_node][1][path_to_poi[i + 1]] # subtree.append_from_path(path_endpoint_2, self.__original_graph) # j = i + 1 # subtree.append_from_path(path_to_poi[j:], self.__original_graph) subtree.append_path(path, self.__graph) subtrees.append(subtree) steiner_tree = self.__merge_subtrees(subtrees) self.__prune_steiner_tree(steiner_tree) if self.__contract_graph: self.__decontract_steiner_tree(steiner_tree) return steiner_tree
suitability_graph = SuitabilityGraph() suitability_graph.append_graph(graph) suitability_graph.extend_suitable_regions(seed, generator) suitability_graph.extend_suitable_regions(seed, generator) total_num_suitable_nodes = len( suitability_graph.get_suitable_nodes(generator)) # start_time = time.clock() # suitable_nodes = suitability_graph.get_suitable_nodes(generator) # dist_paths = {n: dijkstra(suitability_graph, n, consider_node_weights=False) for n in suitable_nodes} # dijkstra_time = time.clock() - start_time start_time = time.clock() suitability_graph.contract_suitable_regions(generator) suitable_nodes_contracted_graph = suitability_graph.get_suitable_nodes( generator) dist_paths_contracted_graph = { n: dijkstra(suitability_graph, n, consider_node_weights=False) for n in suitable_nodes_contracted_graph } dijkstra_time_contracted_graph = time.clock() - start_time # total_num_nodes = len(suitability_graph.keys()) for num_terminals in nums_terminals: for sample in range(num_samples):
class DreyfusIMR: def __init__(self, graph, terminals, contract_graph=True, contracted_graph=None, within_convex_hull=False, dist_paths=None, nodes=None): # Check whether graph is node-weighted. if not graph.is_node_weighted(): raise ( ValueError, "Dreyfus with IMRs algorithm only works with node-weighted graphs." ) # Extract POI from the terminals list. if len(terminals) > 0: self.__poi = terminals[0] else: return # Set object variables. generator = SuitableNodeWeightGenerator() self.__original_graph = graph self.__terminals = terminals self.__contract_graph = contract_graph # Contracted graph... if contract_graph: if contracted_graph is not None: self.__graph = contracted_graph.copy() else: self.__graph = SuitabilityGraph() self.__graph.append_graph(graph) self.__graph.contract_suitable_regions( generator, excluded_nodes=terminals) else: self.__graph = SuitabilityGraph() self.__graph.append_graph(graph) # if nodes is not None: self.__nodes = list(nodes) else: if within_convex_hull: pass # self.__nodes = self.__graph.get_suitable_nodes_within_convex_set(terminals, generator, dist_paths) else: self.__nodes = self.__graph.get_suitable_nodes( generator, excluded_nodes=terminals) # for t in terminals: self.__nodes.append(t) # print(self.__nodes) # self.__dist = {} self.__paths = {} if dist_paths is not None: self.__dist = dict(dist_paths[0]) self.__paths = dict(dist_paths[1]) else: self.__dist, self.__paths = self.__graph.get_dist_paths( origins=self.__nodes, destinations=self.__nodes) # self.__s_d = {} ''' ''' def steiner_tree(self, consider_terminals=False): # set_c = tuple(sorted(self.__terminals[1:])) t_tuples = [tuple([t]) for t in set_c] # for j in self.__nodes: self.__s_d[j] = {} for t in t_tuples: try: self.__s_d[j][t] = [ self.__dist[tuple(sorted([j, t[0]]))], t[0], (None, None) ] except KeyError: self.__s_d[j][t] = [sys.maxint, t[0], (None, None)] # for m in range(2, len(set_c)): # sets_d = [tuple(set_d) for set_d in comb(set_c, m)] for set_d in sets_d: # for i in self.__nodes: self.__s_d[i][set_d] = [sys.maxint, None, (None, None)] # sets_e = self.__create_subsets_e(set_d) # for j in self.__nodes: u = sys.maxint best_subsets = None for set_e in sets_e: set_f = tuple(sorted(list(set(set_d) - set(set_e)))) if len(set_f) > 0: s = self.__s_d[j][set_e][0] + self.__s_d[j][set_f][ 0] else: s = self.__s_d[j][set_e][0] if s < u: u = s best_subsets = (set_e, set_f) for i in self.__nodes: try: dist = self.__dist[tuple(sorted([i, j]))] except KeyError: dist = sys.maxint if consider_terminals: if dist + u < self.__s_d[i][set_d][0]: self.__s_d[i][set_d] = [ dist + u, j, best_subsets ] else: if dist + u < self.__s_d[i][set_d][ 0] and j not in self.__terminals[1:]: self.__s_d[i][set_d] = [ dist + u, j, best_subsets ] # sets_e = self.__create_subsets_e(set_c) # cost = sys.maxint if self.__poi not in self.__s_d: self.__s_d[self.__poi] = {set_c: [cost, None, (None, None)]} else: self.__s_d[self.__poi][set_c] = [cost, None, (None, None)] # for j in self.__nodes: u = sys.maxint best_subsets = None for set_e in sets_e: set_f = tuple(sorted(list(set(set_c) - set(set_e)))) if len(set_f) > 0: s = self.__s_d[j][set_e][0] + self.__s_d[j][set_f][0] else: s = self.__s_d[j][set_e][0] if s < u: u = s best_subsets = (set_e, set_f) try: dist = self.__dist[tuple(sorted([self.__poi, j]))] except KeyError: dist = sys.maxint if consider_terminals: if dist + u < cost: cost = dist + u self.__s_d[self.__poi][set_c] = [cost, j, best_subsets] else: if dist + u < cost and j not in self.__terminals[1:]: cost = dist + u self.__s_d[self.__poi][set_c] = [cost, j, best_subsets] # Reconstruct the Steiner by backtracking steiner_tree = self.__build_steiner_tree_bactracking(self.__poi, set_c) if self.__contract_graph: self.__decontract_steiner_tree(steiner_tree) return steiner_tree ''' ''' @staticmethod def __create_subsets_e(set_): sets_e = [tuple([set_[0]])] l_set = len(set_) for x in range(1, l_set - 1): for y in comb(set_[1:], x): t = [set_[0]] t.extend(y) sets_e.append(tuple(t)) return sets_e ''' ''' def __build_steiner_tree_bactracking(self, node, subset): steiner_tree = SuitabilityGraph() next_node = self.__s_d[node][tuple(subset)][1] print(node, self.__s_d[node][tuple(subset)]) # pdb.set_trace() if next_node is not None: steiner_tree.append_path( self.__paths[tuple(sorted([node, next_node]))], self.__graph) (best_e, best_f) = self.__s_d[node][tuple(subset)][2] # pdb.set_trace() steiner_branch_e = SuitabilityGraph() if best_e is not None and best_e != [next_node]: steiner_branch_e = self.__build_steiner_tree_bactracking( next_node, best_e) steiner_branch_f = SuitabilityGraph() if best_f is not None and best_f != [next_node] and len(best_f) > 0: steiner_branch_f = self.__build_steiner_tree_bactracking( next_node, best_f) steiner_tree.append_graph(steiner_branch_e) steiner_tree.append_graph(steiner_branch_f) return steiner_tree ''' ''' def __find_closest_node_to_node_within_region(self, node, region_id): region = self.__graph.contracted_regions[region_id][0] min_dist = sys.maxint closest_node = None distances, paths = dijkstra(self.__original_graph, node, region.keys()) for n in region: if distances[n] < min_dist: closest_node = n min_dist = distances[n] return closest_node, paths[closest_node] ''' ''' def __decontract_steiner_tree(self, steiner_tree): regions = [] paths = [] trees = [] for n in steiner_tree: try: if n in self.__graph.contracted_regions: dropped_edges = steiner_tree[n][2]['dropped_edges'] neighbors = steiner_tree[n][1].keys() new_terminals = set() for b in neighbors: t = dropped_edges[b] new_terminals.add(t) if b in self.__graph.auxiliary_nodes: bb = [cc for cc in steiner_tree[b][1] if cc != n][0] paths.append([t, bb]) else: paths.append([t, b]) if len(new_terminals) > 1: region = self.__graph.contracted_regions[n][0] d = DreyfusIMR(region, list(new_terminals), contract_graph=False) st = d.steiner_tree() trees.append(st) regions.append(n) except KeyError: pass for r in regions: del steiner_tree[r] for p in paths: steiner_tree.append_path(p, self.__original_graph) for st in trees: steiner_tree.append_graph(st)
class Spiders: def __init__(self, graph, terminals, poi, contract_graph=True, contracted_graph=None, within_convex_hull=False, dist_paths_suitable_nodes=None): # Check whether graph is node-weighted. if not graph.is_node_weighted(): raise (ValueError, "Spiders algorithm only works with node-weighted graphs.") # Store class variables for future references. self.__original_graph = graph self.__terminals = terminals self.__poi = poi self.__contract_graph = contract_graph terminals_poi = list(terminals) terminals_poi.append(poi) generator = SuitableNodeWeightGenerator() # Contracted graph... if contract_graph: if contracted_graph is not None: self.__graph = contracted_graph.copy() else: self.__graph = SuitabilityGraph() self.__graph.append_graph(graph) self.__graph.contract_suitable_regions( generator, excluded_nodes=terminals_poi) else: self.__graph = SuitabilityGraph() self.__graph.append_graph(graph) # Copy distances and paths dictionary since it will be changed. dist_paths = None if dist_paths_suitable_nodes is not None: dist_paths = dict(dist_paths_suitable_nodes) for e in terminals_poi: dist_paths[e] = dijkstra(self.__graph, e) # Get the suitable nodes. if within_convex_hull: self.__suitable_nodes = self.__graph.get_suitable_nodes_within_convex_set( terminals_poi, generator, dist_paths) else: self.__suitable_nodes = self.__graph.get_suitable_nodes( generator, excluded_nodes=terminals_poi) # POI will be included in this list. self.__suitable_nodes.append(poi) # Calculate distances and paths between nodes. IMPORTANT: Only suitable nodes are regarded as start nodes. self.__dist_paths_node_node = {} # self.__dist_paths_node_within_region_node = {} if dist_paths is not None: self.__dist_paths_node_node = { n: dist_paths[n] for n in self.__suitable_nodes } # for n in self.__suitable_nodes: # self.__dist_paths_node_node[n] = dist_paths[n] # if n in self.__graph.contracted_regions: # region = self.__graph.contracted_regions[n][0] # for w in region: # self.__dist_paths_node_within_region_node[w] = \ # dijkstra(self.__original_graph, w, consider_node_weights=False) else: self.__dist_paths_node_node = \ {n: dijkstra(self.__graph, n) for n in self.__suitable_nodes} # for n in self.__suitable_nodes: # self.__dist_paths_node_node[n] = dijkstra(self.__graph, n, consider_node_weights=False) # if n in self.__graph.contracted_regions: # region = self.__graph.contracted_regions[n][0] # for w in region: # self.__dist_paths_node_within_region_node[w] = \ # dijkstra(self.__original_graph, w, consider_node_weights=False) for e in terminals_poi: if e not in self.__dist_paths_node_node: self.__dist_paths_node_node[e] = dijkstra(self.__graph, e) # For every terminal create a subtree which has such terminal as the only node. Each subtree is digraph. self.__subtrees = {} for s in terminals: subtree = SuitabilityGraph() subtree[s] = (self.__graph[s][0], {}) self.__subtrees[s] = subtree # IMPORTANT: This method calculates the distances and paths from suitable nodes only. self.__calculate_distances_paths_to_subtrees() ''' Calculate the distances and paths from every suitable node to every subtree. It also calculates the sorted list of subtrees by distance. It is called every time merging operations are performed. ''' def __calculate_distances_paths_to_subtrees(self): # Distances and paths node-to-subtree self.__dist_paths_node_subtree = {} self.__ordered_subtrees = {} # For every suitable node, get the tuple (distances, paths) to every subtree and store it as the value for # the entry -> node: (distances, paths) in a dictionary which is a class variable. # distances: dictionary with entries -> subtree: distance_to_nearest_node_in_the_subtree # paths: dictionary with entries -> subtree: path_to_nearest_node_in_the_subtree # path: list of nodes ordered from node to nearest node in the subtree for n in self.__suitable_nodes: for s in self.__subtrees: dist, nearest = self.__calculate_distance_node_subtree(n, s) if n not in self.__dist_paths_node_subtree: self.__dist_paths_node_subtree[n] = ({}, {}) self.__dist_paths_node_subtree[n][0][s] = dist try: self.__dist_paths_node_subtree[n][1][ s] = self.__dist_paths_node_node[n][1][nearest] except KeyError: self.__dist_paths_node_subtree[n][1][s] = [] # Once the distances dictionary is ready for the current node, a list of tuples (subtree, distance) ordered # by distance is calculated and stored as a value for the entry -> node: sorted_list_of_subtrees_by_distance # The entry is stored in a dictionary which is a class variable. ordering = sorted(self.__dist_paths_node_subtree[n][0].iteritems(), key=operator.itemgetter(1)) self.__ordered_subtrees[n] = ordering ''' Calculate the distance from node to subtree. It also returns the nearest node within the subtree that is a suitable node, or the terminal if it is the only node within the subtree. ''' def __calculate_distance_node_subtree(self, node, subtree): min_dist = sys.maxint nearest_n = None # Get the distance from the node to every node in the subtree that is a suitable node (or is the only node that # is a terminal) and return the nearest. for n in self.__subtrees[subtree]: if (len(self.__subtrees[subtree].keys()) == 1 and n in self.__terminals) or n in self.__suitable_nodes: try: d = self.__dist_paths_node_node[node][0][n] except KeyError: d = sys.maxint if d < min_dist: min_dist = self.__dist_paths_node_node[node][0][n] nearest_n = n return min_dist, nearest_n ''' Evaluate a ratio similar to Klein and Ravi paper. However, when a node is within a subtree, i.e. its distance to this subtree is zero, the number of subtrees, which acts as divisor in the ratio, is also affected. A fraction of this subtree is taken into account instead of it as a whole. In this way, I prevent this node having advantage over the others. ''' def __evaluate_ratio(self, node, i): dist_subtrees = 0 cont = 0 for j in range(i + 1): if self.__ordered_subtrees[node][j][1] != 0: dist_subtrees += self.__ordered_subtrees[node][j][1] else: cont += 1 return float(self.__graph[node][0] + dist_subtrees) / (i + 1 - cont / 2.) ''' Clever way to get the minimum ratio for a node. ''' def __get_quotient_cost(self, node): i = 1 k = len(self.__subtrees) ratio = self.__evaluate_ratio(node, i) while i < k - 1: if self.__ordered_subtrees[node][i + 1][1] < ratio: i += 1 ratio = self.__evaluate_ratio(node, i) else: break return ratio, i ''' Get the best node amongst the suitable nodes, i.e. the node with the minimum ratio. ''' def __get_best_node(self, min_threshold=None): best_avg = sys.maxint best_node = None best_r = None best_checked = False # Useful to prevent unnecessary double-check whether a node already belongs to a subtree best_found = False # Indicates whether the best node, so far, has been found within a subtree # For every suitable node, get the minimum value of the heuristic function. The goal is to obtain the node # with the minimum minimum value. If the nodes that are being compared have the same minimum value, other # heuristics are taken into account. See V. J. Rayward-Smith paper. for n in self.__suitable_nodes: min_by_node, r = self.__get_quotient_cost(n) if min_threshold < min_by_node < best_avg: best_avg = min_by_node best_node = n best_r = r best_checked = False # A new best node is likely not having been checked yet best_found = False # A new best node is likely not having been found within a subtree elif min_by_node == best_avg: # In case two nodes have the same minimum value dist_0 = self.__dist_paths_node_node[best_node][0][self.__poi] dist_1 = self.__dist_paths_node_node[n][0][self.__poi] # a) The best is the one with minimum distance to the POI. if dist_1 < dist_0: best_node = n best_r = r best_checked = False # A new best node is likely not having been checked yet best_found = False # A new best node is likely not having been found within a subtree continue # b) The best is the one that is not within a subtree. if not best_checked or (best_checked and best_found): if not best_checked: # To prevent checking again # Check if the best node is within any subtree. for i in range(best_r + 1): subtree = self.__ordered_subtrees[best_node][i][0] if best_node in self.__subtrees[subtree]: best_found = True break best_checked = True # Check id the current node is within any subtree. cur_found = False for i in range(r + 1): subtree = self.__ordered_subtrees[n][i][0] if n in self.__subtrees[subtree]: cur_found = True break # This corresponds to the (i) heuristic proposed in the paper when two nodes have the same value. # Only if the best node is within a subtree whereas the current node not, the latter becomes the new # best node. if best_found and not cur_found: best_node = n best_r = r best_checked = True # This new best node has been already checked best_found = False # This new best node is not within a subtree return best_node, best_avg ''' Merge the first two nearest subtrees of the node and the node as well. ''' def __merge_subtrees_and_node(self, node): # Get the first two nearest subtrees to the node. subtree_0 = self.__ordered_subtrees[node][0][0] subtree_1 = self.__ordered_subtrees[node][1][0] # Generate a random id for the new subtree. new_subtree_id = "s_" + utils.id_generator() # Init the new subtree with the nodes of the first nearest subtree. new_subtree = self.__subtrees[subtree_0].copy() # Append the nodes of the second nearest subtree to the new subtree. new_subtree.append_graph(self.__subtrees[subtree_1]) # Get the paths from the node to the first two nearest subtrees. path_0 = self.__dist_paths_node_subtree[node][1][subtree_0] path_1 = self.__dist_paths_node_subtree[node][1][subtree_1] # Append these paths to the new subtree. for path in [path_0, path_1]: new_subtree.append_path(path, self.__graph) # Remove the first two nearest subtrees from the list of subtrees. del self.__subtrees[subtree_0] del self.__subtrees[subtree_1] # Add the new subtree to the list of subtrees. self.__subtrees[new_subtree_id] = new_subtree # Calculate distances and paths for the new list of subtrees. self.__calculate_distances_paths_to_subtrees() ''' Merge a node with a subtree and append the subtree to the list of subtrees. ''' def __merge_subtree_and_node(self, subtree, node): # Generate a random id for the new subtree. new_subtree_id = "s_" + utils.id_generator() # Init the new subtree with the nodes of the subtree. new_subtree = self.__subtrees[subtree].copy() # Get the path from the node to the subtree. path = self.__dist_paths_node_subtree[node][1][subtree] # Append this path to the new subtree. new_subtree.append_path(path, self.__graph) # Remove the subtree from the list of subtrees. del self.__subtrees[subtree] # Add the new subtree to the list of subtrees. self.__subtrees[new_subtree_id] = new_subtree # Calculate distances and paths for the new list of subtrees. self.__calculate_distances_paths_to_subtrees() def __merge_subtrees_through_poi(self, subtree_with_poi, subtree): # Generate a random id for the new subtree. new_subtree_id = "s_" + utils.id_generator() # Init the new subtree with the nodes of the subtree with the POI. new_subtree = self.__subtrees[subtree_with_poi].copy() # Append the nodes of the second subtree to the new subtree. new_subtree.append_graph(self.__subtrees[subtree]) # Find the path through the POI between these two subtrees. min_dist = sys.maxint nearest_n = None if len(self.__subtrees[subtree].keys()) == 1: nearest_n = self.__subtrees[subtree].keys()[0] else: for n in self.__subtrees[subtree]: if n in self.__suitable_nodes: if self.__dist_paths_node_node[n][0][ self.__poi] < min_dist: min_dist = self.__dist_paths_node_node[n][0][ self.__poi] nearest_n = n if nearest_n is None: raise ( RuntimeError, "__merge_subtrees_through_poi: couldn't find the nearest point within the subtree." ) path = self.__dist_paths_node_node[self.__poi][1][nearest_n] # Append this path to the new subtree. new_subtree.append_path(path, self.__graph) # Remove the two subtrees from the list of subtrees. del self.__subtrees[subtree_with_poi] del self.__subtrees[subtree] # Add the new subtree to the list of subtrees. self.__subtrees[new_subtree_id] = new_subtree # Calculate distances and paths for the new list of subtrees. self.__calculate_distances_paths_to_subtrees() return path def __find_closest_node_to_node_within_region(self, node, region_id): region = self.__graph.contracted_regions[region_id][0] min_dist = sys.maxint closest_node = None distances, paths = dijkstra(self.__original_graph, node, region.keys()) for n in region: if distances[n] < min_dist: closest_node = n min_dist = distances[n] return closest_node, paths[closest_node] def __decontract_steiner_tree(self, steiner_tree): regions = [] paths = [] trees = [] for r in steiner_tree: if r in self.__graph.contracted_regions: regions.append(r) neighbors = steiner_tree[r][1].keys() new_terminals = [] for n in neighbors: closest_node_to_n, path = self.__find_closest_node_to_node_within_region( n, r) paths.append(path) new_terminals.append(closest_node_to_n) del steiner_tree[n][1][r] if len(new_terminals) > 1: region = self.__graph.contracted_regions[r][0] s = Spiders(region, new_terminals[1:], new_terminals[0], contract_graph=False) st, _ = s.steiner_tree() trees.append(st) for r in regions: del steiner_tree[r] for p in paths: steiner_tree.append_path(p, self.__original_graph) for st in trees: steiner_tree.append_graph(st) # def __find_closest_node_to_poi_within_region(self, region_id): # region = self.__graph.contracted_regions[region_id][0] # min_dist = sys.maxint # closest_node = None # distances, _ = dijkstra(self.__original_graph, self.__poi, region.keys(), consider_node_weights=False) # for n in region: # if distances[n] < min_dist: # closest_node = n # min_dist = distances[n] # return closest_node # # def __decontract_steiner_tree(self, steiner_tree): # regions = [] # paths = [] # for r in steiner_tree: # if r in self.__graph.contracted_regions: # regions.append(r) # closest_node = self.__find_closest_node_to_poi_within_region(r) # neighbors = steiner_tree[r][1].keys() # _, paths_closest = dijkstra(self.__original_graph, closest_node, neighbors, consider_node_weights=False) # for v in steiner_tree[r][1]: # paths.append(paths_closest[v]) # del steiner_tree[v][1][r] # for r in regions: # del steiner_tree[r] # for p in paths: # steiner_tree.append_from_path(p, self.__original_graph) def __prune_steiner_tree(self, steiner_tree): while True: keys_to_prune = [] for n in steiner_tree: if n not in self.__terminals and n != self.__poi: neighbours = steiner_tree[n][1].keys() if len(neighbours) == 1: neighbour = neighbours[0] del steiner_tree[neighbour][1][n] keys_to_prune.append(n) if len(keys_to_prune) == 0: break for n in keys_to_prune: del steiner_tree[n] ''' The minimum Steiner tree will be the last tree after merging all the subtrees. ''' def steiner_tree(self): # ngh = NetworkXGraphHelper(self.__graph) meeting_nodes = [] while len(self.__subtrees) > 1: # Get the best node according to the heuristic function. best_node, best_avg = self.__get_best_node() # Get the two subtrees that might be merged. subtree_0 = self.__ordered_subtrees[best_node][0][0] subtree_1 = self.__ordered_subtrees[best_node][1][0] # After the best node has been found, there are four possibilities: # a) If the two subtrees are the last ones and any of them contains the POI, I do not need an intermediary # node since there is not chance this meeting node will be used by other subtrees. Thus, I merge the # subtrees through the POI. if len(self.__subtrees) == 2: if self.__poi in self.__subtrees[ subtree_0] or self.__poi in self.__subtrees[subtree_1]: if self.__poi in self.__subtrees[subtree_0]: self.__merge_subtrees_through_poi(subtree_0, subtree_1) else: self.__merge_subtrees_through_poi(subtree_1, subtree_0) # meeting_nodes.append((self.__poi, self.__terminals)) continue # b) If one of the subtrees that might be merged contains the POI and there are more than two subtrees, # I avoid merging this subtree and search for another couple. while (self.__poi in self.__subtrees[subtree_0] or self.__poi in self.__subtrees[subtree_1]) \ and best_node != self.__poi: best_node, best_avg = self.__get_best_node(best_avg) if best_node is None: best_node, best_avg = self.__get_best_node() subtree_0 = self.__ordered_subtrees[best_node][0][0] subtree_1 = self.__ordered_subtrees[best_node][1][0] break subtree_0 = self.__ordered_subtrees[best_node][0][0] subtree_1 = self.__ordered_subtrees[best_node][1][0] # c) If one of them is closer to the POI than to the best node, I merge this subtree with the POI instead of # using the best node as an intermediary meeting node. dist_0 = sys.maxint dist_1 = sys.maxint # If the POI is within any of the subtrees, it does not make sense to check for convenience. if self.__poi not in self.__subtrees[subtree_0]: dist_0 = self.__dist_paths_node_subtree[ self.__poi][0][subtree_0] if self.__poi not in self.__subtrees[subtree_1]: dist_1 = self.__dist_paths_node_subtree[ self.__poi][0][subtree_1] # If one of them is closer to the POI... if dist_0 < self.__ordered_subtrees[best_node][0][ 1] or dist_1 < self.__ordered_subtrees[best_node][1][1]: # Merge the POI with the subtree. if dist_0 < self.__ordered_subtrees[best_node][0][1]: # meeting_nodes.append((self.__poi, [t for t in self.__subtrees[subtree_0] if t in self.__terminals])) self.__merge_subtree_and_node(subtree_0, self.__poi) if dist_1 < self.__ordered_subtrees[best_node][1][1]: # meeting_nodes.append((self.__poi, [t for t in self.__subtrees[subtree_1] if t in self.__terminals])) self.__merge_subtree_and_node(subtree_1, self.__poi) continue # d) The "best node" is in fact the best node. # nodes = [t for t in self.__subtrees[subtree_0] if t in self.__terminals] # nodes.extend([t for t in self.__subtrees[subtree_1] if t in self.__terminals]) # meeting_nodes.append((best_node, nodes)) self.__merge_subtrees_and_node(best_node) # legend = [str(counter + 1) + ". imp->" + str(mn[0]) + " for: " + str(mn[1]) for counter, mn in # enumerate(meeting_nodes)] # # ngh.draw_graph(nodes_2=[self.__poi], nodes_1=self.__terminals, # subgraphs_2=[subtree for _, subtree in self.__subtrees.iteritems()], # node_weight_generator=SuitableNodeWeightGenerator(), # legend=legend, # node_labels=True) # At the end, if the POI is not part of the Steiner tree, merge the POI with the Steiner tree because the POI is # a compulsory meeting node. if self.__poi not in self.__subtrees[self.__subtrees.keys()[0]]: # meeting_nodes.append( # (self.__poi, [t for t in self.__subtrees[self.__subtrees.keys()[0]] if t in self.__terminals])) self.__merge_subtree_and_node(self.__subtrees.keys()[0], self.__poi) steiner_tree = self.__subtrees[self.__subtrees.keys()[0]] self.__prune_steiner_tree(steiner_tree) if self.__contract_graph: self.__decontract_steiner_tree(steiner_tree) return steiner_tree, meeting_nodes
class DreyfusIMRV2: def __init__(self, graph, terminals, contract_graph=True, contracted_graph=None, within_convex_hull=False, dist_paths=None, nodes=None, use_medoid=False): # Check whether graph is node-weighted. if not graph.is_node_weighted(): raise (ValueError, "Dreyfus with IMRs algorithm only works with node-weighted graphs.") # Extract POI from the terminals list. if len(terminals) > 0: self.__poi = terminals[0] else: return # Set object variables. generator = SuitableNodeWeightGenerator() self.__original_graph = graph self.__terminals = terminals self.__contract_graph = contract_graph self.__use_medoid = use_medoid # Contracted graph... if contract_graph: if contracted_graph is not None: self.__graph = contracted_graph.copy() else: self.__graph = SuitabilityGraph() self.__graph.append_graph(graph) self.__graph.contract_suitable_regions(generator, excluded_nodes=terminals, get_centroid_medoid=use_medoid) else: self.__graph = SuitabilityGraph() self.__graph.append_graph(graph) # if nodes is not None: self.__nodes = list(nodes) else: if within_convex_hull: pass # self.__nodes = self.__graph.get_suitable_nodes_within_convex_set(terminals, generator, dist_paths) else: self.__nodes = self.__graph.get_suitable_nodes(generator, excluded_nodes=terminals) # for t in terminals: self.__nodes.append(t) # print(self.__nodes) # self.__dist = {} self.__paths = {} if dist_paths is not None: self.__dist = dict(dist_paths[0]) self.__paths = dict(dist_paths[1]) else: self.__dist, self.__paths = self.__graph.get_dist_paths(origins=self.__nodes, destinations=self.__nodes) # self.__s_d = {} ''' ''' def steiner_tree(self, consider_terminals=False): # set_c = tuple(sorted(self.__terminals[1:])) t_tuples = [tuple([t]) for t in set_c] # for j in self.__nodes: self.__s_d[j] = {} for t in t_tuples: pair_nodes = tuple(sorted([j, t[0]])) try: entry_node = None if j in self.__graph.contracted_regions: path = self.__paths[pair_nodes] if len(path) > 1: if path.index(j) == len(path) - 1: entry_node = path[len(path) - 2] else: entry_node = path[1] else: # pdb.set_trace() pass self.__s_d[j][t] = [self.__dist[pair_nodes], t[0], (None, None), entry_node] except KeyError: self.__s_d[j][t] = [sys.maxint, t[0], (None, None), None] # for m in range(2, len(set_c)): # sets_d = [tuple(set_d) for set_d in comb(set_c, m)] for set_d in sets_d: # for i in self.__nodes: self.__s_d[i][set_d] = [sys.maxint, None, (None, None)] # sets_e = self.__create_subsets_e(set_d) # for j in self.__nodes: u = sys.maxint best_subsets = None for set_e in sets_e: set_f = tuple(sorted(list(set(set_d) - set(set_e)))) if len(set_f) > 0: s = self.__s_d[j][set_e][0] + self.__s_d[j][set_f][0] else: s = self.__s_d[j][set_e][0] # if s < u: u = s best_subsets = (set_e, set_f) for i in self.__nodes: pair_nodes = tuple(sorted([i, j])) # d_n1_n3 = d_n2_n3 = 0 d1 = d2 = d3 = 0 try: dist = self.__dist[pair_nodes] if j in self.__graph.contracted_regions and best_subsets is not None: e_n_1 = self.__s_d[j][best_subsets[0]][3] e_n_2 = self.__s_d[j][best_subsets[1]][3] path = self.__paths[pair_nodes] dropped_edges = self.__graph[j][2]['dropped_edges'] dist_paths = self.__graph[j][2]['dist_paths'] n1 = dropped_edges[e_n_1] n2 = dropped_edges[e_n_2] if len(path) > 1: if path.index(j) == len(path) - 1: e_n_3 = path[len(path) - 2] else: e_n_3 = path[1] n3 = dropped_edges[e_n_3] dr = DreyfusIMR(self.__graph.contracted_regions[j][0], terminals=[n1, n2, n3], contract_graph=False) st = dr.steiner_tree() d1, _ = st.compute_total_weights() # # Use the medoid to compute the internal distance. # if self.__use_medoid: # medoid = self.__graph.contracted_regions[j][4] # d1 = dist_paths[0][tuple(sorted([n1, medoid]))] # d2 = dist_paths[0][tuple(sorted([n2, medoid]))] # d3 = dist_paths[0][tuple(sorted([n3, medoid]))] # else: # d1 = dist_paths[0][tuple(sorted([n1, n3]))] # d2 = dist_paths[0][tuple(sorted([n2, n3]))] # elif j == i: # d1 = dist_paths[0][tuple(sorted([n1, n2]))] except KeyError: dist = sys.maxint cost = dist + u + d1 + d2 + d3 if consider_terminals: if cost < self.__s_d[i][set_d][0]: self.__s_d[i][set_d] = [cost, j, best_subsets] #####################IMPORTANT: NOT COMPLETELY IMPLEMENTED!!!##################### else: if cost < self.__s_d[i][set_d][0] and j not in self.__terminals: entry_node = None if i in self.__graph.contracted_regions: try: path = self.__paths[pair_nodes] if path.index(i) == len(path) - 1: entry_node = path[len(path) - 2] else: entry_node = path[1] except KeyError: pass self.__s_d[i][set_d] = [cost, j, best_subsets, entry_node] # sets_e = self.__create_subsets_e(set_c) # if self.__poi not in self.__s_d: self.__s_d[self.__poi] = {set_c: [sys.maxint, None, (None, None), None]} else: self.__s_d[self.__poi][set_c] = [sys.maxint, None, (None, None), None] # for j in self.__nodes: u = sys.maxint best_subsets = None for set_e in sets_e: set_f = tuple(sorted(list(set(set_c) - set(set_e)))) if len(set_f) > 0: s = self.__s_d[j][set_e][0] + self.__s_d[j][set_f][0] else: s = self.__s_d[j][set_e][0] if s < u: u = s best_subsets = (set_e, set_f) pair_nodes = tuple(sorted([self.__poi, j])) # d_n1_n3 = d_n2_n3 = 0 d1 = d2 = d3 = 0 try: dist = self.__dist[pair_nodes] if j in self.__graph.contracted_regions and best_subsets is not None: e_n_1 = self.__s_d[j][best_subsets[0]][3] e_n_2 = self.__s_d[j][best_subsets[1]][3] path = self.__paths[pair_nodes] if len(path) > 1: if path.index(j) == len(path) - 1: e_n_3 = path[len(path) - 2] else: e_n_3 = path[1] dropped_edges = self.__graph[j][2]['dropped_edges'] dist_paths = self.__graph[j][2]['dist_paths'] n1 = dropped_edges[e_n_1] n2 = dropped_edges[e_n_2] n3 = dropped_edges[e_n_3] dr = DreyfusIMR(self.__graph.contracted_regions[j][0], terminals=[n1, n2, n3], contract_graph=False) st = dr.steiner_tree() d1, _ = st.compute_total_weights() # Use the medoid to compute the internal distance. # if self.__use_medoid: # medoid = self.__graph.contracted_regions[j][4] # d1 = dist_paths[0][tuple(sorted([n1, medoid]))] # d2 = dist_paths[0][tuple(sorted([n2, medoid]))] # d3 = dist_paths[0][tuple(sorted([n3, medoid]))] # else: # d1 = dist_paths[0][tuple(sorted([n1, n3]))] # d2 = dist_paths[0][tuple(sorted([n2, n3]))] except KeyError: dist = sys.maxint cost = dist + u + d1 + d2 + d3 if consider_terminals: if cost < self.__s_d[self.__poi][set_c][0]: self.__s_d[self.__poi][set_c] = [cost, j, best_subsets] #####################IMPORTANT: NOT COMPLETELY IMPLEMENTED!!!##################### else: if cost < self.__s_d[self.__poi][set_c][0] and j not in self.__terminals: self.__s_d[self.__poi][set_c] = [cost, j, best_subsets, None] # Reconstruct the Steiner by backtracking steiner_tree = self.__build_steiner_tree_bactracking(self.__poi, set_c) if self.__contract_graph: self.__decontract_steiner_tree(steiner_tree) return steiner_tree ''' ''' @staticmethod def __create_subsets_e(set_): sets_e = [tuple([set_[0]])] l_set = len(set_) for x in range(1, l_set - 1): for y in comb(set_[1:], x): t = [set_[0]] t.extend(y) sets_e.append(tuple(t)) return sets_e ''' ''' def __build_steiner_tree_bactracking(self, node, subset): steiner_tree = SuitabilityGraph() next_node = self.__s_d[node][tuple(subset)][1] if self.__contract_graph: print(node, self.__s_d[node][tuple(subset)]) # pdb.set_trace() if next_node is not None: steiner_tree.append_path(self.__paths[tuple(sorted([node, next_node]))], self.__graph) (best_e, best_f) = self.__s_d[node][tuple(subset)][2] # pdb.set_trace() steiner_branch_e = SuitabilityGraph() if best_e is not None and best_e != [next_node]: steiner_branch_e = self.__build_steiner_tree_bactracking(next_node, best_e) steiner_branch_f = SuitabilityGraph() if best_f is not None and best_f != [next_node] and len(best_f) > 0: steiner_branch_f = self.__build_steiner_tree_bactracking(next_node, best_f) steiner_tree.append_graph(steiner_branch_e) steiner_tree.append_graph(steiner_branch_f) return steiner_tree ''' ''' def __find_closest_node_to_node_within_region(self, node, region_id): region = self.__graph.contracted_regions[region_id][0] min_dist = sys.maxint closest_node = None distances, paths = dijkstra(self.__original_graph, node, region.keys()) for n in region: if distances[n] < min_dist: closest_node = n min_dist = distances[n] return closest_node, paths[closest_node] ''' ''' def __decontract_steiner_tree(self, steiner_tree): to_drop = [] paths = [] trees = [] # pdb.set_trace() for n in steiner_tree: try: if n in self.__graph.contracted_regions: dropped_edges = steiner_tree[n][2]['dropped_edges'] neighbors = steiner_tree[n][1].keys() new_terminals = set() for b in neighbors: t = dropped_edges[b] new_terminals.add(t) if b in self.__graph.auxiliary_nodes: bb = [cc for cc in steiner_tree[b][1] if cc != n][0] paths.append([t, bb]) del steiner_tree[bb][1][b] to_drop.append(b) else: paths.append([t, b]) del steiner_tree[b][1][n] if len(new_terminals) > 1: region = self.__graph.contracted_regions[n][0] d = DreyfusIMRV2(region, list(new_terminals), contract_graph=False) st = d.steiner_tree() trees.append(st) to_drop.append(n) except KeyError: pass # pdb.set_trace() for r in to_drop: del steiner_tree[r] for p in paths: steiner_tree.append_path(p, self.__original_graph) for st in trees: steiner_tree.append_graph(st) # pdb.set_trace() # for r in steiner_tree: # if r in self.__graph.contracted_regions: # regions.append(r) # neighbors = steiner_tree[r][1].keys() # new_terminals = set() # for n in neighbors: # closest_node_to_n, path = self.__find_closest_node_to_node_within_region(n, r) # paths.append(path) # new_terminals.add(closest_node_to_n) # del steiner_tree[n][1][r] # if len(new_terminals) > 1: # region = self.__graph.contracted_regions[r][0] # d = DreyfusIMR(region, list(new_terminals), contract_graph=False) # st = d.steiner_tree() # trees.append(st) # for r in regions: # del steiner_tree[r] # for p in paths: # steiner_tree.append_from_path(p, self.__original_graph) # for st in trees: # steiner_tree.append_from_graph(st) def enclosing_region(self): enclosing = SuitabilityGraph() paths = [] paths.append(self.__paths[tuple(sorted([323287670, 2392803740]))]) paths.append(self.__paths[tuple(sorted([2392803740, 127578100]))]) paths.append(self.__paths[tuple(sorted([127578100, 3109398450]))]) paths.append(self.__paths[tuple(sorted([3109398450, 342909685]))]) paths.append(self.__paths[tuple(sorted([342909685, 323287670]))]) for path in paths: enclosing.append_path(path, self.__original_graph) return enclosing