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)

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)

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)

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

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)