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 java.util.ArrayList;
26 import java.util.List;
27
28 import org.hipparchus.complex.Complex;
29 import org.hipparchus.complex.ComplexField;
30 import org.hipparchus.exception.LocalizedCoreFormats;
31 import org.hipparchus.exception.MathIllegalStateException;
32 import org.hipparchus.exception.MathRuntimeException;
33 import org.hipparchus.util.FastMath;
34 import org.hipparchus.util.Precision;
35
36 /**
37 * Calculates the eigen decomposition of a non-symmetric real matrix.
38 * <p>
39 * The eigen decomposition of matrix A is a set of two matrices:
40 * \(V\) and \(D\) such that \(A V = V D\) where $\(A\),
41 * \(V\) and \(D\) are all \(m \times m\) matrices.
42 * <p>
43 * This class is similar in spirit to the {@code EigenvalueDecomposition}
44 * class from the <a href="http://math.nist.gov/javanumerics/jama/">JAMA</a>
45 * library, with the following changes:
46 * <ul>
47 * <li>a {@link #getVInv() getVInv} method has been added,</li>
48 * <li>z {@link #getEigenvalue(int) getEigenvalue} method to pick up a
49 * single eigenvalue has been added,</li>
50 * <li>a {@link #getEigenvector(int) getEigenvector} method to pick up a
51 * single eigenvector has been added,</li>
52 * <li>a {@link #getDeterminant() getDeterminant} method has been added.</li>
53 * </ul>
54 * <p>
55 * This class supports non-symmetric matrices, which have complex eigenvalues.
56 * Support for symmetric matrices is provided by {@link EigenDecompositionSymmetric}.
57 * </p>
58 * <p>
59 * As \(A\) is not symmetric, then the eigenvalue matrix \(D\) is block diagonal with
60 * the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, \(\lambda \pm i \mu\),
61 * in 2-by-2 blocks:
62 * </p>
63 * <p>
64 * \[
65 * \begin{bmatrix}
66 * \lambda & \mu\\
67 * -\mu & \lambda
68 * \end{bmatrix}
69 * \]
70 * </p>
71 * <p>
72 * The columns of \(V\) represent the eigenvectors in the sense that \(A V = V D\),
73 * i.e. {@code A.multiply(V)} equals {@code V.multiply(D)}.
74 * The matrix \(V\) may be badly conditioned, or even singular, so the validity of the
75 * equation \(A = V D V^{-1}\) depends upon the condition of \(V\).
76 * </p>
77 * <p>
78 * This implementation is based on the paper by A. Drubrulle, R.S. Martin and
79 * J.H. Wilkinson "The Implicit QL Algorithm" in Wilksinson and Reinsch (1971)
80 * Handbook for automatic computation, vol. 2, Linear algebra, Springer-Verlag,
81 * New-York.
82 *
83 * @see <a href="http://mathworld.wolfram.com/EigenDecomposition.html">MathWorld</a>
84 * @see <a href="http://en.wikipedia.org/wiki/Eigendecomposition_of_a_matrix">Wikipedia</a>
85 * @since 3.0
86 */
87 public class EigenDecompositionNonSymmetric {
88 /** Default epsilon value to use for internal epsilon **/
89 public static final double DEFAULT_EPSILON = 1e-12;
90 /** Internally used epsilon criteria. */
91 private final double epsilon;
92 /** eigenvalues. */
93 private Complex[] eigenvalues;
94 /** Eigenvectors. */
95 private List<FieldVector<Complex>> eigenvectors;
96 /** Cached value of \(V\). */
97 private RealMatrix cachedV;
98 /** Cached value of \(D\). */
99 private RealMatrix cachedD;
100 /** Cached value of \(V^{-1}\). */
101 private RealMatrix cachedVInv;
102
103 /** Calculates the eigen decomposition of the given real matrix.
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 EigenDecompositionNonSymmetric(final RealMatrix matrix) {
110 this(matrix, DEFAULT_EPSILON);
111 }
112
113 /** Calculates the eigen decomposition of the given real matrix.
114 * @param matrix Matrix to decompose.
115 * @param epsilon Epsilon used for internal tests (e.g. is singular, eigenvalue ratio, etc.)
116 * @throws MathIllegalStateException if the algorithm fails to converge.
117 * @throws MathRuntimeException if the decomposition of a general matrix
118 * results in a matrix with zero norm
119 */
120 public EigenDecompositionNonSymmetric(final RealMatrix matrix, double epsilon)
121 throws MathRuntimeException {
122 this.epsilon = epsilon;
123 findEigenVectorsFromSchur(transformToSchur(matrix));
124 }
125
126 /**
127 * Gets the matrix V of the decomposition.
128 * V is a matrix whose columns hold either the real or the
129 * imaginary part of eigenvectors.
130 *
131 * @return the V matrix.
132 */
133 public RealMatrix getV() {
134
135 if (cachedV == null) {
136 final int m = eigenvectors.size();
137 cachedV = MatrixUtils.createRealMatrix(m, m);
138 for (int k = 0; k < m; ++k) {
139 final FieldVector<Complex> ek = eigenvectors.get(k);
140 if (eigenvalues[k].getImaginaryPart() >= 0) {
141 // either it is a real eigenvalue, or it is the first of two conjugate eigenvalues,
142 // we pick up the real part of the eigenvector
143 for (int l = 0; l < m; ++l) {
144 cachedV.setEntry(l, k, ek.getEntry(l).getRealPart());
145 }
146 } else {
147 // second of two conjugate eigenvalues,
148 // we pick up the imaginary part of the eigenvector
149 for (int l = 0; l < m; ++l) {
150 cachedV.setEntry(l, k, -ek.getEntry(l).getImaginaryPart());
151 }
152 }
153 }
154 }
155
156 // return the cached matrix
157 return cachedV;
158
159 }
160
161 /**
162 * Gets the block diagonal matrix D of the decomposition.
163 * D is a block diagonal matrix.
164 * Real eigenvalues are on the diagonal while complex values are on
165 * 2x2 blocks { {real +imaginary}, {-imaginary, real} }.
166 *
167 * @return the D matrix.
168 */
169 public RealMatrix getD() {
170
171 if (cachedD == null) {
172 // cache the matrix for subsequent calls
173 cachedD = MatrixUtils.createRealMatrix(eigenvalues.length, eigenvalues.length);
174 for (int i = 0; i < eigenvalues.length; ++i) {
175 cachedD.setEntry(i, i, eigenvalues[i].getRealPart());
176 if (Precision.compareTo(eigenvalues[i].getImaginaryPart(), 0.0, epsilon) > 0) {
177 cachedD.setEntry(i, i + 1, eigenvalues[i].getImaginaryPart());
178 } else if (Precision.compareTo(eigenvalues[i].getImaginaryPart(), 0.0, epsilon) < 0) {
179 cachedD.setEntry(i, i - 1, eigenvalues[i].getImaginaryPart());
180 }
181 }
182 }
183 return cachedD;
184 }
185
186 /**
187 * Get's the value for epsilon which is used for internal tests (e.g. is singular, eigenvalue ratio, etc.)
188 *
189 * @return the epsilon value.
190 */
191 public double getEpsilon() {
192 return epsilon;
193 }
194
195 /**
196 * Gets the inverse of the matrix V of the decomposition.
197 *
198 * @return the inverse of the V matrix.
199 */
200 public RealMatrix getVInv() {
201
202 if (cachedVInv == null) {
203 cachedVInv = MatrixUtils.inverse(getV(), epsilon);
204 }
205
206 // return the cached matrix
207 return cachedVInv;
208 }
209
210 /**
211 * Gets a copy of the eigenvalues of the original matrix.
212 *
213 * @return a copy of the eigenvalues of the original matrix.
214 *
215 * @see #getD()
216 * @see #getEigenvalue(int)
217 */
218 public Complex[] getEigenvalues() {
219 return eigenvalues.clone();
220 }
221
222 /**
223 * Returns the i<sup>th</sup> eigenvalue of the original matrix.
224 *
225 * @param i index of the eigenvalue (counting from 0)
226 * @return i<sup>th</sup> eigenvalue of the original matrix.
227 *
228 * @see #getD()
229 * @see #getEigenvalues()
230 */
231 public Complex getEigenvalue(final int i) {
232 return eigenvalues[i];
233 }
234
235 /**
236 * Gets a copy of the i<sup>th</sup> eigenvector of the original matrix.
237 * <p>
238 * Note that if the the i<sup>th</sup> is complex this method will throw
239 * an exception.
240 * </p>
241 * @param i Index of the eigenvector (counting from 0).
242 * @return a copy of the i<sup>th</sup> eigenvector of the original matrix.
243 * @see #getD()
244 */
245 public FieldVector<Complex> getEigenvector(final int i) {
246 return eigenvectors.get(i).copy();
247 }
248
249 /**
250 * Computes the determinant of the matrix.
251 *
252 * @return the determinant of the matrix.
253 */
254 public Complex getDeterminant() {
255 Complex determinant = Complex.ONE;
256 for (Complex eigenvalue : eigenvalues) {
257 determinant = determinant.multiply(eigenvalue);
258 }
259 return determinant;
260 }
261
262 /**
263 * Transforms the matrix to Schur form and calculates the eigenvalues.
264 *
265 * @param matrix Matrix to transform.
266 * @return the {@link SchurTransformer Schur transform} for this matrix
267 */
268 private SchurTransformer transformToSchur(final RealMatrix matrix) {
269 final SchurTransformer schurTransform = new SchurTransformer(matrix, epsilon);
270 final double[][] matT = schurTransform.getT().getData();
271 final double norm = matrix.getNorm1();
272
273 eigenvalues = new Complex[matT.length];
274
275 int i = 0;
276 while (i < eigenvalues.length) {
277 if (i == (eigenvalues.length - 1) ||
278 Precision.equals(matT[i + 1][i], 0.0, norm * epsilon)) {
279 eigenvalues[i] = new Complex(matT[i][i]);
280 i++;
281 } else {
282 final double x = matT[i + 1][i + 1];
283 final double p = 0.5 * (matT[i][i] - x);
284 final double z = FastMath.sqrt(FastMath.abs(p * p + matT[i + 1][i] * matT[i][i + 1]));
285 eigenvalues[i++] = new Complex(x + p, +z);
286 eigenvalues[i++] = new Complex(x + p, -z);
287 }
288 }
289 return schurTransform;
290 }
291
292 /**
293 * Performs a division of two complex numbers.
294 *
295 * @param xr real part of the first number
296 * @param xi imaginary part of the first number
297 * @param yr real part of the second number
298 * @param yi imaginary part of the second number
299 * @return result of the complex division
300 */
301 private Complex cdiv(final double xr, final double xi,
302 final double yr, final double yi) {
303 return new Complex(xr, xi).divide(new Complex(yr, yi));
304 }
305
306 /**
307 * Find eigenvectors from a matrix transformed to Schur form.
308 *
309 * @param schur the schur transformation of the matrix
310 * @throws MathRuntimeException if the Schur form has a norm of zero
311 */
312 private void findEigenVectorsFromSchur(final SchurTransformer schur)
313 throws MathRuntimeException {
314 final double[][] matrixT = schur.getT().getData();
315 final double[][] matrixP = schur.getP().getData();
316
317 final int n = matrixT.length;
318
319 // compute matrix norm
320 double norm = 0.0;
321 for (int i = 0; i < n; i++) {
322 for (int j = FastMath.max(i - 1, 0); j < n; j++) {
323 norm += FastMath.abs(matrixT[i][j]);
324 }
325 }
326
327 // we can not handle a matrix with zero norm
328 if (norm == 0.0) {
329 throw new MathRuntimeException(LocalizedCoreFormats.ZERO_NORM);
330 }
331
332 // Backsubstitute to find vectors of upper triangular form
333
334 double r = 0.0;
335 double s = 0.0;
336 double z = 0.0;
337
338 for (int idx = n - 1; idx >= 0; idx--) {
339 double p = eigenvalues[idx].getRealPart();
340 double q = eigenvalues[idx].getImaginaryPart();
341
342 if (Precision.equals(q, 0.0)) {
343 // Real vector
344 int l = idx;
345 matrixT[idx][idx] = 1.0;
346 for (int i = idx - 1; i >= 0; i--) {
347 double w = matrixT[i][i] - p;
348 r = 0.0;
349 for (int j = l; j <= idx; j++) {
350 r += matrixT[i][j] * matrixT[j][idx];
351 }
352 if (Precision.compareTo(eigenvalues[i].getImaginaryPart(), 0.0, epsilon) < 0) {
353 z = w;
354 s = r;
355 } else {
356 l = i;
357 if (Precision.equals(eigenvalues[i].getImaginaryPart(), 0.0)) {
358 if (w != 0.0) {
359 matrixT[i][idx] = -r / w;
360 } else {
361 matrixT[i][idx] = -r / (Precision.EPSILON * norm);
362 }
363 } else {
364 // Solve real equations
365 double x = matrixT[i][i + 1];
366 double y = matrixT[i + 1][i];
367 q = (eigenvalues[i].getRealPart() - p) * (eigenvalues[i].getRealPart() - p) +
368 eigenvalues[i].getImaginaryPart() * eigenvalues[i].getImaginaryPart();
369 double t = (x * s - z * r) / q;
370 matrixT[i][idx] = t;
371 if (FastMath.abs(x) > FastMath.abs(z)) {
372 matrixT[i + 1][idx] = (-r - w * t) / x;
373 } else {
374 matrixT[i + 1][idx] = (-s - y * t) / z;
375 }
376 }
377
378 // Overflow control
379 double t = FastMath.abs(matrixT[i][idx]);
380 if ((Precision.EPSILON * t) * t > 1) {
381 for (int j = i; j <= idx; j++) {
382 matrixT[j][idx] /= t;
383 }
384 }
385 }
386 }
387 } else if (q < 0.0) {
388 // Complex vector
389 int l = idx - 1;
390
391 // Last vector component imaginary so matrix is triangular
392 if (FastMath.abs(matrixT[idx][idx - 1]) > FastMath.abs(matrixT[idx - 1][idx])) {
393 matrixT[idx - 1][idx - 1] = q / matrixT[idx][idx - 1];
394 matrixT[idx - 1][idx] = -(matrixT[idx][idx] - p) / matrixT[idx][idx - 1];
395 } else {
396 final Complex result = cdiv(0.0, -matrixT[idx - 1][idx],
397 matrixT[idx - 1][idx - 1] - p, q);
398 matrixT[idx - 1][idx - 1] = result.getReal();
399 matrixT[idx - 1][idx] = result.getImaginary();
400 }
401
402 matrixT[idx][idx - 1] = 0.0;
403 matrixT[idx][idx] = 1.0;
404
405 for (int i = idx - 2; i >= 0; i--) {
406 double ra = 0.0;
407 double sa = 0.0;
408 for (int j = l; j <= idx; j++) {
409 ra += matrixT[i][j] * matrixT[j][idx - 1];
410 sa += matrixT[i][j] * matrixT[j][idx];
411 }
412 double w = matrixT[i][i] - p;
413
414 if (Precision.compareTo(eigenvalues[i].getImaginaryPart(), 0.0, epsilon) < 0) {
415 z = w;
416 r = ra;
417 s = sa;
418 } else {
419 l = i;
420 if (Precision.equals(eigenvalues[i].getImaginaryPart(), 0.0)) {
421 final Complex c = cdiv(-ra, -sa, w, q);
422 matrixT[i][idx - 1] = c.getReal();
423 matrixT[i][idx] = c.getImaginary();
424 } else {
425 // Solve complex equations
426 double x = matrixT[i][i + 1];
427 double y = matrixT[i + 1][i];
428 double vr = (eigenvalues[i].getRealPart() - p) * (eigenvalues[i].getRealPart() - p) +
429 eigenvalues[i].getImaginaryPart() * eigenvalues[i].getImaginaryPart() - q * q;
430 final double vi = (eigenvalues[i].getRealPart() - p) * 2.0 * q;
431 if (Precision.equals(vr, 0.0) && Precision.equals(vi, 0.0)) {
432 vr = Precision.EPSILON * norm *
433 (FastMath.abs(w) + FastMath.abs(q) + FastMath.abs(x) +
434 FastMath.abs(y) + FastMath.abs(z));
435 }
436 final Complex c = cdiv(x * r - z * ra + q * sa,
437 x * s - z * sa - q * ra, vr, vi);
438 matrixT[i][idx - 1] = c.getReal();
439 matrixT[i][idx] = c.getImaginary();
440
441 if (FastMath.abs(x) > (FastMath.abs(z) + FastMath.abs(q))) {
442 matrixT[i + 1][idx - 1] = (-ra - w * matrixT[i][idx - 1] +
443 q * matrixT[i][idx]) / x;
444 matrixT[i + 1][idx] = (-sa - w * matrixT[i][idx] -
445 q * matrixT[i][idx - 1]) / x;
446 } else {
447 final Complex c2 = cdiv(-r - y * matrixT[i][idx - 1],
448 -s - y * matrixT[i][idx], z, q);
449 matrixT[i + 1][idx - 1] = c2.getReal();
450 matrixT[i + 1][idx] = c2.getImaginary();
451 }
452 }
453
454 // Overflow control
455 double t = FastMath.max(FastMath.abs(matrixT[i][idx - 1]),
456 FastMath.abs(matrixT[i][idx]));
457 if ((Precision.EPSILON * t) * t > 1) {
458 for (int j = i; j <= idx; j++) {
459 matrixT[j][idx - 1] /= t;
460 matrixT[j][idx] /= t;
461 }
462 }
463 }
464 }
465 }
466 }
467
468 // Back transformation to get eigenvectors of original matrix
469 for (int j = n - 1; j >= 0; j--) {
470 for (int i = 0; i <= n - 1; i++) {
471 z = 0.0;
472 for (int k = 0; k <= FastMath.min(j, n - 1); k++) {
473 z += matrixP[i][k] * matrixT[k][j];
474 }
475 matrixP[i][j] = z;
476 }
477 }
478
479 eigenvectors = new ArrayList<>(n);
480 for (int i = 0; i < n; i++) {
481 FieldVector<Complex> ei = new ArrayFieldVector<>(ComplexField.getInstance(), n);
482 for (int j = 0; j < n; j++) {
483 if (Precision.compareTo(eigenvalues[i].getImaginaryPart(), 0.0, epsilon) > 0) {
484 ei.setEntry(j, new Complex(matrixP[j][i], +matrixP[j][i + 1]));
485 } else if (Precision.compareTo(eigenvalues[i].getImaginaryPart(), 0.0, epsilon) < 0) {
486 ei.setEntry(j, new Complex(matrixP[j][i - 1], -matrixP[j][i]));
487 } else {
488 ei.setEntry(j, new Complex(matrixP[j][i]));
489 }
490 }
491 eigenvectors.add(ei);
492 }
493 }
494 }