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 (double eigenvalue : eigenvalues) {
302 determinant *= eigenvalue;
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 × 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 (double v : eigenvalues) {
432 largestEigenvalueNorm = FastMath.max(largestEigenvalueNorm, FastMath.abs(v));
433 }
434 // Corner case: zero matrix, all exactly 0 eigenvalues
435 if (largestEigenvalueNorm == 0.0) {
436 return false;
437 }
438 for (double eigenvalue : eigenvalues) {
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(eigenvalue) / 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 }