regular.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. """Functions for computing and verifying regular graphs."""
  2. import networkx as nx
  3. from networkx.utils import not_implemented_for
  4. __all__ = ["is_regular", "is_k_regular", "k_factor"]
  5. @nx._dispatch
  6. def is_regular(G):
  7. """Determines whether the graph ``G`` is a regular graph.
  8. A regular graph is a graph where each vertex has the same degree. A
  9. regular digraph is a graph where the indegree and outdegree of each
  10. vertex are equal.
  11. Parameters
  12. ----------
  13. G : NetworkX graph
  14. Returns
  15. -------
  16. bool
  17. Whether the given graph or digraph is regular.
  18. Examples
  19. --------
  20. >>> G = nx.DiGraph([(1, 2), (2, 3), (3, 4), (4, 1)])
  21. >>> nx.is_regular(G)
  22. True
  23. """
  24. n1 = nx.utils.arbitrary_element(G)
  25. if not G.is_directed():
  26. d1 = G.degree(n1)
  27. return all(d1 == d for _, d in G.degree)
  28. else:
  29. d_in = G.in_degree(n1)
  30. in_regular = all(d_in == d for _, d in G.in_degree)
  31. d_out = G.out_degree(n1)
  32. out_regular = all(d_out == d for _, d in G.out_degree)
  33. return in_regular and out_regular
  34. @nx._dispatch
  35. @not_implemented_for("directed")
  36. def is_k_regular(G, k):
  37. """Determines whether the graph ``G`` is a k-regular graph.
  38. A k-regular graph is a graph where each vertex has degree k.
  39. Parameters
  40. ----------
  41. G : NetworkX graph
  42. Returns
  43. -------
  44. bool
  45. Whether the given graph is k-regular.
  46. Examples
  47. --------
  48. >>> G = nx.Graph([(1, 2), (2, 3), (3, 4), (4, 1)])
  49. >>> nx.is_k_regular(G, k=3)
  50. False
  51. """
  52. return all(d == k for n, d in G.degree)
  53. @not_implemented_for("directed")
  54. @not_implemented_for("multigraph")
  55. def k_factor(G, k, matching_weight="weight"):
  56. """Compute a k-factor of G
  57. A k-factor of a graph is a spanning k-regular subgraph.
  58. A spanning k-regular subgraph of G is a subgraph that contains
  59. each vertex of G and a subset of the edges of G such that each
  60. vertex has degree k.
  61. Parameters
  62. ----------
  63. G : NetworkX graph
  64. Undirected graph
  65. matching_weight: string, optional (default='weight')
  66. Edge data key corresponding to the edge weight.
  67. Used for finding the max-weighted perfect matching.
  68. If key not found, uses 1 as weight.
  69. Returns
  70. -------
  71. G2 : NetworkX graph
  72. A k-factor of G
  73. Examples
  74. --------
  75. >>> G = nx.Graph([(1, 2), (2, 3), (3, 4), (4, 1)])
  76. >>> G2 = nx.k_factor(G, k=1)
  77. >>> G2.edges()
  78. EdgeView([(1, 2), (3, 4)])
  79. References
  80. ----------
  81. .. [1] "An algorithm for computing simple k-factors.",
  82. Meijer, Henk, Yurai Núñez-Rodríguez, and David Rappaport,
  83. Information processing letters, 2009.
  84. """
  85. from networkx.algorithms.matching import is_perfect_matching, max_weight_matching
  86. class LargeKGadget:
  87. def __init__(self, k, degree, node, g):
  88. self.original = node
  89. self.g = g
  90. self.k = k
  91. self.degree = degree
  92. self.outer_vertices = [(node, x) for x in range(degree)]
  93. self.core_vertices = [(node, x + degree) for x in range(degree - k)]
  94. def replace_node(self):
  95. adj_view = self.g[self.original]
  96. neighbors = list(adj_view.keys())
  97. edge_attrs = list(adj_view.values())
  98. for outer, neighbor, edge_attrs in zip(
  99. self.outer_vertices, neighbors, edge_attrs
  100. ):
  101. self.g.add_edge(outer, neighbor, **edge_attrs)
  102. for core in self.core_vertices:
  103. for outer in self.outer_vertices:
  104. self.g.add_edge(core, outer)
  105. self.g.remove_node(self.original)
  106. def restore_node(self):
  107. self.g.add_node(self.original)
  108. for outer in self.outer_vertices:
  109. adj_view = self.g[outer]
  110. for neighbor, edge_attrs in list(adj_view.items()):
  111. if neighbor not in self.core_vertices:
  112. self.g.add_edge(self.original, neighbor, **edge_attrs)
  113. break
  114. g.remove_nodes_from(self.outer_vertices)
  115. g.remove_nodes_from(self.core_vertices)
  116. class SmallKGadget:
  117. def __init__(self, k, degree, node, g):
  118. self.original = node
  119. self.k = k
  120. self.degree = degree
  121. self.g = g
  122. self.outer_vertices = [(node, x) for x in range(degree)]
  123. self.inner_vertices = [(node, x + degree) for x in range(degree)]
  124. self.core_vertices = [(node, x + 2 * degree) for x in range(k)]
  125. def replace_node(self):
  126. adj_view = self.g[self.original]
  127. for outer, inner, (neighbor, edge_attrs) in zip(
  128. self.outer_vertices, self.inner_vertices, list(adj_view.items())
  129. ):
  130. self.g.add_edge(outer, inner)
  131. self.g.add_edge(outer, neighbor, **edge_attrs)
  132. for core in self.core_vertices:
  133. for inner in self.inner_vertices:
  134. self.g.add_edge(core, inner)
  135. self.g.remove_node(self.original)
  136. def restore_node(self):
  137. self.g.add_node(self.original)
  138. for outer in self.outer_vertices:
  139. adj_view = self.g[outer]
  140. for neighbor, edge_attrs in adj_view.items():
  141. if neighbor not in self.core_vertices:
  142. self.g.add_edge(self.original, neighbor, **edge_attrs)
  143. break
  144. self.g.remove_nodes_from(self.outer_vertices)
  145. self.g.remove_nodes_from(self.inner_vertices)
  146. self.g.remove_nodes_from(self.core_vertices)
  147. # Step 1
  148. if any(d < k for _, d in G.degree):
  149. raise nx.NetworkXUnfeasible("Graph contains a vertex with degree less than k")
  150. g = G.copy()
  151. # Step 2
  152. gadgets = []
  153. for node, degree in list(g.degree):
  154. if k < degree / 2.0:
  155. gadget = SmallKGadget(k, degree, node, g)
  156. else:
  157. gadget = LargeKGadget(k, degree, node, g)
  158. gadget.replace_node()
  159. gadgets.append(gadget)
  160. # Step 3
  161. matching = max_weight_matching(g, maxcardinality=True, weight=matching_weight)
  162. # Step 4
  163. if not is_perfect_matching(g, matching):
  164. raise nx.NetworkXUnfeasible(
  165. "Cannot find k-factor because no perfect matching exists"
  166. )
  167. for edge in g.edges():
  168. if edge not in matching and (edge[1], edge[0]) not in matching:
  169. g.remove_edge(edge[0], edge[1])
  170. for gadget in gadgets:
  171. gadget.restore_node()
  172. return g