1量子ビットVQEを世界一簡単に書く 2020年2月Ver.

#VQEを世界一簡単に書く[^1]
[^1]: 当社比

VQEの解説記事が、最近、いろいろ世に出てきました。勉強された人なら、少しの試行錯誤で書けるでしょう。
けれど「もっと簡単に書きたい」と思う方も大勢いるかと思います。

私もそういうタイプで、その気持ちが強まりすぎたので、BlueqatにはVQEを世界一簡単に[^1]書ける仕組みを用意しているのですが。
自分で言わないと誰も気づいてくれないので、記事を書きます。

# HamiltonianとAnsatzさえあればいい

「何に対して答えを求めるか」は、ユーザが決めざるを得ないので、ハミルトニアンはユーザが作る必要があります。
また、とりあえずどんな回路になりそうかも、今はユーザに決めてもらっています。これはAnsatzと呼ばれます。

QAOA用のAnsatzなど、Blueqat側で用意しているAnsatzもあるのですが、問題に合わせてAnsatzを作ることになるかと思います。
これはユーザに負担を強いることです。ごめんなさい、けど、簡単なので許して下さい。

というわけで、今回は、1量子ビットのVQEを世界一簡単に[^1]書いてみます。

# Hamiltonianは、`blueqat.pauli`から作れる

ごっつ簡単です。`blueqat.pauli`を使えば、

```py
from blueqat.pauli import X, Y, Z, I

h = 1.23 * I - 4.56 * X(0) + 2.45 * Y(0) + 2.34 * Z(0)
```

のように、積和の形で書けます。

## 乱数を使って、ランダムにHamiltonianを作ってみる

今回、何か特別に解きたいハミルトニアンがあるわけではないので、乱数を使ってハミルトニアンを作ってみましょう。


```py
from random import random
from blueqat.pauli import X, Y, Z, I

h = random() * I + random() * X(0) + random() * Y(0) + random() * Z(0)
```

できました。簡単ですね。

#Ansatzは`blueqat.vqe.AnsatzBase`を継承して`get_circuit`を実装すればできる

任意の1量子ビットを作るには、RYゲートとRZゲートを1回ずつ適用したら十分です。[^2]
そのようなAnsatzを作ってみましょう。

[^2]: 「パラメータ3ついるんじゃなかったっけ?」と思われた方もいるかもしれません。確かに、任意の1量子ビットを作るには、3つのパラメータが必要です。けれど、|0>から、任意の1量子ビット状態|Ψ>を作るには、2つのパラメータで十分です。ブロッホ球を思い浮かべてください。|0>状態なので、針は真上に向いています。それを、Z軸と垂直な、例えばY軸まわりにいくらか回転させて、続いてZ軸まわりにいくらか回転させれば、球上のどこにでも針を向けることができます。



```py
from blueqat import Circuit
from blueqat.vqe import AnsatzBase

class OneQubitAnsatz(AnsatzBase):
    def __init__(self, hamiltonian):
        AnsatzBase.__init__(hamiltonian, 2) # 親クラスの__init__に、Hamiltonianとパラメータ数を渡す

    def get_circuit(self, params):
        # RYゲートとRZゲートを適用した回路を返す
        a, b = params
        return Circuit().ry(a)[0].rz(b)[0]
```

簡単ですね。

# HamiltonianとAnsatzがあればVQEはできる

```py
from blueqat.vqe import Vqe

h = random() * I + random() * X(0) + random() * Y(0) + random() * Z(0)
runner = Vqe(OneQubitAnsatz(h))
result = runner.run()

print('Result by VQE')
print(runner.ansatz.get_energy(result.circuit, runner.sampler))
```

`runner = Vqe(OneQubitAnsatz(h))` で、さっき定義したAnsatzを作って、さらに、VQEを動かすための`runner`を作っています。
`result = runner.run()` で、VQEを動かして、結果を得ることができます。

結果からエネルギーを取り出す処理は、恐らく、この記事で最難関でしょう。覚えないでコピペして下さい。[^3]
`runner.ansatz.get_energy(result.circuit, runner.sampler)`

[^3]: これを書くのがあまりにも面倒であることを、Blueqat開発者は十分に理解しているので、今後、より簡単な形に改良するはずです。

これで、エネルギーが取り出せました。ちょっと難しいけれど、まぁまぁ簡単ですね。

## 本当に答えあってるの? numpyで求めた固有値と比較してみよう

```py
import numpy as np
# Hamiltonian to matrix
mat = h.to_matrix()
# Calculate by numpy
print('Result by numpy')
print(np.linalg.eigh(mat))
```

`blueqat.pauli`の機能で、Hamiltonianを`to_matrix()`によりnumpyのarrayに変換できます。[^4]
変換したarrayから、numpyの`np.linalg.eigh`を使って固有値を求めることができます。

[^4]: ただし、この処理は、量子ビット数が多いHamiltonianでは非常に重くなります。特に10量子ビットを越えてくると、待ちきれないほど待たされることになるかと思います。あくまで、小さめのHamiltonianのチェック用だとご理解ください。


# 通しで実行してみよう

ソース全体はこんな感じ

```py
from random import random
import numpy as np
from blueqat import Circuit
from blueqat.pauli import X, Y, Z, I
from blueqat.vqe import AnsatzBase, Vqe

class OneQubitAnsatz(AnsatzBase):
    def __init__(self, hamiltonian):
        super().__init__(hamiltonian, 2)

    def get_circuit(self, params):
        a, b = params
        return Circuit().ry(a)[0].rz(b)[0]


# Calculate by VQE
h = random() * I + random() * X(0) + random() * Y(0) + random() * Z(0)
runner = Vqe(OneQubitAnsatz(h))
result = runner.run()

print('Result by VQE')
print(runner.ansatz.get_energy(result.circuit, runner.sampler))

# Hamiltonian to matrix
mat = h.to_matrix()
# Calculate by numpy
print('Result by numpy')
print(np.linalg.eigh(mat))
```

実行結果はこんな感じ(乱数でHamiltonianを作っているので実行するたびに変わります)

```
Result by VQE
-0.7972144535545334
Result by numpy
(array([-0.7972159 ,  2.24607842]), array([[-0.41818368+0.j        , -0.90836249+0.j        ],
       [ 0.57016708+0.70712935j, -0.26248835-0.32554179j]]))
```

VQEで求まった固有値は-0.7972144535545334
numpyで求まった固有値(のうち、最小のもの)は-0.7972159
ほぼほぼ一致していることが確認できました。

# まとめ

Blueqatを使えば世界一簡単に[^1]VQEが書ける