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_ibm_runtime.fake_provider import FakeJakartaV2  # 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))
1040

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 = FakeJakartaV2()
    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.83110
mitigated = 	 	 0.89605

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 = FakeJakartaV2()
    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: Richardson                │ Noisy error: 0.1089        │
│ Circuit depth: 318       │ Scale factors: 1.0, 2.0, 3.0       │ Mitigated error: 0.0223    │
│ Two qubit gate count: 72 │ Scale method: fold_global          │ Improvement factor: 4.8834 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Linear                    │ Noisy error: 0.1089        │
│ Circuit depth: 318       │ Scale factors: 1.0, 2.0, 3.0       │ Mitigated error: 0.0288    │
│ Two qubit gate count: 72 │ Scale method: fold_all             │ Improvement factor: 3.7812 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Richardson                │ Noisy error: 0.1089        │
│ Circuit depth: 318       │ Scale factors: 1.0, 3.0, 5.0       │ Mitigated error: 0.0352    │
│ Two qubit gate count: 72 │ Scale method: fold_gates_at_random │ Improvement factor: 3.0937 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Richardson                │ Noisy error: 0.1089        │
│ Circuit depth: 318       │ Scale factors: 1.0, 2.0, 3.0       │ Mitigated error: 0.0412    │
│ Two qubit gate count: 72 │ Scale method: fold_gates_at_random │ Improvement factor: 2.6432 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Richardson                │ Noisy error: 0.1089        │
│ Circuit depth: 318       │ Scale factors: 1.0, 3.0, 5.0       │ Mitigated error: 0.0435    │
│ Two qubit gate count: 72 │ Scale method: fold_all             │ Improvement factor: 2.502  │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Richardson                │ Noisy error: 0.1089        │
│ Circuit depth: 318       │ Scale factors: 1.0, 3.0, 5.0       │ Mitigated error: 0.0472    │
│ Two qubit gate count: 72 │ Scale method: fold_global          │ Improvement factor: 2.3054 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Linear                    │ Noisy error: 0.1089        │
│ Circuit depth: 318       │ Scale factors: 1.0, 2.0, 3.0       │ Mitigated error: 0.0475    │
│ Two qubit gate count: 72 │ Scale method: fold_global          │ Improvement factor: 2.291  │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Linear                    │ Noisy error: 0.1089        │
│ Circuit depth: 318       │ Scale factors: 1.0, 2.0, 3.0       │ Mitigated error: 0.0541    │
│ Two qubit gate count: 72 │ Scale method: fold_gates_at_random │ Improvement factor: 2.0117 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Linear                    │ Noisy error: 0.1089        │
│ Circuit depth: 318       │ Scale factors: 1.0, 3.0, 5.0       │ Mitigated error: 0.0569    │
│ Two qubit gate count: 72 │ Scale method: fold_global          │ Improvement factor: 1.9144 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Linear                    │ Noisy error: 0.1089        │
│ Circuit depth: 318       │ Scale factors: 1.0, 3.0, 5.0       │ Mitigated error: 0.0589    │
│ Two qubit gate count: 72 │ Scale method: fold_all             │ Improvement factor: 1.8502 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✔                          │
│ Num qubits: 2            │ Factory: Linear                    │ Noisy error: 0.1089        │
│ Circuit depth: 318       │ Scale factors: 1.0, 3.0, 5.0       │ Mitigated error: 0.0598    │
│ Two qubit gate count: 72 │ Scale method: fold_gates_at_random │ Improvement factor: 1.8203 │
├──────────────────────────┼────────────────────────────────────┼────────────────────────────┤
│ Type: rb                 │ Technique: ZNE                     │ ✘                          │
│ Num qubits: 2            │ Factory: Richardson                │ Noisy error: 0.1089        │
│ Circuit depth: 318       │ Scale factors: 1.0, 2.0, 3.0       │ Mitigated error: 0.2463    │
│ Two qubit gate count: 72 │ Scale method: fold_all             │ Improvement factor: 0.4421 │
└──────────────────────────┴────────────────────────────────────┴────────────────────────────┘

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.83540
mitigated = 	 	 0.90025
calibrated_mitigated = 	 0.94960

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.