Error mitigation with Qibo using noisy simulation#

In this tutorial we will cover how to use Mitiq to apply Zero-Noise Extrapolation (ZNE) to a quantum program written using Qibo. We will demonstrate the use of these two tools by implementing a noisy simulation as random Pauli gates probabilistically applied after each ideal gate.

Defining a circuit#

For simplicity, we will use a single-qubit circuit with ten Pauli X gates that compiles to the identity. This will give us a simple circuit whose probability of measuring the \(|0\rangle\) state at the end, ideally, should be 1.

import os 
os.environ['QIBO_LOG_LEVEL'] = '3' #Supress Qibo INFO messages

from qibo import Circuit, gates

c = Circuit(1)
for _ in range(10):
    c.add(gates.X(0))
c.add(gates.M(0))
MeasurementResult(qubits=(0,), nshots=0)

We will use the probability of measuring the \(|0\rangle\) state as our observable to mitigate.

Defining the executor#

To use Mitiq, an executor function is required to be defined. This function abstracts away the logic of how a circuit is executed, allowing Mitiq to only see the circuit, and the value received from the executor (which, in the case of applying ZNE, must be a float). In the executor, we create a noise map and apply it to the circuit. We then simulate the noisy circuit and return the measured observable. For more detailed information about the noise map features see Qibo’s documentation on noisy simulation.

def executor(circuit, shots=100):
    """Returns the expectation value to be mitigated.
    In this case the expectation value is the probability to get the |0⟩ state.

    Args:
        circuit: Circuit to run.
        shots: Number of times to execute the circuit to compute the expectation value.
    """
    # noise_map = {qubit_id: [(gate, probability)]}
    noise_map = {0: [('X', 0.03), ('Z', 0.03)]}
    noisy_c = circuit.with_pauli_noise(noise_map)

    result = noisy_c(nshots=shots)
    result_freq = result.frequencies(binary=True)
    counts_0 = result_freq.get('0', 0)

    return counts_0 / shots

Applying ZNE#

We can now test the mitigated version of the circuit against the unmitigated one to ensure it is working as expected. We apply ZNE with scale factors 1, 2 and 3 and using LinearFactory (which extrapolates the measured expectation values using a linear extrapolation). For each scaling factor we average over three circuits.

from mitiq import zne
from mitiq.zne.inference import LinearFactory

unmitigated = executor(c)
print(f"Unmitigated result {unmitigated:.3f}")

scale_factors = [1, 2, 3]
factory = LinearFactory(
    scale_factors=scale_factors
)  # default ZNE configuration
mitigated = zne.execute_with_zne(c, executor, factory=factory, num_to_average=3)
print(f"Mitigated result {mitigated:.3f}")
Unmitigated result 0.790
Mitigated result 0.864

The mitigated result is noticeably closer to the noiseless result compared to the result without mitigation. In addition, Mitiq offers the capability to view the extrapolation directly from the RichardsonFactory object.

import matplotlib.pyplot as plt
factory.plot_fit()
plt.show()
../_images/4da99e09e26aa7df922357eb41a7cff80b10abf87ddc01c74dedc9eec6ae50b2.png