Toric CodeΒΆ
This example is a larger Guppy program that implements a toric-code style workflow with syndrome extraction, decoding, and conditional correction.
Source file: guppy_examples/general/toric_code.py
r"""Even-distanced [[16, 2, 4]] code"""
from guppylang import guppy
from guppylang.std.builtins import comptime, result
from guppylang.std.quantum import cx, h, measure, qubit, z
stabilizer_indices_z = [
[0, 1, 4, 5],
[2, 3, 6, 7],
[5, 6, 9, 10],
[7, 4, 11, 8],
[8, 9, 12, 13],
[10, 11, 14, 15],
[13, 14, 1, 2],
[15, 12, 3, 0],
]
stabilizer_indices_x = [
[1, 2, 5, 6], # round 2
[3, 0, 7, 4], # round 2
[4, 5, 8, 9], # round 1
[6, 7, 10, 11], # round 1
[9, 10, 13, 14], # round 2
[11, 8, 15, 12], # round 2
[12, 13, 0, 1], # round 1
[14, 15, 2, 3], # round 1
]
index_0 = [2, 3, 6, 7]
index_1 = [0, 1, 4, 5]
NULL_VALUE = -1
@guppy.comptime
def main() -> None:
data_qubits = [qubit() for _ in range(16)]
stab_indices_x = stabilizer_indices_x
index_0_guppy = index_0
syndromes_0 = [False for _ in range(4)]
for i in range(4):
syndromes_0[i] = syndrome_extraction(
data_qubits, stab_indices_x[index_0_guppy[i]]
)
result("s0", bool_array_as_int(syndromes_0))
indices = decoder_0(syndromes_0)
correction(indices, data_qubits)
index_1_guppy = index_1
syndromes_1 = [False for _ in range(4)]
for i in range(4):
syndromes_1[i] = syndrome_extraction(
data_qubits, stab_indices_x[index_1_guppy[i]]
)
result("s1", bool_array_as_int(syndromes_1))
indices = decoder_1(syndromes_1)
correction(indices, data_qubits)
result("c", bool_array_as_int([measure(q) for q in data_qubits]))
def syndrome_extraction(q: list[qubit], indices: list[int]) -> bool:
ancilla = qubit()
for i in indices:
h(q[i])
cx(q[i], ancilla)
h(q[i])
return measure(ancilla)
@guppy
def dynamic_branching(condition: bool, value: int) -> int:
if condition:
return value
return comptime(NULL_VALUE)
def decoder_0(syndromes: list[bool]) -> list[int]:
null = NULL_VALUE
stabilizers = stabilizer_indices_x
indices = [null, null, null, null]
stab = index_0
for j in range(4):
syn = syndromes[j]
indices[j] = dynamic_branching(syn, stabilizers[stab[j]][2])
return indices
@guppy
def dynamic_branching2(syndrome_result: int) -> tuple[int, int, int, int]:
null = comptime(NULL_VALUE)
idx0 = null
idx1 = null
idx2 = null
idx3 = null
if syndrome_result == 12: # 2^2 + 2^3 = (0, 0, 1, 1)
idx0 = 10
idx1 = 11
elif syndrome_result == 10: # 2^1 + 2^3 = (0, 1, 0, 1)
idx0 = 4
idx1 = 8
elif syndrome_result == 6: # 2^1 + 2^2 = (0, 1, 1, 0)
idx0 = 0
idx1 = 13
elif syndrome_result == 9: # 2^0 + 2^3 = (1, 0, 0, 1)
idx0 = 2
idx1 = 15
elif syndrome_result == 5: # 2^0 + 2^2 = (1, 0, 1, 0)
idx0 = 5
idx1 = 9
elif syndrome_result == 3: # 2^0 + 2^1 = (1, 1, 0, 0)
idx0 = 6
idx1 = 7
elif syndrome_result == 15: # 2^0 + 2^1 + 2^2 + 2^3 = (1, 1, 1, 1)
idx0 = 2
idx1 = 3
idx2 = 14
idx3 = 15
return idx0, idx1, idx2, idx3
def decoder_1(syndromes: list[bool]) -> list[int]:
syndrome_result = 0
for i in range(4):
syndrome_result += dynamic_branching(syndromes[i], 2**i)
return list(dynamic_branching2(syndrome_result))
def correction(indices: list[int], data_qubits: list[qubit]) -> None:
for i, _ in enumerate(data_qubits):
data_q = data_qubits[i]
for idx in indices:
correct_qubit = need_to_correct(i, idx)
correct_if(correct_qubit, data_q)
@guppy
def need_to_correct(idx: int, to_correct_idx: int) -> bool:
return idx == to_correct_idx
@guppy
def correct_if(needed: bool, to_correct_q: qubit) -> None:
if needed:
z(to_correct_q)
def bool_array_as_int(bool_arr: list[bool], big_endian: bool = True) -> int:
"""Convert a comptime bool array to integer"""
integer_value = 0
if big_endian:
for b in bool_arr:
integer_value = (integer_value << 1) | int(b)
else:
for i, b in enumerate(bool_arr):
integer_value = int(b) << i
return integer_value