comparison planemo/lib/python3.7/site-packages/networkx/algorithms/swap.py @ 1:56ad4e20f292 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:32:28 -0400
parents
children
comparison
equal deleted inserted replaced
0:d30785e31577 1:56ad4e20f292
1 # -*- coding: utf-8 -*-
2 """Swap edges in a graph.
3 """
4 # Copyright (C) 2004-2019 by
5 # Aric Hagberg <hagberg@lanl.gov>
6 # Dan Schult <dschult@colgate.edu>
7 # Pieter Swart <swart@lanl.gov>
8 # All rights reserved.
9 # BSD license.
10
11 import math
12 from networkx.utils import py_random_state
13
14 import networkx as nx
15
16 __author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)',
17 'Pieter Swart (swart@lanl.gov)',
18 'Dan Schult (dschult@colgate.edu)',
19 'Joel Miller (joel.c.miller.research@gmail.com)',
20 'Ben Edwards'])
21
22 __all__ = ['double_edge_swap',
23 'connected_double_edge_swap']
24
25
26 @py_random_state(3)
27 def double_edge_swap(G, nswap=1, max_tries=100, seed=None):
28 """Swap two edges in the graph while keeping the node degrees fixed.
29
30 A double-edge swap removes two randomly chosen edges u-v and x-y
31 and creates the new edges u-x and v-y::
32
33 u--v u v
34 becomes | |
35 x--y x y
36
37 If either the edge u-x or v-y already exist no swap is performed
38 and another attempt is made to find a suitable edge pair.
39
40 Parameters
41 ----------
42 G : graph
43 An undirected graph
44
45 nswap : integer (optional, default=1)
46 Number of double-edge swaps to perform
47
48 max_tries : integer (optional)
49 Maximum number of attempts to swap edges
50
51 seed : integer, random_state, or None (default)
52 Indicator of random number generation state.
53 See :ref:`Randomness<randomness>`.
54
55 Returns
56 -------
57 G : graph
58 The graph after double edge swaps.
59
60 Notes
61 -----
62 Does not enforce any connectivity constraints.
63
64 The graph G is modified in place.
65 """
66 if G.is_directed():
67 raise nx.NetworkXError(
68 "double_edge_swap() not defined for directed graphs.")
69 if nswap > max_tries:
70 raise nx.NetworkXError("Number of swaps > number of tries allowed.")
71 if len(G) < 4:
72 raise nx.NetworkXError("Graph has less than four nodes.")
73 # Instead of choosing uniformly at random from a generated edge list,
74 # this algorithm chooses nonuniformly from the set of nodes with
75 # probability weighted by degree.
76 n = 0
77 swapcount = 0
78 keys, degrees = zip(*G.degree()) # keys, degree
79 cdf = nx.utils.cumulative_distribution(degrees) # cdf of degree
80 discrete_sequence = nx.utils.discrete_sequence
81 while swapcount < nswap:
82 # if random.random() < 0.5: continue # trick to avoid periodicities?
83 # pick two random edges without creating edge list
84 # choose source node indices from discrete distribution
85 (ui, xi) = discrete_sequence(2, cdistribution=cdf, seed=seed)
86 if ui == xi:
87 continue # same source, skip
88 u = keys[ui] # convert index to label
89 x = keys[xi]
90 # choose target uniformly from neighbors
91 v = seed.choice(list(G[u]))
92 y = seed.choice(list(G[x]))
93 if v == y:
94 continue # same target, skip
95 if (x not in G[u]) and (y not in G[v]): # don't create parallel edges
96 G.add_edge(u, x)
97 G.add_edge(v, y)
98 G.remove_edge(u, v)
99 G.remove_edge(x, y)
100 swapcount += 1
101 if n >= max_tries:
102 e = ('Maximum number of swap attempts (%s) exceeded ' % n +
103 'before desired swaps achieved (%s).' % nswap)
104 raise nx.NetworkXAlgorithmError(e)
105 n += 1
106 return G
107
108
109 @py_random_state(3)
110 def connected_double_edge_swap(G, nswap=1, _window_threshold=3, seed=None):
111 """Attempts the specified number of double-edge swaps in the graph `G`.
112
113 A double-edge swap removes two randomly chosen edges `(u, v)` and `(x,
114 y)` and creates the new edges `(u, x)` and `(v, y)`::
115
116 u--v u v
117 becomes | |
118 x--y x y
119
120 If either `(u, x)` or `(v, y)` already exist, then no swap is performed
121 so the actual number of swapped edges is always *at most* `nswap`.
122
123 Parameters
124 ----------
125 G : graph
126 An undirected graph
127
128 nswap : integer (optional, default=1)
129 Number of double-edge swaps to perform
130
131 _window_threshold : integer
132
133 The window size below which connectedness of the graph will be checked
134 after each swap.
135
136 The "window" in this function is a dynamically updated integer that
137 represents the number of swap attempts to make before checking if the
138 graph remains connected. It is an optimization used to decrease the
139 running time of the algorithm in exchange for increased complexity of
140 implementation.
141
142 If the window size is below this threshold, then the algorithm checks
143 after each swap if the graph remains connected by checking if there is a
144 path joining the two nodes whose edge was just removed. If the window
145 size is above this threshold, then the algorithm performs do all the
146 swaps in the window and only then check if the graph is still connected.
147
148 seed : integer, random_state, or None (default)
149 Indicator of random number generation state.
150 See :ref:`Randomness<randomness>`.
151
152 Returns
153 -------
154 int
155 The number of successful swaps
156
157 Raises
158 ------
159
160 NetworkXError
161
162 If the input graph is not connected, or if the graph has fewer than four
163 nodes.
164
165 Notes
166 -----
167
168 The initial graph `G` must be connected, and the resulting graph is
169 connected. The graph `G` is modified in place.
170
171 References
172 ----------
173 .. [1] C. Gkantsidis and M. Mihail and E. Zegura,
174 The Markov chain simulation method for generating connected
175 power law random graphs, 2003.
176 http://citeseer.ist.psu.edu/gkantsidis03markov.html
177 """
178 if not nx.is_connected(G):
179 raise nx.NetworkXError("Graph not connected")
180 if len(G) < 4:
181 raise nx.NetworkXError("Graph has less than four nodes.")
182 n = 0
183 swapcount = 0
184 deg = G.degree()
185 # Label key for nodes
186 dk = list(n for n, d in G.degree())
187 cdf = nx.utils.cumulative_distribution(list(d for n, d in G.degree()))
188 discrete_sequence = nx.utils.discrete_sequence
189 window = 1
190 while n < nswap:
191 wcount = 0
192 swapped = []
193 # If the window is small, we just check each time whether the graph is
194 # connected by checking if the nodes that were just separated are still
195 # connected.
196 if window < _window_threshold:
197 # This Boolean keeps track of whether there was a failure or not.
198 fail = False
199 while wcount < window and n < nswap:
200 # Pick two random edges without creating the edge list. Choose
201 # source nodes from the discrete degree distribution.
202 (ui, xi) = discrete_sequence(2, cdistribution=cdf, seed=seed)
203 # If the source nodes are the same, skip this pair.
204 if ui == xi:
205 continue
206 # Convert an index to a node label.
207 u = dk[ui]
208 x = dk[xi]
209 # Choose targets uniformly from neighbors.
210 v = seed.choice(list(G.neighbors(u)))
211 y = seed.choice(list(G.neighbors(x)))
212 # If the target nodes are the same, skip this pair.
213 if v == y:
214 continue
215 if x not in G[u] and y not in G[v]:
216 G.remove_edge(u, v)
217 G.remove_edge(x, y)
218 G.add_edge(u, x)
219 G.add_edge(v, y)
220 swapped.append((u, v, x, y))
221 swapcount += 1
222 n += 1
223 # If G remains connected...
224 if nx.has_path(G, u, v):
225 wcount += 1
226 # Otherwise, undo the changes.
227 else:
228 G.add_edge(u, v)
229 G.add_edge(x, y)
230 G.remove_edge(u, x)
231 G.remove_edge(v, y)
232 swapcount -= 1
233 fail = True
234 # If one of the swaps failed, reduce the window size.
235 if fail:
236 window = int(math.ceil(window / 2))
237 else:
238 window += 1
239 # If the window is large, then there is a good chance that a bunch of
240 # swaps will work. It's quicker to do all those swaps first and then
241 # check if the graph remains connected.
242 else:
243 while wcount < window and n < nswap:
244 # Pick two random edges without creating the edge list. Choose
245 # source nodes from the discrete degree distribution.
246 (ui, xi) = nx.utils.discrete_sequence(2, cdistribution=cdf)
247 # If the source nodes are the same, skip this pair.
248 if ui == xi:
249 continue
250 # Convert an index to a node label.
251 u = dk[ui]
252 x = dk[xi]
253 # Choose targets uniformly from neighbors.
254 v = seed.choice(list(G.neighbors(u)))
255 y = seed.choice(list(G.neighbors(x)))
256 # If the target nodes are the same, skip this pair.
257 if v == y:
258 continue
259 if x not in G[u] and y not in G[v]:
260 G.remove_edge(u, v)
261 G.remove_edge(x, y)
262 G.add_edge(u, x)
263 G.add_edge(v, y)
264 swapped.append((u, v, x, y))
265 swapcount += 1
266 n += 1
267 wcount += 1
268 # If the graph remains connected, increase the window size.
269 if nx.is_connected(G):
270 window += 1
271 # Otherwise, undo the changes from the previous window and decrease
272 # the window size.
273 else:
274 while swapped:
275 (u, v, x, y) = swapped.pop()
276 G.add_edge(u, v)
277 G.add_edge(x, y)
278 G.remove_edge(u, x)
279 G.remove_edge(v, y)
280 swapcount -= 1
281 window = int(math.ceil(window / 2))
282 return swapcount