This example is written for the Spin-2 backend. If it is offline or the authentication unsuccessful, the code will fallback to simulation using Qiskit Aer.
For quantum devices, in particular spin-qubits, the measurement error is significant with respect to other sources of errors. We can reduce the effect of measurement errors using measurement error mitigation. This is particularly important when different qubits have different readout fidelities or when there is a cross-talk between the qubit readouts. Reducing the measurement error can be essential for the improvement of the performance of a quantum algorithm.
For more details see Qiskit textbook on measurement error mitigation and Mitiq documentation.
We will be measuring a Bell state first without error mitigation, and then apply the measurement error mitigation tools available in Qiskit to improve the result. We perform the calculations on real hardware (or on the Qiskit QASM simulator when the hardware is not available). Below we will go through this process in detail.
For information on an advanced technique for measurement error mitigation, have a look at the references at the bottom of the notebook.
To install the required dependencies for this notebook, run:
pip install qiskit quantuminspire requests
import numpy as np
import qiskit
import requests
from qiskit import Aer, QuantumCircuit, QuantumRegister, assemble, execute, transpile
from qiskit.ignis.mitigation.measurement import CompleteMeasFitter, complete_meas_cal
from qiskit.providers.aer.noise import NoiseModel, ReadoutError
from qiskit.providers.aer.noise.errors import depolarizing_error, pauli_error
from qiskit.visualization import plot_histogram
from quantuminspire.api import QuantumInspireAPI
from quantuminspire.credentials import get_authentication
from quantuminspire.qiskit import QI
def get_qi_calibration_from_qiskit_result(qi_job, authentication) -> dict:
""" Return calibration data from a QI job """
api = QI.get_api()
project = api.get_project(qi_job.job_id())
# NOTE: due to limited capabilties of the QI framework this is the latest job, perhaps not the actual job
job = qi_job.get_jobs()[0]
result = api.get_result_from_job(job["id"])
c = result["calibration"]
result = requests.get(c, auth=authentication)
calibration = result.json()
return calibration
def spin_2_noise_model(p: float = 0.01):
""" Define noise model representation for Spin-2 """
noise_model = NoiseModel()
error_gate = pauli_error([("X", p), ("I", 1 - p)])
noise_model.add_all_qubit_quantum_error(error_gate, ["u1", "u2", "u3", "rx", "ry", "x", "y"])
read_err = ReadoutError([[0.9, 0.1], [0.2, 0.8]])
noise_model.add_readout_error(read_err, [0])
read_err = ReadoutError([[0.7, 0.3], [0.25, 0.75]])
noise_model.add_readout_error(read_err, [1])
return noise_model
noise_model_spin2 = spin_2_noise_model()
We expect the user to have set up the token authentication as described here in order to be able to access the Spin-2 backend in the following cells.
try:
authentication = get_authentication()
QI.set_authentication(authentication)
qi_backend = QI.get_backend("Spin-2")
except:
print("QI connection not available, fall-back to simulation.")
qi_backend = None
Enter email: Enter password ········ QI connection not available, fall-back to simulation.
def execute_circuit_spin2(qc, qi_backend, shots=2048):
""" Execute circuit on Spin-2 with fall-back to simulation """
sim = False
if qi_backend is None:
sim = True
else:
api = QI.get_api()
backend = api.get_backend_type(qi_backend.backend_name)
if backend["status"] == "OFFLINE":
print(f"Backend {qi_backend} is offline, fall-back to simulation")
sim = True
if sim:
backend = Aer.get_backend("qasm_simulator")
result = backend.run(qc, shots=shots, noise_model=noise_model_spin2).result()
qi_job = None
else:
try:
qi_job = execute(qc, backend=qi_backend, shots=shots)
result = qi_job.result()
except Exception as ex:
print(f"Failed to run on backend {qi_backend}: {ex}, fall-back to simulation")
backend = Aer.get_backend("qasm_simulator")
result = backend.run(qc, shots=shots, noise_model=noise_model_spin2).result()
qi_job = None
return result, qi_job
Run error mitigation test on a Bell state $(\left|00\right\rangle+\left|11\right\rangle)/\sqrt{2}$.
qc = QuantumCircuit(2, name="Bell state")
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
display(qc.draw(output="mpl"))
bell_measurement_result, qi_job = execute_circuit_spin2(qc, qi_backend)
noisy_counts = bell_measurement_result.get_counts()
print(noisy_counts)
{'01': 278, '11': 656, '10': 441, '00': 673}
if qi_job:
cr = get_qi_calibration_from_qiskit_result(qi_job, authentication)
measurement_error_results = cr["parameters"]["system"]["readout_error_calibration"]["qiskit_result"]
measurement_error_results = qiskit.result.Result.from_dict(measurement_error_results)
timestamp = cr["parameters"]["system"]["readout_error_calibration"]["timestamp"]
print(f"Using calibration of {timestamp}")
else:
measurement_error_results = np.array(
[
[0.7421875, 0.12890625, 0.0, 0.0390625],
[0.11328125, 0.62890625, 0.0, 0.13671875],
[0.140625, 0.05859375, 1.0, 0.11328125],
[0.00390625, 0.18359375, 0.0, 0.7109375],
]
)
In Qiskit we mitigate the noise by creating a measurement filter object. Then, taking the results from above, we use this to calculate a mitigated set of counts.
if isinstance(measurement_error_results, np.ndarray):
meas_fitter = CompleteMeasFitter(None, state_labels=["00", "01", "10", "11"])
meas_fitter.cal_matrix = measurement_error_results
else:
state_labels = ["00", "01", "10", "11"]
meas_fitter = CompleteMeasFitter(measurement_error_results, state_labels, circlabel="")
meas_filter = meas_fitter.filter
# Results with error mitigation
mitigated_results = meas_filter.apply(bell_measurement_result)
mitigated_counts = mitigated_results.get_counts()
To see the results most clearly, let's plot both the noisy and mitigated results.
plot_histogram([noisy_counts, mitigated_counts], legend=["noisy", "mitigated"])
Note that the skew between the two qubits has reduced significantly.
The process of measurement error mitigation can also be done using the existing tools from Qiskit. This handles the collection of data for the basis states, the construction of the matrices, and the calculation of the inverse. The latter can be done using the pseudoinverse, as we saw above. However, the default is an even more sophisticated method which is using least-squares fitting.
As an example, let's stick with doing error mitigation for a pair of qubits. For this, we define a two-qubit quantum register, and feed it into the function complete_meas_cal
.
qr = QuantumRegister(2)
meas_calibs, state_labels = complete_meas_cal(qr=qr, circlabel="")
This creates a set of circuits that are used to take measurements for each of the four basis states of two qubits: $\left|00\right\rangle$, $\left|01\right\rangle$, $\left|10\right\rangle$ and $\left|11\right\rangle$.
for circuit in meas_calibs:
print(f"Circuit {circuit.name}")
display(circuit.draw(output="mpl"))
Circuit cal_00
Circuit cal_01
Circuit cal_10
Circuit cal_11
Let's now run these circuits.
cal_results = [execute_circuit_spin2(qc, qi_backend, shots=2048)[0] for qc in meas_calibs]
With the results we can construct the calibration matrix, which we have been calling $M$.
meas_fitter = CompleteMeasFitter(cal_results, state_labels, circlabel="")
meas_fitter.cal_matrix
meas_fitter.plot_calibration()
print(f"Readout fidelity of our system: {meas_fitter.readout_fidelity():.2f}")
Readout fidelity of our system: 0.61
Note that the error mitigation protocol described above does not change the execution of the quantum algorithms. There is another mitigation technique called readout rebalancing that does change the executed algorithms. Readout rebalancing places strategic $X$ gates before measurements in order to reduce the variability in the measurement results. Check out the paper Readout Rebalancing for Near Term Quantum Computers and the corresponding notebook for more information.