1 /*
2 * Licensed to the Hipparchus project 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 Hipparchus project 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 package org.hipparchus.linear;
18
19 import java.lang.reflect.Array;
20
21 import org.hipparchus.complex.Complex;
22 import org.hipparchus.complex.ComplexField;
23 import org.hipparchus.exception.LocalizedCoreFormats;
24 import org.hipparchus.exception.MathIllegalArgumentException;
25 import org.hipparchus.exception.MathRuntimeException;
26 import org.hipparchus.util.FastMath;
27 import org.hipparchus.util.Precision;
28
29 /**
30 * Given a matrix A, it computes a complex eigen decomposition AV = VD.
31 *
32 * <p>
33 * Complex Eigen Decomposition differs from the {@link EigenDecompositionSymmetric} since it
34 * computes the eigen vectors as complex eigen vectors (if applicable).
35 * </p>
36 *
37 * <p>
38 * Beware that in the complex case, you do not always have \(V \times V^{T} = I\) or even a
39 * diagonal matrix, even if the eigenvectors that form the columns of the V
40 * matrix are independent. On example is the square matrix
41 * \[
42 * A = \left(\begin{matrix}
43 * 3 & -2\\
44 * 4 & -1
45 * \end{matrix}\right)
46 * \]
47 * which has two conjugate eigenvalues \(\lambda_1=1+2i\) and \(\lambda_2=1-2i\)
48 * with associated eigenvectors \(v_1^T = (1, 1-i)\) and \(v_2^T = (1, 1+i)\).
49 * \[
50 * V\timesV^T = \left(\begin{matrix}
51 * 2 & 2\\
52 * 2 & 0
53 * \end{matrix}\right)
54 * \]
55 * which is not the identity matrix. Therefore, despite \(A \times V = V \times D\),
56 * \(A \ne V \times D \time V^T\), which would hold for real eigendecomposition.
57 * </p>
58 * <p>
59 * Also note that for consistency with Wolfram langage
60 * <a href="https://reference.wolfram.com/language/ref/Eigenvectors.html">eigenvectors</a>,
61 * we add zero vectors when the geometric multiplicity of the eigenvalue is smaller than
62 * its algebraic multiplicity (hence the regular eigenvector matrix should be non-square).
63 * With these additional null vectors, the eigenvectors matrix becomes square. This happens
64 * for example with the square matrix
65 * \[
66 * A = \left(\begin{matrix}
67 * 1 & 0 & 0\\
68 * -2 & 1 & 0\\
69 * 0 & 0 & 1
70 * \end{matrix}\right)
71 * \]
72 * Its characteristic polynomial is \((1-\lambda)^3\), hence is has one eigen value \(\lambda=1\)
73 * with algebraic multiplicity 3. However, this eigenvalue leads to only two eigenvectors
74 * \(v_1=(0, 1, 0)\) and \(v_2=(0, 0, 1)\), hence its geometric multiplicity is only 2, not 3.
75 * So we add a third zero vector \(v_3=(0, 0, 0)\), in the same way Wolfram language does.
76 * </p>
77 *
78 * Compute complex eigen values from the Schur transform. Compute complex eigen
79 * vectors based on eigen values and the inverse iteration method.
80 *
81 * see: <a href="https://en.wikipedia.org/wiki/Inverse_iteration">Inverse iteration</a>
82 * <a href="https://en.wikiversity.org/wiki/Shifted_inverse_iteration">Shifted inverse iteration</a>
83 * <a href="https://www.robots.ox.ac.uk/~sjrob/Teaching/EngComp/ecl4.pdf">Computation of matrix eigenvalues and eigenvectors</a>
84 *
85 */
86 public class ComplexEigenDecomposition {
87
88 /** Default threshold below which eigenvectors are considered equal. */
89 public static final double DEFAULT_EIGENVECTORS_EQUALITY = 1.0e-5;
90 /** Default value to use for internal epsilon. */
91 public static final double DEFAULT_EPSILON = 1e-12;
92 /** Internally used epsilon criteria for final AV=VD check. */
93 public static final double DEFAULT_EPSILON_AV_VD_CHECK = 1e-6;
94 /** Maximum number of inverse iterations. */
95 private static final int MAX_ITER = 10;
96 /** complex eigenvalues. */
97 private Complex[] eigenvalues;
98 /** Eigenvectors. */
99 private FieldVector<Complex>[] eigenvectors;
100 /** Cached value of V. */
101 private FieldMatrix<Complex> V;
102 /** Cached value of D. */
103 private FieldMatrix<Complex> D;
104 /** Internally used threshold below which eigenvectors are considered equal. */
105 private final double eigenVectorsEquality;
106 /** Internally used epsilon criteria. */
107 private final double epsilon;
108 /** Internally used epsilon criteria for final AV=VD check. */
109 private final double epsilonAVVDCheck;
110
111 /**
112 * Constructor for decomposition.
113 * <p>
114 * This constructor uses the default values {@link #DEFAULT_EIGENVECTORS_EQUALITY},
115 * {@link #DEFAULT_EPSILON} and {@link #DEFAULT_EPSILON_AV_VD_CHECK}
116 * </p>
117 * @param matrix
118 * real matrix.
119 */
120 public ComplexEigenDecomposition(final RealMatrix matrix) {
121 this(matrix, DEFAULT_EIGENVECTORS_EQUALITY,
122 DEFAULT_EPSILON, DEFAULT_EPSILON_AV_VD_CHECK);
123 }
124
125 /**
126 * Constructor for decomposition.
127 * <p>
128 * The {@code eigenVectorsEquality} threshold is used to ensure the L∞-normalized
129 * eigenvectors found using inverse iteration are different from each other.
130 * if \(min(|e_i-e_j|,|e_i+e_j|)\) is smaller than this threshold, the algorithm
131 * considers it has found again an already known vector, so it drops it and attempts
132 * a new inverse iteration with a different start vector. This value should be
133 * much larger than {@code epsilon} which is used for convergence
134 * </p>
135 * @param matrix real matrix.
136 * @param eigenVectorsEquality threshold below which eigenvectors are considered equal
137 * @param epsilon Epsilon used for internal tests (e.g. is singular, eigenvalue ratio, etc.)
138 * @param epsilonAVVDCheck Epsilon criteria for final AV=VD check
139 * @since 1.8
140 */
141 public ComplexEigenDecomposition(final RealMatrix matrix, final double eigenVectorsEquality,
142 final double epsilon, final double epsilonAVVDCheck) {
143
144 if (!matrix.isSquare()) {
145 throw new MathIllegalArgumentException(LocalizedCoreFormats.NON_SQUARE_MATRIX,
146 matrix.getRowDimension(), matrix.getColumnDimension());
147 }
148 this.eigenVectorsEquality = eigenVectorsEquality;
149 this.epsilon = epsilon;
150 this.epsilonAVVDCheck = epsilonAVVDCheck;
151
152 // computing the eigen values
153 findEigenValues(matrix);
154 // computing the eigen vectors
155 findEigenVectors(convertToFieldComplex(matrix));
156
157 // V
158 final int m = eigenvectors.length;
159 V = MatrixUtils.createFieldMatrix(ComplexField.getInstance(), m, m);
160 for (int k = 0; k < m; ++k) {
161 V.setColumnVector(k, eigenvectors[k]);
162 }
163
164 // D
165 D = MatrixUtils.createFieldDiagonalMatrix(eigenvalues);
166
167 checkDefinition(matrix);
168 }
169
170 /**
171 * Getter of the eigen values.
172 *
173 * @return eigen values.
174 */
175 public Complex[] getEigenvalues() {
176 return eigenvalues.clone();
177 }
178
179 /**
180 * Getter of the eigen vectors.
181 *
182 * @param i
183 * which eigen vector.
184 * @return eigen vector.
185 */
186 public FieldVector<Complex> getEigenvector(final int i) {
187 return eigenvectors[i].copy();
188 }
189
190 /** Reset eigenvalues and eigen vectors from matrices.
191 * <p>
192 * This method is intended to be called by sub-classes (mainly {@link OrderedComplexEigenDecomposition})
193 * that reorder the matrices elements. It rebuild the eigenvalues and eigen vectors arrays
194 * from the D and V matrices.
195 * </p>
196 * @since 2.1
197 */
198 protected void matricesToEigenArrays() {
199 for (int i = 0; i < eigenvalues.length; ++i) {
200 eigenvalues[i] = D.getEntry(i, i);
201 }
202 for (int i = 0; i < eigenvectors.length; ++i) {
203 for (int j = 0; j < eigenvectors[i].getDimension(); ++j) {
204 eigenvectors[i].setEntry(j, V.getEntry(j, i));
205 }
206 }
207 }
208
209 /**
210 * Confirm if there are complex eigen values.
211 *
212 * @return true if there are complex eigen values.
213 */
214 public boolean hasComplexEigenvalues() {
215 for (int i = 0; i < eigenvalues.length; i++) {
216 if (!Precision.equals(eigenvalues[i].getImaginary(), 0.0, epsilon)) {
217 return true;
218 }
219 }
220 return false;
221 }
222
223 /**
224 * Computes the determinant.
225 *
226 * @return the determinant.
227 */
228 public double getDeterminant() {
229 Complex determinant = new Complex(1, 0);
230 for (Complex lambda : eigenvalues) {
231 determinant = determinant.multiply(lambda);
232 }
233 return determinant.getReal();
234 }
235
236 /**
237 * Getter V.
238 *
239 * @return V.
240 */
241 public FieldMatrix<Complex> getV() {
242 return V;
243 }
244
245 /**
246 * Getter D.
247 *
248 * @return D.
249 */
250 public FieldMatrix<Complex> getD() {
251 return D;
252 }
253
254 /**
255 * Getter VT.
256 *
257 * @return VT.
258 */
259 public FieldMatrix<Complex> getVT() {
260 return V.transpose();
261 }
262
263 /**
264 * Compute eigen values using the Schur transform.
265 *
266 * @param matrix
267 * real matrix to compute eigen values.
268 */
269 protected void findEigenValues(final RealMatrix matrix) {
270 final SchurTransformer schurTransform = new SchurTransformer(matrix);
271 final double[][] matT = schurTransform.getT().getData();
272
273 eigenvalues = new Complex[matT.length];
274
275 for (int i = 0; i < eigenvalues.length; i++) {
276 if (i == (eigenvalues.length - 1) || Precision.equals(matT[i + 1][i], 0.0, epsilon)) {
277 eigenvalues[i] = new Complex(matT[i][i]);
278 } else {
279 final double x = matT[i + 1][i + 1];
280 final double p = 0.5 * (matT[i][i] - x);
281 final double z = FastMath.sqrt(FastMath.abs(p * p + matT[i + 1][i] * matT[i][i + 1]));
282 eigenvalues[i] = new Complex(x + p, z);
283 eigenvalues[i + 1] = new Complex(x + p, -z);
284 i++;
285 }
286 }
287
288 }
289
290 /**
291 * Compute the eigen vectors using the inverse power method.
292 *
293 * @param matrix
294 * real matrix to compute eigen vectors.
295 */
296 @SuppressWarnings("unchecked")
297 protected void findEigenVectors(final FieldMatrix<Complex> matrix) {
298 // number of eigen values/vectors
299 int n = eigenvalues.length;
300
301 // eigen vectors
302 eigenvectors = (FieldVector<Complex>[]) Array.newInstance(FieldVector.class, n);
303
304 // computing eigen vector based on eigen values and inverse iteration
305 for (int i = 0; i < eigenvalues.length; i++) {
306
307 // shifted non-singular matrix matrix A-(λ+ε)I that is close to the singular matrix A-λI
308 Complex mu = eigenvalues[i].add(epsilon);
309 final FieldMatrix<Complex> shifted = matrix.copy();
310 for (int k = 0; k < matrix.getColumnDimension(); ++k) {
311 shifted.setEntry(k, k, shifted.getEntry(k, k).subtract(mu));
312 }
313
314 // solver for linear system (A - (λ+ε)I) Bₖ₊₁ = Bₖ
315 FieldDecompositionSolver<Complex> solver = new FieldQRDecomposition<>(shifted).getSolver();
316
317 // loop over possible start vectors
318 for (int p = 0; eigenvectors[i] == null && p < matrix.getColumnDimension(); ++p) {
319
320 // find a vector to start iterations
321 FieldVector<Complex> b = findStart(p);
322
323 if (getNorm(b).norm() > Precision.SAFE_MIN) {
324 // start vector is a good candidate for inverse iteration
325
326 // perform inverse iteration
327 double delta = Double.POSITIVE_INFINITY;
328 for (int k = 0; delta > epsilon && k < MAX_ITER; k++) {
329
330 // solve (A - (λ+ε)) Bₖ₊₁ = Bₖ
331 final FieldVector<Complex> bNext = solver.solve(b);
332
333 // normalize according to L∞ norm
334 normalize(bNext);
335
336 // compute convergence criterion, comparing Bₖ and both ±Bₖ₊₁
337 // as iterations sometimes flip between two opposite vectors
338 delta = separation(b, bNext);
339
340 // prepare next iteration
341 b = bNext;
342
343 }
344
345 // check we have not found again an already known vector
346 for (int j = 0; b != null && j < i; ++j) {
347 if (separation(eigenvectors[j], b) <= eigenVectorsEquality) {
348 // the selected start vector leads us to found a known vector again,
349 // we must try another start
350 b = null;
351 }
352 }
353 eigenvectors[i] = b;
354
355 }
356 }
357
358 if (eigenvectors[i] == null) {
359 // for consistency with Wolfram langage
360 // https://reference.wolfram.com/language/ref/Eigenvectors.html
361 // we add zero vectors when the geometric multiplicity of the eigenvalue
362 // is smaller than its algebraic multiplicity (hence the regular eigenvector
363 // matrix should be non-square). With these additional null vectors, the
364 // eigenvectors matrix becomes square
365 eigenvectors[i] = MatrixUtils.createFieldVector(ComplexField.getInstance(), n);
366 }
367
368 }
369 }
370
371 /** Find a start vector orthogonal to all already found normalized eigenvectors.
372 * @param index index of the vector
373 * @return start vector
374 */
375 private FieldVector<Complex> findStart(final int index) {
376
377 // create vector
378 final FieldVector<Complex> start =
379 MatrixUtils.createFieldVector(ComplexField.getInstance(),
380 eigenvalues.length);
381
382 // initialize with a canonical vector
383 start.setEntry(index, Complex.ONE);
384
385 return start;
386
387 }
388
389 /**
390 * Compute the L∞ norm of the a given vector.
391 *
392 * @param vector
393 * vector.
394 * @return L∞ norm.
395 */
396 private Complex getNorm(FieldVector<Complex> vector) {
397 double normR = 0;
398 Complex normC = Complex.ZERO;
399 for (int i = 0; i < vector.getDimension(); i++) {
400 final Complex ci = vector.getEntry(i);
401 final double ni = FastMath.hypot(ci.getReal(), ci.getImaginary());
402 if (ni > normR) {
403 normR = ni;
404 normC = ci;
405 }
406 }
407 return normC;
408 }
409
410 /** Normalize a vector with respect to L∞ norm.
411 * @param v vector to normalized
412 */
413 private void normalize(final FieldVector<Complex> v) {
414 final Complex invNorm = getNorm(v).reciprocal();
415 for (int j = 0; j < v.getDimension(); ++j) {
416 v.setEntry(j, v.getEntry(j).multiply(invNorm));
417 }
418 }
419
420 /** Compute the separation between two normalized vectors (which may be in opposite directions).
421 * @param v1 first normalized vector
422 * @param v2 second normalized vector
423 * @return min (|v1 - v2|, |v1+v2|)
424 */
425 private double separation(final FieldVector<Complex> v1, final FieldVector<Complex> v2) {
426 double deltaPlus = 0;
427 double deltaMinus = 0;
428 for (int j = 0; j < v1.getDimension(); ++j) {
429 final Complex bCurrj = v1.getEntry(j);
430 final Complex bNextj = v2.getEntry(j);
431 deltaPlus = FastMath.max(deltaPlus,
432 FastMath.hypot(bNextj.getReal() + bCurrj.getReal(),
433 bNextj.getImaginary() + bCurrj.getImaginary()));
434 deltaMinus = FastMath.max(deltaMinus,
435 FastMath.hypot(bNextj.getReal() - bCurrj.getReal(),
436 bNextj.getImaginary() - bCurrj.getImaginary()));
437 }
438 return FastMath.min(deltaPlus, deltaMinus);
439 }
440
441 /**
442 * Check definition of the decomposition in runtime.
443 *
444 * @param matrix
445 * matrix to be decomposed.
446 */
447 protected void checkDefinition(final RealMatrix matrix) {
448 FieldMatrix<Complex> matrixC = convertToFieldComplex(matrix);
449
450 // checking definition of the decomposition
451 // testing A*V = V*D
452 FieldMatrix<Complex> AV = matrixC.multiply(getV());
453 FieldMatrix<Complex> VD = getV().multiply(getD());
454 if (!equalsWithPrecision(AV, VD, epsilonAVVDCheck)) {
455 throw new MathRuntimeException(LocalizedCoreFormats.FAILED_DECOMPOSITION,
456 matrix.getRowDimension(), matrix.getColumnDimension());
457
458 }
459
460 }
461
462 /**
463 * Helper method that checks with two matrix is equals taking into account a
464 * given precision.
465 *
466 * @param matrix1 first matrix to compare
467 * @param matrix2 second matrix to compare
468 * @param tolerance tolerance on matrices entries
469 * @return true is matrices entries are equal within tolerance,
470 * false otherwise
471 */
472 private boolean equalsWithPrecision(final FieldMatrix<Complex> matrix1,
473 final FieldMatrix<Complex> matrix2, final double tolerance) {
474 boolean toRet = true;
475 for (int i = 0; i < matrix1.getRowDimension(); i++) {
476 for (int j = 0; j < matrix1.getColumnDimension(); j++) {
477 Complex c1 = matrix1.getEntry(i, j);
478 Complex c2 = matrix2.getEntry(i, j);
479 if (c1.add(c2.negate()).norm() > tolerance) {
480 toRet = false;
481 break;
482 }
483 }
484 }
485 return toRet;
486 }
487
488 /**
489 * It converts a real matrix into a complex field matrix.
490 *
491 * @param matrix
492 * real matrix.
493 * @return complex matrix.
494 */
495 private FieldMatrix<Complex> convertToFieldComplex(RealMatrix matrix) {
496 final FieldMatrix<Complex> toRet =
497 MatrixUtils.createFieldIdentityMatrix(ComplexField.getInstance(),
498 matrix.getRowDimension());
499 for (int i = 0; i < toRet.getRowDimension(); i++) {
500 for (int j = 0; j < toRet.getColumnDimension(); j++) {
501 toRet.setEntry(i, j, new Complex(matrix.getEntry(i, j)));
502 }
503 }
504 return toRet;
505 }
506 }