Vertices in my graph are composed of {name, category} where category is one of {red, grn, blu, ylw}. Edges in my graph are weighted and directed. In the visualization, the thick end of the edge represents the destination.
My graph has negative cycles, for example [A-red, B-red, C-blu, X-grn, D-blu, A-red] has a cost of -1, comprised of edge costs [1, 1, -1, -3, 1].
I want to find the shortest path between arbitrary {source, destination} vertices subject to the following conditions:
- Each vertex may only be visited once.
- The count of vertexes visited along the path must be <= m
- The count of categories visited along the path must be <= n
So, for example, assuming a function shortest_path(source, dest, m, n)
then:
shortest_path(('A','red'), ('D','ylw'), 100, 100)
would return [A-red, B-red, C-blu, X-grn, D-ylw] which has a cost of -2. This path visits 4 vertex categories [red, blu, grn, ylw].
shortest_path(('A','red'), ('D','ylw'), 100, 3)
would return [A-red, B-red, C-blu, D-ylw] for a cost of 3. This path visits 3 vertex categories [red, blu, ylw].
Any suggestions here? I believe Dijkstra's algorithm and Bellman-Ford are out here as my graph contains negative cycles. So I think i need a brute force search of edges, integrating the stopping conditions mentioned above.
Python code for initializing my graph is here:
import networkx as nx
class Vertex(object):
def __init__(self, name, category):
self.name = name
self.category = category
def __hash__(self):
return hash((self.name, self.category))
def __eq__(self, other):
if isinstance(other, Vertex):
return other.name == self.name and other.category == self.category
def __str__(self):
return '{}-{}'.format(self.name, self.category)
def __repr__(self):
return str(self)
d = {
Vertex('A','red') : {Vertex('B','red') : {'weight':1}},
Vertex('B','red') : {Vertex('C','blu') : {'weight':1}},
Vertex('C','blu') : {Vertex('D','ylw') : {'weight':1}, Vertex('X','grn') : {'weight':-1}},
Vertex('D','ylw') : {Vertex('A','red') : {'weight':1}},
Vertex('X','grn') : {Vertex('D','ylw') : {'weight':-3}},
}
G = nx.DiGraph(d)
pos = nx.circular_layout(G)
nx.draw_networkx(G,pos, arrows=True, with_labels=True)
labels = nx.get_edge_attributes(G,'weight')
nx.draw_networkx_edge_labels(G,pos, edge_labels=labels);
Update Here is my current traversal algorithm, its essentially a DFS but I record both the parent of each vertex I examine, and the cost of traversing from the parent to the vertex. This allows me to reconstruct a path and compute a total cost for the path. However, I'm not sure its a great solution. My aim is to get the algorithm working well then port to cython for performance (my real graph is huge).
#DFS
def calcd(parents, distances):
d = 0
for v in parents[1:]:
d+=distances[v]
return d
def backtrace(parent, start, end):
path = [end]
while path[-1] != start:
path.append(parent[path[-1]])
path.reverse()
return path
def dfs(graph, source, target):
visited = set()
stack = list()
parent = {}
distance = {}
stack.append(source)
while stack:
u = stack.pop()
uob = graph[u] #edges outbound from u
for v, eparams in uob.items():
parent[v] = u
distance[v] = eparams['weight']
if v == target:
path = backtrace(parent, source, target)
print('Reached Target via {}'.format(path))
print('calcd', calcd(path, distance))
if v not in visited:
visited.add(v)
stack.append(v)
source, target = Vertex('A','red'), Vertex('D','ylw')
dfs(G, source, target)
This prints:
Reached Target via [A-red, B-red, C-blu, D-ylw] calcd 3
Reached Target via [A-red, B-red, C-blu, X-grn, D-ylw] calcd -2