アダマールテスト
前回までで測定・基底・期待値・密度演算子など基本的な理解が進んだので、アルゴリズムの理解をしたいと思います。
今回はアダマールテスト。
後々に量子位相推定やHHLアルゴリズムなど種々のアルゴリズムに発展する基本的なアルゴリズムのようなのでやってみます。
教科書ではサラッと書かれていますが、ちゃんと計算しようとすると密度演算子の知識が必要です。
対象とする読者
- 量子プログラミングに興味を持ち始めたばかりのエンジニア
- 量子計算に興味を持ち始めたばかりの高校生・大学生 など
前提とする知識
- 簡単な論理演算(AND, OR, NOT)が分かること
- 基本的な量子ゲート(X, CX, CCX)が分かること
- ブラケットでの計算がわかること
- 密度演算子がわかること
バージョン情報
- Python 3.9.13
- Qiskit 0.20.2
目次
アダマールテストとは
量子回路
アダマールテストとは上図のように第一量子ビットは初期状態で、それ以外の任意の数の量子ビットは任意の状態を初期状態としています。
そして第一量子ビットにアダマールゲートをかけた後、第一量子ビットを制御ビットとしそれ以外の量子ビットに対して制御ユニタリーゲート(controlled - )をかけます。
最後に第一量子ビットにもう一度アダマールゲートをかけて第一量子ビットを測定するというものです。
用途
この回路を使うとユニタリー行列の固有値がわかります。
ただ、後で詳細説明しますが、量子回路に使われるユニタリー行列の固有値はなので、あえて量子コンピュータを使って求めるものではないです。 (私の私見のため、正しくないかもしれません。詳しい方がいたら補足お願いします。) アダマールテストを利用したスワップテストや量子位相推定アルゴリズムのことを考慮すると、むしろここで大事なのは「位相キックバック」と呼ばれる第一量子ビットの位相にユニタリーの固有値に相当する位相が乗る現象によって、第一量子ビットの確率分布がその分変化するということが重要と思います。
代数的に解いてみる
準備:ユニタリー行列とその固有値
ユニタリー行列とは
まず、ユニタリー行列は元々線形代数の言葉なので量子力学特有の名称・行列ではないです。
によると、自身と自身のエルミート共役(転置して複素共役を取ったもの)の積が単位行列になるものです。
数学的な定義だと僕も実はよくわからないのですが、物理学的には量子力学的に有効な操作(演算、つまり量子ビットをBloch球上で回転させること)であれば全てユニタリーです。 アダマールゲートやXゲートなどを行列でと表すなら、それらの逆回転がエルミート共役です。
もう少し細かく話すと、量子状態の時間発展は以下のシュレディンガー方程式で記述されます。
この式のはハミルトニアンを示します。 この式の解は初期時刻を用いて一般的に下記の形式で表されます。
この指数関数部分がユニタリー行列で表されます。 ハミルトニアンで記述される量子系はエネルギー保存している(だからハミルトニアンで書いているのですけど)ので、時間を巻き戻したら元に戻りますよねって話です。 物理学の言葉で「時間反転対称性」と言います。 巻き戻したら
となるだけで、これは
の変換なので、逆回転ということです。
時間反転について、ここでは複素共役しか言及してませんが、転置についてはハミルトニアンがエルミート行列(自身と自身のエルミート共役が同一)であることから言及しなくて良くなっています。
とすると
ユニタリー行列の固有値
これは数学の話なので、以下の記事での解説に譲ります。
エッセンスとして重要なことは以下です。
これらのことから、ユニタリ行列をその固有ベクトルである量子状態にかけた場合以下になります。
この固有値の位相が第一量子ビットの確率分布から分かるというのがアダマールテストです。
部分トレース
今回は第一量子ビットとそれ以外の量子ビットがあり、第一量子ビットだけ測定するというスキームですので、このような場合は部分トレースをとる考え方が必要になります。 whyitsso.net
部分トレースについては前回の記事でも解説しています。
入力状態がユニタリー行列の固有値の場合
入力状態がユニタリ行列の固有ベクトルだと仮定します。
するとが成り立ちますので、これを使って式変形していきます。
この後、第一量子ビットにアダマールゲートをかけて整理すると以下になります。
従って、第一量子ビットを測定してその結果が0又は1となる確率はそれぞれ以下になります。
この式と実際に測定した結果を照らし合わせてを推定します、というか実際には既知のユニタリーを実行するので、ユニタリーを利用してこの分布をコントロールします。
入力状態がユニタリー行列の固有値ではない場合
入力状態がユニタリー行列の固有状態では無い場合も
ここまでは固有状態である場合と同じですが、この後アダマールゲートを第一量子ビットにかけると状況がちょっと変わります。
この後、第一量子ビットだけを測定したときに第一量子ビットの状態それぞれが観測される確率を求めるため、密度演算子をこの状態から求めます。
から
よって、第一量子ビットを測定したときにが観測される確率およびが観測される確率はそれぞれ以下になります。
ということでやっと教科書に書いてあるような式が現れました。
教科書ではサラッと書かれていますが、密度演算子と行列計算を使いこなせる必要があります。
制御ユニタリーがかかった後の量子ビットの状態
入力する量子ビットの状態は固有状態ではないが、(固有値がで、固有状態がと予めわかっているとすると)以下のように書き換えられます。
そうすると、2回目のアダマールゲートをかけた後の標的量子ビットの状態は以下になります。
上記のように任意のユニタリーとそれに対応する固有値で表現するとわかりにくいですが、よく使うゲートであれば固有値はで固有ベクトルはそれぞれの軸上の単位ベクトルになりますので、上記の式で言えばのどちらか一方だけが残ります。
従って、もう一つアダマールテスト回路を作成すれば「入力状態がユニタリー行列の固有値の場合」に帰着します。
以下で具体的に計算してみます。
制御Xゲートの場合
となります。
制御Zゲートの場合
となります。
制御Yゲートの場合
の固有ベクトルは (ポアンカレ球の斜め直線偏光に相当する状態)で、対応する固有値はです。 従って
となります。
量子コンピュータで計算させてみる
入力状態がユニタリー行列の固有値の場合
ユニタリー行列が制御Xゲートの場合
の固有ベクトルはで、対応する固有値はです。 従ってのため、第一量子ビットの確率は以下となります。
i) 第二量子ビットの状態がの時
ii) 第二量子ビットの状態がの時
i)について実行する量子回路です。
シミュレータで実行した結果です。
実際にIBMQの実機の結果です。
ということで、予め計算した通りになりました。
これらのソースコードです。
qr_control = QuantumRegister(1, 'control') qr_target = QuantumRegister(1, 'target') cr = ClassicalRegister(1) qc = QuantumCircuit(qr_control, qr_target, cr) qc.h(qr_target) qc.barrier() qc.h(qr_control) qc.cx(qr_control, qr_target) qc.h(qr_control) qc.barrier() qc.measure(qr_control, cr[0]) qc.draw(output='mpl') ## シミュレータ backend = Aer.get_backend('qasm_simulator') job = execute(qc, backend, shot=1024) result = job.result() plot_histogram(result.get_counts()) ## 実機(マシンはその時空いているもので良いです) backend = provider.get_backend('ibmq_belem') job = execute(qc, backend, shots=1024) result = job.result() plot_histogram(result.get_counts())
とはいえ、i)の場合アダマール2回やってに戻ってきただけ、と思われても嫌なのでii)も計算しておきます。
ii)について実行する量子回路です。
シミュレータで実行した結果です。
実機の結果です。
ということで、第一量子ビットからしたらアダマール2回実行したようにしか見えませんがちゃんと第二量子ビットの影響を受けていることがわかります。
この時のソースコードです。
qr_control = QuantumRegister(1, 'control') qr_target = QuantumRegister(1, 'target') cr = ClassicalRegister(1) qc = QuantumCircuit(qr_control, qr_target, cr) qc.x(qr_target) qc.h(qr_target) qc.barrier() qc.h(qr_control) qc.cx(qr_control, qr_target) qc.h(qr_control) qc.barrier() qc.measure(qr_control, cr[0]) qc.draw(output='mpl') ### シミュレータ backend = Aer.get_backend('qasm_simulator') job = execute(qc, backend, shot=1024) result = job.result() plot_histogram(result.get_counts()) ### 実機(その時に空いているマシンなら何でも可) backend = provider.get_backend('ibmq_belem') job = execute(qc, backend, shots=1024) result = job.result() plot_histogram(result.get_counts())
ユニタリー行列がRxゲートの場合
同じように任意の回転ゲートでも確率分布が変化することを確認します。
従ってのため、第一量子ビットの確率は以下となります。
i) 第二量子ビットの状態がの時、のため
ii) 第二量子ビットの状態がの時、のため
i)について実行する量子回路は以下です。
シミュレータで実行した結果です。
実機の結果です。
この時のソースコードです。
qr_control = QuantumRegister(1, 'control') qr_target = QuantumRegister(1, 'target') cr = ClassicalRegister(1) qc = QuantumCircuit(qr_control, qr_target, cr) qc.h(qr_target) qc.barrier() qc.h(qr_control) qc.crx(np.pi / 2, qr_control, qr_target) qc.h(qr_control) qc.barrier() qc.measure(qr_control, cr[0]) qc.draw(output='mpl') ## 実行(シミュレーション) backend = Aer.get_backend('qasm_simulator') job = execute(qc, backend, shot=1024) result = job.result() plot_histogram(result.get_counts()) ## 実行(実機) backend = provider.get_backend('ibmq_quito') job = execute(qc, backend, shots=1024) result = job.result() plot_histogram(result.get_counts())
ii)について実行する量子回路は以下です。
この時のソースコードです。
qr_control = QuantumRegister(1, 'control') qr_target = QuantumRegister(1, 'target') cr = ClassicalRegister(1) qc = QuantumCircuit(qr_control, qr_target, cr) qc.x(qr_target) qc.h(qr_target) qc.barrier() qc.h(qr_control) qc.crx(np.pi / 2, qr_control, qr_target) qc.h(qr_control) qc.barrier() qc.measure(qr_control, cr[0]) qc.draw(output='mpl') ## 実行(シミュレータ) backend = Aer.get_backend('qasm_simulator') job = execute(qc, backend, shot=1024) result = job.result() plot_histogram(result.get_counts()) ## 実行(実機) backend = provider.get_backend('ibmq_quito') job = execute(qc, backend, shots=1024) result = job.result() plot_histogram(result.get_counts())
ということで、予め求めた固有値の通りに確率分布が得られました。
入力状態がユニタリー行列の固有値ではない場合
入力状態はでユニタリーはXゲートの場合
が複素数になる場合です。
従って
この時の量子回路と実行回路です。
この時のソースコードです。
qr_control = QuantumRegister(1, 'control') qr_target = QuantumRegister(1, 'target') cr = ClassicalRegister(1) qc = QuantumCircuit(qr_control, qr_target, cr) qc.rx(-np.pi / 3, qr_target) qc.barrier() qc.h(qr_control) qc.cx(qr_control, qr_target) qc.h(qr_control) qc.barrier() qc.measure(qr_control, cr[0]) qc.draw(output='mpl') ## 実行(シミュレータ) backend = Aer.get_backend('qasm_simulator') job = execute(qc, backend, shot=1024) result = job.result() plot_histogram(result.get_counts()) ## 実行(実機) backend = provider.get_backend('ibmq_quito') job = execute(qc, backend, shots=1024) result = job.result() plot_histogram(result.get_counts())
入力状態はでユニタリーはXゲートの場合
が半端な実数になる場合です。
従って
この時の量子回路と実行結果です。
この時のソースコードです。
qr_control = QuantumRegister(1, 'control') qr_target = QuantumRegister(1, 'target') cr = ClassicalRegister(1) qc = QuantumCircuit(qr_control, qr_target, cr) qc.ry(np.pi / 3, qr_target) qc.barrier() qc.h(qr_control) qc.cx(qr_control, qr_target) qc.h(qr_control) qc.barrier() qc.measure(qr_control, cr[0]) qc.draw(output='mpl') ## 実行(シミュレータ) backend = Aer.get_backend('qasm_simulator') job = execute(qc, backend, shot=1024) result = job.result() plot_histogram(result.get_counts()) ## 実行(実機) backend = provider.get_backend('ibmq_manila') job = execute(qc, backend, shots=1024) result = job.result() plot_histogram(result.get_counts())
ということで、第一量子ビットの確率はに従う分布になりました。
まとめ
- アダマールテストを用いると、制御ユニタリーに用いたユニタリー演算子の固有値がわかる
- アダマールテストによって第一量子ビットの確率分布がユニタリー演算子の固有値に応じた位相の影響を受ける、「位相キックバック」が起こる
- かけるユニタリーと入力する第二量子ビットの状態によって第一量子ビットの確率分布を制御できる