blob: bf6f272a541852a9eb96fb67647d4f7390abdd33 [file] [log] [blame]
# -*- coding: utf-8 -*-
"""
Flow based connectivity algorithms
"""
import itertools
import networkx as nx
__author__ = '\n'.join(['Jordi Torrents <jtorrents@milnou.net>'])
__all__ = [ 'average_node_connectivity',
'local_node_connectivity',
'node_connectivity',
'local_edge_connectivity',
'edge_connectivity',
'all_pairs_node_connectivity_matrix',
'dominating_set',
]
def average_node_connectivity(G):
r"""Returns the average connectivity of a graph G.
The average connectivity `\bar{\kappa}` of a graph G is the average
of local node connectivity over all pairs of nodes of G [1]_ .
.. math::
\bar{\kappa}(G) = \frac{\sum_{u,v} \kappa_{G}(u,v)}{{n \choose 2}}
Parameters
----------
G : NetworkX graph
Undirected graph
Returns
-------
K : float
Average node connectivity
See also
--------
local_node_connectivity
node_connectivity
local_edge_connectivity
edge_connectivity
max_flow
ford_fulkerson
References
----------
.. [1] Beineke, L., O. Oellermann, and R. Pippert (2002). The average
connectivity of a graph. Discrete mathematics 252(1-3), 31-45.
http://www.sciencedirect.com/science/article/pii/S0012365X01001807
"""
if G.is_directed():
iter_func = itertools.permutations
else:
iter_func = itertools.combinations
H, mapping = _aux_digraph_node_connectivity(G)
num = 0.
den = 0.
for u,v in iter_func(G, 2):
den += 1
num += local_node_connectivity(G, u, v, aux_digraph=H, mapping=mapping)
if den == 0: # Null Graph
return 0
return num/den
def _aux_digraph_node_connectivity(G):
r""" Creates a directed graph D from an undirected graph G to compute flow
based node connectivity.
For an undirected graph G having `n` nodes and `m` edges we derive a
directed graph D with 2n nodes and 2m+n arcs by replacing each
original node `v` with two nodes `vA`,`vB` linked by an (internal)
arc in D. Then for each edge (u,v) in G we add two arcs (uB,vA)
and (vB,uA) in D. Finally we set the attribute capacity = 1 for each
arc in D [1].
For a directed graph having `n` nodes and `m` arcs we derive a
directed graph D with 2n nodes and m+n arcs by replacing each
original node `v` with two nodes `vA`,`vB` linked by an (internal)
arc `(vA,vB)` in D. Then for each arc (u,v) in G we add one arc (uB,vA)
in D. Finally we set the attribute capacity = 1 for each arc in D.
References
----------
.. [1] Kammer, Frank and Hanjo Taubig. Graph Connectivity. in Brandes and
Erlebach, 'Network Analysis: Methodological Foundations', Lecture
Notes in Computer Science, Volume 3418, Springer-Verlag, 2005.
http://www.informatik.uni-augsburg.de/thi/personen/kammer/Graph_Connectivity.pdf
"""
directed = G.is_directed()
mapping = {}
D = nx.DiGraph()
for i,node in enumerate(G):
mapping[node] = i
D.add_node('%dA' % i,id=node)
D.add_node('%dB' % i,id=node)
D.add_edge('%dA' % i, '%dB' % i, capacity=1)
edges = []
for (source, target) in G.edges():
edges.append(('%sB' % mapping[source], '%sA' % mapping[target]))
if not directed:
edges.append(('%sB' % mapping[target], '%sA' % mapping[source]))
D.add_edges_from(edges, capacity=1)
return D, mapping
def local_node_connectivity(G, s, t, aux_digraph=None, mapping=None):
r"""Computes local node connectivity for nodes s and t.
Local node connectivity for two non adjacent nodes s and t is the
minimum number of nodes that must be removed (along with their incident
edges) to disconnect them.
This is a flow based implementation of node connectivity. We compute the
maximum flow on an auxiliary digraph build from the original input
graph (see below for details). This is equal to the local node
connectivity because the value of a maximum s-t-flow is equal to the
capacity of a minimum s-t-cut (Ford and Fulkerson theorem) [1]_ .
Parameters
----------
G : NetworkX graph
Undirected graph
s : node
Source node
t : node
Target node
aux_digraph : NetworkX DiGraph (default=None)
Auxiliary digraph to compute flow based node connectivity. If None
the auxiliary digraph is build.
mapping : dict (default=None)
Dictionary with a mapping of node names in G and in the auxiliary digraph.
Returns
-------
K : integer
local node connectivity for nodes s and t
Examples
--------
>>> # Platonic icosahedral graph has node connectivity 5
>>> # for each non adjacent node pair
>>> G = nx.icosahedral_graph()
>>> nx.local_node_connectivity(G,0,6)
5
Notes
-----
This is a flow based implementation of node connectivity. We compute the
maximum flow using the Ford and Fulkerson algorithm on an auxiliary digraph
build from the original input graph:
For an undirected graph G having `n` nodes and `m` edges we derive a
directed graph D with 2n nodes and 2m+n arcs by replacing each
original node `v` with two nodes `v_A`, `v_B` linked by an (internal)
arc in `D`. Then for each edge (`u`, `v`) in G we add two arcs
(`u_B`, `v_A`) and (`v_B`, `u_A`) in `D`. Finally we set the attribute
capacity = 1 for each arc in `D` [1]_ .
For a directed graph G having `n` nodes and `m` arcs we derive a
directed graph `D` with `2n` nodes and `m+n` arcs by replacing each
original node `v` with two nodes `v_A`, `v_B` linked by an (internal)
arc `(v_A, v_B)` in D. Then for each arc `(u,v)` in G we add one arc
`(u_B,v_A)` in `D`. Finally we set the attribute capacity = 1 for
each arc in `D`.
This is equal to the local node connectivity because the value of
a maximum s-t-flow is equal to the capacity of a minimum s-t-cut (Ford
and Fulkerson theorem).
See also
--------
node_connectivity
all_pairs_node_connectivity_matrix
local_edge_connectivity
edge_connectivity
max_flow
ford_fulkerson
References
----------
.. [1] Kammer, Frank and Hanjo Taubig. Graph Connectivity. in Brandes and
Erlebach, 'Network Analysis: Methodological Foundations', Lecture
Notes in Computer Science, Volume 3418, Springer-Verlag, 2005.
http://www.informatik.uni-augsburg.de/thi/personen/kammer/Graph_Connectivity.pdf
"""
if aux_digraph is None or mapping is None:
H, mapping = _aux_digraph_node_connectivity(G)
else:
H = aux_digraph
return nx.max_flow(H,'%sB' % mapping[s], '%sA' % mapping[t])
def node_connectivity(G, s=None, t=None):
r"""Returns 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. 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 is a flow based implementation. The algorithm is based in
solving a number of max-flow problems (ie local st-node connectivity,
see local_node_connectivity) to determine the capacity of the
minimum cut on an auxiliary directed network that corresponds to the
minimum node cut of G. It handles both directed and undirected graphs.
Parameters
----------
G : NetworkX graph
Undirected graph
s : node
Source node. Optional (default=None)
t : node
Target node. Optional (default=None)
Returns
-------
K : integer
Node connectivity of G, or local node connectivity if source
and target were provided
Examples
--------
>>> # Platonic icosahedral graph is 5-node-connected
>>> G = nx.icosahedral_graph()
>>> nx.node_connectivity(G)
5
>>> nx.node_connectivity(G, 3, 7)
5
Notes
-----
This is a flow based implementation of node connectivity. The
algorithm works by solving `O((n-\delta-1+\delta(\delta-1)/2)` max-flow
problems on an auxiliary digraph. Where `\delta` is the minimum degree
of G. For details about the auxiliary digraph and the computation of
local node connectivity see local_node_connectivity.
This implementation is based on algorithm 11 in [1]_. We use the Ford
and Fulkerson algorithm to compute max flow (see ford_fulkerson).
See also
--------
local_node_connectivity
all_pairs_node_connectivity_matrix
local_edge_connectivity
edge_connectivity
max_flow
ford_fulkerson
References
----------
.. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
"""
# Local node connectivity
if s is not None and t is not None:
if s not in G:
raise nx.NetworkXError('node %s not in graph' % s)
if t not in G:
raise nx.NetworkXError('node %s not in graph' % t)
return local_node_connectivity(G, s, t)
# Global node connectivity
if G.is_directed():
if not nx.is_weakly_connected(G):
return 0
iter_func = itertools.permutations
# I think that it is necessary to consider both predecessors
# and successors for directed graphs
def neighbors(v):
return itertools.chain.from_iterable([G.predecessors_iter(v),
G.successors_iter(v)])
else:
if not nx.is_connected(G):
return 0
iter_func = itertools.combinations
neighbors = G.neighbors_iter
# Initial guess \kappa = n - 1
K = G.order()-1
deg = G.degree()
min_deg = min(deg.values())
v = next(n for n,d in deg.items() if d==min_deg)
# Reuse the auxiliary digraph
H, mapping = _aux_digraph_node_connectivity(G)
# compute local node connectivity with all non-neighbors nodes
for w in set(G) - set(neighbors(v)) - set([v]):
K = min(K, local_node_connectivity(G, v, w,
aux_digraph=H, mapping=mapping))
# Same for non adjacent pairs of neighbors of v
for x,y in iter_func(neighbors(v), 2):
if y in G[x]: continue
K = min(K, local_node_connectivity(G, x, y,
aux_digraph=H, mapping=mapping))
return K
def all_pairs_node_connectivity_matrix(G):
"""Return a numpy 2d ndarray with node connectivity between all pairs
of nodes.
Parameters
----------
G : NetworkX graph
Undirected graph
Returns
-------
K : 2d numpy ndarray
node connectivity between all pairs of nodes.
See also
--------
local_node_connectivity
node_connectivity
local_edge_connectivity
edge_connectivity
max_flow
ford_fulkerson
"""
try:
import numpy
except ImportError:
raise ImportError(\
"all_pairs_node_connectivity_matrix() requires NumPy")
n = G.order()
M = numpy.zeros((n, n), dtype=int)
# Create auxiliary Digraph
D, mapping = _aux_digraph_node_connectivity(G)
if G.is_directed():
for u, v in itertools.permutations(G, 2):
K = local_node_connectivity(G, u, v, aux_digraph=D, mapping=mapping)
M[mapping[u],mapping[v]] = K
else:
for u, v in itertools.combinations(G, 2):
K = local_node_connectivity(G, u, v, aux_digraph=D, mapping=mapping)
M[mapping[u],mapping[v]] = M[mapping[v],mapping[u]] = K
return M
def _aux_digraph_edge_connectivity(G):
"""Auxiliary digraph for computing flow based edge connectivity
If the input graph is undirected, we replace each edge (u,v) with
two reciprocal arcs (u,v) and (v,u) and then we set the attribute
'capacity' for each arc to 1. If the input graph is directed we simply
add the 'capacity' attribute. Part of algorithm 1 in [1]_ .
References
----------
.. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms. (this is a
chapter, look for the reference of the book).
http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
"""
if G.is_directed():
if nx.get_edge_attributes(G, 'capacity'):
return G
D = G.copy()
capacity = dict((e,1) for e in D.edges())
nx.set_edge_attributes(D, 'capacity', capacity)
return D
else:
D = G.to_directed()
capacity = dict((e,1) for e in D.edges())
nx.set_edge_attributes(D, 'capacity', capacity)
return D
def local_edge_connectivity(G, u, v, aux_digraph=None):
r"""Returns local edge connectivity for nodes s and t in G.
Local edge connectivity for two nodes s and t is the minimum number
of edges that must be removed to disconnect them.
This is a flow based implementation of edge connectivity. We compute the
maximum flow on an auxiliary digraph build from the original
network (see below for details). This is equal to the local edge
connectivity because the value of a maximum s-t-flow is equal to the
capacity of a minimum s-t-cut (Ford and Fulkerson theorem) [1]_ .
Parameters
----------
G : NetworkX graph
Undirected or directed graph
s : node
Source node
t : node
Target node
aux_digraph : NetworkX DiGraph (default=None)
Auxiliary digraph to compute flow based edge connectivity. If None
the auxiliary digraph is build.
Returns
-------
K : integer
local edge connectivity for nodes s and t
Examples
--------
>>> # Platonic icosahedral graph has edge connectivity 5
>>> # for each non adjacent node pair
>>> G = nx.icosahedral_graph()
>>> nx.local_edge_connectivity(G,0,6)
5
Notes
-----
This is a flow based implementation of edge connectivity. We compute the
maximum flow using the Ford and Fulkerson algorithm on an auxiliary digraph
build from the original graph:
If the input graph is undirected, we replace each edge (u,v) with
two reciprocal arcs `(u,v)` and `(v,u)` and then we set the attribute
'capacity' for each arc to 1. If the input graph is directed we simply
add the 'capacity' attribute. This is an implementation of algorithm 1
in [1]_.
The maximum flow in the auxiliary network is equal to the local edge
connectivity because the value of a maximum s-t-flow is equal to the
capacity of a minimum s-t-cut (Ford and Fulkerson theorem).
See also
--------
local_node_connectivity
node_connectivity
edge_connectivity
max_flow
ford_fulkerson
References
----------
.. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
"""
if aux_digraph is None:
H = _aux_digraph_edge_connectivity(G)
else:
H = aux_digraph
return nx.max_flow(H, u, v)
def edge_connectivity(G, s=None, t=None):
r"""Returns the edge connectivity of the graph or digraph G.
The edge connectivity is equal to the minimum number of edges that
must be removed to disconnect G or render it trivial. If source
and target nodes are provided, this function returns the local edge
connectivity: the minimum number of edges that must be removed to
break all paths from source to target in G.
This is a flow based implementation. The algorithm is based in solving
a number of max-flow problems (ie local st-edge connectivity, see
local_edge_connectivity) to determine the capacity of the minimum
cut on an auxiliary directed network that corresponds to the minimum
edge cut of G. It handles both directed and undirected graphs.
Parameters
----------
G : NetworkX graph
Undirected or directed graph
s : node
Source node. Optional (default=None)
t : node
Target node. Optional (default=None)
Returns
-------
K : integer
Edge connectivity for G, or local edge connectivity if source
and target were provided
Examples
--------
>>> # Platonic icosahedral graph is 5-edge-connected
>>> G = nx.icosahedral_graph()
>>> nx.edge_connectivity(G)
5
Notes
-----
This is a flow based implementation of global edge connectivity. For
undirected graphs the algorithm works by finding a 'small' dominating
set of nodes of G (see algorithm 7 in [1]_ ) and computing local max flow
(see local_edge_connectivity) between an arbitrary node in the dominating
set and the rest of nodes in it. This is an implementation of
algorithm 6 in [1]_ .
For directed graphs, the algorithm does n calls to the max flow function.
This is an implementation of algorithm 8 in [1]_ . We use the Ford and
Fulkerson algorithm to compute max flow (see ford_fulkerson).
See also
--------
local_node_connectivity
node_connectivity
local_edge_connectivity
max_flow
ford_fulkerson
References
----------
.. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
"""
# Local edge connectivity
if s is not None and t is not None:
if s not in G:
raise nx.NetworkXError('node %s not in graph' % s)
if t not in G:
raise nx.NetworkXError('node %s not in graph' % t)
return local_edge_connectivity(G, s, t)
# Global edge connectivity
if G.is_directed():
# Algorithm 8 in [1]
if not nx.is_weakly_connected(G):
return 0
# initial value for lambda is min degree (\delta(G))
L = min(G.degree().values())
# reuse auxiliary digraph
H = _aux_digraph_edge_connectivity(G)
nodes = G.nodes()
n = len(nodes)
for i in range(n):
try:
L = min(L, local_edge_connectivity(G, nodes[i],
nodes[i+1], aux_digraph=H))
except IndexError: # last node!
L = min(L, local_edge_connectivity(G, nodes[i],
nodes[0], aux_digraph=H))
return L
else: # undirected
# Algorithm 6 in [1]
if not nx.is_connected(G):
return 0
# initial value for lambda is min degree (\delta(G))
L = min(G.degree().values())
# reuse auxiliary digraph
H = _aux_digraph_edge_connectivity(G)
# A dominating set is \lambda-covering
# We need a dominating set with at least two nodes
for node in G:
D = dominating_set(G, start_with=node)
v = D.pop()
if D: break
else:
# in complete graphs the dominating sets will always be of one node
# thus we return min degree
return L
for w in D:
L = min(L, local_edge_connectivity(G, v, w, aux_digraph=H))
return L
def dominating_set(G, start_with=None):
# Algorithm 7 in [1]
all_nodes = set(G)
if start_with is None:
v = set(G).pop() # pick a node
else:
if start_with not in G:
raise nx.NetworkXError('node %s not in G' % start_with)
v = start_with
D = set([v])
ND = set([nbr for nbr in G[v]])
other = all_nodes - ND - D
while other:
w = other.pop()
D.add(w)
ND.update([nbr for nbr in G[w] if nbr not in D])
other = all_nodes - ND - D
return D
def is_dominating_set(G, nbunch):
# Proposed by Dan on the mailing list
allnodes=set(G)
testset=set(n for n in nbunch if n in G)
nbrs=set()
for n in testset:
nbrs.update(G[n])
if nbrs - allnodes: # some nodes left--not dominating
return False
else:
return True