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

154 statements  

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

1"""Test sequences for graphiness. 

2""" 

3import heapq 

4 

5import networkx as nx 

6 

7__all__ = [ 

8 "is_graphical", 

9 "is_multigraphical", 

10 "is_pseudographical", 

11 "is_digraphical", 

12 "is_valid_degree_sequence_erdos_gallai", 

13 "is_valid_degree_sequence_havel_hakimi", 

14] 

15 

16 

17@nx._dispatch(graphs=None) 

18def is_graphical(sequence, method="eg"): 

19 """Returns True if sequence is a valid degree sequence. 

20 

21 A degree sequence is valid if some graph can realize it. 

22 

23 Parameters 

24 ---------- 

25 sequence : list or iterable container 

26 A sequence of integer node degrees 

27 

28 method : "eg" | "hh" (default: 'eg') 

29 The method used to validate the degree sequence. 

30 "eg" corresponds to the Erdős-Gallai algorithm 

31 [EG1960]_, [choudum1986]_, and 

32 "hh" to the Havel-Hakimi algorithm 

33 [havel1955]_, [hakimi1962]_, [CL1996]_. 

34 

35 Returns 

36 ------- 

37 valid : bool 

38 True if the sequence is a valid degree sequence and False if not. 

39 

40 Examples 

41 -------- 

42 >>> G = nx.path_graph(4) 

43 >>> sequence = (d for n, d in G.degree()) 

44 >>> nx.is_graphical(sequence) 

45 True 

46 

47 To test a non-graphical sequence: 

48 >>> sequence_list = [d for n, d in G.degree()] 

49 >>> sequence_list[-1] += 1 

50 >>> nx.is_graphical(sequence_list) 

51 False 

52 

53 References 

54 ---------- 

55 .. [EG1960] Erdős and Gallai, Mat. Lapok 11 264, 1960. 

56 .. [choudum1986] S.A. Choudum. "A simple proof of the Erdős-Gallai theorem on 

57 graph sequences." Bulletin of the Australian Mathematical Society, 33, 

58 pp 67-70, 1986. https://doi.org/10.1017/S0004972700002872 

59 .. [havel1955] Havel, V. "A Remark on the Existence of Finite Graphs" 

60 Casopis Pest. Mat. 80, 477-480, 1955. 

61 .. [hakimi1962] Hakimi, S. "On the Realizability of a Set of Integers as 

62 Degrees of the Vertices of a Graph." SIAM J. Appl. Math. 10, 496-506, 1962. 

63 .. [CL1996] G. Chartrand and L. Lesniak, "Graphs and Digraphs", 

64 Chapman and Hall/CRC, 1996. 

65 """ 

66 if method == "eg": 

67 valid = is_valid_degree_sequence_erdos_gallai(list(sequence)) 

68 elif method == "hh": 

69 valid = is_valid_degree_sequence_havel_hakimi(list(sequence)) 

70 else: 

71 msg = "`method` must be 'eg' or 'hh'" 

72 raise nx.NetworkXException(msg) 

73 return valid 

74 

75 

76def _basic_graphical_tests(deg_sequence): 

77 # Sort and perform some simple tests on the sequence 

78 deg_sequence = nx.utils.make_list_of_ints(deg_sequence) 

79 p = len(deg_sequence) 

80 num_degs = [0] * p 

81 dmax, dmin, dsum, n = 0, p, 0, 0 

82 for d in deg_sequence: 

83 # Reject if degree is negative or larger than the sequence length 

84 if d < 0 or d >= p: 

85 raise nx.NetworkXUnfeasible 

86 # Process only the non-zero integers 

87 elif d > 0: 

88 dmax, dmin, dsum, n = max(dmax, d), min(dmin, d), dsum + d, n + 1 

89 num_degs[d] += 1 

90 # Reject sequence if it has odd sum or is oversaturated 

91 if dsum % 2 or dsum > n * (n - 1): 

92 raise nx.NetworkXUnfeasible 

93 return dmax, dmin, dsum, n, num_degs 

94 

95 

96@nx._dispatch(graphs=None) 

97def is_valid_degree_sequence_havel_hakimi(deg_sequence): 

98 r"""Returns True if deg_sequence can be realized by a simple graph. 

99 

100 The validation proceeds using the Havel-Hakimi theorem 

101 [havel1955]_, [hakimi1962]_, [CL1996]_. 

102 Worst-case run time is $O(s)$ where $s$ is the sum of the sequence. 

103 

104 Parameters 

105 ---------- 

106 deg_sequence : list 

107 A list of integers where each element specifies the degree of a node 

108 in a graph. 

109 

110 Returns 

111 ------- 

112 valid : bool 

113 True if deg_sequence is graphical and False if not. 

114 

115 Examples 

116 -------- 

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

118 >>> sequence = (d for _, d in G.degree()) 

119 >>> nx.is_valid_degree_sequence_havel_hakimi(sequence) 

120 True 

121 

122 To test a non-valid sequence: 

123 >>> sequence_list = [d for _, d in G.degree()] 

124 >>> sequence_list[-1] += 1 

125 >>> nx.is_valid_degree_sequence_havel_hakimi(sequence_list) 

126 False 

127 

128 Notes 

129 ----- 

130 The ZZ condition says that for the sequence d if 

131 

132 .. math:: 

133 |d| >= \frac{(\max(d) + \min(d) + 1)^2}{4*\min(d)} 

134 

135 then d is graphical. This was shown in Theorem 6 in [1]_. 

136 

137 References 

138 ---------- 

139 .. [1] I.E. Zverovich and V.E. Zverovich. "Contributions to the theory 

140 of graphic sequences", Discrete Mathematics, 105, pp. 292-303 (1992). 

141 .. [havel1955] Havel, V. "A Remark on the Existence of Finite Graphs" 

142 Casopis Pest. Mat. 80, 477-480, 1955. 

143 .. [hakimi1962] Hakimi, S. "On the Realizability of a Set of Integers as 

144 Degrees of the Vertices of a Graph." SIAM J. Appl. Math. 10, 496-506, 1962. 

145 .. [CL1996] G. Chartrand and L. Lesniak, "Graphs and Digraphs", 

146 Chapman and Hall/CRC, 1996. 

147 """ 

148 try: 

149 dmax, dmin, dsum, n, num_degs = _basic_graphical_tests(deg_sequence) 

150 except nx.NetworkXUnfeasible: 

151 return False 

152 # Accept if sequence has no non-zero degrees or passes the ZZ condition 

153 if n == 0 or 4 * dmin * n >= (dmax + dmin + 1) * (dmax + dmin + 1): 

154 return True 

155 

156 modstubs = [0] * (dmax + 1) 

157 # Successively reduce degree sequence by removing the maximum degree 

158 while n > 0: 

159 # Retrieve the maximum degree in the sequence 

160 while num_degs[dmax] == 0: 

161 dmax -= 1 

162 # If there are not enough stubs to connect to, then the sequence is 

163 # not graphical 

164 if dmax > n - 1: 

165 return False 

166 

167 # Remove largest stub in list 

168 num_degs[dmax], n = num_degs[dmax] - 1, n - 1 

169 # Reduce the next dmax largest stubs 

170 mslen = 0 

171 k = dmax 

172 for i in range(dmax): 

173 while num_degs[k] == 0: 

174 k -= 1 

175 num_degs[k], n = num_degs[k] - 1, n - 1 

176 if k > 1: 

177 modstubs[mslen] = k - 1 

178 mslen += 1 

179 # Add back to the list any non-zero stubs that were removed 

180 for i in range(mslen): 

181 stub = modstubs[i] 

182 num_degs[stub], n = num_degs[stub] + 1, n + 1 

183 return True 

184 

185 

186@nx._dispatch(graphs=None) 

187def is_valid_degree_sequence_erdos_gallai(deg_sequence): 

188 r"""Returns True if deg_sequence can be realized by a simple graph. 

189 

190 The validation is done using the Erdős-Gallai theorem [EG1960]_. 

191 

192 Parameters 

193 ---------- 

194 deg_sequence : list 

195 A list of integers 

196 

197 Returns 

198 ------- 

199 valid : bool 

200 True if deg_sequence is graphical and False if not. 

201 

202 Examples 

203 -------- 

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

205 >>> sequence = (d for _, d in G.degree()) 

206 >>> nx.is_valid_degree_sequence_erdos_gallai(sequence) 

207 True 

208 

209 To test a non-valid sequence: 

210 >>> sequence_list = [d for _, d in G.degree()] 

211 >>> sequence_list[-1] += 1 

212 >>> nx.is_valid_degree_sequence_erdos_gallai(sequence_list) 

213 False 

214 

215 Notes 

216 ----- 

217 

218 This implementation uses an equivalent form of the Erdős-Gallai criterion. 

219 Worst-case run time is $O(n)$ where $n$ is the length of the sequence. 

220 

221 Specifically, a sequence d is graphical if and only if the 

222 sum of the sequence is even and for all strong indices k in the sequence, 

223 

224 .. math:: 

225 

226 \sum_{i=1}^{k} d_i \leq k(k-1) + \sum_{j=k+1}^{n} \min(d_i,k) 

227 = k(n-1) - ( k \sum_{j=0}^{k-1} n_j - \sum_{j=0}^{k-1} j n_j ) 

228 

229 A strong index k is any index where d_k >= k and the value n_j is the 

230 number of occurrences of j in d. The maximal strong index is called the 

231 Durfee index. 

232 

233 This particular rearrangement comes from the proof of Theorem 3 in [2]_. 

234 

235 The ZZ condition says that for the sequence d if 

236 

237 .. math:: 

238 |d| >= \frac{(\max(d) + \min(d) + 1)^2}{4*\min(d)} 

239 

240 then d is graphical. This was shown in Theorem 6 in [2]_. 

241 

242 References 

243 ---------- 

244 .. [1] A. Tripathi and S. Vijay. "A note on a theorem of Erdős & Gallai", 

245 Discrete Mathematics, 265, pp. 417-420 (2003). 

246 .. [2] I.E. Zverovich and V.E. Zverovich. "Contributions to the theory 

247 of graphic sequences", Discrete Mathematics, 105, pp. 292-303 (1992). 

248 .. [EG1960] Erdős and Gallai, Mat. Lapok 11 264, 1960. 

249 """ 

250 try: 

251 dmax, dmin, dsum, n, num_degs = _basic_graphical_tests(deg_sequence) 

252 except nx.NetworkXUnfeasible: 

253 return False 

254 # Accept if sequence has no non-zero degrees or passes the ZZ condition 

255 if n == 0 or 4 * dmin * n >= (dmax + dmin + 1) * (dmax + dmin + 1): 

256 return True 

257 

258 # Perform the EG checks using the reformulation of Zverovich and Zverovich 

259 k, sum_deg, sum_nj, sum_jnj = 0, 0, 0, 0 

260 for dk in range(dmax, dmin - 1, -1): 

261 if dk < k + 1: # Check if already past Durfee index 

262 return True 

263 if num_degs[dk] > 0: 

264 run_size = num_degs[dk] # Process a run of identical-valued degrees 

265 if dk < k + run_size: # Check if end of run is past Durfee index 

266 run_size = dk - k # Adjust back to Durfee index 

267 sum_deg += run_size * dk 

268 for v in range(run_size): 

269 sum_nj += num_degs[k + v] 

270 sum_jnj += (k + v) * num_degs[k + v] 

271 k += run_size 

272 if sum_deg > k * (n - 1) - k * sum_nj + sum_jnj: 

273 return False 

274 return True 

275 

276 

277@nx._dispatch(graphs=None) 

278def is_multigraphical(sequence): 

279 """Returns True if some multigraph can realize the sequence. 

280 

281 Parameters 

282 ---------- 

283 sequence : list 

284 A list of integers 

285 

286 Returns 

287 ------- 

288 valid : bool 

289 True if deg_sequence is a multigraphic degree sequence and False if not. 

290 

291 Examples 

292 -------- 

293 >>> G = nx.MultiGraph([(1, 2), (1, 3), (2, 3), (3, 4), (4, 2), (5, 1), (5, 4)]) 

294 >>> sequence = (d for _, d in G.degree()) 

295 >>> nx.is_multigraphical(sequence) 

296 True 

297 

298 To test a non-multigraphical sequence: 

299 >>> sequence_list = [d for _, d in G.degree()] 

300 >>> sequence_list[-1] += 1 

301 >>> nx.is_multigraphical(sequence_list) 

302 False 

303 

304 Notes 

305 ----- 

306 The worst-case run time is $O(n)$ where $n$ is the length of the sequence. 

307 

308 References 

309 ---------- 

310 .. [1] S. L. Hakimi. "On the realizability of a set of integers as 

311 degrees of the vertices of a linear graph", J. SIAM, 10, pp. 496-506 

312 (1962). 

313 """ 

314 try: 

315 deg_sequence = nx.utils.make_list_of_ints(sequence) 

316 except nx.NetworkXError: 

317 return False 

318 dsum, dmax = 0, 0 

319 for d in deg_sequence: 

320 if d < 0: 

321 return False 

322 dsum, dmax = dsum + d, max(dmax, d) 

323 if dsum % 2 or dsum < 2 * dmax: 

324 return False 

325 return True 

326 

327 

328@nx._dispatch(graphs=None) 

329def is_pseudographical(sequence): 

330 """Returns True if some pseudograph can realize the sequence. 

331 

332 Every nonnegative integer sequence with an even sum is pseudographical 

333 (see [1]_). 

334 

335 Parameters 

336 ---------- 

337 sequence : list or iterable container 

338 A sequence of integer node degrees 

339 

340 Returns 

341 ------- 

342 valid : bool 

343 True if the sequence is a pseudographic degree sequence and False if not. 

344 

345 Examples 

346 -------- 

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

348 >>> sequence = (d for _, d in G.degree()) 

349 >>> nx.is_pseudographical(sequence) 

350 True 

351 

352 To test a non-pseudographical sequence: 

353 >>> sequence_list = [d for _, d in G.degree()] 

354 >>> sequence_list[-1] += 1 

355 >>> nx.is_pseudographical(sequence_list) 

356 False 

357 

358 Notes 

359 ----- 

360 The worst-case run time is $O(n)$ where n is the length of the sequence. 

361 

362 References 

363 ---------- 

364 .. [1] F. Boesch and F. Harary. "Line removal algorithms for graphs 

365 and their degree lists", IEEE Trans. Circuits and Systems, CAS-23(12), 

366 pp. 778-782 (1976). 

367 """ 

368 try: 

369 deg_sequence = nx.utils.make_list_of_ints(sequence) 

370 except nx.NetworkXError: 

371 return False 

372 return sum(deg_sequence) % 2 == 0 and min(deg_sequence) >= 0 

373 

374 

375@nx._dispatch(graphs=None) 

376def is_digraphical(in_sequence, out_sequence): 

377 r"""Returns True if some directed graph can realize the in- and out-degree 

378 sequences. 

379 

380 Parameters 

381 ---------- 

382 in_sequence : list or iterable container 

383 A sequence of integer node in-degrees 

384 

385 out_sequence : list or iterable container 

386 A sequence of integer node out-degrees 

387 

388 Returns 

389 ------- 

390 valid : bool 

391 True if in and out-sequences are digraphic False if not. 

392 

393 Examples 

394 -------- 

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

396 >>> in_seq = (d for n, d in G.in_degree()) 

397 >>> out_seq = (d for n, d in G.out_degree()) 

398 >>> nx.is_digraphical(in_seq, out_seq) 

399 True 

400 

401 To test a non-digraphical scenario: 

402 >>> in_seq_list = [d for n, d in G.in_degree()] 

403 >>> in_seq_list[-1] += 1 

404 >>> nx.is_digraphical(in_seq_list, out_seq) 

405 False 

406 

407 Notes 

408 ----- 

409 This algorithm is from Kleitman and Wang [1]_. 

410 The worst case runtime is $O(s \times \log n)$ where $s$ and $n$ are the 

411 sum and length of the sequences respectively. 

412 

413 References 

414 ---------- 

415 .. [1] D.J. Kleitman and D.L. Wang 

416 Algorithms for Constructing Graphs and Digraphs with Given Valences 

417 and Factors, Discrete Mathematics, 6(1), pp. 79-88 (1973) 

418 """ 

419 try: 

420 in_deg_sequence = nx.utils.make_list_of_ints(in_sequence) 

421 out_deg_sequence = nx.utils.make_list_of_ints(out_sequence) 

422 except nx.NetworkXError: 

423 return False 

424 # Process the sequences and form two heaps to store degree pairs with 

425 # either zero or non-zero out degrees 

426 sumin, sumout, nin, nout = 0, 0, len(in_deg_sequence), len(out_deg_sequence) 

427 maxn = max(nin, nout) 

428 maxin = 0 

429 if maxn == 0: 

430 return True 

431 stubheap, zeroheap = [], [] 

432 for n in range(maxn): 

433 in_deg, out_deg = 0, 0 

434 if n < nout: 

435 out_deg = out_deg_sequence[n] 

436 if n < nin: 

437 in_deg = in_deg_sequence[n] 

438 if in_deg < 0 or out_deg < 0: 

439 return False 

440 sumin, sumout, maxin = sumin + in_deg, sumout + out_deg, max(maxin, in_deg) 

441 if in_deg > 0: 

442 stubheap.append((-1 * out_deg, -1 * in_deg)) 

443 elif out_deg > 0: 

444 zeroheap.append(-1 * out_deg) 

445 if sumin != sumout: 

446 return False 

447 heapq.heapify(stubheap) 

448 heapq.heapify(zeroheap) 

449 

450 modstubs = [(0, 0)] * (maxin + 1) 

451 # Successively reduce degree sequence by removing the maximum out degree 

452 while stubheap: 

453 # Take the first value in the sequence with non-zero in degree 

454 (freeout, freein) = heapq.heappop(stubheap) 

455 freein *= -1 

456 if freein > len(stubheap) + len(zeroheap): 

457 return False 

458 

459 # Attach out stubs to the nodes with the most in stubs 

460 mslen = 0 

461 for i in range(freein): 

462 if zeroheap and (not stubheap or stubheap[0][0] > zeroheap[0]): 

463 stubout = heapq.heappop(zeroheap) 

464 stubin = 0 

465 else: 

466 (stubout, stubin) = heapq.heappop(stubheap) 

467 if stubout == 0: 

468 return False 

469 # Check if target is now totally connected 

470 if stubout + 1 < 0 or stubin < 0: 

471 modstubs[mslen] = (stubout + 1, stubin) 

472 mslen += 1 

473 

474 # Add back the nodes to the heap that still have available stubs 

475 for i in range(mslen): 

476 stub = modstubs[i] 

477 if stub[1] < 0: 

478 heapq.heappush(stubheap, stub) 

479 else: 

480 heapq.heappush(zeroheap, stub[0]) 

481 if freeout < 0: 

482 heapq.heappush(zeroheap, freeout) 

483 return True