polynomials.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. """Provides algorithms supporting the computation of graph polynomials.
  2. Graph polynomials are polynomial-valued graph invariants that encode a wide
  3. variety of structural information. Examples include the Tutte polynomial,
  4. chromatic polynomial, characteristic polynomial, and matching polynomial. An
  5. extensive treatment is provided in [1]_.
  6. For a simple example, the `~sympy.matrices.matrices.MatrixDeterminant.charpoly`
  7. method can be used to compute the characteristic polynomial from the adjacency
  8. matrix of a graph. Consider the complete graph ``K_4``:
  9. >>> import sympy
  10. >>> x = sympy.Symbol("x")
  11. >>> G = nx.complete_graph(4)
  12. >>> A = nx.adjacency_matrix(G)
  13. >>> M = sympy.SparseMatrix(A.todense())
  14. >>> M.charpoly(x).as_expr()
  15. x**4 - 6*x**2 - 8*x - 3
  16. .. [1] Y. Shi, M. Dehmer, X. Li, I. Gutman,
  17. "Graph Polynomials"
  18. """
  19. from collections import deque
  20. import networkx as nx
  21. from networkx.utils import not_implemented_for
  22. __all__ = ["tutte_polynomial", "chromatic_polynomial"]
  23. @not_implemented_for("directed")
  24. def tutte_polynomial(G):
  25. r"""Returns the Tutte polynomial of `G`
  26. This function computes the Tutte polynomial via an iterative version of
  27. the deletion-contraction algorithm.
  28. The Tutte polynomial `T_G(x, y)` is a fundamental graph polynomial invariant in
  29. two variables. It encodes a wide array of information related to the
  30. edge-connectivity of a graph; "Many problems about graphs can be reduced to
  31. problems of finding and evaluating the Tutte polynomial at certain values" [1]_.
  32. In fact, every deletion-contraction-expressible feature of a graph is a
  33. specialization of the Tutte polynomial [2]_ (see Notes for examples).
  34. There are several equivalent definitions; here are three:
  35. Def 1 (rank-nullity expansion): For `G` an undirected graph, `n(G)` the
  36. number of vertices of `G`, `E` the edge set of `G`, `V` the vertex set of
  37. `G`, and `c(A)` the number of connected components of the graph with vertex
  38. set `V` and edge set `A` [3]_:
  39. .. math::
  40. T_G(x, y) = \sum_{A \in E} (x-1)^{c(A) - c(E)} (y-1)^{c(A) + |A| - n(G)}
  41. Def 2 (spanning tree expansion): Let `G` be an undirected graph, `T` a spanning
  42. tree of `G`, and `E` the edge set of `G`. Let `E` have an arbitrary strict
  43. linear order `L`. Let `B_e` be the unique minimal nonempty edge cut of
  44. $E \setminus T \cup {e}$. An edge `e` is internally active with respect to
  45. `T` and `L` if `e` is the least edge in `B_e` according to the linear order
  46. `L`. The internal activity of `T` (denoted `i(T)`) is the number of edges
  47. in $E \setminus T$ that are internally active with respect to `T` and `L`.
  48. Let `P_e` be the unique path in $T \cup {e}$ whose source and target vertex
  49. are the same. An edge `e` is externally active with respect to `T` and `L`
  50. if `e` is the least edge in `P_e` according to the linear order `L`. The
  51. external activity of `T` (denoted `e(T)`) is the number of edges in
  52. $E \setminus T$ that are externally active with respect to `T` and `L`.
  53. Then [4]_ [5]_:
  54. .. math::
  55. T_G(x, y) = \sum_{T \text{ a spanning tree of } G} x^{i(T)} y^{e(T)}
  56. Def 3 (deletion-contraction recurrence): For `G` an undirected graph, `G-e`
  57. the graph obtained from `G` by deleting edge `e`, `G/e` the graph obtained
  58. from `G` by contracting edge `e`, `k(G)` the number of cut-edges of `G`,
  59. and `l(G)` the number of self-loops of `G`:
  60. .. math::
  61. T_G(x, y) = \begin{cases}
  62. x^{k(G)} y^{l(G)}, & \text{if all edges are cut-edges or self-loops} \\
  63. T_{G-e}(x, y) + T_{G/e}(x, y), & \text{otherwise, for an arbitrary edge $e$ not a cut-edge or loop}
  64. \end{cases}
  65. Parameters
  66. ----------
  67. G : NetworkX graph
  68. Returns
  69. -------
  70. instance of `sympy.core.add.Add`
  71. A Sympy expression representing the Tutte polynomial for `G`.
  72. Examples
  73. --------
  74. >>> C = nx.cycle_graph(5)
  75. >>> nx.tutte_polynomial(C)
  76. x**4 + x**3 + x**2 + x + y
  77. >>> D = nx.diamond_graph()
  78. >>> nx.tutte_polynomial(D)
  79. x**3 + 2*x**2 + 2*x*y + x + y**2 + y
  80. Notes
  81. -----
  82. Some specializations of the Tutte polynomial:
  83. - `T_G(1, 1)` counts the number of spanning trees of `G`
  84. - `T_G(1, 2)` counts the number of connected spanning subgraphs of `G`
  85. - `T_G(2, 1)` counts the number of spanning forests in `G`
  86. - `T_G(0, 2)` counts the number of strong orientations of `G`
  87. - `T_G(2, 0)` counts the number of acyclic orientations of `G`
  88. Edge contraction is defined and deletion-contraction is introduced in [6]_.
  89. Combinatorial meaning of the coefficients is introduced in [7]_.
  90. Universality, properties, and applications are discussed in [8]_.
  91. Practically, up-front computation of the Tutte polynomial may be useful when
  92. users wish to repeatedly calculate edge-connectivity-related information
  93. about one or more graphs.
  94. References
  95. ----------
  96. .. [1] M. Brandt,
  97. "The Tutte Polynomial."
  98. Talking About Combinatorial Objects Seminar, 2015
  99. https://math.berkeley.edu/~brandtm/talks/tutte.pdf
  100. .. [2] A. Björklund, T. Husfeldt, P. Kaski, M. Koivisto,
  101. "Computing the Tutte polynomial in vertex-exponential time"
  102. 49th Annual IEEE Symposium on Foundations of Computer Science, 2008
  103. https://ieeexplore.ieee.org/abstract/document/4691000
  104. .. [3] Y. Shi, M. Dehmer, X. Li, I. Gutman,
  105. "Graph Polynomials," p. 14
  106. .. [4] Y. Shi, M. Dehmer, X. Li, I. Gutman,
  107. "Graph Polynomials," p. 46
  108. .. [5] A. Nešetril, J. Goodall,
  109. "Graph invariants, homomorphisms, and the Tutte polynomial"
  110. https://iuuk.mff.cuni.cz/~andrew/Tutte.pdf
  111. .. [6] D. B. West,
  112. "Introduction to Graph Theory," p. 84
  113. .. [7] G. Coutinho,
  114. "A brief introduction to the Tutte polynomial"
  115. Structural Analysis of Complex Networks, 2011
  116. https://homepages.dcc.ufmg.br/~gabriel/seminars/coutinho_tuttepolynomial_seminar.pdf
  117. .. [8] J. A. Ellis-Monaghan, C. Merino,
  118. "Graph polynomials and their applications I: The Tutte polynomial"
  119. Structural Analysis of Complex Networks, 2011
  120. https://arxiv.org/pdf/0803.3079.pdf
  121. """
  122. import sympy
  123. x = sympy.Symbol("x")
  124. y = sympy.Symbol("y")
  125. stack = deque()
  126. stack.append(nx.MultiGraph(G))
  127. polynomial = 0
  128. while stack:
  129. G = stack.pop()
  130. bridges = set(nx.bridges(G))
  131. e = None
  132. for i in G.edges:
  133. if (i[0], i[1]) not in bridges and i[0] != i[1]:
  134. e = i
  135. break
  136. if not e:
  137. loops = list(nx.selfloop_edges(G, keys=True))
  138. polynomial += x ** len(bridges) * y ** len(loops)
  139. else:
  140. # deletion-contraction
  141. C = nx.contracted_edge(G, e, self_loops=True)
  142. C.remove_edge(e[0], e[0])
  143. G.remove_edge(*e)
  144. stack.append(G)
  145. stack.append(C)
  146. return sympy.simplify(polynomial)
  147. @not_implemented_for("directed")
  148. def chromatic_polynomial(G):
  149. r"""Returns the chromatic polynomial of `G`
  150. This function computes the chromatic polynomial via an iterative version of
  151. the deletion-contraction algorithm.
  152. The chromatic polynomial `X_G(x)` is a fundamental graph polynomial
  153. invariant in one variable. Evaluating `X_G(k)` for an natural number `k`
  154. enumerates the proper k-colorings of `G`.
  155. There are several equivalent definitions; here are three:
  156. Def 1 (explicit formula):
  157. For `G` an undirected graph, `c(G)` the number of connected components of
  158. `G`, `E` the edge set of `G`, and `G(S)` the spanning subgraph of `G` with
  159. edge set `S` [1]_:
  160. .. math::
  161. X_G(x) = \sum_{S \subseteq E} (-1)^{|S|} x^{c(G(S))}
  162. Def 2 (interpolating polynomial):
  163. For `G` an undirected graph, `n(G)` the number of vertices of `G`, `k_0 = 0`,
  164. and `k_i` the number of distinct ways to color the vertices of `G` with `i`
  165. unique colors (for `i` a natural number at most `n(G)`), `X_G(x)` is the
  166. unique Lagrange interpolating polynomial of degree `n(G)` through the points
  167. `(0, k_0), (1, k_1), \dots, (n(G), k_{n(G)})` [2]_.
  168. Def 3 (chromatic recurrence):
  169. For `G` an undirected graph, `G-e` the graph obtained from `G` by deleting
  170. edge `e`, `G/e` the graph obtained from `G` by contracting edge `e`, `n(G)`
  171. the number of vertices of `G`, and `e(G)` the number of edges of `G` [3]_:
  172. .. math::
  173. X_G(x) = \begin{cases}
  174. x^{n(G)}, & \text{if $e(G)=0$} \\
  175. X_{G-e}(x) - X_{G/e}(x), & \text{otherwise, for an arbitrary edge $e$}
  176. \end{cases}
  177. This formulation is also known as the Fundamental Reduction Theorem [4]_.
  178. Parameters
  179. ----------
  180. G : NetworkX graph
  181. Returns
  182. -------
  183. instance of `sympy.core.add.Add`
  184. A Sympy expression representing the chromatic polynomial for `G`.
  185. Examples
  186. --------
  187. >>> C = nx.cycle_graph(5)
  188. >>> nx.chromatic_polynomial(C)
  189. x**5 - 5*x**4 + 10*x**3 - 10*x**2 + 4*x
  190. >>> G = nx.complete_graph(4)
  191. >>> nx.chromatic_polynomial(G)
  192. x**4 - 6*x**3 + 11*x**2 - 6*x
  193. Notes
  194. -----
  195. Interpretation of the coefficients is discussed in [5]_. Several special
  196. cases are listed in [2]_.
  197. The chromatic polynomial is a specialization of the Tutte polynomial; in
  198. particular, `X_G(x) = `T_G(x, 0)` [6]_.
  199. The chromatic polynomial may take negative arguments, though evaluations
  200. may not have chromatic interpretations. For instance, `X_G(-1)` enumerates
  201. the acyclic orientations of `G` [7]_.
  202. References
  203. ----------
  204. .. [1] D. B. West,
  205. "Introduction to Graph Theory," p. 222
  206. .. [2] E. W. Weisstein
  207. "Chromatic Polynomial"
  208. MathWorld--A Wolfram Web Resource
  209. https://mathworld.wolfram.com/ChromaticPolynomial.html
  210. .. [3] D. B. West,
  211. "Introduction to Graph Theory," p. 221
  212. .. [4] J. Zhang, J. Goodall,
  213. "An Introduction to Chromatic Polynomials"
  214. https://math.mit.edu/~apost/courses/18.204_2018/Julie_Zhang_paper.pdf
  215. .. [5] R. C. Read,
  216. "An Introduction to Chromatic Polynomials"
  217. Journal of Combinatorial Theory, 1968
  218. https://math.berkeley.edu/~mrklug/ReadChromatic.pdf
  219. .. [6] W. T. Tutte,
  220. "Graph-polynomials"
  221. Advances in Applied Mathematics, 2004
  222. https://www.sciencedirect.com/science/article/pii/S0196885803000411
  223. .. [7] R. P. Stanley,
  224. "Acyclic orientations of graphs"
  225. Discrete Mathematics, 2006
  226. https://math.mit.edu/~rstan/pubs/pubfiles/18.pdf
  227. """
  228. import sympy
  229. x = sympy.Symbol("x")
  230. stack = deque()
  231. stack.append(nx.MultiGraph(G, contraction_idx=0))
  232. polynomial = 0
  233. while stack:
  234. G = stack.pop()
  235. edges = list(G.edges)
  236. if not edges:
  237. polynomial += (-1) ** G.graph["contraction_idx"] * x ** len(G)
  238. else:
  239. e = edges[0]
  240. C = nx.contracted_edge(G, e, self_loops=True)
  241. C.graph["contraction_idx"] = G.graph["contraction_idx"] + 1
  242. C.remove_edge(e[0], e[0])
  243. G.remove_edge(*e)
  244. stack.append(G)
  245. stack.append(C)
  246. return polynomial