comparison planemo/lib/python3.7/site-packages/networkx/algorithms/sparsifiers.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 # Copyright (C) 2018
2 # Robert Gmyr <robert@gmyr.net>
3 # All rights reserved.
4 # BSD license.
5 """Functions for computing sparsifiers of graphs."""
6 import math
7 import networkx as nx
8 from networkx.utils import not_implemented_for, py_random_state
9
10 __all__ = ['spanner']
11
12
13 @py_random_state(3)
14 @not_implemented_for('directed')
15 @not_implemented_for('multigraph')
16 def spanner(G, stretch, weight=None, seed=None):
17 """Returns a spanner of the given graph with the given stretch.
18
19 A spanner of a graph G = (V, E) with stretch t is a subgraph
20 H = (V, E_S) such that E_S is a subset of E and the distance between
21 any pair of nodes in H is at most t times the distance between the
22 nodes in G.
23
24 Parameters
25 ----------
26 G : NetworkX graph
27 An undirected simple graph.
28
29 stretch : float
30 The stretch of the spanner.
31
32 weight : object
33 The edge attribute to use as distance.
34
35 seed : integer, random_state, or None (default)
36 Indicator of random number generation state.
37 See :ref:`Randomness<randomness>`.
38
39 Returns
40 -------
41 NetworkX graph
42 A spanner of the given graph with the given stretch.
43
44 Raises
45 ------
46 ValueError
47 If a stretch less than 1 is given.
48
49 Notes
50 -----
51 This function implements the spanner algorithm by Baswana and Sen,
52 see [1].
53
54 This algorithm is a randomized las vegas algorithm: The expected
55 running time is O(km) where k = (stretch + 1) // 2 and m is the
56 number of edges in G. The returned graph is always a spanner of the
57 given graph with the specified stretch. For weighted graphs the
58 number of edges in the spanner is O(k * n^(1 + 1 / k)) where k is
59 defined as above and n is the number of nodes in G. For unweighted
60 graphs the number of edges is O(n^(1 + 1 / k) + kn).
61
62 References
63 ----------
64 [1] S. Baswana, S. Sen. A Simple and Linear Time Randomized
65 Algorithm for Computing Sparse Spanners in Weighted Graphs.
66 Random Struct. Algorithms 30(4): 532-563 (2007).
67 """
68 if stretch < 1:
69 raise ValueError('stretch must be at least 1')
70
71 k = (stretch + 1) // 2
72
73 # initialize spanner H with empty edge set
74 H = nx.empty_graph()
75 H.add_nodes_from(G.nodes)
76
77 # phase 1: forming the clusters
78 # the residual graph has V' from the paper as its node set
79 # and E' from the paper as its edge set
80 residual_graph = _setup_residual_graph(G, weight)
81 # clustering is a dictionary that maps nodes in a cluster to the
82 # cluster center
83 clustering = {v: v for v in G.nodes}
84 sample_prob = math.pow(G.number_of_nodes(), - 1 / k)
85 size_limit = 2 * math.pow(G.number_of_nodes(), 1 + 1 / k)
86
87 i = 0
88 while i < k - 1:
89 # step 1: sample centers
90 sampled_centers = set()
91 for center in set(clustering.values()):
92 if seed.random() < sample_prob:
93 sampled_centers.add(center)
94
95 # combined loop for steps 2 and 3
96 edges_to_add = set()
97 edges_to_remove = set()
98 new_clustering = {}
99 for v in residual_graph.nodes:
100 if clustering[v] in sampled_centers:
101 continue
102
103 # step 2: find neighboring (sampled) clusters and
104 # lightest edges to them
105 lightest_edge_neighbor, lightest_edge_weight =\
106 _lightest_edge_dicts(residual_graph, clustering, v)
107 neighboring_sampled_centers =\
108 set(lightest_edge_weight.keys()) & sampled_centers
109
110 # step 3: add edges to spanner
111 if not neighboring_sampled_centers:
112 # connect to each neighboring center via lightest edge
113 for neighbor in lightest_edge_neighbor.values():
114 edges_to_add.add((v, neighbor))
115 # remove all incident edges
116 for neighbor in residual_graph.adj[v]:
117 edges_to_remove.add((v, neighbor))
118
119 else: # there is a neighboring sampled center
120 closest_center = min(neighboring_sampled_centers,
121 key=lightest_edge_weight.get)
122 closest_center_weight = lightest_edge_weight[closest_center]
123 closest_center_neighbor =\
124 lightest_edge_neighbor[closest_center]
125
126 edges_to_add.add((v, closest_center_neighbor))
127 new_clustering[v] = closest_center
128
129 # connect to centers with edge weight less than
130 # closest_center_weight
131 for center, edge_weight in lightest_edge_weight.items():
132 if edge_weight < closest_center_weight:
133 neighbor = lightest_edge_neighbor[center]
134 edges_to_add.add((v, neighbor))
135
136 # remove edges to centers with edge weight less than
137 # closest_center_weight
138 for neighbor in residual_graph.adj[v]:
139 neighbor_cluster = clustering[neighbor]
140 neighbor_weight = lightest_edge_weight[neighbor_cluster]
141 if neighbor_cluster == closest_center or neighbor_weight < closest_center_weight:
142 edges_to_remove.add((v, neighbor))
143
144 # check whether iteration added too many edges to spanner,
145 # if so repeat
146 if len(edges_to_add) > size_limit:
147 # an iteration is repeated O(1) times on expectation
148 continue
149
150 # iteration succeeded
151 i = i + 1
152
153 # actually add edges to spanner
154 for u, v in edges_to_add:
155 _add_edge_to_spanner(H, residual_graph, u, v, weight)
156
157 # actually delete edges from residual graph
158 residual_graph.remove_edges_from(edges_to_remove)
159
160 # copy old clustering data to new_clustering
161 for node, center in clustering.items():
162 if center in sampled_centers:
163 new_clustering[node] = center
164 clustering = new_clustering
165
166 # step 4: remove intra-cluster edges
167 for u in residual_graph.nodes:
168 for v in list(residual_graph.adj[u]):
169 if clustering[u] == clustering[v]:
170 residual_graph.remove_edge(u, v)
171
172 # update residual graph node set
173 for v in list(residual_graph.nodes):
174 if v not in clustering:
175 residual_graph.remove_node(v)
176
177 # phase 2: vertex-cluster joining
178 for v in residual_graph.nodes:
179 lightest_edge_neighbor, _ =\
180 _lightest_edge_dicts(residual_graph, clustering, v)
181 for neighbor in lightest_edge_neighbor.values():
182 _add_edge_to_spanner(H, residual_graph, v, neighbor, weight)
183
184 return H
185
186
187 def _setup_residual_graph(G, weight):
188 """Setup residual graph as a copy of G with unique edges weights.
189
190 The node set of the residual graph corresponds to the set V' from
191 the Baswana-Sen paper and the edge set corresponds to the set E'
192 from the paper.
193
194 This function associates distinct weights to the edges of the
195 residual graph (even for unweighted input graphs), as required by
196 the algorithm.
197
198 Parameters
199 ----------
200 G : NetworkX graph
201 An undirected simple graph.
202
203 weight : object
204 The edge attribute to use as distance.
205
206 Returns
207 -------
208 NetworkX graph
209 The residual graph used for the Baswana-Sen algorithm.
210 """
211 residual_graph = G.copy()
212
213 # establish unique edge weights, even for unweighted graphs
214 for u, v in G.edges():
215 if not weight:
216 residual_graph[u][v]['weight'] = (id(u), id(v))
217 else:
218 residual_graph[u][v]['weight'] = (G[u][v][weight], id(u), id(v))
219
220 return residual_graph
221
222
223 def _lightest_edge_dicts(residual_graph, clustering, node):
224 """Find the lightest edge to each cluster.
225
226 Searches for the minimum-weight edge to each cluster adjacent to
227 the given node.
228
229 Parameters
230 ----------
231 residual_graph : NetworkX graph
232 The residual graph used by the Baswana-Sen algorithm.
233
234 clustering : dictionary
235 The current clustering of the nodes.
236
237 node : node
238 The node from which the search originates.
239
240 Returns
241 -------
242 lightest_edge_neighbor, lightest_edge_weight : dictionary, dictionary
243 lightest_edge_neighbor is a dictionary that maps a center C to
244 a node v in the corresponding cluster such that the edge from
245 the given node to v is the lightest edge from the given node to
246 any node in cluster. lightest_edge_weight maps a center C to the
247 weight of the aforementioned edge.
248
249 Notes
250 -----
251 If a cluster has no node that is adjacent to the given node in the
252 residual graph then the center of the cluster is not a key in the
253 returned dictionaries.
254 """
255 lightest_edge_neighbor = {}
256 lightest_edge_weight = {}
257 for neighbor in residual_graph.adj[node]:
258 neighbor_center = clustering[neighbor]
259 weight = residual_graph[node][neighbor]['weight']
260 if neighbor_center not in lightest_edge_weight or\
261 weight < lightest_edge_weight[neighbor_center]:
262 lightest_edge_neighbor[neighbor_center] = neighbor
263 lightest_edge_weight[neighbor_center] = weight
264 return lightest_edge_neighbor, lightest_edge_weight
265
266
267 def _add_edge_to_spanner(H, residual_graph, u, v, weight):
268 """Add the edge {u, v} to the spanner H and take weight from
269 the residual graph.
270
271 Parameters
272 ----------
273 H : NetworkX graph
274 The spanner under construction.
275
276 residual_graph : NetworkX graph
277 The residual graph used by the Baswana-Sen algorithm. The weight
278 for the edge is taken from this graph.
279
280 u : node
281 One endpoint of the edge.
282
283 v : node
284 The other endpoint of the edge.
285
286 weight : object
287 The edge attribute to use as distance.
288 """
289 H.add_edge(u, v)
290 if weight:
291 H[u][v][weight] = residual_graph[u][v]['weight'][0]