量子コンピュータのシミュレータも発達しています。今回はテンセントの最新のツールで、テンソルネットワークベースの量子回路シミュレータです。国内の技術が遅れてるとか結構警告してきましたが、もう後ろ向きなことを言うのめんどいので、ガンガン最新を追っていきます!
テンソルネットワークは従来の状態ベクトル法と呼ばれる状態ベクトルや量子ゲートのテンソル積を用いて量子状態を保存しながら計算する手法に変わるものとして注目を浴びており、近年急速に利用が増加しています。NISQと相性が良く、FTQCとは相性はあまり良くないですが、FTQCは当面先の話なので、NISQ向けとして活用が進みます。
バックエンドにはTensorFlow / PyTorch / JAX などが利用されるため、CPU/GPU/TPUなどの深層学習向けのハードが使われます。早速見てみましょう。
githubはこちら
https://github.com/tencent-quantum-lab/tensorcircuit
論文はこちら
https://arxiv.org/abs/2205.10091
ドキュメントはこちら
https://tensorcircuit.readthedocs.io/en/latest/
チュートリアルもたくさんあるし、ガンガン解説していきます。基本的なテンソルネットワークについてはいろいろな記事を出しているのでそちらをご覧ください。
論文
TensorCircuit: a Quantum Software Framework for the NISQ Era
https://quantum-journal.org/papers/q-2023-02-02-912/
イントロ
これからより大規模に複雑になる量子コンピューティングに対して、新しくテンソルネットワークベースでフレームワークを組んでいる。TensorFlow, Pytorch, JAXなどのフレームワークの上に組まれている。
PQC
変分量子計算と呼ばれるパラメータを導入したものがVQE/QAOA/QMLなどの文脈で利用されるので、それらに最適なフレームワークとなっている。パラメータを導入した変分量子回路について最適化を通じて量子変分原理からハミルトニアンの期待値を求める。
機械学習
自動微分の導入により便利に、JITコンパイラの導入、VMAPの導入、GPUなどのハードウェアを意識。
テンソルネットワークエンジン
cotengra や googleのtensornetworkなどの標準的なライブラリに基づいてエンジンが構築され、einsum/matmulによって実際に計算される。
チュートリアル
import tensorcircuit as tc
c = tc.Circuit(2)
c.H(0)
c.CNOT(0,1)
c.rx(1, theta=0.2)
print(c.wavefunction())
print(c.expectation_ps(z=[0, 1]))
print(c.sample(allow_state=True, batch=1024, format="count_dict_bin"))
量子回路は一般的な形ですね。下のprintは状態ベクトル、ハミルトニアン期待値、あとはサンプリングになってます。状態ベクトルはおそらく量子ビットの最大数には限りがあると思います。
tf.Tensor(
[0.70357419+0.j 0. -0.07059289j 0. -0.07059289j
0.70357419+0.j ], shape=(4,), dtype=complex128)
tf.Tensor((0.9800665772491607+0j), shape=(), dtype=complex128)
{'00': 533, '01': 2, '10': 3, '11': 486}
バックエンドを含めた設定があります。
tc.set_backend("tensorflow")
tc.set_dtype("complex128")
tc.set_contractor("greedy")
自動微分の設定はこちら
def forward(theta):
c = tc.Circuit(2)
c.R(0, theta=theta, alpha=0.5, phi=0.8)
return tc.backend.real(c.expectation((tc.gates.z(), [0])))
g = tc.backend.grad(forward)
g = tc.backend.jit(g)
theta = tc.array_to_tensor(1.0)
print(g(theta))
便利ですね。ほとんどの計算が搭載されています。
なんかチュートリアルをやってみます。正直ちょっと書き方は書きづらい感じを受けますが、一通りの機能は揃ってるのでなんとか使います。
QAOAですね。
import tensorcircuit as tc
import tensorflow as tf
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import clear_output
import random
K = tc.set_backend("tensorflow")
nlayers = 3 # the number of layers
ncircuits = 6 # six circuits with different initial parameters are going to be optimized at the same time
nnodes = 8 # the number of nodes
なんかレイヤーが3、6つの回路を同時に計算して比較するようです。ノード数は8ということは量子ビット数は8になりそうです。
問題設定
グラフで問題設定をしていますね。
# a graph instance
each node is connected to three nodes
for example, node 0 is connected to nodes 1,7,3
example_graph_dict = {
0: {1: {"weight": 1.0}, 7: {"weight": 1.0}, 3: {"weight": 1.0}},
1: {0: {"weight": 1.0}, 2: {"weight": 1.0}, 3: {"weight": 1.0}},
2: {1: {"weight": 1.0}, 3: {"weight": 1.0}, 5: {"weight": 1.0}},
3: {1: {"weight": 1.0}, 2: {"weight": 1.0}, 0: {"weight": 1.0}},
4: {7: {"weight": 1.0}, 6: {"weight": 1.0}, 5: {"weight": 1.0}},
5: {6: {"weight": 1.0}, 4: {"weight": 1.0}, 2: {"weight": 1.0}},
6: {7: {"weight": 1.0}, 4: {"weight": 1.0}, 5: {"weight": 1.0}},
7: {4: {"weight": 1.0}, 6: {"weight": 1.0}, 0: {"weight": 1.0}},
}
pos = nx.spring_layout(nx.to_networkx_graph(example_graph_dict))
colors = ["c" for key in example_graph_dict.items()]
convert to a NetworkX graph
example_graph = nx.to_networkx_graph(example_graph_dict)
nx.draw_networkx(example_graph, with_labels=True, node_color=colors, pos=pos)
ax = plt.gca()
ax.set_facecolor("w")
QAOAの量子回路を作ります。
def QAOAansatz(params, g=example_graph, return_circuit=False):
n = len(g.nodes) # the number of nodes
c = tc.Circuit(n)
for i in range(n): #横磁場で初期状態は|+>ですね。
c.H(i)
# PQC
for j in range(nlayers):
# U_j
for e in g.edges:
c.exp1(
e[0],
e[1],
unitary=tc.gates._zz_matrix,
theta=g[e[0]][e[1]].get("weight", 1.0) * params[2 * j],
) #エッジごとにZZを指定してます。maxcutなので局所磁場はなし
# V_j
for i in range(n):
c.rx(i, theta=params[2 * j + 1]) #横磁場のmixerハミルトニアン
# whether to return the circuit
if return_circuit is True:
return c
# calculate the loss function
loss = 0.0
for e in g.edges:
loss += c.expectation_ps(z=[e[0], e[1]]) * g[e[0]][e[1]].get("weight", 1.0) #ロスというかハミルトニアンの期待値を求めてますね。
return K.real(loss)
実装は結構地道な感じです。QAOA向けの時間発展の演算子も知識がないと実装キツそうです。知ってる人なら作れそうです。
# use vvag to get the losses and gradients with different random circuit instances
QAOA_vvag = K.jit(
tc.backend.vvag(QAOAansatz, argnums=0, vectorized_argnums=0), static_argnums=(1, 2)
)
複数のインスタンスを計算していますね。
params = K.implicit_randn(
shape=[ncircuits, 2 * nlayers], stddev=0.1
) # initial parameters
opt = K.optimizer(tf.keras.optimizers.Adam(1e-2))
list_of_loss = [[] for i in range(ncircuits)]
for i in range(300):
loss, grads = QAOA_vvag(params, example_graph)
params = opt.update(grads, params) # gradient descent
# visualise the progress
clear_output(wait=True)
list_of_loss = np.hstack((list_of_loss, K.numpy(loss)[:, np.newaxis]))
plt.xlabel("Iteration")
plt.ylabel("Cost")
for index in range(ncircuits):
plt.plot(range(i + 1), list_of_loss[index])
legend = ["circuit %d" % leg for leg in range(ncircuits)]
plt.legend(legend)
plt.show()
実装は結構知らないと出来なさそうですね。
パラメータ数は、6回路分にレイヤーp=3の二倍を設定してます。
最適化ソルバーはAdamですね。
あとは普通に期待値を求めています。特殊な計算はなさそうです。
どうやら状態ベクトルかなんかで確率振幅から最大確率のものを取ってるようです。それぞれ期待値の計算は異なりますが、答えは同じになってるようです。
# print all results
for num_circuit in range(ncircuits):
c = QAOAansatz(params=params[num_circuit], g=example_graph, return_circuit=True)
loss = QAOAansatz(params=params[num_circuit], g=example_graph)
# find the states with max probabilities
probs = K.numpy(c.probability()).round(decimals=4)
index = np.where(probs == max(probs))[0]
states = []
for i in index:
states.append(f"{bin(i)[2:]:0>{c._nqubits}}")
print("Circuit #%d" % num_circuit)
print("cost:", K.numpy(loss), "\nbit strings:", states, "\n")
今回はテンセントのTensor Circuitを見ましたが、結構知らないと作れないようになってたので、中級以上の人向けかなと思いました。もっと簡単になるのでしょうか。ただ、量子回路をつかって高速にGPUで計算できそうなので便利そうでした。
以上です。