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 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912
|
%% GraphBLAS: graph algorithms in the language of linear algebra
% GraphBLAS is a library for creating graph algorithms based on sparse
% linear algebraic operations over semirings. Visit http://graphblas.org
% for more details and resources. See also the SuiteSparse:GraphBLAS
% User Guide in this package.
%
% http://faculty.cse.tamu.edu/davis
%
% SuiteSparse:GraphBLAS, Timothy A. Davis, (c) 2017-2022, All Rights Reserved.
% SPDX-License-Identifier: Apache-2.0
%% GraphBLAS: faster and more general sparse matrices for MATLAB
% GraphBLAS is not only useful for creating graph algorithms; it also
% supports a wide range of sparse matrix data types and operations.
% MATLAB can compute C=A*B with just two semirings: 'plus.times.double'
% and 'plus.times.complex' for complex matrices. GraphBLAS has 2,518
% built-in semirings, such as 'max.plus'
% (https://en.wikipedia.org/wiki/Tropical_semiring). These semirings can
% be used to construct a wide variety of graph algorithms, based on
% operations on sparse adjacency matrices.
%
% MATLAB and GraphBLAS both provide sparse matrices of type double,
% logical, and double complex. GraphBLAS adds sparse matrices of type:
% single, int8, int16, int32, int64, uint8, uint16, uint32, uint64, and
% single complex (with MATLAB matrices, these types can only be held in
% full matrices).
% reset to the default number of threads
clear all
ncores = demo_nproc ;
GrB.clear ;
fprintf ('\n# of threads used by GraphBLAS: %d\n', GrB.threads) ;
format compact
rng ('default') ;
X = 100 * rand (2) ;
G = GrB (X) % GraphBLAS copy of a matrix X, same type
%% Sparse integer matrices
% Here's an int8 version of the same matrix:
S = int8 (G) % convert G to a full built-in int8 matrix
S (1,1) = 0 % add an explicit zero to S
G = GrB (X, 'int8') % a GraphBLAS full int8 matrix
G (1,1) = 0 % add an explicit zero to G
G = GrB.prune (G) % a GraphBLAS sparse int8 matrix
try
S = sparse (S) ; % built-in sparse matrices cannot be int8
catch me
display (me)
end
%% Sparse single-precision matrices
% Matrix operations in GraphBLAS are typically as fast, or faster than
% MATLAB. The following test is unfair, since it compares computing
% X*X with MATLAB in double precision and with GraphBLAS in single
% precision. You would naturally expect GraphBLAS to be faster.
%
% CAVEAT: MATLAB R2021a uses SuiteSparse:GraphBLAS v3.3.3 for C=A*B,
% so on that version of MATLAB, we're comparing 2 versions of GraphBLAS
% by the same author.
%
% Please wait ...
n = 1e5 ;
X = spdiags (rand (n, 201), -100:100, n, n) ;
G = GrB (X, 'single') ;
tic
G2 = G*G ;
gb_time = toc ;
tic
X2 = X*X ;
builtin_time = toc ;
fprintf ('\nGraphBLAS time: %g sec (in single)\n', gb_time) ;
fprintf ('%s time: %g sec (in double)\n', demo_whoami, builtin_time) ;
fprintf ('Speedup of GraphBLAS over %s: %g\n', ...
demo_whoami, builtin_time / gb_time) ;
fprintf ('\n# of threads used by GraphBLAS: %d\n', GrB.threads) ;
%% Mixing MATLAB and GraphBLAS matrices
% The error in the last computation is about eps('single') since
% GraphBLAS did its computation in single precision, while MATLAB used
% double precision. MATLAB and GraphBLAS matrices can be easily
% combined, as in X2-G2. The sparse single precision matrices take less
% memory space.
err = norm (X2 - G2, 1) / norm (X2,1)
eps ('single')
whos G G2 X X2
%% Faster matrix operations
% But even with standard double precision sparse matrices, GraphBLAS is
% typically faster than the built-in MATLAB methods. Here's a fair
% way to compare (caveat: these both use GraphBLAS in MATLAB R2021a):
G = GrB (X) ;
tic
G2 = G*G ;
gb_time = toc ;
err = norm (X2 - G2, 1) / norm (X2,1)
fprintf ('\nGraphBLAS time: %g sec (in double)\n', gb_time) ;
fprintf ('%s time: %g sec (in double)\n', demo_whoami, builtin_time) ;
fprintf ('Speedup of GraphBLAS over %s: %g\n', ...
demo_whoami, builtin_time / gb_time) ;
fprintf ('\n# of threads used by GraphBLAS: %d\n', GrB.threads) ;
%% A wide range of semirings
% MATLAB can only compute C=A*B using the standard '+.*.double' and
% '+.*.complex' semirings. A semiring is defined in terms of a string,
% 'add.mult.type', where 'add' is a monoid that takes the place of the
% additive operator, 'mult' is the multiplicative operator, and 'type' is
% the data type for the two inputs to the mult operator.
%
% In the standard semiring, C=A*B is defined as:
%
% C(i,j) = sum (A(i,:).' .* B(:,j))
%
% using 'plus' as the monoid and 'times' as the multiplicative operator.
% But in a more general semiring, 'sum' can be any monoid, which is an
% associative and commutative operator that has an identity value. For
% example, in the 'max.plus' tropical algebra, C(i,j) for C=A*B is
% defined as:
%
% C(i,j) = max (A(i,:).' + B(:,j))
%%
% This can be computed in GraphBLAS with:
%
% C = GrB.mxm ('max.+', A, B)
n = 3 ;
A = rand (n) ;
B = rand (n) ;
C = zeros (n) ;
for i = 1:n
for j = 1:n
C(i,j) = max (A (i,:).' + B (:,j)) ;
end
end
C2 = GrB.mxm ('max.+', A, B) ;
fprintf ('\nerr = norm (C-C2,1) = %g\n', norm (C-C2,1)) ;
%% The max.plus tropical semiring
% Here are details of the "max.plus" tropical semiring. The identity
% value is -inf since max(x,-inf) = max (-inf,x) = x for any x.
% The identity for the conventional "plus.times" semiring is zero,
% since x+0 = 0+x = x for any x.
GrB.semiringinfo ('max.+.double') ;
%% A boolean semiring
% MATLAB cannot multiply two logical matrices. MATLAB R2019a converts
% them to double and uses the conventional +.*.double semiring instead.
% In GraphBLAS, this is the common Boolean 'or.and.logical' semiring,
% which is widely used in linear algebraic graph algorithms.
GrB.semiringinfo ('|.&.logical') ;
%%
clear
A = sparse (rand (3) > 0.5)
B = sparse (rand (3) > 0.2)
%%
try
% MATLAB R2019a does this by casting A and B to double
C1 = A*B
catch
% MATLAB R2018a throws an error
fprintf ('MATLAB R2019a required for C=A*B with logical\n') ;
fprintf ('matrices. Explicitly converting to double:\n') ;
C1 = double (A) * double (B)
end
C2 = GrB (A) * GrB (B)
%%
% Note that C1 is a MATLAB sparse double matrix, and contains non-binary
% values. C2 is a GraphBLAS logical matrix.
whos
GrB.type (C2)
%% GraphBLAS operators, monoids, and semirings
% The C interface for SuiteSparse:GraphBLAS allows for arbitrary types
% and operators to be constructed. However, the MATLAB interface to
% SuiteSparse:GraphBLAS is restricted to pre-defined types and operators:
% a mere 13 types, 212 unary operators, 401 binary operators, 77 monoids,
% 22 select operators (each of which can be used for all 13 types),
% and 2,518 semirings.
%
% That gives you a lot of tools to create all kinds of interesting
% graph algorithms. For example:
%
% GrB.dnn % sparse deep neural network (http://graphchallenge.org)
% GrB.mis % maximal independent set
%
% See 'help GrB.binopinfo' for a list of the binary operators, and
% 'help GrB.monoidinfo' for the ones that can be used as the additive
% monoid in a semiring. 'help GrB.unopinfo' lists the unary operators.
% 'help GrB.semiringinfo' describes the semirings.
%%
help GrB.binopinfo
%%
help GrB.monoidinfo
%%
help GrB.unopinfo
%%
help GrB.semiringinfo
%% Element-wise operations
% Binary operators can be used in element-wise matrix operations, like
% C=A+B and C=A.*B. For the matrix addition C=A+B, the pattern of C is
% the set union of A and B, and the '+' operator is applied for entries
% in the intersection. Entries in A but not B, or in B but not A, are
% assigned to C without using the operator. The '+' operator is used for
% C=A+B but any operator can be used with GrB.eadd.
%%
A = GrB (sprand (3, 3, 0.5)) ;
B = GrB (sprand (3, 3, 0.5)) ;
C1 = A + B
C2 = GrB.eadd ('+', A, B)
err = norm (C1-C2,1)
%% Subtracting two matrices
% A-B and GrB.eadd ('-', A, B) are not the same thing, since the '-'
% operator is not applied to an entry that is in B but not A.
C1 = A-B
C2 = GrB.eadd ('-', A, B)
%%
% But these give the same result. GrB.eunion applies the operator
% as op(alpha,B) when A(i,j) is not present but B(i,j) is, and
% as op(A,beta) when A(i,j) is present but B(i,j) is not.
% In this case, both alpha and beta are zero.
C1 = A-B
C2 = GrB.eadd ('+', A, GrB.apply ('-', B))
C3 = GrB.eunion ('-', A, 0, B, 0)
err = norm (C1-C2,1)
err = norm (C1-C3,1)
%% Element-wise 'multiplication'
% For C = A.*B, the result C is the set intersection of the pattern of A
% and B. The operator is applied to entries in both A and B. Entries in
% A but not B, or B but not A, do not appear in the result C.
C1 = A.*B
C2 = GrB.emult ('*', A, B)
C3 = double (A) .* double (B)
%%
% Just as in GrB.eadd and GrB.eunion, any operator can be used in GrB.emult:
A
B
C2 = GrB.emult ('max', A, B)
%% Overloaded operators
% The following operators all work as you would expect for any matrix.
% The matrices A and B can be GraphBLAS matrices, or MATLAB sparse or
% dense matrices, in any combination, or scalars where appropriate,
% The matrix M is logical (MATLAB or GraphBLAS):
%
% A+B A-B A*B A.*B A./B A.\B A.^b A/b C=A(I,J) C(M)=A
% -A +A ~A A' A.' A&B A|B b\A C(I,J)=A C=A(M)
% A~=B A>B A==B A<=B A>=B A<B [A,B] [A;B] C(A)
% A(1:end,1:end)
%
% For A^b, b must be a non-negative integer.
C1 = [A B] ;
C2 = [double(A) double(B)] ;
assert (isequal (double (C1), C2))
%%
C1 = A^2
C2 = double (A)^2 ;
err = norm (C1 - C2, 1)
assert (err < 1e-12)
%%
C1 = A (1:2,2:end)
A = double (A) ;
C2 = A (1:2,2:end) ;
assert (isequal (double (C1), C2))
%% Overloaded functions
% Many MATLAB built-in functions can be used with GraphBLAS matrices:
%
% A few differences with the built-in functions:
%
% S = sparse (G) % converts G to sparse/hypersparse
% F = full (G) % adds explicit zeros, so numel(F)==nnz(F)
% F = full (G,type,id) % adds explicit identity values to a GrB matrix
% disp (G, level) % display a GrB matrix G; level=2 is the default.
%
% In the list below, the first set of Methods are overloaded built-in
% methods. They are used as-is on GraphBLAS matrices, such as C=abs(G).
% The Static methods are prefixed with "GrB.", as in C = GrB.apply ( ... ).
%%
methods GrB
%% Zeros are handled differently
% Explicit zeros cannot be automatically dropped from a GraphBLAS matrix,
% like they are in MATLAB sparse matrices. In a shortest-path problem,
% for example, an edge A(i,j) that is missing has an infinite weight,
% (the monoid identity of min(x,y) is +inf). A zero edge weight A(i,j)=0
% is very different from an entry that is not present in A. However, if
% a GraphBLAS matrix is converted into a MATLAB sparse matrix, explicit
% zeros are dropped, which is the convention for a MATLAB sparse matrix.
% They can also be dropped from a GraphBLAS matrix using the GrB.select
% method.
%%
G = GrB (magic (2)) ;
G (1,1) = 0 % G(1,1) still appears as an explicit entry
A = double (G) % but it's dropped when converted to MATLAB sparse
H = GrB.select ('nonzero', G) % drops the explicit zeros from G
fprintf ('nnz (G): %d nnz (A): %g nnz (H): %g\n', ...
nnz (G), nnz (A), nnz (H)) ;
fprintf ('num entries in G: %d\n', GrB.entries (G)) ;
%% Displaying contents of a GraphBLAS matrix
% Unlike MATLAB, the default is to display just a few entries of a GrB matrix.
% Here are all 100 entries of a 10-by-10 matrix, using a non-default disp(G,3):
%%
G = GrB (rand (10)) ;
% display everything:
disp (G,3)
%%
% That was disp(G,3), so every entry was printed. It's a little long, so
% the default is not to print everything.
%%
% With the default display (level = 2):
G
%%
% That was disp(G,2) or just display(G), which is what is printed by a
% MATLAB statement that doesn't have a trailing semicolon. With
% level = 1, disp(G,1) gives just a terse summary:
disp (G,1)
%% Storing a matrix by row or by column
% MATLAB stores its sparse matrices by column, refered to as 'sparse by
% col' in SuiteSparse:GraphBLAS. In the 'sparse by col' format, each
% column of the matrix is stored as a list of entries, with their value
% and row index. In the 'sparse by row' format, each row is stored as a
% list of values and their column indices. GraphBLAS uses both 'by row'
% and 'by col', and the two formats can be intermixed arbitrarily. In
% its C interface, the default format is 'by row'. However, for better
% compatibility with MATLAB, the SuiteSparse:GraphBLAS MATLAB interface
% uses 'by col' by default instead.
%%
rng ('default') ;
GrB.clear ; % clear prior GraphBLAS settings
fprintf ('the default format is: %s\n', GrB.format) ;
C = sparse (rand (2))
G = GrB (C)
GrB.format (G)
%%
% Many graph algorithms work better in 'by row' format, with matrices
% stored by row. For example, it is common to use A(i,j) for the edge
% (i,j), and many graph algorithms need to access the out-adjacencies of
% nodes, which is the row A(i,;) for node i. If the 'by row' format is
% desired, GrB.format ('by row') tells GraphBLAS to create all subsequent
% matrices in the 'by row' format. Converting from a MATLAB sparse matrix
% (in standard 'by col' format) takes a little more time (requiring a
% transpose), but subsequent graph algorithms can be faster.
%%
G = GrB (C, 'by row')
fprintf ('the format of G is: %s\n', GrB.format (G)) ;
H = GrB (C)
fprintf ('the format of H is: %s\n', GrB.format (H)) ;
err = norm (H-G,1)
%% Hypersparse, sparse, bitmap, and full matrices
% SuiteSparse:GraphBLAS can use four kinds of sparse matrix data
% structures: hypersparse, sparse, bitmap, and full, in both 'by col' and
% 'by row' formats, for a total of eight different combinations. In the
% 'sparse by col' that MATLAB uses for its sparse matrices, an m-by-n
% matrix A takes O(n+nnz(A)) space. MATLAB can create huge column
% vectors, but not huge matrices (when n is huge).
clear
huge = 2^48 - 1 ;
C = sparse (huge, 1) % MATLAB can create a huge-by-1 sparse column
try
C = sparse (huge, huge) % but this fails
catch me
error_expected = me
end
%%
% In a GraphBLAS hypersparse matrix, an m-by-n matrix A takes only
% O(nnz(A)) space. The difference can be huge if nnz (A) << n.
clear
huge = 2^48 - 1 ;
G = GrB (huge, 1) % no problem for GraphBLAS
H = GrB (huge, huge) % this works in GraphBLAS too
%%
% Operations on huge hypersparse matrices are very fast; no component of
% the time or space complexity is Omega(n).
I = randperm (huge, 2) ;
J = randperm (huge, 2) ;
H (I,J) = magic (2) ; % add 4 nonzeros to random locations in H
H (I,I) = 10 * [1 2 ; 3 4] ; % so H^2 is not all zero
H = H^2 ; % square H
H = (H' * 2) ; % transpose H and double the entries
K = pi * spones (H) ;
H = H + K % add pi to each entry in H
%% numel uses vpa if the matrix is really huge
e1 = numel (G) % this is huge, but still a flint
e2 = numel (H) % this is huge^2, which needs vpa
whos e1 e2
%%
% All of these matrices take very little memory space:
whos C G H K
%% The mask and accumulator
% When not used in overloaded operators or built-in functions, many
% GraphBLAS methods of the form GrB.method ( ... ) can optionally use a
% mask and/or an accumulator operator. If the accumulator is '+' in
% GrB.mxm, for example, then C = C + A*B is computed. The mask acts much
% like logical indexing in MATLAB. With a logical mask matrix M,
% C<M>=A*B allows only part of C to be assigned. If M(i,j) is true, then
% C(i,j) can be modified. If false, then C(i,j) is not modified.
%
% For example, to set all values in C that are greater than 0.5 to 3:
%%
A = rand (3)
C = GrB.assign (A, A > 0.5, 3) ; % in GraphBLAS
C1 = GrB (A) ; C1 (A > .5) = 3 % also in GraphBLAS
C2 = A ; C2 (A > .5) = 3 % in MATLAB
err = norm (C - C1, 1)
err = norm (C - C2, 1)
%% The descriptor
% Most GraphBLAS functions of the form GrB.method ( ... ) take an optional
% last argument, called the descriptor. It is a MATLAB struct that can
% modify the computations performed by the method. 'help
% GrB.descriptorinfo' gives all the details. The following is a short
% summary of the primary settings:
%
% d.out = 'default' or 'replace', clears C after the accum op is used.
%
% d.mask = 'default' or 'complement', to use M or ~M as the mask matrix;
% 'structural', or 'structural complement', to use the pattern
% of M or ~M.
%
% d.in0 = 'default' or 'transpose', to transpose A for C=A*B, C=A+B, etc.
%
% d.in1 = 'default' or 'transpose', to transpose B for C=A*B, C=A+B, etc.
%
% d.kind = 'default', 'GrB', 'sparse', or 'full'; the output of GrB.method.
A = sparse (rand (2)) ;
B = sparse (rand (2)) ;
C1 = A'*B ;
C2 = GrB.mxm ('+.*', A, B, struct ('in0', 'transpose')) ;
err = norm (C1-C2,1)
%% Integer arithmetic is different in GraphBLAS
% MATLAB supports integer arithmetic on its full matrices, using int8,
% int16, int32, int64, uint8, uint16, uint32, or uint64 data types. None
% of these integer data types can be used to construct a MATLAB sparse
% matrix, which can only be double, double complex, or logical.
% Furthermore, C=A*B is not defined for integer types in MATLAB, except
% when A and/or B are scalars.
%
% GraphBLAS supports all of those types for all of its matrices (hyper,
% sparse, bitmap, or full). All operations are supported, including C=A*B
% when A or B are any integer type, in 1000s of semirings.
%
% However, integer arithmetic differs in GraphBLAS and MATLAB. In MATLAB,
% integer values saturate if they exceed their maximum value. In
% GraphBLAS, integer operators act in a modular fashion. The latter is
% essential when computing C=A*B over a semiring. A saturating integer
% operator cannot be used as a monoid since it is not associative.
%%
C = uint8 (magic (3)) ;
G = GrB (C) ;
C1 = C * 40
C2 = G * uint8 (40)
S = double (C1 < 255) ;
assert (isequal (double (C1).*S, double (C2).*S))
%% Example graph algorithm: Luby's method in GraphBLAS
% The GrB.mis function is variant of Luby's randomized algorithm [Luby
% 1985]. It is a parallel method for finding an maximal independent set
% of nodes, where no two nodes are adjacent. See the GraphBLAS/@GrB/mis.m
% function for details. The graph must be symmetric with a zero-free
% diagonal, so A is symmetrized first and any diagonal entries are removed.
A = GrB (A) ;
A = GrB.offdiag (A|A') ;
tic
s = GrB.mis (A) ;
toc
fprintf ('# nodes in the graph: %g\n', size (A,1)) ;
fprintf ('# edges: : %g\n', GrB.entries (A) / 2) ;
fprintf ('size of maximal independent set found: %g\n', ...
full (double (sum (s)))) ;
% make sure it's independent
p = find (s) ;
S = A (p,p) ;
assert (GrB.entries (S) == 0)
% make sure it's maximal
notp = find (s == 0) ;
S = A (notp, p) ;
deg = GrB.vreduce ('+.int64', S) ;
assert (logical (all (deg > 0)))
%% Sparse deep neural network
% The 2019 MIT GraphChallenge (see http://graphchallenge.org) is to solve
% a set of large sparse deep neural network problems. In this demo, the
% MATLAB reference solution is compared with a solution using GraphBLAS,
% for a randomly constructed neural network. See the GrB.dnn and
% dnn_builtin.m functions for details.
clear
rng ('default') ;
nlayers = 16 ;
nneurons = 4096 ;
nfeatures = 30000 ;
fprintf ('# layers: %d\n', nlayers) ;
fprintf ('# neurons: %d\n', nneurons) ;
fprintf ('# features: %d\n', nfeatures) ;
fprintf ('# of threads used: %d\n', GrB.threads) ;
tic
Y0 = sprand (nfeatures, nneurons, 0.1) ;
for layer = 1:nlayers
W {layer} = sprand (nneurons, nneurons, 0.01) * 0.2 ;
bias {layer} = -0.2 * ones (1, nneurons) ;
end
t_setup = toc ;
fprintf ('construct problem time: %g sec\n', t_setup) ;
% convert the problem from MATLAB to GraphBLAS
t = tic ;
[W_gb, bias_gb, Y0_gb] = dnn_builtin2gb (W, bias, Y0) ;
t = toc (t) ;
fprintf ('setup time: %g sec\n', t) ;
%% Solving the sparse deep neural network problem with GraphbLAS
% Please wait ...
tic
Y1 = GrB.dnn (W_gb, bias_gb, Y0_gb) ;
gb_time = toc ;
fprintf ('total time in GraphBLAS: %g sec\n', gb_time) ;
%% Solving the sparse deep neural network problem with MATLAB
% Please wait ...
tic
Y2 = dnn_builtin (W, bias, Y0) ;
builtin_time = toc ;
fprintf ('total time in %s: %g sec\n', demo_whoami, builtin_time) ;
fprintf ('Speedup of GraphBLAS over %s: %g\n', ...
demo_whoami, builtin_time / gb_time) ;
fprintf ('\n# of threads used by GraphBLAS: %d\n', GrB.threads) ;
err = norm (Y1-Y2,1)
%% For objects, GraphBLAS has better colon notation than MATLAB
% The MATLAB notation C = A (start:inc:fini) is very handy, and
% it works great if A is a MATLAB matrix. But for objects like
% the GraphBLAS matrix, MATLAB starts by creating the explicit
% index vector I = start:inc:fini. That's fine if the matrix is
% modest in size, but GraphBLAS can construct huge matrices.
% The problem is that 1:n cannot be explicitly constructed when n
% is huge.
%
% The C API for GraphBLAS can represent the colon notation
% start:inc:fini in an implicit manner, so it can do the indexing
% without actually forming the explicit list I = start:inc:fini.
% But there is no access to this method using the MATLAB notation
% start:inc:fini.
%
% Thus, to compute C = A (start:inc:fini) for very huge matrices,
% you need to use use a cell array to represent the colon notation,
% as { start, inc, fini }, instead of start:inc:fini. See
% 'help GrB.extract', 'help GrB.assign' for the functional form.
% For the overloaded syntax C(I,J)=A and C=A(I,J), see
% 'help GrB/subsasgn' and 'help GrB/subsref'. The cell array
% syntax isn't conventional, but it is far faster than the MATLAB
% colon notation for objects, and takes far less memory when I is huge.
%%
n = 1e14 ;
H = GrB (n, n) ; % a huge empty matrix
I = [1 1e9 1e12 1e14] ;
M = magic (4)
H (I,I) = M ;
J = {1, 1e13} ; % represents 1:1e13 colon notation
C1 = H (J, J) % computes C1 = H (1:e13,1:1e13)
c = nonzeros (C1) ;
m = nonzeros (M (1:3, 1:3)) ;
assert (isequal (c, m)) ;
%%
try
% try to compute the same thing with colon
% notation (1:1e13), but this fails:
C2 = H (1:1e13, 1:1e13)
catch me
error_expected = me
end
%% Iterative solvers work as-is
% Many built-in functions work with GraphBLAS matrices unmodified.
if (~demo_octave)
% Octave7: gmres does not yet work for @GrB input matrices
A = sparse (rand (4)) ;
b = sparse (rand (4,1)) ;
x = gmres (A,b)
norm (A*x-b)
x = gmres (GrB(A), GrB(b))
norm (A*x-b)
end
%% ... even in single precision
if (~demo_octave)
% Octave7: gmres does not yet work for @GrB input matrices
x = gmres (GrB(A,'single'), GrB(b,'single'))
norm (A*x-b)
end
%%
% Both of the following uses of minres (A,b) fail to converge because A
% is not symmetric, as the method requires. Both failures are correctly
% reported, and both the MATLAB version and the GraphBLAS version return
% the same incorrect vector x.
if (exist ('minres'))
x = minres (A, b)
x = minres (GrB(A), GrB(b))
end
%%
% With a proper symmetric matrix
if (exist ('minres'))
A = A+A' ;
x = minres (A, b)
norm (A*x-b)
x = minres (GrB(A), GrB(b))
norm (A*x-b)
end
%% Extreme performance differences between GraphBLAS and MATLAB.
% The GraphBLAS operations used so far are perhaps 2x to 50x faster than
% the corresponding MATLAB operations, depending on how many cores your
% computer has. To run a demo illustrating a 500x or more speedup versus
% MATLAB, run this demo:
%
% gbdemo2
%
% It will illustrate an assignment C(I,J)=A that can take under a second
% in GraphBLAS but several minutes in MATLAB. To make the comparsion
% even more dramatic, try:
%
% gbdemo2 (20000)
%
% assuming you have enough memory.
%% Sparse logical indexing is much, much faster in GraphBLAS
% The mask in GraphBLAS acts much like logical indexing in MATLAB, but it
% is not quite the same. MATLAB logical indexing takes the form:
%
% C (M) = A (M)
%
% which computes the same thing as the GraphBLAS statement:
%
% C = GrB.assign (C, M, A)
%
% The GrB.assign statement computes C(M)=A(M), and it is vastly faster
% than C(M)=A(M) for MATLAB sparse matrices, even if the time to convert
% the GrB matrix back to a MATLAB sparse matrix is included.
%
% GraphBLAS can also compute C(M)=A(M) using overloaded operators for
% subsref and subsasgn, but C = GrB.assign (C, M, A) is a bit faster.
%
% Here are both methods in GraphBLAS (both are very fast). Setting up:
clear
n = 4000 ;
tic
C = sprand (n, n, 0.1) ;
A = 100 * sprand (n, n, 0.1) ;
M = (C > 0.5) ;
t_setup = toc ;
fprintf ('nnz(C): %g, nnz(M): %g, nnz(A): %g\n', ...
nnz(C), nnz(M), nnz(A)) ;
fprintf ('\nsetup time: %g sec\n', t_setup) ;
%% First method in GraphBLAS, with GrB.assign
% Including the time to convert C1 from a GraphBLAS
% matrix to a MATLAB sparse matrix:
tic
C1 = GrB.assign (C, M, A) ;
C1 = double (C1) ;
gb_time = toc ;
fprintf ('\nGraphBLAS time: %g sec for GrB.assign\n', gb_time) ;
%% Second method in GraphBLAS, with C(M)=A(M)
% now using overloaded operators, also include the time to
% convert back to a MATLAB sparse matrix, for good measure:
A2 = GrB (A) ;
C2 = GrB (C) ;
tic
C2 (M) = A2 (M) ;
C2 = double (C2) ;
gb_time2 = toc ;
fprintf ('\nGraphBLAS time: %g sec for C(M)=A(M)\n', gb_time2) ;
%% Now with MATLAB matrices, with C(M)=A(M)
% Please wait, this will take about 10 minutes or so ...
tic
C (M) = A (M) ;
builtin_time = toc ;
fprintf ('\nGraphBLAS time: %g sec (GrB.assign)\n', gb_time) ;
fprintf ('GraphBLAS time: %g sec (overloading)\n', gb_time2) ;
fprintf ('MATLAB time: %g sec\n', builtin_time) ;
fprintf ('Speedup of GraphBLAS (overloading) over %s: %g\n', ...
demo_whoami, builtin_time / gb_time2) ;
fprintf ('Speedup of GraphBLAS (GrB.assign) over %s: %g\n', ...
demo_whoami, builtin_time / gb_time) ;
fprintf ('\n# of threads used by GraphBLAS: %d\n', GrB.threads) ;
assert (isequal (C1, C))
assert (isequal (C2, C))
fprintf ('Results of GrB and %s match perfectly.\n', demo_whoami)
%% Even more extreme performance differences for C(M)=A(M)
%
% See the tmask script in this GraphBLAS/demo folder for a test comparing
% C(M)=A(M) for a sequence of large matrices. The test takes far to long
% to run in this demo, because MATLAB is exceedingly slow. Below are
% the results on a 4-core Dell laptop:
%
% n nnz(C) nnz(M) GraphBLAS MATLAB speedup
% (sec) (sec)
% 2,048 20,432 2,048 0.005 0.024 4.7
% 4,096 40,908 4,096 0.003 0.115 39
% 8,192 81,876 8,191 0.009 0.594 68
% 16,384 163,789 16,384 0.009 2.53 273
% 32,768 327,633 32,767 0.014 12.4 864
% 65,536 655,309 65,536 0.025 65.9 2,617
% 131,072 1,310,677 131,070 0.055 276.2 4,986
% 262,144 2,621,396 262,142 0.071 1,077. 15,172
% 524,288 5,242,830 524,288 0.114 5,855. 51,274
% 1,048,576 10,485,713 1,048,576 0.197 27,196. 137,776
% 2,097,152 20,971,475 2,097,152 0.406 100,799. 248,200
% 4,194,304 41,942,995 4,194,304 0.855 ~4 days? 500,000?
%
% The last run for MATLAB never finished but I estimate it would
% take about 4 to 5 days.
%% Limitations and their future solutions
% The MATLAB interface for SuiteSparse:GraphBLAS is a work-in-progress.
% It has some limitations, most of which will be resolved over time.
%
% (1) Nonblocking mode:
%
% GraphBLAS has a 'non-blocking' mode, in which operations can be left
% pending and completed later. SuiteSparse:GraphBLAS uses the
% non-blocking mode to speed up a sequence of assignment operations, such
% as C(I,J)=A. However, in its MATLAB interface, this would require a
% MATLAB mexFunction to modify its inputs. That breaks the MATLAB API
% standard, so it cannot be safely done. As a result, using GraphBLAS
% via its MATLAB interface can be slower than when using its C API.
%%
% (2) Integer element-wise operations:
%
% Integer operations in MATLAB saturate, so that uint8(255)+1 is 255. To
% allow for integer monoids, GraphBLAS uses modular arithmetic instead.
% This is the only way that C=A*B can be defined for integer semirings.
% However, saturating integer operators could be added in the future, so
% that element- wise integer operations on GraphBLAS sparse integer
% matrices could work just the same as their MATLAB counterparts.
%
% So in the future, you could perhaps write this, for both sparse and
% dense integer matrices A and B:
%
% C = GrB.eadd ('+saturate.int8', A, B)
%
% to compute the same thing as C=A+B in MATLAB for its full int8
% matrices. Note that MATLAB can do this only for dense integer
% matrices, since it doesn't support sparse integer matrices.
%%
% (3) Faster methods:
%
% Most methods in this MATLAB interface are based on efficient parallel C
% functions in GraphBLAS itself, and are typically as fast or faster than
% the equivalent built-in operators and functions in MATLAB.
%
% There are few notable exceptions; these will be addressed in the future.
% These include reshape, issymmetric, and ishermitian, all of which should
% be faster in a future release.
%%
% (4) Linear indexing:
%
% If A is an m-by-n 2D MATLAB matrix, with n > 1, A(:) is a column vector
% of length m*n. The index operation A(i) accesses the ith entry in the
% vector A(:). This is called linear indexing in MATLAB. It is not yet
% fully available for GraphBLAS matrices in this MATLAB interface to
% GraphBLAS, but will be added in the future.
%%
% (5) Implicit singleton dimension expansion
%
% In MATLAB C=A+B where A is m-by-n and B is a 1-by-n row vector
% implicitly expands B to a matrix, computing C(i,j)=A(i,j)+B(j). This
% implicit expansion is not yet supported in GraphBLAS with C=A+B.
% However, it can be done with C = GrB.mxm ('+.+', A, diag(GrB(B))).
% That's a nice example of the power of semirings, but it's not
% immediately obvious, and not as clear a syntax as C=A+B. The
% GraphBLAS/@GrB/dnn.m function uses this 'plus.plus' semiring to
% apply the bias to each neuron.
A = magic (3)
B = 1000:1000:3000
C1 = A + B
C2 = GrB.mxm ('+.+', A, diag (GrB (B)))
err = norm (C1-C2,1)
%%
% (6) MATLAB object overhead.
%
% The GrB matrix is a MATLAB object, and there are some cases where
% performance issues can arise as a result. Extracting the contents of
% a MATLAB object (G.field) takes much more time than for a MATLAB struct
% with % the same syntax, and building an object has similar issues. The
% difference is small, and it does not affect large problems. But if you
% have many calls to GrB operations with a small amount of work, then the
% time can be dominated by the MATLAB object-oriented overhead.
%
% There is no solution or workaround to this issue.
A = rand (3,4) ;
G = GrB (A) ;
tic
for k = 1:100000
[m, n] = size (A) ;
end
toc
tic
for k = 1:100000
[m, n] = size (G) ;
end
toc
%% GraphBLAS operations
% In addition to the overloaded operators (such as C=A*B) and overloaded
% functions (such as L=tril(A)), GraphBLAS also has methods of the form
% GrB.method. Most of them take an optional input matrix Cin, which is
% the initial value of the matrix C for the expression below, an optional
% mask matrix M, and an optional accumulator operator.
%
% in GrB syntax: C<#M,replace> = accum (C, A*B)
%
% in @GrB: C = GrB.mxm (Cin, M, accum, semiring, A, B, desc) ;
%
% In the above expression, #M is either empty (no mask), M (with a mask
% matrix) or ~M (with a complemented mask matrix), as determined by the
% descriptor (desc). 'replace' can be used to clear C after it is used
% in accum(C,T) but before it is assigned with C<...> = Z, where
% Z=accum(C,T). The matrix T is the result of some operation, such as
% T=A*B for GrB.mxm, or T=op(A,B) for GrB.eadd.
%
% For a complete list of GraphBLAS overloaded operators and methods, type:
%
% help GrB
%
% Thanks for watching!
%
% Tim Davis, Texas A&M University, http://faculty.cse.tamu.edu/davis,
% https://twitter.com/DocSparse
|