Source code for loom_repetition_code.applicator.shrink

"""
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=duplicate-code
from loom.eka import ChannelType, Circuit, PauliOperator, Stabilizer
from loom.eka.operations import Shrink
from loom.eka.utilities import Direction
from loom.interpreter import InterpretationStep
from loom.interpreter.utilities import Cbit

from ..code_factory import RepetitionCode


# pylint: disable=too-many-arguments, too-many-positional-arguments, too-many-locals
[docs] def shrink_consistency_check( interpretation_step: InterpretationStep, operation: Shrink ) -> RepetitionCode: """ Check the consistency of the shrink operation. Parameters ---------- interpretation_step : InterpretationStep InterpretationStep containing the block to shrink. operation : Shrink Shrink operation description. Returns ------- RepetitionCode Block to shrink. """ # Extract Block block = interpretation_step.get_block(operation.input_block_name) # Check valid Block type if not isinstance(block, RepetitionCode): raise ValueError( f"This shrink operation is not supported for {type(block)} blocks." ) # Check shrink size will leave more than one data qubit in the block if operation.length >= (len(block.data_qubits) - 1): raise ValueError("Shrink size is too large.") # Check shrink is applied in the correct direction if operation.direction in [Direction.BOTTOM, Direction.TOP]: raise ValueError( "Repetition code does not support " f"shrinking in the {operation.direction} direction." ) return block
[docs] def get_qubits_to_measure( block: RepetitionCode, direction: Direction, length: int ) -> list[tuple[int, int]]: """Find the qubits to measure during the shrink operation. Parameters ---------- block : RepetitionCode Block to shrink. direction : Direction Direction of the shrink operation. length : int Length of the shrink operation. Returns ------- list[tuple[int, int]] List containing the qubits to measure. """ # Extract qubit in the boundary to be shrunk boundary_qubit = block.boundary_qubits(direction=direction) # Generate a list of vectors shifting the new left boundary qubit after shrink match direction: case Direction.LEFT: shift_vectors = [(i, 0) for i in range(length)] case Direction.RIGHT: shift_vectors = [(-i, 0) for i in range(length)] # Extract qubits to measure, using the boundary as a reference qubits_to_measure = [ tuple(coord1 + coord2 for coord1, coord2 in zip(boundary_qubit, shift_vect)) for shift_vect in shift_vectors ] return qubits_to_measure # type: ignore
[docs] def find_shrink_circuit( interpretation_step: InterpretationStep, check_type: str, qubits_to_measure: list[tuple[int, int]], circuit_name: str, ) -> tuple[Circuit, list[Cbit]]: """Generate a circuit to measure the qubits to be shrunk. Qubits are measured in the Z basis. If the check type is Z, the qubits are measured directly, else they are applied a Hadamard gate before measurement. Parameters ---------- interpretation_step : InterpretationStep Interpretation step containing the block to shrink. check_type : str Type of the stabilizer operators. qubits_to_measure : list[tuple[int,int]] List of qubits to measure. circuit_name : str Name of the circuit. Returns ------- tuple[Circuit, list[Cbit]] Tuple containing the shrink circuit and the classical bits associated with the measured qubits. """ # Generate fresh classical bits and extract the channels cbits = [interpretation_step.get_new_cbit_MUT(f"c_{q}") for q in qubits_to_measure] cbit_channels = [ interpretation_step.get_channel_MUT( f"{cbit[0]}_{cbit[1]}", channel_type=ChannelType.CLASSICAL ) for cbit in cbits ] # Create sequence of measurements for every measured data qubit measure_circuit_seq = [ [ Circuit( "Measurement", channels=[interpretation_step.get_channel_MUT(q), cbc], ) for q, cbc in zip(qubits_to_measure, cbit_channels, strict=True) ] ] if check_type == "X": # If phaseflip code qubits can already be measured shrink_circuit_list = measure_circuit_seq else: # If bitflip code qubits need to be Hadamard transformed before measurement basis_change_circuit_seq = [ [ Circuit("H", channels=[interpretation_step.get_channel_MUT(qb)]) for qb in qubits_to_measure ] ] # Add basis change before measurement shrink_circuit_list = basis_change_circuit_seq + measure_circuit_seq # Construct shrink circuit shrink_circuit = Circuit( name=circuit_name, circuit=shrink_circuit_list, ) return shrink_circuit, cbits
[docs] def find_new_stabilizers( block: RepetitionCode, qubits_to_measure: list[tuple[int, int]] ) -> list[Stabilizer]: """Find the new set of stabilizers after the shrink operation. We also compute the uuids of the stabilizers that need to be removed, if the block shrunk from the left, as they will be used to update one of the logical operators. Parameters ---------- block : RepetitionCode Block to shrink. qubits_to_measure : list[tuple[int,int]] List of qubits to measure. Returns ------- new_stabs : list[Stabilizer] New set of stabilizers after the shrink operation. """ stabs_to_remove = [ stab for stab in block.stabilizers if any(qb in qubits_to_measure for qb in stab.data_qubits) ] # Combine the stabilizers to get the new set of stabilizers new_stabs = list(set(block.stabilizers) - set(stabs_to_remove)) return new_stabs
[docs] def get_logical_operator_and_updates( interpretation_step: InterpretationStep, block: RepetitionCode, check_type: str, is_left: bool, qubits_to_measure: list[tuple[int, int]], cbits: list[Cbit], ) -> tuple[ list[list[PauliOperator]], list[dict[str, tuple[str, ...]]], list[dict[str, tuple[Cbit, ...]]], ]: """ Generate new logical operators and their respective evolution and updates to be added to the interpretation step. We treat the two logicals separately in as "long" and "short". The long corresponds to the one covering the entire chain, with type opposite to the check type. The short corresponds to the one covering the left boundary qubit by default, with type equal to the check type. Parameters ---------- interpretation_step : InterpretationStep The interpretation step. block : RepetitionCode Block to shrink. check_type : str Type of the stabilizer operators. is_left : bool Boolean defining whether shrink operations is performed from right or left. qubits_to_measure : list[tuple[int,int]] List of qubits to measure. cbits : list[Cbit] List of classical bits associated with measured qubits. Returns ------- tuple[ list[list[PauliOperator]], list[dict[str,tuple[str,...]]], list[dict[str,tuple[Cbit,...]]] ] Tuple containing the new logical operators, their evolution and updates. """ other_check_type = "X" if check_type == "Z" else "Z" # Find remaining qubits remaining_qubits = list(set(block.data_qubits) - set(qubits_to_measure)) # Extract old long and short operators old_logs = [block.logical_x_operators[0], block.logical_z_operators[0]] old_short_log, old_long_log = sorted(old_logs, key=lambda x: len(x.data_qubits)) # Check if the short logical qubit inside the qubits to measure to_shift = old_short_log.data_qubits[0] in qubits_to_measure # Define new long logical operator and updates new_long_log = PauliOperator( pauli=other_check_type * len(remaining_qubits), data_qubits=remaining_qubits ) long_log_updates = {new_long_log.uuid: tuple(cbits)} long_log_evolution = {new_long_log.uuid: (old_long_log.uuid,)} # Define new short logical operator and updates # If the short logical operator is inside the qubits to measure, shift it if to_shift: selector_function = min if is_left else max new_qubit = selector_function(remaining_qubits, key=lambda x: x[0]) new_short_log, stabs_required = block.get_shifted_equivalent_logical_operator( new_qubit ) id_stabs_required = [stab.uuid for stab in stabs_required] short_log_evolution = { new_short_log.uuid: tuple([old_short_log.uuid] + id_stabs_required) } # Add to the updates the cbits associated with the measured qubits and the # stabilizers required for the shift. # These cbits are the last syndromes measured for those stabilizers. cbits = interpretation_step.retrieve_cbits_from_stabilizers( stabs_required, block ) short_log_updates = {new_short_log.uuid: cbits} else: new_short_log = old_short_log short_log_evolution = {} short_log_updates = {} # New logicals new_logs = [[new_long_log], [new_short_log]] # Updates and evolution log_evolution = [long_log_evolution, short_log_evolution] log_updates = [long_log_updates, short_log_updates] return new_logs, log_evolution, log_updates
[docs] def shrink( interpretation_step: InterpretationStep, operation: Shrink, same_timeslice: bool, debug_mode: bool, ) -> InterpretationStep: """ Shrink a Repetition Code chain in the left or right specified direction. The algorithm is the following: - A.) DATA QUBITS - A.1) Find measured data qubits during shrink - B.) CIRCUIT - B.1) Generate shrink circuit and classical bits - B.2) Add circuit to the interpretation step - C.) - STABILIZERS - C.1) Find new set of stabilizers - D.) LOGICAL OPERATORS - D.1) Extract logical operators and updates - D.2) Update the logical operator history - E.) NEW BLOCK AND NEW INTERPRETATION STEP - E.1) Create the new block - E.2) Update the block history Parameters ---------- interpretation_step : InterpretationStep Interpretation step containing the block to shrink. operation : Shrink Shrink operation description. same_timeslice : bool Flag indicating whether the operation is part of the same timestep as the previous operation. debug_mode : bool Flag indicating whether the interpretation should be done in debug mode. Activating debug mode will enable commutation validation for Block. Returns ------- InterpretationStep Interpretation step after the shrink operation. """ # Check consistency of the shrink operation block = shrink_consistency_check(interpretation_step, operation) # Get shrink direction is_left = operation.direction == Direction.LEFT # Extract stabilizer type check_type = block.check_type # A) DATA QUBITS # A.1) Find measured data qubits during shrink qubits_to_measure = get_qubits_to_measure( block, direction=operation.direction, length=operation.length ) # B) CIRCUIT # B.1) Generate shrink circuit and classical bits circuit_name = ( f"shrink {block.unique_label} by {operation.length} from {operation.direction}" ) shrink_circuit, cbits = find_shrink_circuit( interpretation_step, check_type, qubits_to_measure, circuit_name ) # B.2) Add circuit to the interpretation step interpretation_step.append_circuit_MUT(shrink_circuit, same_timeslice) # C) STABILIZERS # C.1) Find new set of stabilizers new_stabilizers = find_new_stabilizers(block, qubits_to_measure) # D) LOGICAL OPERATORS # D.1) Extract logical operators and updates new_logs, log_evolution, log_updates = get_logical_operator_and_updates( interpretation_step, block, check_type, is_left, qubits_to_measure, cbits, ) # D.2) Update the logical operator history ordering = 1 if check_type == "Z" else -1 new_log_x_ops, new_log_z_ops = new_logs[::ordering] logical_x_evolution, logical_z_evolution = log_evolution[::ordering] logical_x_updates, logical_z_updates = log_updates[::ordering] # Update the logical operator evolution interpretation_step.logical_x_evolution.update(logical_x_evolution) interpretation_step.logical_z_evolution.update(logical_z_evolution) # Update the logical operator updates for op_id, measurements in logical_x_updates.items(): interpretation_step.update_logical_operator_updates_MUT( "X", op_id, measurements, True, ) for op_id, measurements in logical_z_updates.items(): interpretation_step.update_logical_operator_updates_MUT( "Z", op_id, measurements, True, ) # E) NEW BLOCK # E.1) Create the new block new_block = RepetitionCode( stabilizers=new_stabilizers, logical_x_operators=new_log_x_ops, logical_z_operators=new_log_z_ops, unique_label=block.unique_label, skip_validation=not debug_mode, ) # E.2) Update the block history interpretation_step.update_block_history_and_evolution_MUT( new_blocks=(new_block,), old_blocks=(block,), ) return interpretation_step