function [N,flag,bound] = nulls(A,k,tol) %NULLS Null space of a (possibly sparse) matrix. % % [N,flag,bound] = nulls(A,k,tol) attempts to compute the null space of A. % % k An initial estimate as to the dimension of the null space. % Defaults to 1 if ommitted. It is only a hint, which if % accurate, can speed up the computation (but not dramatically). % % tol A tolerance: a vector v is considered to be a null % vector if norm(A*v) < tol*norm(A,1). % Defaults to 1e-14 if ommitted. % % N A matrix whose columns are orthonormal and are null vectors of A. % % flag 0 if the null space was computed successfully. % 1 if the null space might be larger than the number of columns in N. % % bound If flag==0, this is the dimension of the null space of A. % If flag==1, this is an upper bound on the dimensio of the null space. % % The algorithm uses normalized inverse U iteration with condition % estimation and normalized inverse L1*U iteration. For futher details, % see Gotsman and Toledo, "On the computation of null spaces of sparse % rectangular matrices (manuscript, 2005). % % The main computational cost in this algorithm is the sparse LU % factorization of A. It is suitable for any matrix that Matlab can % factor in sparse mode. % % Author: Sivan Toledo % Version 1.0, July 2005 t0 = cputime; if ( issparse(A) == 0) A = sparse(A); end; [m n] = size(A); normA = norm(A,1); warning off MATLAB:nearlySingularMatrix if (nargin < 1) error('Not enough input arguments.'); end if (nargin < 2) k = 1; end if (nargin < 3) tol = 1e-14; end t1 = cputime; [L,U,P,COLPERM] = lu(A,1.0); for j=1:n INVPERM(find(COLPERM(:,j))) = j; end t2 = cputime; fprintf(1,'Factorization time in nulls is %.2f seconds\n',t2-t1); fprintf(1,'nulls: nnz(L) = %.01e, nnz(U) = %.01e\n',nnz(L),nnz(U)); % for the accuracy_u experiment %adu = abs(diag(U)); %global acc_u; %global t; %[acc_u(t,2) acc_u(t,3) ] = min(adu); % factorization_error_norm = norm( A - P'*L*U*COLPERM' , 1) / norm( A, 1 ) for i=1:n if (U(i,i)==0) % U(i,i)=eps*eps*normA; U(i,i)=1e-100; end end if (m > n) L1 = L(1:n,:); else L1 = L; end % factorization_error_norm = norm( A - P'*L*U*COLPERM' , 1) / norm( A, 1 ) %%% %%% first, iterate to find the null space of U %%% kin = k; kout = 0; while ( (kout < n) ) % disp('U iteration') Nu = normalizedinversetriangulariteration(U,kin); [Nu,kout] = filter(A,Nu,tol*normA); if (kout < kin) break; else kin = kin*2; end; end; dim_null_U = kout; %%% %%% now check whether L1 is ill conditioned %%% % disp('L1 iteration (for condition estimation)') Nl = normalizedinversetriangulariteration(L1,1); [Nl,kout] = filter(L1,Nl,tol*1.0); if (kout == 0) %%% %%% L1 is not ill conditioned, so we return the null space of U %%% N = Nu; flag = 0; bound = dim_null_U; fprintf(1,'nulls: case 1, null(A)==null(U), dim(null(A))==%d\n',bound); else %%% %%% L1 is ill conditioned, so we run inverse iteration on L1*U %%% fprintf(1,'nulls: L1 is ill conditioned\n'); kin = dim_null_U + 1; kout = 0; while ( (kout < n) ) % disp('LU iteration') Nlu = normalizedinverseLUiteration(L1,U,kin); [Nlu,kout] = filter2(L1,U,Nlu,tol*normA); if (kout < kin) break; else kin = kin*2; end; end; bound = kout %%% %%% There are two special cases were we can still determine the null space %%% if (bound == dim_null_U) % We got lucky % L1 was ill conditioned, but the null space of A is that of U N = Nu; flag = 0; fprintf(1,'nulls: case 2, null(A)==null(U), dim(null(A))==%d\n',bound); elseif (bound == 1 & norm( A*Nlu(:,1) ) > tol*normA) % if L1*U has one null vector, and it's not a null vector % of A, then A has no null vectors N = zeros(m,0); flag = 0; bound = 0; fprintf(1,'nulls: case 3, null(A)==null(L1*U), dim(null(A))==0\n'); else [N,R] = qr([ Nu Nlu ],0); [N,kout] = filter(A,N,tol*normA); kout_combined = kout if ( kout == bound ) flag = 0; fprintf(1,'nulls: case 4, found null(A), dim(null(A))==%d\n',bound); else flag = 1; fprintf(1,'nulls: case 5, %d <= dim(null(A)) <= %d\n',kout,bound); end; end; end; t3 = cputime; fprintf(1,'Total time in nulls is %.2f seconds\n',t3-t0); warning on MATLAB:nearlySingularMatrix %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % normalized inverse LU iteration % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function W = normalizedinverseLUiteration(L,U,k) function X = invLU(W) Tt = U' \ W; Q = L' \ Tt; Y = L\Q; X = U \ Y; end; if (0) opts.issymm = true; opts.isreal = true; [W,D] = eigs( @invLU, size(L,1), k, 'LM', opts); else % of if (1) W = rand(n,k); [W,R] = qr(W,0); for i=1:3; % iter = i % now we have an approximation W for the right singular vectors Tt = U' \ W; Q = L' \ Tt; [Q,R] = qr(Q,0); % we start with an approximation Q for the left singular vectors Y = L\Q; X = U \ Y; [W,R] = qr(X,0); end end; % of if (1) W = W(INVPERM,:); if (norm( isnan(W) + isinf(W) , 1 ) > 0) disp('nulls: ****************************************') disp('nulls: *** infs or nans in iverse iteration ***') disp('nulls: ****************************************') disp('nulls: *** infs or nans in iverse iteration ***') end; end; % of nested function %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % normalized inverse iteration % % for a triangular matrix Z % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function W = normalizedinversetriangulariteration(Z,k) function X = invZ(W) Q = Z' \ W; X = Z \ Q; end; if (0) opts.issymm = true; opts.isreal = true; [W,D] = eigs( @invZ, size(Z,1), k, 'LM', opts); else % of if (1) W = rand(n,k); [W,R] = qr(W,0); for i=1:3; % now we have an approximation W for the right singular vectors Q = Z' \ W; [Q,R] = qr(Q,0); % we start with an approximation Q for the left singular vectors X = Z \ Q; [W,R] = qr(X,0); end end; % of if (1) W = W(INVPERM,:); if (norm( isnan(W) + isinf(W) , 1 ) > 0) disp('nulls: ****************************************') disp('nulls: *** infs or nans in iverse iteration ***') disp('nulls: ****************************************') end; end; % of nested function %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % filter only the prefix columns of W that satisfy % % norm( Z*W(:,k) ) <= thresh % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function [Wout,kout] = filter(Z,W,thresh) [dummy k] = size(W); kout=0; while ((kout < k) & norm(Z*W(:,kout+1)) < thresh) kout = kout+1; end; Wout = W(:,1:kout); end; % of nested function %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % filter only the prefix columns of W that satisfy % % norm( Z*T**W(:,k) ) <= thresh % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function [Wout,kout] = filter2(Z,T,W,thresh) [dummy k] = size(W); kout=0; while ((kout < k) & norm(Z*(T*W(:,kout+1))) < thresh) kout = kout+1; end; Wout = W(:,1:kout); end; % of nested function end % of main function