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)