wiener.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. """Functions related to the Wiener Index of a graph.
  2. The Wiener Index is a topological measure of a graph
  3. related to the distance between nodes and their degree.
  4. The Schultz Index and Gutman Index are similar measures.
  5. They are used categorize molecules via the network of
  6. atoms connected by chemical bonds. The indices are
  7. correlated with functional aspects of the molecules.
  8. References
  9. ----------
  10. .. [1] `Wikipedia: Wiener Index <https://en.wikipedia.org/wiki/Wiener_index>`_
  11. .. [2] M.V. Diudeaa and I. Gutman, Wiener-Type Topological Indices,
  12. Croatica Chemica Acta, 71 (1998), 21-51.
  13. https://hrcak.srce.hr/132323
  14. """
  15. import itertools as it
  16. import networkx as nx
  17. __all__ = ["wiener_index", "schultz_index", "gutman_index", "hyper_wiener_index"]
  18. @nx._dispatchable(edge_attrs="weight")
  19. def wiener_index(G, weight=None):
  20. """Returns the Wiener index of the given graph.
  21. The *Wiener index* of a graph is the sum of the shortest-path
  22. (weighted) distances between each pair of reachable nodes.
  23. For pairs of nodes in undirected graphs, only one orientation
  24. of the pair is counted.
  25. Parameters
  26. ----------
  27. G : NetworkX graph
  28. weight : string or None, optional (default: None)
  29. If None, every edge has weight 1.
  30. If a string, use this edge attribute as the edge weight.
  31. Any edge attribute not present defaults to 1.
  32. The edge weights are used to computing shortest-path distances.
  33. Returns
  34. -------
  35. number
  36. The Wiener index of the graph `G`.
  37. Raises
  38. ------
  39. NetworkXError
  40. If the graph `G` is not connected.
  41. Notes
  42. -----
  43. If a pair of nodes is not reachable, the distance is assumed to be
  44. infinity. This means that for graphs that are not
  45. strongly-connected, this function returns ``inf``.
  46. The Wiener index is not usually defined for directed graphs, however
  47. this function uses the natural generalization of the Wiener index to
  48. directed graphs.
  49. Examples
  50. --------
  51. The Wiener index of the (unweighted) complete graph on *n* nodes
  52. equals the number of pairs of the *n* nodes, since each pair of
  53. nodes is at distance one::
  54. >>> n = 10
  55. >>> G = nx.complete_graph(n)
  56. >>> nx.wiener_index(G) == n * (n - 1) / 2
  57. True
  58. Graphs that are not strongly-connected have infinite Wiener index::
  59. >>> G = nx.empty_graph(2)
  60. >>> nx.wiener_index(G)
  61. inf
  62. References
  63. ----------
  64. .. [1] `Wikipedia: Wiener Index <https://en.wikipedia.org/wiki/Wiener_index>`_
  65. """
  66. connected = nx.is_strongly_connected(G) if G.is_directed() else nx.is_connected(G)
  67. if not connected:
  68. return float("inf")
  69. spl = nx.shortest_path_length(G, weight=weight)
  70. total = sum(it.chain.from_iterable(nbrs.values() for node, nbrs in spl))
  71. # Need to account for double counting pairs of nodes in undirected graphs.
  72. return total if G.is_directed() else total / 2
  73. @nx.utils.not_implemented_for("directed")
  74. @nx.utils.not_implemented_for("multigraph")
  75. @nx._dispatchable(edge_attrs="weight")
  76. def schultz_index(G, weight=None):
  77. r"""Returns the Schultz Index (of the first kind) of `G`
  78. The *Schultz Index* [3]_ of a graph is the sum over all node pairs of
  79. distances times the sum of degrees. Consider an undirected graph `G`.
  80. For each node pair ``(u, v)`` compute ``dist(u, v) * (deg(u) + deg(v)``
  81. where ``dist`` is the shortest path length between two nodes and ``deg``
  82. is the degree of a node.
  83. The Schultz Index is the sum of these quantities over all (unordered)
  84. pairs of nodes.
  85. Parameters
  86. ----------
  87. G : NetworkX graph
  88. The undirected graph of interest.
  89. weight : string or None, optional (default: None)
  90. If None, every edge has weight 1.
  91. If a string, use this edge attribute as the edge weight.
  92. Any edge attribute not present defaults to 1.
  93. The edge weights are used to computing shortest-path distances.
  94. Returns
  95. -------
  96. number
  97. The first kind of Schultz Index of the graph `G`.
  98. Examples
  99. --------
  100. The Schultz Index of the (unweighted) complete graph on *n* nodes
  101. equals the number of pairs of the *n* nodes times ``2 * (n - 1)``,
  102. since each pair of nodes is at distance one and the sum of degree
  103. of two nodes is ``2 * (n - 1)``.
  104. >>> n = 10
  105. >>> G = nx.complete_graph(n)
  106. >>> nx.schultz_index(G) == (n * (n - 1) / 2) * (2 * (n - 1))
  107. True
  108. Graph that is disconnected
  109. >>> nx.schultz_index(nx.empty_graph(2))
  110. inf
  111. References
  112. ----------
  113. .. [1] I. Gutman, Selected properties of the Schultz molecular topological index,
  114. J. Chem. Inf. Comput. Sci. 34 (1994), 1087–1089.
  115. https://doi.org/10.1021/ci00021a009
  116. .. [2] M.V. Diudeaa and I. Gutman, Wiener-Type Topological Indices,
  117. Croatica Chemica Acta, 71 (1998), 21-51.
  118. https://hrcak.srce.hr/132323
  119. .. [3] H. P. Schultz, Topological organic chemistry. 1.
  120. Graph theory and topological indices of alkanes,i
  121. J. Chem. Inf. Comput. Sci. 29 (1989), 239–257.
  122. """
  123. if not nx.is_connected(G):
  124. return float("inf")
  125. spl = nx.shortest_path_length(G, weight=weight)
  126. d = dict(G.degree, weight=weight)
  127. return sum(dist * (d[u] + d[v]) for u, info in spl for v, dist in info.items()) / 2
  128. @nx.utils.not_implemented_for("directed")
  129. @nx.utils.not_implemented_for("multigraph")
  130. @nx._dispatchable(edge_attrs="weight")
  131. def gutman_index(G, weight=None):
  132. r"""Returns the Gutman Index for the graph `G`.
  133. The *Gutman Index* measures the topology of networks, especially for molecule
  134. networks of atoms connected by bonds [1]_. It is also called the Schultz Index
  135. of the second kind [2]_.
  136. Consider an undirected graph `G` with node set ``V``.
  137. The Gutman Index of a graph is the sum over all (unordered) pairs of nodes
  138. of nodes ``(u, v)``, with distance ``dist(u, v)`` and degrees ``deg(u)``
  139. and ``deg(v)``, of ``dist(u, v) * deg(u) * deg(v)``
  140. Parameters
  141. ----------
  142. G : NetworkX graph
  143. weight : string or None, optional (default: None)
  144. If None, every edge has weight 1.
  145. If a string, use this edge attribute as the edge weight.
  146. Any edge attribute not present defaults to 1.
  147. The edge weights are used to computing shortest-path distances.
  148. Returns
  149. -------
  150. number
  151. The Gutman Index of the graph `G`.
  152. Examples
  153. --------
  154. The Gutman Index of the (unweighted) complete graph on *n* nodes
  155. equals the number of pairs of the *n* nodes times ``(n - 1) * (n - 1)``,
  156. since each pair of nodes is at distance one and the product of degree of two
  157. vertices is ``(n - 1) * (n - 1)``.
  158. >>> n = 10
  159. >>> G = nx.complete_graph(n)
  160. >>> nx.gutman_index(G) == (n * (n - 1) / 2) * ((n - 1) * (n - 1))
  161. True
  162. Graphs that are disconnected
  163. >>> G = nx.empty_graph(2)
  164. >>> nx.gutman_index(G)
  165. inf
  166. References
  167. ----------
  168. .. [1] M.V. Diudeaa and I. Gutman, Wiener-Type Topological Indices,
  169. Croatica Chemica Acta, 71 (1998), 21-51.
  170. https://hrcak.srce.hr/132323
  171. .. [2] I. Gutman, Selected properties of the Schultz molecular topological index,
  172. J. Chem. Inf. Comput. Sci. 34 (1994), 1087–1089.
  173. https://doi.org/10.1021/ci00021a009
  174. """
  175. if not nx.is_connected(G):
  176. return float("inf")
  177. spl = nx.shortest_path_length(G, weight=weight)
  178. d = dict(G.degree, weight=weight)
  179. return sum(dist * d[u] * d[v] for u, vinfo in spl for v, dist in vinfo.items()) / 2
  180. @nx.utils.not_implemented_for("directed")
  181. @nx.utils.not_implemented_for("multigraph")
  182. @nx._dispatchable(edge_attrs="weight")
  183. def hyper_wiener_index(G, weight=None):
  184. r"""Returns the Hyper-Wiener index of the graph `G`.
  185. The Hyper-Wiener index of a connected graph `G` is defined as
  186. .. math::
  187. WW(G) = \frac{1}{2} \sum_{u,v \in V(G)} (d(u,v) + d(u,v)^2)
  188. where ``d(u, v)`` is the shortest-path distance between nodes ``u`` and ``v``.
  189. Parameters
  190. ----------
  191. G : NetworkX graph
  192. An undirected, connected graph.
  193. weight : string or None, optional (default: None)
  194. The edge attribute to use for calculating shortest-path distances.
  195. If None, all edges are considered to have a weight of 1.
  196. Returns
  197. -------
  198. float
  199. The Hyper-Wiener index of the graph G.
  200. Returns float("inf") if the graph is not connected.
  201. References
  202. ----------
  203. .. [1] M. Randić, "Novel molecular descriptor for structure-property studies,"
  204. Chemical Physics Letters, vol. 211, pp. 478-483, 1993.
  205. .. [2] `Wikipedia: Hyper-Wiener Index <https://en.wikipedia.org/wiki/Hyper-Wiener_index>`_
  206. Examples
  207. --------
  208. >>> G = nx.path_graph(4)
  209. >>> nx.hyper_wiener_index(G)
  210. 30.0
  211. >>> G = nx.cycle_graph(4)
  212. >>> nx.hyper_wiener_index(G)
  213. 20.0
  214. """
  215. if not nx.is_connected(G):
  216. return float("inf")
  217. spl = nx.shortest_path_length(G, weight=weight)
  218. total = sum(dist + dist**2 for _, lengths in spl for dist in lengths.values())
  219. return total / 2