1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.commons.math.linear; 19 20 import org.apache.commons.math.MathRuntimeException; 21 import org.apache.commons.math.exception.util.LocalizedFormats; 22 import org.apache.commons.math.util.FastMath; 23 24 /** 25 * Calculates the compact Singular Value Decomposition of a matrix. 26 * <p> 27 * The Singular Value Decomposition of matrix A is a set of three matrices: U, 28 * Σ and V such that A = U × Σ × V<sup>T</sup>. Let A be 29 * a m × n matrix, then U is a m × p orthogonal matrix, Σ is a 30 * p × p diagonal matrix with positive or null elements, V is a p × 31 * n orthogonal matrix (hence V<sup>T</sup> is also orthogonal) where 32 * p=min(m,n). 33 * </p> 34 * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $ 35 * @since 2.0 36 */ 37 public class SingularValueDecompositionImpl implements 38 SingularValueDecomposition { 39 40 /** Number of rows of the initial matrix. */ 41 private int m; 42 43 /** Number of columns of the initial matrix. */ 44 private int n; 45 46 /** Eigen decomposition of the tridiagonal matrix. */ 47 private EigenDecomposition eigenDecomposition; 48 49 /** Singular values. */ 50 private double[] singularValues; 51 52 /** Cached value of U. */ 53 private RealMatrix cachedU; 54 55 /** Cached value of U<sup>T</sup>. */ 56 private RealMatrix cachedUt; 57 58 /** Cached value of S. */ 59 private RealMatrix cachedS; 60 61 /** Cached value of V. */ 62 private RealMatrix cachedV; 63 64 /** Cached value of V<sup>T</sup>. */ 65 private RealMatrix cachedVt; 66 67 /** 68 * Calculates the compact Singular Value Decomposition of the given matrix. 69 * @param matrix 70 * The matrix to decompose. 71 * @exception InvalidMatrixException 72 * (wrapping a 73 * {@link org.apache.commons.math.ConvergenceException} if 74 * algorithm fails to converge 75 */ SingularValueDecompositionImpl(final RealMatrix matrix)76 public SingularValueDecompositionImpl(final RealMatrix matrix) 77 throws InvalidMatrixException { 78 79 m = matrix.getRowDimension(); 80 n = matrix.getColumnDimension(); 81 82 cachedU = null; 83 cachedS = null; 84 cachedV = null; 85 cachedVt = null; 86 87 double[][] localcopy = matrix.getData(); 88 double[][] matATA = new double[n][n]; 89 // 90 // create A^T*A 91 // 92 for (int i = 0; i < n; i++) { 93 for (int j = i; j < n; j++) { 94 matATA[i][j] = 0.0; 95 for (int k = 0; k < m; k++) { 96 matATA[i][j] += localcopy[k][i] * localcopy[k][j]; 97 } 98 matATA[j][i]=matATA[i][j]; 99 } 100 } 101 102 double[][] matAAT = new double[m][m]; 103 // 104 // create A*A^T 105 // 106 for (int i = 0; i < m; i++) { 107 for (int j = i; j < m; j++) { 108 matAAT[i][j] = 0.0; 109 for (int k = 0; k < n; k++) { 110 matAAT[i][j] += localcopy[i][k] * localcopy[j][k]; 111 } 112 matAAT[j][i]=matAAT[i][j]; 113 } 114 } 115 int p; 116 if (m>=n) { 117 p=n; 118 // compute eigen decomposition of A^T*A 119 eigenDecomposition = new EigenDecompositionImpl( 120 new Array2DRowRealMatrix(matATA),1.0); 121 singularValues = eigenDecomposition.getRealEigenvalues(); 122 cachedV = eigenDecomposition.getV(); 123 // compute eigen decomposition of A*A^T 124 eigenDecomposition = new EigenDecompositionImpl( 125 new Array2DRowRealMatrix(matAAT),1.0); 126 cachedU = eigenDecomposition.getV().getSubMatrix(0, m - 1, 0, p - 1); 127 } else { 128 p=m; 129 // compute eigen decomposition of A*A^T 130 eigenDecomposition = new EigenDecompositionImpl( 131 new Array2DRowRealMatrix(matAAT),1.0); 132 singularValues = eigenDecomposition.getRealEigenvalues(); 133 cachedU = eigenDecomposition.getV(); 134 135 // compute eigen decomposition of A^T*A 136 eigenDecomposition = new EigenDecompositionImpl( 137 new Array2DRowRealMatrix(matATA),1.0); 138 cachedV = eigenDecomposition.getV().getSubMatrix(0,n-1,0,p-1); 139 } 140 for (int i = 0; i < p; i++) { 141 singularValues[i] = FastMath.sqrt(FastMath.abs(singularValues[i])); 142 } 143 // Up to this point, U and V are computed independently of each other. 144 // There still a sign indetermination of each column of, say, U. 145 // The sign is set such that A.V_i=sigma_i.U_i (i<=p) 146 // The right sign corresponds to a positive dot product of A.V_i and U_i 147 for (int i = 0; i < p; i++) { 148 RealVector tmp = cachedU.getColumnVector(i); 149 double product=matrix.operate(cachedV.getColumnVector(i)).dotProduct(tmp); 150 if (product<0) { 151 cachedU.setColumnVector(i, tmp.mapMultiply(-1.0)); 152 } 153 } 154 } 155 156 /** {@inheritDoc} */ getU()157 public RealMatrix getU() throws InvalidMatrixException { 158 // return the cached matrix 159 return cachedU; 160 161 } 162 163 /** {@inheritDoc} */ getUT()164 public RealMatrix getUT() throws InvalidMatrixException { 165 166 if (cachedUt == null) { 167 cachedUt = getU().transpose(); 168 } 169 170 // return the cached matrix 171 return cachedUt; 172 173 } 174 175 /** {@inheritDoc} */ getS()176 public RealMatrix getS() throws InvalidMatrixException { 177 178 if (cachedS == null) { 179 180 // cache the matrix for subsequent calls 181 cachedS = MatrixUtils.createRealDiagonalMatrix(singularValues); 182 183 } 184 return cachedS; 185 } 186 187 /** {@inheritDoc} */ getSingularValues()188 public double[] getSingularValues() throws InvalidMatrixException { 189 return singularValues.clone(); 190 } 191 192 /** {@inheritDoc} */ getV()193 public RealMatrix getV() throws InvalidMatrixException { 194 // return the cached matrix 195 return cachedV; 196 197 } 198 199 /** {@inheritDoc} */ getVT()200 public RealMatrix getVT() throws InvalidMatrixException { 201 202 if (cachedVt == null) { 203 cachedVt = getV().transpose(); 204 } 205 206 // return the cached matrix 207 return cachedVt; 208 209 } 210 211 /** {@inheritDoc} */ getCovariance(final double minSingularValue)212 public RealMatrix getCovariance(final double minSingularValue) { 213 214 // get the number of singular values to consider 215 final int p = singularValues.length; 216 int dimension = 0; 217 while ((dimension < p) && (singularValues[dimension] >= minSingularValue)) { 218 ++dimension; 219 } 220 221 if (dimension == 0) { 222 throw MathRuntimeException.createIllegalArgumentException( 223 LocalizedFormats.TOO_LARGE_CUTOFF_SINGULAR_VALUE, 224 minSingularValue, singularValues[0]); 225 } 226 227 final double[][] data = new double[dimension][p]; 228 getVT().walkInOptimizedOrder(new DefaultRealMatrixPreservingVisitor() { 229 /** {@inheritDoc} */ 230 @Override 231 public void visit(final int row, final int column, 232 final double value) { 233 data[row][column] = value / singularValues[row]; 234 } 235 }, 0, dimension - 1, 0, p - 1); 236 237 RealMatrix jv = new Array2DRowRealMatrix(data, false); 238 return jv.transpose().multiply(jv); 239 240 } 241 242 /** {@inheritDoc} */ getNorm()243 public double getNorm() throws InvalidMatrixException { 244 return singularValues[0]; 245 } 246 247 /** {@inheritDoc} */ getConditionNumber()248 public double getConditionNumber() throws InvalidMatrixException { 249 return singularValues[0] / singularValues[singularValues.length - 1]; 250 } 251 252 /** {@inheritDoc} */ getRank()253 public int getRank() throws IllegalStateException { 254 255 final double threshold = FastMath.max(m, n) * FastMath.ulp(singularValues[0]); 256 257 for (int i = singularValues.length - 1; i >= 0; --i) { 258 if (singularValues[i] > threshold) { 259 return i + 1; 260 } 261 } 262 return 0; 263 264 } 265 266 /** {@inheritDoc} */ getSolver()267 public DecompositionSolver getSolver() { 268 return new Solver(singularValues, getUT(), getV(), getRank() == Math 269 .max(m, n)); 270 } 271 272 /** Specialized solver. */ 273 private static class Solver implements DecompositionSolver { 274 275 /** Pseudo-inverse of the initial matrix. */ 276 private final RealMatrix pseudoInverse; 277 278 /** Singularity indicator. */ 279 private boolean nonSingular; 280 281 /** 282 * Build a solver from decomposed matrix. 283 * @param singularValues 284 * singularValues 285 * @param uT 286 * U<sup>T</sup> matrix of the decomposition 287 * @param v 288 * V matrix of the decomposition 289 * @param nonSingular 290 * singularity indicator 291 */ Solver(final double[] singularValues, final RealMatrix uT, final RealMatrix v, final boolean nonSingular)292 private Solver(final double[] singularValues, final RealMatrix uT, 293 final RealMatrix v, final boolean nonSingular) { 294 double[][] suT = uT.getData(); 295 for (int i = 0; i < singularValues.length; ++i) { 296 final double a; 297 if (singularValues[i]>0) { 298 a=1.0 / singularValues[i]; 299 } else { 300 a=0.0; 301 } 302 final double[] suTi = suT[i]; 303 for (int j = 0; j < suTi.length; ++j) { 304 suTi[j] *= a; 305 } 306 } 307 pseudoInverse = v.multiply(new Array2DRowRealMatrix(suT, false)); 308 this.nonSingular = nonSingular; 309 } 310 311 /** 312 * Solve the linear equation A × X = B in least square sense. 313 * <p> 314 * The m×n matrix A may not be square, the solution X is such that 315 * ||A × X - B|| is minimal. 316 * </p> 317 * @param b 318 * right-hand side of the equation A × X = B 319 * @return a vector X that minimizes the two norm of A × X - B 320 * @exception IllegalArgumentException 321 * if matrices dimensions don't match 322 */ solve(final double[] b)323 public double[] solve(final double[] b) throws IllegalArgumentException { 324 return pseudoInverse.operate(b); 325 } 326 327 /** 328 * Solve the linear equation A × X = B in least square sense. 329 * <p> 330 * The m×n matrix A may not be square, the solution X is such that 331 * ||A × X - B|| is minimal. 332 * </p> 333 * @param b 334 * right-hand side of the equation A × X = B 335 * @return a vector X that minimizes the two norm of A × X - B 336 * @exception IllegalArgumentException 337 * if matrices dimensions don't match 338 */ solve(final RealVector b)339 public RealVector solve(final RealVector b) 340 throws IllegalArgumentException { 341 return pseudoInverse.operate(b); 342 } 343 344 /** 345 * Solve the linear equation A × X = B in least square sense. 346 * <p> 347 * The m×n matrix A may not be square, the solution X is such that 348 * ||A × X - B|| is minimal. 349 * </p> 350 * @param b 351 * right-hand side of the equation A × X = B 352 * @return a matrix X that minimizes the two norm of A × X - B 353 * @exception IllegalArgumentException 354 * if matrices dimensions don't match 355 */ solve(final RealMatrix b)356 public RealMatrix solve(final RealMatrix b) 357 throws IllegalArgumentException { 358 return pseudoInverse.multiply(b); 359 } 360 361 /** 362 * Check if the decomposed matrix is non-singular. 363 * @return true if the decomposed matrix is non-singular 364 */ isNonSingular()365 public boolean isNonSingular() { 366 return nonSingular; 367 } 368 369 /** 370 * Get the pseudo-inverse of the decomposed matrix. 371 * @return inverse matrix 372 */ getInverse()373 public RealMatrix getInverse() { 374 return pseudoInverse; 375 } 376 377 } 378 379 } 380