| | """ |
| | Kanevsky all minimum node k cutsets algorithm. |
| | """ |
| |
|
| | import copy |
| | from collections import defaultdict |
| | from itertools import combinations |
| | from operator import itemgetter |
| |
|
| | import networkx as nx |
| | from networkx.algorithms.flow import ( |
| | build_residual_network, |
| | edmonds_karp, |
| | shortest_augmenting_path, |
| | ) |
| |
|
| | from .utils import build_auxiliary_node_connectivity |
| |
|
| | default_flow_func = edmonds_karp |
| |
|
| |
|
| | __all__ = ["all_node_cuts"] |
| |
|
| |
|
| | @nx._dispatchable |
| | def all_node_cuts(G, k=None, flow_func=None): |
| | r"""Returns all minimum k cutsets of an undirected graph G. |
| | |
| | This implementation is based on Kanevsky's algorithm [1]_ for finding all |
| | minimum-size node cut-sets of an undirected graph G; ie the set (or sets) |
| | of nodes of cardinality equal to the node connectivity of G. Thus if |
| | removed, would break G into two or more connected components. |
| | |
| | Parameters |
| | ---------- |
| | G : NetworkX graph |
| | Undirected graph |
| | |
| | k : Integer |
| | Node connectivity of the input graph. If k is None, then it is |
| | computed. Default value: None. |
| | |
| | flow_func : function |
| | Function to perform the underlying flow computations. Default value is |
| | :func:`~networkx.algorithms.flow.edmonds_karp`. This function performs |
| | better in sparse graphs with right tailed degree distributions. |
| | :func:`~networkx.algorithms.flow.shortest_augmenting_path` will |
| | perform better in denser graphs. |
| | |
| | |
| | Returns |
| | ------- |
| | cuts : a generator of node cutsets |
| | Each node cutset has cardinality equal to the node connectivity of |
| | the input graph. |
| | |
| | Examples |
| | -------- |
| | >>> # A two-dimensional grid graph has 4 cutsets of cardinality 2 |
| | >>> G = nx.grid_2d_graph(5, 5) |
| | >>> cutsets = list(nx.all_node_cuts(G)) |
| | >>> len(cutsets) |
| | 4 |
| | >>> all(2 == len(cutset) for cutset in cutsets) |
| | True |
| | >>> nx.node_connectivity(G) |
| | 2 |
| | |
| | Notes |
| | ----- |
| | This implementation is based on the sequential algorithm for finding all |
| | minimum-size separating vertex sets in a graph [1]_. The main idea is to |
| | compute minimum cuts using local maximum flow computations among a set |
| | of nodes of highest degree and all other non-adjacent nodes in the Graph. |
| | Once we find a minimum cut, we add an edge between the high degree |
| | node and the target node of the local maximum flow computation to make |
| | sure that we will not find that minimum cut again. |
| | |
| | See also |
| | -------- |
| | node_connectivity |
| | edmonds_karp |
| | shortest_augmenting_path |
| | |
| | References |
| | ---------- |
| | .. [1] Kanevsky, A. (1993). Finding all minimum-size separating vertex |
| | sets in a graph. Networks 23(6), 533--541. |
| | http://onlinelibrary.wiley.com/doi/10.1002/net.3230230604/abstract |
| | |
| | """ |
| | if not nx.is_connected(G): |
| | raise nx.NetworkXError("Input graph is disconnected.") |
| |
|
| | |
| | |
| |
|
| | if nx.density(G) == 1: |
| | yield from () |
| | return |
| |
|
| | |
| | |
| | seen = [] |
| | |
| | |
| | H = build_auxiliary_node_connectivity(G) |
| | H_nodes = H.nodes |
| | mapping = H.graph["mapping"] |
| | |
| | |
| | original_H_pred = copy.copy(H._pred) |
| | R = build_residual_network(H, "capacity") |
| | kwargs = {"capacity": "capacity", "residual": R} |
| | |
| | if flow_func is None: |
| | flow_func = default_flow_func |
| | if flow_func is shortest_augmenting_path: |
| | kwargs["two_phase"] = True |
| | |
| | |
| | if k is None: |
| | k = nx.node_connectivity(G, flow_func=flow_func) |
| | |
| | |
| | X = {n for n, d in sorted(G.degree(), key=itemgetter(1), reverse=True)[:k]} |
| | |
| | if _is_separating_set(G, X): |
| | seen.append(X) |
| | yield X |
| |
|
| | for x in X: |
| | |
| | |
| | non_adjacent = set(G) - {x} - set(G[x]) |
| | for v in non_adjacent: |
| | |
| | |
| | R = flow_func(H, f"{mapping[x]}B", f"{mapping[v]}A", **kwargs) |
| | flow_value = R.graph["flow_value"] |
| |
|
| | if flow_value == k: |
| | |
| | E1 = flowed_edges = [ |
| | (u, w) for (u, w, d) in R.edges(data=True) if d["flow"] != 0 |
| | ] |
| | VE1 = incident_nodes = {n for edge in E1 for n in edge} |
| | |
| | |
| | |
| | saturated_edges = [ |
| | (u, w, d) |
| | for (u, w, d) in R.edges(data=True) |
| | if d["capacity"] == d["flow"] or d["capacity"] == 0 |
| | ] |
| | R.remove_edges_from(saturated_edges) |
| | R_closure = nx.transitive_closure(R) |
| | |
| | |
| | L = nx.condensation(R) |
| | cmap = L.graph["mapping"] |
| | inv_cmap = defaultdict(list) |
| | for n, scc in cmap.items(): |
| | inv_cmap[scc].append(n) |
| | |
| | VE1 = {cmap[n] for n in VE1} |
| | |
| | |
| | |
| | for antichain in nx.antichains(L): |
| | |
| | |
| | if not set(antichain).issubset(VE1): |
| | continue |
| | |
| | |
| | |
| | |
| | |
| | S = set() |
| | for scc in antichain: |
| | S.update(inv_cmap[scc]) |
| | S_ancestors = set() |
| | for n in S: |
| | S_ancestors.update(R_closure._pred[n]) |
| | S.update(S_ancestors) |
| | if f"{mapping[x]}B" not in S or f"{mapping[v]}A" in S: |
| | continue |
| | |
| | cutset = set() |
| | for u in S: |
| | cutset.update((u, w) for w in original_H_pred[u] if w not in S) |
| | |
| | |
| | if any(H_nodes[u]["id"] != H_nodes[w]["id"] for u, w in cutset): |
| | continue |
| | node_cut = {H_nodes[u]["id"] for u, _ in cutset} |
| |
|
| | if len(node_cut) == k: |
| | |
| | |
| | if x in node_cut or v in node_cut: |
| | continue |
| | if node_cut not in seen: |
| | yield node_cut |
| | seen.append(node_cut) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | H.add_edge(f"{mapping[x]}B", f"{mapping[v]}A", capacity=1) |
| | H.add_edge(f"{mapping[v]}B", f"{mapping[x]}A", capacity=1) |
| | |
| | R.add_edge(f"{mapping[x]}B", f"{mapping[v]}A", capacity=1) |
| | R.add_edge(f"{mapping[v]}A", f"{mapping[x]}B", capacity=0) |
| | R.add_edge(f"{mapping[v]}B", f"{mapping[x]}A", capacity=1) |
| | R.add_edge(f"{mapping[x]}A", f"{mapping[v]}B", capacity=0) |
| |
|
| | |
| | R.add_edges_from(saturated_edges) |
| |
|
| |
|
| | def _is_separating_set(G, cut): |
| | """Assumes that the input graph is connected""" |
| | if len(cut) == len(G) - 1: |
| | return True |
| |
|
| | H = nx.restricted_view(G, cut, []) |
| | if nx.is_connected(H): |
| | return False |
| | return True |
| |
|