Breaking into error mitigation with Mitiq’s calibration module#
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 backendRun 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
, andmitiq.zne.scaling.folding.fold_all
), and ZNE factories for extrapolation (such asmitiq.zne.inference.RichardsonFactory
andmitiq.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.