View Javadoc
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    *      https://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  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  
23  package org.hipparchus.linear;
24  
25  import org.hipparchus.exception.LocalizedCoreFormats;
26  import org.hipparchus.exception.MathIllegalArgumentException;
27  import org.hipparchus.exception.MathIllegalStateException;
28  import org.hipparchus.exception.MathRuntimeException;
29  import org.hipparchus.util.FastMath;
30  import org.hipparchus.util.Precision;
31  
32  /**
33   * Calculates the eigen decomposition of a symmetric real matrix.
34   * <p>
35   * The eigen decomposition of matrix A is a set of two matrices:
36   * \(V\) and \(D\) such that \(A V = V D\) where $\(A\),
37   * \(V\) and \(D\) are all \(m \times m\) matrices.
38   * <p>
39   * This class is similar in spirit to the {@code EigenvalueDecomposition}
40   * class from the <a href="http://math.nist.gov/javanumerics/jama/">JAMA</a>
41   * library, with the following changes:
42   * </p>
43   * <ul>
44   *   <li>a {@link #getVT() getVt} method has been added,</li>
45   *   <li>a {@link #getEigenvalue(int) getEigenvalue} method to pick up a
46   *       single eigenvalue has been added,</li>
47   *   <li>a {@link #getEigenvector(int) getEigenvector} method to pick up a
48   *       single eigenvector has been added,</li>
49   *   <li>a {@link #getDeterminant() getDeterminant} method has been added.</li>
50   *   <li>a {@link #getSolver() getSolver} method has been added.</li>
51   * </ul>
52   * <p>
53   * As \(A\) is symmetric, then \(A = V D V^T\) where the eigenvalue matrix \(D\)
54   * is diagonal and the eigenvector matrix \(V\) is orthogonal, i.e.
55   * {@code A = V.multiply(D.multiply(V.transpose()))} and
56   * {@code V.multiply(V.transpose())} equals the identity matrix.
57   * </p>
58   * <p>
59   * The columns of \(V\) represent the eigenvectors in the sense that \(A V = V D\),
60   * i.e. {@code A.multiply(V)} equals {@code V.multiply(D)}.
61   * The matrix \(V\) may be badly conditioned, or even singular, so the validity of the
62   * equation \(A = V D V^{-1}\) depends upon the condition of \(V\).
63   * </p>
64   * This implementation is based on the paper by A. Drubrulle, R.S. Martin and
65   * J.H. Wilkinson "The Implicit QL Algorithm" in Wilksinson and Reinsch (1971)
66   * Handbook for automatic computation, vol. 2, Linear algebra, Springer-Verlag,
67   * New-York.
68   *
69   * @see <a href="http://mathworld.wolfram.com/EigenDecomposition.html">MathWorld</a>
70   * @see <a href="http://en.wikipedia.org/wiki/Eigendecomposition_of_a_matrix">Wikipedia</a>
71   */
72  public class EigenDecompositionSymmetric {
73  
74      /** Default epsilon value to use for internal epsilon **/
75      public static final double DEFAULT_EPSILON = 1e-12;
76  
77      /** Maximum number of iterations accepted in the implicit QL transformation */
78      private static final byte MAX_ITER = 30;
79  
80      /** Internally used epsilon criteria. */
81      private final double epsilon;
82  
83      /** Eigenvalues. */
84      private double[] eigenvalues;
85  
86      /** Eigenvectors. */
87      private ArrayRealVector[] eigenvectors;
88  
89      /** Cached value of V. */
90      private RealMatrix cachedV;
91  
92      /** Cached value of D. */
93      private DiagonalMatrix cachedD;
94  
95      /** Cached value of Vt. */
96      private RealMatrix cachedVt;
97  
98      /**
99       * Calculates the eigen decomposition of the given symmetric real matrix.
100      * <p>
101      * This constructor uses the {@link #DEFAULT_EPSILON default epsilon} and
102      * decreasing order for eigenvalues.
103      * </p>
104      * @param matrix Matrix to decompose.
105      * @throws MathIllegalStateException if the algorithm fails to converge.
106      * @throws MathRuntimeException if the decomposition of a general matrix
107      * results in a matrix with zero norm
108      */
109     public EigenDecompositionSymmetric(final RealMatrix matrix) {
110         this(matrix, DEFAULT_EPSILON, true);
111     }
112 
113     /**
114      * Calculates the eigen decomposition of the given real matrix.
115      * <p>
116      * Supports decomposition of a general matrix since 3.1.
117      *
118      * @param matrix Matrix to decompose.
119      * @param epsilon Epsilon used for internal tests (e.g. is singular, eigenvalue ratio, etc.)
120      * @param decreasing if true, eigenvalues will be sorted in decreasing order
121      * @throws MathIllegalStateException if the algorithm fails to converge.
122      * @throws MathRuntimeException if the decomposition of a general matrix
123      * results in a matrix with zero norm
124      * @since 3.0
125      */
126     public EigenDecompositionSymmetric(final RealMatrix matrix,
127                                        final double epsilon, final boolean decreasing)
128         throws MathRuntimeException {
129 
130         this.epsilon = epsilon;
131         MatrixUtils.checkSymmetric(matrix, epsilon);
132 
133         // transform the matrix to tridiagonal
134         final TriDiagonalTransformer transformer = new TriDiagonalTransformer(matrix);
135 
136         findEigenVectors(transformer.getMainDiagonalRef(),
137                          transformer.getSecondaryDiagonalRef(),
138                          transformer.getQ().getData(),
139                          decreasing);
140 
141     }
142 
143     /**
144      * Calculates the eigen decomposition of the symmetric tridiagonal matrix.
145      * <p>
146      * The Householder matrix is assumed to be the identity matrix.
147      * </p>
148      * <p>
149      * This constructor uses the {@link #DEFAULT_EPSILON default epsilon} and
150      * decreasing order for eigenvalues.
151      * </p>
152      * @param main Main diagonal of the symmetric tridiagonal form.
153      * @param secondary Secondary of the tridiagonal form.
154      * @throws MathIllegalStateException if the algorithm fails to converge.
155      */
156     public EigenDecompositionSymmetric(final double[] main, final double[] secondary) {
157         this(main, secondary, DEFAULT_EPSILON, true);
158     }
159 
160     /**
161      * Calculates the eigen decomposition of the symmetric tridiagonal
162      * matrix.  The Householder matrix is assumed to be the identity matrix.
163      *
164      * @param main Main diagonal of the symmetric tridiagonal form.
165      * @param secondary Secondary of the tridiagonal form.
166      * @param epsilon Epsilon used for internal tests (e.g. is singular, eigenvalue ratio, etc.)
167      * @param decreasing if true, eigenvalues will be sorted in decreasing order
168      * @throws MathIllegalStateException if the algorithm fails to converge.
169      * @since 3.0
170      */
171     public EigenDecompositionSymmetric(final double[] main, final double[] secondary,
172                                        final double epsilon, final boolean decreasing) {
173         this.epsilon = epsilon;
174         final int size = main.length;
175         final double[][] z = new double[size][size];
176         for (int i = 0; i < size; i++) {
177             z[i][i] = 1.0;
178         }
179         findEigenVectors(main.clone(), secondary.clone(), z, decreasing);
180     }
181 
182     /**
183      * Gets the matrix V of the decomposition.
184      * V is an orthogonal matrix, i.e. its transpose is also its inverse.
185      * The columns of V are the eigenvectors of the original matrix.
186      * No assumption is made about the orientation of the system axes formed
187      * by the columns of V (e.g. in a 3-dimension space, V can form a left-
188      * or right-handed system).
189      *
190      * @return the V matrix.
191      */
192     public RealMatrix getV() {
193 
194         if (cachedV == null) {
195             final int m = eigenvectors.length;
196             cachedV = MatrixUtils.createRealMatrix(m, m);
197             for (int k = 0; k < m; ++k) {
198                 cachedV.setColumnVector(k, eigenvectors[k]);
199             }
200         }
201         // return the cached matrix
202         return cachedV;
203     }
204 
205     /**
206      * Gets the diagonal matrix D of the decomposition.
207      * D is a diagonal matrix.
208      * @return the D matrix.
209      *
210      * @see #getEigenvalues()
211       */
212     public DiagonalMatrix getD() {
213 
214         if (cachedD == null) {
215             // cache the matrix for subsequent calls
216             cachedD = new DiagonalMatrix(eigenvalues);
217         }
218 
219         return cachedD;
220 
221     }
222 
223     /**
224      * Get's the value for epsilon which is used for internal tests (e.g. is singular, eigenvalue ratio, etc.)
225      *
226      * @return the epsilon value.
227      */
228     public double getEpsilon() { return epsilon; }
229 
230     /**
231      * Gets the transpose of the matrix V of the decomposition.
232      * V is an orthogonal matrix, i.e. its transpose is also its inverse.
233      * The columns of V are the eigenvectors of the original matrix.
234      * No assumption is made about the orientation of the system axes formed
235      * by the columns of V (e.g. in a 3-dimension space, V can form a left-
236      * or right-handed system).
237      *
238      * @return the transpose of the V matrix.
239      */
240     public RealMatrix getVT() {
241 
242         if (cachedVt == null) {
243             final int m = eigenvectors.length;
244             cachedVt = MatrixUtils.createRealMatrix(m, m);
245             for (int k = 0; k < m; ++k) {
246                 cachedVt.setRowVector(k, eigenvectors[k]);
247             }
248         }
249 
250         // return the cached matrix
251         return cachedVt;
252     }
253 
254     /**
255      * Gets a copy of the eigenvalues of the original matrix.
256      *
257      * @return a copy of the eigenvalues of the original matrix.
258      *
259      * @see #getD()
260      * @see #getEigenvalue(int)
261      */
262     public double[] getEigenvalues() {
263         return eigenvalues.clone();
264     }
265 
266     /**
267      * Returns the i<sup>th</sup> eigenvalue of the original matrix.
268      *
269      * @param i index of the eigenvalue (counting from 0)
270      * @return real part of the i<sup>th</sup> eigenvalue of the original
271      * matrix.
272      *
273      * @see #getD()
274      * @see #getEigenvalues()
275      */
276     public double getEigenvalue(final int i) {
277         return eigenvalues[i];
278     }
279 
280     /**
281      * Gets a copy of the i<sup>th</sup> eigenvector of the original matrix.
282      * <p>
283      * Note that if the the i<sup>th</sup> is complex this method will throw
284      * an exception.
285      * </p>
286      * @param i Index of the eigenvector (counting from 0).
287      * @return a copy of the i<sup>th</sup> eigenvector of the original matrix.
288      * @see #getD()
289      */
290     public RealVector getEigenvector(final int i) {
291         return eigenvectors[i].copy();
292     }
293 
294     /**
295      * Computes the determinant of the matrix.
296      *
297      * @return the determinant of the matrix.
298      */
299     public double getDeterminant() {
300         double determinant = 1;
301         for (int i = 0; i < eigenvalues.length; ++i) {
302             determinant *= eigenvalues[i];
303         }
304         return determinant;
305     }
306 
307     /**
308      * Computes the square-root of the matrix.
309      * This implementation assumes that the matrix is positive definite.
310      *
311      * @return the square-root of the matrix.
312      * @throws MathRuntimeException if the matrix is not
313      * symmetric or not positive definite.
314      */
315     public RealMatrix getSquareRoot() {
316 
317         final double[] sqrtEigenValues = new double[eigenvalues.length];
318         for (int i = 0; i < eigenvalues.length; i++) {
319             final double eigen = eigenvalues[i];
320             if (eigen <= 0) {
321                 throw new MathRuntimeException(LocalizedCoreFormats.UNSUPPORTED_OPERATION);
322             }
323             sqrtEigenValues[i] = FastMath.sqrt(eigen);
324         }
325         final RealMatrix sqrtEigen = MatrixUtils.createRealDiagonalMatrix(sqrtEigenValues);
326         final RealMatrix v = getV();
327         final RealMatrix vT = getVT();
328 
329         return v.multiply(sqrtEigen).multiply(vT);
330 
331     }
332 
333     /** Gets a solver for finding the \(A \times X = B\) solution in exact linear sense.
334      * @return a solver
335      */
336     public DecompositionSolver getSolver() {
337         return new Solver();
338     }
339 
340     /** Specialized solver. */
341     private class Solver implements DecompositionSolver {
342 
343         /**
344          * Solves the linear equation \(A \times X = B\)for symmetric matrices A.
345          * <p>
346          * This method only finds exact linear solutions, i.e. solutions for
347          * which ||A &times; X - B|| is exactly 0.
348          * </p>
349          *
350          * @param b Right-hand side of the equation \(A \times X = B\).
351          * @return a Vector X that minimizes the 2-norm of \(A \times X - B\).
352          *
353          * @throws MathIllegalArgumentException if the matrices dimensions do not match.
354          * @throws MathIllegalArgumentException if the decomposed matrix is singular.
355          */
356         @Override
357         public RealVector solve(final RealVector b) {
358             if (!isNonSingular()) {
359                 throw new MathIllegalArgumentException(LocalizedCoreFormats.SINGULAR_MATRIX);
360             }
361 
362             final int m = eigenvalues.length;
363             if (b.getDimension() != m) {
364                 throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
365                                                        b.getDimension(), m);
366             }
367 
368             final double[] bp = new double[m];
369             for (int i = 0; i < m; ++i) {
370                 final ArrayRealVector v = eigenvectors[i];
371                 final double[] vData = v.getDataRef();
372                 final double s = v.dotProduct(b) / eigenvalues[i];
373                 for (int j = 0; j < m; ++j) {
374                     bp[j] += s * vData[j];
375                 }
376             }
377 
378             return new ArrayRealVector(bp, false);
379         }
380 
381         /** {@inheritDoc} */
382         @Override
383         public RealMatrix solve(RealMatrix b) {
384 
385             if (!isNonSingular()) {
386                 throw new MathIllegalArgumentException(LocalizedCoreFormats.SINGULAR_MATRIX);
387             }
388 
389             final int m = eigenvalues.length;
390             if (b.getRowDimension() != m) {
391                 throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
392                                                        b.getRowDimension(), m);
393             }
394 
395             final int nColB = b.getColumnDimension();
396             final double[][] bp = new double[m][nColB];
397             final double[] tmpCol = new double[m];
398             for (int k = 0; k < nColB; ++k) {
399                 for (int i = 0; i < m; ++i) {
400                     tmpCol[i] = b.getEntry(i, k);
401                     bp[i][k]  = 0;
402                 }
403                 for (int i = 0; i < m; ++i) {
404                     final ArrayRealVector v = eigenvectors[i];
405                     final double[] vData = v.getDataRef();
406                     double s = 0;
407                     for (int j = 0; j < m; ++j) {
408                         s += v.getEntry(j) * tmpCol[j];
409                     }
410                     s /= eigenvalues[i];
411                     for (int j = 0; j < m; ++j) {
412                         bp[j][k] += s * vData[j];
413                     }
414                 }
415             }
416 
417             return new Array2DRowRealMatrix(bp, false);
418 
419         }
420 
421         /**
422          * Checks whether the decomposed matrix is non-singular.
423          *
424          * @return true if the decomposed matrix is non-singular.
425          */
426         @Override
427         public boolean isNonSingular() {
428             double largestEigenvalueNorm = 0.0;
429             // Looping over all values (in case they are not sorted in decreasing
430             // order of their norm).
431             for (int i = 0; i < eigenvalues.length; ++i) {
432                 largestEigenvalueNorm = FastMath.max(largestEigenvalueNorm, FastMath.abs(eigenvalues[i]));
433             }
434             // Corner case: zero matrix, all exactly 0 eigenvalues
435             if (largestEigenvalueNorm == 0.0) {
436                 return false;
437             }
438             for (int i = 0; i < eigenvalues.length; ++i) {
439                 // Looking for eigenvalues that are 0, where we consider anything much much smaller
440                 // than the largest eigenvalue to be effectively 0.
441                 if (Precision.equals(FastMath.abs(eigenvalues[i]) / largestEigenvalueNorm, 0, epsilon)) {
442                     return false;
443                 }
444             }
445             return true;
446         }
447 
448         /**
449          * Get the inverse of the decomposed matrix.
450          *
451          * @return the inverse matrix.
452          * @throws MathIllegalArgumentException if the decomposed matrix is singular.
453          */
454         @Override
455         public RealMatrix getInverse() {
456             if (!isNonSingular()) {
457                 throw new MathIllegalArgumentException(LocalizedCoreFormats.SINGULAR_MATRIX);
458             }
459 
460             final int m = eigenvalues.length;
461             final double[][] invData = new double[m][m];
462 
463             for (int i = 0; i < m; ++i) {
464                 final double[] invI = invData[i];
465                 for (int j = 0; j < m; ++j) {
466                     double invIJ = 0;
467                     for (int k = 0; k < m; ++k) {
468                         final double[] vK = eigenvectors[k].getDataRef();
469                         invIJ += vK[i] * vK[j] / eigenvalues[k];
470                     }
471                     invI[j] = invIJ;
472                 }
473             }
474             return MatrixUtils.createRealMatrix(invData);
475         }
476 
477         /** {@inheritDoc} */
478         @Override
479         public int getRowDimension() {
480             return eigenvalues.length;
481         }
482 
483         /** {@inheritDoc} */
484         @Override
485         public int getColumnDimension() {
486             return eigenvalues.length;
487         }
488 
489     }
490 
491     /**
492      * Find eigenvalues and eigenvectors (Dubrulle et al., 1971)
493      * @param main main diagonal of the tridiagonal matrix
494      * @param secondary secondary diagonal of the tridiagonal matrix
495      * @param householderMatrix Householder matrix of the transformation
496      * @param decreasing if true, eigenvalues will be sorted in decreasing order
497      * to tridiagonal form.
498      */
499     private void findEigenVectors(final double[] main, final double[] secondary,
500                                   final double[][] householderMatrix, final boolean decreasing) {
501         final double[][]z = householderMatrix.clone();
502         final int n = main.length;
503         eigenvalues = new double[n];
504         final double[] e = new double[n];
505         for (int i = 0; i < n - 1; i++) {
506             eigenvalues[i] = main[i];
507             e[i] = secondary[i];
508         }
509         eigenvalues[n - 1] = main[n - 1];
510         e[n - 1] = 0;
511 
512         // Determine the largest main and secondary value in absolute term.
513         double maxAbsoluteValue = 0;
514         for (int i = 0; i < n; i++) {
515             if (FastMath.abs(eigenvalues[i]) > maxAbsoluteValue) {
516                 maxAbsoluteValue = FastMath.abs(eigenvalues[i]);
517             }
518             if (FastMath.abs(e[i]) > maxAbsoluteValue) {
519                 maxAbsoluteValue = FastMath.abs(e[i]);
520             }
521         }
522         // Make null any main and secondary value too small to be significant
523         if (maxAbsoluteValue != 0) {
524             for (int i=0; i < n; i++) {
525                 if (FastMath.abs(eigenvalues[i]) <= Precision.EPSILON * maxAbsoluteValue) {
526                     eigenvalues[i] = 0;
527                 }
528                 if (FastMath.abs(e[i]) <= Precision.EPSILON * maxAbsoluteValue) {
529                     e[i]=0;
530                 }
531             }
532         }
533 
534         for (int j = 0; j < n; j++) {
535             int its = 0;
536             int m;
537             do {
538                 for (m = j; m < n - 1; m++) {
539                     double delta = FastMath.abs(eigenvalues[m]) +
540                         FastMath.abs(eigenvalues[m + 1]);
541                     if (FastMath.abs(e[m]) + delta == delta) {
542                         break;
543                     }
544                 }
545                 if (m != j) {
546                     if (its == MAX_ITER) {
547                         throw new MathIllegalStateException(LocalizedCoreFormats.CONVERGENCE_FAILED,
548                                                             MAX_ITER);
549                     }
550                     its++;
551                     double q = (eigenvalues[j + 1] - eigenvalues[j]) / (2 * e[j]);
552                     double t = FastMath.sqrt(1 + q * q);
553                     if (q < 0.0) {
554                         q = eigenvalues[m] - eigenvalues[j] + e[j] / (q - t);
555                     } else {
556                         q = eigenvalues[m] - eigenvalues[j] + e[j] / (q + t);
557                     }
558                     double u = 0.0;
559                     double s = 1.0;
560                     double c = 1.0;
561                     int i;
562                     for (i = m - 1; i >= j; i--) {
563                         double p = s * e[i];
564                         double h = c * e[i];
565                         if (FastMath.abs(p) >= FastMath.abs(q)) {
566                             c = q / p;
567                             t = FastMath.sqrt(c * c + 1.0);
568                             e[i + 1] = p * t;
569                             s = 1.0 / t;
570                             c *= s;
571                         } else {
572                             s = p / q;
573                             t = FastMath.sqrt(s * s + 1.0);
574                             e[i + 1] = q * t;
575                             c = 1.0 / t;
576                             s *= c;
577                         }
578                         if (e[i + 1] == 0.0) {
579                             eigenvalues[i + 1] -= u;
580                             e[m] = 0.0;
581                             break;
582                         }
583                         q = eigenvalues[i + 1] - u;
584                         t = (eigenvalues[i] - q) * s + 2.0 * c * h;
585                         u = s * t;
586                         eigenvalues[i + 1] = q + u;
587                         q = c * t - h;
588                         for (int ia = 0; ia < n; ia++) {
589                             p = z[ia][i + 1];
590                             z[ia][i + 1] = s * z[ia][i] + c * p;
591                             z[ia][i] = c * z[ia][i] - s * p;
592                         }
593                     }
594                     if (t == 0.0 && i >= j) {
595                         continue;
596                     }
597                     eigenvalues[j] -= u;
598                     e[j] = q;
599                     e[m] = 0.0;
600                 }
601             } while (m != j);
602         }
603 
604         // Sort the eigen values (and vectors) in desired order
605         for (int i = 0; i < n; i++) {
606             int k = i;
607             double p = eigenvalues[i];
608             for (int j = i + 1; j < n; j++) {
609                 if (eigenvalues[j] > p == decreasing) {
610                     k = j;
611                     p = eigenvalues[j];
612                 }
613             }
614             if (k != i) {
615                 eigenvalues[k] = eigenvalues[i];
616                 eigenvalues[i] = p;
617                 for (int j = 0; j < n; j++) {
618                     p = z[j][i];
619                     z[j][i] = z[j][k];
620                     z[j][k] = p;
621                 }
622             }
623         }
624 
625         // Determine the largest eigen value in absolute term.
626         maxAbsoluteValue = 0;
627         for (int i = 0; i < n; i++) {
628             if (FastMath.abs(eigenvalues[i]) > maxAbsoluteValue) {
629                 maxAbsoluteValue = FastMath.abs(eigenvalues[i]);
630             }
631         }
632         // Make null any eigen value too small to be significant
633         if (maxAbsoluteValue != 0.0) {
634             for (int i=0; i < n; i++) {
635                 if (FastMath.abs(eigenvalues[i]) < Precision.EPSILON * maxAbsoluteValue) {
636                     eigenvalues[i] = 0;
637                 }
638             }
639         }
640         eigenvectors = new ArrayRealVector[n];
641         for (int i = 0; i < n; i++) {
642             eigenvectors[i] = new ArrayRealVector(n);
643             for (int j = 0; j < n; j++) {
644                 eigenvectors[i].setEntry(j, z[j][i]);
645             }
646         }
647     }
648 
649 }