#include "slalib.h" #include "slamac.h" double rms ( double a, double b ); void slaSvd ( int m, int n, int mp, int np, double *a, double *w, double *v, double *work, int *jstat ) /* ** - - - - - - - ** s l a S v d ** - - - - - - - ** ** Singular value decomposition. ** ** (double precision) ** ** This routine expresses a given matrix a as the product of ** three matrices u, w, v: ** ** a = u x w x vt ** ** where: ** ** a is any m (rows) x n (columns) matrix, where m >= n ** u is an m x n column-orthogonal matrix ** w is an n x n diagonal matrix with w(i,i) >= 0 ** vt is the transpose of an n x n orthogonal matrix ** ** Note that m and n, above, are the logical dimensions of the ** matrices and vectors concerned, which can be located in ** arrays of larger physical dimensions, given by mp and np. ** ** Given: ** m,n int numbers of rows and columns in matrix a ** mp,np int physical dimensions of the array containing a ** a double[mp][np] array containing m x n matrix a ** ** Returned: ** *a double[mp][np] array containing m x n column-orthogonal matrix u ** *w double[n] n x n diagonal matrix w (diagonal elements only) ** *v double[np][np] array containing n x n orthogonal matrix v ** *work double[n] workspace ** *jstat int 0 = OK ** -1 = the a array is the wrong shape ** >0 = 1 + index of w for which convergence failed. ** ** (n.b. v contains matrix v, not the transpose of matrix v) ** ** References: ** The algorithm is an adaptation of the routine SVD in the EISPACK ** library (Garbow et al 1977, Eispack guide extension, Springer ** Verlag), which is a Fortran 66 implementation of the Algol ** routine SVD of Wilkinson & Reinsch 1971 (Handbook for Automatic ** Computation, vol 2, Ed Bauer et al, Springer Verlag). For the ** non-specialist, probably the clearest general account of the use ** of SVD in least squares problems is given in Numerical Recipes ** (Press et al 1986, Cambridge University Press). ** ** From slamac.h: TRUE, FALSE ** ** Example call (note handling of "adjustable dimension" 2D arrays): ** ** double a[MP][NP], w[NP], v[NP][NP], work[NP]; ** int m, n, j; ** : ** slaSvd ( m, n, MP, NP, (double *) a, w, (double *) v, work, &j ); ** ** Last revision: 24 June 1997 ** ** Copyright P.T.Wallace. All rights reserved. */ /* Maximum number of iterations in QR phase */ #define ITMAX 30 { int i, k, l, j, k1, its, l1, i1, cancel; double g, scale, an, s, x, f, h, cn, c, y, z; double *ai, *aj, *ak; double *vi, *vj, *vk; /* Check that the matrix is the right size and shape */ if ( m < n || m > mp || n > np ) { *jstat = -1; } else { *jstat = 0; /* Householder reduction to bidiagonal form */ g = 0.0; scale = 0.0; an = 0.0; for ( i = 0, ai = a; i < n; i++, ai += np ) { l = i + 1; work[i] = scale * g; g = 0.0; s = 0.0; scale = 0.0; if ( i < m ) { for ( k = i, ak = ai; k < m; k++, ak += np ) { scale += fabs ( ak[i] ); } if ( scale != 0.0 ) { for ( k = i, ak = ai; k < m; k++, ak += np ) { x = ak[i] / scale; ak[i] = x; s += x * x; } f = ai[i]; g = - dsign ( sqrt ( s ), f ); h = f * g - s; ai[i] = f - g; if ( i != n - 1 ) { for ( j = l; j < n; j++ ) { s = 0.0; for ( k = i, ak = ai; k < m; k++, ak += np ) { s += ak[i] * ak[j]; } f = s / h; for ( k = i, ak = ai; k < m; k++, ak += np ) { ak[j] += f * ak[i]; } } } for ( k = i, ak = ai; k < m; k++, ak += np ) { ak[i] *= scale; } } } w[i] = scale * g; g = 0.0; s = 0.0; scale = 0.0; if ( i < m && i != n - 1 ) { for ( k = l; k < n; k++ ) { scale += fabs ( ai[k] ); } if ( scale != 0.0 ) { for ( k = l; k < n; k++ ) { x = ai[k] / scale; ai[k] = x; s += x * x; } f = ai[l]; g = - dsign ( sqrt ( s ), f ); h = f * g - s; ai[l] = f - g; for ( k = l; k < n; k++ ) { work[k] = ai[k] / h; } if ( i != m-1 ) { for ( j = l, aj = a + l*np; j < m; j++, aj += np ) { s = 0.0; for ( k = l; k < n; k++ ) { s += aj[k] * ai[k]; } for ( k = l; k < n; k++ ) { aj[k] += s * work[k]; } } } for ( k = l; k < n; k++ ) { ai[k] *= scale; } } } /* Overestimate of largest column norm for convergence test */ cn = fabs ( w[i] ) + fabs ( work[i] ); an = gmax ( an, cn ); } /* Accumulation of right-hand transformations */ for ( i = n - 1, ai = a + ( n - 1 ) * np, vi = v + ( n - 1 ) * np; i >= 0; i--, ai -= np, vi -= np ) { if ( i != n - 1 ) { if ( g != 0.0 ) { for ( j = l, vj = v + l * np; j < n; j++, vj += np ) { vj[i] = ( ai[j] / ai[l] ) / g; } for ( j = l; j < n; j++ ) { s = 0.0; for ( k = l, vk = v + l*np; k < n; k++, vk += np ) { s += ai[k] * vk[j]; } for ( k = l, vk = v + l*np; k < n; k++, vk += np ) { vk[j] += s * vk[i]; } } } for ( j = l, vj = v + l*np; j < n; j++, vj += np ) { vi[j] = 0.0; vj[i] = 0.0; } } vi[i] = 1.0; g = work[i]; l = i; } /* Accumulation of left-hand transformations */ for ( i = n - 1, ai = a + i*np; i >= 0; i--, ai -= np ) { l = i + 1; g = w[i]; if ( i != n - 1 ) { for ( j = l; j < n; j++ ) { ai[j] = 0.0; } } if ( g != 0.0 ) { if ( i != n - 1 ) { for ( j = l; j < n; j++ ) { s = 0.0; for ( k = l, ak = a + l * np; k < m; k++, ak += np ) { s += ak[i] * ak[j]; } f = ( s / ai[i] ) / g; for ( k = i, ak = a + i * np; k < m; k++, ak += np ) { ak[j] += f * ak[i]; } } } for ( j = i, aj = ai; j < m; j++, aj += np ) { aj[i] /= g; } } else { for ( j = i, aj = ai; j < m; j++, aj += np ) { aj[i] = 0.0; } } ai[i] += 1.0; } /* Diagonalization of the bidiagonal form */ for ( k = n - 1; k >= 0; k-- ) { k1 = k - 1; /* Iterate until converged */ for ( its = 1; its <= ITMAX; its++ ) { /* Test for splitting into submatrices */ cancel = TRUE; for ( l = k; l >= 0; l-- ) { l1 = l - 1; if ( an + fabs ( work[l] ) == an ) { cancel = FALSE; break; } /* (Following never attempted for l=0 because work[0] is zero) */ if ( an + fabs ( w[l1] ) == an ) { break; } } /* Cancellation of work[l] if l>0 */ if ( cancel ) { c = 0.0; s = 1.0; for ( i = l; i <= k; i++ ) { f = s * work[i]; if ( an + fabs ( f ) == an ) { break; } g = w[i]; h = rms ( f, g ); w[i] = h; c = g / h; s = - f / h; for ( j = 0, aj = a; j < m; j++, aj += np ) { y = aj[l1]; z = aj[i]; aj[l1] = y * c + z * s; aj[i] = - y * s + z * c; } } } /* Converged? */ z = w[k]; if ( l == k ) { /* Yes: ensure singular values non-negative */ if ( z < 0.0 ) { w[k] = -z; for ( j = 0, vj = v; j < n; j++, vj += np ) { vj[k] = -vj[k]; } } /* Stop iterating */ break; } else { /* Not converged yet: set status if iteration limit reached */ if ( its >= ITMAX ) { *jstat = k + 1; } /* Shift from bottom 2 x 2 minor */ x = w[l]; y = w[k1]; g = work[k1]; h = work[k]; f = ( ( y - z ) * ( y + z ) + ( g - h ) * ( g + h ) ) / ( 2.0 * h * y ); g = ( fabs ( f ) <= 1e15 ) ? rms ( f, 1.0 ) : fabs ( f ); f = ( ( x - z ) * ( x + z ) + h * ( y / ( f + dsign ( g, f ) ) - h ) ) / x; /* Next QR transformation */ c = 1.0; s = 1.0; for ( i1 = l; i1 <= k1; i1++ ) { i = i1 + 1; g = work[i]; y = w[i]; h = s * g; g = c * g; z = rms ( f, h ); work[i1] = z; if ( z != 0.0 ) { c = f / z; s = h / z; } else { c = 1.0; s = 0.0; } f = x * c + g * s; g = - x * s + g * c; h = y * s; y = y * c; for ( j = 0, vj = v; j < n; j++, vj += np ) { x = vj[i1]; z = vj[i]; vj[i1] = x * c + z * s; vj[i] = - x * s + z * c; } z = rms ( f, h ); w[i1] = z; if ( z != 0.0 ) { c = f / z; s = h / z; } f = c * g + s * y; x = - s * g + c * y; for ( j = 0, aj = a; j < m; j++, aj += np ) { y = aj[i1]; z = aj[i]; aj[i1] = y * c + z * s; aj[i] = - y * s + z * c; } } work[l] = 0.0; work[k] = f; w[k] = x; } } } } } double rms ( double a, double b ) /* sqrt(a*a+b*b) with protection against under/overflow */ { double wa, wb, w; wa = fabs ( a ); wb = fabs ( b ); if ( wa > wb ) { w = wa; wa = wb; wb = w; } if ( wb == 0.0 ) { return 0.0; } else { w = wa / wb; return wb * sqrt ( 1.0 + w * w ); } }