量子ビットをイメージでつかむ（番外編）

　[一般社団法人日本量子コンピューティング協会](https://www.jqca.org/)様が新たに量子エンジニア（ゲート式）アドバンスコースとして第１回の[教材](https://docs.google.com/presentation/d/1ULAlAaLP0PsSZYlGn3wOdnWTLxvScs00g_RIqk75ON4/edit#slide=id.g2eca391dad4_0_1)を上げていただいているので、勝手ながら解説してみます。例によってJupyter notebookを https://github.com/sin-gee/ImageOfQuantumBit/blob/main/ImageOfGrover2.ipynb に置いておきますのでよろしければ試してみてください。
　さて、今回対象にする量子回路は[教材](https://docs.google.com/presentation/d/1ULAlAaLP0PsSZYlGn3wOdnWTLxvScs00g_RIqk75ON4/edit#slide=id.g2eca391dad4_0_1)の30頁を確認いただきたいですが、最初のバリアの左側と、（面積的にはかなりを占める）中央と、拡散行列と記している右側に分かれています。実は基本構造は[こちら](https://qiita.com/sin-gee/items/f17d6d6e4585cec11323)で解説したものと同じです。
 　左側、アダマール部分の解説は飛ばして、中央部分のゴチャゴチャしたところから解説します。この部分、エントリーコースの「バイトのシフト問題」の流用です。A,B,Cさんが以下のシフトを希望していて、どう組めばいいでしょう？という問題でした。
 |  |朝 |昼 |夜 |
 |---|---|---|---|
 |Aさん|〇|||
 |Bさん||〇|〇|
 |Cさん|〇||〇|

　でいささか不条理とも思える設定ですが、シフトを分け合うことはできません。例えばBさんの希望を叶えるとBさんは昼と夜両方のシフトに入ります。昼はBさん、夜はCさん．．．ということはできないです。
　エントリーコースではZゲートより左までで測定する設定でした。Answerビットが１である場合の、A,B,Cのビットが１であるのものが答えでした。（AさんとBさんがシフトに入ります。）
 ***
　エントリーコース以降に追加された右側を見ていきます。
　Zゲートの意味については[こちら](https://qiita.com/sin-gee/items/f17d6d6e4585cec11323)を見ていただくとして、そこから右側が何をしているかを見ていきます。これは位相キックバックと呼ばれるものです。この動作を見るため、Zゲートの左側CCXと右側CCXの作用を取り出してみてみます。
  最初にCCXを作用する前の状態です。一番左がAnswerビットに相当します。数値の横バーは振幅が無いことを示します（つまりAnswerビットは0）。
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3921213/a67bf14e-6d03-4ad4-8106-241c177f72a0.png)
　CCXを作用させます。下位2ビットとも1の場合にAnswerが1になります（右端の状態）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3921213/c51b4565-e055-422b-8a07-09e04003b5fa.png)
　ここでAnswerにZゲートを作用させます。|1xx>の位相（右の４つ）を反転させますが、振幅があるのは|111>のみですので、これが反転します（マイナスになります）。
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3921213/f9d259b0-e5ed-47da-bada-9b00e3aefaf7.png)
　再びCCXを作用させます。Answerビットは0となりますが、下位2ビットが両方1の場合(|11>)に位相が反転しています（マイナスになっています）。
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3921213/802c6937-1612-4701-809b-293b4736b7c6.png)
　これを順繰り行うことで、答えであるAさんとBさんのビットの位相のみを反転させる訳です。
***
　次に右側の拡散行列ですが、[こちら](https://qiita.com/sin-gee/items/f17d6d6e4585cec11323)とほぼ同じです。ただし、今回は量子ビットが3ビットになったのと、CZではなくCCXが使われていて前後のアダマールを挟まれている点が異なります。CZとH+CX+Hで同じ動きをするのでしょうか？結論から言えば同じです。
　まず、2ビットH+CX+H版はこうなります。
 ```python:

import numpy as np
import math
import matplotlib.patches as patches
def vector_to_vector_rotation_angle(v1, v2): #２ベクトルの角度をπ単位で算出
    return  np.arccos(np.inner(v1,v2)/(np.dot(np.linalg.norm(v1),np.linalg.norm(v2))))

dims = 2**2 # 2量子ビットのため次元数は2^2となる
x = (1/dims**0.5)
one = np.ones((dims,1))
sKet = x*one             # |s>を求める
sBra = np.transpose(sKet)# <s|を求める

HxH = np.array([[ 1, 1, 1, 1],
                [ 1,-1, 1,-1],
                [ 1, 1,-1,-1],
                [ 1,-1,-1, 1]])/2
XxX = np.array([[ 0, 0, 0, 1],
                [ 0, 0, 1, 0],
                [ 0, 1, 0, 0],
                [ 1, 0, 0, 0]])
CZ  = np.array([[ 1, 0, 0, 0],
                [ 0, 1, 0, 0],
                [ 0, 0, 1, 0],
                [ 0, 0, 0,-1]])
CX  = np.array([[ 1, 0, 0, 0],
                [ 0, 1, 0, 0],
                [ 0, 0, 0, 1],
                [ 0, 0, 1, 0]])
IH  = np.array([[ 1, 1, 0, 0],
                [ 1,-1, 0, 0],
                [ 0, 0, 1, 1],
                [ 0, 0, 1,-1]])/2**0.5

q00 = np.array([[1],[0],[0],[0]]) # Try any Vector

print('1回目のアダマール後')
G_1 = np.dot(HxH,q00)
print(G_1)

print('1回目のXゲート後')
G_2 = np.dot(XxX,G_1)
print(G_2)

print('CX前のHゲート後')
G_20 = np.dot(IH,G_2)
print(G_20)

print('CXゲート後')
G_3 = np.dot(CX,G_20)
print(G_3)

print('CX後のHゲート後')
G_30 = np.dot(IH,G_3)
print(G_30)

print('2回目のXゲート後')
G_4 = np.dot(XxX,G_30)
print(G_4)

print('2回目のHゲート後')
t00 = np.dot(HxH,G_4)
print('変換後のベクトル')
print(t00)

print('変換前と変換後（に原点で反転させた）のベクトルの間の角度は',round(vector_to_vector_rotation_angle( q00.reshape(-1),-t00.reshape(-1))/np.pi,3),'pi')
print('変換前のベクトルと|s>との間の角度は',                     round(vector_to_vector_rotation_angle( q00.reshape(-1),sKet.reshape(-1))/np.pi,3),'pi')
print('変換後（に原点で反転させた）ベクトルと|s>との間の角度は',   round(vector_to_vector_rotation_angle(sKet.reshape(-1),-t00.reshape(-1))/np.pi,3),'pi')

 ```

次に3ビットH+CCX+H版
```python:
import numpy as np
import math
import matplotlib.patches as patches
def vector_to_vector_rotation_angle(v1, v2): #２ベクトル間の角度をπ単位で算出
    return  np.arccos(np.inner(v1,v2)/(np.dot(np.linalg.norm(v1),np.linalg.norm(v2))))

dims = 2**3 # 3量子ビットのため次元数は2^3となる
x = (1/dims**0.5)
one = np.ones((dims,1))
sKet = x*one             # |s>を求める
sBra = np.transpose(sKet)# <s|を求める

HxHxH =np.array([[ 1, 1, 1, 1, 1, 1, 1, 1],
                 [ 1,-1, 1,-1, 1,-1, 1,-1],
                 [ 1, 1,-1,-1, 1, 1,-1,-1],
                 [ 1,-1,-1, 1, 1,-1,-1, 1],
                 [ 1, 1, 1, 1,-1,-1,-1,-1], 
                 [ 1,-1, 1,-1,-1, 1,-1, 1], 
                 [ 1, 1,-1,-1,-1,-1, 1, 1],
                 [ 1,-1,-1, 1,-1, 1, 1,-1]
                ])/2**1.5
XxXxX =np.array([[ 0, 0, 0, 0, 0, 0, 0, 1],
                 [ 0, 0, 0, 0, 0, 0, 1, 0],
                 [ 0, 0, 0, 0, 0, 1, 0, 0],
                 [ 0, 0, 0, 0, 1, 0, 0, 0],
                 [ 0, 0, 0, 1, 0, 0, 0, 0],
                 [ 0, 0, 1, 0, 0, 0, 0, 0],
                 [ 0, 1, 0, 0, 0, 0, 0, 0],
                 [ 1, 0, 0, 0, 0, 0, 0, 0]
                ])
CCX  = np.array([[ 1, 0, 0, 0, 0, 0, 0, 0],
                 [ 0, 1, 0, 0, 0, 0, 0, 0],
                 [ 0, 0, 1, 0, 0, 0, 0, 0],
                 [ 0, 0, 0, 1, 0, 0, 0, 0],
                 [ 0, 0, 0, 0, 1, 0, 0, 0],
                 [ 0, 0, 0, 0, 0, 1, 0, 0],
                 [ 0, 0, 0, 0, 0, 0, 0, 1],
                 [ 0, 0, 0, 0, 0, 0, 1, 0]
                ])
IIH  = np.array([[ 1, 1, 0, 0, 0, 0, 0, 0],
                 [ 1,-1, 0, 0, 0, 0, 0, 0],
                 [ 0, 0, 1, 1, 0, 0, 0, 0],
                 [ 0, 0, 1,-1, 0, 0, 0, 0],
                 [ 0, 0, 0, 0, 1, 1, 0, 0],
                 [ 0, 0, 0, 0, 1,-1, 0, 0],
                 [ 0, 0, 0, 0, 0, 0, 1, 1],
                 [ 0, 0, 0, 0, 0, 0, 1,-1]
                ])/2**0.5

q000 = np.array([[1],[0],[0],[0],[0],[0],[0],[0]]) # Try any Vector

print('1回目のアダマール後')
G_1 = np.dot(HxHxH,q000)
print(G_1)

print('1回目のXゲート後')
G_2 = np.dot(XxXxX,G_1)
print(G_2)

print('CCX前のHゲート後')
G_20 = np.dot(IIH,G_2)
print(G_20)

print('CCXゲート後')
G_3 = np.dot(CCX,G_20)
print(G_3)

print('CCX後のHゲート後')
G_30 = np.dot(IIH,G_3)
print(G_30)

print('2回目のXゲート後')
G_4 = np.dot(XxXxX,G_30)

print(G_4)
print('2回目のHゲート後')
t000 = np.dot(HxHxH,G_4)
print('変換後のベクトル')
print(t00)

print('変換前と変換後（に原点で反転させた）ベクトルの間の角度は',round(vector_to_vector_rotation_angle(q000.reshape(-1),-t000.reshape(-1))/np.pi,3),'pi')
print('変換前のベクトルと|s>との間の角度は',                   round(vector_to_vector_rotation_angle(q000.reshape(-1), sKet.reshape(-1))/np.pi,3),'pi')
print('変換後（に原点で反転させた）ベクトルと|s>との間の角度は', round(vector_to_vector_rotation_angle(sKet.reshape(-1),-t000.reshape(-1))/np.pi,3),'pi')
```

最後に3ビットCCZ版
```python:
import numpy as np
import math
import matplotlib.patches as patches
def vector_to_vector_rotation_angle(v1, v2): #２ベクトル間の角度をπ単位で算出
    return  np.arccos(np.inner(v1,v2)/(np.dot(np.linalg.norm(v1),np.linalg.norm(v2))))

dims = 2**3 # 3量子ビットのため次元数は2^3となる
x = (1/dims**0.5)
one = np.ones((dims,1))
sKet = x*one             # |s>を求める
sBra = np.transpose(sKet)# <s|を求める

HxHxH =np.array([[ 1, 1, 1, 1, 1, 1, 1, 1],
                 [ 1,-1, 1,-1, 1,-1, 1,-1],
                 [ 1, 1,-1,-1, 1, 1,-1,-1],
                 [ 1,-1,-1, 1, 1,-1,-1, 1],
                 [ 1, 1, 1, 1,-1,-1,-1,-1], 
                 [ 1,-1, 1,-1,-1, 1,-1, 1], 
                 [ 1, 1,-1,-1,-1,-1, 1, 1],
                 [ 1,-1,-1, 1,-1, 1, 1,-1]
                ])/2**1.5
XxXxX =np.array([[ 0, 0, 0, 0, 0, 0, 0, 1],
                 [ 0, 0, 0, 0, 0, 0, 1, 0],
                 [ 0, 0, 0, 0, 0, 1, 0, 0],
                 [ 0, 0, 0, 0, 1, 0, 0, 0],
                 [ 0, 0, 0, 1, 0, 0, 0, 0],
                 [ 0, 0, 1, 0, 0, 0, 0, 0],
                 [ 0, 1, 0, 0, 0, 0, 0, 0],
                 [ 1, 0, 0, 0, 0, 0, 0, 0]
                ])
CCZ  = np.array([[ 1, 0, 0, 0, 0, 0, 0, 0],
                 [ 0, 1, 0, 0, 0, 0, 0, 0],
                 [ 0, 0, 1, 0, 0, 0, 0, 0],
                 [ 0, 0, 0, 1, 0, 0, 0, 0],
                 [ 0, 0, 0, 0, 1, 0, 0, 0],
                 [ 0, 0, 0, 0, 0, 1, 0, 0],
                 [ 0, 0, 0, 0, 0, 0, 1, 0],
                 [ 0, 0, 0, 0, 0, 0, 0,-1]
                ])

q000 = np.array([[1],[0],[0],[0],[0],[0],[0],[0]]) # Try any Vector

print('1回目のアダマール後')
G_1 = np.dot(HxHxH,q000)
print(G_1)

print('1回目のXゲート後')
G_2 = np.dot(XxXxX,G_1)
print(G_2)

print('CCXゲート後')
G_3 = np.dot(CCZ,G_2)
print(G_3)

print('2回目のXゲート後')
G_4 = np.dot(XxXxX,G_30)
print(G_4)

print('2回目のHゲート後')
t000 = np.dot(HxHxH,G_4)
print('変換後のベクトル')
print(t00)

print('変換前と変換後（に原点で反転させた）ベクトルの間の角度は',round(vector_to_vector_rotation_angle(q000.reshape(-1),-t000.reshape(-1))/np.pi,3),'pi')
print('変換前のベクトルと|s>との間の角度は',                   round(vector_to_vector_rotation_angle(q000.reshape(-1), sKet.reshape(-1))/np.pi,3),'pi')
print('変換後（に原点で反転させた）ベクトルと|s>との間の角度は', round(vector_to_vector_rotation_angle(sKet.reshape(-1),-t000.reshape(-1))/np.pi,3),'pi')
```

ぜひ皆さんの手元で確認してみてください。