Mid-Circuit Measurement & Reset

This notebook performs a repetition code calculation with the following H-Series features:

  • Qubit Reuse via Mid-circuit measurements with reset

  • Classicaly-conditioned operations

The notebook performs a D=3, T=1 repetition code. Three physical qubits are used to encode one logical qubit. The physical qubit register is initialised in the \(|000\rangle\) state encoding the logical \(|0\rangle\) state.

One ancilla qubit is used to perform two syndrome measurements:

  1. \(\hat{Z}_{q[0]} \hat{Z}_{q[1]} \hat{I}_{q[2]}\)

  2. \(\hat{I}_{q[0]} \hat{Z}_{q[1]} \hat{Z}_{q[2]}\)

Subsequently, classically-conditioned operations are used to correct any errors on the physical qubits using the syndrome measurement results. Finally, direct measurements on the physical qubits are performed to verify the final state of the logical qubit is \(|0\rangle\).

Syndrome Measurement Circuit Primitive

In the code cell below, a circuit primitive is defined to detect errors on two physical qubits with one ancilla qubit.

from pytket.circuit import Circuit, OpType, CircBox
from pytket.circuit.display import render_circuit_jupyter
def syndrome_extraction():
    circuit = Circuit(3, 1)
    circuit.CX(1, 0)
    circuit.CX(2, 0)
    circuit.Measure(0, 0)
    circuit.add_gate(OpType.Reset, [0])
    return CircBox(circuit)
syndrome_box = syndrome_extraction()
render_circuit_jupyter(syndrome_box.get_circuit())

Repetition Code Circuit

Initially, a pytket.circuit.Circuit is instantiated with three physical qubits (data register), one ancilla qubits (ancilla register). Additionally, two classical registers are added: the first to store output from syndrome measurements (syndrome register); and the second (output register) to store output from direct measurement on phyiscal qubits.

The use of mid-circuit measurement is straightforward. Note the use of measure and reset on the ancilla qubits. This example also utilizes conditional logic available with Quantinuum devices as well as Registers and IDs available in pytket. See Classical and conditional operations and Registers and IDs for additional examples.

The circuit is named “Repetition Code”. This name is used by the Job submitted to H-series later in this notebook.

from pytket.circuit import Circuit

Set up circuit object

circuit = Circuit(name="Repetition Code")

Reserve registries

Add qubit register, the data qubits

data = circuit.add_q_register("data", 3)

Add qubit register, the ancilla qubit

ancilla = circuit.add_q_register("ancilla", 1)

Add classical registers for the syndromes

syndrome = circuit.add_c_register("syndrome", 2)

Add classical registers for the output

output = circuit.add_c_register("output", 3)

The syndrome measurement primitive, defined above, is added twice as pytket.circuit.CircBox. The first measures \(\hat{Z}_{q[0]} \hat{Z}_{q[1]} \hat{I}_{q[2]}\) and the second measures \(\hat{I}_{q[0]} \hat{Z}_{q[1]} \hat{Z}_{q[2]}\). This is one round of syndrome measurements. The CircBox instances are decomposed with pytket.passes.DecomposeBoxes.

from pytket.passes import DecomposeBoxes

Syndrome Extraction 1: ZZI

circuit.add_circbox(syndrome_box, [ancilla[0], data[0], data[1], syndrome[0]])

Syndrome Extraction 2: IZZ

circuit.add_circbox(syndrome_box, [ancilla[0], data[1], data[2], syndrome[1]])
DecomposeBoxes().apply(circuit)

In the cell below, classically-conditioned operations (pytket.circuit.OpType.X) are performed using pytket.circuit.logic_exp.reg_eq. The function, reg_eq, checks if the measurement output stored in the classical register is equivalent to a particular value. If the equiavlence check is True, the desired operation is applied to the specified qubit.

The X operation is applied to qubit data[0]. The reg_ex checks if the classical output is 01 (little endian - syndrome[0] = 1 and syndrome[1] = 0).

from pytket.circuit.logic_exp import reg_eq
circuit.X(data[0], condition=reg_eq(syndrome, 1))

The X operation is applied to qubit data[2]. The reg_ex checks if the classical output is 10 (syndrome[0] = 0 and syndrome[1] = 1). If there is no error from the first syndrome measurement (syndrome[0] = 0), but error from the second syndrome measurement (syndrome[1] = 1), then there is a bitflip on the qubit data[2].

if(syndromes==2) -> 01 -> check 1 bad -> X on qubit 2

circuit.X(data[2], condition=reg_eq(syndrome, 2))

The X operation is applied to qubit data[1]. The reg_ex checks if the classical output is 11 (syndrome[0] = 1 and syndrome[1] = 1). If there is error from the first syndrome measurement (syndrome[0] = 1) and error from the second syndrome measurement (syndrome[1] = 1), then there is a bitflip on the qubit data[1].

if(syndromes==3) -> 11 -> check 1 and 2 bad -> X on qubit 1

circuit.X(data[1], condition=reg_eq(syndrome, 3))

Finally, measurement gates are added to the data qubit register.

Measure out data qubits

circuit.Measure(data[0], output[0])
circuit.Measure(data[1], output[1])
circuit.Measure(data[2], output[2])

The display tool in pytket is used to visualise the circuit in jupyter.

from pytket.circuit.display import render_circuit_jupyter
render_circuit_jupyter(circuit)

Select Device

Login to the Quantinuum API using your credentials and check the device status.

from pytket.extensions.quantinuum import QuantinuumBackend
machine = "H1-1E"
backend = QuantinuumBackend(device_name=machine)
backend.login()

Circuit Compilation

pytket includes many features for optimizing circuits. This includes reducing the number of gates where possible and resynthesizing circuits for a quantum computer’s native gate set. See the pytket User Manual for more information on all the options that are available.

Here the circuit is compiled with get_compiled_circuit, which includes optimizing the gates and resynthesizing the circuit to Quantinuum’s native gate set. The optimisation_level sets the level of optimisation to perform during compilation, check Default Compilation in the pytket-quantinuum documentation for more details.

compiled_circuit = backend.get_compiled_circuit(circuit, optimisation_level=2)
render_circuit_jupyter(compiled_circuit)

Submit and Run the Circuit

n_shots = 100
h1_cost = backend.cost(compiled_circuit, n_shots=n_shots, syntax_checker="H1-1SC")
print(f"Cost: {h1_cost} HQC")
handle = backend.process_circuit(compiled_circuit, n_shots=n_shots)
print(handle)
status = backend.circuit_status(handle)
print(status)
import json
result = backend.get_result(handle)
with open("pytket_mcmr_example.json", "w") as file:
    json.dump(result.to_dict(), file)

Analyze Results

We will now take the raw results and apply a majority vote to determine how many times we got 0 vs 1.

First, define a majority vote function.

def majority(result):
    """Returns whether the output should be considered a 0 or 1."""
    if result.count(0) > result.count(1):
        return 0
    elif result.count(0) < result.count(1):
        return 1
    else:
        raise Exception("count(0) should not equal count(1)")

Now process the output:

result_output_cnts = result.get_counts([output[i] for i in range(output.size)])
result_output_cnts

Here, determine how many times 0 vs 1 was observed using the majority vote function.

zeros = 0  # Counts the shots with majority zeros
ones = 0  # Counts the shots with majority ones
for out in result_output_cnts:
    m = majority(out)
    if m == 0:
        zeros += result_output_cnts[out]
    else:
        ones += result_output_cnts[out]

A logical zero was initialized, so our error rate should be number of ones / total number of shots: ones/shots

p = ones / n_shots
print(f"The error-rate is: p = {p}")