量子ゲート回路プログラミングに入門してみよう

こんにちは。Monacaチームの内藤です。


最近、量子コンピュータという言葉を、ニュースなどでも耳にするようになりました。次世代のコンピュータとして期待され、もし実現出来れば、現在のコンピューターの並列計算よりも格段に計算パワーがアップする可能性を持っていると言われています。

そこで、この記事では、そんな量子コンピューターの雰囲気を少しでも味わってみるために、qiskitというpythonのライブラリを使って、量子コンピュータの仕組みの一つである量子ゲート回路プログラミングをシミュレータで試してみたこと・勉強したことについてまとめてみました

なお、この記事のコードを試してみるためには、pythonの環境としてVersion 3.6が必要です。私の環境では、3.8.12を使いました。以下のpipコマンドで、必要なライブラリを組み込みます。

% pip install 'qiskit[visualization]'

一番簡単なサンプルコード

では、さっそく最初のサンプルコードを書いてみましょう。sample001.pyとして、次の様なコードを記述します。

from qiskit import QuantumCircuit, transpile
from qiskit.providers.aer import QasmSimulator

circuit = QuantumCircuit(1, 1)
circuit.measure([0], [0])
circuit.draw(output='mpl', filename="result001.png")
simulator = QasmSimulator()
compiled = transpile(circuit)
job = simulator.run(compiled, shots=100)
result = job.result()
dict = result.get_counts(compiled)
print(dict)

このコードは、次の様な意味になります。最初は、全体の雰囲気がわかれば十分だと思います。

1,2行目で、ライブラリを読み込んでいます。
4行目で、回路を作成しています。第一引数は量子ビットを1ビット、第二引数は古典ビットを1ビット用意していることを意味します。QuantimCircuitで作成したばかりの量子ビットは|0>という状態を持っています。
5行目で、出来たばかりの量子ビットを古典ビットで計測しています。つまり、|0>という状態を「計測」していることになります。量子ゲート回路の作成はここで終わりです。
6行目は、この量子ゲート回路をresult001.pngというファイル名でpng画像として出力しています。
7行目〜10行目で、量子ゲート回路のシミュレータを作成してコンパイルし、100回実行(シミュレート)しています。
11行目で、100回実行した結果を取り出し、12行目でそれを出力しています。

さて、それではこのコードを実行してみましょう

% python sample001.py
{'0': 100}

実行しました!
でも、この結果一体どういうことでしょうか?

実はこれは「0」という結果が100回得られました、という意味です。
先に書いた様に、作成したばかりの量子ビットは|0>という状態です。つまり「0」です。そして、それを計測したのだから、「0」が得られた、ただそれだけのことなのです。せっかくなので、この回路の図が作成されているので、それも見てみましょう。

すごいシンプルですが、こんな感じです。

ここで、もう一度 この回路について説明してみましょう。この回路は、量子ビット(q)と、古典ビット(c)が1つずつあります。そして、このうちの量子ビットというのは、|0>や|1>といった状態を持ちます。この|0>や|1>というのは、通常のデジタル回路における0や1とほぼ同じように考えることができます。
そして、この回路では、量子ビットをすぐ古典ビットで「計測」しています。QuantumCircuitで最初に作成された量子ビットは|0>という状態なので、これを古典ビットで計測すると0になります。もちろん、100回計測すると、100回とも0になります。
これが、今回の結果

% python sample001.py
{'0': 100}

の意味なのでした。

量子ビットの性質

さて、これだけでは、量子ビットって古典ビットと同じように思えますが、もちろん、量子ビットは古典ビットとは異なる性質を持っています。

まず、量子ビットは、古典ビットのような「0,1の値」ではなくて、量子力学的な「スピン」と呼ばれるものに対応し、「方向」と関連しています。方向を持つというとは、何かベクトル的なものであるということです。

例えで言うと、コインの「表か裏か」のようなものです。いま、コマがテーブルの上にあって、それを真上からみてみましょう。コインはきっと、「表」か「裏」かになっています。つまり、真上から見ると表(これを|0>とラベルします)か裏(これを|1>とラベルします)のどちらかであるため、0または1の値をとる古典ビットのように思うことが出来るのです。

では、このコインを「横から見る」とどうなるでしょうか? 横から見たら、表でも裏でもない状態になりそうです。先程、表を0、裏を1としたのですが、ということは、横から見たらなんとなくその値は0と1の中間である0.5になりそうではありませんか? 古典的に考えると、0.5という値になりそうです。
(ここでは、簡単のために、0と1の古典的な中間状態として0.5が得られそうであるという議論をしていますが、この0や1は人為的に付与したラベルに過ぎないので、本来は、z方向のスピン演算子の、 |0>に対応する固有値+1、|1>に対応する固有値-1で考えるべきです。今の場合は、スピン固有値λとラベルLの間には、L = (λ+1)/2という関係があるので、λが+1のときと-1のときの中間が0であることを考えると、この時対応するラベルはL=0.5になるので、これで良いことになります)

そうなのですが、実は量子ビットでは、例え「横から見た」としても、その結果は、かならず「表(0)」か「裏(1)」かのどちらかになってしまいます。その意味では、古典的な考え方、連続的な変化量とは異なっています。でもその代わりに、量子ビットを「横から見た」結果が「表」になるか「裏」になるか、その可能性は半々になります。つまり、「横から見る」と、このコインは、50%の確率で表になり、50%の確率で裏になるという訳です。
(ここでは詳しく立ち入りませんが、「横から見た」ことで元の状態は壊されてしまいます。そのため、「横から見た」結果が白だっとすると、その状態をもう一度「横から見た」場合、必ず白になります)

ということはつまり、「横から見た」場合は、表(0)になる可能性は50%で、裏(1)になる可能性も50%なので、その「期待値として」は0.5になります。この「期待値」0.5というのは、古典的に考えたときの0.5にちょうど一致しています。ここが重要で、1量子ビットを考える場合、古典的な表(0)と裏(1)が一定の割合(ここでは50%ずつ)でまざったような状態として考えることが出来き、その期待値として古典的な結果(0.5)を再現します。

量子ビットを「横から」観測した場合のサンプルコード

では、作成した量子ビットを「横から見る」サンプルコードを書いてみましょう。

import numpy as np
from qiskit import QuantumCircuit, transpile
from qiskit.providers.aer import QasmSimulator

circuit = QuantumCircuit(1, 1)
circuit.rx(np.pi/2, 0)
circuit.measure([0], [0])
circuit.draw(output='mpl', filename="result002.png")
simulator = QasmSimulator()
compiled = transpile(circuit)
job = simulator.run(compiled, shots=100)
result = job.result()
dict = result.get_counts(compiled)
print(dict)

このコードには、新しく circuit.rx(np.pi/2, 0) という行が追加されています。これは、観測している系をx軸のまわりに90度回転する(ブロッホ球を90度回転する)ことに相当します。(「横から見る」といっても、どっち側の横から見るかによって計算が変わりますが、この場合は、y軸負の向きから見ていることに相当します。最終的に表・裏の確率が50%になるのは、どちら側の「横」から見るかには依存しませんが、プログラムコードまたは計算式に現れるブロッホ球の回転角は変わるので注意です)

このコードを実行すると、予想通り

% python sample002.py
{'0': 50, '1': 50}

このようになり、100回シミュレートしたうち、0の状態なるのが50回、1の状態になるのは50回となりました。(シミュレーションなので、ぴったり50:50にならずに誤差により多少ずれる場合もあります)
この場合の回路図 result002.png は


です。このRxが量子ビットに対するブロッホ球をx軸の周りに90度(π/2)回転しているわけです。ちなみに、行列表現を使うと、|0> と |1> は

となります。これらは、それぞれパウリ行列σ_zの固有ベクトルで、それぞれ固有値1と-1に対応します。

回転行列Rxは

となります。(この式を導くには、やや面倒です。パウリ行列(σ_x, σ_y, σ_z)とベクトルとの内積をとり、これを列スピン、行スピンで挟んだものが回転で不変になるという要請をおくと、このような変換性が出てきます。詳しくは割愛します)

ちょっと注意することとしては、360度(θ=2π)回転しても、この回転行列Rxは単位行列に戻りません。そのため、スピンは、720度回転しないと、もとに戻らないという性質を持っています。

このため、上の量子回路で観測する前(古典回路に接続する前)の状態は

となります。そして、これを計測する場合は、表(<0|)との内積をとって、その絶対値の二乗が確率になるので、

となります。つまり、表になる確率が1/2 = 50%になるということです。

特定の方向のスピン固有値

上では、90度回転させたブロッホ球を考えていたのですが、これは座標系を都度考えなくてはいけないので、結構面倒ですし、ミスも起こりやすいです。実際には同じことなのですが、スピン演算子が系の回転によってどのように変換するかを考えることで、数式を使って明確に導出することが出来ます。

少しだけ一般化して、状態|0>と|1>の混ざった量子ビット|q> = c_1 |0 > + c_1 |1>の、方向vのスピンσ_vの期待値(確率振幅)を計算したいとします。いま、簡単のために、vの方向は、z軸正の向きとのなす各がθであると仮定し、それぞz'方向とします。

すると、z'方向のスピン演算子σ_z'は

このとき、z'方向のスピン演算子とz方向のスピン演算子の間には、次の様な関係があります。

そのため、このスピンの確率振幅は

となります。ここで、|0>と|1>が正規直交完全系(CONS)が成り立つとして、1 = |0><0| + |1><1| をつかいました。結局、この式により、量子状態|q>の任意の方向のスピンは、Rx(-θ)|q>状態の0になる確率と1になる確率を足したものになります。この右辺は結局

なので、qiskitでシミュレーションを行うことが可能です。これ結局、最初からブロッホ球をθ回転させたものと同じで、次の回路図になります。

量子もつれ状態のスピン

ここまでは1量子ビットの話だけでした。この1量子ビットの話をベースに、複数量子ビットの場合も扱うことが出来ます。特に、量子もつれ状態という状態が重要です。次の2量子ビット系の状態は、最大もつれ状態(あるいはベル状態)と呼ばれる、もっとも「もつれ」ている状態の例になっています。

この|00>とか|11>というのは、第0量子ビットが0、第1量子ビットが1のそれぞれzスピンの値を並べて表記したものです。つまり、|ab>というのは、第0量子ビットのzスピンが|a>、第1量子ビットのzスピンが|b>の状態となる、|a>⨂|b>のことです。

この状態を作るには、最初の状態|00>に、量子ビット0にR_y(π/2)をかけて

の状態を作成します。(添字の0、1は、どちらの量子ビットを表しているかのために導入しました)また、R_yと(今回は使っていませんが)R_zは

です。

これにさらに制御Not回路(以後では、これをCnotと記述します)を作用させます。制御Not回路Cnotは次のように、第0量子ビットの値が0なら第1量子ビットをそのまま、第0量子ビットの値が1なら第1量子ビットを反転させる回路です。

これにより、上の状態(|00>-|10>)/√2は、

となります。(第0ビットが1の時だけ、第1ビットが反転しているところがC_not回路の影響です)

これをプログラムで書いて、シミュレーションさせてみましょう。プログラムは次の様になります。

import numpy as np
from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.providers.aer import QasmSimulator

circuit = QuantumCircuit(2, 2)
circuit.ry(-np.pi/2, 0)
circuit.cx(0,1)
circuit.measure([0, 1], [0, 1])
circuit.draw(output='mpl', filename="result004.png")
simulator = QasmSimulator()
compiled = transpile(circuit)
job = simulator.run(compiled, shots=100)
result = job.result()
dict = result.get_counts(compiled)
print(dict)

対応する回路図は

となります。実行結果は

{'00': 50, '11': 50}

つまり、|00>になる確率が50%、|11>になる確率が50%となっています。

この2量子ビットの状態は、「それぞれの量子ビットが独立にそれぞれ|0>と|1>の「まざった」状態を持っていて、それが2量子ビットある」のではなくて、「2つの量子ビットがお互いの関係をもったまま、何か「不確定な」状態になっている」ことが重要です。このため、第0量子ビットが|0>であるときはかならず第1量子ビットも|0>になり、第0量子ビットが|1>であるときはかならず第1量子ビットも|1>になっています。

この量子もつれの状態は、ベルの不等式(CHSH不等式)を破るという量子論の著しい特徴を持っているのですが、ここでは割愛します。(参考:https://github.com/knight9999/chsh_inequality

まとめ

簡単にではありますが、qiskitのライブラリをつかって、簡単な量子ゲート回路のコードを見てみました。これを使うと、手軽にさまざまな演算子Oの期待値(行列要素あるいは確率振幅)<q|O|q>をシミュレートすることが出来るのが面白いのではないでしょうか?

量子ゲート回路を使って、何か具体的な計算回路を作るのは大変そうですが(自分もまだ勉強中です)、いろいろコードを作って遊んでみるのもいいかなと思います。