regular.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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._dispatchable
  6. def is_regular(G):
  7. """Determines whether a graph is regular.
  8. A regular graph is a graph where all nodes have the same degree. A regular
  9. digraph is a graph where all nodes have the same indegree and all nodes
  10. have the same outdegree.
  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. if len(G) == 0:
  25. raise nx.NetworkXPointlessConcept("Graph has no nodes.")
  26. n1 = nx.utils.arbitrary_element(G)
  27. if not G.is_directed():
  28. d1 = G.degree(n1)
  29. return all(d1 == d for _, d in G.degree)
  30. else:
  31. d_in = G.in_degree(n1)
  32. in_regular = (d_in == d for _, d in G.in_degree)
  33. d_out = G.out_degree(n1)
  34. out_regular = (d_out == d for _, d in G.out_degree)
  35. return all(in_regular) and all(out_regular)
  36. @not_implemented_for("directed")
  37. @nx._dispatchable
  38. def is_k_regular(G, k):
  39. """Determines whether the graph ``G`` is a k-regular graph.
  40. A k-regular graph is a graph where each vertex has degree k.
  41. Parameters
  42. ----------
  43. G : NetworkX graph
  44. Returns
  45. -------
  46. bool
  47. Whether the given graph is k-regular.
  48. Examples
  49. --------
  50. >>> G = nx.Graph([(1, 2), (2, 3), (3, 4), (4, 1)])
  51. >>> nx.is_k_regular(G, k=3)
  52. False
  53. """
  54. return all(d == k for n, d in G.degree)
  55. @not_implemented_for("directed")
  56. @not_implemented_for("multigraph")
  57. @nx._dispatchable(preserve_edge_attrs=True, returns_graph=True)
  58. def k_factor(G, k, matching_weight="weight"):
  59. """Compute a `k`-factor of a graph.
  60. A `k`-factor of a graph is a spanning `k`-regular subgraph.
  61. A spanning `k`-regular subgraph of `G` is a subgraph that contains
  62. each node of `G` and a subset of the edges of `G` such that each
  63. node has degree `k`.
  64. Parameters
  65. ----------
  66. G : NetworkX graph
  67. An undirected graph.
  68. k : int
  69. The degree of the `k`-factor.
  70. matching_weight: string, optional (default="weight")
  71. Edge attribute name corresponding to the edge weight.
  72. If not present, the edge is assumed to have weight 1.
  73. Used for finding the max-weighted perfect matching.
  74. Returns
  75. -------
  76. NetworkX graph
  77. A `k`-factor of `G`.
  78. Examples
  79. --------
  80. >>> G = nx.Graph([(1, 2), (2, 3), (3, 4), (4, 1)])
  81. >>> KF = nx.k_factor(G, k=1)
  82. >>> KF.edges()
  83. EdgeView([(1, 2), (3, 4)])
  84. References
  85. ----------
  86. .. [1] "An algorithm for computing simple k-factors.",
  87. Meijer, Henk, Yurai Núñez-Rodríguez, and David Rappaport,
  88. Information processing letters, 2009.
  89. """
  90. # Validate minimum degree requirement.
  91. if any(d < k for _, d in G.degree):
  92. raise nx.NetworkXUnfeasible("Graph contains a vertex with degree less than k")
  93. g = G.copy()
  94. gadgets = []
  95. # Replace each node with a gadget.
  96. for node, degree in G.degree:
  97. is_large = k >= degree / 2.0
  98. # Create gadget nodes.
  99. outer = [(node, i) for i in range(degree)]
  100. if is_large:
  101. core = [(node, i) for i in range(degree, 2 * degree - k)]
  102. inner = []
  103. else:
  104. core = [(node, i) for i in range(2 * degree, 2 * degree + k)]
  105. inner = [(node, i) for i in range(degree, 2 * degree)]
  106. # Connect gadget nodes to neighbors.
  107. g.add_edges_from(zip(outer, inner))
  108. for outer_n, (neighbor, attrs) in zip(outer, g[node].items()):
  109. g.add_edge(outer_n, neighbor, **attrs)
  110. # Add internal edges.
  111. g.add_edges_from((u, v) for u in core for v in (outer if is_large else inner))
  112. g.remove_node(node)
  113. gadgets.append((node, outer, core, inner))
  114. # Find perfect matching.
  115. m = nx.max_weight_matching(g, maxcardinality=True, weight=matching_weight)
  116. if not nx.is_perfect_matching(g, m):
  117. raise nx.NetworkXUnfeasible(
  118. "Cannot find k-factor because no perfect matching exists"
  119. )
  120. # Keep only edges in matching.
  121. g.remove_edges_from(e for e in g.edges if e not in m and e[::-1] not in m)
  122. # Restore original nodes and remove gadgets.
  123. for node, outer, core, inner in gadgets:
  124. g.add_node(node)
  125. core_set = set(core)
  126. for outer_n in outer:
  127. for neighbor, attrs in g._adj[outer_n].items():
  128. if neighbor not in core_set:
  129. g.add_edge(node, neighbor, **attrs)
  130. break
  131. g.remove_nodes_from(outer + core + inner)
  132. return g