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

119 statements  

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

1""" Fast approximation for node connectivity 

2""" 

3import itertools 

4from operator import itemgetter 

5 

6import networkx as nx 

7 

8__all__ = [ 

9 "local_node_connectivity", 

10 "node_connectivity", 

11 "all_pairs_node_connectivity", 

12] 

13 

14 

15@nx._dispatch(name="approximate_local_node_connectivity") 

16def local_node_connectivity(G, source, target, cutoff=None): 

17 """Compute node connectivity between source and target. 

18 

19 Pairwise or local node connectivity between two distinct and nonadjacent 

20 nodes is the minimum number of nodes that must be removed (minimum 

21 separating cutset) to disconnect them. By Menger's theorem, this is equal 

22 to the number of node independent paths (paths that share no nodes other 

23 than source and target). Which is what we compute in this function. 

24 

25 This algorithm is a fast approximation that gives an strict lower 

26 bound on the actual number of node independent paths between two nodes [1]_. 

27 It works for both directed and undirected graphs. 

28 

29 Parameters 

30 ---------- 

31 

32 G : NetworkX graph 

33 

34 source : node 

35 Starting node for node connectivity 

36 

37 target : node 

38 Ending node for node connectivity 

39 

40 cutoff : integer 

41 Maximum node connectivity to consider. If None, the minimum degree 

42 of source or target is used as a cutoff. Default value None. 

43 

44 Returns 

45 ------- 

46 k: integer 

47 pairwise node connectivity 

48 

49 Examples 

50 -------- 

51 >>> # Platonic octahedral graph has node connectivity 4 

52 >>> # for each non adjacent node pair 

53 >>> from networkx.algorithms import approximation as approx 

54 >>> G = nx.octahedral_graph() 

55 >>> approx.local_node_connectivity(G, 0, 5) 

56 4 

57 

58 Notes 

59 ----- 

60 This algorithm [1]_ finds node independents paths between two nodes by 

61 computing their shortest path using BFS, marking the nodes of the path 

62 found as 'used' and then searching other shortest paths excluding the 

63 nodes marked as used until no more paths exist. It is not exact because 

64 a shortest path could use nodes that, if the path were longer, may belong 

65 to two different node independent paths. Thus it only guarantees an 

66 strict lower bound on node connectivity. 

67 

68 Note that the authors propose a further refinement, losing accuracy and 

69 gaining speed, which is not implemented yet. 

70 

71 See also 

72 -------- 

73 all_pairs_node_connectivity 

74 node_connectivity 

75 

76 References 

77 ---------- 

78 .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for 

79 Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 

80 http://eclectic.ss.uci.edu/~drwhite/working.pdf 

81 

82 """ 

83 if target == source: 

84 raise nx.NetworkXError("source and target have to be different nodes.") 

85 

86 # Maximum possible node independent paths 

87 if G.is_directed(): 

88 possible = min(G.out_degree(source), G.in_degree(target)) 

89 else: 

90 possible = min(G.degree(source), G.degree(target)) 

91 

92 K = 0 

93 if not possible: 

94 return K 

95 

96 if cutoff is None: 

97 cutoff = float("inf") 

98 

99 exclude = set() 

100 for i in range(min(possible, cutoff)): 

101 try: 

102 path = _bidirectional_shortest_path(G, source, target, exclude) 

103 exclude.update(set(path)) 

104 K += 1 

105 except nx.NetworkXNoPath: 

106 break 

107 

108 return K 

109 

110 

111@nx._dispatch(name="approximate_node_connectivity") 

112def node_connectivity(G, s=None, t=None): 

113 r"""Returns an approximation for node connectivity for a graph or digraph G. 

114 

115 Node connectivity is equal to the minimum number of nodes that 

116 must be removed to disconnect G or render it trivial. By Menger's theorem, 

117 this is equal to the number of node independent paths (paths that 

118 share no nodes other than source and target). 

119 

120 If source and target nodes are provided, this function returns the 

121 local node connectivity: the minimum number of nodes that must be 

122 removed to break all paths from source to target in G. 

123 

124 This algorithm is based on a fast approximation that gives an strict lower 

125 bound on the actual number of node independent paths between two nodes [1]_. 

126 It works for both directed and undirected graphs. 

127 

128 Parameters 

129 ---------- 

130 G : NetworkX graph 

131 Undirected graph 

132 

133 s : node 

134 Source node. Optional. Default value: None. 

135 

136 t : node 

137 Target node. Optional. Default value: None. 

138 

139 Returns 

140 ------- 

141 K : integer 

142 Node connectivity of G, or local node connectivity if source 

143 and target are provided. 

144 

145 Examples 

146 -------- 

147 >>> # Platonic octahedral graph is 4-node-connected 

148 >>> from networkx.algorithms import approximation as approx 

149 >>> G = nx.octahedral_graph() 

150 >>> approx.node_connectivity(G) 

151 4 

152 

153 Notes 

154 ----- 

155 This algorithm [1]_ finds node independents paths between two nodes by 

156 computing their shortest path using BFS, marking the nodes of the path 

157 found as 'used' and then searching other shortest paths excluding the 

158 nodes marked as used until no more paths exist. It is not exact because 

159 a shortest path could use nodes that, if the path were longer, may belong 

160 to two different node independent paths. Thus it only guarantees an 

161 strict lower bound on node connectivity. 

162 

163 See also 

164 -------- 

165 all_pairs_node_connectivity 

166 local_node_connectivity 

167 

168 References 

169 ---------- 

170 .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for 

171 Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 

172 http://eclectic.ss.uci.edu/~drwhite/working.pdf 

173 

174 """ 

175 if (s is not None and t is None) or (s is None and t is not None): 

176 raise nx.NetworkXError("Both source and target must be specified.") 

177 

178 # Local node connectivity 

179 if s is not None and t is not None: 

180 if s not in G: 

181 raise nx.NetworkXError(f"node {s} not in graph") 

182 if t not in G: 

183 raise nx.NetworkXError(f"node {t} not in graph") 

184 return local_node_connectivity(G, s, t) 

185 

186 # Global node connectivity 

187 if G.is_directed(): 

188 connected_func = nx.is_weakly_connected 

189 iter_func = itertools.permutations 

190 

191 def neighbors(v): 

192 return itertools.chain(G.predecessors(v), G.successors(v)) 

193 

194 else: 

195 connected_func = nx.is_connected 

196 iter_func = itertools.combinations 

197 neighbors = G.neighbors 

198 

199 if not connected_func(G): 

200 return 0 

201 

202 # Choose a node with minimum degree 

203 v, minimum_degree = min(G.degree(), key=itemgetter(1)) 

204 # Node connectivity is bounded by minimum degree 

205 K = minimum_degree 

206 # compute local node connectivity with all non-neighbors nodes 

207 # and store the minimum 

208 for w in set(G) - set(neighbors(v)) - {v}: 

209 K = min(K, local_node_connectivity(G, v, w, cutoff=K)) 

210 # Same for non adjacent pairs of neighbors of v 

211 for x, y in iter_func(neighbors(v), 2): 

212 if y not in G[x] and x != y: 

213 K = min(K, local_node_connectivity(G, x, y, cutoff=K)) 

214 return K 

215 

216 

217@nx._dispatch(name="approximate_all_pairs_node_connectivity") 

218def all_pairs_node_connectivity(G, nbunch=None, cutoff=None): 

219 """Compute node connectivity between all pairs of nodes. 

220 

221 Pairwise or local node connectivity between two distinct and nonadjacent 

222 nodes is the minimum number of nodes that must be removed (minimum 

223 separating cutset) to disconnect them. By Menger's theorem, this is equal 

224 to the number of node independent paths (paths that share no nodes other 

225 than source and target). Which is what we compute in this function. 

226 

227 This algorithm is a fast approximation that gives an strict lower 

228 bound on the actual number of node independent paths between two nodes [1]_. 

229 It works for both directed and undirected graphs. 

230 

231 

232 Parameters 

233 ---------- 

234 G : NetworkX graph 

235 

236 nbunch: container 

237 Container of nodes. If provided node connectivity will be computed 

238 only over pairs of nodes in nbunch. 

239 

240 cutoff : integer 

241 Maximum node connectivity to consider. If None, the minimum degree 

242 of source or target is used as a cutoff in each pair of nodes. 

243 Default value None. 

244 

245 Returns 

246 ------- 

247 K : dictionary 

248 Dictionary, keyed by source and target, of pairwise node connectivity 

249 

250 Examples 

251 -------- 

252 A 3 node cycle with one extra node attached has connectivity 2 between all 

253 nodes in the cycle and connectivity 1 between the extra node and the rest: 

254 

255 >>> G = nx.cycle_graph(3) 

256 >>> G.add_edge(2, 3) 

257 >>> import pprint # for nice dictionary formatting 

258 >>> pprint.pprint(nx.all_pairs_node_connectivity(G)) 

259 {0: {1: 2, 2: 2, 3: 1}, 

260 1: {0: 2, 2: 2, 3: 1}, 

261 2: {0: 2, 1: 2, 3: 1}, 

262 3: {0: 1, 1: 1, 2: 1}} 

263 

264 See Also 

265 -------- 

266 local_node_connectivity 

267 node_connectivity 

268 

269 References 

270 ---------- 

271 .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for 

272 Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 

273 http://eclectic.ss.uci.edu/~drwhite/working.pdf 

274 """ 

275 if nbunch is None: 

276 nbunch = G 

277 else: 

278 nbunch = set(nbunch) 

279 

280 directed = G.is_directed() 

281 if directed: 

282 iter_func = itertools.permutations 

283 else: 

284 iter_func = itertools.combinations 

285 

286 all_pairs = {n: {} for n in nbunch} 

287 

288 for u, v in iter_func(nbunch, 2): 

289 k = local_node_connectivity(G, u, v, cutoff=cutoff) 

290 all_pairs[u][v] = k 

291 if not directed: 

292 all_pairs[v][u] = k 

293 

294 return all_pairs 

295 

296 

297def _bidirectional_shortest_path(G, source, target, exclude): 

298 """Returns shortest path between source and target ignoring nodes in the 

299 container 'exclude'. 

300 

301 Parameters 

302 ---------- 

303 

304 G : NetworkX graph 

305 

306 source : node 

307 Starting node for path 

308 

309 target : node 

310 Ending node for path 

311 

312 exclude: container 

313 Container for nodes to exclude from the search for shortest paths 

314 

315 Returns 

316 ------- 

317 path: list 

318 Shortest path between source and target ignoring nodes in 'exclude' 

319 

320 Raises 

321 ------ 

322 NetworkXNoPath 

323 If there is no path or if nodes are adjacent and have only one path 

324 between them 

325 

326 Notes 

327 ----- 

328 This function and its helper are originally from 

329 networkx.algorithms.shortest_paths.unweighted and are modified to 

330 accept the extra parameter 'exclude', which is a container for nodes 

331 already used in other paths that should be ignored. 

332 

333 References 

334 ---------- 

335 .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for 

336 Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 

337 http://eclectic.ss.uci.edu/~drwhite/working.pdf 

338 

339 """ 

340 # call helper to do the real work 

341 results = _bidirectional_pred_succ(G, source, target, exclude) 

342 pred, succ, w = results 

343 

344 # build path from pred+w+succ 

345 path = [] 

346 # from source to w 

347 while w is not None: 

348 path.append(w) 

349 w = pred[w] 

350 path.reverse() 

351 # from w to target 

352 w = succ[path[-1]] 

353 while w is not None: 

354 path.append(w) 

355 w = succ[w] 

356 

357 return path 

358 

359 

360def _bidirectional_pred_succ(G, source, target, exclude): 

361 # does BFS from both source and target and meets in the middle 

362 # excludes nodes in the container "exclude" from the search 

363 

364 # handle either directed or undirected 

365 if G.is_directed(): 

366 Gpred = G.predecessors 

367 Gsucc = G.successors 

368 else: 

369 Gpred = G.neighbors 

370 Gsucc = G.neighbors 

371 

372 # predecessor and successors in search 

373 pred = {source: None} 

374 succ = {target: None} 

375 

376 # initialize fringes, start with forward 

377 forward_fringe = [source] 

378 reverse_fringe = [target] 

379 

380 level = 0 

381 

382 while forward_fringe and reverse_fringe: 

383 # Make sure that we iterate one step forward and one step backwards 

384 # thus source and target will only trigger "found path" when they are 

385 # adjacent and then they can be safely included in the container 'exclude' 

386 level += 1 

387 if level % 2 != 0: 

388 this_level = forward_fringe 

389 forward_fringe = [] 

390 for v in this_level: 

391 for w in Gsucc(v): 

392 if w in exclude: 

393 continue 

394 if w not in pred: 

395 forward_fringe.append(w) 

396 pred[w] = v 

397 if w in succ: 

398 return pred, succ, w # found path 

399 else: 

400 this_level = reverse_fringe 

401 reverse_fringe = [] 

402 for v in this_level: 

403 for w in Gpred(v): 

404 if w in exclude: 

405 continue 

406 if w not in succ: 

407 succ[w] = v 

408 reverse_fringe.append(w) 

409 if w in pred: 

410 return pred, succ, w # found path 

411 

412 raise nx.NetworkXNoPath(f"No path between {source} and {target}.")