Spaces:
Paused
Paused
| """ Fast approximation for node connectivity | |
| """ | |
| import itertools | |
| from operator import itemgetter | |
| import networkx as nx | |
| __all__ = [ | |
| "local_node_connectivity", | |
| "node_connectivity", | |
| "all_pairs_node_connectivity", | |
| ] | |
| def local_node_connectivity(G, source, target, cutoff=None): | |
| """Compute node connectivity between source and target. | |
| Pairwise or local node connectivity between two distinct and nonadjacent | |
| nodes is the minimum number of nodes that must be removed (minimum | |
| separating cutset) to disconnect them. By Menger's theorem, this is equal | |
| to the number of node independent paths (paths that share no nodes other | |
| than source and target). Which is what we compute in this function. | |
| This algorithm is a fast approximation that gives an strict lower | |
| bound on the actual number of node independent paths between two nodes [1]_. | |
| It works for both directed and undirected graphs. | |
| Parameters | |
| ---------- | |
| G : NetworkX graph | |
| source : node | |
| Starting node for node connectivity | |
| target : node | |
| Ending node for node connectivity | |
| cutoff : integer | |
| Maximum node connectivity to consider. If None, the minimum degree | |
| of source or target is used as a cutoff. Default value None. | |
| Returns | |
| ------- | |
| k: integer | |
| pairwise node connectivity | |
| Examples | |
| -------- | |
| >>> # Platonic octahedral graph has node connectivity 4 | |
| >>> # for each non adjacent node pair | |
| >>> from networkx.algorithms import approximation as approx | |
| >>> G = nx.octahedral_graph() | |
| >>> approx.local_node_connectivity(G, 0, 5) | |
| 4 | |
| Notes | |
| ----- | |
| This algorithm [1]_ finds node independents paths between two nodes by | |
| computing their shortest path using BFS, marking the nodes of the path | |
| found as 'used' and then searching other shortest paths excluding the | |
| nodes marked as used until no more paths exist. It is not exact because | |
| a shortest path could use nodes that, if the path were longer, may belong | |
| to two different node independent paths. Thus it only guarantees an | |
| strict lower bound on node connectivity. | |
| Note that the authors propose a further refinement, losing accuracy and | |
| gaining speed, which is not implemented yet. | |
| See also | |
| -------- | |
| all_pairs_node_connectivity | |
| node_connectivity | |
| References | |
| ---------- | |
| .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for | |
| Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 | |
| http://eclectic.ss.uci.edu/~drwhite/working.pdf | |
| """ | |
| if target == source: | |
| raise nx.NetworkXError("source and target have to be different nodes.") | |
| # Maximum possible node independent paths | |
| if G.is_directed(): | |
| possible = min(G.out_degree(source), G.in_degree(target)) | |
| else: | |
| possible = min(G.degree(source), G.degree(target)) | |
| K = 0 | |
| if not possible: | |
| return K | |
| if cutoff is None: | |
| cutoff = float("inf") | |
| exclude = set() | |
| for i in range(min(possible, cutoff)): | |
| try: | |
| path = _bidirectional_shortest_path(G, source, target, exclude) | |
| exclude.update(set(path)) | |
| K += 1 | |
| except nx.NetworkXNoPath: | |
| break | |
| return K | |
| def node_connectivity(G, s=None, t=None): | |
| r"""Returns an approximation for node connectivity for a graph or digraph G. | |
| Node connectivity is equal to the minimum number of nodes that | |
| must be removed to disconnect G or render it trivial. By Menger's theorem, | |
| this is equal to the number of node independent paths (paths that | |
| share no nodes other than source and target). | |
| If source and target nodes are provided, this function returns the | |
| local node connectivity: the minimum number of nodes that must be | |
| removed to break all paths from source to target in G. | |
| This algorithm is based on a fast approximation that gives an strict lower | |
| bound on the actual number of node independent paths between two nodes [1]_. | |
| It works for both directed and undirected graphs. | |
| Parameters | |
| ---------- | |
| G : NetworkX graph | |
| Undirected graph | |
| s : node | |
| Source node. Optional. Default value: None. | |
| t : node | |
| Target node. Optional. Default value: None. | |
| Returns | |
| ------- | |
| K : integer | |
| Node connectivity of G, or local node connectivity if source | |
| and target are provided. | |
| Examples | |
| -------- | |
| >>> # Platonic octahedral graph is 4-node-connected | |
| >>> from networkx.algorithms import approximation as approx | |
| >>> G = nx.octahedral_graph() | |
| >>> approx.node_connectivity(G) | |
| 4 | |
| Notes | |
| ----- | |
| This algorithm [1]_ finds node independents paths between two nodes by | |
| computing their shortest path using BFS, marking the nodes of the path | |
| found as 'used' and then searching other shortest paths excluding the | |
| nodes marked as used until no more paths exist. It is not exact because | |
| a shortest path could use nodes that, if the path were longer, may belong | |
| to two different node independent paths. Thus it only guarantees an | |
| strict lower bound on node connectivity. | |
| See also | |
| -------- | |
| all_pairs_node_connectivity | |
| local_node_connectivity | |
| References | |
| ---------- | |
| .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for | |
| Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 | |
| http://eclectic.ss.uci.edu/~drwhite/working.pdf | |
| """ | |
| if (s is not None and t is None) or (s is None and t is not None): | |
| raise nx.NetworkXError("Both source and target must be specified.") | |
| # Local node connectivity | |
| if s is not None and t is not None: | |
| 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") | |
| return local_node_connectivity(G, s, t) | |
| # Global node connectivity | |
| if G.is_directed(): | |
| connected_func = nx.is_weakly_connected | |
| iter_func = itertools.permutations | |
| def neighbors(v): | |
| return itertools.chain(G.predecessors(v), G.successors(v)) | |
| else: | |
| connected_func = nx.is_connected | |
| iter_func = itertools.combinations | |
| neighbors = G.neighbors | |
| if not connected_func(G): | |
| return 0 | |
| # Choose a node with minimum degree | |
| v, minimum_degree = min(G.degree(), key=itemgetter(1)) | |
| # Node connectivity is bounded by minimum degree | |
| K = minimum_degree | |
| # compute local node connectivity with all non-neighbors nodes | |
| # and store the minimum | |
| for w in set(G) - set(neighbors(v)) - {v}: | |
| K = min(K, local_node_connectivity(G, v, w, cutoff=K)) | |
| # Same for non adjacent pairs of neighbors of v | |
| for x, y in iter_func(neighbors(v), 2): | |
| if y not in G[x] and x != y: | |
| K = min(K, local_node_connectivity(G, x, y, cutoff=K)) | |
| return K | |
| def all_pairs_node_connectivity(G, nbunch=None, cutoff=None): | |
| """Compute node connectivity between all pairs of nodes. | |
| Pairwise or local node connectivity between two distinct and nonadjacent | |
| nodes is the minimum number of nodes that must be removed (minimum | |
| separating cutset) to disconnect them. By Menger's theorem, this is equal | |
| to the number of node independent paths (paths that share no nodes other | |
| than source and target). Which is what we compute in this function. | |
| This algorithm is a fast approximation that gives an strict lower | |
| bound on the actual number of node independent paths between two nodes [1]_. | |
| It works for both directed and undirected graphs. | |
| Parameters | |
| ---------- | |
| G : NetworkX graph | |
| nbunch: container | |
| Container of nodes. If provided node connectivity will be computed | |
| only over pairs of nodes in nbunch. | |
| cutoff : integer | |
| Maximum node connectivity to consider. If None, the minimum degree | |
| of source or target is used as a cutoff in each pair of nodes. | |
| Default value None. | |
| Returns | |
| ------- | |
| K : dictionary | |
| Dictionary, keyed by source and target, of pairwise node connectivity | |
| Examples | |
| -------- | |
| A 3 node cycle with one extra node attached has connectivity 2 between all | |
| nodes in the cycle and connectivity 1 between the extra node and the rest: | |
| >>> G = nx.cycle_graph(3) | |
| >>> G.add_edge(2, 3) | |
| >>> import pprint # for nice dictionary formatting | |
| >>> pprint.pprint(nx.all_pairs_node_connectivity(G)) | |
| {0: {1: 2, 2: 2, 3: 1}, | |
| 1: {0: 2, 2: 2, 3: 1}, | |
| 2: {0: 2, 1: 2, 3: 1}, | |
| 3: {0: 1, 1: 1, 2: 1}} | |
| See Also | |
| -------- | |
| local_node_connectivity | |
| node_connectivity | |
| References | |
| ---------- | |
| .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for | |
| Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 | |
| http://eclectic.ss.uci.edu/~drwhite/working.pdf | |
| """ | |
| if nbunch is None: | |
| nbunch = G | |
| else: | |
| nbunch = set(nbunch) | |
| directed = G.is_directed() | |
| if directed: | |
| iter_func = itertools.permutations | |
| else: | |
| iter_func = itertools.combinations | |
| all_pairs = {n: {} for n in nbunch} | |
| for u, v in iter_func(nbunch, 2): | |
| k = local_node_connectivity(G, u, v, cutoff=cutoff) | |
| all_pairs[u][v] = k | |
| if not directed: | |
| all_pairs[v][u] = k | |
| return all_pairs | |
| def _bidirectional_shortest_path(G, source, target, exclude): | |
| """Returns shortest path between source and target ignoring nodes in the | |
| container 'exclude'. | |
| Parameters | |
| ---------- | |
| G : NetworkX graph | |
| source : node | |
| Starting node for path | |
| target : node | |
| Ending node for path | |
| exclude: container | |
| Container for nodes to exclude from the search for shortest paths | |
| Returns | |
| ------- | |
| path: list | |
| Shortest path between source and target ignoring nodes in 'exclude' | |
| Raises | |
| ------ | |
| NetworkXNoPath | |
| If there is no path or if nodes are adjacent and have only one path | |
| between them | |
| Notes | |
| ----- | |
| This function and its helper are originally from | |
| networkx.algorithms.shortest_paths.unweighted and are modified to | |
| accept the extra parameter 'exclude', which is a container for nodes | |
| already used in other paths that should be ignored. | |
| References | |
| ---------- | |
| .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for | |
| Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 | |
| http://eclectic.ss.uci.edu/~drwhite/working.pdf | |
| """ | |
| # call helper to do the real work | |
| results = _bidirectional_pred_succ(G, source, target, exclude) | |
| pred, succ, w = results | |
| # build path from pred+w+succ | |
| path = [] | |
| # from source to w | |
| while w is not None: | |
| path.append(w) | |
| w = pred[w] | |
| path.reverse() | |
| # from w to target | |
| w = succ[path[-1]] | |
| while w is not None: | |
| path.append(w) | |
| w = succ[w] | |
| return path | |
| def _bidirectional_pred_succ(G, source, target, exclude): | |
| # does BFS from both source and target and meets in the middle | |
| # excludes nodes in the container "exclude" from the search | |
| # handle either directed or undirected | |
| if G.is_directed(): | |
| Gpred = G.predecessors | |
| Gsucc = G.successors | |
| else: | |
| Gpred = G.neighbors | |
| Gsucc = G.neighbors | |
| # predecessor and successors in search | |
| pred = {source: None} | |
| succ = {target: None} | |
| # initialize fringes, start with forward | |
| forward_fringe = [source] | |
| reverse_fringe = [target] | |
| level = 0 | |
| while forward_fringe and reverse_fringe: | |
| # Make sure that we iterate one step forward and one step backwards | |
| # thus source and target will only trigger "found path" when they are | |
| # adjacent and then they can be safely included in the container 'exclude' | |
| level += 1 | |
| if level % 2 != 0: | |
| this_level = forward_fringe | |
| forward_fringe = [] | |
| for v in this_level: | |
| for w in Gsucc(v): | |
| if w in exclude: | |
| continue | |
| if w not in pred: | |
| forward_fringe.append(w) | |
| pred[w] = v | |
| if w in succ: | |
| return pred, succ, w # found path | |
| else: | |
| this_level = reverse_fringe | |
| reverse_fringe = [] | |
| for v in this_level: | |
| for w in Gpred(v): | |
| if w in exclude: | |
| continue | |
| if w not in succ: | |
| succ[w] = v | |
| reverse_fringe.append(w) | |
| if w in pred: | |
| return pred, succ, w # found path | |
| raise nx.NetworkXNoPath(f"No path between {source} and {target}.") | |