123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- """Functions for computing and verifying regular graphs."""
- import networkx as nx
- from networkx.utils import not_implemented_for
- __all__ = ["is_regular", "is_k_regular", "k_factor"]
- @nx._dispatch
- def is_regular(G):
- """Determines whether the graph ``G`` is a regular graph.
- A regular graph is a graph where each vertex has the same degree. A
- regular digraph is a graph where the indegree and outdegree of each
- vertex are equal.
- Parameters
- ----------
- G : NetworkX graph
- Returns
- -------
- bool
- Whether the given graph or digraph is regular.
- Examples
- --------
- >>> G = nx.DiGraph([(1, 2), (2, 3), (3, 4), (4, 1)])
- >>> nx.is_regular(G)
- True
- """
- n1 = nx.utils.arbitrary_element(G)
- if not G.is_directed():
- d1 = G.degree(n1)
- return all(d1 == d for _, d in G.degree)
- else:
- d_in = G.in_degree(n1)
- in_regular = all(d_in == d for _, d in G.in_degree)
- d_out = G.out_degree(n1)
- out_regular = all(d_out == d for _, d in G.out_degree)
- return in_regular and out_regular
- @nx._dispatch
- @not_implemented_for("directed")
- def is_k_regular(G, k):
- """Determines whether the graph ``G`` is a k-regular graph.
- A k-regular graph is a graph where each vertex has degree k.
- Parameters
- ----------
- G : NetworkX graph
- Returns
- -------
- bool
- Whether the given graph is k-regular.
- Examples
- --------
- >>> G = nx.Graph([(1, 2), (2, 3), (3, 4), (4, 1)])
- >>> nx.is_k_regular(G, k=3)
- False
- """
- return all(d == k for n, d in G.degree)
- @not_implemented_for("directed")
- @not_implemented_for("multigraph")
- def k_factor(G, k, matching_weight="weight"):
- """Compute a k-factor of G
- A k-factor of a graph is a spanning k-regular subgraph.
- A spanning k-regular subgraph of G is a subgraph that contains
- each vertex of G and a subset of the edges of G such that each
- vertex has degree k.
- Parameters
- ----------
- G : NetworkX graph
- Undirected graph
- matching_weight: string, optional (default='weight')
- Edge data key corresponding to the edge weight.
- Used for finding the max-weighted perfect matching.
- If key not found, uses 1 as weight.
- Returns
- -------
- G2 : NetworkX graph
- A k-factor of G
- Examples
- --------
- >>> G = nx.Graph([(1, 2), (2, 3), (3, 4), (4, 1)])
- >>> G2 = nx.k_factor(G, k=1)
- >>> G2.edges()
- EdgeView([(1, 2), (3, 4)])
- References
- ----------
- .. [1] "An algorithm for computing simple k-factors.",
- Meijer, Henk, Yurai Núñez-Rodríguez, and David Rappaport,
- Information processing letters, 2009.
- """
- from networkx.algorithms.matching import is_perfect_matching, max_weight_matching
- class LargeKGadget:
- def __init__(self, k, degree, node, g):
- self.original = node
- self.g = g
- self.k = k
- self.degree = degree
- self.outer_vertices = [(node, x) for x in range(degree)]
- self.core_vertices = [(node, x + degree) for x in range(degree - k)]
- def replace_node(self):
- adj_view = self.g[self.original]
- neighbors = list(adj_view.keys())
- edge_attrs = list(adj_view.values())
- for outer, neighbor, edge_attrs in zip(
- self.outer_vertices, neighbors, edge_attrs
- ):
- self.g.add_edge(outer, neighbor, **edge_attrs)
- for core in self.core_vertices:
- for outer in self.outer_vertices:
- self.g.add_edge(core, outer)
- self.g.remove_node(self.original)
- def restore_node(self):
- self.g.add_node(self.original)
- for outer in self.outer_vertices:
- adj_view = self.g[outer]
- for neighbor, edge_attrs in list(adj_view.items()):
- if neighbor not in self.core_vertices:
- self.g.add_edge(self.original, neighbor, **edge_attrs)
- break
- g.remove_nodes_from(self.outer_vertices)
- g.remove_nodes_from(self.core_vertices)
- class SmallKGadget:
- def __init__(self, k, degree, node, g):
- self.original = node
- self.k = k
- self.degree = degree
- self.g = g
- self.outer_vertices = [(node, x) for x in range(degree)]
- self.inner_vertices = [(node, x + degree) for x in range(degree)]
- self.core_vertices = [(node, x + 2 * degree) for x in range(k)]
- def replace_node(self):
- adj_view = self.g[self.original]
- for outer, inner, (neighbor, edge_attrs) in zip(
- self.outer_vertices, self.inner_vertices, list(adj_view.items())
- ):
- self.g.add_edge(outer, inner)
- self.g.add_edge(outer, neighbor, **edge_attrs)
- for core in self.core_vertices:
- for inner in self.inner_vertices:
- self.g.add_edge(core, inner)
- self.g.remove_node(self.original)
- def restore_node(self):
- self.g.add_node(self.original)
- for outer in self.outer_vertices:
- adj_view = self.g[outer]
- for neighbor, edge_attrs in adj_view.items():
- if neighbor not in self.core_vertices:
- self.g.add_edge(self.original, neighbor, **edge_attrs)
- break
- self.g.remove_nodes_from(self.outer_vertices)
- self.g.remove_nodes_from(self.inner_vertices)
- self.g.remove_nodes_from(self.core_vertices)
-
- if any(d < k for _, d in G.degree):
- raise nx.NetworkXUnfeasible("Graph contains a vertex with degree less than k")
- g = G.copy()
-
- gadgets = []
- for node, degree in list(g.degree):
- if k < degree / 2.0:
- gadget = SmallKGadget(k, degree, node, g)
- else:
- gadget = LargeKGadget(k, degree, node, g)
- gadget.replace_node()
- gadgets.append(gadget)
-
- matching = max_weight_matching(g, maxcardinality=True, weight=matching_weight)
-
- if not is_perfect_matching(g, matching):
- raise nx.NetworkXUnfeasible(
- "Cannot find k-factor because no perfect matching exists"
- )
- for edge in g.edges():
- if edge not in matching and (edge[1], edge[0]) not in matching:
- g.remove_edge(edge[0], edge[1])
- for gadget in gadgets:
- gadget.restore_node()
- return g
|