量子コンピュータの演算紹介

こんにちは、LIFULLの[shiibass](https://qiita.com/shiibass)です。
[LIFULL Advent Calendarその２](https://qiita.com/advent-calendar/2017/lifull2)の記事になります。

今回は量子計算のビット演算について記事を書きます。行列演算が出てきますのでご注意ください。

## 量子計算の状態について
量子計算においては、1量子ビットが保持する情報は0になる確率と1になる確率です。詳しく書くと、


```math
|0⟩ =\left(
\begin{matrix}
1 \\
0 \\ 
\end{matrix}
\right),
|1⟩ =\left(
\begin{matrix}
0 \\
1 \\ 
\end{matrix}
\right)
```

はそれぞれ、1の確率で0になる状態、1の確率で1になる状態を表していて、任意の量子ビットの状態は

```math
|q⟩=\alpha|0⟩+\beta|1⟩ \ \ (|\alpha|^2+|\beta|^2=1, \alpha, \beta \in \mathbb{C})
```

と書け、$|\alpha|^2, |\beta|^2$はそれぞれ0, 1になる確率を表します。ここで、ベクトルを$|q⟩$のような表記にしておくとビット数が増えたときに可読性が上がります。もしくは$|↑⟩, |↓⟩$で表すこともあり、例えば$|01⟩$や$|↑↓⟩$のように状態を表します。量子計算では複数の状態を一度に扱うことができ、巨大な量子ビット演算でも小さく分解された計算を複数回行うことで処理ができます。私の認識では、量子計算における状態空間はヒルベルト空間で、線形性が成り立ち、ヒルベルト空間のテンソル積もヒルベルト空間であることが理由と思っていますがあまり自信はないです。

## 量子ビットの演算
ここでは量子ビットの演算の一部を紹介します。量子ビットの演算ではユニタリ行列が深く関係していますが、行列計算ができればなんとかなります。以下では演算子の名前は[IBM Q](https://www.research.ibm.com/ibm-q/)に従うものとします。

### X
$X$は$x$軸の周りに$\pi$回転させる演算を表し、行列で書くと

```math
X = \left(
\begin{matrix}
0 & 1 \\
1 & 0\\ 
\end{matrix}
\right)
```
となります。Pythonで例を書くと以下のように、0になる確率が1の状態から1になる確率が1の状態に変換されました。

```math
\left(
\begin{matrix}
0 & 1 \\
1 & 0\\ 
\end{matrix}
\right)
\left(
\begin{matrix}
1 \\
0\\ 
\end{matrix}
\right)
=
\left(
\begin{matrix}
0 \\
1\\ 
\end{matrix}
\right)

```


```py
import numpy as np
q0 = np.array([1, 0])
X = np.array([
        [0, 1],
        [1, 0]
    ])
np.dot(X, q0) # array([0, 1])
```

### H
$H$はアダマール(Hadamard)変換と呼ばれていて、重ね合わせの状態を作るときによく使われます。行列で書くと

```math
H = \frac{1}{\sqrt{2}}\left(
\begin{matrix}
1 & 1 \\
1 & -1\\ 
\end{matrix}
\right)
```
となります。Pythonで例を書くと以下のように0になる確率が1の状態から0と1になる確率がそれぞれ0.5になりました($0.70710678^2=0.5$)。1量子ビットで状態0と状態1を同時に扱うことができたことになり、量子計算が組み合わせ計算に強い理由になります。
アダマールに関して興味のある方はアダマール行列を調べてみると関係性が見えてくると思います。

```math
\frac{1}{\sqrt{2}}\left(
\begin{matrix}
1 & 1 \\
1 & -1\\ 
\end{matrix}
\right)\left(
\begin{matrix}
1 \\
0\\ 
\end{matrix}
\right)
=
\frac{1}{\sqrt{2}}\left(
\begin{matrix}
1 \\
1 \\ 
\end{matrix}
\right)

```

```py

import math
H = 1/math.sqrt(2) * np.array([
        [1, 1],
        [1, -1]
    ])
np.dot(H, q0) # array([ 0.70710678,  0.70710678])
```

### Z

$Z$は$z$軸の周りに$\pi$回転させる演算を表し、位相を反転させます。行列で書くと

```math
Z = \left(
\begin{matrix}
1 & 0 \\
0 & -1\\ 
\end{matrix}
\right)
```
となり、Pythonで例を書くと以下のようになります。確率1で1になる状態は変わっていないのですが位相が変化しています。位相は量子回路を組む際に重要なのですが今回は触れません。


```math
\left(
\begin{matrix}
1 & 0 \\
0 & -1\\ 
\end{matrix}
\right)
\left(
\begin{matrix}
0 \\
1\\ 
\end{matrix}
\right)
=
\left(
\begin{matrix}
0 \\
-1\\ 
\end{matrix}
\right)

```



```py
q1 = np.array([0, 1])
Z = np.array([
        [1, 0],
        [0, -1]
    ])
np.dot(Z, q1) # array([ 0, -1])
```

## 制御NOTゲート
最後に制御NOTゲートについて触れます。今まで記したゲートは1量子ビットに対して作用する演算でしたが、制御NOTゲートは2量子ビットに対して作用する演算になっていて、第1ビットが1のときのみ第2ビットをビット反転させるというのものです。行列で書くと以下のようになります。

```math
C_X = \left(
\begin{matrix}
1 & 0 & 0 & 0\\
0 & 1 & 0 & 0\\
0 & 0 & 0 & 1\\
0 & 0 & 1 & 0 
\end{matrix}
\right)

```
例えば$|1⟩|0⟩$に作用させると$|1⟩|1⟩$に、$|0⟩|0⟩$に作用させると$|0⟩|0⟩$のままです。$|1⟩|0⟩$は成分表記に直すと

```math
|1⟩|0⟩ = |10⟩ = \left(
\begin{matrix}
0\\
1
\end{matrix}
\right)
\otimes
\left(
\begin{matrix}
1\\
0 
\end{matrix}
\right)\\
=\left(
\begin{matrix}
0\times 1\\
0 \times 0\\
1 \times 1\\
1 \times 0
\end{matrix}
\right)
=\left(
\begin{matrix}
0\\
0\\
1\\
0
\end{matrix}
\right)

```

となっていて、テンソル積$\otimes$についてはあまり触れませんが本記事では

```math

|x⟩|y⟩ = \left(
\begin{matrix}
x_0\\
x_1
\end{matrix}
\right)
\otimes
\left(
\begin{matrix}
y_0\\
y_1
\end{matrix}
\right)\\
=\left(
\begin{matrix}
x_0y_0\\
x_0y_1\\
x_1y_0\\
x_1y_1
\end{matrix}
\right)
```

この計算規則だけ覚えておけば問題ありません。各成分を掛け合わせするような演算です。

制御NOTゲートについてPythonで書くと以下のように$|1⟩|0⟩$が$|1⟩|1⟩$になりました。


```math

\left(
\begin{matrix}
1 & 0 & 0 & 0\\
0 & 1 & 0 & 0\\
0 & 0 & 0 & 1\\
0 & 0 & 1 & 0 
\end{matrix}
\right)

(|1⟩|0⟩)=
\left(
\begin{matrix}
1 & 0 & 0 & 0\\
0 & 1 & 0 & 0\\
0 & 0 & 0 & 1\\
0 & 0 & 1 & 0 
\end{matrix}
\right)
\left(
\begin{matrix}
0\\
0\\
1\\
0 
\end{matrix}
\right)
=\left(
\begin{matrix}
0\\
0\\
0\\
1 
\end{matrix}
\right)\\

(|1⟩|1⟩)=
\left(
\begin{matrix}
0 \times 0\\
0\times 1\\
1\times 0\\
1 \times 1
\end{matrix}
\right)
=\left(
\begin{matrix}
0\\
0\\
0\\
1
\end{matrix}
\right)


```



```py

q1 = np.array([0, 1])
q0 = np.array([1, 0])
Cx = np.array([
        [1, 0, 0, 0],
        [0, 1, 0, 0],
        [0, 0, 0, 1],
        [0, 0, 1, 0]
    ])
np.dot(Cx, np.tensordot(q1, q0, axes=0).reshape(1, 4)[0]) # array([0, 0, 0, 1])

np.tensordot(q1, q1, axes=0).reshape(1, 4)[0] # array([0, 0, 0, 1])
```


## まとめ
今回は量子計算の基礎について書きました。数学や物理の用語が出てきましたが、行列演算によって量子状態がどのように変化するかを把握することのほうが大事です。余力があれば書籍などで用語の定義を調べてみるのもいいと思います。量子コンピュータでは演算子を使って高速で処理できるアルゴリズムを組むことができ、次回は簡単な量子プログラムを組んでみます。