Quantum Computing

Qiskit을 이용한 양자 컴퓨팅 예제 (7) - 양자 푸리에 변환 (Quantum Fourier Transform)

Firewood91 2024. 12. 30. 01:47
반응형

 

 
이번 포스팅에서는 양자 푸리에 변환에 대해 알아보도록 하겠습니다. 양자 푸리에 변환(Quantum Fourier Transform, QFT)이산 푸리에 변환(Discrete Fourier Transform, DFT)을 큐비트 상태에 적용하여 위상에 대한 정보를 얻어내는 변환입니다.  


이산 푸리에 변환 (Discrete Fourier Transform, DFT)

어떠한 유한한 이산 신호(Sample data)가 있을때, 이를 주파수 도메인으로 변환하는 연산을 우리는 "이산 푸리에 변환"이라고 부릅니다. 주어진 데이터 샘플 $x_0, x_1, \cdots x_{N-1}$에 대해서, 이산 푸리에 변환에 대한 수학적 정의는 아래와 같습니다.

\begin{eqnarray} y_k = \frac{1}{\sqrt{N}} \sum_{j=0}^{N-1} x_j \cdot e^{-2 \pi i k \cdot j /N}, \ \ k= 0 ,1, 2, \cdots , N-1 \label{1}\tag{1} \end{eqnarray}

여기서 $N$은 샘플 데이터의 총 개수이며, $x_n$은 시간 도메인에서 $n$-번째 샘플 값, $y_k$는 주파수 도메인에서 $k$-번째 주파수 성분을 의미합니다.

 

이에 대한 역변환(Inverse Discrete Fourier Transformation)은 아래와 같습니다:

\begin{eqnarray} x_n = \frac{1}{N} \sum_{k=0}^{N-1} y_k \cdot e^{2\pi i k \cdot n /N},  \ \ n=0,1, \cdots , N-1 \label{2}\tag{2}\end{eqnarray}

 

이런 이산 푸리에 변환으로부터, 우리는 주어진 데이터의 위상 정보와 주어진 주파수 신호에서의 진폭 크기를 알아낼 수 있습니다. 

 

양자 푸리에 변환 (Quantum Fourier Transform)

위에서 정의된 이산 푸리에 변환처럼, 양자 상태 간에도 이산 푸리에 변환을 아래와 같이 적용할 수 있습니다:

\begin{eqnarray} |j \rangle \longrightarrow \frac{1}{\sqrt{n}} \sum_{k=0}^{n-1} \omega^{jk} |k \rangle,  \label{3}\tag{3} \end{eqnarray}

이며, 여기서 $\omega=e^{2\pi i /n}$을 의미합니다. 위에서 정의된 이산 푸리에 변환과 지수 함수의 부호가 다른데, 이는 관습적으로 정의된 것으로 양자 푸리에 변환에서는 정변환에 +부호를 사용하고 역변환에 - 부호를 사용합니다. 따라서, 식 (\ref{3})에 대한 역 변환은 아래와 같이 주어집니다:

\begin{eqnarray} |k\rangle \longrightarrow \frac{1}{\sqrt{n}} \sum_{j=0}^{n-1} \omega^{-kj} |j\rangle.  \label{4}\tag{4} \end{eqnarray}

 

주어진 양자 푸리에 변환 식 (\ref{3})을 아래와 같이 행렬식으로도 쓸 수 있습니다:

\begin{eqnarray} \begin{pmatrix} |0\rangle  \\  |1\rangle  \\  |2 \rangle \\  | 3\rangle \\ \vdots  \\ |n-1\rangle \end{pmatrix}   = \frac{1}{\sqrt{n}}\begin{pmatrix} 1 & 1 & 1 & 1 & \cdots & 1 \\ 1 & \omega & \omega^2 & \omega^3 & \cdots & \omega^{n-1} \\ 1 & \omega^2 & \omega^4 & \omega^6 & \cdots & \omega^{n-2} \\ 1 & \omega^3 & \omega^6 & \omega^9 & \cdots & \omega^{n-3} \\ \vdots & \vdots & \vdots & \vdots & \cdots & \vdots \\ 1 & \omega^{n-1} & \omega^{n-2} & \omega^{n-3} & \cdots & \omega \\ \end{pmatrix} \begin{pmatrix} |0\rangle \\ |1\rangle \\ |2\rangle \\ |3\rangle \\ \vdots \\ |n-1\rangle \end{pmatrix}. \label{5}\tag{5}\end{eqnarray}

 

그런데, 실제 큐비트는 $|0\rangle$ 또는 $|1\rangle$ 상태로 기술이 됩니다. 즉 위에서 주어진 상태들을 이진법(binary) 표기로 바꿔줘야 합니다. 일반적으로, 어떤 10진법의 정수 $j$를 이진법으로 바꾸는 식은 아래와 같습니다:

\begin{eqnarray} j = \sum_{n=1}^s j_n 2^{s-n} = j_1 2^{s-1} + j_2 2^{s-2} + \cdots + j_s 2^0 = j_1j_2j_3\cdots j_s {}_{(2)}. \label{6}\tag{6} \end{eqnarray}

위 식에서 $s$가 의미하는 것은 이진법 표기에서의 자릿수(혹은 큐비트의 개수)가 되겠습니다. 

 

식 (\ref{6})의 양변을 $2^s$로 나누면, 아애롸 같이 이진 소숫점(binary decimal) 표기로도 적을 수 있습니다:

\begin{eqnarray} \frac{j}{2^s} = \sum_{n=1}^s j_n 2^{-n} = \frac{1}{2} j_1  + \frac{1}{2^2} j_2 + \cdots \frac{1}{2^s} j_s \equiv 0.j_1 j_2 j_3 \cdots + j_s {}_{(2)}. \label{7}\tag{7} \end{eqnarray}

 

그럼, $n$개의 큐비트 시스템을 생각해 보겠습니다. 한개의 큐비트는 2개의 상태를 가질 수 있으므로, 전체 가능한 상태의 수는 $2^n$개가 될 것입니다. 띠라서, 식 (\ref{3})에서 10진법의 표기로 좌변은 주어진 큐비트 수를 이용해 이진법의 표현으로 쓸 수 있습니다:
\begin{eqnarray} |j\rangle = |j_0 j_1 j_2 \cdots j_{n-1} \rangle. \label{8}\tag{8} \end{eqnarray}

여기서 우변의 $|j_i\rangle$는 0 또는 1로만 표현이 될 것이며, 큐비트의 개수가 $n$개 이므로, 총 $2^n$개 상태를 기술할 수 있을 것입니다.

 

한편, 식 (\ref{3})의 우변 역시 이진법으로 표기가 가능합니다. 그렇지만, 앞에 시그마 기호는 십진법 표기를 기준으로 만들어진 것이므로, 이를 이진법에 맞게 변형해줘야 합니다. 식 (\ref{6})을 이용해서, 우변을 이진법의 형태로 바꿔보면 아래와 같이 식을 다시 쓸 수 있습니다:

\begin{eqnarray} \frac{1}{\sqrt{2^n}} \sum_{k=0}^{2^n-1} \omega^{jk} |k\rangle = \frac{1}{\sqrt{2^n}} \sum_{k=0}^{2^n-1} e^{2\pi i j k/2^n} |k\rangle &=& \frac{1}{\sqrt{2^n}} \sum_{k_1=0}^{1} \sum_{k_2=0}^{1} \cdots \sum_{k_n=0}^{1} e^{2\pi i j \sum_{l=1}^n k_l 2^{n-l}/2^n} |k_1 k_2 \cdots k_n \rangle \\[12pt] &=& \frac{1}{\sqrt{2^n}} \sum_{k_1=0}^{1} \sum_{k_2=0}^{1} \cdots \sum_{k_n=0}^{1} e^{2\pi i j \sum_{l=1}^n k_l 2^{-l} } |k_1 k_2 \cdots k_n \rangle \\[12pt] &=& \frac{1}{\sqrt{2^n}} \sum_{k_1=0}^{1} \sum_{k_2=0}^{1} \cdots \sum_{k_n=0}^{1} \bigotimes_{l=1}^n e^{2\pi i j k_l 2^{-l} } |k_l \rangle \\[12pt] &=& \frac{1}{\sqrt{2^n}} \bigotimes_{l=1}^n \left[ \sum_{k_l=0}^{1} e^{2\pi i j k_l 2^{-l} } |k_l \rangle \right] \\[12pt] &=& \frac{1}{\sqrt{2^n}} \bigotimes_{l=1}^n \left[ |0 \rangle + e^{2\pi i j 2^{-l}} | 1 \rangle \right]. \label{9}\tag{9} \end{eqnarray}

 

따라서, 위 식 (\ref{9})로부터 우리는 일반화된 양자 푸리에 변환 식을 아래와 같이 얻을 수 있습니다:

\begin{eqnarray} {\rm QFT} |j\rangle = \frac{1}{\sqrt{2^n}} \bigotimes_{l=1}^n \left[ |0 \rangle + e^{2\pi i j 2^{-l}} | 1 \rangle \right]. \label{10}\tag{10} \end{eqnarray}

혹은 식 (\ref{7})로부터 $j/2^l$이 이진 소숫점 표기로 쓸 수 있기에, 위 식 (\ref{10})을 풀어서 쓰면 아래와 같이도 쓸 수 있습니다:

\begin{eqnarray} {\rm QFT} | j \rangle = \frac{1}{\sqrt{2^n}} \left[ \left( |0 \rangle + e^{2\pi i 0.j_n } | 1 \rangle \right) \otimes \left( |0 \rangle + e^{2\pi i 0.j_{n-1}j_n } | 1 \rangle \right) \otimes \cdots \otimes \left( |0 \rangle + e^{2\pi i 0.j_1\cdots j_{n-1}j_n } | 1 \rangle \right) \right]. \label{11}\tag{11} \end{eqnarray}

 

예제: 2-큐비트 시스템

가장 간단한 예인 2큐비트 시스템에 대해서 양자 푸리에 변환을 적용해 보겠습니다.

 

식 (\ref{11})을 2큐비트 시스템에서 적용하면 아래와 같습니다:

\begin{eqnarray} {\rm QFT} | j_1 j_2 \rangle = \frac{1}{\sqrt{2^2}} \left[ \left( | 0 \rangle + e^{2\pi i 0.j_2} | 1 \rangle \right) \otimes \left( |0 \rangle + e^{2\pi i 0.j_1j_2} | 1\rangle \right) \right] \label{12}\tag{12} \end{eqnarray}

여기서 첫번째 괄호는 $j_2$ 값에 따라 결정됩니다. 만약 $j_2=0$이면 $|0 \rangle + |1\rangle$이고, 만약 $j_2=1$이면 $|0 \rangle - | 1 \rangle $일 것입니다. 즉, 첫번째 괄호는 $|j_2\rangle$ 상태에 대한 H 게이트임을 알 수 있습니다.

\begin{eqnarray} H |j_2 \rangle = \frac{1}{\sqrt{2}} \left( |0 \rangle + e^{2\pi i j_2/2}| 1 \rangle \right). \label{13}\tag{13} \end{eqnarray}

 

두번째 괄호 안에 두번째 식은 $e^{2\pi i 0.j_1j_2}$로 위상에 대한 부분이 앞에 괄호와 다른데, 이는 H게이트를 적용한 후에, $|1\rangle$ 상태의 phase를 회전시켜주는 제어 게이트를 적용하여 얻을 수 있습니다:

\begin{eqnarray} R_2 H |j_1 \rangle =\frac{R_2}{\sqrt{2}} \left[ |0 \rangle + e^{2\pi i \left( \frac{j_1}{2} \right)} | 1 \rangle \right] = \frac{1}{\sqrt{2}} \left[ |0 \rangle + e^{2\pi i \left( \frac{j_1}{2} + \frac{j_1j_2}{2^2} \right)} |1 \rangle \right]. \label{14}\tag{14} \end{eqnarray}

제어 회전 게이트는 행렬로 아래와 같이 일반적으로 주어집니다:

\begin{eqnarray} R_k = \begin{pmatrix} 1 & 0 \\ 0 & e^{i 2\pi/2^k} \end{pmatrix}. \end{eqnarray}

 

위에 식 (\ref{13})과 (\ref{14})에서 주어진 두 식을 텐서곱 해주면, 우리는 2큐비트 시스템에 대한 양자 푸리에 변환 결과를 얻을 수 있습니다. 

 

 

Qiskit 코드

위에서의 예제를 Qiskit으로 분석해 보겠습니다.  

 

1) 먼저 초기 상태를 $|j_1 j_2 \rangle = |1 0 \rangle$로 주겠습니다.

from qiskit import QuantumCircuit, transpile
from qiskit.visualization import plot_bloch_multivector, plot_histogram
from qiskit_aer import AerSimulator
import matplotlib.pyplot as plt

# 양자 회로 초기화
qc = QuantumCircuit(2)

# 초기 상태 설정: |j⟩ = |10⟩
qc.initialize([0, 0, 1, 0], [0, 1])  # 상태 |10⟩ 생성

 

2) 그 다음 식 (\ref{14})를 따라서, 첫번째 큐비트에 H 게이트를 적용한 후 제어 회전 게이트를 걸어보겠습니다:

# 첫 번째 큐비트에 H 게이트 적용
qc.h(0)  # |0⟩ + e^{2\pi i 0.j_2}|1⟩

# 제어된 회전 게이트 R2 (CR1)
qc.cp(np.pi / 2, 1, 0)  # e^{2\pi i 0.j_1j_2}를 추가

 

여기서 qc.cp(추가할 위상, 제어 큐비트, 타겟 큐비트)를 의미합니다. 즉, 주어진 명령어는 1번 큐비트를 제어 큐비트로 하여 0번 큐비트의 위상을 변경한다는 뜻입니다. 

 

3) 마지막으로, 식 (\ref{13})을 따라서, 두번째 큐비트에 H 게이트를 걸어주고, 두 큐비트의 순서를 변경하겠습니다:

# 두 번째 큐비트에 H 게이트 적용
qc.h(1)  # |0⟩ + e^{2\pi i 0.j_1}|1⟩

# 큐비트 순서 변경 (SWAP)
qc.swap(0, 1)

 

이 결과는 2큐비트 상태에 대한 양자 푸리에 변환을 직접 만들어 본 것이며, 이에 대한 양자 회로도는 아래와 같습니다:

2큐비트에 대한 양자 푸리에 변환

 

이 회로에 대해 1024번 시뮬레이션 했을 때, 각 상태들의 수를 표현하는 히스토그램 값은 아래와 같습니다:

직접 주어진 식을 이용해 계산해보면, $|10\rangle$ 상태에 대한 QFT 결과는 아래와 같습니다:

\begin{eqnarray} {\rm QFT} |10 \rangle = \frac{1}{\sqrt{4}} \left[ |00 \rangle + i | 01 \rangle - | 10 \rangle -i |11 \rangle \right]. \end{eqnarray}

따라서, 각각의 상태가 가질 확률은 1/4에 가까우며, 위에 히스토그램 결과가 이론값에 가깝다는 것을 알 수 있습니다.

 

일반적인 양자 푸리에 변환

보다 일반적인 양자 푸리에 변환에 대한 양자 회로도는 식 (\ref{11})로부터 아래와 같이 주어집니다: 

n-큐비트 상태에 대한 양자 푸리에 변환을 보여주는 양자 회로도. (출처: 위키피디아)

 

결국 주어진 큐비트의 순서에 따라, H게이트와 제어 회전 게이트 $R_k$를 조합하여 식 (\ref{11})의 괄호 항들을 구성하고, 이들의 텐서곱으로부터 QFT를 구성할 수 있다는 뜻입니다. 

 

매번 주어진 회로에 대해서 QFT 게이트를 만드는 것은 번거롭기 때문에, Qiskit 에서는 아래와 같이 QFT를 라이브러리로 제공합니다:

from qiskit.circuit.library import QFT

 

회로에 대한 함수는 QFT(num_qubits=큐빗수, do_swaps=스왑여부)와 같은 변수를 입력하여 작동됩니다. QFT를 정의하였다면, 아래와 같은 명령어로 양자 회로에 추가만 해주면 됩니다:

qft = QFT(num_qubits=2, do_swaps=True) 
qc.append(qft, range(2))

 

 

위에 예제에서는 이해를 돕기 위해 기존에 주어진 게이트들로 직접 QFT를 구성해 봤지만, Qiskit에서 제공하는 QFT 라이브러리를 통해 양자 푸리에 변환을 보다 쉽게 적용할 수 있습니다. 아래는 라이브러리 함수를 이용한 QFT 코드에 대한 예제입니다: 

from qiskit import QuantumCircuit, transpile
from qiskit.circuit.library import QFT
from qiskit.visualization import plot_bloch_multivector, plot_histogram
from qiskit_aer import AerSimulator
import matplotlib.pyplot as plt
import numpy as np

# 양자 회로 초기화
qc = QuantumCircuit(2)

# 초기 상태 설정: |j⟩ = |10⟩
qc.initialize([0, 0, 1, 0], [0, 1])  # 상태 |10⟩ 생성

# QFT 회로 추가
qft = QFT(num_qubits=2, do_swaps=True)  # 2큐비트 QFT 회로 생성
qc.append(qft, range(2))  # QFT 추가. *range(적용할 큐비트 범위)->[0,1]로 대체 가능.

qc.save_statevector()

# 회로 시각화
fig = qc.draw(output="mpl")  # Matplotlib 객체 반환
fig.savefig("quantum_circuit_qft.png", dpi=300)  # PNG로 저장

# 상태 확인
simulator = AerSimulator()
compiled_circuit = transpile(qc, simulator)
result = simulator.run(compiled_circuit).result()
statevector = result.get_statevector()

# 측정 추가 및 결과 확인
qc.measure_all()
compiled_circuit = transpile(qc, simulator)
result = simulator.run(compiled_circuit, shots=1024).result()
counts = result.get_counts()

print("Measurement Results:", counts)
fig2 = plot_histogram(counts)
fig2.savefig("histogram_qft.png", dpi=300)  # PNG로 저장
#plt.show()

 

위에 예제와 코드의 차이점을 살펴 보시고, 출력되는 결과를 비교해 보시기 바랍니다. 더 나아가, 큐비트 수를 늘려서 양자 푸리에 변환을 적용해 보시기 바랍니다!

 

출력 결과

2큐비트 상태에 양자 푸리에 변환을 적용한 회로를 1024번 시뮬레이션 한 후, 측정한 결과.

 

QFT 라이브러리를 사용했을 때의 양자 회로도

 

반응형