はじめに
最近、量子コンピュータのニュースを見る機会が増え、元々物理を勉強していたこともあって、興味が沸き勉強を始めました。そんな折、Qiitaのアドベンドカレンダー2017に量子コンピュータがあり、無謀にも参加登録しました。
今回は勉強したことを、実際にpyQuilで動かしてみたので、そのコードと簡単な解説をしたいと思います。
初学者なので記事の内容は大目に見てください。誤り等がありましたらご指摘ください。
勉強に使った書籍、ツール等
- 量子コンピュータ入門で勉強しました。リンク先は2版ですが、私が読んだのは1版でした。量子コンピュータと普通のコンピュータの違い、量子ビット、量子論理ゲートなど基本から学べます。途中で量子力学で使うディラックのブラケット記号が出てきます。この辺の計算をしたことがあると式を追うのが楽です。また式もたくさん出てくるのですが、具体的な数値を入れた簡単な例が出ていて、理解しやすかったです。私は5章まで読みました。本当はショアのアルゴリズムを実装したかったのですが、そこまで行けなかったです。。。
- pyQuilを使って、実際に量子コンピュータの量子論理ゲートを動かしました。実際は量子コンピュータのシュミレータのようですが、Pythonで簡単に使い始めることができます。
やったこと
作成したサンプルコードはgithubにあります。
サンプルコードでは、量子論理ゲートを使うと量子ビットがどのように変化するのかを試しています。量子論理ゲートは、量子ビットを変換する行列表現として考えればわかりやすいと思いますので、コードがどの行列表現に対応しているかを説明していきます。
処理がしやすいように初期処理や結果出力するクラスを作成しているのですが、それは後で説明します。
パウリゲート
私が理解しやすかったパウリゲートから行きます。
名前の通りパウリ行列がその表現になります。
\sigma_{x} = \begin{pmatrix}
0 & 1 \\
1 & 0
\end{pmatrix}\\
\sigma_{y} = \begin{pmatrix}
0 & -i \\
i & 0
\end{pmatrix}\\
\sigma_{z} = \begin{pmatrix}
1 & 0 \\
0 & -1
\end{pmatrix}
量子ビットの0, 1を以下のベクトルと考えると、
|0> = \begin{pmatrix}
1 \\
0
\end{pmatrix}\\
|1> = \begin{pmatrix}
0 \\
1
\end{pmatrix}
量子ビット0, 1がパウリゲート(ここの例ではxを使う)の入力になると、出力は、
\sigma_{x}|0> = \begin{pmatrix}
0 & 1 \\
1 & 0
\end{pmatrix}
\begin{pmatrix}
1 \\
0
\end{pmatrix} = \begin{pmatrix}
0 \\
1
\end{pmatrix} = |1>\\
\sigma_{x}|1> = \begin{pmatrix}
0 & 1 \\
1 & 0
\end{pmatrix}
\begin{pmatrix}
0 \\
1
\end{pmatrix} = \begin{pmatrix}
1 \\
0
\end{pmatrix} = |0> \\
となり、量子ビットの0と1が入れ替わっているのがわかります。
実際にコードを見ると、
def test_pauli_x(self):
self.q = qcuit()
self.q.set_initial_value(0)
self.q.inst(X(0))
results = self.q.run()
self.assertListEqual([[1]], results)
- self.q = qcuit(): オブジェクト作成
- self.q.set_initial_value(0): 入力が0
- self.q.inst(X(0)): パウリゲート(X)を作用
- results = self.q.run(): 実行して結果を取得
- self.assertListEqual([[1]], results): 出力は1
と、実際に量子ビットが0から1に変換されているのがわかります。
その他のパウリゲートも同じように変換が確認できるので説明は省略します。
アダマールゲート
アルゴリズムの中でよく使われるアダマールゲートですが、以下の行列表現です。
H = 1/\sqrt{2}\begin{pmatrix}
1 & 1 \\
1 & -1
\end{pmatrix}
量子ビット0を入力とすると、
H|0> = 1/\sqrt{2}\begin{pmatrix}
1 & 1 \\
1 & -1
\end{pmatrix}
\begin{pmatrix}
1 \\
0
\end{pmatrix} = 1/\sqrt{2}\begin{pmatrix}
1 \\
1
\end{pmatrix} = 1/\sqrt{2}|0> + 1/\sqrt{2}|1>
コードを見ると、
def test_hadamard(self):
self.q = qcuit()
self.q.set_initial_value(0)
self.q.inst(H(0))
alpha, beta = self.q.get_wavefunction()
self.assertAlmostEqual(math.sqrt(1/2), alpha, 6)
self.assertAlmostEqual(math.sqrt(1/2), beta, 6)
- self.q = qcuit(): オブジェクト作成
- self.q.set_initial_value(0): 入力が0
- self.q.inst(H(0)): アダマールゲート(H)を作用
- alpha, beta = self.q.get_wavefunction(): 波動関数(最後の式)の係数を取得
- self.assertAlmostEqual(math.sqrt(1/2), alpha, 6): |0>の係数が$1/\sqrt{2}$
- self.assertAlmostEqual(math.sqrt(1/2), beta, 6): |1>の係数が$1/\sqrt{2}$
制御NOTゲート
基本的なゲートなのですが、入力する量子ビットが2つなので、後に回しました。
2つのビットは制御ビットと標的ビットと言います。以下のルールで標的ビットを変更します。
- 制御ビットが0の場合は、標的ビットは変更なし
- 制御ビットが1の場合は、標的ビットを変更する(|0>から|1>、もしくは|1>から|0>)
コードを見ると、
self.q.set_initial_value(1, 0)
self.q.inst(CNOT(0, 1))
results = self.q.run()
self.assertListEqual([[1, 1]], results)
- self.q.set_initial_value(1, 0): 入力が1と0
- self.q.inst(CNOT(0, 1)): 制御NOTゲートを作用。引数は入力のインデックスを表している(つまり第一引数の1が制御ビットで、第二引数の0が標的ビット)
- results = self.q.run(): 実行して結果を取得
- self.assertListEqual([[1, 1]], results): 制御ビットが1なので、標的ビットの0を1に変更した結果となっている
スワップゲート
その名の通り、2つの量子ビットを入れ替えるゲートになります。
コードを見ると、
def test_swap(self):
self.q = qcuit()
self.q.set_initial_value(1, 0)
self.q.inst(SWAP(0, 1))
results = self.q.run()
self.assertListEqual([[0, 1]], results)
- self.q.set_initial_value(1, 0): 入力が1と0
- self.q.inst(SWAP(0, 1)): 第一引数と第二引数にスワップゲートを作用
- results = self.q.run(): 実行して結果を取得
- self.assertListEqual([[0, 1]], results): 入力の順と入れ替わって出力
上記はpyQuilで定義済みのスワップゲートですが、制御NOTゲートを組み合わせて定義できるので、やってみたコードが以下です。
# CNOTで実装
self.q.set_initial_value(1, 0)
self.q.inst(CNOT(0, 1))
self.q.inst(CNOT(1, 0))
self.q.inst(CNOT(0, 1))
results = self.q.run()
self.assertListEqual([[0, 1]], results)
制御NOTゲートを3つ組み合わせています。量子コンピュータ入門に載っていたので実際にやってみました。結果を見るとちゃんと入力と出力が逆になっています。
qcuitクラス
量子論理ゲートを見てきましたが、実はqcuitクラスは自分で作りました。入力や出力をわかりやすくするためです。注意が必要な点だけ解説をします。
入力処理
def set_initial_value(self, *args):
'''
入力する量子ビットの初期値|0> or |1>を設定する
入力値は0 or 1
'''
for arg in args:
if arg not in [0, 1]:
raise(Exception('Unexpected value. Included value except 0 and 1.'))
self.p = Program()
self.classical_regs = []
for i in range(len(args)):
if args[i] == 0:
self.p.inst(I(i))
else:
self.p.inst(X(i))
self.classical_regs.append(i)
入力のビットを設定しているメソッドです。簡単に流れを説明します。
- 最初に0と1しか受け付けないようにチェックをしています。
- 引数の値(0か1)に応じて、self.p.inst(I(i))とself.p.inst(X(i))をしています。ここでiは、引数のインデックスです。I(i)とX(i)ですが、実はpyQuilでは量子ビットの入力が初期値で|0>になっています。I(i)は単位行列と考えてもらえればよく、そのまま|0>です。X(i)は上記でやったように|1>に変換しています。ここで入力値を制御しています。
出力処理
def run(self):
'''
入力の量子ビットに設定した量子論理ゲートを作用させて、出力結果を得る
'''
for reg in self.classical_regs:
self.p.measure(reg, reg)
return self.qvm.run(self.p, self.classical_regs)
出力結果を得るメソッドです。簡単に言うと、入力の順番で出力結果を出力します。
self.classical_regsに入力した量子ビットの順番が格納されているので、self.p.measure(reg, reg)で、出力対象に設定します。
return self.qvm.run(self.p, self.classical_regs)で、入力と同じ順で出力の順番を設定しています。
さいごに
今回無謀な挑戦でしたが、量子コンピュータ入門を読み、実際にpyQuilを触ることまでできるようになりました。簡単な量子論理ゲートを扱う程度でしたが、今後はもっと高度なアルゴリズムに挑戦していきたいと思います。
今回の体験をブログにも記事を書いていますので、そちらもよかったら見てください。
Qiitaのアドベントカレンダー2017で量子コンピュータの記事を書いた