Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 748 lines (627 sloc) 27.694 kb
5936f7e @jakevdp add breadth-first search
jakevdp authored
1 """
2 Routines for traversing graphs in compressed sparse format
3 """
4
5 # Author: Jake Vanderplas -- <vanderplas@astro.washington.edu>
6 # License: BSD, (C) 2012
7
8 import numpy as np
9 cimport numpy as np
10
11 from scipy.sparse import csr_matrix, isspmatrix, isspmatrix_csr, isspmatrix_csc
0a7946b @rgommers BUG: use absolute imports in sparse.csgraph Cython files.
rgommers authored
12 from scipy.sparse.csgraph._validation import validate_graph
13 from scipy.sparse.csgraph._tools import reconstruct_path
5936f7e @jakevdp add breadth-first search
jakevdp authored
14
15 cimport cython
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
16 from libc cimport stdlib
5936f7e @jakevdp add breadth-first search
jakevdp authored
17
e29642e @jakevdp move parameters to a common file
jakevdp authored
18 include 'parameters.pxi'
5936f7e @jakevdp add breadth-first search
jakevdp authored
19
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
20 def connected_components(csgraph, directed=True, connection='weak',
21 return_labels=True):
540716a @pv DOC: add function signatures to all Cython-based routine docstrings (…
pv authored
22 """
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
23 connected_components(csgraph, directed=True, connection='weak',
24 return_labels=True)
540716a @pv DOC: add function signatures to all Cython-based routine docstrings (…
pv authored
25
26 Analyze the connected components of a sparse graph
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
27
276b9f5 @pv DOC: add some missing versionadded:: statements
pv authored
28 .. versionadded:: 0.11.0
29
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
30 Parameters
31 ----------
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
32 csgraph : array_like or sparse matrix
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
33 The N x N matrix representing the compressed sparse graph. The input
34 csgraph will be converted to csr format for the calculation.
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
35 directed : bool, optional
36 If True (default), then operate on a directed graph: only
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
37 move from point i to point j along paths csgraph[i, j].
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
38 If False, then find the shortest path on an undirected graph: the
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
39 algorithm can progress from point i to j along csgraph[i, j] or
40 csgraph[j, i].
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
41 connection : str, optional
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
42 ['weak'|'strong']. For directed graphs, the type of connection to
43 use. Nodes i and j are strongly connected if a path exists both
44 from i to j and from j to i. Nodes i and j are weakly connected if
45 only one of these paths exists. If directed == False, this keyword
46 is not referenced.
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
47 return_labels : str, optional
48 If True (default), then return the labels for each of the connected
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
49 components.
50
51 Returns
52 -------
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
53 n_components: int
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
54 The number of connected components.
55 labels: ndarray
56 The length-N array of labels of the connected components.
e2c502f @timleslie DOC: sparse: add link to Strongly Connected Components algorithm deta…
timleslie authored
57
58 References
59 ----------
60 .. [1] D. J. Pearce, "An Improved Algorithm for Finding the Strongly
61 Connected Components of a Directed Graph", Technical Report, 2005
62
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
63 """
64 if connection.lower() not in ['weak', 'strong']:
65 raise ValueError("connection must be 'weak' or 'strong'")
66
67 # weak connections <=> components of undirected graph
68 if connection.lower() == 'weak':
69 directed = False
70
71 csgraph = validate_graph(csgraph, directed,
72 dense_output=False)
73
74 labels = np.empty(csgraph.shape[0], dtype=ITYPE)
75 labels.fill(NULL_IDX)
76
77 if directed:
78 n_components = _connected_components_directed(csgraph.indices,
79 csgraph.indptr,
80 labels)
81 else:
82 csgraph_T = csgraph.T.tocsr()
83 n_components = _connected_components_undirected(csgraph.indices,
84 csgraph.indptr,
85 csgraph_T.indices,
86 csgraph_T.indptr,
87 labels)
88
89 if return_labels:
90 return n_components, labels
91 else:
92 return n_components
93
94
c55bfbb @jakevdp move csgraph code to private submodules
jakevdp authored
95 def breadth_first_tree(csgraph, i_start, directed=True):
540716a @pv DOC: add function signatures to all Cython-based routine docstrings (…
pv authored
96 r"""
97 breadth_first_tree(csgraph, i_start, directed=True)
98
99 Return the tree generated by a breadth-first search
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
100
101 Note that a breadth-first tree from a specified node is unique.
102
276b9f5 @pv DOC: add some missing versionadded:: statements
pv authored
103 .. versionadded:: 0.11.0
104
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
105 Parameters
106 ----------
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
107 csgraph : array_like or sparse matrix
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
108 The N x N matrix representing the compressed sparse graph. The input
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
109 csgraph will be converted to csr format for the calculation.
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
110 i_start : int
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
111 The index of starting node.
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
112 directed : bool, optional
113 If True (default), then operate on a directed graph: only
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
114 move from point i to point j along paths csgraph[i, j].
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
115 If False, then find the shortest path on an undirected graph: the
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
116 algorithm can progress from point i to j along csgraph[i, j] or
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
117 csgraph[j, i].
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
118
119 Returns
120 -------
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
121 cstree : csr matrix
122 The N x N directed compressed-sparse representation of the breadth-
123 first tree drawn from csgraph, starting at the specified node.
e2ac520 @jakevdp cleanup, tests, doc
jakevdp authored
124
125 Examples
126 --------
127 The following example shows the computation of a depth-first tree
128 over a simple four-component graph, starting at node 0::
129
130 input graph breadth first tree from (0)
131
132 (0) (0)
133 / \ / \
134 3 8 3 8
135 / \ / \
136 (3)---5---(1) (3) (1)
137 \ / /
138 6 2 2
139 \ / /
140 (2) (2)
141
37fb4e0 @jakevdp move Notes before Examples in doc
jakevdp authored
142 In compressed sparse representation, the solution looks like this:
e2ac520 @jakevdp cleanup, tests, doc
jakevdp authored
143
144 >>> from scipy.sparse import csr_matrix
c55bfbb @jakevdp move csgraph code to private submodules
jakevdp authored
145 >>> from scipy.sparse.csgraph import breadth_first_tree
e2ac520 @jakevdp cleanup, tests, doc
jakevdp authored
146 >>> X = csr_matrix([[0, 8, 0, 3],
147 ... [0, 0, 2, 5],
148 ... [0, 0, 0, 6],
149 ... [0, 0, 0, 0]])
c55bfbb @jakevdp move csgraph code to private submodules
jakevdp authored
150 >>> Tcsr = breadth_first_tree(X, 0, directed=False)
e2ac520 @jakevdp cleanup, tests, doc
jakevdp authored
151 >>> Tcsr.toarray().astype(int)
152 array([[0, 8, 0, 3],
153 [0, 0, 2, 0],
154 [0, 0, 0, 0],
155 [0, 0, 0, 0]])
156
157 Note that the resulting graph is a Directed Acyclic Graph which spans
158 the graph. A breadth-first tree from a given node is unique.
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
159 """
c55bfbb @jakevdp move csgraph code to private submodules
jakevdp authored
160 node_list, predecessors = breadth_first_order(csgraph, i_start,
161 directed, True)
e2ac520 @jakevdp cleanup, tests, doc
jakevdp authored
162 return reconstruct_path(csgraph, predecessors, directed)
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
163
164
c55bfbb @jakevdp move csgraph code to private submodules
jakevdp authored
165 def depth_first_tree(csgraph, i_start, directed=True):
540716a @pv DOC: add function signatures to all Cython-based routine docstrings (…
pv authored
166 r"""
167 depth_first_tree(csgraph, i_start, directed=True)
168
169 Return a tree generated by a depth-first search.
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
170
171 Note that a tree generated by a depth-first search is not unique:
172 it depends on the order that the children of each node are searched.
173
276b9f5 @pv DOC: add some missing versionadded:: statements
pv authored
174 .. versionadded:: 0.11.0
175
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
176 Parameters
177 ----------
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
178 csgraph : array_like or sparse matrix
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
179 The N x N matrix representing the compressed sparse graph. The input
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
180 csgraph will be converted to csr format for the calculation.
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
181 i_start : int
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
182 The index of starting node.
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
183 directed : bool, optional
184 If True (default), then operate on a directed graph: only
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
185 move from point i to point j along paths csgraph[i, j].
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
186 If False, then find the shortest path on an undirected graph: the
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
187 algorithm can progress from point i to j along csgraph[i, j] or
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
188 csgraph[j, i].
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
189
190 Returns
191 -------
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
192 cstree : csr matrix
193 The N x N directed compressed-sparse representation of the depth-
194 first tree drawn from csgraph, starting at the specified node.
e2ac520 @jakevdp cleanup, tests, doc
jakevdp authored
195
196 Examples
197 --------
198 The following example shows the computation of a depth-first tree
199 over a simple four-component graph, starting at node 0::
200
201 input graph depth first tree from (0)
202
203 (0) (0)
204 / \ \
205 3 8 8
206 / \ \
207 (3)---5---(1) (3) (1)
208 \ / \ /
209 6 2 6 2
210 \ / \ /
211 (2) (2)
212
37fb4e0 @jakevdp move Notes before Examples in doc
jakevdp authored
213 In compressed sparse representation, the solution looks like this:
e2ac520 @jakevdp cleanup, tests, doc
jakevdp authored
214
215 >>> from scipy.sparse import csr_matrix
c55bfbb @jakevdp move csgraph code to private submodules
jakevdp authored
216 >>> from scipy.sparse.csgraph import depth_first_tree
e2ac520 @jakevdp cleanup, tests, doc
jakevdp authored
217 >>> X = csr_matrix([[0, 8, 0, 3],
218 ... [0, 0, 2, 5],
219 ... [0, 0, 0, 6],
220 ... [0, 0, 0, 0]])
c55bfbb @jakevdp move csgraph code to private submodules
jakevdp authored
221 >>> Tcsr = depth_first_tree(X, 0, directed=False)
e2ac520 @jakevdp cleanup, tests, doc
jakevdp authored
222 >>> Tcsr.toarray().astype(int)
223 array([[0, 8, 0, 0],
224 [0, 0, 2, 0],
225 [0, 0, 0, 6],
226 [0, 0, 0, 0]])
227
228 Note that the resulting graph is a Directed Acyclic Graph which spans
229 the graph. Unlike a breadth-first tree, a depth-first tree of a given
230 graph is not unique if the graph contains cycles. If the above solution
231 had begun with the edge connecting nodes 0 and 3, the result would have
232 been different.
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
233 """
c55bfbb @jakevdp move csgraph code to private submodules
jakevdp authored
234 node_list, predecessors = depth_first_order(csgraph, i_start,
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
235 directed, True)
e2ac520 @jakevdp cleanup, tests, doc
jakevdp authored
236 return reconstruct_path(csgraph, predecessors, directed)
74c352c @jakevdp add depth-first and breadth-first tree functions
jakevdp authored
237
238
c55bfbb @jakevdp move csgraph code to private submodules
jakevdp authored
239 def breadth_first_order(csgraph, i_start,
240 directed=True, return_predecessors=True):
540716a @pv DOC: add function signatures to all Cython-based routine docstrings (…
pv authored
241 """
242 breadth_first_order(csgraph, i_start, directed=True, return_predecessors=True)
243
244 Return a breadth-first ordering starting with specified node.
fdc8c62 @jakevdp add depth-first search
jakevdp authored
245
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
246 Note that a breadth-first order is not unique, but the tree which it
247 generates is unique.
5936f7e @jakevdp add breadth-first search
jakevdp authored
248
276b9f5 @pv DOC: add some missing versionadded:: statements
pv authored
249 .. versionadded:: 0.11.0
250
5936f7e @jakevdp add breadth-first search
jakevdp authored
251 Parameters
252 ----------
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
253 csgraph : array_like or sparse matrix
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
254 The N x N compressed sparse graph. The input csgraph will be
255 converted to csr format for the calculation.
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
256 i_start : int
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
257 The index of starting node.
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
258 directed : bool, optional
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
259 If True (default), then operate on a directed graph: only
260 move from point i to point j along paths csgraph[i, j].
261 If False, then find the shortest path on an undirected graph: the
5936f7e @jakevdp add breadth-first search
jakevdp authored
262 algorithm can progress from point i to j along csgraph[i, j] or
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
263 csgraph[j, i].
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
264 return_predecessors : bool, optional
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
265 If True (default), then return the predecesor array (see below).
5936f7e @jakevdp add breadth-first search
jakevdp authored
266
267 Returns
268 -------
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
269 node_array : ndarray, one dimension
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
270 The breadth-first list of nodes, starting with specified node. The
271 length of node_array is the number of nodes reachable from the
272 specified node.
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
273 predecessors : ndarray, one dimension
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
274 Returned only if return_predecessors is True.
275 The length-N list of predecessors of each node in a breadth-first
276 tree. If node i is in the tree, then its parent is given by
277 predecessors[i]. If node i is not in the tree (and for the parent
278 node) then predecessors[i] = -9999.
5936f7e @jakevdp add breadth-first search
jakevdp authored
279 """
280 global NULL_IDX
09e9680 @jakevdp add validation tools
jakevdp authored
281 csgraph = validate_graph(csgraph, directed, dense_output=False)
5936f7e @jakevdp add breadth-first search
jakevdp authored
282 cdef int N = csgraph.shape[0]
283
284 cdef np.ndarray node_list = np.empty(N, dtype=ITYPE)
285 cdef np.ndarray predecessors = np.empty(N, dtype=ITYPE)
286 node_list.fill(NULL_IDX)
287 predecessors.fill(NULL_IDX)
288
289 if directed:
a379593 @jakevdp use canonical form of dense matrices across module
jakevdp authored
290 length = _breadth_first_directed(i_start,
5936f7e @jakevdp add breadth-first search
jakevdp authored
291 csgraph.indices, csgraph.indptr,
292 node_list, predecessors)
293 else:
294 csgraph_T = csgraph.T.tocsr()
295 length = _breadth_first_undirected(i_start,
296 csgraph.indices, csgraph.indptr,
297 csgraph_T.indices, csgraph_T.indptr,
298 node_list, predecessors)
299
300 if return_predecessors:
301 return node_list[:length], predecessors
302 else:
303 return node_list[:length]
304
305
a379593 @jakevdp use canonical form of dense matrices across module
jakevdp authored
306 cdef unsigned int _breadth_first_directed(
5936f7e @jakevdp add breadth-first search
jakevdp authored
307 unsigned int head_node,
308 np.ndarray[ITYPE_t, ndim=1, mode='c'] indices,
309 np.ndarray[ITYPE_t, ndim=1, mode='c'] indptr,
310 np.ndarray[ITYPE_t, ndim=1, mode='c'] node_list,
311 np.ndarray[ITYPE_t, ndim=1, mode='c'] predecessors):
fdc8c62 @jakevdp add depth-first search
jakevdp authored
312 # Inputs:
313 # head_node: (input) index of the node from which traversal starts
314 # indices: (input) CSR indices of graph
315 # indptr: (input) CSR indptr of graph
316 # node_list: (output) breadth-first list of nodes
317 # predecessors: (output) list of predecessors of nodes in breadth-first
318 # tree. Should be initialized to NULL_IDX
319 # Returns:
320 # n_nodes: the number of nodes in the breadth-first tree
5936f7e @jakevdp add breadth-first search
jakevdp authored
321 global NULL_IDX
322
323 cdef unsigned int i, pnode, cnode
324 cdef unsigned int i_nl, i_nl_end
325 cdef unsigned int N = node_list.shape[0]
326
327 node_list[0] = head_node
328 i_nl = 0
329 i_nl_end = 1
330
331 while i_nl < i_nl_end:
332 pnode = node_list[i_nl]
333
334 for i from indptr[pnode] <= i < indptr[pnode + 1]:
335 cnode = indices[i]
336 if (cnode == head_node):
337 continue
338 elif (predecessors[cnode] == NULL_IDX):
339 node_list[i_nl_end] = cnode
340 predecessors[cnode] = pnode
341 i_nl_end += 1
342
343 i_nl += 1
344
345 return i_nl
346
347
348 cdef unsigned int _breadth_first_undirected(
349 unsigned int head_node,
350 np.ndarray[ITYPE_t, ndim=1, mode='c'] indices1,
351 np.ndarray[ITYPE_t, ndim=1, mode='c'] indptr1,
352 np.ndarray[ITYPE_t, ndim=1, mode='c'] indices2,
353 np.ndarray[ITYPE_t, ndim=1, mode='c'] indptr2,
354 np.ndarray[ITYPE_t, ndim=1, mode='c'] node_list,
355 np.ndarray[ITYPE_t, ndim=1, mode='c'] predecessors):
fdc8c62 @jakevdp add depth-first search
jakevdp authored
356 # Inputs:
357 # head_node: (input) index of the node from which traversal starts
358 # indices1: (input) CSR indices of graph
359 # indptr1: (input) CSR indptr of graph
360 # indices2: (input) CSR indices of transposed graph
361 # indptr2: (input) CSR indptr of transposed graph
362 # node_list: (output) breadth-first list of nodes
363 # predecessors: (output) list of predecessors of nodes in breadth-first
364 # tree. Should be initialized to NULL_IDX
365 # Returns:
366 # n_nodes: the number of nodes in the breadth-first tree
5936f7e @jakevdp add breadth-first search
jakevdp authored
367 global NULL_IDX
368
369 cdef unsigned int i, pnode, cnode
370 cdef unsigned int i_nl, i_nl_end
371 cdef unsigned int N = node_list.shape[0]
372
373 node_list[0] = head_node
374 i_nl = 0
375 i_nl_end = 1
376
377 while i_nl < i_nl_end:
378 pnode = node_list[i_nl]
379
380 for i from indptr1[pnode] <= i < indptr1[pnode + 1]:
381 cnode = indices1[i]
382 if (cnode == head_node):
383 continue
384 elif (predecessors[cnode] == NULL_IDX):
385 node_list[i_nl_end] = cnode
386 predecessors[cnode] = pnode
387 i_nl_end += 1
388
389 for i from indptr2[pnode] <= i < indptr2[pnode + 1]:
390 cnode = indices2[i]
391 if (cnode == head_node):
392 continue
393 elif (predecessors[cnode] == NULL_IDX):
394 node_list[i_nl_end] = cnode
395 predecessors[cnode] = pnode
396 i_nl_end += 1
397
398 i_nl += 1
399
400 return i_nl
fdc8c62 @jakevdp add depth-first search
jakevdp authored
401
402
c55bfbb @jakevdp move csgraph code to private submodules
jakevdp authored
403 def depth_first_order(csgraph, i_start,
404 directed=True, return_predecessors=True):
540716a @pv DOC: add function signatures to all Cython-based routine docstrings (…
pv authored
405 """
406 depth_first_order(csgraph, i_start, directed=True, return_predecessors=True)
407
408 Return a depth-first ordering starting with specified node.
fdc8c62 @jakevdp add depth-first search
jakevdp authored
409
410 Note that a depth-first order is not unique. Furthermore, for graphs
411 with cycles, the tree generated by a depth-first search is not
412 unique either.
413
276b9f5 @pv DOC: add some missing versionadded:: statements
pv authored
414 .. versionadded:: 0.11.0
415
fdc8c62 @jakevdp add depth-first search
jakevdp authored
416 Parameters
417 ----------
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
418 csgraph : array_like or sparse matrix
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
419 The N x N compressed sparse graph. The input csgraph will be
420 converted to csr format for the calculation.
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
421 i_start : int
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
422 The index of starting node.
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
423 directed : bool, optional
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
424 If True (default), then operate on a directed graph: only
425 move from point i to point j along paths csgraph[i, j].
426 If False, then find the shortest path on an undirected graph: the
fdc8c62 @jakevdp add depth-first search
jakevdp authored
427 algorithm can progress from point i to j along csgraph[i, j] or
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
428 csgraph[j, i].
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
429 return_predecessors : bool, optional
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
430 If True (default), then return the predecesor array (see below).
fdc8c62 @jakevdp add depth-first search
jakevdp authored
431
432 Returns
433 -------
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
434 node_array : ndarray, one dimension
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
435 The breadth-first list of nodes, starting with specified node. The
436 length of node_array is the number of nodes reachable from the
437 specified node.
e37131b @rgommers DOC: merge wiki edits, sparse module.
rgommers authored
438 predecessors : ndarray, one dimension
8650134 @jakevdp Fix typos and address doc string standards
jakevdp authored
439 Returned only if return_predecessors is True.
440 The length-N list of predecessors of each node in a breadth-first
441 tree. If node i is in the tree, then its parent is given by
442 predecessors[i]. If node i is not in the tree (and for the parent
443 node) then predecessors[i] = -9999.
fdc8c62 @jakevdp add depth-first search
jakevdp authored
444 """
445 global NULL_IDX
09e9680 @jakevdp add validation tools
jakevdp authored
446 csgraph = validate_graph(csgraph, directed, dense_output=False)
fdc8c62 @jakevdp add depth-first search
jakevdp authored
447 cdef int N = csgraph.shape[0]
448
c89abe6 @pv BUG: sparse.csgraph: fix invalid integer types
pv authored
449 node_list = np.empty(N, dtype=ITYPE)
450 predecessors = np.empty(N, dtype=ITYPE)
451 root_list = np.empty(N, dtype=ITYPE)
452 flag = np.zeros(N, dtype=ITYPE)
fdc8c62 @jakevdp add depth-first search
jakevdp authored
453 node_list.fill(NULL_IDX)
454 predecessors.fill(NULL_IDX)
455 root_list.fill(NULL_IDX)
456
457 if directed:
a379593 @jakevdp use canonical form of dense matrices across module
jakevdp authored
458 length = _depth_first_directed(i_start,
fdc8c62 @jakevdp add depth-first search
jakevdp authored
459 csgraph.indices, csgraph.indptr,
460 node_list, predecessors,
461 root_list, flag)
462 else:
463 csgraph_T = csgraph.T.tocsr()
464 length = _depth_first_undirected(i_start,
465 csgraph.indices, csgraph.indptr,
466 csgraph_T.indices, csgraph_T.indptr,
467 node_list, predecessors,
468 root_list, flag)
469
470 if return_predecessors:
471 return node_list[:length], predecessors
472 else:
473 return node_list[:length]
474
475
a379593 @jakevdp use canonical form of dense matrices across module
jakevdp authored
476 cdef unsigned int _depth_first_directed(
fdc8c62 @jakevdp add depth-first search
jakevdp authored
477 unsigned int head_node,
478 np.ndarray[ITYPE_t, ndim=1, mode='c'] indices,
479 np.ndarray[ITYPE_t, ndim=1, mode='c'] indptr,
480 np.ndarray[ITYPE_t, ndim=1, mode='c'] node_list,
481 np.ndarray[ITYPE_t, ndim=1, mode='c'] predecessors,
482 np.ndarray[ITYPE_t, ndim=1, mode='c'] root_list,
c89abe6 @pv BUG: sparse.csgraph: fix invalid integer types
pv authored
483 np.ndarray[ITYPE_t, ndim=1, mode='c'] flag):
fdc8c62 @jakevdp add depth-first search
jakevdp authored
484 cdef unsigned int i, j, i_nl_end, cnode, pnode
485 cdef unsigned int N = node_list.shape[0]
486 cdef int no_children, i_root
487
488 node_list[0] = head_node
489 root_list[0] = head_node
490 i_root = 0
491 i_nl_end = 1
492 flag[head_node] = 1
493
494 while i_root >= 0:
495 pnode = root_list[i_root]
496 no_children = True
497 for i from indptr[pnode] <= i < indptr[pnode + 1]:
498 cnode = indices[i]
499 if flag[cnode]:
500 continue
501 else:
502 i_root += 1
503 root_list[i_root] = cnode
504 node_list[i_nl_end] = cnode
505 predecessors[cnode] = pnode
506 flag[cnode] = 1
507 i_nl_end += 1
508 no_children = False
509 break
510
511 if i_nl_end == N:
512 break
513
514 if no_children:
515 i_root -= 1
516
517 return i_nl_end
518
519
520 cdef unsigned int _depth_first_undirected(
521 unsigned int head_node,
522 np.ndarray[ITYPE_t, ndim=1, mode='c'] indices1,
523 np.ndarray[ITYPE_t, ndim=1, mode='c'] indptr1,
524 np.ndarray[ITYPE_t, ndim=1, mode='c'] indices2,
525 np.ndarray[ITYPE_t, ndim=1, mode='c'] indptr2,
526 np.ndarray[ITYPE_t, ndim=1, mode='c'] node_list,
527 np.ndarray[ITYPE_t, ndim=1, mode='c'] predecessors,
528 np.ndarray[ITYPE_t, ndim=1, mode='c'] root_list,
c89abe6 @pv BUG: sparse.csgraph: fix invalid integer types
pv authored
529 np.ndarray[ITYPE_t, ndim=1, mode='c'] flag):
fdc8c62 @jakevdp add depth-first search
jakevdp authored
530 cdef unsigned int i, j, i_nl_end, cnode, pnode
531 cdef unsigned int N = node_list.shape[0]
532 cdef int no_children, i_root
533
534 node_list[0] = head_node
535 root_list[0] = head_node
536 i_root = 0
537 i_nl_end = 1
538 flag[head_node] = 1
539
540 while i_root >= 0:
541 pnode = root_list[i_root]
542 no_children = True
543
544 for i from indptr1[pnode] <= i < indptr1[pnode + 1]:
545 cnode = indices1[i]
546 if flag[cnode]:
547 continue
548 else:
549 i_root += 1
550 root_list[i_root] = cnode
551 node_list[i_nl_end] = cnode
552 predecessors[cnode] = pnode
553 flag[cnode] = 1
554 i_nl_end += 1
555 no_children = False
556 break
557
558 if no_children:
559 for i from indptr2[pnode] <= i < indptr2[pnode + 1]:
560 cnode = indices2[i]
561 if flag[cnode]:
562 continue
563 else:
564 i_root += 1
565 root_list[i_root] = cnode
566 node_list[i_nl_end] = cnode
567 predecessors[cnode] = pnode
568 flag[cnode] = 1
569 i_nl_end += 1
570 no_children = False
571 break
572
573 if i_nl_end == N:
574 break
575
576 if no_children:
577 i_root -= 1
578
579 return i_nl_end
580
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
581
f746399 @timleslie Address review comments: Use ITYPE instead of np.int32. Shorten long …
timleslie authored
582 cdef int _connected_components_directed(
583 np.ndarray[ITYPE_t, ndim=1, mode='c'] indices,
584 np.ndarray[ITYPE_t, ndim=1, mode='c'] indptr,
585 np.ndarray[ITYPE_t, ndim=1, mode='c'] labels):
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
586 """
f746399 @timleslie Address review comments: Use ITYPE instead of np.int32. Shorten long …
timleslie authored
587 Uses an iterative version of Tarjan's algorithm to find the
588 strongly connected components of a directed graph represented as a
589 sparse matrix (scipy.sparse.csc_matrix or scipy.sparse.csr_matrix).
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
590
f746399 @timleslie Address review comments: Use ITYPE instead of np.int32. Shorten long …
timleslie authored
591 The algorithmic complexity is for a graph with E edges and V
592 vertices is O(E + V).
b478919 @timleslie Update inline documentation
timleslie authored
593 The storage requirement is 2*V integer arrays.
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
594
b478919 @timleslie Update inline documentation
timleslie authored
595 Uses an iterative version of the algorithm described here:
596 http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.102.1707
e2c502f @timleslie DOC: sparse: add link to Strongly Connected Components algorithm deta…
timleslie authored
597
598 For more details of the memory optimisations used see here:
599 http://www.timl.id.au/?p=327
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
600 """
f746399 @timleslie Address review comments: Use ITYPE instead of np.int32. Shorten long …
timleslie authored
601 cdef int v, w, index, low_v, low_w, label, j
602 cdef int SS_head, root, stack_head, f, b
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
603 cdef int VOID = -1
604 cdef int END = -2
605 cdef int N = labels.shape[0]
f746399 @timleslie Address review comments: Use ITYPE instead of np.int32. Shorten long …
timleslie authored
606 cdef np.ndarray[ITYPE_t, ndim=1, mode="c"] SS, lowlinks, stack_f, stack_b
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
607
f746399 @timleslie Address review comments: Use ITYPE instead of np.int32. Shorten long …
timleslie authored
608 lowlinks = labels
609 SS = np.ndarray((N,), dtype=ITYPE)
610 stack_b = np.ndarray((N,), dtype=ITYPE)
611 stack_f = SS
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
612
b478919 @timleslie Update inline documentation
timleslie authored
613 # The stack of nodes which have been backtracked and are in the current SCC
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
614 SS.fill(VOID)
615 SS_head = END
b478919 @timleslie Update inline documentation
timleslie authored
616
f746399 @timleslie Address review comments: Use ITYPE instead of np.int32. Shorten long …
timleslie authored
617 # The array containing the lowlinks of nodes not yet assigned an SCC. Shares
618 # memory with the labels array, since they are not used at the same time.
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
619 lowlinks.fill(VOID)
b478919 @timleslie Update inline documentation
timleslie authored
620
f746399 @timleslie Address review comments: Use ITYPE instead of np.int32. Shorten long …
timleslie authored
621 # The DFS stack. Stored with both forwards and backwards pointers to allow
622 # us to move a node up to the top of the stack, as we only need to visit
623 # each node once. stack_f shares memory with SS, as nodes aren't put on the
624 # SS stack until after they've been popped from the DFS stack.
b478919 @timleslie Update inline documentation
timleslie authored
625 stack_head = END
626 stack_f.fill(VOID)
627 stack_b.fill(VOID)
628
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
629 index = 0
f746399 @timleslie Address review comments: Use ITYPE instead of np.int32. Shorten long …
timleslie authored
630 # Count SCC labels backwards so as not to class with lowlinks values.
631 label = N - 1
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
632 for v in range(N):
633 if lowlinks[v] == VOID:
b478919 @timleslie Update inline documentation
timleslie authored
634 # DFS-stack push
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
635 stack_head = v
636 stack_f[v] = END
637 stack_b[v] = END
638 while stack_head != END:
639 v = stack_head
640 if lowlinks[v] == VOID:
641 lowlinks[v] = index
642 index += 1
643
644 # Add successor nodes
645 for j from indptr[v] <= j < indptr[v+1]:
646 w = indices[j]
647 if lowlinks[w] == VOID:
b478919 @timleslie Update inline documentation
timleslie authored
648 # DFS-stack push
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
649 if stack_f[w] != VOID:
f746399 @timleslie Address review comments: Use ITYPE instead of np.int32. Shorten long …
timleslie authored
650 # w is already inside the stack, so excise it.
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
651 f = stack_f[w]
652 b = stack_b[w]
653 if b != END:
654 stack_f[b] = f
655 if f != END:
656 stack_b[f] = b
657
658 stack_f[w] = stack_head
659 stack_b[w] = END
660 stack_b[stack_head] = w
661 stack_head = w
662
663 else:
b478919 @timleslie Update inline documentation
timleslie authored
664 # DFS-stack pop
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
665 stack_head = stack_f[v]
666 if stack_head >= 0:
667 stack_b[stack_head] = END
668 stack_f[v] = VOID
669 stack_b[v] = VOID
670
671 root = 1 # True
672 low_v = lowlinks[v]
673 for j from indptr[v] <= j < indptr[v+1]:
674 low_w = lowlinks[indices[j]]
675 if low_w < low_v:
676 low_v = low_w
677 root = 0 # False
678 lowlinks[v] = low_v
679
680 if root: # Found a root node
681 index -= 1
682 # while S not empty and rindex[v] <= rindex[top[S]
b478919 @timleslie Update inline documentation
timleslie authored
683 while SS_head != END and lowlinks[v] <= lowlinks[SS_head]:
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
684 w = SS_head # w = pop(S)
685 SS_head = SS[w]
686 SS[w] = VOID
687
688 labels[w] = label # rindex[w] = c
689 index -= 1 # index = index - 1
690 labels[v] = label # rindex[v] = c
691 label -= 1 # c = c - 1
692 else:
693 SS[v] = SS_head # push(S, v)
694 SS_head = v
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
695
f746399 @timleslie Address review comments: Use ITYPE instead of np.int32. Shorten long …
timleslie authored
696 # labels count down from N-1 to zero. Modify them so they
7d6d641 @jakevdp implement connected components in cython
jakevdp authored
697 # count upward from 0
698 labels *= -1
699 labels += (N - 1)
90daaef @timleslie Replace recursive algorithm with iterative algorithm
timleslie authored
700 return (N - 1) - label
9381880 @timleslie Add an optimized WCC algorithm
timleslie authored
701
702 cdef int _connected_components_undirected(
703 np.ndarray[ITYPE_t, ndim=1, mode='c'] indices1,
704 np.ndarray[ITYPE_t, ndim=1, mode='c'] indptr1,
705 np.ndarray[ITYPE_t, ndim=1, mode='c'] indices2,
706 np.ndarray[ITYPE_t, ndim=1, mode='c'] indptr2,
707 np.ndarray[ITYPE_t, ndim=1, mode='c'] labels):
708
709 cdef int v, w, j, label, SS_head
710 cdef int N = labels.shape[0]
711 cdef int VOID = -1
712 cdef int END = -2
713 labels.fill(VOID)
714 label = 0
715
716 # Share memory for the stack and labels, since labels are only
717 # applied once a node has been popped from the stack.
718 cdef np.ndarray[ITYPE_t, ndim=1, mode="c"] SS = labels
719 SS_head = END
720 for v in range(N):
721 if labels[v] == VOID:
722 # SS.push(v)
723 SS_head = v
724 SS[v] = END
725
726 while SS_head != END:
727 # v = SS.pop()
728 v = SS_head
729 SS_head = SS[v]
730
731 labels[v] = label
732
733 # Push children onto the stack if they havn't been
734 # seen at all yet.
735 for j from indptr1[v] <= j < indptr1[v+1]:
736 w = indices1[j]
737 if SS[w] == VOID:
738 SS[w] = SS_head
739 SS_head = w
740 for j from indptr2[v] <= j < indptr2[v+1]:
741 w = indices2[j]
742 if SS[w] == VOID:
743 SS[w] = SS_head
744 SS_head = w
745 label += 1
746
747 return label
Something went wrong with that request. Please try again.