Skip to content

Instantly share code, notes, and snippets.

@cummins-orgs
Forked from gotgenes/edgeswap.py
Last active September 30, 2019 01:39
Show Gist options
  • Save cummins-orgs/6ad628c0f86555bcc995995bdb2a5394 to your computer and use it in GitHub Desktop.
Save cummins-orgs/6ad628c0f86555bcc995995bdb2a5394 to your computer and use it in GitHub Desktop.
Edge swap graph.
# Copyright (c) 2011-2012 Christopher D. Lasher
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
##########################################
# Matt Cummins Note (29th September 2019).
# Made a couple of minor edits to get this code working in 2019 networkx with Python 3
##########################################
class EdgeSwapGraph(nx.Graph):
"""An interaction graph which can produce a "random" graph from
itself by an iterative edge-swap technique that preserves the degree
distribution of the original graph.
"""
def randomize_by_edge_swaps(self, num_iterations):
"""Randomizes the graph by swapping edges in such a way that
preserves the degree distribution of the original graph.
The underlying idea stems from the following. Say we have this
original formation of edges in the original graph:
head1 head2
| |
| |
| |
tail1 tail2
Then we wish to swap the edges between these four nodes as one
of the two following possibilities:
head1 head2 head1---head2
\ /
X
/ \
tail1 tail2 tail1---tail2
We approach both by following through the first of the two
possibilities, but before committing to the edge creation, give
a chance that we flip the nodes `head1` and `tail1`.
See the following references for the algorithm:
- F. Viger and M. Latapy, "Efficient and Simple Generation of
Random Simple Connected Graphs with Prescribed Degree
Sequence," Computing and Combinatorics, 2005.
- M. Mihail and N.K. Vishnoi, "On Generating Graphs with
Prescribed Vertex Degrees for Complex Network Modeling,"
ARACNE 2002, 2002, pp. 1–11.
- R. Milo, N. Kashtan, S. Itzkovitz, M.E.J. Newman, and U.
Alon, "On the uniform generation of random graphs with
prescribed degree sequences," cond-mat/0312028, Dec. 2003.
:Parameters:
- `num_iterations`: the number of iterations for edge swapping
to perform; this value will be multiplied by the number of
edges in the graph to get the total number of iterations
"""
newgraph = self.copy()
edge_list = list(newgraph.edges())
num_edges = len(edge_list)
total_iterations = num_edges * num_iterations
for i in range(total_iterations):
rand_index1 = int(round(random.random() * (num_edges - 1)))
rand_index2 = int(round(random.random() * (num_edges - 1)))
original_edge1 = edge_list[rand_index1]
original_edge2 = edge_list[rand_index2]
head1, tail1 = original_edge1
head2, tail2 = original_edge2
# Flip a coin to see if we should swap head1 and tail1 for
# the connections
if random.random() >= 0.5:
head1, tail1 = tail1, head1
# The plan now is to pair head1 with tail2, and head2 with
# tail1
#
# To avoid self-loops in the graph, we have to check that,
# by pairing head1 with tail2 (respectively, head2 with
# tail1) that head1 and tail2 are not actually the same
# node. For example, suppose we have the edges (a, b) and
# (b, c) to swap.
#
# b
# / \
# a c
#
# We would have new edges (a, c) and (b, b) if we didn't do
# this check.
if head1 == tail2 or head2 == tail1:
continue
# Trying to avoid multiple edges between same pair of nodes;
# for example, suppose we had the following
#
# a c
# |* | | original edge pair being looked at
# | * |
# | *| * existing edge, not in the pair
# b d
#
# Then we might accidentally create yet another (a, d) edge.
# Note that this also solves the case of the following,
# missed by the first check, for edges (a, b) and (a, c)
#
# a
# / \
# b c
#
# These edges already exist.
if newgraph.has_edge(head1, tail2) or newgraph.has_edge(
head2, tail1):
continue
# Suceeded checks, perform the swap
original_edge1_data = newgraph[head1][tail1]
original_edge2_data = newgraph[head2][tail2]
newgraph.remove_edges_from((original_edge1, original_edge2))
new_edge1 = (head1, tail2, original_edge1_data)
new_edge2 = (head2, tail1, original_edge2_data)
newgraph.add_edges_from((new_edge1, new_edge2))
# Now update the entries at the indices randomly selected
edge_list[rand_index1] = (head1, tail2)
edge_list[rand_index2] = (head2, tail1)
assert len(newgraph.edges()) == num_edges
return newgraph
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment