Noisy simulation
Quantum circuits running on real machines are affected by a variety of stochastic noises. In QURI Parts, noise models can be defined to represent these noises and reproduce them on simulators. (Qulacs is used in this tutorial.)
Prerequisite
QURI Parts modules used in this tutorial: quri-parts-circuit
, quri-parts-core
, and quri-parts-qulacs
. You can install them as follows:
!pip install "quri-parts[qulacs]"
Overview
Prepare a circuit
First, prepare a circuit to apply noise.
from quri_parts.circuit import QuantumCircuit
circuit = QuantumCircuit(3)
circuit.add_H_gate(2)
circuit.add_X_gate(0)
circuit.add_CNOT_gate(2, 1)
circuit.add_Z_gate(2)
Create a noise model
Next, create a noise model. Create several NoiseInstruction
s that represent noises and their application conditions, and add them to NoiseModel
.
(This is a noise model to illustrate API functionality and is not a realistic example. Noise models should be adjusted to match the characteristics of the actual equipment of interest.)
import quri_parts.circuit.gate_names as gate_names
from quri_parts.circuit.noise import (
BitFlipNoise,
BitPhaseFlipNoise,
DepolarizingNoise,
DepthIntervalNoise,
MeasurementNoise,
NoiseModel,
PauliNoise,
PhaseFlipNoise,
)
noises = [
# Single qubit noise
BitFlipNoise(
error_prob=0.004,
qubit_indices=[0, 2], # Qubit 0 or 2
target_gates=[gate_names.H, gate_names.CNOT], # H or CNOT gates
),
DepolarizingNoise(
error_prob=0.003,
qubit_indices=[], # All qubits
target_gates=[gate_names.X, gate_names.CNOT] # X or CNOT gates
),
PhaseFlipNoise(
error_prob=0.002,
qubit_indices=[1, 0], # Qubit 0 or 1
target_gates=[] # All kind of gates
),
BitPhaseFlipNoise(
error_prob=0.001,
qubit_indices=[], # All qubits
target_gates=[], # All kind of gates
),
# Multi qubit noise
PauliNoise(
pauli_list=[[1, 2], [2, 3]],
prob_list=[0.001, 0.002],
qubit_indices=[1, 2], # 2 qubit gates applying to qubits (1, 2) or (2, 1)
target_gates=[gate_names.CNOT] # CNOT gates
),
# Circuit noise
DepthIntervalNoise([PhaseFlipNoise(0.001)], depth_interval=5),
MeasurementNoise([BitFlipNoise(0.004), DepolarizingNoise(0.003)]),
]
model = NoiseModel(noises)
For single qubit noises, you can specify the target qubit indices and the target gate names. If the argument is omitted or an empty list is given, all qubits or gates are treated as targets.
The method to specify application condition is similar for multi qubit noises, but the target qubit indices requires the complete set of qubits (in any order) for the target gate. The noise is applied to the qubits sorted as specified in the target gate. For example, if the target gate is specified as CNOT(5, 3), then the noise applied to the qubits (5, 3) not (3, 5).
Interface
This section is devoted to introduce the NoiseInstruction
interface and various NoiseInstruction
s provided by QURI Parts. The NoiseInstruction
is a type alias that represents 2 types of noise instructions:
CircuitNoiseInstruction
: Represents the noise applied depending on the structure of the circuit.GateNoiseInstruction
: Represents the noise that is applied when individual gates act on qubits.
CircuitNoiseInstruction
A CircuitNoiseInstruction
represents the noise applied depending on the structure of the circuit. Here are the list of CircuitNoiseInstruction
QURI Parts provide.
Name | Description | Input |
---|---|---|
GateIntervalNoise | For each qubit, given single qubit noises are applied each time a certain number of gates are applied. | - single_qubit_noises - gate_interval |
DepthIntervalNoise | Apply the given single qubit GateNoiseInstruction to all qubits every time a certain depth is advanced. | - single_qubit_noises - depth_interval |
MeasurementNoise | Noise which occurs during the measurement of a qubit | - single_qubit_noises - qubit_indices |
GateNoiseInstruction
GateNoiseInstruction
represents the noise that is applied when individual gates act on qubits. It is a dataclass with the following attributes:
- name: Name of the noise.
- qubit_count: Number of qubits this error affects.
- params: Parameters such as error probability, etc. (Depends on the concrete error type.)
- qubit_indices: Indices of qubits this error affects.
- target_gates: Gates affected by this error.
QURI Parts provide several implemetations of GateNoiseInstruction
s and some special sub-types of them, which we list in later subsections:
Basic GateNoiseInstruction
Here, we first start with the basic implementations of GateNoiseInstruction
. They are constructed with 3 parameters:
- error_prob: The probability the noise causes error.
- qubit_indices: The qubit on which the noise can occur. If nothing is passed it, it indicates that the noise can happen on all the qubits in the circuit.
- target_gates: The gates that can generate the noise. If nothing is passed it, it indicates that all the gates are subjected to the noise.
Here we list out these errors:
Name | Description | Input |
---|---|---|
BitFlipNoise | Single qubit bit flip noise | - error_prob - qubit_indices - target_gates |
PhaseFlipNoise | Single qubit phase flip noise | - error_prob - qubit_indices - target_gates |
BitPhaseFlipNoise | Single qubit bit and phase flip noise. | - error_prob - qubit_indices - target_gates |
DepolarizingNoise | Single qubit depolarizing noise. | - error_prob - qubit_indices - target_gates |
PauliNoise
PauliNoise
is a subtype of GateNoiseInstruction
that involves Pauli gates acting on multiple qubits. We summarize the PauliNoise
that QURI Parts provide. Note that in the input column, we omit the arguments qubit_indices
and target_gates
as all the noise instructions require them. We also include the formula of the density matrix after the noise is applied to the state.
Name | Description | Input | Density matrix after noise |
---|---|---|---|
PauliNoise | Multi qubit Pauli noise. | - pauli_list - prob_list - eq_tolerance | |
GeneralDepolarizingNoise | Multi qubit general depolarizing noise | - error_prob - qubit_count |
Here note that in the GeneralDepolarizingNoise
, the operator is given by products of Pauli matrices:
And note that the summation in the first term does not include .
Kraus Noises
We also provide another sub-type of GateNoiseInstruction
: the AbstractKrausNoise
. It is a GateNoiseInstruction
with a kraus_operators
property which returns the list of explicit Kraus operator matrices that defines the noise. Here, we list out all of them. Note that in the "input" column, we omit the qubit_indices
and target_gates
arguments as all the GateNoiseInstruction
require them.
Name | Description | Input |
---|---|---|
KrausNoise | Multi qubit Kraus noise | - kraus_list: list of Kraus operator matrices |
ResetNoise | Single qubit reset noise. | - p0: Probability of resetting to - p1: Probability of resetting to |
PhaseDampingNoise | Single qubit phase damping noise. | - phase_damping_rate |
AmplitudeDampingNoise | Single qubit amplitude damping noise. | - amplitude_damping_rate - excited_state_population |
PhaseAmplitudeDampingNoise | Single qubit phase and amplitude damping noise. | - phase_damping_rate - amplitude_damping_rate - excited_state_population |
ThermalRelaxationNoise | Sigle qubit thermal relaxation noise. | - t1 - t2 - gate_time - excited_state_population |
For example, let's look at the reset noise:
from quri_parts.circuit.noise import ResetNoise
reset_noise = ResetNoise(p0=0.04, p1=0.16)
reset_noise.kraus_operators
#output
(array([[0.89442719, 0. ],
[0. , 0.89442719]]),
array([[0.2, 0. ],
[0. , 0. ]]),
array([[0. , 0.2],
[0. , 0. ]]),
array([[0. , 0. ],
[0.4, 0. ]]),
array([[0. , 0. ],
[0. , 0.4]]))
NoiseModel
Finally, we introduce the NoiseModel
object. It is the object in QURI Parts that represents a noise model containing multiple GateNoiseInstruction
s and CircuitNoiseInstrction
s. It is also the object that is passed around to create noisy circuit in QURI Parts. A NoiseModel
is created by a sequence of NoiseInstruction
s. Please note that the noise do not commute with each other, so that the order in the sequence of NoiseInstruction
matters.
It also provides some convenient methods for us to modify our model. For example, we can use add_noise
and extend
to add noise instructions to the model:
model = NoiseModel([
BitFlipNoise(
error_prob=0.004,
qubit_indices=[0, 2],
target_gates=[gate_names.H, gate_names.CNOT],
),
DepolarizingNoise(
error_prob=0.003,
target_gates=[gate_names.X, gate_names.CNOT]
),
])
# Add a single instruction
model.add_noise(PhaseFlipNoise(error_prob=0.002, qubit_indices=[1, 0]))
model.add_noise(BitPhaseFlipNoise(error_prob=0.001,))
# Add a sequence of instructions
model.extend([
PauliNoise(
pauli_list=[[1, 2], [2, 3]],
prob_list=[0.001, 0.002],
qubit_indices=[1, 2],
target_gates=[gate_names.CNOT]
),
DepthIntervalNoise([PhaseFlipNoise(0.001)], depth_interval=5),
MeasurementNoise([BitFlipNoise(0.004), DepolarizingNoise(0.003)])
])
We also provide .noises_for_circuit
to inspect the CircuitNoiseInstrcution
s in the model. .noises_for_gate
identify the gate noise applied to a gate on a particular
from quri_parts.circuit import X
print("Circuit noises:", model.noises_for_circuit())
print("")
print("Gate noise on X(0):")
for noise in model.noises_for_gate(X(0)):
print(noise)
#output
Circuit noises: [DepthIntervalNoise(name=DepthIntervalNoise), MeasurementNoise(name=MeasurementNoise)]
Gate noise on X(0):
((0,), DepolarizingNoise(name='DepolarizingNoise', qubit_count=1, params=(0.003,), qubit_indices=(), target_gates=('X', 'CNOT')))
((0,), PhaseFlipNoise(name='PhaseFlipNoise', qubit_count=1, params=(0.002,), qubit_indices=(1, 0), target_gates=()))
((0,), BitPhaseFlipNoise(name='BitPhaseFlipNoise', qubit_count=1, params=(0.001,), qubit_indices=(), target_gates=()))
Simulating noisy system with Qulacs
Convert circuit with noise model
If you need a Qulacs circuit with the noise model applied directly, the following circuit conversion function is provided. For purposes such as sampling or estimation, it is usually not necessary for the user to perform this conversion. However, if you choose to work with Qulacs directly, please check out the Qulacs tutorial for noisy simulation.
from quri_parts.qulacs.circuit.noise import convert_circuit_with_noise_model
qulacs_circuit = convert_circuit_with_noise_model(circuit, model)
Sampling simulation with Qulacs
For sampling, several functions are provided to create a Sampler with a noise model applied.
from quri_parts.qulacs.sampler import create_qulacs_density_matrix_sampler
density_matrix_sampler = create_qulacs_density_matrix_sampler(model)
counts = density_matrix_sampler(circuit, shots=1000)
counts
#output
Counter({1: 486, 7: 490, 0: 9, 5: 8, 3: 5, 4: 1, 6: 1})
from quri_parts.qulacs.sampler import create_qulacs_stochastic_state_vector_sampler
stochastic_state_vector_sampler = create_qulacs_stochastic_state_vector_sampler(model)
counts = stochastic_state_vector_sampler(circuit, shots=1000)
counts
#output
Counter({1: 478, 7: 489, 5: 19, 3: 9, 6: 1, 0: 4})
Density matrix sampler (created by create_qulacs_density_matrix_sampler()
) uses the density matrix for calculating measurement probability for sampling, while stochastic state vector sampler (created by create_qulacs_stochastic_state_vector_sampler()
) performs sampling by repeating stochastic state vector simulation for a specified shot count. The computation time varies with the type of circuit, but in general, stochastic state vector sampler is advantageous when the number of shots is less than about 10^3.
The usage of Sampler
with noise model is the same as that of other Sampler
, except that a NoiseModel
should be given on creation. As with regular Sampler
, there are versions that support concurrent execution. Please refer to the API documentation and the sampler tutorial for details on how to use Sampler
.
Estimation of operator expectation value with Qulacs
Also for estimation, you can create an Estimator
with noise model applied.
from quri_parts.qulacs.estimator import create_qulacs_density_matrix_estimator
density_matrix_estimator = create_qulacs_density_matrix_estimator(model)
Simillar to the Sampler
case, the usage of Estimator
with noise model is the same as that of other Estimator
s, except that a NoiseModel
should be given on creation. As with regular Estimator
, there are versions that support parametric circuit and/or concurrent execution. Please refer to the API documentation and Estimator tutorial for details on how to use Estimator
.
Finally, let's take a simple example to see the noise model is working. Create a circuit with only one X gate applied and calculate the expectation value with an empty noise model.
from quri_parts.core.operator import pauli_label
from quri_parts.core.state import quantum_state
circuit = QuantumCircuit(1)
circuit.add_X_gate(0)
state = quantum_state(1, circuit=circuit)
pauli = pauli_label("Z0")
empty_model = NoiseModel()
estimator = create_qulacs_density_matrix_estimator(empty_model)
estimate = estimator(pauli, state)
estimate.value
#output
(-1+0j)
The result is as expected. Now let's add a bit flip noise with probability 0.5 to the noise model.
bitflip_model = NoiseModel([BitFlipNoise(0.5)])
noised_estimator = create_qulacs_density_matrix_estimator(bitflip_model)
noised_estimate = noised_estimator(pauli, state)
noised_estimate.value
#output
0j
We are getting the expected effect.