Breaking into error mitigation with Mitiq’s calibration module#

../_images/calibration.png

This tutorial helps answer the question: “What quantum error mitigation technique should I use for my problem?”. The newly introduced mitiq.calibration module helps answer that in an optimized way, through Benchmarks and Strategies.

More specifically, this tutorial covers:

  • Getting started with Mitiq’s calibration module with ZNE

  • Use Qiskit noisy simulator with FakeJakarta as backend

  • Run calibration with some special settings, RBSettings, and logging the results

Getting started with Mitiq#

from mitiq.benchmarks import generate_rb_circuits
from mitiq.zne import execute_with_zne
from mitiq.zne.scaling import (
    fold_gates_at_random,
    fold_global,
    fold_all
)
from mitiq.zne.inference import LinearFactory, RichardsonFactory
from mitiq import (
    Calibrator,
    Settings,
    execute_with_mitigation,
    MeasurementResult,
)

from qiskit.providers.fake_provider import FakeJakarta  # Fake (simulated) QPU

Define the circuit to study#

Global variables#

Define global variables for the quantum circuit of interest: number of qubits, depth of the quantum circuit and number of shots.

n_qubits = 2
depth_circuit = 100
shots = 10 ** 4

Quantum circuit: Randomized benchmarking (RB)#

We now use Mitiq’s built-in generate_rb_circuits from the mitiq.benchmarks module to define the quantum circuit.

circuit = generate_rb_circuits(n_qubits, depth_circuit,return_type="qiskit")[0]
circuit.measure_all()
print(len(circuit))
1023

We define a function that executes the quantum circuits and returns the expectation value. This is consumed by Mitiq’s execute_with_zne. In this example, the expectation value is the probability of measuring the ground state, which is what one would expect from an ideal randomized benchmarking circuit.

def execute_circuit(circuit):
    """Execute the input circuit and return the expectation value of |00..0><00..0|"""
    noisy_backend = FakeJakarta()
    noisy_result = noisy_backend.run(circuit, shots=shots).result()
    noisy_counts = noisy_result.get_counts(circuit)
    noisy_expectation_value = noisy_counts[n_qubits * "0"] / shots
    return noisy_expectation_value
mitigated = execute_with_zne(circuit, execute_circuit, factory=LinearFactory([1, 3, 5]))
unmitigated = execute_circuit(circuit)
ideal = 1 #property of RB circuits

print("ideal = \t \t",ideal)
print("unmitigated = \t \t", "{:.5f}".format(unmitigated))
print("mitigated = \t \t", "{:.5f}".format(mitigated))
ideal = 	 	 1
unmitigated = 	 	 0.82320
mitigated = 	 	 0.88871

Using calibration to improve the results#

Let’s consider a noisy backend using the Qiskit noisy simulator, FakeJakarta. Note that the executor passed to the Calibrator object must return counts, as opposed to expectation values.

def execute_calibration(qiskit_circuit):
    """Execute the input circuits and return the measurement results."""
    noisy_backend = FakeJakarta()
    noisy_result = noisy_backend.run(qiskit_circuit, shots=shots).result()
    noisy_counts = noisy_result.get_counts(qiskit_circuit)
    noisy_counts = { k.replace(" ",""):v for k, v in noisy_counts.items()}
    measurements = MeasurementResult.from_counts(noisy_counts)
    return measurements

We import from the calibration module the key ingredients to use mitiq.calibration: the Calibrator class, the mitiq.calibration.settings.Settings class and the execute_with_mitigation function.

Currently mitiq.calibration supports ZNE as a technique to calibrate from, tuning different scale factors, extrapolation methods and circuit scaling methods.

Let’s run the calibration using an ad-hoc RBSettings while logging the results for comparison.

  • benchmarks: Circuit type: “rb”

  • strategies: use various “zne” strategies, testing various “scale_noise” methods (such as mitiq.zne.scaling.folding.fold_global, mitiq.zne.scaling.folding.fold_gates_at_random, and mitiq.zne.scaling.folding.fold_all), and ZNE factories for extrapolation (such as mitiq.zne.inference.RichardsonFactory and mitiq.zne.inference.LinearFactory)

RBSettings = Settings(
    benchmarks=[
        {
            "circuit_type": "rb",
            "num_qubits": 2,
            "circuit_depth": int(depth_circuit / 2),
        },
    ],
    strategies=[
        {
            "technique": "zne",
            "scale_noise": fold_global,
            "factory": RichardsonFactory([1.0, 2.0, 3.0]),
        },
        {
            "technique": "zne",
            "scale_noise": fold_global,
            "factory": RichardsonFactory([1.0, 3.0, 5.0]),
        },
        {
            "technique": "zne",
            "scale_noise": fold_global,
            "factory": LinearFactory([1.0, 2.0, 3.0]),
        },
        {
            "technique": "zne",
            "scale_noise": fold_global,
            "factory": LinearFactory([1.0, 3.0, 5.0]),
        },

        {
            "technique": "zne",
            "scale_noise": fold_gates_at_random,
            "factory": RichardsonFactory([1.0, 2.0, 3.0]),
        },
        {
            "technique": "zne",
            "scale_noise": fold_gates_at_random,
            "factory": RichardsonFactory([1.0, 3.0, 5.0]),
        },
        {
            "technique": "zne",
            "scale_noise": fold_gates_at_random,
            "factory": LinearFactory([1.0, 2.0, 3.0]),
        },
        {
            "technique": "zne",
            "scale_noise": fold_gates_at_random,
            "factory": LinearFactory([1.0, 3.0, 5.0]),
        },

        {
            "technique": "zne",
            "scale_noise": fold_all,
            "factory": RichardsonFactory([1.0, 2.0, 3.0]),
        },
        {
            "technique": "zne",
            "scale_noise": fold_all,
            "factory": RichardsonFactory([1.0, 3.0, 5.0]),
        },
        {
            "technique": "zne",
            "scale_noise": fold_all,
            "factory": LinearFactory([1.0, 2.0, 3.0]),
        },
        {
            "technique": "zne",
            "scale_noise": fold_all,
            "factory": LinearFactory([1.0, 3.0, 5.0]),
        },

    ],
)
cal = Calibrator(execute_calibration, frontend="qiskit", settings=RBSettings)
cal.run(log="flat")
┌──────────────────────────┬────────────────────────────────────┬────────────────────────────┐
│ benchmark                │ strategy                           │ performance                │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Linear                    │ Noisy error: 0.0951        │
│ Circuit depth: 310       │ Scale factors: 1.0, 2.0, 3.0       │ Mitigated error: 0.0231    │
│ Two qubit gate count: 74 │ Scale method: fold_all             │ Improvement factor: 4.111  │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Richardson                │ Noisy error: 0.0951        │
│ Circuit depth: 310       │ Scale factors: 1.0, 3.0, 5.0       │ Mitigated error: 0.0269    │
│ Two qubit gate count: 74 │ Scale method: fold_all             │ Improvement factor: 3.5337 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Richardson                │ Noisy error: 0.0951        │
│ Circuit depth: 310       │ Scale factors: 1.0, 3.0, 5.0       │ Mitigated error: 0.037     │
│ Two qubit gate count: 74 │ Scale method: fold_global          │ Improvement factor: 2.572  │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Richardson                │ Noisy error: 0.0951        │
│ Circuit depth: 310       │ Scale factors: 1.0, 3.0, 5.0       │ Mitigated error: 0.0373    │
│ Two qubit gate count: 74 │ Scale method: fold_gates_at_random │ Improvement factor: 2.5522 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Richardson                │ Noisy error: 0.0951        │
│ Circuit depth: 310       │ Scale factors: 1.0, 2.0, 3.0       │ Mitigated error: 0.0383    │
│ Two qubit gate count: 74 │ Scale method: fold_gates_at_random │ Improvement factor: 2.483  │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Linear                    │ Noisy error: 0.0951        │
│ Circuit depth: 310       │ Scale factors: 1.0, 2.0, 3.0       │ Mitigated error: 0.0428    │
│ Two qubit gate count: 74 │ Scale method: fold_gates_at_random │ Improvement factor: 2.2202 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Linear                    │ Noisy error: 0.0951        │
│ Circuit depth: 310       │ Scale factors: 1.0, 2.0, 3.0       │ Mitigated error: 0.047     │
│ Two qubit gate count: 74 │ Scale method: fold_global          │ Improvement factor: 2.0234 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Linear                    │ Noisy error: 0.0951        │
│ Circuit depth: 310       │ Scale factors: 1.0, 3.0, 5.0       │ Mitigated error: 0.0483    │
│ Two qubit gate count: 74 │ Scale method: fold_global          │ Improvement factor: 1.9689 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Linear                    │ Noisy error: 0.0951        │
│ Circuit depth: 310       │ Scale factors: 1.0, 3.0, 5.0       │ Mitigated error: 0.0502    │
│ Two qubit gate count: 74 │ Scale method: fold_gates_at_random │ Improvement factor: 1.8941 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Linear                    │ Noisy error: 0.0951        │
│ Circuit depth: 310       │ Scale factors: 1.0, 3.0, 5.0       │ Mitigated error: 0.0547    │
│ Two qubit gate count: 74 │ Scale method: fold_all             │ Improvement factor: 1.7399 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Richardson                │ Noisy error: 0.0951        │
│ Circuit depth: 310       │ Scale factors: 1.0, 2.0, 3.0       │ Mitigated error: 0.0704    │
│ Two qubit gate count: 74 │ Scale method: fold_global          │ Improvement factor: 1.3509 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✘                          │
│ Num qubits: 2            │ Factory: Richardson                │ Noisy error: 0.0951        │
│ Circuit depth: 310       │ Scale factors: 1.0, 2.0, 3.0       │ Mitigated error: 0.1851    │
│ Two qubit gate count: 74 │ Scale method: fold_all             │ Improvement factor: 0.5138 │
└──────────────────────────┴────────────────────────────────────┴────────────────────────────┘

As you can see above, several experiments were run, and each one has either a cross (✘) or a check (✔) to signal whether the error mitigation experiment obtained an expectation value that is better than the non-mitigated one.

calibrated_mitigated=execute_with_mitigation(circuit, execute_circuit, calibrator=cal)
mitigated=execute_with_zne(circuit, execute_circuit, factory=LinearFactory([1, 3, 5]))
unmitigated=execute_circuit(circuit)

print("ideal = \t \t",ideal)
print("unmitigated = \t \t", "{:.5f}".format(unmitigated))
print("mitigated = \t \t", "{:.5f}".format(mitigated))
print("calibrated_mitigated = \t", "{:.5f}".format(calibrated_mitigated))
ideal = 	 	 1
unmitigated = 	 	 0.81880
mitigated = 	 	 0.89792
calibrated_mitigated = 	 0.94460

We can see that the mitigated and calibrated-mitigated values show improvement over the unmitigated value, and that the calibrated value shows a larger improvement, achieving the objective of the calibration process.