Source code for pytket.extensions.quantinuum.backends.leakage_gadget

# Copyright 2020-2024 Quantinuum
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.
"""Methods for generating a leakage detection Pytket Circuit."""

from typing import List, Dict, Tuple, Counter, cast, Sequence
from pytket import Circuit, Qubit, Bit, OpType  # type: ignore
from pytket.backends.backendresult import BackendResult
from pytket.utils.outcomearray import OutcomeArray

LEAKAGE_DETECTION_BIT_NAME_ = "leakage_detection_bit"
LEAKAGE_DETECTION_QUBIT_NAME_ = "leakage_detection_qubit"

[docs] def get_leakage_gadget_circuit( circuit_qubit: Qubit, postselection_qubit: Qubit, postselection_bit: Bit ) -> Circuit: """ Returns a two qubit Circuit for detecting leakage errors. :param circuit_qubit: Generated circuit detects whether leakage errors have occurred in this qubit. :param postselection_qubit: Measured qubit to detect leakage error. :param postselection_bit: Leakage detection result is written to this bit. :return: Circuit for detecting leakage errors for specified ids. """ c = Circuit() c.add_qubit(circuit_qubit) c.add_qubit(postselection_qubit) c.add_bit(postselection_bit) c.X(postselection_qubit) c.add_barrier([circuit_qubit, postselection_qubit]) c.H(postselection_qubit).ZZMax(postselection_qubit, circuit_qubit) c.add_barrier([circuit_qubit, postselection_qubit]) c.ZZMax(postselection_qubit, circuit_qubit).H(postselection_qubit).Z(circuit_qubit) c.add_barrier([circuit_qubit, postselection_qubit]) c.Measure(postselection_qubit, postselection_bit) c.add_gate(OpType.Reset, [postselection_qubit]) return c
[docs] def get_detection_circuit(circuit: Circuit, n_device_qubits: int) -> Circuit: """ For a passed circuit, appends a leakage detection circuit for each end of circuit measurement using spare device qubits. All additional Qubit added for leakage detection are written to a new register "leakage_detection_qubit" and all additional Bit are written to a new register "leakage_detection_bit". :param circuit: Circuit to have leakage detection added. :param n_device_qubits: Total number of qubits supported by the device being compiled to. :return: Circuit with leakage detection circuitry added. """ n_qubits: int = circuit.n_qubits if n_qubits == 0: raise ValueError( "Circuit for Leakage Gadget Postselection must have at least one Qubit." ) n_spare_qubits: int = n_device_qubits - n_qubits if n_spare_qubits <= 0: raise ValueError("Device has no spare qubits for adding leakage detection.") # construct detection circuit detection_circuit: Circuit = Circuit() postselection_qubits: List[Qubit] = [ Qubit(LEAKAGE_DETECTION_QUBIT_NAME_, i) for i in range(n_spare_qubits) ] for q in circuit.qubits + postselection_qubits: detection_circuit.add_qubit(q) for b in circuit.bits: detection_circuit.add_bit(b) # construct a Circuit that is the original Circuit without # end of Circuit Measure gates end_circuit_measures: Dict[Qubit, Bit] = {} for com in circuit: if com.op.type == OpType.Barrier: detection_circuit.add_barrier(com.args) continue # first check if a mid circuit measure needs to be readded for q in com.qubits: # this condition only true if this Qubit has previously had a # "mid-circuit" measure operation if q in end_circuit_measures: detection_circuit.Measure(q, end_circuit_measures.pop(q)) if com.op.type == OpType.Measure: # if this is "mid-circuit" then this will be rewritten later end_circuit_measures[com.qubits[0]] = com.bits[0] elif com.op.params: detection_circuit.add_gate(com.op.type, com.op.params, com.args) else: detection_circuit.add_gate(com.op.type, com.args) # for each entry in end_circuit_measures, we want to add a leakage_gadget_circuit # we try to use each free architecture qubit as few times as possible ps_q_index: int = 0 ps_b_index: int = 0 for q in end_circuit_measures: if q.reg_name == LEAKAGE_DETECTION_QUBIT_NAME_: raise ValueError( "Leakage Gadget scheme makes a qubit register named " "'leakage_detection_qubit' but this already exists in" " the passed circuit." ) ps_q_index = 0 if ps_q_index == n_spare_qubits else ps_q_index leakage_detection_bit: Bit = Bit(LEAKAGE_DETECTION_BIT_NAME_, ps_b_index) if leakage_detection_bit in circuit.bits: raise ValueError( "Leakage Gadget scheme makes a new Bit named 'leakage_detection_bit'" " but this already exists in the passed circuit." ) leakage_gadget_circuit: Circuit = get_leakage_gadget_circuit( q, postselection_qubits[ps_q_index], leakage_detection_bit ) detection_circuit.append(leakage_gadget_circuit) # increment value for adding postselection to ps_q_index += 1 ps_b_index += 1 # finally measure the original qubits for q, b in end_circuit_measures.items(): detection_circuit.Measure(q, b) detection_circuit.remove_blank_wires() return detection_circuit
[docs] def prune_shots_detected_as_leaky(result: BackendResult) -> BackendResult: """ For all states with a Bit with name "leakage_detection_bit" in a state 1 sets the counts to 0. :param result: Shots returned from device. :type result: BackendResult :return: Shots with leakage cases removed. :rtype: BackendResult """ regular_bits: List[Bit] = [ b for b in result.c_bits if b.reg_name != LEAKAGE_DETECTION_BIT_NAME_ ] leakage_bits: List[Bit] = [ b for b in result.c_bits if b.reg_name == LEAKAGE_DETECTION_BIT_NAME_ ] received_counts: Counter[Tuple[int, ...]] = result.get_counts( cbits=regular_bits + leakage_bits ) discarded_counts: Counter[Tuple[int, ...]] = Counter( { tuple(state[: len(regular_bits)]): received_counts[state] for state in received_counts if not any(state[-len(leakage_bits) :]) } ) return BackendResult( counts=Counter( { OutcomeArray.from_readouts([key]): val for key, val in discarded_counts.items() } ), c_bits=cast(Sequence[Bit], regular_bits), )