Qulacs Python Advanced Guide

This chapter is for those who are familiar with the terminology of quantum information and want fine tuning for numerical calculations. For more information on terminology, please see the textbook Quantum Computation and Quantum Information by M.A. Nielsen et al..

Quantum States

This class allocates and manages \(2^n\) complex arrays on the CPU/GPU with the precision of complex128.

Create and Destroy

Necessary memory is secured at the time of instance creation and released when the instance is destroyed, but you can explicitly destroy it to release the memory with del.

[2]:
from qulacs import QuantumState
n = 2
state = QuantumState(n)
print(state)
del state
 *** Quantum State ***
 * Qubit Count : 2
 * Dimension   : 4
 * State vector :
(1,0)
(0,0)
(0,0)
(0,0)

Transform between quantum state and numpy array

Transformation between quantum state and numpy array can be realized by get_vector or load function. In principle, it is not checked whether the norm is saved. While get_vector returns all the element as an array, get_amplitude can be used to quickly get a single element.

[3]:
from qulacs import QuantumState

state = QuantumState(2)
vec = state.get_vector()
print(vec)
state.load([0,1,2,3])
print(state.get_vector())
print(state.get_amplitude(2))
[1.+0.j 0.+0.j 0.+0.j 0.+0.j]
[0.+0.j 1.+0.j 2.+0.j 3.+0.j]
(2+0j)

Copy quantum states

Quantum states can generate new instances of the same state by copy. By giving a quantum state to the load function, it is possible to copy a quantum vector of another quantum state without allocating a new area in the existing quantum state. This allows you to reuse the already allocated space. You can use the allocate_buffer function if you want to allocate a state vector of the same size as the quantum state you already have and without copying the state.

[4]:
from qulacs import QuantumState

initial_state = QuantumState(3)
buffer = initial_state.allocate_buffer()
for ind in range(10):
    buffer.load(initial_state)
    # some computation and get results

Store quantum state

Quantum states can be converted to JSON string. You can restore a quantum state by loading JSON.

[5]:
from qulacs import QuantumState
from qulacs import state

o_state = QuantumState(2)
o_state.set_Haar_random_state()
print("original:", o_state.get_vector())
state_json = o_state.to_json()

r_state = state.from_json(state_json)
print("restored:", r_state.get_vector())
original: [-0.53409555-0.11980945j  0.23272193+0.42256166j -0.65314356-0.07012461j
 -0.02987127+0.18778582j]
restored: [-0.53409555-0.11980945j  0.23272193+0.42256166j -0.65314356-0.07012461j
 -0.02987127+0.18778582j]

Quantum states can also be saved to files in pickle format.

[6]:
from qulacs import QuantumState
import pickle

state = QuantumState(2)

# store
with open('state.pickle', 'wb') as f:
    pickle.dump(state, f)

# load
with open('state.pickle', 'rb') as f:
    state = pickle.load(f)

Initialization of quantum state

The following is an function initializing a quantum state to a specific state.

[7]:
from qulacs import QuantumState

n = 3
state = QuantumState(n)
# Initialize as |0> state
state.set_zero_state()
print(state.get_vector())

# Initialize the specified value to the calculation base in binary notation
state.set_computational_basis(0b101)
print(state.get_vector())

# Initialize to random pure state with Haar measure using argument value as seed
# If no value is specified, the time function is used as a seed. Pseudo random number uses xorshift.
state.set_Haar_random_state(0)
print(state.get_vector())
[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 1.+0.j 0.+0.j 0.+0.j]
[-0.08457288-0.14143014j -0.13386446+0.11328032j -0.22521966-0.16546737j
 -0.15105932+0.48125064j -0.45087363+0.17271267j -0.05855838+0.32498025j
  0.35972119+0.02643361j -0.10103482-0.35651694j]

Check quantum state

The following example is a list of functions to check quantum state information without changing the quantum state.

[8]:
from qulacs import QuantumState

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

# Get quantum bit numbers
qubit_count = state.get_qubit_count()
print("qubit_count", qubit_count)

# Get the probability that the specified qubit will be measured as 0
prob = state.get_zero_probability(1)
print("zero_prob_1", prob)

# Get arbitrary marginal probabilities
# Argument is an array of the same length as the number of qubits
# Specify 0,1,2. 0,1 is the probability of the subscript measured at that value
# 2 means that bit is peripheralized.
# For example, calculation of the probability that the third is measured as 0 and the 0th is measured as 1:
prob = state.get_marginal_probability([1,2,2,0,2])
print("marginal_prob", prob)

# Get the entropy of the probability distribution when measured on the Z basis
ent = state.get_entropy()
print("entropy", ent)

# Get squared norm (<a|a>)
# Because the operation may not be Trace preserving, the norm of state does not necessarily to be 1.
sq_norm = state.get_squared_norm()
print("sqaured_norm", sq_norm)

# Measure and sample all the qubits on Z-basis as many times as given by the argument.
# Returns a list of integers converted from the resulting binaries.
samples = state.sampling(10)
print("sampling", samples)

# You can supply a random seed as second argument.
# If the same seed is given, always returns the same sampling result.
samples_with_seed = state.sampling(10, 314)
print("sampling (with seed)", samples_with_seed)

# Get a character string indicating whether the state vector is on CPU or GPU
dev_type = state.get_device_name()
print("device", dev_type)
qubit_count 5
zero_prob_1 0.4601075596424598
marginal_prob 0.20030608663813237
entropy 3.1082736424124735
sqaured_norm 0.9999999999999999
sampling [11, 18, 12, 29, 21, 13, 28, 24, 10, 22]
sampling (with seed) [23, 18, 28, 14, 17, 30, 9, 17, 16, 10]
device cpu

Deformation of quantum state

The following functions modify a quantum state.

[9]:
from qulacs import QuantumState
state = QuantumState(2)
state.set_computational_basis(0)
buffer = QuantumState(2)
buffer.set_computational_basis(2)
print("state" , state.get_vector())
print("buffer", buffer.get_vector())

# Sum of quantum state (state <- state+buffer)
# Add the buffer state to the state to create a superposition state.
# The norm after the operation generally is not 1.
state.add_state(buffer)
print("added", state.get_vector())

# Product of quantum state and complex number
# Multiplies all elements by the complex number of the argument.
# The norm after operation generally is not 1.
coef = 0.5 + 0.1j
state.multiply_coef(coef)
print("mul_coef", state.get_vector())

# Pruduct of a quantum state and complex numbers specified by an index of each element
# Multiplies the amplitude of |00> by `coef_func(0)`, the amplitude of |01> by `coef_func(1)`.
# The norm after operation generally is not 1.
def coef_func(i: int) -> complex:
    assert 0 <= i < 2**2
    return 1j**i
state.multiply_elementwise_function(coef_func)
print("mul_elementwise_func", state.get_vector())

# Normalize quantum states
# Provide the current squared norm as an argument.
squared_norm = state.get_squared_norm()
print("sq_norm", squared_norm)
state.normalize(squared_norm)
print("normalized", state.get_vector())
print("sq_norm", state.get_squared_norm())
state [1.+0.j 0.+0.j 0.+0.j 0.+0.j]
buffer [0.+0.j 0.+0.j 1.+0.j 0.+0.j]
added [1.+0.j 0.+0.j 1.+0.j 0.+0.j]
mul_coef [0.5+0.1j 0. +0.j  0.5+0.1j 0. +0.j ]
mul_elementwise_func [ 0.5+0.1j  0. +0.j  -0.5-0.1j  0. -0.j ]
sq_norm 0.52
normalized [ 0.69337525+0.13867505j  0.        +0.j         -0.69337525-0.13867505j
  0.        -0.j        ]
sq_norm 0.9999999999999998

Opetation on classic registers

Quantum states have classical registers as integer arrays with variable length. The classical register is used to write the result of the Instrument operation or to describe a gate that executes conditions as the result of the classical register. The value of a classic register that has not yet been written is 0. The classical register is copied at the same time when the quantum state is copied by the copy and load functions.

[10]:
from qulacs import QuantumState
state = QuantumState(3)
position = 0
# Write the value to `position`-th register
state.set_classical_value(position, 20)
# Get the value of the `position`-th register
obtained = state.get_classical_value(position)
print(obtained)
20

Calculation between quantum states

The inner product and tensor product between quantum states can be obtained by inner_product and tensor_product respectively.

[11]:
from qulacs import QuantumState
from qulacs.state import inner_product, tensor_product

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

# Calculation of inner product
value = inner_product(state_bra, state_ket)
print(value)

n1 = 1
state_ket1 = QuantumState(n1)
state_ket1.set_computational_basis(1)
n2 = 2
state_ket2 = QuantumState(n2)
state_ket2.set_computational_basis(2)

# Calculation of tensor product
tensor_product_state = tensor_product(state_ket1, state_ket2)
print(tensor_product_state.get_vector())
(-0.056292589186392766-0.06355316981838662j)
[0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j]

Swap and delete qubits

You can swap indices of a qubit with permutate_qubit(). You can get a projection onto a specified qubit with drop_qubit().

[12]:
from qulacs import QuantumState
from qulacs.state import permutate_qubit, drop_qubit

n = 3
state = QuantumState(n)
state.set_Haar_random_state()
print("original:", state.get_vector())
# new qubit 0 is old qubit 1
# new qubit 1 is old qubit 2,
# new qubit 2 is old qubit 0,
permutate = permutate_qubit(state, [1, 2, 0])
print("permutate:", permutate.get_vector())
print()

n = 3
state = QuantumState(n)
state.set_Haar_random_state()
print("original:", state.get_vector())
state0 = drop_qubit(state, [1], [0])
print("projection to 0:", state0.get_vector()) # projection: qubit 1 is 0
state1 = drop_qubit(state, [1], [1])
print("projection to 1:", state1.get_vector()) # projection: qubit 1 is 1
original: [ 0.04935285-0.33525621j -0.40848741-0.16861513j  0.0877062 +0.3016868j
  0.43151866+0.12561444j  0.05137289-0.07313601j  0.10155306-0.05927549j
  0.12474423-0.07303469j  0.23710293+0.53875064j]
permutate: [ 0.04935285-0.33525621j  0.0877062 +0.3016868j   0.05137289-0.07313601j
  0.12474423-0.07303469j -0.40848741-0.16861513j  0.43151866+0.12561444j
  0.10155306-0.05927549j  0.23710293+0.53875064j]

original: [-0.13129252+0.41425428j -0.06117213+0.11127952j -0.30889329-0.26279468j
 -0.14177694+0.05919262j  0.03556784-0.19266318j -0.24280479-0.45063361j
  0.383783  -0.0233139j   0.34842192+0.19315843j]
projection to 0: [-0.13129252+0.41425428j -0.06117213+0.11127952j  0.03556784-0.19266318j
 -0.24280479-0.45063361j]
projection to 1: [-0.30889329-0.26279468j -0.14177694+0.05919262j  0.383783  -0.0233139j
  0.34842192+0.19315843j]

Calculate partial trace

With partial_trace(), you can obtain a partial trace of a given qubit of a given quantum state as a density matrix. The indices of the converted qubits are reassigned based on the order of the qubits before conversion.

[13]:
from qulacs import QuantumState, DensityMatrix
from qulacs.gate import H, X
from qulacs.state import partial_trace

state = QuantumState(3)
state.set_computational_basis(0)
H(0).update_quantum_state(state)
X(1).update_quantum_state(state)
print(state.get_vector())

trace = partial_trace(state, [1])
print(trace.get_matrix())

dm_state = DensityMatrix(3)
dm_state.set_computational_basis(0)
H(0).update_quantum_state(dm_state)
X(1).update_quantum_state(dm_state)
print(dm_state.get_matrix())

trace = partial_trace(dm_state, [1])
print(trace.get_matrix())
[0.        +0.j 0.        +0.j 0.70710678+0.j 0.70710678+0.j
 0.        +0.j 0.        +0.j 0.        +0.j 0.        +0.j]
[[0.5+0.j 0.5+0.j 0. +0.j 0. +0.j]
 [0.5+0.j 0.5+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.5+0.j 0.5+0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j]
 [0. +0.j 0. +0.j 0.5+0.j 0.5+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 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j]]
[[0.5+0.j 0.5+0.j 0. +0.j 0. +0.j]
 [0.5+0.j 0.5+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]]

Calculation using GPU

When Qulacs is installed from qulacs-gpu package, QuantumStateGpu is available. Except the different class name, the usage is the same as QuantumState.

from qulacs import QuantumStateGpu
state = QuantumStateGpu(2)
print(state)
# print(state.get_device_name())
# gpu

Though the usage is the same as QuantumState, there are two aspects to keep in mind:

  • The get_vector function takes a long time because it requires copying between the GPU and CPU. This function should be avoided whenever possible.

  • inner_product between CPU / GPU states cannot be calculated. It is possible to load a state vector between the GPU and CPU state vectors, but it is time consuming and should be avoided.

DensityMatrix

DensityMatrix is a class to hold a quantum state as a density matrix. While StateVector can hold a pure state, DensityMatrix can also hold a mixed state, or probabilistic mixture of multiple states. Use the density matrix \(\sum_i p_i\ket{\psi_i}\bra{\psi_i}\) when each state is \(\ket{\psi_i}\) with probability \(p_i\). This may seem redundant since there are no multi-state composite states in this chapter, but it will be useful when using Probabilistic gates, etc., described below. Basically, DensityMatrix can be operated in the same way as QuantumState.

Generate and delete

The necessary memory is allocated when the instance is created. The memory is released when Python interpreter destroys the instance, but can be released with del if you want to explicitly destroy it to free memory.

[14]:
from qulacs import QuantumState
n = 2
state = DensityMatrix(n)
print(state)
del state
 *** Density Matrix ***
 * Qubit Count : 2
 * Dimension   : 4
 * Density 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) (0,0)

Conversion between quantum states and numpy array

You can convert quantum states and numpy array mutually with get_matrix and load. If the array is 1-dimensional, it is converted from a state vector to a density matrix, and if it is 2-dimensional, it is read as a density matrix. Basically, whether the norm is conserved or not is not checked.

[15]:
from qulacs import DensityMatrix

state = DensityMatrix(2)
mat = state.get_matrix()
print(mat)
state.load([0,1,2,3])
print(state.get_matrix())
state.load([[0,1,2,3], [1,2,3,4], [2,3,4,5], [3,4,5,6]])
print(state.get_matrix())
[[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 1.+0.j 2.+0.j 3.+0.j]
 [0.+0.j 2.+0.j 4.+0.j 6.+0.j]
 [0.+0.j 3.+0.j 6.+0.j 9.+0.j]]
[[0.+0.j 1.+0.j 2.+0.j 3.+0.j]
 [1.+0.j 2.+0.j 3.+0.j 4.+0.j]
 [2.+0.j 3.+0.j 4.+0.j 5.+0.j]
 [3.+0.j 4.+0.j 5.+0.j 6.+0.j]]

Copy between quantum states

A quantum state can be instantiated by copy to create a new instance of itself. Also, by giving a quantum state to the load function, you can copy the quantum vector or density matrix of another quantum state without allocating new space in the existing quantum state. This allows you to reuse the space you have already allocated. If you want to allocate a density matrix of the same size as the quantum state you already have, but do not need to copy the state, you can use the allocate_buffer function.

[16]:
from qulacs import QuantumState, DensityMatrix

initial_state = DensityMatrix(3)
copied_state = initial_state.copy()
buffer = initial_state.allocate_buffer()
buffer.load(initial_state)
state_vector = QuantumState(3)
buffer.load(state_vector)

Store quantum states

DensityMatrix can also be converted to/from JSON string

[17]:
from qulacs import DensityMatrix
from qulacs import state

o_state = DensityMatrix(2)
o_state.set_Haar_random_state()
print("original:", o_state.get_matrix())
state_json = o_state.to_json()

r_state = state.from_json(state_json)
print("restored:", r_state.get_matrix())
original: [[ 0.69553616+0.j         -0.30796432+0.24210111j -0.00753329-0.048494j
  -0.16911174-0.16523753j]
 [-0.30796432-0.24210111j  0.22062831+0.j         -0.01354418+0.02409399j
   0.01736242+0.13202679j]
 [-0.00753329+0.048494j   -0.01354418-0.02409399j  0.00346268+0.j
   0.01335228-0.01000109j]
 [-0.16911174+0.16523753j  0.01736242-0.13202679j  0.01335228+0.01000109j
   0.08037285+0.j        ]]
restored: [[ 0.69553616+0.j         -0.30796432+0.24210111j -0.00753329-0.048494j
  -0.16911174-0.16523753j]
 [-0.30796432-0.24210111j  0.22062831+0.j         -0.01354418+0.02409399j
   0.01736242+0.13202679j]
 [-0.00753329+0.048494j   -0.01354418-0.02409399j  0.00346268+0.j
   0.01335228-0.01000109j]
 [-0.16911174+0.16523753j  0.01736242-0.13202679j  0.01335228+0.01000109j
   0.08037285+0.j        ]]

Density matrices can be stored to files in pickle format.

[18]:
from qulacs import QuantumState
import pickle

state = QuantumState(2)

# store
with open('state.pickle', 'wb') as f:
    pickle.dump(state, f)

# load
with open('state.pickle', 'rb') as f:
    state = pickle.load(f)

Initialize quantum states

The following example shows functions to initialize a quantum state to a specific pure state.

[19]:
from qulacs import DensityMatrix

n = 2
state = DensityMatrix(n)

# Initialize as |0> state.
state.set_zero_state()
print(state.get_matrix())

# Initialize as computational basis specified in binary format.
state.set_computational_basis(0b10)
print(state.get_matrix())


# Initialize as a random pure state in Haar measure with the seed given as an argument.
# If you do not give the seed, `time` function is used for seed.
# Xorshift is used for psuedo random.
state.set_Haar_random_state(0)
print(state.get_matrix())
[[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 1.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j]]
[[ 0.06955138+0.j         -0.01203783+0.07302921j  0.10872467+0.04574116j
  -0.14160694+0.15896533j]
 [-0.01203783-0.07302921j  0.07876443+0.j          0.02921052-0.12207811j
   0.19142327+0.12117438j]
 [ 0.10872467-0.04574116j  0.02921052+0.12207811j  0.20004359+0.j
  -0.11681879+0.3416283j ]
 [-0.14160694-0.15896533j  0.19142327-0.12117438j -0.11681879-0.3416283j
   0.6516406 +0.j        ]]

Check quantum states

The following example shows functions to check information of quantum states without changing the states.

[20]:
from qulacs import DensityMatrix

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

# Get quantum bit numbers
qubit_count = state.get_qubit_count()
print("qubit_count", qubit_count)

# Get the probability that the specified qubit will be measured as 0
prob = state.get_zero_probability(1)
print("zero_prob_1", prob)

# Get arbitrary marginal probabilities
# Argument is an array of the same length as the number of qubits
# Specify 0,1,2. 0,1 is the probability of the subscript measured at that value
# 2 means that bit is peripheralized.
# For example, calculation of the probability that the third is measured as 0 and the 0th is measured as 1:
prob = state.get_marginal_probability([1,2,2,0,2])
print("marginal_prob", prob)

# Get the entropy of the probability distribution when measured on the Z basis
ent = state.get_entropy()
print("entropy", ent)

# Get squared norm (<a|a>)
# Because the operation may not be Trace preserving, the norm of state does not necessarily to be 1.
sq_norm = state.get_squared_norm()
print("sqaured_norm", sq_norm)

# Measure and sample all the qubits on Z-basis as many times as given by the argument.
# Returns a list of integers converted from the resulting binaries.
samples = state.sampling(10)
print("sampling", samples)

# You can supply a random seed as second argument.
# If the same seed is given, always returns the same sampling result.
samples_with_seed = state.sampling(10, 314)
print("sampling (with seed)", samples_with_seed)

# Get a character string indicating whether the state vector is on CPU or GPU
dev_type = state.get_device_name()
print("device", dev_type)
qubit_count 5
zero_prob_1 0.46010755964245986
marginal_prob 0.20030608663813237
entropy 3.108273642412474
sqaured_norm 1.0000000000000002
sampling [11, 30, 30, 30, 24, 6, 24, 3, 10, 29]
sampling (with seed) [23, 18, 28, 14, 17, 30, 9, 17, 16, 10]
device cpu

Deformation of quantum states

The following functions modify a quantum state. add_state and multiply_coef calculates for each element in a density matrix.

It is fundamentally different in behavior from the operation of the same name in ``QuantumState``, which corresponds to the quantum state

  • add_state of QuantumState creates superpositon, but add_state of DensityMatrix create mixed state

  • The operation corresponding to multiply_coef(z) of QuantumState is multiply_coef(abs(z)**2) for DensityMatrix

Using state.make_superposition() state.make_mixture() is recommended since add_state() multiply_coef() make users confused with those generated by QuantumState.

[21]:
from qulacs import DensityMatrix
state = DensityMatrix(2)
state.set_computational_basis(0)
buffer = DensityMatrix(2)
buffer.set_computational_basis(2)
print("state" , state.get_matrix())
print("buffer", buffer.get_matrix())

# Sum of quantum state (state <- state+buffer)
# Add the buffer state to the state to create a superposition state.
# The norm after the operation generally is not 1.
state.add_state(buffer)
print("added", state.get_matrix())

# Product of quantum state and complex number
# Multiplies all elements by the complex number of the argument.
# The norm after operation generally is not 1.
coef = 3.0
state.multiply_coef(coef)
print("mul_coef", state.get_matrix())

# Normalize quantum states
# Provide the current squared norm as an argument.
squared_norm = state.get_squared_norm()
print("sq_norm", squared_norm)
state.normalize(squared_norm)
print("normalized", state.get_matrix())
print("sq_norm", state.get_squared_norm())
state [[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]]
buffer [[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]]
added [[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]]
mul_coef [[3.+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 3.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j]]
sq_norm 6.0
normalized [[0.5+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.5+0.j 0. +0.j]
 [0. +0.j 0. +0.j 0. +0.j 0. +0.j]]
sq_norm 1.0

Operation on classical registers

DensityMatrix also has classical registers as integer arrays with variable length.

[22]:
from qulacs import DensityMatrix
state = DensityMatrix(3)
position = 0
# Set the value at `position`-th register.
state.set_classical_value(position, 20)
# Get the value at `position`-th register.
obtained = state.get_classical_value(position)
print(obtained)
20

Creating superposition states and mixture states

You can create superposition states and mixture states by using make_superposition() make_mixture() in state module. These states can also be created by applying add_state() multiply_coef() to QuantumState DensityMatrix, but is deprecated due to low readability.

[23]:
from qulacs import QuantumState, DensityMatrix
from qulacs.state import make_superposition, make_mixture
# from QuantumState |a> and |b>, create a superposition state p|a> + q|b>
a = QuantumState(2)
a.set_computational_basis(0b00)
b = QuantumState(2)
b.set_computational_basis(0b11)
p = 1 / 2
q = 1 / 2
c = make_superposition(p, a, q, b)
print(c.get_vector())
# from QuantumState |a> and DensityMatrix |b><b|, create a mixture state p|a><a| + q|b><b|
# You can also create a mixture states from two QuantumState or two DensitMatrix
a = QuantumState(2)
a.set_computational_basis(0b00)
b = DensityMatrix(2)
b.set_computational_basis(0b11)
p = 1 / 2
q = 1 / 2
c = make_mixture(p, a, q, b)
print(c.get_matrix())
[0.5+0.j 0. +0.j 0. +0.j 0.5+0.j]
[[0.5+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.5+0.j]]

Quantum gates

Types of quantum gate

Quantum gates are divided into two types: special gates and general gates. In Qulacs, quantum gates are not limited to unitary operators, and the operation of updating an arbitrary quantum state, such as Instrument and CPTP-map, is also called a gate.

Special gates are those that have a pre-specified gate matrix and can only perform limited deformations on quantum gates. For example, Pauli gate, rotation Pauli gate, projection measurement, etc. are supported. The advantage of a special gate is that the update function of the quantum state is more efficient than a general gate with limited properties. Also, at the time of definition, it holds information on whether or not it is diagonalized by each qubit at the basis of some Pauli, and this information is used for circuit optimization. The disadvantage of special gates is that the possible operations on the gates are limited for the reasons mentioned above.

A gate that has an explicit operation gate matrix is called a general gate. The advantage of general gates is that you can specify the gate matrix as you like, but the disadvantage is that the updates are slower than special gates.

Common operations for quantum gates

The gate matrix of the generated quantum gate can be obtained with the get_matrix function. Control qubits are not included in the gate matrix. In particular, be careful when obtain gates that do not have a gate matrix (for example, \(n\)-qubit Pauli rotating gates), which require a very large amount of memory and time. print function displays the gate information.

[24]:
import numpy as np
from qulacs.gate import X
gate = X(2)
mat = gate.get_matrix()
print(mat)
print(gate)
[[0.+0.j 1.+0.j]
 [1.+0.j 0.+0.j]]
 *** gate info ***
 * gate name : X
 * target    :
 2 : commute X
 * control   :
 * Pauli     : yes
 * Clifford  : yes
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no

Quantum gates can also be converted to/from JSON string. Some types of gates are not supported.

[25]:
from qulacs.gate import X
from qulacs import gate

o_gate = X(2)
print(o_gate)
gate_json = o_gate.to_json()

r_gate = gate.from_json(gate_json)
print(r_gate)
 *** gate info ***
 * gate name : X
 * target    :
 2 : commute X
 * control   :
 * Pauli     : yes
 * Clifford  : yes
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no

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

Special gate

The special gates are listed below.

1 qubit gate

Takes the index of the target bit as the first argument.

[ ]:
from qulacs.gate import Identity # Identity matrix
from qulacs.gate import X, Y, Z     # Pauli
from qulacs.gate import H, S, Sdag, sqrtX, sqrtXdag, sqrtY, sqrtYdag # クリフォード
from qulacs.gate import T, Tdag # T gate
from qulacs.gate import P0, P1 # Projection to 0,1 (not normalized)
target = 3
gate = T(target)
print(gate)
 *** gate info ***
 * gate name : T
 * target    :
 3 : commute
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : yes
 * Parametric: no
 * Diagonal  : no

Identity does not update the quantum state, but when it is included into the quantum circuit, it is counted as a gate that consumes 1 step.

1 qubit rotating gate

Take the index of the target bit as the first argument and the rotation angle as the second argument.

[ ]:
import numpy as np
from qulacs.gate import RX, RY, RZ
target = 0
angle = 0.1
gate = RX(target, angle)
print(gate)
print(gate.get_matrix())
 *** gate info ***
 * gate name : X-rotation
 * target    :
 0 : commute X
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no

[[0.99875026+0.j         0.        +0.04997917j]
 [0.        +0.04997917j 0.99875026+0.j        ]]

The definition of rotating operation is \(R_X(\theta) = \exp(i\frac{\theta}{2} X)\)です。

IBMQ basis gate

IBMQ basis gate is a gate based on the virtual-Z decomposition defined by IBMQ’s OpenQASM.

[ ]:
from qulacs.gate import U1,U2,U3
print(U3(0, 0.1, 0.2, 0.3))
 *** gate info ***
 * gate name : DenseMatrix
 * target    :
 0 : commute
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no
 * Matrix
            (0.99875,0) (-0.0477469,-0.0147699)
 (0.0489829,0.00992933)     (0.876486,0.478826)

Definitions are:

  • \(U_1(\lambda) = R_Z(\lambda)\)

  • \(U_2(\phi, \lambda) = R_Z(\phi+\frac{\pi}{2}) R_X(\frac{\pi}{2}) R_Z(\lambda-\frac{\pi}{2})\)

  • \(U_3(\theta, \phi, \lambda) = R_Z(\phi+3\pi) R_X(\pi/2) R_Z(\theta+\pi) R_X(\pi/2) R_Z(\lambda)\)

U3 matches the degree of freedom of any 1-qubit unitary operation.

2 qubit gate

Take the indexes of the target bit in the first and second arguments. The first argument of the CNOT gate is a control qubit. The remaining gates are symmetric operations.

[ ]:
from qulacs.gate import CNOT, CZ, SWAP
control = 5
target = 2
target2 = 3
gate = CNOT(control, target)
print(gate)
gate = CZ(control, target)
gate = SWAP(target, target2)
 *** gate info ***
 * gate name : CNOT
 * target    :
 2 : commute X
 * control   :
 5 : value 1
 * Pauli     : no
 * Clifford  : yes
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no

Multi-bit Pauli operation

Multi-bit Pauli operations define a gate with arguments as a list of target qubits and a list of Pauli operators. Since the update speed of an \(n\)-qubit Pauli operation has the same order as the update cost of a 1-qubit Pauli operation, Pauli’s tensor product should be defined as the gate in this form. In the Pauli operator specification, 1, 2, and 3 correspond to X, Y, and Z, respectively.

[ ]:
from qulacs.gate import Pauli
target_list = [0,3,5]
pauli_index = [1,3,1] # 1:X , 2:Y, 3:Z
gate = Pauli(target_list, pauli_index) # = X_0 Z_3 X_5
print(gate)
print(gate.get_matrix())
 *** gate info ***
 * gate name : Pauli
 * target    :
 0 : commute X
 3 : commute     Z
 5 : commute X
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no

[[ 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  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 -1.-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  1.+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 -1.-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]]

Multi-bit Pauli rotating operation

Multi-bit Pauli rotating operation rotates multi-bit Pauli operator. The multi-bit Pauli rotation becomes heavy calculation when the gate matrix is calculated greedily, but it can be updated efficiently if it is defined in this form.

[ ]:
from qulacs.gate import PauliRotation
target_list = [0,3,5]
pauli_index = [1,3,1] # 1:X , 2:Y, 3:Z
angle = 0.5
gate = PauliRotation(target_list, pauli_index, angle) # = exp(i angle/2 X_0 Z_3 X_5)
print(gate)
print(gate.get_matrix().shape)
 *** gate info ***
 * gate name : Pauli-rotation
 * target    :
 0 : commute X
 3 : commute     Z
 5 : commute X
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no

(8, 8)

Reversible circuit

Reversible circuit performs permutation operation between basis by giving a bijective function to a total number of \(2^n\) index. This is equivalent to the gate matrix being a permutation matrix. Please note that it will not work properly unless the given function is bijective.

[ ]:
from qulacs.gate import ReversibleBoolean
def upper(val, dim):
    return (val+1)%dim
target_list = [0,1]
gate = ReversibleBoolean(target_list, upper)
print(gate)
state = QuantumState(3)
state.load(np.arange(2**3))
print(state.get_vector())
gate.update_quantum_state(state)
print(state.get_vector())
 *** gate info ***
 * gate name : ReversibleBoolean
 * target    :
 0 : commute
 1 : commute
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no

[0.+0.j 1.+0.j 2.+0.j 3.+0.j 4.+0.j 5.+0.j 6.+0.j 7.+0.j]
[3.+0.j 0.+0.j 1.+0.j 2.+0.j 7.+0.j 4.+0.j 5.+0.j 6.+0.j]

The above code moves the elements of the vector down one by one in the subspace of the qubit of interest (the bottom element moves to the top). ### State reflection This gate is \((I-2\ket{a}\bra{a})\), which is defined with the quantum state \(\ket{a}\) as an argument. This corresponds to the operation of reflecting based on the quantum state \(\ket{a}\). This gate appears in Grover search. The number of qubits on which this gate operates must match the number of qubits in the quantum state given as an argument.

[ ]:
from qulacs.gate import StateReflection
from qulacs import QuantumState
axis = QuantumState(2)
axis.set_Haar_random_state(0)
state = QuantumState(2)
gate = StateReflection(axis)
gate.update_quantum_state(state)
print("axis", axis.get_vector())
print("reflected", state.get_vector())
gate.update_quantum_state(state)
print("two reflection", state.get_vector())
axis [0.26990561+0.18885571j 0.42520294-0.30443016j 0.33036863-0.07179331j
 0.02371619+0.70933j   ]
reflected [-0.78296897+0.j          0.11454257-0.32493882j  0.15121954-0.16353884j
  0.28072431+0.37394642j]
two reflection [ 1.00000000e+00-2.77555756e-17j -5.55111512e-17+0.00000000e+00j
 -2.77555756e-17+2.77555756e-17j  0.00000000e+00-1.11022302e-16j]

General gate

This is a quantum gate with a gate matrix.

Dense matrix gate

Gate defined based on a dense matrix.

[ ]:
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)

Sparse matrix gate

A gate defined based on a sparse matrix. If the elements are sparse enough, they can be updated faster than a dense matrix. Define sparse matrices using scipy’s csc_matrix.

[ ]:
from qulacs import QuantumState
from qulacs.gate import SparseMatrix
from scipy.sparse import csc_matrix
mat = csc_matrix((2,2))
mat[1,1] = 1
print("sparse matrix", mat)

gate = SparseMatrix([0], mat)
print(gate)

qs = QuantumState(2)
qs.load([1,2,3,4])
gate.update_quantum_state(qs)
print(qs.get_vector())
sparse matrix   (1, 1)  1.0
 *** gate info ***
 * gate name : SparseMatrix
 * target    :
 0 : commute
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no
 * Matrix
0 0
0 (1,0)


[0.+0.j 2.+0.j 0.+0.j 4.+0.j]

Add control bit

General gates can add a control qubit using the add_control_qubit function. You can also specify whether the target qubit has an effect when control qubit is 0 or 1.

[ ]:
import numpy as np
from qulacs.gate import to_matrix_gate, X

index = 0
x_gate = X(index)
x_mat_gate = to_matrix_gate(x_gate)

# Operate only when 1st-qubit is 0
control_index = 1
control_with_value = 0
x_mat_gate.add_control_qubit(control_index, control_with_value)
print(x_mat_gate)

from qulacs import QuantumState
state = QuantumState(3)
state.load(np.arange(2**3))
print(state.get_vector())

x_mat_gate.update_quantum_state(state)
print(state.get_vector())
 *** gate info ***
 * gate name : DenseMatrix
 * target    :
 0 : commute X
 * control   :
 1 : value 0
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no
 * Matrix
(0,0) (1,0)
(1,0) (0,0)

[0.+0.j 1.+0.j 2.+0.j 3.+0.j 4.+0.j 5.+0.j 6.+0.j 7.+0.j]
[1.+0.j 0.+0.j 2.+0.j 3.+0.j 5.+0.j 4.+0.j 6.+0.j 7.+0.j]

Operation to create a new gate from multiple gates

Gate product

Combine successively the operating quantum gates to create a new single quantum gate. This reduces access to quantum states.

[ ]:
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 the new gate by combining gates
# The gate in the first augement is applied first
x_and_ry_gate = merge(x_gate, ry_gate)
print(x_and_ry_gate)
 *** gate info ***
 * gate name : DenseMatrix
 * target    :
 1 : commute
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no
 * Matrix
 (0.382683,0)   (0.92388,0)
  (0.92388,0) (-0.382683,0)

Gate sum

You can add multiple gates to create a new gate. This is useful for making the projection of the Pauli operator \(P\) onto the +1 eigenvalue space such as \((I + P) / 2\).

[ ]:
import numpy as np
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)

Random unitary

Use the RandomUnitary function to sample a random unitary matrix with the Haar measure and generate a dense matrix gate.

[ ]:
from qulacs.gate import RandomUnitary
target_list = [2,3]
gate = RandomUnitary(target_list)
print(gate)
 *** gate info ***
 * gate name : DenseMatrix
 * target    :
 2 : commute
 3 : commute
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : no
 * Matrix
 (-0.549679,-0.199309)    (-0.41282,0.191987)  (-0.430215,-0.393607)  (-0.152234,-0.296078)
   (0.255527,0.290869)   (0.136685,-0.154401)  (0.0998426,-0.553577) (-0.700763,-0.0097331)
   (0.056699,0.690511) (-0.488394,-0.0743973)  (0.0480177,-0.260356)    (0.439362,0.113074)
 (0.156442,-0.0611172)  (-0.695389,-0.150231)    (0.177095,0.492055) (-0.433419,-0.0657552)

Stochastic operation

Using the Probabilistic function, create the stochastic operation by giving multiple gate operations and probability distribution. If the sum of the given probability distributions is less than 1, Identity will operate with a probability less than 1.

[ ]:
from qulacs.gate import Probabilistic, H, Z
distribution = [0.2, 0.2, 0.2]
gate_list = [H(0), Z(0), X(1)]
gate = Probabilistic(distribution, gate_list)
print(gate)

from qulacs import QuantumState
state = QuantumState(2)
for _ in range(10):
    gate.update_quantum_state(state)
    print(state.get_vector())
 *** gate info ***
 * gate name : Generic gate
 * target    :
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : yes

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

BitFlipNoise, DephasingNoise, IndependentXZNoise, DepolarizingNoise, and TwoQubitDepolarizingNoise gates are defined as stochastic gates. Probabilistic instances are generated by entering the error probabilities.

[ ]:
from qulacs.gate import BitFlipNoise, DephasingNoise, IndependentXZNoise, DepolarizingNoise, TwoQubitDepolarizingNoise
target = 0
second_target = 1
error_prob = 0.8
gate = BitFlipNoise(target, error_prob) # X: prob
gate = DephasingNoise(target, error_prob) # Z: prob
gate = IndependentXZNoise(target, error_prob) # X,Z : prob*(1-prob), Y: prob*prob
gate = DepolarizingNoise(target, error_prob) # X,Y,Z : prob/3
gate = TwoQubitDepolarizingNoise(target, second_target, error_prob) # {I,X,Y,Z} \times {I,X,Y,Z} \setminus {II} : prob/15

from qulacs import QuantumState
state = QuantumState(2)
for _ in range(10):
    gate.update_quantum_state(state)
    print(state.get_vector())
[1.+0.j 0.+0.j 0.+0.j 0.+0.j]
[0.-0.j 0.+1.j 0.-0.j 0.+0.j]
[0.-0.j 0.+1.j 0.-0.j 0.+0.j]
[ 0.-0.j -0.-1.j  0.-0.j -0.-0.j]
[0.+0.j 0.-0.j 0.+0.j 0.-1.j]
[0.+0.j 0.-0.j 0.+0.j 0.-1.j]
[-1.+0.j  0.+0.j -0.+0.j  0.+0.j]
[ 0.-0.j  0.+0.j  0.+0.j -0.-1.j]
[ 0.-0.j  0.+0.j  0.+0.j -0.-1.j]
[ 0.-0.j  0.+0.j  0.+0.j -0.-1.j]

Noisy evolution

There are two gates, NoisyEvolution and NoisyEvolution_fast, that interact from the environment and are attenuated by time evolution. They can be used by the following steps

  1. set up the system to be interacted with by the environment as a Hamiltonian and specify the operator of the interaction.

  2. specify the value to be evolved in time and the time to be varied microscopically.

  3. solve the differential equation.

    • NoisyEvolution solves differential equations using Runge-Kutta method.

    • NoisyEvolution_fast extracts a matrix and finds it by diagonalization.

It is recommended to use NoisyEvolution_fast because NoisyEvolution_fast does not require specifying the time to make a small change and it is fast.

[ ]:
from qulacs import QuantumState, Observable, GeneralQuantumOperator
from qulacs.gate import NoisyEvolution, NoisyEvolution_fast, H

n = 2
observable = Observable(n)
observable.add_operator(1., "X 0")

# create hamiltonian and collapse operator
hamiltonian = Observable(n)
hamiltonian.add_operator(1., "Z 0 Z 1")

decay_rate_z = 0.2
decay_rate_p = 0.6
decay_rate_m = 0.1

# interacting operator
c_ops = [GeneralQuantumOperator(n) for _ in range(3*n)]
c_ops[0].add_operator(decay_rate_z, "Z 0")
c_ops[1].add_operator(decay_rate_z, "Z 1")
c_ops[2].add_operator(decay_rate_p/2, "X 0")
c_ops[2].add_operator(decay_rate_p/2*1j, "Y 0")
c_ops[3].add_operator(decay_rate_p/2, "X 1")
c_ops[3].add_operator(decay_rate_p/2*1j, "Y 1")
c_ops[4].add_operator(decay_rate_m/2, "X 0")
c_ops[4].add_operator(-decay_rate_m/2*1j, "Y 0")
c_ops[5].add_operator(decay_rate_m/2, "X 1")
c_ops[5].add_operator(-decay_rate_m/2*1j, "Y 1")

time = 2.
gate = NoisyEvolution_fast(hamiltonian, c_ops, time)
#dt = .1
#gate = NoisyEvolution(hamiltonian, c_ops, time, dt)

exp = 0.
n_samples = 1000
state = QuantumState(n)
for k in range(n_samples):
   state.set_zero_state()
   H(0).update_quantum_state(state)
   H(1).update_quantum_state(state)
   gate.update_quantum_state(state)
   exp += observable.get_expectation_value(state) / n_samples
   print(f"[{k}] exp: {exp}")
[0] exp: -0.0006155544536970823
[1] exp: -0.0006155544536970823
[2] exp: 0.00028465213360111513
[3] exp: -0.0003309023200959672
[4] exp: -0.0009464567737930495
[5] exp: -0.0009464567737930495
[6] exp: -0.0009464567737930495
[7] exp: -0.0009464567737930495
[8] exp: -0.0009464567737930495
[9] exp: -0.0015620112274901319
[10] exp: -0.0022830973241566837
[11] exp: -0.0022830973241566837
[12] exp: -0.0022830973241566837
[13] exp: -0.002898651777853766
[14] exp: -0.002898651777853766
[15] exp: -0.0035142062315508486
[16] exp: -0.0035142062315508486
[17] exp: -0.004129760685247931
[18] exp: -0.004129760685247931
[19] exp: -0.004745315138945013
[20] exp: -0.004745315138945013
[21] exp: -0.004745315138945013
[22] exp: -0.005360869592642096
[23] exp: -0.005976424046339178
[24] exp: -0.006591978500036261
[25] exp: -0.006591978500036261
[26] exp: -0.006591978500036261
[27] exp: -0.007207532953733343
[28] exp: -0.007823087407430426
[29] exp: -0.007823087407430426
[30] exp: -0.008438641861127508
[31] exp: -0.008438641861127508
[32] exp: -0.00905419631482459
[33] exp: -0.008646834619727773
[34] exp: -0.009262389073424856
[35] exp: -0.009877943527121938
[36] exp: -0.01049349798081902
[37] exp: -0.011419090630198456
[38] exp: -0.012034645083895538
[39] exp: -0.01265019953759262
[40] exp: -0.01265019953759262
[41] exp: -0.01265019953759262
[42] exp: -0.013265753991289703
[43] exp: -0.013692461721774537
[44] exp: -0.01434682754881815
[45] exp: -0.01434682754881815
[46] exp: -0.014962382002515233
[47] exp: -0.014962382002515233
[48] exp: -0.015577936456212315
[49] exp: -0.015577936456212315
[50] exp: -0.016193490909909396
[51] exp: -0.016809045363606476
[52] exp: -0.017424599817303557
[53] exp: -0.018040154271000638
[54] exp: -0.01865570872469772
[55] exp: -0.0192712631783948
[56] exp: -0.01988681763209188
[57] exp: -0.02050237208578896
[58] exp: -0.02111792653948604
[59] exp: -0.022007366313092046
[60] exp: -0.022007366313092046
[61] exp: -0.02139181185939496
[62] exp: -0.022007366313092042
[63] exp: -0.022007366313092042
[64] exp: -0.022622920766789123
[65] exp: -0.021856515255194026
[66] exp: -0.022472069708891106
[67] exp: -0.021856515255194026
[68] exp: -0.022472069708891106
[69] exp: -0.02154025585060333
[70] exp: -0.02215581030430041
[71] exp: -0.02277136475799749
[72] exp: -0.02215581030430041
[73] exp: -0.02154025585060333
[74] exp: -0.02154025585060333
[75] exp: -0.02154025585060333
[76] exp: -0.020924701396906248
[77] exp: -0.02027348570580591
[78] exp: -0.020889040159502992
[79] exp: -0.020889040159502992
[80] exp: -0.021504594613200072
[81] exp: -0.021504594613200072
[82] exp: -0.020889040159502992
[83] exp: -0.021504594613200072
[84] exp: -0.022120149066897153
[85] exp: -0.022120149066897153
[86] exp: -0.021243892746641374
[87] exp: -0.021243892746641374
[88] exp: -0.021243892746641374
[89] exp: -0.021243892746641374
[90] exp: -0.022185495290800217
[91] exp: -0.022801049744497297
[92] exp: -0.023416604198194378
[93] exp: -0.02403215865189146
[94] exp: -0.024774427325995912
[95] exp: -0.025389981779692993
[96] exp: -0.024616313200662678
[97] exp: -0.02523186765435976
[98] exp: -0.02584742210805684
[99] exp: -0.02584742210805684
[100] exp: -0.02584742210805684
[101] exp: -0.02646297656175392
[102] exp: -0.027078531015451
[103] exp: -0.02646297656175392
[104] exp: -0.02738285481457356
[105] exp: -0.02799840926827064
[106] exp: -0.02861396372196772
[107] exp: -0.0292295181756648
[108] exp: -0.02861396372196772
[109] exp: -0.0292295181756648
[110] exp: -0.0292295181756648
[111] exp: -0.028844127085841716
[112] exp: -0.029459681539538797
[113] exp: -0.030075235993235878
[114] exp: -0.03069079044693296
[115] exp: -0.03130634490063004
[116] exp: -0.03192189935432713
[117] exp: -0.03253745380802421
[118] exp: -0.033153008261721295
[119] exp: -0.033153008261721295
[120] exp: -0.03256731390197995
[121] exp: -0.031697881936676685
[122] exp: -0.0326262076677459
[123] exp: -0.0326262076677459
[124] exp: -0.03324176212144298
[125] exp: -0.03329003790769807
[126] exp: -0.03329003790769807
[127] exp: -0.033178062110633705
[128] exp: -0.033178062110633705
[129] exp: -0.03379361656433079
[130] exp: -0.03469337345856942
[131] exp: -0.0353089279122665
[132] exp: -0.035274712648132055
[133] exp: -0.03457582550242758
[134] exp: -0.03519137995612467
[135] exp: -0.03519137995612467
[136] exp: -0.03548433926214502
[137] exp: -0.0360998937158421
[138] exp: -0.036715448169539186
[139] exp: -0.03764651733823437
[140] exp: -0.03799390757259266
[141] exp: -0.03735273794352331
[142] exp: -0.037968292397220396
[143] exp: -0.03858384685091748
[144] exp: -0.039199401304614565
[145] exp: -0.03981495575831165
[146] exp: -0.04043051021200873
[147] exp: -0.04104606466570582
[148] exp: -0.040898738600438696
[149] exp: -0.04151429305413578
[150] exp: -0.042389071112415314
[151] exp: -0.0430046255661124
[152] exp: -0.04362018001980948
[153] exp: -0.044235734473506566
[154] exp: -0.04485128892720365
[155] exp: -0.045466843380900734
[156] exp: -0.04632420363925691
[157] exp: -0.046939758092954
[158] exp: -0.046939758092954
[159] exp: -0.046939758092954
[160] exp: -0.04755531254665108
[161] exp: -0.047945530209456085
[162] exp: -0.04856108466315317
[163] exp: -0.04917663911685025
[164] exp: -0.04979219357054734
[165] exp: -0.05040774802424442
[166] exp: -0.05096822096932116
[167] exp: -0.05096822096932116
[168] exp: -0.05096822096932116
[169] exp: -0.05096822096932116
[170] exp: -0.05070501056763049
[171] exp: -0.05028426867636907
[172] exp: -0.04966871422267199
[173] exp: -0.05028426867636907
[174] exp: -0.049786289569978874
[175] exp: -0.05040184402367596
[176] exp: -0.05018326709830215
[177] exp: -0.05079882155199923
[178] exp: -0.05079882155199923
[179] exp: -0.051414376005696316
[180] exp: -0.0520299304593934
[181] exp: -0.0520299304593934
[182] exp: -0.05244870743096235
[183] exp: -0.05332093018234459
[184] exp: -0.05255129095555087
[185] exp: -0.053406234553578354
[186] exp: -0.053406234553578354
[187] exp: -0.053406234553578354
[188] exp: -0.053406234553578354
[189] exp: -0.05402178900727544
[190] exp: -0.05402178900727544
[191] exp: -0.05463734346097252
[192] exp: -0.05463734346097252
[193] exp: -0.055252897914669606
[194] exp: -0.05586845236836669
[195] exp: -0.05586845236836669
[196] exp: -0.05586845236836669
[197] exp: -0.05586845236836669
[198] exp: -0.056484006822063774
[199] exp: -0.05709956127576086
[200] exp: -0.05771511572945794
[201] exp: -0.057257331940338795
[202] exp: -0.05787288639403588
[203] exp: -0.05848844084773296
[204] exp: -0.05848844084773296
[205] exp: -0.05848844084773296
[206] exp: -0.05910399530143005
[207] exp: -0.05971954975512713
[208] exp: -0.060335104208824215
[209] exp: -0.0609506586625213
[210] exp: -0.061566213116218384
[211] exp: -0.06218176756991547
[212] exp: -0.061566213116218384
[213] exp: -0.061566213116218384
[214] exp: -0.061566213116218384
[215] exp: -0.06218176756991547
[216] exp: -0.06279732202361255
[217] exp: -0.06341287647730963
[218] exp: -0.06341287647730963
[219] exp: -0.06320242027713613
[220] exp: -0.06381797473083321
[221] exp: -0.06381797473083321
[222] exp: -0.06381797473083321
[223] exp: -0.0644335291845303
[224] exp: -0.0644335291845303
[225] exp: -0.0644335291845303
[226] exp: -0.06504908363822738
[227] exp: -0.06566045904518757
[228] exp: -0.06629484601530813
[229] exp: -0.06691040046900522
[230] exp: -0.0675259549227023
[231] exp: -0.0675259549227023
[232] exp: -0.0675259549227023
[233] exp: -0.06814150937639939
[234] exp: -0.0675259549227023
[235] exp: -0.0675259549227023
[236] exp: -0.06658499178537876
[237] exp: -0.06720054623907584
[238] exp: -0.06781610069277293
[239] exp: -0.06843165514647001
[240] exp: -0.06763770681196025
[241] exp: -0.06763770681196025
[242] exp: -0.06825326126565734
[243] exp: -0.06763770681196025
[244] exp: -0.06853694568164893
[245] exp: -0.06915250013534602
[246] exp: -0.0697680545890431
[247] exp: -0.0697680545890431
[248] exp: -0.07038360904274019
[249] exp: -0.07099916349643727
[250] exp: -0.07161471795013435
[251] exp: -0.07161471795013435
[252] exp: -0.07223027240383144
[253] exp: -0.07284582685752852
[254] exp: -0.07346138131122561
[255] exp: -0.07284582685752852
[256] exp: -0.07366915832301378
[257] exp: -0.07428471277671087
[258] exp: -0.07428471277671087
[259] exp: -0.07490026723040795
[260] exp: -0.07551582168410503
[261] exp: -0.07645485198073491
[262] exp: -0.077013721174219
[263] exp: -0.07639816672052191
[264] exp: -0.07565678898420304
[265] exp: -0.07565678898420304
[266] exp: -0.07565678898420304
[267] exp: -0.07626129807519072
[268] exp: -0.0768768525288878
[269] exp: -0.0768768525288878
[270] exp: -0.0768768525288878
[271] exp: -0.07749240698258489
[272] exp: -0.07831312700969287
[273] exp: -0.07831312700969287
[274] exp: -0.07892868146338995
[275] exp: -0.07954423591708704
[276] exp: -0.07954423591708704
[277] exp: -0.08015979037078412
[278] exp: -0.08059094536032106
[279] exp: -0.08059094536032106
[280] exp: -0.08120649981401815
[281] exp: -0.08157819624890524
[282] exp: -0.08219375070260232
[283] exp: -0.0828093051562994
[284] exp: -0.0828093051562994
[285] exp: -0.08342485960999649
[286] exp: -0.08404041406369357
[287] exp: -0.08404041406369357
[288] exp: -0.08465596851739066
[289] exp: -0.08527152297108774
[290] exp: -0.08527152297108774
[291] exp: -0.08527152297108774
[292] exp: -0.08527152297108774
[293] exp: -0.08588707742478482
[294] exp: -0.0867567128540497
[295] exp: -0.0867567128540497
[296] exp: -0.08769186862224454
[297] exp: -0.08830742307594162
[298] exp: -0.08762675616137959
[299] exp: -0.08824231061507667
[300] exp: -0.08885786506877376
[301] exp: -0.08947341952247084
[302] exp: -0.09008897397616793
[303] exp: -0.08992489440956956
[304] exp: -0.09054044886326665
[305] exp: -0.09115600331696373
[306] exp: -0.09115600331696373
[307] exp: -0.09177155777066082
[308] exp: -0.0923871122243579
[309] exp: -0.09300266667805498
[310] exp: -0.09300266667805498
[311] exp: -0.09361822113175207
[312] exp: -0.09362259542014313
[313] exp: -0.0927733626660045
[314] exp: -0.09338891711970158
[315] exp: -0.09400447157339867
[316] exp: -0.09400447157339867
[317] exp: -0.09400447157339867
[318] exp: -0.09462002602709575
[319] exp: -0.09523558048079284
[320] exp: -0.09466317228765633
[321] exp: -0.09527872674135342
[322] exp: -0.0958942811950505
[323] exp: -0.0958942811950505
[324] exp: -0.09650983564874759
[325] exp: -0.09712539010244467
[326] exp: -0.09712539010244467
[327] exp: -0.09774094455614175
[328] exp: -0.09835649900983884
[329] exp: -0.09835649900983884
[330] exp: -0.09897205346353592
[331] exp: -0.09985418221730988
[332] exp: -0.10046973667100696
[333] exp: -0.09995536553146189
[334] exp: -0.10028784636961895
[335] exp: -0.10090340082331603
[336] exp: -0.10090340082331603
[337] exp: -0.10090340082331603
[338] exp: -0.10151895527701311
[339] exp: -0.10151895527701311
[340] exp: -0.10156128221918276
[341] exp: -0.10217683667287984
[342] exp: -0.10217683667287984
[343] exp: -0.10279239112657693
[344] exp: -0.10340794558027401
[345] exp: -0.10340794558027401
[346] exp: -0.10340794558027401
[347] exp: -0.1040235000339711
[348] exp: -0.1040235000339711
[349] exp: -0.1040235000339711
[350] exp: -0.10372845672845833
[351] exp: -0.10372845672845833
[352] exp: -0.10449360452292311
[353] exp: -0.10449360452292311
[354] exp: -0.10365886584907227
[355] exp: -0.10427442030276936
[356] exp: -0.10350990238566947
[357] exp: -0.10412545683936655
[358] exp: -0.10474101129306364
[359] exp: -0.10412545683936655
[360] exp: -0.10474101129306364
[361] exp: -0.10535656574676072
[362] exp: -0.1059721202004578
[363] exp: -0.1059721202004578
[364] exp: -0.10658767465415489
[365] exp: -0.10720322910785197
[366] exp: -0.10781878356154906
[367] exp: -0.10720322910785197
[368] exp: -0.10781878356154906
[369] exp: -0.10843433801524614
[370] exp: -0.10937599813535294
[371] exp: -0.1103166763434265
[372] exp: -0.11038340107086211
[373] exp: -0.11038340107086211
[374] exp: -0.1109989555245592
[375] exp: -0.11161450997825628
[376] exp: -0.11223006443195337
[377] exp: -0.11220202348282973
[378] exp: -0.11281757793652682
[379] exp: -0.11220202348282973
[380] exp: -0.11281757793652682
[381] exp: -0.11281757793652682
[382] exp: -0.1134331323902239
[383] exp: -0.11404868684392098
[384] exp: -0.11466424129761807
[385] exp: -0.11382570392058199
[386] exp: -0.11444125837427907
[387] exp: -0.11520844342214584
[388] exp: -0.11520844342214584
[389] exp: -0.11599975787962051
[390] exp: -0.1166153123333176
[391] exp: -0.1166153123333176
[392] exp: -0.11754910433681005
[393] exp: -0.11754910433681005
[394] exp: -0.11816465879050714
[395] exp: -0.11729727862856759
[396] exp: -0.11791283308226468
[397] exp: -0.11852838753596176
[398] exp: -0.11851878407068736
[399] exp: -0.11851878407068736
[400] exp: -0.11913433852438444
[401] exp: -0.12004965856631672
[402] exp: -0.11942412855813303
[403] exp: -0.11942412855813303
[404] exp: -0.12003968301183011
[405] exp: -0.12089213649740388
[406] exp: -0.12150769095110096
[407] exp: -0.12150769095110096
[408] exp: -0.12089213649740388
[409] exp: -0.12150769095110096
[410] exp: -0.12212324540479805
[411] exp: -0.12273879985849513
[412] exp: -0.12335435431219222
[413] exp: -0.12273879985849513
[414] exp: -0.12273879985849513
[415] exp: -0.12335435431219222
[416] exp: -0.1239699087658893
[417] exp: -0.12458546321958638
[418] exp: -0.12550893711564767
[419] exp: -0.12612449156934474
[420] exp: -0.1267400460230418
[421] exp: -0.12735560047673888
[422] exp: -0.12801527002970922
[423] exp: -0.1286308244834063
[424] exp: -0.12801527002970922
[425] exp: -0.12801527002970922
[426] exp: -0.12866152456840213
[427] exp: -0.1292770790220992
[428] exp: -0.1283833689492389
[429] exp: -0.12922282598498694
[430] exp: -0.12922282598498694
[431] exp: -0.12922282598498694
[432] exp: -0.129838380438684
[433] exp: -0.12915689756143836
[434] exp: -0.12977245201513543
[435] exp: -0.1303880064688325
[436] exp: -0.13100356092252957
[437] exp: -0.13161911537622664
[438] exp: -0.13161911537622664
[439] exp: -0.1322346698299237
[440] exp: -0.1322346698299237
[441] exp: -0.1322346698299237
[442] exp: -0.13285022428362078
[443] exp: -0.13240052676667394
[444] exp: -0.133016081220371
[445] exp: -0.13240052676667394
[446] exp: -0.133016081220371
[447] exp: -0.13363163567406808
[448] exp: -0.13424719012776515
[449] exp: -0.13424719012776515
[450] exp: -0.13486274458146222
[451] exp: -0.1354782990351593
[452] exp: -0.1355789395032875
[453] exp: -0.1355789395032875
[454] exp: -0.13619449395698457
[455] exp: -0.13681004841068165
[456] exp: -0.13681004841068165
[457] exp: -0.13742560286437872
[458] exp: -0.13804115731807579
[459] exp: -0.13865671177177286
[460] exp: -0.13804115731807579
[461] exp: -0.13804115731807579
[462] exp: -0.13804115731807579
[463] exp: -0.13836275404663967
[464] exp: -0.13897830850033674
[465] exp: -0.13836275404663967
[466] exp: -0.13906106231693482
[467] exp: -0.13906106231693482
[468] exp: -0.13999196215156873
[469] exp: -0.13937640769787166
[470] exp: -0.13999196215156873
[471] exp: -0.1406075166052658
[472] exp: -0.1406075166052658
[473] exp: -0.1406075166052658
[474] exp: -0.1406075166052658
[475] exp: -0.14122307105896287
[476] exp: -0.14183862551265994
[477] exp: -0.14183862551265994
[478] exp: -0.142454179966357
[479] exp: -0.142454179966357
[480] exp: -0.14306973442005408
[481] exp: -0.14368528887375115
[482] exp: -0.14430084332744822
[483] exp: -0.14430084332744822
[484] exp: -0.1451826007693763
[485] exp: -0.14579815522307338
[486] exp: -0.14579815522307338
[487] exp: -0.14641370967677045
[488] exp: -0.14702926413046752
[489] exp: -0.1476448185841646
[490] exp: -0.1476448185841646
[491] exp: -0.14828957592651873
[492] exp: -0.1489051303802158
[493] exp: -0.1489051303802158
[494] exp: -0.14952068483391287
[495] exp: -0.15013623928760994
[496] exp: -0.150751793741307
[497] exp: -0.15136734819500408
[498] exp: -0.15136734819500408
[499] exp: -0.15198290264870115
[500] exp: -0.15259845710239822
[501] exp: -0.15342576279459505
[502] exp: -0.1527157862429297
[503] exp: -0.15333134069662677
[504] exp: -0.15400963967833595
[505] exp: -0.15462519413203302
[506] exp: -0.1552407485857301
[507] exp: -0.15609221841700868
[508] exp: -0.15609221841700868
[509] exp: -0.15670777287070575
[510] exp: -0.15756360783863435
[511] exp: -0.15817916229233142
[512] exp: -0.1587947167460285
[513] exp: -0.15941027119972556
[514] exp: -0.1603515042602213
[515] exp: -0.16096705871391837
[516] exp: -0.16096705871391837
[517] exp: -0.16096705871391837
[518] exp: -0.16096705871391837
[519] exp: -0.16096705871391837
[520] exp: -0.16158261316761544
[521] exp: -0.1621981676213125
[522] exp: -0.16281372207500958
[523] exp: -0.16373090678339808
[524] exp: -0.16283280712951734
[525] exp: -0.1634483615832144
[526] exp: -0.16283280712951734
[527] exp: -0.1634483615832144
[528] exp: -0.16406391603691148
[529] exp: -0.16467947049060855
[530] exp: -0.1644017050894879
[531] exp: -0.1644017050894879
[532] exp: -0.1644017050894879
[533] exp: -0.16377946085970288
[534] exp: -0.16439501531339995
[535] exp: -0.16377946085970288
[536] exp: -0.16439501531339995
[537] exp: -0.16439501531339995
[538] exp: -0.16501056976709702
[539] exp: -0.1656261242207941
[540] exp: -0.16624167867449116
[541] exp: -0.16685723312818823
[542] exp: -0.1674727875818853
[543] exp: -0.1674727875818853
[544] exp: -0.16808834203558237
[545] exp: -0.16870389648927944
[546] exp: -0.1693194509429765
[547] exp: -0.16993500539667358
[548] exp: -0.16993500539667358
[549] exp: -0.17055055985037065
[550] exp: -0.17116611430406772
[551] exp: -0.17144413665008618
[552] exp: -0.17205969110378325
[553] exp: -0.17267524555748032
[554] exp: -0.1732908000111774
[555] exp: -0.17340554640099856
[556] exp: -0.17402110085469563
[557] exp: -0.17364917907930433
[558] exp: -0.17364917907930433
[559] exp: -0.17270769519848728
[560] exp: -0.17332324965218435
[561] exp: -0.17393880410588142
[562] exp: -0.1745543585595785
[563] exp: -0.1745543585595785
[564] exp: -0.17516991301327556
[565] exp: -0.17516991301327556
[566] exp: -0.17578546746697263
[567] exp: -0.1764010219206697
[568] exp: -0.17701657637436677
[569] exp: -0.17767756727117226
[570] exp: -0.17829312172486933
[571] exp: -0.1789086761785664
[572] exp: -0.17952423063226347
[573] exp: -0.18013978508596054
[574] exp: -0.1804705539298588
[575] exp: -0.17973981678169074
[576] exp: -0.17973981678169074
[577] exp: -0.1803553712353878
[578] exp: -0.18097092568908488
[579] exp: -0.1803553712353878
[580] exp: -0.1803553712353878
[581] exp: -0.1803553712353878
[582] exp: -0.18097092568908488
[583] exp: -0.18097092568908488
[584] exp: -0.18158648014278195
[585] exp: -0.18158648014278195
[586] exp: -0.18097092568908488
[587] exp: -0.18158648014278195
[588] exp: -0.18097092568908488
[589] exp: -0.18097092568908488
[590] exp: -0.18158648014278195
[591] exp: -0.18220203459647902
[592] exp: -0.1828175890501761
[593] exp: -0.1828175890501761
[594] exp: -0.1820724559922206
[595] exp: -0.1820724559922206
[596] exp: -0.18268801044591768
[597] exp: -0.18330356489961475
[598] exp: -0.18391911935331182
[599] exp: -0.18391911935331182
[600] exp: -0.18330356489961475
[601] exp: -0.18391911935331182
[602] exp: -0.1845346738070089
[603] exp: -0.18515022826070596
[604] exp: -0.18515022826070596
[605] exp: -0.18515022826070596
[606] exp: -0.18515022826070596
[607] exp: -0.18606380694628738
[608] exp: -0.18655856768528278
[609] exp: -0.18655856768528278
[610] exp: -0.18743839411350321
[611] exp: -0.18819919063543192
[612] exp: -0.188814745089129
[613] exp: -0.18943029954282606
[614] exp: -0.18943029954282606
[615] exp: -0.19004585399652313
[616] exp: -0.19004585399652313
[617] exp: -0.19097182613698546
[618] exp: -0.19073563167538965
[619] exp: -0.19135118612908672
[620] exp: -0.19150988368625607
[621] exp: -0.19212543813995314
[622] exp: -0.19249690955466886
[623] exp: -0.19212397847611082
[624] exp: -0.1927395329298079
[625] exp: -0.1927395329298079
[626] exp: -0.19335508738350496
[627] exp: -0.19335508738350496
[628] exp: -0.19358784467544485
[629] exp: -0.19420339912914192
[630] exp: -0.19420339912914192
[631] exp: -0.194818953582839
[632] exp: -0.19543450803653606
[633] exp: -0.19543450803653606
[634] exp: -0.19605006249023313
[635] exp: -0.19605006249023313
[636] exp: -0.19605006249023313
[637] exp: -0.19659884679466627
[638] exp: -0.19721440124836334
[639] exp: -0.1978299557020604
[640] exp: -0.19844551015575748
[641] exp: -0.19906106460945455
[642] exp: -0.19967661906315162
[643] exp: -0.2002921735168487
[644] exp: -0.20090772797054576
[645] exp: -0.20152328242424283
[646] exp: -0.2021388368779399
[647] exp: -0.2021388368779399
[648] exp: -0.2021388368779399
[649] exp: -0.2021388368779399
[650] exp: -0.2021388368779399
[651] exp: -0.2021388368779399
[652] exp: -0.2021388368779399
[653] exp: -0.20275439133163697
[654] exp: -0.20336994578533404
[655] exp: -0.20398550023903111
[656] exp: -0.20398550023903111
[657] exp: -0.20460105469272818
[658] exp: -0.20521660914642526
[659] exp: -0.20608586916399166
[660] exp: -0.20670142361768873
[661] exp: -0.2073169780713858
[662] exp: -0.20670142361768873
[663] exp: -0.2074788621088741
[664] exp: -0.20809441656257116
[665] exp: -0.20870997101626823
[666] exp: -0.2093255254699653
[667] exp: -0.2093255254699653
[668] exp: -0.2093255254699653
[669] exp: -0.20994107992366237
[670] exp: -0.21055663437735944
[671] exp: -0.2111721888310565
[672] exp: -0.21196939145049798
[673] exp: -0.21258494590419505
[674] exp: -0.21320050035789212
[675] exp: -0.21383806905960145
[676] exp: -0.21445362351329852
[677] exp: -0.21383806905960145
[678] exp: -0.21290012410261003
[679] exp: -0.2135156785563071
[680] exp: -0.2135156785563071
[681] exp: -0.21413123301000417
[682] exp: -0.2135156785563071
[683] exp: -0.21413123301000417
[684] exp: -0.21413123301000417
[685] exp: -0.21474678746370124
[686] exp: -0.21474678746370124
[687] exp: -0.21474678746370124
[688] exp: -0.2153623419173983
[689] exp: -0.21597789637109538
[690] exp: -0.2150429143742402
[691] exp: -0.21565846882793727
[692] exp: -0.21551390800633224
[693] exp: -0.21551390800633224
[694] exp: -0.21551390800633224
[695] exp: -0.21551390800633224
[696] exp: -0.21612946246002931
[697] exp: -0.21551390800633224
[698] exp: -0.21612946246002931
[699] exp: -0.21612946246002931
[700] exp: -0.21674501691372638
[701] exp: -0.21674501691372638
[702] exp: -0.21763149675266333
[703] exp: -0.21763149675266333
[704] exp: -0.21844270576049665
[705] exp: -0.21782715130679958
[706] exp: -0.21844270576049665
[707] exp: -0.21905826021419372
[708] exp: -0.21905826021419372
[709] exp: -0.2196738146678908
[710] exp: -0.22028936912158786
[711] exp: -0.22090492357528493
[712] exp: -0.22090492357528493
[713] exp: -0.22182850664546006
[714] exp: -0.2214704732413619
[715] exp: -0.22208602769505897
[716] exp: -0.22270158214875604
[717] exp: -0.22331713660245311
[718] exp: -0.22393269105615018
[719] exp: -0.22454824550984726
[720] exp: -0.22516379996354433
[721] exp: -0.22471567931191222
[722] exp: -0.2253312337656093
[723] exp: -0.22594678821930636
[724] exp: -0.22656234267300343
[725] exp: -0.22656234267300343
[726] exp: -0.22563124834934478
[727] exp: -0.22624680280304185
[728] exp: -0.22695221209638056
[729] exp: -0.22756776655007763
[730] exp: -0.2281833210037747
[731] exp: -0.22879887545747177
[732] exp: -0.22791825864325793
[733] exp: -0.22791825864325793
[734] exp: -0.22791825864325793
[735] exp: -0.228533813096955
[736] exp: -0.22914936755065207
[737] exp: -0.22976492200434914
[738] exp: -0.22976492200434914
[739] exp: -0.23006736939645864
[740] exp: -0.23006736939645864
[741] exp: -0.2306829238501557
[742] exp: -0.23129847830385278
[743] exp: -0.23191403275754985
[744] exp: -0.23269331572343316
[745] exp: -0.23229780870770897
[746] exp: -0.23291336316140604
[747] exp: -0.2335289176151031
[748] exp: -0.2335289176151031
[749] exp: -0.23414447206880018
[750] exp: -0.23476002652249725
[751] exp: -0.23537558097619432
[752] exp: -0.2359911354298914
[753] exp: -0.2366816862279394
[754] exp: -0.23729724068163646
[755] exp: -0.2366816862279394
[756] exp: -0.23729724068163646
[757] exp: -0.2366816862279394
[758] exp: -0.23729724068163646
[759] exp: -0.23751966337302066
[760] exp: -0.23813521782671773
[761] exp: -0.23813521782671773
[762] exp: -0.23751966337302066
[763] exp: -0.23748434234037025
[764] exp: -0.23809989679406732
[765] exp: -0.2387154512477644
[766] exp: -0.23933100570146146
[767] exp: -0.23994656015515853
[768] exp: -0.2405621146088556
[769] exp: -0.24117766906255267
[770] exp: -0.24179322351624974
[771] exp: -0.2424087779699468
[772] exp: -0.24256286025472576
[773] exp: -0.24317841470842283
[774] exp: -0.2437939691621199
[775] exp: -0.24388015971144672
[776] exp: -0.2430025795504418
[777] exp: -0.24361813400413887
[778] exp: -0.24423368845783594
[779] exp: -0.2442418955889244
[780] exp: -0.24485745004262147
[781] exp: -0.24547300449631854
[782] exp: -0.2456807075066957
[783] exp: -0.24629626196039278
[784] exp: -0.24713400058278212
[785] exp: -0.24651844612908505
[786] exp: -0.24651844612908505
[787] exp: -0.24713400058278212
[788] exp: -0.2477495550364792
[789] exp: -0.24836510949017626
[790] exp: -0.24836510949017626
[791] exp: -0.24836510949017626
[792] exp: -0.24898066394387333
[793] exp: -0.2495962183975704
[794] exp: -0.2495962183975704
[795] exp: -0.25051117355932184
[796] exp: -0.25112672801301894
[797] exp: -0.25112672801301894
[798] exp: -0.25174228246671604
[799] exp: -0.25235783692041314
[800] exp: -0.25297339137411023
[801] exp: -0.25358894582780733
[802] exp: -0.25420450028150443
[803] exp: -0.25482005473520153
[804] exp: -0.2554356091888986
[805] exp: -0.2554356091888986
[806] exp: -0.2560511636425957
[807] exp: -0.2566667180962928
[808] exp: -0.2572822725499899
[809] exp: -0.257897827003687
[810] exp: -0.2585133814573841
[811] exp: -0.2591289359110812
[812] exp: -0.2597444903647783
[813] exp: -0.2603600448184754
[814] exp: -0.2609755992721725
[815] exp: -0.2615911537258696
[816] exp: -0.26194188750198566
[817] exp: -0.26194188750198566
[818] exp: -0.26255744195568276
[819] exp: -0.26317299640937986
[820] exp: -0.26255744195568276
[821] exp: -0.26255744195568276
[822] exp: -0.26255744195568276
[823] exp: -0.26317299640937986
[824] exp: -0.26378855086307695
[825] exp: -0.26440410531677405
[826] exp: -0.26440410531677405
[827] exp: -0.26501965977047115
[828] exp: -0.2658114564052007
[829] exp: -0.2658114564052007
[830] exp: -0.26654768954301
[831] exp: -0.26654768954301
[832] exp: -0.2657089792429236
[833] exp: -0.2663245336966207
[834] exp: -0.2663245336966207
[835] exp: -0.2669400881503178
[836] exp: -0.2669400881503178
[837] exp: -0.2675556426040149
[838] exp: -0.268171197057712
[839] exp: -0.26878675151140907
[840] exp: -0.268171197057712
[841] exp: -0.268171197057712
[842] exp: -0.268171197057712
[843] exp: -0.26878675151140907
[844] exp: -0.26940230596510617
[845] exp: -0.27001786041880327
[846] exp: -0.26940230596510617
[847] exp: -0.2696050634297452
[848] exp: -0.26894336376939737
[849] exp: -0.26955891822309447
[850] exp: -0.26955891822309447
[851] exp: -0.2700922968507422
[852] exp: -0.2700922968507422
[853] exp: -0.2700922968507422
[854] exp: -0.2707078513044393
[855] exp: -0.2713234057581364
[856] exp: -0.2713234057581364
[857] exp: -0.2713234057581364
[858] exp: -0.2719389602118335
[859] exp: -0.2723312986160437
[860] exp: -0.2729468530697408
[861] exp: -0.2735624075234379
[862] exp: -0.274177961977135
[863] exp: -0.274177961977135
[864] exp: -0.274177961977135
[865] exp: -0.2747935164308321
[866] exp: -0.2754090708845292
[867] exp: -0.2760246253382263
[868] exp: -0.2766401797919234
[869] exp: -0.2772557342456205
[870] exp: -0.2778712886993176
[871] exp: -0.2784868431530147
[872] exp: -0.2791023976067118
[873] exp: -0.2784868431530147
[874] exp: -0.2784868431530147
[875] exp: -0.2791023976067118
[876] exp: -0.2782980485067252
[877] exp: -0.27735874637169267
[878] exp: -0.27797430082538976
[879] exp: -0.27858985527908686
[880] exp: -0.27920540973278396
[881] exp: -0.27982096418648106
[882] exp: -0.27982096418648106
[883] exp: -0.27982096418648106
[884] exp: -0.28043651864017816
[885] exp: -0.28043651864017816
[886] exp: -0.28105207309387525
[887] exp: -0.28105207309387525
[888] exp: -0.28166762754757235
[889] exp: -0.28228318200126945
[890] exp: -0.28289873645496655
[891] exp: -0.2825211540269534
[892] exp: -0.2831367084806505
[893] exp: -0.2831367084806505
[894] exp: -0.2837522629343476
[895] exp: -0.2843678173880447
[896] exp: -0.2837522629343476
[897] exp: -0.2843347405672988
[898] exp: -0.2843347405672988
[899] exp: -0.2845791570436383
[900] exp: -0.2845791570436383
[901] exp: -0.2839636025899412
[902] exp: -0.2845791570436383
[903] exp: -0.2839636025899412
[904] exp: -0.2833480481362441
[905] exp: -0.2839636025899412
[906] exp: -0.2845791570436383
[907] exp: -0.2851947114973354
[908] exp: -0.28543574532739174
[909] exp: -0.28605129978108884
[910] exp: -0.28605129978108884
[911] exp: -0.28529738883815065
[912] exp: -0.28529738883815065
[913] exp: -0.2859254146897928
[914] exp: -0.2868482797677258
[915] exp: -0.2874638342214229
[916] exp: -0.28807938867512
[917] exp: -0.28807938867512
[918] exp: -0.2886949431288171
[919] exp: -0.28960982734083196
[920] exp: -0.29022538179452906
[921] exp: -0.29084093624822616
[922] exp: -0.29145649070192325
[923] exp: -0.29207204515562035
[924] exp: -0.29268759960931745
[925] exp: -0.29268759960931745
[926] exp: -0.29330315406301455
[927] exp: -0.29391870851671165
[928] exp: -0.29391870851671165
[929] exp: -0.29453426297040874
[930] exp: -0.29545259668810003
[931] exp: -0.29545259668810003
[932] exp: -0.29483704223440294
[933] exp: -0.29545259668810003
[934] exp: -0.29606815114179713
[935] exp: -0.29606815114179713
[936] exp: -0.29668370559549423
[937] exp: -0.2972992600491913
[938] exp: -0.2979148145028884
[939] exp: -0.2979148145028884
[940] exp: -0.2979148145028884
[941] exp: -0.2979148145028884
[942] exp: -0.2985303689565855
[943] exp: -0.2991459234102826
[944] exp: -0.2991459234102826
[945] exp: -0.2991459234102826
[946] exp: -0.2997614778639797
[947] exp: -0.2997614778639797
[948] exp: -0.2997614778639797
[949] exp: -0.3003770323176768
[950] exp: -0.3013107890652462
[951] exp: -0.3013107890652462
[952] exp: -0.3004164880131131
[953] exp: -0.299800933559416
[954] exp: -0.30024286071658635
[955] exp: -0.29962730626288925
[956] exp: -0.30024286071658635
[957] exp: -0.3002399965717314
[958] exp: -0.3008555510254285
[959] exp: -0.3014711054791256
[960] exp: -0.3008555510254285
[961] exp: -0.3014711054791256
[962] exp: -0.3014711054791256
[963] exp: -0.3014711054791256
[964] exp: -0.3020866599328227
[965] exp: -0.3020866599328227
[966] exp: -0.3027022143865198
[967] exp: -0.3033177688402169
[968] exp: -0.303933323293914
[969] exp: -0.3045488777476111
[970] exp: -0.3051644322013082
[971] exp: -0.3051644322013082
[972] exp: -0.3051644322013082
[973] exp: -0.30424607952190996
[974] exp: -0.3036183112721912
[975] exp: -0.3042338657258883
[976] exp: -0.3048494201795854
[977] exp: -0.3054649746332825
[978] exp: -0.30453069264814825
[979] exp: -0.30514624710184535
[980] exp: -0.30433271597198613
[981] exp: -0.30512125684171765
[982] exp: -0.30512125684171765
[983] exp: -0.30450570238802055
[984] exp: -0.30476178907673923
[985] exp: -0.30537734353043633
[986] exp: -0.3059928979841334
[987] exp: -0.3059928979841334
[988] exp: -0.3066084524378305
[989] exp: -0.3072240068915276
[990] exp: -0.3072240068915276
[991] exp: -0.3072240068915276
[992] exp: -0.3076771002911508
[993] exp: -0.3076771002911508
[994] exp: -0.3082926547448479
[995] exp: -0.3082926547448479
[996] exp: -0.308908209198545
[997] exp: -0.308908209198545
[998] exp: -0.3095237636522421
[999] exp: -0.3101393181059392

CPTP mapping

CPTP is created by giving a list of Claus operators that satisfy the completeness.

[ ]:
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)

from qulacs import QuantumState
from qulacs.gate import H,merge
state = QuantumState(2)
for _ in range(10):
    state.set_zero_state()
    merge(H(0),H(1)).update_quantum_state(state)
    gate.update_quantum_state(state)
    print(state.get_vector())
[0.+0.j 0.+0.j 1.+0.j 0.+0.j]
[0.+0.j 1.+0.j 0.+0.j 0.+0.j]
[0.+0.j 0.+0.j 1.+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 1.+0.j]
[1.+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 1.+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 1.+0.j 0.+0.j]

The AmplitudeDampingNoise gate is defined as CPTP-map.

[ ]:
from qulacs.gate import AmplitudeDampingNoise
target = 0
damping_rate = 0.1
AmplitudeDampingNoise(target, damping_rate)
#K_0: [[1,0],[0,sqrt(1-p)]], K_1: [[0,sqrt(p)], [0,0]]
 *** gate info ***
 * gate name : Generic gate
 * target    :
 * control   :
 * Pauli     : no
 * Clifford  : no
 * Gaussian  : no
 * Parametric: no
 * Diagonal  : yes

Instrument

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

[ ]:
from qulacs import QuantumState
from qulacs.gate import merge,Instrument, P0,P1

gate00 = merge(P0(0),P0(1))
gate01 = merge(P1(0),P0(1))
gate10 = merge(P0(0),P1(1))
gate11 = merge(P1(0),P1(1))

gate_list = [gate00, gate01, gate10, gate11]
classical_pos = 0
gate = Instrument(gate_list, classical_pos)

from qulacs import QuantumState
from qulacs.gate import H,merge
state = QuantumState(2)
for index in range(10):
    state.set_zero_state()
    merge(H(0),H(1)).update_quantum_state(state)
    gate.update_quantum_state(state)
    result = state.get_classical_value(classical_pos)
    print(index, format(result,"b").zfill(2), state.get_vector())
0 01 [0.+0.j 1.+0.j 0.+0.j 0.+0.j]
1 01 [0.+0.j 1.+0.j 0.+0.j 0.+0.j]
2 01 [0.+0.j 1.+0.j 0.+0.j 0.+0.j]
3 10 [0.+0.j 0.+0.j 1.+0.j 0.+0.j]
4 00 [1.+0.j 0.+0.j 0.+0.j 0.+0.j]
5 01 [0.+0.j 1.+0.j 0.+0.j 0.+0.j]
6 11 [0.+0.j 0.+0.j 0.+0.j 1.+0.j]
7 11 [0.+0.j 0.+0.j 0.+0.j 1.+0.j]
8 00 [1.+0.j 0.+0.j 0.+0.j 0.+0.j]
9 10 [0.+0.j 0.+0.j 1.+0.j 0.+0.j]

Note that Measurement gate is defined as Instrument.

[ ]:
from qulacs.gate import Measurement
target = 0
classical_pos = 0
gate = Measurement(target, classical_pos)

Adaptive operation

This is a gate that uses a function that returns a Boolean value with variable-length list of classical register values as an argument, and determines whether to perform an operation according to the conditions obtained from the classical register. Conditions can be written as python functions. A python function must be a function that takes a list of type int as an argument and returns a bool type value.

[ ]:
from qulacs.gate import Adaptive, X

def func(list):
    print("func is called! content is ",list)
    return list[0]==1


gate = Adaptive(X(0), func)

state = QuantumState(1)
state.set_zero_state()

# func returns False, so X does not operate
state.set_classical_value(0,0)
gate.update_quantum_state(state)
print(state.get_vector())

# func returns True, so X operates
state.set_classical_value(0,1)
gate.update_quantum_state(state)
print(state.get_vector())
func is called! content is  [0]
[1.+0.j 0.+0.j]
func is called! content is  [1]
[0.+0.j 1.+0.j]

Operator

Pauli operator

Observables are represented as linear combinations of Pauli operators with real coefficients. PauliOperator class is a class that expresses each term with the coefficient added to the \(n\)-qubit Pauli operator. Unlike gates, operator cannot update quantum states.

Create Pauli operator and obtain state

[ ]:
from qulacs import PauliOperator
coef = 0.1
s = "X 0 Y 1 Z 3"
pauli = PauliOperator(s, coef)

# Added pauli symbol later
pauli.add_single_Pauli(3, 2)

# Get the subscript of each pauli symbol
index_list = pauli.get_index_list()

# Get pauli symbols (I,X,Y,Z -> 0,1,2,3)
pauli_id_list = pauli.get_pauli_id_list()

# Get pauli coefficient
coef = pauli.get_coef()

# Create a copy of pauli operator
another_pauli = pauli.copy()

s = ["I","X","Y","Z"]
pauli_str = [s[i] for i in pauli_id_list]
terms_str = [item[0]+str(item[1]) for item in zip(pauli_str,index_list)]
full_str = str(coef) + " " + " ".join(terms_str)
print(full_str)
(0.1+0j) X0 Y1 Z3 Y3

Expected value of Pauli operator

You can evaluate the expected value and transition moment of the Pauli operator for one state.

[ ]:
from qulacs import PauliOperator, QuantumState

n = 5
coef = 2.0
Pauli_string = "X 0 X 1 Y 2 Z 4"
pauli = PauliOperator(Pauli_string,coef)

# Calculate expectation value <a|H|a>
state = QuantumState(n)
state.set_Haar_random_state()
value = pauli.get_expectation_value(state)
print("expect", value)

# Calculate transition moment <a|H|b>
# The first arguments comes to the bra side
bra = QuantumState(n)
bra.set_Haar_random_state()
value = pauli.get_transition_amplitude(bra, state)
print("transition", value)
expect (0.6132028798856664+0j)
transition (0.5279713648817308-0.6040063466314933j)

General linear operators

The linear operator GeneralQuantumOperator is represented by a linear combination of the Pauli operators with a complex coefficient. PauliOperator with coefficient can be added as a term with add_operator.

[ ]:
from qulacs import GeneralQuantumOperator, PauliOperator, QuantumState

n = 5
operator = GeneralQuantumOperator(n)

# Pauli operator can be added
coef = 2.0+0.5j
Pauli_string = "X 0 X 1 Y 2 Z 4"
pauli = PauliOperator(Pauli_string,coef)
operator.add_operator(pauli)
# Pauli operator can also be added directly from coefficients and strings
operator.add_operator(0.5j, "Y 1 Z 4")

# Get number of terms
term_count = operator.get_term_count()

# Get number of quantum bit
qubit_count = operator.get_qubit_count()

# Get specific terms as PauliOperator
index = 1
pauli = operator.get_term(index)


# Expected value calculation <a|H|a>
## Generally not self-adjoint, can return complex value
state = QuantumState(n)
state.set_Haar_random_state()
value = operator.get_expectation_value(state)
print("expect", value)

# Transition moment calculation <a|H|b>
# The first arguments comes to the bra side
bra = QuantumState(n)
bra.set_Haar_random_state()
value = operator.get_transition_amplitude(bra, state)
print("transition", value)
expect (-0.12123883067235244+0.004874745096201068j)
transition (-0.19076380005803-0.03521147847936834j)

Store linear operators

GeneralQuantumOperator can be converted to/from JSON string.

[ ]:
from qulacs import GeneralQuantumOperator
from qulacs import quantum_operator

o_operator = GeneralQuantumOperator(3)
o_operator.add_operator(1.0, "X 2 Y 0")
o_operator.add_operator(0.5j, "Y 1 Z 0")
operator_json = o_operator.to_json()
print("original:", o_operator)

r_operator = quantum_operator.from_json(operator_json)
print("restored:", r_operator)
original: (1,0) X 2 Y 0 + (0,0.5) Y 1 Z 0
restored: (1,0) X 2 Y 0 + (0,0.5) Y 1 Z 0

Generation of observables using OpenFermion

OpenFermion is a tool that gives Hamiltonian to be solved by chemical calculation in the form of Pauli operator. The output of this tool can be read in the form of a file or a string and can be used in the form of an operator.

[ ]:
from qulacs.quantum_operator import create_quantum_operator_from_openfermion_file
from qulacs.quantum_operator import create_quantum_operator_from_openfermion_text

open_fermion_text = """
(-0.8126100000000005+0j) [] +
(0.04532175+0j) [X0 Z1 X2] +
(0.04532175+0j) [X0 Z1 X2 Z3] +
(0.04532175+0j) [Y0 Z1 Y2] +
(0.04532175+0j) [Y0 Z1 Y2 Z3] +
(0.17120100000000002+0j) [Z0] +
(0.17120100000000002+0j) [Z0 Z1] +
(0.165868+0j) [Z0 Z1 Z2] +
(0.165868+0j) [Z0 Z1 Z2 Z3] +
(0.12054625+0j) [Z0 Z2] +
(0.12054625+0j) [Z0 Z2 Z3] +
(0.16862325+0j) [Z1] +
(-0.22279649999999998+0j) [Z1 Z2 Z3] +
(0.17434925+0j) [Z1 Z3] +
(-0.22279649999999998+0j) [Z2]
"""

operator = create_quantum_operator_from_openfermion_text(open_fermion_text)
print(operator.get_term_count())
print(operator.get_qubit_count())
# In the case of create_quantum_operator_from_openfermion_file, specify the path of the file where the above is written in the argument.
15
4

Hermite operator / observable

The Hermite operator is represented by a real linear combination of the Pauli operators. Equivalent to the GeneralQuatnumOperator class, except that eigenvalues and expected values are guaranteed to be real.

The function to read and process from an external file is possible by replacing the quantum_operator with the observable, and using functions such as create_observable_from_openfermion_file create_observable_from_openfermion_text create_split_observable.

Separates operators into diagonal and off-diagonal terms

When reading an operator from a file, you can separate it into diagonal and off-diagonal components with the create_split_observable function.

[ ]:
from qulacs.observable import create_split_observable, create_observable_from_openfermion_file

# H2.txt must be placed in openfermon format beforehand.
operator = create_observable_from_openfermion_file("./H2.txt")
diag, nondiag = create_split_observable("./H2.txt")
print(operator.get_term_count(), diag.get_term_count(), nondiag.get_term_count())
print(operator.get_qubit_count(), diag.get_qubit_count(), nondiag.get_qubit_count())

Compute ground state

You can compute a ground state of the operator by power method, Arnoldi method or Lanczos method. After computation, the corresponding ground state is assigned to state in the argument.

Power method
[ ]:
from qulacs import Observable, QuantumState
from qulacs.observable import create_observable_from_openfermion_file

n = 4
operator = create_observable_from_openfermion_file("./H2.txt")
state = QuantumState(n)
state.set_Haar_random_state()
value = operator.solve_ground_state_eigenvalue_by_power_method(state, 50)
print(value)
Arnoldi method
[ ]:
from qulacs import Observable, QuantumState
from qulacs.observable import create_observable_from_openfermion_file

n = 4
operator = create_observable_from_openfermion_file("./H2.txt")
state = QuantumState(n)
state.set_Haar_random_state()
value = operator.solve_ground_state_eigenvalue_by_arnoldi_method(state, 50)
print(value)
Lanczos method
[ ]:
from qulacs import Observable, QuantumState
from qulacs.observable import create_observable_from_openfermion_file

n = 4
operator = create_observable_from_openfermion_file("./H2.txt")
state = QuantumState(n)
state.set_Haar_random_state()
value = operator.solve_ground_state_eigenvalue_by_lanczos_method(state, 50)
print(value)

Apply to a quantum state

An operator can be applied to a quantum state.

[ ]:
from qulacs import Observable, QuantumState
from qulacs.observable import create_observable_from_openfermion_file

n = 4
operator = create_observable_from_openfermion_file("./H2.txt")
state = QuantumState(n)
state.set_Haar_random_state()
result = QuantumState(n)
work_state = QuantumState(n)
operator.apply_to_state(work_state, state, result)
print(result)

Quantum Circuits

Structure of a quantum circuit

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

[ ]:
from qulacs import QuantumState, QuantumCircuit
from qulacs.gate import Z
n = 3
state = QuantumState(n)
state.set_zero_state()

circuit = QuantumCircuit(n)

# Add hadamard gate to quantum circuit
for i in range(n):
    circuit.add_H_gate(i)

# Create gate, which can also be added
for i in range(n):
    circuit.add_gate(Z(i))

# Operate quantum circuit to state
circuit.update_quantum_state(state)

print(state.get_vector())
[ 0.35355339+0.j -0.35355339-0.j -0.35355339-0.j  0.35355339+0.j
 -0.35355339-0.j  0.35355339+0.j  0.35355339+0.j -0.35355339-0.j]

Calculation and optimization of depth of quantum circuits

By combining quantum gates into a single quantum gate, the number of quantum gates and the time required for numerical calculations can be reduced. (Of course, if the number of target qubits increases, or if a quantum gate with a dedicated function is synthesized into a quantum gate without a dedicated function, the total calculation time may not decrease. It depends.)

The code below uses the optimize function to repeat the greedy synthesis of the quantum gate of the quantum circuit until the target qubit becomes three.

[ ]:
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 the depth (depth=10)
print(circuit.calculate_depth())

# Optimization
opt = QuantumCircuitOptimizer()
# The maximum quantum gate size allowed to be created
max_block_size = 1
opt.optimize(circuit, max_block_size)

# Calculate the depth (depth=1へ)
print(circuit.calculate_depth())
10
1

Debugging quantum circuits

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

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


Store QuantumCircuit

QuantumCircuit can be converted to/from JSON string. If it has unsupported gates, the conversion fails with an error.

[ ]:
from qulacs import QuantumCircuit
from qulacs import circuit

o_circuit = QuantumCircuit(3)
o_circuit.add_H_gate(0)
o_circuit.add_CNOT_gate(0, 1)
o_circuit.add_RZ_gate(2, 0.7)
circuit_json = o_circuit.to_json()
print(o_circuit)

r_circuit = circuit.from_json(circuit_json)
print(r_circuit)
*** Quantum Circuit Info ***
# of qubit: 3
# of step : 2
# of gate : 3
# of 1 qubit gate: 2
# of 2 qubit gate: 1
Clifford  : no
Gaussian  : no


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


Quantum circuits can be stored to files in pickle format.

[2]:
from qulacs import QuantumCircuit
import pickle

circuit = QuantumCircuit(3)
circuit.add_H_gate(0)
circuit.add_CNOT_gate(0, 1)
circuit.add_RZ_gate(2, 0.7)

# store
with open('circuit.pickle', 'wb') as f:
    pickle.dump(circuit, f)

# load
with open('circuit.pickle', 'rb') as f:
    circuit = pickle.load(f)

Parametric Quantum Circuits

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

Application examples of parametric quantum circuits

Quantum gates with one rotation angle (RX, RY, RZ, multi_qubit_pauli_rotation) can be added to quantum circuits as parametric quantum gates. For a quantum gate added as a parametric gate, the number of parametric gates can be extracted after the quantum circuit is configured, and the rotation angle can be changed later.

[ ]:
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.0292587,0.0842756)
   (-0.0199201,0.242442)
    (0.181468,-0.104986)
  (0.0995482,-0.0233467)
     (0.16811,0.0345288)
   (0.0562338,-0.225101)
    (-0.131558,0.100174)
    (0.144257,-0.120167)
  (-0.173724,-0.0457571)
  (-0.142581,-0.0917222)
     (0.210296,0.176269)
   (0.0014233,0.0511538)
   (-0.00440611,0.26444)
   (0.0175043,0.0854573)
    (-0.197366,0.133705)
     (0.191306,0.316103)
   (-0.132828,-0.128943)
   (-0.00599114,0.03745)
  (-0.203638,-0.0249769)
   (-0.157577,0.0372862)
   (-0.132844,-0.105019)
  (-0.136556,-0.0918098)
   (-0.0584003,0.043396)
    (-0.02902,0.0680596)
   (0.0952735,-0.052631)
   (0.0738972,-0.127688)
   (0.165229,-0.0591462)
(0.0608202,-0.000749349)
  (-0.0873545,0.0887971)
    (-0.12346,0.0469901)
 (0.0344535,-0.00645451)
   (-0.216478,0.0550732)

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

Compute a gradient of parametric quantum circuit

You can compute a gradient of parametric circuit by GradCalculator or ParametricQuantumCircuit.backprop().

  • In GradCalculator, compute a gradient by calculating expected value with CausalConeSimulator.

  • In ParametricQuantumCircuit.backprop(), compute a gradient by back propagation.

[ ]:
from qulacs import ParametricQuantumCircuit, GradCalculator, Observable

n = 2
observable = Observable(n)
observable.add_operator(1.0, "X 0")
circuit = ParametricQuantumCircuit(n)

theta = [2.2, 1.4, 0.8]
circuit.add_parametric_RX_gate(0, theta[0])
circuit.add_parametric_RY_gate(0, theta[1])
circuit.add_parametric_RZ_gate(0, theta[2])

# GradCalculatorの場合
gcalc = GradCalculator()
print(gcalc.calculate_grad(circuit, observable))
# 第三引数に回転角を指定することも可能
print(gcalc.calculate_grad(circuit, observable, theta))
# Backpropを使って求めた場合
print(circuit.backprop(observable))
[(0.1329240611221506+0j), (0.06968868323709176+0j), (0.14726262077628172+0j)]
[(0.1329240611221506+0j), (0.06968868323709176+0j), (0.14726262077628172+0j)]
[0.1329240611221506, 0.06968868323709171, 0.14726262077628177]

Store parametric quantum circuits

Parametric quantum circuits can be converted to/from JSON string. If it has unsupported gates, the conversion fails with an error.

[3]:
from qulacs import ParametricQuantumCircuit
from qulacs import circuit

o_circuit = ParametricQuantumCircuit(3)
o_circuit.add_H_gate(0)
o_circuit.add_parametric_RX_gate(1, 0.3)
o_circuit.add_multi_Pauli_rotation_gate([0, 1, 2], [1, 3, 2], 1.4)
circuit_json = o_circuit.to_json()
print(o_circuit)

r_circuit = circuit.from_json(circuit_json)
print(r_circuit)
*** Quantum Circuit Info ***
# of qubit: 3
# of step : 2
# of gate : 3
# of 1 qubit gate: 2
# of 2 qubit gate: 0
# of 3 qubit gate: 1
Clifford  : no
Gaussian  : no

*** Parameter Info ***
# of parameter: 1

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

*** Parameter Info ***
# of parameter: 1

Parametric quantum circuits can be converted to files in pickle format.

[4]:
from qulacs import ParametricQuantumCircuit
from qulacs import circuit

circuit = ParametricQuantumCircuit(3)
circuit.add_H_gate(0)
circuit.add_parametric_RX_gate(1, 0.3)
circuit.add_multi_Pauli_rotation_gate([0, 1, 2], [1, 3, 2], 1.4)

# store
with open('circuit.pickle', 'wb') as f:
    pickle.dump(circuit, f)

# load
with open('circuit.pickle', 'rb') as f:
    circuit = pickle.load(f)

Simulator

QuantumCircuitSimulator

You can manage a circuit and quantum states together. It has a buffer for quantum state to swap the state to use.

[ ]:
from qulacs import QuantumState, QuantumCircuit, QuantumCircuitSimulator, Observable
n = 3
state = QuantumState(n)

# 回路を作成
circuit = QuantumCircuit(n)
for i in range(n):
   circuit.add_H_gate(i)

# シミュレータクラスを作成
sim = QuantumCircuitSimulator(circuit, state)

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

# ゲート数
print("gate_count: ", sim.get_gate_count())

# 実行
sim.simulate()

# 量子状態を表示
print(state)

# 期待値
observable = Observable(1)
observable.add_operator(1.0, "Z 0")
print("expectation_value: ", sim.get_expectation_value(observable))

# 量子状態を入れ替え
print("swap")
sim.swap_state_and_buffer()
# ランダムな純粋状態へ初期化
sim.initialize_random_state(seed=0)
sim.simulate()
print(state)

# 量子状態をコピー(バッファ->現状態)
sim.copy_state_from_buffer()
sim.simulate()
print(state)

# 量子状態をコピー(現状態->バッファ)
sim.copy_state_to_buffer()
sim.simulate()
print(state)
gate_count:  3
 *** Quantum State ***
 * Qubit Count : 3
 * Dimension   : 8
 * State vector :
(0.353553,0)
(0.353553,0)
(0.353553,0)
(0.353553,0)
(0.353553,0)
(0.353553,0)
(0.353553,0)
(0.353553,0)

expectation_value:  0j
swap
 *** Quantum State ***
 * Qubit Count : 3
 * Dimension   : 8
 * State vector :
  (-0.298916,0.160953)
  (0.015405,-0.237144)
  (-0.215765,0.171064)
(-0.257959,-0.0506326)
 (-0.121612,0.0424348)
(-0.0329899,-0.400262)
  (0.327376,-0.414262)
   (0.345253,0.327824)

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

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

NoiseSimulator

The NoiseSimulator allows you to run your circuit in a simulated noise environment.

To set up noise, you should add a gate to the circuit using add_noise_gate(). The first argument specifies the gate, the second the type of noise to add ("Depolarizing" / "BitFlip" / "Dephasing" / "IndependentXZ" / "AmplitudeDamping"), and the third the probability of noise generation. In the case of a two-qubit gate, you can use "Depolarizing" only.

[ ]:
import random
from qulacs import QuantumState, QuantumCircuit, NoiseSimulator
from qulacs.gate import sqrtX, sqrtY, T, CNOT, CZ

n = 3
depth = 10
one_qubit_noise = ["Depolarizing", "BitFlip", "Dephasing", "IndependentXZ", "AmplitudeDamping"]
circuit = QuantumCircuit(n)

for d in range(depth):
   for i in range(n):
      r = random.randint(0, 4)
      noise_type = random.randint(0, 4)
      if r == 0:
            circuit.add_noise_gate(sqrtX(i), one_qubit_noise[noise_type], 0.01)
      elif r == 1:
            circuit.add_noise_gate(sqrtY(i), one_qubit_noise[noise_type], 0.01)
      elif r == 2:
            circuit.add_noise_gate(T(i), one_qubit_noise[noise_type], 0.01)
      elif r == 3:
            if i + 1 < n:
               circuit.add_noise_gate(CNOT(i, i+1), "Depolarizing", 0.01)
      elif r == 4:
            if i + 1 < n:
               circuit.add_noise_gate(CZ(i, i+1), "Depolarizing", 0.01)

state = QuantumState(n)
state.set_Haar_random_state()
sim = NoiseSimulator(circuit, state)
sim.execute(100)
print(state)
 *** Quantum State ***
 * Qubit Count : 3
 * Dimension   : 8
 * State vector :
  (0.424423,-0.125395)
 (0.0193726,-0.298116)
  (-0.357495,0.227471)
   (0.0119428,0.13876)
 (-0.351555,0.0521885)
    (0.230969,0.21148)
 (0.274671,-0.0305064)
(-0.462659,-0.0337342)

CausalConeSimulator

CausalConeSimulator allows you to extract the gates associated with a given observable by traversing the circuit in reverse. Only the extracted gates can be applied to obtain the expected value of the physical quantity.

This allows you to find the expected value of a circuit of large size qubits, since the shallower the depth of the circuit, the fewer the gates associated with it.

[ ]:
from qulacs import QuantumState, ParametricQuantumCircuit, CausalConeSimulator, Observable
n = 100
observable = Observable(1)
observable.add_operator(1.0, "Z 0")
circuit = ParametricQuantumCircuit(n)
for i in range(n):
   circuit.add_parametric_RX_gate(i, 1.0)
   circuit.add_parametric_RY_gate(i, 1.0)

ccs = CausalConeSimulator(circuit, observable)
print(ccs.get_expectation_value())
(0.2919265817264289+0j)