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