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:
\(\hat{Z}_{q[0]} \hat{Z}_{q[1]} \hat{I}_{q[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}")