FFTの⊗的直観
離散フーリエ変換の高速アルゴリズムであるFFTはなぜ速いのか? について、テンソル積の意味でローカルな演算子に分解しているから、という直観的理解をする。実質的に量子フーリエ変換の回路をエミュレーションしていることになる。
DFT
逆変換は複素共役で
規格化を除いてユニタリとなる。
FFT
DFTの係数行列は密なので、そのままだと
この計算がいまいちピンと来なかったが、これは分割統治を テンソル積の意味で やっているとみなすと直観に適う。これを説明してみる。
DFTの積分核には基底の番号が現れるので、
ここで二通りのインデックスを考える。
直感的には
このインデックスによるDFTを実行する。ここで、入力側の基底インデックスと出力側の基底インデックスを一旦別の物にする。 これが計算で効いてくる。テンソル積分解された基底添字で表されたインデックスをDFTに代入すると
となる。これを計算していく。
積分核の分解
積分核の指数にある
の振る舞いが重要になる。これは以下のように再帰的に分解できる。ただし、ここに
最後の初項に
この式は複雑だが、
ここで、総和記号による足し上げは、それ以降のすべての項に掛かっていることに注意。
説明としては
StringDiagramでのバタフライ演算
ここで、String Diagramの出番だ。この式をみると、演算の対象となっているのは
は複雑ではあるが、この項の前後で添字は置き換わってない。これはこの項はこの瞬間採用している基底ベクトルに対しては掛け算作用素のようになっていることを意味する。掛け算作用素は疎行列だから演算は高速に実施できる。それはたかだか
そういうことで、
ただし、先のやり方で計算すると計算結果の添字は
これは量子フーリエ変換の回路と似ているが、そもそも数学的に同じものである。
よくFFTの演算はバタフライ演算と呼ばれるが、テンソル積分解とString Diagramの観点からは、テンソル積の意味でセパラブル/ローカルな演算子(と、掛け算作用素)への帰着が本質的だとわかる。一般の線形作用素は
例えば
は作用素テンソルの恒等式だが、計算量の観点では
と等しくない。この差を利用できるためには、演算子がテンソル積の意味でローカルな演算子に分解できればよい。
最後に計算量見積もりを確認しよう。
これは分かりづらいが、よくある
となり、
(追記)実装してみる
あとはString Diagramの通りに、
処理が完了すると、
import numpy as np
# size幅のプレーンなビット列としてreverseしたintを返す。
def bitreverse(idx:int,size:int)->int:
return int(f"{idx:0{size}b}"[::-1],2)
# 下位atビットまでを抽出した整数を返す。0-origin
def lowermasked(idx:int, at:int)->int:
mask = (1 << (at+1)) - 1
return idx & mask
# k-bit(0-origin)の値を返す。なお0がfalse
def getBit(idx:int,at:int) -> bool:
return (idx & (1 << at)) != 0
# テンソル成分に対する2ddft(実質hadamard)
def hadamard(input_array,at:int,bitSize:int):
new_array = np.zeros(input_array.size, dtype=np.complex128)
for i in range(0,1<<bitSize):
# アダマール(dim2DFT)を実施
if getBit(i,at): # 立ってる
new_array[i] = input_array[i & ~(1<<at)] - input_array[i | (1<<at)]
else: # 折れてる
new_array[i] = input_array[i & ~(1<<at)] + input_array[i | (1<<at)]
return new_array
# 位相適用
def twidle(input_array,at:int,bitSize:int,conjecture:bool):
new_array = np.zeros(input_array.size, dtype=np.complex128)
for i in range(0,1<<bitSize):
if getBit(i,at):
# twidleを適用
twidleFactor = (-1 if conjecture else 1)* lowermasked(i,at) / (1<<(at+1)) # at は0-origin
new_array[i] = input_array[i] * np.exp(-2*np.pi*1j*twidleFactor)
else:
new_array[i] = input_array[i]
return new_array
# 添字のbitreverse
def arrangeIndex(input_array,bitSize:int):
new_array = np.zeros(input_array.size, dtype=np.complex128)
for i in range(0,1<<bitSize):
new_array[i] = input_array[bitreverse(i,bitSize)]
return new_array
# fft実施。サブルーチンはO(N),繰り返しはbitSize = log NなのでNlogN
def fft(input_array,bitSize:int,conjecture:bool):
array = input_array.copy()
for j in reversed(range(0,bitSize)):
array = hadamard(array,j,bitSize)
if j != 0:
array = twidle(array,j,bitSize,conjecture)
return arrangeIndex(array,bitSize)
chatgpt氏に適当にテストコードを書いてもらおう。
import matplotlib.pyplot as plt
# 以下はGPT氏にテスト関数を書いてもらう
def generate_sine_wave(frequency, sample_rate, duration):
"""
正弦波データを生成
:param frequency: 周波数(Hz)
:param sample_rate: サンプリング周波数(Hz)
:param duration: 信号の長さ(秒)
:return: 時間軸, 正弦波信号
"""
t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
signal = np.sin(2 * np.pi * frequency * t)
return t, signal
def plot_spectrum(signal, sample_rate, fft_func, title="Spectrum"):
"""
信号のスペクトルをプロット
:param signal: 入力信号
:param sample_rate: サンプリング周波数(Hz)
:param fft_func: FFT関数
:param title: プロットのタイトル
"""
N = len(signal)
fft_result = fft_func(signal, int(np.log2(N)), conjecture=False)
freq = np.fft.fftfreq(N, 1 / sample_rate)
plt.figure(figsize=(10, 6))
plt.plot(freq[:N // 2], np.abs(fft_result)[:N // 2]) # 正の周波数のみプロット
plt.title(title)
plt.xlabel("Frequency (Hz)")
plt.ylabel("Amplitude")
plt.grid()
plt.show()
sample_rate = 1024 # サンプリング周波数 (Hz)
duration = 1.0 # 信号の長さ (秒)
freq1, freq2 = 50, 200 # 正弦波の周波数 (Hz)
t, sine_wave1 = generate_sine_wave(freq1, sample_rate, duration)
_, sine_wave2 = generate_sine_wave(freq2, sample_rate, duration)
signal = sine_wave1 + sine_wave2 # 2つの正弦波を加算
# 自作FFTの結果をプロット
plot_spectrum(signal, sample_rate, fft, title="Spectrum (Custom FFT)") # 自作の
# NumPy FFTの結果をプロット
def numpy_fft_wrapper(signal, bitSize, conjecture):
return np.fft.fft(signal) # npについてるやつ
plot_spectrum(signal, sample_rate, numpy_fft_wrapper, title="Spectrum (NumPy FFT)")
Discussion