EigenDecompositionNonSymmetric.java

  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.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */

  21. package org.hipparchus.linear;

  22. import java.util.ArrayList;
  23. import java.util.List;

  24. import org.hipparchus.complex.Complex;
  25. import org.hipparchus.complex.ComplexField;
  26. import org.hipparchus.exception.LocalizedCoreFormats;
  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.  * Calculates the eigen decomposition of a non-symmetric real matrix.
  33.  * <p>
  34.  * The eigen decomposition of matrix A is a set of two matrices:
  35.  * \(V\) and \(D\) such that \(A V = V D\) where $\(A\),
  36.  * \(V\) and \(D\) are all \(m \times m\) matrices.
  37.  * <p>
  38.  * This class is similar in spirit to the {@code EigenvalueDecomposition}
  39.  * class from the <a href="http://math.nist.gov/javanumerics/jama/">JAMA</a>
  40.  * library, with the following changes:
  41.  * <ul>
  42.  *   <li>a {@link #getVInv() getVInv} method has been added,</li>
  43.  *   <li>z {@link #getEigenvalue(int) getEigenvalue} method to pick up a
  44.  *       single eigenvalue has been added,</li>
  45.  *   <li>a {@link #getEigenvector(int) getEigenvector} method to pick up a
  46.  *       single eigenvector has been added,</li>
  47.  *   <li>a {@link #getDeterminant() getDeterminant} method has been added.</li>
  48.  * </ul>
  49.  * <p>
  50.  * This class supports non-symmetric matrices, which have complex eigenvalues.
  51.  * Support for symmetric matrices is provided by {@link EigenDecompositionSymmetric}.
  52.  * </p>
  53.  * <p>
  54.  * As \(A\) is not symmetric, then the eigenvalue matrix \(D\) is block diagonal with
  55.  * the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, \(\lambda \pm i \mu\),
  56.  * in 2-by-2 blocks:
  57.  * </p>
  58.  * <p>
  59.  * \[
  60.  *   \begin{bmatrix}
  61.  *    \lambda &amp; \mu\\
  62.  *    -\mu    &amp; \lambda
  63.  *   \end{bmatrix}
  64.  * \]
  65.  * </p>
  66.  * <p>
  67.  * The columns of \(V\) represent the eigenvectors in the sense that \(A V = V D\),
  68.  * i.e. {@code A.multiply(V)} equals {@code V.multiply(D)}.
  69.  * The matrix \(V\) may be badly conditioned, or even singular, so the validity of the
  70.  * equation \(A = V D V^{-1}\) depends upon the condition of \(V\).
  71.  * </p>
  72.  * <p>
  73.  * This implementation is based on the paper by A. Drubrulle, R.S. Martin and
  74.  * J.H. Wilkinson "The Implicit QL Algorithm" in Wilksinson and Reinsch (1971)
  75.  * Handbook for automatic computation, vol. 2, Linear algebra, Springer-Verlag,
  76.  * New-York.
  77.  *
  78.  * @see <a href="http://mathworld.wolfram.com/EigenDecomposition.html">MathWorld</a>
  79.  * @see <a href="http://en.wikipedia.org/wiki/Eigendecomposition_of_a_matrix">Wikipedia</a>
  80.  * @since 3.0
  81.  */
  82. public class EigenDecompositionNonSymmetric {
  83.     /** Default epsilon value to use for internal epsilon **/
  84.     public static final double DEFAULT_EPSILON = 1e-12;
  85.     /** Internally used epsilon criteria. */
  86.     private final double epsilon;
  87.     /** eigenvalues. */
  88.     private Complex[] eigenvalues;
  89.     /** Eigenvectors. */
  90.     private List<FieldVector<Complex>> eigenvectors;
  91.     /** Cached value of \(V\). */
  92.     private RealMatrix cachedV;
  93.     /** Cached value of \(D\). */
  94.     private RealMatrix cachedD;
  95.     /** Cached value of \(V^{-1}\). */
  96.     private RealMatrix cachedVInv;

  97.     /** Calculates the eigen decomposition of the given real matrix.
  98.      * @param matrix Matrix to decompose.
  99.      * @throws MathIllegalStateException if the algorithm fails to converge.
  100.      * @throws MathRuntimeException if the decomposition of a general matrix
  101.      * results in a matrix with zero norm
  102.      */
  103.     public EigenDecompositionNonSymmetric(final RealMatrix matrix) {
  104.         this(matrix, DEFAULT_EPSILON);
  105.     }

  106.     /** Calculates the eigen decomposition of the given real matrix.
  107.      * @param matrix Matrix to decompose.
  108.      * @param epsilon Epsilon used for internal tests (e.g. is singular, eigenvalue ratio, etc.)
  109.      * @throws MathIllegalStateException if the algorithm fails to converge.
  110.      * @throws MathRuntimeException if the decomposition of a general matrix
  111.      * results in a matrix with zero norm
  112.      */
  113.     public EigenDecompositionNonSymmetric(final RealMatrix matrix, double epsilon)
  114.         throws MathRuntimeException {
  115.         this.epsilon = epsilon;
  116.         findEigenVectorsFromSchur(transformToSchur(matrix));
  117.     }

  118.     /**
  119.      * Gets the matrix V of the decomposition.
  120.      * V is a matrix whose columns hold either the real or the
  121.      * imaginary part of eigenvectors.
  122.      *
  123.      * @return the V matrix.
  124.      */
  125.     public RealMatrix getV() {

  126.         if (cachedV == null) {
  127.             final int m = eigenvectors.size();
  128.             cachedV = MatrixUtils.createRealMatrix(m, m);
  129.             for (int k = 0; k < m; ++k) {
  130.                 final FieldVector<Complex> ek = eigenvectors.get(k);
  131.                 if (eigenvalues[k].getImaginaryPart() >= 0) {
  132.                     // either it is a real eigenvalue, or it is the first of two conjugate eigenvalues,
  133.                     // we pick up the real part of the eigenvector
  134.                     for (int l = 0; l < m; ++l) {
  135.                         cachedV.setEntry(l, k, ek.getEntry(l).getRealPart());
  136.                     }
  137.                 } else {
  138.                     // second of two conjugate eigenvalues,
  139.                     // we pick up the imaginary part of the eigenvector
  140.                     for (int l = 0; l < m; ++l) {
  141.                         cachedV.setEntry(l, k, -ek.getEntry(l).getImaginaryPart());
  142.                     }
  143.                 }
  144.             }
  145.         }

  146.         // return the cached matrix
  147.         return cachedV;

  148.     }

  149.     /**
  150.      * Gets the block diagonal matrix D of the decomposition.
  151.      * D is a block diagonal matrix.
  152.      * Real eigenvalues are on the diagonal while complex values are on
  153.      * 2x2 blocks { {real +imaginary}, {-imaginary, real} }.
  154.      *
  155.      * @return the D matrix.
  156.      */
  157.     public RealMatrix getD() {

  158.         if (cachedD == null) {
  159.             // cache the matrix for subsequent calls
  160.             cachedD = MatrixUtils.createRealMatrix(eigenvalues.length, eigenvalues.length);
  161.             for (int i = 0; i < eigenvalues.length; ++i) {
  162.                 cachedD.setEntry(i, i, eigenvalues[i].getRealPart());
  163.                 if (Precision.compareTo(eigenvalues[i].getImaginaryPart(), 0.0, epsilon) > 0) {
  164.                     cachedD.setEntry(i, i + 1, eigenvalues[i].getImaginaryPart());
  165.                 } else if (Precision.compareTo(eigenvalues[i].getImaginaryPart(), 0.0, epsilon) < 0) {
  166.                     cachedD.setEntry(i, i - 1, eigenvalues[i].getImaginaryPart());
  167.                 }
  168.             }
  169.         }
  170.         return cachedD;
  171.     }

  172.     /**
  173.      * Get's the value for epsilon which is used for internal tests (e.g. is singular, eigenvalue ratio, etc.)
  174.      *
  175.      * @return the epsilon value.
  176.      */
  177.     public double getEpsilon() {
  178.         return epsilon;
  179.     }

  180.     /**
  181.      * Gets the inverse of the matrix V of the decomposition.
  182.      *
  183.      * @return the inverse of the V matrix.
  184.      */
  185.     public RealMatrix getVInv() {

  186.         if (cachedVInv == null) {
  187.             cachedVInv = MatrixUtils.inverse(getV(), epsilon);
  188.         }

  189.         // return the cached matrix
  190.         return cachedVInv;
  191.     }

  192.     /**
  193.      * Gets a copy of the eigenvalues of the original matrix.
  194.      *
  195.      * @return a copy of the eigenvalues of the original matrix.
  196.      *
  197.      * @see #getD()
  198.      * @see #getEigenvalue(int)
  199.      */
  200.     public Complex[] getEigenvalues() {
  201.         return eigenvalues.clone();
  202.     }

  203.     /**
  204.      * Returns the i<sup>th</sup> eigenvalue of the original matrix.
  205.      *
  206.      * @param i index of the eigenvalue (counting from 0)
  207.      * @return i<sup>th</sup> eigenvalue of the original matrix.
  208.      *
  209.      * @see #getD()
  210.      * @see #getEigenvalues()
  211.      */
  212.     public Complex getEigenvalue(final int i) {
  213.         return eigenvalues[i];
  214.     }

  215.     /**
  216.      * Gets a copy of the i<sup>th</sup> eigenvector of the original matrix.
  217.      * <p>
  218.      * Note that if the the i<sup>th</sup> is complex this method will throw
  219.      * an exception.
  220.      * </p>
  221.      * @param i Index of the eigenvector (counting from 0).
  222.      * @return a copy of the i<sup>th</sup> eigenvector of the original matrix.
  223.      * @see #getD()
  224.      */
  225.     public FieldVector<Complex> getEigenvector(final int i) {
  226.         return eigenvectors.get(i).copy();
  227.     }

  228.     /**
  229.      * Computes the determinant of the matrix.
  230.      *
  231.      * @return the determinant of the matrix.
  232.      */
  233.     public Complex getDeterminant() {
  234.         Complex determinant = Complex.ONE;
  235.         for (Complex eigenvalue : eigenvalues) {
  236.             determinant = determinant.multiply(eigenvalue);
  237.         }
  238.         return determinant;
  239.     }

  240.     /**
  241.      * Transforms the matrix to Schur form and calculates the eigenvalues.
  242.      *
  243.      * @param matrix Matrix to transform.
  244.      * @return the {@link SchurTransformer Schur transform} for this matrix
  245.      */
  246.     private SchurTransformer transformToSchur(final RealMatrix matrix) {
  247.         final SchurTransformer schurTransform = new SchurTransformer(matrix, epsilon);
  248.         final double[][] matT = schurTransform.getT().getData();
  249.         final double norm = matrix.getNorm1();

  250.         eigenvalues = new Complex[matT.length];

  251.         int i = 0;
  252.         while (i < eigenvalues.length) {
  253.             if (i == (eigenvalues.length - 1) ||
  254.                 Precision.equals(matT[i + 1][i], 0.0, norm * epsilon)) {
  255.                 eigenvalues[i] = new Complex(matT[i][i]);
  256.                 i++;
  257.             } else {
  258.                 final double x   = matT[i + 1][i + 1];
  259.                 final double p   = 0.5 * (matT[i][i] - x);
  260.                 final double z   = FastMath.sqrt(FastMath.abs(p * p + matT[i + 1][i] * matT[i][i + 1]));
  261.                 eigenvalues[i++] = new Complex(x + p, +z);
  262.                 eigenvalues[i++] = new Complex(x + p, -z);
  263.             }
  264.         }
  265.         return schurTransform;
  266.     }

  267.     /**
  268.      * Performs a division of two complex numbers.
  269.      *
  270.      * @param xr real part of the first number
  271.      * @param xi imaginary part of the first number
  272.      * @param yr real part of the second number
  273.      * @param yi imaginary part of the second number
  274.      * @return result of the complex division
  275.      */
  276.     private Complex cdiv(final double xr, final double xi,
  277.                          final double yr, final double yi) {
  278.         return new Complex(xr, xi).divide(new Complex(yr, yi));
  279.     }

  280.     /**
  281.      * Find eigenvectors from a matrix transformed to Schur form.
  282.      *
  283.      * @param schur the schur transformation of the matrix
  284.      * @throws MathRuntimeException if the Schur form has a norm of zero
  285.      */
  286.     private void findEigenVectorsFromSchur(final SchurTransformer schur)
  287.         throws MathRuntimeException {
  288.         final double[][] matrixT = schur.getT().getData();
  289.         final double[][] matrixP = schur.getP().getData();

  290.         final int n = matrixT.length;

  291.         // compute matrix norm
  292.         double norm = 0.0;
  293.         for (int i = 0; i < n; i++) {
  294.            for (int j = FastMath.max(i - 1, 0); j < n; j++) {
  295.                norm += FastMath.abs(matrixT[i][j]);
  296.            }
  297.         }

  298.         // we can not handle a matrix with zero norm
  299.         if (norm == 0.0) {
  300.            throw new MathRuntimeException(LocalizedCoreFormats.ZERO_NORM);
  301.         }

  302.         // Backsubstitute to find vectors of upper triangular form

  303.         double r = 0.0;
  304.         double s = 0.0;
  305.         double z = 0.0;

  306.         for (int idx = n - 1; idx >= 0; idx--) {
  307.             double p = eigenvalues[idx].getRealPart();
  308.             double q = eigenvalues[idx].getImaginaryPart();

  309.             if (Precision.equals(q, 0.0)) {
  310.                 // Real vector
  311.                 int l = idx;
  312.                 matrixT[idx][idx] = 1.0;
  313.                 for (int i = idx - 1; i >= 0; i--) {
  314.                     double w = matrixT[i][i] - p;
  315.                     r = 0.0;
  316.                     for (int j = l; j <= idx; j++) {
  317.                         r += matrixT[i][j] * matrixT[j][idx];
  318.                     }
  319.                     if (Precision.compareTo(eigenvalues[i].getImaginaryPart(), 0.0, epsilon) < 0) {
  320.                         z = w;
  321.                         s = r;
  322.                     } else {
  323.                         l = i;
  324.                         if (Precision.equals(eigenvalues[i].getImaginaryPart(), 0.0)) {
  325.                             if (w != 0.0) {
  326.                                 matrixT[i][idx] = -r / w;
  327.                             } else {
  328.                                 matrixT[i][idx] = -r / (Precision.EPSILON * norm);
  329.                             }
  330.                         } else {
  331.                             // Solve real equations
  332.                             double x = matrixT[i][i + 1];
  333.                             double y = matrixT[i + 1][i];
  334.                             q = (eigenvalues[i].getRealPart() - p) * (eigenvalues[i].getRealPart() - p) +
  335.                                 eigenvalues[i].getImaginaryPart() * eigenvalues[i].getImaginaryPart();
  336.                             double t = (x * s - z * r) / q;
  337.                             matrixT[i][idx] = t;
  338.                             if (FastMath.abs(x) > FastMath.abs(z)) {
  339.                                 matrixT[i + 1][idx] = (-r - w * t) / x;
  340.                             } else {
  341.                                 matrixT[i + 1][idx] = (-s - y * t) / z;
  342.                             }
  343.                         }

  344.                         // Overflow control
  345.                         double t = FastMath.abs(matrixT[i][idx]);
  346.                         if ((Precision.EPSILON * t) * t > 1) {
  347.                             for (int j = i; j <= idx; j++) {
  348.                                 matrixT[j][idx] /= t;
  349.                             }
  350.                         }
  351.                     }
  352.                 }
  353.             } else if (q < 0.0) {
  354.                 // Complex vector
  355.                 int l = idx - 1;

  356.                 // Last vector component imaginary so matrix is triangular
  357.                 if (FastMath.abs(matrixT[idx][idx - 1]) > FastMath.abs(matrixT[idx - 1][idx])) {
  358.                     matrixT[idx - 1][idx - 1] = q / matrixT[idx][idx - 1];
  359.                     matrixT[idx - 1][idx]     = -(matrixT[idx][idx] - p) / matrixT[idx][idx - 1];
  360.                 } else {
  361.                     final Complex result = cdiv(0.0, -matrixT[idx - 1][idx],
  362.                                                 matrixT[idx - 1][idx - 1] - p, q);
  363.                     matrixT[idx - 1][idx - 1] = result.getReal();
  364.                     matrixT[idx - 1][idx]     = result.getImaginary();
  365.                 }

  366.                 matrixT[idx][idx - 1] = 0.0;
  367.                 matrixT[idx][idx]     = 1.0;

  368.                 for (int i = idx - 2; i >= 0; i--) {
  369.                     double ra = 0.0;
  370.                     double sa = 0.0;
  371.                     for (int j = l; j <= idx; j++) {
  372.                         ra += matrixT[i][j] * matrixT[j][idx - 1];
  373.                         sa += matrixT[i][j] * matrixT[j][idx];
  374.                     }
  375.                     double w = matrixT[i][i] - p;

  376.                     if (Precision.compareTo(eigenvalues[i].getImaginaryPart(), 0.0, epsilon) < 0) {
  377.                         z = w;
  378.                         r = ra;
  379.                         s = sa;
  380.                     } else {
  381.                         l = i;
  382.                         if (Precision.equals(eigenvalues[i].getImaginaryPart(), 0.0)) {
  383.                             final Complex c = cdiv(-ra, -sa, w, q);
  384.                             matrixT[i][idx - 1] = c.getReal();
  385.                             matrixT[i][idx] = c.getImaginary();
  386.                         } else {
  387.                             // Solve complex equations
  388.                             double x = matrixT[i][i + 1];
  389.                             double y = matrixT[i + 1][i];
  390.                             double vr = (eigenvalues[i].getRealPart() - p) * (eigenvalues[i].getRealPart() - p) +
  391.                                         eigenvalues[i].getImaginaryPart() * eigenvalues[i].getImaginaryPart() - q * q;
  392.                             final double vi = (eigenvalues[i].getRealPart() - p) * 2.0 * q;
  393.                             if (Precision.equals(vr, 0.0) && Precision.equals(vi, 0.0)) {
  394.                                 vr = Precision.EPSILON * norm *
  395.                                      (FastMath.abs(w) + FastMath.abs(q) + FastMath.abs(x) +
  396.                                       FastMath.abs(y) + FastMath.abs(z));
  397.                             }
  398.                             final Complex c     = cdiv(x * r - z * ra + q * sa,
  399.                                                        x * s - z * sa - q * ra, vr, vi);
  400.                             matrixT[i][idx - 1] = c.getReal();
  401.                             matrixT[i][idx]     = c.getImaginary();

  402.                             if (FastMath.abs(x) > (FastMath.abs(z) + FastMath.abs(q))) {
  403.                                 matrixT[i + 1][idx - 1] = (-ra - w * matrixT[i][idx - 1] +
  404.                                                            q * matrixT[i][idx]) / x;
  405.                                 matrixT[i + 1][idx]     = (-sa - w * matrixT[i][idx] -
  406.                                                            q * matrixT[i][idx - 1]) / x;
  407.                             } else {
  408.                                 final Complex c2        = cdiv(-r - y * matrixT[i][idx - 1],
  409.                                                                -s - y * matrixT[i][idx], z, q);
  410.                                 matrixT[i + 1][idx - 1] = c2.getReal();
  411.                                 matrixT[i + 1][idx]     = c2.getImaginary();
  412.                             }
  413.                         }

  414.                         // Overflow control
  415.                         double t = FastMath.max(FastMath.abs(matrixT[i][idx - 1]),
  416.                                                 FastMath.abs(matrixT[i][idx]));
  417.                         if ((Precision.EPSILON * t) * t > 1) {
  418.                             for (int j = i; j <= idx; j++) {
  419.                                 matrixT[j][idx - 1] /= t;
  420.                                 matrixT[j][idx] /= t;
  421.                             }
  422.                         }
  423.                     }
  424.                 }
  425.             }
  426.         }

  427.         // Back transformation to get eigenvectors of original matrix
  428.         for (int j = n - 1; j >= 0; j--) {
  429.             for (int i = 0; i <= n - 1; i++) {
  430.                 z = 0.0;
  431.                 for (int k = 0; k <= FastMath.min(j, n - 1); k++) {
  432.                     z += matrixP[i][k] * matrixT[k][j];
  433.                 }
  434.                 matrixP[i][j] = z;
  435.             }
  436.         }

  437.         eigenvectors = new ArrayList<>(n);
  438.         for (int i = 0; i < n; i++) {
  439.             FieldVector<Complex> ei = new ArrayFieldVector<>(ComplexField.getInstance(), n);
  440.             for (int j = 0; j < n; j++) {
  441.                 if (Precision.compareTo(eigenvalues[i].getImaginaryPart(), 0.0, epsilon) > 0) {
  442.                     ei.setEntry(j, new Complex(matrixP[j][i], +matrixP[j][i + 1]));
  443.                 } else if (Precision.compareTo(eigenvalues[i].getImaginaryPart(), 0.0, epsilon) < 0) {
  444.                     ei.setEntry(j, new Complex(matrixP[j][i - 1], -matrixP[j][i]));
  445.                 } else {
  446.                     ei.setEntry(j, new Complex(matrixP[j][i]));
  447.                 }
  448.             }
  449.             eigenvectors.add(ei);
  450.         }
  451.     }
  452. }