Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/generators/joint_degree_seq.py: 7%

232 statements  

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

1"""Generate graphs with a given joint degree and directed joint degree""" 

2 

3import networkx as nx 

4from networkx.utils import py_random_state 

5 

6__all__ = [ 

7 "is_valid_joint_degree", 

8 "is_valid_directed_joint_degree", 

9 "joint_degree_graph", 

10 "directed_joint_degree_graph", 

11] 

12 

13 

14@nx._dispatch(graphs=None) 

15def is_valid_joint_degree(joint_degrees): 

16 """Checks whether the given joint degree dictionary is realizable. 

17 

18 A *joint degree dictionary* is a dictionary of dictionaries, in 

19 which entry ``joint_degrees[k][l]`` is an integer representing the 

20 number of edges joining nodes of degree *k* with nodes of degree 

21 *l*. Such a dictionary is realizable as a simple graph if and only 

22 if the following conditions are satisfied. 

23 

24 - each entry must be an integer, 

25 - the total number of nodes of degree *k*, computed by 

26 ``sum(joint_degrees[k].values()) / k``, must be an integer, 

27 - the total number of edges joining nodes of degree *k* with 

28 nodes of degree *l* cannot exceed the total number of possible edges, 

29 - each diagonal entry ``joint_degrees[k][k]`` must be even (this is 

30 a convention assumed by the :func:`joint_degree_graph` function). 

31 

32 

33 Parameters 

34 ---------- 

35 joint_degrees : dictionary of dictionary of integers 

36 A joint degree dictionary in which entry ``joint_degrees[k][l]`` 

37 is the number of edges joining nodes of degree *k* with nodes of 

38 degree *l*. 

39 

40 Returns 

41 ------- 

42 bool 

43 Whether the given joint degree dictionary is realizable as a 

44 simple graph. 

45 

46 References 

47 ---------- 

48 .. [1] M. Gjoka, M. Kurant, A. Markopoulou, "2.5K Graphs: from Sampling 

49 to Generation", IEEE Infocom, 2013. 

50 .. [2] I. Stanton, A. Pinar, "Constructing and sampling graphs with a 

51 prescribed joint degree distribution", Journal of Experimental 

52 Algorithmics, 2012. 

53 """ 

54 

55 degree_count = {} 

56 for k in joint_degrees: 

57 if k > 0: 

58 k_size = sum(joint_degrees[k].values()) / k 

59 if not k_size.is_integer(): 

60 return False 

61 degree_count[k] = k_size 

62 

63 for k in joint_degrees: 

64 for l in joint_degrees[k]: 

65 if not float(joint_degrees[k][l]).is_integer(): 

66 return False 

67 

68 if (k != l) and (joint_degrees[k][l] > degree_count[k] * degree_count[l]): 

69 return False 

70 elif k == l: 

71 if joint_degrees[k][k] > degree_count[k] * (degree_count[k] - 1): 

72 return False 

73 if joint_degrees[k][k] % 2 != 0: 

74 return False 

75 

76 # if all above conditions have been satisfied then the input 

77 # joint degree is realizable as a simple graph. 

78 return True 

79 

80 

81def _neighbor_switch(G, w, unsat, h_node_residual, avoid_node_id=None): 

82 """Releases one free stub for ``w``, while preserving joint degree in G. 

83 

84 Parameters 

85 ---------- 

86 G : NetworkX graph 

87 Graph in which the neighbor switch will take place. 

88 w : integer 

89 Node id for which we will execute this neighbor switch. 

90 unsat : set of integers 

91 Set of unsaturated node ids that have the same degree as w. 

92 h_node_residual: dictionary of integers 

93 Keeps track of the remaining stubs for a given node. 

94 avoid_node_id: integer 

95 Node id to avoid when selecting w_prime. 

96 

97 Notes 

98 ----- 

99 First, it selects *w_prime*, an unsaturated node that has the same degree 

100 as ``w``. Second, it selects *switch_node*, a neighbor node of ``w`` that 

101 is not connected to *w_prime*. Then it executes an edge swap i.e. removes 

102 (``w``,*switch_node*) and adds (*w_prime*,*switch_node*). Gjoka et. al. [1] 

103 prove that such an edge swap is always possible. 

104 

105 References 

106 ---------- 

107 .. [1] M. Gjoka, B. Tillman, A. Markopoulou, "Construction of Simple 

108 Graphs with a Target Joint Degree Matrix and Beyond", IEEE Infocom, '15 

109 """ 

110 

111 if (avoid_node_id is None) or (h_node_residual[avoid_node_id] > 1): 

112 # select unsaturated node w_prime that has the same degree as w 

113 w_prime = next(iter(unsat)) 

114 else: 

115 # assume that the node pair (v,w) has been selected for connection. if 

116 # - neighbor_switch is called for node w, 

117 # - nodes v and w have the same degree, 

118 # - node v=avoid_node_id has only one stub left, 

119 # then prevent v=avoid_node_id from being selected as w_prime. 

120 

121 iter_var = iter(unsat) 

122 while True: 

123 w_prime = next(iter_var) 

124 if w_prime != avoid_node_id: 

125 break 

126 

127 # select switch_node, a neighbor of w, that is not connected to w_prime 

128 w_prime_neighbs = G[w_prime] # slightly faster declaring this variable 

129 for v in G[w]: 

130 if (v not in w_prime_neighbs) and (v != w_prime): 

131 switch_node = v 

132 break 

133 

134 # remove edge (w,switch_node), add edge (w_prime,switch_node) and update 

135 # data structures 

136 G.remove_edge(w, switch_node) 

137 G.add_edge(w_prime, switch_node) 

138 h_node_residual[w] += 1 

139 h_node_residual[w_prime] -= 1 

140 if h_node_residual[w_prime] == 0: 

141 unsat.remove(w_prime) 

142 

143 

144@py_random_state(1) 

145@nx._dispatch(graphs=None) 

146def joint_degree_graph(joint_degrees, seed=None): 

147 """Generates a random simple graph with the given joint degree dictionary. 

148 

149 Parameters 

150 ---------- 

151 joint_degrees : dictionary of dictionary of integers 

152 A joint degree dictionary in which entry ``joint_degrees[k][l]`` is the 

153 number of edges joining nodes of degree *k* with nodes of degree *l*. 

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

155 Indicator of random number generation state. 

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

157 

158 Returns 

159 ------- 

160 G : Graph 

161 A graph with the specified joint degree dictionary. 

162 

163 Raises 

164 ------ 

165 NetworkXError 

166 If *joint_degrees* dictionary is not realizable. 

167 

168 Notes 

169 ----- 

170 In each iteration of the "while loop" the algorithm picks two disconnected 

171 nodes *v* and *w*, of degree *k* and *l* correspondingly, for which 

172 ``joint_degrees[k][l]`` has not reached its target yet. It then adds 

173 edge (*v*, *w*) and increases the number of edges in graph G by one. 

174 

175 The intelligence of the algorithm lies in the fact that it is always 

176 possible to add an edge between such disconnected nodes *v* and *w*, 

177 even if one or both nodes do not have free stubs. That is made possible by 

178 executing a "neighbor switch", an edge rewiring move that releases 

179 a free stub while keeping the joint degree of G the same. 

180 

181 The algorithm continues for E (number of edges) iterations of 

182 the "while loop", at the which point all entries of the given 

183 ``joint_degrees[k][l]`` have reached their target values and the 

184 construction is complete. 

185 

186 References 

187 ---------- 

188 .. [1] M. Gjoka, B. Tillman, A. Markopoulou, "Construction of Simple 

189 Graphs with a Target Joint Degree Matrix and Beyond", IEEE Infocom, '15 

190 

191 Examples 

192 -------- 

193 >>> joint_degrees = { 

194 ... 1: {4: 1}, 

195 ... 2: {2: 2, 3: 2, 4: 2}, 

196 ... 3: {2: 2, 4: 1}, 

197 ... 4: {1: 1, 2: 2, 3: 1}, 

198 ... } 

199 >>> G = nx.joint_degree_graph(joint_degrees) 

200 >>> 

201 """ 

202 

203 if not is_valid_joint_degree(joint_degrees): 

204 msg = "Input joint degree dict not realizable as a simple graph" 

205 raise nx.NetworkXError(msg) 

206 

207 # compute degree count from joint_degrees 

208 degree_count = {k: sum(l.values()) // k for k, l in joint_degrees.items() if k > 0} 

209 

210 # start with empty N-node graph 

211 N = sum(degree_count.values()) 

212 G = nx.empty_graph(N) 

213 

214 # for a given degree group, keep the list of all node ids 

215 h_degree_nodelist = {} 

216 

217 # for a given node, keep track of the remaining stubs 

218 h_node_residual = {} 

219 

220 # populate h_degree_nodelist and h_node_residual 

221 nodeid = 0 

222 for degree, num_nodes in degree_count.items(): 

223 h_degree_nodelist[degree] = range(nodeid, nodeid + num_nodes) 

224 for v in h_degree_nodelist[degree]: 

225 h_node_residual[v] = degree 

226 nodeid += int(num_nodes) 

227 

228 # iterate over every degree pair (k,l) and add the number of edges given 

229 # for each pair 

230 for k in joint_degrees: 

231 for l in joint_degrees[k]: 

232 # n_edges_add is the number of edges to add for the 

233 # degree pair (k,l) 

234 n_edges_add = joint_degrees[k][l] 

235 

236 if (n_edges_add > 0) and (k >= l): 

237 # number of nodes with degree k and l 

238 k_size = degree_count[k] 

239 l_size = degree_count[l] 

240 

241 # k_nodes and l_nodes consist of all nodes of degree k and l 

242 k_nodes = h_degree_nodelist[k] 

243 l_nodes = h_degree_nodelist[l] 

244 

245 # k_unsat and l_unsat consist of nodes of degree k and l that 

246 # are unsaturated (nodes that have at least 1 available stub) 

247 k_unsat = {v for v in k_nodes if h_node_residual[v] > 0} 

248 

249 if k != l: 

250 l_unsat = {w for w in l_nodes if h_node_residual[w] > 0} 

251 else: 

252 l_unsat = k_unsat 

253 n_edges_add = joint_degrees[k][l] // 2 

254 

255 while n_edges_add > 0: 

256 # randomly pick nodes v and w that have degrees k and l 

257 v = k_nodes[seed.randrange(k_size)] 

258 w = l_nodes[seed.randrange(l_size)] 

259 

260 # if nodes v and w are disconnected then attempt to connect 

261 if not G.has_edge(v, w) and (v != w): 

262 # if node v has no free stubs then do neighbor switch 

263 if h_node_residual[v] == 0: 

264 _neighbor_switch(G, v, k_unsat, h_node_residual) 

265 

266 # if node w has no free stubs then do neighbor switch 

267 if h_node_residual[w] == 0: 

268 if k != l: 

269 _neighbor_switch(G, w, l_unsat, h_node_residual) 

270 else: 

271 _neighbor_switch( 

272 G, w, l_unsat, h_node_residual, avoid_node_id=v 

273 ) 

274 

275 # add edge (v, w) and update data structures 

276 G.add_edge(v, w) 

277 h_node_residual[v] -= 1 

278 h_node_residual[w] -= 1 

279 n_edges_add -= 1 

280 

281 if h_node_residual[v] == 0: 

282 k_unsat.discard(v) 

283 if h_node_residual[w] == 0: 

284 l_unsat.discard(w) 

285 return G 

286 

287 

288@nx._dispatch(graphs=None) 

289def is_valid_directed_joint_degree(in_degrees, out_degrees, nkk): 

290 """Checks whether the given directed joint degree input is realizable 

291 

292 Parameters 

293 ---------- 

294 in_degrees : list of integers 

295 in degree sequence contains the in degrees of nodes. 

296 out_degrees : list of integers 

297 out degree sequence contains the out degrees of nodes. 

298 nkk : dictionary of dictionary of integers 

299 directed joint degree dictionary. for nodes of out degree k (first 

300 level of dict) and nodes of in degree l (second level of dict) 

301 describes the number of edges. 

302 

303 Returns 

304 ------- 

305 boolean 

306 returns true if given input is realizable, else returns false. 

307 

308 Notes 

309 ----- 

310 Here is the list of conditions that the inputs (in/out degree sequences, 

311 nkk) need to satisfy for simple directed graph realizability: 

312 

313 - Condition 0: in_degrees and out_degrees have the same length 

314 - Condition 1: nkk[k][l] is integer for all k,l 

315 - Condition 2: sum(nkk[k])/k = number of nodes with partition id k, is an 

316 integer and matching degree sequence 

317 - Condition 3: number of edges and non-chords between k and l cannot exceed 

318 maximum possible number of edges 

319 

320 

321 References 

322 ---------- 

323 [1] B. Tillman, A. Markopoulou, C. T. Butts & M. Gjoka, 

324 "Construction of Directed 2K Graphs". In Proc. of KDD 2017. 

325 """ 

326 V = {} # number of nodes with in/out degree. 

327 forbidden = {} 

328 if len(in_degrees) != len(out_degrees): 

329 return False 

330 

331 for idx in range(len(in_degrees)): 

332 i = in_degrees[idx] 

333 o = out_degrees[idx] 

334 V[(i, 0)] = V.get((i, 0), 0) + 1 

335 V[(o, 1)] = V.get((o, 1), 0) + 1 

336 

337 forbidden[(o, i)] = forbidden.get((o, i), 0) + 1 

338 

339 S = {} # number of edges going from in/out degree nodes. 

340 for k in nkk: 

341 for l in nkk[k]: 

342 val = nkk[k][l] 

343 if not float(val).is_integer(): # condition 1 

344 return False 

345 

346 if val > 0: 

347 S[(k, 1)] = S.get((k, 1), 0) + val 

348 S[(l, 0)] = S.get((l, 0), 0) + val 

349 # condition 3 

350 if val + forbidden.get((k, l), 0) > V[(k, 1)] * V[(l, 0)]: 

351 return False 

352 

353 return all(S[s] / s[0] == V[s] for s in S) 

354 

355 

356def _directed_neighbor_switch( 

357 G, w, unsat, h_node_residual_out, chords, h_partition_in, partition 

358): 

359 """Releases one free stub for node w, while preserving joint degree in G. 

360 

361 Parameters 

362 ---------- 

363 G : networkx directed graph 

364 graph within which the edge swap will take place. 

365 w : integer 

366 node id for which we need to perform a neighbor switch. 

367 unsat: set of integers 

368 set of node ids that have the same degree as w and are unsaturated. 

369 h_node_residual_out: dict of integers 

370 for a given node, keeps track of the remaining stubs to be added. 

371 chords: set of tuples 

372 keeps track of available positions to add edges. 

373 h_partition_in: dict of integers 

374 for a given node, keeps track of its partition id (in degree). 

375 partition: integer 

376 partition id to check if chords have to be updated. 

377 

378 Notes 

379 ----- 

380 First, it selects node w_prime that (1) has the same degree as w and 

381 (2) is unsaturated. Then, it selects node v, a neighbor of w, that is 

382 not connected to w_prime and does an edge swap i.e. removes (w,v) and 

383 adds (w_prime,v). If neighbor switch is not possible for w using 

384 w_prime and v, then return w_prime; in [1] it's proven that 

385 such unsaturated nodes can be used. 

386 

387 References 

388 ---------- 

389 [1] B. Tillman, A. Markopoulou, C. T. Butts & M. Gjoka, 

390 "Construction of Directed 2K Graphs". In Proc. of KDD 2017. 

391 """ 

392 w_prime = unsat.pop() 

393 unsat.add(w_prime) 

394 # select node t, a neighbor of w, that is not connected to w_prime 

395 w_neighbs = list(G.successors(w)) 

396 # slightly faster declaring this variable 

397 w_prime_neighbs = list(G.successors(w_prime)) 

398 

399 for v in w_neighbs: 

400 if (v not in w_prime_neighbs) and w_prime != v: 

401 # removes (w,v), add (w_prime,v) and update data structures 

402 G.remove_edge(w, v) 

403 G.add_edge(w_prime, v) 

404 

405 if h_partition_in[v] == partition: 

406 chords.add((w, v)) 

407 chords.discard((w_prime, v)) 

408 

409 h_node_residual_out[w] += 1 

410 h_node_residual_out[w_prime] -= 1 

411 if h_node_residual_out[w_prime] == 0: 

412 unsat.remove(w_prime) 

413 return None 

414 

415 # If neighbor switch didn't work, use unsaturated node 

416 return w_prime 

417 

418 

419def _directed_neighbor_switch_rev( 

420 G, w, unsat, h_node_residual_in, chords, h_partition_out, partition 

421): 

422 """The reverse of directed_neighbor_switch. 

423 

424 Parameters 

425 ---------- 

426 G : networkx directed graph 

427 graph within which the edge swap will take place. 

428 w : integer 

429 node id for which we need to perform a neighbor switch. 

430 unsat: set of integers 

431 set of node ids that have the same degree as w and are unsaturated. 

432 h_node_residual_in: dict of integers 

433 for a given node, keeps track of the remaining stubs to be added. 

434 chords: set of tuples 

435 keeps track of available positions to add edges. 

436 h_partition_out: dict of integers 

437 for a given node, keeps track of its partition id (out degree). 

438 partition: integer 

439 partition id to check if chords have to be updated. 

440 

441 Notes 

442 ----- 

443 Same operation as directed_neighbor_switch except it handles this operation 

444 for incoming edges instead of outgoing. 

445 """ 

446 w_prime = unsat.pop() 

447 unsat.add(w_prime) 

448 # slightly faster declaring these as variables. 

449 w_neighbs = list(G.predecessors(w)) 

450 w_prime_neighbs = list(G.predecessors(w_prime)) 

451 # select node v, a neighbor of w, that is not connected to w_prime. 

452 for v in w_neighbs: 

453 if (v not in w_prime_neighbs) and w_prime != v: 

454 # removes (v,w), add (v,w_prime) and update data structures. 

455 G.remove_edge(v, w) 

456 G.add_edge(v, w_prime) 

457 if h_partition_out[v] == partition: 

458 chords.add((v, w)) 

459 chords.discard((v, w_prime)) 

460 

461 h_node_residual_in[w] += 1 

462 h_node_residual_in[w_prime] -= 1 

463 if h_node_residual_in[w_prime] == 0: 

464 unsat.remove(w_prime) 

465 return None 

466 

467 # If neighbor switch didn't work, use the unsaturated node. 

468 return w_prime 

469 

470 

471@py_random_state(3) 

472@nx._dispatch(graphs=None) 

473def directed_joint_degree_graph(in_degrees, out_degrees, nkk, seed=None): 

474 """Generates a random simple directed graph with the joint degree. 

475 

476 Parameters 

477 ---------- 

478 degree_seq : list of tuples (of size 3) 

479 degree sequence contains tuples of nodes with node id, in degree and 

480 out degree. 

481 nkk : dictionary of dictionary of integers 

482 directed joint degree dictionary, for nodes of out degree k (first 

483 level of dict) and nodes of in degree l (second level of dict) 

484 describes the number of edges. 

485 seed : hashable object, optional 

486 Seed for random number generator. 

487 

488 Returns 

489 ------- 

490 G : Graph 

491 A directed graph with the specified inputs. 

492 

493 Raises 

494 ------ 

495 NetworkXError 

496 If degree_seq and nkk are not realizable as a simple directed graph. 

497 

498 

499 Notes 

500 ----- 

501 Similarly to the undirected version: 

502 In each iteration of the "while loop" the algorithm picks two disconnected 

503 nodes v and w, of degree k and l correspondingly, for which nkk[k][l] has 

504 not reached its target yet i.e. (for given k,l): n_edges_add < nkk[k][l]. 

505 It then adds edge (v,w) and always increases the number of edges in graph G 

506 by one. 

507 

508 The intelligence of the algorithm lies in the fact that it is always 

509 possible to add an edge between disconnected nodes v and w, for which 

510 nkk[degree(v)][degree(w)] has not reached its target, even if one or both 

511 nodes do not have free stubs. If either node v or w does not have a free 

512 stub, we perform a "neighbor switch", an edge rewiring move that releases a 

513 free stub while keeping nkk the same. 

514 

515 The difference for the directed version lies in the fact that neighbor 

516 switches might not be able to rewire, but in these cases unsaturated nodes 

517 can be reassigned to use instead, see [1] for detailed description and 

518 proofs. 

519 

520 The algorithm continues for E (number of edges in the graph) iterations of 

521 the "while loop", at which point all entries of the given nkk[k][l] have 

522 reached their target values and the construction is complete. 

523 

524 References 

525 ---------- 

526 [1] B. Tillman, A. Markopoulou, C. T. Butts & M. Gjoka, 

527 "Construction of Directed 2K Graphs". In Proc. of KDD 2017. 

528 

529 Examples 

530 -------- 

531 >>> in_degrees = [0, 1, 1, 2] 

532 >>> out_degrees = [1, 1, 1, 1] 

533 >>> nkk = {1: {1: 2, 2: 2}} 

534 >>> G = nx.directed_joint_degree_graph(in_degrees, out_degrees, nkk) 

535 >>> 

536 """ 

537 if not is_valid_directed_joint_degree(in_degrees, out_degrees, nkk): 

538 msg = "Input is not realizable as a simple graph" 

539 raise nx.NetworkXError(msg) 

540 

541 # start with an empty directed graph. 

542 G = nx.DiGraph() 

543 

544 # for a given group, keep the list of all node ids. 

545 h_degree_nodelist_in = {} 

546 h_degree_nodelist_out = {} 

547 # for a given group, keep the list of all unsaturated node ids. 

548 h_degree_nodelist_in_unsat = {} 

549 h_degree_nodelist_out_unsat = {} 

550 # for a given node, keep track of the remaining stubs to be added. 

551 h_node_residual_out = {} 

552 h_node_residual_in = {} 

553 # for a given node, keep track of the partition id. 

554 h_partition_out = {} 

555 h_partition_in = {} 

556 # keep track of non-chords between pairs of partition ids. 

557 non_chords = {} 

558 

559 # populate data structures 

560 for idx, i in enumerate(in_degrees): 

561 idx = int(idx) 

562 if i > 0: 

563 h_degree_nodelist_in.setdefault(i, []) 

564 h_degree_nodelist_in_unsat.setdefault(i, set()) 

565 h_degree_nodelist_in[i].append(idx) 

566 h_degree_nodelist_in_unsat[i].add(idx) 

567 h_node_residual_in[idx] = i 

568 h_partition_in[idx] = i 

569 

570 for idx, o in enumerate(out_degrees): 

571 o = out_degrees[idx] 

572 non_chords[(o, in_degrees[idx])] = non_chords.get((o, in_degrees[idx]), 0) + 1 

573 idx = int(idx) 

574 if o > 0: 

575 h_degree_nodelist_out.setdefault(o, []) 

576 h_degree_nodelist_out_unsat.setdefault(o, set()) 

577 h_degree_nodelist_out[o].append(idx) 

578 h_degree_nodelist_out_unsat[o].add(idx) 

579 h_node_residual_out[idx] = o 

580 h_partition_out[idx] = o 

581 

582 G.add_node(idx) 

583 

584 nk_in = {} 

585 nk_out = {} 

586 for p in h_degree_nodelist_in: 

587 nk_in[p] = len(h_degree_nodelist_in[p]) 

588 for p in h_degree_nodelist_out: 

589 nk_out[p] = len(h_degree_nodelist_out[p]) 

590 

591 # iterate over every degree pair (k,l) and add the number of edges given 

592 # for each pair. 

593 for k in nkk: 

594 for l in nkk[k]: 

595 n_edges_add = nkk[k][l] 

596 

597 if n_edges_add > 0: 

598 # chords contains a random set of potential edges. 

599 chords = set() 

600 

601 k_len = nk_out[k] 

602 l_len = nk_in[l] 

603 chords_sample = seed.sample( 

604 range(k_len * l_len), n_edges_add + non_chords.get((k, l), 0) 

605 ) 

606 

607 num = 0 

608 while len(chords) < n_edges_add: 

609 i = h_degree_nodelist_out[k][chords_sample[num] % k_len] 

610 j = h_degree_nodelist_in[l][chords_sample[num] // k_len] 

611 num += 1 

612 if i != j: 

613 chords.add((i, j)) 

614 

615 # k_unsat and l_unsat consist of nodes of in/out degree k and l 

616 # that are unsaturated i.e. those nodes that have at least one 

617 # available stub 

618 k_unsat = h_degree_nodelist_out_unsat[k] 

619 l_unsat = h_degree_nodelist_in_unsat[l] 

620 

621 while n_edges_add > 0: 

622 v, w = chords.pop() 

623 chords.add((v, w)) 

624 

625 # if node v has no free stubs then do neighbor switch. 

626 if h_node_residual_out[v] == 0: 

627 _v = _directed_neighbor_switch( 

628 G, 

629 v, 

630 k_unsat, 

631 h_node_residual_out, 

632 chords, 

633 h_partition_in, 

634 l, 

635 ) 

636 if _v is not None: 

637 v = _v 

638 

639 # if node w has no free stubs then do neighbor switch. 

640 if h_node_residual_in[w] == 0: 

641 _w = _directed_neighbor_switch_rev( 

642 G, 

643 w, 

644 l_unsat, 

645 h_node_residual_in, 

646 chords, 

647 h_partition_out, 

648 k, 

649 ) 

650 if _w is not None: 

651 w = _w 

652 

653 # add edge (v,w) and update data structures. 

654 G.add_edge(v, w) 

655 h_node_residual_out[v] -= 1 

656 h_node_residual_in[w] -= 1 

657 n_edges_add -= 1 

658 chords.discard((v, w)) 

659 

660 if h_node_residual_out[v] == 0: 

661 k_unsat.discard(v) 

662 if h_node_residual_in[w] == 0: 

663 l_unsat.discard(w) 

664 return G