distance_regular.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. """
  2. =======================
  3. Distance-regular graphs
  4. =======================
  5. """
  6. from collections import defaultdict
  7. from itertools import combinations_with_replacement
  8. from math import log
  9. import networkx as nx
  10. from networkx.utils import not_implemented_for
  11. from .distance_measures import diameter
  12. __all__ = [
  13. "is_distance_regular",
  14. "is_strongly_regular",
  15. "intersection_array",
  16. "global_parameters",
  17. ]
  18. @nx._dispatchable
  19. def is_distance_regular(G):
  20. """Returns True if the graph is distance regular, False otherwise.
  21. A connected graph G is distance-regular if for any nodes x,y
  22. and any integers i,j=0,1,...,d (where d is the graph
  23. diameter), the number of vertices at distance i from x and
  24. distance j from y depends only on i,j and the graph distance
  25. between x and y, independently of the choice of x and y.
  26. Parameters
  27. ----------
  28. G: Networkx graph (undirected)
  29. Returns
  30. -------
  31. bool
  32. True if the graph is Distance Regular, False otherwise
  33. Examples
  34. --------
  35. >>> G = nx.hypercube_graph(6)
  36. >>> nx.is_distance_regular(G)
  37. True
  38. See Also
  39. --------
  40. intersection_array, global_parameters
  41. Notes
  42. -----
  43. For undirected and simple graphs only
  44. References
  45. ----------
  46. .. [1] Brouwer, A. E.; Cohen, A. M.; and Neumaier, A.
  47. Distance-Regular Graphs. New York: Springer-Verlag, 1989.
  48. .. [2] Weisstein, Eric W. "Distance-Regular Graph."
  49. http://mathworld.wolfram.com/Distance-RegularGraph.html
  50. """
  51. try:
  52. intersection_array(G)
  53. return True
  54. except nx.NetworkXError:
  55. return False
  56. def global_parameters(b, c):
  57. """Returns global parameters for a given intersection array.
  58. Given a distance-regular graph G with diameter d and integers b_i,
  59. c_i,i = 0,....,d such that for any 2 vertices x,y in G at a distance
  60. i=d(x,y), there are exactly c_i neighbors of y at a distance of i-1 from x
  61. and b_i neighbors of y at a distance of i+1 from x.
  62. Thus, a distance regular graph has the global parameters,
  63. [[c_0,a_0,b_0],[c_1,a_1,b_1],......,[c_d,a_d,b_d]] for the
  64. intersection array [b_0,b_1,.....b_{d-1};c_1,c_2,.....c_d]
  65. where a_i+b_i+c_i=k , k= degree of every vertex.
  66. Parameters
  67. ----------
  68. b : list
  69. c : list
  70. Returns
  71. -------
  72. iterable
  73. An iterable over three tuples.
  74. Examples
  75. --------
  76. >>> G = nx.dodecahedral_graph()
  77. >>> b, c = nx.intersection_array(G)
  78. >>> list(nx.global_parameters(b, c))
  79. [(0, 0, 3), (1, 0, 2), (1, 1, 1), (1, 1, 1), (2, 0, 1), (3, 0, 0)]
  80. References
  81. ----------
  82. .. [1] Weisstein, Eric W. "Global Parameters."
  83. From MathWorld--A Wolfram Web Resource.
  84. http://mathworld.wolfram.com/GlobalParameters.html
  85. See Also
  86. --------
  87. intersection_array
  88. """
  89. return ((y, b[0] - x - y, x) for x, y in zip(b + [0], [0] + c))
  90. @not_implemented_for("directed")
  91. @not_implemented_for("multigraph")
  92. @nx._dispatchable
  93. def intersection_array(G):
  94. """Returns the intersection array of a distance-regular graph.
  95. Given a distance-regular graph G with integers b_i, c_i,i = 0,....,d
  96. such that for any 2 vertices x,y in G at a distance i=d(x,y), there
  97. are exactly c_i neighbors of y at a distance of i-1 from x and b_i
  98. neighbors of y at a distance of i+1 from x.
  99. A distance regular graph's intersection array is given by,
  100. [b_0,b_1,.....b_{d-1};c_1,c_2,.....c_d]
  101. Parameters
  102. ----------
  103. G: Networkx graph (undirected)
  104. Returns
  105. -------
  106. b,c: tuple of lists
  107. Examples
  108. --------
  109. >>> G = nx.icosahedral_graph()
  110. >>> nx.intersection_array(G)
  111. ([5, 2, 1], [1, 2, 5])
  112. References
  113. ----------
  114. .. [1] Weisstein, Eric W. "Intersection Array."
  115. From MathWorld--A Wolfram Web Resource.
  116. http://mathworld.wolfram.com/IntersectionArray.html
  117. See Also
  118. --------
  119. global_parameters
  120. """
  121. # the input graph is very unlikely to be distance-regular: here are the
  122. # number a(n) of connected simple graphs, and the number b(n) of
  123. # distance-regular graphs among them:
  124. #
  125. # n | 1 2 3 4 5 6 7 8 9 10
  126. # -----+------------------------------------------------------------------
  127. # a(n) | 1 1 2 6 21 112 853 11117 261080 11716571 https://oeis.org/A001349
  128. # b(n) | 1 1 1 2 2 4 2 5 4 7 https://oeis.org/A241814
  129. #
  130. # in light of this, let's compute shortest path lengths as we go instead of
  131. # precomputing them all
  132. # test for regular graph (all degrees must be equal)
  133. if not nx.is_regular(G) or not nx.is_connected(G):
  134. raise nx.NetworkXError("Graph is not distance regular.")
  135. path_length = defaultdict(dict)
  136. bint = {} # 'b' intersection array
  137. cint = {} # 'c' intersection array
  138. # see https://doi.org/10.1016/j.ejc.2004.07.004, Theorem 1.5, page 81:
  139. # the diameter of a distance-regular graph is at most (8 log_2 n) / 3,
  140. # so let's compute it as we go in the hope that we can stop early
  141. diam = 0
  142. max_diameter_for_dr_graphs = (8 * log(len(G), 2)) / 3
  143. for u, v in combinations_with_replacement(G, 2):
  144. # compute needed shortest path lengths
  145. pl_u = path_length[u]
  146. if v not in pl_u:
  147. pl_u.update(nx.single_source_shortest_path_length(G, u))
  148. for x, distance in pl_u.items():
  149. path_length[x][u] = distance
  150. i = path_length[u][v]
  151. diam = max(diam, i)
  152. # diameter too large: graph can't be distance-regular
  153. if diam > max_diameter_for_dr_graphs:
  154. raise nx.NetworkXError("Graph is not distance regular.")
  155. vnbrs = G[v]
  156. # compute needed path lengths
  157. for n in vnbrs:
  158. pl_n = path_length[n]
  159. if u not in pl_n:
  160. pl_n.update(nx.single_source_shortest_path_length(G, n))
  161. for x, distance in pl_n.items():
  162. path_length[x][n] = distance
  163. # number of neighbors of v at a distance of i-1 from u
  164. c = sum(1 for n in vnbrs if pl_u[n] == i - 1)
  165. # number of neighbors of v at a distance of i+1 from u
  166. b = sum(1 for n in vnbrs if pl_u[n] == i + 1)
  167. # b, c are independent of u and v
  168. if cint.get(i, c) != c or bint.get(i, b) != b:
  169. raise nx.NetworkXError("Graph is not distance regular")
  170. bint[i] = b
  171. cint[i] = c
  172. return (
  173. [bint.get(j, 0) for j in range(diam)],
  174. [cint.get(j + 1, 0) for j in range(diam)],
  175. )
  176. # TODO There is a definition for directed strongly regular graphs.
  177. @not_implemented_for("directed")
  178. @not_implemented_for("multigraph")
  179. @nx._dispatchable
  180. def is_strongly_regular(G):
  181. """Returns True if and only if the given graph is strongly
  182. regular.
  183. An undirected graph is *strongly regular* if
  184. * it is regular,
  185. * each pair of adjacent vertices has the same number of neighbors in
  186. common,
  187. * each pair of nonadjacent vertices has the same number of neighbors
  188. in common.
  189. Each strongly regular graph is a distance-regular graph.
  190. Conversely, if a distance-regular graph has diameter two, then it is
  191. a strongly regular graph. For more information on distance-regular
  192. graphs, see :func:`is_distance_regular`.
  193. Parameters
  194. ----------
  195. G : NetworkX graph
  196. An undirected graph.
  197. Returns
  198. -------
  199. bool
  200. Whether `G` is strongly regular.
  201. Examples
  202. --------
  203. The cycle graph on five vertices is strongly regular. It is
  204. two-regular, each pair of adjacent vertices has no shared neighbors,
  205. and each pair of nonadjacent vertices has one shared neighbor::
  206. >>> G = nx.cycle_graph(5)
  207. >>> nx.is_strongly_regular(G)
  208. True
  209. """
  210. # Here is an alternate implementation based directly on the
  211. # definition of strongly regular graphs:
  212. #
  213. # return (all_equal(G.degree().values())
  214. # and all_equal(len(common_neighbors(G, u, v))
  215. # for u, v in G.edges())
  216. # and all_equal(len(common_neighbors(G, u, v))
  217. # for u, v in non_edges(G)))
  218. #
  219. # We instead use the fact that a distance-regular graph of diameter
  220. # two is strongly regular.
  221. return is_distance_regular(G) and diameter(G) == 2