Simulating noisy quantum circuits with Paddle Quantum¶
Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.
Introduction to quantum noises¶
In ideal models, we usually assume that quantum circuits are operating on a closed physical system. However, real quantum devices suffer from incoherent noises introduced by unwanted interactions between the system and the environment. This type of noise can significantly change the performance of quantum computation tasks and hence can hardly be ignored for near-term quantum devices. Consequently, designing robust quantum algorithms under the presence of noise is crucial for utilizing quantum computation in the real world. With the noise module of Paddle Quantum, we can now not only design and simulate quantum algorithms but also examine various noises' influence and further develop error mitigation schemes.
Building noise models in Paddle Quantum¶
Noise model and quantum channel¶
The evolution of a closed quantum system is always unitary. Mathematically, we can describe such a process as implementing a parameterized quantum circuit $U(\vec{\theta})$,
$$ \rho \longrightarrow U(\vec{\theta}) \rho U^\dagger(\vec{\theta}), \tag{1} $$where $\rho$ is the initial quantum state, $\vec{\theta}$ is a vector containing all the parameters. The most intuitive type of noise one can think of is the error that appears in these parameters, $$ \rho \longrightarrow U(\vec{\theta}+\vec{\epsilon}) \rho U^\dagger(\vec{\theta}+\vec{\epsilon}), \tag{2} $$
$\vec{\epsilon}$ can be a white noise sampled from Gaussian distributions. This kind of noise is a specific example of coherent noises. Coherent noise usually occurs due to device calibration errors or quantum control errors. We want to emphasize that one also uses unitary transformation $U(\vec{\epsilon})$ to describe coherent noises. In certain cases, coherent noises can be more damaging than incoherent noises [1].
Most of the time, the real problem lies on the evolution of an open quantum system that is non-unitary. Under this circumstance, we need a more general description beyond the unitary transformation to characterize incoherent noises, the language of quantum channels. To keep the discussion precise, we use operator-sum representation [2] to introduce a quantum channel as
$$ \mathcal{E}(\rho) = \sum_{k=0}^{m-1} E_k \rho E_k^{\dagger}, \tag{3} $$where $\{E_k\}$ are Kraus operators, and they satisfy the completeness condition $\sum_k E_k^\dagger E_k = I$. Mathematically, a quantum channel is completely positive and trace-preserving [2].
Under this representation, we can explicitly observe the results of implementing a quantum channel: Suppose we start with a pure state $\rho = |\psi\rangle\langle \psi|$, then we send it through a noisy quantum channel (e.g., $m = 2$ ). Eventually, we will get a mixed state $\mathcal{E}(\rho) = E_0 \rho E_0^\dagger + E_1 \rho E_1^\dagger$. Let's take the bit flip noise as an example:
$$ \mathcal{E}_{BF}(\rho) = (1 - p) I \rho I+ p X \rho X, \tag{4} $$where $X,I$ are Pauli operators. The corresponding Kraus operators are:
$$ E_0 = \sqrt{1-p} \begin{bmatrix} 1&0 \\ 0&1 \end{bmatrix}, E_1 = \sqrt{p} \begin{bmatrix} 0& 1 \\ 1 &0 \end{bmatrix}. \tag{5} $$The physical meaning of this quantum channel is there exist a probability $p$ that the state $|0\rangle$ will flip into $|1\rangle$, and vice versa. In Paddle Quantum, we can use this quantum channel by Circuit.bit_flip(p, which_qubit)
, where p
is the noise level.
Note: For a quantum channel, the Kraus operator representation is not necessarily unique [3].
Implementation with Paddle Quantum¶
In this section, we will learn how to build a noise model in Paddle Quantum. First, we initialize a qubit to $|0\rangle$.
import paddle_quantum
from paddle_quantum.ansatz import Circuit
# Change to density matrix mode
paddle_quantum.set_backend('density_matrix')
# Define the number of qubits, here we use one single qubit
num_qubits = 1
# Initialize the quantum circuit
cir = Circuit(num_qubits)
# Initialize the qubit to |0><0|
init_state = paddle_quantum.state.zero_state(num_qubits)
# Mesure in the computational basis
cir(init_state).measure(shots = 1024, plot = True)
/home/zl/miniconda3/envs/pq/lib/python3.8/site-packages/openfermion/hamiltonians/hartree_fock.py:11: DeprecationWarning: Please use `OptimizeResult` from the `scipy.optimize` namespace, the `scipy.optimize.optimize` namespace is deprecated. from scipy.optimize.optimize import OptimizeResult /home/zl/miniconda3/envs/pq/lib/python3.8/site-packages/paddle/tensor/creation.py:125: DeprecationWarning: `np.object` is a deprecated alias for the builtin `object`. To silence this warning, use `object` by itself. Doing this will not modify any behavior and is safe. Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations if data.dtype == np.object: /home/zl/miniconda3/envs/pq/lib/python3.8/site-packages/paddle/tensor/creation.py:125: DeprecationWarning: `np.object` is a deprecated alias for the builtin `object`. To silence this warning, use `object` by itself. Doing this will not modify any behavior and is safe. Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations if data.dtype == np.object: /home/zl/miniconda3/envs/pq/lib/python3.8/site-packages/paddle/fluid/framework.py:1104: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here. Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations elif dtype == np.bool:
{'0': 1.0, '1': 0.0}
Then, we add a bit flip channel with $p=0.1$, and measure the qubit after this channel. Note: Noisy module in Paddle Quantum only supports density matrix operation mode.
# Noise level
p = 0.1
# Add the bit flip noisy channel
cir.bit_flip(p, 0)
# Execute the circuit
# Note: Noisy module in Paddle Quantum only supports density matrix operation mode
fin_state = cir(init_state)
# Measure in the computational basis
fin_state.measure(shots = 1024, plot = True)
print('Quantum state after the bit flip quantum channel:\n', fin_state.data.numpy())
Quantum state after the bit flip quantum channel: [[0.8999999+0.j 0. +0.j] [0. +0.j 0.1 +0.j]]
As we can see, the quantum state has been transformed to a mixed state $0.9 | 0 \rangle \langle 0 | + 0.1 | 1 \rangle \langle 1 |$ (with probability $p=0.1$ ) after the bit flip channel.
Common quantum channels¶
Paddle Quantum supports many other common noisy channels.
Phase Flip Channel
Similar to the bit-flip channel, the phase flip channel flips the phase of a qubit with probability $p$,
$$ \mathcal{E}_{PF}(\rho) = (1 - p) \rho + p Z \rho Z. \tag{6} $$Bit-Phase Flip Channel
$$ \mathcal{E}_{BPF}(\rho) = (1-p) \rho + p Y \rho Y. \tag{7} $$Depolarizing Channel
The quantum state will be in the maximally mixed state $I/2$ with probability $p$ or in the original state with probability $1-p$ after the single qubit depolarizing channel. The depolarizing channel can also be understood as applying Pauli noises symmetrically,
$$ \mathcal{E}_{D}(\rho) = (1 - p) \rho + \frac{p}{3} \left( X \rho X+ Y \rho Y + Z \rho Z \right). \tag{8} $$Pauli Channel
The Pauli channel applies Pauli noises asymmetrically,
$$ \mathcal{E}_{Pauli}(\rho) = (1 - p_x - p_y - p_z) \rho + p_x X \rho X + p_y Y \rho Y + p_z Z \rho Z. \tag{9} $$Amplitude Damping Channel
The amplitude damping channel can be used to model the process of energy dissipation,
$$ \mathcal{E}_{AD}(\rho) = E_0 \rho E_0^\dagger + E_1 \rho E_1^\dagger, \tag{10} $$where $\gamma$ is the damping factor,
$$ E_0 = \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1 - \gamma} \end{bmatrix}, E_1 = \begin{bmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{bmatrix}. \tag{11} $$Phase Damping Channel
The phase damping channel describes the loss of quantum information without loss of energy,
$$ \mathcal{E}_{PD}(\rho) = E_0 \rho E_0^\dagger + E_1 \rho E_1^\dagger, \tag{12} $$where $\gamma$ is the damping factor,
$$ E_0 = \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1 - \gamma} \end{bmatrix}, E_1 = \begin{bmatrix} 0 & 0 \\ 0 & \sqrt{\gamma} \end{bmatrix}. \tag{13} $$Generalized Amplitude Damping Channel
The generalized amplitude damping channel describes energy exchange between the system and the environment at finite temperatures. It is a common noise in superconducting quantum computations [4]. Interested readers can find more information here API document.
Note: In Paddle Quantum, we can use these noisy channels through Circuit.phase_flip()
, Circuit.bit_phase_flip()
, Circuit.depolarizing()
, Circuit.pauli_channel()
, Circuit.amplitude_damping()
, Circuit.phase_damping()
, and Circuit.generalized_amplitude_damping()
.
Note: One usually choose the amplitude damping channel and the phase damping channel to model noises since they describe the physical process in real quantum systems (modeling $T_1$ and $T_2$ process).
Customized Channel¶
One can also use Circuit.customized_channel()
in Paddle Quantum to add customized noisy channels. This is accomplished through user-defined Kraus operators. Here, we provide an example to reproduce the bit flip channel using customized_channel function:
import paddle
import numpy as np
# Noise level
p = 0.1
# We use customized Kraus operator to represent the bit flip channel
complex_dtype = paddle_quantum.get_dtype()
a_0 = paddle.to_tensor(np.sqrt(1 - p) * np.array([[1, 0], [0, 1]], dtype=complex_dtype))
a_1 = paddle.to_tensor(np.sqrt(p) * np.array([[0, 1], [1, 0]], dtype=complex_dtype))
Kraus_ops = [a_0, a_1]
# Initialize the circuit
num_qubits = 1
cir = Circuit(num_qubits)
# Add customized channel, input is a list of Kraus operators
cir.kraus_channel(Kraus_ops, 0)
# Execute the circuit
fin_state = cir(init_state)
# Compare the results
cir_1 = Circuit(num_qubits)
cir_1.bit_flip(p, 0)
fin_state_1 = cir_1(init_state)
print('quantum state after the customized channel:\n', fin_state.data.numpy())
print('\n quantum state after the bit flip channel:\n', fin_state_1.data.numpy())
print('\n are the two the same?', bool((fin_state.data - fin_state_1.data).abs().sum() < 1e-6))
quantum state after the customized channel: [[0.90000004+0.j 0. +0.j] [0. +0.j 0.1 +0.j]] quantum state after the bit flip channel: [[0.8999999+0.j 0. +0.j] [0. +0.j 0.1 +0.j]] are the two the same? True
Discussion: Simulating noisy entanglement resources with Paddle Quantum¶
Many important quantum technologies require pre-shared entanglement resources, including quantum teleportation, state transformation, and distributed quantum computing. For instance, we want the allocated entanglement resources are in maximally entangled states under ideal circumstances. But in reality, noise always exists due to interactions between the system and the environment during preparation stage, transmission, and preservation. Here, we use the depolarizing channel to simulate how a white noise could affect Bell states:
import paddle
from paddle import matmul, trace
from paddle_quantum.ansatz import Circuit
from paddle_quantum.state import bell_state
# Noise level
p_trans = 0.1
p_store = 0.01
# Initialize the circuit
num_qubits = 2
cir = Circuit(num_qubits)
# The initial state is Bell state
init_state = bell_state(2)
# Apply the depolarizing channel to each qubit, modeling the noise introduced by transmission
cir.depolarizing(p_trans, 0)
cir.depolarizing(p_trans, 1)
# Execute the circuit
status_mid = cir(init_state)
# Apply the amplitude damping channel to each qubit, modeling the noise introduced by storage
cir.amplitude_damping(p_store, 0)
cir.amplitude_damping(p_store, 1)
# Execute the circuit
status_fin = cir(status_mid)
fidelity_mid = paddle.real(trace(matmul(init_state.data, status_mid.data)))
fidelity_fin = paddle.real(trace(matmul(init_state.data, status_fin.data)))
print("Fidelity between the initial state and the Bell state", 1)
print("after transmission (depolarizing channel), the fidelity between the entangled state and Bell state {:.5f}".format(fidelity_mid.numpy()[0]))
print("after preservation (amplitude damping channel), the fidelity between the entangled state and Bell state {:.5f}".format(fidelity_fin.numpy()[0]))
Fidelity between the initial state and the Bell state 1 after transmission (depolarizing channel), the fidelity between the entangled state and Bell state 0.85750 after preservation (amplitude damping channel), the fidelity between the entangled state and Bell state 0.73556
Note: Interested readers can check tutorials on the LOCCNet module of Paddle Quantum, where we discuss the concept of entanglement distillation.
Application: Simulating noisy VQE with Paddle Quantum¶
Variational Quantum Eigensolver (VQE) [5] is designed to find the ground state energy of a given molecular Hamiltonian using variational quantum circuits. Interested readers can find more details from the previous tutorial VQE.
For illustration purposes, we use VQE to find the ground state energy for the following Hamiltonian:
$$ H = 0.4 \, Z \otimes I + 0.4 \, I \otimes Z + 0.2 \, X \otimes X. \tag{14} $$Then, we add the amplitude damping channel and compare the performance of the noisy circuit and the noiseless circuit on this task:
import numpy as np
import paddle
from paddle_quantum.ansatz import Circuit
from paddle_quantum.qinfo import pauli_str_to_matrix
# Hyperparameters
num_qubits = 2
theta_size = 4
ITR = 100
LR = 0.4
SEED = 999
p = 0.1
# Construct Hamiltonian using Pauli string
H_info = [[0.4, 'z0'], [0.4, 'z1'], [0.2, 'x0,x1']]
# Convert the Pauli string to a matrix
complex_dtype = paddle_quantum.get_dtype()
H_matrix = pauli_str_to_matrix(H_info, num_qubits).numpy().astype(complex_dtype)
class vqe_noisy(paddle.nn.Layer):
def __init__(self):
super(vqe_noisy, self).__init__()
# Initialize circuit
self.cir = Circuit(num_qubits)
# Add parameterized gates
self.cir.ry([0, 1])
self.cir.cnot([0, 1])
self.cir.ry([0, 1])
# Add amplitude damping channel
self.cir.amplitude_damping(p, [0, 1])
# Define loss function and forward function
def forward(self):
# Execute the circuit
state = self.cir(init_state)
# Expectation value of Hamiltonian
loss = loss_func(state)
return loss, self.cir
# Construct a noiseless circuit
class vqe_noise_free(paddle.nn.Layer):
def __init__(self):
super(vqe_noise_free, self).__init__()
self.cir = Circuit(num_qubits)
self.cir.ry([0, 1])
self.cir.cnot([0, 1])
self.cir.ry([0, 1])
def forward(self):
state = self.cir(init_state)
loss = loss_func(state)
return loss, self.cir
# Train noisy VQE circuit
print('========== Training Noisy VQE ==========')
loss_list = []
parameter_list = []
# Define the dimension of parameters
vqe = vqe_noisy()
# Generally, we use Adam optimizer to get a better convergence, you can change to SVG or RMS prop.
opt = paddle.optimizer.Adam(learning_rate = LR, parameters = vqe.parameters())
# Define initial state
init_state = paddle_quantum.state.zero_state(num_qubits)
# Define loss function
loss_func = paddle_quantum.loss.ExpecVal(paddle_quantum.Hamiltonian(H_info))
# Optimization iteration
for itr in range(ITR):
# Forward, to calculate loss function
loss, cir = vqe()
# Backpropagate to minimize the loss function
loss.backward()
opt.minimize(loss)
opt.clear_grad()
# Record the learning curve
loss_list.append(loss.numpy()[0])
parameter_list.append(vqe.parameters()[0].numpy())
if itr % 10 == 0:
print('iter:', itr, ' loss: %.4f' % loss.numpy())
# Train the noiseless VQE in the same way
print('========== Training Noise Free VQE ==========')
loss_list_no_noise = []
parameter_list_no_noise = []
vqe_no_noise = vqe_noise_free()
opt_no_noise = paddle.optimizer.Adam(learning_rate = LR, parameters = vqe_no_noise.parameters())
for itr in range(ITR):
loss, cir = vqe_no_noise()
loss.backward()
opt_no_noise.minimize(loss)
opt_no_noise.clear_grad()
loss_list_no_noise.append(loss.numpy()[0])
parameter_list_no_noise.append(vqe_no_noise.parameters()[0].numpy())
if itr % 10 == 0:
print('iter:', itr, ' loss: %.4f' % loss.numpy())
print('\nGround state energy from noisy circuit: ', loss_list[-1], "Ha")
print('Ground state energy from noiseless circuit: ', loss_list_no_noise[-1], "Ha")
print('Actual ground state energy: ', np.linalg.eigh(H_matrix)[0][0], "Ha")
========== Training Noisy VQE ========== iter: 0 loss: -0.2233 iter: 10 loss: -0.6082 iter: 20 loss: -0.6373 iter: 30 loss: -0.6554 iter: 40 loss: -0.6618 iter: 50 loss: -0.6614 iter: 60 loss: -0.6621 iter: 70 loss: -0.6620 iter: 80 loss: -0.6621 iter: 90 loss: -0.6621 ========== Training Noise Free VQE ========== iter: 0 loss: -0.1235 iter: 10 loss: -0.7679 iter: 20 loss: -0.7933 iter: 30 loss: -0.8177 iter: 40 loss: -0.8219 iter: 50 loss: -0.8239 iter: 60 loss: -0.8244 iter: 70 loss: -0.8245 iter: 80 loss: -0.8245 iter: 90 loss: -0.8246 Ground state energy from noisy circuit: -0.6621509 Ha Ground state energy from noiseless circuit: -0.82461375 Ha Actual ground state energy: -0.82462114 Ha
As we can see, noisy VQE behaves much worse than the noiseless version as expected and couldn't satisfy chemical accuracy $\varepsilon = 0.0016$ Ha.
Conclusion¶
Noise is an unavoidable feature of quantum devices in the NISQ era. Therefore, designing robust quantum algorithms under the presence of noise and further developing error mitigation schemes are two important research directions. With the noise module in Paddle Quantum, we hope to provide a platform simulating real physical systems and help developing near-term quantum computation applications. Standing together with the research community, the noise module will help us explore what we can achieve with noisy devices, design more robust quantum algorithms, and eventually leads to trustworthy quantum solutions in areas including AI and quantum chemistry.
References¶
[1] Iverson, J. K., & Preskill, J. Coherence in logical quantum channels. New Journal of Physics, 22(7), 073066 (2020).
[2] Nielsen, M. A. & Chuang, I. L. Quantum computation and quantum information. Cambridge university press (2010).
[3] Preskill, J. Quantum Information Lecture Notes. Chapter 3 (2018).
[4] Chirolli, L., & Burkard, G. Decoherence in solid-state qubits. Advances in Physics, 57(3), 225-285 (2008).
[5] Peruzzo, A. et al. A variational eigenvalue solver on a photonic quantum processor. Nat. Commun. 5, 4213 (2014).