123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- """Flow based node and edge disjoint paths."""
- import networkx as nx
- # Define the default maximum flow function to use for the underlying
- # maximum flow computations
- from networkx.algorithms.flow import (
- edmonds_karp,
- preflow_push,
- shortest_augmenting_path,
- )
- from networkx.exception import NetworkXNoPath
- default_flow_func = edmonds_karp
- from itertools import filterfalse as _filterfalse
- # Functions to build auxiliary data structures.
- from .utils import build_auxiliary_edge_connectivity, build_auxiliary_node_connectivity
- __all__ = ["edge_disjoint_paths", "node_disjoint_paths"]
- def edge_disjoint_paths(
- G, s, t, flow_func=None, cutoff=None, auxiliary=None, residual=None
- ):
- """Returns the edges disjoint paths between source and target.
- Edge disjoint paths are paths that do not share any edge. The
- number of edge disjoint paths between source and target is equal
- to their edge connectivity.
- Parameters
- ----------
- G : NetworkX graph
- s : node
- Source node for the flow.
- t : node
- Sink node for the flow.
- flow_func : function
- A function for computing the maximum flow among a pair of nodes.
- The function has to accept at least three parameters: a Digraph,
- a source node, and a target node. And return a residual network
- that follows NetworkX conventions (see :meth:`maximum_flow` for
- details). If flow_func is None, the default maximum flow function
- (:meth:`edmonds_karp`) is used. The choice of the default function
- may change from version to version and should not be relied on.
- Default value: None.
- cutoff : integer or None (default: None)
- Maximum number of paths to yield. If specified, the maximum flow
- algorithm will terminate when the flow value reaches or exceeds the
- cutoff. This only works for flows that support the cutoff parameter
- (most do) and is ignored otherwise.
- auxiliary : NetworkX DiGraph
- Auxiliary digraph to compute flow based edge connectivity. It has
- to have a graph attribute called mapping with a dictionary mapping
- node names in G and in the auxiliary digraph. If provided
- it will be reused instead of recreated. Default value: None.
- residual : NetworkX DiGraph
- Residual network to compute maximum flow. If provided it will be
- reused instead of recreated. Default value: None.
- Returns
- -------
- paths : generator
- A generator of edge independent paths.
- Raises
- ------
- NetworkXNoPath
- If there is no path between source and target.
- NetworkXError
- If source or target are not in the graph G.
- See also
- --------
- :meth:`node_disjoint_paths`
- :meth:`edge_connectivity`
- :meth:`maximum_flow`
- :meth:`edmonds_karp`
- :meth:`preflow_push`
- :meth:`shortest_augmenting_path`
- Examples
- --------
- We use in this example the platonic icosahedral graph, which has node
- edge connectivity 5, thus there are 5 edge disjoint paths between any
- pair of nodes.
- >>> G = nx.icosahedral_graph()
- >>> len(list(nx.edge_disjoint_paths(G, 0, 6)))
- 5
- If you need to compute edge disjoint paths on several pairs of
- nodes in the same graph, it is recommended that you reuse the
- data structures that NetworkX uses in the computation: the
- auxiliary digraph for edge connectivity, and the residual
- network for the underlying maximum flow computation.
- Example of how to compute edge disjoint paths among all pairs of
- nodes of the platonic icosahedral graph reusing the data
- structures.
- >>> import itertools
- >>> # You also have to explicitly import the function for
- >>> # building the auxiliary digraph from the connectivity package
- >>> from networkx.algorithms.connectivity import build_auxiliary_edge_connectivity
- >>> H = build_auxiliary_edge_connectivity(G)
- >>> # And the function for building the residual network from the
- >>> # flow package
- >>> from networkx.algorithms.flow import build_residual_network
- >>> # Note that the auxiliary digraph has an edge attribute named capacity
- >>> R = build_residual_network(H, "capacity")
- >>> result = {n: {} for n in G}
- >>> # Reuse the auxiliary digraph and the residual network by passing them
- >>> # as arguments
- >>> for u, v in itertools.combinations(G, 2):
- ... k = len(list(nx.edge_disjoint_paths(G, u, v, auxiliary=H, residual=R)))
- ... result[u][v] = k
- >>> all(result[u][v] == 5 for u, v in itertools.combinations(G, 2))
- True
- You can also use alternative flow algorithms for computing edge disjoint
- paths. For instance, in dense networks the algorithm
- :meth:`shortest_augmenting_path` will usually perform better than
- the default :meth:`edmonds_karp` which is faster for sparse
- networks with highly skewed degree distributions. Alternative flow
- functions have to be explicitly imported from the flow package.
- >>> from networkx.algorithms.flow import shortest_augmenting_path
- >>> len(list(nx.edge_disjoint_paths(G, 0, 6, flow_func=shortest_augmenting_path)))
- 5
- Notes
- -----
- This is a flow based implementation of edge disjoint paths. We compute
- the maximum flow between source and target on an auxiliary directed
- network. The saturated edges in the residual network after running the
- maximum flow algorithm correspond to edge disjoint paths between source
- and target in the original network. This function handles both directed
- and undirected graphs, and can use all flow algorithms from NetworkX flow
- package.
- """
- if s not in G:
- raise nx.NetworkXError(f"node {s} not in graph")
- if t not in G:
- raise nx.NetworkXError(f"node {t} not in graph")
- if flow_func is None:
- flow_func = default_flow_func
- if auxiliary is None:
- H = build_auxiliary_edge_connectivity(G)
- else:
- H = auxiliary
- # Maximum possible edge disjoint paths
- possible = min(H.out_degree(s), H.in_degree(t))
- if not possible:
- raise NetworkXNoPath
- if cutoff is None:
- cutoff = possible
- else:
- cutoff = min(cutoff, possible)
- # Compute maximum flow between source and target. Flow functions in
- # NetworkX return a residual network.
- kwargs = {
- "capacity": "capacity",
- "residual": residual,
- "cutoff": cutoff,
- "value_only": True,
- }
- if flow_func is preflow_push:
- del kwargs["cutoff"]
- if flow_func is shortest_augmenting_path:
- kwargs["two_phase"] = True
- R = flow_func(H, s, t, **kwargs)
- if R.graph["flow_value"] == 0:
- raise NetworkXNoPath
- # Saturated edges in the residual network form the edge disjoint paths
- # between source and target
- cutset = [
- (u, v)
- for u, v, d in R.edges(data=True)
- if d["capacity"] == d["flow"] and d["flow"] > 0
- ]
- # This is equivalent of what flow.utils.build_flow_dict returns, but
- # only for the nodes with saturated edges and without reporting 0 flows.
- flow_dict = {n: {} for edge in cutset for n in edge}
- for u, v in cutset:
- flow_dict[u][v] = 1
- # Rebuild the edge disjoint paths from the flow dictionary.
- paths_found = 0
- for v in list(flow_dict[s]):
- if paths_found >= cutoff:
- # preflow_push does not support cutoff: we have to
- # keep track of the paths founds and stop at cutoff.
- break
- path = [s]
- if v == t:
- path.append(v)
- yield path
- continue
- u = v
- while u != t:
- path.append(u)
- try:
- u, _ = flow_dict[u].popitem()
- except KeyError:
- break
- else:
- path.append(t)
- yield path
- paths_found += 1
- def node_disjoint_paths(
- G, s, t, flow_func=None, cutoff=None, auxiliary=None, residual=None
- ):
- r"""Computes node disjoint paths between source and target.
- Node disjoint paths are paths that only share their first and last
- nodes. The number of node independent paths between two nodes is
- equal to their local node connectivity.
- Parameters
- ----------
- G : NetworkX graph
- s : node
- Source node.
- t : node
- Target node.
- flow_func : function
- A function for computing the maximum flow among a pair of nodes.
- The function has to accept at least three parameters: a Digraph,
- a source node, and a target node. And return a residual network
- that follows NetworkX conventions (see :meth:`maximum_flow` for
- details). If flow_func is None, the default maximum flow function
- (:meth:`edmonds_karp`) is used. See below for details. The choice
- of the default function may change from version to version and
- should not be relied on. Default value: None.
- cutoff : integer or None (default: None)
- Maximum number of paths to yield. If specified, the maximum flow
- algorithm will terminate when the flow value reaches or exceeds the
- cutoff. This only works for flows that support the cutoff parameter
- (most do) and is ignored otherwise.
- auxiliary : NetworkX DiGraph
- Auxiliary digraph to compute flow based node connectivity. It has
- to have a graph attribute called mapping with a dictionary mapping
- node names in G and in the auxiliary digraph. If provided
- it will be reused instead of recreated. Default value: None.
- residual : NetworkX DiGraph
- Residual network to compute maximum flow. If provided it will be
- reused instead of recreated. Default value: None.
- Returns
- -------
- paths : generator
- Generator of node disjoint paths.
- Raises
- ------
- NetworkXNoPath
- If there is no path between source and target.
- NetworkXError
- If source or target are not in the graph G.
- Examples
- --------
- We use in this example the platonic icosahedral graph, which has node
- connectivity 5, thus there are 5 node disjoint paths between any pair
- of non neighbor nodes.
- >>> G = nx.icosahedral_graph()
- >>> len(list(nx.node_disjoint_paths(G, 0, 6)))
- 5
- If you need to compute node disjoint paths between several pairs of
- nodes in the same graph, it is recommended that you reuse the
- data structures that NetworkX uses in the computation: the
- auxiliary digraph for node connectivity and node cuts, and the
- residual network for the underlying maximum flow computation.
- Example of how to compute node disjoint paths reusing the data
- structures:
- >>> # You also have to explicitly import the function for
- >>> # building the auxiliary digraph from the connectivity package
- >>> from networkx.algorithms.connectivity import build_auxiliary_node_connectivity
- >>> H = build_auxiliary_node_connectivity(G)
- >>> # And the function for building the residual network from the
- >>> # flow package
- >>> from networkx.algorithms.flow import build_residual_network
- >>> # Note that the auxiliary digraph has an edge attribute named capacity
- >>> R = build_residual_network(H, "capacity")
- >>> # Reuse the auxiliary digraph and the residual network by passing them
- >>> # as arguments
- >>> len(list(nx.node_disjoint_paths(G, 0, 6, auxiliary=H, residual=R)))
- 5
- You can also use alternative flow algorithms for computing node disjoint
- paths. For instance, in dense networks the algorithm
- :meth:`shortest_augmenting_path` will usually perform better than
- the default :meth:`edmonds_karp` which is faster for sparse
- networks with highly skewed degree distributions. Alternative flow
- functions have to be explicitly imported from the flow package.
- >>> from networkx.algorithms.flow import shortest_augmenting_path
- >>> len(list(nx.node_disjoint_paths(G, 0, 6, flow_func=shortest_augmenting_path)))
- 5
- Notes
- -----
- This is a flow based implementation of node disjoint paths. We compute
- the maximum flow between source and target on an auxiliary directed
- network. The saturated edges in the residual network after running the
- maximum flow algorithm correspond to node disjoint paths between source
- and target in the original network. This function handles both directed
- and undirected graphs, and can use all flow algorithms from NetworkX flow
- package.
- See also
- --------
- :meth:`edge_disjoint_paths`
- :meth:`node_connectivity`
- :meth:`maximum_flow`
- :meth:`edmonds_karp`
- :meth:`preflow_push`
- :meth:`shortest_augmenting_path`
- """
- if s not in G:
- raise nx.NetworkXError(f"node {s} not in graph")
- if t not in G:
- raise nx.NetworkXError(f"node {t} not in graph")
- if auxiliary is None:
- H = build_auxiliary_node_connectivity(G)
- else:
- H = auxiliary
- mapping = H.graph.get("mapping", None)
- if mapping is None:
- raise nx.NetworkXError("Invalid auxiliary digraph.")
- # Maximum possible edge disjoint paths
- possible = min(H.out_degree(f"{mapping[s]}B"), H.in_degree(f"{mapping[t]}A"))
- if not possible:
- raise NetworkXNoPath
- if cutoff is None:
- cutoff = possible
- else:
- cutoff = min(cutoff, possible)
- kwargs = {
- "flow_func": flow_func,
- "residual": residual,
- "auxiliary": H,
- "cutoff": cutoff,
- }
- # The edge disjoint paths in the auxiliary digraph correspond to the node
- # disjoint paths in the original graph.
- paths_edges = edge_disjoint_paths(H, f"{mapping[s]}B", f"{mapping[t]}A", **kwargs)
- for path in paths_edges:
- # Each node in the original graph maps to two nodes in auxiliary graph
- yield list(_unique_everseen(H.nodes[node]["id"] for node in path))
- def _unique_everseen(iterable):
- # Adapted from https://docs.python.org/3/library/itertools.html examples
- "List unique elements, preserving order. Remember all elements ever seen."
- # unique_everseen('AAAABBBCCDAABBB') --> A B C D
- seen = set()
- seen_add = seen.add
- for element in _filterfalse(seen.__contains__, iterable):
- seen_add(element)
- yield element
|