こんにちは、テンソルネットがわかりづらいという質問を結構インターンでもらいましたので、ちょっとやってみます。
1、まずは普通の量子もつれ回路を作ってみます。
量子もつれ回路はH-CXで作れます。Cは制御ビットを表しています。
q- --H-- --C--
|
q- ----- --X--
今回はGoogleのtensornetworkライブラリを使いました。もちろんcuTensorNetでもいいです。
!pip install tensornetwork
まずはツールを読み込み、初期状態の状態ベクトルを準備します。HゲートとCXゲートを設定します。CXゲートは4*4ではなく、2*2*2*2のように腕の数を行列からrank4のテンソルに変更しておきます。
import numpy as np
import tensornetwork as tn
#量子ビットを準備
psi0 = tn.Node(np.array([1,0]))
psi1 = tn.Node(np.array([1,0]))
#Hゲートを準備
H = tn.Node(np.array([[1,1],[1,-1]])/np.sqrt(2))
CX = tn.Node(np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]]).reshape(2,2,2,2))
次にエッジを作ります。psi0の量子状態はまずHと繋いでからCXに。psi1の方はCXに直接繋ぎます。
繋いだら縮約を実行し、最終的に得られたノードをテンソルとして取り出します。
最終的なテンソルは状態ベクトルの形になってるはずなので、ベクトルの形に変更をします。
edge1 = psi0[0] ^ H[0]
edge2 = H[1] ^ CX[0]
edge3 = psi1[0] ^ CX[1]
con1 = tn.contract(edge1)
con2 = tn.contract(edge2)
con3 = tn.contract(edge3)
print(con3.tensor.reshape(4))
そうすると、
[0.70710678 0. 0. 0.70710678]
きちんと|00>と|11>状態の確率振幅が得られました。
2、次にCNOTゲートの仕組みをテンソルで確認
次にCNOTゲートの仕組みをテンソルで確認します。制御ビットとターゲットビットの仕組みをテンソル縮約で確認します。
--C--
|
--X--
上記のようにCを制御ビット、Xをターゲットビットとします。CXゲートは条件付きゲートですから、制御ビット側が0の時にはターゲットビットは何もせず、制御ビット側が1の時にはターゲットビットはXの操作をします。
今回は制御側の動きがどうターゲットビット側のテンソルに影響するのかみてみます。下記のように状態を置きます。制御側のビットは|0>を採用します。測定はもちろん|0>が得られるはずです。
#量子ビットと測定を準備
psi0 = tn.Node(np.array([1,0]))
m0 = tn.Node(np.array([1,0]))
#CXゲートを準備
CX = tn.Node(np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]]).reshape(2,2,2,2))
このように表現しました。
|0>--C-- M=|0>
|
--X--
エッジを作成し縮約します。
edge1 = psi0[0] ^ CX[0]
edge2 = m0[0] ^ CX[2]
con1 = tn.contract(edge1)
con2 = tn.contract(edge2)
制御ビット側の入力(初期量子状態)と出力側(測定結果の量子状態)を縮約した結果、このテンソルは行列になります。
--U--
縮約結果のノードは行列になっているはずです。確認します。
print(con2.tensor)
[[1 0]
[0 1]]
単位行列になりました。制御側のビットの値が|0>の場合には、ターゲットビット側は単位行列が適用されています。次に制御側を|1>にしてみます。
#量子ビットを準備
psi0 = tn.Node(np.array([0,1]))
m0 = tn.Node(np.array([0,1]))
#CXゲートを準備
CX = tn.Node(np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]]).reshape(2,2,2,2))
初期状態と測定結果を変更した以外は同じです。
edge1 = psi0[0] ^ CX[0]
edge2 = m0[0] ^ CX[2]
con1 = tn.contract(edge1)
con2 = tn.contract(edge2)
print(con2.tensor)
計算結果は、
[[0 1]
[1 0]]
Xゲートとなりました。制御ビットの値によって行列が変化するのがわかりました。最後に制御側の入力量子状態を任意の量子状態とします。
import sympy as sym
a, b = sym.symbols('a b')
#量子ビットを準備
psi1 = tn.Node(np.array([a,b]))
m1 = tn.Node(np.array([a,b]))
#CXゲートを準備
CX = tn.Node(np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]]).reshape(2,2,2,2))
量子回路自体は先ほどと同じです。初期の量子状態を確率振幅[a,b]と設定しました。
edge1 = psi1[0] ^ CX[0]
edge2 = m1[0] ^ CX[2]
con1 = tn.contract(edge1)
con2 = tn.contract(edge2)
print(con2.tensor)
計算結果は、
[[a**2 b**2]
[b**2 a**2]]
こうなりました。確かに制御側の量子ビットの量子状態によってターゲット側の行列が変化していますね。複数のマルチコントロールドゲートでも同様の計算結果が得られると思いますので、ぜひ試してみてください。