blob: 33d882d33f4609c71d6e4458b84a38d7310b4def [file] [log] [blame]
# -*- coding: utf-8 -*-
"""Swap edges in a graph.
"""
# Copyright (C) 2004-2012 by
# Aric Hagberg <hagberg@lanl.gov>
# Dan Schult <dschult@colgate.edu>
# Pieter Swart <swart@lanl.gov>
# All rights reserved.
# BSD license.
import math
import random
import networkx as nx
__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)',
'Pieter Swart (swart@lanl.gov)',
'Dan Schult (dschult@colgate.edu)'
'Joel Miller (joel.c.miller.research@gmail.com)'
'Ben Edwards'])
__all__ = ['double_edge_swap',
'connected_double_edge_swap']
def double_edge_swap(G, nswap=1, max_tries=100):
"""Swap two edges in the graph while keeping the node degrees fixed.
A double-edge swap removes two randomly chosen edges u-v and x-y
and creates the new edges u-x and v-y::
u--v u v
becomes | |
x--y x y
If either the edge u-x or v-y already exist no swap is performed
and another attempt is made to find a suitable edge pair.
Parameters
----------
G : graph
An undirected graph
nswap : integer (optional, default=1)
Number of double-edge swaps to perform
max_tries : integer (optional)
Maximum number of attempts to swap edges
Returns
-------
G : graph
The graph after double edge swaps.
Notes
-----
Does not enforce any connectivity constraints.
The graph G is modified in place.
"""
if G.is_directed():
raise nx.NetworkXError(\
"double_edge_swap() not defined for directed graphs.")
if nswap>max_tries:
raise nx.NetworkXError("Number of swaps > number of tries allowed.")
if len(G) < 4:
raise nx.NetworkXError("Graph has less than four nodes.")
# Instead of choosing uniformly at random from a generated edge list,
# this algorithm chooses nonuniformly from the set of nodes with
# probability weighted by degree.
n=0
swapcount=0
keys,degrees=zip(*G.degree().items()) # keys, degree
cdf=nx.utils.cumulative_distribution(degrees) # cdf of degree
while swapcount < nswap:
# if random.random() < 0.5: continue # trick to avoid periodicities?
# pick two random edges without creating edge list
# choose source node indices from discrete distribution
(ui,xi)=nx.utils.discrete_sequence(2,cdistribution=cdf)
if ui==xi:
continue # same source, skip
u=keys[ui] # convert index to label
x=keys[xi]
# choose target uniformly from neighbors
v=random.choice(list(G[u]))
y=random.choice(list(G[x]))
if v==y:
continue # same target, skip
if (x not in G[u]) and (y not in G[v]): # don't create parallel edges
G.add_edge(u,x)
G.add_edge(v,y)
G.remove_edge(u,v)
G.remove_edge(x,y)
swapcount+=1
if n >= max_tries:
e=('Maximum number of swap attempts (%s) exceeded '%n +
'before desired swaps achieved (%s).'%nswap)
raise nx.NetworkXAlgorithmError(e)
n+=1
return G
def connected_double_edge_swap(G, nswap=1):
"""Attempt nswap double-edge swaps in the graph G.
A double-edge swap removes two randomly chosen edges u-v and x-y
and creates the new edges u-x and v-y::
u--v u v
becomes | |
x--y x y
If either the edge u-x or v-y already exist no swap is performed so
the actual count of swapped edges is always <= nswap
Parameters
----------
G : graph
An undirected graph
nswap : integer (optional, default=1)
Number of double-edge swaps to perform
Returns
-------
G : int
The number of successful swaps
Notes
-----
The initial graph G must be connected, and the resulting graph is connected.
The graph G is modified in place.
References
----------
.. [1] C. Gkantsidis and M. Mihail and E. Zegura,
The Markov chain simulation method for generating connected
power law random graphs, 2003.
http://citeseer.ist.psu.edu/gkantsidis03markov.html
"""
import math
if not nx.is_connected(G):
raise nx.NetworkXError("Graph not connected")
if len(G) < 4:
raise nx.NetworkXError("Graph has less than four nodes.")
n=0
swapcount=0
deg=G.degree()
dk=list(deg.keys()) # Label key for nodes
cdf=nx.utils.cumulative_distribution(list(G.degree().values()))
window=1
while n < nswap:
wcount=0
swapped=[]
while wcount < window and n < nswap:
# Pick two random edges without creating edge list
# Choose source nodes from discrete degree distribution
(ui,xi)=nx.utils.discrete_sequence(2,cdistribution=cdf)
if ui==xi:
continue # same source, skip
u=dk[ui] # convert index to label
x=dk[xi]
# Choose targets uniformly from neighbors
v=random.choice(G.neighbors(u))
y=random.choice(G.neighbors(x)) #
if v==y: continue # same target, skip
if (not G.has_edge(u,x)) and (not G.has_edge(v,y)):
G.remove_edge(u,v)
G.remove_edge(x,y)
G.add_edge(u,x)
G.add_edge(v,y)
swapped.append((u,v,x,y))
swapcount+=1
n+=1
wcount+=1
if nx.is_connected(G):
window+=1
else:
# not connected, undo changes from previous window, decrease window
while swapped:
(u,v,x,y)=swapped.pop()
G.add_edge(u,v)
G.add_edge(x,y)
G.remove_edge(u,x)
G.remove_edge(v,y)
swapcount-=1
window = int(math.ceil(float(window)/2))
return swapcount