行列積状態(MPStateクラス)¶
行列積状態の操作¶
量子状態ベクトルを使って量子計算のシミュレーションが実行できますが、量 子ビット数Nに対してO(2^N)のメモリが必要になるため、あまり大きなサイズ の量子計算は実行できません。現在普及している一般的なPCレベルだと、25〜 30量子ビットくらいが限界になります。しかし、現実的な量子状態を考えると 必ずしもO(2^N)の自由度をフルに使わないといけないわけではありません。必 要な自由度に応じて量子状態を圧縮してうまく表現する手法として、テンソル ネットワークを用いた行列積状態(matrix product state)で量子状態を表現す る手法が知られています。qlazyではMPStateクラスを使って行列積状態をシミュ レーションすることができます。内部でgoogleの tensornetworkを使っています。
簡単な例¶
はじめに簡単な例を示します。
>>> from qlazy import MPState
>>>
>>> mps = MPState(qubit_num=100)
>>> mps.h(0)
>>> [mps.cx(i,i+1) for i in range(99)]
>>> md = mps.m(shots=100)
>>> print(md.frequency)
Counter({'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000': 55, '1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111': 45})
QStateと同様に量子ゲートを次々に適用して最後に測定メソッドにより測定値 を得ることができます。QStateとの大きな違いは、25〜30量子ビットを超える 量子状態でも難なく計算できるということです(大量にエンタングルしてしま うような計算は難しいのですが…)。
MPStateクラスの使い方について以下で詳細に説明します。
生成・初期化¶
MPStateクラスのコンストラクタに量子ビット数を指定することで、そのイン スタンスを作成できます。
>>> from qlazy import MPState
>>> mps = MPState(qubit_num=100)
ゲート演算¶
行列積状態として表現された量子状態に対して以下のようにゲート演算をする ことができます。記法および対応しているゲートはQStateと全く同様です。
>>> mps.h(0).cx(0,1)
>>> ...
カスタム・ゲートの追加¶
MPStateクラスを継承することで、自分専用の量子ゲートを簡単に作成・追加す ることができます。ベル状態を作成する回路をbellメソッドとして、MPStateを 継承したMyMPStateクラスに追加する例を示します。
>>> class MyMPState(MPState):
>>> def bell(self, q0, q1):
>>> self.h(q0).cx(q0,q1)
>>> return self
>>>
>>> mps = MyMPState(qubit_num=2)
>>> mps.bell(0,1)
>>> ...
パウリ積の演算¶
パウリ演算子X,Y,Zのテンソル積を定義して行列積状態に演算することができま す。パウリ積を扱うために、まず、
>>> from qlazy import MPState, PauliProduct
のようにPauliProductクラスをimportする必要があります。例えば、3量子ビッ トの行列積状態mpsに対して、X2 Y0 Z1というパウリ積を演算したい場合、
>>> mps = MPState(qubit_num=3)
>>> pp = PauliProduct(pauli_str="XYZ", qid=[2,0,1])
>>> mps.operate_pp(pp=pp)
のようにoperate_ppメソッドのppオプションにPauliProductのインスタンスを指 定します。制御化されたパウリ積はoperate_ppメソッドのqctrlオプションに制御 量子ビット番号を指定することで実現できます。以下のようにします。
>>> mps = MPState(qubit_num=4)
>>> pp = PauliProduct(pauli_str="XYZ", qid=[2,0,1])
>>> mps.operate_pp(pp=pp, qctrl=3)
量子回路の演算¶
量子状態に量子回路を演算することができます。量子回路はQCircクラスを使って以下のように用意します。
>>> from qlazy import QCirc
>>> qc = QCirc().h(0).cx(0,1)
>>> qc.show()
q[0] -H-*-
q[1] ---X-
これを行列積状態に演算するために’operate_qcirc’メソッドを使います。
>>> mps = MPState(qubit_num=2)
>>> mps.operate_qcirc(qc)
‘operate_pp’と同様に制御ビットを追加することもできます。
>>> mps.operate_qcirc(qc, qctrl=3)
ここで1点注意事項があります。演算できる量子回路はユニタリに限ります。 測定ゲートのような非ユニタリゲートを含むものは演算できません。
メモリ解放¶
行列積状態インスタンスのメモリは使用されなくなったら自動的に解放されますが、 明示的に解放したい場合、
>>> del mps
とすれば好きなタイミングで解放することができます。
クラス・メソッド’del_all’を使えば複数の密度演算子のインスタンスを一気に 解放することができます。
>>> mps_0 = MPState(qubit_num=2)
>>> mps_1 = MPState(qubit_num=3)
>>> mps_2 = MPState(qubit_num=4)
>>> ...
>>> MPState.del_all(mps_0, mps_1, mps_2)
このとき、引数に指定するのは行列積状態のリストやタプルであっても良いで すし、それらの入れ子でもOKです。例えば、
>>> mps_A = [mps_1, mps_2]
>>> MPState.del_all(mps_0, mps_A)
>>>
>>> mps_B = [mps_3, [mps_4, mps_5]]
>>> MPState.del_all(de_B)
という指定の仕方でも大丈夫です。
リセット¶
すでに生成済みの行列積状態を破棄することなく、再度初期化して使いたい場 合は、reset()メソッドを使います。
>>> mps.reset()
とすれば、mpsは強制的に|00…0>となります。
また、
>>> mps.reset(qid=[1,5])
のように、量子番号リストを引数で与えると、その番号に対応した量子ビット のみを強制的に|0>にすることもできます。
特定の量子ビットだけをリセットする場合、内部的には当該量子ビットを測定 してからリセットするようにしているため、当該量子ビットと他の量子ビット がエンタングルしている場合、リセットの影響が他の量子ビットにも及びます。 つまり、実行のたびに結果が変わる可能性がありますので、ご注意ください。
行列積状態の表示¶
量子ビット全体の状態表示¶
QStateと同様にshowメソッドで量子状態を表示することができます。
>>> mps = MPState(qubit_num=2).h(0).cx(0,1)
>>> mps.show()
c[00] = +0.7071+0.0000*i : 0.5000 |++++++
c[01] = +0.0000+0.0000*i : 0.0000 |
c[10] = +0.0000+0.0000*i : 0.0000 |
c[11] = +0.7071+0.0000*i : 0.5000 |++++++
内部で行列積状態から量子状態ベクトルに変換しているので、量子ビット数が 大きな行列積状態を表示しようとするとメモリが足りなくなる可能性がありま すので、ご注意ください。
特定量子ビットの状態表示¶
特定の量子ビットを選択して表示することもできます。例えば、
>>> mps = MPState(qubit_num=3)
>>> mps.h(0)
>>> mps.h(2)
>>> mps.show(qid=[0,2])
とすると、0番目と2番目の量子ビットに関する量子状態が表示されます。結果 は以下になります。
c[00] = +0.5000+0.0000*i : 0.2500 |++++
c[01] = +0.5000+0.0000*i : 0.2500 |++++
c[10] = +0.5000+0.0000*i : 0.2500 |++++
c[11] = +0.5000+0.0000*i : 0.2500 |++++
また、
>>> mps.show(qid=[1])
とすると、以下になります。
c[0] = +1.0000+0.0000*i : 1.0000 |+++++++++++
c[1] = +0.0000+0.0000*i : 0.0000 |
表示したい量子ビットとそれ以外の量子ビットがエンタングルしている場合、 表示結果は確率的になります。例えば、
>>> mps = MPState(qubit_num=2).h(0).cx(0,1)
>>> mps.show()
c[00] = +0.7071+0.0000*i : 0.5000 |++++++
c[01] = +0.0000+0.0000*i : 0.0000 |
c[10] = +0.0000+0.0000*i : 0.0000 |
c[11] = +0.7071+0.0000*i : 0.5000 |++++++
のようなエンタングルした状態があったときに、
>>> mps.show(qid=[0])
とすると、
c[0] = +1.0000+0.0000*i : 1.0000 |+++++++++++
c[1] = +0.0000+0.0000*i : 0.0000 |
となったり、
c[0] = +0.0000+0.0000*i : 0.0000 |
c[1] = +1.0000+0.0000*i : 1.0000 |+++++++++++
となったりします。
非ゼロ成分のみ表示¶
確率振幅がゼロでない成分だけを表示したい場合、以下のように’nonzero’オ プションを使います。
>>> mps.show(nonzero=True)
とすると、
c[00] = +0.7071+0.0000*i : 0.5000 |++++++
c[11] = +0.7071+0.0000*i : 0.5000 |++++++
のように非ゼロ成分のみが表示されます。状態ベクトルの成分のほとんどが ゼロになっているようなものをコンパクトに表示したい場合にお使いください。
正規化に関する注意事項¶
showメソッドで表示する際、ノルムを1にする正規化を行うとともに、全体に かかっている位相項(グローバル位相項)を括り出して、これを無視して表示 するようにしています。グローバル位相項を括り出す際には、C[00..0]の係数 が正の実数になるようにしています。C[00..0]が0の場合は、グローバル位相 項を括りだすことはしません。
通常はこれで問題なく状態表示ができますが、グローバル位相項を括りだす際 にc[00..0]以外の係数を正の実数にしたい場合があります(c[00..0]が0だった 場合など)。その場合、showメソッドのprealオプションに何番目の係数を正の 実数にしたいかを指定します。2番目の係数を正の実数に正規化して表示した い場合は、
>>> mps.show(preal=2)
のようにします。また、グローバル位相を括りだしたくない場合は、
mps.show(preal=-1)
のようにprealに-1を指定します。
行列積状態の確率振幅値の取得¶
get_ampメソッドで、特定の量子ビットに対応した確率振幅の複素係数をnumpy 配列として取得できます。qidを指定しない場合は、全量子ビットに対応した 確率振幅となります。
>>> vec = mps.get_amp() # 全量子ビットに対する確率振幅
>>> vec = mps.get_amp(qid=[0,3]) # 指定した量子ビットに対応した確率振幅
指定した量子ビットとそれ以外がエンタングルしている場合、実行のたびに結 果が変わりますのでご注意ください(showメソッドと同様)。
引数指定のないget_ampメソッドをampプロパティとして定義していますので、 qs.get_amp()と同じ結果は、以下でも取得できます。
>>> vec = mps.amp
測定¶
測定の実行¶
mメソッドでZ軸方向の測定(計算基底での測定)が実行できます。以下のような 引数を与えて実行します。
>>> md = qs.m(qid=[0,3], shots=100)
qidには測定したい量子ビットの番号リストを指定します。何も指定しない場 合、すべての量子ビットに関する測定が実行されます。shotsには測定回数を 指定します。何も指定しない場合、1回の測定が実行されます。この測定メソッ ドは、測定クラスMDataMPStateのインスタンスを返すので、それを例えば変数 mdで受けるというのが、一つの使い方です。
>>> mps = MPState(qubit_num=2)
>>> mps.h(0).cx(0,1)
>>> md = mps.m(qid=[0,1], shots=100)
測定値の頻度情報はpython標準のコンテナデータ型のサブクラスCounter形式 でfrequencyプロパティに格納されています。また、最後の測定値はバイナリ の文字列でlastプロパティに格納されています。各々、以下のように表示する ことができます。
>>> print(md.frequency) # 頻度情報
Counter({'00':53,'11':47})
>>> print(md.last) # 最後の結果
11
測定後の状態は、最後の測定結果に従い、
>>> mps.show()
c[00] = +0.0000+0.0000*i : 0.0000 |
c[01] = +0.0000+0.0000*i : 0.0000 |
c[10] = +0.0000+0.0000*i : 0.0000 |
c[11] = +1.0000+0.0000*i : 1.0000 |+++++++++++
のように変化します。
注意事項¶
数十〜数百量子ビットの量子回路でもそれほど深くない回路でエンタングルが あまりないような場合は難なく量子回路の演算が実行できたりするのですが、 最後に多数の量子ビットに対して大量のショット数で測定するようなことをす ると、測定結果取得に途方もない時間がかかる場合があります。
行列積状態を使ったシミュレーションの主な用途は、NISCデバイス上での量子 機械学習や量子化学計算や最適化問題の求解等のシミュレーションと思われま すので、測定値を取得するのではなく、後述の期待値計算でやってみることを とりあえずおすすめします。
一回限りの測定¶
計算基底で単純に1回だけ測定してその測定値を得たい場合、measureメソッ ドを使うこともできます。
>>> mps = MPState(qubit=2).h(0).cx(0,1)
>>> mval = mps.measure(qid=[0,1])
>>> print(mval)
>>> mps.show()
とやればmvalに文字列’00’または’11’が格納されます。量子状態は測定後の状 態になります。
11
c[00] = +0.0000+0.0000*i : 0.0000 |
c[01] = +0.0000+0.0000*i : 0.0000 |
c[10] = +0.0000+0.0000*i : 0.0000 |
c[11] = +1.0000+0.0000*i : 1.0000 |+++++++++++
行列積状態に関する計算¶
2つの行列積状態の内積¶
MPStateクラスのinproメソッドを使います。使用例を以下に示します。
>>> mps_0 = MPState(qubit_num=2)
>>> mps_1 = MPState(qubit_num=2).x(0).x(1)
>>> v = mps_0.inpro(mps_1)
0j
状態|00>と状態|11>の内積<00|11>を計算しています。
2つの量子状態の忠実度¶
MPStateクラスのfidelityメソッドを使います。使用例を以下に示します。
>>> mps_0 = MPState(qubit_num=2)
>>> mps_1 = MPState(qubit_num=2).x(0).x(1)
>>> v = qs_0.fidelity(qs_1)
0.0
状態|00>と状態|11>の内積の絶対値|<00|11>|を計算しています。
オブザーバブルの期待値¶
行列積状態に対するオブザーバブルの期待値を計算できます。MPStateクラス のexpectメソッドに引数としてObservableクラスのインスタンスを指定します。 使用例を以下に示します。
期待値を求めたい物理量(オブザーバブル)を
>>> ob = Observable("z_0 + 2.0 * z_1")
のように作成します(例えばZ0+2*Z1というオブザーバブルの場合)。あるいは、
>>> ob = Observable()
>>> ob.add_wpp(weight=1.0, pp=PauliPruduct('Z', [0]))
>>> ob.add_wpp(weight=2.0, pp=PauliPruduct('Z', [1]))
のように作成することもできます。あるいは、
>>> from qlazy.Observable import X, Y, Z
>>> ob = Z(0) + 2.0 * Z(1)
のように作成することもできます(詳細についてはObservableのドキュメント を参照してください)。
現在の行列積状態がmpsで与えられているとすると、
>>> exp = mps.expect(observable=ob)
のようにして期待値expを求めることができます。
以上