Automatic Leakage Error Detection

Quantum computers are known to be noisy, with a high chance of errors occurring when executing a sequence of operations. These errors can come from a variety of sources and are typically hard to mitigate. Investigating the source of errors, how they manifest at the quantum circuit level, and how to mitigate them, is a wide area of research.

A regular experiment using the QuantinuumBackend works as follows: circuits are defined using pytket, compiled to the device constraints using QuantinuumBackend.get_compiled_circuit and sent to the device provider using QuantinuumBackend.process_circuits; results are retrieved using QuantinuumBackend.get_results. Results are received in a BackendResult object and represented as a series of “shots”, a binary string corresponding to the measurement results for each Qubit after each execution of the circuit on the device. This set of shots gives a probability distribution over the set of basis states spanned by the device qubits. Broadly, the closer the probability distribution represented by the set of shots matches the ideal probability distribution, the more accurate the computation can be considered (note we are considering noise here, but the number of shots taken also affects this).

One source of error in computation is from “leakage”. During execution of hardware level 2-qubit gates, there is a small probability for the qubit to experience leakage to electronic states outside the qubit subspace. When a qubit has leaked, none of the remaining gates in the circuit acting on the qubit have any effect, but a measurement on that qubit will falsely result in a ‘1’. For more information we refer to Eliminating Leakage Errors in Hyperfine Qubits by D. Hayes, D. Stack, B. Bjork, A. C. Potter, C. H. Baldwin and R. P. Stutz.

Using a special circuit “gadget” and an ancilla qubit, it’s possible to detect leakage errors at the quantum circuit level and so discard erroneous shots. In this notebook we will show how to automatically run experiments with leakage detection added using QuantinuumBackend.process_circuits and how to process BackendResult returned from QuantinuumBackend to remove leaky results. We will also show how this scheme can improve results using a repeated gate benchmarking experiment.

The leakage detection circuit, or “gadget”, looks like so:

from pytket.extensions.quantinuum.backends.leakage_gadget import (
    get_leakage_gadget_circuit,
)
from pytket.circuit.display import render_circuit_jupyter
from pytket import Qubit, Bit
render_circuit_jupyter(
    get_leakage_gadget_circuit(
        Qubit("experiment_qubit", 0),
        Qubit("leakage_detection_qubit", 0),
        Bit("leakage_detection_bit", 0),
    )
)

The action of this Circuit is such that if leakage hasn’t occurred, then the experiment_qubit state is unchanged and the leakage_detection_qubit is in state “0”, while if leakage has occurred then the leakage_detection_qubit will be in state “1” and so can easily be detected.

The pytket-quantinuum package has methods for automatically modifying a pytket Circuit to add leakage detection gadgets just before any end of circuit measurement gates.

Let’s create a basic Bell pair circuit and then modify it.

from pytket.extensions.quantinuum.backends.leakage_gadget import get_detection_circuit
from pytket import Circuit
bell_pair_circuit = Circuit(2).H(0).CX(0, 1).measure_all()
bell_pair_leakage_detection_circuit = get_detection_circuit(
    circuit=bell_pair_circuit, n_device_qubits=4
)
render_circuit_jupyter(bell_pair_leakage_detection_circuit)

We can see that for each of the Bell pair qubits a leakage detection gadget has been appended before the final measurement.

The parameter n_device_qubits tells get_detection_circuit how many qubits the device has. In this case we stated there were 4 device qubits while the Bell pair circuit had 2 qubits, meaning a separate qubit was used for each leakage detection gadget. However, if there are too few device qubits then get_detection_circuit will reuse ancilla qubits to do multiple leakage detections, assigning the results to different Bit. We can see this by setting n_device_qubits = 3.

render_circuit_jupyter(get_detection_circuit(circuit=bell_pair_circuit, n_device_qubits=3))

The QuantinuumBackend.process_circuits method takes an optional kwarg leakage_detection which when True, will pass every circuit through get_detection_circuit before passing it to the hardware. This means all circuits will be executed with leakage detection added.

When passing a ResultHandle to QuantinuumBackend.get_result, the retrieved BackendResult will include measurement results from the leakage detection qubits.

from pytket.extensions.quantinuum import QuantinuumBackend
circuit = Circuit(2, 2).H(0).CX(0, 1).measure_all()
# Note for demonstration purposes this is not calling the real hardware.
backend = QuantinuumBackend(device_name="H1-1E")
handle = backend.process_circuit(circuit, n_shots=10000, leakage_detection=True)
result = backend.get_result(handle)

We can see in the returned results that there are additional Bit for detecting leakage.

By passing an ordering of Bit to result.get_counts(), we ensure that the final two entries in each bitstring correspond to the measurement results for the leakage detection Qubit.

If each of these entries is a “1” then leakage has occurred and these counts should be removed.

print("Bit with measurement results:", list(result.c_bits.keys()))
for bitstring, count in result.get_counts(
    [Bit(0), Bit(1), Bit("leakage_detection_bit", 0), Bit("leakage_detection_bit", 1)]
).items():
    print(bitstring, count)

We can now use the method prune_shots_detected_as_leaky to modify this BackendResult object, removing counts corresponding to leaked results.

from pytket.extensions.quantinuum import prune_shots_detected_as_leaky
pruned_result = prune_shots_detected_as_leaky(result)
print("Bit with measurement results:", list(pruned_result.c_bits.keys()))
for bitstring, count in pruned_result.get_counts([Bit(0), Bit(1)]).items():
    print(bitstring, count)

Benchmarking circuit improvement with leakage detection

In this section we will demonstrate how using leakage detection can improve the results of a circuit. Since the probability of a qubit having leaked during a circuit increases with the number of 2-qubit gates that the qubit participates in, we use a deep circuit in order to measure a statistically significant improvement. The following circuit repeats the native 2-qubit gate many times and is such that the final state is ideally \(| 00\rangle\).

circuit = Circuit(2, 2)
for _ in range(200):
    circuit.ZZMax(0, 1)
    circuit.add_barrier([0, 1])
circuit.measure_all()

We next run the circuit through the Quantinuum H1-1 emulator, which realistically models leakage during the 2-qubit gates. We create two circuit handles, one without and the other with leakage detection.

backend = QuantinuumBackend(device_name="H1-1E")
compiled_circuit = backend.get_compiled_circuit(circuit, optimisation_level=0)
handle_no_leakage = backend.process_circuit(
    compiled_circuit, n_shots=10000, leakage_detection=False
)
handle_leakage = backend.process_circuit(
    compiled_circuit, n_shots=10000, leakage_detection=True
)

When submitting circuits to the Quantinuum hardware emulator, it is a good idea to check the status of the circuits.

for handle in [handle_no_leakage, handle_leakage]:
    print(backend.circuit_status(handle).status)

We now get the results from the circuits, and compare the success probabilities.

import numpy as np
counts_no_leakage = backend.get_result(handle_no_leakage).get_counts()
counts_leakage = prune_shots_detected_as_leaky(
    backend.get_result(handle_leakage)
).get_counts()
print("Counts without leakage detection:", counts_no_leakage)
print("Counts with leakage detection:", counts_leakage)
n_shots_no_leakage = sum(counts_no_leakage.values())
n_shots_leakage = sum(counts_leakage.values())
prob_no_leakage = counts_no_leakage.get((0, 0)) / n_shots_no_leakage
prob_leakage = counts_leakage.get((0, 0)) / n_shots_leakage
std_no_leakage = np.sqrt(prob_no_leakage * (1 - prob_no_leakage) / n_shots_no_leakage)
std_leakage = np.sqrt(prob_leakage * (1 - prob_leakage) / n_shots_leakage)
print(
    f"Success probability without leakage detection: {round(prob_no_leakage,4)} +/- {round(std_no_leakage,4)}"
)
print(
    f"Success probability with leakage detection:    {round(prob_leakage,4)} +/- {round(std_leakage,4)}"
)