Mid-Circuit Measurement & Reset

The notebook performs a \([[3,1,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. The following system features are consumed:

  • MCMR to reuse ancilla qubits

  • Classicaly-conditioned operations

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\).

Basic Usage

Measurements can added added to a circuit using . A qubit and a bit must be specified. Each OpType.Measure operation must be followed by . Operations can be conditioned based on the value of the bit used store the MCMR result.

from pytket.circuit import Circuit

circuit = Circuit(1, 1)
circuit.H(0)
circuit.Measure(circuit.qubits[0], circuit.bits[0])
circuit.Reset(circuit.qubits[0])
circuit.X(circuit.qubits[0], condition_bits=circuit.bits, condition_value=1)

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")

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].

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].

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])

Nexus Workflow

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

import qnexus as qnx
import datetime

config = qnx.QuantinuumConfig(device_name="H1-Emulator")

project = qnx.projects.get_or_create("MCMR")
qnx.context.set_active_project(project)

name_suffix = datetime.datetime.now().strftime("%Y_%m_%d")
ref_circuit = qnx.circuits.upload(circuit=circuit, name=f"repetition-code-circuit-{name_suffix}", description="TKET circuit for [[3,1,1]] code")

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.

ref_compile_job = qnx.start_compile_job(
    circuits=[ref_circuit],
    optimisation_level=0,
    backend_config=config,
    name=f"compile-job-{name_suffix}"
)
qnx.jobs.wait_for(ref_compile_job)
ref_compiled_circuit = qnx.jobs.results(ref_compile_job)[0].get_output()
compiled_circuit = ref_compiled_circuit.download_circuit()
compiled_circuit.get_commands()

Job Costing and Submission

n_shots = 100
h1_cost = qnx.client.circuits.cost(
    ref_compiled_circuit, 
    n_shots=n_shots, 
    backend_config=qnx.QuantinuumConfig(device_name="H1-1")
)
print(f"Cost: {h1_cost} HQC")
ref_execute_job = qnx.start_execute_job(
    circuits=[ref_compiled_circuit],
    backend_config=config,
    n_shots=[n_shots],
    name=f"execute-job-{name_suffix}"
)
qnx.jobs.wait_for(ref_execute_job)
ref_result = qnx.jobs.results(ref_execute_job)[0]
result = ref_result.download_result()

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}")