Measurement Error Mitigation on Quantum Inspire

Backends

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.

What does it do and what is it used for?

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.

How does it work?

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

Example Code

Imports and definitions

[19]:
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
[20]:
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()

Set up connection to QI

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.

[21]:
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.
[22]:
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

Error mitigation for Spin-2

Run error mitigation test on a Bell state \((\left|00\right\rangle+\left|11\right\rangle)/\sqrt{2}\).

[23]:
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)
../_images/notebooks_measurement-error-mitigation_14_0.png
{'01': 278, '11': 656, '10': 441, '00': 673}
[24]:
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.

[25]:
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="")
[26]:
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.

[27]:
plot_histogram([noisy_counts, mitigated_counts], legend=["noisy", "mitigated"])
[27]:
../_images/notebooks_measurement-error-mitigation_20_0.png

Note that the skew between the two qubits has reduced significantly.

Measurement of the error matrix

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.

[28]:
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\).

[29]:
for circuit in meas_calibs:
    print(f"Circuit {circuit.name}")
    display(circuit.draw(output="mpl"))
Circuit cal_00
../_images/notebooks_measurement-error-mitigation_26_1.png
Circuit cal_01
../_images/notebooks_measurement-error-mitigation_26_3.png
Circuit cal_10
../_images/notebooks_measurement-error-mitigation_26_5.png
Circuit cal_11
../_images/notebooks_measurement-error-mitigation_26_7.png

Let’s now run these circuits.

[30]:
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\).

[31]:
meas_fitter = CompleteMeasFitter(cal_results, state_labels, circlabel="")
meas_fitter.cal_matrix

meas_fitter.plot_calibration()
../_images/notebooks_measurement-error-mitigation_30_0.png
[32]:
print(f"Readout fidelity of our system: {meas_fitter.readout_fidelity():.2f}")
Readout fidelity of our system: 0.61

Want to know more?

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.