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