def create_rpp_edgelist(g_strongly_connected, graph_full, edge_weight='distance', max_distance=1600): """ Create the edgelist for the RPP algorithm. This includes: - Required state edges (deduped) - Required non-state roads that connect state roads into one connected component with minimum additional distance - Optional roads that connect the nodes of the contracted state edges (these distances are calculated here using first haversine distance to filter the candidate set down using `max_distance` as a threshold, then calculating the true shortest path distance) Args: g_strongly_connected (networkx MultiDiGraph): of strongly connected roads graph_full (networkx Graph): full graph with all granular edges edge_weight (str): edge attribute for distance in `g_strongly_connected` and `graph_full` max_distance (int): max haversine distance used to add candidate optional edges for. Returns: Dataframe of edgelist described above. """ dfrpp_list = [] for e in g_strongly_connected.edges(data=True): if 'granular' not in e[2]: dfrpp_list.append({ 'start_node': e[0], 'end_node': e[1], 'distance_haversine': e[2]['distance'], 'required': 1, 'distance': e[2]['distance'], 'path': e[2]['path'], 'length': e[2]['length'] }) for n1, n2 in [comb for comb in itertools.combinations(g_strongly_connected.nodes(), 2)]: if n1 == n2: continue if not g_strongly_connected.has_edge(n1, n2): distance_haversine = haversine(g_strongly_connected.nodes[n1]['x'], g_strongly_connected.nodes[n1]['y'], g_strongly_connected.nodes[n2]['x'], g_strongly_connected.nodes[n2]['y']) # only add optional edges whose haversine distance is less than `max_distance` if distance_haversine > max_distance: continue dfrpp_list.append({ 'start_node': n1, 'end_node': n2, 'distance_haversine': distance_haversine, 'required': 0, 'distance': spm.dijkstra_path_length(graph_full, n1, n2, edge_weight), 'path': spm.dijkstra_path(graph_full, n1, n2, edge_weight), 'length': nx.dijkstra_path_length(graph_full, n1, n2, 'length') }) # create dataframe dfrpp = pd.DataFrame(dfrpp_list) # create order dfrpp = dfrpp[['start_node', 'end_node', 'distance_haversine', 'distance', 'required', 'path', 'length']] return dfrpp
def get_shortest_paths_distances(graph, pairs, edge_weight_name='distance'): """ Calculate shortest distance between each pair of nodes in a graph Args: graph (networkx graph) pairs (list[2tuple]): List of length 2 tuples containing node pairs to calculate shortest path between edge_weight_name (str): edge attribute used for distance calculation Returns: dict: mapping each pair in `pairs` to the shortest path using `edge_weight_name` between them. """ distances = {} for pair in pairs: distances[pair] = spm.dijkstra_path_length(graph, pair[0], pair[1], weight=edge_weight_name) return distances
def add_augmenting_path_to_graph(g_req, g_full, min_weight_pairs, edge_weight_name='distance'): """ Add the min weight matching edges to the original graph Note the resulting graph could (and likely will) have edges that didn't exist on the original graph. To get the true circuit, we must breakdown these augmented edges into the shortest path through the edges that do exist. This is done with `create_eulerian_circuit`. Args: graph (networkx graph): min_weight_pairs (list[2tuples): output of `dedupe_matching` specifying the odd degree nodes to link together edge_weight_name (str): edge attribute used for distance calculation Returns: networkx graph: `graph` augmented with edges between the odd nodes specified in `min_weight_pairs` """ graph_aug = g_req.copy() # so we don't mess with the original graph for pair in min_weight_pairs: path = spm.dijkstra_path(g_full, pair[0], pair[1], weight=edge_weight_name) turn_length = g_full[path[0]][path[1]][0]['turn_length'] graph_aug.add_edge( pair[0], pair[1], **{ 'distance': spm.dijkstra_path_length(g_full, pair[0], pair[1], weight=edge_weight_name), 'augmented': True, 'turn_length': turn_length, 'length': nx.dijkstra_path_length(g_full, pair[0], pair[1], weight='length') }) return graph_aug
def find_minimum_weight_edges_to_connect_components(dfsp, graph, edge_weight='distance', top=10): """ Given a dataframe of haversine distances between many pairs of nodes, calculate the min weight way to connect all the components in `graph`. At each iteration, the true shortest path (dijkstra_path_length) is calculated for the top `top` closest node pairs using haversine distance. This heuristic improves efficiency at the cost of potentially not finding the true min weight connectors. If this is a concern, increase `top`. Args: dfsp (dataframe): calculated with `shortest_paths_between_components` with haversine distance between all node candidate node pairs graph (networkx graph): used for the true shortest path calculation edge_weight (str): edge attribute used shortest path calculation in `graph` top (int): number of pairs for which shortest path calculation is performed at each iteration Returns: list[tuple3] containing just the connectors needed to connect all the components in `graph`. """ # find shortest edges to add to make one big connected component dfsp = dfsp.copy() new_required_edges = [] while sum(dfsp.index[dfsp['start_comp'] != dfsp['end_comp']]) > 0: # calculate path distance for top 10 shortest dfsp['path_distance'] = None dfsp['path'] = dfsp['path2'] = [[]] * len(dfsp) for i in dfsp.index[dfsp['start_comp'] != dfsp['end_comp']][0:top]: if dfsp.loc[i]['path_distance'] is None: dfsp.loc[i, 'path_distance'] = spm.dijkstra_path_length(graph, dfsp.loc[i, 'start_node'], dfsp.loc[i, 'end_node'], edge_weight) dfsp.at[i, 'path'] = spm.dijkstra_path(graph, dfsp.loc[i, 'start_node'], dfsp.loc[i, 'end_node'], edge_weight) dfsp.at[i, 'length'] = nx.dijkstra_path_length(graph, dfsp.loc[i, 'start_node'], dfsp.loc[i, 'end_node'], 'length') dfsp.sort_values('path_distance', inplace=True) # The first index where start and end comps are different is the index containing the shortest connecting path between start comp and end comp first_index = dfsp.index[dfsp['start_comp'] != dfsp['end_comp']][0] start_comp = dfsp.loc[first_index]['start_comp'] end_comp = dfsp.loc[first_index]['end_comp'] start_node = dfsp.loc[first_index]['start_node'] end_node = dfsp.loc[first_index]['end_node'] path_distance = dfsp.loc[first_index]['path_distance'] path = dfsp.loc[first_index]['path'] length = dfsp.loc[first_index]['length'] dfsp_rev_pair = dfsp[dfsp['start_comp'] == end_comp] dfsp_rev_pair = dfsp_rev_pair[dfsp_rev_pair['end_comp'] == start_comp] for i in dfsp_rev_pair.index[0:top]: if dfsp_rev_pair.loc[i]['path_distance'] is None: dfsp_rev_pair.loc[i, 'path_distance'] = spm.dijkstra_path_length(graph, dfsp_rev_pair.loc[i, 'start_node'], dfsp_rev_pair.loc[i, 'end_node'], edge_weight) dfsp_rev_pair.at[i, 'path'] = spm.dijkstra_path(graph, dfsp_rev_pair.loc[i, 'start_node'], dfsp_rev_pair.loc[i, 'end_node'], edge_weight) dfsp_rev_pair.at[i, 'length'] = nx.dijkstra_path_length(graph, dfsp.loc[i, 'start_node'], dfsp.loc[i, 'end_node'], 'length') dfsp_rev_pair.sort_values('path_distance', inplace=True) first_index_rev = dfsp_rev_pair.index[0] start_node_rev = dfsp_rev_pair.loc[first_index_rev]['start_node'] end_node_rev = dfsp_rev_pair.loc[first_index_rev]['end_node'] path_distance_rev = dfsp_rev_pair.loc[first_index_rev]['path_distance'] path_rev = dfsp_rev_pair.loc[first_index_rev]['path'] length_rev = dfsp_rev_pair.loc[first_index_rev]['length'] dfsp.loc[dfsp['end_comp'] == end_comp, 'end_comp'] = start_comp dfsp.loc[dfsp['start_comp'] == end_comp, 'start_comp'] = start_comp dfsp.sort_values('haversine_distance', inplace=True) new_required_edges.append((start_node, end_node, {'distance': path_distance, 'path': path, 'length': length, 'start_comp': start_comp, 'end_comp': end_comp})) new_required_edges.append((start_node_rev, end_node_rev, {'distance': path_distance_rev, 'length': length_rev, 'path': path_rev, 'start_comp': end_comp, 'end_comp': start_comp})) return new_required_edges