【課題】量子相関を調べる#
第一回の実習ではCHSH不等式の破れを調べるために、2つの量子ビットの相関関数
QCシミュレータの使い方#
実習で見たように、QCで実現される量子状態は、量子力学の公理に基づいて理論的に計算・予測できます。そこで用いられる数学的操作も単なる足し算や掛け算(線形演算)なので、実はQCの量子状態は(古典)計算機で比較的簡単に計算できます。当然のことですが、QCは何も魔法のブラックボックスというわけではありません。
ただし、古典計算機で量子状態を再現するためには、特殊な場合を除いて、量子ビット数の指数関数的な量のメモリが必要になります。これも前半で見たように、
なので、
しかし、逆に言うと、
シミュレーションはローカル(手元のPythonを動かしているコンピュータ)で実行できるので、ジョブを投げて結果を待つ時間が省けます。この課題ではたくさんの細かい量子計算をするので、実機を使わず、AerSimulator
というQiskitに含まれるシミュレータを利用します。
# まずは全てインポート
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize, Bounds
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit_aer.primitives import SamplerV2 as Sampler
from qiskit.visualization import plot_histogram
print('notebook ready')
AerSimulatorはAerSimulator()
でインスタンス化します。実機の場合はSamplerにバックエンドオブジェクトを渡していましたが、シミュレータのSamplerは内部で自動的にAerSimulatorを生成するのでバックエンドを与える必要がありません。
simulator = AerSimulator()
sampler = Sampler()
print(simulator.name)
問題1:シミュレータを使う#
実習の内容を再現してみましょう。ポイントは実機とシミュレータで極力同じコードが動くようになっているということです。
circuits = []
##################
### EDIT BELOW ###
##################
#講義と同様に4通りの回路を用意し、circuitsに編入する
##################
### EDIT ABOVE ###
##################
# シミュレータにはショット数の制限がないので、時間の許す限りいくらでも大きい値を使っていい
shots = 10000
# 実習と同じく transpile() - 今は「おまじない」と思ってよい
circuits = transpile(circuits, backend=simulator)
# シミュレータもバックエンドと同じように振る舞うので、runメソッドで回路とショット数を受け取り、ジョブオブジェクトを返す
job = sampler.run(circuits, shots=shots)
# シミュレータから渡されたジョブオブジェクトは実機のジョブと全く同じように扱える
result = job.result()
c_arr = np.zeros(4, dtype=float)
##################
### EDIT BELOW ###
##################
#講義と同様にSamplerの結果からc_arrを計算する
##################
### EDIT ABOVE ###
##################
c_arr /= shots
s_val = c_arr[0] - c_arr[1] + c_arr[2] + c_arr[3]
print('S =', s_val)
上のように、AerSimulator
のSamplerは実機の際と同様にrun
関数を実行でき、ヒストグラムデータを返します。実機ではショット数に制限がありますが、シミュレータにはありません。ただしショット数が多いほど、当然実行に時間がかかります。といってもこの程度の回路であれば常識的なショット数ならほぼ瞬間的にジョブの実行が終わります。また、シミュレータにはノイズがない[1]ので、
問題2:Ryの角度を連続的に変える#
ここまで測定の直前のRyゲートの引数に特定の値のみ使ってきましたが、この角度を細かく変えていくとどうなるでしょうか。
# Consider 20 points each for theta and phi (400 points total)
ntheta = 20
nchi = 20
thetas = np.linspace(0., np.pi, ntheta)
chis = np.linspace(0., np.pi, nchi)
# Construct a circuit for each (theta, chi) pair
circuits = []
# np.ndindex returns an iterator over a multi-dimensional array
# -> idx = (0, 0), (0, 1), ..., (1, 0), (1, 1), ...
for idx in np.ndindex(ntheta, nchi):
theta = thetas[idx[0]]
chi = chis[idx[1]]
circuit = QuantumCircuit(2, name=f'circuit_{idx[0]}_{idx[1]}')
# Create a circuit that forms a Bell state, applies Ry gates with theta and chi
# as arguments, and measures the state
##################
### EDIT BELOW ###
##################
#circuit.?
##################
### EDIT ABOVE ###
##################
circuit.measure_all()
circuits.append(circuit)
# Execute all circuits in Sampler and retrieve the results
shots = 10000
circuits = transpile(circuits, backend=simulator)
job = sampler.run(circuits, shots=shots)
result = job.result()
# Compute the C values for each (theta, chi)
c_values = np.zeros((ntheta, nchi), dtype=float)
for icirc, idx in enumerate(np.ndindex(ntheta, nchi)):
# This is the counts dict for the (theta, chi) pair
counts = result[icirc].data.meas.get_counts()
##################
### EDIT BELOW ###
##################
#c_values[idx] = ?
##################
### EDIT ABOVE ###
##################
# Making a 2D plot using imshow()
# The theta dimension of c_values must be reversed because imshow() puts the origin at the top left corner
dtheta = (thetas[1] - thetas[0]) * 0.5
dchi = (chis[1] - chis[0]) * 0.5
plt.imshow(c_values[::-1], extent=(chis[0] - dchi, chis[-1] + dchi, thetas[0] - dtheta, thetas[-1] + dtheta))
plt.xlabel(r'$\chi$')
plt.ylabel(r'$\theta$')
plt.colorbar(label='C')
# Place markers at theta and chi values that realize |S| = 2 sqrt(2)
plt.scatter([np.pi / 4., np.pi / 4., 3. * np.pi / 4.], [0., np.pi / 2., np.pi / 2.], c='red', marker='+')
plt.scatter([3. * np.pi / 4.], [0.], c='white', marker='+');
プロット上に、合わせて
問題3:混合状態での評価#
回路の初期状態をBell状態
を作ります。右二つのケットに対応する量子ビットを今までと同様右からAとBと呼び、この状態を様々な基底で測定します。一番左のケットに対応する量子ビットCには何もせず、ただ測定をし、しかもその結果を無視します[2]。
それでは、問題2のGHZバージョンを作ってみましょう。
# Construct a circuit for each (theta, chi) pair
circuits_ghz = []
# np.ndindex returns an iterator over a multi-dimensional array
# -> idx = (0, 0), (0, 1), ..., (1, 0), (1, 1), ...
for idx in np.ndindex(ntheta, nchi):
theta = thetas[idx[0]]
chi = chis[idx[1]]
circuit = QuantumCircuit(3, name=f'circuit_{idx[0]}_{idx[1]}')
# Create a circuit that forms a GHZ state and then measures the two qubits
# along theta and chi bases
##################
### EDIT BELOW ###
##################
#circuit.?
##################
### EDIT ABOVE ###
##################
circuit.measure_all()
circuits_ghz.append(circuit)
# Execute all circuits in qasm_simulator and retrieve the results
circuits_ghz = transpile(circuits_ghz, backend=simulator)
sim_job_ghz = sampler.run(circuits_ghz, shots=shots)
result_ghz = sim_job_ghz.result()
def counts_ignoring_qubit2(counts, bitstring):
"""Add the counts of cases where qubit C is 0 and 1"""
return counts.get(f'0{bitstring}', 0) + counts.get(f'1{bitstring}', 0)
# Compute the C values for each (theta, chi)
c_values_ghz = np.zeros((ntheta, nchi), dtype=float)
for icirc, idx in enumerate(np.ndindex(ntheta, nchi)):
# This is the counts dict for the (theta, chi) pair
counts = result_ghz[icirc].data.meas.get_counts()
##################
### EDIT BELOW ###
##################
#c_values_ghz[idx] = ?
##################
### EDIT ABOVE ###
##################
# Making a 2D plot using imshow()
# The theta dimension of c_values must be reversed because imshow() puts the origin at the top left corner
plt.imshow(c_values_ghz[::-1], extent=(chis[0] - dchi, chis[-1] + dchi, thetas[0] - dtheta, thetas[-1] + dtheta))
plt.xlabel(r'$\chi$')
plt.ylabel(r'$\theta$')
plt.colorbar(label='C');
ベル状態と明らかに違う挙動をしているのがわかります。原始的な方法ですが、計算したc_values_ghz
から総当たりで
max_abs_s = 0.
# Use ndindex to iterate over all index combinations
for ikappa, ilambda, imu, inu in np.ndindex(ntheta, nchi, ntheta, nchi):
abs_s = abs(c_values_ghz[ikappa, ilambda] - c_values_ghz[ikappa, inu] + c_values_ghz[imu, ilambda] + c_values_ghz[imu, inu])
max_abs_s = max(abs_s, max_abs_s)
print(f'max |S| = {max_abs_s}')
量子ビットAとBに「古典的」な状態が実現しているようです。(有限のショット数でシミュレーションをしているため、統計誤差によって
背景1:測定基底の変換#
(背景セクションは読み飛ばしても課題に支障ありません)
さて、おさらいをすると、上の
fig, axs = plt.subplots(2, 2, figsize=[12., 6.])
for circuit, ax in zip(circuits, axs.reshape(-1)):
circuit.draw('mpl', ax=ax)
ax.set_title(circuit.name)
ベル状態を作るところまではすべての回路で共通で、その後それぞれ異なる角度の
ここまで「測定」とはレジスタの量子状態
例えば1量子ビットにおいて、計算基底状態は
すると、
つまり、計算基底状態が
一般に、量子力学的には、2つの異なる状態
と表現されるならば、「基底
量子計算においても、アルゴリズムの一部として、計算の結果実現した状態を特定の基底で測定するということが多々あります。ところが、ここで若干問題があります。量子コンピュータは実装上、計算基底でしか測定ができないのです。量子力学の理論的には特別でない
そこで、量子計算では、状態を任意の基底で測定することを諦め、反対に状態を変化させてしまいます。例えば、本当は上の
なので、
が得られます。この
このように、測定を行いたい基底(ここでは
背景2:観測量の期待値とその計算法#
次に、量子計算でも多出する(ワークブックでは特に変分法と変分量子固有値ソルバー法を学習する以降)概念である「観測量の期待値」について説明します。
観測量とはそのまま「観測できる量」のことで、量子状態から取り出せる(古典的)情報のこととも言えます。例えば、何かしらの粒子の運動を量子力学的に記述した場合、その粒子の位置や運動量などが観測量です。
ケットで表される量子状態に対して、観測量はケットに作用する「エルミート演算子」で表現されます。エルミート以前にそもそも演算子という言葉をこれまで使っていませんが、量子力学において演算子とは、状態ケットを他の状態ケットに変換する作用で、特に線形なもの、すなわち
が成り立つような
さて、それではエルミート演算子はというと、(細かい定義は参考文献に譲りここで必要な最小限のことだけ述べると、)対角化可能で、実数の固有値を持つという性質を持つ演算子のことです。つまり、
が成り立つような状態
さて、
と分解されるとき、この状態を固有ベクトル基底
そのような測定を多数回繰り返して
となります。ここから、量子コンピュータにおいて観測量の期待値を計算する方法を見出すことができます。具体的には、
観測量をエルミート演算子で表現する
演算子を対角化し、固有値と対応する固有ベクトルを求める
固有ベクトルを基底として、レジスタの状態を測定する
測定から得られた確率分布を重みとして固有値の平均値を取る
です[6]。3の測定の際には、上のセクションで説明した測定基底の変換を利用します。
背景3:CHSH不等式の解釈#
実習の中で、
という量を計算しました。ここで、counts.get('lm', 0) / shots
に対応)。実はこの量
まず、1つの量子ビットに対して、固有値が
で定義できる、という具合です。
次に、2つの量子ビットA, Bからなるレジスタを考え、
で定義します。これらの固有ベクトルを使って、レジスタの状態
と分解します。ケットはAが右、Bが左になるよう並べました。すると、積
です。
最後に、同じ結果を計算基底での測定で表すために、
で結びついているなら、状態
が成り立ちます。確認のためはっきりさせておくと、左辺は
を表していたのでした。
これを踏まえて、CHSH不等式の左辺は結局何を計算していたのか、見直してみましょう。ベル状態を
とおきます。
がわかります。
観測量
4つのパラメータ
を用いて という量を定義すると、エンタングルメントのない古典力学において である。
となります。
問題2の解説:一般の 演算子の期待値#
上のように
したがって、
となります。得られたプロットはこの関数形にしたがっているでしょうか?
問題3の解説:ベル状態の何がすごいのか?#
ここまで、かなり天下り的に「エンタングルメント」や「ベル状態」が登場してきて、CHSH不等式が破れているから量子力学だ、と言われても、そもそも量子現象がないとしたら何が期待されるのか、なぜベル状態が不思議なのか、といったある種の「出発点」が欠けていると感じている方も多いかと思います(量子ネイティブを育成するという観点からは、出発点が量子力学でありエンタングルメントは当たり前、ということでいいのかもしれませんが)。量子現象のない「古典力学」とはすなわち、ボールを投げたら放物線を描いて地面に落ちる、といった我々の日常的な感覚に沿った物理法則体系とも言えます。そこで、一旦量子力学を忘れて、日常的な感覚でベル状態を解釈することを試みましょう。
量子ビットAとBはそれぞれどんな状態にある?
量子コンピュータ上で実験をしているので、量子ビットAとBは1mmも離れていません(IBMQのような超電導型のコンピュータの実態は、数mm四方のシリコンチップ上にプリントされた金属の回路です)が、これまでの議論にそもそもAとBの間の距離は一切登場しませんでした。実際、AとBをエンタングルさせてから、何らかの方法で二つを100m引き離したとしても、AとBの関係は全く変わりません。そのようにバラバラにして扱える二つの物があるとしたら、日常的な感覚で言えば、それぞれの物が独立に何らかの状態にあると思うのが自然です。ケットの記法で言えば、
ところが、式(4)を見るとわかるように、量子ビットAもBも、
五分五分の確率で
ベル状態を計算基底で測定すると、00か11というビット列が等しい確率で得られます。ならば、二つの量子ビットが相関しているとしても、全体が
再び式(4)を眺めると、この解釈も正しくないことがわかります。例えば式(3)を用いると
となり、今度は全体が
全ての
式(6)によると、AとBをどんな基底で測定しても、基底が揃ってさえいれば、100%相関した測定結果が得られることになります。ならば、
残念ながらそれも正しくありません。ポイントは、測定をする基底をベル状態の生成の後に決められる(回路で言えば
結論として、ベル状態では、量子ビットAもBも特定の状態にはなく、Aを測定した時に初めてBの状態が決まる(もしくはその逆)、そしてAとBがどれだけ離れていてもそれが成り立つ、ということになります。こういう言い方をすると、あたかもAとBがテレパシーで繋がっているようで、これが例えばアインシュタインがエンタングルメントの概念を「spooky action at a distance」と称して嫌がった(量子力学の定式化に間違いがあると主張した)理由です。
実際にはテレパシーがあるわけではないのですが、量子力学には「部分系の状態」という概念そのものが成り立たないような状態が存在し、古典力学と本質的に異なることは確かなようです。ちなみに、AとBが可分状態にあるとすれば、CHSH不等式の左辺に登場する
です。最後の不等式では、
様々な可分状態がランダムに登場するとしても、全ての状態の組み合わせについて上の不等式が成り立つので、全体の平均は常に2以下となります。これが、「古典力学では
提出するもの
問題1, 2, 3において完成した回路のコード(EDIT BELOW / EDIT ABOVEの間を埋める)とシミュレーション結果によるプロット
おまけ(評価対象外):実験2で、量子ビットCをどのような基底で測定しても、その結果を無視する限りにおいて
の値は変わらないということの証明おまけ(評価対象外):実験2で、量子ビットCをある基底で測定し、その結果が0であった時のみを考慮すると、ABにベル状態を回復することができる。そのような基底の同定と、できれば実験2のように量子回路を組んで実験1と同じプロットが得られることの確認