945 lines
29 KiB
Python
945 lines
29 KiB
Python
|
from heapq import heappush, heappop
|
||
|
from itertools import count
|
||
|
|
||
|
import networkx as nx
|
||
|
from networkx.utils import not_implemented_for
|
||
|
from networkx.utils import pairwise
|
||
|
from networkx.utils import empty_generator
|
||
|
from networkx.algorithms.shortest_paths.weighted import _weight_function
|
||
|
|
||
|
__all__ = [
|
||
|
"all_simple_paths",
|
||
|
"is_simple_path",
|
||
|
"shortest_simple_paths",
|
||
|
"all_simple_edge_paths",
|
||
|
]
|
||
|
|
||
|
|
||
|
def is_simple_path(G, nodes):
|
||
|
"""Returns True if and only if `nodes` form a simple path in `G`.
|
||
|
|
||
|
A *simple path* in a graph is a nonempty sequence of nodes in which
|
||
|
no node appears more than once in the sequence, and each adjacent
|
||
|
pair of nodes in the sequence is adjacent in the graph.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
nodes : list
|
||
|
A list of one or more nodes in the graph `G`.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
bool
|
||
|
Whether the given list of nodes represents a simple path in `G`.
|
||
|
|
||
|
Notes
|
||
|
-----
|
||
|
An empty list of nodes is not a path but a list of one node is a
|
||
|
path. Here's an explanation why.
|
||
|
|
||
|
This function operates on *node paths*. One could also consider
|
||
|
*edge paths*. There is a bijection between node paths and edge
|
||
|
paths.
|
||
|
|
||
|
The *length of a path* is the number of edges in the path, so a list
|
||
|
of nodes of length *n* corresponds to a path of length *n* - 1.
|
||
|
Thus the smallest edge path would be a list of zero edges, the empty
|
||
|
path. This corresponds to a list of one node.
|
||
|
|
||
|
To convert between a node path and an edge path, you can use code
|
||
|
like the following::
|
||
|
|
||
|
>>> from networkx.utils import pairwise
|
||
|
>>> nodes = [0, 1, 2, 3]
|
||
|
>>> edges = list(pairwise(nodes))
|
||
|
>>> edges
|
||
|
[(0, 1), (1, 2), (2, 3)]
|
||
|
>>> nodes = [edges[0][0]] + [v for u, v in edges]
|
||
|
>>> nodes
|
||
|
[0, 1, 2, 3]
|
||
|
|
||
|
Examples
|
||
|
--------
|
||
|
>>> G = nx.cycle_graph(4)
|
||
|
>>> nx.is_simple_path(G, [2, 3, 0])
|
||
|
True
|
||
|
>>> nx.is_simple_path(G, [0, 2])
|
||
|
False
|
||
|
|
||
|
"""
|
||
|
# The empty list is not a valid path. Could also return
|
||
|
# NetworkXPointlessConcept here.
|
||
|
if len(nodes) == 0:
|
||
|
return False
|
||
|
# If the list is a single node, just check that the node is actually
|
||
|
# in the graph.
|
||
|
if len(nodes) == 1:
|
||
|
return nodes[0] in G
|
||
|
# Test that no node appears more than once, and that each
|
||
|
# adjacent pair of nodes is adjacent.
|
||
|
return len(set(nodes)) == len(nodes) and all(v in G[u] for u, v in pairwise(nodes))
|
||
|
|
||
|
|
||
|
def all_simple_paths(G, source, target, cutoff=None):
|
||
|
"""Generate all simple paths in the graph G from source to target.
|
||
|
|
||
|
A simple path is a path with no repeated nodes.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
G : NetworkX graph
|
||
|
|
||
|
source : node
|
||
|
Starting node for path
|
||
|
|
||
|
target : nodes
|
||
|
Single node or iterable of nodes at which to end path
|
||
|
|
||
|
cutoff : integer, optional
|
||
|
Depth to stop the search. Only paths of length <= cutoff are returned.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
path_generator: generator
|
||
|
A generator that produces lists of simple paths. If there are no paths
|
||
|
between the source and target within the given cutoff the generator
|
||
|
produces no output.
|
||
|
|
||
|
Examples
|
||
|
--------
|
||
|
This iterator generates lists of nodes::
|
||
|
|
||
|
>>> G = nx.complete_graph(4)
|
||
|
>>> for path in nx.all_simple_paths(G, source=0, target=3):
|
||
|
... print(path)
|
||
|
...
|
||
|
[0, 1, 2, 3]
|
||
|
[0, 1, 3]
|
||
|
[0, 2, 1, 3]
|
||
|
[0, 2, 3]
|
||
|
[0, 3]
|
||
|
|
||
|
You can generate only those paths that are shorter than a certain
|
||
|
length by using the `cutoff` keyword argument::
|
||
|
|
||
|
>>> paths = nx.all_simple_paths(G, source=0, target=3, cutoff=2)
|
||
|
>>> print(list(paths))
|
||
|
[[0, 1, 3], [0, 2, 3], [0, 3]]
|
||
|
|
||
|
To get each path as the corresponding list of edges, you can use the
|
||
|
:func:`networkx.utils.pairwise` helper function::
|
||
|
|
||
|
>>> paths = nx.all_simple_paths(G, source=0, target=3)
|
||
|
>>> for path in map(nx.utils.pairwise, paths):
|
||
|
... print(list(path))
|
||
|
[(0, 1), (1, 2), (2, 3)]
|
||
|
[(0, 1), (1, 3)]
|
||
|
[(0, 2), (2, 1), (1, 3)]
|
||
|
[(0, 2), (2, 3)]
|
||
|
[(0, 3)]
|
||
|
|
||
|
Pass an iterable of nodes as target to generate all paths ending in any of several nodes::
|
||
|
|
||
|
>>> G = nx.complete_graph(4)
|
||
|
>>> for path in nx.all_simple_paths(G, source=0, target=[3, 2]):
|
||
|
... print(path)
|
||
|
...
|
||
|
[0, 1, 2]
|
||
|
[0, 1, 2, 3]
|
||
|
[0, 1, 3]
|
||
|
[0, 1, 3, 2]
|
||
|
[0, 2]
|
||
|
[0, 2, 1, 3]
|
||
|
[0, 2, 3]
|
||
|
[0, 3]
|
||
|
[0, 3, 1, 2]
|
||
|
[0, 3, 2]
|
||
|
|
||
|
Iterate over each path from the root nodes to the leaf nodes in a
|
||
|
directed acyclic graph using a functional programming approach::
|
||
|
|
||
|
>>> from itertools import chain
|
||
|
>>> from itertools import product
|
||
|
>>> from itertools import starmap
|
||
|
>>> from functools import partial
|
||
|
>>>
|
||
|
>>> chaini = chain.from_iterable
|
||
|
>>>
|
||
|
>>> G = nx.DiGraph([(0, 1), (1, 2), (0, 3), (3, 2)])
|
||
|
>>> roots = (v for v, d in G.in_degree() if d == 0)
|
||
|
>>> leaves = (v for v, d in G.out_degree() if d == 0)
|
||
|
>>> all_paths = partial(nx.all_simple_paths, G)
|
||
|
>>> list(chaini(starmap(all_paths, product(roots, leaves))))
|
||
|
[[0, 1, 2], [0, 3, 2]]
|
||
|
|
||
|
The same list computed using an iterative approach::
|
||
|
|
||
|
>>> G = nx.DiGraph([(0, 1), (1, 2), (0, 3), (3, 2)])
|
||
|
>>> roots = (v for v, d in G.in_degree() if d == 0)
|
||
|
>>> leaves = (v for v, d in G.out_degree() if d == 0)
|
||
|
>>> all_paths = []
|
||
|
>>> for root in roots:
|
||
|
... for leaf in leaves:
|
||
|
... paths = nx.all_simple_paths(G, root, leaf)
|
||
|
... all_paths.extend(paths)
|
||
|
>>> all_paths
|
||
|
[[0, 1, 2], [0, 3, 2]]
|
||
|
|
||
|
Iterate over each path from the root nodes to the leaf nodes in a
|
||
|
directed acyclic graph passing all leaves together to avoid unnecessary
|
||
|
compute::
|
||
|
|
||
|
>>> G = nx.DiGraph([(0, 1), (2, 1), (1, 3), (1, 4)])
|
||
|
>>> roots = (v for v, d in G.in_degree() if d == 0)
|
||
|
>>> leaves = [v for v, d in G.out_degree() if d == 0]
|
||
|
>>> all_paths = []
|
||
|
>>> for root in roots:
|
||
|
... paths = nx.all_simple_paths(G, root, leaves)
|
||
|
... all_paths.extend(paths)
|
||
|
>>> all_paths
|
||
|
[[0, 1, 3], [0, 1, 4], [2, 1, 3], [2, 1, 4]]
|
||
|
|
||
|
Notes
|
||
|
-----
|
||
|
This algorithm uses a modified depth-first search to generate the
|
||
|
paths [1]_. A single path can be found in $O(V+E)$ time but the
|
||
|
number of simple paths in a graph can be very large, e.g. $O(n!)$ in
|
||
|
the complete graph of order $n$.
|
||
|
|
||
|
References
|
||
|
----------
|
||
|
.. [1] R. Sedgewick, "Algorithms in C, Part 5: Graph Algorithms",
|
||
|
Addison Wesley Professional, 3rd ed., 2001.
|
||
|
|
||
|
See Also
|
||
|
--------
|
||
|
all_shortest_paths, shortest_path
|
||
|
|
||
|
"""
|
||
|
if source not in G:
|
||
|
raise nx.NodeNotFound(f"source node {source} not in graph")
|
||
|
if target in G:
|
||
|
targets = {target}
|
||
|
else:
|
||
|
try:
|
||
|
targets = set(target)
|
||
|
except TypeError as e:
|
||
|
raise nx.NodeNotFound(f"target node {target} not in graph") from e
|
||
|
if source in targets:
|
||
|
return empty_generator()
|
||
|
if cutoff is None:
|
||
|
cutoff = len(G) - 1
|
||
|
if cutoff < 1:
|
||
|
return empty_generator()
|
||
|
if G.is_multigraph():
|
||
|
return _all_simple_paths_multigraph(G, source, targets, cutoff)
|
||
|
else:
|
||
|
return _all_simple_paths_graph(G, source, targets, cutoff)
|
||
|
|
||
|
|
||
|
def _all_simple_paths_graph(G, source, targets, cutoff):
|
||
|
visited = dict.fromkeys([source])
|
||
|
stack = [iter(G[source])]
|
||
|
while stack:
|
||
|
children = stack[-1]
|
||
|
child = next(children, None)
|
||
|
if child is None:
|
||
|
stack.pop()
|
||
|
visited.popitem()
|
||
|
elif len(visited) < cutoff:
|
||
|
if child in visited:
|
||
|
continue
|
||
|
if child in targets:
|
||
|
yield list(visited) + [child]
|
||
|
visited[child] = None
|
||
|
if targets - set(visited.keys()): # expand stack until find all targets
|
||
|
stack.append(iter(G[child]))
|
||
|
else:
|
||
|
visited.popitem() # maybe other ways to child
|
||
|
else: # len(visited) == cutoff:
|
||
|
for target in (targets & (set(children) | {child})) - set(visited.keys()):
|
||
|
yield list(visited) + [target]
|
||
|
stack.pop()
|
||
|
visited.popitem()
|
||
|
|
||
|
|
||
|
def _all_simple_paths_multigraph(G, source, targets, cutoff):
|
||
|
visited = dict.fromkeys([source])
|
||
|
stack = [(v for u, v in G.edges(source))]
|
||
|
while stack:
|
||
|
children = stack[-1]
|
||
|
child = next(children, None)
|
||
|
if child is None:
|
||
|
stack.pop()
|
||
|
visited.popitem()
|
||
|
elif len(visited) < cutoff:
|
||
|
if child in visited:
|
||
|
continue
|
||
|
if child in targets:
|
||
|
yield list(visited) + [child]
|
||
|
visited[child] = None
|
||
|
if targets - set(visited.keys()):
|
||
|
stack.append((v for u, v in G.edges(child)))
|
||
|
else:
|
||
|
visited.popitem()
|
||
|
else: # len(visited) == cutoff:
|
||
|
for target in targets - set(visited.keys()):
|
||
|
count = ([child] + list(children)).count(target)
|
||
|
for i in range(count):
|
||
|
yield list(visited) + [target]
|
||
|
stack.pop()
|
||
|
visited.popitem()
|
||
|
|
||
|
|
||
|
def all_simple_edge_paths(G, source, target, cutoff=None):
|
||
|
"""Generate lists of edges for all simple paths in G from source to target.
|
||
|
|
||
|
A simple path is a path with no repeated nodes.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
G : NetworkX graph
|
||
|
|
||
|
source : node
|
||
|
Starting node for path
|
||
|
|
||
|
target : nodes
|
||
|
Single node or iterable of nodes at which to end path
|
||
|
|
||
|
cutoff : integer, optional
|
||
|
Depth to stop the search. Only paths of length <= cutoff are returned.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
path_generator: generator
|
||
|
A generator that produces lists of simple paths. If there are no paths
|
||
|
between the source and target within the given cutoff the generator
|
||
|
produces no output.
|
||
|
For multigraphs, the list of edges have elements of the form `(u,v,k)`.
|
||
|
Where `k` corresponds to the edge key.
|
||
|
|
||
|
Examples
|
||
|
--------
|
||
|
|
||
|
Print the simple path edges of a Graph::
|
||
|
|
||
|
>>> g = nx.Graph([(1, 2), (2, 4), (1, 3), (3, 4)])
|
||
|
>>> for path in sorted(nx.all_simple_edge_paths(g, 1, 4)):
|
||
|
... print(path)
|
||
|
[(1, 2), (2, 4)]
|
||
|
[(1, 3), (3, 4)]
|
||
|
|
||
|
Print the simple path edges of a MultiGraph. Returned edges come with
|
||
|
their associated keys::
|
||
|
|
||
|
>>> mg = nx.MultiGraph()
|
||
|
>>> mg.add_edge(1, 2, key="k0")
|
||
|
'k0'
|
||
|
>>> mg.add_edge(1, 2, key="k1")
|
||
|
'k1'
|
||
|
>>> mg.add_edge(2, 3, key="k0")
|
||
|
'k0'
|
||
|
>>> for path in sorted(nx.all_simple_edge_paths(mg, 1, 3)):
|
||
|
... print(path)
|
||
|
[(1, 2, 'k0'), (2, 3, 'k0')]
|
||
|
[(1, 2, 'k1'), (2, 3, 'k0')]
|
||
|
|
||
|
|
||
|
Notes
|
||
|
-----
|
||
|
This algorithm uses a modified depth-first search to generate the
|
||
|
paths [1]_. A single path can be found in $O(V+E)$ time but the
|
||
|
number of simple paths in a graph can be very large, e.g. $O(n!)$ in
|
||
|
the complete graph of order $n$.
|
||
|
|
||
|
References
|
||
|
----------
|
||
|
.. [1] R. Sedgewick, "Algorithms in C, Part 5: Graph Algorithms",
|
||
|
Addison Wesley Professional, 3rd ed., 2001.
|
||
|
|
||
|
See Also
|
||
|
--------
|
||
|
all_shortest_paths, shortest_path, all_simple_paths
|
||
|
|
||
|
"""
|
||
|
if source not in G:
|
||
|
raise nx.NodeNotFound("source node %s not in graph" % source)
|
||
|
if target in G:
|
||
|
targets = {target}
|
||
|
else:
|
||
|
try:
|
||
|
targets = set(target)
|
||
|
except TypeError:
|
||
|
raise nx.NodeNotFound("target node %s not in graph" % target)
|
||
|
if source in targets:
|
||
|
return []
|
||
|
if cutoff is None:
|
||
|
cutoff = len(G) - 1
|
||
|
if cutoff < 1:
|
||
|
return []
|
||
|
if G.is_multigraph():
|
||
|
for simp_path in _all_simple_edge_paths_multigraph(G, source, targets, cutoff):
|
||
|
yield simp_path
|
||
|
else:
|
||
|
for simp_path in _all_simple_paths_graph(G, source, targets, cutoff):
|
||
|
yield list(zip(simp_path[:-1], simp_path[1:]))
|
||
|
|
||
|
|
||
|
def _all_simple_edge_paths_multigraph(G, source, targets, cutoff):
|
||
|
if not cutoff or cutoff < 1:
|
||
|
return []
|
||
|
visited = [source]
|
||
|
stack = [iter(G.edges(source, keys=True))]
|
||
|
|
||
|
while stack:
|
||
|
children = stack[-1]
|
||
|
child = next(children, None)
|
||
|
if child is None:
|
||
|
stack.pop()
|
||
|
visited.pop()
|
||
|
elif len(visited) < cutoff:
|
||
|
if child[1] in targets:
|
||
|
yield visited[1:] + [child]
|
||
|
elif child[1] not in [v[0] for v in visited[1:]]:
|
||
|
visited.append(child)
|
||
|
stack.append(iter(G.edges(child[1], keys=True)))
|
||
|
else: # len(visited) == cutoff:
|
||
|
for (u, v, k) in [child] + list(children):
|
||
|
if v in targets:
|
||
|
yield visited[1:] + [(u, v, k)]
|
||
|
stack.pop()
|
||
|
visited.pop()
|
||
|
|
||
|
|
||
|
@not_implemented_for("multigraph")
|
||
|
def shortest_simple_paths(G, source, target, weight=None):
|
||
|
"""Generate all simple paths in the graph G from source to target,
|
||
|
starting from shortest ones.
|
||
|
|
||
|
A simple path is a path with no repeated nodes.
|
||
|
|
||
|
If a weighted shortest path search is to be used, no negative weights
|
||
|
are allowed.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
G : NetworkX graph
|
||
|
|
||
|
source : node
|
||
|
Starting node for path
|
||
|
|
||
|
target : node
|
||
|
Ending node for path
|
||
|
|
||
|
weight : string or function
|
||
|
If it is a string, it is the name of the edge attribute to be
|
||
|
used as a weight.
|
||
|
|
||
|
If it is a function, the weight of an edge is the value returned
|
||
|
by the function. The function must accept exactly three positional
|
||
|
arguments: the two endpoints of an edge and the dictionary of edge
|
||
|
attributes for that edge. The function must return a number.
|
||
|
|
||
|
If None all edges are considered to have unit weight. Default
|
||
|
value None.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
path_generator: generator
|
||
|
A generator that produces lists of simple paths, in order from
|
||
|
shortest to longest.
|
||
|
|
||
|
Raises
|
||
|
------
|
||
|
NetworkXNoPath
|
||
|
If no path exists between source and target.
|
||
|
|
||
|
NetworkXError
|
||
|
If source or target nodes are not in the input graph.
|
||
|
|
||
|
NetworkXNotImplemented
|
||
|
If the input graph is a Multi[Di]Graph.
|
||
|
|
||
|
Examples
|
||
|
--------
|
||
|
|
||
|
>>> G = nx.cycle_graph(7)
|
||
|
>>> paths = list(nx.shortest_simple_paths(G, 0, 3))
|
||
|
>>> print(paths)
|
||
|
[[0, 1, 2, 3], [0, 6, 5, 4, 3]]
|
||
|
|
||
|
You can use this function to efficiently compute the k shortest/best
|
||
|
paths between two nodes.
|
||
|
|
||
|
>>> from itertools import islice
|
||
|
>>> def k_shortest_paths(G, source, target, k, weight=None):
|
||
|
... return list(
|
||
|
... islice(nx.shortest_simple_paths(G, source, target, weight=weight), k)
|
||
|
... )
|
||
|
>>> for path in k_shortest_paths(G, 0, 3, 2):
|
||
|
... print(path)
|
||
|
[0, 1, 2, 3]
|
||
|
[0, 6, 5, 4, 3]
|
||
|
|
||
|
Notes
|
||
|
-----
|
||
|
This procedure is based on algorithm by Jin Y. Yen [1]_. Finding
|
||
|
the first $K$ paths requires $O(KN^3)$ operations.
|
||
|
|
||
|
See Also
|
||
|
--------
|
||
|
all_shortest_paths
|
||
|
shortest_path
|
||
|
all_simple_paths
|
||
|
|
||
|
References
|
||
|
----------
|
||
|
.. [1] Jin Y. Yen, "Finding the K Shortest Loopless Paths in a
|
||
|
Network", Management Science, Vol. 17, No. 11, Theory Series
|
||
|
(Jul., 1971), pp. 712-716.
|
||
|
|
||
|
"""
|
||
|
if source not in G:
|
||
|
raise nx.NodeNotFound(f"source node {source} not in graph")
|
||
|
|
||
|
if target not in G:
|
||
|
raise nx.NodeNotFound(f"target node {target} not in graph")
|
||
|
|
||
|
if weight is None:
|
||
|
length_func = len
|
||
|
shortest_path_func = _bidirectional_shortest_path
|
||
|
else:
|
||
|
wt = _weight_function(G, weight)
|
||
|
|
||
|
def length_func(path):
|
||
|
return sum(
|
||
|
wt(u, v, G.get_edge_data(u, v)) for (u, v) in zip(path, path[1:])
|
||
|
)
|
||
|
|
||
|
shortest_path_func = _bidirectional_dijkstra
|
||
|
|
||
|
listA = list()
|
||
|
listB = PathBuffer()
|
||
|
prev_path = None
|
||
|
while True:
|
||
|
if not prev_path:
|
||
|
length, path = shortest_path_func(G, source, target, weight=weight)
|
||
|
listB.push(length, path)
|
||
|
else:
|
||
|
ignore_nodes = set()
|
||
|
ignore_edges = set()
|
||
|
for i in range(1, len(prev_path)):
|
||
|
root = prev_path[:i]
|
||
|
root_length = length_func(root)
|
||
|
for path in listA:
|
||
|
if path[:i] == root:
|
||
|
ignore_edges.add((path[i - 1], path[i]))
|
||
|
try:
|
||
|
length, spur = shortest_path_func(
|
||
|
G,
|
||
|
root[-1],
|
||
|
target,
|
||
|
ignore_nodes=ignore_nodes,
|
||
|
ignore_edges=ignore_edges,
|
||
|
weight=weight,
|
||
|
)
|
||
|
path = root[:-1] + spur
|
||
|
listB.push(root_length + length, path)
|
||
|
except nx.NetworkXNoPath:
|
||
|
pass
|
||
|
ignore_nodes.add(root[-1])
|
||
|
|
||
|
if listB:
|
||
|
path = listB.pop()
|
||
|
yield path
|
||
|
listA.append(path)
|
||
|
prev_path = path
|
||
|
else:
|
||
|
break
|
||
|
|
||
|
|
||
|
class PathBuffer:
|
||
|
def __init__(self):
|
||
|
self.paths = set()
|
||
|
self.sortedpaths = list()
|
||
|
self.counter = count()
|
||
|
|
||
|
def __len__(self):
|
||
|
return len(self.sortedpaths)
|
||
|
|
||
|
def push(self, cost, path):
|
||
|
hashable_path = tuple(path)
|
||
|
if hashable_path not in self.paths:
|
||
|
heappush(self.sortedpaths, (cost, next(self.counter), path))
|
||
|
self.paths.add(hashable_path)
|
||
|
|
||
|
def pop(self):
|
||
|
(cost, num, path) = heappop(self.sortedpaths)
|
||
|
hashable_path = tuple(path)
|
||
|
self.paths.remove(hashable_path)
|
||
|
return path
|
||
|
|
||
|
|
||
|
def _bidirectional_shortest_path(
|
||
|
G, source, target, ignore_nodes=None, ignore_edges=None, weight=None
|
||
|
):
|
||
|
"""Returns the shortest path between source and target ignoring
|
||
|
nodes and edges in the containers ignore_nodes and ignore_edges.
|
||
|
|
||
|
This is a custom modification of the standard bidirectional shortest
|
||
|
path implementation at networkx.algorithms.unweighted
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
G : NetworkX graph
|
||
|
|
||
|
source : node
|
||
|
starting node for path
|
||
|
|
||
|
target : node
|
||
|
ending node for path
|
||
|
|
||
|
ignore_nodes : container of nodes
|
||
|
nodes to ignore, optional
|
||
|
|
||
|
ignore_edges : container of edges
|
||
|
edges to ignore, optional
|
||
|
|
||
|
weight : None
|
||
|
This function accepts a weight argument for convenience of
|
||
|
shortest_simple_paths function. It will be ignored.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
path: list
|
||
|
List of nodes in a path from source to target.
|
||
|
|
||
|
Raises
|
||
|
------
|
||
|
NetworkXNoPath
|
||
|
If no path exists between source and target.
|
||
|
|
||
|
See Also
|
||
|
--------
|
||
|
shortest_path
|
||
|
|
||
|
"""
|
||
|
# call helper to do the real work
|
||
|
results = _bidirectional_pred_succ(G, source, target, ignore_nodes, ignore_edges)
|
||
|
pred, succ, w = results
|
||
|
|
||
|
# build path from pred+w+succ
|
||
|
path = []
|
||
|
# from w to target
|
||
|
while w is not None:
|
||
|
path.append(w)
|
||
|
w = succ[w]
|
||
|
# from source to w
|
||
|
w = pred[path[0]]
|
||
|
while w is not None:
|
||
|
path.insert(0, w)
|
||
|
w = pred[w]
|
||
|
|
||
|
return len(path), path
|
||
|
|
||
|
|
||
|
def _bidirectional_pred_succ(G, source, target, ignore_nodes=None, ignore_edges=None):
|
||
|
"""Bidirectional shortest path helper.
|
||
|
Returns (pred,succ,w) where
|
||
|
pred is a dictionary of predecessors from w to the source, and
|
||
|
succ is a dictionary of successors from w to the target.
|
||
|
"""
|
||
|
# does BFS from both source and target and meets in the middle
|
||
|
if ignore_nodes and (source in ignore_nodes or target in ignore_nodes):
|
||
|
raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
|
||
|
if target == source:
|
||
|
return ({target: None}, {source: None}, source)
|
||
|
|
||
|
# handle either directed or undirected
|
||
|
if G.is_directed():
|
||
|
Gpred = G.predecessors
|
||
|
Gsucc = G.successors
|
||
|
else:
|
||
|
Gpred = G.neighbors
|
||
|
Gsucc = G.neighbors
|
||
|
|
||
|
# support optional nodes filter
|
||
|
if ignore_nodes:
|
||
|
|
||
|
def filter_iter(nodes):
|
||
|
def iterate(v):
|
||
|
for w in nodes(v):
|
||
|
if w not in ignore_nodes:
|
||
|
yield w
|
||
|
|
||
|
return iterate
|
||
|
|
||
|
Gpred = filter_iter(Gpred)
|
||
|
Gsucc = filter_iter(Gsucc)
|
||
|
|
||
|
# support optional edges filter
|
||
|
if ignore_edges:
|
||
|
if G.is_directed():
|
||
|
|
||
|
def filter_pred_iter(pred_iter):
|
||
|
def iterate(v):
|
||
|
for w in pred_iter(v):
|
||
|
if (w, v) not in ignore_edges:
|
||
|
yield w
|
||
|
|
||
|
return iterate
|
||
|
|
||
|
def filter_succ_iter(succ_iter):
|
||
|
def iterate(v):
|
||
|
for w in succ_iter(v):
|
||
|
if (v, w) not in ignore_edges:
|
||
|
yield w
|
||
|
|
||
|
return iterate
|
||
|
|
||
|
Gpred = filter_pred_iter(Gpred)
|
||
|
Gsucc = filter_succ_iter(Gsucc)
|
||
|
|
||
|
else:
|
||
|
|
||
|
def filter_iter(nodes):
|
||
|
def iterate(v):
|
||
|
for w in nodes(v):
|
||
|
if (v, w) not in ignore_edges and (w, v) not in ignore_edges:
|
||
|
yield w
|
||
|
|
||
|
return iterate
|
||
|
|
||
|
Gpred = filter_iter(Gpred)
|
||
|
Gsucc = filter_iter(Gsucc)
|
||
|
|
||
|
# predecesssor and successors in search
|
||
|
pred = {source: None}
|
||
|
succ = {target: None}
|
||
|
|
||
|
# initialize fringes, start with forward
|
||
|
forward_fringe = [source]
|
||
|
reverse_fringe = [target]
|
||
|
|
||
|
while forward_fringe and reverse_fringe:
|
||
|
if len(forward_fringe) <= len(reverse_fringe):
|
||
|
this_level = forward_fringe
|
||
|
forward_fringe = []
|
||
|
for v in this_level:
|
||
|
for w in Gsucc(v):
|
||
|
if w not in pred:
|
||
|
forward_fringe.append(w)
|
||
|
pred[w] = v
|
||
|
if w in succ:
|
||
|
# found path
|
||
|
return pred, succ, w
|
||
|
else:
|
||
|
this_level = reverse_fringe
|
||
|
reverse_fringe = []
|
||
|
for v in this_level:
|
||
|
for w in Gpred(v):
|
||
|
if w not in succ:
|
||
|
succ[w] = v
|
||
|
reverse_fringe.append(w)
|
||
|
if w in pred:
|
||
|
# found path
|
||
|
return pred, succ, w
|
||
|
|
||
|
raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
|
||
|
|
||
|
|
||
|
def _bidirectional_dijkstra(
|
||
|
G, source, target, weight="weight", ignore_nodes=None, ignore_edges=None
|
||
|
):
|
||
|
"""Dijkstra's algorithm for shortest paths using bidirectional search.
|
||
|
|
||
|
This function returns the shortest path between source and target
|
||
|
ignoring nodes and edges in the containers ignore_nodes and
|
||
|
ignore_edges.
|
||
|
|
||
|
This is a custom modification of the standard Dijkstra bidirectional
|
||
|
shortest path implementation at networkx.algorithms.weighted
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
G : NetworkX graph
|
||
|
|
||
|
source : node
|
||
|
Starting node.
|
||
|
|
||
|
target : node
|
||
|
Ending node.
|
||
|
|
||
|
weight: string, function, optional (default='weight')
|
||
|
Edge data key or weight function corresponding to the edge weight
|
||
|
|
||
|
ignore_nodes : container of nodes
|
||
|
nodes to ignore, optional
|
||
|
|
||
|
ignore_edges : container of edges
|
||
|
edges to ignore, optional
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
length : number
|
||
|
Shortest path length.
|
||
|
|
||
|
Returns a tuple of two dictionaries keyed by node.
|
||
|
The first dictionary stores distance from the source.
|
||
|
The second stores the path from the source to that node.
|
||
|
|
||
|
Raises
|
||
|
------
|
||
|
NetworkXNoPath
|
||
|
If no path exists between source and target.
|
||
|
|
||
|
Notes
|
||
|
-----
|
||
|
Edge weight attributes must be numerical.
|
||
|
Distances are calculated as sums of weighted edges traversed.
|
||
|
|
||
|
In practice bidirectional Dijkstra is much more than twice as fast as
|
||
|
ordinary Dijkstra.
|
||
|
|
||
|
Ordinary Dijkstra expands nodes in a sphere-like manner from the
|
||
|
source. The radius of this sphere will eventually be the length
|
||
|
of the shortest path. Bidirectional Dijkstra will expand nodes
|
||
|
from both the source and the target, making two spheres of half
|
||
|
this radius. Volume of the first sphere is pi*r*r while the
|
||
|
others are 2*pi*r/2*r/2, making up half the volume.
|
||
|
|
||
|
This algorithm is not guaranteed to work if edge weights
|
||
|
are negative or are floating point numbers
|
||
|
(overflows and roundoff errors can cause problems).
|
||
|
|
||
|
See Also
|
||
|
--------
|
||
|
shortest_path
|
||
|
shortest_path_length
|
||
|
"""
|
||
|
if ignore_nodes and (source in ignore_nodes or target in ignore_nodes):
|
||
|
raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
|
||
|
if source == target:
|
||
|
return (0, [source])
|
||
|
|
||
|
# handle either directed or undirected
|
||
|
if G.is_directed():
|
||
|
Gpred = G.predecessors
|
||
|
Gsucc = G.successors
|
||
|
else:
|
||
|
Gpred = G.neighbors
|
||
|
Gsucc = G.neighbors
|
||
|
|
||
|
# support optional nodes filter
|
||
|
if ignore_nodes:
|
||
|
|
||
|
def filter_iter(nodes):
|
||
|
def iterate(v):
|
||
|
for w in nodes(v):
|
||
|
if w not in ignore_nodes:
|
||
|
yield w
|
||
|
|
||
|
return iterate
|
||
|
|
||
|
Gpred = filter_iter(Gpred)
|
||
|
Gsucc = filter_iter(Gsucc)
|
||
|
|
||
|
# support optional edges filter
|
||
|
if ignore_edges:
|
||
|
if G.is_directed():
|
||
|
|
||
|
def filter_pred_iter(pred_iter):
|
||
|
def iterate(v):
|
||
|
for w in pred_iter(v):
|
||
|
if (w, v) not in ignore_edges:
|
||
|
yield w
|
||
|
|
||
|
return iterate
|
||
|
|
||
|
def filter_succ_iter(succ_iter):
|
||
|
def iterate(v):
|
||
|
for w in succ_iter(v):
|
||
|
if (v, w) not in ignore_edges:
|
||
|
yield w
|
||
|
|
||
|
return iterate
|
||
|
|
||
|
Gpred = filter_pred_iter(Gpred)
|
||
|
Gsucc = filter_succ_iter(Gsucc)
|
||
|
|
||
|
else:
|
||
|
|
||
|
def filter_iter(nodes):
|
||
|
def iterate(v):
|
||
|
for w in nodes(v):
|
||
|
if (v, w) not in ignore_edges and (w, v) not in ignore_edges:
|
||
|
yield w
|
||
|
|
||
|
return iterate
|
||
|
|
||
|
Gpred = filter_iter(Gpred)
|
||
|
Gsucc = filter_iter(Gsucc)
|
||
|
|
||
|
push = heappush
|
||
|
pop = heappop
|
||
|
# Init: Forward Backward
|
||
|
dists = [{}, {}] # dictionary of final distances
|
||
|
paths = [{source: [source]}, {target: [target]}] # dictionary of paths
|
||
|
fringe = [[], []] # heap of (distance, node) tuples for
|
||
|
# extracting next node to expand
|
||
|
seen = [{source: 0}, {target: 0}] # dictionary of distances to
|
||
|
# nodes seen
|
||
|
c = count()
|
||
|
# initialize fringe heap
|
||
|
push(fringe[0], (0, next(c), source))
|
||
|
push(fringe[1], (0, next(c), target))
|
||
|
# neighs for extracting correct neighbor information
|
||
|
neighs = [Gsucc, Gpred]
|
||
|
# variables to hold shortest discovered path
|
||
|
# finaldist = 1e30000
|
||
|
finalpath = []
|
||
|
dir = 1
|
||
|
while fringe[0] and fringe[1]:
|
||
|
# choose direction
|
||
|
# dir == 0 is forward direction and dir == 1 is back
|
||
|
dir = 1 - dir
|
||
|
# extract closest to expand
|
||
|
(dist, _, v) = pop(fringe[dir])
|
||
|
if v in dists[dir]:
|
||
|
# Shortest path to v has already been found
|
||
|
continue
|
||
|
# update distance
|
||
|
dists[dir][v] = dist # equal to seen[dir][v]
|
||
|
if v in dists[1 - dir]:
|
||
|
# if we have scanned v in both directions we are done
|
||
|
# we have now discovered the shortest path
|
||
|
return (finaldist, finalpath)
|
||
|
|
||
|
wt = _weight_function(G, weight)
|
||
|
for w in neighs[dir](v):
|
||
|
if dir == 0: # forward
|
||
|
minweight = wt(v, w, G.get_edge_data(v, w))
|
||
|
vwLength = dists[dir][v] + minweight
|
||
|
else: # back, must remember to change v,w->w,v
|
||
|
minweight = wt(w, v, G.get_edge_data(w, v))
|
||
|
vwLength = dists[dir][v] + minweight
|
||
|
|
||
|
if w in dists[dir]:
|
||
|
if vwLength < dists[dir][w]:
|
||
|
raise ValueError("Contradictory paths found: negative weights?")
|
||
|
elif w not in seen[dir] or vwLength < seen[dir][w]:
|
||
|
# relaxing
|
||
|
seen[dir][w] = vwLength
|
||
|
push(fringe[dir], (vwLength, next(c), w))
|
||
|
paths[dir][w] = paths[dir][v] + [w]
|
||
|
if w in seen[0] and w in seen[1]:
|
||
|
# see if this path is better than than the already
|
||
|
# discovered shortest path
|
||
|
totaldist = seen[0][w] + seen[1][w]
|
||
|
if finalpath == [] or finaldist > totaldist:
|
||
|
finaldist = totaldist
|
||
|
revpath = paths[1][w][:]
|
||
|
revpath.reverse()
|
||
|
finalpath = paths[0][w] + revpath[1:]
|
||
|
raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
|