Python Tutorial

Quantum states

Generate quantum states

Generate \(n\) qubit quantum state and initialize it as \(\left|0\right>^{\otimes n}\).

[1]:
from qulacs import QuantumState
# Generate 5 qubit state
n = 5
state = QuantumState(n)
# Initialize as |00000>
state.set_zero_state()

The quantum state can not be generated if the memory is not sufficient.

Obtain data of quantum states

The quantum state is expressed as an array of length \(2^n\). Note that if the quantum state is formed by GPU, and If the \(n\) is large, it can become extremely heavy operation.

[2]:
from qulacs import QuantumState
n = 5
state = QuantumState(n)
state.set_zero_state()
# Obtain state vector as numpy array
data = state.get_vector()
print(data)
[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]

Initialize quantum states

The generated quantum state can be initialized as a computational basis, or a random state.

[3]:
from qulacs import QuantumState
n = 5
state = QuantumState(n)
state.set_zero_state()
# Initialize as |00101>
state.set_computational_basis(0b00101)
print(state.get_vector())
# Generate random initial state
state.set_Haar_random_state()
print(state.get_vector())
# Generate random initial state with specifying seed
seed = 0
state.set_Haar_random_state(seed)
print(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.15288771+0.09518259j -0.09425367+0.03861159j -0.05536341+0.00789662j
  0.0940157 -0.05557305j  0.1545892 -0.07198446j -0.03716513+0.02058911j
  0.03705915+0.14986423j  0.11286983-0.0532145j   0.26156705-0.00567617j
 -0.21049203+0.02588394j  0.03015688-0.13549472j -0.03223217-0.04269361j
  0.08834552-0.03187188j  0.02230196-0.1931069j   0.23539418-0.04396539j
  0.08287765+0.03878752j -0.23154572+0.21837285j  0.12294496+0.04516656j
 -0.06663951-0.02234198j -0.02088798-0.19753772j  0.15883044+0.1338028j
  0.10159162-0.07612246j -0.07501653+0.06349231j  0.23882471-0.03687719j
 -0.027509  -0.0642422j  -0.04664696+0.17370059j  0.24072434-0.02911506j
  0.14005551-0.03788459j  0.0301706 +0.0869898j  -0.0594587 -0.38821123j
  0.07373735-0.18018923j -0.04819191+0.11740737j]
[ 0.09232558+0.06460115j  0.14544754-0.10413526j  0.11300793-0.02455806j
  0.00811251+0.2426378j  -0.01116588+0.23770313j -0.10691448+0.0487731j
 -0.01654446+0.17073103j  0.22250403+0.01934699j  0.04728154+0.22585226j
  0.04475383+0.20375993j -0.10592159+0.10428549j -0.10175932-0.04016904j
  0.04241271+0.08723859j  0.18205362+0.06190871j  0.14103367-0.12925877j
 -0.08269267+0.08879486j -0.14479848-0.0183179j  -0.32601567+0.06762062j
  0.03482754+0.04464901j  0.09181499+0.05497985j  0.06870746+0.12628442j
 -0.00624006-0.21793139j -0.11181371+0.2659879j  -0.04589826+0.00891387j
 -0.04058365+0.30265587j -0.13894575-0.04392724j -0.03499327+0.0184768j
  0.05033425-0.07376874j  0.07124237+0.15451312j  0.09319498+0.08341551j
 -0.03002195-0.14677347j -0.05309219+0.10184815j]

Copy and load quantum state data

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

[4]:
from qulacs import QuantumState
n = 5
state = QuantumState(n)
state.set_computational_basis(0b00101)
# Copy to make 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]

Operation on a classic register

Quantum state can be read and written as classic register.

[5]:
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 3rd classical register
obtained_value = state.get_classical_value(register_position)
print(obtained_value)
1

Calculations on quantum states

There are various calculations can be applied to quantum state.

[1]:
from qulacs import QuantumState
n = 5
state = QuantumState(n)
state.set_Haar_random_state()
# Calculate norm
norm = state.get_squared_norm()
print("squred norm : ",norm)
# Calculation of entropy when measured with z-basis
entropy = state.get_entropy()
print("entropy : ",entropy)
# Calculate the probability of getting 0 measuring index-th qubit with Z basis
index = 3
zero_probability = state.get_zero_probability(index)
print("prob_meas_3rd : ",zero_probability)
# Calculate marginal probabilities (The following is an example of the probability that 0,3-th qubit is measured as 0 and 1,2-th qubit is measured as 1)
value_list = [0,1,1,0,2]
marginal_probability = state.get_marginal_probability(value_list)
print("marginal_prob : ",marginal_probability)
squred norm :  1.0000000000000002
entropy :  3.1179008329119045
prob_meas_3rd :  0.5633787619052777
marginal_prob :  0.0347875492304793

Inner product of quantum states

Inner product of quantum states can be calculated by inner_product function.

[7]:
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 inner product
value = inner_product(state_bra, state_ket)
print(value)
(0.009659627924345642+0.0037543729576012927j)

Release quantum state

Using del, quantum state can be forcibly released from memory. Without using del, the quantum state can still be released when it is no longer used. But it is convenient when memory shows severe conditions.

[8]:
from qulacs import QuantumState
n = 5
state = QuantumState(n)
# Release quantum state
del state

Obtain detailed information of quantum state

Information of quantum state can be shown by directly printing the object.

[9]:
from qulacs import QuantumState
n = 5
state = QuantumState(n)
print(state)
 *** Quantum State ***
 * Qubit Count : 5
 * Dimension   : 32
 * State vector :
(1,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)
(0,0)

Quantum gate

Generation and operation of quantum gate

Quantum gates implemented by default are defined in gate module.

[10]:
import numpy as np
from qulacs import QuantumState
from qulacs.gate import X, RY, DenseMatrix
n = 3
state = QuantumState(n)
state.set_zero_state()
print(state.get_vector())
# Operate X on 1st-qubit
index = 1
x_gate = X(index)
x_gate.update_quantum_state(state)
print(state.get_vector())
# Rotate 1st-qubit by Y Pauli with pi/4.0
angle = np.pi / 4.0
ry_gate = RY(index, angle)
ry_gate.update_quantum_state(state)
print(state.get_vector())
# Apply quantum gate created by gate matrix to 2nd-qubit
dense_gate = DenseMatrix(2, [[0,1],[1,0]])
dense_gate.update_quantum_state(state)
print(state.get_vector())
# Release gate
del x_gate
del ry_gate
del dense_gate
[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 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
[0.38268343+0.j 0.        +0.j 0.92387953+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.38268343+0.j 0.        +0.j 0.92387953+0.j 0.        +0.j]

Quantum gates implemented by default are as following:

  • single-qubit Pauli operation: Identity, X,Y,Z

  • single-qubit Clifford operation : H,S,Sdag, T,Tdag,sqrtX,sqrtXdag,sqrtY,sqrtYdag

  • two-qubit Clifford operation : CNOT, CZ, SWAP

  • single-qubit Pauli rotation : RX, RY, RZ

  • General Pauli operation : Pauli, PauliRotation

  • IBMQ basis-gate : U1, U2, U3

  • General gate : DenseMatrix

  • Measurement : Measurement

  • Noise : BitFlipNoise, DephasingNoise, IndepenedentXZNoise, DepolarizingNoise

Rotation gates RX, RY, and RZ operate as Pauli rotation \(\exp(i\frac{\theta}{2}P)\) based on corresponding Pauli operator \(P\) and argument \(\theta\). Please check the API documents for details of each gate.

Merge of quantum gates

By combining successively operating quantum gates, a new single quantum gate can be generated. By doing so, we can reduce access to quantum states.

[11]:
import numpy as np
from qulacs import QuantumState
from qulacs.gate import X, RY, merge
n = 3
state = QuantumState(n)
state.set_zero_state()
index = 1
x_gate = X(index)
angle = np.pi / 4.0
ry_gate = RY(index, angle)
# Create new gate by merging gates
# First argument applies first
x_and_ry_gate = merge(x_gate, ry_gate)
x_and_ry_gate.update_quantum_state(state)
print(state.get_vector())
[0.38268343+0.j 0.        +0.j 0.92387953+0.j 0.        +0.j
 0.        +0.j 0.        +0.j 0.        +0.j 0.        +0.j]

Sum of quantum gate matrices

The sum of quantum gate matrices elements can be obtained. (Please don’t do this when there is control-qubit, because the operation is undefined yet)

[12]:
import numpy as np
from qulacs import QuantumState
from qulacs.gate import P0,P1,add, merge, Identity, X, Z
gate00 = merge(P0(0),P0(1))
gate11 = merge(P1(0),P1(1))
# |00><00| + |11><11|
proj_00_or_11 = add(gate00, gate11)
print(proj_00_or_11)
gate_ii_zz = add(Identity(0), merge(Z(0),Z(1)))
gate_ii_xx = add(Identity(0), merge(X(0),X(1)))
proj_00_plus_11 = merge(gate_ii_zz, gate_ii_xx)
# ((|00>+|11>)(<00|+<11|))/2 = (II + ZZ)(II + XX)/4
proj_00_plus_11.multiply_scalar(0.25)
print(proj_00_plus_11)
 *** gate info ***
 * gate name : DenseMatrix
 * target    :
 0 : commute
 1 : commute
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no
 * Matrix
(1,0) (0,0) (0,0) (0,0)
(0,0) (0,0) (0,0) (0,0)
(0,0) (0,0) (0,0) (0,0)
(0,0) (0,0) (0,0) (1,0)

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

Special quantum gate and common quantum gate

In Qulacs, the basic quantum gates are devided in two ways: * Special gate: There are dedicated speed-up functions for utilizing the special gate. * Common gate: The matrices of the gate are consistent in operating on other matrix.

The special gate is faster than the common gate because of the dedicated functions. But in special gate, operations that change the function of a quantum gate, such as increasing the number of control qubits, cannot be performed later. This kind of change can be made only when the special gate is transformed into common gate, which can be realized by gate.to_matrix_gate. Here’s an example:

[13]:
import numpy as np
from qulacs import QuantumState
from qulacs.gate import to_matrix_gate, X
n = 3
state = QuantumState(n)
state.set_zero_state()
index = 0
x_gate = X(index)
x_mat_gate = to_matrix_gate(x_gate)
# Only operate when 1st-qubit is 0
control_index = 1
control_with_value = 0
x_mat_gate.add_control_qubit(control_index, control_with_value)
x_mat_gate.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 0.+0.j 0.+0.j]

Obtain gate matrix of quantum gate

Gate matrix of generated quantum gate can be obtained, but gate matrices do not include control qubit. Especially, be careful for those gates who don’t have gate matrix (ex. n-qubit Pauli rotation gate), it takes extremely long time and memory to obtain the matrix.

[14]:
import numpy as np
from qulacs import QuantumState
from qulacs.gate import X, RY, merge
n = 3
state = QuantumState(n)
state.set_zero_state()
index = 1
x_gate = X(index)
angle = np.pi / 4.0
ry_gate = RY(index, angle)
x_and_ry_gate = merge(x_gate, ry_gate)
# Obtain gate matrix
matrix = x_and_ry_gate.get_matrix()
print(matrix)
[[ 0.38268343+0.j  0.92387953+0.j]
 [ 0.92387953+0.j -0.38268343+0.j]]

Obtain information of quantum gate

Information of quantum gate can be shown by directly printing the object. Only when quantum gate have matrix elements explicitly, the gate matrix can be displayed.

[15]:
from qulacs.gate import X, to_matrix_gate
gate = X(0)
print(gate)
print(to_matrix_gate(gate))
 *** gate info ***
 * gate name : X
 * target    :
 0 : commute X
 * control   :
 * Pauli     : yes
 * Clifford  : yes
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no

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

Implement of common quantum gate

Qulacs implements various maps of quantum information in the following forms.

Unitary operation

Implemented as quantum gate.

Projection operator and Kraus operator, etc.

Implemented as quantum gate. In general, the norm of quantum state is not preserved after operation.

The gate can be generated by DenseMatrix.

[16]:
from qulacs.gate import DenseMatrix
# 1-qubit gate
gate = DenseMatrix(0, [[0,1],[1,0]])
print(gate)
# 2-qubit gate
gate = DenseMatrix([0,1], [[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]])
print(gate)
 *** gate info ***
 * gate name : DenseMatrix
 * target    :
 0 : commute
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no
 * Matrix
(0,0) (1,0)
(1,0) (0,0)

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

Stochastic unitary operations

With given multiple unitary operations and probability distributions, stochastic unitary operations can be created by Probabilistic function.

[17]:
from qulacs.gate import Probabilistic, X, Y
distribution = [0.1, 0.2, 0.3]
gate_list = [X(0), Y(0), X(1)]
gate = Probabilistic(distribution, gate_list)

If the sum of the probabilities is less than 1, the remaining probabilities functionalize as Identity.

CPTP-map

CPTP-map can be created by giving the CPTP function a list of Kraus operators satisfying completeness.

[18]:
from qulacs.gate import merge,CPTP, P0,P1
gate00 = merge(P0(0),P0(1))
gate01 = merge(P0(0),P1(1))
gate10 = merge(P1(0),P0(1))
gate11 = merge(P1(0),P1(1))
gate_list = [gate00, gate01, gate10, gate11]
gate = CPTP(gate_list)

POVM

Since it is the same as Instrument in numerical calculation, it is realized as Instrument.

Instrument

in addition to the general CPTP-map operation, Instrument is an operation to get the array subscript of the Claus operator that acts randomly. For example, a measurement on the Z basis is to operate on the CPTP-map consisting of P0 and P1 and knowing which one is operated. In cppsim, this is achieved by specifying the information of the CPTP-map and the address of the classic register where the subscript of the operated Claus operator is written in the Instrument function.

[19]:
from qulacs import QuantumState
from qulacs.gate import merge,Instrument, P0,P1
gate00 = merge(P0(0),P0(1))
gate01 = merge(P0(0),P1(1))
gate10 = merge(P1(0),P0(1))
gate11 = merge(P1(0),P1(1))
gate_list = [gate00, gate01, gate10, gate11]
classical_pos = 0
gate = Instrument(gate_list, classical_pos)
state = QuantumState(2)
state.set_Haar_random_state()
print(state)
gate.update_quantum_state(state)
result = state.get_classical_value(classical_pos)
print(state)
print(result)
 *** Quantum State ***
 * Qubit Count : 2
 * Dimension   : 4
 * State vector :
(0.0384086,-0.443594)
  (0.153984,0.292821)
(-0.386978,0.0323475)
  (0.690361,0.254749)

 *** Quantum State ***
 * Qubit Count : 2
 * Dimension   : 4
 * State vector :
              (0,0)
              (0,0)
              (0,0)
(0.938164,0.346191)

3
  • Adaptive: Determines whether to perform the operation according to the condition using the value written to the classical register. Conditions can be written as python functions. The python function must take a list of type “unsigned int” as an argument and return a bool.

[3]:
from qulacs.gate import Adaptive, X
def func(list):
    return list[0]==1
gate = Adaptive(X(0), func)
state = QuantumState(2)
state.set_Haar_random_state()
# func returns False, and gate is not applied
state.set_classical_value(0,0)
gate.update_quantum_state(state)
# func returns True, and gate is applied
state.set_classical_value(0,1)
gate.update_quantum_state(state)

CP-map

If Kraus-rank is 1, please treat it as a single Kraus operator as described above. In other cases, please adjust the Kraus operator so that it becomes TP, and then adjust it by applying the Identity operator multiplied by a constant with the multiply_scalar function.

Quantum circuit

Constitution of quantum circuit

A quantum circuit is represented as a set of quantum gates. For example, a quantum circuit can be made as follows:

[21]:
from qulacs import QuantumState, QuantumCircuit
from qulacs.gate import Z
n = 5
state = QuantumState(n)
state.set_zero_state()
# Define quantum circuit
circuit = QuantumCircuit(n)
# Add hadamard gate to quantum circuit
for i in range(n):
    circuit.add_H_gate(i)
# Gate can be generated and added
for i in range(n):
    circuit.add_gate(Z(i))
# Apply quantum circuit to quantum state
circuit.update_quantum_state(state)
print(state.get_vector())
[ 0.1767767+0.j -0.1767767-0.j -0.1767767-0.j  0.1767767+0.j
 -0.1767767-0.j  0.1767767+0.j  0.1767767+0.j -0.1767767-0.j
 -0.1767767-0.j  0.1767767+0.j  0.1767767+0.j -0.1767767-0.j
  0.1767767+0.j -0.1767767-0.j -0.1767767-0.j  0.1767767+0.j
 -0.1767767-0.j  0.1767767+0.j  0.1767767+0.j -0.1767767-0.j
  0.1767767+0.j -0.1767767-0.j -0.1767767-0.j  0.1767767+0.j
  0.1767767+0.j -0.1767767-0.j -0.1767767-0.j  0.1767767+0.j
 -0.1767767-0.j  0.1767767+0.j  0.1767767+0.j -0.1767767-0.j]

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.

Calculation and optimization of depth of quantum circuits

By merging quantum gates into a single quantum gate, the number of quantum gates can be reduced and the time required for numerical calculations can be reduced. (Of course, when the number of target qubits increases, or when a quantum gate with a dedicated function is merged into a quantum gate without a dedicated function, the total calculation time will not necessarily decrease.)

The code below uses the optimize function to repeat merging the quantum gates of the quantum circuit until the target qubit becomes three by greedy algorithm.

[22]:
from qulacs import QuantumCircuit
from qulacs.circuit import QuantumCircuitOptimizer
n = 5
depth = 10
circuit = QuantumCircuit(n)
for d in range(depth):
    for i in range(n):
        circuit.add_H_gate(i)
# Calculate depth (depth=10)
print(circuit.calculate_depth())
# Optimization
opt = QuantumCircuitOptimizer()
# Maximum quantum gate size allowed to be created
max_block_size = 1
opt.optimize(circuit, max_block_size)
# Calculate depth (depth=1へ)
print(circuit.calculate_depth())
10
1

Information debugging of quantum circuits

When print a quantum circuit, statistical information about the gates included in the quantum circuit will be displayed.

[23]:
from qulacs import QuantumCircuit
from qulacs.circuit import QuantumCircuitOptimizer
n = 5
depth = 10
circuit = QuantumCircuit(n)
for d in range(depth):
    for i in range(n):
        circuit.add_H_gate(i)
print(circuit)
*** Quantum Circuit Info ***
# of qubit: 5
# of step : 10
# of gate : 50
# of 1 qubit gate: 50
Clifford  : yes
Gaussian  : no


Observable

Generation of observable

Observables are represented as a set of Pauli operators. The Pauli operator can be defined as follows:

[24]:
from qulacs import Observable
n = 5
coef = 2.0
# Set Pauli operators: X_0 X_1 Y_2 Z_4
Pauli_string = "X 0 X 1 Y 2 Z 4"
observable = Observable(n)
observable.add_operator(coef,Pauli_string)

Observable evaluation

Evaluation of the expected value of the observable against the state can be obtained.

[25]:
from qulacs import Observable, QuantumState
n = 5
coef = 2.0
Pauli_string = "X 0 X 1 Y 2 Z 4"
observable = Observable(n)
observable.add_operator(coef,Pauli_string)
state = QuantumState(n)
state.set_Haar_random_state()
# Calculate expectation value
value = observable.get_expectation_value(state)
print(value)
-0.42507924552241977

Parametric quantum circuit

Defining a quantum circuit as the ParametricQuantumCircuit class allows you to use some functions that are useful for optimizing quantum circuits using variational methods, in addition to the usual functions of the QuantumCircuit class.

Examples of parametric quantum circuits

Quantum gates with one rotation angle (X-rot, Y-rot, Z-rot, multi_qubit_pauli_rotation) can be added to quantum circuits as parametric quantum gates. For quantum gates added as parametric gates, the number of parametric gates can be extracted after the quantum circuit is constructed, and the rotation angle can be changed later.

[26]:
from qulacs import ParametricQuantumCircuit
from qulacs import QuantumState
import numpy as np
n = 5
depth = 10
# construct parametric quantum circuit with random rotation
circuit = ParametricQuantumCircuit(n)
for d in range(depth):
    for i in range(n):
        angle = np.random.rand()
        circuit.add_parametric_RX_gate(i,angle)
        angle = np.random.rand()
        circuit.add_parametric_RY_gate(i,angle)
        angle = np.random.rand()
        circuit.add_parametric_RZ_gate(i,angle)
    for i in range(d%2, n-1, 2):
        circuit.add_CNOT_gate(i,i+1)
# add multi-qubit Pauli rotation gate as parametric gate (X_0 Y_3 Y_1 X_4)
target = [0,3,1,4]
pauli_ids = [1,2,2,1]
angle = np.random.rand()
circuit.add_parametric_multi_Pauli_rotation_gate(target, pauli_ids, angle)
# get variable parameter count, and get current parameter
parameter_count = circuit.get_parameter_count()
param = [circuit.get_parameter(ind) for ind in range(parameter_count)]
# set 3rd parameter to 0
circuit.set_parameter(3, 0.)
# update quantum state
state = QuantumState(n)
circuit.update_quantum_state(state)
# output state and circuit info
print(state)
print(circuit)
 *** Quantum State ***
 * Qubit Count : 5
 * Dimension   : 32
 * State vector :
     (0.0644046,0.0543203)
    (-0.0124557,0.0773343)
   (-0.0994673,-0.0232714)
    (-0.0906349,0.0126735)
    (-0.0055132,0.0615354)
       (0.0257616,0.11585)
     (0.0572645,-0.176249)
      (0.148752,-0.097278)
    (0.0190231,-0.0245991)
   (-0.00852605,0.0232178)
    (0.00586012,0.0554068)
     (0.0484442,-0.232187)
    (-0.0155285,0.0231175)
   (-0.0303796,-0.0352834)
     (-0.183101,0.0680983)
      (0.267338,-0.195386)
    (-0.239673,-0.0333867)
    (-0.155652,-0.0435336)
       (0.223335,0.251257)
    (-0.167392,-0.0063077)
     (-0.127799,0.0654382)
     (0.363626,-0.0430651)
    (0.0865432,-0.0511235)
      (0.0467295,0.238852)
    (-0.0438296,0.0167142)
     (0.0477825,0.0855392)
    (-0.0323637,-0.121396)
      (0.246739,0.0580693)
     (-0.0387275,0.233282)
     (-0.245181,0.0552884)
(-0.000639744,-0.00135822)
   (-0.0788815,-0.0741289)

*** Quantum Circuit Info ***
# of qubit: 5
# of step : 41
# of gate : 171
# of 1 qubit gate: 150
# of 2 qubit gate: 20
# of 3 qubit gate: 0
# of 4 qubit gate: 1
Clifford  : no
Gaussian  : no

*** Parameter Info ***
# of parameter: 151