# Python 教材

## 量子状態

### 量子状態の生成

量子状態は `QuantumState` クラスを用いて生成します。生成した量子状態は $|0\rangle^{\otimes n}$ に初期化されています。
量子状態を `print` することで量子状態の情報を表示できます。

```python
from qulacs import QuantumState

# 2-qubitの状態を生成
n = 2
state = QuantumState(n)
print(state)
```

```
 *** Quantum State ***
 * Qubit Count : 2
 * Dimension   : 4
 * State vector :
(1,0)
(0,0)
(0,0)
(0,0)
```

一般的なノートパソコンやデスクトップPCでは26,27量子ビット程度がメモリ容量から作成できる限界です。

### 量子状態の初期化

生成した量子状態は `set_computational_basis` 関数で計算基底に初期化したり、`set_Haar_random_state` 関数でランダムな状態に初期化することが出来ます。
なお、Qulacsでは量子ビットの添え字は0から始まり、かつ $\ket{0000}$ と書いたときに一番右のビットが0-th qubitになります(他のライブラリや教科書では一番左が0-th qubitであることもあります)。

```python
from qulacs import QuantumState

n = 2
state = QuantumState(n)

# |00>に初期化
state.set_zero_state()

# 基底を二進数と見た時の整数値を入れて、その状態に初期化
state.set_computational_basis(0b01)
print(state)

# シードを指定してランダムな初期状態を生成
# (シードを省略した場合は時刻を用いてシードを決定します。)
seed = 0
state.set_Haar_random_state(seed)
print(state)
```

```
 *** Quantum State ***
 * Qubit Count : 2
 * Dimension   : 4
 * State vector :
(0,0)
(1,0)
(0,0)
(0,0)

 *** Quantum State ***
 * Qubit Count : 2
 * Dimension   : 4
 * State vector :
  (0.0531326,0.551481)
    (0.382474,0.10121)
(-0.499658,-0.0931227)
  (0.474029,-0.231262)
```

### 状態ベクトルの取得と設定

量子状態の状態ベクトルは `get_vector` 関数でnumpy arrayとして取得できます。また、`load` 関数でnumpy arrayやlistを与えることで量子状態を設定できます。

```python
import numpy as np
from qulacs import QuantumState

n = 2
state = QuantumState(n)

# 状態ベクトルを取得
vec = state.get_vector()
print(type(vec), vec.dtype)
print(vec)

# 状態ベクトルを設定
myvec = np.array([0.5,0.5,0.5,0.5])
state.load(myvec)
print(state)
```

```
<class 'numpy.ndarray'> complex128
[1.+0.j 0.+0.j 0.+0.j 0.+0.j]
 *** Quantum State ***
 * Qubit Count : 2
 * Dimension   : 4
 * State vector :
(0.5,0)
(0.5,0)
(0.5,0)
(0.5,0)
```

### 量子状態に関する情報の計算

量子状態に対して、量子状態の状態を変えずに量子状態に関する情報を計算できます。例えば、指定した添え字のqubitを測定した時に、0を得る確率は `get_zero_probability` 関数で計算できます。

```python
from qulacs import QuantumState

n = 5
state = QuantumState(n)
state.set_Haar_random_state(0)

# 指定した添え字のqubitをZ基底で測定して0を得る確率の計算
index = 3
zero_probability = state.get_zero_probability(index)
print("prob_meas_3rd : ",zero_probability)
```

```
prob_meas_3rd :  0.6015549753834679
```

量子状態を測定した時の結果をサンプリングするには `sampling` 関数が使えます。関数の引数はサンプリングするデータの個数です。

```python
import numpy as np
from qulacs import QuantumState

n = 2
state = QuantumState(n)
state.load([1/np.sqrt(2), 0, 0.5, 0.5])
data = state.sampling(10)
print(data)
print([format(value, "b").zfill(2) for value in data]) # 二進数表示
```

```
[0, 3, 3, 3, 3, 0, 2, 3, 0, 3]
['00', '11', '11', '11', '11', '00', '10', '11', '00', '11']
```

このほかにも多くの関数が用意されています。詳しくはAdvancedの章を参照してください。

### 量子状態の内積

量子状態の内積は `inner_product` 関数で計算できます。

```python
from qulacs import QuantumState
from qulacs.state import inner_product

n = 5
state_bra = QuantumState(n)
state_ket = QuantumState(n)
state_bra.set_Haar_random_state()
state_ket.set_computational_basis(0)

# 内積値の計算
value = inner_product(state_bra, state_ket)
print(value)
```

```
(0.1265907720817918+0.10220657039660046j)
```

## 量子ゲート

### 量子ゲートの生成

量子ゲートは `qulacs.gate` モジュールの中で定義されています。このモジュールでは幾つかの典型的な量子ゲートが既に定義されています。例えば、X-gateは以下のように生成できます。量子ゲートを `print` することでゲートの情報を表示できます。

```python
from qulacs.gate import X

target_index = 1
x_gate = X(target_index)
print(x_gate)
```

```
 *** gate info ***
 * gate name : X
 * target    :
 1 : commute X
 * control   :
 * Pauli     : yes
 * Clifford  : yes
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no
```

### 量子ゲートの作用

量子ゲートは `update_quantum_state` 関数で量子状態を更新できます。例えば、Xゲートを1st qubitに作用するには以下のようなコードを書きます。

```python
from qulacs import QuantumState
from qulacs.gate import X

n = 2
state = QuantumState(n)
print(state)

index = 1
x_gate = X(index)
x_gate.update_quantum_state(state)
print(state)
```

```
 *** Quantum State ***
 * Qubit Count : 2
 * Dimension   : 4
 * State vector :
(1,0)
(0,0)
(0,0)
(0,0)

 *** Quantum State ***
 * Qubit Count : 2
 * Dimension   : 4
 * State vector :
(0,0)
(0,0)
(1,0)
(0,0)
```

### 様々な量子ゲート

下記にしばしば使う名前の付いたゲートを紹介します。どのゲートも `update_quantum_state` 関数を用いて量子状態を更新できます。その他のゲートについては、Advancedの章を参照してください。

```python
import numpy as np

# パウリゲート、アダマールゲート、Tゲート
from qulacs.gate import X, Y, Z, H, T
target = 2
x_gate = X(target)
y_gate = Y(target)
z_gate = Z(target)
h_gate = H(target)
t_gate = T(target)

# パウリ回転ゲート
from qulacs.gate import RX, RY, RZ
angle = np.pi / 4.0
rx_gate = RX(target, angle)
ry_gate = RY(target, angle)
rz_gate = RZ(target, angle)

# CNOT, CZ, SWAPゲート
from qulacs.gate import CNOT, CZ, SWAP
control = 1
target2 = 1
cnot_gate = CNOT(control, target)
cz_gate = CZ(control, target)
swap_gate = SWAP(target, target2)
```

ここでは名前のついたいくつかのゲートについて、動作説明と共に紹介します。

#### TOFFOLIゲート

TOFFOLIゲートは第1, 2引数にコントロールindexを、第3引数にターゲットのindexをもちます。
第1, 2引数で与えられたコントロールindexに対応する量子ビットがどちらも $\ket{1}$ のときに、第3引数で与えられたターゲットindexに対応する量子ビットが反転します。

```python
# TOFFOLIゲート
from qulacs.gate import TOFFOLI
control1 = 0
control2 = 1
target1 = 1
toffoli_gate = TOFFOLI(control1, control2, target1)
```

下記にTOFFOLIゲートの動作の例を示しています。コントロールindexとして0, 1、ターゲットのindexとして2を与えられているため、0-th qubitと1-st qubitが $\ket{1}$ のときに2-nd qubitの量子ビットが反転します。
TOFFOLIゲートの作用前と作用後では、 $\ket{011}$ と $\ket{111}$ の量子状態が入れ替わっていることが分かります。

```python
from qulacs import QuantumState
from qulacs.gate import TOFFOLI

n = 3
state = QuantumState(n)

state.set_Haar_random_state(0)
print(state)

toffoli_gate = TOFFOLI(0, 1, 2)

toffoli_gate.update_quantum_state(state)

print(state)

del state
```

```
*** Quantum State ***
 * Qubit Count : 3
 * Dimension   : 8
 * State vector : 
(-0.0845729,-0.14143)
  (-0.133864,0.11328)
 (-0.22522,-0.165467)
 (-0.151059,0.481251)
 (-0.450874,0.172713)
 (-0.0585584,0.32498)
 (0.359721,0.0264336)
(-0.101035,-0.356517)

 *** Quantum State ***
 * Qubit Count : 3
 * Dimension   : 8
 * State vector : 
(-0.0845729,-0.14143)
  (-0.133864,0.11328)
 (-0.22522,-0.165467)
(-0.101035,-0.356517)
 (-0.450874,0.172713)
 (-0.0585584,0.32498)
 (0.359721,0.0264336)
 (-0.151059,0.481251)
```

#### FREDKINゲート

FREDKINゲートは第1引数にコントロールindexを、第2, 3引数にターゲットのindexをもちます。
第1引数で与えられたコントロールindexに対応する量子ビットが $\ket{1}$ のときに、第2, 3引数で与えられたターゲットindexに対応する量子ビットが入れ替わります。

```python
# FREDKINゲート
from qulacs.gate import FREDKIN
control1 = 0
target1 = 1
target2 = 2
fredkin_gate = FREDKIN(control1, target1, target2)
```

下記にFREDKINゲートの動作の例を示しています。コントロールindexとして0、ターゲットのindexとして1, 2を与えられているため、0-th qubitが $\ket{1}$ のときに1-st qubit, 2-nd qubitの量子ビットが入れ替わります。FREDKINゲートの作用前と作用後では、 $\ket{011}$ と $\ket{101} $の量子状態が入れ替わっていることが分かります。

```python
from qulacs import QuantumState
from qulacs.gate import FREDKIN

n = 3
state = QuantumState(n)

state.set_Haar_random_state(0)
print(state)

fredkin_gate = FREDKIN(0, 1, 2)

fredkin_gate.update_quantum_state(state)

print(state)
```

```
 *** Quantum State ***
 * Qubit Count : 3
 * Dimension   : 8
 * State vector : 
(-0.0845729,-0.14143)
  (-0.133864,0.11328)
 (-0.22522,-0.165467)
 (-0.151059,0.481251)
 (-0.450874,0.172713)
 (-0.0585584,0.32498)
 (0.359721,0.0264336)
(-0.101035,-0.356517)

 *** Quantum State ***
 * Qubit Count : 3
 * Dimension   : 8
 * State vector : 
(-0.0845729,-0.14143)
  (-0.133864,0.11328)
 (-0.22522,-0.165467)
 (-0.0585584,0.32498)
 (-0.450874,0.172713)
 (-0.151059,0.481251)
 (0.359721,0.0264336)
(-0.101035,-0.356517)
```

### 一般的な量子ゲート

量子ゲートの行列をnumpy arrayで指定してゲートを生成するには、`DenseMatrix` クラスを用います。一つ目の引数が作用する添え字で、二つ目が行列です。1量子ビットゲートの場合は一つの整数と2×2行列を与えます。

```python
from qulacs.gate import DenseMatrix

gate = DenseMatrix(1, [[0,1],[1,0]])
print(gate)
```

```
 *** gate info ***
 * gate name : DenseMatrix
 * target    :
 1 : commute
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no
 * Matrix
(0,0) (1,0)
(1,0) (0,0)
```

2量子ビット以上の大きさのゲートを作るには、一つ目の引数に対象となる添え字のリストを、二つ目に行列を与えます。$n$ 量子ビットゲートを作るとき、 行列の大きさは $2^n$ 次元でなければなりません。

```python
from qulacs.gate import DenseMatrix

gate = DenseMatrix([0,1], [[0,1,0,0],[1,0,0,0],[0,0,0,1],[0,0,1,0]])
print(gate)
```

```
 *** gate info ***
 * gate name : DenseMatrix
 * target    :
 5 : commute
 3 : commute
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no
 * Matrix
(0,0) (1,0) (0,0) (0,0)
(1,0) (0,0) (0,0) (0,0)
(0,0) (0,0) (0,0) (1,0)
(0,0) (0,0) (1,0) (0,0)
```

なお、ゲート行列の列や行を数えるときに下位ビットとなる添え字は、ゲート生成時に与える添え字の順番に対応するため、上記の例で作用する添え字のリストが `[0,1]` と `[1,0]` では意味が異なることに注意してください。以下は添え字を入れ替えた時の違いを表しています。

```python
from qulacs import QuantumState
from qulacs.gate import DenseMatrix

gate1 = DenseMatrix([0,1], [[0,1,0,0],[1,0,0,0],[0,0,0,1],[0,0,1,0]])
gate2 = DenseMatrix([1,0], [[0,1,0,0],[1,0,0,0],[0,0,0,1],[0,0,1,0]])
state = QuantumState(2)

state.set_zero_state()
gate1.update_quantum_state(state)
print(state.get_vector())

state.set_zero_state()
gate2.update_quantum_state(state)
print(state.get_vector())
```

```
[0.+0.j 1.+0.j 0.+0.j 0.+0.j]
[0.+0.j 0.+0.j 1.+0.j 0.+0.j]
```

### コントロールビットの追加

行列ゲートには、`add_control_qubit` 関数を用いてコントロールビットを追加できます。一つ目の引数はコントロールビットの添え字、二つ目の引数は0か1で、コントロールビットがその値だった時にtargetに操作を行います。例えばCNOTゲートではコントロールビットの値が1の時にtargetに走査を行うため、二つの目の引数は1になります。なお、Xゲートのような名前の付いた特殊なゲートはコントロールビットの追加が出来ません。これらにコントロールビットを追加するには、次のセクションの「一般的な行列ゲートへの変換」を参照してください。

```python
from qulacs.gate import DenseMatrix

gate = DenseMatrix(1, [[0,1],[1,0]])
gate.add_control_qubit(3,1)
print(gate)
```

```
 *** gate info ***
 * gate name : DenseMatrix
 * target    :
 1 : commute
 * control   :
 3 : value 1
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no
 * Matrix
(0,0) (1,0)
(1,0) (0,0)
```

### 一般的な行列ゲートへの変換

Xゲートのような名前の付いた特殊なゲートは一般的な行列ゲートより高速に量子状態を更新できる一方、`add_control_qubit` 関数のような加工が行えません。特殊なゲートを元にしてゲートを加工するには、`to_matrix_gate` 関数を用いて特殊なゲートを一般的なゲートに変換します。

``` python
from qulacs.gate import X, to_matrix_gate

gate = X(1)
print(gate)
gate = to_matrix_gate(gate)
print(gate)
gate.add_control_qubit(3,1)
```

```
 *** gate info ***
 * gate name : X
 * target    :
 1 : commute X
 * control   :
 * Pauli     : yes
 * Clifford  : yes
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no

 *** gate info ***
 * gate name : DenseMatrix
 * target    :
 1 : commute X
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no
 * Matrix
(0,0) (1,0)
(1,0) (0,0)
```

変換によりゲート名が `X` から `DenseMatrix` に変わり陽にゲート行列を保持していることが分かります。

### 量子ゲートの合成

`qulacs.gate` モジュール内に存在する `merge` 関数を用いることで複数のゲートを合成できます。合成したゲートは各ゲートを順番に作用させた場合と同じ立ち振る舞いをします。

```python
from qulacs import QuantumState
from qulacs.gate import X, Y, merge

n = 2
state1 = QuantumState(n)

index = 1
x_gate = X(index)
y_gate = Y(index)

x_gate.update_quantum_state(state1)
y_gate.update_quantum_state(state1)
print(state1)

state2 = QuantumState(n)

merged_gate = merge([x_gate, y_gate])
merged_gate.update_quantum_state(state2)
print(state2)
```

```
*** Quantum State ***
 * Qubit Count : 2
 * Dimension   : 4
 * State vector : 
(0,-1)
(0,-0)
 (0,0)
 (0,0)

 *** Quantum State ***
 * Qubit Count : 2
 * Dimension   : 4
 * State vector : 
(0,-1)
 (0,0)
 (0,0)
 (0,0)
```

### 量子ゲートのゲート行列の取得

生成した量子ゲートのゲート行列は `get_matrix` 関数で取得できます。なお重要な注意点として、controlled-qubitがあるゲートの場合、controlled-qubitはゲート行列には含まれません。このため、例えばCNOTゲートのゲート行列は2x2行列になります。

```python
from qulacs.gate import H, CNOT

h_gate = H(2)
matrix = h_gate.get_matrix()
print(matrix)
cnot_gate = CNOT(1,2)
matrix = cnot_gate.get_matrix()
print(matrix)
```

```
[[ 0.70710678+0.j  0.70710678+0.j]
 [ 0.70710678+0.j -0.70710678+0.j]]
[[0.+0.j 1.+0.j]
 [1.+0.j 0.+0.j]]
```

## 量子ゲートの逆操作の取得

生成した量子ゲート `U` のエルミート共役 `U†` は `get_inverse` 関数で取得できます。エルミート共役 `U†` は量子ゲート `U` の逆操作を表現します。エルミート共役を取得する機能が実装されていない場合は例外が送出されます。

```python
from qulacs.gate import S, DepolarizingNoise

s_gate = S(2)
s_dagger_gate = s_gate.get_inverse()
print("original:\n", s_gate.get_matrix())
print("inversed:\n", s_dagger_gate.get_matrix())

noise_gate = DepolarizingNoise(0, 0.05)
try:
        noise_gate.get_inverse()
        assert 0, "No exception occured. should not reach here."
except Exception as err:
        print(f"Exception occured as expected: {err}")
```

```
original:
 [[1.+0.j 0.+0.j]
 [0.+0.j 0.+1.j]]
inversed:
 [[ 1.+0.j  0.+0.j]
 [ 0.+0.j -0.-1.j]]
Exception occured as expected: this gate don't have get_inverse function
```

## 量子回路

### 量子回路の生成と構成

量子回路は `QuantumCircuit` クラスとして定義されています。`QuantumCircuit` クラスには `add_<gatename>_gate` としてゲートを追加するか、`add_gate` 関数を用いてゲートのインスタンスを追加できます。量子回路を `print` することで量子回路の情報が表示されます。

``` python
from qulacs import QuantumCircuit

n = 5
circuit = QuantumCircuit(n)
circuit.add_H_gate(0)
circuit.add_X_gate(2)

from qulacs.gate import X
gate = X(2)
circuit.add_gate(gate)

print(circuit)
```

```
*** Quantum Circuit Info ***
# of qubit: 5
# of step : 2
# of gate : 3
# of 1 qubit gate: 3
Clifford  : yes
Gaussian  : no
```

### 量子回路の作用

量子回路も量子ゲートのように `update_quantum_state` 関数を用いて量子状態を更新できます。

``` python
from qulacs import QuantumCircuit

n=3
circuit = QuantumCircuit(n)
circuit.add_H_gate(1)
circuit.add_RX_gate(2,0.1)

from qulacs import QuantumState
state = QuantumState(n)
circuit.update_quantum_state(state)
print(state)
```

```
 *** Quantum State ***
 * Qubit Count : 3
 * Dimension   : 8
 * State vector :
 (0.706223,0)
        (0,0)
 (0.706223,0)
        (0,0)
(0,0.0353406)
        (0,0)
(0,0.0353406)
        (0,0)
```

## 量子回路の逆操作の取得

量子回路も量子ゲートと同様にして逆操作を取得することができます。
量子回路の中にエルミート共役を取得する機能が実装されていない量子ゲートが含まれている場合は例外が送出されます。

```python
from qulacs import QuantumCircuit

n=3
circuit = QuantumCircuit(n)
circuit.add_H_gate(1)
circuit.add_RX_gate(2,0.1)

inverse_circuit = circuit.get_inverse()

from qulacs import QuantumState
state = QuantumState(n)
circuit.update_quantum_state(state)
inverse_circuit.update_quantum_state(state)
print(state)
```

```
 *** Quantum State ***
 * Qubit Count : 3
 * Dimension   : 8
 * State vector : 
(1,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
```
