193 lines
6 KiB
Python
193 lines
6 KiB
Python
|
"""
|
||
|
Provides functions for finding and testing for locally `(k, l)`-connected
|
||
|
graphs.
|
||
|
|
||
|
"""
|
||
|
import copy
|
||
|
import networkx as nx
|
||
|
|
||
|
__all__ = ["kl_connected_subgraph", "is_kl_connected"]
|
||
|
|
||
|
|
||
|
def kl_connected_subgraph(G, k, l, low_memory=False, same_as_graph=False):
|
||
|
"""Returns the maximum locally `(k, l)`-connected subgraph of `G`.
|
||
|
|
||
|
A graph is locally `(k, l)`-connected if for each edge `(u, v)` in the
|
||
|
graph there are at least `l` edge-disjoint paths of length at most `k`
|
||
|
joining `u` to `v`.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
G : NetworkX graph
|
||
|
The graph in which to find a maximum locally `(k, l)`-connected
|
||
|
subgraph.
|
||
|
|
||
|
k : integer
|
||
|
The maximum length of paths to consider. A higher number means a looser
|
||
|
connectivity requirement.
|
||
|
|
||
|
l : integer
|
||
|
The number of edge-disjoint paths. A higher number means a stricter
|
||
|
connectivity requirement.
|
||
|
|
||
|
low_memory : bool
|
||
|
If this is True, this function uses an algorithm that uses slightly
|
||
|
more time but less memory.
|
||
|
|
||
|
same_as_graph : bool
|
||
|
If True then return a tuple of the form `(H, is_same)`,
|
||
|
where `H` is the maximum locally `(k, l)`-connected subgraph and
|
||
|
`is_same` is a Boolean representing whether `G` is locally `(k,
|
||
|
l)`-connected (and hence, whether `H` is simply a copy of the input
|
||
|
graph `G`).
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
NetworkX graph or two-tuple
|
||
|
If `same_as_graph` is True, then this function returns a
|
||
|
two-tuple as described above. Otherwise, it returns only the maximum
|
||
|
locally `(k, l)`-connected subgraph.
|
||
|
|
||
|
See also
|
||
|
--------
|
||
|
is_kl_connected
|
||
|
|
||
|
References
|
||
|
----------
|
||
|
.. [1]: Chung, Fan and Linyuan Lu. "The Small World Phenomenon in Hybrid
|
||
|
Power Law Graphs." *Complex Networks*. Springer Berlin Heidelberg,
|
||
|
2004. 89--104.
|
||
|
|
||
|
"""
|
||
|
H = copy.deepcopy(G) # subgraph we construct by removing from G
|
||
|
|
||
|
graphOK = True
|
||
|
deleted_some = True # hack to start off the while loop
|
||
|
while deleted_some:
|
||
|
deleted_some = False
|
||
|
# We use `for edge in list(H.edges()):` instead of
|
||
|
# `for edge in H.edges():` because we edit the graph `H` in
|
||
|
# the loop. Hence using an iterator will result in
|
||
|
# `RuntimeError: dictionary changed size during iteration`
|
||
|
for edge in list(H.edges()):
|
||
|
(u, v) = edge
|
||
|
# Get copy of graph needed for this search
|
||
|
if low_memory:
|
||
|
verts = {u, v}
|
||
|
for i in range(k):
|
||
|
for w in verts.copy():
|
||
|
verts.update(G[w])
|
||
|
G2 = G.subgraph(verts).copy()
|
||
|
else:
|
||
|
G2 = copy.deepcopy(G)
|
||
|
###
|
||
|
path = [u, v]
|
||
|
cnt = 0
|
||
|
accept = 0
|
||
|
while path:
|
||
|
cnt += 1 # Found a path
|
||
|
if cnt >= l:
|
||
|
accept = 1
|
||
|
break
|
||
|
# record edges along this graph
|
||
|
prev = u
|
||
|
for w in path:
|
||
|
if prev != w:
|
||
|
G2.remove_edge(prev, w)
|
||
|
prev = w
|
||
|
# path = shortest_path(G2, u, v, k) # ??? should "Cutoff" be k+1?
|
||
|
try:
|
||
|
path = nx.shortest_path(G2, u, v) # ??? should "Cutoff" be k+1?
|
||
|
except nx.NetworkXNoPath:
|
||
|
path = False
|
||
|
# No Other Paths
|
||
|
if accept == 0:
|
||
|
H.remove_edge(u, v)
|
||
|
deleted_some = True
|
||
|
if graphOK:
|
||
|
graphOK = False
|
||
|
# We looked through all edges and removed none of them.
|
||
|
# So, H is the maximal (k,l)-connected subgraph of G
|
||
|
if same_as_graph:
|
||
|
return (H, graphOK)
|
||
|
return H
|
||
|
|
||
|
|
||
|
def is_kl_connected(G, k, l, low_memory=False):
|
||
|
"""Returns True if and only if `G` is locally `(k, l)`-connected.
|
||
|
|
||
|
A graph is locally `(k, l)`-connected if for each edge `(u, v)` in the
|
||
|
graph there are at least `l` edge-disjoint paths of length at most `k`
|
||
|
joining `u` to `v`.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
G : NetworkX graph
|
||
|
The graph to test for local `(k, l)`-connectedness.
|
||
|
|
||
|
k : integer
|
||
|
The maximum length of paths to consider. A higher number means a looser
|
||
|
connectivity requirement.
|
||
|
|
||
|
l : integer
|
||
|
The number of edge-disjoint paths. A higher number means a stricter
|
||
|
connectivity requirement.
|
||
|
|
||
|
low_memory : bool
|
||
|
If this is True, this function uses an algorithm that uses slightly
|
||
|
more time but less memory.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
bool
|
||
|
Whether the graph is locally `(k, l)`-connected subgraph.
|
||
|
|
||
|
See also
|
||
|
--------
|
||
|
kl_connected_subgraph
|
||
|
|
||
|
References
|
||
|
----------
|
||
|
.. [1]: Chung, Fan and Linyuan Lu. "The Small World Phenomenon in Hybrid
|
||
|
Power Law Graphs." *Complex Networks*. Springer Berlin Heidelberg,
|
||
|
2004. 89--104.
|
||
|
|
||
|
"""
|
||
|
graphOK = True
|
||
|
for edge in G.edges():
|
||
|
(u, v) = edge
|
||
|
# Get copy of graph needed for this search
|
||
|
if low_memory:
|
||
|
verts = {u, v}
|
||
|
for i in range(k):
|
||
|
[verts.update(G.neighbors(w)) for w in verts.copy()]
|
||
|
G2 = G.subgraph(verts)
|
||
|
else:
|
||
|
G2 = copy.deepcopy(G)
|
||
|
###
|
||
|
path = [u, v]
|
||
|
cnt = 0
|
||
|
accept = 0
|
||
|
while path:
|
||
|
cnt += 1 # Found a path
|
||
|
if cnt >= l:
|
||
|
accept = 1
|
||
|
break
|
||
|
# record edges along this graph
|
||
|
prev = u
|
||
|
for w in path:
|
||
|
if w != prev:
|
||
|
G2.remove_edge(prev, w)
|
||
|
prev = w
|
||
|
# path = shortest_path(G2, u, v, k) # ??? should "Cutoff" be k+1?
|
||
|
try:
|
||
|
path = nx.shortest_path(G2, u, v) # ??? should "Cutoff" be k+1?
|
||
|
except nx.NetworkXNoPath:
|
||
|
path = False
|
||
|
# No Other Paths
|
||
|
if accept == 0:
|
||
|
graphOK = False
|
||
|
break
|
||
|
# return status
|
||
|
return graphOK
|