グローバーのアルゴリズム基礎(3):3量子ビット以上の場合
前回まででグローバーのアルゴリズムの基本要素である「オラクル回路」「位相反転増幅回路」の理論と実装の対応を解説しました。
前回までは2量子ビットの場合でしたが、一般に3量子ビットの場合の実装方法を解説します。
シリーズ予定:
対象とする読者
- 量子プログラミング・量子計算に興味を持って勉強し始めたエンジニア・高校生・大学生
- Groverのアルゴリズムについて改めて知りたい方 など
前提とする知識
- 簡単な論理演算(AND, OR, NOT)が分かること
- 基本的な量子ゲート(H, X, CX, CCX, Z, CZ)が分かること
- 線形代数(行列・ベクトルの積、直積、あとできれば線形変換)が分かること
- ベクトルのブラケット記法(など、もしくはDirac記法ともいう)が分かること
バージョン情報
- Python 3.7.3
- Qiskit 0.23.1
目次
Groverのアルゴリズムのおさらい
オラクル回路(量子オラクル)
オラクル回路は見つけ出したい量子状態をマークして、その状態の位相を反転する回路でした。 一般に量子オラクルはマーキングしたい状態をとし、その個数をとすると下記のように表します。
2量子ビットの場合での場合、この量子オラクルを通過後の状態は下記になります。
位相反転増幅回路
マーキングした状態の確率振幅の大きさを増幅する位相反転増幅は下記です。
2量子ビットの場合でがマーキングした状態とすると、位相反転増幅後は
ということでマーキングした状態を取り出すことができました。
2量子ビットの場合の量子回路
上記のオラクル回路・位相反転増幅回路を組み合わせたGrover回路の一例は下図です。
3量子ビット以上の場合に生じる課題
結論から説明すると、オラクル回路が現状のCZゲート
による実装方法では3量子ビット以上で困ります。
3量子ビットでは初期状態(オラクル回路への入力)が次の式のようになります。
例えばがマーキングしたい状態だとして、2量子ビットゲートであるCZゲート
だけではこの位相を反転させられません。
なお、位相反転増幅回路は元のまま、MultiControlledToffoliでよいです。
3量子ビット以上でのGroverのアルゴリズムの実装方法
3量子ビット以上の場合の量子回路
結論から、下記の量子回路を実装すればよいです。
(もちろん、マーキングを達成できればこれ以外でもいいと思います。ただ冗長な回路になると思います。)
qr = QuantumRegister(3, 'data') oracle = QuantumRegister(1, 'oracle') cr = ClassicalRegister(3, 'output') grover = QuantumCircuit(qr, oracle, cr) # 重ね合わせ状態の生成 grover.h(qr[:3]) grover.barrier() # オラクル grover.x(oracle) grover.h(oracle) grover.mct(qr, oracle) grover.h(oracle) grover.x(oracle) grover.barrier() # 位相反転増幅 grover.h(qr) grover.x(qr) grover.barrier(qr) grover.h(qr[-1]) grover.mct(qr[:-1], qr[-1]) grover.h(qr[-1]) grover.barrier(qr) grover.x(qr) grover.h(qr) # 測定 grover.measure(qr, cr) grover.draw(output='mpl')
※4つ目の量子ビット('oracle'と名付けたはオラクル回路のための補助ビットで、そのあとの演算では用いられません。
以降、N量子ビットのGroverのアルゴリズムを実装する場合はMCTゲートを下に伸ばしていく形で、最低でも(N + 1)量子ビットあれば実装可能です。
オラクル回路が正しく動作していることの確認
図2の量子回路が正しくGroverのアルゴリズムの実装となっていることを確かめます。
まず、オラクル回路の入力状態は下記です。
第4量子ビット(一番右)は図2の補助ビット('oracle')です。
そして、マーキングしたい状態が (対応する入力は)の場合、オラクル回路の出力は
でマーキング状態だけ位相が反転するようになっています。
シミュレータで動作を確認します。
statevector_simulatorで動作確認
ソース
qr = QuantumRegister(3, 'data') oracle = QuantumRegister(1, 'oracle') cr = ClassicalRegister(3, 'output') grover = QuantumCircuit(qr, oracle, cr) # 重ね合わせ状態の生成 grover.h(qr[:3]) grover.barrier() # オラクル grover.x(oracle) grover.h(oracle) grover.mct(qr, oracle) grover.h(oracle) grover.x(oracle) grover.barrier() backend = Aer.get_backend('statevector_simulator') job = execute(grover, backend=backend) result = job.result() states = result.get_statevector() state_key = 0 for state in states: print(f'{int(bin(state_key)[2:]):04}: {state}') state_key += 1 grover.draw(output='mpl')
この状態ベクトルの確率振幅のシミュレーション結果(状態: 確率振幅)は下記です。
(左から順に第4量子ビット、・・・、第1量子ビットで並んでいます。)
0000: (0.35355339059327384-1.2989340843532405e-16j) 0001: (0.35355339059327384-1.2989340843532405e-16j) 0010: (0.35355339059327384-1.2989340843532405e-16j) 0011: (0.35355339059327384-1.2989340843532405e-16j) 0100: (0.35355339059327384-1.2989340843532405e-16j) 0101: (0.35355339059327384-1.2989340843532405e-16j) 0110: (0.35355339059327384-1.2989340843532405e-16j) 0111: (-0.35355339059327384+1.7319121124709873e-16j) 1000: (-1.1496735851465458e-17+4.329780281177469e-17j) 1001: (-1.1496735851465458e-17+4.329780281177469e-17j) 1010: (-1.1496735851465458e-17+4.329780281177469e-17j) 1011: (-1.1496735851465458e-17+4.329780281177469e-17j) 1100: (-1.1496735851465458e-17+4.329780281177469e-17j) 1101: (-1.1496735851465458e-17+4.329780281177469e-17j) 1110: (-1.1496735851465458e-17+4.329780281177469e-17j) 1111: (1.1496735851465458e-17-5.166667716518644e-33j)
取りうる状態がすべてシミュレートされるので1000
以降は不要なのですが、大きさが限りになくゼロに近い振幅で表示されます。
0111
以前を確認するとマーキングしたい0111
だけ大きさが負になっており、目的の回路ができています。
位相反転増幅回路が正しく動作していることの確認
位相反転増幅回路の入力状態は下記です。
第4量子ビットには何も演算されないので位相反転増幅した結果は下記となります。
というように、マーキングしたの確率振幅が大きくなっています。
また、その大きさが規格化されていることも分かります。
statevector_simulatorで動作確認
ソース
qr = QuantumRegister(3, 'data') oracle = QuantumRegister(1, 'oracle') cr = ClassicalRegister(3, 'output') grover = QuantumCircuit(qr, oracle, cr) # 重ね合わせ状態の生成 grover.h(qr[:3]) grover.barrier() # オラクル grover.x(oracle) grover.h(oracle) grover.mct(qr, oracle) grover.h(oracle) grover.x(oracle) grover.barrier() # 位相反転増幅 grover.h(qr) grover.x(qr) grover.barrier(qr) grover.h(qr[-1]) grover.mct(qr[:-1], qr[-1]) grover.h(qr[-1]) grover.barrier(qr) grover.x(qr) grover.h(qr) backend = Aer.get_backend('statevector_simulator') job = execute(grover, backend=backend) result = job.result() states = result.get_statevector() state_key = 0 for state in states: print(f'{int(bin(state_key)[2:]):04}: {state}') state_key += 1 grover.draw(output='mpl')
この状態ベクトルの確率振幅のシミュレーション結果(状態: 確率振幅)は下記です。 (左から順に第4量子ビット、・・・、第1量子ビットで並んでいます。)
0000: (-0.1767766952966371+1.4071785913826776e-16j) 0001: (-0.17677669529663703+9.74200563264931e-17j) 0010: (-0.176776695296637+9.742005632649305e-17j) 0011: (-0.17677669529663703+5.4122253514718377e-17j) 0100: (-0.17677669529663703+1.190689577323804e-16j) 0101: (-0.17677669529663698+3.2473352108831075e-17j) 0110: (-0.17677669529663695+3.247335210883104e-17j) 0111: (-0.8838834764831851+8.551316055325503e-16j) 1000: (5.748367925732717e-18-3.247335210883103e-17j) 1001: (5.748367925732721e-18-3.247335210883103e-17j) 1010: (5.748367925732723e-18-3.247335210883103e-17j) 1011: (5.748367925732726e-18-3.247335210883103e-17j) 1100: (5.74836792573272e-18-3.247335210883103e-17j) 1101: (5.748367925732726e-18-3.247335210883103e-17j) 1110: (5.7483679257327305e-18-3.247335210883103e-17j) 1111: (2.874183962866363e-17-7.577115492060576e-17j)
0111
の状態が一番確率振幅の大きさが大きくなっています。
qasm_simulatorで動作確認
図2の回路で、測定をした場合の確率分布が下記です。