Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/networkx/algorithms/dag.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 # -*- coding: utf-8 -*- | |
2 # Copyright (C) 2006-2019 by | |
3 # Aric Hagberg <hagberg@lanl.gov> | |
4 # Dan Schult <dschult@colgate.edu> | |
5 # Pieter Swart <swart@lanl.gov> | |
6 # All rights reserved. | |
7 # BSD license. | |
8 # | |
9 # Authors: | |
10 # Aric Hagberg <aric.hagberg@gmail.com> | |
11 # Dan Schult <dschult@colgate.edu> | |
12 # Ben Edwards <bedwards@cs.unm.edu> | |
13 # Neil Girdhar <neil.girdhar@mcgill.ca> | |
14 # | |
15 """Algorithms for directed acyclic graphs (DAGs). | |
16 | |
17 Note that most of these functions are only guaranteed to work for DAGs. | |
18 In general, these functions do not check for acyclic-ness, so it is up | |
19 to the user to check for that. | |
20 """ | |
21 | |
22 from collections import defaultdict, deque | |
23 from math import gcd | |
24 from functools import partial | |
25 from itertools import chain | |
26 from itertools import product | |
27 from itertools import starmap | |
28 import heapq | |
29 | |
30 import networkx as nx | |
31 from networkx.algorithms.traversal.breadth_first_search import \ | |
32 descendants_at_distance | |
33 from networkx.generators.trees import NIL | |
34 from networkx.utils import arbitrary_element | |
35 from networkx.utils import consume | |
36 from networkx.utils import pairwise | |
37 from networkx.utils import not_implemented_for | |
38 | |
39 __all__ = ['descendants', | |
40 'ancestors', | |
41 'topological_sort', | |
42 'lexicographical_topological_sort', | |
43 'all_topological_sorts', | |
44 'is_directed_acyclic_graph', | |
45 'is_aperiodic', | |
46 'transitive_closure', | |
47 'transitive_closure_dag', | |
48 'transitive_reduction', | |
49 'antichains', | |
50 'dag_longest_path', | |
51 'dag_longest_path_length', | |
52 'dag_to_branching'] | |
53 | |
54 chaini = chain.from_iterable | |
55 | |
56 | |
57 def descendants(G, source): | |
58 """Returns all nodes reachable from `source` in `G`. | |
59 | |
60 Parameters | |
61 ---------- | |
62 G : NetworkX DiGraph | |
63 A directed acyclic graph (DAG) | |
64 source : node in `G` | |
65 | |
66 Returns | |
67 ------- | |
68 set() | |
69 The descendants of `source` in `G` | |
70 """ | |
71 if not G.has_node(source): | |
72 raise nx.NetworkXError("The node %s is not in the graph." % source) | |
73 des = set(n for n, d in nx.shortest_path_length(G, source=source).items()) | |
74 return des - {source} | |
75 | |
76 | |
77 def ancestors(G, source): | |
78 """Returns all nodes having a path to `source` in `G`. | |
79 | |
80 Parameters | |
81 ---------- | |
82 G : NetworkX DiGraph | |
83 A directed acyclic graph (DAG) | |
84 source : node in `G` | |
85 | |
86 Returns | |
87 ------- | |
88 set() | |
89 The ancestors of source in G | |
90 """ | |
91 if not G.has_node(source): | |
92 raise nx.NetworkXError("The node %s is not in the graph." % source) | |
93 anc = set(n for n, d in nx.shortest_path_length(G, target=source).items()) | |
94 return anc - {source} | |
95 | |
96 | |
97 def has_cycle(G): | |
98 """Decides whether the directed graph has a cycle.""" | |
99 try: | |
100 consume(topological_sort(G)) | |
101 except nx.NetworkXUnfeasible: | |
102 return True | |
103 else: | |
104 return False | |
105 | |
106 | |
107 def is_directed_acyclic_graph(G): | |
108 """Returns True if the graph `G` is a directed acyclic graph (DAG) or | |
109 False if not. | |
110 | |
111 Parameters | |
112 ---------- | |
113 G : NetworkX graph | |
114 | |
115 Returns | |
116 ------- | |
117 bool | |
118 True if `G` is a DAG, False otherwise | |
119 """ | |
120 return G.is_directed() and not has_cycle(G) | |
121 | |
122 | |
123 def topological_sort(G): | |
124 """Returns a generator of nodes in topologically sorted order. | |
125 | |
126 A topological sort is a nonunique permutation of the nodes such that an | |
127 edge from u to v implies that u appears before v in the topological sort | |
128 order. | |
129 | |
130 Parameters | |
131 ---------- | |
132 G : NetworkX digraph | |
133 A directed acyclic graph (DAG) | |
134 | |
135 Returns | |
136 ------- | |
137 iterable | |
138 An iterable of node names in topological sorted order. | |
139 | |
140 Raises | |
141 ------ | |
142 NetworkXError | |
143 Topological sort is defined for directed graphs only. If the graph `G` | |
144 is undirected, a :exc:`NetworkXError` is raised. | |
145 | |
146 NetworkXUnfeasible | |
147 If `G` is not a directed acyclic graph (DAG) no topological sort exists | |
148 and a :exc:`NetworkXUnfeasible` exception is raised. This can also be | |
149 raised if `G` is changed while the returned iterator is being processed | |
150 | |
151 RuntimeError | |
152 If `G` is changed while the returned iterator is being processed. | |
153 | |
154 Examples | |
155 -------- | |
156 To get the reverse order of the topological sort: | |
157 | |
158 >>> DG = nx.DiGraph([(1, 2), (2, 3)]) | |
159 >>> list(reversed(list(nx.topological_sort(DG)))) | |
160 [3, 2, 1] | |
161 | |
162 If your DiGraph naturally has the edges representing tasks/inputs | |
163 and nodes representing people/processes that initiate tasks, then | |
164 topological_sort is not quite what you need. You will have to change | |
165 the tasks to nodes with dependence reflected by edges. The result is | |
166 a kind of topological sort of the edges. This can be done | |
167 with :func:`networkx.line_graph` as follows: | |
168 | |
169 >>> list(nx.topological_sort(nx.line_graph(DG))) | |
170 [(1, 2), (2, 3)] | |
171 | |
172 Notes | |
173 ----- | |
174 This algorithm is based on a description and proof in | |
175 "Introduction to Algorithms: A Creative Approach" [1]_ . | |
176 | |
177 See also | |
178 -------- | |
179 is_directed_acyclic_graph, lexicographical_topological_sort | |
180 | |
181 References | |
182 ---------- | |
183 .. [1] Manber, U. (1989). | |
184 *Introduction to Algorithms - A Creative Approach.* Addison-Wesley. | |
185 """ | |
186 if not G.is_directed(): | |
187 raise nx.NetworkXError( | |
188 "Topological sort not defined on undirected graphs.") | |
189 | |
190 indegree_map = {v: d for v, d in G.in_degree() if d > 0} | |
191 # These nodes have zero indegree and ready to be returned. | |
192 zero_indegree = [v for v, d in G.in_degree() if d == 0] | |
193 | |
194 while zero_indegree: | |
195 node = zero_indegree.pop() | |
196 if node not in G: | |
197 raise RuntimeError("Graph changed during iteration") | |
198 for _, child in G.edges(node): | |
199 try: | |
200 indegree_map[child] -= 1 | |
201 except KeyError: | |
202 raise RuntimeError("Graph changed during iteration") | |
203 if indegree_map[child] == 0: | |
204 zero_indegree.append(child) | |
205 del indegree_map[child] | |
206 | |
207 yield node | |
208 | |
209 if indegree_map: | |
210 raise nx.NetworkXUnfeasible("Graph contains a cycle or graph changed " | |
211 "during iteration") | |
212 | |
213 | |
214 def lexicographical_topological_sort(G, key=None): | |
215 """Returns a generator of nodes in lexicographically topologically sorted | |
216 order. | |
217 | |
218 A topological sort is a nonunique permutation of the nodes such that an | |
219 edge from u to v implies that u appears before v in the topological sort | |
220 order. | |
221 | |
222 Parameters | |
223 ---------- | |
224 G : NetworkX digraph | |
225 A directed acyclic graph (DAG) | |
226 | |
227 key : function, optional | |
228 This function maps nodes to keys with which to resolve ambiguities in | |
229 the sort order. Defaults to the identity function. | |
230 | |
231 Returns | |
232 ------- | |
233 iterable | |
234 An iterable of node names in lexicographical topological sort order. | |
235 | |
236 Raises | |
237 ------ | |
238 NetworkXError | |
239 Topological sort is defined for directed graphs only. If the graph `G` | |
240 is undirected, a :exc:`NetworkXError` is raised. | |
241 | |
242 NetworkXUnfeasible | |
243 If `G` is not a directed acyclic graph (DAG) no topological sort exists | |
244 and a :exc:`NetworkXUnfeasible` exception is raised. This can also be | |
245 raised if `G` is changed while the returned iterator is being processed | |
246 | |
247 RuntimeError | |
248 If `G` is changed while the returned iterator is being processed. | |
249 | |
250 Notes | |
251 ----- | |
252 This algorithm is based on a description and proof in | |
253 "Introduction to Algorithms: A Creative Approach" [1]_ . | |
254 | |
255 See also | |
256 -------- | |
257 topological_sort | |
258 | |
259 References | |
260 ---------- | |
261 .. [1] Manber, U. (1989). | |
262 *Introduction to Algorithms - A Creative Approach.* Addison-Wesley. | |
263 """ | |
264 if not G.is_directed(): | |
265 msg = "Topological sort not defined on undirected graphs." | |
266 raise nx.NetworkXError(msg) | |
267 | |
268 if key is None: | |
269 def key(node): | |
270 return node | |
271 | |
272 nodeid_map = {n: i for i, n in enumerate(G)} | |
273 | |
274 def create_tuple(node): | |
275 return key(node), nodeid_map[node], node | |
276 | |
277 indegree_map = {v: d for v, d in G.in_degree() if d > 0} | |
278 # These nodes have zero indegree and ready to be returned. | |
279 zero_indegree = [create_tuple(v) for v, d in G.in_degree() if d == 0] | |
280 heapq.heapify(zero_indegree) | |
281 | |
282 while zero_indegree: | |
283 _, _, node = heapq.heappop(zero_indegree) | |
284 | |
285 if node not in G: | |
286 raise RuntimeError("Graph changed during iteration") | |
287 for _, child in G.edges(node): | |
288 try: | |
289 indegree_map[child] -= 1 | |
290 except KeyError: | |
291 raise RuntimeError("Graph changed during iteration") | |
292 if indegree_map[child] == 0: | |
293 heapq.heappush(zero_indegree, create_tuple(child)) | |
294 del indegree_map[child] | |
295 | |
296 yield node | |
297 | |
298 if indegree_map: | |
299 msg = "Graph contains a cycle or graph changed during iteration" | |
300 raise nx.NetworkXUnfeasible(msg) | |
301 | |
302 | |
303 @not_implemented_for('undirected') | |
304 def all_topological_sorts(G): | |
305 """Returns a generator of _all_ topological sorts of the directed graph G. | |
306 | |
307 A topological sort is a nonunique permutation of the nodes such that an | |
308 edge from u to v implies that u appears before v in the topological sort | |
309 order. | |
310 | |
311 Parameters | |
312 ---------- | |
313 G : NetworkX DiGraph | |
314 A directed graph | |
315 | |
316 Returns | |
317 ------- | |
318 generator | |
319 All topological sorts of the digraph G | |
320 | |
321 Raises | |
322 ------ | |
323 NetworkXNotImplemented | |
324 If `G` is not directed | |
325 NetworkXUnfeasible | |
326 If `G` is not acyclic | |
327 | |
328 Examples | |
329 -------- | |
330 To enumerate all topological sorts of directed graph: | |
331 | |
332 >>> DG = nx.DiGraph([(1, 2), (2, 3), (2, 4)]) | |
333 >>> list(nx.all_topological_sorts(DG)) | |
334 [[1, 2, 4, 3], [1, 2, 3, 4]] | |
335 | |
336 Notes | |
337 ----- | |
338 Implements an iterative version of the algorithm given in [1]. | |
339 | |
340 References | |
341 ---------- | |
342 .. [1] Knuth, Donald E., Szwarcfiter, Jayme L. (1974). | |
343 "A Structured Program to Generate All Topological Sorting Arrangements" | |
344 Information Processing Letters, Volume 2, Issue 6, 1974, Pages 153-157, | |
345 ISSN 0020-0190, | |
346 https://doi.org/10.1016/0020-0190(74)90001-5. | |
347 Elsevier (North-Holland), Amsterdam | |
348 """ | |
349 if not G.is_directed(): | |
350 raise nx.NetworkXError( | |
351 "Topological sort not defined on undirected graphs.") | |
352 | |
353 # the names of count and D are chosen to match the global variables in [1] | |
354 # number of edges originating in a vertex v | |
355 count = dict(G.in_degree()) | |
356 # vertices with indegree 0 | |
357 D = deque([v for v, d in G.in_degree() if d == 0]) | |
358 # stack of first value chosen at a position k in the topological sort | |
359 bases = [] | |
360 current_sort = [] | |
361 | |
362 # do-while construct | |
363 while True: | |
364 assert all([count[v] == 0 for v in D]) | |
365 | |
366 if len(current_sort) == len(G): | |
367 yield list(current_sort) | |
368 | |
369 # clean-up stack | |
370 while len(current_sort) > 0: | |
371 assert len(bases) == len(current_sort) | |
372 q = current_sort.pop() | |
373 | |
374 # "restores" all edges (q, x) | |
375 # NOTE: it is important to iterate over edges instead | |
376 # of successors, so count is updated correctly in multigraphs | |
377 for _, j in G.out_edges(q): | |
378 count[j] += 1 | |
379 assert count[j] >= 0 | |
380 # remove entries from D | |
381 while len(D) > 0 and count[D[-1]] > 0: | |
382 D.pop() | |
383 | |
384 # corresponds to a circular shift of the values in D | |
385 # if the first value chosen (the base) is in the first | |
386 # position of D again, we are done and need to consider the | |
387 # previous condition | |
388 D.appendleft(q) | |
389 if D[-1] == bases[-1]: | |
390 # all possible values have been chosen at current position | |
391 # remove corresponding marker | |
392 bases.pop() | |
393 else: | |
394 # there are still elements that have not been fixed | |
395 # at the current position in the topological sort | |
396 # stop removing elements, escape inner loop | |
397 break | |
398 | |
399 else: | |
400 if len(D) == 0: | |
401 raise nx.NetworkXUnfeasible("Graph contains a cycle.") | |
402 | |
403 # choose next node | |
404 q = D.pop() | |
405 # "erase" all edges (q, x) | |
406 # NOTE: it is important to iterate over edges instead | |
407 # of successors, so count is updated correctly in multigraphs | |
408 for _, j in G.out_edges(q): | |
409 count[j] -= 1 | |
410 assert count[j] >= 0 | |
411 if count[j] == 0: | |
412 D.append(j) | |
413 current_sort.append(q) | |
414 | |
415 # base for current position might _not_ be fixed yet | |
416 if len(bases) < len(current_sort): | |
417 bases.append(q) | |
418 | |
419 if len(bases) == 0: | |
420 break | |
421 | |
422 | |
423 def is_aperiodic(G): | |
424 """Returns True if `G` is aperiodic. | |
425 | |
426 A directed graph is aperiodic if there is no integer k > 1 that | |
427 divides the length of every cycle in the graph. | |
428 | |
429 Parameters | |
430 ---------- | |
431 G : NetworkX DiGraph | |
432 A directed graph | |
433 | |
434 Returns | |
435 ------- | |
436 bool | |
437 True if the graph is aperiodic False otherwise | |
438 | |
439 Raises | |
440 ------ | |
441 NetworkXError | |
442 If `G` is not directed | |
443 | |
444 Notes | |
445 ----- | |
446 This uses the method outlined in [1]_, which runs in $O(m)$ time | |
447 given $m$ edges in `G`. Note that a graph is not aperiodic if it is | |
448 acyclic as every integer trivial divides length 0 cycles. | |
449 | |
450 References | |
451 ---------- | |
452 .. [1] Jarvis, J. P.; Shier, D. R. (1996), | |
453 "Graph-theoretic analysis of finite Markov chains," | |
454 in Shier, D. R.; Wallenius, K. T., Applied Mathematical Modeling: | |
455 A Multidisciplinary Approach, CRC Press. | |
456 """ | |
457 if not G.is_directed(): | |
458 raise nx.NetworkXError( | |
459 "is_aperiodic not defined for undirected graphs") | |
460 | |
461 s = arbitrary_element(G) | |
462 levels = {s: 0} | |
463 this_level = [s] | |
464 g = 0 | |
465 lev = 1 | |
466 while this_level: | |
467 next_level = [] | |
468 for u in this_level: | |
469 for v in G[u]: | |
470 if v in levels: # Non-Tree Edge | |
471 g = gcd(g, levels[u] - levels[v] + 1) | |
472 else: # Tree Edge | |
473 next_level.append(v) | |
474 levels[v] = lev | |
475 this_level = next_level | |
476 lev += 1 | |
477 if len(levels) == len(G): # All nodes in tree | |
478 return g == 1 | |
479 else: | |
480 return g == 1 and nx.is_aperiodic(G.subgraph(set(G) - set(levels))) | |
481 | |
482 | |
483 @not_implemented_for('undirected') | |
484 def transitive_closure(G, reflexive=False): | |
485 """ Returns transitive closure of a directed graph | |
486 | |
487 The transitive closure of G = (V,E) is a graph G+ = (V,E+) such that | |
488 for all v, w in V there is an edge (v, w) in E+ if and only if there | |
489 is a path from v to w in G. | |
490 | |
491 Handling of paths from v to v has some flexibility within this definition. | |
492 A reflexive transitive closure creates a self-loop for the path | |
493 from v to v of length 0. The usual transitive closure creates a | |
494 self-loop only if a cycle exists (a path from v to v with length > 0). | |
495 We also allow an option for no self-loops. | |
496 | |
497 Parameters | |
498 ---------- | |
499 G : NetworkX DiGraph | |
500 A directed graph | |
501 reflexive : Bool or None, optional (default: False) | |
502 Determines when cycles create self-loops in the Transitive Closure. | |
503 If True, trivial cycles (length 0) create self-loops. The result | |
504 is a reflexive tranistive closure of G. | |
505 If False (the default) non-trivial cycles create self-loops. | |
506 If None, self-loops are not created. | |
507 | |
508 Returns | |
509 ------- | |
510 NetworkX DiGraph | |
511 The transitive closure of `G` | |
512 | |
513 Raises | |
514 ------ | |
515 NetworkXNotImplemented | |
516 If `G` is not directed | |
517 | |
518 References | |
519 ---------- | |
520 .. [1] http://www.ics.uci.edu/~eppstein/PADS/PartialOrder.py | |
521 | |
522 TODO this function applies to all directed graphs and is probably misplaced | |
523 here in dag.py | |
524 """ | |
525 if reflexive is None: | |
526 TC = G.copy() | |
527 for v in G: | |
528 edges = ((v, u) for u in nx.dfs_preorder_nodes(G, v) if v != u) | |
529 TC.add_edges_from(edges) | |
530 return TC | |
531 if reflexive is True: | |
532 TC = G.copy() | |
533 for v in G: | |
534 edges = ((v, u) for u in nx.dfs_preorder_nodes(G, v)) | |
535 TC.add_edges_from(edges) | |
536 return TC | |
537 # reflexive is False | |
538 TC = G.copy() | |
539 for v in G: | |
540 edges = ((v, w) for u, w in nx.edge_dfs(G, v)) | |
541 TC.add_edges_from(edges) | |
542 return TC | |
543 | |
544 | |
545 @not_implemented_for('undirected') | |
546 def transitive_closure_dag(G, topo_order=None): | |
547 """ Returns the transitive closure of a directed acyclic graph. | |
548 | |
549 This function is faster than the function `transitive_closure`, but fails | |
550 if the graph has a cycle. | |
551 | |
552 The transitive closure of G = (V,E) is a graph G+ = (V,E+) such that | |
553 for all v, w in V there is an edge (v, w) in E+ if and only if there | |
554 is a non-null path from v to w in G. | |
555 | |
556 Parameters | |
557 ---------- | |
558 G : NetworkX DiGraph | |
559 A directed acyclic graph (DAG) | |
560 | |
561 topo_order: list or tuple, optional | |
562 A topological order for G (if None, the function will compute one) | |
563 | |
564 Returns | |
565 ------- | |
566 NetworkX DiGraph | |
567 The transitive closure of `G` | |
568 | |
569 Raises | |
570 ------ | |
571 NetworkXNotImplemented | |
572 If `G` is not directed | |
573 NetworkXUnfeasible | |
574 If `G` has a cycle | |
575 | |
576 Notes | |
577 ----- | |
578 This algorithm is probably simple enough to be well-known but I didn't find | |
579 a mention in the literature. | |
580 """ | |
581 if topo_order is None: | |
582 topo_order = list(topological_sort(G)) | |
583 | |
584 TC = G.copy() | |
585 | |
586 # idea: traverse vertices following a reverse topological order, connecting | |
587 # each vertex to its descendants at distance 2 as we go | |
588 for v in reversed(topo_order): | |
589 TC.add_edges_from((v, u) for u in descendants_at_distance(TC, v, 2)) | |
590 | |
591 return TC | |
592 | |
593 | |
594 @not_implemented_for('undirected') | |
595 def transitive_reduction(G): | |
596 """ Returns transitive reduction of a directed graph | |
597 | |
598 The transitive reduction of G = (V,E) is a graph G- = (V,E-) such that | |
599 for all v,w in V there is an edge (v,w) in E- if and only if (v,w) is | |
600 in E and there is no path from v to w in G with length greater than 1. | |
601 | |
602 Parameters | |
603 ---------- | |
604 G : NetworkX DiGraph | |
605 A directed acyclic graph (DAG) | |
606 | |
607 Returns | |
608 ------- | |
609 NetworkX DiGraph | |
610 The transitive reduction of `G` | |
611 | |
612 Raises | |
613 ------ | |
614 NetworkXError | |
615 If `G` is not a directed acyclic graph (DAG) transitive reduction is | |
616 not uniquely defined and a :exc:`NetworkXError` exception is raised. | |
617 | |
618 References | |
619 ---------- | |
620 https://en.wikipedia.org/wiki/Transitive_reduction | |
621 | |
622 """ | |
623 if not is_directed_acyclic_graph(G): | |
624 msg = "Directed Acyclic Graph required for transitive_reduction" | |
625 raise nx.NetworkXError(msg) | |
626 TR = nx.DiGraph() | |
627 TR.add_nodes_from(G.nodes()) | |
628 descendants = {} | |
629 # count before removing set stored in descendants | |
630 check_count = dict(G.in_degree) | |
631 for u in G: | |
632 u_nbrs = set(G[u]) | |
633 for v in G[u]: | |
634 if v in u_nbrs: | |
635 if v not in descendants: | |
636 descendants[v] = {y for x, y in nx.dfs_edges(G, v)} | |
637 u_nbrs -= descendants[v] | |
638 check_count[v] -= 1 | |
639 if check_count[v] == 0: | |
640 del descendants[v] | |
641 TR.add_edges_from((u, v) for v in u_nbrs) | |
642 return TR | |
643 | |
644 | |
645 @not_implemented_for('undirected') | |
646 def antichains(G, topo_order=None): | |
647 """Generates antichains from a directed acyclic graph (DAG). | |
648 | |
649 An antichain is a subset of a partially ordered set such that any | |
650 two elements in the subset are incomparable. | |
651 | |
652 Parameters | |
653 ---------- | |
654 G : NetworkX DiGraph | |
655 A directed acyclic graph (DAG) | |
656 | |
657 topo_order: list or tuple, optional | |
658 A topological order for G (if None, the function will compute one) | |
659 | |
660 Returns | |
661 ------- | |
662 generator object | |
663 | |
664 Raises | |
665 ------ | |
666 NetworkXNotImplemented | |
667 If `G` is not directed | |
668 | |
669 NetworkXUnfeasible | |
670 If `G` contains a cycle | |
671 | |
672 Notes | |
673 ----- | |
674 This function was originally developed by Peter Jipsen and Franco Saliola | |
675 for the SAGE project. It's included in NetworkX with permission from the | |
676 authors. Original SAGE code at: | |
677 | |
678 https://github.com/sagemath/sage/blob/master/src/sage/combinat/posets/hasse_diagram.py | |
679 | |
680 References | |
681 ---------- | |
682 .. [1] Free Lattices, by R. Freese, J. Jezek and J. B. Nation, | |
683 AMS, Vol 42, 1995, p. 226. | |
684 """ | |
685 if topo_order is None: | |
686 topo_order = list(nx.topological_sort(G)) | |
687 | |
688 TC = nx.transitive_closure_dag(G, topo_order) | |
689 antichains_stacks = [([], list(reversed(topo_order)))] | |
690 | |
691 while antichains_stacks: | |
692 (antichain, stack) = antichains_stacks.pop() | |
693 # Invariant: | |
694 # - the elements of antichain are independent | |
695 # - the elements of stack are independent from those of antichain | |
696 yield antichain | |
697 while stack: | |
698 x = stack.pop() | |
699 new_antichain = antichain + [x] | |
700 new_stack = [ | |
701 t for t in stack if not ((t in TC[x]) or (x in TC[t]))] | |
702 antichains_stacks.append((new_antichain, new_stack)) | |
703 | |
704 | |
705 @not_implemented_for('undirected') | |
706 def dag_longest_path(G, weight='weight', default_weight=1, topo_order=None): | |
707 """Returns the longest path in a directed acyclic graph (DAG). | |
708 | |
709 If `G` has edges with `weight` attribute the edge data are used as | |
710 weight values. | |
711 | |
712 Parameters | |
713 ---------- | |
714 G : NetworkX DiGraph | |
715 A directed acyclic graph (DAG) | |
716 | |
717 weight : str, optional | |
718 Edge data key to use for weight | |
719 | |
720 default_weight : int, optional | |
721 The weight of edges that do not have a weight attribute | |
722 | |
723 topo_order: list or tuple, optional | |
724 A topological order for G (if None, the function will compute one) | |
725 | |
726 Returns | |
727 ------- | |
728 list | |
729 Longest path | |
730 | |
731 Raises | |
732 ------ | |
733 NetworkXNotImplemented | |
734 If `G` is not directed | |
735 | |
736 See also | |
737 -------- | |
738 dag_longest_path_length | |
739 | |
740 """ | |
741 if not G: | |
742 return [] | |
743 | |
744 if topo_order is None: | |
745 topo_order = nx.topological_sort(G) | |
746 | |
747 dist = {} # stores {v : (length, u)} | |
748 for v in topo_order: | |
749 us = [(dist[u][0] + data.get(weight, default_weight), u) | |
750 for u, data in G.pred[v].items()] | |
751 | |
752 # Use the best predecessor if there is one and its distance is | |
753 # non-negative, otherwise terminate. | |
754 maxu = max(us, key=lambda x: x[0]) if us else (0, v) | |
755 dist[v] = maxu if maxu[0] >= 0 else (0, v) | |
756 | |
757 u = None | |
758 v = max(dist, key=lambda x: dist[x][0]) | |
759 path = [] | |
760 while u != v: | |
761 path.append(v) | |
762 u = v | |
763 v = dist[v][1] | |
764 | |
765 path.reverse() | |
766 return path | |
767 | |
768 | |
769 @not_implemented_for('undirected') | |
770 def dag_longest_path_length(G, weight='weight', default_weight=1): | |
771 """Returns the longest path length in a DAG | |
772 | |
773 Parameters | |
774 ---------- | |
775 G : NetworkX DiGraph | |
776 A directed acyclic graph (DAG) | |
777 | |
778 weight : string, optional | |
779 Edge data key to use for weight | |
780 | |
781 default_weight : int, optional | |
782 The weight of edges that do not have a weight attribute | |
783 | |
784 Returns | |
785 ------- | |
786 int | |
787 Longest path length | |
788 | |
789 Raises | |
790 ------ | |
791 NetworkXNotImplemented | |
792 If `G` is not directed | |
793 | |
794 See also | |
795 -------- | |
796 dag_longest_path | |
797 """ | |
798 path = nx.dag_longest_path(G, weight, default_weight) | |
799 path_length = 0 | |
800 for (u, v) in pairwise(path): | |
801 path_length += G[u][v].get(weight, default_weight) | |
802 | |
803 return path_length | |
804 | |
805 | |
806 def root_to_leaf_paths(G): | |
807 """Yields root-to-leaf paths in a directed acyclic graph. | |
808 | |
809 `G` must be a directed acyclic graph. If not, the behavior of this | |
810 function is undefined. A "root" in this graph is a node of in-degree | |
811 zero and a "leaf" a node of out-degree zero. | |
812 | |
813 When invoked, this function iterates over each path from any root to | |
814 any leaf. A path is a list of nodes. | |
815 | |
816 """ | |
817 roots = (v for v, d in G.in_degree() if d == 0) | |
818 leaves = (v for v, d in G.out_degree() if d == 0) | |
819 all_paths = partial(nx.all_simple_paths, G) | |
820 # TODO In Python 3, this would be better as `yield from ...`. | |
821 return chaini(starmap(all_paths, product(roots, leaves))) | |
822 | |
823 | |
824 @not_implemented_for('multigraph') | |
825 @not_implemented_for('undirected') | |
826 def dag_to_branching(G): | |
827 """Returns a branching representing all (overlapping) paths from | |
828 root nodes to leaf nodes in the given directed acyclic graph. | |
829 | |
830 As described in :mod:`networkx.algorithms.tree.recognition`, a | |
831 *branching* is a directed forest in which each node has at most one | |
832 parent. In other words, a branching is a disjoint union of | |
833 *arborescences*. For this function, each node of in-degree zero in | |
834 `G` becomes a root of one of the arborescences, and there will be | |
835 one leaf node for each distinct path from that root to a leaf node | |
836 in `G`. | |
837 | |
838 Each node `v` in `G` with *k* parents becomes *k* distinct nodes in | |
839 the returned branching, one for each parent, and the sub-DAG rooted | |
840 at `v` is duplicated for each copy. The algorithm then recurses on | |
841 the children of each copy of `v`. | |
842 | |
843 Parameters | |
844 ---------- | |
845 G : NetworkX graph | |
846 A directed acyclic graph. | |
847 | |
848 Returns | |
849 ------- | |
850 DiGraph | |
851 The branching in which there is a bijection between root-to-leaf | |
852 paths in `G` (in which multiple paths may share the same leaf) | |
853 and root-to-leaf paths in the branching (in which there is a | |
854 unique path from a root to a leaf). | |
855 | |
856 Each node has an attribute 'source' whose value is the original | |
857 node to which this node corresponds. No other graph, node, or | |
858 edge attributes are copied into this new graph. | |
859 | |
860 Raises | |
861 ------ | |
862 NetworkXNotImplemented | |
863 If `G` is not directed, or if `G` is a multigraph. | |
864 | |
865 HasACycle | |
866 If `G` is not acyclic. | |
867 | |
868 Examples | |
869 -------- | |
870 To examine which nodes in the returned branching were produced by | |
871 which original node in the directed acyclic graph, we can collect | |
872 the mapping from source node to new nodes into a dictionary. For | |
873 example, consider the directed diamond graph:: | |
874 | |
875 >>> from collections import defaultdict | |
876 >>> from operator import itemgetter | |
877 >>> | |
878 >>> G = nx.DiGraph(nx.utils.pairwise('abd')) | |
879 >>> G.add_edges_from(nx.utils.pairwise('acd')) | |
880 >>> B = nx.dag_to_branching(G) | |
881 >>> | |
882 >>> sources = defaultdict(set) | |
883 >>> for v, source in B.nodes(data='source'): | |
884 ... sources[source].add(v) | |
885 >>> len(sources['a']) | |
886 1 | |
887 >>> len(sources['d']) | |
888 2 | |
889 | |
890 To copy node attributes from the original graph to the new graph, | |
891 you can use a dictionary like the one constructed in the above | |
892 example:: | |
893 | |
894 >>> for source, nodes in sources.items(): | |
895 ... for v in nodes: | |
896 ... B.nodes[v].update(G.nodes[source]) | |
897 | |
898 Notes | |
899 ----- | |
900 This function is not idempotent in the sense that the node labels in | |
901 the returned branching may be uniquely generated each time the | |
902 function is invoked. In fact, the node labels may not be integers; | |
903 in order to relabel the nodes to be more readable, you can use the | |
904 :func:`networkx.convert_node_labels_to_integers` function. | |
905 | |
906 The current implementation of this function uses | |
907 :func:`networkx.prefix_tree`, so it is subject to the limitations of | |
908 that function. | |
909 | |
910 """ | |
911 if has_cycle(G): | |
912 msg = 'dag_to_branching is only defined for acyclic graphs' | |
913 raise nx.HasACycle(msg) | |
914 paths = root_to_leaf_paths(G) | |
915 B, root = nx.prefix_tree(paths) | |
916 # Remove the synthetic `root` and `NIL` nodes in the prefix tree. | |
917 B.remove_node(root) | |
918 B.remove_node(NIL) | |
919 return B |