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