1%% GraphBLAS: graph algorithms in the language of linear algebra
2% GraphBLAS is a library for creating graph algorithms based on sparse
3% linear algebraic operations over semirings.  Visit http://graphblas.org
4% for more details and resources.  See also the SuiteSparse:GraphBLAS
5% User Guide in this package.
6%
7% http://faculty.cse.tamu.edu/davis
8%
9% SuiteSparse:GraphBLAS, Timothy A. Davis, (c) 2017-2021, All Rights Reserved.
10% SPDX-License-Identifier: GPL-3.0-or-later
11
12%% GraphBLAS: faster and more general sparse matrices for MATLAB
13% GraphBLAS is not only useful for creating graph algorithms; it also
14% supports a wide range of sparse matrix data types and operations.
15% MATLAB can compute C=A*B with just two semirings: 'plus.times.double'
16% and 'plus.times.complex' for complex matrices.  GraphBLAS has 2,518
17% built-in semirings, such as 'max.plus'
18% (https://en.wikipedia.org/wiki/Tropical_semiring).  These semirings can
19% be used to construct a wide variety of graph algorithms, based on
20% operations on sparse adjacency matrices.
21%
22% MATLAB and GraphBLAS both provide sparse matrices of type double,
23% logical, and double complex.  GraphBLAS adds sparse matrices of type:
24% single, int8, int16, int32, int64, uint8, uint16, uint32, uint64, and
25% single complex (with MATLAB matrices, these types can only be held in
26% full matrices).
27
28% reset to the default number of threads
29clear all
30maxNumCompThreads ('automatic') ;
31GrB.clear ;
32fprintf ('\n# of threads used by GraphBLAS: %d\n', GrB.threads) ;
33
34format compact
35rng ('default') ;
36X = 100 * rand (2) ;
37G = GrB (X)              % GraphBLAS copy of a matrix X, same type
38
39%% Sparse integer matrices
40% Here's an int8 version of the same matrix:
41
42S = int8 (G)             % convert G to a full MATLAB int8 matrix
43S (1,1) = 0              % add an explicit zero to S
44G = GrB (X, 'int8')      % a GraphBLAS full int8 matrix
45G (1,1) = 0              % add an explicit zero to G
46G = GrB.prune (G)        % a GraphBLAS sparse int8 matrix
47
48try
49    S = sparse (S) ;     % MATLAB can't create sparse int8 matrices
50catch me
51    display (me)
52end
53
54%% Sparse single-precision matrices
55% Matrix operations in GraphBLAS are typically as fast, or faster than
56% MATLAB.  Here's an unfair comparison: computing X*X with MATLAB in
57% double precision and with GraphBLAS in single precision.  You would
58% naturally expect GraphBLAS to be faster.
59%
60% CAVEAT:  MATLAB R2021a uses SuiteSparse:GraphBLAS v3.3.3 for C=A*B,
61% so on that version of MATLAB, we're comparing 2 versions of GraphBLAS
62% by the same author.
63%
64% Please wait ...
65
66n = 1e5 ;
67X = spdiags (rand (n, 201), -100:100, n, n) ;
68G = GrB (X, 'single') ;
69tic
70G2 = G*G ;
71gb_time = toc ;
72tic
73X2 = X*X ;
74matlab_time = toc ;
75fprintf ('\nGraphBLAS time: %g sec (in single)\n', gb_time) ;
76fprintf ('MATLAB time:    %g sec (in double)\n', matlab_time) ;
77fprintf ('Speedup of GraphBLAS over MATLAB: %g\n', ...
78    matlab_time / gb_time) ;
79fprintf ('\n# of threads used by GraphBLAS: %d\n', GrB.threads) ;
80
81%% Mixing MATLAB and GraphBLAS matrices
82% The error in the last computation is about eps('single') since
83% GraphBLAS did its computation in single precision, while MATLAB used
84% double precision.  MATLAB and GraphBLAS matrices can be easily
85% combined, as in X2-G2.  The sparse single precision matrices take less
86% memory space.
87
88err = norm (X2 - G2, 1) / norm (X2,1)
89eps ('single')
90whos G G2 X X2
91
92%% Faster matrix operations
93% But even with standard double precision sparse matrices, GraphBLAS is
94% typically faster than the built-in MATLAB methods.  Here's a fair
95% comparison (caveat: these both use GraphBLAS in MATLAB R2021a):
96
97G = GrB (X) ;
98tic
99G2 = G*G ;
100gb_time = toc ;
101err = norm (X2 - G2, 1) / norm (X2,1)
102fprintf ('\nGraphBLAS time: %g sec (in double)\n', gb_time) ;
103fprintf ('MATLAB time:    %g sec (in double)\n', matlab_time) ;
104fprintf ('Speedup of GraphBLAS over MATLAB: %g\n', ...
105    matlab_time / gb_time) ;
106fprintf ('\n# of threads used by GraphBLAS: %d\n', GrB.threads) ;
107
108%% A wide range of semirings
109% MATLAB can only compute C=A*B using the standard '+.*.double' and
110% '+.*.complex' semirings.  A semiring is defined in terms of a string,
111% 'add.mult.type', where 'add' is a monoid that takes the place of the
112% additive operator, 'mult' is the multiplicative operator, and 'type' is
113% the data type for the two inputs to the mult operator.
114%
115% In the standard semiring, C=A*B is defined as:
116%
117%   C(i,j) = sum (A(i,:).' .* B(:,j))
118%
119% using 'plus' as the monoid and 'times' as the multiplicative operator.
120% But in a more general semiring, 'sum' can be any monoid, which is an
121% associative and commutative operator that has an identity value.  For
122% example, in the 'max.plus' tropical algebra, C(i,j) for C=A*B is
123% defined as:
124%
125%   C(i,j) = max (A(i,:).' + B(:,j))
126
127%%
128% This can be computed in GraphBLAS with:
129%
130%   C = GrB.mxm ('max.+', A, B)
131
132n = 3 ;
133A = rand (n) ;
134B = rand (n) ;
135C = zeros (n) ;
136for i = 1:n
137    for j = 1:n
138        C(i,j) = max (A (i,:).' + B (:,j)) ;
139    end
140end
141C2 = GrB.mxm ('max.+', A, B) ;
142fprintf ('\nerr = norm (C-C2,1) = %g\n', norm (C-C2,1)) ;
143
144%% The max.plus tropical semiring
145% Here are details of the "max.plus" tropical semiring.  The identity
146% value is -inf since max(x,-inf) = max (-inf,x) = x for any x.
147% The identity for the conventional "plus.times" semiring is zero,
148% since x+0 = 0+x = x for any x.
149
150GrB.semiringinfo ('max.+.double') ;
151
152%% A boolean semiring
153% MATLAB cannot multiply two logical matrices.  MATLAB R2019a converts
154% them to double and uses the conventional +.*.double semiring instead.
155% In GraphBLAS, this is the common Boolean 'or.and.logical' semiring,
156% which is widely used in linear algebraic graph algorithms.
157
158GrB.semiringinfo ('|.&.logical') ;
159
160%%
161clear
162A = sparse (rand (3) > 0.5)
163B = sparse (rand (3) > 0.2)
164
165%%
166try
167    % MATLAB R2019a does this by casting A and B to double
168    C1 = A*B
169catch
170    % MATLAB R2018a throws an error
171    fprintf ('MATLAB R2019a required for C=A*B with logical\n') ;
172    fprintf ('matrices.  Explicitly converting to double:\n') ;
173    C1 = double (A) * double (B)
174end
175C2 = GrB (A) * GrB (B)
176
177%%
178% Note that C1 is a MATLAB sparse double matrix, and contains non-binary
179% values.  C2 is a GraphBLAS logical matrix.
180whos
181GrB.type (C2)
182
183%% GraphBLAS operators, monoids, and semirings
184% The C interface for SuiteSparse:GraphBLAS allows for arbitrary types
185% and operators to be constructed.  However, the MATLAB interface to
186% SuiteSparse:GraphBLAS is restricted to pre-defined types and operators:
187% a mere 13 types, 212 unary operators, 401 binary operators, 77 monoids,
188% 22 select operators (each of which can be used for all 13 types),
189% and 2,518 semirings.
190%
191% That gives you a lot of tools to create all kinds of interesting
192% graph algorithms.  For example:
193%
194%   GrB.bfs    % breadth-first search
195%   GrB.dnn    % sparse deep neural network (http://graphchallenge.org)
196%   GrB.mis    % maximal independent set
197%
198% See 'help GrB.binopinfo' for a list of the binary operators, and
199% 'help GrB.monoidinfo' for the ones that can be used as the additive
200% monoid in a semiring.  'help GrB.unopinfo' lists the unary operators.
201% 'help GrB.semiringinfo' describes the semirings.
202
203%%
204help GrB.binopinfo
205
206%%
207help GrB.monoidinfo
208
209%%
210help GrB.unopinfo
211
212%%
213help GrB.semiringinfo
214
215%% Element-wise operations
216% Binary operators can be used in element-wise matrix operations, like
217% C=A+B and C=A.*B.  For the matrix addition C=A+B, the pattern of C is
218% the set union of A and B, and the '+' operator is applied for entries
219% in the intersection.  Entries in A but not B, or in B but not A, are
220% assigned to C without using the operator.  The '+' operator is used for
221% C=A+B but any operator can be used with GrB.eadd.
222
223%%
224A = GrB (sprand (3, 3, 0.5)) ;
225B = GrB (sprand (3, 3, 0.5)) ;
226C1 = A + B
227C2 = GrB.eadd ('+', A, B)
228err = norm (C1-C2,1)
229
230%% Subtracting two matrices
231% A-B and GrB.eadd ('-', A, B) are not the same thing, since the '-'
232% operator is not applied to an entry that is in B but not A.
233
234C1 = A-B
235C2 = GrB.eadd ('-', A, B)
236
237%%
238% But these give the same result
239
240C1 = A-B
241C2 = GrB.eadd ('+', A, GrB.apply ('-', B))
242err = norm (C1-C2,1)
243
244%% Element-wise 'multiplication'
245% For C = A.*B, the result C is the set intersection of the pattern of A
246% and B.  The operator is applied to entries in both A and B.  Entries in
247% A but not B, or B but not A, do not appear in the result C.
248
249C1 = A.*B
250C2 = GrB.emult ('*', A, B)
251C3 = double (A) .* double (B)
252
253%%
254% Just as in GrB.eadd, any operator can be used in GrB.emult:
255
256A
257B
258C2 = GrB.emult ('max', A, B)
259
260%% Overloaded operators
261% The following operators all work as you would expect for any matrix.
262% The matrices A and B can be GraphBLAS matrices, or MATLAB sparse or
263% dense matrices, in any combination, or scalars where appropriate,
264% The matrix M is logical (MATLAB or GraphBLAS):
265%
266%    A+B   A-B  A*B   A.*B  A./B  A.\B  A.^b   A/b   C=A(I,J)  C(M)=A
267%    -A    +A   ~A    A'    A.'   A&B   A|B    b\A   C(I,J)=A  C=A(M)
268%    A~=B  A>B  A==B  A<=B  A>=B  A<B   [A,B]  [A;B] C(A)
269%    A(1:end,1:end)
270%
271% For A^b, b must be a non-negative integer.
272
273C1 = [A B] ;
274C2 = [double(A) double(B)] ;
275assert (isequal (double (C1), C2))
276
277%%
278C1 = A^2
279C2 = double (A)^2 ;
280err = norm (C1 - C2, 1)
281assert (err < 1e-12)
282
283%%
284C1 = A (1:2,2:end)
285A = double (A) ;
286C2 = A (1:2,2:end) ;
287assert (isequal (double (C1), C2))
288
289%% Overloaded functions
290% Many MATLAB built-in functions can be used with GraphBLAS matrices:
291%
292% A few differences with the built-in functions:
293%
294%   S = sparse (G)        % converts G to sparse/hypersparse
295%   F = full (G)          % adds explicit zeros, so numel(F)==nnz(F)
296%   F = full (G,type,id)  % adds explicit identity values to a GrB matrix
297%   disp (G, level)       % display a GrB matrix G; level=2 is the default.
298%
299% In the list below, the first set of Methods are overloaded built-in
300% methods.  They are used as-is on GraphBLAS matrices, such as C=abs(G).
301% The Static methods are prefixed with "GrB.", as in C = GrB.apply ( ... ).
302
303%%
304
305methods GrB
306
307%% Zeros are handled differently
308% Explicit zeros cannot be automatically dropped from a GraphBLAS matrix,
309% like they are in MATLAB sparse matrices.  In a shortest-path problem,
310% for example, an edge A(i,j) that is missing has an infinite weight,
311% (the monoid identity of min(x,y) is +inf).  A zero edge weight A(i,j)=0
312% is very different from an entry that is not present in A.  However, if
313% a GraphBLAS matrix is converted into a MATLAB sparse matrix, explicit
314% zeros are dropped, which is the convention for a MATLAB sparse matrix.
315% They can also be dropped from a GraphBLAS matrix using the GrB.select
316% method.
317
318%%
319
320G = GrB (magic (2)) ;
321G (1,1) = 0      % G(1,1) still appears as an explicit entry
322A = double (G)   % but it's dropped when converted to MATLAB sparse
323H = GrB.select ('nonzero', G)  % drops the explicit zeros from G
324fprintf ('nnz (G): %d  nnz (A): %g nnz (H): %g\n', ...
325    nnz (G), nnz (A), nnz (H)) ;
326fprintf ('num entries in G: %d\n', GrB.entries (G)) ;
327
328%% Displaying contents of a GraphBLAS matrix
329% Unlike MATLAB, the default is to display just a few entries of a GrB matrix.
330% Here are all 100 entries of a 10-by-10 matrix, using a non-default disp(G,3):
331
332%%
333G = GrB (rand (10)) ;
334% display everything:
335disp (G,3)
336
337%%
338% That was disp(G,3), so every entry was printed.  It's a little long, so
339% the default is not to print everything.
340
341%%
342% With the default display (level = 2):
343G
344
345%%
346% That was disp(G,2) or just display(G), which is what is printed by a
347% MATLAB statement that doesn't have a trailing semicolon.  With
348% level = 1, disp(G,1) gives just a terse summary:
349disp (G,1)
350
351%% Storing a matrix by row or by column
352% MATLAB stores its sparse matrices by column, refered to as 'sparse by
353% col' in SuiteSparse:GraphBLAS.  In the 'sparse by col' format, each
354% column of the matrix is stored as a list of entries, with their value
355% and row index.  In the 'sparse by row' format, each row is stored as a
356% list of values and their column indices.  GraphBLAS uses both 'by row'
357% and 'by col', and the two formats can be intermixed arbitrarily.  In
358% its C interface, the default format is 'by row'.  However, for better
359% compatibility with MATLAB, the SuiteSparse:GraphBLAS MATLAB interface
360% uses 'by col' by default instead.
361
362%%
363rng ('default') ;
364GrB.clear ;                      % clear prior GraphBLAS settings
365fprintf ('the default format is: %s\n', GrB.format) ;
366C = sparse (rand (2))
367G = GrB (C)
368GrB.format (G)
369
370%%
371% Many graph algorithms work better in 'by row' format, with matrices
372% stored by row.  For example, it is common to use A(i,j) for the edge
373% (i,j), and many graph algorithms need to access the out-adjacencies of
374% nodes, which is the row A(i,;) for node i.  If the 'by row' format is
375% desired, GrB.format ('by row') tells GraphBLAS to create all subsequent
376% matrices in the 'by row' format.  Converting from a MATLAB sparse matrix
377% (in standard 'by col' format) takes a little more time (requiring a
378% transpose), but subsequent graph algorithms can be faster.
379
380%%
381G = GrB (C, 'by row')
382fprintf ('the format of G is:    %s\n', GrB.format (G)) ;
383H = GrB (C)
384fprintf ('the format of H is:    %s\n', GrB.format (H)) ;
385err = norm (H-G,1)
386
387%% Hypersparse, sparse, bitmap, and full matrices
388% SuiteSparse:GraphBLAS can use four kinds of sparse matrix data
389% structures: hypersparse, sparse, bitmap, and full, in both 'by col' and
390% 'by row' formats, for a total of eight different combinations.  In the
391% 'sparse by col' that MATLAB uses for its sparse matrices, an m-by-n
392% matrix A takes O(n+nnz(A)) space.  MATLAB can create huge column
393% vectors, but not huge matrices (when n is huge).
394
395clear
396[c, huge] = computer ;
397C = sparse (huge, 1)    % MATLAB can create a huge-by-1 sparse column
398try
399    C = sparse (huge, huge)     % but this fails
400catch me
401    error_expected = me
402end
403
404%%
405% In a GraphBLAS hypersparse matrix, an m-by-n matrix A takes only
406% O(nnz(A)) space.  The difference can be huge if nnz (A) << n.
407
408clear
409[c, huge] = computer ;
410G = GrB (huge, 1)            % no problem for GraphBLAS
411H = GrB (huge, huge)         % this works in GraphBLAS too
412
413%%
414% Operations on huge hypersparse matrices are very fast; no component of
415% the time or space complexity is Omega(n).
416
417I = randperm (huge, 2) ;
418J = randperm (huge, 2) ;
419H (I,J) = magic (2) ;        % add 4 nonzeros to random locations in H
420H (I,I) = 10 * [1 2 ; 3 4] ; % so H^2 is not all zero
421H = H^2 ;                    % square H
422H = (H' * 2) ;               % transpose H and double the entries
423K = pi * spones (H) ;
424H = H + K                    % add pi to each entry in H
425
426%% numel uses vpa if the matrix is really huge
427e1 = numel (G)               % this is huge, but still a flint
428e2 = numel (H)               % this is huge^2, which needs vpa
429whos e1 e2
430
431%%
432% All of these matrices take very little memory space:
433whos C G H K
434
435%% The mask and accumulator
436% When not used in overloaded operators or built-in functions, many
437% GraphBLAS methods of the form GrB.method ( ... ) can optionally use a
438% mask and/or an accumulator operator.  If the accumulator is '+' in
439% GrB.mxm, for example, then C = C + A*B is computed.  The mask acts much
440% like logical indexing in MATLAB.  With a logical mask matrix M,
441% C<M>=A*B allows only part of C to be assigned.  If M(i,j) is true, then
442% C(i,j) can be modified.  If false, then C(i,j) is not modified.
443%
444% For example, to set all values in C that are greater than 0.5 to 3:
445
446%%
447A = rand (3)
448C = GrB.assign (A, A > 0.5, 3) ;     % in GraphBLAS
449C1 = GrB (A) ; C1 (A > .5) = 3       % also in GraphBLAS
450C2 = A       ; C2 (A > .5) = 3       % in MATLAB
451err = norm (C - C1, 1)
452err = norm (C - C2, 1)
453
454%% The descriptor
455% Most GraphBLAS functions of the form GrB.method ( ... ) take an optional
456% last argument, called the descriptor.  It is a MATLAB struct that can
457% modify the computations performed by the method.  'help
458% GrB.descriptorinfo' gives all the details.  The following is a short
459% summary of the primary settings:
460%
461% d.out  = 'default' or 'replace', clears C after the accum op is used.
462%
463% d.mask = 'default' or 'complement', to use M or ~M as the mask matrix;
464%          'structural', or 'structural complement', to use the pattern
465%           of M or ~M.
466%
467% d.in0  = 'default' or 'transpose', to transpose A for C=A*B, C=A+B, etc.
468%
469% d.in1  = 'default' or 'transpose', to transpose B for C=A*B, C=A+B, etc.
470%
471% d.kind = 'default', 'GrB', 'sparse', or 'full'; the output of GrB.method.
472
473A = sparse (rand (2)) ;
474B = sparse (rand (2)) ;
475C1 = A'*B ;
476C2 = GrB.mxm ('+.*', A, B, struct ('in0', 'transpose')) ;
477err = norm (C1-C2,1)
478
479%% Integer arithmetic is different in GraphBLAS
480% MATLAB supports integer arithmetic on its full matrices, using int8,
481% int16, int32, int64, uint8, uint16, uint32, or uint64 data types.  None
482% of these integer data types can be used to construct a MATLAB sparse
483% matrix, which can only be double, double complex, or logical.
484% Furthermore, C=A*B is not defined for integer types in MATLAB, except
485% when A and/or B are scalars.
486%
487% GraphBLAS supports all of those types for all of its matrices (hyper,
488% sparse, bitmap, or full).  All operations are supported, including C=A*B
489% when A or B are any integer type, in 1000s of semirings.
490%
491% However, integer arithmetic differs in GraphBLAS and MATLAB.  In MATLAB,
492% integer values saturate if they exceed their maximum value.  In
493% GraphBLAS, integer operators act in a modular fashion.  The latter is
494% essential when computing C=A*B over a semiring.  A saturating integer
495% operator cannot be used as a monoid since it is not associative.
496
497%%
498C = uint8 (magic (3)) ;
499G = GrB (C) ;
500C1 = C * 40
501C2 = G * uint8 (40)
502S = double (C1 < 255) ;
503assert (isequal (double (C1).*S, double (C2).*S))
504
505%% An example graph algorithm: breadth-first search
506% The breadth-first search of a graph finds all nodes reachable from the
507% source node, and their level, v.  v=GrB.bfs(A,s) or v=bfs_matlab(A,s)
508% compute the same thing, but GrB.bfs uses GraphBLAS matrices and
509% operations, while bfs_matlab uses pure MATLAB operations.  v is defined
510% as v(s) = 1 for the source node, v(i) = 2 for nodes adjacent to the
511% source, and so on.
512
513clear
514rng ('default') ;
515n = 1e5 ;
516A = logical (sprandn (n, n, 1e-3)) ;
517
518tic
519v1 = GrB.bfs (A, 1) ;
520gb_time = toc ;
521
522tic
523v2 = bfs_matlab (A, 1) ;
524matlab_time = toc ;
525
526assert (isequal (double (v1'), v2))
527fprintf ('\nnodes reached: %d of %d\n', nnz (v2), n) ;
528fprintf ('GraphBLAS time: %g sec\n', gb_time) ;
529fprintf ('MATLAB time:    %g sec\n', matlab_time) ;
530fprintf ('Speedup of GraphBLAS over MATLAB: %g\n', ...
531    matlab_time / gb_time) ;
532fprintf ('\n# of threads used by GraphBLAS: %d\n', GrB.threads) ;
533
534%% Example graph algorithm: Luby's method in GraphBLAS
535% The GrB.mis function is variant of Luby's randomized algorithm [Luby
536% 1985].  It is a parallel method for finding an maximal independent set
537% of nodes, where no two nodes are adjacent.  See the GraphBLAS/@GrB/mis.m
538% function for details.  The graph must be symmetric with a zero-free
539% diagonal, so A is symmetrized first and any diagonal entries are removed.
540
541A = GrB (A) ;
542A = GrB.offdiag (A|A') ;
543
544tic
545s = GrB.mis (A) ;
546toc
547fprintf ('# nodes in the graph: %g\n', size (A,1)) ;
548fprintf ('# edges: : %g\n', GrB.entries (A) / 2) ;
549fprintf ('size of maximal independent set found: %g\n', ...
550    full (double (sum (s)))) ;
551
552% make sure it's independent
553p = find (s) ;
554S = A (p,p) ;
555assert (GrB.entries (S) == 0)
556
557% make sure it's maximal
558notp = find (s == 0) ;
559S = A (notp, p) ;
560deg = GrB.vreduce ('+.int64', S) ;
561assert (logical (all (deg > 0)))
562
563%% Sparse deep neural network
564% The 2019 MIT GraphChallenge (see http://graphchallenge.org) is to solve
565% a set of large sparse deep neural network problems.  In this demo, the
566% MATLAB reference solution is compared with a solution using GraphBLAS,
567% for a randomly constructed neural network.  See the GrB.dnn and
568% dnn_matlab.m functions for details.
569
570clear
571rng ('default') ;
572nlayers = 16 ;
573nneurons = 4096 ;
574nfeatures = 30000 ;
575fprintf ('# layers:   %d\n', nlayers) ;
576fprintf ('# neurons:  %d\n', nneurons) ;
577fprintf ('# features: %d\n', nfeatures) ;
578fprintf ('# of threads used: %d\n', GrB.threads) ;
579
580tic
581Y0 = sprand (nfeatures, nneurons, 0.1) ;
582for layer = 1:nlayers
583    W {layer} = sprand (nneurons, nneurons, 0.01) * 0.2 ;
584    bias {layer} = -0.2 * ones (1, nneurons) ;
585end
586t_setup = toc ;
587fprintf ('construct problem time: %g sec\n', t_setup) ;
588
589% convert the problem from MATLAB to GraphBLAS
590t = tic ;
591[W_gb, bias_gb, Y0_gb] = dnn_mat2gb (W, bias, Y0) ;
592t = toc (t) ;
593fprintf ('setup time: %g sec\n', t) ;
594
595%% Solving the sparse deep neural network problem with GraphbLAS
596% Please wait ...
597
598tic
599Y1 = GrB.dnn (W_gb, bias_gb, Y0_gb) ;
600gb_time = toc ;
601fprintf ('total time in GraphBLAS: %g sec\n', gb_time) ;
602
603%% Solving the sparse deep neural network problem with MATLAB
604% Please wait ...
605
606tic
607Y2 = dnn_matlab (W, bias, Y0) ;
608matlab_time = toc ;
609fprintf ('total time in MATLAB:    %g sec\n', matlab_time) ;
610fprintf ('Speedup of GraphBLAS over MATLAB: %g\n', ...
611    matlab_time / gb_time) ;
612fprintf ('\n# of threads used by GraphBLAS: %d\n', GrB.threads) ;
613
614err = norm (Y1-Y2,1)
615
616%% For objects, GraphBLAS has better colon notation than MATLAB
617% The MATLAB notation C = A (start:inc:fini) is very handy, and
618% it works great if A is a MATLAB matrix.  But for objects like
619% the GraphBLAS matrix, MATLAB starts by creating the explicit
620% index vector I = start:inc:fini.  That's fine if the matrix is
621% modest in size, but GraphBLAS can construct huge matrices.
622% The problem is that 1:n cannot be explicitly constructed when n
623% is huge.
624%
625% The C API for GraphBLAS can represent the colon notation
626% start:inc:fini in an implicit manner, so it can do the indexing
627% without actually forming the explicit list I = start:inc:fini.
628% But there is no access to this method using the MATLAB notation
629% start:inc:fini.
630%
631% Thus, to compute C = A (start:inc:fini) for very huge matrices,
632% you need to use use a cell array to represent the colon notation,
633% as { start, inc, fini }, instead of start:inc:fini. See
634% 'help GrB.extract', 'help GrB.assign' for the functional form.
635% For the overloaded syntax C(I,J)=A and C=A(I,J), see
636% 'help GrB/subsasgn' and 'help GrB/subsref'.  The cell array
637% syntax isn't conventional, but it is far faster than the MATLAB
638% colon notation for objects, and takes far less memory when I is huge.
639
640%%
641n = 1e14 ;
642H = GrB (n, n) ;            % a huge empty matrix
643I = [1 1e9 1e12 1e14] ;
644M = magic (4)
645H (I,I) = M ;
646J = {1, 1e13} ;            % represents 1:1e13 colon notation
647C1 = H (J, J)              % computes C1 = H (1:e13,1:1e13)
648c = nonzeros (C1) ;
649m = nonzeros (M (1:3, 1:3)) ;
650assert (isequal (c, m)) ;
651
652%%
653try
654    % try to compute the same thing with colon
655    % notation (1:1e13), but this fails:
656    C2 = H (1:1e13, 1:1e13)
657catch me
658    error_expected = me
659end
660
661%% Iterative solvers work as-is
662% Many built-in functions work with GraphBLAS matrices unmodified.
663
664A = sparse (rand (4)) ;
665b = sparse (rand (4,1)) ;
666x = gmres (A,b)
667norm (A*x-b)
668x = gmres (GrB(A), GrB(b))
669norm (A*x-b)
670
671%% ... even in single precision
672x = gmres (GrB(A,'single'), GrB(b,'single'))
673norm (A*x-b)
674
675%%
676% Both of the following uses of minres (A,b) fail to converge because A
677% is not symmetric, as the method requires.  Both failures are correctly
678% reported, and both the MATLAB version and the GraphBLAS version return
679% the same incorrect vector x.
680
681x = minres (A, b)
682x = minres (GrB(A), GrB(b))
683
684%%
685% With a proper symmetric matrix
686
687A = A+A' ;
688x = minres (A, b)
689norm (A*x-b)
690x = minres (GrB(A), GrB(b))
691norm (A*x-b)
692
693%% Extreme performance differences between GraphBLAS and MATLAB.
694% The GraphBLAS operations used so far are perhaps 2x to 50x faster than
695% the corresponding MATLAB operations, depending on how many cores your
696% computer has.  To run a demo illustrating a 500x or more speedup versus
697% MATLAB, run this demo:
698%
699%    gbdemo2
700%
701% It will illustrate an assignment C(I,J)=A that can take under a second
702% in GraphBLAS but several minutes in MATLAB.  To make the comparsion
703% even more dramatic, try:
704%
705%    gbdemo2 (20000)
706%
707% assuming you have enough memory.
708
709%% Sparse logical indexing is much, much faster in GraphBLAS
710% The mask in GraphBLAS acts much like logical indexing in MATLAB, but it
711% is not quite the same.  MATLAB logical indexing takes the form:
712%
713%       C (M) = A (M)
714%
715% which computes the same thing as the GraphBLAS statement:
716%
717%       C = GrB.assign (C, M, A)
718%
719% The GrB.assign statement computes C(M)=A(M), and it is vastly faster
720% than C(M)=A(M) for MATLAB sparse matrices, even if the time to convert
721% the GrB matrix back to a MATLAB sparse matrix is included.
722%
723% GraphBLAS can also compute C(M)=A(M) using overloaded operators for
724% subsref and subsasgn, but C = GrB.assign (C, M, A) is a bit faster.
725%
726% Here are both methods in GraphBLAS (both are very fast).  Setting up:
727
728clear
729n = 4000 ;
730tic
731C = sprand (n, n, 0.1) ;
732A = 100 * sprand (n, n, 0.1) ;
733M = (C > 0.5) ;
734t_setup = toc ;
735fprintf ('nnz(C): %g, nnz(M): %g, nnz(A): %g\n', ...
736    nnz(C), nnz(M), nnz(A)) ;
737fprintf ('\nsetup time:     %g sec\n', t_setup) ;
738
739%% First method in GraphBLAS, with GrB.assign
740% Including the time to convert C1 from a GraphBLAS
741% matrix to a MATLAB sparse matrix:
742tic
743C1 = GrB.assign (C, M, A) ;
744C1 = double (C1) ;
745gb_time = toc ;
746fprintf ('\nGraphBLAS time: %g sec for GrB.assign\n', gb_time) ;
747
748%% Second method in GraphBLAS, with C(M)=A(M)
749% now using overloaded operators, also include the time to
750% convert back to a MATLAB sparse matrix, for good measure:
751A2 = GrB (A) ;
752C2 = GrB (C) ;
753tic
754C2 (M) = A2 (M) ;
755C2 = double (C2) ;
756gb_time2 = toc ;
757fprintf ('\nGraphBLAS time: %g sec for C(M)=A(M)\n', gb_time2) ;
758
759%% Now with MATLAB matrices, with C(M)=A(M)
760% Please wait, this will take about 10 minutes or so ...
761
762tic
763C (M) = A (M) ;
764matlab_time = toc ;
765
766fprintf ('\nGraphBLAS time: %g sec (GrB.assign)\n', gb_time) ;
767fprintf ('GraphBLAS time: %g sec (overloading)\n', gb_time2) ;
768fprintf ('MATLAB time:    %g sec\n', matlab_time) ;
769fprintf ('Speedup of GraphBLAS (overloading) over MATLAB: %g\n', ...
770    matlab_time / gb_time2) ;
771fprintf ('Speedup of GraphBLAS (GrB.assign)  over MATLAB: %g\n', ...
772    matlab_time / gb_time) ;
773fprintf ('\n# of threads used by GraphBLAS: %d\n', GrB.threads) ;
774
775assert (isequal (C1, C))
776assert (isequal (C2, C))
777fprintf ('Results of GrB and MATLAB match perfectly.\n')
778
779%% Limitations and their future solutions
780% The MATLAB interface for SuiteSparse:GraphBLAS is a work-in-progress.
781% It has some limitations, most of which will be resolved over time.
782%
783% (1) Nonblocking mode:
784%
785% GraphBLAS has a 'non-blocking' mode, in which operations can be left
786% pending and completed later.  SuiteSparse:GraphBLAS uses the
787% non-blocking mode to speed up a sequence of assignment operations, such
788% as C(I,J)=A.  However, in its MATLAB interface, this would require a
789% MATLAB mexFunction to modify its inputs.  That breaks the MATLAB API
790% standard, so it cannot be safely done.  As a result, using GraphBLAS
791% via its MATLAB interface can be slower than when using its C API.
792
793%%
794% (2) Integer element-wise operations:
795%
796% Integer operations in MATLAB saturate, so that uint8(255)+1 is 255.  To
797% allow for integer monoids, GraphBLAS uses modular arithmetic instead.
798% This is the only way that C=A*B can be defined for integer semirings.
799% However, saturating integer operators could be added in the future, so
800% that element- wise integer operations on GraphBLAS sparse integer
801% matrices could work just the same as their MATLAB counterparts.
802%
803% So in the future, you could perhaps write this, for both sparse and
804% dense integer matrices A and B:
805%
806%       C = GrB.eadd ('+saturate.int8', A, B)
807%
808% to compute the same thing as C=A+B in MATLAB for its full int8
809% matrices.  Note that MATLAB can do this only for dense integer
810% matrices, since it doesn't support sparse integer matrices.
811
812%%
813% (3) Faster methods:
814%
815% Most methods in this MATLAB interface are based on efficient parallel C
816% functions in GraphBLAS itself, and are typically as fast or faster than
817% the equivalent built-in operators and functions in MATLAB.
818%
819% There are few notable exceptions; these will be addressed in the future.
820% These include bandwidth, istriu, istril, isdiag, reshape, issymmetric,
821% and ishermitian, all of which should be faster in a future release.
822
823%%
824% Here is an example that illustrates the performance of istril.
825A = sparse (rand (2000)) ;
826tic
827c1 = istril (A) ;
828matlab_time = toc ;
829A = GrB (A) ;
830tic
831c2 = istril (A) ;
832gb_time = toc ;
833fprintf ('\nMATLAB: %g sec, GraphBLAS: %g sec\n', ...
834    matlab_time, gb_time) ;
835if (gb_time > matlab_time)
836    fprintf ('GraphBLAS is slower by a factor of %g\n', ...
837        gb_time / matlab_time) ;
838end
839
840%%
841% (4) Linear indexing:
842%
843% If A is an m-by-n 2D MATLAB matrix, with n > 1, A(:) is a column vector
844% of length m*n.  The index operation A(i) accesses the ith entry in the
845% vector A(:).  This is called linear indexing in MATLAB.  It is not yet
846% available for GraphBLAS matrices in this MATLAB interface to GraphBLAS,
847% but will be added in the future.
848
849%%
850% (5) Implicit singleton dimension expansion
851%
852% In MATLAB C=A+B where A is m-by-n and B is a 1-by-n row vector
853% implicitly expands B to a matrix, computing C(i,j)=A(i,j)+B(j).  This
854% implicit expansion is not yet suported in GraphBLAS with C=A+B.
855% However, it can be done with C = GrB.mxm ('+.+', A, diag(GrB(B))).
856% That's a nice example of the power of semirings, but it's not
857% immediately obvious, and not as clear a syntax as C=A+B.  The
858% GraphBLAS/@GrB/dnn.m function uses this 'plus.plus' semiring to
859% apply the bias to each neuron.
860
861A = magic (3)
862B = 1000:1000:3000
863C1 = A + B
864C2 = GrB.mxm ('+.+', A, diag (GrB (B)))
865err = norm (C1-C2,1)
866
867%%
868% (6) MATLAB object overhead.
869%
870% The GrB matrix is a MATLAB object, and there are some cases where
871% performance issues can arise as a result.  Extracting the contents of
872% a MATLAB object (G.field) takes much more time than for a MATLAB struct
873% with % the same syntax, and building an object has similar issues.  The
874% difference is small, and it does not affect large problems.  But if you
875% have many calls to GrB operations with a small amount of work, then the
876% time can be dominated by the MATLAB object-oriented overhead.
877%
878% There is no solution or workaround to this issue.
879
880A = rand (3,4) ;
881G = GrB (A) ;
882tic
883for k = 1:100000
884    [m, n] = size (A) ;
885end
886toc
887tic
888for k = 1:100000
889    [m, n] = size (G) ;
890end
891toc
892
893%% GraphBLAS operations
894% In addition to the overloaded operators (such as C=A*B) and overloaded
895% functions (such as L=tril(A)), GraphBLAS also has methods of the form
896% GrB.method.  Most of them take an optional input matrix Cin, which is
897% the initial value of the matrix C for the expression below, an optional
898% mask matrix M, and an optional accumulator operator.
899%
900%   in GrB syntax:  C<#M,replace> = accum (C, A*B)
901%
902%   in @GrB MATLAB: C = GrB.mxm (Cin, M, accum, semiring, A, B, desc) ;
903%
904% In the above expression, #M is either empty (no mask), M (with a mask
905% matrix) or ~M (with a complemented mask matrix), as determined by the
906% descriptor (desc).  'replace' can be used to clear C after it is used
907% in accum(C,T) but before it is assigned with C<...> = Z, where
908% Z=accum(C,T).  The matrix T is the result of some operation, such as
909% T=A*B for GrB.mxm, or T=op(A,B) for GrB.eadd.
910%
911% For a complete list of GraphBLAS overloaded operators and methods, type:
912%
913%   help GrB
914%
915% Thanks for watching!
916%
917% Tim Davis, Texas A&M University, http://faculty.cse.tamu.edu/davis,
918% https://twitter.com/DocSparse
919
920