テンソルネットワークで計算する量子ゲート方式
本記事は量子コンピューター Advent Calendar 2023の20日目の記事です。
量子ゲート方式の原理については以下の記事を参照してください。
テンソルネットワークについて
テンソルとは?
テンソルネットワークにおけるテンソルとは多次元配列のことを指します。例えばスカラー、ベクトル、行列は
とその要素も書き下すことでより丁寧な表現になります。ただこのように毎度要素も書き下しているようだとページ内を占有してしまいます。そこで要素の一つを代表させてベクトルならば
テンソルは添え字(
は
テンソルのダイアグラム表現
行列をベクトルに作用させたり、内積をとるなどの計算は、テンソルをダイアグラム(図式)で表現することで視覚的に理解できるようになります。ベースとなるのはスカラーです。スカラーを図で表すならば次のような形になります。

続いてベクトルです。ベクトルはこのスカラーに対して脚を一本追加します。脚の名前はなんでも良いですが、ベクトルの要素を

行列はさらに脚が二本になります。行列の要素は

そのほかのテンソルも脚の数と同じ分だけ図上でも脚を設置していけば、テンソルをダイアグラム化することができます。次にこのダイアグラムを使って計算したいと思います。実行する計算は次のものです。
この時の結果はベクトル
となります。この式の右辺はダイアグラム上だと、

と書かれます。つまり行列

結果、残る脚は一本となりダイアグラム上でベクトルが新たに出来上がる様子が見て取れます。このように共通した脚

テンソルの縮約を表現したこれらのダイアグラムはテンソルネットワークと呼ばれます。テンソルネットワークとして有名なものとしてMPSやMERAが挙げられます。ぜひ調べてみてください。
テンソルネットワークの恩恵
テンソルネットワークから得られる恩恵は、メモリ使用量と計算量を見積もれる点にあります。計算量については、小さな計算量で済ませられる方法が見つけられると言ったほうが正確かもしれません(参考文献[1]参照)。ここではメモリ使用量について説明します。
例えばテンソル

ここで脚
しっかりとメモリ使用量を削減できていることが確認できます。この

縦軸:対数スケール、青:分解前のパターン数、オレンジ:分解後のパターン数
テンソルネットワークと量子ゲート方式
量子ゲート方式を採用した量子コンピュータはその計算モデルとして量子回路が使われます。例えば以下のようなものです。

量子回路での量子状態ははじめ、すべて0状態の量子ビット
ここで古典コンピュータで量子ゲート方式の量子コンピュータシミュレーションを行う場合、量子ビットが増えたり、回路が深くなるほどメモリ使用量や計算量が大きくなっていきます。量子状態の時間発展は行列とベクトルの計算になるので、そこにテンソルネットワークを用いることで古典コンピュータでも効率的に計算しようという目論見です。
量子回路のテンソルネットワーク化
1量子ビットの場合
まず1量子ビットの場合を考えます。1量子ビットにおける量子状態
という2つの要素を格納するベクトルで、量子ゲートは
という2×2の行列になります。この量子状態

量子ゲートを次々作用させる場合、量子回路からはどのような順番で量子ゲートが作用するかが確認できます。テンソルネットワーク上でも量子回路が描く順番と同じように量子ゲートを縮約させれば、テンソルネットワークで量子状態が時間発展するという量子回路と同じ解釈をすることができます。つまり量子回路はテンソルネットワーク化することが可能なのです。
多量子ビットの場合
多量子ビットの場合でも一量子ビットの時と変わりません。例えば
と書けますが、これをテンソルネットワークで描画すると、一例として、

が描けます。これは全体として

量子もつれ生成をテンソルネットワークで計算
TensorNetworkライブラリ(Python)
テンソルネットワークをPythonコードとして記述し、量子もつれ状態を生成する量子回路を組みたいと思います。使うライブラリはのGoogle社から提供されているTensorNetworkです↓。
ドキュメントは↓を参照してください。
このライブラリの使い方を説明します。実行環境はJupyter Notebookを想定します。TensorNetworkは以下のコマンドでインストールしてください。
!pip install tensornetwork
またnumpyも使うので入っていなければ適宜インストールお願いします。
TensorNetworkの使い方を簡単に説明します。必要な手順は以下の三つです。
- ノード(Node)の定義
- エッジ(Edge)の結合
- テンソルの縮約
ノードとはテンソルやエッジが定義されているオブジェクトを指します。エッジとはテンソルの脚を指します。例として次の計算を実行してみましょう。
これは初めに紹介した
とします。
1. ノード(Node)の定義
ベクトル
import numpy as np
import tensornetwork as tn # TensorNetworkライブラリのインポート
v = np.array([1,2]) # ベクトルvの定義
A = np.array([[1,2],[3,4]]) # 行列Aの定義
v_Node = tn.Node(v, name="vector_v") # ベクトルvのノードの定義
A_Node = tn.Node(A, name="matrix_A") # 行列Aのノードの定義
ndarrayとして予め宣言したvとAに対して、tn.Nodeでノードが定義されます。ここで
A_Node
と単体でA_Nodeを実行してみてください。結果として以下のものが得られるはずです。
Node
(
name : 'matrix_A',
tensor :
array([[1, 2],
[3, 4]]),
edges :
[
Edge(Dangling Edge)[0]
,
Edge(Dangling Edge)[1]
]
)
定義したノードの中にname="matrix_A"として与えたノードの名前nameやテンソルtensor、テンソルの脚のリストedgesが格納されているのが確認できます。試しに
A_Node.tensor
を実行してみてください。結果として
array([[1, 2],
[3, 4]])
が出力されるはずです。これは初めに宣言した行列Edge(Dangling Edge)[0]にアクセスするならばA_Node.edges[0]でも可能ですが、.edgesの部分は省略して、
A_Node[0]
で簡潔に書くことができます。試しに実行してみてください。所望の結果が得られるはずです。数式とエッジ指定方法の対応ですが、
| テンソルの脚 | 指定方法 |
|---|---|
| A_Node[0] | |
| A_Node[1] |
2. エッジ(Edge)の結合
A_Node[1]、v_Node[0]です。これらは次のように結合します。
connect = tn.connect(A_Node[1], v_Node[0])
これで完了です[2]。
3. テンソルの縮約
最後に結合した脚で縮約をとります。次の通りです。
w_Node = tn.contract(connect)
w_Nodeはベクトル
w_Node.tensor
を実行すると所望する結果
array([ 5, 11])
が得られます。これが一連の流れになります。これまでのコードのまとめをおいておきます。
一連のコード
import numpy as np
import tensornetwork as tn # TensorNetworkライブラリのインポート
v = np.array([1,2]) # ベクトルvの定義
A = np.array([[1,2],[3,4]]) # 行列Aの定義
v_Node = tn.Node(v, name="vector_v") # ベクトルvのノードの定義
A_Node = tn.Node(A, name="matrix_A") # 行列Aのノードの定義
connect = tn.connect(A_Node[1], v_Node[0]) # 脚の結合
w_Node = tn.contract(connect) # 縮約する
w_Node.tensor # 結果表示
縮約をもう少し簡単化する
ここまで例として挙げたベクトルの計算は、縮約させたい脚がtn.contractで事足りましたが、結合させる脚が複数ある場合、脚の数だけtn.contractを実行させる必要があります。そこで自動的に一気に縮約をとってくれるtn.contractors.autoを紹介します。結合から縮約までの一連の流れは以下の通りです。
tn.connect(A_Node[1], v_Node[0]) # 脚の結合
w_Node = tn.contractors.auto( # 縮約する
nodes=[v_Node, A_Node],
output_edge_order=[A_Node[0]]
)
w_Node.tensor # 結果表示
nodesには縮約対象となるノードのリストを指定します。output_edge_orderには縮約後も縮約結果として残るエッジのリストを指定します。この例ではA_Node[0]つまりoutput_edge_orderにA_Node[0]が指定されています。
tn.contractors.autoを使った際の一連の流れのコードをおいておきます。
一連のコード
import numpy as np
import tensornetwork as tn # TensorNetworkライブラリのインポート
v = np.array([1,2]) # ベクトルvの定義
A = np.array([[1,2],[3,4]]) # 行列Aの定義
v_Node = tn.Node(v, name="vector_v") # ベクトルvのノードの定義
A_Node = tn.Node(A, name="matrix_A") # 行列Aのノードの定義
tn.connect(A_Node[1], v_Node[0]) # 脚の結合
w_Node = tn.contractors.auto( # 縮約する
nodes=[v_Node, A_Node],
output_edge_order=[A_Node[0]]
)
w_Node.tensor # 結果表示
量子もつれ生成をテンソルネットワークで計算
では本題です。2量子ビットの量子もつれ状態を生成してみましょう。初期状態は次の通りです。
生成する量子もつれ状態は次の通りです。

この量子回路をテンソルネットワーク化することを考えるならば、

エッジの指定方法付き(後述の量子もつれ状態生成コードにて使用)
と書くことができます。この量子回路全体を
となります。この確率振幅の大きさの二乗
import numpy as np
import tensornetwork as tn
O = np.array([[0, 0],[0, 0]])
I = np.array([[1, 0],[0, 1]])
X = np.array([[0, 1],[1, 0]])
H = np.array([[1, 1],[1,-1]]) / np.sqrt(2) # アダマールゲート定義
CX = np.array([[I, O],[O, X]]) # CXゲート定義
zero_state = np.array([1, 0]) # |0>状態定義
one_state = np.array([0, 1]) # |1>状態定義
H_node = tn.Node(H, name="Hadamard Gate") # アダマールゲートノード化
CX_node = tn.Node(CX, name="Controlled NOT Gate") # CXゲートノード化
init_node_1 = tn.Node(zero_state, name="zero_init_state") # 初期状態|q_1>=|0>ノード化
init_node_0 = tn.Node(zero_state, name="zero_init_state") # 初期状態|q_0>=|0>ノード化
zero_final_node_1 = tn.Node(zero_state, name="zero_final_state") # 終状態|q'_1>=|0>ノード化
zero_final_node_0 = tn.Node(zero_state, name="zero_final_state") # 終状態|q'_0>=|0>ノード化
one_final_node_1 = tn.Node(one_state, name="one_final_state") # 終状態|q'_1>=|1>ノード化
one_final_node_0 = tn.Node(one_state, name="one_final_state") # 終状態|q'_0>=|1>ノード化
final_node_1 = zero_final_node_1 # 終状態の設定|q'_1>
final_node_0 = zero_final_node_0 # 終状態の設定|q'_0>
# |q_1>から|q'_1>までの脚の結合
tn.connect(H_node[1], init_node_1[0]) # 初期状態|q_1>とアダマールゲートの結合
tn.connect(CX_node[1], H_node[0]) # アダマールゲートとCXゲートの結合
tn.connect(final_node_1[0], CX_node[0]) # CXゲートと終状態|q'_1>の結合
# |q_0>から|q'_0>までの脚の結合
tn.connect(CX_node[3], init_node_0[0]) # 初期状態|q_0>とCXゲートの結合
tn.connect(final_node_0[0], CX_node[2]) # CXゲートと終状態|q'_0>の結合
# 縮約
result = tn.contractors.auto(
[init_node_1, init_node_0, H_node, CX_node, final_node_1, final_node_0]
)
# 確率計算
np.abs(result.tensor)**2
final_node_1がfinal_node_0がzero_final_node_1 zero_final_node_0、one_final_node_1 one_final_node_0を代入することで
の4パターンを観測する確率が計算できます。計算結果を共有します。
| 観測した量子状態 |
TensorNetworkで計算した確率 | 正解の確率 |
|---|---|---|
| 0.4999999999999999 | 1/2 | |
| 0.0 | 0 | |
| 0.0 | 0 | |
| 0.4999999999999999 | 1/2 |
正解の確率と同じ結果が得られていることが確認できます[3]。
最後に
一通り量子回路をテンソルネットワークで記述するところまで説明ができました。本当は説明したりない箇所がいくつかあったりするのですが、これ以上記述すると記事が長くなってしまうので一旦ここで終わらせておきます。最後まで読んでいただきありがとうございました!
追記:量子もつれ状態生成とそれを観測するコードを↓に格納しています。
Appendix
参考文献
[1] 西野 友年(著)「テンソルネットワーク入門」株式会社講談社サイエンティフィク
[2] 量子ソフトウェア産学協働ゼミ https://github.com/utokyo-qsw/joint-seminar
実行環境
OS: Windows 11
Python: 3.10.6
notebook: 7.0.6
tensornetwork: 0.4.6
numpy: 1.26.2
-
「結合させること」=「縮約(後述)を取る」と解釈する場合があります。本記事ではダイアグラム上で脚と脚がくっついているだけで、この時点では縮約は取っていないことに注意してください。 ↩︎
-
ちなみに
connect = A_Node[1] ^ v_Node[0]という書き方も可能です。これを実行する場合はimport numpyの箇所から再実行してください。connect = tn.connect(A_Node[1], v_Node[0])を実行してからconnect = A_Node[1] ^ v_Node[0]を実行するとすでにエッジが結合しているためエラーを吐きます。 ↩︎ -
実は確率を得るだけならば
での縮約までとる必要はないんですが、|q'_{1}q'_{0}\rangle という状態を観測することを実感してもらうため|q'_{1}q'_{0}\rangle の縮約も含めています。 ↩︎|q'_{1}q'_{0}\rangle
Discussion