ComplexEigenDecomposition.java

  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. import java.lang.reflect.Array;

  19. import org.hipparchus.complex.Complex;
  20. import org.hipparchus.complex.ComplexField;
  21. import org.hipparchus.exception.LocalizedCoreFormats;
  22. import org.hipparchus.exception.MathIllegalArgumentException;
  23. import org.hipparchus.exception.MathRuntimeException;
  24. import org.hipparchus.util.FastMath;
  25. import org.hipparchus.util.Precision;

  26. /**
  27.  * Given a matrix A, it computes a complex eigen decomposition AV = VD.
  28.  *
  29.  * <p>
  30.  * Complex Eigen Decomposition differs from the {@link EigenDecompositionSymmetric} since it
  31.  * computes the eigen vectors as complex eigen vectors (if applicable).
  32.  * </p>
  33.  *
  34.  * <p>
  35.  * Beware that in the complex case, you do not always have \(V \times V^{T} = I\) or even a
  36.  * diagonal matrix, even if the eigenvectors that form the columns of the V
  37.  * matrix are independent. On example is the square matrix
  38.  * \[
  39.  * A = \left(\begin{matrix}
  40.  * 3 &amp; -2\\
  41.  * 4 &amp; -1
  42.  * \end{matrix}\right)
  43.  * \]
  44.  * which has two conjugate eigenvalues \(\lambda_1=1+2i\) and \(\lambda_2=1-2i\)
  45.  * with associated eigenvectors \(v_1^T = (1, 1-i)\) and \(v_2^T = (1, 1+i)\).
  46.  * \[
  47.  * V\timesV^T = \left(\begin{matrix}
  48.  * 2 &amp; 2\\
  49.  * 2 &amp; 0
  50.  * \end{matrix}\right)
  51.  * \]
  52.  * which is not the identity matrix. Therefore, despite \(A \times V = V \times D\),
  53.  * \(A \ne V \times D \time V^T\), which would hold for real eigendecomposition.
  54.  * </p>
  55.  * <p>
  56.  * Also note that for consistency with Wolfram langage
  57.  * <a href="https://reference.wolfram.com/language/ref/Eigenvectors.html">eigenvectors</a>,
  58.  * we add zero vectors when the geometric multiplicity of the eigenvalue is smaller than
  59.  * its algebraic multiplicity (hence the regular eigenvector matrix should be non-square).
  60.  * With these additional null vectors, the eigenvectors matrix becomes square. This happens
  61.  * for example with the square matrix
  62.  * \[
  63.  * A = \left(\begin{matrix}
  64.  *  1 &amp; 0 &amp; 0\\
  65.  * -2 &amp; 1 &amp; 0\\
  66.  *  0 &amp; 0 &amp; 1
  67.  * \end{matrix}\right)
  68.  * \]
  69.  * Its characteristic polynomial is \((1-\lambda)^3\), hence is has one eigen value \(\lambda=1\)
  70.  * with algebraic multiplicity 3. However, this eigenvalue leads to only two eigenvectors
  71.  * \(v_1=(0, 1, 0)\) and \(v_2=(0, 0, 1)\), hence its geometric multiplicity is only 2, not 3.
  72.  * So we add a third zero vector \(v_3=(0, 0, 0)\), in the same way Wolfram language does.
  73.  * </p>
  74.  *
  75.  * Compute complex eigen values from the Schur transform. Compute complex eigen
  76.  * vectors based on eigen values and the inverse iteration method.
  77.  *
  78.  * see: <a href="https://en.wikipedia.org/wiki/Inverse_iteration">Inverse iteration</a>
  79.  * <a href="https://en.wikiversity.org/wiki/Shifted_inverse_iteration">Shifted inverse iteration</a>
  80.  * <a href="https://www.robots.ox.ac.uk/~sjrob/Teaching/EngComp/ecl4.pdf">Computation of matrix eigenvalues and eigenvectors</a>
  81.  *
  82.  */
  83. public class ComplexEigenDecomposition {

  84.     /** Default threshold below which eigenvectors are considered equal. */
  85.     public static final double DEFAULT_EIGENVECTORS_EQUALITY = 1.0e-5;
  86.     /** Default value to use for internal epsilon. */
  87.     public static final double DEFAULT_EPSILON = 1e-12;
  88.     /** Internally used epsilon criteria for final AV=VD check. */
  89.     public static final double DEFAULT_EPSILON_AV_VD_CHECK = 1e-6;
  90.     /** Maximum number of inverse iterations. */
  91.     private static final int MAX_ITER = 10;
  92.     /** complex eigenvalues. */
  93.     private Complex[] eigenvalues;
  94.     /** Eigenvectors. */
  95.     private FieldVector<Complex>[] eigenvectors;
  96.     /** Cached value of V. */
  97.     private FieldMatrix<Complex> V;
  98.     /** Cached value of D. */
  99.     private FieldMatrix<Complex> D;
  100.     /** Internally used threshold below which eigenvectors are considered equal. */
  101.     private final double eigenVectorsEquality;
  102.     /** Internally used epsilon criteria. */
  103.     private final double epsilon;
  104.     /** Internally used epsilon criteria for final AV=VD check. */
  105.     private final double epsilonAVVDCheck;

  106.     /**
  107.      * Constructor for decomposition.
  108.      * <p>
  109.      * This constructor uses the default values {@link #DEFAULT_EIGENVECTORS_EQUALITY},
  110.      * {@link #DEFAULT_EPSILON} and {@link #DEFAULT_EPSILON_AV_VD_CHECK}
  111.      * </p>
  112.      * @param matrix
  113.      *            real matrix.
  114.      */
  115.     public ComplexEigenDecomposition(final RealMatrix matrix) {
  116.         this(matrix, DEFAULT_EIGENVECTORS_EQUALITY,
  117.              DEFAULT_EPSILON, DEFAULT_EPSILON_AV_VD_CHECK);
  118.     }

  119.     /**
  120.      * Constructor for decomposition.
  121.      * <p>
  122.      * The {@code eigenVectorsEquality} threshold is used to ensure the L∞-normalized
  123.      * eigenvectors found using inverse iteration are different from each other.
  124.      * if \(min(|e_i-e_j|,|e_i+e_j|)\) is smaller than this threshold, the algorithm
  125.      * considers it has found again an already known vector, so it drops it and attempts
  126.      * a new inverse iteration with a different start vector. This value should be
  127.      * much larger than {@code epsilon} which is used for convergence
  128.      * </p>
  129.      * @param matrix real matrix.
  130.      * @param eigenVectorsEquality threshold below which eigenvectors are considered equal
  131.      * @param epsilon Epsilon used for internal tests (e.g. is singular, eigenvalue ratio, etc.)
  132.      * @param epsilonAVVDCheck Epsilon criteria for final AV=VD check
  133.      * @since 1.8
  134.      */
  135.     public ComplexEigenDecomposition(final RealMatrix matrix, final double eigenVectorsEquality,
  136.                                      final double epsilon, final double epsilonAVVDCheck) {

  137.         if (!matrix.isSquare()) {
  138.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NON_SQUARE_MATRIX,
  139.                                                    matrix.getRowDimension(), matrix.getColumnDimension());
  140.         }
  141.         this.eigenVectorsEquality = eigenVectorsEquality;
  142.         this.epsilon              = epsilon;
  143.         this.epsilonAVVDCheck     = epsilonAVVDCheck;

  144.         // computing the eigen values
  145.         findEigenValues(matrix);
  146.         // computing the eigen vectors
  147.         findEigenVectors(convertToFieldComplex(matrix));

  148.         // V
  149.         final int m = eigenvectors.length;
  150.         V = MatrixUtils.createFieldMatrix(ComplexField.getInstance(), m, m);
  151.         for (int k = 0; k < m; ++k) {
  152.             V.setColumnVector(k, eigenvectors[k]);
  153.         }

  154.         // D
  155.         D = MatrixUtils.createFieldDiagonalMatrix(eigenvalues);

  156.         checkDefinition(matrix);
  157.     }

  158.     /**
  159.      * Getter of the eigen values.
  160.      *
  161.      * @return eigen values.
  162.      */
  163.     public Complex[] getEigenvalues() {
  164.         return eigenvalues.clone();
  165.     }

  166.     /**
  167.      * Getter of the eigen vectors.
  168.      *
  169.      * @param i
  170.      *            which eigen vector.
  171.      * @return eigen vector.
  172.      */
  173.     public FieldVector<Complex> getEigenvector(final int i) {
  174.         return eigenvectors[i].copy();
  175.     }

  176.     /** Reset eigenvalues and eigen vectors from matrices.
  177.      * <p>
  178.      * This method is intended to be called by sub-classes (mainly {@link OrderedComplexEigenDecomposition})
  179.      * that reorder the matrices elements. It rebuild the eigenvalues and eigen vectors arrays
  180.      * from the D and V matrices.
  181.      * </p>
  182.      * @since 2.1
  183.      */
  184.     protected void matricesToEigenArrays() {
  185.         for (int i = 0; i < eigenvalues.length; ++i) {
  186.             eigenvalues[i] = D.getEntry(i, i);
  187.         }
  188.         for (int i = 0; i < eigenvectors.length; ++i) {
  189.             for (int j = 0; j < eigenvectors[i].getDimension(); ++j) {
  190.                 eigenvectors[i].setEntry(j, V.getEntry(j, i));
  191.             }
  192.         }
  193.     }

  194.     /**
  195.      * Confirm if there are complex eigen values.
  196.      *
  197.      * @return true if there are complex eigen values.
  198.      */
  199.     public boolean hasComplexEigenvalues() {
  200.         for (Complex eigenvalue : eigenvalues) {
  201.             if (!Precision.equals(eigenvalue.getImaginary(), 0.0, epsilon)) {
  202.                 return true;
  203.             }
  204.         }
  205.         return false;
  206.     }

  207.     /**
  208.      * Computes the determinant.
  209.      *
  210.      * @return the determinant.
  211.      */
  212.     public double getDeterminant() {
  213.         Complex determinant = new Complex(1, 0);
  214.         for (Complex lambda : eigenvalues) {
  215.             determinant = determinant.multiply(lambda);
  216.         }
  217.         return determinant.getReal();
  218.     }

  219.     /**
  220.      * Getter V.
  221.      *
  222.      * @return V.
  223.      */
  224.     public FieldMatrix<Complex> getV() {
  225.         return V;
  226.     }

  227.     /**
  228.      * Getter D.
  229.      *
  230.      * @return D.
  231.      */
  232.     public FieldMatrix<Complex> getD() {
  233.         return D;
  234.     }

  235.     /**
  236.      * Getter VT.
  237.      *
  238.      * @return VT.
  239.      */
  240.     public FieldMatrix<Complex> getVT() {
  241.         return V.transpose();
  242.     }

  243.     /**
  244.      * Compute eigen values using the Schur transform.
  245.      *
  246.      * @param matrix
  247.      *            real matrix to compute eigen values.
  248.      */
  249.     protected void findEigenValues(final RealMatrix matrix) {
  250.         final SchurTransformer schurTransform = new SchurTransformer(matrix);
  251.         final double[][] matT = schurTransform.getT().getData();

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

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

  265.     }

  266.     /**
  267.      * Compute the eigen vectors using the inverse power method.
  268.      *
  269.      * @param matrix
  270.      *            real matrix to compute eigen vectors.
  271.      */
  272.     @SuppressWarnings("unchecked")
  273.     protected void findEigenVectors(final FieldMatrix<Complex> matrix) {
  274.         // number of eigen values/vectors
  275.         int n = eigenvalues.length;

  276.         // eigen vectors
  277.         eigenvectors = (FieldVector<Complex>[]) Array.newInstance(FieldVector.class, n);

  278.         // computing eigen vector based on eigen values and inverse iteration
  279.         for (int i = 0; i < eigenvalues.length; i++) {

  280.             // shifted non-singular matrix matrix A-(λ+ε)I that is close to the singular matrix A-λI
  281.             Complex mu = eigenvalues[i].add(epsilon);
  282.             final FieldMatrix<Complex> shifted = matrix.copy();
  283.             for (int k = 0; k < matrix.getColumnDimension(); ++k) {
  284.                 shifted.setEntry(k, k, shifted.getEntry(k, k).subtract(mu));
  285.             }

  286.             // solver for linear system (A - (λ+ε)I) Bₖ₊₁ = Bₖ
  287.             FieldDecompositionSolver<Complex> solver = new FieldQRDecomposition<>(shifted).getSolver();

  288.             // loop over possible start vectors
  289.             for (int p = 0; eigenvectors[i] == null && p < matrix.getColumnDimension(); ++p) {

  290.                 // find a vector to start iterations
  291.                 FieldVector<Complex> b = findStart(p);

  292.                 if (getNorm(b).norm() > Precision.SAFE_MIN) {
  293.                     // start vector is a good candidate for inverse iteration

  294.                     // perform inverse iteration
  295.                     double delta = Double.POSITIVE_INFINITY;
  296.                     for (int k = 0; delta > epsilon && k < MAX_ITER; k++) {

  297.                         // solve (A - (λ+ε)) Bₖ₊₁ = Bₖ
  298.                         final FieldVector<Complex> bNext = solver.solve(b);

  299.                         // normalize according to L∞ norm
  300.                         normalize(bNext);

  301.                         // compute convergence criterion, comparing Bₖ and both ±Bₖ₊₁
  302.                         // as iterations sometimes flip between two opposite vectors
  303.                         delta = separation(b, bNext);

  304.                         // prepare next iteration
  305.                         b = bNext;

  306.                     }

  307.                     // check we have not found again an already known vector
  308.                     for (int j = 0; b != null && j < i; ++j) {
  309.                         if (separation(eigenvectors[j], b) <= eigenVectorsEquality) {
  310.                             // the selected start vector leads us to found a known vector again,
  311.                             // we must try another start
  312.                             b = null;
  313.                         }
  314.                     }
  315.                     eigenvectors[i] = b;

  316.                 }
  317.             }

  318.             if (eigenvectors[i] == null) {
  319.                 // for consistency with Wolfram langage
  320.                 // https://reference.wolfram.com/language/ref/Eigenvectors.html
  321.                 // we add zero vectors when the geometric multiplicity of the eigenvalue
  322.                 // is smaller than its algebraic multiplicity (hence the regular eigenvector
  323.                 // matrix should be non-square). With these additional null vectors, the
  324.                 // eigenvectors matrix becomes square
  325.                 eigenvectors[i] = MatrixUtils.createFieldVector(ComplexField.getInstance(), n);
  326.             }

  327.         }
  328.     }

  329.     /** Find a start vector orthogonal to all already found normalized eigenvectors.
  330.      * @param index index of the vector
  331.      * @return start vector
  332.      */
  333.     private FieldVector<Complex> findStart(final int index) {

  334.         // create vector
  335.         final FieldVector<Complex> start =
  336.                         MatrixUtils.createFieldVector(ComplexField.getInstance(),
  337.                                                       eigenvalues.length);

  338.         // initialize with a canonical vector
  339.         start.setEntry(index, Complex.ONE);

  340.         return start;

  341.     }

  342.     /**
  343.      * Compute the L∞ norm of the a given vector.
  344.      *
  345.      * @param vector
  346.      *            vector.
  347.      * @return L∞ norm.
  348.      */
  349.     private Complex getNorm(FieldVector<Complex> vector) {
  350.         double  normR = 0;
  351.         Complex normC = Complex.ZERO;
  352.         for (int i = 0; i < vector.getDimension(); i++) {
  353.             final Complex ci = vector.getEntry(i);
  354.             final double  ni = FastMath.hypot(ci.getReal(), ci.getImaginary());
  355.             if (ni > normR) {
  356.                 normR = ni;
  357.                 normC = ci;
  358.             }
  359.         }
  360.         return normC;
  361.     }

  362.     /** Normalize a vector with respect to L∞ norm.
  363.      * @param v vector to normalized
  364.      */
  365.     private void normalize(final FieldVector<Complex> v) {
  366.         final Complex invNorm = getNorm(v).reciprocal();
  367.         for (int j = 0; j < v.getDimension(); ++j) {
  368.             v.setEntry(j, v.getEntry(j).multiply(invNorm));
  369.         }
  370.     }

  371.     /** Compute the separation between two normalized vectors (which may be in opposite directions).
  372.      * @param v1 first normalized vector
  373.      * @param v2 second normalized vector
  374.      * @return min (|v1 - v2|, |v1+v2|)
  375.      */
  376.     private double separation(final FieldVector<Complex> v1, final FieldVector<Complex> v2) {
  377.         double deltaPlus  = 0;
  378.         double deltaMinus = 0;
  379.         for (int j = 0; j < v1.getDimension(); ++j) {
  380.             final Complex bCurrj = v1.getEntry(j);
  381.             final Complex bNextj = v2.getEntry(j);
  382.             deltaPlus  = FastMath.max(deltaPlus,
  383.                                       FastMath.hypot(bNextj.getReal()      + bCurrj.getReal(),
  384.                                                      bNextj.getImaginary() + bCurrj.getImaginary()));
  385.             deltaMinus = FastMath.max(deltaMinus,
  386.                                       FastMath.hypot(bNextj.getReal()      - bCurrj.getReal(),
  387.                                                      bNextj.getImaginary() - bCurrj.getImaginary()));
  388.         }
  389.         return FastMath.min(deltaPlus, deltaMinus);
  390.     }

  391.     /**
  392.      * Check definition of the decomposition in runtime.
  393.      *
  394.      * @param matrix
  395.      *            matrix to be decomposed.
  396.      */
  397.     protected void checkDefinition(final RealMatrix matrix) {
  398.         FieldMatrix<Complex> matrixC = convertToFieldComplex(matrix);

  399.         // checking definition of the decomposition
  400.         // testing A*V = V*D
  401.         FieldMatrix<Complex> AV = matrixC.multiply(getV());
  402.         FieldMatrix<Complex> VD = getV().multiply(getD());
  403.         if (!equalsWithPrecision(AV, VD, epsilonAVVDCheck)) {
  404.             throw new MathRuntimeException(LocalizedCoreFormats.FAILED_DECOMPOSITION,
  405.                                            matrix.getRowDimension(), matrix.getColumnDimension());

  406.         }

  407.     }

  408.     /**
  409.      * Helper method that checks with two matrix is equals taking into account a
  410.      * given precision.
  411.      *
  412.      * @param matrix1 first matrix to compare
  413.      * @param matrix2 second matrix to compare
  414.      * @param tolerance tolerance on matrices entries
  415.      * @return true is matrices entries are equal within tolerance,
  416.      * false otherwise
  417.      */
  418.     private boolean equalsWithPrecision(final FieldMatrix<Complex> matrix1,
  419.                                         final FieldMatrix<Complex> matrix2, final double tolerance) {
  420.         boolean toRet = true;
  421.         for (int i = 0; i < matrix1.getRowDimension(); i++) {
  422.             for (int j = 0; j < matrix1.getColumnDimension(); j++) {
  423.                 Complex c1 = matrix1.getEntry(i, j);
  424.                 Complex c2 = matrix2.getEntry(i, j);
  425.                 if (c1.add(c2.negate()).norm() > tolerance) {
  426.                     toRet = false;
  427.                     break;
  428.                 }
  429.             }
  430.         }
  431.         return toRet;
  432.     }

  433.     /**
  434.      * It converts a real matrix into a complex field matrix.
  435.      *
  436.      * @param matrix
  437.      *            real matrix.
  438.      * @return complex matrix.
  439.      */
  440.     private FieldMatrix<Complex> convertToFieldComplex(RealMatrix matrix) {
  441.         final FieldMatrix<Complex> toRet =
  442.                         MatrixUtils.createFieldIdentityMatrix(ComplexField.getInstance(),
  443.                                                               matrix.getRowDimension());
  444.         for (int i = 0; i < toRet.getRowDimension(); i++) {
  445.             for (int j = 0; j < toRet.getColumnDimension(); j++) {
  446.                 toRet.setEntry(i, j, new Complex(matrix.getEntry(i, j)));
  447.             }
  448.         }
  449.         return toRet;
  450.     }
  451. }