blob: 1b3085c8aa27f0138ce99a0e67efa35ef0446d6a [file] [log] [blame]
Raymonddee08492015-04-02 10:43:13 -07001/*
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
18package org.apache.commons.math.linear;
19
20import org.apache.commons.math.MathRuntimeException;
21import org.apache.commons.math.MaxIterationsExceededException;
22import org.apache.commons.math.exception.util.LocalizedFormats;
23import org.apache.commons.math.util.MathUtils;
24import org.apache.commons.math.util.FastMath;
25
26/**
27 * Calculates the eigen decomposition of a real <strong>symmetric</strong>
28 * matrix.
29 * <p>
30 * The eigen decomposition of matrix A is a set of two matrices: V and D such
31 * that A = V D V<sup>T</sup>. A, V and D are all m &times; m matrices.
32 * </p>
33 * <p>
34 * As of 2.0, this class supports only <strong>symmetric</strong> matrices, and
35 * hence computes only real realEigenvalues. This implies the D matrix returned
36 * by {@link #getD()} is always diagonal and the imaginary values returned
37 * {@link #getImagEigenvalue(int)} and {@link #getImagEigenvalues()} are always
38 * null.
39 * </p>
40 * <p>
41 * When called with a {@link RealMatrix} argument, this implementation only uses
42 * the upper part of the matrix, the part below the diagonal is not accessed at
43 * all.
44 * </p>
45 * <p>
46 * This implementation is based on the paper by A. Drubrulle, R.S. Martin and
47 * J.H. Wilkinson 'The Implicit QL Algorithm' in Wilksinson and Reinsch (1971)
48 * Handbook for automatic computation, vol. 2, Linear algebra, Springer-Verlag,
49 * New-York
50 * </p>
51 * @version $Revision: 1002040 $ $Date: 2010-09-28 09:18:31 +0200 (mar. 28 sept. 2010) $
52 * @since 2.0
53 */
54public class EigenDecompositionImpl implements EigenDecomposition {
55
56 /** Maximum number of iterations accepted in the implicit QL transformation */
57 private byte maxIter = 30;
58
59 /** Main diagonal of the tridiagonal matrix. */
60 private double[] main;
61
62 /** Secondary diagonal of the tridiagonal matrix. */
63 private double[] secondary;
64
65 /**
66 * Transformer to tridiagonal (may be null if matrix is already
67 * tridiagonal).
68 */
69 private TriDiagonalTransformer transformer;
70
71 /** Real part of the realEigenvalues. */
72 private double[] realEigenvalues;
73
74 /** Imaginary part of the realEigenvalues. */
75 private double[] imagEigenvalues;
76
77 /** Eigenvectors. */
78 private ArrayRealVector[] eigenvectors;
79
80 /** Cached value of V. */
81 private RealMatrix cachedV;
82
83 /** Cached value of D. */
84 private RealMatrix cachedD;
85
86 /** Cached value of Vt. */
87 private RealMatrix cachedVt;
88
89 /**
90 * Calculates the eigen decomposition of the given symmetric matrix.
91 * @param matrix The <strong>symmetric</strong> matrix to decompose.
92 * @param splitTolerance dummy parameter, present for backward compatibility only.
93 * @exception InvalidMatrixException (wrapping a
94 * {@link org.apache.commons.math.ConvergenceException} if algorithm
95 * fails to converge
96 */
97 public EigenDecompositionImpl(final RealMatrix matrix,final double splitTolerance)
98 throws InvalidMatrixException {
99 if (isSymmetric(matrix)) {
100 transformToTridiagonal(matrix);
101 findEigenVectors(transformer.getQ().getData());
102 } else {
103 // as of 2.0, non-symmetric matrices (i.e. complex eigenvalues) are
104 // NOT supported
105 // see issue https://issues.apache.org/jira/browse/MATH-235
106 throw new InvalidMatrixException(
107 LocalizedFormats.ASSYMETRIC_EIGEN_NOT_SUPPORTED);
108 }
109 }
110
111 /**
112 * Calculates the eigen decomposition of the symmetric tridiagonal
113 * matrix. The Householder matrix is assumed to be the identity matrix.
114 * @param main Main diagonal of the symmetric triadiagonal form
115 * @param secondary Secondary of the tridiagonal form
116 * @param splitTolerance dummy parameter, present for backward compatibility only.
117 * @exception InvalidMatrixException (wrapping a
118 * {@link org.apache.commons.math.ConvergenceException} if algorithm
119 * fails to converge
120 */
121 public EigenDecompositionImpl(final double[] main,final double[] secondary,
122 final double splitTolerance)
123 throws InvalidMatrixException {
124 this.main = main.clone();
125 this.secondary = secondary.clone();
126 transformer = null;
127 final int size=main.length;
128 double[][] z = new double[size][size];
129 for (int i=0;i<size;i++) {
130 z[i][i]=1.0;
131 }
132 findEigenVectors(z);
133 }
134
135 /**
136 * Check if a matrix is symmetric.
137 * @param matrix
138 * matrix to check
139 * @return true if matrix is symmetric
140 */
141 private boolean isSymmetric(final RealMatrix matrix) {
142 final int rows = matrix.getRowDimension();
143 final int columns = matrix.getColumnDimension();
144 final double eps = 10 * rows * columns * MathUtils.EPSILON;
145 for (int i = 0; i < rows; ++i) {
146 for (int j = i + 1; j < columns; ++j) {
147 final double mij = matrix.getEntry(i, j);
148 final double mji = matrix.getEntry(j, i);
149 if (FastMath.abs(mij - mji) >
150 (FastMath.max(FastMath.abs(mij), FastMath.abs(mji)) * eps)) {
151 return false;
152 }
153 }
154 }
155 return true;
156 }
157
158 /** {@inheritDoc} */
159 public RealMatrix getV() throws InvalidMatrixException {
160
161 if (cachedV == null) {
162 final int m = eigenvectors.length;
163 cachedV = MatrixUtils.createRealMatrix(m, m);
164 for (int k = 0; k < m; ++k) {
165 cachedV.setColumnVector(k, eigenvectors[k]);
166 }
167 }
168 // return the cached matrix
169 return cachedV;
170
171 }
172
173 /** {@inheritDoc} */
174 public RealMatrix getD() throws InvalidMatrixException {
175 if (cachedD == null) {
176 // cache the matrix for subsequent calls
177 cachedD = MatrixUtils.createRealDiagonalMatrix(realEigenvalues);
178 }
179 return cachedD;
180 }
181
182 /** {@inheritDoc} */
183 public RealMatrix getVT() throws InvalidMatrixException {
184
185 if (cachedVt == null) {
186 final int m = eigenvectors.length;
187 cachedVt = MatrixUtils.createRealMatrix(m, m);
188 for (int k = 0; k < m; ++k) {
189 cachedVt.setRowVector(k, eigenvectors[k]);
190 }
191
192 }
193
194 // return the cached matrix
195 return cachedVt;
196 }
197
198 /** {@inheritDoc} */
199 public double[] getRealEigenvalues() throws InvalidMatrixException {
200 return realEigenvalues.clone();
201 }
202
203 /** {@inheritDoc} */
204 public double getRealEigenvalue(final int i) throws InvalidMatrixException,
205 ArrayIndexOutOfBoundsException {
206 return realEigenvalues[i];
207 }
208
209 /** {@inheritDoc} */
210 public double[] getImagEigenvalues() throws InvalidMatrixException {
211 return imagEigenvalues.clone();
212 }
213
214 /** {@inheritDoc} */
215 public double getImagEigenvalue(final int i) throws InvalidMatrixException,
216 ArrayIndexOutOfBoundsException {
217 return imagEigenvalues[i];
218 }
219
220 /** {@inheritDoc} */
221 public RealVector getEigenvector(final int i)
222 throws InvalidMatrixException, ArrayIndexOutOfBoundsException {
223 return eigenvectors[i].copy();
224 }
225
226 /**
227 * Return the determinant of the matrix
228 * @return determinant of the matrix
229 */
230 public double getDeterminant() {
231 double determinant = 1;
232 for (double lambda : realEigenvalues) {
233 determinant *= lambda;
234 }
235 return determinant;
236 }
237
238 /** {@inheritDoc} */
239 public DecompositionSolver getSolver() {
240 return new Solver(realEigenvalues, imagEigenvalues, eigenvectors);
241 }
242
243 /** Specialized solver. */
244 private static class Solver implements DecompositionSolver {
245
246 /** Real part of the realEigenvalues. */
247 private double[] realEigenvalues;
248
249 /** Imaginary part of the realEigenvalues. */
250 private double[] imagEigenvalues;
251
252 /** Eigenvectors. */
253 private final ArrayRealVector[] eigenvectors;
254
255 /**
256 * Build a solver from decomposed matrix.
257 * @param realEigenvalues
258 * real parts of the eigenvalues
259 * @param imagEigenvalues
260 * imaginary parts of the eigenvalues
261 * @param eigenvectors
262 * eigenvectors
263 */
264 private Solver(final double[] realEigenvalues,
265 final double[] imagEigenvalues,
266 final ArrayRealVector[] eigenvectors) {
267 this.realEigenvalues = realEigenvalues;
268 this.imagEigenvalues = imagEigenvalues;
269 this.eigenvectors = eigenvectors;
270 }
271
272 /**
273 * Solve the linear equation A &times; X = B for symmetric matrices A.
274 * <p>
275 * This method only find exact linear solutions, i.e. solutions for
276 * which ||A &times; X - B|| is exactly 0.
277 * </p>
278 * @param b
279 * right-hand side of the equation A &times; X = B
280 * @return a vector X that minimizes the two norm of A &times; X - B
281 * @exception IllegalArgumentException
282 * if matrices dimensions don't match
283 * @exception InvalidMatrixException
284 * if decomposed matrix is singular
285 */
286 public double[] solve(final double[] b)
287 throws IllegalArgumentException, InvalidMatrixException {
288
289 if (!isNonSingular()) {
290 throw new SingularMatrixException();
291 }
292
293 final int m = realEigenvalues.length;
294 if (b.length != m) {
295 throw MathRuntimeException.createIllegalArgumentException(
296 LocalizedFormats.VECTOR_LENGTH_MISMATCH,
297 b.length, m);
298 }
299
300 final double[] bp = new double[m];
301 for (int i = 0; i < m; ++i) {
302 final ArrayRealVector v = eigenvectors[i];
303 final double[] vData = v.getDataRef();
304 final double s = v.dotProduct(b) / realEigenvalues[i];
305 for (int j = 0; j < m; ++j) {
306 bp[j] += s * vData[j];
307 }
308 }
309
310 return bp;
311
312 }
313
314 /**
315 * Solve the linear equation A &times; X = B for symmetric matrices A.
316 * <p>
317 * This method only find exact linear solutions, i.e. solutions for
318 * which ||A &times; X - B|| is exactly 0.
319 * </p>
320 * @param b
321 * right-hand side of the equation A &times; X = B
322 * @return a vector X that minimizes the two norm of A &times; X - B
323 * @exception IllegalArgumentException
324 * if matrices dimensions don't match
325 * @exception InvalidMatrixException
326 * if decomposed matrix is singular
327 */
328 public RealVector solve(final RealVector b)
329 throws IllegalArgumentException, InvalidMatrixException {
330
331 if (!isNonSingular()) {
332 throw new SingularMatrixException();
333 }
334
335 final int m = realEigenvalues.length;
336 if (b.getDimension() != m) {
337 throw MathRuntimeException.createIllegalArgumentException(
338 LocalizedFormats.VECTOR_LENGTH_MISMATCH, b
339 .getDimension(), m);
340 }
341
342 final double[] bp = new double[m];
343 for (int i = 0; i < m; ++i) {
344 final ArrayRealVector v = eigenvectors[i];
345 final double[] vData = v.getDataRef();
346 final double s = v.dotProduct(b) / realEigenvalues[i];
347 for (int j = 0; j < m; ++j) {
348 bp[j] += s * vData[j];
349 }
350 }
351
352 return new ArrayRealVector(bp, false);
353
354 }
355
356 /**
357 * Solve the linear equation A &times; X = B for symmetric matrices A.
358 * <p>
359 * This method only find exact linear solutions, i.e. solutions for
360 * which ||A &times; X - B|| is exactly 0.
361 * </p>
362 * @param b
363 * right-hand side of the equation A &times; X = B
364 * @return a matrix X that minimizes the two norm of A &times; X - B
365 * @exception IllegalArgumentException
366 * if matrices dimensions don't match
367 * @exception InvalidMatrixException
368 * if decomposed matrix is singular
369 */
370 public RealMatrix solve(final RealMatrix b)
371 throws IllegalArgumentException, InvalidMatrixException {
372
373 if (!isNonSingular()) {
374 throw new SingularMatrixException();
375 }
376
377 final int m = realEigenvalues.length;
378 if (b.getRowDimension() != m) {
379 throw MathRuntimeException
380 .createIllegalArgumentException(
381 LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
382 b.getRowDimension(), b.getColumnDimension(), m,
383 "n");
384 }
385
386 final int nColB = b.getColumnDimension();
387 final double[][] bp = new double[m][nColB];
388 for (int k = 0; k < nColB; ++k) {
389 for (int i = 0; i < m; ++i) {
390 final ArrayRealVector v = eigenvectors[i];
391 final double[] vData = v.getDataRef();
392 double s = 0;
393 for (int j = 0; j < m; ++j) {
394 s += v.getEntry(j) * b.getEntry(j, k);
395 }
396 s /= realEigenvalues[i];
397 for (int j = 0; j < m; ++j) {
398 bp[j][k] += s * vData[j];
399 }
400 }
401 }
402
403 return MatrixUtils.createRealMatrix(bp);
404
405 }
406
407 /**
408 * Check if the decomposed matrix is non-singular.
409 * @return true if the decomposed matrix is non-singular
410 */
411 public boolean isNonSingular() {
412 for (int i = 0; i < realEigenvalues.length; ++i) {
413 if ((realEigenvalues[i] == 0) && (imagEigenvalues[i] == 0)) {
414 return false;
415 }
416 }
417 return true;
418 }
419
420 /**
421 * Get the inverse of the decomposed matrix.
422 * @return inverse matrix
423 * @throws InvalidMatrixException
424 * if decomposed matrix is singular
425 */
426 public RealMatrix getInverse() throws InvalidMatrixException {
427
428 if (!isNonSingular()) {
429 throw new SingularMatrixException();
430 }
431
432 final int m = realEigenvalues.length;
433 final double[][] invData = new double[m][m];
434
435 for (int i = 0; i < m; ++i) {
436 final double[] invI = invData[i];
437 for (int j = 0; j < m; ++j) {
438 double invIJ = 0;
439 for (int k = 0; k < m; ++k) {
440 final double[] vK = eigenvectors[k].getDataRef();
441 invIJ += vK[i] * vK[j] / realEigenvalues[k];
442 }
443 invI[j] = invIJ;
444 }
445 }
446 return MatrixUtils.createRealMatrix(invData);
447
448 }
449
450 }
451
452 /**
453 * Transform matrix to tridiagonal.
454 * @param matrix
455 * matrix to transform
456 */
457 private void transformToTridiagonal(final RealMatrix matrix) {
458
459 // transform the matrix to tridiagonal
460 transformer = new TriDiagonalTransformer(matrix);
461 main = transformer.getMainDiagonalRef();
462 secondary = transformer.getSecondaryDiagonalRef();
463
464 }
465
466 /**
467 * Find eigenvalues and eigenvectors (Dubrulle et al., 1971)
468 * @param householderMatrix Householder matrix of the transformation
469 * to tri-diagonal form.
470 */
471 private void findEigenVectors(double[][] householderMatrix) {
472
473 double[][]z = householderMatrix.clone();
474 final int n = main.length;
475 realEigenvalues = new double[n];
476 imagEigenvalues = new double[n];
477 double[] e = new double[n];
478 for (int i = 0; i < n - 1; i++) {
479 realEigenvalues[i] = main[i];
480 e[i] = secondary[i];
481 }
482 realEigenvalues[n - 1] = main[n - 1];
483 e[n - 1] = 0.0;
484
485 // Determine the largest main and secondary value in absolute term.
486 double maxAbsoluteValue=0.0;
487 for (int i = 0; i < n; i++) {
488 if (FastMath.abs(realEigenvalues[i])>maxAbsoluteValue) {
489 maxAbsoluteValue=FastMath.abs(realEigenvalues[i]);
490 }
491 if (FastMath.abs(e[i])>maxAbsoluteValue) {
492 maxAbsoluteValue=FastMath.abs(e[i]);
493 }
494 }
495 // Make null any main and secondary value too small to be significant
496 if (maxAbsoluteValue!=0.0) {
497 for (int i=0; i < n; i++) {
498 if (FastMath.abs(realEigenvalues[i])<=MathUtils.EPSILON*maxAbsoluteValue) {
499 realEigenvalues[i]=0.0;
500 }
501 if (FastMath.abs(e[i])<=MathUtils.EPSILON*maxAbsoluteValue) {
502 e[i]=0.0;
503 }
504 }
505 }
506
507 for (int j = 0; j < n; j++) {
508 int its = 0;
509 int m;
510 do {
511 for (m = j; m < n - 1; m++) {
512 double delta = FastMath.abs(realEigenvalues[m]) + FastMath.abs(realEigenvalues[m + 1]);
513 if (FastMath.abs(e[m]) + delta == delta) {
514 break;
515 }
516 }
517 if (m != j) {
518 if (its == maxIter)
519 throw new InvalidMatrixException(
520 new MaxIterationsExceededException(maxIter));
521 its++;
522 double q = (realEigenvalues[j + 1] - realEigenvalues[j]) / (2 * e[j]);
523 double t = FastMath.sqrt(1 + q * q);
524 if (q < 0.0) {
525 q = realEigenvalues[m] - realEigenvalues[j] + e[j] / (q - t);
526 } else {
527 q = realEigenvalues[m] - realEigenvalues[j] + e[j] / (q + t);
528 }
529 double u = 0.0;
530 double s = 1.0;
531 double c = 1.0;
532 int i;
533 for (i = m - 1; i >= j; i--) {
534 double p = s * e[i];
535 double h = c * e[i];
536 if (FastMath.abs(p) >= FastMath.abs(q)) {
537 c = q / p;
538 t = FastMath.sqrt(c * c + 1.0);
539 e[i + 1] = p * t;
540 s = 1.0 / t;
541 c = c * s;
542 } else {
543 s = p / q;
544 t = FastMath.sqrt(s * s + 1.0);
545 e[i + 1] = q * t;
546 c = 1.0 / t;
547 s = s * c;
548 }
549 if (e[i + 1] == 0.0) {
550 realEigenvalues[i + 1] -= u;
551 e[m] = 0.0;
552 break;
553 }
554 q = realEigenvalues[i + 1] - u;
555 t = (realEigenvalues[i] - q) * s + 2.0 * c * h;
556 u = s * t;
557 realEigenvalues[i + 1] = q + u;
558 q = c * t - h;
559 for (int ia = 0; ia < n; ia++) {
560 p = z[ia][i + 1];
561 z[ia][i + 1] = s * z[ia][i] + c * p;
562 z[ia][i] = c * z[ia][i] - s * p;
563 }
564 }
565 if (t == 0.0 && i >= j)
566 continue;
567 realEigenvalues[j] -= u;
568 e[j] = q;
569 e[m] = 0.0;
570 }
571 } while (m != j);
572 }
573
574 //Sort the eigen values (and vectors) in increase order
575 for (int i = 0; i < n; i++) {
576 int k = i;
577 double p = realEigenvalues[i];
578 for (int j = i + 1; j < n; j++) {
579 if (realEigenvalues[j] > p) {
580 k = j;
581 p = realEigenvalues[j];
582 }
583 }
584 if (k != i) {
585 realEigenvalues[k] = realEigenvalues[i];
586 realEigenvalues[i] = p;
587 for (int j = 0; j < n; j++) {
588 p = z[j][i];
589 z[j][i] = z[j][k];
590 z[j][k] = p;
591 }
592 }
593 }
594
595 // Determine the largest eigen value in absolute term.
596 maxAbsoluteValue=0.0;
597 for (int i = 0; i < n; i++) {
598 if (FastMath.abs(realEigenvalues[i])>maxAbsoluteValue) {
599 maxAbsoluteValue=FastMath.abs(realEigenvalues[i]);
600 }
601 }
602 // Make null any eigen value too small to be significant
603 if (maxAbsoluteValue!=0.0) {
604 for (int i=0; i < n; i++) {
605 if (FastMath.abs(realEigenvalues[i])<MathUtils.EPSILON*maxAbsoluteValue) {
606 realEigenvalues[i]=0.0;
607 }
608 }
609 }
610 eigenvectors = new ArrayRealVector[n];
611 double[] tmp = new double[n];
612 for (int i = 0; i < n; i++) {
613 for (int j = 0; j < n; j++) {
614 tmp[j] = z[j][i];
615 }
616 eigenvectors[i] = new ArrayRealVector(tmp);
617 }
618 }
619}