basic.py 8.0 KB

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