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 |
