File: bfs.m

package info (click to toggle)
suitesparse-graphblas 7.4.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 67,112 kB
  • sloc: ansic: 1,072,243; cpp: 8,081; sh: 512; makefile: 503; asm: 369; python: 125; awk: 10
file content (175 lines) | stat: -rw-r--r-- 6,127 bytes parent folder | download | duplicates (2)
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
function [v, parent] = bfs (A, s, varargin)
%GRB.BFS breadth-first search of a graph, using its adjacency matrix.
% v = GrB.bfs (A, s) performs the breadth-first search of the directed
% graph represented by the square adjacency matrix A.  The breadth-first
% search starts at node s.  The output v is a sparse vector of size
% n-by-1, with the level of each node, where v(s)=1, and v(i)=k if the
% path with the fewest edges from from s to i has k-1 edges.  If i is not
% reachable from s, then v(i) is implicitly zero and does not appear in
% the pattern of v.
%
% [v, parent] = GrB.bfs (A, s) also computes the parent vector,
% representing the breadth-first search tree.  parent(s)=s denotes the
% root of the tree, and parent(c)=p if node p is the parent of c in the
% tree.  The parent vector is sparse, and parent (i) is not present if i
% is not found in the breadth-first search.
%
% Optional string arguments can be provided, after A and s:
%
%   'undirected' or 'symmetric':  A is assumed to be symmetric, and
%       represents an undirected graph.  Results are undefined if A is
%       unsymmetric, and 'check' is not specified.
%
%   'directed' or 'unsymmetric':  A is assumed to be unsymmetric, and
%       presents a directed graph.  This is the default.
%
%   'check': with the 'undirected' or 'symmetric' option, A is checked to
%       ensure that it is symmetric.  The default is not to check.
%
% For best performance, if A represents a directed graph, it should be a
% GraphBLAS matrix stored by row on input.  That is, GrB.format (A)
% should report 'by row'.  (If A represents a directed graph but is
% stored 'by col' on input, it is first converted to 'by row', which is
% costly).  If A is an undirected graph, then it can be stored in either
% format ('by row' or 'by col').
%
% A must be square.  Only the pattern, spones (A), is considered; the
% values of its entries (the edge weights of the graph) are ignored.
%
% Example:
%
%   A = bucky ;
%   s = 1 ;
%   [v pi] = GrB.bfs (A, s)
%   figure (1) ;
%   subplot (1,2,1) ; plot (graph (A)) ;
%   pi2 = full (double (pi)) ;
%   pi2 (s) = 0 ;
%   subplot (1,2,2) ; treeplot (pi2) ; title ('BFS tree') ;
%   n = size (A,1) ;
%   for level = 1:n
%       level
%       inlevel = find (v == level)
%       parents = full (double (pi (inlevel)))
%       if (isempty (inlevel))
%           break ;
%       end
%   end
%
% See also graph/bfsearch, graph/shortestpathtree, treeplot.

% SuiteSparse:GraphBLAS, Timothy A. Davis, (c) 2017-2022, All Rights Reserved.
% SPDX-License-Identifier: Apache-2.0

% NOTE: this is a high-level algorithm that uses GrB objects.

%-------------------------------------------------------------------------
% initializations
%-------------------------------------------------------------------------

[m, n] = size (A) ;
if (m ~= n)
    error ('GrB:error', 'A must be square') ;
end

% get the string options
kind = 'directed' ;
check = false ;
for k = 1:nargin-2
    arg = lower (varargin {k}) ;
    switch arg
        case { 'undirected', 'symmetric' }
            kind = 'undirected' ;
        case { 'directed', 'unsymmetric' }
            kind = 'directed' ;
        case { 'check' }
            check = true ;
        otherwise
            error ('GrB:error', 'unknown option') ;
    end
end

% set the descriptors
desc_rc.out  = 'replace' ;
desc_rc.mask = 'complement' ;
desc_s.mask = 'structural' ;

% determine the method to use, and convert A if necessary
if (isequal (kind, 'undirected'))
    if (check && ~issymmetric (A))
        error ('GrB:error', 'A must be symmetric') ;
    end
    if (GrB.isbycol (A))
        % A is stored by column but undirected, so use q*A' instead of q*A
        desc_rc.in1 = 'transpose' ;
    end
else
    if (GrB.isbycol (A))
        % this can be costly
        A = GrB (A, 'by row') ;
    end
end

% determine the integer type to use
int_type = 'int64' ;
if (n < intmax ('int32'))
    int_type = 'int32' ;
end

% initialize v as a full integer vector
v = full (GrB (1, n, int_type)) ;

%-------------------------------------------------------------------------
% do the BFS
%-------------------------------------------------------------------------

if (nargout == 1)

    %---------------------------------------------------------------------
    % just compute the level of each node
    %---------------------------------------------------------------------

    q = GrB (1, n, 'logical') ;                  % q = sparse (1,n)
    q = GrB.subassign (q, { s }, true) ;         % q (s) = 1
    for level = 1:n
        % assign the current level: v<q> = level
        v = GrB.subassign (v, q, level, desc_s) ;
        % quit if q is empty
        if (~any (q)), break, end
        % move to the next level:  q<~v,replace> = q*A
        q = GrB.mxm (q, v, 'any.pair.logical', q, A, desc_rc) ;
    end

else

    %---------------------------------------------------------------------
    % compute both the level and the parent
    %---------------------------------------------------------------------

    parent = full (GrB (1, n, int_type)) ;       % parent = zeros (1,n)
    parent = GrB.subassign (parent, { s }, s) ;  % parent (s) = s
    q = GrB (1, n, int_type) ;                   % q = sparse (1,n)
    q = GrB.subassign (q, { s }, s) ;            % q (s) = s
    id = GrB (1:n, int_type, 'by row') ;         % id = 1:n
    semiring = ['any.1st.' int_type] ;           % any.1st.integer semiring
    for level = 1:n
        % assign the current level: v<q> = level
        v = GrB.subassign (v, q, level, desc_s) ;
        % quit if q is empty
        if (~any (q)), break, end
        % move to the next level:  q<~v,replace> = q*A,
        % using the any-first-integer semiring (int32 or int64)
        q = GrB.mxm (q, v, semiring, q, A, desc_rc) ;
        % assign parents: parent<q> = q
        parent = GrB.assign (parent, q, q, desc_s) ;
        % q(i) = i for all entries in q, using q<q>=1:n
        q = GrB.assign (q, q, id, desc_s) ;
    end
    % remove zeros from parent
    parent = GrB.prune (parent) ;

end

% remove zeros from v
v = GrB.prune (v) ;