Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/regular.py: 12%

99 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-20 07:00 +0000

1"""Functions for computing and verifying regular graphs.""" 

2import networkx as nx 

3from networkx.utils import not_implemented_for 

4 

5__all__ = ["is_regular", "is_k_regular", "k_factor"] 

6 

7 

8@nx._dispatch 

9def is_regular(G): 

10 """Determines whether the graph ``G`` is a regular graph. 

11 

12 A regular graph is a graph where each vertex has the same degree. A 

13 regular digraph is a graph where the indegree and outdegree of each 

14 vertex are equal. 

15 

16 Parameters 

17 ---------- 

18 G : NetworkX graph 

19 

20 Returns 

21 ------- 

22 bool 

23 Whether the given graph or digraph is regular. 

24 

25 Examples 

26 -------- 

27 >>> G = nx.DiGraph([(1, 2), (2, 3), (3, 4), (4, 1)]) 

28 >>> nx.is_regular(G) 

29 True 

30 

31 """ 

32 n1 = nx.utils.arbitrary_element(G) 

33 if not G.is_directed(): 

34 d1 = G.degree(n1) 

35 return all(d1 == d for _, d in G.degree) 

36 else: 

37 d_in = G.in_degree(n1) 

38 in_regular = all(d_in == d for _, d in G.in_degree) 

39 d_out = G.out_degree(n1) 

40 out_regular = all(d_out == d for _, d in G.out_degree) 

41 return in_regular and out_regular 

42 

43 

44@not_implemented_for("directed") 

45@nx._dispatch 

46def is_k_regular(G, k): 

47 """Determines whether the graph ``G`` is a k-regular graph. 

48 

49 A k-regular graph is a graph where each vertex has degree k. 

50 

51 Parameters 

52 ---------- 

53 G : NetworkX graph 

54 

55 Returns 

56 ------- 

57 bool 

58 Whether the given graph is k-regular. 

59 

60 Examples 

61 -------- 

62 >>> G = nx.Graph([(1, 2), (2, 3), (3, 4), (4, 1)]) 

63 >>> nx.is_k_regular(G, k=3) 

64 False 

65 

66 """ 

67 return all(d == k for n, d in G.degree) 

68 

69 

70@not_implemented_for("directed") 

71@not_implemented_for("multigraph") 

72@nx._dispatch(edge_attrs="matching_weight") 

73def k_factor(G, k, matching_weight="weight"): 

74 """Compute a k-factor of G 

75 

76 A k-factor of a graph is a spanning k-regular subgraph. 

77 A spanning k-regular subgraph of G is a subgraph that contains 

78 each vertex of G and a subset of the edges of G such that each 

79 vertex has degree k. 

80 

81 Parameters 

82 ---------- 

83 G : NetworkX graph 

84 Undirected graph 

85 

86 matching_weight: string, optional (default='weight') 

87 Edge data key corresponding to the edge weight. 

88 Used for finding the max-weighted perfect matching. 

89 If key not found, uses 1 as weight. 

90 

91 Returns 

92 ------- 

93 G2 : NetworkX graph 

94 A k-factor of G 

95 

96 Examples 

97 -------- 

98 >>> G = nx.Graph([(1, 2), (2, 3), (3, 4), (4, 1)]) 

99 >>> G2 = nx.k_factor(G, k=1) 

100 >>> G2.edges() 

101 EdgeView([(1, 2), (3, 4)]) 

102 

103 References 

104 ---------- 

105 .. [1] "An algorithm for computing simple k-factors.", 

106 Meijer, Henk, Yurai Núñez-Rodríguez, and David Rappaport, 

107 Information processing letters, 2009. 

108 """ 

109 

110 from networkx.algorithms.matching import is_perfect_matching, max_weight_matching 

111 

112 class LargeKGadget: 

113 def __init__(self, k, degree, node, g): 

114 self.original = node 

115 self.g = g 

116 self.k = k 

117 self.degree = degree 

118 

119 self.outer_vertices = [(node, x) for x in range(degree)] 

120 self.core_vertices = [(node, x + degree) for x in range(degree - k)] 

121 

122 def replace_node(self): 

123 adj_view = self.g[self.original] 

124 neighbors = list(adj_view.keys()) 

125 edge_attrs = list(adj_view.values()) 

126 for outer, neighbor, edge_attrs in zip( 

127 self.outer_vertices, neighbors, edge_attrs 

128 ): 

129 self.g.add_edge(outer, neighbor, **edge_attrs) 

130 for core in self.core_vertices: 

131 for outer in self.outer_vertices: 

132 self.g.add_edge(core, outer) 

133 self.g.remove_node(self.original) 

134 

135 def restore_node(self): 

136 self.g.add_node(self.original) 

137 for outer in self.outer_vertices: 

138 adj_view = self.g[outer] 

139 for neighbor, edge_attrs in list(adj_view.items()): 

140 if neighbor not in self.core_vertices: 

141 self.g.add_edge(self.original, neighbor, **edge_attrs) 

142 break 

143 g.remove_nodes_from(self.outer_vertices) 

144 g.remove_nodes_from(self.core_vertices) 

145 

146 class SmallKGadget: 

147 def __init__(self, k, degree, node, g): 

148 self.original = node 

149 self.k = k 

150 self.degree = degree 

151 self.g = g 

152 

153 self.outer_vertices = [(node, x) for x in range(degree)] 

154 self.inner_vertices = [(node, x + degree) for x in range(degree)] 

155 self.core_vertices = [(node, x + 2 * degree) for x in range(k)] 

156 

157 def replace_node(self): 

158 adj_view = self.g[self.original] 

159 for outer, inner, (neighbor, edge_attrs) in zip( 

160 self.outer_vertices, self.inner_vertices, list(adj_view.items()) 

161 ): 

162 self.g.add_edge(outer, inner) 

163 self.g.add_edge(outer, neighbor, **edge_attrs) 

164 for core in self.core_vertices: 

165 for inner in self.inner_vertices: 

166 self.g.add_edge(core, inner) 

167 self.g.remove_node(self.original) 

168 

169 def restore_node(self): 

170 self.g.add_node(self.original) 

171 for outer in self.outer_vertices: 

172 adj_view = self.g[outer] 

173 for neighbor, edge_attrs in adj_view.items(): 

174 if neighbor not in self.core_vertices: 

175 self.g.add_edge(self.original, neighbor, **edge_attrs) 

176 break 

177 self.g.remove_nodes_from(self.outer_vertices) 

178 self.g.remove_nodes_from(self.inner_vertices) 

179 self.g.remove_nodes_from(self.core_vertices) 

180 

181 # Step 1 

182 if any(d < k for _, d in G.degree): 

183 raise nx.NetworkXUnfeasible("Graph contains a vertex with degree less than k") 

184 g = G.copy() 

185 

186 # Step 2 

187 gadgets = [] 

188 for node, degree in list(g.degree): 

189 if k < degree / 2.0: 

190 gadget = SmallKGadget(k, degree, node, g) 

191 else: 

192 gadget = LargeKGadget(k, degree, node, g) 

193 gadget.replace_node() 

194 gadgets.append(gadget) 

195 

196 # Step 3 

197 matching = max_weight_matching(g, maxcardinality=True, weight=matching_weight) 

198 

199 # Step 4 

200 if not is_perfect_matching(g, matching): 

201 raise nx.NetworkXUnfeasible( 

202 "Cannot find k-factor because no perfect matching exists" 

203 ) 

204 

205 for edge in g.edges(): 

206 if edge not in matching and (edge[1], edge[0]) not in matching: 

207 g.remove_edge(edge[0], edge[1]) 

208 

209 for gadget in gadgets: 

210 gadget.restore_node() 

211 

212 return g