common.title

Generate Image


Overview
Service overview
Terms of service

Privacy policy

Contact

Sign in
Sign up
common.title

PyTorchだけで量子計算!

Yuichiro Minato

2024/01/08 13:25

!pip --no-cache-dir install -U torch
Requirement already satisfied: torch in /opt/conda/lib/python3.10/site-packages (2.1.2)

Requirement already satisfied: filelock in /opt/conda/lib/python3.10/site-packages (from torch) (3.13.1)

Requirement already satisfied: typing-extensions in /opt/conda/lib/python3.10/site-packages (from torch) (4.5.0)

Requirement already satisfied: sympy in /opt/conda/lib/python3.10/site-packages (from torch) (1.12)

Requirement already satisfied: networkx in /opt/conda/lib/python3.10/site-packages (from torch) (3.2.1)

Requirement already satisfied: jinja2 in /opt/conda/lib/python3.10/site-packages (from torch) (3.1.2)

Requirement already satisfied: fsspec in /opt/conda/lib/python3.10/site-packages (from torch) (2023.5.0)

Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.1.105 in /opt/conda/lib/python3.10/site-packages (from torch) (12.1.105)

Requirement already satisfied: nvidia-cuda-runtime-cu12==12.1.105 in /opt/conda/lib/python3.10/site-packages (from torch) (12.1.105)

Requirement already satisfied: nvidia-cuda-cupti-cu12==12.1.105 in /opt/conda/lib/python3.10/site-packages (from torch) (12.1.105)

あんまり詳しくは書かないのですが、PyTorchを使って量子計算や機械学習を進めます。 まずは簡単な量子回路を書いてみますが、基本的には、

    • 量子ビット
    • 量子ゲート
    • 測定
    • 期待値を取るときのハミルトニアン

などあたりで表現が多少これまでの手法と異なります。

説明のためにはこれまでのような量子回路描画用のツールよりも単純にNetworkXのようなものを使ってみます。

import matplotlib.pyplot as plt import networkx as nx import numpy as np G = nx.Graph() nx.add_path(G, ["q0", "H", "C", "M0"]) nx.add_path(G, ["q1", "X", "M1"]) nx.add_path(G, ["C", "X"]) pos = {"q0":[0, 0], "H":[1, 0], "C":[2, 0], "M0":[3, 0], "q1":[0, -1], "X":[2, -1], "M1":[3, -1]} nx.draw(G, pos, with_labels = True) plt.show()
<Figure size 640x480 with 1 Axes>output

q0とq1が量子ビットに対応し、通常は状態ベクトルが個別に|0> = [1,0] として与えられます。

Hゲートは量子ゲートなのでユニタリ行列が。CXゲートに関しては、NetworkX上ではCとXが分離していますが、通常は腕が2本、ノードが点1つの行列になっているはずですが、今回はノードが2つ、それぞれのノードに対して腕が3本ずつになっています。測定もM0とM1でノードが割り振られています。

以前でもどこかで記事を書いた気がしますが、また最初からやってみます。

import torch.optim as optim import torch import numpy as np

q0は|0>なので、 同様に q1も設定します。

q0 = torch.tensor([1.,0]) q1 = torch.tensor([1,0])

次にHゲートです。こちらは行列ですので、素直に行列で実装します。

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

試しにq0とHを計算するには、q0とHを行列演算すればいいのでした。

M0= torch.matmul(q0,H)
print(M0)
tensor([0.7071, 0.7071])

無事状態ベクトルが取れましたね。

続いてeinsumでも計算してみたいと思います。基本的には同じことなのですが、うまくいきました。

M0 = torch.einsum("a,ab->b", (q0, H)) print(M0)
tensor([0.7071, 0.7071])

おそらくeinsumで計算した方が簡単そうですね。 後ほど変分回路での計算もしてみたいので、勾配情報を含んで量子計算できるかみてみます。 勾配を持たせるには状態ベクトルや量子ゲートをfloatで書き直す必要がありました。

q0 = torch.tensor([1.,0.]) H = torch.tensor([[1.,1],[1,-1]],requires_grad=True)/np.sqrt(2)

計算してみます。

M0 = torch.einsum("a,ab->b", (q0, H)) print(M0)
tensor([0.7071, 0.7071], grad_fn=<ViewBackward0>)

今度はベル状態を作ってみます。 量子ビットを二つとHゲート、CXゲートを準備します。

q0 = torch.tensor([1.,0]) q1 = torch.tensor([1.,0]) H = torch.tensor([[1.,1],[1,-1]])/np.sqrt(2) CX = torch.tensor([[1.,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]])

試しにちょっとずつ計算してみます。まずは、q0にHゲートをかけてみます。

res1 = torch.einsum("a,ab->b", (q0,H)) print(res1)
tensor([0.7071, 0.7071])

次にこの量子状態とq1をCXに入れてみます。ただ、CXはこのまま使うのではなく、テンソルに変形します。行列からテンソルにします。

CX_four = CX.reshape([2,2,2,2]) print(CX_four)
tensor([[[[1., 0.],

          [0., 0.]],



         [[0., 1.],

          [0., 0.]]],





        [[[0., 0.],

          [0., 1.]],


これでorder-4のテンソルになりました。計算をしてみます。

res2 = torch.einsum("a,b,abcd->cd", (res1, q1, CX_four)).reshape(4) print(res2)
tensor([0.7071, 0.0000, 0.0000, 0.7071])

どうでしょうか、ベル状態ができました。簡単ですね。 もちろんまとめてやっても大丈夫です。

res2 = torch.einsum("a,ab,c,bcde->de", (q0, H, q1, CX_four)).reshape(4) print(res2)
tensor([0.7071, 0.0000, 0.0000, 0.7071])

できました。

せっかくなので量子古典の変分計算もしてみたいと思います。今回はHの代わりにRY(なるべく複素数を使いたくないので、、、)を置いてみて、変分アルゴで|11>を作ってみます。

|11>の判定として、ハミルトニアンの期待値を使ってみたいと思います。Z0+Z1の値が-2になるように学習をしたいと思います。今回は期待値を求めたいので、Zを配置したテンソルを準備します。Z0の期待値を図に書いてみると、

G = nx.Graph() nx.add_path(G, [0,1,2,3,4,5,6]) nx.add_path(G, [7,8,10,11]) nx.add_path(G, [2,8]) nx.add_path(G, [4,10]) pos = {0:[0, 0], 1:[1, 0], 2:[2, 0], 3:[3, 0], 4:[4, 0], 5:[5, 0], 6:[6, 0], 7:[0, -1], 8:[2, -1], 10:[4, -1], 11:[6, -1], } nx.draw(G, pos) plt.show()
<Figure size 640x480 with 1 Axes>output

同様に<psi|Z0+Z1|psi>を求めれば良いことになります。

#パラメータをランダム初期化 a = torch.tensor([np.random.rand()],requires_grad=True) #最適化アルゴリズムを見つけてパラメータを最適化します op = optim.Adam([a],lr=0.05) #量子ビットを準備 q0 = torch.tensor([1.,0]) q1 = torch.tensor([1.,0]) #期待値測定用のZを準備します Z = torch.tensor([[1.,0],[0,-1]]) #計算結果格納用のリスト arr = [] #最適化ループを回します for _ in range(100): #RYゲートですが、aをうまく使って勾配を失わないようにpytorchの関数を使います。 RY = torch.tensor([[1,0],[0,1]])*torch.cos(a/2) + torch.tensor([[0,-1],[-1,0]])*torch.sin(a/2) #途中までテンソルの計算を終わらせておきます。 net1 = torch.einsum("a,ab,c,bcde->de", (q0, RY, q1, CX_four)) net2 = torch.einsum("a,ab,c,bcde->de", (q0, RY, q1, CX_four)) #Z0とZ1の期待値をそれぞれ求めてたし合わせます。期待値が-2になれば両方のビットは1になるはず。 expt = torch.einsum("ab,cb,ac->", (net1, net2, Z)) + torch.einsum("ab,ad,bd->", (net1, net2, Z)) #期待値をリストに格納 arr.append(expt.detach().numpy()) #最適化 op.zero_grad() expt.backward() op.step() #描画 plt.plot(arr) plt.show()
<Figure size 640x480 with 1 Axes>output

PyTorchでVQEができました。aのパラメータが最適化されているかどうかみます。

print(a)
tensor([3.1281], requires_grad=True)

ほぼ3.14になっていて、RYで量子ビットが1に。その後CXで両方1に。|11>が実現できました。 PyTorchの機能だけで量子計算ができました。GPUなどの活用だけでなく、シームレスに古典NNと統合できますね!以上です。

© 2024, blueqat Inc. All rights reserved