다중 큐비트와 얽힘 - 다중 큐비트와 얽힌 상태

Pt J·2020년 11월 18일
0

[斷] QISKit

목록 보기
9/11
post-thumbnail

이 포스트의 내용은 Qiskit Textbook | Multiple Qubits and Entanglement - Multiple Qubits and Entangled States을 통해 공부한 흔적임을 밝힙니다.

지난시간까지 단일 큐비트와 단일 큐비트 연산에 대해 알아보았다.
하지만 단일 큐비트는 개별적으로는 계산 상의 이점을 제공하지 않으며
양자 컴퓨터의 진정한 힘은 큐비트 사이의 상호작용에서 나온다.
그런 의미에서 다중 큐비트에 대해 이야기하도록 하겠다.

다중 큐비트 상태 표현

하나의 비트는 두 가지 가능한 상태를 가지고 있고
하나의 큐비트 상태에는 두 개의 복소수 진폭이 존재한다.
마찬가지로 두 개의 비트는 네 개의 가능한 상태를 가지고 있으며
두 개의 큐비트 상태는 네 개의 복소수 진폭을 가진다.

그것은 다음과 같은 4차원 벡터에 표현할 수 있다.

a=a0000+a0101+a1010+a1111=[a00a01a10a11]|a\rangle = a_{00}|00\rangle + a_{01}|01\rangle + a_{10}|10\rangle + a_{11}|11\rangle = \begin{bmatrix}a_{00}\\a_{01}\\a_{10}\\a_{11}\end{bmatrix}

확률을 구하는 방식도 단일 큐비트와 다르지 않다.

p(00)=00a2=a002p(|00\rangle) = | \langle00|a\rangle|^2 = |a_{00}|^2
a002+a012+a102+a112=1|a_{00}|^2 + |a_{01}|^2 + |a_{10}|^2 + |a_{11}|^2 = 1

다중 큐비트의 상태는 큐비트 간의 텐서곱을 통해 나타낼 수 있다.

예를 들어, a=[a0a1]|a\rangle = \begin{bmatrix}a_0\\a_1\end{bmatrix}b=[b0b1]|b\rangle = \begin{bmatrix}b_0\\b_1\end{bmatrix}에 대하여

ba=ba=[b0×[a0a1]b1×[a0a1]]=[b0a0b0a1b1a0b1a1]|ba\rangle = |b\rangle \otimes |a\rangle = \begin{bmatrix}b_0 \times \begin{bmatrix}a_0\\a_1\end{bmatrix}\\ b_1 \times \begin{bmatrix}a_0\\a_1\end{bmatrix}\end{bmatrix} = \begin{bmatrix}b_0a_0\\b_0a_1\\b_1a_0\\b_1a_1\end{bmatrix}

그리고 마찬가지로 3개 혹은 그 이상의 큐비트에 대해서도 텐서곱으로 상태벡터를 구할 수 있다.

cba=[c0b0a0c0b0a1c0b1a0c0b1a1c1b0a0c1b0a1c1b1a0c1b1a1]|cba\rangle = \begin{bmatrix}c_0b_0a_0\\c_0b_0a_1\\c_0b_1a_0\\c_0b_1a_1\\c_1b_0a_0\\c_1b_0a_1\\c_1b_1a_0\\c_1b_1a_1\end{bmatrix}

nn개의 큐비트가 있다면 그 상태벡터는 2n2^n개의 복소수 진폭으로 나타난다.
이를 통해 큐비트가 증가할수록 상태벡터가 기하급수적으로 커진다는 것을 알 수 있다.

이를 코드 상에서 작성해보자.
먼저 필요한 패키지들을 준비한다.

from qiskit import *
from math import pi
import numpy as np
from qiskit.visualization import plot_bloch_multivector, plot_histogram

그리고 세 개의 큐비트를 준비하고 H 게이트를 각각 적용한다.

qc = QuantumCircuit(3)

for qubit in range(3):
    qc.h(qubit)

qc.draw()

H 게이트로 인해 각각의 큐비트는 +|+\rangle 상태가 되었을 것이므로
다음과 같은 상태벡터가 나타날 것을 예측할 수 있다.

+++=18[11111111]|+++\rangle = {1\over\sqrt8}\begin{bmatrix}1\\1\\1\\1\\1\\1\\1\\1\end{bmatrix}

실제로 실행해보면,

backend = Aer.get_backend('statevector_simulator')
final_state = execute(qc,backend).result().get_statevector()

from qiskit_textbook.tools import array_to_latex
array_to_latex(final_state, pretext="\\text{Statevector} = ")
Statevector=[1818181818181818]\text{Statevector} = \begin{bmatrix}{1\over\sqrt8}\\{1\over\sqrt8}\\{1\over\sqrt8}\\{1\over\sqrt8}\\{1\over\sqrt8}\\{1\over\sqrt8}\\{1\over\sqrt8}\\{1\over\sqrt8}\end{bmatrix}

다중 큐비트 상태 벡터에 단일 큐비트 게이트 적용

지금까지 다뤘던 게이트들은 단일 큐비트 게이트다.
예를 들어 X 게이트는 다음과 같이 표현되며

X=[0110]X = \begin{bmatrix}0&1\\1&0\end{bmatrix}

0|0\rangle에 적용하면 다음과 같다.

X0=[0110][10]=[01]X|0\rangle = \begin{bmatrix}0&1\\1&0\end{bmatrix}\begin{bmatrix}1\\0\end{bmatrix} = \begin{bmatrix}0\\1\end{bmatrix}

그런데 다중 큐비트 벡터의 한 큐비트에 대해 X 게이트를 적용하려면 어떻게 해야 될까?

다중 큐비트 상태벡터를 구할 때 텐서곱을 사용했던 것처럼
그 상태벡터에 작동하는 행렬을 구하기 위해서 텐서곱을 사용한다.

예를 들어, 다음과 같은 코드가 있다고 하자.

qc = QuantumCircuit(2)
qc.h(0)
qc.x(1)
qc.draw()

상태벡터의 연산은 다음과 같이 이루어진다.

Xq1Hq0=(XH)q1q0X|q_1\rangle\otimes H|q_0\rangle = (X\otimes H)|q_1q_0\rangle
XH=[0110]12[1111]=12[0×[1111]1×[1111]1×[1111]0×[1111]] =12[0011001111001100]=[0HH0]X\otimes H = \begin{bmatrix}0&1\\1&0\end{bmatrix}\otimes{1\over\sqrt2}\begin{bmatrix}1&1\\1&-1\end{bmatrix} = {1\over\sqrt2}\begin{bmatrix}0\times\begin{bmatrix}1&1\\1&-1\end{bmatrix}&1\times\begin{bmatrix}1&1\\1&-1\end{bmatrix}\\1\times\begin{bmatrix}1&1\\1&-1\end{bmatrix}&0\times\begin{bmatrix}1&1\\1&-1\end{bmatrix}\end{bmatrix}\\\ \\ = {1\over\sqrt2}\begin{bmatrix}0&0&1&1\\0&0&1&-1\\1&1&0&0\\1&-1&0&0\end{bmatrix} = \begin{bmatrix}0&H\\H&0\end{bmatrix}

따라서 XH=[0HH0]X \otimes H = \begin{bmatrix}0&H\\H&0\end{bmatrix}q1q0|q_1q_0\rangle에 적용한 것과 같다.

이를 직접 계산하지 않아도 회로의 모든 연산을 합쳐 하나의 행렬로 만들어주는
unitary_simulator를 사용하면 XHX \otimes H의 행렬을 구할 수 있다.

backend = Aer.get_backend('unitary_simulator')
unitary = execute(qc,backend).result().get_unitary()

from qiskit_textbook.tools import array_to_latex
array_to_latex(unitary, pretext="\\text{Circuit = }\n")
Circuit=[001212001212121200121200]\text{Circuit}=\begin{bmatrix}0&0&{1\over\sqrt2}&{1\over\sqrt2}\\0&0&{1\over\sqrt2}&-{1\over\sqrt2}\\{1\over\sqrt2}&{1\over\sqrt2}&0&0\\{1\over\sqrt2}&-{1\over\sqrt2}&0&0\end{bmatrix}

만약 한 번에 하나의 큐비트만 게이트를 적용하고자 한다면
다른 큐비트에 I 게이트를 적용하는 것으로 텐서곱을 수행하면 된다.

XIX\otimes I

코드 상으로는 다음과 같이 작성되며

qc = QuantumCircuit(2)
qc.x(1)
qc.draw()

실행해보면,

backend = Aer.get_backend('unitary_simulator')
unitary = execute(qc,backend).result().get_unitary()

array_to_latex(unitary, pretext="\\text{Circuit = } ")
Circuit=[0010000110000100]\text{Circuit}=\begin{bmatrix}0&0&1&0\\0&0&0&1\\1&0&0&0\\0&1&0&0\end{bmatrix}

XI=[0II0]X\otimes I = \begin{bmatrix}0 & I \\ I & 0\end{bmatrix}임을 확인할 수 있다.

다중 큐비트 게이트

다중 큐비트 벡터에 단일 큐비트 게이트를 적용할 수도 있지만
다중 큐비트 게이트를 이용하여 그 상호작용을 회로에 적용할 수도 있다.

중요하게 사용되는 대표적인 2-큐비트 게이트로는 CNOT 게이트가 있다.

CNOT 게이트

CNOT 게이트가 적용되는 두 개의 큐비트를 각각 컨트롤과 타겟이라고 하며,
컨트롤이 1|1\rangle인 경우에만 타겟에 X 게이트가 적용된다.

코드로 작성하면 담음과 같다.

qc = QuantumCircuit(2)
qc.cx(0, 1)
qc.draw()

cx 메서드에서 첫번째 인자가 컨트롤, 두번째 인자가 타겟이다.

큐비트들이 중첩 상태에 있지 않고 0|0\rangle 또는 1|1\rangle의 값을 가질 때
CNOT 게이트의 진리표는 다음과 같다.

Input (t, c)Output (t, c)
0000
0111
1010
1101

이는 다음과 같은 두 개의 행렬 중 하나를 통해 이루어진다.

CNOT=[1000000100100100]CNOT=[1000010000010010]\text{CNOT} = \begin{bmatrix}1&0&0&0\\0&0&0&1\\0&0&1&0\\0&1&0&0\end{bmatrix} \quad \text{CNOT} = \begin{bmatrix}1&0&0&0\\0&1&0&0\\0&0&0&1\\0&0&1&0\end{bmatrix}

둘 중 무엇이 사용되는지는 컨트롤과 타겟의 순서에 달려 있다.

q0q_0가 컨트롤, q1q_1가 타겟인 상황에는 윗쪽 CNOT 행렬이 적용된다.

a=[a00a01a10a11]CNOTa=[a00a11a10a01]|a\rangle = \begin{bmatrix}a_{00}\\a_{01}\\a_{10}\\a_{11}\end{bmatrix} \quad \text{CNOT}|a\rangle = \begin{bmatrix}a_{00}\\a_{11}\\a_{10}\\a_{01}\end{bmatrix}\begin{matrix}\\\leftarrow\\\\\leftarrow\end{matrix}

01|01\rangle11|11\rangle의 위상이 뒤바뀐 것을 확인할 수 있다.

중첩된 큐비트의 CNOT

CNOT 게이트에서 컨트롤이 0|0\rangle이면 타겟은 변화가 없고
컨트롤이 1|1\rangle이면 타겟이 반전된다.

그런데 컨트롤이 중첩된 +|+\rangle이라면 타겟은 어떻게 동작할까?
코드를 직접 실행해봄으로써 이를 알아보자.

qc = QuantumCircuit(2)
qc.h(0)
qc.draw()

초기 상태의 상태벡터를 확인해보면,

backend = Aer.get_backend('statevector_simulator')
final_state = execute(qc,backend).result().get_statevector()
array_to_latex(final_state, pretext="\\text{Statevector = }")
Statevector=[121200]\text{Statevector}=\begin{bmatrix}{1\over\sqrt2}\\{1\over\sqrt2}\\0\\0\end{bmatrix}

계산을 통해서도 같은 결과를 확인할 수 있다.

0+=0+=12(00+01)|0\rangle\otimes|+\rangle=|0+\rangle={1\over\sqrt2}(|00\rangle+|01\rangle)

이제 CNOT 게이트를 적용해보자.

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.draw()

상태벡터를 확인해보면,

backend = Aer.get_backend('statevector_simulator')
final_state = execute(qc,backend).result().get_statevector()
array_to_latex(final_state, pretext="\\text{Statevector = }")
Statevector=[120012]\text{Statevector}=\begin{bmatrix}{1\over\sqrt2}\\0\\0\\{1\over\sqrt2}\end{bmatrix}

CNOT0+=0+=12(00+11)\text{CNOT}|0\rangle\otimes|+\rangle=|0+\rangle={1\over\sqrt2}(|00\rangle+|11\rangle) 라는 결과를 확인할 수 있다.

얽힌 상태

절반의 확률로 00|00\rangle, 또 절반의 확률로 11|11\rangle로 나타나는 상태를 보았다.
이것은 절대 01|01\rangle이나 10|10\rangle로 측정되지 않는다.
이와 같이 두 큐비트가 얽혀 있어 하나의 값이 정해지면 다른 것도 정해지는 상태를 벨Bell 상태라고 한다.

results = execute(qc, backend).result().get_counts()
plot_histogram(results)

이렇게 결합된 상태는 두 개의 개별적인 큐비트 상태로 작성할 수 없으며 이는 흥미로운 의미를 갖는다.
두 큐비트는 중첩된 채 하나를 측정하면 다른 하나도 함께 붕괴된다.
예를 들어, 위에 있는 큐비트가 1|1\rangle로 측정되면 아래의 큐비트를 측정하지 않아도 다음과 같이 결정된다.

12(00+11)measure11{1\over\sqrt2}(|00\rangle+|11\rangle)\overset\text{measure}\longrightarrow|11\rangle

심지어 광년 단위의 먼 거리에 분리하더라도 한 큐비트가 측정되면 다른 하나도 즉각적으로 붕괴되는 것으로 보인다.

다음과 같은 코드를 사용하면 12(01+10){1\over\sqrt2}(|01\rangle+|10\rangle)의 상태를 가진 벨 상태도 만들 수 있다.

qc = QuantumCircuit(2)
qc.x(1)
qc.h(0)
qc.cx(0, 1)

backend = Aer.get_backend('statevector_simulator')
final_state = execute(qc,backend).result().get_statevector()
array_to_latex(final_state, pretext="\\text{Statevector = }")
Statevector=[012120]\text{Statevector}=\begin{bmatrix}0\\{1\over\sqrt2}\\{1\over\sqrt2}\\0\end{bmatrix}
profile
Peter J Online Space - since July 2020

0개의 댓글