sympy で量子計算

sympy で量子回路のシミュレーションができる。ざっくりまとめた。

# ブラケット

1-qubit は二準位系であり、任意の状態は二つの基底 `|0>, |1>` の重ね合わせで表現できる。3-qubit の場合は、基底は 8つ必要になって、 `|000>, |001>, ....` となる。これら、**computational basis** と呼ばれる基底状態は、`Qubit`クラスで表現される。

```python
>>> from sympy.physics.quantum.qubit import Qubit
>>> Qubit(0), Qubit('1'), Qubit(0,1,0), Qubit('010')
(|0>, |1>, |010>, |010>)
```

ケットの中身を見れば、「基底の2進数表現」と言える。これに対して「基底の10進表現」なるものも存在し、`IntQubit` クラスで表す。当然、`Qubit` と `IntQubit` は互いに変換可能である。（`IntQubit(0) -> Qubit(0) or Qubit(0,0)...` なる任意性はあるが、系の粒子数は不変であるので問題にならない。）

```python
>>> from sympy.physics.quantum.qubit import Qubit, IntQubit
>>> IntQubit(0), IntQubit(Qubit('010')), IntQubit(0,1,0)
(|0>, |2>, |2>)
```

状態はケットで表されるが、ブラにしたいこともある。その場合は、`QubitBra` `IntQubitBra` を使ってもよいし、ケットにダガーを書けるというのでもよい。

```python
>>> from sympy.physics.quantum.qubit import Qubit, IntQubit, QubitBra, IntQubitBra
>>> QubitBra(0), QubitBra('010'), IntQubitBra(0,1,0), IntQubitBra(QubitBra(0,1,0))
(<0|, <010|, <2|, <2|)

>>> from sympy.physics.quantum.dagger import Dagger
>>> Dagger(Qubit(0,1,0)), Dagger(IntQubit(4))
(<010|, <4|)
```

内積をとりたければブラとケットを `*` 演算子でつなげて、 `doit()` を呼び出す。

```python
>>> zero, one = Qubit(0), Qubit(1)
>>> (Dagger(zero)*zero).doit(), (Dagger(zero)*one).doit(), (Dagger(one)*one).doit()
(1, 0, 1) # corresponds to <0|0>, <0|1>, <1|0>
```

# 量子ゲート（=ユニタリ変換）
1-qubitに働く量子ゲート(例: `X, Y, Z, H ...`)を記述したい場合、「何番目の粒子にゲートを作用させるか」を明示する。0番目の粒子に Xゲートをかけたいなら `X(0)` と記述する。なお、**0番目の粒子とは一番右の粒子である**。同様に、2-qubit に働く量子ゲート(例: `CNOT`) を記述したい場合は、`CNOT(0,1)` と書く。この例では、 0番目の粒子（一番右）がコントロールビットで、1番目の粒子（右から2番目）がターゲットビットである。つまり`CNOT(control,target)`。順序に注意。

量子ゲートと、`Qubit` を演算子 `*` で挟んであげて、`qapply` をすると量子ゲートを通したことになる。

```python

>>> qapply(H(0)*Qubit('00')) # 一番右側の粒子が重ね合わせ状態になる。
sqrt(2)*|00>/2 + sqrt(2)*|01>/2

>>> qapply(H(0)*H(1)*Qubit('00')) # 各ビットにHを書けると linear superposition になる
|00>/2 + |01>/2 + |10>/2 + |11>/2

>>> qapply(CNOT(0,1)*Qubit('01')) # 制御ビットが立っていれば足しこむ
|11>
>>> qapply(CNOT(0,1)*Qubit('00')) # 制御ビットが立っていなければスルー
|00>
```

具体例として、computational basisとベル基底を相互変換をする量子回路の計算をしよう。

```bell.py
from sympy.physics.quantum.qubit import Qubit
from sympy.physics.quantum.gate import H, CNOT
from sympy.physics.quantum.qapply import qapply
bell = {}
# to Bell
for yx in ['00', '10', '01', '11']:
    result = qapply(CNOT(0,1)*H(0)*Qubit(yx))
    bell[yx] = result
    print (f'{yx} -> ', result)
# from Bell
for i, state in bell.items():
    result = qapply(H(0)*CNOT(0,1)*state)
    print(f'beta{i} -> ', result)
```

出力はこう。確かにうまくいっている。

```
00 ->  sqrt(2)*|00>/2 + sqrt(2)*|11>/2
10 ->  sqrt(2)*|01>/2 + sqrt(2)*|10>/2
01 ->  sqrt(2)*|00>/2 - sqrt(2)*|11>/2
11 ->  -sqrt(2)*|01>/2 + sqrt(2)*|10>/2
beta00 ->  |00>
beta10 ->  |10>
beta01 ->  |01>
beta11 ->  |11>
```

# 測定
`measure_all` を呼ぶと、computational basis への射影をしてくれる。`measure_partial` を呼ぶと特定のbase だけ部分的に測定できる。


```python
# 2-qubit, linear superposition の測定
>>> from sympy.physics.quantum.qubit import Qubit, measure_all, measure_partial
>>> from sympy.physics.quantum.gate import H
>>> from sympy.physics.quantum.qapply import qapply

>>> measure_all(qapply(H(0)*H(1)*Qubit('00')))
[(|00>, 1/4), (|01>, 1/4), (|10>, 1/4), (|11>, 1/4)]

>>> measure_partial(qapply(H(0)*H(1)*Qubit('00')), (0,))
[(sqrt(2)*|00>/2 + sqrt(2)*|10>/2, 1/2), (sqrt(2)*|01>/2 + sqrt(2)*|11>/2, 1/2)]
```


# Next Step
ここまでできれば、あとはゴリゴリかける。有名なアルゴリズムはライブラリ化されているので使ってみるのもよい（グローバー、QFT、Shor）。