File Coverage

lib/PDL/MatrixOps/svd.c
Criterion Covered Total %
statement 41 41 100.0
branch 30 32 93.7
condition n/a
subroutine n/a
pod n/a
total 71 73 97.2


line stmt bran cond sub pod time code
1             /* From bryant@sioux.stanford.edu Sat Apr 3 14:57:54 1993
2             Return-Path:
3             Received: from sioux.stanford.edu by alnitak.usc.edu (4.1/SMI-4.1+ucs-3.6)
4             id AA12724; Sat, 3 Apr 93 14:57:52 PST
5             Received: from oglala.ice (oglala.Stanford.EDU) by sioux.stanford.edu (4.1/inc-1.0)
6             id AA07300; Sat, 3 Apr 93 14:53:25 PST
7             Date: Sat, 3 Apr 93 14:53:25 PST
8             From: bryant@sioux.stanford.edu (Bryant Marks)
9             Message-Id: <9304032253.AA07300@sioux.stanford.edu>
10             To: ajayshah@rcf.usc.edu
11             Subject: Re: SVD
12             Status: ORr
13              
14              
15             > Hi! Long ago you sent me an svd routine in C based on code
16             > from Nash in Pascal. Has this changed any over the years? (Your
17             > email is dated July 1992). Is your code available by anon ftp?
18              
19             Hi Ajay,
20              
21             I don't think I have changed the code -- but here's my most recent
22             version of the code, you can check to see if it's any different.
23             Currently it's not available via anonymous ftp but feel free to
24             redistribute the code -- it seems to work well in the application
25             I'm using it in.
26              
27              
28             Bryant
29             */
30              
31             /* This SVD routine is based on pgs 30-48 of "Compact Numerical Methods
32             for Computers" by J.C. Nash (1990), used to compute the pseudoinverse.
33             Modifications include:
34             Translation from Pascal to ANSI C.
35             Array indexing from 0 rather than 1.
36             Float replaced by double everywhere.
37             Support for the Matrix structure.
38             I changed the array indexing so that the matricies (float [][])
39             could be replaced be a single list (double *) for more
40             efficient communication with Mathematica.
41             */
42              
43             /* rjrw 7/7/99: changed z back to a vector, moved one line... */
44              
45             /*
46             Derek A. Lamb 2016: added comments to aid understanding, since
47             Nash's book will only get more difficult to find in the future.
48             */
49              
50             /*
51             Form a singular value decomposition of matrix A which is stored in
52             the first nRow*nCol elements of working array W. Upon return, the
53             first nRow*nCol elements of W will become the product U x S of a
54             thin svd, where S is the diagonal (rectangular) matrix of singular
55             values. The last nCol*nCol elements of W will be the square matrix
56             V of a thin svd. On return, Z will contain the squares of the
57             singular values.
58              
59             The input matrix A is assumed to have nRows >= nCol. If it does
60             not, one should input the transpose of A, A", to find the the svd
61             of A, since A = U x S x V" and A" = V x S x U". (The " means
62             transpose.)
63             */
64              
65             #include
66             #define TOLERANCE 1.0e-22
67              
68             #ifdef MAIN
69             #include
70             #define NC 2
71             #define NR 2
72             int main()
73             {
74             int i,j,n,m;
75             double w[NC*(NR+NC)], z[NC*NC];
76             void SVD(double *W, double *Z, int nRow, int nCol);
77              
78             for (i=0;i
79             w[i] = 0.;
80             }
81              
82             for (i=0;i
83             z[i] = 0.;
84             }
85             w[0] = 1; w[1] = 3; w[NC] = -4; w[NC+1] = 3;
86              
87             SVD(w, z, NR, NC);
88              
89             printf("W:\n");
90             for (i=0;i
91             printf("%d %g\n",i,w[i]);
92             }
93             printf("\nZ:\n");
94             for (i=0;i
95             printf("%d %g\n",i,z[i]);
96             }
97             return 0;
98             }
99             #endif
100              
101 23           void SVD(double *W, double *Z, int nRow, int nCol)
102             {
103             int i, j, k, EstColRank, RotCount, SweepCount, slimit;
104             double eps, e2, tol, vt, p, x0, y0, q, r, c0, s0, d1, d2;
105 23           eps = TOLERANCE;
106             /*set a limit on the number of sweeps allowed. A suggested limit is slimit=max([nCol/4],6).*/
107 23           slimit = nCol/4;
108 23 50         if (slimit < 6.0)
109 23           slimit = 6;
110 23           SweepCount = 0;
111 23           e2 = 10.0*nRow*eps*eps;
112 23           tol = eps*.1; /*set convergence tolerances */
113 23           EstColRank = nCol; /* current estimate of rank */
114             /* Set V matrix to the unit matrix of order nCol.
115             V is stored in elements nCol*nRow to nCol*(nRow+nCol)-1 of array W. */
116 142 100         for (i=0; i
117 1122 100         for (j=0; j
118 1003           W[nCol*(nRow+i)+j] = 0.0;
119             }
120 119           W[nCol*(nRow+i)+i] = 1.0; /* rjrw 7/7/99: moved this line out of j loop */
121             }
122 23           RotCount = EstColRank*(EstColRank-1)/2;
123              
124             /*until convergence is achieved or too many sweeps are carried out*/
125 157 100         while (RotCount != 0 && SweepCount <= slimit)
    100          
126             {
127 134           RotCount = EstColRank*(EstColRank-1)/2; /* rotation counter */
128 134           SweepCount++;
129 773 100         for (j=0; j
130             {
131 3658 100         for (k=j+1; k
132             {
133 3019           p = q = r = 0.0;
134             /*
135             Some helpful definitions from Nash's book (pg 34) to
136             understand this p, q, & r rotation business:
137              
138             p = x"y; (" means transpose)
139             q = x"x - y"y;
140             v = sqrt(4*p^2+q^2);
141             if (q>=0) {
142             cos(phi) = sqrt((v+q)/(2*v));
143             sin(phi) = p/(v*cos(phi));
144             } else if (q<0) {,
145             (sgn(p)=+1 for p>=0, -1 for p<0);
146             sin(phi) = sgn(p)*sqrt((v-q)/(2*v));
147             cos(phi) = p/(v*sin(phi));
148             }
149             This formulation avoids the subtraction of two nearly
150             equal numbers, which is bound to happen to q and v as
151             the matrix approaches orthogonality.
152             */
153 79826 100         for (i=0; i
154             {
155 76807           x0 = W[nCol*i+j]; y0 = W[nCol*i+k];
156 76807           p += x0*y0; q += x0*x0; r += y0*y0;
157             }
158 3019           Z[j] = q; Z[k] = r;
159             /* Convergence test: will rotation exchange order of columns?*/
160 3019 100         if (q >= r) /* check if columns are ordered */
161             { /* columns are ordered, so try convergence test */
162 2643 50         if (q<=e2*Z[0] || fabs(p)<=tol*q) RotCount--;
    100          
163             /* There is no more work on this particular pair of
164             columns in the current sweep. The first condition
165             checks for very small column norms in BOTH
166             columns, for which no rotation makes sense. The
167             second condition determines if the inner product
168             is small with respect to the larger of the
169             columns, which implies a very small rotation
170             angle. */
171             else
172             {/* columns are in order, but their inner product is not small */
173 2328           p /= q; r = 1 - r/q; vt = sqrt(4*p*p+r*r);
174             /* DAL thinks the fabs is unnecessary in the
175             next line: vt is non-negative; q>=r, r/q <=1
176             so after the assignment 0<= r <=1, so r/vt is
177             positive, so everything inside the sqrt
178             should be positive even without the fabs. abs
179             isn't in Nash's book. c0 and s0 are cos(phi) and sin(phi) as above for q>=0*/
180 2328           c0 = sqrt(fabs(.5*(1+r/vt))); s0 = p/(vt*c0);
181             /* this little for loop (and the one below) is just rotation, inlined here for efficiency */
182 90792 100         for (i=0; i
183             {
184 88464           d1 = W[nCol*i+j]; d2 = W[nCol*i+k];
185 88464           W[nCol*i+j] = d1*c0+d2*s0; W[nCol*i+k] = -d1*s0+d2*c0;
186             }
187             }
188             }
189             else /* columns out of order -- must rotate */
190             { /* note: r>q, and cannot be zero since both are sums
191             of squares for the svd. In the case of a real
192             symmetric matrix, this assumption must be
193             questioned. */
194 376           p /= r; q = q/r-1; vt = sqrt(4*p*p+q*q);
195 376           s0 = sqrt(fabs(.5*(1-q/vt)));
196             /* DAL wondering about the fabs again, since after
197             assignment q is <0 and vt is again positive, so
198             everything inside the fabs should be positive
199             already */
200 376 100         if (p<0) s0 = -s0; /*s0 and c0 are sin(phi) and cos(phi) as above for q<0 */
201 376           c0 = p/(vt*s0);
202 14591 100         for (i=0; i
203             {
204 14215           d1 = W[nCol*i+j]; d2 = W[nCol*i+k];
205 14215           W[nCol*i+j] = d1*c0+d2*s0; W[nCol*i+k] = -d1*s0+d2*c0;
206             }
207             }
208             /* Both angle calculations have been set up so that
209             large numbers do not occur in intermediate
210             quantities. This is easy in the svd case, since
211             quantities x2,y2 cannot be negative.*/
212             } /* loop on k */
213             } /* loop on j */
214 136 100         while (EstColRank>=3 && Z[(EstColRank-1)]<=Z[0]*tol+tol*tol)
    100          
215 2           EstColRank--;
216             }
217             #if DEBUG
218             if (SweepCount > slimit)
219             fprintf(stderr, "Sweeps = %d\n", SweepCount);
220             #endif
221 23           }