CHSH不等式の破れを確認する

この最初の実習では、量子コンピュータにおいて量子力学的状態、特に「エンタングルメント」が実現しているか検証してみましょう。実習を通じて量子力学の概念と量子コンピューティングの基礎を紹介していきます。

\(\newcommand{\ket}[1]{|#1\rangle}\)

本当に量子コンピュータなのか?

このワークブックの主旨が量子コンピュータ(QC)を使おう、ということですが、QCなんて数年前までSFの世界の存在でした。それが今やクラウドの計算リソースとして使えるというわけですが、ではそもそも私たちがこれから使おうとしている機械は本当にQCなのでしょうか。どうしたらそれが調べられるでしょうか。

QCの基本的な仕組みは、「何らかの物理的な系(超電導共振器や冷却原子など)をうまく操作して、求める計算の結果がその系の量子状態に表現されるようにする」ということです。つまり、量子状態が長く保たれてかつ思うように操作できる対象と、「計算」という実体のなさそうなものを具体的な「量子操作」に対応させるアルゴリズムの両方があって初めてQCが成り立ちます。アルゴリズムの部分はこのワークブックを通じて少しずつ紹介していくので、今回は「量子状態が保たれ、それを操作できる」ということを確認してみましょう。

CHSH不等式

量子力学的状態が実際に存在するかどうかを確かめる実験として、CHSH不等式[CHSH69,NC00]の検証というものがあります。かいつまんで言うと、CHSH不等式とは「二体系の特定の観測量について、エンタングルメントなど量子力学固有の現象がなければ保たれる不等式」です。やや回りくどいロジックですが、つまりQC(だと考えられる機械)で測ったこの観測量の値がCHSH不等式を破っていれば、その機械は実際に量子現象を利用しているかもしれないということになります。

通常このような実験を行うには高度なセットアップ(レーザーと非線形結晶、冷却原子など)が必要ですが、クラウドQCではブラウザひとつしか要りません。このワークブックではJupyter NotebookでPythonのプログラムを書き、IBM Quantum Experienceを通じてIBM Q System Oneを利用します。

Qiskitの基本構造

IBM Q System OneのQCで量子計算を実行するには、IBMの提供するQiskitというPythonライブラリを利用します。Qiskitの基本的な使い方は

  1. 使用する量子ビットの数を決める

  2. 量子ビットに量子計算の操作(ゲート)をかけて、量子回路を作る

  3. 回路を実行して計算結果を得る。ここでは二通りのオプションがあり、

    • 回路をQCの実機に送り、実行させる。

    • 回路をシミュレートする。

  4. 計算結果を解析する。

です。以下でこの流れを一通り、重要な概念の説明を混ぜながら実行してみましょう。ただし、今回は実機のみ利用します。回路のシミュレーションに関しては第一回の課題を参照してください。

量子ビット、量子レジスタ

量子ビット(qubit=キュビット)とは量子コンピュータの基本構成要素のことで、量子情報の入れ物の最小単位です。そして、量子ビットの集まりを量子レジスタと呼びます。

量子レジスタは量子コンピュータ中で常に一つの「状態」にあります。量子レジスタの状態を物理学の習わしに従ってしばしば「ケット」という\(\ket{\psi}\)のような記号で表します1。量子力学に不慣れな方はこの記法で怯んでしまうかもしれませんが、ケット自体はただの記号なのであまり気にしないでください。別に「枠」なしで\(\psi\)と書いても、絵文字を使って🔱と書いても、何でも構いません。

重要なのは各量子ビットに対して2つの基底状態が定義できることで、量子計算の習わしではそれらを\(\ket{0}\)\(\ket{1}\)で表し、「計算基底」とも呼びます2。そして、量子ビットの任意の状態は、2つの複素数\(\alpha, \beta\)を使って

\[ \alpha \ket{0} + \beta \ket{1} \]

と2つの基底の「重ね合わせ」で表せます。ここで\(\alpha, \beta\)を確率振幅、もしくは単に振幅(amplitude)と呼びます。繰り返しですが別に表記法自体に深い意味はなく、例えば同じ状態を\([\alpha, \beta]\)と書いてもいいわけです3

量子ビットの任意の状態が2つの複素数で表せるということは、逆に言えば一つの量子ビットには2つの複素数に相当する情報を記録できるということになります。ただこれには少し注釈があって、量子力学の決まりごとから、\(\alpha\)\(\beta\)

\[ |\alpha|^2 + |\beta|^2 = 1 \]

という関係を満たさなければならず、かつ全体の位相(global phase)は意味を持たない、つまり

\[ \alpha \ket{0} + \beta \ket{1} \sim e^{i\theta} (\alpha \ket{0} + \beta \ket{1}) \]

(ここで \(\sim\) は「同じ量子状態を表す」という意味)という制約があります。

複素数1つは実数2つで書けるので、\(\alpha\)\(\beta\)をあわせて実数4つ分の情報が入っているようですが、2つの拘束条件があるため、実際の自由度は 4-2=2 個です。自由度の数をあらわにして量子ビットの状態を記述するときは、

\[ e^{-i\phi/2}\cos\frac{\theta}{2}\ket{0} + e^{i\phi/2}\sin\frac{\theta}{2}\ket{1} \]

と書いたりもします。この表記法をブロッホ球表現と呼ぶこともあります。

面白くなるのは量子ビットが複数ある場合です。例えば量子ビット2つなら、それぞれに\(\ket{0}, \ket{1}\)の計算基底があるので、任意の状態は

\[ \alpha \ket{0}\ket{0} + \beta \ket{0}\ket{1} + \gamma \ket{1}\ket{0} + \delta \ket{1}\ket{1} \]

と4つの複素数を使った重ね合わせになります。2つの量子ビットの基底を並べた\(\ket{0}\ket{0}\)のような状態が、このレジスタの計算基底ということになります。\(\ket{00}\)と略したりもします。

上で登場した量子力学の決まりごとはこの場合

\[ |\alpha|^2 + |\beta|^2 + |\gamma|^2 + |\delta|^2 = 1 \]

\[ \alpha \ket{00} + \beta \ket{01} + \gamma \ket{10} + \delta \ket{11} \sim e^{i\theta} (\alpha \ket{00} + \beta \ket{01} + \gamma \ket{10} + \delta \ket{11}) \]

となります。量子ビットがいくつあっても拘束条件は2つだけです。

つまり、量子ビット\(n\)個のレジスタでは、基底の数が\(2^n\)個で、それぞれに複素数の振幅がかかるので、実数\(2 \times 2^n - 2\)個分の情報が記録できることになります。これが量子計算に関して「指数関数的」という表現がよく用いられる所以です。

量子レジスタの計算基底状態の表記法としては、上に書いたようにケットを\(n\)個並べたり\(n\)個の0/1を一つのケットの中に並べたりする方法がありますが、さらにコンパクトなのが、0/1の並び(ビット列)を二進数とみなして、対応する(十進数の)数字で表現する方法です。例えば4量子ビットのレジスタで状態\(\ket{0000}\)\(\ket{1111}\)はそれぞれ\(\ket{0}\)\(\ket{15}\)と書けます。

ただし、ここで注意すべきなのは、左右端のどちらが「1の位」なのか事前に約束しないといけないことです。\(\ket{0100}\)\(\ket{4}\)(右端が1の位)とするか\(\ket{2}\)(左端が1の位)とするかは約束次第です。このワークブックでは、Qiskitでの定義に従って、右端を1の位とします。同時に、レジスタの最初の量子ビットが1の位に対応するようにしたいので、ケットや0/1を並べて計算基底を表現するときは、右から順にレジスタの量子ビットを並べていくことにします。

Qiskitには量子レジスタオブジェクトがあり、

from qiskit import QuantumRegister
register = QuantumRegister(4, 'myregister')

のように量子ビット数(この場合4)と名前('myregister')を指定して初期化します。初期状態では、量子ビットはすべて\(\ket{0}\)状態にあります。レジスタオブジェクトはこのままではあまり使い道がなく、基本的には次に紹介する量子回路の一部として利用します。

ゲート、回路、測定

量子計算とは、端的に言えば、量子レジスタに特定の状態を生成し、その振幅を利用することと言えます。

とは言っても、いきなり「えいや」と好きな量子状態を作れるわけではなく、パターンの決まった単純操作(\(\ket{0}\)\(\ket{1}\)を入れ替える、ブロッホ球表現での位相角度\(\phi\)を増減させる、など)を順番に組み合わせて複雑な状態を作っていきます。この単純操作のオペレーションのことを一般に量子ゲートといい、ゲートの種類や順番を指定したプログラムに相当するものを量子回路と呼びます。

Qiskitでは、量子回路をQuantumCircuitオブジェクトで表します。

from qiskit import QuantumCircuit, QuantumRegister
register = QuantumRegister(4, 'myregister')
circuit = QuantumCircuit(register)

という具合です。

作られた量子回路は、量子ビットの数が決まっているもののゲートが一つもない「空っぽ」の状態なので、そこにゲートをかけていきます。例えば下で説明するアダマールゲートをレジスタの2個目の量子ビットに作用させるには

circuit.h(register[1])

とします。

上で「振幅を利用する」という曖昧な表現をしましたが、それはいろいろな利用の仕方があるからです。しかし、どんな方法であっても、必ず量子レジスタの測定という操作を行います。量子コンピュータから何かしらの情報を得るための唯一の方法が測定です。

測定は量子レジスタの状態を「覗き見る」ような操作ですが、一回の測定操作で具体的に起きることは、各量子ビットに対して0もしくは1という値が得られるというだけです。つまり、量子状態が\(2^n\)個の計算基底の複雑な重ね合わせであったとしても、測定をすると一つの計算基底に対応するビット列が出てくるだけということになります。しかも、一度測定してしまった量子ビットはもう状態を変えてしまっていて、複雑な重ね合わせは失われてしまいます。

ではこの「一つの計算基底」がどの基底なのかというと、実は特殊な場合を除いて決まっていません。全く同じ回路を何度も実行して測定を繰り返すと、その都度ランダムにビット列が決まります。ただし、このランダムさには法則があって、特定のビット列が得られる確率は、対応する計算基底の振幅の絶対値自乗となっています。つまり、\(n\)ビットレジスタの状態\(\sum_{j=0}^{2^n-1} c_j \ket{j}\)があるとき、測定でビット列\(k\)が得られる確率は\(|c_k|^2\)です。

したがって、回路の実行と測定を何度も繰り返して、それぞれのビット列が現れる頻度を記録すれば、だんだん\(|c_j|^2\)の値がわかっていき、レジスタがどんな量子状態にあるのか見えてきます。しかし、わかるのは\(c_j\)の絶対値だけで、複素位相については知る術なしです。どうもすっきりしませんが、これが量子コンピュータから情報を得る方法です。

逆に、指数関数的な内部の情報量をうまく使って計算を行いつつ、測定という限定的な方法でも答えが読み出せるように工夫するのが、量子アルゴリズム設計の真髄ということになります。例えば理想的には、何か計算の答えが整数\(k\)であり、それを計算する回路の終状態が単純に\(\ket{k}\)となるようであれば、一度の測定で答えがわかる(上でいった特殊な場合に相当)わけです。単純に\(\ket{k}\)でなくても、重ね合わせ\(\sum_{j=0}^{2^n-1} c_j \ket{j}\)において\(|c_k| \gg |c_{j \neq k}|\)を実現できれば、数回の測定で答えが高確率でわかります。素因数分解アルゴリズムを学習するで紹介する位相推定アルゴリズムはその好例です。

よく使うゲート

IBM Q System Oneのような超電導振動子を利用した量子コンピュータでは、実際に使用できるゲートは量子ビット1つにかかるものと2つにかかるものに限定されます。しかし、それらを十分な数組み合わせれば、\(n\)量子ビットレジスタにおいてどのような状態も実現できることが、数学的に証明できます。

1量子ビットの操作

1量子ビットの操作でよく使われるゲートには、以下のようなものがあります。(表中コードのi, jは量子ビットの番号)

ゲート名

説明

Qiskitコード

\(X\)

\(\ket{0}\)\(\ket{1}\)を入れ替える。

circuit.x(i)

\(Z\)

\(\ket{1}\)の振幅を\(-1\)倍する。

circuit.z(i)

\(H\)(アダマールゲート)

計算基底それぞれに対して、以下の変形をする。

\[\begin{split}H\ket{0} = \frac{1}{\sqrt{2}} (\ket{0} + \ket{1}) \\ H\ket{1} = \frac{1}{\sqrt{2}} (\ket{0} - \ket{1})\end{split}\]

(「量子状態にゲートを作用させる」ことをケットの記法で書くときは、ゲートに対応する記号をケットに左からかけます。)
例えば状態\(\ket{\psi} = \alpha\ket{0} + \beta\ket{1}\)に対しては、

\[\begin{split}\begin{align} H\ket{\psi} & = \alpha \frac{1}{\sqrt{2}} (\ket{0} + \ket{1}) + \beta \frac{1}{\sqrt{2}} (\ket{0} - \ket{1}) \\ & = \frac{1}{\sqrt{2}} (\alpha + \beta) \ket{0} + \frac{1}{\sqrt{2}} (\alpha - \beta) \ket{1} \end{align}\end{split}\]

となる。

circuit.h(i)

\(R_{y}\)

パラメータ\(\theta\)を取り、計算基底それぞれに対して、以下の変形をする。

\[\begin{split}R_{y}(\theta)\ket{0} = \cos\frac{\theta}{2}\ket{0} + \sin\frac{\theta}{2}\ket{1} \\ R_{y}(\theta)\ket{1} = -\sin\frac{\theta}{2}\ket{0} + \cos\frac{\theta}{2}\ket{1}\end{split}\]

circuit.ry(theta, i)

\(R_{z}\)

パラメータ\(\phi\)を取り、計算基底それぞれに対して、以下の変形をする。

\[\begin{split}R_{z}(\phi)\ket{0} = e^{-i\phi/2}\ket{0} \\ R_{z}(\phi)\ket{1} = e^{i\phi/2}\ket{1}\end{split}\]

circuit.rz(phi, i)

それでは、2量子ビットレジスタの第0ビットに\(H, R_y, X\)の順にゲートをかけて、最後に測定をする回路をQiskitで書いてみましょう。

# まずは必要になるpythonモジュールをすべてインポートしておく
import numpy as np
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, IBMQ, execute
from qiskit.providers.ibmq import least_busy
from qiskit.tools.monitor import job_monitor
from qiskit.visualization import plot_histogram

print('notebook ready')
circuit = QuantumCircuit(2) # レジスタを介さずビット数を指定して回路を作成することもできます
circuit.h(0) # その場合、ゲートにはregister[0]ではなく直接量子ビットの番号を指定します
circuit.ry(np.pi / 2., 0) # θ = π/2
circuit.x(0)
circuit.measure_all()

print('This circuit has', circuit.num_qubits, 'qubits and', circuit.size(), 'operations')
This circuit has 2 qubits and 5 operations

最後のプリント文で、ゲートが3つなのにも関わらず “5 operations” と出てくるのは、各量子ビットの測定も一つのオペレーションとして数えられるからです。

量子計算に慣れる目的で、この\(H, R_y(\pi/2), X\)という順の操作で第0ビットに何が起こるかを辿ってみましょう。初期状態は\(\ket{0}\)で、ケット記法では操作は左からかけていくので、\(X R_y(\pi/2) H \ket{0}\)を計算することになります。

\[\begin{split} \begin{align} X R_y\left(\frac{\pi}{2}\right) H \ket{0} & = X R_y\left(\frac{\pi}{2}\right) \frac{1}{\sqrt{2}}(\ket{0} + \ket{1}) \\ & = \frac{1}{\sqrt{2}} X \left[\left(\cos\left(\frac{\pi}{4}\right)\ket{0} + \sin\left(\frac{\pi}{4}\right)\ket{1}\right) + \left(-\sin\left(\frac{\pi}{4}\right)\ket{0} + \cos\left(\frac{\pi}{4}\right)\ket{1}\right)\right] \\ & = \frac{1}{\sqrt{2}} X \frac{1}{\sqrt{2}} \left[\left(\ket{0} + \ket{1}\right) + \left(-\ket{0} + \ket{1}\right)\right] \\ & = X \ket{1} \\ & = \ket{0} \end{align} \end{split}\]

なので、結局\(\ket{0}\)状態に戻る操作でした。

2量子ビットの操作

2量子ビットの操作は、量子ビットの超電導素子での実装の都合上、全て「制御ゲート」(controlled gates)という方式で行われます。この方式では、2つのビットのうち片方を制御(control)、もう片方を標的(target)として、制御ビットが1の時だけ標的ビットに何らかの操作がかかります。

例として、任意の1ビットゲート\(U\)を制御ゲート化した\(C^i_j[U]\)を考えます。ここで\(i\)が制御、\(j\)が標的ビットとします。ケットの添字でビットの番号を表して(reminder: 並べて書くときは右から順に番号を振ります)

\[\begin{split} \begin{align} C^1_0[U](\ket{0}_1\ket{0}_0) & = \ket{0}_1\ket{0}_0 \\ C^1_0[U](\ket{0}_1\ket{1}_0) & = \ket{0}_1\ket{1}_0 \\ C^1_0[U](\ket{1}_1\ket{0}_0) & = \ket{1}_1U\ket{0}_0 \\ C^1_0[U](\ket{1}_1\ket{1}_0) & = \ket{1}_1U\ket{1}_0 \end{align} \end{split}\]

です。

上で紹介した頻出する1ビットゲート\(X, Z, H, R_y, R_z\)のうち、\(H\)以外は制御ゲート化バージョンもよく使われます。特に\(C[X]\)はCXやCNOTとも呼ばれ、量子計算の基本要素として多様されます。実際、全ての2量子ビット制御ゲートはCNOTと1量子ビットゲートの組み合わせに分解できます。

ゲート名

説明

Qiskitコード

\(C^i_j[X]\), CX, CNOT

ビット\(i\)が1である計算基底において、ビット\(j\)\(X\)を作用させる。

circuit.cx(i, j)

\(C^i_j[Z]\)

ビット\(i, j\)が1である計算基底の符号を反転させる。

circuit.cz(i, j)

\(C^i_j[R_{y}]\)

パラメータ\(\theta\)を取り、ビット\(i\)が1である計算基底において、ビット\(j\)\(R_y\)を作用させる。

circuit.cry(theta, i, j)

\(C^i_j[R_{z}]\)

パラメータ\(\phi\)を取り、ビット\(i\)が1である計算基底において、ビット\(j\)\(R_z\)を作用させる。

circuit.crz(phi, i, j)

Qiskitで2ビットレジスタに制御ゲートを用い、計算基底\(\ket{0}, \ket{1}, \ket{2}, \ket{3}\)の振幅の絶対値自乗が\(1:2:3:4\)の比になるような状態を作ってみましょう。さらに\(C^0_1[Z]\)ゲートを使って\(\ket{3}\)だけ振幅の符号が他と異なるようにします。

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('This circuit has', circuit.num_qubits, 'qubits and', circuit.size(), 'operations')
This circuit has 2 qubits and 6 operations

やや複雑ですが、また計算を追ってみましょう。まず角度\(\theta_1, \theta_2, \theta_3\)は定義から以下の関係を満たします。

\[\begin{split} \begin{align} R_y(\theta_1)\ket{0} & = \sqrt{\frac{3}{10}} \ket{0} + \sqrt{\frac{7}{10}} \ket{1} \\ R_y(\theta_2)\ket{0} & = \sqrt{\frac{1}{3}} \ket{0} + \sqrt{\frac{2}{3}} \ket{1} \\ R_y(\theta_3 - \theta_2)R_y(\theta_2)\ket{0} & = R_y(\theta_3)\ket{0} = \sqrt{\frac{3}{7}} \ket{0} + \sqrt{\frac{4}{7}} \ket{1}. \end{align} \end{split}\]

したがって、

\[\begin{split} \begin{align} & C^1_0[R_y(\theta_3 - \theta_2)]R_{y1}(\theta_1)R_{y0}(\theta_2)\ket{0}_1\ket{0}_0 \\ = & C^1_0[R_y(\theta_3 - \theta_2)]\left(\sqrt{\frac{3}{10}} \ket{0}_1 + \sqrt{\frac{7}{10}} \ket{1}_1\right) R_y(\theta_2)\ket{0}_0\\ = & \sqrt{\frac{3}{10}} \ket{0}_1 R_y(\theta_2)\ket{0}_0 + \sqrt{\frac{7}{10}} \ket{1}_1 R_y(\theta_3)\ket{0}_0 \\ = & \sqrt{\frac{3}{10}} \ket{0}_1 \left(\sqrt{\frac{1}{3}} \ket{0}_0 + \sqrt{\frac{2}{3}} \ket{1}_0\right) + \sqrt{\frac{7}{10}} \ket{1}_1 \left(\sqrt{\frac{3}{7}} \ket{0}_0 + \sqrt{\frac{4}{7}} \ket{1}_0\right) \\ = & \sqrt{\frac{1}{10}} \ket{00} + \sqrt{\frac{2}{10}} \ket{01} + \sqrt{\frac{3}{10}} \ket{10} + \sqrt{\frac{4}{10}} \ket{11} \end{align} \end{split}\]

最初の行で、ビット0と1にかかる\(R_y\)ゲートをそれぞれ\(R_{y0}, R_{y1}\)と表しました。

最後に\(C[Z]\)をかけると、\(\ket{11}\)だけ符号が反転します。

回路図の書き方と読み方

量子回路を可視化する方法として、「回路図」の標準的な描き方が決まっています。QiskitではQuantumCircuitオブジェクトのdraw()というメソッドを使って自動描画できます。

circuit.draw('mpl')
_images/chsh_inequality_14_0.png

ここで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ビットゲートから伸びている先端の丸い縦線は制御を表します。一番下の二重線は「古典レジスタ」に対応し、測定結果の0/1が記録される部分です。

CHSH不等式を計算する回路を書く

それではいよいよ本題に入りましょう。CHSH不等式は二体系の4つの観測量の関係式なので、2ビットの回路を4つ用意します。そして、それぞれにおいて「ベル状態」\(1/\sqrt{2}(\ket{00} + \ket{11})\)を実現します。ベル状態は「どちらの量子ビットについても\(\ket{0}\)でも\(\ket{1}\)でもない状態」つまり、全体としては一つの定まった(純粋)状態であるにも関わらず、部分を見ると純粋でない状態です。このような時、二つの量子ビットはエンタングルしているといいます。エンタングルメントの存在は量子力学の非常に重要な特徴です。

ベル状態はアダマールゲートとCNOTゲートを組み合わせて作ります。ここでは回路を4つ作るので、ループを回してcircuitsという配列に回路を足していきます。

CHSH不等式の検証用の観測量を作るために、量子ビット1に対し、回路0と2では測定の直前に\(R_y(-\pi/4)\)、1と3では\(R_y(-3\pi/4)\)を作用させます。また回路2と3では量子ビット0に\(R_y(-\pi/2)\)を同じく測定の直前に作用させます。

circuits = []
for ic in range(4):
    circuit = QuantumCircuit(2, name='circuit{}'.format(ic))
    circuit.h(0)
    circuit.cx(0, 1)
    circuits.append(circuit)

circuits[0].ry(-np.pi / 4., 1)
circuits[1].ry(-3. * np.pi / 4., 1)
circuits[2].ry(-np.pi / 4., 1)
circuits[3].ry(-3. * np.pi / 4., 1)

circuits[2].ry(-np.pi / 2., 0)
circuits[3].ry(-np.pi / 2., 0)

for circuit in circuits:
    circuit.measure_all()

# draw()にmatplotlibのaxesオブジェクトを渡すと、そこに描画してくれる
# 一つのノートブックセルで複数プロットしたい時などに便利
for circuit in circuits:
    ax = plt.figure().add_subplot()
    circuit.draw('mpl', ax=ax)
_images/chsh_inequality_19_0.png _images/chsh_inequality_19_1.png _images/chsh_inequality_19_2.png _images/chsh_inequality_19_3.png

それぞれの回路で2ビットレジスタの基底\(\ket{00}, \ket{01}, \ket{10}, \ket{11}\)が現れる確率を計算してみましょう。

回路0の状態は

\[\begin{split} \begin{align} R_{y1}\left(-\frac{\pi}{4}\right) C^0_1[X] H_0 \ket{0}_1\ket{0}_0 = & R_{y1}\left(-\frac{\pi}{4}\right) \frac{1}{\sqrt{2}} (\ket{0}_1\ket{0}_0 + \ket{1}_1\ket{1}_0) \\ = & \frac{1}{\sqrt{2}} \left[(c\ket{0}_1 - s\ket{1}_1)\ket{0}_0 + (s\ket{0}_1 + c\ket{1}_1)\ket{1}_0\right]\\ = & \frac{1}{\sqrt{2}} (c\ket{00} + s\ket{01} - s\ket{10} + c\ket{11}). \end{align} \end{split}\]

簡単のため\(c = \cos(\pi/8), s = \sin(\pi/8)\)とおきました。

したがって回路0での確率\(P^0_{l} \, (l=00,01,10,11)\)

\[\begin{split} P^0_{00} = P^0_{11} = \frac{c^2}{2} \\ P^0_{01} = P^0_{10} = \frac{s^2}{2} \end{split}\]

同様に、回路1の状態は

\[ R_{y1}\left(-\frac{3\pi}{4}\right) \frac{1}{\sqrt{2}} (\ket{0}_1\ket{0}_0 + \ket{1}_1\ket{1}_0) = \frac{1}{\sqrt{2}} (s\ket{00} + c\ket{01} - c\ket{10} + s\ket{11}) \]

で確率\(P^1_{l}\)

\[\begin{split} P^1_{00} = P^1_{11} = \frac{s^2}{2} \\ P^1_{01} = P^1_{10} = \frac{c^2}{2} \end{split}\]

です。回路2の状態は

\[\begin{split} \begin{align} & R_{y1}\left(-\frac{\pi}{4}\right) R_{y0}\left(-\frac{\pi}{2}\right) \frac{1}{\sqrt{2}} (\ket{0}_1\ket{0}_0 + \ket{1}_1\ket{1}_0) \\ = & \frac{1}{\sqrt{2}} \left[ \frac{1}{\sqrt{2}} (c\ket{0}_1 - s\ket{1}_1) (\ket{0}_0 - \ket{1}_0) + \frac{1}{\sqrt{2}} (s\ket{0}_1 + c\ket{1}_1) (\ket{0}_0 + \ket{1}_0) \right] \\ = & \frac{1}{2} \left[ (s+c)\ket{00} + (s-c)\ket{01} - (s-c)\ket{10} + (s+c)\ket{11} \right] \end{align} \end{split}\]

で確率\(P^2_{l}\)

\[\begin{split} P^2_{00} = P^2_{11} = \frac{(s + c)^2}{4} \\ P^2_{01} = P^2_{10} = \frac{(s - c)^2}{4} \end{split}\]

同様に回路3の状態と確率\(P^3_i\)

\[\begin{split} \begin{align} & R_{y1}\left(-\frac{3\pi}{4}\right) R_{y0}\left(-\frac{\pi}{2}\right) \frac{1}{\sqrt{2}} (\ket{0}_1\ket{0}_0 + \ket{1}_1\ket{1}_0) \\ = & \frac{1}{2} \left[ (s+c)\ket{00} - (s-c)\ket{01} + (s-c)\ket{10} + (s+c)\ket{11} \right] \end{align} \end{split}\]
\[\begin{split} P^3_{00} = P^3_{11} = \frac{(s + c)^2}{4} \\ P^3_{01} = P^3_{10} = \frac{(s - c)^2}{4} \end{split}\]

となります。

それぞれの回路でビット0と1で同じ値が観測される確率\(P^{i}_{00} + P^{i}_{11}\)から異なる値が観測される確率\(P^{i}_{01} + P^{i}_{10}\)を引いた値を\(C^{i}\)と定義します。

\[\begin{split} C^0 = c^2 - s^2 = \cos\left(\frac{\pi}{4}\right) = \frac{1}{\sqrt{2}} \\ C^1 = s^2 - c^2 = -\frac{1}{\sqrt{2}} \\ C^2 = 2sc = \sin\left(\frac{\pi}{4}\right) = \frac{1}{\sqrt{2}} \\ C^3 = 2sc = \frac{1}{\sqrt{2}} \end{split}\]

なので、これらの組み合わせ\(S = C^0 - C^1 + C^2 + C^3\)の値は\(2\sqrt{2}\)です。

実は、エンタングルメントが起こらない場合、この観測量\(S\)の値は2を超えられないことが知られています。例えば\(R_y\)ゲートをかける前の状態がベル状態ではなく、確率\(\frac{1}{2}\)\(\ket{00}\)、確率\(\frac{1}{2}\)\(\ket{11}\)という「混合状態」である場合、

\[\begin{split} C^0 = \frac{1}{\sqrt{2}} \\ C^1 = -\frac{1}{\sqrt{2}} \\ C^2 = 0 \\ C^3 = 0 \end{split}\]

となり、\(S = \sqrt{2} < 2\)です。これがCHSH不等式です。

それでは、IBMQの「量子コンピュータ」が実際にエンタングル状態を生成できるのか、上の四つの回路から\(S\)の値を計算して確認してみましょう。

回路を実機で実行する

まずはトークンを使ってIBMQに接続します。

IBMQ.enable_account('__paste_your_token_here__')

認証が済んだら、利用する量子コンピュータ(「バックエンド」と呼びます)を選びます。

# IBMQプロバイダ(実機へのアクセスを管理するオブジェクト)
provider = IBMQ.get_provider(hub='ibm-q', group='open', project='main')

# バックエンド(実機)のうち量子ビット数2個以上のもののリストをプロバイダから取得し、一番空いているものを選ぶ
backend_filter = lambda b: (not b.configuration().simulator) and (b.configuration().n_qubits >= 2) and b.status().operational
backend = least_busy(provider.backends(filters=backend_filter))

print('Jobs will run on', backend.name())

回路をバックエンドに送るには、execute関数を使います。引数shotsで回路の実行と測定を何回繰り返すか指定します。バックエンドごとに一度のジョブでの最大ショット数が決められていますが、大半の場合8192 (=213)回となっています。

一つのジョブで複数の回路をshots回ずつ実行できます。一度に送れる回路の数はバックエンドごとに決まっています。

shots = 8192

job = execute(circuits, backend=backend, shots=shots)

job_monitor(job, interval=2)

これで回路がジョブとしてバックエンドに送られ、キューに入りました。ジョブの実行結果はexecute関数の返り値であるジョブオブジェクトから参照します。

IBMQのバックエンドは世界中からたくさんのユーザーに利用されているため、場合によっては予約されているジョブが多数あってキューにかなりの待ち時間が生じることがあります。

バックエンドごとのキューの長さはIBM Quantum Experienceのホーム画面の右側で確認できます。この右側のコラムでバックエンドを一つクリックすると詳細が表示され、一番下の Your access providers という欄でバックエンドの最大ショット数と最大ジョブ数を確認できます。

また、自分の投じたジョブのステータスは左のツールバーのうちのJobsボタンから確認できます。

Qiskitプログラム中からもジョブのステータスを確認できます。いくつか方法がありますが、シンプルに一つのジョブをテキストベースでモニターするだけなら上のようにjob_monitorを使います。

量子測定結果の解析

ジョブオブジェクトのresult()というメソッドを呼ぶと、ジョブが完了して結果が帰ってくるまでコードの実行が止まります。実行結果はオブジェクトとして返され、それのget_countsというメソッドを使うと、各ビット列が何回観測されたかというヒストグラムデータがPythonのdictとして得られます。

result = job.result()

counts = []
for circuit in circuits:
    c = result.get_counts(circuit)
    counts.append(c)
    
print(counts)

Tip

ノートブックの接続が切れてしまったり、過去に走らせたジョブの結果を再び解析したくなったりした場合は、ジョブIDを使ってretrieve_jobというメソッドでジョブオブジェクトを再構成することができます。過去に走らせたジョブはIBM Quantum Experienceのホームページにリストされているので、そこにあるジョブID(603d8bef43659838aのような)をコピーし、

backend = provider.get_backend('backend_you_used')
job = backend.retrieve_job('job_id')

とすると、executeによって返されたのと同じようにジョブオブジェクトが生成されます。

Qiskitから提供されているplot_histogram関数を使って、この情報を可視化できます。プロットの縦軸は観測回数を全測定数で割って、観測確率に規格化してあります。

for c in counts:
    ax = plt.figure().add_subplot()
    plot_histogram(c, ax=ax)
_images/chsh_inequality_32_0.png _images/chsh_inequality_32_1.png _images/chsh_inequality_32_2.png _images/chsh_inequality_32_3.png

上で、\(c^2 = (\sqrt{2} + 1) / 2\sqrt{2} \sim 0.85\), \(s^2 = (\sqrt{2} - 1) / 2\sqrt{2} \sim 0.15\)なので、得られた確率は当たらずとも遠からずというところでしょうか。

実は現在の量子コンピュータにはまだ様々なノイズやエラーがあり、計算結果は往々にして理論的な値から統計誤差の範囲を超えてずれます。特定のエラーに関しては多少の緩和法も存在しますが、全て防げるわけでは決してありません。現在の量子コンピュータを指して “Noisy intermediate-scale quantum (NISQ) device” と呼んだりしますが、このNoisyの部分はこのような簡単な実験でもすでに顕著に現れるわけです。

逆に、NISQデバイスを有効活用するには、ノイズやエラーがあっても意味のある結果が得られるようなロバストな回路が求められます。変分法と変分量子固有値ソルバー法を学習するで紹介する変分量子回路を用いた最適化などがその候補として注目されています。

さて、それでは最後にCHSH不等式の破れを確認してみましょう。\(C^{0}, C^{1}, C^{2}, C^{3}\)を計算して\(S\)を求めます。

C = []
for c in counts:
    C.append((c['00'] + c['11'] - c['01'] - c['10']) / shots)
    
S = C[0] - C[1] + C[2] + C[3]

print('C:', C)
print('S =', S)
if S > 2.:
    print('Yes, we are using a quantum computer!')
C: [0.613525390625, -0.615234375, 0.59814453125, 0.635498046875]
S = 2.46240234375
Yes, we are using a quantum computer!

無事、\(S\)が2を超えました。

参考文献

CHSH69

John F. Clauser, Michael A. Horne, Abner Shimony, and Richard A. Holt. Proposed experiment to test local hidden-variable theories. Phys. Rev. Lett., 23:880–884, Oct 1969. URL: https://link.aps.org/doi/10.1103/PhysRevLett.23.880, doi:10.1103/PhysRevLett.23.880.

NC00

Michael A Nielsen and Isaac L Chuang. Quantum Computation and Quantum Information, chapter 2.6, pages 111–117. Cambridge University Press, 2000.

1

正確には、状態がケットで表せるのはこのレジスタが他のレジスタとエンタングルしていないときに限られますが、ここでは詳細を割愛します。

2

ここで言う「基底」は線形代数での意味(basis)で、「線形空間中の任意のベクトルを要素の線形和で表せる最小の集合」です。基底となる量子状態だから「基底状態」と呼びます。化学や量子力学で言うところのエネルギーの最も低い状態「基底状態」(ground state)とは関係ありません。

3

実際に量子計算のシミュレーションをコンピュータ上で行う時などは、量子レジスタの状態を複素数の配列で表すので、この表記の方がよく対応します。