Search code examples
matrixlapackeigenvector

numerical diagonalization of a unitary matrix


To numerically diagonalize a unitary matrix I use the LAPACK routine zgeev.

The problem is: In case of degeneracies the degenerate subspace is not orthonormalized, since the routine is for general matrices.

However, since in my case the matrices are unitary, the basis can be always orthonormalized. Is there a better solution than applying QR-algorithm afterwards to the degenerate subspace?


Solution

  • Short answer: Schur decomposition!

    If a square matrix A is complex, then its Schur factorization is A=ZTZ*, where Z is unitary and T is upper triangular. If A happens to be unitary, T must also be unitary. Since T is both unitary and triangular, it is diagonal (proof here,.or there) Let's consider the vectors Z.e_i, where e_i are the vectors of the canonical basis. These vectors obviously form an orthonormal basis. Moreover, these vectors are eigenvectors of the matrix A. Hence, the columns of the unitary matrix Z are eigenvectors of the unitary matrix A and form an orthonormal basis.

    As a consequence, computing a Schur decomposition of a unitary matrix is equivalent to finding one of its orthogonal basis of eigenvectors.

    ZGEESX computes the eigenvalues, the Schur form, and, optionally, the matrix of Schur vectors for GE matrices

    The resulting T can also be tested to check that A is unitary.

    Here is a piece of python code testing it, though scipy's scipy.linalg.schur makes use of Lapack's zgees for Schur decomposition. I used hpaulj's code to generate random unitary matrix as shown in How to create random orthonormal matrix in python numpy

    import numpy as np
    import scipy.linalg
    
    #from hpaulj, https://stackoverflow.com/questions/38426349/how-to-create-random-orthonormal-matrix-in-python-numpy
    def rvs(dim=3):
         random_state = np.random
         H = np.eye(dim)
         D = np.ones((dim,))
         for n in range(1, dim):
             x = random_state.normal(size=(dim-n+1,))
             D[n-1] = np.sign(x[0])
             x[0] -= D[n-1]*np.sqrt((x*x).sum())
             # Householder transformation
             Hx = (np.eye(dim-n+1) - 2.*np.outer(x, x)/(x*x).sum())
             mat = np.eye(dim)
             mat[n-1:, n-1:] = Hx
             H = np.dot(H, mat)
             # Fix the last sign such that the determinant is 1
         D[-1] = (-1)**(1-(dim % 2))*D.prod()
         # Equivalent to np.dot(np.diag(D), H) but faster, apparently
         H = (D*H.T).T
         return H
    
    n=42
    A= rvs(n)
    A = A.astype(complex)
    T,Z=scipy.linalg.schur(A,output='complex',lwork=None,overwrite_a=False,sort=None,check_finite=True)
    
    #print T
    normT=np.linalg.norm(T,ord=None) #2-norm
    eigenvalues=[]
    for i in range(n):
        eigenvalues.append(T[i,i])
        T[i,i]=0.
    normTu=np.linalg.norm(T,ord=None)
    print 'must be very low if A is unitary: ',normTu/normT
    
    #print Z
    for i in range(n):
        v=Z[:,i]
        w=A.dot(v)-eigenvalues[i]*v
        print i,'must be very low if column i of Z is eigenvector of A: ',np.linalg.norm(w,ord=None)/np.linalg.norm(v,ord=None)