/**
  * Only eigenvalues of a symmetric real matrix are computed.
  *
  * @param A a symmetric real matrix
  * @return a 1D {@code double} array containing the eigenvalues in decreasing order (absolute
  *     value)
  */
 public static double[] computeEigenvalues(Matrix A) {
   SparseMatrix S = (SparseMatrix) decompose(A, false)[1];
   int m = S.getRowDimension();
   int n = S.getColumnDimension();
   int len = m >= n ? n : m;
   double[] s = ArrayOperator.allocateVector(len, 0);
   for (int i = 0; i < len; i++) {
     s[i] = S.getEntry(i, i);
   }
   return s;
 }
  /**
   * Do eigenvalue decomposition for a real symmetric tridiagonal matrix, i.e. T = VDV'.
   *
   * @param T a real symmetric tridiagonal matrix
   * @param computeV if V is to be computed
   * @return a {@code Matrix} array [V, D]
   */
  private static Matrix[] diagonalizeTD(Matrix T, boolean computeV) {

    int m = T.getRowDimension();
    int n = T.getColumnDimension();
    int len = m >= n ? n : m;
    int idx = 0;

    /*
     * The tridiagonal matrix T is
     * s[0] e[0]
     * e[0] s[1] e[1]
     *      e[1] ...
     *               s[len - 2] e[len - 2]
     *               e[len - 2] s[len - 1]
     */
    double[] s = ArrayOperator.allocateVector(len, 0);
    double[] e = ArrayOperator.allocateVector(len, 0);

    for (int i = 0; i < len - 1; i++) {
      s[i] = T.getEntry(i, i);
      e[i] = T.getEntry(i, i + 1);
    }
    s[len - 1] = T.getEntry(len - 1, len - 1);

    // V': each row of V' is a right singular vector
    double[][] Vt = null;
    if (computeV) Vt = eye(n, n).getData();

    double[] mu = ArrayOperator.allocate1DArray(len, 0);

    /*
     * T0 = ITI', V = I
     * T = IT0I' = VT0G1G1'Vt = VT0G1(G1'Vt)
     *   = VG1G1'T0G1(G1'Vt) = (VG1)(G1'T0G1)(G1'Vt)
     *   = (VG1)T1(G1'Vt)
     *   ...
     *   = (Gn-1...G2G1)D(G1G2...Gn-1)'
     *
     * where G = |cs  sn|
     *           |-sn cs|
     * G' = |cs -sn|
     *      |sn  cs|
     */

    /*
     * Find B_hat, i.e. the bottommost unreduced submatrix of B.
     * Index starts from 0.
     */
    // *********************************************************

    int i_start = 0;
    int i_end = len - 1;
    // int cnt_shifted = 0;
    int ind = 1;
    while (true) {

      idx = len - 2;
      while (idx >= 0) {
        if (e[idx] == 0) {
          idx--;
        } else {
          break;
        }
      }
      i_end = idx + 1;
      // Now idx = -1 or e[idx] != 0
      // If idx = -1, then e[0] = 0, i_start = i_end = 0, e = 0
      // Else if e[idx] != 0, then e[i] = 0 for i_end = idx + 1 <= i <= len - 1
      while (idx >= 0) {
        if (e[idx] != 0) {
          idx--;
        } else {
          break;
        }
      }
      i_start = idx + 1;
      // Now idx = -1 or e[idx] = 0
      // If idx = -1 i_start = 0
      // Else if e[idx] = 0, then e[idx + 1] != 0, e[i_end - 1] != 0
      // i.e. e[i] != 0 for i_start <= i <= i_end - 1

      if (i_start == i_end) {
        break;
      }

      // Apply the stopping criterion to B_hat
      // If any e[i] is set to zero, return to loop

      boolean set2Zero = false;
      mu[i_start] = abs(s[i_start]);
      for (int j = i_start; j < i_end; j++) {
        mu[j + 1] = abs(s[j + 1]) * mu[j] / (mu[j] + abs(e[j]));
        if (abs(e[j]) <= mu[j] * tol) {
          e[j] = 0;
          set2Zero = true;
        }
      }
      if (set2Zero) {
        continue;
      }

      implicitSymmetricShiftedQR(s, e, Vt, i_start, i_end, computeV);
      // cnt_shifted++;

      if (ind == maxIter) {
        break;
      }

      ind++;
    }

    // fprintf("cnt_shifted: %d\n", cnt_shifted);
    // *********************************************************

    // Quick sort eigenvalues and eigenvectors
    quickSort(s, Vt, 0, len - 1, "descend", computeV);

    Matrix[] VD = new Matrix[2];
    VD[0] = computeV ? new DenseMatrix(Vt).transpose() : null;
    VD[1] = buildD(s, m, n);

    /*disp("T:");
    printMatrix(T);
    disp("VDV':");
    Matrix V = VD[0];
    Matrix D = VD[1];
    disp(V.mtimes(D).mtimes(V.transpose()));*/

    return VD;
  }
  /**
   * Tridiagonalize a real symmetric matrix A, i.e. A = Q * T * Q' such that Q is an orthogonal
   * matrix and T is a tridiagonal matrix.
   *
   * <p>A = QTQ' <=> Q'AQ = T
   *
   * @param A a real symmetric matrix
   * @param computeV if V is to be computed
   * @return a {@code Matrix} array [Q, T]
   */
  private static Matrix[] tridiagonalize(Matrix A, boolean computeV) {
    A = full(A).copy();
    int m = A.getRowDimension();
    int n = A.getColumnDimension();
    Matrix[] QT = new Matrix[2];
    double[] a = ArrayOperator.allocateVector(n, 0);
    double[] b = ArrayOperator.allocateVector(n, 0);
    double[][] AData = ((DenseMatrix) A).getData();
    double c = 0;
    double s = 0;
    double r = 0;
    for (int j = 0; j < n - 2; j++) {
      a[j] = AData[j][j];
      // Householder transformation on columns of A(j+1:m, j+1:n)
      // Compute the norm of A(j+1:m, j)
      c = 0;
      for (int i = j + 1; i < m; i++) {
        c += Math.pow(AData[i][j], 2);
      }
      if (c == 0) continue;
      s = Math.sqrt(c);
      b[j] = AData[j + 1][j] > 0 ? -s : s;
      r = Math.sqrt(s * (s + abs(AData[j + 1][j])));

      /*double[] u1 = ArrayOperation.allocate1DArray(n, 0);
      for (int k = j + 1; k < m; k++) {
      	u1[k] = AData[k][j];
      }
      u1[j + 1] -= b[j];
      for (int k = j + 1; k < m; k++) {
      	u1[k] /= r;
      }
      Matrix H = eye(n).minus(new DenseMatrix(u1, 1).mtimes(new DenseMatrix(u1, 2)));
      disp(new DenseMatrix(u1, 1));
      disp(eye(n));
      disp(H);
      disp(A);
      disp(H.mtimes(A).mtimes(H));
      disp(A);*/

      AData[j + 1][j] -= b[j];
      for (int k = j + 1; k < m; k++) {
        AData[k][j] /= r;
      }

      double[] w = new double[n - j - 1];
      double[] u = new double[n - j - 1];
      double[] v = new double[n - j - 1];

      for (int i = j + 1, t = 0; i < m; i++, t++) {
        u[t] = AData[i][j];
      }

      // v = B33 * u
      for (int i = j + 1, t = 0; i < m; i++, t++) {
        double[] ARow_i = AData[i];
        s = 0;
        for (int k = j + 1, l = 0; k < n; k++, l++) {
          s += ARow_i[k] * u[l];
        }
        v[t] = s;
      }

      c = ArrayOperator.innerProduct(u, v) / 2;
      for (int i = j + 1, t = 0; i < m; i++, t++) {
        w[t] = v[t] - c * u[t];
      }

      /*disp(w);
      Matrix B33 = new DenseMatrix(n - j - 1, n - j - 1, 0);
      for (int i = j + 1, t = 0; i < m; i++, t++) {
      	double[] ARow_i = AData[i];
      	for (int k = j + 1, l = 0; k < n; k++, l++) {
      		B33.setEntry(t, l, ARow_i[k]);
      	}
      }
      disp(B33);
      Matrix U = new DenseMatrix(u, 1);
      disp(U.transpose().mtimes(U));
      disp(U);
      Matrix W = B33.mtimes(U).minus(U);
      disp(W);
      disp(B33.minus(plus(U.mtimes(W.transpose()), W.mtimes(U.transpose()))));
      Matrix Hk = eye(n - j - 1).minus(U.mtimes(U.transpose()));
      disp(Hk);
      disp(Hk.mtimes(B33).mtimes(Hk));*/
      for (int i = j + 1, t = 0; i < m; i++, t++) {
        double[] ARow_i = AData[i];
        for (int k = j + 1, l = 0; k < n; k++, l++) {
          ARow_i[k] = ARow_i[k] - (u[t] * w[l] + w[t] * u[l]);
        }
      }
      // disp(A);

      /*fprintf("Householder transformation on n - 1 columns:\n");
      disp(A);*/
      // disp(A);
      // Householder transformation on rows of A(j:m, j+1:n)

    }
    a[n - 2] = AData[n - 2][n - 2];
    a[n - 1] = AData[n - 1][n - 1];
    b[n - 2] = AData[n - 1][n - 2];
    QT = unpack(A, a, b, computeV);
    return QT;
  }