all.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. """Operations on many graphs.
  2. """
  3. from itertools import chain, repeat
  4. import networkx as nx
  5. __all__ = ["union_all", "compose_all", "disjoint_union_all", "intersection_all"]
  6. def union_all(graphs, rename=()):
  7. """Returns the union of all graphs.
  8. The graphs must be disjoint, otherwise an exception is raised.
  9. Parameters
  10. ----------
  11. graphs : iterable
  12. Iterable of NetworkX graphs
  13. rename : iterable , optional
  14. Node names of graphs can be changed by specifying the tuple
  15. rename=('G-','H-') (for example). Node "u" in G is then renamed
  16. "G-u" and "v" in H is renamed "H-v". Infinite generators (like itertools.count)
  17. are also supported.
  18. Returns
  19. -------
  20. U : a graph with the same type as the first graph in list
  21. Raises
  22. ------
  23. ValueError
  24. If `graphs` is an empty list.
  25. Notes
  26. -----
  27. To force a disjoint union with node relabeling, use
  28. disjoint_union_all(G,H) or convert_node_labels_to integers().
  29. Graph, edge, and node attributes are propagated to the union graph.
  30. If a graph attribute is present in multiple graphs, then the value
  31. from the last graph in the list with that attribute is used.
  32. See Also
  33. --------
  34. union
  35. disjoint_union_all
  36. """
  37. R = None
  38. seen_nodes = set()
  39. # rename graph to obtain disjoint node labels
  40. def add_prefix(graph, prefix):
  41. if prefix is None:
  42. return graph
  43. def label(x):
  44. return f"{prefix}{x}"
  45. return nx.relabel_nodes(graph, label)
  46. rename = chain(rename, repeat(None))
  47. graphs = (add_prefix(G, name) for G, name in zip(graphs, rename))
  48. for i, G in enumerate(graphs):
  49. G_nodes_set = set(G.nodes)
  50. if i == 0:
  51. # Union is the same type as first graph
  52. R = G.__class__()
  53. elif G.is_multigraph() != R.is_multigraph():
  54. raise nx.NetworkXError("All graphs must be graphs or multigraphs.")
  55. elif not seen_nodes.isdisjoint(G_nodes_set):
  56. raise nx.NetworkXError(
  57. "The node sets of the graphs are not disjoint.",
  58. "Use appropriate rename"
  59. "=(G1prefix,G2prefix,...,GNprefix)"
  60. "or use disjoint_union(G1,G2,...,GN).",
  61. )
  62. seen_nodes |= G_nodes_set
  63. R.graph.update(G.graph)
  64. R.add_nodes_from(G.nodes(data=True))
  65. R.add_edges_from(
  66. G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True)
  67. )
  68. if R is None:
  69. raise ValueError("cannot apply union_all to an empty list")
  70. return R
  71. def disjoint_union_all(graphs):
  72. """Returns the disjoint union of all graphs.
  73. This operation forces distinct integer node labels starting with 0
  74. for the first graph in the list and numbering consecutively.
  75. Parameters
  76. ----------
  77. graphs : iterable
  78. Iterable of NetworkX graphs
  79. Returns
  80. -------
  81. U : A graph with the same type as the first graph in list
  82. Raises
  83. ------
  84. ValueError
  85. If `graphs` is an empty list.
  86. Notes
  87. -----
  88. It is recommended that the graphs be either all directed or all undirected.
  89. Graph, edge, and node attributes are propagated to the union graph.
  90. If a graph attribute is present in multiple graphs, then the value
  91. from the last graph in the list with that attribute is used.
  92. """
  93. def yield_relabeled(graphs):
  94. first_label = 0
  95. for G in graphs:
  96. yield nx.convert_node_labels_to_integers(G, first_label=first_label)
  97. first_label += len(G)
  98. R = union_all(yield_relabeled(graphs))
  99. return R
  100. def compose_all(graphs):
  101. """Returns the composition of all graphs.
  102. Composition is the simple union of the node sets and edge sets.
  103. The node sets of the supplied graphs need not be disjoint.
  104. Parameters
  105. ----------
  106. graphs : iterable
  107. Iterable of NetworkX graphs
  108. Returns
  109. -------
  110. C : A graph with the same type as the first graph in list
  111. Raises
  112. ------
  113. ValueError
  114. If `graphs` is an empty list.
  115. Notes
  116. -----
  117. It is recommended that the supplied graphs be either all directed or all
  118. undirected.
  119. Graph, edge, and node attributes are propagated to the union graph.
  120. If a graph attribute is present in multiple graphs, then the value
  121. from the last graph in the list with that attribute is used.
  122. """
  123. R = None
  124. # add graph attributes, H attributes take precedent over G attributes
  125. for i, G in enumerate(graphs):
  126. if i == 0:
  127. # create new graph
  128. R = G.__class__()
  129. elif G.is_multigraph() != R.is_multigraph():
  130. raise nx.NetworkXError("All graphs must be graphs or multigraphs.")
  131. R.graph.update(G.graph)
  132. R.add_nodes_from(G.nodes(data=True))
  133. R.add_edges_from(
  134. G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True)
  135. )
  136. if R is None:
  137. raise ValueError("cannot apply compose_all to an empty list")
  138. return R
  139. def intersection_all(graphs):
  140. """Returns a new graph that contains only the nodes and the edges that exist in
  141. all graphs.
  142. Parameters
  143. ----------
  144. graphs : iterable
  145. Iterable of NetworkX graphs
  146. Returns
  147. -------
  148. R : A new graph with the same type as the first graph in list
  149. Raises
  150. ------
  151. ValueError
  152. If `graphs` is an empty list.
  153. Notes
  154. -----
  155. Attributes from the graph, nodes, and edges are not copied to the new
  156. graph.
  157. """
  158. R = None
  159. for i, G in enumerate(graphs):
  160. G_nodes_set = set(G.nodes)
  161. G_edges_set = set(G.edges(keys=True) if G.is_multigraph() else G.edges())
  162. if i == 0:
  163. # create new graph
  164. R = G.__class__()
  165. node_intersection = G_nodes_set
  166. edge_intersection = G_edges_set
  167. elif G.is_multigraph() != R.is_multigraph():
  168. raise nx.NetworkXError("All graphs must be graphs or multigraphs.")
  169. else:
  170. node_intersection &= G_nodes_set
  171. edge_intersection &= G_edges_set
  172. R.graph.update(G.graph)
  173. if R is None:
  174. raise ValueError("cannot apply intersection_all to an empty list")
  175. R.add_nodes_from(node_intersection)
  176. R.add_edges_from(edge_intersection)
  177. return R