"""
Copyright 2024 Entropica Labs Pte Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
# pylint: disable=too-many-locals, duplicate-code
from __future__ import annotations
from uuid import uuid4
from functools import cached_property
from pydantic.dataclasses import dataclass
from loom.eka import (
Block,
Lattice,
LatticeType,
PauliOperator,
Stabilizer,
SyndromeCircuit,
Circuit,
Channel,
)
from loom.eka.utilities import dataclass_config
[docs]
@dataclass(config=dataclass_config)
class FiveQubitPerfectCode(Block):
"""
Represents the five qubit perfect code, also known as the
Laflamme code, as a subclass of ``Block``.
"""
[docs]
@classmethod
def create(
cls,
lattice: Lattice,
unique_label: str | None = None,
position: tuple[int, int] = (0, 0),
skip_validation: bool = False,
) -> FiveQubitPerfectCode:
"""Create a `Block` object for a five qubit perfect code/Laflamme code block.
The five qubit code is a [[5, 1, 3]] quantum error-correcting code that can
correct arbitrary single-qubit errors on any one qubit, and detect errors on any
two qubits. There are 4 stabilizers of support 4 that are symmetric under cyclic
permutation. The block is defined on a pentagonal lattice.
NOTE: There are two stabilizers labelled with "XZZX" but with different data
qubits. The two correspond to XZZXI and IXZZX stabilizers, but as the stabilizer
class does not accept "I" as an input, these two become labelled identically.
The circuit implementation is not fault tolerant, and is based on the stabilizer
circuit from https://en.wikipedia.org/wiki/Five-qubit_error_correcting_code
Parameters
----------
lattice : Lattice
Lattice on which the block is defined. The qubit indices depend on the type
of lattice.
unique_label : str, optional
Label for the block. It must be unique among all blocks in the initial CRD.
If no label is provided, a unique label is generated automatically using the
uuid module.
position : tuple[int, ...], optional
Position of the top left corner of the block on the lattice,
by default (0, 0).
skip_validation : bool, optional
Skip validation of the block object, by default False.
Returns
-------
Block
Block object for the five qubit perfect code
"""
# Input validation
if lattice.lattice_type != LatticeType.POLY_2D:
raise ValueError(
"The creation of five qubit perfect code blocks is "
"currently only supported for 2D pentagonal lattices. Instead "
f"the lattice is of type {lattice.lattice_type}."
)
if lattice.unit_cell_size != 9:
raise ValueError(
"The five qubit perfect code block requires a lattice with exactly 5 "
f"qubits and 4 ancilla in each unit cell. Got {lattice.unit_cell_size} "
"qubits and ancilla instead."
)
if not isinstance(position, tuple) or any(
not isinstance(x, int) for x in position
):
raise ValueError(
f"`position` must be a tuple of integers. Got '{position}' instead."
)
if unique_label is None:
unique_label = str(uuid4())
# Define the stabilizers
looped_stabilizer_supports = [(0, 0, i % 5) for i in range(8)]
stab_labels = ["XZZX", "XZZX", "XXZZ", "ZXXZ"]
stabilizers = [
Stabilizer(
pauli=stab_labels[i],
data_qubits=sorted(looped_stabilizer_supports[i : i + 4]),
ancilla_qubits=[(0, 0, 5 + i)],
)
for i in range(4)
]
# Define the logical operators
all_data_qubits = [(0, 0, i) for i in range(5)]
logical_x_operator = PauliOperator(
pauli="X" * 5,
data_qubits=all_data_qubits,
)
logical_z_operator = PauliOperator(
pauli="Z" * 5,
data_qubits=all_data_qubits,
)
# Define the syndrome extraction circuits
syndrome_circuits = [
cls.generate_syndrome_extraction_circuits("XZZXI"),
cls.generate_syndrome_extraction_circuits("IXZZX"),
cls.generate_syndrome_extraction_circuits("XIXZZ"),
cls.generate_syndrome_extraction_circuits("ZXIXZ"),
]
# Define the stabilizer to syndrome mapping
stabilizer_to_circuit = {
stab.uuid: syndrome_circuits[i].uuid for i, stab in enumerate(stabilizers)
}
block = cls(
unique_label=unique_label,
stabilizers=stabilizers,
logical_x_operators=[logical_x_operator],
logical_z_operators=[logical_z_operator],
syndrome_circuits=syndrome_circuits,
stabilizer_to_circuit=stabilizer_to_circuit,
skip_validation=skip_validation,
)
if position == (0, 0):
return block
return block.shift(position)
# Instance methods
def __eq__(self, other: FiveQubitPerfectCode) -> bool:
if not isinstance(other, FiveQubitPerfectCode):
raise NotImplementedError(
f"Cannot compare FiveQubitPerfectCode with {type(other)}"
)
return super().__eq__(other)
[docs]
def shift(
self, position: tuple[int, ...], new_label: str | None = None
) -> FiveQubitPerfectCode:
return super().shift(position, new_label)
[docs]
def rename(self, name: str) -> FiveQubitPerfectCode:
return super().rename(name)
@cached_property
def stabilizers_labels(self) -> dict[str, dict[str, tuple[int, ...]]]:
return super().stabilizers_labels