graphviews.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. """View of Graphs as SubGraph, Reverse, Directed, Undirected.
  2. In some algorithms it is convenient to temporarily morph
  3. a graph to exclude some nodes or edges. It should be better
  4. to do that via a view than to remove and then re-add.
  5. In other algorithms it is convenient to temporarily morph
  6. a graph to reverse directed edges, or treat a directed graph
  7. as undirected, etc. This module provides those graph views.
  8. The resulting views are essentially read-only graphs that
  9. report data from the original graph object. We provide an
  10. attribute G._graph which points to the underlying graph object.
  11. Note: Since graphviews look like graphs, one can end up with
  12. view-of-view-of-view chains. Be careful with chains because
  13. they become very slow with about 15 nested views.
  14. For the common simple case of node induced subgraphs created
  15. from the graph class, we short-cut the chain by returning a
  16. subgraph of the original graph directly rather than a subgraph
  17. of a subgraph. We are careful not to disrupt any edge filter in
  18. the middle subgraph. In general, determining how to short-cut
  19. the chain is tricky and much harder with restricted_views than
  20. with induced subgraphs.
  21. Often it is easiest to use .copy() to avoid chains.
  22. """
  23. import networkx as nx
  24. from networkx.classes.coreviews import (
  25. FilterAdjacency,
  26. FilterAtlas,
  27. FilterMultiAdjacency,
  28. UnionAdjacency,
  29. UnionMultiAdjacency,
  30. )
  31. from networkx.classes.filters import no_filter
  32. from networkx.exception import NetworkXError
  33. from networkx.utils import not_implemented_for
  34. __all__ = ["generic_graph_view", "subgraph_view", "reverse_view"]
  35. def generic_graph_view(G, create_using=None):
  36. if create_using is None:
  37. newG = G.__class__()
  38. else:
  39. newG = nx.empty_graph(0, create_using)
  40. if G.is_multigraph() != newG.is_multigraph():
  41. raise NetworkXError("Multigraph for G must agree with create_using")
  42. newG = nx.freeze(newG)
  43. # create view by assigning attributes from G
  44. newG._graph = G
  45. newG.graph = G.graph
  46. newG._node = G._node
  47. if newG.is_directed():
  48. if G.is_directed():
  49. newG._succ = G._succ
  50. newG._pred = G._pred
  51. # newG._adj is synced with _succ
  52. else:
  53. newG._succ = G._adj
  54. newG._pred = G._adj
  55. # newG._adj is synced with _succ
  56. elif G.is_directed():
  57. if G.is_multigraph():
  58. newG._adj = UnionMultiAdjacency(G._succ, G._pred)
  59. else:
  60. newG._adj = UnionAdjacency(G._succ, G._pred)
  61. else:
  62. newG._adj = G._adj
  63. return newG
  64. def subgraph_view(G, filter_node=no_filter, filter_edge=no_filter):
  65. """View of `G` applying a filter on nodes and edges.
  66. `subgraph_view` provides a read-only view of the input graph that excludes
  67. nodes and edges based on the outcome of two filter functions `filter_node`
  68. and `filter_edge`.
  69. The `filter_node` function takes one argument --- the node --- and returns
  70. `True` if the node should be included in the subgraph, and `False` if it
  71. should not be included.
  72. The `filter_edge` function takes two (or three arguments if `G` is a
  73. multi-graph) --- the nodes describing an edge, plus the edge-key if
  74. parallel edges are possible --- and returns `True` if the edge should be
  75. included in the subgraph, and `False` if it should not be included.
  76. Both node and edge filter functions are called on graph elements as they
  77. are queried, meaning there is no up-front cost to creating the view.
  78. Parameters
  79. ----------
  80. G : networkx.Graph
  81. A directed/undirected graph/multigraph
  82. filter_node : callable, optional
  83. A function taking a node as input, which returns `True` if the node
  84. should appear in the view.
  85. filter_edge : callable, optional
  86. A function taking as input the two nodes describing an edge (plus the
  87. edge-key if `G` is a multi-graph), which returns `True` if the edge
  88. should appear in the view.
  89. Returns
  90. -------
  91. graph : networkx.Graph
  92. A read-only graph view of the input graph.
  93. Examples
  94. --------
  95. >>> G = nx.path_graph(6)
  96. Filter functions operate on the node, and return `True` if the node should
  97. appear in the view:
  98. >>> def filter_node(n1):
  99. ... return n1 != 5
  100. ...
  101. >>> view = nx.subgraph_view(G, filter_node=filter_node)
  102. >>> view.nodes()
  103. NodeView((0, 1, 2, 3, 4))
  104. We can use a closure pattern to filter graph elements based on additional
  105. data --- for example, filtering on edge data attached to the graph:
  106. >>> G[3][4]["cross_me"] = False
  107. >>> def filter_edge(n1, n2):
  108. ... return G[n1][n2].get("cross_me", True)
  109. ...
  110. >>> view = nx.subgraph_view(G, filter_edge=filter_edge)
  111. >>> view.edges()
  112. EdgeView([(0, 1), (1, 2), (2, 3), (4, 5)])
  113. >>> view = nx.subgraph_view(G, filter_node=filter_node, filter_edge=filter_edge,)
  114. >>> view.nodes()
  115. NodeView((0, 1, 2, 3, 4))
  116. >>> view.edges()
  117. EdgeView([(0, 1), (1, 2), (2, 3)])
  118. """
  119. newG = nx.freeze(G.__class__())
  120. newG._NODE_OK = filter_node
  121. newG._EDGE_OK = filter_edge
  122. # create view by assigning attributes from G
  123. newG._graph = G
  124. newG.graph = G.graph
  125. newG._node = FilterAtlas(G._node, filter_node)
  126. if G.is_multigraph():
  127. Adj = FilterMultiAdjacency
  128. def reverse_edge(u, v, k=None):
  129. return filter_edge(v, u, k)
  130. else:
  131. Adj = FilterAdjacency
  132. def reverse_edge(u, v, k=None):
  133. return filter_edge(v, u)
  134. if G.is_directed():
  135. newG._succ = Adj(G._succ, filter_node, filter_edge)
  136. newG._pred = Adj(G._pred, filter_node, reverse_edge)
  137. # newG._adj is synced with _succ
  138. else:
  139. newG._adj = Adj(G._adj, filter_node, filter_edge)
  140. return newG
  141. @not_implemented_for("undirected")
  142. def reverse_view(G):
  143. """View of `G` with edge directions reversed
  144. `reverse_view` returns a read-only view of the input graph where
  145. edge directions are reversed.
  146. Identical to digraph.reverse(copy=False)
  147. Parameters
  148. ----------
  149. G : networkx.DiGraph
  150. Returns
  151. -------
  152. graph : networkx.DiGraph
  153. Examples
  154. --------
  155. >>> G = nx.DiGraph()
  156. >>> G.add_edge(1, 2)
  157. >>> G.add_edge(2, 3)
  158. >>> G.edges()
  159. OutEdgeView([(1, 2), (2, 3)])
  160. >>> view = nx.reverse_view(G)
  161. >>> view.edges()
  162. OutEdgeView([(2, 1), (3, 2)])
  163. """
  164. newG = generic_graph_view(G)
  165. newG._succ, newG._pred = G._pred, G._succ
  166. # newG._adj is synced with _succ
  167. return newG