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 >>> G = nx.path_graph(4)
44 >>> c = nx.bipartite.color(G)
45 >>> print(c)
46 {0: 1, 1: 0, 2: 1, 3: 0}
47
48 You can use this to set a node attribute indicating the bipartite set:
49
50 >>> nx.set_node_attributes(G, c, name="bipartite")
51 >>> G.nodes[0]["bipartite"]
52 1
53 >>> G.nodes[1]["bipartite"]
54 0
55 """
56 if G.is_directed():
57 import itertools
58
59 def neighbors(v):
60 return itertools.chain.from_iterable([G.predecessors(v), G.successors(v)])
61
62 else:
63 neighbors = G.neighbors
64
65 color = {}
66 for n in G: # handle disconnected graphs
67 if n in color or len(G[n]) == 0: # skip isolates
68 continue
69 queue = [n]
70 color[n] = 1 # nodes seen with color (1 or 0)
71 while queue:
72 v = queue.pop()
73 c = 1 - color[v] # opposite color of node v
74 for w in neighbors(v):
75 if w in color:
76 if color[w] == color[v]:
77 raise nx.NetworkXError("Graph is not bipartite.")
78 else:
79 color[w] = c
80 queue.append(w)
81 # color isolates with 0
82 color.update(dict.fromkeys(nx.isolates(G), 0))
83 return color
84
85
86@nx._dispatchable
87def is_bipartite(G):
88 """Returns True if graph G is bipartite, False if not.
89
90 Parameters
91 ----------
92 G : NetworkX graph
93
94 Examples
95 --------
96 >>> G = nx.path_graph(4)
97 >>> nx.is_bipartite(G)
98 True
99 >>> G = nx.cycle_graph(7)
100 >>> nx.is_bipartite(G)
101 False
102
103 See Also
104 --------
105 color, is_bipartite_node_set
106 """
107 try:
108 color(G)
109 return True
110 except nx.NetworkXError:
111 return False
112
113
114@nx._dispatchable
115def is_bipartite_node_set(G, nodes):
116 """Returns True if nodes and G/nodes are a bipartition of G.
117
118 Parameters
119 ----------
120 G : NetworkX graph
121
122 nodes: list or container
123 Check if nodes are a one of a bipartite set.
124
125 Examples
126 --------
127 >>> G = nx.path_graph(4)
128 >>> X = set([1, 3])
129 >>> nx.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 >>> G = nx.path_graph(4)
196 >>> X, Y = nx.bipartite.sets(G)
197 >>> list(X)
198 [0, 2]
199 >>> list(Y)
200 [1, 3]
201
202 See Also
203 --------
204 color
205
206 """
207 if G.is_directed():
208 is_connected = nx.is_weakly_connected
209 else:
210 is_connected = nx.is_connected
211 if top_nodes is not None:
212 X = set(top_nodes)
213 Y = set(G) - X
214 else:
215 if not is_connected(G):
216 msg = "Disconnected graph: Ambiguous solution for bipartite sets."
217 raise nx.AmbiguousSolution(msg)
218 c = color(G)
219 X = {n for n, is_top in c.items() if is_top}
220 Y = {n for n, is_top in c.items() if not is_top}
221 return (X, Y)
222
223
224@nx._dispatchable(graphs="B")
225def density(B, nodes):
226 """Returns density of bipartite graph B.
227
228 Parameters
229 ----------
230 B : NetworkX graph
231
232 nodes: list or container
233 Nodes in one node set of the bipartite graph.
234
235 Returns
236 -------
237 d : float
238 The bipartite density
239
240 Examples
241 --------
242 >>> G = nx.complete_bipartite_graph(3, 2)
243 >>> nx.bipartite.density(G, nodes={0, 1, 2})
244 1.0
245 >>> nx.bipartite.density(G, nodes={3, 4})
246 1.0
247
248 Notes
249 -----
250 The container of nodes passed as argument must contain all nodes
251 in one of the two bipartite node sets to avoid ambiguity in the
252 case of disconnected graphs.
253 See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
254 for further details on how bipartite graphs are handled in NetworkX.
255
256 See Also
257 --------
258 color
259 """
260 n = len(B)
261 m = nx.number_of_edges(B)
262 nb = len(nodes)
263 nt = n - nb
264 if m == 0: # includes cases n==0 and n==1
265 d = 0.0
266 else:
267 if B.is_directed():
268 d = m / (2 * nb * nt)
269 else:
270 d = m / (nb * nt)
271 return d
272
273
274@nx._dispatchable(graphs="B", edge_attrs="weight")
275def degrees(B, nodes, weight=None):
276 """Returns the degrees of the two node sets in the bipartite graph B.
277
278 Parameters
279 ----------
280 B : NetworkX graph
281
282 nodes: list or container
283 Nodes in one node set of the bipartite graph.
284
285 weight : string or None, optional (default=None)
286 The edge attribute that holds the numerical value used as a weight.
287 If None, then each edge has weight 1.
288 The degree is the sum of the edge weights adjacent to the node.
289
290 Returns
291 -------
292 (degX,degY) : tuple of dictionaries
293 The degrees of the two bipartite sets as dictionaries keyed by node.
294
295 Examples
296 --------
297 >>> G = nx.complete_bipartite_graph(3, 2)
298 >>> degX, degY = nx.bipartite.degrees(G, nodes={3, 4})
299 >>> dict(degX)
300 {0: 2, 1: 2, 2: 2}
301
302 Notes
303 -----
304 The container of nodes passed as argument must contain all nodes
305 in one of the two bipartite node sets to avoid ambiguity in the
306 case of disconnected graphs.
307 See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
308 for further details on how bipartite graphs are handled in NetworkX.
309
310 See Also
311 --------
312 color, density
313 """
314 bottom = set(nodes)
315 top = set(B) - bottom
316 return (B.degree(top, weight), B.degree(bottom, weight))