-
-
Save cummins-orgs/6ad628c0f86555bcc995995bdb2a5394 to your computer and use it in GitHub Desktop.
Edge swap graph.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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