This page was generated from the notebook examples/notebooks/6_Noise_Simulation.ipynb.

Simulating noise on MPQP

Quantum computers hold immense potential, but a major hurdle is the quantum noise. Noise refers to anything that disrupts a quantum computer’s calculations. Being able to simulate and study the behavior of quantum circuits under the effect of noise is crucial in the NISQ era.

In this Notebook, we describe how to run noisy simulations by defining noise models, adding them to a QCircuit, and finally run the circuit. In this notebook, we will focus, with a practical approach, on the simulation of Depolarizing noise model.

[7]:
from mpqp import QCircuit, Language
from mpqp.gates import *
from mpqp.noise import Depolarizing
from mpqp.measures import BasisMeasure
from mpqp.execution import *

Instantiating a Depolarizing noise model

We define an abstract class NoiseModel representing noisy channels acting on the qubits of the circuit, either after each gate application, or as an interaction with the environement (what is called idle noise). Each predefined noise model should extend this class, which has common attributes targets (indicating the indices of the qubits affected by this noise model) and the optional gates (indicating specific gates after which the noise will be applied)

If one wants to apply a depolarizing noise on the circuit, he can use the class Depolarizing, which is extending the class NoiseModel. We then can specify two additional argument: a first mandatory argument indicating the probability, or the error rate of the channel, and the dimension parameter allowing us to target specific gates within a quantum circuit.

This flexibility allows researchers to simulate noise in various scenarios.

First, we can define the Depolarizing model by providing a probability and the list of target qubits. One can target all qubits of a circuit or just select specific ones. By default, the parameter dimensionis equal to 1, and having a multi-qubit target will imply a tensor product of one qubit channels based on the instantiated depolarizing noise.

[2]:
Depolarizing(0.5, [0, 1, 2])
[2]:
Depolarizing(0.5, [0, 1, 2], 1)

One can also precise a higher dimension when the number of target qubits allows it, and this can imply the application of several nose models. In fact, if the number of targets is higher than the dimension, we will consider all possible combinations of the target qubits that matches the depolarizing dimension. However, if the number of target qubits is equal to the dimension, a unique noise model will be applied.

[3]:
Depolarizing(0.1, [0, 1, 2], dimension=2)
[3]:
Depolarizing(0.1, [0, 1, 2], 2)

If we want to attach a noise model to specific gates, we can specify them in a list and input them in the parameter gates. Then, the noise will be applied only after gates that appear in the list in parameter, and for which target qubits (in the sense of application of the unitary operation, so it includes control qubits) were precised in the second parameter targets. When precising the gates one has to give the class of the gate, extending NativeGate, and not an instance of the gate.

[4]:
Depolarizing(0.23, [2, 3], gates=[H, Rx, U])
[4]:
Depolarizing(0.23, [2, 3], 1, [H, Rx, U])

Note that in the previous example, only one-qubit gates were specified for the noise. If the dimension is higher, one has to input gates for which the size matches exactly the dimension.

[5]:
d = Depolarizing(0.45, [1, 3, 4], dimension=2, gates=[CNOT, CZ])
[10]:
d.to_other_language(Language.BRAKET)
[10]:
TwoQubitDepolarizing('probability': 0.45, 'qubit_count': 2)
[9]:
print(d.to_other_language(Language.MY_QLM))
Depolarizing channel, p = 0.45:
[[0.74161985 0.         0.         0.        ]
 [0.         0.74161985 0.         0.        ]
 [0.         0.         0.74161985 0.        ]
 [0.         0.         0.         0.74161985]]
[[0.        +0.j 0.17320508+0.j 0.        +0.j 0.        +0.j]
 [0.17320508+0.j 0.        +0.j 0.        +0.j 0.        +0.j]
 [0.        +0.j 0.        +0.j 0.        +0.j 0.17320508+0.j]
 [0.        +0.j 0.        +0.j 0.17320508+0.j 0.        +0.j]]
[[0.+0.j         0.-0.17320508j 0.+0.j         0.+0.j        ]
 [0.+0.17320508j 0.+0.j         0.+0.j         0.+0.j        ]
 [0.+0.j         0.+0.j         0.+0.j         0.-0.17320508j]
 [0.+0.j         0.+0.j         0.+0.17320508j 0.+0.j        ]]
[[ 0.17320508+0.j  0.        +0.j  0.        +0.j  0.        +0.j]
 [ 0.        +0.j -0.17320508+0.j  0.        +0.j -0.        +0.j]
 [ 0.        +0.j  0.        +0.j  0.17320508+0.j  0.        +0.j]
 [ 0.        +0.j -0.        +0.j  0.        +0.j -0.17320508+0.j]]
[[0.        +0.j 0.        +0.j 0.17320508+0.j 0.        +0.j]
 [0.        +0.j 0.        +0.j 0.        +0.j 0.17320508+0.j]
 [0.17320508+0.j 0.        +0.j 0.        +0.j 0.        +0.j]
 [0.        +0.j 0.17320508+0.j 0.        +0.j 0.        +0.j]]
[[0.        +0.j 0.        +0.j 0.        +0.j 0.17320508+0.j]
 [0.        +0.j 0.        +0.j 0.17320508+0.j 0.        +0.j]
 [0.        +0.j 0.17320508+0.j 0.        +0.j 0.        +0.j]
 [0.17320508+0.j 0.        +0.j 0.        +0.j 0.        +0.j]]
[[0.+0.j         0.+0.j         0.+0.j         0.-0.17320508j]
 [0.+0.j         0.+0.j         0.+0.17320508j 0.+0.j        ]
 [0.+0.j         0.-0.17320508j 0.+0.j         0.+0.j        ]
 [0.+0.17320508j 0.+0.j         0.+0.j         0.+0.j        ]]
[[ 0.        +0.j  0.        +0.j  0.17320508+0.j  0.        +0.j]
 [ 0.        +0.j -0.        +0.j  0.        +0.j -0.17320508+0.j]
 [ 0.17320508+0.j  0.        +0.j  0.        +0.j  0.        +0.j]
 [ 0.        +0.j -0.17320508+0.j  0.        +0.j -0.        +0.j]]
[[0.+0.j         0.+0.j         0.-0.17320508j 0.+0.j        ]
 [0.+0.j         0.+0.j         0.+0.j         0.-0.17320508j]
 [0.+0.17320508j 0.+0.j         0.+0.j         0.+0.j        ]
 [0.+0.j         0.+0.17320508j 0.+0.j         0.+0.j        ]]
[[0.+0.j         0.+0.j         0.+0.j         0.-0.17320508j]
 [0.+0.j         0.+0.j         0.-0.17320508j 0.+0.j        ]
 [0.+0.j         0.+0.17320508j 0.+0.j         0.+0.j        ]
 [0.+0.17320508j 0.+0.j         0.+0.j         0.+0.j        ]]
[[ 0.        +0.j  0.        +0.j  0.        +0.j -0.17320508+0.j]
 [ 0.        +0.j  0.        +0.j  0.17320508+0.j  0.        +0.j]
 [ 0.        +0.j  0.17320508+0.j  0.        +0.j  0.        +0.j]
 [-0.17320508+0.j  0.        +0.j  0.        +0.j  0.        +0.j]]
[[ 0.+0.j          0.+0.j          0.-0.17320508j  0.+0.j        ]
 [ 0.+0.j         -0.+0.j          0.+0.j          0.+0.17320508j]
 [ 0.+0.17320508j  0.+0.j          0.+0.j          0.+0.j        ]
 [ 0.+0.j          0.-0.17320508j  0.+0.j         -0.+0.j        ]]
[[ 0.17320508+0.j  0.        +0.j  0.        +0.j  0.        +0.j]
 [ 0.        +0.j  0.17320508+0.j  0.        +0.j  0.        +0.j]
 [ 0.        +0.j  0.        +0.j -0.17320508+0.j -0.        +0.j]
 [ 0.        +0.j  0.        +0.j -0.        +0.j -0.17320508+0.j]]
[[ 0.        +0.j  0.17320508+0.j  0.        +0.j  0.        +0.j]
 [ 0.17320508+0.j  0.        +0.j  0.        +0.j  0.        +0.j]
 [ 0.        +0.j  0.        +0.j -0.        +0.j -0.17320508+0.j]
 [ 0.        +0.j  0.        +0.j -0.17320508+0.j -0.        +0.j]]
[[ 0.+0.j          0.-0.17320508j  0.+0.j          0.+0.j        ]
 [ 0.+0.17320508j  0.+0.j          0.+0.j          0.+0.j        ]
 [ 0.+0.j          0.+0.j         -0.+0.j          0.+0.17320508j]
 [ 0.+0.j          0.+0.j          0.-0.17320508j -0.+0.j        ]]
[[ 0.17320508+0.j  0.        +0.j  0.        +0.j  0.        +0.j]
 [ 0.        +0.j -0.17320508+0.j  0.        +0.j -0.        +0.j]
 [ 0.        +0.j  0.        +0.j -0.17320508+0.j -0.        +0.j]
 [ 0.        +0.j -0.        +0.j -0.        +0.j  0.17320508+0.j]]

Adding noise to the circuit

Once we define the desired noise models, we have to attach them to the circuit. One way of doing this is by instantiating directly the circuit with the list of Instruction and NoiseModel.

[4]:
circuit_1 = QCircuit([H(0), CNOT(0,1), Y(1), BasisMeasure([0,1], shots=100), Depolarizing(0.3, [0], gates=[H])])
print(circuit_1)
     ┌───┐          ┌─┐
q_0: ┤ H ├──■───────┤M├───
     └───┘┌─┴─┐┌───┐└╥┘┌─┐
q_1: ─────┤ X ├┤ Y ├─╫─┤M├
          └───┘└───┘ ║ └╥┘
c: 2/════════════════╩══╩═
                     0  1
NoiseModel: Depolarizing(0.3, [0], 1, [H])

One can also use the method add(...) on an already instantiated QCircuit.

[21]:
circuit_2 = QCircuit([H(0), CNOT(0,1), Y(1), BasisMeasure([0,1], shots=100)])
circuit_2.add([Depolarizing(0.08, [0]), Depolarizing(0.13, [1])])
circuit_2.pretty_print()
QCircuit : Size (Qubits,Cbits) = (2, 2), Nb instructions = 4
Depolarizing noise: probability 0.08 on qubits [0]
Depolarizing noise: probability 0.13 on qubits [1]
     ┌───┐          ┌─┐
q_0: ┤ H ├──■───────┤M├───
     └───┘┌─┴─┐┌───┐└╥┘┌─┐
q_1: ─────┤ X ├┤ Y ├─╫─┤M├
          └───┘└───┘ ║ └╥┘
c: 2/════════════════╩══╩═
                     0  1

We can get the list of noise models attached to a circuit using the noises attributes.

[22]:
print(circuit_2.noises)
[Depolarizing(0.08, [0], 1), Depolarizing(0.13, [1], 1)]

One can also retrieve the circuit without any noise model.

[23]:
circuit_2.without_noises()
[23]:
QCircuit([H(0), CNOT(0,1), Y(1), BasisMeasure(0, 1, shots=100)], nb_qubits=2, nb_cbits=2, label="None")

When translating a QCircuit to another SDK’s circuit, if the noise is directly defined withing the circuit, we also include the attached noise models. It is the case for AWS Braket Circuits.

[24]:
noisy_braket_circuit = circuit_2.to_other_language(Language.BRAKET)
print(noisy_braket_circuit)

T  : │         0          │         1          │         2          │
      ┌───┐ ┌────────────┐       ┌────────────┐
q0 : ─┤ H ├─┤ DEPO(0.08) ├───●───┤ DEPO(0.08) ├──────────────────────
      └───┘ └────────────┘   │   └────────────┘
                           ┌─┴─┐ ┌────────────┐ ┌───┐ ┌────────────┐
q1 : ──────────────────────┤ X ├─┤ DEPO(0.13) ├─┤ Y ├─┤ DEPO(0.13) ├─
                           └───┘ └────────────┘ └───┘ └────────────┘
T  : │         0          │         1          │         2          │

Running noisy circuits

Once we defined our noisy circuit, we would eventually like to simulate the circuit on a noisy simulator. For this example, we will focus only on AWS Braket devices.

All AvailableDevice must implemented a method called is_noisy_simulator(), indicating wether a given device can simulate noisy circuit.

[25]:
for device in AWSDevice:
    print(device.name, "|", device.is_noisy_simulator())
BRAKET_LOCAL_SIMULATOR | True
BRAKET_SV1_SIMULATOR | False
BRAKET_DM1_SIMULATOR | True
BRAKET_TN1_SIMULATOR | False
BRAKET_IONQ_HARMONY | False
BRAKET_IONQ_ARIA_1 | False
BRAKET_IONQ_ARIA_2 | False
BRAKET_IONQ_FORTE_1 | False
BRAKET_OQC_LUCY | False
BRAKET_QUERA_AQUILA | False
BRAKET_RIGETTI_ASPEN_M_3 | False

For running the noisy circuit, we use the exact same way as in the noiseless case: we just call the run function with the circuit and the requested devices.

[30]:
result = run(circuit_2, AWSDevice.BRAKET_LOCAL_SIMULATOR) # this line is valid for both noisy and non noisy cases
print(result)

Result: AWSDevice, BRAKET_LOCAL_SIMULATOR
Counts: [6, 49, 32, 13]
Probabilities: [0.06 0.49 0.32 0.13]
State: 01, Index: 1, Count: 49, Probability: 0.49
State: 11, Index: 3, Count: 13, Probability: 0.13
State: 10, Index: 2, Count: 32, Probability: 0.32
State: 00, Index: 0, Count: 6, Probability: 0.06
Error: None