Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/networkx/generators/geometric.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 # Copyright (C) 2004-2019 by | |
3 # Aric Hagberg <hagberg@lanl.gov> | |
4 # Dan Schult <dschult@colgate.edu> | |
5 # Pieter Swart <swart@lanl.gov> | |
6 # All rights reserved. | |
7 # BSD license. | |
8 # | |
9 # Authors: Aric Hagberg (hagberg@lanl.gov) | |
10 # Dan Schult (dschult@colgate.edu) | |
11 # Ben Edwards (BJEdwards@gmail.com) | |
12 # Arya McCarthy (admccarthy@smu.edu) | |
13 # Cole MacLean (maclean.cole@gmail.com) | |
14 | |
15 """Generators for geometric graphs. | |
16 """ | |
17 | |
18 from bisect import bisect_left | |
19 from itertools import combinations | |
20 from itertools import product | |
21 from math import sqrt | |
22 import math | |
23 try: | |
24 from scipy.spatial import cKDTree as KDTree | |
25 except ImportError: | |
26 _is_scipy_available = False | |
27 else: | |
28 _is_scipy_available = True | |
29 | |
30 import networkx as nx | |
31 from networkx.utils import nodes_or_number, py_random_state | |
32 | |
33 __all__ = ['geographical_threshold_graph', 'waxman_graph', | |
34 'navigable_small_world_graph', 'random_geometric_graph', | |
35 'soft_random_geometric_graph', 'thresholded_random_geometric_graph'] | |
36 | |
37 | |
38 def euclidean(x, y): | |
39 """Returns the Euclidean distance between the vectors ``x`` and ``y``. | |
40 | |
41 Each of ``x`` and ``y`` can be any iterable of numbers. The | |
42 iterables must be of the same length. | |
43 | |
44 """ | |
45 return sqrt(sum((a - b) ** 2 for a, b in zip(x, y))) | |
46 | |
47 | |
48 def _fast_edges(G, radius, p): | |
49 """Returns edge list of node pairs within `radius` of each other | |
50 using scipy KDTree and Minkowski distance metric `p` | |
51 | |
52 Requires scipy to be installed. | |
53 """ | |
54 pos = nx.get_node_attributes(G, 'pos') | |
55 nodes, coords = list(zip(*pos.items())) | |
56 kdtree = KDTree(coords) # Cannot provide generator. | |
57 edge_indexes = kdtree.query_pairs(radius, p) | |
58 edges = ((nodes[u], nodes[v]) for u, v in edge_indexes) | |
59 return edges | |
60 | |
61 | |
62 def _slow_edges(G, radius, p): | |
63 """Returns edge list of node pairs within `radius` of each other | |
64 using Minkowski distance metric `p` | |
65 | |
66 Works without scipy, but in `O(n^2)` time. | |
67 """ | |
68 # TODO This can be parallelized. | |
69 edges = [] | |
70 for (u, pu), (v, pv) in combinations(G.nodes(data='pos'), 2): | |
71 if sum(abs(a - b) ** p for a, b in zip(pu, pv)) <= radius ** p: | |
72 edges.append((u, v)) | |
73 return edges | |
74 | |
75 | |
76 @py_random_state(5) | |
77 @nodes_or_number(0) | |
78 def random_geometric_graph(n, radius, dim=2, pos=None, p=2, seed=None): | |
79 """Returns a random geometric graph in the unit cube of dimensions `dim`. | |
80 | |
81 The random geometric graph model places `n` nodes uniformly at | |
82 random in the unit cube. Two nodes are joined by an edge if the | |
83 distance between the nodes is at most `radius`. | |
84 | |
85 Edges are determined using a KDTree when SciPy is available. | |
86 This reduces the time complexity from $O(n^2)$ to $O(n)$. | |
87 | |
88 Parameters | |
89 ---------- | |
90 n : int or iterable | |
91 Number of nodes or iterable of nodes | |
92 radius: float | |
93 Distance threshold value | |
94 dim : int, optional | |
95 Dimension of graph | |
96 pos : dict, optional | |
97 A dictionary keyed by node with node positions as values. | |
98 p : float, optional | |
99 Which Minkowski distance metric to use. `p` has to meet the condition | |
100 ``1 <= p <= infinity``. | |
101 | |
102 If this argument is not specified, the :math:`L^2` metric | |
103 (the Euclidean distance metric), p = 2 is used. | |
104 This should not be confused with the `p` of an Erdős-Rényi random | |
105 graph, which represents probability. | |
106 seed : integer, random_state, or None (default) | |
107 Indicator of random number generation state. | |
108 See :ref:`Randomness<randomness>`. | |
109 | |
110 Returns | |
111 ------- | |
112 Graph | |
113 A random geometric graph, undirected and without self-loops. | |
114 Each node has a node attribute ``'pos'`` that stores the | |
115 position of that node in Euclidean space as provided by the | |
116 ``pos`` keyword argument or, if ``pos`` was not provided, as | |
117 generated by this function. | |
118 | |
119 Examples | |
120 -------- | |
121 Create a random geometric graph on twenty nodes where nodes are joined by | |
122 an edge if their distance is at most 0.1:: | |
123 | |
124 >>> G = nx.random_geometric_graph(20, 0.1) | |
125 | |
126 Notes | |
127 ----- | |
128 This uses a *k*-d tree to build the graph. | |
129 | |
130 The `pos` keyword argument can be used to specify node positions so you | |
131 can create an arbitrary distribution and domain for positions. | |
132 | |
133 For example, to use a 2D Gaussian distribution of node positions with mean | |
134 (0, 0) and standard deviation 2:: | |
135 | |
136 >>> import random | |
137 >>> n = 20 | |
138 >>> pos = {i: (random.gauss(0, 2), random.gauss(0, 2)) for i in range(n)} | |
139 >>> G = nx.random_geometric_graph(n, 0.2, pos=pos) | |
140 | |
141 References | |
142 ---------- | |
143 .. [1] Penrose, Mathew, *Random Geometric Graphs*, | |
144 Oxford Studies in Probability, 5, 2003. | |
145 | |
146 """ | |
147 # TODO Is this function just a special case of the geographical | |
148 # threshold graph? | |
149 # | |
150 # n_name, nodes = n | |
151 # half_radius = {v: radius / 2 for v in nodes} | |
152 # return geographical_threshold_graph(nodes, theta=1, alpha=1, | |
153 # weight=half_radius) | |
154 # | |
155 n_name, nodes = n | |
156 G = nx.Graph() | |
157 G.add_nodes_from(nodes) | |
158 # If no positions are provided, choose uniformly random vectors in | |
159 # Euclidean space of the specified dimension. | |
160 if pos is None: | |
161 pos = {v: [seed.random() for i in range(dim)] for v in nodes} | |
162 nx.set_node_attributes(G, pos, 'pos') | |
163 | |
164 if _is_scipy_available: | |
165 edges = _fast_edges(G, radius, p) | |
166 else: | |
167 edges = _slow_edges(G, radius, p) | |
168 G.add_edges_from(edges) | |
169 | |
170 return G | |
171 | |
172 | |
173 @py_random_state(6) | |
174 @nodes_or_number(0) | |
175 def soft_random_geometric_graph(n, radius, dim=2, pos=None, p=2, p_dist=None, | |
176 seed=None): | |
177 r"""Returns a soft random geometric graph in the unit cube. | |
178 | |
179 The soft random geometric graph [1] model places `n` nodes uniformly at | |
180 random in the unit cube in dimension `dim`. Two nodes of distance, `dist`, | |
181 computed by the `p`-Minkowski distance metric are joined by an edge with | |
182 probability `p_dist` if the computed distance metric value of the nodes | |
183 is at most `radius`, otherwise they are not joined. | |
184 | |
185 Edges within `radius` of each other are determined using a KDTree when | |
186 SciPy is available. This reduces the time complexity from :math:`O(n^2)` | |
187 to :math:`O(n)`. | |
188 | |
189 Parameters | |
190 ---------- | |
191 n : int or iterable | |
192 Number of nodes or iterable of nodes | |
193 radius: float | |
194 Distance threshold value | |
195 dim : int, optional | |
196 Dimension of graph | |
197 pos : dict, optional | |
198 A dictionary keyed by node with node positions as values. | |
199 p : float, optional | |
200 Which Minkowski distance metric to use. | |
201 `p` has to meet the condition ``1 <= p <= infinity``. | |
202 | |
203 If this argument is not specified, the :math:`L^2` metric | |
204 (the Euclidean distance metric), p = 2 is used. | |
205 | |
206 This should not be confused with the `p` of an Erdős-Rényi random | |
207 graph, which represents probability. | |
208 p_dist : function, optional | |
209 A probability density function computing the probability of | |
210 connecting two nodes that are of distance, dist, computed by the | |
211 Minkowski distance metric. The probability density function, `p_dist`, | |
212 must be any function that takes the metric value as input | |
213 and outputs a single probability value between 0-1. The scipy.stats | |
214 package has many probability distribution functions implemented and | |
215 tools for custom probability distribution definitions [2], and passing | |
216 the .pdf method of scipy.stats distributions can be used here. If the | |
217 probability function, `p_dist`, is not supplied, the default function | |
218 is an exponential distribution with rate parameter :math:`\lambda=1`. | |
219 seed : integer, random_state, or None (default) | |
220 Indicator of random number generation state. | |
221 See :ref:`Randomness<randomness>`. | |
222 | |
223 Returns | |
224 ------- | |
225 Graph | |
226 A soft random geometric graph, undirected and without self-loops. | |
227 Each node has a node attribute ``'pos'`` that stores the | |
228 position of that node in Euclidean space as provided by the | |
229 ``pos`` keyword argument or, if ``pos`` was not provided, as | |
230 generated by this function. | |
231 | |
232 Examples | |
233 -------- | |
234 Default Graph: | |
235 | |
236 G = nx.soft_random_geometric_graph(50, 0.2) | |
237 | |
238 Custom Graph: | |
239 | |
240 Create a soft random geometric graph on 100 uniformly distributed nodes | |
241 where nodes are joined by an edge with probability computed from an | |
242 exponential distribution with rate parameter :math:`\lambda=1` if their | |
243 Euclidean distance is at most 0.2. | |
244 | |
245 Notes | |
246 ----- | |
247 This uses a *k*-d tree to build the graph. | |
248 | |
249 The `pos` keyword argument can be used to specify node positions so you | |
250 can create an arbitrary distribution and domain for positions. | |
251 | |
252 For example, to use a 2D Gaussian distribution of node positions with mean | |
253 (0, 0) and standard deviation 2 | |
254 | |
255 The scipy.stats package can be used to define the probability distribution | |
256 with the .pdf method used as `p_dist`. | |
257 | |
258 :: | |
259 | |
260 >>> import random | |
261 >>> import math | |
262 >>> n = 100 | |
263 >>> pos = {i: (random.gauss(0, 2), random.gauss(0, 2)) for i in range(n)} | |
264 >>> p_dist = lambda dist : math.exp(-dist) | |
265 >>> G = nx.soft_random_geometric_graph(n, 0.2, pos=pos, p_dist=p_dist) | |
266 | |
267 References | |
268 ---------- | |
269 .. [1] Penrose, Mathew D. "Connectivity of soft random geometric graphs." | |
270 The Annals of Applied Probability 26.2 (2016): 986-1028. | |
271 [2] scipy.stats - | |
272 https://docs.scipy.org/doc/scipy/reference/tutorial/stats.html | |
273 | |
274 """ | |
275 n_name, nodes = n | |
276 G = nx.Graph() | |
277 G.name = 'soft_random_geometric_graph({}, {}, {})'.format(n, radius, dim) | |
278 G.add_nodes_from(nodes) | |
279 # If no positions are provided, choose uniformly random vectors in | |
280 # Euclidean space of the specified dimension. | |
281 if pos is None: | |
282 pos = {v: [seed.random() for i in range(dim)] for v in nodes} | |
283 nx.set_node_attributes(G, pos, 'pos') | |
284 | |
285 # if p_dist function not supplied the default function is an exponential | |
286 # distribution with rate parameter :math:`\lambda=1`. | |
287 if p_dist is None: | |
288 | |
289 def p_dist(dist): | |
290 return math.exp(-dist) | |
291 | |
292 def should_join(pair): | |
293 u, v = pair | |
294 u_pos, v_pos = pos[u], pos[v] | |
295 dist = (sum(abs(a - b) ** p for a, b in zip(u_pos, v_pos)))**(1 / p) | |
296 # Check if dist <= radius parameter. This check is redundant if scipy | |
297 # is available and _fast_edges routine is used, but provides the | |
298 # check in case scipy is not available and all edge combinations | |
299 # need to be checked | |
300 if dist <= radius: | |
301 return seed.random() < p_dist(dist) | |
302 else: | |
303 return False | |
304 | |
305 if _is_scipy_available: | |
306 edges = _fast_edges(G, radius, p) | |
307 G.add_edges_from(filter(should_join, edges)) | |
308 else: | |
309 G.add_edges_from(filter(should_join, combinations(G, 2))) | |
310 | |
311 return G | |
312 | |
313 | |
314 @py_random_state(7) | |
315 @nodes_or_number(0) | |
316 def geographical_threshold_graph(n, theta, dim=2, pos=None, weight=None, | |
317 metric=None, p_dist=None, seed=None): | |
318 r"""Returns a geographical threshold graph. | |
319 | |
320 The geographical threshold graph model places $n$ nodes uniformly at | |
321 random in a rectangular domain. Each node $u$ is assigned a weight | |
322 $w_u$. Two nodes $u$ and $v$ are joined by an edge if | |
323 | |
324 .. math:: | |
325 | |
326 (w_u + w_v)h(r) \ge \theta | |
327 | |
328 where `r` is the distance between `u` and `v`, h(r) is a probability of | |
329 connection as a function of `r`, and :math:`\theta` as the threshold | |
330 parameter. h(r) corresponds to the p_dist parameter. | |
331 | |
332 Parameters | |
333 ---------- | |
334 n : int or iterable | |
335 Number of nodes or iterable of nodes | |
336 theta: float | |
337 Threshold value | |
338 dim : int, optional | |
339 Dimension of graph | |
340 pos : dict | |
341 Node positions as a dictionary of tuples keyed by node. | |
342 weight : dict | |
343 Node weights as a dictionary of numbers keyed by node. | |
344 metric : function | |
345 A metric on vectors of numbers (represented as lists or | |
346 tuples). This must be a function that accepts two lists (or | |
347 tuples) as input and yields a number as output. The function | |
348 must also satisfy the four requirements of a `metric`_. | |
349 Specifically, if $d$ is the function and $x$, $y$, | |
350 and $z$ are vectors in the graph, then $d$ must satisfy | |
351 | |
352 1. $d(x, y) \ge 0$, | |
353 2. $d(x, y) = 0$ if and only if $x = y$, | |
354 3. $d(x, y) = d(y, x)$, | |
355 4. $d(x, z) \le d(x, y) + d(y, z)$. | |
356 | |
357 If this argument is not specified, the Euclidean distance metric is | |
358 used. | |
359 | |
360 .. _metric: https://en.wikipedia.org/wiki/Metric_%28mathematics%29 | |
361 p_dist : function, optional | |
362 A probability density function computing the probability of | |
363 connecting two nodes that are of distance, r, computed by metric. | |
364 The probability density function, `p_dist`, must | |
365 be any function that takes the metric value as input | |
366 and outputs a single probability value between 0-1. | |
367 The scipy.stats package has many probability distribution functions | |
368 implemented and tools for custom probability distribution | |
369 definitions [2], and passing the .pdf method of scipy.stats | |
370 distributions can be used here. If the probability | |
371 function, `p_dist`, is not supplied, the default exponential function | |
372 :math: `r^{-2}` is used. | |
373 seed : integer, random_state, or None (default) | |
374 Indicator of random number generation state. | |
375 See :ref:`Randomness<randomness>`. | |
376 | |
377 Returns | |
378 ------- | |
379 Graph | |
380 A random geographic threshold graph, undirected and without | |
381 self-loops. | |
382 | |
383 Each node has a node attribute ``pos`` that stores the | |
384 position of that node in Euclidean space as provided by the | |
385 ``pos`` keyword argument or, if ``pos`` was not provided, as | |
386 generated by this function. Similarly, each node has a node | |
387 attribute ``weight`` that stores the weight of that node as | |
388 provided or as generated. | |
389 | |
390 Examples | |
391 -------- | |
392 Specify an alternate distance metric using the ``metric`` keyword | |
393 argument. For example, to use the `taxicab metric`_ instead of the | |
394 default `Euclidean metric`_:: | |
395 | |
396 >>> dist = lambda x, y: sum(abs(a - b) for a, b in zip(x, y)) | |
397 >>> G = nx.geographical_threshold_graph(10, 0.1, metric=dist) | |
398 | |
399 .. _taxicab metric: https://en.wikipedia.org/wiki/Taxicab_geometry | |
400 .. _Euclidean metric: https://en.wikipedia.org/wiki/Euclidean_distance | |
401 | |
402 Notes | |
403 ----- | |
404 If weights are not specified they are assigned to nodes by drawing randomly | |
405 from the exponential distribution with rate parameter $\lambda=1$. | |
406 To specify weights from a different distribution, use the `weight` keyword | |
407 argument:: | |
408 | |
409 >>> import random | |
410 >>> n = 20 | |
411 >>> w = {i: random.expovariate(5.0) for i in range(n)} | |
412 >>> G = nx.geographical_threshold_graph(20, 50, weight=w) | |
413 | |
414 If node positions are not specified they are randomly assigned from the | |
415 uniform distribution. | |
416 | |
417 References | |
418 ---------- | |
419 .. [1] Masuda, N., Miwa, H., Konno, N.: | |
420 Geographical threshold graphs with small-world and scale-free | |
421 properties. | |
422 Physical Review E 71, 036108 (2005) | |
423 .. [2] Milan Bradonjić, Aric Hagberg and Allon G. Percus, | |
424 Giant component and connectivity in geographical threshold graphs, | |
425 in Algorithms and Models for the Web-Graph (WAW 2007), | |
426 Antony Bonato and Fan Chung (Eds), pp. 209--216, 2007 | |
427 """ | |
428 n_name, nodes = n | |
429 G = nx.Graph() | |
430 G.add_nodes_from(nodes) | |
431 # If no weights are provided, choose them from an exponential | |
432 # distribution. | |
433 if weight is None: | |
434 weight = {v: seed.expovariate(1) for v in G} | |
435 # If no positions are provided, choose uniformly random vectors in | |
436 # Euclidean space of the specified dimension. | |
437 if pos is None: | |
438 pos = {v: [seed.random() for i in range(dim)] for v in nodes} | |
439 # If no distance metric is provided, use Euclidean distance. | |
440 if metric is None: | |
441 metric = euclidean | |
442 nx.set_node_attributes(G, weight, 'weight') | |
443 nx.set_node_attributes(G, pos, 'pos') | |
444 | |
445 # if p_dist is not supplied, use default r^-2 | |
446 if p_dist is None: | |
447 def p_dist(r): | |
448 return r**-2 | |
449 | |
450 # Returns ``True`` if and only if the nodes whose attributes are | |
451 # ``du`` and ``dv`` should be joined, according to the threshold | |
452 # condition. | |
453 def should_join(pair): | |
454 u, v = pair | |
455 u_pos, v_pos = pos[u], pos[v] | |
456 u_weight, v_weight = weight[u], weight[v] | |
457 return (u_weight + v_weight) * p_dist(metric(u_pos, v_pos)) >= theta | |
458 | |
459 G.add_edges_from(filter(should_join, combinations(G, 2))) | |
460 return G | |
461 | |
462 | |
463 @py_random_state(6) | |
464 @nodes_or_number(0) | |
465 def waxman_graph(n, beta=0.4, alpha=0.1, L=None, domain=(0, 0, 1, 1), | |
466 metric=None, seed=None): | |
467 r"""Returns a Waxman random graph. | |
468 | |
469 The Waxman random graph model places `n` nodes uniformly at random | |
470 in a rectangular domain. Each pair of nodes at distance `d` is | |
471 joined by an edge with probability | |
472 | |
473 .. math:: | |
474 p = \beta \exp(-d / \alpha L). | |
475 | |
476 This function implements both Waxman models, using the `L` keyword | |
477 argument. | |
478 | |
479 * Waxman-1: if `L` is not specified, it is set to be the maximum distance | |
480 between any pair of nodes. | |
481 * Waxman-2: if `L` is specified, the distance between a pair of nodes is | |
482 chosen uniformly at random from the interval `[0, L]`. | |
483 | |
484 Parameters | |
485 ---------- | |
486 n : int or iterable | |
487 Number of nodes or iterable of nodes | |
488 beta: float | |
489 Model parameter | |
490 alpha: float | |
491 Model parameter | |
492 L : float, optional | |
493 Maximum distance between nodes. If not specified, the actual distance | |
494 is calculated. | |
495 domain : four-tuple of numbers, optional | |
496 Domain size, given as a tuple of the form `(x_min, y_min, x_max, | |
497 y_max)`. | |
498 metric : function | |
499 A metric on vectors of numbers (represented as lists or | |
500 tuples). This must be a function that accepts two lists (or | |
501 tuples) as input and yields a number as output. The function | |
502 must also satisfy the four requirements of a `metric`_. | |
503 Specifically, if $d$ is the function and $x$, $y$, | |
504 and $z$ are vectors in the graph, then $d$ must satisfy | |
505 | |
506 1. $d(x, y) \ge 0$, | |
507 2. $d(x, y) = 0$ if and only if $x = y$, | |
508 3. $d(x, y) = d(y, x)$, | |
509 4. $d(x, z) \le d(x, y) + d(y, z)$. | |
510 | |
511 If this argument is not specified, the Euclidean distance metric is | |
512 used. | |
513 | |
514 .. _metric: https://en.wikipedia.org/wiki/Metric_%28mathematics%29 | |
515 | |
516 seed : integer, random_state, or None (default) | |
517 Indicator of random number generation state. | |
518 See :ref:`Randomness<randomness>`. | |
519 | |
520 Returns | |
521 ------- | |
522 Graph | |
523 A random Waxman graph, undirected and without self-loops. Each | |
524 node has a node attribute ``'pos'`` that stores the position of | |
525 that node in Euclidean space as generated by this function. | |
526 | |
527 Examples | |
528 -------- | |
529 Specify an alternate distance metric using the ``metric`` keyword | |
530 argument. For example, to use the "`taxicab metric`_" instead of the | |
531 default `Euclidean metric`_:: | |
532 | |
533 >>> dist = lambda x, y: sum(abs(a - b) for a, b in zip(x, y)) | |
534 >>> G = nx.waxman_graph(10, 0.5, 0.1, metric=dist) | |
535 | |
536 .. _taxicab metric: https://en.wikipedia.org/wiki/Taxicab_geometry | |
537 .. _Euclidean metric: https://en.wikipedia.org/wiki/Euclidean_distance | |
538 | |
539 Notes | |
540 ----- | |
541 Starting in NetworkX 2.0 the parameters alpha and beta align with their | |
542 usual roles in the probability distribution. In earlier versions their | |
543 positions in the expression were reversed. Their position in the calling | |
544 sequence reversed as well to minimize backward incompatibility. | |
545 | |
546 References | |
547 ---------- | |
548 .. [1] B. M. Waxman, *Routing of multipoint connections*. | |
549 IEEE J. Select. Areas Commun. 6(9),(1988) 1617--1622. | |
550 """ | |
551 n_name, nodes = n | |
552 G = nx.Graph() | |
553 G.add_nodes_from(nodes) | |
554 (xmin, ymin, xmax, ymax) = domain | |
555 # Each node gets a uniformly random position in the given rectangle. | |
556 pos = {v: (seed.uniform(xmin, xmax), seed.uniform(ymin, ymax)) for v in G} | |
557 nx.set_node_attributes(G, pos, 'pos') | |
558 # If no distance metric is provided, use Euclidean distance. | |
559 if metric is None: | |
560 metric = euclidean | |
561 # If the maximum distance L is not specified (that is, we are in the | |
562 # Waxman-1 model), then find the maximum distance between any pair | |
563 # of nodes. | |
564 # | |
565 # In the Waxman-1 model, join nodes randomly based on distance. In | |
566 # the Waxman-2 model, join randomly based on random l. | |
567 if L is None: | |
568 L = max(metric(x, y) for x, y in combinations(pos.values(), 2)) | |
569 | |
570 def dist(u, v): return metric(pos[u], pos[v]) | |
571 else: | |
572 def dist(u, v): return seed.random() * L | |
573 | |
574 # `pair` is the pair of nodes to decide whether to join. | |
575 def should_join(pair): | |
576 return seed.random() < beta * math.exp(-dist(*pair) / (alpha * L)) | |
577 | |
578 G.add_edges_from(filter(should_join, combinations(G, 2))) | |
579 return G | |
580 | |
581 | |
582 @py_random_state(5) | |
583 def navigable_small_world_graph(n, p=1, q=1, r=2, dim=2, seed=None): | |
584 r"""Returns a navigable small-world graph. | |
585 | |
586 A navigable small-world graph is a directed grid with additional long-range | |
587 connections that are chosen randomly. | |
588 | |
589 [...] we begin with a set of nodes [...] that are identified with the set | |
590 of lattice points in an $n \times n$ square, | |
591 $\{(i, j): i \in \{1, 2, \ldots, n\}, j \in \{1, 2, \ldots, n\}\}$, | |
592 and we define the *lattice distance* between two nodes $(i, j)$ and | |
593 $(k, l)$ to be the number of "lattice steps" separating them: | |
594 $d((i, j), (k, l)) = |k - i| + |l - j|$. | |
595 | |
596 For a universal constant $p >= 1$, the node $u$ has a directed edge to | |
597 every other node within lattice distance $p$---these are its *local | |
598 contacts*. For universal constants $q >= 0$ and $r >= 0$ we also | |
599 construct directed edges from $u$ to $q$ other nodes (the *long-range | |
600 contacts*) using independent random trials; the $i$th directed edge from | |
601 $u$ has endpoint $v$ with probability proportional to $[d(u,v)]^{-r}$. | |
602 | |
603 -- [1]_ | |
604 | |
605 Parameters | |
606 ---------- | |
607 n : int | |
608 The length of one side of the lattice; the number of nodes in | |
609 the graph is therefore $n^2$. | |
610 p : int | |
611 The diameter of short range connections. Each node is joined with every | |
612 other node within this lattice distance. | |
613 q : int | |
614 The number of long-range connections for each node. | |
615 r : float | |
616 Exponent for decaying probability of connections. The probability of | |
617 connecting to a node at lattice distance $d$ is $1/d^r$. | |
618 dim : int | |
619 Dimension of grid | |
620 seed : integer, random_state, or None (default) | |
621 Indicator of random number generation state. | |
622 See :ref:`Randomness<randomness>`. | |
623 | |
624 References | |
625 ---------- | |
626 .. [1] J. Kleinberg. The small-world phenomenon: An algorithmic | |
627 perspective. Proc. 32nd ACM Symposium on Theory of Computing, 2000. | |
628 """ | |
629 if (p < 1): | |
630 raise nx.NetworkXException("p must be >= 1") | |
631 if (q < 0): | |
632 raise nx.NetworkXException("q must be >= 0") | |
633 if (r < 0): | |
634 raise nx.NetworkXException("r must be >= 1") | |
635 | |
636 G = nx.DiGraph() | |
637 nodes = list(product(range(n), repeat=dim)) | |
638 for p1 in nodes: | |
639 probs = [0] | |
640 for p2 in nodes: | |
641 if p1 == p2: | |
642 continue | |
643 d = sum((abs(b - a) for a, b in zip(p1, p2))) | |
644 if d <= p: | |
645 G.add_edge(p1, p2) | |
646 probs.append(d**-r) | |
647 cdf = list(nx.utils.accumulate(probs)) | |
648 for _ in range(q): | |
649 target = nodes[bisect_left(cdf, seed.uniform(0, cdf[-1]))] | |
650 G.add_edge(p1, target) | |
651 return G | |
652 | |
653 | |
654 @py_random_state(7) | |
655 @nodes_or_number(0) | |
656 def thresholded_random_geometric_graph(n, radius, theta, dim=2, | |
657 pos=None, weight=None, p=2, seed=None): | |
658 r"""Returns a thresholded random geometric graph in the unit cube. | |
659 | |
660 The thresholded random geometric graph [1] model places `n` nodes | |
661 uniformly at random in the unit cube of dimensions `dim`. Each node | |
662 `u` is assigned a weight :math:`w_u`. Two nodes `u` and `v` are | |
663 joined by an edge if they are within the maximum connection distance, | |
664 `radius` computed by the `p`-Minkowski distance and the summation of | |
665 weights :math:`w_u` + :math:`w_v` is greater than or equal | |
666 to the threshold parameter `theta`. | |
667 | |
668 Edges within `radius` of each other are determined using a KDTree when | |
669 SciPy is available. This reduces the time complexity from :math:`O(n^2)` | |
670 to :math:`O(n)`. | |
671 | |
672 Parameters | |
673 ---------- | |
674 n : int or iterable | |
675 Number of nodes or iterable of nodes | |
676 radius: float | |
677 Distance threshold value | |
678 theta: float | |
679 Threshold value | |
680 dim : int, optional | |
681 Dimension of graph | |
682 pos : dict, optional | |
683 A dictionary keyed by node with node positions as values. | |
684 weight : dict, optional | |
685 Node weights as a dictionary of numbers keyed by node. | |
686 p : float, optional | |
687 Which Minkowski distance metric to use. `p` has to meet the condition | |
688 ``1 <= p <= infinity``. | |
689 | |
690 If this argument is not specified, the :math:`L^2` metric | |
691 (the Euclidean distance metric), p = 2 is used. | |
692 | |
693 This should not be confused with the `p` of an Erdős-Rényi random | |
694 graph, which represents probability. | |
695 seed : integer, random_state, or None (default) | |
696 Indicator of random number generation state. | |
697 See :ref:`Randomness<randomness>`. | |
698 | |
699 Returns | |
700 ------- | |
701 Graph | |
702 A thresholded random geographic graph, undirected and without | |
703 self-loops. | |
704 | |
705 Each node has a node attribute ``'pos'`` that stores the | |
706 position of that node in Euclidean space as provided by the | |
707 ``pos`` keyword argument or, if ``pos`` was not provided, as | |
708 generated by this function. Similarly, each node has a nodethre | |
709 attribute ``'weight'`` that stores the weight of that node as | |
710 provided or as generated. | |
711 | |
712 Examples | |
713 -------- | |
714 Default Graph: | |
715 | |
716 G = nx.thresholded_random_geometric_graph(50, 0.2, 0.1) | |
717 | |
718 Custom Graph: | |
719 | |
720 Create a thresholded random geometric graph on 50 uniformly distributed | |
721 nodes where nodes are joined by an edge if their sum weights drawn from | |
722 a exponential distribution with rate = 5 are >= theta = 0.1 and their | |
723 Euclidean distance is at most 0.2. | |
724 | |
725 Notes | |
726 ----- | |
727 This uses a *k*-d tree to build the graph. | |
728 | |
729 The `pos` keyword argument can be used to specify node positions so you | |
730 can create an arbitrary distribution and domain for positions. | |
731 | |
732 For example, to use a 2D Gaussian distribution of node positions with mean | |
733 (0, 0) and standard deviation 2 | |
734 | |
735 If weights are not specified they are assigned to nodes by drawing randomly | |
736 from the exponential distribution with rate parameter :math:`\lambda=1`. | |
737 To specify weights from a different distribution, use the `weight` keyword | |
738 argument:: | |
739 | |
740 :: | |
741 | |
742 >>> import random | |
743 >>> import math | |
744 >>> n = 50 | |
745 >>> pos = {i: (random.gauss(0, 2), random.gauss(0, 2)) for i in range(n)} | |
746 >>> w = {i: random.expovariate(5.0) for i in range(n)} | |
747 >>> G = nx.thresholded_random_geometric_graph(n, 0.2, 0.1, 2, pos, w) | |
748 | |
749 References | |
750 ---------- | |
751 .. [1] http://cole-maclean.github.io/blog/files/thesis.pdf | |
752 | |
753 """ | |
754 | |
755 n_name, nodes = n | |
756 G = nx.Graph() | |
757 namestr = 'thresholded_random_geometric_graph({}, {}, {}, {})' | |
758 G.name = namestr.format(n, radius, theta, dim) | |
759 G.add_nodes_from(nodes) | |
760 # If no weights are provided, choose them from an exponential | |
761 # distribution. | |
762 if weight is None: | |
763 weight = {v: seed.expovariate(1) for v in G} | |
764 # If no positions are provided, choose uniformly random vectors in | |
765 # Euclidean space of the specified dimension. | |
766 if pos is None: | |
767 pos = {v: [seed.random() for i in range(dim)] for v in nodes} | |
768 # If no distance metric is provided, use Euclidean distance. | |
769 | |
770 nx.set_node_attributes(G, weight, 'weight') | |
771 nx.set_node_attributes(G, pos, 'pos') | |
772 | |
773 # Returns ``True`` if and only if the nodes whose attributes are | |
774 # ``du`` and ``dv`` should be joined, according to the threshold | |
775 # condition and node pairs are within the maximum connection | |
776 # distance, ``radius``. | |
777 def should_join(pair): | |
778 u, v = pair | |
779 u_weight, v_weight = weight[u], weight[v] | |
780 u_pos, v_pos = pos[u], pos[v] | |
781 dist = (sum(abs(a - b) ** p for a, b in zip(u_pos, v_pos)))**(1 / p) | |
782 # Check if dist is <= radius parameter. This check is redundant if | |
783 # scipy is available and _fast_edges routine is used, but provides | |
784 # the check in case scipy is not available and all edge combinations | |
785 # need to be checked | |
786 if dist <= radius: | |
787 return theta <= u_weight + v_weight | |
788 else: | |
789 return False | |
790 | |
791 if _is_scipy_available: | |
792 edges = _fast_edges(G, radius, p) | |
793 G.add_edges_from(filter(should_join, edges)) | |
794 else: | |
795 G.add_edges_from(filter(should_join, combinations(G, 2))) | |
796 | |
797 return G |