スワップテスト
アダマールゲートに続いてスワップテストです。 これはアダマールテストのユニタリーゲートについて、SWAPゲートを用いる場合です。 大雑把には制御量子ビット・入力量子ビット1(標的量子ビット1)・入力量子ビット2(標的量子ビット2)の3種類の役割を持つ量子ビットが存在します。 (入力量子ビットは1量子ビットとは限らず、複数量子ビットからなる場合もあります。) 入力量子ビット1, 2を2つのベクトルとした場合の2ベクトル間の内積を評価する量子回路です。
内積を評価する場面はさまざまあり、実装も簡単なため量子機械学習ほか様々な場面で使われるようです。
前提とする知識
- 線形代数がわかること
- 簡単な論理演算(AND, OR, NOT)が分かること
- 基本的な量子ゲート(X, CX, CCX)が分かること
バージョン情報
- Python 3.9.13
- Qiskit 0.36.2
目次
SWAPテスト回路
アダマールテストおさらい
アダマールテストの詳細については前回の解説を参照してください。
量子回路の形は下図の通りで、この回路を使うことで、ユニタリーによって第一量子ビットを測定した時の状態の確率分布を変化させられることがSWAPテストでは重要な意味があります。

この時の第一量子ビットの及び
状態の確率分布は以下です。
SWAPテスト回路の理論
SWAPテストによって二つのベクトルの内積を計算できます。 内積の値がアダマールテストの第一量子ビットの確率分布に反映されるのでその確率から内積を逆算できる、ということです。
入力する2つの量子状態を以下のように定義します。
この2つのベクトルを以下の量子回路に入力することで、その内積
を評価できます。

第一量子ビットの状態を測定した時に、となる確率が以下となることから内積を評価できます。
SWAPテストの仕組み導出
まずアダマールゲートを実行すると
そして前回解説した制御SWAPゲートにより入力ビットの状態を反転させると以下になります。
そして最後に第一量子ビットにアダマールゲートをかけると全体の量子状態の最終状態は以下になります。
補助量子ビットの状態がとなる確率はベクトル
の
成分の長さを求めれば良いので以下となります。
などは内積取ってしまったらただの数なので自由に交換できますので式を整理し
以上では愚直に計算しましたが、制御ユニタリで実行しているゲートに対応するものとしてSWAPゲートを示す
にして実行したアダマールテストです。
SWAPゲートを示すユニタリーは以下です。
この行列の固有ベクトルは
のため、一般の入力状態
は固有状態とは限りません。
従って第一量子ビットの状態がである確率は以下となります。
ということで、やはり愚直にやった計算と一致しました。
そのためユニタリーがわかっていれば、今後アダマールテストの計算する時は愚直に計算しなくとも、確率の式に当てはめれば良いです。
この確率の式から、入力ベクトルが直交している場合は確率0.5で平行であれば確率1であることがわかる。(ベクトルの正負の向きまではわからない。)
SWAPテスト回路の実装
実際に内積を計算してみます。
単純なベクトルの計算
今はの二次元平面上のベクトルを考えています。
普段量子ビットを考えるときは[tex | 0 \rangle]とはBlock球面上の上下正反対を向いたベクトル(下図左)ですが、この二つのベクトルは正規直交基底(直交して、その平面・空間の軸に平行な単位ベクトル)なので直交座標上では直交しています(下図右)。

わかりやすい例だとなら
で
なら
です。
は
に固定して、
を
から
まで直交座標上で
(Bloch球面上で
)だけ連続的に変化させた時は
のため下図のようになります。(はBloch球面上の
となす角度)

から
だけ角度が違うものが
に一致して、確かに直交して
になってますね。
スワップテスト回路で内積を計算
単純にベクトル計算した時と同じように、以下のようには固定して
をY軸に周りに
から
を経由して一周して
に戻るまで角度を変えていった場合に内積が計算できていることを確認します。

これでの角度を0から
に変えた時に第一量子ビットで
が観測される確率が下図です。

からを算出すると下図です。
ノイズの関係でマイナスになる関係で残念ながら自乗を外せません。

ベクトル計算から求めた場合と測定から求めた場合とでの誤差をRMSEで求めると0.02程度でした。
目盛の間隔が0.2ずつなのでその1/10です。
この誤差が大きいのか小さいのかは実際に解きたい問題に依存すると思うのでここでは評価しませんが、ほぼほぼベクトル計算だけから求めた場合と同等の値を測定から求められると思って良いと思います。
この時のソースです。
effs = np.linspace(0, 2, 51)
print(effs)
probs = []
for idx, eff in enumerate(effs):
rad = np.pi * eff
qr_control = QuantumRegister(1, 'control')
qr_1 = QuantumRegister(1, 'a')
qr_2 = QuantumRegister(1, 'b')
cr = ClassicalRegister(1)
qc = QuantumCircuit(qr_control, qr_1, qr_2, cr)
qc.rx(rad, qr_2)
qc.barrier()
qc.h(qr_control)
qc.cx(qr_1, qr_2)
qc.ccx(qr_control, qr_2, qr_1)
qc.cx(qr_1, qr_2)
qc.h(qr_control)
qc.barrier()
qc.measure(qr_control, cr)
backend = Aer.get_backend('qasm_simulator')
job = execute(qc, backend, shot=1024)
result = job.result()
probs.append({
'theta': rad,
'prob': calc_p0(result)
})
df_result = pd.DataFrame(probs)
display(df_result)
thetas = np.linspace(0, 2, 51)
thetas = np.asarray([theta * np.pi for theta in thetas])
cos = np.cos(thetas / 2)
fig = plt.figure()
plt.scatter(df_result['theta'] / np.pi, (df_result['prob']*2-1), marker='o', label='測定結果から求めた結果', color='blue')
plt.plot(thetas / np.pi, cos**2, label='代数的に求めた結果', color='red', linewidth=2)
rmse = sqrt(mean_squared_error(df_result['prob']*2-1, cos**2))
xlim = plt.xlim()
ylim = plt.ylim()
dy = ylim[1] - ylim[0]
plt.text(x=xlim[0], y=ylim[0]+0.1*dy, s=f'RMSE: {rmse:.3f}', fontsize=12)
plt.xlabel('θ / π', fontsize=15)
plt.ylabel('|<a|b>|^2', fontsize=15)
plt.legend(fontsize=12)
plt.show()
plt.close()
光の系でスワップテストを実装(ちゃんと読んでないですけど実験の提案だけかも)の論文がありました。
まとめ
参考
SWAPゲート・制御SWAPゲート
先日アダマールゲートを解説したので続いてSWAPテストを解説したいのですが、その前にSWAPゲートと制御SWAPゲートを解説します。
対象とする読者
- 量子プログラミングに興味を持ち始めたばかりのエンジニア
- 量子計算に興味を持ち始めたばかりの高校生・大学生
- 数学・論理演算・電子回路に慣れていない方
など
前提とする知識
- 簡単な論理演算(AND, OR, NOT)が分かること
- 基本的な量子ゲート(X, CX, CCX)が分かること
バージョン情報
- Python 3.9.13
- Qiskit 0.36.2
目次
SWAPゲート
SWAPゲート実装
英単語「swap」の意味は「交換する」という動詞です。
2つの量子ビットの値を交換するというゲートです。
下図のようにCXゲートを3つ組み合わせます。

この時の入出力の対応表は以下になっております。
| 入力 | 出力 | ||
|---|---|---|---|
| 量子ビット1(q1) | 量子ビット2(q2) | 量子ビット1(q1) | 量子ビット2(q2) |
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 1 |
それぞれの入力を用いて出力を測定した結果が下図です。

図のキャプションに書いた通りグラフのx軸に書かれたビット列は右から第一量子ビットで読みます。 そうすると入力のビット列と出力のビット列が交換されていることが図の(2)(3)を読んでいただくとわかっていただけると思います。
この時のソースは以下です。
qrs = [
[0, 0],
[0, 1],
[1, 0],
[1, 1]
]
fig_result, axes = plt.subplots(2, 2, figsize=(10, 10))
for idx, (q1, q2) in enumerate(qrs):
qr_1 = QuantumRegister(1, 'q1')
qr_2 = QuantumRegister(1, 'q2')
cr = ClassicalRegister(2)
qc = QuantumCircuit(qr_1, qr_2, cr)
if q1 == 1:
qc.x(qr_1)
if q2 == 1:
qc.x(qr_2)
qc.barrier()
qc.cx(qr_1, qr_2)
qc.cx(qr_2, qr_1)
qc.cx(qr_1, qr_2)
qc.barrier()
qc.measure(qr_1, cr[0])
qc.measure(qr_2, cr[1])
fig, ax = plt.subplots(1, 1)
qc.draw(output='mpl', ax=ax)
plt.show()
plt.close()
backend = Aer.get_backend('qasm_simulator')
job = execute(qc, backend, shot=1024)
result = job.result()
plt.figure(fig_result)
plot_histogram(result.get_counts(), ax=axes[q1][q2])
axes[q1][q2].set_title(f'({idx+1}) 入力が|{q1}{q2}>の場合', fontsize=12)
plt.figure(fig_result)
plt.tight_layout()
plt.show()
plt.close()
重ね合わせ状態のSWAP
重ね合わせ状態もその係数ごと交換されます。 つまり
ができます。

入力の状態が交換されて、出力は以下の式に対応する確率になっていれば良いです。
ビット列の読み順は式とグラフとで逆順なのでグラフ上でのが式上の
に対応していて確率振幅0.75の自乗で約0.56、グラフ上の
が式上の
に対応していて確率振幅0.25の自乗で約0.05、
と
はそのままで確率振幅0.43の自乗で0.18に近いので、計算通りでしょう。
ということで、重ね合わせ状態もSWAPできることを確かめました。
この時のソースです。
qr_1 = QuantumRegister(1, 'q1')
qr_2 = QuantumRegister(1, 'q2')
cr = ClassicalRegister(2)
qc = QuantumCircuit(qr_1, qr_2, cr)
qc.ry(np.pi / 3, qr_1)
qc.ry(2 * np.pi / 3, qr_2)
qc.cx(qr_1, qr_2)
qc.cx(qr_2, qr_1)
qc.cx(qr_1, qr_2)
qc.barrier()
qc.measure(qr_1, cr[0])
qc.measure(qr_2, cr[1])
qc.draw(output='mpl')
backend = Aer.get_backend('qasm_simulator')
job = execute(qc, backend, shot=1024)
result = job.result()
plot_histogram(result.get_counts())
複数量子ビットへのSWAPゲート実装
感覚的にはこういう交換をしたいですね。
これを行うには互いの量子ビットのiビット目を交換すれば良いです。 従って下図のような量子回路になります。

SWAPが起きるか確認した結果です。

第一量子ビットの状態がで第二量子ビットの状態が
になっていることを示しています。
よってちゃんと思った通りにSWAPできていますね。
この時のソースです。
qr_1 = QuantumRegister(3, 'q1')
qr_2 = QuantumRegister(3, 'q2')
cr = ClassicalRegister(6)
qc = QuantumCircuit(qr_1, qr_2, cr)
qc.x(qr_2[1])
qc.x(qr_2[2])
qc.barrier()
qc.cx(qr_1[0], qr_2[0])
qc.cx(qr_2[0], qr_1[0])
qc.cx(qr_1[0], qr_2[0])
qc.barrier()
qc.cx(qr_1[1], qr_2[1])
qc.cx(qr_2[1], qr_1[1])
qc.cx(qr_1[1], qr_2[1])
qc.barrier()
qc.cx(qr_1[2], qr_2[2])
qc.cx(qr_2[2], qr_1[2])
qc.cx(qr_1[2], qr_2[2])
qc.barrier()
qc.measure(qr_1, cr[:len(qr_1)])
qc.measure(qr_2, cr[len(qr_1):])
qc.draw(output='mpl')
backend = Aer.get_backend('qasm_simulator')
job = execute(qc, backend, shot=1024)
result = job.result()
plot_histogram(result.get_counts())
制御SWAPゲート
感覚的にはこんな感じですよね。

SWAPゲートの中身はCXゲートなので、CXがかかるのに必要な制御量子ビットを一つ増やすことで実装可能です。

SWAPが起きることを確認します。
まずはSWAPが起きない場合です。

入力状態が (第一量子ビットが制御量子ビット)に対して、測定結果は
のため、SWAPは起きていません。
この時のソースです。
qr_control = QuantumRegister(1, 'control')
qr_1 = QuantumRegister(1, 'q1')
qr_2 = QuantumRegister(1, 'q2')
cr = ClassicalRegister(3)
qc = QuantumCircuit(qr_control, qr_1, qr_2, cr)
qc.x(qr_1)
qc.barrier()
qc.ccx(qr_control, qr_1, qr_2)
qc.ccx(qr_control, qr_2, qr_1)
qc.ccx(qr_control, qr_1, qr_2)
qc.barrier()
qc.measure(qr_control, cr[0])
qc.measure(qr_1, cr[1])
qc.measure(qr_2, cr[2])
qc.draw(output='mpl')
backend = Aer.get_backend('qasm_simulator')
job = execute(qc, backend, shot=1024)
result = job.result()
plot_histogram(result.get_counts())
そしてSWAPが起きる場合です。

入力状態が (第一量子ビットが制御量子ビット)に対して、測定結果は
のため、SWAPが起きています。
この時のソースです。
qr_control = QuantumRegister(1, 'control')
qr_1 = QuantumRegister(1, 'q1')
qr_2 = QuantumRegister(1, 'q2')
cr = ClassicalRegister(3)
qc = QuantumCircuit(qr_control, qr_1, qr_2, cr)
qc.x(qr_control)
qc.x(qr_1)
qc.barrier()
qc.ccx(qr_control, qr_1, qr_2)
qc.ccx(qr_control, qr_2, qr_1)
qc.ccx(qr_control, qr_1, qr_2)
qc.barrier()
qc.measure(qr_control, cr[0])
qc.measure(qr_1, cr[1])
qc.measure(qr_2, cr[2])
qc.draw(output='mpl')
backend = Aer.get_backend('qasm_simulator')
job = execute(qc, backend, shot=1024)
result = job.result()
plot_histogram(result.get_counts())
以上より、制御SWAPゲートの動作を確認できました。
効率の良い制御SWAPゲート
よく考えたら制御SWAPゲートは以下で簡単に実装できました。


ソースは割愛しますが、最初と最後のCCXをただのCXに直しました。
CCXや2つ以上の制御量子ビットを持つ制御ユニタリーゲートは実際には多数の制御ユニタリーゲートに分解して実行されます。 (これは本質的には物理では2体問題しか扱えないからですが、詳細は省きます。)
この事情によって、ソフトウェア的にはどちらでも良い制御SWAPゲートの実装ですが、ハードウェアの事情を加味するとゲートの数が増えるほどゲートエラーが発生するリスクが高まります。
実際に比較してみました。



not usedと記した量子ビットは実際には未使用ということではなく、実行するibmq_quitoの1の量子ビットを想定して、ancilaとして使われる想定です。
ただ、実行したjobを確認すると思ったようには動いてなさそうなので、transplieの仕組みを理解する必要があります。
ひとまず実行結果を確認すると期待していた状態の確率は後者の回路の方が高いことがわかりました。
やはり、なるべくゲート数を減らすことが重要であることがわかりました。
嶋田さんの本 を読むとこの回路はFredkinゲートというそうです。
まとめ
- SWAPゲートは入力した量子ビットの状態を交換する
- 重ね合わせ状態の確率振幅も交換できる
- 複数量子ビットのSWAPゲートは対応する量子ビット同士でそれぞれSWAPゲートを実行する
- 制御SWAPゲートは制御量子ビットの状態に応じてSWAPゲートの実行を切り替える
- 制御SWAPゲートはFredkinゲートで実装できる
次回はこのSWAPゲート・制御SWAPゲートを使ってスワップテストを解説します。
アダマールテスト
前回までで測定・基底・期待値・密度演算子など基本的な理解が進んだので、アルゴリズムの理解をしたいと思います。
今回はアダマールテスト。
後々に量子位相推定や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())
ということで、第一量子ビットの確率はに従う分布になりました。
まとめ
- アダマールテストを用いると、制御ユニタリーに用いたユニタリー演算子の固有値がわかる
- アダマールテストによって第一量子ビットの確率分布がユニタリー演算子の固有値に応じた位相の影響を受ける、「位相キックバック」が起こる
- かけるユニタリーと入力する第二量子ビットの状態によって第一量子ビットの確率分布を制御できる
参考
密度演算子・密度行列
お久しぶりです。
色々忙しくて前回から随分間が空いてしまいました。
当初アダマールテストとかのアルゴリズムの記事を書こうとしましたが、それらを勉強したり説明するうえで、部分系や測定や確率分布の話が避けて通れませんでした。
先に密度演算子について解説して、部分系の取り扱い方法を整理しようと思います。
対象とする読者
- 量子プログラミングに興味を持ち始めたばかりのエンジニア
- 量子計算に興味を持ち始めたばかりの高校生・大学生
など
前提とする知識
- ブラケットを使った線形代数がわかること
- 量子状態をブラケットないしBloch球で表現できること
- ユニタリー発展がわかること
- 射影測定がわかること
バージョン情報
- Python 3.9.13
- Qiskit 0.20.2
目次
定義
密度演算子・密度行列は以下で定義されます。
ここで、は量子系のとりうる状態で、
はそれに対する確率(実数)です。
性質
トレースは1
トレースとは行列の対角成分だけの和を取ることで、以下となります。
つまり、トレースは確率の和を示しています。
なお、3行目から4行目の計算において、
では正規直交基底のため、
となることを利用しています。
対角成分はゼロ以上
対角成分は確率を示すのでそれはそうなのですが、ちゃんと式の上で確かめられます。
ある量子状態をとした場合以下となります。
確率を示す物理量とも言えるので、その期待値がちゃんと確率になっているというのがわかります。
定理
密度演算子のユニタリー発展
状態がユニタリー発展して
となるので
ということで、元の状態とユニタリー発展
が既知であれば計算から求められます。
測定後の密度演算子
測定値が得られる測定演算子
(射影演算子とは限らない)を使って量子状態
を測定した時に、測定値
が得られる確率
はこちらで説明したように、以下の式で求まります。
で示されるいくつかの量子状態
がある場合も同様に、その
に対する条件付き確率は以下で求まります。
ここでの式変形では次の行列の性質を利用しています。
さまざまなについての
を合計することで
が得られます。
(確率論の言葉では「周辺化」と言います。)
以上を用いて測定後の密度演算子を求めるが、そのために測定後の量子状態を確認するとこちらで説明したように
となることを用いて、測定後の密度演算子は以下になる
ここで、については確率の乗法法則
より
確率的に分布する量子状態
ある状態が確率
で存在するとき、全体の密度演算子は
なぜ密度演算子が必要なのか
量子状態(波動関数・状態ベクトル)はまさに状態そのものを示していますがその係数は確率振幅であるので測定に直接結びつきません。 一方で、密度演算子は確率そのものでそのもので表されているため、測定と直接結びつけることができます。
また、行列成分の現れ方でノイズの影響下にあるのか、エンタングルメント状態なのかなどを判断できます。 測定結果を理論と照らし合わせるために非常に重要です。
一方で、元々使っていた量子状態(波動関数・状態ベクトル)はどういう時に使うかというと位相を解析したい時ですかね、、。
詳しい方いらしゃったらコメントいただけますと嬉しいです。
密度演算子の具体例
よくある例ですけど、せっかくなのでQiskit使って測定まで含めて確認してみます。
純粋状態
測定対象となる状態は
です。 この1つの状態が単一で存在します。
とすると、密度演算子は
対角要素が各状態の確率に等しく、と
の確率はそれぞれ
です。
また、
となるのですが、このように
となる状態を純粋状態(Pure State)と言います。
これをZ測定します。 この時の量子回路は以下となります。

この測定結果は下図です。 1024回測定しました。

なお、この時のソースです。
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.h(0)
circuit.barrier()
circuit.measure(qr, cr)
backend = Aer.get_backend('qasm_simulator')
job = execute(circuit, backend=backend, shots=1024)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()
混合状態
測定対象となる状態は
か
がそれぞれ確率50%です。 従って密度演算子は
また、
となるのですが、このように
となる状態を混合状態(Mixed State)と言います。
これをZ測定します。
こちらも対角要素が各状態の確率に等しく、と
の確率はそれぞれ
です。
ただし、量子回路は以下のどちらかを50%のランダムで実行することになります。

量子ビットにXゲートがかかるかかからないかを乱数で50%の確率で決めます。
この測定結果は下図です。

なお、この時のソースです。(※実行するには時間がかかります)
gates = [1 if val >= 0.5 else 0 for val in np.random.rand(1024)]
results = list()
for idx, gate in enumerate(gates):
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
if gate:
circuit.x(0)
circuit.barrier()
circuit.measure(qr, cr)
backend = Aer.get_backend('qasm_simulator')
job = execute(circuit, backend=backend, shots=1)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()
results.append(count)
counts_classic = {'0': 0, '1': 0}
for result in results:
for bit, count in result.items():
counts_classic[bit] += count
plot_histogram(counts_classic)
重ね合わせ状態と混合状態の見分け方
重ね合わせ状態も古典的混合も似たような結果になりました。
数式だとはっきり違いがわかりますが、測定だと違いがわかりません。
測定対象の量子系が重ね合わせ状態か混合状態かどちらなのか予めわからない場合、またより一般的に対象の系がどのような理論やモデルに基づくのかがわからない場合にどうやって測定から見分けたらいいでしょうか?
これが密度演算子で理解できます。
先ほどの例ではZ測定するつもりで、密度演算子もZ基底で記述しました。
これをX測定してみます。
重ね合わせ状態
測定対象となる状態は
です。 この1つの状態が単一で存在します。
とすると、密度演算子は
状態である確率100%です。
今度の量子回路はこちらになります。

この測定結果は下図です。 1024回測定しました。

予定通り100%で観測されました。(アダマールゲートで基底変換しているので、この図のが
に相当します。)
この時のソースは以下です。
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.h(0)
circuit.barrier()
circuit.h(0)
circuit.measure(qr, cr)
backend = Aer.get_backend('qasm_simulator')
job = execute(circuit, backend=backend, shots=1024)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()
plot_histogram(count)
混合状態
一方で混合状態をX基底で記述すると以下になります。
か
がそれぞれ確率50%です。 従って密度演算子は
ということで、Z基底で記述した時と同じく、と
状態が50%ずつの確率となります。
この時の量子回路は以下のどちらかとなります。

と
の混合状態をX測定する場合
この測定結果は以下となります。

ということで、密度演算子から考えた通り、と
状態が約50%ずつで観測されました。
このように未知の量子系であっても、適切な測定方法を用いることで得られる確率分布と理論を照らし合わせて、モデル・量子系を推定することができます。
この時のソースは以下です。
gates = [1 if val >= 0.5 else 0 for val in np.random.rand(1024)]
results = list()
for idx, gate in enumerate(gates):
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
if gate:
circuit.x(0)
circuit.barrier()
circuit.h(0)
circuit.measure(qr, cr)
if idx < 10:
fig, ax = plt.subplots(1, 1)
circuit.draw(output='mpl', ax=ax)
plt.show()
plt.close()
backend = Aer.get_backend('qasm_simulator')
job = execute(circuit, backend=backend, shots=1)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()
results.append(count)
counts_classic = {'0': 0, '1': 0}
for result in results:
for bit, count in result.items():
counts_classic[bit] += count
plot_histogram(counts_classic)
量子状態トモグラフィー
上記では簡単な例で実験結果と理論を比較して状態を推定するということを紹介しました。 実際にはより多量子ビット系になりますが要領は同じで、さまざまな測定から密度演算子の要素を推定し、理論と照らし合わせて量子状態を推定します。 このような手法を「量子状態トモグラフィー」と言います。 「トモグラフィー」は物理学に限らず一般的な言葉で、日常で触れるところではMRIの画像がそうです。 磁気共鳴の空間分布を解析することによって、脳や臓器の構造と状態を指定しそして可視化しています。
参考までに量子状態トモグラフィーについて以下の記事を紹介します。
いずれこのブログでも解説してみたいですが、いつになることやら、、。
部分系・部分トレース
量子系Aと量子系Bの複合系がある時の全体系の密度演算子をとします。
例えば、二つの量子ビットからなる一つの量子系でも良いです。
この時、二つの量子ビットをどちらも測定すればの4状態の確率は密度演算子の対角成分に示された通りになるでしょう。
では、量子ビットAだけを測定した場合、量子ビットAの状態 が測定される確率、またAの密度演算子
はどのようになるでしょうか?という問題です。
結論としては次の式です。
Bについてだけトレースを取ります。(トレースアウトする、とよく言います。)
実際にはより具体的にの成分を計算する際に以下のように計算します。
もしくはのように簡単に表せる場合は以下で計算できます。
Bell状態を使った具体例
Bell状態の一つ
の密度演算子は以下です。
これを実際にについてトレースアウトすると以下です。
ということで、量子ビットAはと
がそれぞれ確率
で観測されます。
また
から、この状態は混合状態だということがわかりました。
このBell状態を使った例は計算結果と実際に測定される状態のイメージがつきやすいわかりやすい例でしたが、この部分トレースという方法は一般の自明でない状態についての確率を予め計算から見積もりたい時に強力なツールとなります。
まとめ
今回は密度演算子の基礎について説明しました。
- 密度演算子は各状態の確率を示す演算子
- 密度演算子のトレース(全状態の確率の和)は1
- 密度演算子の対角成分は非負
- 密度演算子の自乗のトレースを計算することでその状態が純粋状態か混合状態かを見分けることが可能
- 部分的に量子ビットを測定する場合の密度演算子は測定しない量子ビットの分を部分トレースすることで計算可能
次回はこれらを用いてアダマールテストについて解説する予定です。
また、今年、2022年4月よりフリーランスのデータサイエンティストとして活動することになりました。
量子アプリケーション開発というものはまだ市場がほとんどありませんので、主な事業は通常のシステム開発や人工知能アプリケーション開発、またデータ分析の業務委託です。
このブログでの機械学習や量子計算・量子コンピュータについての解説や情報発信は引き続き行いますので、今後とも読んでいただけますと嬉しいです。
私の事業用のホームページがこちらになります。
参考文献
- 量子情報科学ウィンタースクール2010 2日目:量子情報理論の基礎Iの14ページ以降
- エンタングル状態
- 密度演算子 - 純粋状態・混合状態 - 量子コンピュータ入門 - 物理とか
- 量子力学的測定の定式化と部分系・部分トレース - 物理とかの4.部分トレース
- 量子計算量理論入門の2.4混合状態(36ページ)
- 量子力学II講義ノートの1.5 密度演算子(16ページ)
物理量の期待値
前回の射影測定で状態がわかると物理量(演算子)の値がわかります。
また、一般に状態は重ね合わせのため、多数回測定したときには期待値が得られますのでその計算方法を紹介します。
量子力学では頻繁に登場する概念ですが、量子計算は状態を測定できればいいので物理量を意識することはほとんどありません。
ただ、VQEなど一部のアルゴリズムでは物理量(といっても)の期待値が必要になる場合ばありますので量子コンピュータを用いた場合の期待値計算もご紹介します。
対象とする読者
- 量子プログラミングに興味を持ち始めたばかりのエンジニア
- 量子計算に興味を持ち始めたばかりの高校生・大学生 など
対象でない読者
- 量子情報処理の研究室ご出身の方
など
※もちろん読んでいただける分には嬉しいですが、
特に前半部分は電子回路の授業で扱われていたり教科書にも載っている内容となりますので
いまさらと感じさせてしまうかもしれません。
前提とする知識
バージョン情報
- Python 3.7.3
- Qiskit 0.23.1
目次
物理量の演算子
物理量
物理量とは可観測量(Observable)とも呼ばれ、量子力学で実際に何らかの値として測定されうる量のことです。
例えば下記があります。(太字はベクトル量です。)
- 位置
- 速度
(もしくは運動量
)
- 粒子数
- 位相
- エネルギー
- スピン
量子計算で一番馴染みがありそうなのはエネルギーだと思います。
エネルギーの演算子がハミルトニアンで、エネルギー固有値がその状態が持つエネルギーで、その一番小さいエネルギーを持つ状態を
とし、その1つ上のエネルギーを持つ状態を
としたのが超伝導量子ビットです。
下図は初期の超伝導磁束量子ビットでRabi振動(量子状態のマイクロ波制御)を観測した論文に掲載されている図です。
超伝導磁束量子ビットのエネルギーは超伝導リングを貫く磁束(磁場)で変化します(右図A)。
一番下の曲線が基底状態(量子計算でいうところの状態)のエネルギーの変化を、またその一つ上の曲線が第一励起状態(
)のエネルギーの変化の理論計算値を示しています。
図Bは飛ばして図Cが基底状態に置いた超伝導量子ビットにある磁場(横軸)をかけたあと、様々な周波数のマイクロ波を照射した結果、吸収された周波数(縦軸)を示します。
図Cの実線が基底状態と第一励起状態のエネルギーの理論値の差で、点が測定された周波数です。
マイクロ波フォトンのエネルギーは (
はPlank定数で、
が周波数)で計算されるので、周波数を測定することでその状態における系のエネルギーを測定できたことに相当します。
引用した論文はこちら arxiv.org
演算子の定義
物理量の概念や物理学における実体が以上で理解いただけたとして、数学上の物理量の定義は一般に下記です。
ここでは物理量
の固有ベクトル(状態ベクトル)で
がその固有ベクトルに対応する固有値です。
量子計算では状態ベクトルは基本的に (とその組み合わせ)しか用いません。
これはスピンが上()を向いているか、下(
)を向いているかに相当します。
パウリ行列の固有ベクトルを量子計算で使う
に対応しており、下記のように表せます。
量子ビットがなら「1」でZ軸上向き、そして
なら「-1」でZ軸下向きを表します。
測定値に中間の値はなく、同一の状態として生成した量子ビットを複数回測定した場合に、
の測定値は
の確率で「1」となり
の確率で「-1」となります。
また後述しますが、この時の期待値は通常の統計における期待値と同様に±1にそれぞれの重みを乗じたものの和になります。
パウリ行列
代表的な物理量としてパウリ行列をブラケット記法でご紹介します。
ここで
ここで
それぞれのパウリ行列に対応する基底に変換すると、いずれについてもになるのが面白いところですね。
それぞれの軸について量子ビットがどちらを向いているかが確かに表されています。
余談ですが、物理学ではパウリ行列単独で物理量となることはなく、スピン()が物理量となります。
物理量の期待値の計算方法
物理量の期待値
は任意の量子状態
を用いて
例えば、について、状態が
の場合における期待値
は下記です。
期待値計算の例題
単一の物理量
- 量子状態が
の場合
量子コンピュータ(IBMQ)上では測定の基底変換はできないので等価になるように状態を回転させます。
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.h(qr)
circuit.barrier()
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()
plot_histogram(count)

をX基底で測定した結果 (左) 量子回路 (右) 測定結果
と表記されているが、
の分布を観察しているのと等価である。
そして得られた分布から
num_sampling = sum([dist for dist in count.values()])
print(f'サンプリング数: {num_sampling}')
>> サンプリング数: 8192
observable = [1, -1]
exp_val = sum([(cnt / num_sampling) * val for cnt, val in zip(observable, count.values())])
print(f'期待値: {exp_val}')
>> 期待値: 0.01953125
となり、代数計算した場合とほぼ同じくゼロとなった。
- 量子状態が
の場合
で量子コンピュータ(IBMQ)上では先ほどと同じ計算過程によって
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.h(qr)
circuit.barrier()
circuit.h(qr)
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()
plot_histogram(count)

をX基底で測定した結果
num_sampling = sum([dist for dist in count.values()])
print(f'サンプリング数: {num_sampling}')
>> サンプリング数: 8192
observable = [1, -1]
exp_val = sum([(cnt / num_sampling) * val for cnt, val in zip(observable, count.values())])
print(f'期待値: {exp_val}')
>> 期待値: 0.818603515625
である。
物理量の積
量子ビットが2つの場合の
(1つ目の量子ビットのと2つ目の量子ビットの
の積の期待値)
全体の物理量は2状態かける2状態の総当たり(直積)となり、
- 量子状態が
の場合
で、量子コンピュータ(IBMQ)を用いた場合もこれまで通り下記です。
qr = QuantumRegister(2)
cr = ClassicalRegister(2)
circuit = QuantumCircuit(qr, cr)
circuit.h(qr[1])
circuit.rz(np.pi / 2, qr[1])
circuit.barrier()
circuit.rz(-np.pi / 2, qr[1])
circuit.h(qr[1])
circuit.barrier()
circuit.measure(qr, cr)
circuit.draw(output='mpl')
backend = provider.get_backend('ibmq_quito')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()
num_sampling = sum([dist for dist in count.values()])
print(f'サンプリング数: {num_sampling}')
>> サンプリング数: 8192
observable = [1, -1, -1, 1]
exp_val = sum([(cnt / num_sampling) * val for cnt, val in zip(observable, count.values())])
print(f'期待値: {exp_val}')
>> 期待値: 0.9560546875

をそれぞれZ基底とY基底で測定した結果
- 量子状態が
の場合
で、量子コンピュータ(IBMQ)を用いた場合もこれまで通り下記です。
qr = QuantumRegister(2)
cr = ClassicalRegister(2)
circuit = QuantumCircuit(qr, cr)
circuit.h(qr)
circuit.rz(np.pi / 2, qr[1])
circuit.cz(qr[0], qr[1])
circuit.barrier()
circuit.rz(-np.pi / 2, qr[1])
circuit.h(qr[1])
circuit.barrier()
circuit.measure(qr, cr)
circuit.draw(output='mpl')
backend = provider.get_backend('ibmq_quito')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()
plot_histogram(count)
num_sampling = sum([dist for dist in count.values()])
print(f'サンプリング数: {num_sampling}')
>> サンプリング数: 8192
observable = [1, -1, -1, 1]
exp_val = sum([(cnt / num_sampling) * val for cnt, val in zip(observable, count.values())])
print(f'期待値: {exp_val}')
>> 期待値: 0.74365234375

を測定した結果
わかりにくいですが、この重ね合わせ状態はグラフの両端に対応します。
まとめ
射影測定
変分分類器の解説をした時に計算結果を取り出すのに最後測定をするという話をしました。
量子機械学習に限った話ではありませんが、量子回路での計算(演算)結果を取り出すには適した量子ビットを適した方法で測定することが必要になります。
古典コンピュータではANDとかOR回路で演算した結果の電圧値を読み出すような行為に相当します。
量子ビットの場合はどの量子ビットをどのような方法で読み出すかで取り出せる情報が異なります。
将来的にこういうハードウェアを理解しないといけないようなことはソフトウェア工学的には無くなるべきと思いますが、現状ではまだこのような原始的なレベルの理解が必要なので勉強しておきましょう。
対象とする読者
- 量子プログラミングに興味を持ち始めたばかりのエンジニア
- 量子計算に興味を持ち始めたばかりの高校生・大学生
- 数学・論理演算・電子回路に慣れていない方
など
前提とする知識
バージョン情報
- Python 3.9.5
- Qiskit 0.29.0
目次
測定理論
量子測定の仮定
量子力学にはいくつかの仮定(postulate)があり、その一つに測定についての項目があります。
様々な本で解説されており、本によって具体的な文は異なりますが、主旨は同一です。
ここではNielsen-Chuangの2.2.3節にある文を引用します。
Postulate3: Quantum measurements are described by a collectionof measurement operators. These are operators acting on the state space of the system being measured. The index m refers to the measurement outcomes that may occur in the experiment. If the state of the quantum system is
immediately before the measurement then the probability that result m occurs is given by
and the state of the system after the measurement isThe measurement operators satisfy the completeness equation,
手元に英語版しかなくすみません、、
これはコペンハーゲン解釈ではいわゆる「波束の収縮」に当たる文言ですが、その哲学的な話には立ち入らず、量子計算に用いることができる数学的なエッセンスだけ取り出して解釈します。
この仮定から量子状態とその測定について理解できることは次の3点です。
- ある測定値(に対応するインデックス)
が得られる測定演算子(行列)を
とする
- 測定前の量子状態
において測定値
が得られる確率
は(2.92)式で得られる
- 測定後の量子状態は(2.93)式で得られる状態に変化する
測定というのは対象となる量子系(ここでは量子ビット)に対して測定器と結合させることによって測定値が得られるのですが、この際には何らかの物理現象を介しています。
超伝導量子ビットでは初期では電荷計や磁気センサーを、また最近ではマイクロ波の透過・吸収・反射などを利用して読み出しています。
これらの測定器との結合時に起こる物理現象によってそれまで純粋に他と相互作用することなくユニタリー発展していた量子系はその測定後に何らかの状態に遷移してしまうということです。
この遷移するプロセスも厳密には何らかの時間発展なのだと思いますが、ここでは不連続な変化が起きることとします。
一般の量子測定
今回のテーマは「射影測定」なのですが、それ以外の測定もありますので「量子測定」の全体像を整理しますと下記のベン図になります。

この図の通り、まず一般的な測定があって、その特別な場合として射影測定があります。
まずは一般的な測定から解説します。
測定とは
具体的な方法としては先の電荷計や磁気センサーなどで、量子系から物理量を得て、それに対応する情報を得るということです。
測定演算子
ある測定値
が得られる測定演算子は
で、その集合を
と表します。
この時に具体的にどのような測定器でどのような測定をするかは考慮していません。
先の(2.94)式
を満たしていればよいです。
測定前の状態をとし、測定後の状態を
とした場合に、測定後の状態は測定前の状態と測定演算子を用いて下記で表します。
射影測定
量子計算でよく使うのは射影測定で、主に3種類あります。
- X測定(
測定)
- 量子状態が
か
かを測定
- 量子状態が
- Y測定(
測定)
- 光の偏光が
か
かを測定(本当は両矢印なのですが、tex記号に無い、、)
(一般の量子状態で簡潔に表せる状態はなく、光の偏光状態くらいが特別な表現があります。)
- 光の偏光が
- Z測定(
測定)
- 量子状態が
か
かを測定
- 量子状態が
通常はか
かを求めるZ測定がほとんどだと思います。
ただ、論文とかで式を追っていったときにが出てきたらやってることは射影測定だということを思い出していただければと思います。
ここでは簡単のために3種類紹介しましたが、一般に正規直交基底となる軸に射影する演算子を用いていれば、軸はX, Y, Zでなくとも良いですが量子計算ではまず使われないと思います。
それぞれの測定がハードウェア的に何をしているかは物理系によります。
偏光であればは右回り円偏光、
は左回り円偏光に対応し、偏光板などを用いて光が通過する/しないを観測します。
超伝導量子ビット(トランズモン)の場合はは基底状態に、
は第一励起状態に対応しており、マイクロ波の反射・透過・吸収を計測して、どちらの量子状態にいるかを観測します。
その他色々ありますが、量子回路に落とし込んでいるため、プログラミングする際にどのような物理系かは意識しなくて良くなっています。
計算基底(Computational Basis)
1量子ビットの場合、具体的にはなど、
軸上の基底そのものです。
複数量子ビットの場合、具体的にはなど、1量子ビットの計算基底の組み合わせです。
射影測定とは
基底の軸上で測定で、射影演算子を用いた測定です。
具体的にが満たすべき条件は下記です。
(
はKroneckerのdelta)
これらを満たせれば何でも良いのですが、通常は測定値に対応した状態
を用いて
が用いられます。
Z測定であれば(エネルギー固有値
のインデックス
に対応)であり、対応する状態は
で、射影演算子は
です。
ちゃんと1, 2を満たしていますので行列計算してみてください。
量子回路での対応
実際にそれぞれの基底で測定するにはどのようにプログラミングしてどのような量子回路を構成するのかを確認します。
以下のコードでcircuit.barrier()の上までが状態生成で、circuit.barrier()の下で測定方法を指定します。
Z測定
の4状態を実機を使ってZ基底で測定してみます。
| 0 >を測定
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.barrier()
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()

をZ基底で測定する場合.
(左) 測定対象の状態を示すBloch球. (中) 測定まで含めた量子回路. (右) 測定結果.
| 1 >を測定
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.x(qr)
circuit.barrier()
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()

をZ基底で測定する場合.
(左) 測定対象の状態を示すBloch球. (中) 測定まで含めた量子回路. (右) 測定結果.
Z基底による測定によってと
を見分けられています。
| + >を測定
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.h(qr)
circuit.barrier()
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()

をZ基底で測定する場合.
(左) 測定対象の状態を示すBloch球. (中) 測定まで含めた量子回路. (右) 測定結果.
図4右のグラフでと表示されている状態が
に相当し、
が
に相当します。
| - >を測定
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.h(qr)
circuit.z(qr)
circuit.barrier()
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()

をZ基底で測定する場合.
(左) 測定対象の状態を示すBloch球. (中) 測定まで含めた量子回路. (右) 測定結果.
状態はBloch球上では明らか別の状態なのですが、Z基底による測定上では見分けることができません。
下記のX測定で改めて確認しますが、X測定では見分けることが可能です。
X測定
の4状態を実機を使ってX基底で測定してみます。
| 0 >を測定
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.barrier()
circuit.h(qr)
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()

をX基底で測定する場合.
(左) 測定対象の状態を示すBloch球. (中) 測定まで含めた量子回路. (右) 測定結果.
| 1 >を測定
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.x(qr)
circuit.barrier()
circuit.h(qr)
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()

をX基底で測定する場合.
(左) 測定対象の状態を示すBloch球. (中) 測定まで含めた量子回路. (右) 測定結果.
X基底の場合は状態と
状態の見分けがつかなくなりました。
| + >を測定
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.h(qr)
circuit.barrier()
circuit.h(qr)
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()

をX基底で測定する場合.
(左) 測定対象の状態を示すBloch球. (中) 測定まで含めた量子回路. (右) 測定結果.
| - >を測定
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.h(qr)
circuit.z(qr)
circuit.barrier()
circuit.h(qr)
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()

をX基底で測定する場合.
(左) 測定対象の状態を示すBloch球. (中) 測定まで含めた量子回路. (右) 測定結果.
X測定の場合はと
を見分けることができています。
測定したい状態に合わせて基底を変えましょう!
また、ここでは測定したい状態の方を回転させましたが、偏光を量子ビットとした場合は量子状態はそのままに、偏光板の向きや位相など測定の方を変えることができます。
超伝導量子ビットの場合は状態の方を変えるしかない、、、と思います。
間違ってたらすみません、どなたか教えてください。
Y測定
の2状態を実機を使ってY基底で測定してみます。
Y軸上を向いた状態を見分けられ、X測定・Z測定では見分けられず、Y測定で見分けられます。 まずはX, Z測定してみます。
| + > + | - >をX基底で測定
import numpy as np
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.h(qr)
circuit.rz(np.pi / 2, qr)
circuit.barrier()
circuit.h(qr)
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()

をX基底で測定する場合.
(左) 測定対象の状態を示すBloch球. (中) 測定まで含めた量子回路. (右) 測定結果.
| + > + | - >をZ基底で測定
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.h(qr)
circuit.rz(np.pi / 2, qr)
circuit.barrier()
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()

をZ基底で測定する場合.
(左) 測定対象の状態を示すBloch球. (中) 測定まで含めた量子回路. (右) 測定結果.
| + > + | - >をY基底で測定
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.h(qr)
circuit.rz(np.pi / 2, qr)
circuit.barrier()
circuit.rz(-np.pi / 2, qr)
circuit.h(qr)
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()

をY基底で測定する場合.
(左) 測定対象の状態を示すBloch球. (中) 測定まで含めた量子回路. (右) 測定結果.
| + > - | - >をY基底で測定
qr = QuantumRegister(1)
cr = ClassicalRegister(1)
circuit = QuantumCircuit(qr, cr)
circuit.h(qr)
circuit.rz(-np.pi / 2, qr)
circuit.barrier()
circuit.rz(-np.pi / 2, qr)
circuit.h(qr)
circuit.measure(qr, cr)
backend = provider.get_backend('ibmq_armonk')
job = execute(circuit, backend=backend, shots=8192)
job_monitor(job, interval = 2)
result = job.result()
count = result.get_counts()

をY基底で測定する場合.
(左) 測定対象の状態を示すBloch球. (中) 測定まで含めた量子回路. (右) 測定結果.
量子測定をもっと知りたい方へ
量子計算する上では計測・観測というのは状態を読み出して終わりですが、量子力学としては重要な理論の一つです。
理論をちゃんと勉強しようとするのは大学・大学院での研究になってしまいますが、一般向けの読み物として下記のようなものがあります。
まとめ
量子測定とは何か、数学的な定義、物理系との対応、そして実際に実機で測定する際の量子回路とコーディングをまとめました。
- 量子計算で主に使うのはX測定・Y測定・Z測定
- Z測定はそのままで、X測定はHゲートの後にZ測定、Y測定は
ゲート・Hゲートの後にZ測定
今回扱ったのは基本的な状態の観測だけなので、任意の状態をそれぞの基底で観測した時にどういう確率分布になるか試してみてください。
量子計測理論についてもNielsen & Chuangにあります。
PennyLaneを用いた変分分類器(VariationalClassifier)
前回は実用的でなかったので、今回は分類器を作る例をPennyLaneのチュートリアル2つを通して解説します。
今回対象のチュートリアル
こちらのチュートリアルではirisデータセットを使って分類アルゴリズムを実装しています。
なお、多クラス分類のチュートリアルも
にありますので、機会があればやってみてください。
どのような問題をときたいときに、どのような量子回路を定義すればいいのかなど細かい解説は今後に譲りますが、 全体の流れとしては量子回路の部分は通常の機械学習のネットワークのようなものだと感じてもらえればいいなと思います。
対象とする読者
前提とする知識
バージョン情報
- Python 3.9.5
- PennyLane 0.15.1
目次
2クラス変分分類器
変分分類器とは
そもそも「変分分類器(Variational Classifier)」とはなにか?というのが気になると思います。
変分分類器とは、変分アルゴリズムによって最適化した量子回路によって作成された分類器です。
変分アルゴリズムと量子機械学習、量子回路学習
「変分」というのは「変分法」のことです。
機械学習の文脈で「変分法」というと「変分推論」「変分ベイズ学習」などが当てはまると思います。
適用例としては変分オートエンコーダ(Variational Auto Encoder; VAE)がそうです。
求めたい潜在変数の確率分布を何らか(VAEの場合は正規分布)を仮定して、学習で得られる分布との誤差(変分)をなるべく減らして近づける、というものだと思います。
量子計算での変分も似たようなものです。
真に知りたい量子状態に対してある時得られた量子状態
との誤差(変分)を誤差関数で評価し、その誤差関数が最小なものが真に知りたかった状態に近いだろう、という考えのもとその量子状態を作り出す量子回路をデータから求めます。
(厳密には物理学の「変分法」は解析力学由来の言葉なので、機械学習文脈での「変分法」とは若干ニュアンスが違いますが、結局の使われどころは同じです。)
回路のパラメータを機械学習で求めることになるので「量子回路学習」とも呼ばれます。
またここで用いられる量子回路は「変分量子回路」と呼ばれます。
この辺りの詳細はまた別に解説することにして、変分アルゴリズムという難しい言い方をしましたが、機械学習と同じようにデータから量子回路のパラメータを最適化してその量子回路を使って分類問題を解きましょうということです。
下記で物理学会誌の記事で京都大学の藤井先生と大阪大学の御手洗先生が解説されています。 https://www.jps.or.jp/books/gakkaishi/2019/09/74-09seriesAIphys1.pdf
その中で示されている量子回路学習の手順の図が機械学習の手順と似ていてわかりやすいと思いました。

で特徴量を量子ビット上にエンコーディングし、
が計算を行う操作で機械学習でいうところのニューラルネットワークなどの機械学習アルゴリズムに相当します。
これらの「量子コンピュータ」と書かれた枠の中だけが機械学習の手順と異なっており、そこを差し替えれば機械学習とやることは同じなのだと思います。
また、この辺りの説明は嶋田さんの本でも解説されています。
![]()
量子コンピューティング 基本アルゴリズムから量子機械学習まで [ 情報処理学会出版委員会 ]
2クラス変分分類器の量子回路
分類器の箇所のコードはこちらです。
import pennylane as qml
from pennylane import numpy as np
from pennylane.optimize import NesterovMomentumOptimizer
dev = qml.device("default.qubit", wires=2)
def layer(W):
qml.Rot(W[0, 0], W[0, 1], W[0, 2], wires=0)
qml.Rot(W[1, 0], W[1, 1], W[1, 2], wires=1)
qml.CNOT(wires=[0, 1])
@qml.qnode(dev)
def circuit(weights, angles):
statepreparation(angles)
for W in weights:
layer(W)
return qml.expval(qml.PauliZ(0))
それを図にするとこちらです。
今回のレイヤー数は6になっています。
(statepreparationはデータセットの特徴量を量子ビットにエンコーディングする箇所(のこと)なので一旦説明を省きます。)

最後の第一量子ビットを測定することで、2クラスのどちらに分類するかの確率が求まります。
ソースコードではqml.expval(qml.PauliZ(0))となっているので求めているのはです。
その結果がどちらであるかで2クラス分類しています。
PennyLaneではどの基底で測定するかを選ぶことができ、今回は基底(
の2状態)で測定することが2クラス分類に対応しています。
ただ、すみません、どうしてこの量子回路だと分類問題が解けるのかというのはまだ理解が足りていません、、
詳細についてはまたの機会に解説解説させていただければと思います。
また、現状の量子回路学習の課題として、解きたい問題に対してどのような回路を構成すべきかという定石(機械学習でいうところの画像だったらCNN, 時系列だったらRNNなど)が無いようです。
これからいろんな問題について適用されて知見が溜まることを期待します。
なお、測定についてはNielsen-Chuangに詳しく書かれています。
![]()
【新品】量子コンピュータと量子通信 2 量子コンピュータとアルゴリズム Michael A.Nielsen/共著 Isaac L.Chuang/共著 木村達也/訳
NIIの根本先生の本も物理現象に即して解説されていてわかりやすいです。
データ読み込み〜モデル学習までの基本的な流れ
振幅エンコーディング
まずデータ(問題設定)を量子回路に置き換える方法は主に次の2通りがあります。
- 基底エンコーディング:量子ビットの
をフラグのように対応させます。
以前解説させていただいた、Groverのアルゴリズムの入力がこちらに当たります。 - 振幅エンコーディング:量子ビットの確率振幅(の位相)をデータに対応させます。
今回用いるのがこちらの手法です。
チュートリアルでは下記になっています。
def get_angles(x):
beta0 = 2 * np.arcsin(np.sqrt(x[1] ** 2) / np.sqrt(x[0] ** 2 + x[1] ** 2 + 1e-12))
beta1 = 2 * np.arcsin(np.sqrt(x[3] ** 2) / np.sqrt(x[2] ** 2 + x[3] ** 2 + 1e-12))
beta2 = 2 * np.arcsin(
np.sqrt(x[2] ** 2 + x[3] ** 2)
/ np.sqrt(x[0] ** 2 + x[1] ** 2 + x[2] ** 2 + x[3] ** 2)
)
return np.array([beta2, -beta1 / 2, beta1 / 2, -beta0 / 2, beta0 / 2])
def statepreparation(a):
qml.RY(a[0], wires=0)
qml.CNOT(wires=[0, 1])
qml.RY(a[1], wires=1)
qml.CNOT(wires=[0, 1])
qml.RY(a[2], wires=1)
qml.PauliX(wires=0)
qml.CNOT(wires=[0, 1])
qml.RY(a[3], wires=1)
qml.CNOT(wires=[0, 1])
qml.RY(a[4], wires=1)
qml.PauliX(wires=0)
@qml.qnode(dev)
def circuit(weights, angles):
statepreparation(angles)
for W in weights:
layer(W)
return qml.expval(qml.PauliZ(0))
def variational_classifier(var, angles):
weights = var[0]
bias = var[1]
return circuit(weights, angles) + bias
def cost(weights, features, labels):
predictions = [variational_classifier(weights, f) for f in features]
return square_loss(labels, predictions)
# irisデータセットの読み込み
data = np.loadtxt("variational_classifier/data/iris_classes1and2_scaled.txt")
X = data[:, 0:2]
# パディング
padding = 0.3 * np.ones((len(X), 1))
X_pad = np.c_[np.c_[X, padding], np.zeros((len(X), 1))]
# 正規化(標準化)
normalization = np.sqrt(np.sum(X_pad ** 2, -1))
X_norm = (X_pad.T / normalization).T
# 角度(位相)に変換
features = np.array([get_angles(x) for x in X_norm])
今回のirisもそうですが一般に特徴量は実数です。
対して回転ゲートは
の周期性があります。
特徴量の値が大きいと何周回ったのかわからなくなります。
なのでできるだけ1周程度に収めるために正規化(ここでは標準化)をしています。
そしてそのあと、学習処理
var = opt.step(lambda v: cost(v, feats_train_batch, Y_train_batch), var)
にて、実際に振幅エンコーディングします。
(cost関数にて量子回路を定義しています。必要な関数は並べてあるので、お手数ですが少し式を追ってください。)
誤差関数
測定結果の値に対して、教師データとの精度を誤差関数で評価します。
def square_loss(labels, predictions):
loss = 0
for l, p in zip(labels, predictions):
loss = loss + (l - p) ** 2
loss = loss / len(labels)
return loss
def cost(weights, features, labels):
predictions = [variational_classifier(weights, f) for f in features]
return square_loss(labels, predictions)
予測結果の正解ラベルへの当てはまり具合を平均自乗誤差で評価します。
この辺りはもう機械学習と同じ流れです。
最適化アルゴリズム
誤差関数での評価結果に基づいて、パラメータ更新をします。
opt = NesterovMomentumOptimizer(0.01) var = opt.step(lambda v: cost(v, feats_train_batch, Y_train_batch), var)
varが変分量子回路 x 6層分ののパラメータ
です。
分類結果
学習済みの量子回路を使って特徴量空間をクラスで色分けしたのが下図です。

まとめ
- 量子回路(変分量子回路)を用いた2クラス分類ができることを確かめた
- 量子回路学習とは量子回路のパラメータを機械学習的に最適化することで、この時の量子回路を変分量子回路という
- どのような問題設定の時にどのような変分量子回路にすればいいかという定石は定まってなく、個別に計算式・量子回路を考えて実装する必要がある

