1"""Graph diameter, radius, eccentricity and other properties."""
2
3import math
4
5import networkx as nx
6from networkx.utils import not_implemented_for
7
8__all__ = [
9 "eccentricity",
10 "diameter",
11 "harmonic_diameter",
12 "radius",
13 "periphery",
14 "center",
15 "barycenter",
16 "resistance_distance",
17 "kemeny_constant",
18 "effective_graph_resistance",
19]
20
21
22def _extrema_bounding(G, compute="diameter", weight=None):
23 """Compute requested extreme distance metric of undirected graph G
24
25 Computation is based on smart lower and upper bounds, and in practice
26 linear in the number of nodes, rather than quadratic (except for some
27 border cases such as complete graphs or circle shaped graphs).
28
29 Parameters
30 ----------
31 G : NetworkX graph
32 An undirected graph
33
34 compute : string denoting the requesting metric
35 "diameter" for the maximal eccentricity value,
36 "radius" for the minimal eccentricity value,
37 "periphery" for the set of nodes with eccentricity equal to the diameter,
38 "center" for the set of nodes with eccentricity equal to the radius,
39 "eccentricities" for the maximum distance from each node to all other nodes in G
40
41 weight : string, function, or None
42 If this is a string, then edge weights will be accessed via the
43 edge attribute with this key (that is, the weight of the edge
44 joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
45 such edge attribute exists, the weight of the edge is assumed to
46 be one.
47
48 If this is a function, the weight of an edge is the value
49 returned by the function. The function must accept exactly three
50 positional arguments: the two endpoints of an edge and the
51 dictionary of edge attributes for that edge. The function must
52 return a number.
53
54 If this is None, every edge has weight/distance/cost 1.
55
56 Weights stored as floating point values can lead to small round-off
57 errors in distances. Use integer weights to avoid this.
58
59 Weights should be positive, since they are distances.
60
61 Returns
62 -------
63 value : value of the requested metric
64 int for "diameter" and "radius" or
65 list of nodes for "center" and "periphery" or
66 dictionary of eccentricity values keyed by node for "eccentricities"
67
68 Raises
69 ------
70 NetworkXError
71 If the graph consists of multiple components
72 ValueError
73 If `compute` is not one of "diameter", "radius", "periphery", "center", or "eccentricities".
74
75 Notes
76 -----
77 This algorithm was proposed in [1]_ and discussed further in [2]_ and [3]_.
78
79 References
80 ----------
81 .. [1] F. W. Takes, W. A. Kosters,
82 "Determining the diameter of small world networks."
83 Proceedings of the 20th ACM international conference on Information and
84 knowledge management, 2011
85 https://dl.acm.org/doi/abs/10.1145/2063576.2063748
86 .. [2] F. W. Takes, W. A. Kosters,
87 "Computing the Eccentricity Distribution of Large Graphs."
88 Algorithms, 2013
89 https://www.mdpi.com/1999-4893/6/1/100
90 .. [3] M. Borassi, P. Crescenzi, M. Habib, W. A. Kosters, A. Marino, F. W. Takes,
91 "Fast diameter and radius BFS-based computation in (weakly connected)
92 real-world graphs: With an application to the six degrees of separation
93 games."
94 Theoretical Computer Science, 2015
95 https://www.sciencedirect.com/science/article/pii/S0304397515001644
96 """
97 # init variables
98 degrees = dict(G.degree()) # start with the highest degree node
99 minlowernode = max(degrees, key=degrees.get)
100 N = len(degrees) # number of nodes
101 # alternate between smallest lower and largest upper bound
102 high = False
103 # status variables
104 ecc_lower = dict.fromkeys(G, 0)
105 ecc_upper = dict.fromkeys(G, math.inf)
106 candidates = set(G)
107
108 # (re)set bound extremes
109 minlower = math.inf
110 maxlower = 0
111 minupper = math.inf
112 maxupper = 0
113
114 # repeat the following until there are no more candidates
115 while candidates:
116 if high:
117 current = maxuppernode # select node with largest upper bound
118 else:
119 current = minlowernode # select node with smallest lower bound
120 high = not high
121
122 # get distances from/to current node and derive eccentricity
123 dist = nx.shortest_path_length(G, source=current, weight=weight)
124
125 if len(dist) != N:
126 msg = "Cannot compute metric because graph is not connected."
127 raise nx.NetworkXError(msg)
128 current_ecc = max(dist.values())
129
130 # print status update
131 # print ("ecc of " + str(current) + " (" + str(ecc_lower[current]) + "/"
132 # + str(ecc_upper[current]) + ", deg: " + str(dist[current]) + ") is "
133 # + str(current_ecc))
134 # print(ecc_upper)
135
136 # (re)set bound extremes
137 maxuppernode = None
138 minlowernode = None
139
140 # update node bounds
141 for i in candidates:
142 # update eccentricity bounds
143 d = dist[i]
144 ecc_lower[i] = low = max(ecc_lower[i], max(d, (current_ecc - d)))
145 ecc_upper[i] = upp = min(ecc_upper[i], current_ecc + d)
146
147 # update min/max values of lower and upper bounds
148 minlower = min(ecc_lower[i], minlower)
149 maxlower = max(ecc_lower[i], maxlower)
150 minupper = min(ecc_upper[i], minupper)
151 maxupper = max(ecc_upper[i], maxupper)
152
153 # update candidate set
154 if compute == "diameter":
155 ruled_out = {
156 i
157 for i in candidates
158 if ecc_upper[i] <= maxlower and 2 * ecc_lower[i] >= maxupper
159 }
160 elif compute == "radius":
161 ruled_out = {
162 i
163 for i in candidates
164 if ecc_lower[i] >= minupper and ecc_upper[i] + 1 <= 2 * minlower
165 }
166 elif compute == "periphery":
167 ruled_out = {
168 i
169 for i in candidates
170 if ecc_upper[i] < maxlower
171 and (maxlower == maxupper or ecc_lower[i] > maxupper)
172 }
173 elif compute == "center":
174 ruled_out = {
175 i
176 for i in candidates
177 if ecc_lower[i] > minupper
178 and (minlower == minupper or ecc_upper[i] + 1 < 2 * minlower)
179 }
180 elif compute == "eccentricities":
181 ruled_out = set()
182 else:
183 msg = "compute must be one of 'diameter', 'radius', 'periphery', 'center', 'eccentricities'"
184 raise ValueError(msg)
185
186 ruled_out.update(i for i in candidates if ecc_lower[i] == ecc_upper[i])
187 candidates -= ruled_out
188
189 # for i in ruled_out:
190 # print("removing %g: ecc_u: %g maxl: %g ecc_l: %g maxu: %g"%
191 # (i,ecc_upper[i],maxlower,ecc_lower[i],maxupper))
192 # print("node %g: ecc_u: %g maxl: %g ecc_l: %g maxu: %g"%
193 # (4,ecc_upper[4],maxlower,ecc_lower[4],maxupper))
194 # print("NODE 4: %g"%(ecc_upper[4] <= maxlower))
195 # print("NODE 4: %g"%(2 * ecc_lower[4] >= maxupper))
196 # print("NODE 4: %g"%(ecc_upper[4] <= maxlower
197 # and 2 * ecc_lower[4] >= maxupper))
198
199 # updating maxuppernode and minlowernode for selection in next round
200 for i in candidates:
201 if (
202 minlowernode is None
203 or (
204 ecc_lower[i] == ecc_lower[minlowernode]
205 and degrees[i] > degrees[minlowernode]
206 )
207 or (ecc_lower[i] < ecc_lower[minlowernode])
208 ):
209 minlowernode = i
210
211 if (
212 maxuppernode is None
213 or (
214 ecc_upper[i] == ecc_upper[maxuppernode]
215 and degrees[i] > degrees[maxuppernode]
216 )
217 or (ecc_upper[i] > ecc_upper[maxuppernode])
218 ):
219 maxuppernode = i
220
221 # print status update
222 # print (" min=" + str(minlower) + "/" + str(minupper) +
223 # " max=" + str(maxlower) + "/" + str(maxupper) +
224 # " candidates: " + str(len(candidates)))
225 # print("cand:",candidates)
226 # print("ecc_l",ecc_lower)
227 # print("ecc_u",ecc_upper)
228 # wait = input("press Enter to continue")
229
230 # return the correct value of the requested metric
231 if compute == "diameter":
232 return maxlower
233 if compute == "radius":
234 return minupper
235 if compute == "periphery":
236 p = [v for v in G if ecc_lower[v] == maxlower]
237 return p
238 if compute == "center":
239 c = [v for v in G if ecc_upper[v] == minupper]
240 return c
241 if compute == "eccentricities":
242 return ecc_lower
243 return None
244
245
246@nx._dispatchable(edge_attrs="weight")
247def eccentricity(G, v=None, sp=None, weight=None):
248 """Returns the eccentricity of nodes in G.
249
250 The eccentricity of a node v is the maximum distance from v to
251 all other nodes in G.
252
253 Parameters
254 ----------
255 G : NetworkX graph
256 A graph
257
258 v : node, optional
259 Return value of specified node
260
261 sp : dict of dicts, optional
262 All pairs shortest path lengths as a dictionary of dictionaries
263
264 weight : string, function, or None (default=None)
265 If this is a string, then edge weights will be accessed via the
266 edge attribute with this key (that is, the weight of the edge
267 joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
268 such edge attribute exists, the weight of the edge is assumed to
269 be one.
270
271 If this is a function, the weight of an edge is the value
272 returned by the function. The function must accept exactly three
273 positional arguments: the two endpoints of an edge and the
274 dictionary of edge attributes for that edge. The function must
275 return a number.
276
277 If this is None, every edge has weight/distance/cost 1.
278
279 Weights stored as floating point values can lead to small round-off
280 errors in distances. Use integer weights to avoid this.
281
282 Weights should be positive, since they are distances.
283
284 Returns
285 -------
286 ecc : dictionary
287 A dictionary of eccentricity values keyed by node.
288
289 Examples
290 --------
291 >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)])
292 >>> dict(nx.eccentricity(G))
293 {1: 2, 2: 3, 3: 2, 4: 2, 5: 3}
294
295 >>> dict(
296 ... nx.eccentricity(G, v=[1, 5])
297 ... ) # This returns the eccentricity of node 1 & 5
298 {1: 2, 5: 3}
299
300 """
301 # if v is None: # none, use entire graph
302 # nodes=G.nodes()
303 # elif v in G: # is v a single node
304 # nodes=[v]
305 # else: # assume v is a container of nodes
306 # nodes=v
307 order = G.order()
308 e = {}
309 for n in G.nbunch_iter(v):
310 if sp is None:
311 length = nx.shortest_path_length(G, source=n, weight=weight)
312
313 L = len(length)
314 else:
315 try:
316 length = sp[n]
317 L = len(length)
318 except TypeError as err:
319 raise nx.NetworkXError('Format of "sp" is invalid.') from err
320 if L != order:
321 if G.is_directed():
322 msg = (
323 "Found infinite path length because the digraph is not"
324 " strongly connected"
325 )
326 else:
327 msg = "Found infinite path length because the graph is not connected"
328 raise nx.NetworkXError(msg)
329
330 e[n] = max(length.values())
331
332 if v in G:
333 return e[v] # return single value
334 return e
335
336
337@nx._dispatchable(edge_attrs="weight")
338def diameter(G, e=None, usebounds=False, weight=None):
339 """Returns the diameter of the graph G.
340
341 The diameter is the maximum eccentricity.
342
343 Parameters
344 ----------
345 G : NetworkX graph
346 A graph
347
348 e : eccentricity dictionary, optional
349 A precomputed dictionary of eccentricities.
350
351 usebounds : bool, optional
352 If `True`, use extrema bounding (see Notes) when computing the diameter
353 for undirected graphs. Extrema bounding may accelerate the
354 distance calculation for some graphs. `usebounds` is ignored if `G` is
355 directed or if `e` is not `None`. Default is `False`.
356
357 weight : string, function, or None
358 If this is a string, then edge weights will be accessed via the
359 edge attribute with this key (that is, the weight of the edge
360 joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
361 such edge attribute exists, the weight of the edge is assumed to
362 be one.
363
364 If this is a function, the weight of an edge is the value
365 returned by the function. The function must accept exactly three
366 positional arguments: the two endpoints of an edge and the
367 dictionary of edge attributes for that edge. The function must
368 return a number.
369
370 If this is None, every edge has weight/distance/cost 1.
371
372 Weights stored as floating point values can lead to small round-off
373 errors in distances. Use integer weights to avoid this.
374
375 Weights should be positive, since they are distances.
376
377 Returns
378 -------
379 d : integer
380 Diameter of graph
381
382 Notes
383 -----
384 When ``usebounds=True``, the computation makes use of smart lower
385 and upper bounds and is often linear in the number of nodes, rather than
386 quadratic (except for some border cases such as complete graphs or circle
387 shaped-graphs).
388
389 Examples
390 --------
391 >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)])
392 >>> nx.diameter(G)
393 3
394
395 See Also
396 --------
397 eccentricity
398 """
399 if usebounds is True and e is None and not G.is_directed():
400 return _extrema_bounding(G, compute="diameter", weight=weight)
401 if e is None:
402 e = eccentricity(G, weight=weight)
403 return max(e.values())
404
405
406@nx._dispatchable(edge_attrs="weight")
407def harmonic_diameter(G, sp=None, *, weight=None):
408 """Returns the harmonic diameter of the graph G.
409
410 The harmonic diameter of a graph is the harmonic mean of the distances
411 between all pairs of distinct vertices. Graphs that are not strongly
412 connected have infinite diameter and mean distance, making such
413 measures not useful. Restricting the diameter or mean distance to
414 finite distances yields paradoxical values (e.g., a perfect match
415 would have diameter one). The harmonic mean handles gracefully
416 infinite distances (e.g., a perfect match has harmonic diameter equal
417 to the number of vertices minus one), making it possible to assign a
418 meaningful value to all graphs.
419
420 Note that in [1] the harmonic diameter is called "connectivity length":
421 however, "harmonic diameter" is a more standard name from the
422 theory of metric spaces. The name "harmonic mean distance" is perhaps
423 a more descriptive name, but is not used in the literature, so we use the
424 name "harmonic diameter" here.
425
426 Parameters
427 ----------
428 G : NetworkX graph
429 A graph
430
431 sp : dict of dicts, optional
432 All-pairs shortest path lengths as a dictionary of dictionaries
433
434 weight : string, function, or None (default=None)
435 If None, every edge has weight/distance 1.
436 If a string, use this edge attribute as the edge weight.
437 Any edge attribute not present defaults to 1.
438 If a function, the weight of an edge is the value returned by the function.
439 The function must accept exactly three positional arguments:
440 the two endpoints of an edge and the dictionary of edge attributes for
441 that edge. The function must return a number.
442
443 Returns
444 -------
445 hd : float
446 Harmonic diameter of graph
447
448 References
449 ----------
450 .. [1] Massimo Marchiori and Vito Latora, "Harmony in the small-world".
451 *Physica A: Statistical Mechanics and Its Applications*
452 285(3-4), pages 539-546, 2000.
453 <https://doi.org/10.1016/S0378-4371(00)00311-3>
454 """
455 order = G.order()
456
457 sum_invd = 0
458 for n in G:
459 if sp is None:
460 length = nx.single_source_dijkstra_path_length(G, n, weight=weight)
461 else:
462 try:
463 length = sp[n]
464 L = len(length)
465 except TypeError as err:
466 raise nx.NetworkXError('Format of "sp" is invalid.') from err
467
468 for d in length.values():
469 # Note that this will skip the zero distance from n to itself,
470 # as it should be, but also zero-weight paths in weighted graphs.
471 if d != 0:
472 sum_invd += 1 / d
473
474 if sum_invd != 0:
475 return order * (order - 1) / sum_invd
476 if order > 1:
477 return math.inf
478 return math.nan
479
480
481@nx._dispatchable(edge_attrs="weight")
482def periphery(G, e=None, usebounds=False, weight=None):
483 """Returns the periphery of the graph G.
484
485 The periphery is the set of nodes with eccentricity equal to the diameter.
486
487 Parameters
488 ----------
489 G : NetworkX graph
490 A graph
491
492 e : eccentricity dictionary, optional
493 A precomputed dictionary of eccentricities.
494
495 usebounds : bool, optional
496 If `True`, use extrema bounding (see Notes) when computing the periphery
497 for undirected graphs. Extrema bounding may accelerate the
498 distance calculation for some graphs. `usebounds` is ignored if `G` is
499 directed or if `e` is not `None`. Default is `False`.
500
501 weight : string, function, or None
502 If this is a string, then edge weights will be accessed via the
503 edge attribute with this key (that is, the weight of the edge
504 joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
505 such edge attribute exists, the weight of the edge is assumed to
506 be one.
507
508 If this is a function, the weight of an edge is the value
509 returned by the function. The function must accept exactly three
510 positional arguments: the two endpoints of an edge and the
511 dictionary of edge attributes for that edge. The function must
512 return a number.
513
514 If this is None, every edge has weight/distance/cost 1.
515
516 Weights stored as floating point values can lead to small round-off
517 errors in distances. Use integer weights to avoid this.
518
519 Weights should be positive, since they are distances.
520
521 Returns
522 -------
523 p : list
524 List of nodes in periphery
525
526 Notes
527 -----
528 When ``usebounds=True``, the computation makes use of smart lower
529 and upper bounds and is often linear in the number of nodes, rather than
530 quadratic (except for some border cases such as complete graphs or circle
531 shaped-graphs).
532
533 Examples
534 --------
535 >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)])
536 >>> nx.periphery(G)
537 [2, 5]
538
539 See Also
540 --------
541 barycenter
542 center
543 """
544 if usebounds is True and e is None and not G.is_directed():
545 return _extrema_bounding(G, compute="periphery", weight=weight)
546 if e is None:
547 e = eccentricity(G, weight=weight)
548 diameter = max(e.values())
549 p = [v for v in e if e[v] == diameter]
550 return p
551
552
553@nx._dispatchable(edge_attrs="weight")
554def radius(G, e=None, usebounds=False, weight=None):
555 """Returns the radius of the graph G.
556
557 The radius is the minimum eccentricity.
558
559 Parameters
560 ----------
561 G : NetworkX graph
562 A graph
563
564 e : eccentricity dictionary, optional
565 A precomputed dictionary of eccentricities.
566
567 usebounds : bool, optional
568 If `True`, use extrema bounding (see Notes) when computing the radius
569 for undirected graphs. Extrema bounding may accelerate the
570 distance calculation for some graphs. `usebounds` is ignored if `G` is
571 directed or if `e` is not `None`. Default is `False`.
572
573 weight : string, function, or None
574 If this is a string, then edge weights will be accessed via the
575 edge attribute with this key (that is, the weight of the edge
576 joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
577 such edge attribute exists, the weight of the edge is assumed to
578 be one.
579
580 If this is a function, the weight of an edge is the value
581 returned by the function. The function must accept exactly three
582 positional arguments: the two endpoints of an edge and the
583 dictionary of edge attributes for that edge. The function must
584 return a number.
585
586 If this is None, every edge has weight/distance/cost 1.
587
588 Weights stored as floating point values can lead to small round-off
589 errors in distances. Use integer weights to avoid this.
590
591 Weights should be positive, since they are distances.
592
593 Returns
594 -------
595 r : integer
596 Radius of graph
597
598 Notes
599 -----
600 When ``usebounds=True``, the computation makes use of smart lower
601 and upper bounds and is often linear in the number of nodes, rather than
602 quadratic (except for some border cases such as complete graphs or circle
603 shaped-graphs).
604
605 Examples
606 --------
607 >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)])
608 >>> nx.radius(G)
609 2
610
611 """
612 if usebounds is True and e is None and not G.is_directed():
613 return _extrema_bounding(G, compute="radius", weight=weight)
614 if e is None:
615 e = eccentricity(G, weight=weight)
616 return min(e.values())
617
618
619@nx._dispatchable(edge_attrs="weight")
620def center(G, e=None, usebounds=False, weight=None):
621 """Returns the center of the graph G.
622
623 The center is the set of nodes with eccentricity equal to radius.
624
625 Parameters
626 ----------
627 G : NetworkX graph
628 A graph
629
630 e : eccentricity dictionary, optional
631 A precomputed dictionary of eccentricities.
632
633 usebounds : bool, optional
634 If `True`, use extrema bounding (see Notes) when computing the center
635 for undirected graphs. Extrema bounding may accelerate the
636 distance calculation for some graphs. `usebounds` is ignored if `G` is
637 directed or if `e` is not `None`. Default is `False`.
638
639 weight : string, function, or None
640 If this is a string, then edge weights will be accessed via the
641 edge attribute with this key (that is, the weight of the edge
642 joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
643 such edge attribute exists, the weight of the edge is assumed to
644 be one.
645
646 If this is a function, the weight of an edge is the value
647 returned by the function. The function must accept exactly three
648 positional arguments: the two endpoints of an edge and the
649 dictionary of edge attributes for that edge. The function must
650 return a number.
651
652 If this is None, every edge has weight/distance/cost 1.
653
654 Weights stored as floating point values can lead to small round-off
655 errors in distances. Use integer weights to avoid this.
656
657 Weights should be positive, since they are distances.
658
659 Returns
660 -------
661 c : list
662 List of nodes in center
663
664 Notes
665 -----
666 When ``usebounds=True``, the computation makes use of smart lower
667 and upper bounds and is often linear in the number of nodes, rather than
668 quadratic (except for some border cases such as complete graphs or circle
669 shaped-graphs).
670
671 Examples
672 --------
673 >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)])
674 >>> list(nx.center(G))
675 [1, 3, 4]
676
677 See Also
678 --------
679 :func:`~networkx.algorithms.tree.distance_measures.center` : tree center
680 barycenter
681 periphery
682 :func:`~networkx.algorithms.tree.distance_measures.centroid` : tree centroid
683 """
684 if usebounds is True and e is None and not G.is_directed():
685 return _extrema_bounding(G, compute="center", weight=weight)
686 if e is None and weight is None and not G.is_directed() and nx.is_tree(G):
687 return nx.tree.center(G)
688 if e is None:
689 e = eccentricity(G, weight=weight)
690 radius = min(e.values())
691 p = [v for v in e if e[v] == radius]
692 return p
693
694
695@nx._dispatchable(edge_attrs="weight", mutates_input={"attr": 2})
696def barycenter(G, weight=None, attr=None, sp=None):
697 r"""Calculate barycenter of a connected graph, optionally with edge weights.
698
699 The :dfn:`barycenter` a
700 :func:`connected <networkx.algorithms.components.is_connected>` graph
701 :math:`G` is the subgraph induced by the set of its nodes :math:`v`
702 minimizing the objective function
703
704 .. math::
705
706 \sum_{u \in V(G)} d_G(u, v),
707
708 where :math:`d_G` is the (possibly weighted) :func:`path length
709 <networkx.algorithms.shortest_paths.generic.shortest_path_length>`.
710 The barycenter is also called the :dfn:`median`. See [West01]_, p. 78.
711
712 Parameters
713 ----------
714 G : :class:`networkx.Graph`
715 The connected graph :math:`G`.
716 weight : :class:`str`, optional
717 Passed through to
718 :func:`~networkx.algorithms.shortest_paths.generic.shortest_path_length`.
719 attr : :class:`str`, optional
720 If given, write the value of the objective function to each node's
721 `attr` attribute. Otherwise do not store the value.
722 sp : dict of dicts, optional
723 All pairs shortest path lengths as a dictionary of dictionaries
724
725 Returns
726 -------
727 list
728 Nodes of `G` that induce the barycenter of `G`.
729
730 Raises
731 ------
732 NetworkXNoPath
733 If `G` is disconnected. `G` may appear disconnected to
734 :func:`barycenter` if `sp` is given but is missing shortest path
735 lengths for any pairs.
736 ValueError
737 If `sp` and `weight` are both given.
738
739 Examples
740 --------
741 >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)])
742 >>> nx.barycenter(G)
743 [1, 3, 4]
744
745 See Also
746 --------
747 center
748 periphery
749 :func:`~networkx.algorithms.tree.distance_measures.centroid` : tree centroid
750 """
751 if weight is None and attr is None and sp is None:
752 if not G.is_directed() and nx.is_tree(G):
753 return nx.tree.centroid(G)
754
755 if sp is None:
756 sp = nx.shortest_path_length(G, weight=weight)
757 else:
758 sp = sp.items()
759 if weight is not None:
760 raise ValueError("Cannot use both sp, weight arguments together")
761 smallest, barycenter_vertices, n = float("inf"), [], len(G)
762 for v, dists in sp:
763 if len(dists) < n:
764 raise nx.NetworkXNoPath(
765 f"Input graph {G} is disconnected, so every induced subgraph "
766 "has infinite barycentricity."
767 )
768 barycentricity = sum(dists.values())
769 if attr is not None:
770 G.nodes[v][attr] = barycentricity
771 if barycentricity < smallest:
772 smallest = barycentricity
773 barycenter_vertices = [v]
774 elif barycentricity == smallest:
775 barycenter_vertices.append(v)
776 if attr is not None:
777 nx._clear_cache(G)
778 return barycenter_vertices
779
780
781@not_implemented_for("directed")
782@nx._dispatchable(edge_attrs="weight")
783def resistance_distance(G, nodeA=None, nodeB=None, weight=None, invert_weight=True):
784 """Returns the resistance distance between pairs of nodes in graph G.
785
786 The resistance distance between two nodes of a graph is akin to treating
787 the graph as a grid of resistors with a resistance equal to the provided
788 weight [1]_, [2]_.
789
790 If weight is not provided, then a weight of 1 is used for all edges.
791
792 If two nodes are the same, the resistance distance is zero.
793
794 Parameters
795 ----------
796 G : NetworkX graph
797 A graph
798
799 nodeA : node or None, optional (default=None)
800 A node within graph G.
801 If None, compute resistance distance using all nodes as source nodes.
802
803 nodeB : node or None, optional (default=None)
804 A node within graph G.
805 If None, compute resistance distance using all nodes as target nodes.
806
807 weight : string or None, optional (default=None)
808 The edge data key used to compute the resistance distance.
809 If None, then each edge has weight 1.
810
811 invert_weight : boolean (default=True)
812 Proper calculation of resistance distance requires building the
813 Laplacian matrix with the reciprocal of the weight. Not required
814 if the weight is already inverted. Weight cannot be zero.
815
816 Returns
817 -------
818 rd : dict or float
819 If `nodeA` and `nodeB` are given, resistance distance between `nodeA`
820 and `nodeB`. If `nodeA` or `nodeB` is unspecified (the default), a
821 dictionary of nodes with resistance distances as the value.
822
823 Raises
824 ------
825 NetworkXNotImplemented
826 If `G` is a directed graph.
827
828 NetworkXError
829 If `G` is not connected, or contains no nodes,
830 or `nodeA` is not in `G` or `nodeB` is not in `G`.
831
832 Examples
833 --------
834 >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)])
835 >>> round(nx.resistance_distance(G, 1, 3), 10)
836 0.625
837
838 Notes
839 -----
840 The implementation is based on Theorem A in [2]_. Self-loops are ignored.
841 Multi-edges are contracted in one edge with weight equal to the harmonic sum of the weights.
842
843 References
844 ----------
845 .. [1] Wikipedia
846 "Resistance distance."
847 https://en.wikipedia.org/wiki/Resistance_distance
848 .. [2] D. J. Klein and M. Randic.
849 Resistance distance.
850 J. of Math. Chem. 12:81-95, 1993.
851 """
852 import numpy as np
853
854 if len(G) == 0:
855 raise nx.NetworkXError("Graph G must contain at least one node.")
856 if not nx.is_connected(G):
857 raise nx.NetworkXError("Graph G must be strongly connected.")
858 if nodeA is not None and nodeA not in G:
859 raise nx.NetworkXError("Node A is not in graph G.")
860 if nodeB is not None and nodeB not in G:
861 raise nx.NetworkXError("Node B is not in graph G.")
862
863 G = G.copy()
864 node_list = list(G)
865
866 # Invert weights
867 if invert_weight and weight is not None:
868 if G.is_multigraph():
869 for u, v, k, d in G.edges(keys=True, data=True):
870 d[weight] = 1 / d[weight]
871 else:
872 for u, v, d in G.edges(data=True):
873 d[weight] = 1 / d[weight]
874
875 # Compute resistance distance using the Pseudo-inverse of the Laplacian
876 # Self-loops are ignored
877 L = nx.laplacian_matrix(G, weight=weight).todense()
878 Linv = np.linalg.pinv(L, hermitian=True)
879
880 # Return relevant distances
881 if nodeA is not None and nodeB is not None:
882 i = node_list.index(nodeA)
883 j = node_list.index(nodeB)
884 return Linv.item(i, i) + Linv.item(j, j) - Linv.item(i, j) - Linv.item(j, i)
885
886 elif nodeA is not None:
887 i = node_list.index(nodeA)
888 d = {}
889 for n in G:
890 j = node_list.index(n)
891 d[n] = Linv.item(i, i) + Linv.item(j, j) - Linv.item(i, j) - Linv.item(j, i)
892 return d
893
894 elif nodeB is not None:
895 j = node_list.index(nodeB)
896 d = {}
897 for n in G:
898 i = node_list.index(n)
899 d[n] = Linv.item(i, i) + Linv.item(j, j) - Linv.item(i, j) - Linv.item(j, i)
900 return d
901
902 else:
903 d = {}
904 for n in G:
905 i = node_list.index(n)
906 d[n] = {}
907 for n2 in G:
908 j = node_list.index(n2)
909 d[n][n2] = (
910 Linv.item(i, i)
911 + Linv.item(j, j)
912 - Linv.item(i, j)
913 - Linv.item(j, i)
914 )
915 return d
916
917
918@not_implemented_for("directed")
919@nx._dispatchable(edge_attrs="weight")
920def effective_graph_resistance(G, weight=None, invert_weight=True):
921 """Returns the Effective graph resistance of G.
922
923 Also known as the Kirchhoff index.
924
925 The effective graph resistance is defined as the sum
926 of the resistance distance of every node pair in G [1]_.
927
928 If weight is not provided, then a weight of 1 is used for all edges.
929
930 The effective graph resistance of a disconnected graph is infinite.
931
932 Parameters
933 ----------
934 G : NetworkX graph
935 A graph
936
937 weight : string or None, optional (default=None)
938 The edge data key used to compute the effective graph resistance.
939 If None, then each edge has weight 1.
940
941 invert_weight : boolean (default=True)
942 Proper calculation of resistance distance requires building the
943 Laplacian matrix with the reciprocal of the weight. Not required
944 if the weight is already inverted. Weight cannot be zero.
945
946 Returns
947 -------
948 RG : float
949 The effective graph resistance of `G`.
950
951 Raises
952 ------
953 NetworkXNotImplemented
954 If `G` is a directed graph.
955
956 NetworkXError
957 If `G` does not contain any nodes.
958
959 Examples
960 --------
961 >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)])
962 >>> round(nx.effective_graph_resistance(G), 10)
963 10.25
964
965 Notes
966 -----
967 The implementation is based on Theorem 2.2 in [2]_. Self-loops are ignored.
968 Multi-edges are contracted in one edge with weight equal to the harmonic sum of the weights.
969
970 References
971 ----------
972 .. [1] Wolfram
973 "Kirchhoff Index."
974 https://mathworld.wolfram.com/KirchhoffIndex.html
975 .. [2] W. Ellens, F. M. Spieksma, P. Van Mieghem, A. Jamakovic, R. E. Kooij.
976 Effective graph resistance.
977 Lin. Alg. Appl. 435:2491-2506, 2011.
978 """
979 import numpy as np
980
981 if len(G) == 0:
982 raise nx.NetworkXError("Graph G must contain at least one node.")
983
984 # Disconnected graphs have infinite Effective graph resistance
985 if not nx.is_connected(G):
986 return float("inf")
987
988 # Invert weights
989 G = G.copy()
990 if invert_weight and weight is not None:
991 if G.is_multigraph():
992 for u, v, k, d in G.edges(keys=True, data=True):
993 d[weight] = 1 / d[weight]
994 else:
995 for u, v, d in G.edges(data=True):
996 d[weight] = 1 / d[weight]
997
998 # Get Laplacian eigenvalues
999 mu = np.sort(nx.laplacian_spectrum(G, weight=weight))
1000
1001 # Compute Effective graph resistance based on spectrum of the Laplacian
1002 # Self-loops are ignored
1003 return float(np.sum(1 / mu[1:]) * G.number_of_nodes())
1004
1005
1006@nx.utils.not_implemented_for("directed")
1007@nx._dispatchable(edge_attrs="weight")
1008def kemeny_constant(G, *, weight=None):
1009 """Returns the Kemeny constant of the given graph.
1010
1011 The *Kemeny constant* (or Kemeny's constant) of a graph `G`
1012 can be computed by regarding the graph as a Markov chain.
1013 The Kemeny constant is then the expected number of time steps
1014 to transition from a starting state i to a random destination state
1015 sampled from the Markov chain's stationary distribution.
1016 The Kemeny constant is independent of the chosen initial state [1]_.
1017
1018 The Kemeny constant measures the time needed for spreading
1019 across a graph. Low values indicate a closely connected graph
1020 whereas high values indicate a spread-out graph.
1021
1022 If weight is not provided, then a weight of 1 is used for all edges.
1023
1024 Since `G` represents a Markov chain, the weights must be positive.
1025
1026 Parameters
1027 ----------
1028 G : NetworkX graph
1029
1030 weight : string or None, optional (default=None)
1031 The edge data key used to compute the Kemeny constant.
1032 If None, then each edge has weight 1.
1033
1034 Returns
1035 -------
1036 float
1037 The Kemeny constant of the graph `G`.
1038
1039 Raises
1040 ------
1041 NetworkXNotImplemented
1042 If the graph `G` is directed.
1043
1044 NetworkXError
1045 If the graph `G` is not connected, or contains no nodes,
1046 or has edges with negative weights.
1047
1048 Examples
1049 --------
1050 >>> G = nx.complete_graph(5)
1051 >>> round(nx.kemeny_constant(G), 10)
1052 3.2
1053
1054 Notes
1055 -----
1056 The implementation is based on equation (3.3) in [2]_.
1057 Self-loops are allowed and indicate a Markov chain where
1058 the state can remain the same. Multi-edges are contracted
1059 in one edge with weight equal to the sum of the weights.
1060
1061 References
1062 ----------
1063 .. [1] Wikipedia
1064 "Kemeny's constant."
1065 https://en.wikipedia.org/wiki/Kemeny%27s_constant
1066 .. [2] Lovász L.
1067 Random walks on graphs: A survey.
1068 Paul Erdös is Eighty, vol. 2, Bolyai Society,
1069 Mathematical Studies, Keszthely, Hungary (1993), pp. 1-46
1070 """
1071 import numpy as np
1072 import scipy as sp
1073
1074 if len(G) == 0:
1075 raise nx.NetworkXError("Graph G must contain at least one node.")
1076 if not nx.is_connected(G):
1077 raise nx.NetworkXError("Graph G must be connected.")
1078 if nx.is_negatively_weighted(G, weight=weight):
1079 raise nx.NetworkXError("The weights of graph G must be nonnegative.")
1080
1081 # Compute matrix H = D^-1/2 A D^-1/2
1082 A = nx.adjacency_matrix(G, weight=weight)
1083 n, m = A.shape
1084 diags = A.sum(axis=1)
1085 with np.errstate(divide="ignore"):
1086 diags_sqrt = 1.0 / np.sqrt(diags)
1087 diags_sqrt[np.isinf(diags_sqrt)] = 0
1088 DH = sp.sparse.dia_array((diags_sqrt, 0), shape=(m, n)).tocsr()
1089 H = DH @ (A @ DH)
1090
1091 # Compute eigenvalues of H
1092 eig = np.sort(sp.linalg.eigvalsh(H.todense()))
1093
1094 # Compute the Kemeny constant
1095 return float(np.sum(1 / (1 - eig[:-1])))