function stats = UFstats (A, nometis, Z) %UFstats compute matrix statistics for the UF Sparse Matrix Collection % Example: % stats = UFstats (A,nometis,Z) % % A: a sparse matrix % nometis: if nonzero then metis(A,'col') is not used, nor is metis used in the % dmperm+ ordering. % Z: empty, or a sparse matrix the same size as A. Only used for psym and % nzero statistics, described below. % % Requires amd, cholmod, metis, RBio, and Csparse. Computes the following % statistics, returning them as fields in the stats struct: % % nrows number of rows % ncols number of columns % nnz number of entries in A % RBtype Rutherford/Boeing type % isBinary 1 if binary, 0 otherwise % isReal 1 if real, 0 if complex % cholcand 1 if a candidate for sparse Cholesky, 0 otherwise % nsym numeric symmetry (0 to 1, where 1=symmetric) % psym pattern symmetry (0 to 1, where 1=symmetric) % nnzdiag nnz (diag (A)) if A is square, 0 otherwise % nzero nnz (Z) % amd_lnz nnz(L) for chol(C(p,p)) where, C=A+A', p=amd(C) % amd_flops flop count for chol(C(p,p)) where, C=A+A', p=amd(C) % amd_vnz nnz in Householder vectors for qr(A(:,colamd(A))) % amd_rnz nnz in R for qr(A(:,colamd(A))) % metis_lnz nnz(L) for chol(C(p,p)) where, C=A+A', p=metis(C) % metis_flops flop count for chol(C(p,p)) where, C=A+A', p=metis(C) % metis_vnz nnz in Householder vectors for qr(A(:,metis(A,'col'))) % metis_rnz nnz in R for qr(A(:,metis(A,'col'))) % nblocks # of blocks from dmperm % sprank sprank(A) % nzoff # of entries not in diagonal blocks from dmperm % ncc # of strongly connected components % dmperm_lnz nnz(L), using dmperm plus amd or metis % dmperm_unz nnz(U), using dmperm plus amd or metis % dmperm_flops flop count with dperm plus % dmperm_vnz nnz in Householder vectors for dmperm plus % dmperm_rnz nnz in R for dmperm plus % posdef 1 if positive definite, 0 otherwise % % The *_lnz, *_unz, and *_flops statistics are not computed for rectangular % or structurally singular matrices. nzoff and the dmperm_* stats are not % computed for structurally singular matrices. If a statistic is not computed, % it is set to -2. If an attempt to compute the statistic was made but failed, % it is set to -1. % % See also UFget, UFindex, amd, metis, RBtype, cs_scc, cs_sqr, dmperm. % Copyright 2006, Timothy A. Davis % Requires the SuiteSparse set of packages: CHOLMOD, AMD, COLAMD, RBio, CSparse; % and METIS. if (nargin < 2) nometis = 0 ; end %------------------------------------------------------------------------------- % ensure the matrix is sparse %------------------------------------------------------------------------------- if (~issparse (A)) A = sparse (A) ; end %------------------------------------------------------------------------------- % basic stats %------------------------------------------------------------------------------- [m n] = size (A) ; stats.nrows = m ; stats.ncols = n ; stats.nnz = nnz (A) ; stats.RBtype = RBtype (A) ; % Rutherford/Boeing type stats.isBinary = (stats.RBtype (1) == 'p') ; stats.isReal = (stats.RBtype (1) ~= 'c') ; %------------------------------------------------------------------------------- % symmetry and Cholesky candidacy %------------------------------------------------------------------------------- % get the symmetry [s xmatched pmatched nzoffdiag nnzdiag] = spsym (A) ; stats.cholcand = (s >= 6) ; % check if Cholesky candidate if (m ~= n) stats.nsym = 0 ; stats.psym = 0 ; elseif (nzoffdiag > 0) stats.nsym = xmatched / nzoffdiag ; stats.psym = pmatched / nzoffdiag ; else stats.nsym = 1 ; stats.psym = 1 ; end stats.nnzdiag = nnzdiag ; if (nargin > 2) stats.nzero = nnz (Z) ; % recompute the pattern symmetry with Z included if (m == n) try AZ = A+Z ; if (nnz (AZ) ~= nnz (A) + nnz (Z)) error ('A and Z overlap!') end [s xmatched pmatched nzoffdiag] = spsym (AZ) ; clear AZ if (nzoffdiag > 0) stats.psym = pmatched / nzoffdiag ; else stats.psym = 1 ; end catch fprintf ('failed to compute symmetry of pattern of A+Z\n') ; end end else stats.nzero = 0 ; end %------------------------------------------------------------------------------- % intialize ordering statistics %------------------------------------------------------------------------------- % if square, Cholesky of C(p,p) where C=A+A', p = amd(C) stats.amd_lnz = -1 ; % nnz (chol (C)) stats.amd_flops = -1 ; % flop counts for chol (C) % if square or rectangular stats.amd_vnz = -1 ; % nnz (V), upper bound on L, for A(:,colamd(A)) stats.amd_rnz = -1 ; % nnz (R), upper bound on U, for A(:,colamd(A)) % if square, Cholesky of C(p,p) where C=A+A', p = metis(C) stats.metis_lnz = -1 ; % nnz (chol (C)) stats.metis_flops = -1 ; % flop counts for chol (C) % if square or rectangular stats.metis_vnz = -1 ; % nnz (V), upper bound on L, for A(:,metis(A)) stats.metis_rnz = -1 ; % nnz (R), upper bound on U, for A(:,metis(A)) % dmperm analysis stats.nblocks = -1 ; % # of blocks in block-triangular form stats.sprank = -1 ; % structural rank stats.nzoff = -1 ; % # of entries of A in off-diagonal blocks % cs_scc2 stats.ncc = -1 ; % # of strongly connected components % dmperm: best of amd/metis on each square block, best of colamd/metis % on rectangular blocks stats.dmperm_lnz = -1 ; % nnz (L), for square struct full rank matrices stats.dmperm_unz = -1 ; % nnz (U) + nzoff, for square struct full rank mat stats.dmperm_flops = -1 ; % Cholesky flop count of each square block stats.dmperm_vnz = -1 ; % nnz (V), upper bound on L stats.dmperm_rnz = -1 ; % nnz (R), upper bound on U d = max (m,n) ; % if the matrix has a symmetric nonzero pattern, nzoff will always be zero if (stats.psym == 1) stats.nzoff = 0 ; end %------------------------------------------------------------------------------- % determine if positive definite %------------------------------------------------------------------------------- if (~stats.cholcand) % not a candidate for Cholesky, so it cannot be positive definite stats.posdef = 0 ; else % try chol try [x, cstats] = cholmod (A, ones (stats.ncols,1)) ; rcond = cstats (1) ; fprintf ('rcond: %g\n', rcond) ; stats.posdef = (rcond > 0) ; catch % chol failed fprintf ('sparse Cholesky failed\n') ; stats.posdef = -1 ; end clear x cstats end %------------------------------------------------------------------------------- % transpose A if m < n, for ordering methods %------------------------------------------------------------------------------- if (m < n) try A = A' ; % A is now tall and thin, or square catch fprintf ('transpose failed...\n') ; return ; end [m n] = size (A) ; end if (~isreal (A)) try A = spones (A) ; catch fprintf ('conversion from complex failed...\n') ; return ; end end %------------------------------------------------------------------------------- % order entire matrix with AMD and METIS, if square %------------------------------------------------------------------------------- if (m == n) try if (stats.RBtype (2) == 'u') C = A|A' ; else C = A ; end catch fprintf ('A+A'' failed\n') ; end % order the whole matrix with AMD try p = amd (C) ; c = symbfact (C (p,p)) ; stats.amd_lnz = sum (c) ; stats.amd_flops = sum (c.^2) ; catch fprintf ('amd failed\n') ; end clear p c % order the whole matrix with METIS try p = metis (C) ; c = symbfact (C (p,p)) ; stats.metis_lnz = sum (c) ; stats.metis_flops = sum (c.^2) ; catch fprintf ('metis failed\n') ; end clear p c C else % not computed if rectangular stats.amd_lnz = -2 ; stats.amd_flops = -2 ; stats.metis_lnz = -2 ; stats.metis_flops = -2 ; end %------------------------------------------------------------------------------- % order entire matrix with COLAMD, for LU bounds %------------------------------------------------------------------------------- try % do not ignore any rows, and do not do etree postordering q = colamdmex (A, [d 10]) ; [vnz,rnz] = cs_sqr (A (:,q)) ; stats.amd_rnz = rnz ; stats.amd_vnz = vnz ; catch fprintf ('colamd failed\n') ; end clear q %------------------------------------------------------------------------------- % order entire matrix with METIS, for LU bounds %------------------------------------------------------------------------------- if (~nometis) try q = metis (A, 'col') ; [vnz,rnz] = cs_sqr (A (:,q)) ; stats.metis_rnz = rnz ; stats.metis_vnz = vnz ; catch fprintf ('metis(A''*A) failed\n') ; end end clear q %------------------------------------------------------------------------------- % strongly connected components %------------------------------------------------------------------------------- try % find the # of strongly connected components of the graph of a square A, % or # of connected components of the bipartite graph of a rectangular A. % [p,q,r,s] = cs_scc2 (A) ; if (m == n) [p r] = cs_scc (A) ; else [p r] = cs_scc (spaugment (A)) ; end stats.ncc = length (r) - 1 ; clear p r catch fprintf ('cs_scc failed\n') ; end %------------------------------------------------------------------------------- % Dulmage-Mendelsohn permutation, and order each block %------------------------------------------------------------------------------- try % find the Dulmage-Mendelsohn decomposition [p,q,r,s,cc,rr] = cs_dmperm (A) ; nblocks = length (r) - 1 ; stats.nblocks = nblocks ; stats.sprank = rr(4)-1 ; catch fprintf ('dmperm failed\n') ; end ok_square = 1 ; ok_vnz = 1 ; try mm = diff (r) ; nn = diff (s) ; square = all (mm == nn) ; if (~square) % not computed if the matrix is rectangular stats.dmperm_lnz = -2 ; stats.dmperm_unz = -2 ; stats.dmperm_flops = -2 ; end if (stats.sprank < min (m,n)) % do not report DMPERM results for structurally singular matrices stats.nzoff = -2 ; stats.dmperm_lnz = -2 ; stats.dmperm_unz = -2 ; stats.dmperm_flops = -2 ; stats.dmperm_vnz = -2 ; stats.dmperm_rnz = -2 ; elseif (nblocks == n && m == n) % square triangular or diagonal C = A (p,q) ; clear p q r s stats.nzoff = nnz (triu (C, 1)) ; stats.dmperm_lnz = n ; stats.dmperm_unz = n + stats.nzoff ; stats.dmperm_flops = n ; stats.dmperm_vnz = n ; stats.dmperm_rnz = nnz (C) ; elseif (nblocks == 1 && m == n) % only one block of structural full rank, so don't redo analysis clear p q r s stats.nzoff = 0 ; if (stats.metis_lnz < 0 || stats.amd_lnz < stats.metis_lnz) stats.dmperm_lnz = stats.amd_lnz ; stats.dmperm_unz = stats.amd_lnz ; stats.dmperm_flops = stats.amd_flops ; else stats.dmperm_lnz = stats.metis_lnz ; stats.dmperm_unz = stats.metis_lnz ; stats.dmperm_flops = stats.metis_flops ; end if (stats.metis_vnz < 0 || stats.amd_rnz < stats.metis_rnz) stats.dmperm_vnz = stats.amd_vnz ; stats.dmperm_rnz = stats.amd_rnz ; else stats.dmperm_vnz = stats.metis_vnz ; stats.dmperm_rnz = stats.metis_rnz ; end else % analyze each block of the permuted matrix C = A (p,q) ; clear p q nzoff = nnz (C) ; lnz = 0 ; unz = 0 ; flops = 0 ; vnz = 0 ; rnz = 0 ; for k = 1:nblocks i1 = r (k) ; i2 = r (k+1) - 1 ; j1 = s (k) ; j2 = s (k+1) - 1 ; if (i2-i1 == 1 && j2-j1 == 1) % singleton case nzoff = nzoff - 1 ; unz = unz + 1 ; lnz = lnz + 1 ; flops = flops + 1 ; rnz = rnz + 1 ; vnz = vnz + 1 ; continue ; end % get the kth block S = C (i1:i2, j1:j2) ; [ms ns] = size (S) ; nzoff = nzoff - nnz (S) ; if (ok_square) try if (square) % all blocks are square, analyze a square block % best of amd and metis if (nometis) [pblock c] = analyze (S|S', 'sym', 1) ; else [pblock c] = analyze (S|S', 'sym', 3) ; end lnzblock = sum (c) ; unz = unz + lnzblock ; lnz = lnz + lnzblock ; flops = flops + sum (c.^2) ; clear c pblock end catch % ordering failed, but keep going to compute nzoff ok_square = 0 ; end end if (ok_vnz) try % analyze a rectangular block, or LU bounds for square block if (ms < ns) S = S' ; end % best of amd and metis try if (nometis) pblock = analyze (S, 'col', 1) ; else pblock = analyze (S, 'col', 3) ; end catch pblock = colamdmex (S, [d 10]) ; end [vnz2,rnz2] = cs_sqr (S (:, pblock)) ; rnz = rnz + rnz2 ; vnz = vnz + vnz2 ; catch % ordering failed, but keep going to compute nzoff ok_vnz = 0 ; end end clear S pblock end stats.nzoff = nzoff ; if (ok_square) if (~square) stats.dmperm_unz = -2 ; stats.dmperm_lnz = -2 ; stats.dmperm_flops = -2 ; else stats.dmperm_lnz = lnz ; stats.dmperm_unz = unz + nzoff ; stats.dmperm_flops = flops ; end end if (ok_vnz) stats.dmperm_vnz = vnz ; stats.dmperm_rnz = rnz + nzoff ; end clear C r s end if (~ok_square) fprintf ('dmperm (square: lnz, unz, flops) ordering failed\n') ; end if (~ok_vnz) fprintf ('dmperm (LU bounds: vnz, rnz) ordering failed\n') ; end catch fprintf ('dmperm ordering and nzoff failed\n') ; end