Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/algorithms/tournament.py: 51%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

65 statements  

1"""Functions concerning tournament graphs. 

2 

3A `tournament graph`_ is a complete oriented graph. In other words, it 

4is a directed graph in which there is exactly one directed edge joining 

5each pair of distinct nodes. For each function in this module that 

6accepts a graph as input, you must provide a tournament graph. The 

7responsibility is on the caller to ensure that the graph is a tournament 

8graph: 

9 

10 >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 0)]) 

11 >>> nx.is_tournament(G) 

12 True 

13 

14To access the functions in this module, you must access them through the 

15:mod:`networkx.tournament` module:: 

16 

17 >>> nx.tournament.is_reachable(G, 0, 1) 

18 True 

19 

20.. _tournament graph: https://en.wikipedia.org/wiki/Tournament_%28graph_theory%29 

21 

22""" 

23 

24from itertools import combinations 

25 

26import networkx as nx 

27from networkx.utils import arbitrary_element, not_implemented_for, py_random_state 

28 

29__all__ = [ 

30 "hamiltonian_path", 

31 "is_reachable", 

32 "is_strongly_connected", 

33 "is_tournament", 

34 "random_tournament", 

35 "score_sequence", 

36 "tournament_matrix", 

37] 

38 

39 

40def index_satisfying(iterable, condition): 

41 """Returns the index of the first element in `iterable` that 

42 satisfies the given condition. 

43 

44 If no such element is found (that is, when the iterable is 

45 exhausted), this returns the length of the iterable (that is, one 

46 greater than the last index of the iterable). 

47 

48 `iterable` must not be empty. If `iterable` is empty, this 

49 function raises :exc:`ValueError`. 

50 

51 """ 

52 # Pre-condition: iterable must not be empty. 

53 for i, x in enumerate(iterable): 

54 if condition(x): 

55 return i 

56 # If we reach the end of the iterable without finding an element 

57 # that satisfies the condition, return the length of the iterable, 

58 # which is one greater than the index of its last element. If the 

59 # iterable was empty, `i` will not be defined, so we raise an 

60 # exception. 

61 try: 

62 return i + 1 

63 except NameError as err: 

64 raise ValueError("iterable must be non-empty") from err 

65 

66 

67@not_implemented_for("undirected") 

68@not_implemented_for("multigraph") 

69@nx._dispatchable 

70def is_tournament(G): 

71 """Returns True if and only if `G` is a tournament. 

72 

73 A tournament is a directed graph, with neither self-loops nor 

74 multi-edges, in which there is exactly one directed edge joining 

75 each pair of distinct nodes. 

76 

77 Parameters 

78 ---------- 

79 G : NetworkX graph 

80 A directed graph representing a tournament. 

81 

82 Returns 

83 ------- 

84 bool 

85 Whether the given graph is a tournament graph. 

86 

87 Examples 

88 -------- 

89 >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 0)]) 

90 >>> nx.is_tournament(G) 

91 True 

92 

93 Notes 

94 ----- 

95 Some definitions require a self-loop on each node, but that is not 

96 the convention used here. 

97 

98 """ 

99 # In a tournament, there is exactly one directed edge joining each pair. 

100 return ( 

101 all((v in G[u]) ^ (u in G[v]) for u, v in combinations(G, 2)) 

102 and nx.number_of_selfloops(G) == 0 

103 ) 

104 

105 

106@not_implemented_for("undirected") 

107@not_implemented_for("multigraph") 

108@nx._dispatchable 

109def hamiltonian_path(G): 

110 """Returns a Hamiltonian path in the given tournament graph. 

111 

112 Each tournament has a Hamiltonian path. If furthermore, the 

113 tournament is strongly connected, then the returned Hamiltonian path 

114 is a Hamiltonian cycle (by joining the endpoints of the path). 

115 

116 Parameters 

117 ---------- 

118 G : NetworkX graph 

119 A directed graph representing a tournament. 

120 

121 Returns 

122 ------- 

123 path : list 

124 A list of nodes which form a Hamiltonian path in `G`. 

125 

126 Examples 

127 -------- 

128 >>> G = nx.DiGraph([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]) 

129 >>> nx.is_tournament(G) 

130 True 

131 >>> nx.tournament.hamiltonian_path(G) 

132 [0, 1, 2, 3] 

133 

134 Notes 

135 ----- 

136 This is a recursive implementation with an asymptotic running time 

137 of $O(n^2)$, ignoring multiplicative polylogarithmic factors, where 

138 $n$ is the number of nodes in the graph. 

139 

140 """ 

141 if len(G) == 0: 

142 return [] 

143 if len(G) == 1: 

144 return [arbitrary_element(G)] 

145 v = arbitrary_element(G) 

146 hampath = hamiltonian_path(G.subgraph(set(G) - {v})) 

147 # Get the index of the first node in the path that does *not* have 

148 # an edge to `v`, then insert `v` before that node. 

149 index = index_satisfying(hampath, lambda u: v not in G[u]) 

150 hampath.insert(index, v) 

151 return hampath 

152 

153 

154@py_random_state(1) 

155@nx._dispatchable(graphs=None, returns_graph=True) 

156def random_tournament(n, seed=None): 

157 r"""Returns a random tournament graph on `n` nodes. 

158 

159 Parameters 

160 ---------- 

161 n : int 

162 The number of nodes in the returned graph. 

163 seed : integer, random_state, or None (default) 

164 Indicator of random number generation state. 

165 See :ref:`Randomness<randomness>`. 

166 

167 Returns 

168 ------- 

169 G : DiGraph 

170 A tournament on `n` nodes, with exactly one directed edge joining 

171 each pair of distinct nodes. 

172 

173 Notes 

174 ----- 

175 This algorithm adds, for each pair of distinct nodes, an edge with 

176 uniformly random orientation. In other words, `\binom{n}{2}` flips 

177 of an unbiased coin decide the orientations of the edges in the 

178 graph. 

179 

180 """ 

181 # Flip an unbiased coin for each pair of distinct nodes. 

182 coins = (seed.random() for i in range((n * (n - 1)) // 2)) 

183 pairs = combinations(range(n), 2) 

184 edges = ((u, v) if r < 0.5 else (v, u) for (u, v), r in zip(pairs, coins)) 

185 return nx.DiGraph(edges) 

186 

187 

188@not_implemented_for("undirected") 

189@not_implemented_for("multigraph") 

190@nx._dispatchable 

191def score_sequence(G): 

192 """Returns the score sequence for the given tournament graph. 

193 

194 The score sequence is the sorted list of the out-degrees of the 

195 nodes of the graph. 

196 

197 Parameters 

198 ---------- 

199 G : NetworkX graph 

200 A directed graph representing a tournament. 

201 

202 Returns 

203 ------- 

204 list 

205 A sorted list of the out-degrees of the nodes of `G`. 

206 

207 Examples 

208 -------- 

209 >>> G = nx.DiGraph([(1, 0), (1, 3), (0, 2), (0, 3), (2, 1), (3, 2)]) 

210 >>> nx.is_tournament(G) 

211 True 

212 >>> nx.tournament.score_sequence(G) 

213 [1, 1, 2, 2] 

214 

215 """ 

216 return sorted(d for v, d in G.out_degree()) 

217 

218 

219@not_implemented_for("undirected") 

220@not_implemented_for("multigraph") 

221@nx._dispatchable(preserve_edge_attrs={"G": {"weight": 1}}) 

222def tournament_matrix(G): 

223 r"""Returns the tournament matrix for the given tournament graph. 

224 

225 This function requires SciPy. 

226 

227 The *tournament matrix* of a tournament graph with edge set *E* is 

228 the matrix *T* defined by 

229 

230 .. math:: 

231 

232 T_{i j} = 

233 \begin{cases} 

234 +1 & \text{if } (i, j) \in E \\ 

235 -1 & \text{if } (j, i) \in E \\ 

236 0 & \text{if } i == j. 

237 \end{cases} 

238 

239 An equivalent definition is `T = A - A^T`, where *A* is the 

240 adjacency matrix of the graph `G`. 

241 

242 Parameters 

243 ---------- 

244 G : NetworkX graph 

245 A directed graph representing a tournament. 

246 

247 Returns 

248 ------- 

249 SciPy sparse array 

250 The tournament matrix of the tournament graph `G`. 

251 

252 Raises 

253 ------ 

254 ImportError 

255 If SciPy is not available. 

256 

257 """ 

258 A = nx.adjacency_matrix(G) 

259 return A - A.T 

260 

261 

262@not_implemented_for("undirected") 

263@not_implemented_for("multigraph") 

264@nx._dispatchable 

265def is_reachable(G, s, t): 

266 """Decides whether there is a path from `s` to `t` in the 

267 tournament. 

268 

269 This function is more theoretically efficient than the reachability 

270 checks than the shortest path algorithms in 

271 :mod:`networkx.algorithms.shortest_paths`. 

272 

273 The given graph **must** be a tournament, otherwise this function's 

274 behavior is undefined. 

275 

276 Parameters 

277 ---------- 

278 G : NetworkX graph 

279 A directed graph representing a tournament. 

280 

281 s : node 

282 A node in the graph. 

283 

284 t : node 

285 A node in the graph. 

286 

287 Returns 

288 ------- 

289 bool 

290 Whether there is a path from `s` to `t` in `G`. 

291 

292 Examples 

293 -------- 

294 >>> G = nx.DiGraph([(1, 0), (1, 3), (1, 2), (2, 3), (2, 0), (3, 0)]) 

295 >>> nx.is_tournament(G) 

296 True 

297 >>> nx.tournament.is_reachable(G, 1, 3) 

298 True 

299 >>> nx.tournament.is_reachable(G, 3, 2) 

300 False 

301 

302 Notes 

303 ----- 

304 Although this function is more theoretically efficient than the 

305 generic shortest path functions, a speedup requires the use of 

306 parallelism. Though it may in the future, the current implementation 

307 does not use parallelism, thus you may not see much of a speedup. 

308 

309 This algorithm comes from [1]. 

310 

311 References 

312 ---------- 

313 .. [1] Tantau, Till. 

314 "A note on the complexity of the reachability problem for 

315 tournaments." 

316 *Electronic Colloquium on Computational Complexity*. 2001. 

317 <http://eccc.hpi-web.de/report/2001/092/> 

318 """ 

319 

320 def two_neighborhood(G, v): 

321 """Returns the set of nodes at distance at most two from `v`. 

322 

323 `G` must be a graph and `v` a node in that graph. 

324 

325 The returned set includes the nodes at distance zero (that is, 

326 the node `v` itself), the nodes at distance one (that is, the 

327 out-neighbors of `v`), and the nodes at distance two. 

328 

329 """ 

330 v_adj = G._adj[v] 

331 return { 

332 x 

333 for x, x_pred in G._pred.items() 

334 if x == v or x in v_adj or any(z in v_adj for z in x_pred) 

335 } 

336 

337 def is_closed(G, S): 

338 """Decides whether the given set of nodes is closed. 

339 

340 A set *S* of nodes is *closed* if for each node *u* in the graph 

341 not in *S* and for each node *v* in *S*, there is an edge from 

342 *u* to *v*. 

343 

344 """ 

345 return all(u in S or all(v in unbrs for v in S) for u, unbrs in G._adj.items()) 

346 

347 neighborhoods = (two_neighborhood(G, v) for v in G) 

348 return not any(s in S and t not in S and is_closed(G, S) for S in neighborhoods) 

349 

350 

351@not_implemented_for("undirected") 

352@not_implemented_for("multigraph") 

353@nx._dispatchable(name="tournament_is_strongly_connected") 

354def is_strongly_connected(G): 

355 """Decides whether the given tournament is strongly connected. 

356 

357 This function is more theoretically efficient than the 

358 :func:`~networkx.algorithms.components.is_strongly_connected` 

359 function. 

360 

361 The given graph **must** be a tournament, otherwise this function's 

362 behavior is undefined. 

363 

364 Parameters 

365 ---------- 

366 G : NetworkX graph 

367 A directed graph representing a tournament. 

368 

369 Returns 

370 ------- 

371 bool 

372 Whether the tournament is strongly connected. 

373 

374 Examples 

375 -------- 

376 >>> G = nx.DiGraph([(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (3, 0)]) 

377 >>> nx.is_tournament(G) 

378 True 

379 >>> nx.tournament.is_strongly_connected(G) 

380 True 

381 >>> G.remove_edge(3, 0) 

382 >>> G.add_edge(0, 3) 

383 >>> nx.is_tournament(G) 

384 True 

385 >>> nx.tournament.is_strongly_connected(G) 

386 False 

387 

388 Notes 

389 ----- 

390 Although this function is more theoretically efficient than the 

391 generic strong connectivity function, a speedup requires the use of 

392 parallelism. Though it may in the future, the current implementation 

393 does not use parallelism, thus you may not see much of a speedup. 

394 

395 This algorithm comes from [1]. 

396 

397 References 

398 ---------- 

399 .. [1] Tantau, Till. 

400 "A note on the complexity of the reachability problem for 

401 tournaments." 

402 *Electronic Colloquium on Computational Complexity*. 2001. 

403 <http://eccc.hpi-web.de/report/2001/092/> 

404 

405 """ 

406 return all(is_reachable(G, u, v) for u in G for v in G)