distance_measures.py 37 KB

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