Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/dag.py: 21%
262 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
1"""Algorithms for directed acyclic graphs (DAGs).
3Note that most of these functions are only guaranteed to work for DAGs.
4In general, these functions do not check for acyclic-ness, so it is up
5to the user to check for that.
6"""
8import heapq
9from collections import deque
10from functools import partial
11from itertools import chain, combinations, product, starmap
12from math import gcd
14import networkx as nx
15from networkx.utils import arbitrary_element, not_implemented_for, pairwise
17__all__ = [
18 "descendants",
19 "ancestors",
20 "topological_sort",
21 "lexicographical_topological_sort",
22 "all_topological_sorts",
23 "topological_generations",
24 "is_directed_acyclic_graph",
25 "is_aperiodic",
26 "transitive_closure",
27 "transitive_closure_dag",
28 "transitive_reduction",
29 "antichains",
30 "dag_longest_path",
31 "dag_longest_path_length",
32 "dag_to_branching",
33 "compute_v_structures",
34]
36chaini = chain.from_iterable
39@nx._dispatch
40def descendants(G, source):
41 """Returns all nodes reachable from `source` in `G`.
43 Parameters
44 ----------
45 G : NetworkX Graph
46 source : node in `G`
48 Returns
49 -------
50 set()
51 The descendants of `source` in `G`
53 Raises
54 ------
55 NetworkXError
56 If node `source` is not in `G`.
58 Examples
59 --------
60 >>> DG = nx.path_graph(5, create_using=nx.DiGraph)
61 >>> sorted(nx.descendants(DG, 2))
62 [3, 4]
64 The `source` node is not a descendant of itself, but can be included manually:
66 >>> sorted(nx.descendants(DG, 2) | {2})
67 [2, 3, 4]
69 See also
70 --------
71 ancestors
72 """
73 return {child for parent, child in nx.bfs_edges(G, source)}
76@nx._dispatch
77def ancestors(G, source):
78 """Returns all nodes having a path to `source` in `G`.
80 Parameters
81 ----------
82 G : NetworkX Graph
83 source : node in `G`
85 Returns
86 -------
87 set()
88 The ancestors of `source` in `G`
90 Raises
91 ------
92 NetworkXError
93 If node `source` is not in `G`.
95 Examples
96 --------
97 >>> DG = nx.path_graph(5, create_using=nx.DiGraph)
98 >>> sorted(nx.ancestors(DG, 2))
99 [0, 1]
101 The `source` node is not an ancestor of itself, but can be included manually:
103 >>> sorted(nx.ancestors(DG, 2) | {2})
104 [0, 1, 2]
106 See also
107 --------
108 descendants
109 """
110 return {child for parent, child in nx.bfs_edges(G, source, reverse=True)}
113@nx._dispatch
114def has_cycle(G):
115 """Decides whether the directed graph has a cycle."""
116 try:
117 # Feed the entire iterator into a zero-length deque.
118 deque(topological_sort(G), maxlen=0)
119 except nx.NetworkXUnfeasible:
120 return True
121 else:
122 return False
125@nx._dispatch
126def is_directed_acyclic_graph(G):
127 """Returns True if the graph `G` is a directed acyclic graph (DAG) or
128 False if not.
130 Parameters
131 ----------
132 G : NetworkX graph
134 Returns
135 -------
136 bool
137 True if `G` is a DAG, False otherwise
139 Examples
140 --------
141 Undirected graph::
143 >>> G = nx.Graph([(1, 2), (2, 3)])
144 >>> nx.is_directed_acyclic_graph(G)
145 False
147 Directed graph with cycle::
149 >>> G = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
150 >>> nx.is_directed_acyclic_graph(G)
151 False
153 Directed acyclic graph::
155 >>> G = nx.DiGraph([(1, 2), (2, 3)])
156 >>> nx.is_directed_acyclic_graph(G)
157 True
159 See also
160 --------
161 topological_sort
162 """
163 return G.is_directed() and not has_cycle(G)
166@nx._dispatch
167def topological_generations(G):
168 """Stratifies a DAG into generations.
170 A topological generation is node collection in which ancestors of a node in each
171 generation are guaranteed to be in a previous generation, and any descendants of
172 a node are guaranteed to be in a following generation. Nodes are guaranteed to
173 be in the earliest possible generation that they can belong to.
175 Parameters
176 ----------
177 G : NetworkX digraph
178 A directed acyclic graph (DAG)
180 Yields
181 ------
182 sets of nodes
183 Yields sets of nodes representing each generation.
185 Raises
186 ------
187 NetworkXError
188 Generations are defined for directed graphs only. If the graph
189 `G` is undirected, a :exc:`NetworkXError` is raised.
191 NetworkXUnfeasible
192 If `G` is not a directed acyclic graph (DAG) no topological generations
193 exist and a :exc:`NetworkXUnfeasible` exception is raised. This can also
194 be raised if `G` is changed while the returned iterator is being processed
196 RuntimeError
197 If `G` is changed while the returned iterator is being processed.
199 Examples
200 --------
201 >>> DG = nx.DiGraph([(2, 1), (3, 1)])
202 >>> [sorted(generation) for generation in nx.topological_generations(DG)]
203 [[2, 3], [1]]
205 Notes
206 -----
207 The generation in which a node resides can also be determined by taking the
208 max-path-distance from the node to the farthest leaf node. That value can
209 be obtained with this function using `enumerate(topological_generations(G))`.
211 See also
212 --------
213 topological_sort
214 """
215 if not G.is_directed():
216 raise nx.NetworkXError("Topological sort not defined on undirected graphs.")
218 multigraph = G.is_multigraph()
219 indegree_map = {v: d for v, d in G.in_degree() if d > 0}
220 zero_indegree = [v for v, d in G.in_degree() if d == 0]
222 while zero_indegree:
223 this_generation = zero_indegree
224 zero_indegree = []
225 for node in this_generation:
226 if node not in G:
227 raise RuntimeError("Graph changed during iteration")
228 for child in G.neighbors(node):
229 try:
230 indegree_map[child] -= len(G[node][child]) if multigraph else 1
231 except KeyError as err:
232 raise RuntimeError("Graph changed during iteration") from err
233 if indegree_map[child] == 0:
234 zero_indegree.append(child)
235 del indegree_map[child]
236 yield this_generation
238 if indegree_map:
239 raise nx.NetworkXUnfeasible(
240 "Graph contains a cycle or graph changed during iteration"
241 )
244@nx._dispatch
245def topological_sort(G):
246 """Returns a generator of nodes in topologically sorted order.
248 A topological sort is a nonunique permutation of the nodes of a
249 directed graph such that an edge from u to v implies that u
250 appears before v in the topological sort order. This ordering is
251 valid only if the graph has no directed cycles.
253 Parameters
254 ----------
255 G : NetworkX digraph
256 A directed acyclic graph (DAG)
258 Yields
259 ------
260 nodes
261 Yields the nodes in topological sorted order.
263 Raises
264 ------
265 NetworkXError
266 Topological sort is defined for directed graphs only. If the graph `G`
267 is undirected, a :exc:`NetworkXError` is raised.
269 NetworkXUnfeasible
270 If `G` is not a directed acyclic graph (DAG) no topological sort exists
271 and a :exc:`NetworkXUnfeasible` exception is raised. This can also be
272 raised if `G` is changed while the returned iterator is being processed
274 RuntimeError
275 If `G` is changed while the returned iterator is being processed.
277 Examples
278 --------
279 To get the reverse order of the topological sort:
281 >>> DG = nx.DiGraph([(1, 2), (2, 3)])
282 >>> list(reversed(list(nx.topological_sort(DG))))
283 [3, 2, 1]
285 If your DiGraph naturally has the edges representing tasks/inputs
286 and nodes representing people/processes that initiate tasks, then
287 topological_sort is not quite what you need. You will have to change
288 the tasks to nodes with dependence reflected by edges. The result is
289 a kind of topological sort of the edges. This can be done
290 with :func:`networkx.line_graph` as follows:
292 >>> list(nx.topological_sort(nx.line_graph(DG)))
293 [(1, 2), (2, 3)]
295 Notes
296 -----
297 This algorithm is based on a description and proof in
298 "Introduction to Algorithms: A Creative Approach" [1]_ .
300 See also
301 --------
302 is_directed_acyclic_graph, lexicographical_topological_sort
304 References
305 ----------
306 .. [1] Manber, U. (1989).
307 *Introduction to Algorithms - A Creative Approach.* Addison-Wesley.
308 """
309 for generation in nx.topological_generations(G):
310 yield from generation
313@nx._dispatch
314def lexicographical_topological_sort(G, key=None):
315 """Generate the nodes in the unique lexicographical topological sort order.
317 Generates a unique ordering of nodes by first sorting topologically (for which there are often
318 multiple valid orderings) and then additionally by sorting lexicographically.
320 A topological sort arranges the nodes of a directed graph so that the
321 upstream node of each directed edge precedes the downstream node.
322 It is always possible to find a solution for directed graphs that have no cycles.
323 There may be more than one valid solution.
325 Lexicographical sorting is just sorting alphabetically. It is used here to break ties in the
326 topological sort and to determine a single, unique ordering. This can be useful in comparing
327 sort results.
329 The lexicographical order can be customized by providing a function to the `key=` parameter.
330 The definition of the key function is the same as used in python's built-in `sort()`.
331 The function takes a single argument and returns a key to use for sorting purposes.
333 Lexicographical sorting can fail if the node names are un-sortable. See the example below.
334 The solution is to provide a function to the `key=` argument that returns sortable keys.
337 Parameters
338 ----------
339 G : NetworkX digraph
340 A directed acyclic graph (DAG)
342 key : function, optional
343 A function of one argument that converts a node name to a comparison key.
344 It defines and resolves ambiguities in the sort order. Defaults to the identity function.
346 Yields
347 ------
348 nodes
349 Yields the nodes of G in lexicographical topological sort order.
351 Raises
352 ------
353 NetworkXError
354 Topological sort is defined for directed graphs only. If the graph `G`
355 is undirected, a :exc:`NetworkXError` is raised.
357 NetworkXUnfeasible
358 If `G` is not a directed acyclic graph (DAG) no topological sort exists
359 and a :exc:`NetworkXUnfeasible` exception is raised. This can also be
360 raised if `G` is changed while the returned iterator is being processed
362 RuntimeError
363 If `G` is changed while the returned iterator is being processed.
365 TypeError
366 Results from un-sortable node names.
367 Consider using `key=` parameter to resolve ambiguities in the sort order.
369 Examples
370 --------
371 >>> DG = nx.DiGraph([(2, 1), (2, 5), (1, 3), (1, 4), (5, 4)])
372 >>> list(nx.lexicographical_topological_sort(DG))
373 [2, 1, 3, 5, 4]
374 >>> list(nx.lexicographical_topological_sort(DG, key=lambda x: -x))
375 [2, 5, 1, 4, 3]
377 The sort will fail for any graph with integer and string nodes. Comparison of integer to strings
378 is not defined in python. Is 3 greater or less than 'red'?
380 >>> DG = nx.DiGraph([(1, 'red'), (3, 'red'), (1, 'green'), (2, 'blue')])
381 >>> list(nx.lexicographical_topological_sort(DG))
382 Traceback (most recent call last):
383 ...
384 TypeError: '<' not supported between instances of 'str' and 'int'
385 ...
387 Incomparable nodes can be resolved using a `key` function. This example function
388 allows comparison of integers and strings by returning a tuple where the first
389 element is True for `str`, False otherwise. The second element is the node name.
390 This groups the strings and integers separately so they can be compared only among themselves.
392 >>> key = lambda node: (isinstance(node, str), node)
393 >>> list(nx.lexicographical_topological_sort(DG, key=key))
394 [1, 2, 3, 'blue', 'green', 'red']
396 Notes
397 -----
398 This algorithm is based on a description and proof in
399 "Introduction to Algorithms: A Creative Approach" [1]_ .
401 See also
402 --------
403 topological_sort
405 References
406 ----------
407 .. [1] Manber, U. (1989).
408 *Introduction to Algorithms - A Creative Approach.* Addison-Wesley.
409 """
410 if not G.is_directed():
411 msg = "Topological sort not defined on undirected graphs."
412 raise nx.NetworkXError(msg)
414 if key is None:
416 def key(node):
417 return node
419 nodeid_map = {n: i for i, n in enumerate(G)}
421 def create_tuple(node):
422 return key(node), nodeid_map[node], node
424 indegree_map = {v: d for v, d in G.in_degree() if d > 0}
425 # These nodes have zero indegree and ready to be returned.
426 zero_indegree = [create_tuple(v) for v, d in G.in_degree() if d == 0]
427 heapq.heapify(zero_indegree)
429 while zero_indegree:
430 _, _, node = heapq.heappop(zero_indegree)
432 if node not in G:
433 raise RuntimeError("Graph changed during iteration")
434 for _, child in G.edges(node):
435 try:
436 indegree_map[child] -= 1
437 except KeyError as err:
438 raise RuntimeError("Graph changed during iteration") from err
439 if indegree_map[child] == 0:
440 try:
441 heapq.heappush(zero_indegree, create_tuple(child))
442 except TypeError as err:
443 raise TypeError(
444 f"{err}\nConsider using `key=` parameter to resolve ambiguities in the sort order."
445 )
446 del indegree_map[child]
448 yield node
450 if indegree_map:
451 msg = "Graph contains a cycle or graph changed during iteration"
452 raise nx.NetworkXUnfeasible(msg)
455@not_implemented_for("undirected")
456@nx._dispatch
457def all_topological_sorts(G):
458 """Returns a generator of _all_ topological sorts of the directed graph G.
460 A topological sort is a nonunique permutation of the nodes such that an
461 edge from u to v implies that u appears before v in the topological sort
462 order.
464 Parameters
465 ----------
466 G : NetworkX DiGraph
467 A directed graph
469 Yields
470 ------
471 topological_sort_order : list
472 a list of nodes in `G`, representing one of the topological sort orders
474 Raises
475 ------
476 NetworkXNotImplemented
477 If `G` is not directed
478 NetworkXUnfeasible
479 If `G` is not acyclic
481 Examples
482 --------
483 To enumerate all topological sorts of directed graph:
485 >>> DG = nx.DiGraph([(1, 2), (2, 3), (2, 4)])
486 >>> list(nx.all_topological_sorts(DG))
487 [[1, 2, 4, 3], [1, 2, 3, 4]]
489 Notes
490 -----
491 Implements an iterative version of the algorithm given in [1].
493 References
494 ----------
495 .. [1] Knuth, Donald E., Szwarcfiter, Jayme L. (1974).
496 "A Structured Program to Generate All Topological Sorting Arrangements"
497 Information Processing Letters, Volume 2, Issue 6, 1974, Pages 153-157,
498 ISSN 0020-0190,
499 https://doi.org/10.1016/0020-0190(74)90001-5.
500 Elsevier (North-Holland), Amsterdam
501 """
502 if not G.is_directed():
503 raise nx.NetworkXError("Topological sort not defined on undirected graphs.")
505 # the names of count and D are chosen to match the global variables in [1]
506 # number of edges originating in a vertex v
507 count = dict(G.in_degree())
508 # vertices with indegree 0
509 D = deque([v for v, d in G.in_degree() if d == 0])
510 # stack of first value chosen at a position k in the topological sort
511 bases = []
512 current_sort = []
514 # do-while construct
515 while True:
516 assert all(count[v] == 0 for v in D)
518 if len(current_sort) == len(G):
519 yield list(current_sort)
521 # clean-up stack
522 while len(current_sort) > 0:
523 assert len(bases) == len(current_sort)
524 q = current_sort.pop()
526 # "restores" all edges (q, x)
527 # NOTE: it is important to iterate over edges instead
528 # of successors, so count is updated correctly in multigraphs
529 for _, j in G.out_edges(q):
530 count[j] += 1
531 assert count[j] >= 0
532 # remove entries from D
533 while len(D) > 0 and count[D[-1]] > 0:
534 D.pop()
536 # corresponds to a circular shift of the values in D
537 # if the first value chosen (the base) is in the first
538 # position of D again, we are done and need to consider the
539 # previous condition
540 D.appendleft(q)
541 if D[-1] == bases[-1]:
542 # all possible values have been chosen at current position
543 # remove corresponding marker
544 bases.pop()
545 else:
546 # there are still elements that have not been fixed
547 # at the current position in the topological sort
548 # stop removing elements, escape inner loop
549 break
551 else:
552 if len(D) == 0:
553 raise nx.NetworkXUnfeasible("Graph contains a cycle.")
555 # choose next node
556 q = D.pop()
557 # "erase" all edges (q, x)
558 # NOTE: it is important to iterate over edges instead
559 # of successors, so count is updated correctly in multigraphs
560 for _, j in G.out_edges(q):
561 count[j] -= 1
562 assert count[j] >= 0
563 if count[j] == 0:
564 D.append(j)
565 current_sort.append(q)
567 # base for current position might _not_ be fixed yet
568 if len(bases) < len(current_sort):
569 bases.append(q)
571 if len(bases) == 0:
572 break
575@nx._dispatch
576def is_aperiodic(G):
577 """Returns True if `G` is aperiodic.
579 A directed graph is aperiodic if there is no integer k > 1 that
580 divides the length of every cycle in the graph.
582 Parameters
583 ----------
584 G : NetworkX DiGraph
585 A directed graph
587 Returns
588 -------
589 bool
590 True if the graph is aperiodic False otherwise
592 Raises
593 ------
594 NetworkXError
595 If `G` is not directed
597 Examples
598 --------
599 A graph consisting of one cycle, the length of which is 2. Therefore ``k = 2``
600 divides the length of every cycle in the graph and thus the graph
601 is *not aperiodic*::
603 >>> DG = nx.DiGraph([(1, 2), (2, 1)])
604 >>> nx.is_aperiodic(DG)
605 False
607 A graph consisting of two cycles: one of length 2 and the other of length 3.
608 The cycle lengths are coprime, so there is no single value of k where ``k > 1``
609 that divides each cycle length and therefore the graph is *aperiodic*::
611 >>> DG = nx.DiGraph([(1, 2), (2, 3), (3, 1), (1, 4), (4, 1)])
612 >>> nx.is_aperiodic(DG)
613 True
615 A graph consisting of two cycles: one of length 2 and the other of length 4.
616 The lengths of the cycles share a common factor ``k = 2``, and therefore
617 the graph is *not aperiodic*::
619 >>> DG = nx.DiGraph([(1, 2), (2, 1), (3, 4), (4, 5), (5, 6), (6, 3)])
620 >>> nx.is_aperiodic(DG)
621 False
623 An acyclic graph, therefore the graph is *not aperiodic*::
625 >>> DG = nx.DiGraph([(1, 2), (2, 3)])
626 >>> nx.is_aperiodic(DG)
627 False
629 Notes
630 -----
631 This uses the method outlined in [1]_, which runs in $O(m)$ time
632 given $m$ edges in `G`. Note that a graph is not aperiodic if it is
633 acyclic as every integer trivial divides length 0 cycles.
635 References
636 ----------
637 .. [1] Jarvis, J. P.; Shier, D. R. (1996),
638 "Graph-theoretic analysis of finite Markov chains,"
639 in Shier, D. R.; Wallenius, K. T., Applied Mathematical Modeling:
640 A Multidisciplinary Approach, CRC Press.
641 """
642 if not G.is_directed():
643 raise nx.NetworkXError("is_aperiodic not defined for undirected graphs")
645 s = arbitrary_element(G)
646 levels = {s: 0}
647 this_level = [s]
648 g = 0
649 lev = 1
650 while this_level:
651 next_level = []
652 for u in this_level:
653 for v in G[u]:
654 if v in levels: # Non-Tree Edge
655 g = gcd(g, levels[u] - levels[v] + 1)
656 else: # Tree Edge
657 next_level.append(v)
658 levels[v] = lev
659 this_level = next_level
660 lev += 1
661 if len(levels) == len(G): # All nodes in tree
662 return g == 1
663 else:
664 return g == 1 and nx.is_aperiodic(G.subgraph(set(G) - set(levels)))
667@nx._dispatch(preserve_all_attrs=True)
668def transitive_closure(G, reflexive=False):
669 """Returns transitive closure of a graph
671 The transitive closure of G = (V,E) is a graph G+ = (V,E+) such that
672 for all v, w in V there is an edge (v, w) in E+ if and only if there
673 is a path from v to w in G.
675 Handling of paths from v to v has some flexibility within this definition.
676 A reflexive transitive closure creates a self-loop for the path
677 from v to v of length 0. The usual transitive closure creates a
678 self-loop only if a cycle exists (a path from v to v with length > 0).
679 We also allow an option for no self-loops.
681 Parameters
682 ----------
683 G : NetworkX Graph
684 A directed/undirected graph/multigraph.
685 reflexive : Bool or None, optional (default: False)
686 Determines when cycles create self-loops in the Transitive Closure.
687 If True, trivial cycles (length 0) create self-loops. The result
688 is a reflexive transitive closure of G.
689 If False (the default) non-trivial cycles create self-loops.
690 If None, self-loops are not created.
692 Returns
693 -------
694 NetworkX graph
695 The transitive closure of `G`
697 Raises
698 ------
699 NetworkXError
700 If `reflexive` not in `{None, True, False}`
702 Examples
703 --------
704 The treatment of trivial (i.e. length 0) cycles is controlled by the
705 `reflexive` parameter.
707 Trivial (i.e. length 0) cycles do not create self-loops when
708 ``reflexive=False`` (the default)::
710 >>> DG = nx.DiGraph([(1, 2), (2, 3)])
711 >>> TC = nx.transitive_closure(DG, reflexive=False)
712 >>> TC.edges()
713 OutEdgeView([(1, 2), (1, 3), (2, 3)])
715 However, nontrivial (i.e. length greater than 0) cycles create self-loops
716 when ``reflexive=False`` (the default)::
718 >>> DG = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
719 >>> TC = nx.transitive_closure(DG, reflexive=False)
720 >>> TC.edges()
721 OutEdgeView([(1, 2), (1, 3), (1, 1), (2, 3), (2, 1), (2, 2), (3, 1), (3, 2), (3, 3)])
723 Trivial cycles (length 0) create self-loops when ``reflexive=True``::
725 >>> DG = nx.DiGraph([(1, 2), (2, 3)])
726 >>> TC = nx.transitive_closure(DG, reflexive=True)
727 >>> TC.edges()
728 OutEdgeView([(1, 2), (1, 1), (1, 3), (2, 3), (2, 2), (3, 3)])
730 And the third option is not to create self-loops at all when ``reflexive=None``::
732 >>> DG = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
733 >>> TC = nx.transitive_closure(DG, reflexive=None)
734 >>> TC.edges()
735 OutEdgeView([(1, 2), (1, 3), (2, 3), (2, 1), (3, 1), (3, 2)])
737 References
738 ----------
739 .. [1] https://www.ics.uci.edu/~eppstein/PADS/PartialOrder.py
740 """
741 TC = G.copy()
743 if reflexive not in {None, True, False}:
744 raise nx.NetworkXError("Incorrect value for the parameter `reflexive`")
746 for v in G:
747 if reflexive is None:
748 TC.add_edges_from((v, u) for u in nx.descendants(G, v) if u not in TC[v])
749 elif reflexive is True:
750 TC.add_edges_from(
751 (v, u) for u in nx.descendants(G, v) | {v} if u not in TC[v]
752 )
753 elif reflexive is False:
754 TC.add_edges_from((v, e[1]) for e in nx.edge_bfs(G, v) if e[1] not in TC[v])
756 return TC
759@not_implemented_for("undirected")
760@nx._dispatch(preserve_all_attrs=True)
761def transitive_closure_dag(G, topo_order=None):
762 """Returns the transitive closure of a directed acyclic graph.
764 This function is faster than the function `transitive_closure`, but fails
765 if the graph has a cycle.
767 The transitive closure of G = (V,E) is a graph G+ = (V,E+) such that
768 for all v, w in V there is an edge (v, w) in E+ if and only if there
769 is a non-null path from v to w in G.
771 Parameters
772 ----------
773 G : NetworkX DiGraph
774 A directed acyclic graph (DAG)
776 topo_order: list or tuple, optional
777 A topological order for G (if None, the function will compute one)
779 Returns
780 -------
781 NetworkX DiGraph
782 The transitive closure of `G`
784 Raises
785 ------
786 NetworkXNotImplemented
787 If `G` is not directed
788 NetworkXUnfeasible
789 If `G` has a cycle
791 Examples
792 --------
793 >>> DG = nx.DiGraph([(1, 2), (2, 3)])
794 >>> TC = nx.transitive_closure_dag(DG)
795 >>> TC.edges()
796 OutEdgeView([(1, 2), (1, 3), (2, 3)])
798 Notes
799 -----
800 This algorithm is probably simple enough to be well-known but I didn't find
801 a mention in the literature.
802 """
803 if topo_order is None:
804 topo_order = list(topological_sort(G))
806 TC = G.copy()
808 # idea: traverse vertices following a reverse topological order, connecting
809 # each vertex to its descendants at distance 2 as we go
810 for v in reversed(topo_order):
811 TC.add_edges_from((v, u) for u in nx.descendants_at_distance(TC, v, 2))
813 return TC
816@not_implemented_for("undirected")
817@nx._dispatch
818def transitive_reduction(G):
819 """Returns transitive reduction of a directed graph
821 The transitive reduction of G = (V,E) is a graph G- = (V,E-) such that
822 for all v,w in V there is an edge (v,w) in E- if and only if (v,w) is
823 in E and there is no path from v to w in G with length greater than 1.
825 Parameters
826 ----------
827 G : NetworkX DiGraph
828 A directed acyclic graph (DAG)
830 Returns
831 -------
832 NetworkX DiGraph
833 The transitive reduction of `G`
835 Raises
836 ------
837 NetworkXError
838 If `G` is not a directed acyclic graph (DAG) transitive reduction is
839 not uniquely defined and a :exc:`NetworkXError` exception is raised.
841 Examples
842 --------
843 To perform transitive reduction on a DiGraph:
845 >>> DG = nx.DiGraph([(1, 2), (2, 3), (1, 3)])
846 >>> TR = nx.transitive_reduction(DG)
847 >>> list(TR.edges)
848 [(1, 2), (2, 3)]
850 To avoid unnecessary data copies, this implementation does not return a
851 DiGraph with node/edge data.
852 To perform transitive reduction on a DiGraph and transfer node/edge data:
854 >>> DG = nx.DiGraph()
855 >>> DG.add_edges_from([(1, 2), (2, 3), (1, 3)], color='red')
856 >>> TR = nx.transitive_reduction(DG)
857 >>> TR.add_nodes_from(DG.nodes(data=True))
858 >>> TR.add_edges_from((u, v, DG.edges[u, v]) for u, v in TR.edges)
859 >>> list(TR.edges(data=True))
860 [(1, 2, {'color': 'red'}), (2, 3, {'color': 'red'})]
862 References
863 ----------
864 https://en.wikipedia.org/wiki/Transitive_reduction
866 """
867 if not is_directed_acyclic_graph(G):
868 msg = "Directed Acyclic Graph required for transitive_reduction"
869 raise nx.NetworkXError(msg)
870 TR = nx.DiGraph()
871 TR.add_nodes_from(G.nodes())
872 descendants = {}
873 # count before removing set stored in descendants
874 check_count = dict(G.in_degree)
875 for u in G:
876 u_nbrs = set(G[u])
877 for v in G[u]:
878 if v in u_nbrs:
879 if v not in descendants:
880 descendants[v] = {y for x, y in nx.dfs_edges(G, v)}
881 u_nbrs -= descendants[v]
882 check_count[v] -= 1
883 if check_count[v] == 0:
884 del descendants[v]
885 TR.add_edges_from((u, v) for v in u_nbrs)
886 return TR
889@not_implemented_for("undirected")
890@nx._dispatch
891def antichains(G, topo_order=None):
892 """Generates antichains from a directed acyclic graph (DAG).
894 An antichain is a subset of a partially ordered set such that any
895 two elements in the subset are incomparable.
897 Parameters
898 ----------
899 G : NetworkX DiGraph
900 A directed acyclic graph (DAG)
902 topo_order: list or tuple, optional
903 A topological order for G (if None, the function will compute one)
905 Yields
906 ------
907 antichain : list
908 a list of nodes in `G` representing an antichain
910 Raises
911 ------
912 NetworkXNotImplemented
913 If `G` is not directed
915 NetworkXUnfeasible
916 If `G` contains a cycle
918 Examples
919 --------
920 >>> DG = nx.DiGraph([(1, 2), (1, 3)])
921 >>> list(nx.antichains(DG))
922 [[], [3], [2], [2, 3], [1]]
924 Notes
925 -----
926 This function was originally developed by Peter Jipsen and Franco Saliola
927 for the SAGE project. It's included in NetworkX with permission from the
928 authors. Original SAGE code at:
930 https://github.com/sagemath/sage/blob/master/src/sage/combinat/posets/hasse_diagram.py
932 References
933 ----------
934 .. [1] Free Lattices, by R. Freese, J. Jezek and J. B. Nation,
935 AMS, Vol 42, 1995, p. 226.
936 """
937 if topo_order is None:
938 topo_order = list(nx.topological_sort(G))
940 TC = nx.transitive_closure_dag(G, topo_order)
941 antichains_stacks = [([], list(reversed(topo_order)))]
943 while antichains_stacks:
944 (antichain, stack) = antichains_stacks.pop()
945 # Invariant:
946 # - the elements of antichain are independent
947 # - the elements of stack are independent from those of antichain
948 yield antichain
949 while stack:
950 x = stack.pop()
951 new_antichain = antichain + [x]
952 new_stack = [t for t in stack if not ((t in TC[x]) or (x in TC[t]))]
953 antichains_stacks.append((new_antichain, new_stack))
956@not_implemented_for("undirected")
957@nx._dispatch(edge_attrs={"weight": "default_weight"})
958def dag_longest_path(G, weight="weight", default_weight=1, topo_order=None):
959 """Returns the longest path in a directed acyclic graph (DAG).
961 If `G` has edges with `weight` attribute the edge data are used as
962 weight values.
964 Parameters
965 ----------
966 G : NetworkX DiGraph
967 A directed acyclic graph (DAG)
969 weight : str, optional
970 Edge data key to use for weight
972 default_weight : int, optional
973 The weight of edges that do not have a weight attribute
975 topo_order: list or tuple, optional
976 A topological order for `G` (if None, the function will compute one)
978 Returns
979 -------
980 list
981 Longest path
983 Raises
984 ------
985 NetworkXNotImplemented
986 If `G` is not directed
988 Examples
989 --------
990 >>> DG = nx.DiGraph([(0, 1, {'cost':1}), (1, 2, {'cost':1}), (0, 2, {'cost':42})])
991 >>> list(nx.all_simple_paths(DG, 0, 2))
992 [[0, 1, 2], [0, 2]]
993 >>> nx.dag_longest_path(DG)
994 [0, 1, 2]
995 >>> nx.dag_longest_path(DG, weight="cost")
996 [0, 2]
998 In the case where multiple valid topological orderings exist, `topo_order`
999 can be used to specify a specific ordering:
1001 >>> DG = nx.DiGraph([(0, 1), (0, 2)])
1002 >>> sorted(nx.all_topological_sorts(DG)) # Valid topological orderings
1003 [[0, 1, 2], [0, 2, 1]]
1004 >>> nx.dag_longest_path(DG, topo_order=[0, 1, 2])
1005 [0, 1]
1006 >>> nx.dag_longest_path(DG, topo_order=[0, 2, 1])
1007 [0, 2]
1009 See also
1010 --------
1011 dag_longest_path_length
1013 """
1014 if not G:
1015 return []
1017 if topo_order is None:
1018 topo_order = nx.topological_sort(G)
1020 dist = {} # stores {v : (length, u)}
1021 for v in topo_order:
1022 us = [
1023 (
1024 dist[u][0]
1025 + (
1026 max(data.values(), key=lambda x: x.get(weight, default_weight))
1027 if G.is_multigraph()
1028 else data
1029 ).get(weight, default_weight),
1030 u,
1031 )
1032 for u, data in G.pred[v].items()
1033 ]
1035 # Use the best predecessor if there is one and its distance is
1036 # non-negative, otherwise terminate.
1037 maxu = max(us, key=lambda x: x[0]) if us else (0, v)
1038 dist[v] = maxu if maxu[0] >= 0 else (0, v)
1040 u = None
1041 v = max(dist, key=lambda x: dist[x][0])
1042 path = []
1043 while u != v:
1044 path.append(v)
1045 u = v
1046 v = dist[v][1]
1048 path.reverse()
1049 return path
1052@not_implemented_for("undirected")
1053@nx._dispatch(edge_attrs={"weight": "default_weight"})
1054def dag_longest_path_length(G, weight="weight", default_weight=1):
1055 """Returns the longest path length in a DAG
1057 Parameters
1058 ----------
1059 G : NetworkX DiGraph
1060 A directed acyclic graph (DAG)
1062 weight : string, optional
1063 Edge data key to use for weight
1065 default_weight : int, optional
1066 The weight of edges that do not have a weight attribute
1068 Returns
1069 -------
1070 int
1071 Longest path length
1073 Raises
1074 ------
1075 NetworkXNotImplemented
1076 If `G` is not directed
1078 Examples
1079 --------
1080 >>> DG = nx.DiGraph([(0, 1, {'cost':1}), (1, 2, {'cost':1}), (0, 2, {'cost':42})])
1081 >>> list(nx.all_simple_paths(DG, 0, 2))
1082 [[0, 1, 2], [0, 2]]
1083 >>> nx.dag_longest_path_length(DG)
1084 2
1085 >>> nx.dag_longest_path_length(DG, weight="cost")
1086 42
1088 See also
1089 --------
1090 dag_longest_path
1091 """
1092 path = nx.dag_longest_path(G, weight, default_weight)
1093 path_length = 0
1094 if G.is_multigraph():
1095 for u, v in pairwise(path):
1096 i = max(G[u][v], key=lambda x: G[u][v][x].get(weight, default_weight))
1097 path_length += G[u][v][i].get(weight, default_weight)
1098 else:
1099 for u, v in pairwise(path):
1100 path_length += G[u][v].get(weight, default_weight)
1102 return path_length
1105@nx._dispatch
1106def root_to_leaf_paths(G):
1107 """Yields root-to-leaf paths in a directed acyclic graph.
1109 `G` must be a directed acyclic graph. If not, the behavior of this
1110 function is undefined. A "root" in this graph is a node of in-degree
1111 zero and a "leaf" a node of out-degree zero.
1113 When invoked, this function iterates over each path from any root to
1114 any leaf. A path is a list of nodes.
1116 """
1117 roots = (v for v, d in G.in_degree() if d == 0)
1118 leaves = (v for v, d in G.out_degree() if d == 0)
1119 all_paths = partial(nx.all_simple_paths, G)
1120 # TODO In Python 3, this would be better as `yield from ...`.
1121 return chaini(starmap(all_paths, product(roots, leaves)))
1124@not_implemented_for("multigraph")
1125@not_implemented_for("undirected")
1126@nx._dispatch
1127def dag_to_branching(G):
1128 """Returns a branching representing all (overlapping) paths from
1129 root nodes to leaf nodes in the given directed acyclic graph.
1131 As described in :mod:`networkx.algorithms.tree.recognition`, a
1132 *branching* is a directed forest in which each node has at most one
1133 parent. In other words, a branching is a disjoint union of
1134 *arborescences*. For this function, each node of in-degree zero in
1135 `G` becomes a root of one of the arborescences, and there will be
1136 one leaf node for each distinct path from that root to a leaf node
1137 in `G`.
1139 Each node `v` in `G` with *k* parents becomes *k* distinct nodes in
1140 the returned branching, one for each parent, and the sub-DAG rooted
1141 at `v` is duplicated for each copy. The algorithm then recurses on
1142 the children of each copy of `v`.
1144 Parameters
1145 ----------
1146 G : NetworkX graph
1147 A directed acyclic graph.
1149 Returns
1150 -------
1151 DiGraph
1152 The branching in which there is a bijection between root-to-leaf
1153 paths in `G` (in which multiple paths may share the same leaf)
1154 and root-to-leaf paths in the branching (in which there is a
1155 unique path from a root to a leaf).
1157 Each node has an attribute 'source' whose value is the original
1158 node to which this node corresponds. No other graph, node, or
1159 edge attributes are copied into this new graph.
1161 Raises
1162 ------
1163 NetworkXNotImplemented
1164 If `G` is not directed, or if `G` is a multigraph.
1166 HasACycle
1167 If `G` is not acyclic.
1169 Examples
1170 --------
1171 To examine which nodes in the returned branching were produced by
1172 which original node in the directed acyclic graph, we can collect
1173 the mapping from source node to new nodes into a dictionary. For
1174 example, consider the directed diamond graph::
1176 >>> from collections import defaultdict
1177 >>> from operator import itemgetter
1178 >>>
1179 >>> G = nx.DiGraph(nx.utils.pairwise("abd"))
1180 >>> G.add_edges_from(nx.utils.pairwise("acd"))
1181 >>> B = nx.dag_to_branching(G)
1182 >>>
1183 >>> sources = defaultdict(set)
1184 >>> for v, source in B.nodes(data="source"):
1185 ... sources[source].add(v)
1186 >>> len(sources["a"])
1187 1
1188 >>> len(sources["d"])
1189 2
1191 To copy node attributes from the original graph to the new graph,
1192 you can use a dictionary like the one constructed in the above
1193 example::
1195 >>> for source, nodes in sources.items():
1196 ... for v in nodes:
1197 ... B.nodes[v].update(G.nodes[source])
1199 Notes
1200 -----
1201 This function is not idempotent in the sense that the node labels in
1202 the returned branching may be uniquely generated each time the
1203 function is invoked. In fact, the node labels may not be integers;
1204 in order to relabel the nodes to be more readable, you can use the
1205 :func:`networkx.convert_node_labels_to_integers` function.
1207 The current implementation of this function uses
1208 :func:`networkx.prefix_tree`, so it is subject to the limitations of
1209 that function.
1211 """
1212 if has_cycle(G):
1213 msg = "dag_to_branching is only defined for acyclic graphs"
1214 raise nx.HasACycle(msg)
1215 paths = root_to_leaf_paths(G)
1216 B = nx.prefix_tree(paths)
1217 # Remove the synthetic `root`(0) and `NIL`(-1) nodes from the tree
1218 B.remove_node(0)
1219 B.remove_node(-1)
1220 return B
1223@not_implemented_for("undirected")
1224@nx._dispatch
1225def compute_v_structures(G):
1226 """Iterate through the graph to compute all v-structures.
1228 V-structures are triples in the directed graph where
1229 two parent nodes point to the same child and the two parent nodes
1230 are not adjacent.
1232 Parameters
1233 ----------
1234 G : graph
1235 A networkx DiGraph.
1237 Returns
1238 -------
1239 vstructs : iterator of tuples
1240 The v structures within the graph. Each v structure is a 3-tuple with the
1241 parent, collider, and other parent.
1243 Examples
1244 --------
1245 >>> G = nx.DiGraph()
1246 >>> G.add_edges_from([(1, 2), (0, 5), (3, 1), (2, 4), (3, 1), (4, 5), (1, 5)])
1247 >>> sorted(nx.compute_v_structures(G))
1248 [(0, 5, 1), (0, 5, 4), (1, 5, 4)]
1250 Notes
1251 -----
1252 https://en.wikipedia.org/wiki/Collider_(statistics)
1253 """
1254 for collider, preds in G.pred.items():
1255 for common_parents in combinations(preds, r=2):
1256 # ensure that the colliders are the same
1257 common_parents = sorted(common_parents)
1258 yield (common_parents[0], collider, common_parents[1])