1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

前回の記事では試行的にテンソルネットワーク (MPS) でベイジアンネットワークを表現しました。
本記事では MPS を量子回路に置き換えることによって、ベイジアンネットワークをより浅い量子回路で表現します。量子回路への変換方法については文献 [1] を参考にしました。

ただし、ここで実験した方法では、MPS を完全に量子回路に置き換えることはできず、実機で動かすことも困難という問題があります。
それでも、テンソルネットワークと量子コンピュータの間の関係が見えてくると思います。

元のテンソルネットワーク

前回使用したテンソルネットワークの形式について振り返ります。
上部にある 4 つの矢印 (x1,x2,x3,x4) が入力を、下部にある 1 つの矢印 f(x) が出力を表します。
赤線部分のボンド次元が 2 の場合には、機械的にユニタリ演算子に置き換えることができます。
(以降、ボンド次元 2 を前提とします)

量子回路への置き換え手順

MPS から量子回路への置き換えは機械的に行うことができます。

  • 入出力をケットベクトルに置き換える
  • 前回と同様のテンソルネットワーク変形によって特異値を D の位置に移動させる(図の左)
  • さらに特異値分解を行い、図のように特異値と出力部分を右に追加して ΣV とする(図の右)
  • A を 1 量子ビットゲートに置き換える
  • B,C,D を 2 量子ビットゲートに置き換える(出力の一方は |0⟩ に固定)
  • D の出力を観測する
  • 残った ΣV はユニタリ演算子で表現できないので、古典的に計算する

最後の部分で古典計算が必要になるため、この方法では MPS を完全に量子回路に置き換えたことにはなりません。
テンソル A,B,C,D をそれぞれ量子ゲート U1,U2,U3,U4 で置き換えたとすると、量子回路は下の図のようになります。

  • テンソル A,B,C,D からなる配列 matrices について特異値分解を行い、ΣV を返す関数を次のように実装します。
import numpy as np

def normalize_mps(matrices):
	# テンソルA
	U, s, V = np.linalg.svd(matrices[0], full_matrices=False)
	matrices[0] = U
	matrices[1] = np.einsum('ij,jkl->ikl', np.diag(s) @ V, matrices[1])
	
	# テンソル B,C,D
	for i in range(1, len(matrices)):
		m = matrices[i]
		m = m.reshape([2*m.shape[0], m.shape[2]])
		U, s, V = np.linalg.svd(m, full_matrices=False)
		s = np.diag(s)
		matrices[i] = U.reshape(matrices[i].shape)
		if i < len(matrices) - 1:
			matrices[i+1] = np.einsum('ij,jkl->ikl', s @ V, matrices[i+1])

	return s @ V

A の変換方法

テンソル $A$ は特異値分解を行った時点でユニタリ行列になっているため、そのまま量子ゲート $U_1$ になります。

B, C, D の変換方法

テンソル $B^j_{ik}$ はインデックス $i, j, k$ がバイナリになっているテンソルで、8 個の値を持っています。
これをユニタリ表列 $U_2$ に変換します。

まず、添字の位置に注意しながら 8 個の値を表のように配置して、新たな $2 \times 4$ 行列 $X$ を作ります。
その後、$X$ のカーネル ($\mathrm{Ker} X$) の基底を求めて、それを 2, 4 行目に追加することで、$\mathrm{rank}$ $2$ のユニタリ行列を作ることができます。これが $U_2$ になります。

00 01 10 11
$B^0_{00}$ $B^1_{00}$ $B^0_{10}$ $B^1_{10}$
$B^0_{01}$ $B^1_{01}$ $B^0_{11}$ $B^1_{11}$

$X$ の 1 行目は出力 $k$ が $|0\rangle$ になるときの入力の分布、2 行目は出力 $k$ が $|1\rangle$ になるときの入力の分布になっています。また、$X$ の各列は入力のインデックス $i, j$ を表しています。

$B$ を $U_2$ に変換する関数を次のように実装します。$C, D$ もこの関数で変換できます。

from scipy.linalg import null_space

# tensorの添字は tensor[j][i][k] の順序
def get_unitary_op(tensor):
	U = np.zeros([tensor.shape[2], tensor.shape[0]*tensor.shape[1]])
	
	for (i,j,k) in [(i,j,k) for i in range(tensor.shape[0]) for j in range(tensor.shape[1]) for k in range(tensor.shape[2])]:
		U[k, j*2+i] = tensor[i,j,k]
	
	kernU = null_space(U)
	return np.array([U[0], kernU.T[0], U[1], kernU.T[1]])

量子回路シミュレータで計算

量子回路を実装する準備ができたので、ベイジアンネットワークを qiskit の量子回路シミュレータで計算します。ベイジアンネットワークを(近似的に)MPS で表現できている状態から始めます。

ここでは MPS を定数で設定してしまいます。

mps = []
mps.append(np.array([[-0.96414437, -0.26537828], [-0.26537828,  0.96414437]]))
mps.append(np.array([[[-0.55841113, 0.27210687], [-0.82144954, -0.18497485]],
					[[0.0978425, -0.5045371], [-0.06184265, -0.79823836]]]))
mps.append(np.array([[[-0.61909484, 0.44758962], [-0.62551758, -0.27409111]],
					[[0.36071979, -0.36521617], [0.30875648, 0.76886588]]]))
mps.append(np.array([[[-0.69009564, 0.17513174], [-0.7223939, -0.14502367]],
					[[0.03635054, 0.8075315], [-0.02436595, 0.54423331]]]))

sigmaV = np.array([[2.34089487, 2.96663868], [-1.02656386, 0.81003395]])

MPS をユニタリ行列に変換します。

unitaries = [mps[0]]
for i in range(1, len(mps)):
	unitaries.append(get_unitary_op(mps[i]))

量子回路を構築します。

from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit.circuit.library import UnitaryGate

q = QuantumRegister(4)
c = ClassicalRegister(4)
circ = QuantumCircuit(q, c)

g1 = UnitaryGate(unitaries[0])
circ.append(g1, [0])
g2 = UnitaryGate(unitaries[1])
circ.append(g2, [0, 1])
g3 = UnitaryGate(unitaries[2])
circ.append(g3, [1, 2])
g4 = UnitaryGate(unitaries[3])
circ.append(g4, [2, 3])

circ.save_statevector()
circ.draw('mpl')

qmps5.png

検証可能にするために、ステートベクトルを直接参照します。

backend_sim = AerSimulator()
backend_sim.set_option('method', 'statevector')
result = backend_sim.run(circ).result()

statevec = result.get_statevector()
print(statevec)

Statevector([ 0.2609447 +0.j, 0.31955621+0.j, 0.18339716+0.j, 0.39298557+0.j, 0.31819723+0.j, 0.38287479+0.j, 0.19022988+0.j, 0.46688262+0.j, 0.15942316+0.j, 0.16723119+0.j, -0.02563803+0.j, 0.18929075+0.j, -0.11728279+0.j, -0.12496128+0.j, 0.00934952+0.j, -0.14276504+0.j], dims=(2, 2, 2, 2))

観測する量子ビット以外の 3 個の量子ビットの出力は $|0\rangle$ 固定のため、実際に $|0\rangle$ になっている部分のみ着目します。

result = np.array([statevec[0], statevec[8]])
print(result)

[0.2609447 +0.j 0.15942316+0.j]

最後に $\Sigma V$ を古典的に計算して結果を得ます。

out = np.einsum('i,ij->j', result, sigmaV)
print(out**2)

[0.19997536+0.j 0.81589091+0.j]

前回の記事で入力を $(0, 0, 0, 0)$ としたときの結果と一致しました。
もちろん、各量子ビットに $X$ ゲートを追加することで、他の入力に対する結果を計算することもできます。

おわりに

本記事ではボンド次元が 2 の MPS を量子回路シミュレータで扱えることを確認しました。
しかし、冒頭で触れた問題以外にも以下のような問題があります。

  • ボンド次元 2 で充分な精度で近似できるデータは限られる。
  • 正の実数しか使っていないので、干渉などの量子の強みを活用できていない。

実用を考慮すると近似精度が大きな問題になってくるので、今後テンソルネットワークの表現についてさらに追求していきたいと思います。

参考文献

[1]: Shi-Ju Ran. Encoding of matrix product states into quantum circuits of one- and two-qubit gates. Phys. Rev. A 101, 032310

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?