Marina Salmen
Marina Salmen

Reputation: 85

How to get the shortest path between two nodes in a set of routes using NetworkX?

I am using NetworkX graphs to represent a set of routes, as seen in the image below.

Set of Routes

I know that NetworkX provides shortest_path() to find the shortest path between two nodes in a graph, but I want to find the shortest path considering the set of routes I have available. There's also weight associated with changing from one route to another.

Right now I'm using different graphs to represent each route, but I'm not sure that's the best approach.

For example: the shortest path between nodes 3 and 2 could be using only one route [3, 5, 2] or using two routes [3, 1] and [1, 2] with a cost between them.

Is it possible to achieve this using NetworkX shortest_path?

Upvotes: 1

Views: 1826

Answers (2)

Marina Salmen
Marina Salmen

Reputation: 85

That helped. =) I actually created a generic function to create an extended graph, as the number of routes and the routes itself can be different. Not sure if it's written in the best way but maybe it can help anyone:

def create_extended_graph(graph, route_set, transfer_weight):

    extended_graph = nx.Graph()
    indexes = dict([(node, 0) for node in graph.nodes()])

    for route in route_set:
        for node in route:
            current_node = str(node) + '-' + str(indexes[node])
            current_node_index = node
            extended_graph.add_node(current_node, original_node=node, index=indexes[node], pos=graph.nodes()[node]['pos'])
            if route.index(node) > 0:
                tup = tuple(sorted((previous_node_index, current_node_index)))
                extended_graph.add_edge(current_node, previous_node, weight=nx.get_edge_attributes(graph, 'weight')[tup])
            indexes[node] += 1
            previous_node = current_node
            previous_node_index = current_node_index

    for node in graph.nodes():
        extended_nodes = get_list_nodes_from_att(extended_graph, 'original_node', node)

        list_combinations = combinations(extended_nodes, 2)
        for comb in list_combinations:
            extended_graph.add_edge(comb[0], comb[1], weight=transfer_weight)

    return extended_graph

Upvotes: 0

Joel
Joel

Reputation: 23887

Following your idea of having multiple graphs, I'm going to create a large graph consisting of copies of each of the graphs, but also including edges between the corresponding nodes of the graphs you have. So for each edge color, there is a graph with all of those edges, and for each node in the original graph there are edges between all of its copies, with some cost associated. Now we'll look for paths through this bigger network. It's not perfect in the sense that the code is a bit unclean, but it'll work.

import networkx as nx

nodes = [0,1,2,3,4, 5, 10, 11]
rednodes = ['r{}'.format(node) for node in nodes]   #['r0', 'r1', ...]
rededges = [('r0', 'r1'), ('r1', 'r4'), ('r4', 'r3'), ('r3', 'r5'), ('r5', 'r2')]
bluenodes = ['b{}'.format(node) for node in nodes]  
blueedges = [('b1', 'b2')]
orangenodes = ['o{}'.format(node) for node in nodes]
orangeedges = [('o1', 'o3'), ('o3', 'o11'), ('o11', 'o10')]

G = nx.Graph()
G.add_nodes_from(rednodes+bluenodes+orangenodes)

G.add_edges_from(rededges + blueedges + orangeedges, weight = 1)

#here we add edges between the copies of each node
rb_edges = [('r{}'.format(node), 'b{}'.format(node)) for node in nodes] 
ro_edges = [('r{}'.format(node), 'o{}'.format(node)) for node in nodes]
bo_edges = [('b{}'.format(node), 'o{}'.format(node)) for node in nodes]

G.add_edges_from(rb_edges+ro_edges+bo_edges, weight = 0.2)

#This next step is a bit of a hack.
#we want a short path from 0 to 11, but I can't be sure which of the colors I should 
#start in.  So I create a new 0 and 11 node, which connect to its copies with 0 cost.

temporary_edges = [(0, '{}0'.format(c)) for c in ['r', 'b', 'o']] + [(11, '{}11'.format(c)) for c in ['r', 'b', 'o']]
G.add_edges_from(temporary_edges, weight = 0)
best_option = nx.shortest_path(G, 0, 11, weight = 'weight')
G.remove_edges_from(temporary_edges)      #get rid of those edges
G.remove_nodes_from([0, 11])


print(best_option)
> [0, 'r0', 'r1', 'o1', 'o3', 'o11', 11]

Upvotes: 2

Related Questions