Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/networkx/algorithms/cycles.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author | guerler |
---|---|
date | Fri, 31 Jul 2020 00:32:28 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
0:d30785e31577 | 1:56ad4e20f292 |
---|---|
1 # Copyright (C) 2010-2019 by | |
2 # Aric Hagberg <hagberg@lanl.gov> | |
3 # Dan Schult <dschult@colgate.edu> | |
4 # Pieter Swart <swart@lanl.gov> | |
5 # All rights reserved. | |
6 # BSD license. | |
7 # | |
8 # Authors: Jon Olav Vik <jonovik@gmail.com> | |
9 # Dan Schult <dschult@colgate.edu> | |
10 # Aric Hagberg <hagberg@lanl.gov> | |
11 # Debsankha Manik <dmanik@nld.ds.mpg.de> | |
12 """ | |
13 ======================== | |
14 Cycle finding algorithms | |
15 ======================== | |
16 """ | |
17 | |
18 from collections import defaultdict | |
19 from itertools import tee | |
20 | |
21 import networkx as nx | |
22 from networkx.utils import not_implemented_for, pairwise | |
23 | |
24 __all__ = [ | |
25 'cycle_basis', 'simple_cycles', | |
26 'recursive_simple_cycles', 'find_cycle', | |
27 'minimum_cycle_basis', | |
28 ] | |
29 | |
30 | |
31 @not_implemented_for('directed') | |
32 @not_implemented_for('multigraph') | |
33 def cycle_basis(G, root=None): | |
34 """ Returns a list of cycles which form a basis for cycles of G. | |
35 | |
36 A basis for cycles of a network is a minimal collection of | |
37 cycles such that any cycle in the network can be written | |
38 as a sum of cycles in the basis. Here summation of cycles | |
39 is defined as "exclusive or" of the edges. Cycle bases are | |
40 useful, e.g. when deriving equations for electric circuits | |
41 using Kirchhoff's Laws. | |
42 | |
43 Parameters | |
44 ---------- | |
45 G : NetworkX Graph | |
46 root : node, optional | |
47 Specify starting node for basis. | |
48 | |
49 Returns | |
50 ------- | |
51 A list of cycle lists. Each cycle list is a list of nodes | |
52 which forms a cycle (loop) in G. | |
53 | |
54 Examples | |
55 -------- | |
56 >>> G = nx.Graph() | |
57 >>> nx.add_cycle(G, [0, 1, 2, 3]) | |
58 >>> nx.add_cycle(G, [0, 3, 4, 5]) | |
59 >>> print(nx.cycle_basis(G, 0)) | |
60 [[3, 4, 5, 0], [1, 2, 3, 0]] | |
61 | |
62 Notes | |
63 ----- | |
64 This is adapted from algorithm CACM 491 [1]_. | |
65 | |
66 References | |
67 ---------- | |
68 .. [1] Paton, K. An algorithm for finding a fundamental set of | |
69 cycles of a graph. Comm. ACM 12, 9 (Sept 1969), 514-518. | |
70 | |
71 See Also | |
72 -------- | |
73 simple_cycles | |
74 """ | |
75 gnodes = set(G.nodes()) | |
76 cycles = [] | |
77 while gnodes: # loop over connected components | |
78 if root is None: | |
79 root = gnodes.pop() | |
80 stack = [root] | |
81 pred = {root: root} | |
82 used = {root: set()} | |
83 while stack: # walk the spanning tree finding cycles | |
84 z = stack.pop() # use last-in so cycles easier to find | |
85 zused = used[z] | |
86 for nbr in G[z]: | |
87 if nbr not in used: # new node | |
88 pred[nbr] = z | |
89 stack.append(nbr) | |
90 used[nbr] = set([z]) | |
91 elif nbr == z: # self loops | |
92 cycles.append([z]) | |
93 elif nbr not in zused: # found a cycle | |
94 pn = used[nbr] | |
95 cycle = [nbr, z] | |
96 p = pred[z] | |
97 while p not in pn: | |
98 cycle.append(p) | |
99 p = pred[p] | |
100 cycle.append(p) | |
101 cycles.append(cycle) | |
102 used[nbr].add(z) | |
103 gnodes -= set(pred) | |
104 root = None | |
105 return cycles | |
106 | |
107 | |
108 @not_implemented_for('undirected') | |
109 def simple_cycles(G): | |
110 """Find simple cycles (elementary circuits) of a directed graph. | |
111 | |
112 A `simple cycle`, or `elementary circuit`, is a closed path where | |
113 no node appears twice. Two elementary circuits are distinct if they | |
114 are not cyclic permutations of each other. | |
115 | |
116 This is a nonrecursive, iterator/generator version of Johnson's | |
117 algorithm [1]_. There may be better algorithms for some cases [2]_ [3]_. | |
118 | |
119 Parameters | |
120 ---------- | |
121 G : NetworkX DiGraph | |
122 A directed graph | |
123 | |
124 Returns | |
125 ------- | |
126 cycle_generator: generator | |
127 A generator that produces elementary cycles of the graph. | |
128 Each cycle is represented by a list of nodes along the cycle. | |
129 | |
130 Examples | |
131 -------- | |
132 >>> edges = [(0, 0), (0, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)] | |
133 >>> G = nx.DiGraph(edges) | |
134 >>> len(list(nx.simple_cycles(G))) | |
135 5 | |
136 | |
137 To filter the cycles so that they don't include certain nodes or edges, | |
138 copy your graph and eliminate those nodes or edges before calling | |
139 | |
140 >>> copyG = G.copy() | |
141 >>> copyG.remove_nodes_from([1]) | |
142 >>> copyG.remove_edges_from([(0, 1)]) | |
143 >>> len(list(nx.simple_cycles(copyG))) | |
144 3 | |
145 | |
146 | |
147 Notes | |
148 ----- | |
149 The implementation follows pp. 79-80 in [1]_. | |
150 | |
151 The time complexity is $O((n+e)(c+1))$ for $n$ nodes, $e$ edges and $c$ | |
152 elementary circuits. | |
153 | |
154 References | |
155 ---------- | |
156 .. [1] Finding all the elementary circuits of a directed graph. | |
157 D. B. Johnson, SIAM Journal on Computing 4, no. 1, 77-84, 1975. | |
158 https://doi.org/10.1137/0204007 | |
159 .. [2] Enumerating the cycles of a digraph: a new preprocessing strategy. | |
160 G. Loizou and P. Thanish, Information Sciences, v. 27, 163-182, 1982. | |
161 .. [3] A search strategy for the elementary cycles of a directed graph. | |
162 J.L. Szwarcfiter and P.E. Lauer, BIT NUMERICAL MATHEMATICS, | |
163 v. 16, no. 2, 192-204, 1976. | |
164 | |
165 See Also | |
166 -------- | |
167 cycle_basis | |
168 """ | |
169 def _unblock(thisnode, blocked, B): | |
170 stack = set([thisnode]) | |
171 while stack: | |
172 node = stack.pop() | |
173 if node in blocked: | |
174 blocked.remove(node) | |
175 stack.update(B[node]) | |
176 B[node].clear() | |
177 | |
178 # Johnson's algorithm requires some ordering of the nodes. | |
179 # We assign the arbitrary ordering given by the strongly connected comps | |
180 # There is no need to track the ordering as each node removed as processed. | |
181 # Also we save the actual graph so we can mutate it. We only take the | |
182 # edges because we do not want to copy edge and node attributes here. | |
183 subG = type(G)(G.edges()) | |
184 sccs = [scc for scc in nx.strongly_connected_components(subG) | |
185 if len(scc) > 1] | |
186 | |
187 # Johnson's algorithm exclude self cycle edges like (v, v) | |
188 # To be backward compatible, we record those cycles in advance | |
189 # and then remove from subG | |
190 for v in subG: | |
191 if subG.has_edge(v, v): | |
192 yield [v] | |
193 subG.remove_edge(v, v) | |
194 | |
195 while sccs: | |
196 scc = sccs.pop() | |
197 sccG = subG.subgraph(scc) | |
198 # order of scc determines ordering of nodes | |
199 startnode = scc.pop() | |
200 # Processing node runs "circuit" routine from recursive version | |
201 path = [startnode] | |
202 blocked = set() # vertex: blocked from search? | |
203 closed = set() # nodes involved in a cycle | |
204 blocked.add(startnode) | |
205 B = defaultdict(set) # graph portions that yield no elementary circuit | |
206 stack = [(startnode, list(sccG[startnode]))] # sccG gives comp nbrs | |
207 while stack: | |
208 thisnode, nbrs = stack[-1] | |
209 if nbrs: | |
210 nextnode = nbrs.pop() | |
211 if nextnode == startnode: | |
212 yield path[:] | |
213 closed.update(path) | |
214 # print "Found a cycle", path, closed | |
215 elif nextnode not in blocked: | |
216 path.append(nextnode) | |
217 stack.append((nextnode, list(sccG[nextnode]))) | |
218 closed.discard(nextnode) | |
219 blocked.add(nextnode) | |
220 continue | |
221 # done with nextnode... look for more neighbors | |
222 if not nbrs: # no more nbrs | |
223 if thisnode in closed: | |
224 _unblock(thisnode, blocked, B) | |
225 else: | |
226 for nbr in sccG[thisnode]: | |
227 if thisnode not in B[nbr]: | |
228 B[nbr].add(thisnode) | |
229 stack.pop() | |
230 # assert path[-1] == thisnode | |
231 path.pop() | |
232 # done processing this node | |
233 H = subG.subgraph(scc) # make smaller to avoid work in SCC routine | |
234 sccs.extend(scc for scc in nx.strongly_connected_components(H) | |
235 if len(scc) > 1) | |
236 | |
237 | |
238 @not_implemented_for('undirected') | |
239 def recursive_simple_cycles(G): | |
240 """Find simple cycles (elementary circuits) of a directed graph. | |
241 | |
242 A `simple cycle`, or `elementary circuit`, is a closed path where | |
243 no node appears twice. Two elementary circuits are distinct if they | |
244 are not cyclic permutations of each other. | |
245 | |
246 This version uses a recursive algorithm to build a list of cycles. | |
247 You should probably use the iterator version called simple_cycles(). | |
248 Warning: This recursive version uses lots of RAM! | |
249 | |
250 Parameters | |
251 ---------- | |
252 G : NetworkX DiGraph | |
253 A directed graph | |
254 | |
255 Returns | |
256 ------- | |
257 A list of cycles, where each cycle is represented by a list of nodes | |
258 along the cycle. | |
259 | |
260 Example: | |
261 | |
262 >>> edges = [(0, 0), (0, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)] | |
263 >>> G = nx.DiGraph(edges) | |
264 >>> nx.recursive_simple_cycles(G) | |
265 [[0], [2], [0, 1, 2], [0, 2], [1, 2]] | |
266 | |
267 See Also | |
268 -------- | |
269 cycle_basis (for undirected graphs) | |
270 | |
271 Notes | |
272 ----- | |
273 The implementation follows pp. 79-80 in [1]_. | |
274 | |
275 The time complexity is $O((n+e)(c+1))$ for $n$ nodes, $e$ edges and $c$ | |
276 elementary circuits. | |
277 | |
278 References | |
279 ---------- | |
280 .. [1] Finding all the elementary circuits of a directed graph. | |
281 D. B. Johnson, SIAM Journal on Computing 4, no. 1, 77-84, 1975. | |
282 https://doi.org/10.1137/0204007 | |
283 | |
284 See Also | |
285 -------- | |
286 simple_cycles, cycle_basis | |
287 """ | |
288 # Jon Olav Vik, 2010-08-09 | |
289 def _unblock(thisnode): | |
290 """Recursively unblock and remove nodes from B[thisnode].""" | |
291 if blocked[thisnode]: | |
292 blocked[thisnode] = False | |
293 while B[thisnode]: | |
294 _unblock(B[thisnode].pop()) | |
295 | |
296 def circuit(thisnode, startnode, component): | |
297 closed = False # set to True if elementary path is closed | |
298 path.append(thisnode) | |
299 blocked[thisnode] = True | |
300 for nextnode in component[thisnode]: # direct successors of thisnode | |
301 if nextnode == startnode: | |
302 result.append(path[:]) | |
303 closed = True | |
304 elif not blocked[nextnode]: | |
305 if circuit(nextnode, startnode, component): | |
306 closed = True | |
307 if closed: | |
308 _unblock(thisnode) | |
309 else: | |
310 for nextnode in component[thisnode]: | |
311 if thisnode not in B[nextnode]: # TODO: use set for speedup? | |
312 B[nextnode].append(thisnode) | |
313 path.pop() # remove thisnode from path | |
314 return closed | |
315 | |
316 path = [] # stack of nodes in current path | |
317 blocked = defaultdict(bool) # vertex: blocked from search? | |
318 B = defaultdict(list) # graph portions that yield no elementary circuit | |
319 result = [] # list to accumulate the circuits found | |
320 | |
321 # Johnson's algorithm exclude self cycle edges like (v, v) | |
322 # To be backward compatible, we record those cycles in advance | |
323 # and then remove from subG | |
324 for v in G: | |
325 if G.has_edge(v, v): | |
326 result.append([v]) | |
327 G.remove_edge(v, v) | |
328 | |
329 # Johnson's algorithm requires some ordering of the nodes. | |
330 # They might not be sortable so we assign an arbitrary ordering. | |
331 ordering = dict(zip(G, range(len(G)))) | |
332 for s in ordering: | |
333 # Build the subgraph induced by s and following nodes in the ordering | |
334 subgraph = G.subgraph(node for node in G | |
335 if ordering[node] >= ordering[s]) | |
336 # Find the strongly connected component in the subgraph | |
337 # that contains the least node according to the ordering | |
338 strongcomp = nx.strongly_connected_components(subgraph) | |
339 mincomp = min(strongcomp, key=lambda ns: min(ordering[n] for n in ns)) | |
340 component = G.subgraph(mincomp) | |
341 if len(component) > 1: | |
342 # smallest node in the component according to the ordering | |
343 startnode = min(component, key=ordering.__getitem__) | |
344 for node in component: | |
345 blocked[node] = False | |
346 B[node][:] = [] | |
347 dummy = circuit(startnode, startnode, component) | |
348 return result | |
349 | |
350 | |
351 def find_cycle(G, source=None, orientation=None): | |
352 """Returns a cycle found via depth-first traversal. | |
353 | |
354 The cycle is a list of edges indicating the cyclic path. | |
355 Orientation of directed edges is controlled by `orientation`. | |
356 | |
357 Parameters | |
358 ---------- | |
359 G : graph | |
360 A directed/undirected graph/multigraph. | |
361 | |
362 source : node, list of nodes | |
363 The node from which the traversal begins. If None, then a source | |
364 is chosen arbitrarily and repeatedly until all edges from each node in | |
365 the graph are searched. | |
366 | |
367 orientation : None | 'original' | 'reverse' | 'ignore' (default: None) | |
368 For directed graphs and directed multigraphs, edge traversals need not | |
369 respect the original orientation of the edges. | |
370 When set to 'reverse' every edge is traversed in the reverse direction. | |
371 When set to 'ignore', every edge is treated as undirected. | |
372 When set to 'original', every edge is treated as directed. | |
373 In all three cases, the yielded edge tuples add a last entry to | |
374 indicate the direction in which that edge was traversed. | |
375 If orientation is None, the yielded edge has no direction indicated. | |
376 The direction is respected, but not reported. | |
377 | |
378 Returns | |
379 ------- | |
380 edges : directed edges | |
381 A list of directed edges indicating the path taken for the loop. | |
382 If no cycle is found, then an exception is raised. | |
383 For graphs, an edge is of the form `(u, v)` where `u` and `v` | |
384 are the tail and head of the edge as determined by the traversal. | |
385 For multigraphs, an edge is of the form `(u, v, key)`, where `key` is | |
386 the key of the edge. When the graph is directed, then `u` and `v` | |
387 are always in the order of the actual directed edge. | |
388 If orientation is not None then the edge tuple is extended to include | |
389 the direction of traversal ('forward' or 'reverse') on that edge. | |
390 | |
391 Raises | |
392 ------ | |
393 NetworkXNoCycle | |
394 If no cycle was found. | |
395 | |
396 Examples | |
397 -------- | |
398 In this example, we construct a DAG and find, in the first call, that there | |
399 are no directed cycles, and so an exception is raised. In the second call, | |
400 we ignore edge orientations and find that there is an undirected cycle. | |
401 Note that the second call finds a directed cycle while effectively | |
402 traversing an undirected graph, and so, we found an "undirected cycle". | |
403 This means that this DAG structure does not form a directed tree (which | |
404 is also known as a polytree). | |
405 | |
406 >>> import networkx as nx | |
407 >>> G = nx.DiGraph([(0, 1), (0, 2), (1, 2)]) | |
408 >>> try: | |
409 ... nx.find_cycle(G, orientation='original') | |
410 ... except: | |
411 ... pass | |
412 ... | |
413 >>> list(nx.find_cycle(G, orientation='ignore')) | |
414 [(0, 1, 'forward'), (1, 2, 'forward'), (0, 2, 'reverse')] | |
415 | |
416 """ | |
417 if not G.is_directed() or orientation in (None, 'original'): | |
418 def tailhead(edge): | |
419 return edge[:2] | |
420 elif orientation == 'reverse': | |
421 def tailhead(edge): | |
422 return edge[1], edge[0] | |
423 elif orientation == 'ignore': | |
424 def tailhead(edge): | |
425 if edge[-1] == 'reverse': | |
426 return edge[1], edge[0] | |
427 return edge[:2] | |
428 | |
429 explored = set() | |
430 cycle = [] | |
431 final_node = None | |
432 for start_node in G.nbunch_iter(source): | |
433 if start_node in explored: | |
434 # No loop is possible. | |
435 continue | |
436 | |
437 edges = [] | |
438 # All nodes seen in this iteration of edge_dfs | |
439 seen = {start_node} | |
440 # Nodes in active path. | |
441 active_nodes = {start_node} | |
442 previous_head = None | |
443 | |
444 for edge in nx.edge_dfs(G, start_node, orientation): | |
445 # Determine if this edge is a continuation of the active path. | |
446 tail, head = tailhead(edge) | |
447 if head in explored: | |
448 # Then we've already explored it. No loop is possible. | |
449 continue | |
450 if previous_head is not None and tail != previous_head: | |
451 # This edge results from backtracking. | |
452 # Pop until we get a node whose head equals the current tail. | |
453 # So for example, we might have: | |
454 # (0, 1), (1, 2), (2, 3), (1, 4) | |
455 # which must become: | |
456 # (0, 1), (1, 4) | |
457 while True: | |
458 try: | |
459 popped_edge = edges.pop() | |
460 except IndexError: | |
461 edges = [] | |
462 active_nodes = {tail} | |
463 break | |
464 else: | |
465 popped_head = tailhead(popped_edge)[1] | |
466 active_nodes.remove(popped_head) | |
467 | |
468 if edges: | |
469 last_head = tailhead(edges[-1])[1] | |
470 if tail == last_head: | |
471 break | |
472 edges.append(edge) | |
473 | |
474 if head in active_nodes: | |
475 # We have a loop! | |
476 cycle.extend(edges) | |
477 final_node = head | |
478 break | |
479 else: | |
480 seen.add(head) | |
481 active_nodes.add(head) | |
482 previous_head = head | |
483 | |
484 if cycle: | |
485 break | |
486 else: | |
487 explored.update(seen) | |
488 | |
489 else: | |
490 assert(len(cycle) == 0) | |
491 raise nx.exception.NetworkXNoCycle('No cycle found.') | |
492 | |
493 # We now have a list of edges which ends on a cycle. | |
494 # So we need to remove from the beginning edges that are not relevant. | |
495 | |
496 for i, edge in enumerate(cycle): | |
497 tail, head = tailhead(edge) | |
498 if tail == final_node: | |
499 break | |
500 | |
501 return cycle[i:] | |
502 | |
503 | |
504 @not_implemented_for('directed') | |
505 @not_implemented_for('multigraph') | |
506 def minimum_cycle_basis(G, weight=None): | |
507 """ Returns a minimum weight cycle basis for G | |
508 | |
509 Minimum weight means a cycle basis for which the total weight | |
510 (length for unweighted graphs) of all the cycles is minimum. | |
511 | |
512 Parameters | |
513 ---------- | |
514 G : NetworkX Graph | |
515 weight: string | |
516 name of the edge attribute to use for edge weights | |
517 | |
518 Returns | |
519 ------- | |
520 A list of cycle lists. Each cycle list is a list of nodes | |
521 which forms a cycle (loop) in G. Note that the nodes are not | |
522 necessarily returned in a order by which they appear in the cycle | |
523 | |
524 Examples | |
525 -------- | |
526 >>> G=nx.Graph() | |
527 >>> nx.add_cycle(G, [0,1,2,3]) | |
528 >>> nx.add_cycle(G, [0,3,4,5]) | |
529 >>> print([sorted(c) for c in nx.minimum_cycle_basis(G)]) | |
530 [[0, 1, 2, 3], [0, 3, 4, 5]] | |
531 | |
532 References: | |
533 [1] Kavitha, Telikepalli, et al. "An O(m^2n) Algorithm for | |
534 Minimum Cycle Basis of Graphs." | |
535 http://link.springer.com/article/10.1007/s00453-007-9064-z | |
536 [2] de Pina, J. 1995. Applications of shortest path methods. | |
537 Ph.D. thesis, University of Amsterdam, Netherlands | |
538 | |
539 See Also | |
540 -------- | |
541 simple_cycles, cycle_basis | |
542 """ | |
543 # We first split the graph in commected subgraphs | |
544 return sum((_min_cycle_basis(G.subgraph(c), weight) for c in | |
545 nx.connected_components(G)), []) | |
546 | |
547 | |
548 def _min_cycle_basis(comp, weight): | |
549 cb = [] | |
550 # We extract the edges not in a spanning tree. We do not really need a | |
551 # *minimum* spanning tree. That is why we call the next function with | |
552 # weight=None. Depending on implementation, it may be faster as well | |
553 spanning_tree_edges = list(nx.minimum_spanning_edges(comp, weight=None, | |
554 data=False)) | |
555 edges_excl = [frozenset(e) for e in comp.edges() | |
556 if e not in spanning_tree_edges] | |
557 N = len(edges_excl) | |
558 | |
559 # We maintain a set of vectors orthogonal to sofar found cycles | |
560 set_orth = [set([edge]) for edge in edges_excl] | |
561 for k in range(N): | |
562 # kth cycle is "parallel" to kth vector in set_orth | |
563 new_cycle = _min_cycle(comp, set_orth[k], weight=weight) | |
564 cb.append(list(set().union(*new_cycle))) | |
565 # now update set_orth so that k+1,k+2... th elements are | |
566 # orthogonal to the newly found cycle, as per [p. 336, 1] | |
567 base = set_orth[k] | |
568 set_orth[k + 1:] = [orth ^ base if len(orth & new_cycle) % 2 else orth | |
569 for orth in set_orth[k + 1:]] | |
570 return cb | |
571 | |
572 | |
573 def _min_cycle(G, orth, weight=None): | |
574 """ | |
575 Computes the minimum weight cycle in G, | |
576 orthogonal to the vector orth as per [p. 338, 1] | |
577 """ | |
578 T = nx.Graph() | |
579 | |
580 nodes_idx = {node: idx for idx, node in enumerate(G.nodes())} | |
581 idx_nodes = {idx: node for node, idx in nodes_idx.items()} | |
582 | |
583 nnodes = len(nodes_idx) | |
584 | |
585 # Add 2 copies of each edge in G to T. If edge is in orth, add cross edge; | |
586 # otherwise in-plane edge | |
587 for u, v, data in G.edges(data=True): | |
588 uidx, vidx = nodes_idx[u], nodes_idx[v] | |
589 edge_w = data.get(weight, 1) | |
590 if frozenset((u, v)) in orth: | |
591 T.add_edges_from( | |
592 [(uidx, nnodes + vidx), (nnodes + uidx, vidx)], weight=edge_w) | |
593 else: | |
594 T.add_edges_from( | |
595 [(uidx, vidx), (nnodes + uidx, nnodes + vidx)], weight=edge_w) | |
596 | |
597 all_shortest_pathlens = dict(nx.shortest_path_length(T, weight=weight)) | |
598 cross_paths_w_lens = {n: all_shortest_pathlens[n][nnodes + n] | |
599 for n in range(nnodes)} | |
600 | |
601 # Now compute shortest paths in T, which translates to cyles in G | |
602 start = min(cross_paths_w_lens, key=cross_paths_w_lens.get) | |
603 end = nnodes + start | |
604 min_path = nx.shortest_path(T, source=start, target=end, weight='weight') | |
605 | |
606 # Now we obtain the actual path, re-map nodes in T to those in G | |
607 min_path_nodes = [node if node < nnodes else node - nnodes | |
608 for node in min_path] | |
609 # Now remove the edges that occur two times | |
610 mcycle_pruned = _path_to_cycle(min_path_nodes) | |
611 | |
612 return {frozenset((idx_nodes[u], idx_nodes[v])) for u, v in mcycle_pruned} | |
613 | |
614 | |
615 def _path_to_cycle(path): | |
616 """ | |
617 Removes the edges from path that occur even number of times. | |
618 Returns a set of edges | |
619 """ | |
620 edges = set() | |
621 for edge in pairwise(path): | |
622 # Toggle whether to keep the current edge. | |
623 edges ^= {edge} | |
624 return edges |