エルミート行列

当サイトではアフィリエイトプログラムを利用して商品を紹介しています。

【Python線形代数】エルミート行列の定義と性質

共役転置して不変な正方行列、すなわち
 \[H=H^{*}\tag{1}\]
を満たす行列 $A$ をエルミート行列、または自己随伴行列とよびます。要素が全て実数であるときには、エルミート行列は対称行列を完全に包含する形で一般化された行列なので、エルミート行列の性質は対称行列の性質でもあります。複素数空間においてもエルミート行列の主対角成分は複素共役をとって不変であるために常に実数です。たとえば、
 \[H=\begin{bmatrix}2&1-i\\1+i&3\end{bmatrix}\tag{2}\]
の随伴行列は
 \[H^{*}=\begin{bmatrix}2&1-i\\1+i&3\end{bmatrix}\tag{3}\]
となるので、$H$ はエルミート行列です。

# Python Hermitian matrix

# In[1]

import numpy as np

H = np.array([[2, 1-1j],
              [1+1j, 3]])

# Hを共役転置して随伴行列を生成
H_dagger = np.conjugate(H.T)

print(H_dagger)
# [[2.-0.j 1.-1.j]
#  [1.+1.j 3.-0.j]]

ちなみに、SciPy には行列がエルミートであるかを判定する ishermitian 関数が用意されています。

# In[2]

from scipy.linalg import ishermitian

# Hがエルミート行列であることを判定
ishermitian(H)

# True

先にも説明したように、実対称行列もエルミート行列なので、ishermitian() は対称行列の判定にも使えます(小さいサイズの行列であれば対称行列であることは見た目で明らかですが、目視で確認できないほど大きなサイズの行列の判定に使うと良いでしょう)。

# In[3]

A = np.array([[1, 5],
              [5, 2]])

# 行列Aがエルミート行列であることを判定
ishermitian(A)

# True

エルミート行列の固有値と固有ベクトル

エルミート行列は二つの重要な性質をもちます。
・エルミート行列の固有値は実数である。
・エルミート行列の固有ベクトルは互いに直交する。

固有値は実数であることの証明

エルミート行列 $H$ の固有値を $\lambda$、固有ベクトルを $\boldsymbol{u}$ としたとき、$H\boldsymbol{u}=\lambda\boldsymbol{u}$ の両辺に左側から $\boldsymbol{u}^{*}$ をかけると、
 \[\boldsymbol{u}^{*}H\boldsymbol{u}=\lambda\boldsymbol{u}^{*}\boldsymbol{u}\tag{4}\]
となります。右辺の $\boldsymbol{u}^{*}\boldsymbol{u}$ は $\parallel\boldsymbol{u}\parallel^2$ なので実数です。左辺の共役転置をとると
 \[(\boldsymbol{u}^{*}H\boldsymbol{u})^{*}=\boldsymbol{u}^{*}H^{*}(\boldsymbol{u}^{*})^{*}\tag{5}\]
$(\boldsymbol{u}^{*})^{*}=\boldsymbol{u}$ であり、エルミート行列の定義より $H^{*}=H$ なので、$\boldsymbol{u}^{*}H\boldsymbol{u}$ は共役転置操作に対して不変です:
 \[(\boldsymbol{u}^{*}H\boldsymbol{u})^{*}=\boldsymbol{u}^{*}H\boldsymbol{u}\tag{6}\]
すなわち、$\boldsymbol{u}^{*}H\boldsymbol{u}$ は実数です。
(4) は実数 = $\lambda$ × 実数となり、固有値 $\lambda$ も実数となります。

固有ベクトルが互いに直交することの証明

エルミート行列 $H$ の固有値を $\alpha,\ \beta\ (\alpha\neq \beta)$、それぞれの固有値に対応する固有ベクトルを $\boldsymbol{u},\ \boldsymbol{v}$ とします:
 \[\begin{align*}&H\boldsymbol{u}=\alpha\boldsymbol{u}\tag{7}\\[6pt]&H\boldsymbol{v}=\beta\boldsymbol{v}\tag{8}\end{align*}\]
(7) に左から $\boldsymbol{v}^{*}$ をかけると
 \[\boldsymbol{v}^{*}H\boldsymbol{u}=\alpha\boldsymbol{v}^{*}\boldsymbol{u}\tag{9}\]
(8) の転置共役をとると
 \[\boldsymbol{v}^{*}H^{*}=\beta\boldsymbol{v}^{*}\tag{10}\]
(10) に右から $\boldsymbol{u}$ をかけると
 \[\boldsymbol{v}^{*}H^{*}\boldsymbol{u}=\beta\boldsymbol{v}^{*}\boldsymbol{u}\tag{11}\]
$H^{*}=H$ なので、
 \[\boldsymbol{v}^{*}H\boldsymbol{u}=\beta\boldsymbol{v}^{*}\boldsymbol{u}\tag{12}\]
(9) と (12) を比較すると、$\alpha\neq \beta$ より
 \[\boldsymbol{v}^{*}\boldsymbol{u}=0\tag{13}\]
となるので、$\boldsymbol{v}$ と $\boldsymbol{u}$ は直交します。■

(2) で定義したエルミート行列
 \[H=\begin{bmatrix}2&1-i\\1+i&3\end{bmatrix}\tag{2}\]
について具体的に固有値と固有ベクトルを求めてみます。固有方程式
 \[\begin{vmatrix}2-\lambda & 1-i\\ 1+i & 3-\lambda\end{vmatrix}=0\tag{14}\]
を解くと $\lambda=1,4$ を得ます。$\boldsymbol{u}=\begin{bmatrix}x\\y\end{bmatrix}$ とおいて、$H\boldsymbol{u}=1\boldsymbol{u}$ を解くと、
 \[\boldsymbol{u}=\begin{bmatrix}-2\\1+i\end{bmatrix}\tag{15}\]
が得られます。同様に $\boldsymbol{v}=\begin{bmatrix}x\\y\end{bmatrix}$ とおいて、$H\boldsymbol{v}=4\boldsymbol{v}$ を解くと、
 \[\boldsymbol{v}=\begin{bmatrix}1\\1+i\end{bmatrix}\tag{16}\]
が得られます。$\boldsymbol{u}$ と $\boldsymbol{v}$ の内積をとると、
 \[(\boldsymbol{u},\boldsymbol{v})=\boldsymbol{u}^{*}\boldsymbol{v}=(-2)\cdot 1+(1-i)(1+i)=0\tag{17}\]
となって、確かに固有ベクトル同士は直交しています。

# In[4]

# Hの固有値と固有ベクトル
eigenvalues, eigenvectors = np.linalg.eigh(H)
eigenvalues = np.round(eigenvalues)
eigenvectors = np.round(eigenvectors, 5)
u1, u2 = eigenvectors[:,0], eigenvectors[:,1]

print("固有値{}の固有ベクトル: {}".format(eigenvalues[0], u1))
print("固有値{}の固有ベクトル: {}".format(eigenvalues[1], u2))

# エルミート行列の固有ベクトルが直交していることを確認
print("u1とu2の内積: {}".format(np.vdot(u1, u2)))

# 固有値1.0の固有ベクトル: [-0.8165 +0.j  0.40825+0.40825j]
# 固有値4.0の固有ベクトル: [-0.57735+0.j -0.57735-0.57735j]
# u1とu2の内積: 0j

エルミート行列の対角化

固有ベクトル $\boldsymbol{u},\ \boldsymbol{v}$ を正規化しておきます。
 \[\boldsymbol{u}’=\frac{1}{\sqrt{6}}\begin{bmatrix}-2\\1+i\end{bmatrix},\quad \boldsymbol{v}’=\frac{1}{\sqrt{3}}\begin{bmatrix}1\\1+i\end{bmatrix}\tag{18}\]
$\boldsymbol{u}’,\ \boldsymbol{v}’$ を並べた行列
 \[U=\begin{bmatrix}\boldsymbol{u}’&\boldsymbol{v}’\end{bmatrix}\tag{19}\]
は $U^{*}U=I$ を満たす複素空間の直交行列であり、ユニタリ行列とよばれます (ユニタリ行列については次回記事で詳しく学びます)。$U^{*}HU$ を計算してみると、
 \[U^{*}HU=\begin{bmatrix}1&0\\0&4\end{bmatrix}\tag{20}\]
のように対角化されます。以上の手順を再現するコードも載せておきます。

# In[5]

# ユニタリ対角化関数
def unitary_diagonalization(arr):
    if ishermitian(arr):

        # エルミート行列の固有ベクトル
        _, u = np.linalg.eigh(arr)

        # Uの随伴行列(共役転置)
        u_dagger = np.conjugate(u.T)

        # エルミート行列をユニタリ行列で対角化
        diag_matrix = u_dagger @ arr @ u

        # データ型を整数に変更して小さな誤差が入っている虚部を取り除く
        # 単位行列をかけて対角成分以外の要素の誤差を小さくする
        diag_matrix = diag_matrix.astype('float') * np.identity(arr.shape[0])

        return diag_matrix, u

# Hを対角化
d, _ = unitary_diagonalization(H)

print(d)
# [[ 1. -0.]
#  [-0.  4.]]

先に述べたように、エルミート行列を対角化するユニタリ行列の対角にはエルミート行列の固有値が並びます。エルミート行列の固有値は実数なので、astype メソッドでデータ型を複素数から浮動小数点数に変更してあります。さらに単位行列を掛けることによって、対角成分以外の要素($0$)の誤差を極力小さくするようにしてあります。
 
一般にエルミート行列 $H$ はユニタリ行列 $U$ によって対角化可能なので ($U^{*}HU=D$)、$H=UDU^{*}$ と表すことができます。

エルミート行列とユニタリ行列はそれぞれ、実数空間 $\mathbb{R}^n$ の対称行列と直交行列を複素数空間 $\mathbb{C}^n$ に拡張したものでした。なので、上記のことは $\mathbb{R}^n$ の対称行列と直交行列にもそのまま当てはまります。すなわち、一般に対称行列 $A$ は、$A$ の固有ベクトルを並べた直交行列 $P$ によって
 \[D=P^{T}AP\tag{21}\]
の形に対角化できます。このとき、$D$ は $A$ の固有値を主対角線上に並べた対角行列となっています。
 \[D=\begin{bmatrix}\lambda_{1}\\&\lambda_{2}&&\huge{0}\\&&\ddots\\&\huge{0}&&\:\:\:\:\ddots\\&&&&\lambda_{n}\end{bmatrix}\tag{22}\]
また、$A$ は直交行列 $P$ と対角化された行列 $D$ によって、
 \[A=PDP^{T}\tag{23}\]
のように表せます。

numpy.linalg.eigh()

すでに述べたように、エルミート行列の固有値は必ず実数ですが、numpy.linalg.eig() を使って固有値を求めると、誤差によって虚数部が僅かに値をもちます。

# numpy.linalg.eigh

# In[1]

import numpy as np
np.set_printoptions(precision=3)

H = np.array([[1, 1+2j],
              [1-2j, 2]])

# Hの固有値
eigenvalues, _ = np.linalg.eig(H)

print(eigenvalues)
# [-0.791-8.680e-17j  3.791-1.352e-16j]

NumPy にはエルミート行列専用の固有値・固有ベクトルを求める numpy.linalg.eigh() が用意されていて、固有値を浮動小数点数型で返します。

# In[2]

# Hの固有値と固有ベクトル
eigenvalues, eigenvectors = np.linalg.eig(H)
u1, u2 = eigenvectors[:,0], eigenvectors[:,1]

print("固有値: {}".format(eigenvalues))
print("固有ベクトル①: {}".format(u1))
print("固有ベクトル②: {}".format(u2))

# 固有値: [-0.791-8.680e-17j  3.791-1.352e-16j]
# 固有ベクトル①: [0.78+0.j -0.28+0.559j]
# 固有ベクトル②: [0.28+0.559j 0.78+0.j]

SciPy にも エルミート行列の固有値と固有ベクトルを計算する scipy.linalg.eigh() という関数が用意されています。

エルミート行列自動生成関数

任意サイズのエルミート行列を自動生成する関数を作ってみました。

# random Hermitian matrix generator

# In[1]

# エルミート行列自動生成関数
def generate_hermitian_matrix(low, high, size, seed=0, part_int=False):
    rng = np.random.default_rng(seed)
    matrix = np.diag(rng.integers(low, high, size))
    matrix = matrix.astype('complex')
    for i in range(size):
        for j in range(size):
            if i != j:
                if int_element:
                    element = rng.integers(low, high) + 1j * rng.integers(low, high)
                else:
                    element = rng.uniform(low, high) + 1j * rng.uniform(low, high)
                matrix[i][j] = element
                matrix[j][i] = np.conjugate(element)
    return matrix

# 実数部と虚数部の値の範囲が0~9の3×3のエルミート行列を生成
arr = generate_hermitian_matrix(0, 10, 3, seed=17, part_int=True)

print(arr)
# [[7.+0.j 3.-0.j 3.-9.j]
#  [3.+0.j 8.+0.j 4.-6.j]
#  [3.+9.j 4.+6.j 1.+0.j]]

low と high には、それぞれ各要素の実部と虚部の下限値と上限値を指定します。たとえば、low=0, high=4 とすれば、0 + 0j ~ 3 + 3j の範囲で生成します。size には行列のサイズ、seed には乱数のシードを指定してください。part_int は実部と虚部を整数とするかどうかを指定します。

【Python線形代数】歪エルミート行列

歪エルミート行列 (反エルミート行列) は
 \[A^{*}=-A\tag{1}\]
という性質を満たす行列として定義されます。$H$ がエルミート行列であるとき、$iH$ は歪エルミート行列です:
 \[(iH)^{*}=-iH^{*}\tag{2}\]
たとえば、エルミート行列
 \[H=\begin{bmatrix}2&1-i\\1+i&3\end{bmatrix}\tag{3}\]
に純虚数 $i$ を乗じた行列は歪エルミート行列となります。
 \[iH=\begin{bmatrix}2i&1+i\\-1+i&3i\end{bmatrix}\tag{4}\]
エルミート行列の主対角成分は常に実数であったのに対して、歪エルミート行列の主対角成分は常に純虚数です。

# python_skew_hermitian_matrix

# In[1]

import numpy as np
np.set_printoptions(precision=3)

# エルミート行列
H = np.array([[2, 1-1j],
              [1+1j, 3]])

# 歪エルミート行列
iH = 1j * H

print(iH)
# [[ 0.+2.j  1.+1.j]
#  [-1.+1.j  0.+3.j]]

歪エルミート行列の固有値は常に実数でしたが、歪エルミート行列の固有値は常に純虚数です。

# In[2]

# 歪エルミート行列の固有値
eigvals = np.linalg.eig(iH)[0]

print(eigvals)
# [-2.220e-16+1.j -3.331e-16+4.j]

[参考文献]
・ Wikipedia
・『ストラング:線形代数イントロダクション』

 

コメント

  1. HNaito より:

    下記は誤植と思われますので、ご確認ください。
    (5) 式の下の文章で、u†u は共役転置 → u†Hu は共役転置
    (6) 式の下の文章で、u†u は実数 → u†Hu は実数
    誤植ではありませんが、実数=λ x 実数より → (4) は 実数=λ x 実数となり
    のほうがわかりやすいと思いました。
    (10) 式の下の文章で、右から u†をかける → 右から u をかける
    (17) 式で、=u†u= → =u†v=