basic.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. """
  2. ==========================
  3. Bipartite Graph Algorithms
  4. ==========================
  5. """
  6. import networkx as nx
  7. from networkx.algorithms.components import connected_components
  8. from networkx.exception import AmbiguousSolution
  9. __all__ = [
  10. "is_bipartite",
  11. "is_bipartite_node_set",
  12. "color",
  13. "sets",
  14. "density",
  15. "degrees",
  16. ]
  17. @nx._dispatchable
  18. def color(G):
  19. """Returns a two-coloring of the graph.
  20. Raises an exception if the graph is not bipartite.
  21. Parameters
  22. ----------
  23. G : NetworkX graph
  24. Returns
  25. -------
  26. color : dictionary
  27. A dictionary keyed by node with a 1 or 0 as data for each node color.
  28. Raises
  29. ------
  30. NetworkXError
  31. If the graph is not two-colorable.
  32. Examples
  33. --------
  34. >>> from networkx.algorithms import bipartite
  35. >>> G = nx.path_graph(4)
  36. >>> c = bipartite.color(G)
  37. >>> print(c)
  38. {0: 1, 1: 0, 2: 1, 3: 0}
  39. You can use this to set a node attribute indicating the bipartite set:
  40. >>> nx.set_node_attributes(G, c, "bipartite")
  41. >>> print(G.nodes[0]["bipartite"])
  42. 1
  43. >>> print(G.nodes[1]["bipartite"])
  44. 0
  45. """
  46. if G.is_directed():
  47. import itertools
  48. def neighbors(v):
  49. return itertools.chain.from_iterable([G.predecessors(v), G.successors(v)])
  50. else:
  51. neighbors = G.neighbors
  52. color = {}
  53. for n in G: # handle disconnected graphs
  54. if n in color or len(G[n]) == 0: # skip isolates
  55. continue
  56. queue = [n]
  57. color[n] = 1 # nodes seen with color (1 or 0)
  58. while queue:
  59. v = queue.pop()
  60. c = 1 - color[v] # opposite color of node v
  61. for w in neighbors(v):
  62. if w in color:
  63. if color[w] == color[v]:
  64. raise nx.NetworkXError("Graph is not bipartite.")
  65. else:
  66. color[w] = c
  67. queue.append(w)
  68. # color isolates with 0
  69. color.update(dict.fromkeys(nx.isolates(G), 0))
  70. return color
  71. @nx._dispatchable
  72. def is_bipartite(G):
  73. """Returns True if graph G is bipartite, False if not.
  74. Parameters
  75. ----------
  76. G : NetworkX graph
  77. Examples
  78. --------
  79. >>> from networkx.algorithms import bipartite
  80. >>> G = nx.path_graph(4)
  81. >>> print(bipartite.is_bipartite(G))
  82. True
  83. See Also
  84. --------
  85. color, is_bipartite_node_set
  86. """
  87. try:
  88. color(G)
  89. return True
  90. except nx.NetworkXError:
  91. return False
  92. @nx._dispatchable
  93. def is_bipartite_node_set(G, nodes):
  94. """Returns True if nodes and G/nodes are a bipartition of G.
  95. Parameters
  96. ----------
  97. G : NetworkX graph
  98. nodes: list or container
  99. Check if nodes are a one of a bipartite set.
  100. Examples
  101. --------
  102. >>> from networkx.algorithms import bipartite
  103. >>> G = nx.path_graph(4)
  104. >>> X = set([1, 3])
  105. >>> bipartite.is_bipartite_node_set(G, X)
  106. True
  107. Notes
  108. -----
  109. An exception is raised if the input nodes are not distinct, because in this
  110. case some bipartite algorithms will yield incorrect results.
  111. For connected graphs the bipartite sets are unique. This function handles
  112. disconnected graphs.
  113. """
  114. S = set(nodes)
  115. if len(S) < len(nodes):
  116. # this should maybe just return False?
  117. raise AmbiguousSolution(
  118. "The input node set contains duplicates.\n"
  119. "This may lead to incorrect results when using it in bipartite algorithms.\n"
  120. "Consider using set(nodes) as the input"
  121. )
  122. for CC in (G.subgraph(c).copy() for c in connected_components(G)):
  123. X, Y = sets(CC)
  124. if not (
  125. (X.issubset(S) and Y.isdisjoint(S)) or (Y.issubset(S) and X.isdisjoint(S))
  126. ):
  127. return False
  128. return True
  129. @nx._dispatchable
  130. def sets(G, top_nodes=None):
  131. """Returns bipartite node sets of graph G.
  132. Raises an exception if the graph is not bipartite or if the input
  133. graph is disconnected and thus more than one valid solution exists.
  134. See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
  135. for further details on how bipartite graphs are handled in NetworkX.
  136. Parameters
  137. ----------
  138. G : NetworkX graph
  139. top_nodes : container, optional
  140. Container with all nodes in one bipartite node set. If not supplied
  141. it will be computed. But if more than one solution exists an exception
  142. will be raised.
  143. Returns
  144. -------
  145. X : set
  146. Nodes from one side of the bipartite graph.
  147. Y : set
  148. Nodes from the other side.
  149. Raises
  150. ------
  151. AmbiguousSolution
  152. Raised if the input bipartite graph is disconnected and no container
  153. with all nodes in one bipartite set is provided. When determining
  154. the nodes in each bipartite set more than one valid solution is
  155. possible if the input graph is disconnected.
  156. NetworkXError
  157. Raised if the input graph is not bipartite.
  158. Examples
  159. --------
  160. >>> from networkx.algorithms import bipartite
  161. >>> G = nx.path_graph(4)
  162. >>> X, Y = bipartite.sets(G)
  163. >>> list(X)
  164. [0, 2]
  165. >>> list(Y)
  166. [1, 3]
  167. See Also
  168. --------
  169. color
  170. """
  171. if G.is_directed():
  172. is_connected = nx.is_weakly_connected
  173. else:
  174. is_connected = nx.is_connected
  175. if top_nodes is not None:
  176. X = set(top_nodes)
  177. Y = set(G) - X
  178. else:
  179. if not is_connected(G):
  180. msg = "Disconnected graph: Ambiguous solution for bipartite sets."
  181. raise nx.AmbiguousSolution(msg)
  182. c = color(G)
  183. X = {n for n, is_top in c.items() if is_top}
  184. Y = {n for n, is_top in c.items() if not is_top}
  185. return (X, Y)
  186. @nx._dispatchable(graphs="B")
  187. def density(B, nodes):
  188. """Returns density of bipartite graph B.
  189. Parameters
  190. ----------
  191. B : NetworkX graph
  192. nodes: list or container
  193. Nodes in one node set of the bipartite graph.
  194. Returns
  195. -------
  196. d : float
  197. The bipartite density
  198. Examples
  199. --------
  200. >>> from networkx.algorithms import bipartite
  201. >>> G = nx.complete_bipartite_graph(3, 2)
  202. >>> X = set([0, 1, 2])
  203. >>> bipartite.density(G, X)
  204. 1.0
  205. >>> Y = set([3, 4])
  206. >>> bipartite.density(G, Y)
  207. 1.0
  208. Notes
  209. -----
  210. The container of nodes passed as argument must contain all nodes
  211. in one of the two bipartite node sets to avoid ambiguity in the
  212. case of disconnected graphs.
  213. See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
  214. for further details on how bipartite graphs are handled in NetworkX.
  215. See Also
  216. --------
  217. color
  218. """
  219. n = len(B)
  220. m = nx.number_of_edges(B)
  221. nb = len(nodes)
  222. nt = n - nb
  223. if m == 0: # includes cases n==0 and n==1
  224. d = 0.0
  225. else:
  226. if B.is_directed():
  227. d = m / (2 * nb * nt)
  228. else:
  229. d = m / (nb * nt)
  230. return d
  231. @nx._dispatchable(graphs="B", edge_attrs="weight")
  232. def degrees(B, nodes, weight=None):
  233. """Returns the degrees of the two node sets in the bipartite graph B.
  234. Parameters
  235. ----------
  236. B : NetworkX graph
  237. nodes: list or container
  238. Nodes in one node set of the bipartite graph.
  239. weight : string or None, optional (default=None)
  240. The edge attribute that holds the numerical value used as a weight.
  241. If None, then each edge has weight 1.
  242. The degree is the sum of the edge weights adjacent to the node.
  243. Returns
  244. -------
  245. (degX,degY) : tuple of dictionaries
  246. The degrees of the two bipartite sets as dictionaries keyed by node.
  247. Examples
  248. --------
  249. >>> from networkx.algorithms import bipartite
  250. >>> G = nx.complete_bipartite_graph(3, 2)
  251. >>> Y = set([3, 4])
  252. >>> degX, degY = bipartite.degrees(G, Y)
  253. >>> dict(degX)
  254. {0: 2, 1: 2, 2: 2}
  255. Notes
  256. -----
  257. The container of nodes passed as argument must contain all nodes
  258. in one of the two bipartite node sets to avoid ambiguity in the
  259. case of disconnected graphs.
  260. See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
  261. for further details on how bipartite graphs are handled in NetworkX.
  262. See Also
  263. --------
  264. color, density
  265. """
  266. bottom = set(nodes)
  267. top = set(B) - bottom
  268. return (B.degree(top, weight), B.degree(bottom, weight))