CHSH不等式の破れを確認する#
この最初の実習では、量子コンピュータにおいて量子力学的状態、特に「エンタングルメント」が実現しているか検証してみましょう。実習を通じて量子力学の概念と量子コンピューティングの基礎を紹介していきます。
本当に量子コンピュータなのか?#
このワークブックの主旨が量子コンピュータを使おう、ということですが、量子コンピュータなんて数年前までSFの世界の存在でした。それが今やクラウドの計算リソース(量子演算子・QPU)として使えるというわけですが、ではそもそも私たちがこれから使おうとしている機械は本当にQPUなのでしょうか。どうしたらそれが調べられるでしょうか。
量子コンピュータの基本的な仕組みは、「何らかの物理的な系(超電導共振器や冷却原子など)をうまく操作して、求める計算の結果がその系の量子状態に表現されるようにする」ということです。つまり、量子状態が長く保たれてかつ思うように操作できる対象と、「計算」という実体のなさそうなものを具体的な「量子操作」に対応させるアルゴリズムの両方があって初めて量子コンピューティングが成り立ちます。アルゴリズムの部分はこのワークブックを通じて少しずつ紹介していくので、今回は「量子状態が保たれ、それを操作できる」ということを確認してみましょう。
CHSH不等式#
量子力学的状態が実際に存在するかどうかを確かめる実験として、2022年のノーベル物理学賞でも取り上げられたCHSH不等式[CHSH69]の検証というものがあります。かいつまんで言うと、CHSH不等式とは「二体系の特定の観測量について、エンタングルメントなど量子力学固有の現象がなければ保たれる不等式」です。やや回りくどいロジックですが、つまりQPU(だと考えられる機械)で測ったこの観測量の値がCHSH不等式を破っていれば、その機械は実際に量子現象を利用しているかもしれないということになります。
通常このような実験を行うには高度なセットアップ(レーザーと非線形結晶、冷却原子など)が必要ですが、クラウド量子コンピュータではブラウザひとつしか要りません。このワークブックではJupyter NotebookでPythonのプログラムを書き、IBM QuantumのQPUを利用します。
Qiskitの基本構造#
IBMのQPUで量子計算を実行するには、QiskitというPythonライブラリを利用します。Qiskitの基本的な使い方は
使用する量子ビットの数を決め、量子計算の操作(ゲート)をかけて、量子回路(プログラム)を作る
回路を実行して計算結果を得る。ここでは二通りのオプションがあります。
回路を実際のクラウドQPUに送り、実行させる。
回路をシミュレートする。
計算結果を解析する。
です。以下でこの流れを一通り、重要な概念の説明を混ぜながら実行してみましょう。ただし、今回は実機のみ利用します。回路のシミュレーションに関しては第一回の課題を参照してください。
Qiskitの機能は上のような基本的な量子回路の設計・実行だけではなく、非常に多岐に渡ります。基本的な使い方に関しても多少複雑なところがあるので、わからないことがあればQiskitのドキュメンテーションをあたってみましょう。
量子ビット、量子レジスタ#
量子ビット(qubit=キュビット)とは量子コンピュータの基本構成要素のことで、量子情報の入れ物の最小単位です。そして、量子ビットの集まりを量子レジスタと呼びます。
量子レジスタは量子コンピュータ中で常に一つの「状態」にあります。量子レジスタの状態を物理学の習わしに従ってしばしば「ケット」という
重要なのは各量子ビットに対して2つの基底状態が定義できることで、量子計算の習わしではそれらを
と2つの基底の「重ね合わせ」で表せます。ここで
量子ビットの任意の状態が2つの複素数で表せるということは、逆に言えば一つの量子ビットには2つの複素数に相当する情報を記録できるということになります。ただこれには少し注釈があって、量子力学の決まりごとから、
という関係を満たさなければならず、かつ全体の位相(global phase)は意味を持たない、つまり、任意の実数
(ここで
複素数1つは実数2つで書けるので、
と書いたりもします。この表記法をブロッホ球表現と呼ぶこともあります。
面白くなるのは量子ビットが複数ある場合です。例えば量子ビット2つなら、それぞれに
と4つの複素数を使った重ね合わせになります。2つの量子ビットの基底を並べた
上で登場した量子力学の決まりごとはこの場合
と
となります。量子ビットがいくつあっても拘束条件は2つだけです。
つまり、量子ビット
量子レジスタの計算基底状態の表記法としては、上に書いたようにケットを
ただし、ここで注意すべきなのは、左右端のどちらが「1の位」なのか事前に約束しないといけないことです。
Qiskitには量子レジスタを表すクラスQuantumRegisterがあります。QuantumRegisterは
from qiskit import QuantumRegister
register = QuantumRegister(4, 'myregister')
のように量子ビット数(この場合4)と名前('myregister'
)を指定して初期化します。初期状態では、量子ビットはすべて
ゲート、回路、測定#
量子計算とは、端的に言えば、量子レジスタに特定の状態を生成し、その振幅を利用することと言えます。
とは言っても、いきなり「えいや」と好きな量子状態を作れるわけではなく、パターンの決まった単純操作(
Qiskitでは、量子回路をQuantumCircuit
オブジェクトで表します。
from qiskit import QuantumCircuit, QuantumRegister
register = QuantumRegister(4, 'myregister')
circuit = QuantumCircuit(register)
という具合です。
作られた量子回路は、量子ビットの数が決まっているもののゲートが一つもない「空っぽ」の状態なので、そこにゲートをかけていきます。例えば下で説明するアダマールゲートをレジスタの2個目の量子ビットに作用させるには
circuit.h(register[1])
とします。
上で「振幅を利用する」という曖昧な表現をしましたが、それはいろいろな利用の仕方があるからです。しかし、どんな方法であっても、必ず量子レジスタの測定という操作を行います。量子コンピュータから何かしらの情報を得るための唯一の方法が測定です。Qiskitではmeasure_all
などのメソッドを使って測定を行います。
circuit.measure_all()
測定は量子レジスタの状態を「覗き見る」ような操作ですが、一回の測定操作で具体的に起きることは、各量子ビットに対して0もしくは1という値が得られるというだけです。つまり、量子状態が
ではこの「一つの計算基底」がどの基底なのかというと、実は特殊な場合を除いて決まっていません。全く同じ回路を繰り返し実行して測定すると、毎回ランダムにビット列が決まります。ただし、このランダムさには法則があって、特定のビット列が得られる確率は、対応する計算基底の振幅の絶対値自乗となっています。つまり、
量子計算結果の解析#
回路の実行と測定を何度も繰り返して、それぞれのビット列が現れる頻度を記録すれば、だんだん
逆に、指数関数的な内部の情報量をうまく使って計算を行いつつ、測定という限定的な方法でも答えが読み出せるように工夫するのが、量子アルゴリズム設計の真髄ということになります。例えば理想的には、何か計算の答えが整数
一度の測定で答えがわかるケースを除いて、基本的には多数回の試行から確率分布を推定することになるので、量子回路を量子コンピュータの実機やシミュレータに送って実行させる時には必ず繰り返し数(「ショット数」と呼びます)を指定します。ショット数
よく使うゲート#
IBMのQPUのように超電導振動子を利用する量子コンピュータでは、通常、実際に使用できるゲートは量子ビット1つにかかるものと2つにかかるものに限定されます。しかし、それらを十分な数組み合わせれば、
1量子ビットの操作#
1量子ビットの操作でよく使われるゲートには、以下のようなものがあります。(表中コードのi
, j
は量子ビットの番号)
ゲート名 |
説明 |
Qiskitコード |
---|---|---|
|
||
|
||
計算基底それぞれに対して、以下の変形をする。 (「量子状態にゲートを作用させる」ことをケットの記法で書くときは、ゲートに対応する記号をケットに左からかけます。) となる。 |
|
|
パラメータ |
|
|
パラメータ |
|
それでは、2量子ビットレジスタの第0ビットに
# First, import all the necessary python modules
import numpy as np
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, transpile
from qiskit.visualization import plot_distribution
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit_ibm_runtime.accounts import AccountNotFoundError
# qc_workbook is the original module written for this workbook
# If you encounter an ImportError, edit the environment variable PYTHONPATH or sys.path
from qc_workbook.utils import operational_backend
print('notebook ready')
circuit = QuantumCircuit(2) # You can also create a circuit by specifying the number of bits without using a register
circuit.h(0) # Qubits can be addressed by their index within the circuit
circuit.ry(np.pi / 2., 0) # θ = π/2
circuit.x(0)
# Measurement is always needed to get an output
circuit.measure_all()
print(f'This circuit has {circuit.num_qubits} qubits and {circuit.size()} operations')
This circuit has 2 qubits and 5 operations
最後のプリント文で、ゲートが3つなのにも関わらず “5 operations” と出てくるのは、各量子ビットの測定も一つのオペレーションとして数えられるからです。
量子計算に慣れる目的で、この
なので、結局
2量子ビットの操作#
2量子ビットの操作は、量子ビットの超電導素子での実装の都合上、全て「制御ゲート」(controlled gates)という方式で行われます。この方式では、2つのビットのうち片方を制御(control)、もう片方を標的(target)として、制御ビットが1の時だけ標的ビットに何らかの操作がかかります。
例として、任意の1ビットゲート
です。
上で紹介した頻出する1ビットゲート
ゲート名 |
説明 |
Qiskitコード |
---|---|---|
ビット |
|
|
ビット |
|
|
パラメータ |
|
|
パラメータ |
|
Qiskitで2ビットレジスタに制御ゲートを用い、計算基底
theta1 = 2. * np.arctan(np.sqrt(7. / 3.))
theta2 = 2. * np.arctan(np.sqrt(2.))
theta3 = 2. * np.arctan(np.sqrt(4. / 3))
circuit = QuantumCircuit(2)
circuit.ry(theta1, 1)
circuit.ry(theta2, 0)
circuit.cry(theta3 - theta2, 1, 0) # C[Ry] 1が制御で0が標的
circuit.cz(0, 1) # C[Z] 0が制御で1が標的(実はC[Z]ではどちらが制御でも結果は同じ)
circuit.measure_all()
print(f'This circuit has {circuit.num_qubits} qubits and {circuit.size()} operations')
This circuit has 2 qubits and 6 operations
やや複雑ですが、また計算を追ってみましょう。まず角度
したがって、
最初の行で、ビット0と1にかかる
最後に
回路図の描き方と読み方#
量子回路を可視化する方法として、「回路図」の標準的な描き方が決まっています。QiskitではQuantumCircuit
オブジェクトのdraw()
というメソッドを使って自動描画できます。
circuit.draw('mpl')

ここでdraw()
の引数'mpl'
はmatplotlibライブラリを使ってカラーで描くことを指定しています。実行環境によっては対応していないこともあるので、その場合は引数なしのdraw()
を使います。結果はmpl
の場合に比べて見劣りしますが、内容は同じです。
circuit.draw()
┌────────────┐┌──────────────┐ ░ ┌─┐ q_0: ┤ Ry(1.9106) ├┤ Ry(-0.19649) ├─■──░─┤M├─── ├────────────┤└──────┬───────┘ │ ░ └╥┘┌─┐ q_1: ┤ Ry(1.9823) ├───────■─────────■──░──╫─┤M├ └────────────┘ ░ ║ └╥┘ meas: 2/═════════════════════════════════════╩══╩═ 0 1
回路図は左から右に読んでいきます。水平の2本の実線が上からそれぞれ第0、第1量子ビットに対応し、その上にかぶさっている四角がゲート、最後にある矢印が下に伸びている箱が測定を表します。1ビットゲートから伸びている先端の丸い縦線は制御を表します。一番下の二重線は「古典レジスタ」(量子現象のない物理学を「古典物理学」と呼ぶので、量子でない通常のコンピュータにまつわる概念にはよく「古典 classical」という接頭辞をつけます)に対応し、測定結果の0/1が記録される部分です。
CHSH不等式を計算する回路を書く#
それではいよいよ本題に入りましょう。CHSH不等式を「ベル状態」
ベル状態はアダマールゲートとCNOTゲートを組み合わせて作ります。詳しい説明は課題に譲りますが、CHSH不等式の検証用の観測量を作るために、4つの回路I, II, III, IVを使います。回路IとIIIでは量子ビット1に対し測定の直前にcircuits
というリストに回路を足していきます。
circuits = []
# Circuit I - H, CX[0, 1], Ry(-π/4)[1]
circuit = QuantumCircuit(2, name='circuit_I')
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(-np.pi / 4., 1)
circuit.measure_all()
# Append to list
circuits.append(circuit)
# Circuit II - H, CX[0, 1], Ry(-3π/4)[1]
circuit = QuantumCircuit(2, name='circuit_II')
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(-3. * np.pi / 4., 1)
circuit.measure_all()
# Append to list
circuits.append(circuit)
# Circuit III - H, CX[0, 1], Ry(-π/4)[1], Ry(-π/2)[0]
circuit = QuantumCircuit(2, name='circuit_III')
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(-np.pi / 4., 1)
circuit.ry(-np.pi / 2., 0)
circuit.measure_all()
# Append to list
circuits.append(circuit)
# Circuit IV - H, CX[0, 1], Ry(-3π/4)[1], Ry(-π/2)[0]
circuit = QuantumCircuit(2, name='circuit_IV')
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(-3. * np.pi / 4., 1)
circuit.ry(-np.pi / 2., 0)
circuit.measure_all()
# Append to list
circuits.append(circuit)
# draw() can accept a matplotlib Axes object as an argument, to which the circuit will be drawn
# This is useful when visualizing multiple circuits from a single Jupyter cell
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)

それぞれの回路で2ビットレジスタの基底
回路Iの状態は
簡単のため
したがって回路Iでの確率
同様に、回路IIの状態は
で確率
です。回路IIIの状態は
で確率
同様に回路IVの状態と確率
となります。
それぞれの回路でビット0と1で同じ値が観測される確率
なので、これらの組み合わせ
実は、エンタングルメントが起こらない場合、この観測量
となり、
それでは、IBMの「QPU」が実際にエンタングル状態を生成できるのか、上の四つの回路から
回路を実機で実行する#
まずはIBM Quantumに認証・接続します。すでに認証設定が保存されている場合は
service = QiskitRuntimeService(channel='ibm_quantum')
で接続ができます。設定がない場合はQiskitRuntimeService
のコンストラクタにトークンを渡してIBM Quantumに接続します。
# Specify an instance if you have access to multiple (e.g. premium access plan)
# instance = 'hub-x/group-y/project-z'
instance = None
try:
service = QiskitRuntimeService(channel='ibm_quantum', instance=instance)
except AccountNotFoundError:
service = QiskitRuntimeService(channel='ibm_quantum', token='__paste_your_token_here__', instance=instance)
認証が済んだら、利用する量子コンピュータ(「バックエンド」と呼びます)を選びます。バックエンドで回路を実行するために、Samplerというインターフェースを使います。
# Find the backend that is operational and has the shortest job queue
backend = service.least_busy(filters=operational_backend())
sampler = Sampler(backend)
print(f'Jobs will run on {backend.name}')
回路をバックエンドに送るには、transpile
という関数とSamplerのrun
というメソッドを使います。transpile
については次回トランスパイルと物理的回路で説明するので、今は「おまじない」だと思ってください。run
で回路を送るとき、前述したように同時にショット数を指定します。バックエンドごとに一度のジョブでの最大ショット数が決められており、8192、30000、100000などとさまざまです。回路をバックエンドに渡し、shots
回実行させることをジョブと呼びます。
# max_shots = the maximum number of allowed shots for this backend with the access parameters
shots = min(backend.max_shots, 2000)
print(f'Running four circuits, {shots} shots each')
circuits = transpile(circuits, backend=backend)
# Execute each circuit for `shots` times
job = sampler.run(circuits, shots=shots)
これで回路がバックエンドに送られ、キューに入りました。ジョブの実行結果はrun
メソッドの返り値であるジョブオブジェクトから参照します。
IBMのバックエンドは世界中からたくさんのユーザーに利用されているため、場合によっては予約されているジョブが多数あってキューにかなりの待ち時間が生じることがあります。
バックエンドごとのキューの長さはIBM Quantumのバックエンド一覧ページから確認できます。バックエンドを一つクリックすると詳細が表示され、現在の全ジョブ数が Total pending workloads として表示されます。
また、自分の投じたジョブのステータスはジョブ一覧ページから確認できます。
量子測定結果の解析#
ジョブオブジェクトのresult()
というメソッドを呼ぶと、ジョブが完了して結果が帰ってくるまでコードの実行が止まります。実行結果はオブジェクトとして返され、Samplerに渡した各回路毎にインデックスされています。回路毎のデータのget_counts
というメソッドを使うと、各ビット列が何回観測されたかというヒストグラムデータがPythonのdictとして得られます。
result = job.result()
# List to collect the histogram data from the four circuits
counts_list = []
# Extracting the bit sequence counts from the result object
for idx in range(4):
# get_counts(i) returns the histogram data for circuit i
counts = result[idx].data.meas.get_counts()
# Append to list
counts_list.append(counts)
print(counts_list)
Tip
ノートブックの接続が切れてしまったり、過去に走らせたジョブの結果を再び解析したくなったりした場合は、QiskitRuntimeServiceオブジェクトのjob
というメソッドにジョブIDを渡してジョブオブジェクトを再構成することができます。過去に走らせたジョブはIBM Quantumのホームページにリストされているので、そこにあるジョブID(cgr3kaemln50ss91pj10のような)をコピーし、
job = service.job('__job_id__')
とすると、backend.run
によって返されたのと同じようにジョブオブジェクトが生成されます。
Qiskitから提供されているplot_distribution
関数を使って、この情報を可視化できます。プロットの縦軸は観測回数を全測定数で割って、観測確率に規格化してあります。
fig, axs = plt.subplots(2, 2, sharey=True, figsize=[12., 8.])
for counts, circuit, ax in zip(counts_list, circuits, axs.reshape(-1)):
plot_distribution(counts, ax=ax)
ax.set_title(circuit.name)
ax.yaxis.grid(True)

実は現在の量子コンピュータにはまだ様々なノイズやエラーがあり、計算結果は往々にして理論的な値から統計誤差の範囲を超えてずれます。特定のエラーに関しては緩和法も存在しますが、全て防げるわけでは決してありません。現在の量子コンピュータを指して “Noisy intermediate-scale quantum (NISQ) device” と呼んだりしますが、このNoisyの部分はこのような簡単な実験でもすでに顕著に現れるわけです。
逆に、NISQデバイスを有効活用するには、ノイズやエラーがあっても意味のある結果が得られるようなロバストな回路が求められます。変分法と変分量子固有値ソルバー法を学習するで紹介する変分量子回路を用いた最適化などはその候補として提唱されました。
さて、それでは最後にCHSH不等式の破れを確認してみましょう。
下のコードでcounts
という辞書オブジェクトからキー'00'
などに対応する値を取り出す際にcounts['00']
ではなくcounts.get('00', 0)
としています。二つの表現はcounts
に'00'
というキーが定義されていれば全く同義ですが、キーが定義されていないときは、前者の場合エラーとして実行が止まるのに対して、後者ではデフォルト値として2個目の引数で指定されている0
が返ってきます。qiskitの結果データは一度も測定されなかったビット列についてキーを持たないので、常にget
でカウント数を抽出するようにしましょう。
# C^I, C^II, C^III, C^IVを一つのアレイにする
#(今の場合ただのリストにしてもいいが、純粋な数字の羅列にはnumpy arrayを使うといいことが多い)
c_arr = np.zeros(4, dtype=float)
# enumerate(L)でリストのインデックスと対応する要素に関するループを回せる
for ic, counts in enumerate(counts_list):
# counts['00'] でなく counts.get('00', 0) - 上のテキストを参照
c_arr[ic] = counts.get('00', 0) + counts.get('11', 0) - counts.get('01', 0) - counts.get('10', 0)
# 4つの要素を同時にshotsで規格化(リストではこういうことはできない)
c_arr /= shots
s_val = c_arr[0] - c_arr[1] + c_arr[2] + c_arr[3]
print('C:', c_arr)
print('S =', s_val)
if s_val > 2.:
print('Yes, we are using a quantum computer!')
else:
print('Armonk, we have a problem.')
C: [ 0.61352539 -0.61523438 0.59814453 0.63549805]
S = 2.46240234375
Yes, we are using a quantum computer!
無事、