3.5. Validation of Syndrome Extraction Circuits

import loom.validator
from loom.eka import Block, Stabilizer, PauliOperator, Channel, Circuit

This notebook demonstrates how to use the loom.validator for syndrome extraction circuits describing the full process.

3.5.1. Steane Code Reichardt circuit

Reichardt proposed a circuit that measures in a FT manner the syndrome of Steane’s code.

Reference: https://arxiv.org/abs/1804.06995

Code definition

The code stabilizers are:

Steane Code

Note that the indexing we will be using starts from 0 !

Let’s create the Block object describing this code.

Simultaneously, let’s keep the stabilizers in a format we can access them later on.

# Define qubit layout
# For demonstration purposes we use repeating tuple rather than a simple int
qubit_labels = [(0,), (1,), (2,), (3,), (4,), (5,), (6,)]

# Define stabilizers within a dictionary
stabs_dict: dict[str, list[Stabilizer]] = {
    "X": [],
    "Z": [],
}


# First set of X, Z stabilizers
stab_qub_labels = [(0,), (2,), (4,), (6,)]
# make the stabilizers
stabs_dict["X"] += [Stabilizer("XXXX", stab_qub_labels)]
stabs_dict["Z"] += [Stabilizer("ZZZZ", stab_qub_labels)]

# Second set of X, Z stabilizers
stab_qub_labels = [(1,), (2,), (5,), (6,)]
# make the stabilizers
stabs_dict["X"] += [Stabilizer("XXXX", stab_qub_labels)]
stabs_dict["Z"] += [Stabilizer("ZZZZ", stab_qub_labels)]

# Third set of X, Z stabilizers
stab_qub_labels = [(3,), (4,), (5,), (6,)]
# make the stabilizers
stabs_dict["X"] += [Stabilizer("XXXX", stab_qub_labels)]
stabs_dict["Z"] += [Stabilizer("ZZZZ", stab_qub_labels)]

# Define the Block
steane_block = Block(
    stabilizers=[s for s_list in stabs_dict.values() for s in s_list],
    logical_x_operators=[PauliOperator("X" * 7, qubit_labels)],
    logical_z_operators=[PauliOperator("Z" * 7, qubit_labels)],
)

Circuit testing

The proposed circuit that measures half of those stabilizers is:

Reichardt Circuit

Note that the indexing we will be using starts from 0 !

# Define data qubits and necessary mappings
n_data = steane_block.n_data_qubits
dqubit_channels = [Channel(label=str(qub)) for qub in steane_block.data_qubits]


# Define ancilla qubits
n_ancillas = 3
aqubit_channels = [Channel(label=f"ancilla_qubit_{i}") for i in range(n_ancillas)]

# Define classical channels for ancilla qubits
c_channels = [Channel(type="classical", label=f"c_{i}_0") for i in range(n_ancillas)]
# Construct the circuit
syndrome_extraction_circ = Circuit(
    "first_round",
    circuit=[
        [Circuit("H", channels=[aqubit_channels[0]])],
        [
            Circuit("CNOT", channels=[aqubit_channels[0], dqubit_channels[4]]),
            Circuit("CNOT", channels=[dqubit_channels[6], aqubit_channels[1]]),
            Circuit("CNOT", channels=[dqubit_channels[5], aqubit_channels[2]]),
        ],
        [Circuit("CNOT", channels=[aqubit_channels[0], aqubit_channels[2]])],
        [
            Circuit("CNOT", channels=[aqubit_channels[0], dqubit_channels[0]]),
            Circuit("CNOT", channels=[dqubit_channels[4], aqubit_channels[1]]),
            Circuit("CNOT", channels=[dqubit_channels[1], aqubit_channels[2]]),
        ],
        [
            Circuit("CNOT", channels=[aqubit_channels[0], dqubit_channels[2]]),
            Circuit("CNOT", channels=[dqubit_channels[3], aqubit_channels[1]]),
            Circuit("CNOT", channels=[dqubit_channels[6], aqubit_channels[2]]),
        ],
        [Circuit("CNOT", channels=[aqubit_channels[0], aqubit_channels[1]])],
        [
            Circuit("CNOT", channels=[aqubit_channels[0], dqubit_channels[6]]),
            Circuit("CNOT", channels=[dqubit_channels[5], aqubit_channels[1]]),
            Circuit("CNOT", channels=[dqubit_channels[2], aqubit_channels[2]]),
        ],
        [Circuit("H", channels=[aqubit_channels[0]])],
        [
            Circuit("Measurement", channels=[aqubit_channels[0], c_channels[0]]),
            Circuit("Measurement", channels=[aqubit_channels[1], c_channels[1]]),
            Circuit("Measurement", channels=[aqubit_channels[2], c_channels[2]]),
        ],
    ],
)
# We need to specify the stabilizers that were measured in the order that they
# were measured.
# Dictionary has as key the index of the measurement operation (considering only
# measurement operations) and as value the stabilizer.
measurement_to_stabilizer_map = {
    c_channels[0].label: stabs_dict["X"][0],
    c_channels[1].label: stabs_dict["Z"][2],
    c_channels[2].label: stabs_dict["Z"][1],
}
# Run loom.validator and check result
debug_dict = loom.validator.is_syndrome_extraction_circuit_valid(
    syndrome_extraction_circ,
    steane_block,
    measurement_to_input_stabilizer_map=measurement_to_stabilizer_map,
)

print(debug_dict)
DebugData: valid = True

Sanity checks

1. Indicate that other stabilizers were measured

# Reminder of stabs measured
measurement_to_stabilizer_map = {
    c_channels[0].label: stabs_dict["X"][0],
    c_channels[1].label: stabs_dict["Z"][2],
    c_channels[2].label: stabs_dict["Z"][1],
}

measurement_to_stabilizer_map_wrong = {
    c_channels[0].label: stabs_dict["X"][0],
    c_channels[1].label: stabs_dict["Z"][0],
    c_channels[2].label: stabs_dict["Z"][1],
}
# Run loom.validator and check result
debug_dict = loom.validator.is_syndrome_extraction_circuit_valid(
    syndrome_extraction_circ,
    steane_block,
    measurement_to_input_stabilizer_map=measurement_to_stabilizer_map_wrong,
)

print(debug_dict)
DebugData: valid = False
----------------------------------------
StabilizerMeasurementCheck: valid = False
message: Some measurement(s) did not measure the assigned stabilizer.
output: 
- Expected vs Measured Stabilizers:
Measurement c_1_0: Expected Z_(0,) Z_(2,) Z_(4,) Z_(6,), Measured: Z_(3,) Z_(4,) Z_(5,) Z_(6,)

2. Adding CNOT between two data qubits

# CNOT between data qubits
cy_d0d1 = Circuit(
    "CNOT",
    channels=[dqubit_channels[0], dqubit_channels[6]],
)

# Construct full circuit
circuit_w_cnot = Circuit(
    "circuit_with_added_cnot",
    circuit=syndrome_extraction_circ.circuit + ((cy_d0d1,),),
)
# Run loom.validator and check result
debug_dict = loom.validator.is_syndrome_extraction_circuit_valid(
    circuit_w_cnot,
    steane_block,
    measurement_to_input_stabilizer_map=measurement_to_stabilizer_map,
)

print(debug_dict)
DebugData: valid = False
----------------------------------------
CodeStabilizerCheck: valid = False
message: Some code stabilizer(s) were not found in the output.
output: 
- Missing Stabilizers:
X_(0,) X_(2,) X_(4,) X_(6,)
Z_(0,) Z_(2,) Z_(4,) Z_(6,)
Z_(1,) Z_(2,) Z_(5,) Z_(6,)
Z_(3,) Z_(4,) Z_(5,) Z_(6,)
----------------------------------------
LogicalOperatorCheck: valid = False
message: Some logical states were not transformed correctly.
output: 
- Failed Logical State Transformations:
Initial: LogicalState(('+Z0',)), Allowed Finals: (LogicalState(('+Z0',)),), Actual: None
Initial: LogicalState(('+X0',)), Allowed Finals: (LogicalState(('+X0',)),), Actual: None

3. Performing a logical operation

#  Logical X
x_ops = tuple(
    (
        Circuit(
            "X",
            channels=[dqubit_channels[i]],
        ),
    )
    for i in range(7)
)

# Construct full circuit
circuit_w_log_x = Circuit(
    "circuit_with_log_x",
    circuit=syndrome_extraction_circ.circuit + x_ops,
)
# Run loom.validator and check result
debug_dict = loom.validator.is_syndrome_extraction_circuit_valid(
    circuit_w_log_x,
    steane_block,
    measurement_to_stabilizer_map,
)

print(debug_dict)
DebugData: valid = False
----------------------------------------
LogicalOperatorCheck: valid = False
message: Some logical states were not transformed correctly.
output: 
- Failed Logical State Transformations:
Initial: LogicalState(('+Z0',)), Allowed Finals: (LogicalState(('+Z0',)),), Actual: LogicalState(('-Z0',))

4. Applying a destabilizer operation

# We can see for ourselves that Z3Z7 (reichard indexing) -> Z2Z6 our indexing
# is the destabilizer of the first stabilizer (commuting with all stab + log ops)
z_destab_ops = tuple(
    (
        Circuit(
            "Z",
            channels=[dqubit_channels[i]],
        ),
    )
    for i in [2, 6]
)

# Construct full circuit
circuit_w_destab = Circuit(
    "circuit_w_destab",
    circuit=syndrome_extraction_circ.circuit + z_destab_ops,
)
# Run loom.validator and check result
debug_dict = loom.validator.is_syndrome_extraction_circuit_valid(
    circuit_w_destab,
    steane_block,
    measurement_to_stabilizer_map,
)

print(debug_dict)
DebugData: valid = False
----------------------------------------
CodeStabilizerCheck: valid = False
message: Some code stabilizer(s) were not found in the output.
output: 
- Stabilizers with Incorrect Parity:
X_(3,) X_(4,) X_(5,) X_(6,)

3.5.2. Repetition code with binary operations

Repetition Code Circuit

We shall be using 2 extra ancilla qubits which will act as classical registers.

XOR will be found by performing 2 CNOTS after measuring the ancilla qubits.

# Define Block

rep_code_block = Block(
    stabilizers=[Stabilizer("ZZ", [(0,), (1,)]), Stabilizer("ZZ", [(1,), (2,)])],
    logical_x_operators=[PauliOperator("XXX", [(0,), (1,), (2,)])],
    logical_z_operators=[PauliOperator("ZZZ", [(0,), (1,), (2,)])],
)
# Define circuit
# Define data qubits and necessary mappings
n_data = rep_code_block.n_data_qubits
dqubits = [Channel(label=str(q)) for q in rep_code_block.data_qubits]

# Define ancilla qubits
n_ancillas = 3
aqubit_channels = [Channel(label=f"ancilla_qubit_{i}") for i in range(n_ancillas)]
# and classical channels for ancilla qubits
ac_channels = [
    Channel(type="classical", label=f"c_{a_chan.label}") for a_chan in aqubit_channels
]

# Define ancillas that act as classical register
n_creg_qubits = 2
creg_qubit_channels = [
    Channel(label=f"ancilla_as_classical_{i}") for i in range(n_creg_qubits)
]
# and classical channels for these classical register qubits
cregc_channels = [
    Channel(type="classical", label=f"c_{creg_chan.label}")
    for creg_chan in creg_qubit_channels
]
# Construct circuit
rep_code_syndrome_extraction = Circuit(
    "rep_code_via_ghz",
    circuit=[
        Circuit("H", channels=[aqubit_channels[0]]),
        Circuit("CNOT", channels=[aqubit_channels[0], aqubit_channels[1]]),
        Circuit("CNOT", channels=[aqubit_channels[0], aqubit_channels[2]]),
        *(Circuit("CNOT", channels=[dqubits[i], aqubit_channels[i]]) for i in range(3)),
        *(
            Circuit("Measurement", channels=[aqubit_channels[i], ac_channels[i]])
            for i in range(3)
        ),
        *(
            Circuit("CNOT", channels=[aqubit_channels[i], creg_qubit_channels[j]])
            for j in range(2)
            for i in range(j, j + 2)
        ),
        *(
            Circuit("Measurement", channels=[creg_qubit_channels[i], cregc_channels[i]])
            for i in range(2)
        ),
    ],
)
# Define where the stabilizers will be found
# The first three measurements are the ancillas
# The fourth and fifth measurements are the stabilizer values
rep_measurement_to_stabilizer_map = {
    cregc_channels[0].label: rep_code_block.stabilizers[0],
    cregc_channels[1].label: rep_code_block.stabilizers[1],
}
# Run loom.validator and check result
debug_dict = loom.validator.is_syndrome_extraction_circuit_valid(
    rep_code_syndrome_extraction,
    rep_code_block,
    rep_measurement_to_stabilizer_map,
)

print(debug_dict)
DebugData: valid = True