1、2、3量子ビットの計算の基礎の基礎、N量子ビットの基礎


はじめに

できる人はどんどん書いていきますが、最初から学びたいという時にあまり見るような教材がない気がします。初歩の初歩で、1量子ビットの表現を見返して見ます。


状態ベクトル

量子ビットは状態ベクトルというベクトルで表され、初期化は一般的に∣0⟩という形に。

q = \left[ 

\begin{array}{r}
1\\
0
\end{array}
\right]

こっからスタートです。


ゲート

量子ビットに演算を仕掛けるにはゲート操作が必要です。ゲートはいろんな種類がありますが、まずは基本のパウリ演算子X,Y,Zを見て見ると、

X = \left[ 

\begin{array}{rr}
0&1\\
1&0
\end{array}
\right],
Y = \left[
\begin{array}{rr}
0&-i\\
i&0
\end{array}
\right],
Z = \left[
\begin{array}{rr}
1&0\\
0&-1
\end{array}
\right]

ここら辺がありますが、これを行列計算することでゲートの演算と同じ答えが取り出せます。

Xq = \left[ 

\begin{array}{rr}
0&1\\
1&0
\end{array}
\right]
\left[
\begin{array}{r}
1\\
0
\end{array}
\right] =
\left[
\begin{array}{r}
0\\
1
\end{array}
\right]

Yq = \left[ 

\begin{array}{rr}
0&-i\\
i&0
\end{array}
\right]
\left[
\begin{array}{r}
1\\
0
\end{array}
\right] =
\left[
\begin{array}{r}
0\\
i
\end{array}
\right]

Zq = \left[ 

\begin{array}{rr}
1&0\\
0&-1
\end{array}
\right]
\left[
\begin{array}{r}
1\\
0
\end{array}
\right] =
\left[
\begin{array}{r}
1\\
0
\end{array}
\right]

簡単なコードで書いて見ると、

q = np.array([1,0])

X = np.array([[0,1],[1,0]])
Y = np.array([[0,-1j],[1j,0]]) #複素数表現a+bj
Z = np.array([[1,0],[0,-1]])

print(X@q) # => [0 1]
print(Y@q) # => [0.+0.j 0.+1.j]
print(Z@q) # => [1 0]

print(Z@X@q) # => [ 0 -1]

連続して行うこともできました。重ね合わせを作り出す有名なアダマール行列もあります。

python

H = \frac{1}{\sqrt{2}}

\left[

\begin{array}{rr}

1&1\\

1&-1

\end{array}

\right]

`

こちらも計算では、

Hq = \frac{1}{\sqrt{2}} 

\left[
\begin{array}{rr}
1&1\\
1&-1
\end{array}
\right]
\left[
\begin{array}{r}
1\\
0
\end{array}
\right]=
\left[
\begin{array}{r}
1/\sqrt{2}\\
1/\sqrt{2}
\end{array}
\right]

q = np.array([1,0])

H = np.array([[1/np.sqrt(2),1/np.sqrt(2)],[1/np.sqrt(2),-1/np.sqrt(2)]])

print(H@q) # => [0.70710678 0.70710678]


答えの取り出し方

通常は上記のような状態ベクトルでは答えは取り出せません。答えは確率計算で取り出されます。

def m(qq):

if np.abs(qq[0])*np.abs(qq[0]) > np.random.rand():
print(np.array([1,0]))
else:
print(np.array([0,1]))

m(H@H@q) # => [1,0]


2量子ビットの状態ベクトル

量子ビットは状態ベクトルと呼ばれるもので表現されます。初期化された状態ベクトルは∣0⟩からスタートします。今回は2量子ビットあるので、2量子ビットの状態ベクトルを作りますが、これはテンソル積と呼ばれる計算で表現されます。テンソル積も覚えてしまった方が早いです。

q_1 \otimes q_2 = 

\left[
\begin{array}{r}
1\\
0
\end{array}
\right]
\otimes
\left[
\begin{array}{r}
1\\
0
\end{array}
\right]
=
\left[
\begin{array}{r}
1\\
0\\
0\\
0\\
\end{array}
\right]


2量子ビットゲート

有名なものにCNOTゲートがあります。

CNOT = 

\left[
\begin{array}{rrrr}
1&0&0&0\\
0&1&0&0\\
0&0&0&1\\
0&0&1&0\\
\end{array}
\right]

こちらも上記の状態ベクトルに行列計算をすればいいのですが、

CNOT q_1\otimes q_2 = 

\left[
\begin{array}{rrrr}
1&0&0&0\\
0&1&0&0\\
0&0&0&1\\
0&0&1&0\\
\end{array}
\right]
\left[
\begin{array}{r}
1\\
0\\
0\\
0\\
\end{array}
\right]
=
\left[
\begin{array}{r}
1\\
0\\
0\\
0\\
\end{array}
\right]

この場合には変化がありません。変化がないと学べないので、上記の状態ベクトルを全パターン見てみます。q1やq2が途中の計算で状態ベクトルが個別に変わったとして、まずそれぞれの状態ベクトルは、

\left[ 

\begin{array}{r}
1\\
0
\end{array}
\right]
\otimes
\left[
\begin{array}{r}
0\\
1
\end{array}
\right]
=
\left[
\begin{array}{r}
0\\
1\\
0\\
0\\
\end{array}
\right]

\left[ 

\begin{array}{r}
0\\
1
\end{array}
\right]
\otimes
\left[
\begin{array}{r}
1\\
0
\end{array}
\right]
=
\left[
\begin{array}{r}
0\\
0\\
1\\
0\\
\end{array}
\right]

\left[ 

\begin{array}{r}
0\\
1
\end{array}
\right]
\otimes
\left[
\begin{array}{r}
0\\
1
\end{array}
\right]
=
\left[
\begin{array}{r}
0\\
0\\
0\\
1\\
\end{array}
\right]

そしてCNOTを適用して、

\left[ 

\begin{array}{rrrr}
1&0&0&0\\
0&1&0&0\\
0&0&0&1\\
0&0&1&0\\
\end{array}
\right]
\left[
\begin{array}{r}
0\\
1\\
0\\
0\\
\end{array}
\right]
=
\left[
\begin{array}{r}
0\\
1\\
0\\
0\\
\end{array}
\right]

\left[ 

\begin{array}{rrrr}
1&0&0&0\\
0&1&0&0\\
0&0&0&1\\
0&0&1&0\\
\end{array}
\right]
\left[
\begin{array}{r}
0\\
0\\
1\\
0\\
\end{array}
\right]
=
\left[
\begin{array}{r}
0\\
0\\
0\\
1\\
\end{array}
\right]

\left[ 

\begin{array}{rrrr}
1&0&0&0\\
0&1&0&0\\
0&0&0&1\\
0&0&1&0\\
\end{array}
\right]
\left[
\begin{array}{r}
0\\
0\\
0\\
1\\
\end{array}
\right]
=
\left[
\begin{array}{r}
0\\
0\\
1\\
0\\
\end{array}
\right]

下の2つの場合には状態ベクトルが変化しました。

まず状態ベクトルをpythonで書いて見ると綺麗に計算できました。

import numpy as np

np.kron([1,0],[1,0]) # => array([1, 0, 0, 0])
np.kron([1,0],[0,1]) # => array([0, 1, 0, 0])
np.kron([0,1],[1,0]) # => array([0, 0, 1, 0])
np.kron([0,1],[0,1]) # => array([0, 0, 0, 1])

さらにCNOTまで計算して見ると、

CNOT = [[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]]

print(CNOT@np.kron([1,0],[1,0])) # => [1 0 0 0]
print(CNOT@np.kron([1,0],[0,1])) # => [0 1 0 0]
print(CNOT@np.kron([0,1],[1,0])) # => [0 0 0 1]
print(CNOT@np.kron([0,1],[0,1])) # => [0 0 1 0]

きちんと確認できました。


3量子ビットの状態ベクトル

最初に用意される状態ベクトルはテンソル積と呼ばれる計算で準備できます。

q_1 \otimes q_2 \otimes q_3 = 

\left[
\begin{array}{r}
1\\
0
\end{array}
\right]
\otimes
\left[
\begin{array}{r}
1\\
0
\end{array}
\right]
\otimes
\left[
\begin{array}{r}
1\\
0
\end{array}
\right]
=
\left[
\begin{array}{r}
1\\
0\\
0\\
0\\
0\\
0\\
0\\
0\\
\end{array}
\right]


3量子ビットのゲート計算

3量子ビットのゲートなども回路に登場しますが、普通は2量子ビットまでの相互作用が普通です。ここでは、とりあえずXゲートやCNOTを活用したゲート回路をシミュレーションしてみます。

スクリーンショット 2019-05-26 10.07.35.png

一番上と三番目の量子ビットにXゲート、二番目と一番目の量子ビットにCNOTゲートをかけてみます。普通量子ビットの初期化は∣0⟩なので、上で求めた状態からスタートです。計算は下記のようになります。

CNOT_{12} \cdot X_3 \cdot X_1 

\left[
\begin{array}{r}
1\\
0\\
0\\
0\\
0\\
0\\
0\\
0\\
\end{array}
\right]

1番目にXゲート、3番目にXゲート、1番目と2番目にCNOTゲートをかけます。ここで、かける行列は、足りない部分に単位行列Iを補完して、

CNOT_{12} \otimes I \cdot X_1 \otimes I \otimes X_3 \cdot 

\left[
\begin{array}{r}
1\\
0\\
0\\
0\\
0\\
0\\
0\\
0\\
\end{array}
\right]
=
\left[
\begin{array}{r}
0\\
0\\
0\\
0\\
0\\
0\\
0\\
1\\
\end{array}
\right]

pythonでかくと、

import numpy as np

CNOT = [[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]]
X = [[0,1],[1,0]]
q = [1,0]
I = np.eye(2)

np.kron(CNOT,I)@np.kron(X,np.kron(I,X))@np.kron(q,np.kron(q,q))
# => array([0., 0., 0., 0., 0., 0., 0., 1.])

ここで、最後に出てきた

[0,0,0,0,0,0,0,1]

ですが、こちらを量子ビットに変換します。これは簡単に表現できます。一番左を0として二進法で表現されていますので、1が立っているところが”7″になるので、

bin(7) #=> 111

十進数の”7″を二進数で表現し直すと”111″となるので、こたえは全ての量子ビットが1となります。


つぎに

上記を一般化したN量子ビットを見てみます。


状態ベクトル

量子ビットは状態ベクトルと呼ばれる$2^N$の状態をもっていますが、それはテンソル積と呼ばれる計算で簡単に求めることができます。量子ビットの状態は0のときは[1,0],1のときは[0,1]と表現されます。量子コンピュータでは大体最初はすべて0で初期化されます。

2量子ビットの時は、

import numpy as np

q_0 = [1,0]
q_1 = [1,0]
print(np.kron(q_0,q_1))

#=>[1 0 0 0]

これをN量子ビットで行う際には、上記のテンソル積をN回行えばいいことになります。

def wf(lis,n=0,e=[]):

if n==0:
e = lis[0]
if n==len(lis)-1:
return e
else:
e = np.kron(e,lis[n+1])
return wf(lis,n+1,e)

wf([[1,0] for i in range(3)])
# => array([1, 0, 0, 0, 0, 0, 0, 0])

これが[1,0]の3量子ビットで作られる状態ベクトルです。配列が$2^3=8$要素あります。

これで最初の準備が完了です。


量子ゲート

あとはこれに順番に量子ゲートをかけていきます。量子ゲートの準備も上記のテンソル積を使います。

例題として下記のような回路があった場合、

59c1ff0f-26da-5f02-c33f-b7ba728b3519.png

回路は縦に時間ごとに分割されて準備されます。

CNOT = [[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]]

X = [[0,1],[1,0]]
I = np.eye(2)
A = [[1,0] for i in range(4)]

wf([X,I,X,I])@wf(A)

# => array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.])

bin(10)
# => '0b1010'

計算は簡単です。上記はCNOT,X,Iは決まった行列です。CNOTとXは量子ゲートと呼ばれてそれぞれ上記の行列の形をとります。Iは単位行列で、ゲート操作のないところを埋める役割をします。

Aは初期化された4量子ビットの状態ベクトルです。

wf([X,I,X,I])@wf(A)は、用意された状態ベクトルにX,I,X,Iという最初のゲートの組みをまとめてかけています。出た答えは1が0から数えて10番目にあるので、bin(10)で0b1010というように、二進数に戻しています。それによって、1010という答えが取り出せました。

続けて計算すると、

wf([CNOT,I,X])@wf([X,I,X,I])@wf(A)

#=>array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1.])

bin(15)
#=> '0b1111'

全部1が取り出せました。最後の列は、

wf([X,I,X,X])@wf([CNOT,I,X])@wf([X,I,X,I])@wf(A)

#=>array([0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

bin(4)
#=>'0b0100'

これは0100できちんとXゲートでひっくり返っていることが確認できました。N量子ビットでも同様です。

以上です。