量子状態(QStateクラス) ====================== ## 量子状態の操作 ### 初期化 量子計算を実行するにあたり、まず、量子状態を生成・初期化する必要があり ます。それには、計算に必要な量子ビット数を引数にしてQStateクラスのコン ストラクタを呼び出します。例えば、2量子ビットの初期状態|00>は以下のよ うに取得できます。 >>> qs = QState(quit_num=2, seed=123) 量子計算の結果は確率的なので、計算の結果は毎回変わります。qlazyではそ のような状況を、内部で乱数を生成することによってシミュレートしています。 しかし、固定した結果を毎回得たい場合もあります。そのような際には、以下 のようにseedオプションを指定すれば良いです。 >>> qs = QState(qubit_num=2, seed=123) この初期化について、2つ注意事項があります。 1. 状態は常に|00...0>になります。どれかを|1>にする初期化機能は用意して いません。それをしたい場合はパウリXゲートを使ってビット反転してください。 2. 指定できる量子ビット数の上限は30です。30を超えるとエラーになります。 また、numpyのベクトルを使って、量子状態を初期化することもできます。使 い方は、以下です。 >>> import numpy as np >>> vec = np.array([1,0,0,0]) >>> qs = QState(vector=vec) 指定するベクトルの次元は2のべき乗である必要があります。 ### 複製 量子状態を複製することができます。シミュレータならではの機能です。以下 のようにcloneメソッドを呼び出します。 >>> qs_clone = qs.clone() ### リセット すでに生成済みの量子状態を破棄することなく、再度初期化して使いたい場合 は、reset()メソッドを使います。 >>> qs.reset() とすれば、qsは強制的に|00...>となります。また、 >>> qs.reset(qid=[1,5]) のように、量子番号リストを引数で与えると、その番号に対応した量子ビット のみを強制的に|0>にすることもできます。 特定の量子ビットだけをリセットする場合、内部的には当該量子ビットを測定 してからリセットするようにしているため、当該量子ビットと他の量子ビット がエンタングルしている場合、リセットの影響が他の量子ビットにも及びます。 つまり、実行のたびに結果が変わる可能性がありますので、ご注意ください。 ### メモリ解放 量子状態インスタンスのメモリは使用されなくなったら自動的に解放されますが、 明示的に解放したい場合、 >>> del qs とすれば好きなタイミングで解放することができます。 クラス・メソッド'del_all'を使えば複数の量子状態のインスタンスを一気に 解放することができます。 >>> qs_0 = QState(1) >>> qs_1 = QState(1) >>> qs_2 = QState(1) >>> ... >>> QState.del_all(qs_0, qs_1, qs_2) このとき、引数に指定するのは量子状態のリストやタプルであっても良いですし、 それらの入れ子でもOKです。例えば、 >>> qs_A = [qs_1, qs_2] >>> QState.del_all(qs_0, qs_A) >>> >>> qs_B = [qs_3, [qs_4, qs_5]] >>> QState.del_all(qs_B) という指定の仕方でも大丈夫です。 ### ゲート演算 量子状態の生成・初期化ができたら、各種ゲート演算を行います。 #### パウリX,Y,Zゲート >>> qs.x(q) >>> qs.y(q) >>> qs.z(q) qには量子ビット番号を指定します(以下、同様)。 #### ルートパウリXゲート 2乗したらXになるゲートです。 >>> qs.xr(q) >>> qs.xr_dg(q) # エルミート共役 #### アダマールゲート >>> qs.h(q) #### 位相シフトゲート >>> qs.s(q) >>> qs.t(q) >>> qs.s_dg(q) # エルミート共役 >>> qs.t_dg(q) # エルミート共役 #### 回転ゲート >>> qs.rx(q, phase=xxx) # X軸周りの回転 >>> qs.ry(q, phase=xxx) # Y軸周りの回転 >>> qs.rz(q, phase=xxx) # Z軸周りの回転 phase(実数値)が指定されなければ0ラジアンとみなされます(つまり何もし ない)。phaseに指定する値の単位はPIラジアンです。なので、0.5は0.5*PIラ ジアンを意味します。 #### 制御ユニタリゲート パウリX,Y,Zゲート、ルートパウリXゲート、アダマールゲート、位相シフトゲー ト、回転ゲートを各々制御ユニタリ化したゲートです。引数q0,q1は各々量 子ビット番号を表しています(以下、同様)。q0が制御量子ビット、q1が標 的量子ビットです。 >>> qs.cx(q0,q1) # 制御Xゲート(制御NOT,CNOT) >>> qs.cy(q0,q1) # 制御Xゲート >>> qs.cz(q0,q1) # 制御Zゲート >>> qs.cxr(q0,q1) # 制御XR(ルートパウリX)ゲート >>> qs.cxr_dg(q0,q1) # 制御XR+(ルートパウリX)ゲート (エルミート共役) >>> qs.ch(q0,q1) # 制御Hゲート >>> qs.cs(q0,q1) # 制御Sゲート >>> qs.cs_dg(q0,q1) # 制御S+ゲート (エルミート共役) >>> qs.ct(q0,q1) # 制御Tゲート >>> qs.ct_dg(q0,q1) # 制御T+ゲート (エルミート共役) >>> qs.cp(q0,q1, phase=xxx) # 制御位相シフトゲート >>> qs.crx(q0,q1, phase=xxx) # 制御X軸回転ゲート >>> qs.cry(q0,q1, phase=xxx) # 制御Y軸回転ゲート >>> qs.crz(q0,q1, phase=xxx) # 制御Z軸回転ゲート ### イジング結合ゲート(Ising coupling gate) 翻訳適当? イオントラップ方式の量子コンピュータで基本となる2量子ビットゲートです。 >>> qs.rxx(q0,q1, phase=xxx) # XX演算子に対するイジング結合ゲート >>> qs.ryy(q0,q1, phase=xxx) # YY演算子に対するイジング結合ゲート >>> qs.rzz(q0,q1, phase=xxx) # ZZ演算子に対するイジング結合ゲート #### 交換ゲート(スワップゲート) >>> qs.sw(q0,q1) # q0とq1を入れ替え #### トフォリゲート >>> qs.ccx(q0,q1,q2) # q0,q1: 制御ビット #### 制御スワップゲート(フレドキンゲート) >>> qs.csw(q0,q1,q2) # q0: 制御ビット #### マルチ制御Xゲート 通常のトフォリゲートは、2つの制御ビットと1つの標的ビットを持つ制御Xゲー トですが、3つ以上の制御ビットを持つ制御Xゲート演算を実行できます。ここ では「マルチ制御Xゲート」と呼ぶことにします。 >>> qs.mcx(qid=[q0,q1,..]) 任意の数の量子ビットを対象に演算することができますので、引数には量子ビッ ト番号のリストを指定します。そのリストの一番最後の要素が標的ビット番号、 それ以外の要素が制御ビット番号のリストと解釈されます。 ここで、引数は量子ビット番号の「リスト」であることにご注意ください。 qlazyでは、数が不定の量子ビット番号を指定する必要がある場合、メソッド や関数に「リスト」として与えます(という仕様上のルールにしています)。 #### 量子フーリエ変換 量子フーリエ変換を実行することができます。 >>> qs.qft(qid=[q0,q1,..]) 逆量子フーリエ変換も可能です。 >>> qs.iqft(qid=[q0,q1,..]) #### カスタム・ゲートの追加 QStateクラスを継承することで、自分専用の量子ゲートを簡単に作成・追加す ることができます。ベル状態を作成する回路をbellメソッドとして、QStateを 継承したMyQStateクラスに追加する例を示します。 >>> class MyQState(QState): >>> def bell(self, q0, q1): >>> self.h(q0).cx(q0,q1) >>> return self >>> >>> qs = MyQState(qubit_num=2) >>> qs.bell(0,1) >>> ... これは非常に簡単な例なのであまりご利益を感じないかもしれませんが、大規 模な量子回路を作成したい場合など、便利に使える場面は多いと思います。 ### パウリ積の演算 パウリ演算子X,Y,Zのテンソル積を定義して量子状態に演算することができま す。パウリ積を扱うために、まず、 >>> from qlazy import QState, PauliProduct のようにPauliProductクラスをimportする必要があります。例えば、3量子ビッ トの状態に対して、X2 Y0 Z1というパウリ積を演算したい場合、 >>> qs = QState(qubit_num=3) >>> pp = PauliProduct(pauli_str="XYZ", qid=[2,0,1]) >>> qs.operate_pp(pp=pp) のようにoperate_ppメソッドのppオプションにPauliProductのインスタンスを指 定します。制御化されたパウリ積はoperate_ppメソッドのqctrlオプションに制御 量子ビット番号を指定することで実現できます。以下のようにします。 >>> qs = QState(qubit_num=4) >>> pp = PauliProduct(pauli_str="XYZ", qid=[0,1,2]) >>> qs.operate_pp(pp=pp, qctlr=3) ### 量子回路の演算 量子状態に量子回路を演算することができます。量子回路はQCircクラスを使って以下のように用意します。 >>> from qlazy import QCirc >>> qc = QCirc().h(0).cx(0,1) >>> qc.show() q[0] -H-*- q[1] ---X- これを量子状態に演算するために'operate_qcirc'メソッドを使います。 >>> qs = QState(qubit_num=2) >>> qs.operate_qcirc(qc) 'operate_pp'と同様に制御ビットを追加することもできます。 >>> qs.operate_qcirc(qc, qctrl=3) ここで1点注意事項があります。演算できる量子回路はユニタリに限ります。 測定ゲートのような非ユニタリゲートを含むものは演算できません。 ## 量子状態の表示 ### 量子ビット全体の状態表示 showメソッドで量子状態を表示することができます。 >>> qs = QState(qubit_num=2).h(0).cx(0,1) >>> qs.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 |++++++ ### 特定量子ビットの状態表示 特定の量子ビットを選択して表示することもできます。例えば、 >>> qs = QState(qubit_num=3) >>> qs.h(0) >>> qs.h(2) >>> qs.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 |++++ また、 >>> qs.show(qid=[1]) とすると、以下になります。 c[0] = +1.0000+0.0000*i : 1.0000 |+++++++++++ c[1] = +0.0000+0.0000*i : 0.0000 | 表示したい量子ビットとそれ以外の量子ビットがエンタングルしている場合、 表示結果は確率的になります。例えば、 >>> qs = QState(qubit_num=2).h(0).cx(0,1) >>> qs.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 |++++++ のようなエンタングルした状態があったときに、 >>> qs.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'オ プションを使えばできます。 >>> qs.show(nonzero=True) とすると、 c[00] = +0.7071+0.0000*i : 0.5000 |++++++ c[11] = +0.7071+0.0000*i : 0.5000 |++++++ のように非ゼロ成分のみが表示されます。状態ベクトルの成分のほとんどが ゼロになっているようなものをコンパクトに表示したい場合にお使いください。 ### 正規化に関する注意事項 ノルムを1にする正規化に加えて、全体にかかっている位相項(グローバル位 相項)を括り出して、これを無視して表示するようにしています。グローバル 位相項を括り出す際には、C[00..0]の係数が正の実数になるようにしています。 C[00..0]が0の場合は、グローバル位相項を括りだすことはしません。 通常はこれで問題なく状態表示ができますが、グローバル位相項を括りだす際 にc[00..0]以外の係数を正の実数にしたい場合があります(c[00..0]が0だった 場合など)。その場合、showメソッドのprealオプションに何番目の係数を正の 実数にしたいかを指定します。2番目の係数を正の実数に正規化して表示した い場合は、 >>> qs.show(preal=2) のようにします。また、グローバル位相を括りだしたくない場合は、 >>> qs.show(preal=-1) のようにprealに-1を指定します。 ### 量子状態のデータ取得 get_ampメソッドで、特定の量子ビットに対応した確率振幅の複素係数をnumpy 配列として取得できます。qidを指定しない場合は、全量子ビットに対応した 確率振幅となります。 >>> qs.get_amp() # 全量子ビットに対する確率振幅 >>> qs.get_amp(qid=[0,3]) # 0番目と3番目の量子ビットに対する確率振幅 引数を指定しないget_ampメソッドをampプロパティとして定義していますので。 qs.get_amp()と同じ結果は、以下でも取得できます。 >>> qs.amp 指定した量子ビットとそれ以外がエンタングルしている場合、実行のたびに結 果が変わりますのでご注意ください(showメソッドと同様)。 また、確率振幅でなく各々の絶対値の2乗、つまり各固有状態に対応した確率は、 get_probメソッドを使って、 >>> prob = qs.get_prob(qid=[q0,q1,...]) で取得することができます。probは、ビット文字列をキー、 確率値をバリューとした辞書データです。 >>> print(prob) {'00': 0.5, '11': 0.5} ### 部分系 特定の量子ビットに対応した部分系の量子状態を取得できます。 >>> qs_partial = qs.partial(qid=[1,3]) 指定した量子ビットとそれ以外がエンタングルしている場合、実行のたびに結 果が変わりますのでご注意ください(showメソッドと同様)。 ### ブロッホ球上での座標 blochメソッドで、特定の1つの量子ビットに対応したブロッホ球での座標値 を取得できます。thetaをZ軸とのなす角、phiをZ軸周りの回転角とすると、以 下のように座標値を取得できます。角度の単位はpi(ラジアン)です。0.5は 0.5*piを表します。 >>> theta, phi = qs.bloch(q) ただし、複数の量子ビットを同時に指定することはできません。引数を指定し ない場合、0が指定されたものとみなされます。 また、指定した量子ビットとそれ以外がエンタングルしている場合、実行のた びに結果が変わりますのでご注意ください(showメソッドと同様)。 ## 測定 ### 測定の実行 mメソッドに、以下のような引数を与えて実行します。 >>> md = qs.m(qid=[0,3], shots=100, angle=0.5, phase=0.25) qid,shots,angle,phaseの指定をしない場合、通常のZ軸方向の測定を1回実施 します。qidには 測定したい量子ビットの番号リストを指定します。shotsには測定回数を指定 します。angle,phaseは測定の方向を表しており、angleにはZ軸とのなす角を 単位PIラジアンで指定します。phaseにはX軸プラス方向を基準にしたZ軸周り の角度を単位PIラジアンで指定します。ブロッホ球の2つの角をイメージして もらえれば良いです。この測定メソッドは、測定クラスMDataのインスタンス を返すので、それを例えば変数mdで受けるというのが、一つの使い方です。 X軸、Y軸、Z軸方向の測定をするメッソドも用意されています。 >>> md = qs.mx(qid=[0,2], shots=1000) >>> md = qs.my(qid=[0,2], shots=1000) >>> md = qs.mz(qid=[0,2], shots=1000) mzメソッドはmメソッドと全く同じものです。 また、ベル測定のメソッドもあります。 >>> md = qs.mb(qid=[0,2], shots=1000) 測定のメソッドの後にメソッド・チェーンでQStateのメソッドをつなげること はできませんので、ご注意ください。つまり、 >>> qs.h(0).cx(0,1).m(qid=[0],shots=10).x(0).m(qid=[1],shots=20) というメソッド・チェーンは実行できません。この演算をやりたい場合は、 >>> qs.h(0).cx(0,1).m(qid=[0],shosts=10) >>> qs.x(1).m(qid=[1], shots=20) という具合に、2行に分けてください。 ### 測定結果の表示 showメソッドで表示します。 >>> md.show() direction of measurement: z-axis frq[00] = 49 frq[11] = 51 last state => 00 Z軸方向以外の測定結果の表示例を以下に示します。Z軸方向の測定結果は |0>,|1>ですが、それ以外の方向は|0>,|1>にはならないので、|u>,|d>になるとして、結果を表しています。 X軸方向の測定結果 >>> md.show() direction of measurement: x-axis frq[u] = 49 frq[d] = 51 last state => u Y軸方向の測定結果 >>> md.show() direction of measurement: y-axis frq[u] = 51 frq[d] = 49 last state => d 任意方向の測定結果(angle=0.2, phase=0.3の場合) direction of measurement: theta=0.200*PI, phi=0.300*PI frq[u] = 90 frq[d] = 10 last state => u ベル測定の結果 bell-measurement frq[phi+] = 47 frq[phi-] = 53 last state => phi+ # phi+,phi-,psi+,psi-のどれか ### 測定データの取得 frqプロパティで頻度のリスト、lstプロパティで最後の測定結果を取得できます。 >>> md.frq # 頻度リスト >>> md.lst # 最後の結果 頻度のリストは測定値を十進数で表したときの降順で頻度が格納されたリスト です。例えば、|00>が48回、|11>が52回、それ以外が0回という、2量子ビット の測定は、 >>> print(md.frq) [48,0,0,52] となります。 最後の結果は、測定値を十進数に変換した値です。Z軸方向以外の測定の場合、 u=>0,d=>1のように読み替えて、十進数にしています。ベル測定の場合は、 phi+ => 0, phi- => 3, psi+ => 1, psi- => 2と定義しています。 が、frq,lstプロパティの出力値は正直わかりにくいです(自分で作っておき ながら言うのも何ですが)。おすすめは以下です。 頻度のリストをpython標準のコンテナデータ型のサブクラスCounter形式で取 得したい場合はfrequencyプロパティを、測定値をバイナリの文字列で取得し たい場合はlastプロパティを使います。 >>> print(md.frequency) # 頻度リスト Counter({'00':53,'11':47}) >>> print(md.last) # 最後の結果 11 ### 一回限りの測定 計算基底で単純に1回だけ測定してその測定値を得たい場合、measureメソッ ドを使うこともできます。 >>> qs = QState(qubit=2).h(0).cx(0,1) >>> mval = qs.measure(qid=[0,1]) >>> print(mval) 11 とやればmvalに文字列'00'または'11'が格納されます。そして、量子状態は測 定後の状態になります(例えば以下のように)。 >>> qs.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 |+++++++++++ ## 量子状態に関する計算 ### 2つの量子状態の内積 QStateクラスのinproメソッドを使います。使用例を以下に示します。 >>> qs_0 = QState(2) >>> qs_1 = QState(2).x(0).x(1) >>> v = qs_0.inpro(qs_1) 状態|00>と状態|11>の内積を計算しています。 ### 2つの量子状態の忠実度 QStateクラスのfidelityメソッドを使います。使用例を以下に示します。 >>> qs_0 = QState(2) >>> qs_1 = QState(2).x(0).x(1) >>> v = qs_0.fidelity(qs_1) 状態|00>と状態|11>の内積の絶対値を計算しています。 ### 2つの量子状態のテンソル積 QStateクラスのtensproメソッドを使います。使用例を以下に示します。 >>> qs_1 = QState(1).x(0) >>> qs_2 = QState(2).h(0).cx(0,1) >>> qs_3 = qs_1.tenspro(qs_2) |1>と(|00>+|11>)/sqrt(2)のテンソル積を作成し、変数qs_3に格納しています。 ### 量子状態の複合 QStateクラスのcompositeメソッドを使います。 >>> qs_com = qs.composite(4) qsを4つ複合した状態が生成されます。 ### 量子状態への行列適用 QStateクラスのapplyメソッドを使います。行列をnumpyの配列として定義し、 applyメソッドの引数として渡します。また、適用したい量子ビット番号リス トを指定することもできます。使用例を以下に示します。 >>> import numpy as np >>> qs = QState(5) >>> M = np.array([[0,1],[1,0]]) >>> qs.apply(matrix=M, qid=[2]) 5量子ビット状態|00000>の2番目の量子ビットにXゲートを適用しています。 量子状態の次元よりも変換行列の次元の方が小さくなくてはいけません。また、 qidで指定する引数のリストのサイズをnとしたとき、行列の行数、列数はとも に2^nでなくてはなりません。applyメソッドは、元のインスタンスの内容を変 えます。ご注意ください。 ### シュミット分解 量子状態をシュミット分解した結果のシュミット係数と各基底状態を計算する ことができます。例えば、5量子ビットの状態qsがあったとします。これを量 子ビット[0,1]番目の系と[2,3,4]番目の系にシュミット分解したいとします。 schmidt_decompメソッドを使って、以下のようにします。 >>> coef,qs_0,qs_1 = qs.schmidt_decomp(qid_0=[0,1], qid_1=[2,3,4]) この計算の結果、シュミットランクが4だったとします。coefは4個のシュミッ ト係数(実数値)を要素とするリストになります。qs_0は量子ビットが[0,1] 番目の系に関する4個の基底状態のリスト、qs_1は量子ビットが[2,3,4]番目の 系に関する4個の基底状態のリストとなります。 シュミット係数だけを取得したい場合は、schmidt_coefメソッドを使います。 >>> coef = qs.schmidt_coef(qid_0=[0,1], qid_1=[2,3,4]) どちらのメソッドも0となるシュミット係数を無視しますので、len(coef)がシュ ミットランクになります。シュミットランクが1だった場合、[0,1]番目の系と [2,3,4]番目の系は、テンソル積で分解できる、つまりエンタングルしていな いということなので、シュミット分解はエンタングルメント判定器として使用 することもできます。 ### オブザーバブルの期待値 量子状態に対するオブザーバブルの期待値を計算できます。QStateクラスの 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のドキュメント を参照してください)。 現在の量子状態がqsで与えられているとすると、 >>> exp = qs.expect(observable=ob) のようにして期待値expを求めることができます。 ### 量子状態の時間発展(オブザーバブルをハミルトニアンとする量子系の時間発展) 系のハミルトニアンが与えられたら、量子状態の時間発展は形式的にはユニタ リ演算子として記述できます。このユニタリ演算子のことを「時間発展演算子」 と呼びます。特に、スピンのような2準位の多体系の場合、この時間発展演算 子は、量子回路によって近似的に表現できることが知られています。qlazyで は、この計算を実装しています。QStateクラスのevolveメソッドを使います。 使用例を以下に示します。 >>> qs = QState(2) >>> hm = -2.0*Z(0) + Z(0)*Z(1) + X(0) + X(1) >>> qs.evolve(observable=hm, time=0.1, iteration=10) 1行目で、2粒子を定義しています。量子状態は|00>に初期化されます。2行 目で、"-2.0*Z0*Z1+X0+X1"というパウリ行列で記述されるハミルトニアンを定 義しています。3行目で、このハミルトニアンで規定される時間発展を量子状 態qsに施しています。ここで、引数timeは時間です。iterationは近似精度を 上げるために内部で処理される繰り返し数(整数値)です。timeよりも十分大き な値を指定してください。 以上