1"""Strongly connected components."""
2
3import networkx as nx
4from networkx.utils.decorators import not_implemented_for
5
6__all__ = [
7 "number_strongly_connected_components",
8 "strongly_connected_components",
9 "is_strongly_connected",
10 "kosaraju_strongly_connected_components",
11 "condensation",
12]
13
14
15@not_implemented_for("undirected")
16@nx._dispatchable
17def strongly_connected_components(G):
18 """Generate nodes in strongly connected components of graph.
19
20 Parameters
21 ----------
22 G : NetworkX Graph
23 A directed graph.
24
25 Returns
26 -------
27 comp : generator of sets
28 A generator of sets of nodes, one for each strongly connected
29 component of G.
30
31 Raises
32 ------
33 NetworkXNotImplemented
34 If G is undirected.
35
36 Examples
37 --------
38 Generate a sorted list of strongly connected components, largest first.
39
40 >>> G = nx.cycle_graph(4, create_using=nx.DiGraph())
41 >>> nx.add_cycle(G, [10, 11, 12])
42 >>> [
43 ... len(c)
44 ... for c in sorted(nx.strongly_connected_components(G), key=len, reverse=True)
45 ... ]
46 [4, 3]
47
48 If you only want the largest component, it's more efficient to
49 use max instead of sort.
50
51 >>> largest = max(nx.strongly_connected_components(G), key=len)
52
53 See Also
54 --------
55 connected_components
56 weakly_connected_components
57 kosaraju_strongly_connected_components
58
59 Notes
60 -----
61 Uses Tarjan's algorithm[1]_ with Nuutila's modifications[2]_.
62 Nonrecursive version of algorithm.
63
64 References
65 ----------
66 .. [1] Depth-first search and linear graph algorithms, R. Tarjan
67 SIAM Journal of Computing 1(2):146-160, (1972).
68
69 .. [2] On finding the strongly connected components in a directed graph.
70 E. Nuutila and E. Soisalon-Soinen
71 Information Processing Letters 49(1): 9-14, (1994)..
72
73 """
74 preorder = {}
75 lowlink = {}
76 scc_found = set()
77 scc_queue = []
78 i = 0 # Preorder counter
79 neighbors = {v: iter(G._adj[v]) for v in G}
80 for source in G:
81 if source not in scc_found:
82 queue = [source]
83 while queue:
84 v = queue[-1]
85 if v not in preorder:
86 i = i + 1
87 preorder[v] = i
88 done = True
89 for w in neighbors[v]:
90 if w not in preorder:
91 queue.append(w)
92 done = False
93 break
94 if done:
95 lowlink[v] = preorder[v]
96 for w in G._adj[v]:
97 if w not in scc_found:
98 if preorder[w] > preorder[v]:
99 lowlink[v] = min([lowlink[v], lowlink[w]])
100 else:
101 lowlink[v] = min([lowlink[v], preorder[w]])
102 queue.pop()
103 if lowlink[v] == preorder[v]:
104 scc = {v}
105 while scc_queue and preorder[scc_queue[-1]] > preorder[v]:
106 k = scc_queue.pop()
107 scc.add(k)
108 scc_found.update(scc)
109 yield scc
110 else:
111 scc_queue.append(v)
112
113
114@not_implemented_for("undirected")
115@nx._dispatchable
116def kosaraju_strongly_connected_components(G, source=None):
117 """Generate nodes in strongly connected components of graph.
118
119 Parameters
120 ----------
121 G : NetworkX Graph
122 A directed graph.
123
124 source : node, optional (default=None)
125 Specify a node from which to start the depth-first search.
126 If not provided, the algorithm will start from an arbitrary node.
127
128 Yields
129 ------
130 set
131 A set of all nodes in a strongly connected component of `G`.
132
133 Raises
134 ------
135 NetworkXNotImplemented
136 If `G` is undirected.
137
138 NetworkXError
139 If `source` is not a node in `G`.
140
141 Examples
142 --------
143 Generate a list of strongly connected components of a graph:
144
145 >>> G = nx.cycle_graph(4, create_using=nx.DiGraph())
146 >>> nx.add_cycle(G, [10, 11, 12])
147 >>> sorted(nx.kosaraju_strongly_connected_components(G), key=len, reverse=True)
148 [{0, 1, 2, 3}, {10, 11, 12}]
149
150 If you only want the largest component, it's more efficient to
151 use `max()` instead of `sorted()`.
152
153 >>> max(nx.kosaraju_strongly_connected_components(G), key=len)
154 {0, 1, 2, 3}
155
156 See Also
157 --------
158 strongly_connected_components
159
160 Notes
161 -----
162 Uses Kosaraju's algorithm.
163 """
164 post = list(nx.dfs_postorder_nodes(G.reverse(copy=False), source=source))
165 n = len(post)
166 seen = set()
167 while post and len(seen) < n:
168 r = post.pop()
169 if r in seen:
170 continue
171 new = {r}
172 seen.add(r)
173 stack = [r]
174 while stack and len(seen) < n:
175 v = stack.pop()
176 for w in G._adj[v]:
177 if w not in seen:
178 new.add(w)
179 seen.add(w)
180 stack.append(w)
181 yield new
182
183
184@not_implemented_for("undirected")
185@nx._dispatchable
186def number_strongly_connected_components(G):
187 """Returns number of strongly connected components in graph.
188
189 Parameters
190 ----------
191 G : NetworkX graph
192 A directed graph.
193
194 Returns
195 -------
196 n : integer
197 Number of strongly connected components
198
199 Raises
200 ------
201 NetworkXNotImplemented
202 If G is undirected.
203
204 Examples
205 --------
206 >>> G = nx.DiGraph(
207 ... [(0, 1), (1, 2), (2, 0), (2, 3), (4, 5), (3, 4), (5, 6), (6, 3), (6, 7)]
208 ... )
209 >>> nx.number_strongly_connected_components(G)
210 3
211
212 See Also
213 --------
214 strongly_connected_components
215 number_connected_components
216 number_weakly_connected_components
217
218 Notes
219 -----
220 For directed graphs only.
221 """
222 return sum(1 for scc in strongly_connected_components(G))
223
224
225@not_implemented_for("undirected")
226@nx._dispatchable
227def is_strongly_connected(G):
228 """Test directed graph for strong connectivity.
229
230 A directed graph is strongly connected if and only if every vertex in
231 the graph is reachable from every other vertex.
232
233 Parameters
234 ----------
235 G : NetworkX Graph
236 A directed graph.
237
238 Returns
239 -------
240 connected : bool
241 True if the graph is strongly connected, False otherwise.
242
243 Examples
244 --------
245 >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 0), (2, 4), (4, 2)])
246 >>> nx.is_strongly_connected(G)
247 True
248 >>> G.remove_edge(2, 3)
249 >>> nx.is_strongly_connected(G)
250 False
251
252 Raises
253 ------
254 NetworkXNotImplemented
255 If G is undirected.
256
257 See Also
258 --------
259 is_weakly_connected
260 is_semiconnected
261 is_connected
262 is_biconnected
263 strongly_connected_components
264
265 Notes
266 -----
267 For directed graphs only.
268 """
269 if len(G) == 0:
270 raise nx.NetworkXPointlessConcept(
271 """Connectivity is undefined for the null graph."""
272 )
273
274 return len(next(strongly_connected_components(G))) == len(G)
275
276
277@not_implemented_for("undirected")
278@nx._dispatchable(returns_graph=True)
279def condensation(G, scc=None):
280 """Returns the condensation of G.
281
282 The condensation of G is the graph with each of the strongly connected
283 components contracted into a single node.
284
285 Parameters
286 ----------
287 G : NetworkX DiGraph
288 A directed graph.
289
290 scc: list or generator (optional, default=None)
291 Strongly connected components. If provided, the elements in
292 `scc` must partition the nodes in `G`. If not provided, it will be
293 calculated as scc=nx.strongly_connected_components(G).
294
295 Returns
296 -------
297 C : NetworkX DiGraph
298 The condensation graph C of G. The node labels are integers
299 corresponding to the index of the component in the list of
300 strongly connected components of G. C has a graph attribute named
301 'mapping' with a dictionary mapping the original nodes to the
302 nodes in C to which they belong. Each node in C also has a node
303 attribute 'members' with the set of original nodes in G that
304 form the SCC that the node in C represents.
305
306 Raises
307 ------
308 NetworkXNotImplemented
309 If G is undirected.
310
311 Examples
312 --------
313 Contracting two sets of strongly connected nodes into two distinct SCC
314 using the barbell graph.
315
316 >>> G = nx.barbell_graph(4, 0)
317 >>> G.remove_edge(3, 4)
318 >>> G = nx.DiGraph(G)
319 >>> H = nx.condensation(G)
320 >>> H.nodes.data()
321 NodeDataView({0: {'members': {0, 1, 2, 3}}, 1: {'members': {4, 5, 6, 7}}})
322 >>> H.graph["mapping"]
323 {0: 0, 1: 0, 2: 0, 3: 0, 4: 1, 5: 1, 6: 1, 7: 1}
324
325 Contracting a complete graph into one single SCC.
326
327 >>> G = nx.complete_graph(7, create_using=nx.DiGraph)
328 >>> H = nx.condensation(G)
329 >>> H.nodes
330 NodeView((0,))
331 >>> H.nodes.data()
332 NodeDataView({0: {'members': {0, 1, 2, 3, 4, 5, 6}}})
333
334 Notes
335 -----
336 After contracting all strongly connected components to a single node,
337 the resulting graph is a directed acyclic graph.
338
339 """
340 if scc is None:
341 scc = nx.strongly_connected_components(G)
342 mapping = {}
343 members = {}
344 C = nx.DiGraph()
345 # Add mapping dict as graph attribute
346 C.graph["mapping"] = mapping
347 if len(G) == 0:
348 return C
349 for i, component in enumerate(scc):
350 members[i] = component
351 mapping.update((n, i) for n in component)
352 number_of_components = i + 1
353 C.add_nodes_from(range(number_of_components))
354 C.add_edges_from(
355 (mapping[u], mapping[v]) for u, v in G.edges() if mapping[u] != mapping[v]
356 )
357 # Add a list of members (ie original nodes) to each node (ie scc) in C.
358 nx.set_node_attributes(C, members, "members")
359 return C