# Python Tutorial¶

## Quantum states¶

### Generate quantum states¶

Generate \(n\) qubit quantum states using `QuantumState`

class and initialize it as \(\left|0\right>^{\otimes n}\).

```
[43]:
```

```
from qulacs import QuantumState
# Generate 2 qubit states
n = 2
state = QuantumState(n)
print(state)
```

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

You can not generate the quantum states if the memory is not sufficient.

With the memory of a typical laptop or desktop computer, the limit of qubits you can create is about 26, 27 qubits.

### Initialize quantum states¶

The generated quantum state can be initialized to a computational basis using the `set_computational_basis`

or to a random state using the `set_Harr_random_state`

.

Note that in Qulacs, the subscripts of the qubits start from 0, and the rightmost bit is the 0-th qubit when written as \(\ket{0000}\) (In other libraries and textbooks, the leftmost bit may be the 0-th qubit).

```
[44]:
```

```
from qulacs import QuantumState
n = 2
state = QuantumState(n)
# Initialize as |00>
state.set_zero_state()
# Initialize as |01>
state.set_computational_basis(0b01)
# Generate random initial state
state.set_Haar_random_state()
print(state)
# Generate random initial state with specifying seed
seed = 0
state.set_Haar_random_state(seed)
print(state)
```

```
*** Quantum State ***
* Qubit Count : 2
* Dimension : 4
* State vector :
(0.431665,0.175902)
(-0.5087,-0.239707)
(0.151328,-0.478811)
(0.0969414,0.452692)
*** Quantum State ***
* Qubit Count : 2
* Dimension : 4
* State vector :
(-0.13535,-0.226344)
(-0.214236,0.181293)
(-0.360441,-0.264813)
(-0.241755,0.770192)
```

### Obtain data of quantum state¶

A state vector of a quantum state can be obtained as a numpy array with `get_vector`

function. And you can set a quantum state by giving a numpy array or a list in the `load`

function.

```
[45]:
```

```
import numpy as np
from qulacs import QuantumState
n = 2
state = QuantumState(n)
# Get the state vector
vec = state.get_vector()
print(type(vec), vec.dtype)
print(vec)
# Set the state vector
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)
```

### Copy and load quantum state data¶

The quantum state can be copied and loaded from other quantum state data.

```
[46]:
```

```
from qulacs import QuantumState
n = 5
state = QuantumState(n)
state.set_computational_basis(0b00101)
# Copy to generate another quantum state
second_state = state.copy()
print(second_state.get_vector())
# Generate a new quantum state, and copy from an existing quantum state
third_state = QuantumState(n)
third_state.load(state)
print(third_state.get_vector())
```

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

### Operate classic registers¶

The Quantum state can be read and written as a classic register.

```
[47]:
```

```
from qulacs import QuantumState
n = 5
state = QuantumState(n)
state.set_zero_state()
# Set the 3rd classical register as 1
register_position = 3
register_value = 1
state.set_classical_value(register_position, register_value)
# Obtain the value of the 3rd classical register
obtained_value = state.get_classical_value(register_position)
print(obtained_value)
```

```
1
```

### Calculate quantum states¶

For a quantum state, information about the quantum state can be computed without changing the state of the quantum state. For example, the probability of getting 0 when measuring a qubit with a given index can be calculated with the `get_zero_probability`

function.

```
[48]:
```

```
from qulacs import QuantumState
n = 5
state = QuantumState(n)
state.set_Haar_random_state(0)
# Calculate the probability to get 0 in measurement of the qubit in the given index at Z-basis
index = 3
zero_probability = state.get_zero_probability(index)
print("prob_meas_3rd : ",zero_probability)
```

```
prob_meas_3rd : 0.5199371283324116
```

The `sampling`

function can be used to sample the results of a quantum state measurement. The argument of the function is the number of data to sample.

```
[49]:
```

```
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)
# Show in binary format
print([format(value, "b").zfill(2) for value in data])
```

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

You can find many other functions in “Advanced” section.

### Calculate the inner product of quantum states¶

The inner product of a quantum states can be calculated by the `inner_product`

function.

```
[50]:
```

```
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)
# Calculate the inner product
value = inner_product(state_bra, state_ket)
print(value)
```

```
(0.03141883589278555-0.015629285255538153j)
```

## Quantum gate¶

### Generate quantum gates¶

Quantum gates are defined in the `qulacs.gate`

module. Several typical quantum gates are already defined in this module. For example, X gate can be generated as follows. By `printing`

a quantum gate, you can print information about the gate.

```
[51]:
```

```
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
```

### Operation of quantum gates¶

Quantum gates can update a quantum state with the `update_quantum_state`

function. The following example shows the X gate act on the 1st qubit.

```
[52]:
```

```
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)
```

### Various quantum gates¶

Below is a list of named gates that are often used. Any of the gates can update its quantum state using the `update_quantum_state`

function. For other gates, see the Advanced chapter.

```
[53]:
```

```
import numpy as np
# Pauli gate, Hadamard gate, T gate
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)
# Pauli rotation gate
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 gate
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)
```

Here is an introduction of some of the named gates with examples.

#### TOFFOLI gate¶

TOFFOLI gate has control indices in the first and second arguments, and the target index in the third argument. When the qubits corresponding to the control indices given in the first and second arguments are both \(\ket{1}\), the qubits corresponding to the target index given in the third argument is flipped.

```
[ ]:
```

```
from qulacs.gate import TOFFOLI
control1 = 0
control2 = 1
target1 = 2
toffoli_gate = TOFFOLI(control1, control2, target1)
```

Here is an example of the operation of the TOFFOLI gate. Since 0 and 1 are given as control indices and 2 is given as the target index, the qubit of the 2-nd qubit is flipped when the 0-th qubit and the 1-st qubit are \(\ket{1}\). You can see that the quantum states \(\ket{011}\) and \(\ket{111}\) are swapped before and after the action of the TOFFOLI gate.

```
[2]:
```

```
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)
```

```
*** 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 gate¶

FREDKIN gate has control indices in the first argument, and target indices in the second and third arguments. When the qubit corresponding to the control index given in the first argument is \(\ket{1}\), the qubits corresponding to the target indices given in the second and third arguments are swapped.

```
[6]:
```

```
from qulacs.gate import FREDKIN
control1 = 0
target1 = 1
target2 = 2
fredkin_gate = FREDKIN(control1, target1, target2)
```

Here is an example of the operation of the FREDKIN gate. Since 0 is given as the control index and 1 and 2 are given as the target indices, the qubits of the 1-st qubit and the 2-nd qubit are swapped when the 0-th qubit is \(\ket{1}\).

```
[7]:
```

```
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)
```

### General quantum gates¶

To generate a gate by specifying the matrix of a quantum gate as a numpy array, use the class `DenseMatrix`

. The first argument is the index to act on and the second is the matrix. For a single qubit gate, give an integer and a 2 x 2 matrix.

```
[54]:
```

```
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)
```

To create a gate of size larger than 2 qubits, give a list of target subscripts as the first argument and a matrix as the second. When creating an \(n\)-qubit gate, the matrix must be of dimension \(2^n\).

```
[55]:
```

```
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 :
0 : commute
1 : 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)
```

Note that the indices that are lower bits when counting the columns and rows of a gated matrix correspond to the order of the subscripts given during gate generation, so the list of subscripts acting in the above example has a different meaning for `[0,1]`

and `[1,0]`

. The following shows the difference when the indices are swapped.

```
[56]:
```

```
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]
```

### Append a control bit¶

A control bit can be added to the matrix gate using the `add_control_qubit`

function. The first argument is the index of the control bit, the second argument is 0 or 1, and the operation is performed on target when the control bit has that value. For example, the CNOT gate performs a scan on target when the control bit has a value of 1, so the second argument is 1. Note that special named gates, such as the X gate, do not allow the control bit to be added. To add control bits to these, see
“Conversion to General Matrix Gates” in the next section.

```
[57]:
```

```
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)
```

### Conversion to General Matrix Gates¶

While special named gates such as X gates can update quantum states faster than general matrix gates, they cannot be modified with functions like the `add_control_qubit`

. To process a gate based on a special gate, use the `to_matrix_gate`

function to convert the special gate to a general gate.

```
[58]:
```

```
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)
```

### Merge quantum gates¶

You can merge multiple gates using the `merge`

function in the `qulacs.gate`

module. The merged gate behaves the same as when each gate is applied in order.

```
[1]:
```

```
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)
```

### Obtain a gate matrix from a quantum gate¶

The gate matrix of the generated quantum gate can be obtained with the `get_matrix`

function. An important note is that for gates with controlled-qubit, the controlled-qubit is not included in the gate matrix. Thus, for example, the gate matrix of a CNOT gate is a 2x2 matrix.

```
[59]:
```

```
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]]
```

### Obtain a inverse gate from a quantum gate¶

The hermitian conjugate `U†`

of the generated quantum gate `U`

can be obtained using a `get_inverse`

function. The hermitian conjugate `U†`

represents the inverse operation of the generated quantum gate `U`

. If the hermitian conjugate function is not implemented, an exception is thrown.

```
[2]:
```

```
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
```

## Quantum circuit¶

### Construct the quantum circuit¶

Quantum circuits are defined as the `QuantumCircuit`

class. You can add a gate to the `QuantumCircuit`

class as `add_<gatename>_gate`

or add a gate instance using the `add_gate`

function. You can `print`

the quantum circuit to see the information about the quantum circuit.

```
[60]:
```

```
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
```

Note: The quantum circuit added by `add_gate`

is released from memory when the quantum circuit is released. Therefore, the assigned gate cannot be reused. If you want to reuse the gate given as an argument, make a copy of itself using `gate.copy`

or use the `add_gate_copy`

function.

### Operation of quantum circuits¶

Quantum circuits can also update quantum states with `update_quantum_state`

function like quantum gates.

```
[61]:
```

```
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)
```

### Obtain a inverse circuit from a quantum circuit¶

An inverse circuit of the quantum circuit can be obtained using a `get_inverse`

function, as with the quantum gate. If the quantum circuit contains quantum gates such that hermitian transpose is not implemented, an exception is thrown.

```
[1]:
```

```
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)
```