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]"

Prepare a circuit#

First, prepare a circuit to apply noise.

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

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

In addition to the ones used here, various other NoiseInstruction are available. Please refer to the API documentation for details on each NoiseInstruction.

Sampling simulation with Qulacs#

For sampling, several functions are provided to create a Sampler with a noise model applied.

[3]:
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
[3]:
Counter({7: 487, 1: 483, 4: 1, 5: 11, 3: 12, 6: 5, 0: 1})
[4]:
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
[4]:
Counter({7: 507, 0: 4, 1: 454, 3: 12, 6: 6, 5: 17})

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 sampling simulation 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.

[5]:
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 Estimators, 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 Estimate expectation value of operators 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.

[6]:
from quri_parts.core.operator import pauli_label
from quri_parts.core.state import GeneralCircuitQuantumState
circuit = QuantumCircuit(1)
circuit.add_X_gate(0)
state = GeneralCircuitQuantumState(1, circuit)
pauli = pauli_label("Z0")
empty_model = NoiseModel()
estimator = create_qulacs_density_matrix_estimator(empty_model)
estimate = estimator(pauli, state)
estimate.value
[6]:
(-1+0j)

The result is as expected. Now let’s add a bit flip noise with probability 0.5 to the noise model.

[7]:
bitflip_model = NoiseModel([BitFlipNoise(0.5)])
noised_estimator = create_qulacs_density_matrix_estimator(bitflip_model)
noised_estimate = noised_estimator(pauli, state)
noised_estimate.value
[7]:
0j

We are getting the expected effect.

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.

[8]:
from quri_parts.qulacs.circuit.noise import convert_circuit_with_noise_model
qulacs_circuit = convert_circuit_with_noise_model(circuit, model)