-
Notifications
You must be signed in to change notification settings - Fork 170
/
subspacea.m
319 lines (288 loc) · 9.84 KB
/
subspacea.m
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
function [theta,varargout] = subspacea(F,G,A)
%SUBSPACEA angles between subspaces
% subspacea(F,G,A)
% Finds all min(size(orth(F),2),size(orth(G),2)) principal angles
% between two subspaces spanned by the columns of matrices F and G
% in the A-based scalar product x'*A*y, where A
% is Hermitian and positive definite.
% COS of principal angles is called canonical correlations in statistics.
% [theta,U,V] = subspacea(F,G,A) also computes left and right
% principal (canonical) vectors - columns of U and V, respectively.
%
% If F and G are vectors of unit length and A=I,
% the angle is ACOS(F'*G) in exact arithmetic.
% If A is not provided as a third argument, than A=I and
% the function gives the same largest angle as SUBSPACE.m by Andrew Knyazev,
% see
% http://www.mathworks.com/matlabcentral/fileexchange/Files.jsp?type=category&id=&fileId=54
% MATLAB's SUBSPACE.m function is still badly designed and fails to compute
% some angles accurately.
%
% The optional parameter A is a Hermitian and positive definite matrix,
% or a corresponding function. When A is a function, it must accept a
% matrix as an argument.
% This code requires ORTHA.m, Revision 1.5.8 or above,
% which is included. The standard MATLAB version of ORTH.m
% is used for orthonormalization, but could be replaced by QR.m.
%
% Examples:
% F=rand(10,4); G=randn(10,6); theta = subspacea(F,G);
% computes 4 angles between F and G, while in addition
% A=hilb(10); [theta,U,V] = subspacea(F,G,A);
% computes angles relative to A and corresponding vectors U and V.
%
% The algorithm is described in A. V. Knyazev and M. E. Argentati,
% Principal Angles between Subspaces in an A-Based Scalar Product:
% Algorithms and Perturbation Estimates. SIAM Journal on Scientific Computing,
% 23 (2002), no. 6, 2009-2041.
% http://epubs.siam.org/sam-bin/dbq/article/37733
% Tested under MATLAB R10-14
% Copyright (c) 2000 Andrew Knyazev, Rico Argentati
% Contact email: [email protected]
% License: free software (BSD)
% $Revision: 4.5 $ $Date: 2005/6/27
threshold=sqrt(2)/2; % Define threshold for determining when an angle is small
if size(F,1) ~= size(G,1)
subspaceaError(['The row dimension ' int2str(size(F,1)) ...
' of the matrix F is not the same as ' int2str(size(G,1)) ...
' the row dimension of G'])
end
if nargin<3 % Compute angles using standard inner product
% Trivial column scaling first, if ORTH.m is used later
for i=1:size(F,2),
normi=norm(F(:,i),inf);
%Adjustment makes tol consistent with experimental results
if normi > eps^.981
F(:,i)=F(:,i)/normi;
% Else orth will take care of this
end
end
for i=1:size(G,2),
normi=norm(G(:,i),inf);
%Adjustment makes tol consistent with experimental results
if normi > eps^.981
G(:,i)=G(:,i)/normi;
% Else orth will take care of this
end
end
% Compute angle using standard inner product
QF = orth(F); %This can also be done using QR.m, in which case
QG = orth(G); %the column scaling above is not needed
q = min(size(QF,2),size(QG,2));
[Ys,s,Zs] = svd(QF'*QG,0);
if size(s,1)==1
% make sure s is column for output
s=s(1);
end
s = min(diag(s),1);
theta = max(acos(s),0);
U = QF*Ys;
V = QG*Zs;
indexsmall = s > threshold;
if max(indexsmall) % Check for small angles and recompute only small
RF = U(:,indexsmall);
RG = V(:,indexsmall);
%[Yx,x,Zx] = svd(RG-RF*(RF'*RG),0);
[Yx,x,Zx] = svd(RG-QF*(QF'*RG),0); % Provides more accurate results
if size(x,1)==1
% make sure x is column for output
x=x(1);
end
Tmp = fliplr(RG*Zx);
V(:,indexsmall) = Tmp(:,indexsmall);
U(:,indexsmall) = RF*(RF'*V(:,indexsmall))*...
diag(1./s(indexsmall));
x = diag(x);
thetasmall=flipud(max(asin(min(x,1)),0));
theta(indexsmall) = thetasmall(indexsmall);
end
% Compute angle using inner product relative to A
else
[m,n] = size(F);
if ~isstr(A)
[mA,mA] = size(A);
if any(size(A) ~= mA)
subspaceaError('Matrix A must be a square matrix or a string.')
end
if size(A) ~= m
subspaceaError(['The size ' int2str(size(A)) ...
' of the matrix A is not the same as ' int2str(m) ...
' - the number of rows of F'])
end
end
[QF,AQF]=ortha(A,F);
[QG,AQG]=ortha(A,G);
q = min(size(QF,2),size(QG,2));
[Ys,s,Zs] = svd(QF'*AQG,0);
if size(s,1)==1
% make sure s is column for output
s=s(1);
end
s=min(diag(s),1);
theta = max(acos(s),0);
U = QF*Ys;
V = QG*Zs;
indexsmall = s > threshold;
if max(indexsmall) % Check for small angles and recompute only small
RG = V(:,indexsmall);
AV = AQG*Zs;
ARG = AV(:,indexsmall);
RF = U(:,indexsmall);
%S=RG-RF*(RF'*(ARG));
S=RG-QF*(QF'*(ARG));% A bit more cost, but seems more accurate
% Normalize, so ortha would not delete wanted vectors
for i=1:size(S,2),
normSi=norm(S(:,i),inf);
%Adjustment makes tol consistent with experimental results
if normSi > eps^1.981
QS(:,i)=S(:,i)/normSi;
% Else ortha will take care of this
end
end
[QS,AQS]=ortha(A,QS);
[Yx,x,Zx] = svd(AQS'*S);
if size(x,1)==1
% make sure x is column for output
x=x(1);
end
x = max(diag(x),0);
Tmp = fliplr(RG*Zx);
ATmp = fliplr(ARG*Zx);
V(:,indexsmall) = Tmp(:,indexsmall);
AVindexsmall = ATmp(:,indexsmall);
U(:,indexsmall) = RF*(RF'*AVindexsmall)*...
diag(1./s(indexsmall));
thetasmall=flipud(max(asin(min(x,1)),0));
%Add zeros if necessary
if sum(indexsmall)-size(thetasmall,1)>0
thetasmall=[zeros(sum(indexsmall)-size(thetasmall,1),1)',...
thetasmall']';
end
theta(indexsmall) = thetasmall(indexsmall);
end
end
varargout(1)={U(:,1:q)};
varargout(2)={V(:,1:q)};
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [Q,varargout]=ortha(A,X)
%ORTHA Orthonormalization Relative to matrix A
% Q=ortha(A,X)
% Q=ortha('Afunc',X)
% Computes an orthonormal basis Q for the range of X, relative to the
% scalar product using a positive definite and selfadjoint matrix A.
% That is, Q'*A*Q = I, the columns of Q span the same space as
% columns of X, and rank(Q)=rank(X).
%
% [Q,AQ]=ortha(A,X) also gives AQ = A*Q.
%
% Required input arguments:
% A : either an m x m positive definite and selfadjoint matrix A
% or a linear operator A=A(v) that is positive definite selfadjoint;
% X : m x n matrix containing vectors to be orthonormalized relative
% to A.
%
% ortha(eye(m),X) spans the same space as orth(X)
%
% Examples:
% [q,Aq]=ortha(hilb(20),eye(20,5))
% computes 5 column-vectors q spanned by the first 5 coordinate vectors,
% and orthonormal with respect to the scalar product given by the
% 20x20 Hilbert matrix,
% while an attempt to orthogonalize (in the same scalar product)
% all 20 coordinate vectors using
% [q,Aq]=ortha(hilb(20),eye(20))
% gives 14 column-vectors out of 20.
% Note that rank(hilb(20)) = 13 in double precision.
%
% Algorithm:
% X=orth(X), [U,S,V]=SVD(X'*A*X), then Q=X*U*S^(-1/2)
% If A is ill conditioned an extra step is performed to
% improve the result. This extra step is performed only
% if a test indicates that the program is running on a
% machine that supports higher precison arithmetic
% (greater than 64 bit precision).
%
% See also ORTH, SVD
%
% Copyright (c) 2000 Andrew Knyazev, Rico Argentati
% Contact email: [email protected]
% License: free software (BSD)
% $Revision: 1.5.8 $ $Date: 2001/8/28
% Tested under MATLAB R10-12.1
% Check input parameter A
[m,n] = size(X);
if ~isstr(A)
[mA,mA] = size(A);
if any(size(A) ~= mA)
subspaceaError('Matrix A must be a square matrix or a string.')
end
if size(A) ~= m
subspaceaError(['The size ' int2str(size(A)) ...
' of the matrix A does not match with ' int2str(m) ...
' - the number of rows of X'])
end
end
% Normalize, so ORTH below would not delete wanted vectors
for i=1:size(X,2),
normXi=norm(X(:,i),inf);
%Adjustment makes tol consistent with experimental results
if normXi > eps^.981
X(:,i)=X(:,i)/normXi;
% Else orth will take care of this
end
end
% Make sure X is full rank and orthonormalize
X=orth(X); %This can also be done using QR.m, in which case
%the column scaling above is not needed
%Set tolerance
[m,n]=size(X);
tol=max(m,n)*eps;
% Compute an A-orthonormal basis
if ~isstr(A)
AX = A*X;
else
AX = feval(A,X);
end
XAX = X'*AX;
XAX = 0.5.*(XAX' + XAX);
[U,S,V]=svd(XAX);
if n>1 s=diag(S);
elseif n==1, s=S(1);
else s=0;
end
%Adjustment makes tol consistent with experimental results
threshold1=max(m,n)*max(s)*eps^1.1;
r=sum(s>threshold1);
s(r+1:size(s,1))=1;
S=diag(1./sqrt(s),0);
X=X*U*S;
AX=AX*U*S;
XAX = X'*AX;
% Check subspaceaError against tolerance
subspaceaError=normest(XAX(1:r,1:r)-eye(r));
% Check internal precision, e.g., 80bit FPU registers of P3/P4
precision_test=[1 eps/1024 -1]*[1 1 1]';
if subspaceaError<tol | precision_test==0;
Q=X(:,1:r);
varargout(1)={AX(:,1:r)};
return
end
% Complete another iteration to improve accuracy
% if this machine supports higher internal precision
if ~isstr(A)
AX = A*X;
else
AX = feval(A,X);
end
XAX = X'*AX;
XAX = 0.5.*(XAX' + XAX);
[U,S,V]=svd(XAX);
if n>1 s=diag(S);
elseif n==1, s=S(1);
else s=0;
end
threshold2=max(m,n)*max(s)*eps;
r=sum(s>threshold2);
S=diag(1./sqrt(s(1:r)),0);
Q=X*U(:,1:r)*S(1:r,1:r);
varargout(1)={AX*U(:,1:r)*S(1:r,1:r)};