Source code for loom_rotated_surface_code.applicator.logical_phase_via_ywall

"""
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.

"""

from loom.interpreter.applicator.code_applicator import measureblocksyndromes
from loom.eka.operations import Grow, Shrink, MeasureBlockSyndromes
from loom.interpreter.interpretation_step import InterpretationStep
from loom.eka.utilities import Direction, Orientation, SyndromeMissingError

from .grow import grow
from .shrink import shrink
from .move_corners import move_corners
from .y_wall_out import y_wall_out
from ..operations import LogicalPhaseViaYwall
from ..code_factory import RotatedSurfaceCode


# pylint: disable=too-many-statements, too-many-locals, too-many-branches
[docs] def logical_phase_via_ywall( interpretation_step: InterpretationStep, operation: LogicalPhaseViaYwall, same_timeslice: bool, debug_mode: bool, ) -> InterpretationStep: """ Apply the logical phase via y-wall operation. The algorithm is the following: - A) Begin LogicalPhaseViaYwall composite operation session - B) Run consistency checks - C) Relocate the x logical operator to the appropriate position - D) Grow the block - E) Move a corner to prepare for the y-wall operation - F) Measure the syndromes - G) Apply the y_wall_out operation - H) Move all topological corners back to their geometric positions and \ potentially grow the block towards the initial position - I) Shrink the block - J) Relocate the x logical operator to the initial position - K) End the composite operation session and append the circuit Note that whenever the logical operator is moved, there may be some extra cycles of measurement of the block syndromes if there are missing syndromes. Parameters ---------- interpretation_step : InterpretationStep The current state of the interpretation step. operation : LogicalPhaseViaYwall The operation to be applied. same_timeslice : bool Whether to apply the operation in the same timeslice or not. debug_mode : bool Whether to run in debug mode or not. Returns ------- InterpretationStep The updated interpretation step after applying the operation. """ # A) Begin LogicalPhaseViaYwall composite operation session interpretation_step.begin_composite_operation_session_MUT( same_timeslice=same_timeslice, circuit_name=( f"LogicalPhaseViaYwall on block {operation.input_block_name} " f"towards the {operation.growth_direction.name}." ), ) # B) Run consistency checks # Check that the RotatedSurfaceCode block is square and has odd dimensions # Check that the topological corners coincide with the geometric corners # Store the initial block and the current block, initial_block is a reference that # will never be modified while current_block will be re-assigned during the # operation. init_block = current_block = check_consistency(interpretation_step, operation) growth_direction = operation.growth_direction init_x_log_operator = init_block.logical_x_operators[0] # Extract operation parameters after the checks distance = init_block.size[0] is_growth_towards_negative = growth_direction in [Direction.LEFT, Direction.TOP] is_init_top_left_bulk_stab_x = ( init_block.upper_left_4body_stabilizer.pauli[0] == "X" ) is_top_left_bulk_stab_x = is_growth_towards_negative ^ is_init_top_left_bulk_stab_x is_x_boundary_horizontal = init_block.x_boundary == Orientation.HORIZONTAL # C) Relocate the x logical operator to the appropriate position # Depending on the top-left bulk stabilizer and the x boundary orientation, # the top-left qubit of the x logical operator needs to be moved to a # different position such that after the block is grown, the x logical operator # is in the correct position for the y-wall operation. match (is_top_left_bulk_stab_x, is_x_boundary_horizontal): case (False, False): # Top-left qubit new_x_log_op_top_left_qubit = max( current_block.data_qubits, key=lambda x: -x[0] - x[1] ) case (True, False): # Top-right qubit new_x_log_op_top_left_qubit = max( current_block.data_qubits, key=lambda x: +x[0] - x[1] ) case (True, True): # Bottom-left qubit new_x_log_op_top_left_qubit = max( current_block.data_qubits, key=lambda x: -x[0] + x[1] ) case (False, True): # Top-left qubit new_x_log_op_top_left_qubit = max( current_block.data_qubits, key=lambda x: -x[0] - x[1] ) # Move the x logical operator to the new position try: interpretation_step, current_block = move_logical( interpretation_step, current_block, new_x_log_op_top_left_qubit, "X" ) except SyndromeMissingError: # Measure block syndromes to get missing syndromes interpretation_step = measureblocksyndromes( interpretation_step, MeasureBlockSyndromes(init_block.unique_label, distance), same_timeslice=False, debug_mode=debug_mode, ) # Move the x logical operator to the new position interpretation_step, current_block = move_logical( interpretation_step, current_block, new_x_log_op_top_left_qubit, "X" ) # D) Grow the block # The block is grown to the right if the x boundary is horizontal and down if # the x boundary is vertical growth_length = distance interpretation_step = grow( interpretation_step, Grow(init_block.unique_label, growth_direction, growth_length), same_timeslice=False, debug_mode=debug_mode, ) current_block = interpretation_step.get_block(init_block.unique_label) # Measure block syndromes to finalize Grow interpretation_step = measureblocksyndromes( interpretation_step, MeasureBlockSyndromes(init_block.unique_label, distance), same_timeslice=False, debug_mode=debug_mode, ) # If the growth direction is towards the negative direction, the Z logical operator # needs to be moved to the new top-left qubit of the block. if is_growth_towards_negative: # Move the Z logical operator to the new position interpretation_step, current_block = move_logical( interpretation_step, current_block, current_block.upper_left_qubit, pauli="Z", ) # E) Move the corner to prepare for the y-wall operation # Depending on the top-left bulk stabilizer and the x boundary orientation, # a different corner qubit needs to be moved to prepare for the y-wall operation. current_block: RotatedSurfaceCode = interpretation_step.get_block( init_block.unique_label ) match (is_top_left_bulk_stab_x, is_x_boundary_horizontal): case (False, False): corner_0_location_directions = [Direction.BOTTOM, Direction.LEFT] corner_0_move_direction = Direction.TOP case (True, False): corner_0_location_directions = [Direction.BOTTOM, Direction.RIGHT] corner_0_move_direction = Direction.TOP case (True, True): corner_0_location_directions = [Direction.BOTTOM, Direction.RIGHT] corner_0_move_direction = Direction.LEFT case (False, True): corner_0_location_directions = [Direction.TOP, Direction.RIGHT] corner_0_move_direction = Direction.LEFT corner_0_location = ( set(current_block.boundary_qubits(corner_0_location_directions[0])) .intersection(current_block.boundary_qubits(corner_0_location_directions[1])) .pop() ) # Find the direction to move the corner qubit corner_0_how_far = distance - 1 # Move the corner corner_args = ((corner_0_location, corner_0_move_direction, corner_0_how_far),) interpretation_step = move_corners( interpretation_step, current_block, corner_args, same_timeslice=False, debug_mode=debug_mode, ) # F) Measure the syndromes # Measure the syndromes of the block such that the block is projected onto # the new stabilizers before the y-wall operation is applied. # Measure for distance - 1 since the move_corners operation has already measured # the stabilizers once. interpretation_step = measureblocksyndromes( interpretation_step, MeasureBlockSyndromes(init_block.unique_label, distance - 1), same_timeslice=False, debug_mode=debug_mode, ) # G) Apply the y_wall_out operation # The y_wall_out operation is applied to implement the main part of the # logical phase via y-wall operation. wall_position = distance wall_orientation = init_block.x_boundary.perpendicular() current_block = interpretation_step.get_block(init_block.unique_label) interpretation_step = y_wall_out( interpretation_step, current_block, wall_position, wall_orientation, same_timeslice=False, debug_mode=debug_mode, ) # H) Move all topological corners back to their geometric positions and ## potentially grow the block towards the initial position # Move the topological corners back to their geometric positions current_block = interpretation_step.get_block(init_block.unique_label) # This is a geometric corner but at the incorrect position. # Depending on the top-left bulk stabilizer and the x boundary orientation, # a different corner qubit needs to be moved to the appropriate direction. match (is_top_left_bulk_stab_x, is_x_boundary_horizontal): case (False, False): corner_1_location_directions = [Direction.BOTTOM, Direction.RIGHT] corner_1_move_direction = Direction.LEFT case (True, False): corner_1_location_directions = [Direction.BOTTOM, Direction.LEFT] corner_1_move_direction = Direction.RIGHT case (True, True): corner_1_location_directions = [Direction.TOP, Direction.RIGHT] corner_1_move_direction = Direction.BOTTOM case (False, True): corner_1_location_directions = [Direction.BOTTOM, Direction.RIGHT] corner_1_move_direction = Direction.TOP corner_1_location = ( set(current_block.boundary_qubits(corner_1_location_directions[0])) .intersection(current_block.boundary_qubits(corner_1_location_directions[1])) .pop() ) corner_1_move_how_far = distance - 1 # Corner 1 arguments corner_1_args = (corner_1_location, corner_1_move_direction, corner_1_move_how_far) # Move corner 2 # This is a topological corner but at the incorrect position. current_block = interpretation_step.get_block(init_block.unique_label) # Find the topological corner that is not located at the geometric corner corner_2_location = ( set(current_block.topological_corners) - set(current_block.geometric_corners) ).pop() # It needs to be moved in the opposite direction of corner 0 corner_2_move_direction = corner_0_move_direction.opposite() corner_2_how_far = distance - 1 # Corner 2 arguments corner_2_args = (corner_2_location, corner_2_move_direction, corner_2_how_far) # Move the corners one-by-one for corner_args in (corner_1_args, corner_2_args): # Move the corner interpretation_step = move_corners( interpretation_step=interpretation_step, block=current_block, corner_args=(corner_args,), same_timeslice=False, debug_mode=debug_mode, ) # Obtain the new current_block current_block = interpretation_step.get_block(init_block.unique_label) # Measure block syndromes to ensure that the moving of corners is FT # move_corners already measured the syndromes once, so we can just measure them # (distance - 1) times interpretation_step = measureblocksyndromes( interpretation_step, MeasureBlockSyndromes(init_block.unique_label, distance - 1), same_timeslice=False, debug_mode=debug_mode, ) # If the growth direction is towards the negative direction, the block needs to # be grown by one unit towards the opposite direction of the growth direction # for the final shrink to leave it at the correct position. # NOTE: This is because y_wall_out has been implemented such that the block is # shrunk from the right or from the bottom. if is_growth_towards_negative: # Grow by one towards the opposite direction of the growth direction interpretation_step = grow( interpretation_step, Grow(init_block.unique_label, growth_direction.opposite(), 1), same_timeslice=False, debug_mode=debug_mode, ) # Measure the syndromes of the block such that the block is projected onto # the new stabilizers after the growth. interpretation_step = measureblocksyndromes( interpretation_step, MeasureBlockSyndromes(init_block.unique_label, distance), same_timeslice=False, debug_mode=debug_mode, ) # I) Shrink the block # The block is shrunk to the original size. Since the y_wall_out operation # shrunk the block by 1, the block needs to be shrunk by 1 unit less than the # growth operation. shrink_direction = growth_direction shrink_length = growth_length - (1 if not is_growth_towards_negative else 0) # Shrink the block interpretation_step = shrink( interpretation_step, Shrink(init_block.unique_label, shrink_direction, shrink_length), same_timeslice=False, debug_mode=debug_mode, ) # Measure the syndromes of the block such that the block is projected onto # the new stabilizers after the shrink. interpretation_step = measureblocksyndromes( interpretation_step, MeasureBlockSyndromes(init_block.unique_label, distance), same_timeslice=False, debug_mode=debug_mode, ) # J) Relocate the x logical operator to the initial position # The x logical operator is moved back to its original position. current_block = interpretation_step.get_block(init_block.unique_label) # Find the top-left corner of the initial x logical operator final_x_log_op_top_left_qubit = max( init_x_log_operator.data_qubits, key=lambda x: -x[0] - x[1] ) # Move the x logical operator back to the original position # Syndromes will always be available, so no need for catching exception here interpretation_step, current_block = move_logical( interpretation_step, current_block, final_x_log_op_top_left_qubit, "X" ) # K) End the operation session and append the circuit logical_phase_circuit = interpretation_step.end_composite_operation_session_MUT() interpretation_step.append_circuit_MUT(logical_phase_circuit, same_timeslice) return interpretation_step
[docs] def check_consistency( interpretation_step: InterpretationStep, operation: LogicalPhaseViaYwall ) -> RotatedSurfaceCode: """ Check that the block is consistent with the logical phase via y-wall operation. Parameters ---------- interpretation_step: InterpretationStep The InterpretationStep that contains the block to which the operation is applied. operation: LogicalPhaseViaYwall The operation to be applied. Returns ------- RotatedSurfaceCode The block to which the operation is applied. Raises ------ ValueError If the block is not square and has does not have odd dimensions. If the topological corners do not coincide with the geometric corners. """ # Obtain the info from interpretation step block = interpretation_step.get_block(operation.input_block_name) if not isinstance(block, RotatedSurfaceCode): raise ValueError( f"The LogicalPhaseViaYwall operation can only be applied to " f"RotatedSurfaceCode blocks. The block {block.unique_label} " f"is a {block.__class__.__name__} block." ) dim_x, dim_y = block.size if dim_x != dim_y or dim_x % 2 != 1: raise ValueError( "Block must be square and have odd dimensions for the logical " "phase via y-wall operation." ) if set(block.topological_corners) != set(block.geometric_corners): raise ValueError( "Topological corners must coincide with geometric corners for " "the logical phase via y-wall operation." ) growth_direction = operation.growth_direction if growth_direction.to_orientation() != block.x_boundary: raise ValueError( f"The growth direction ({growth_direction.name}) must be parallel to the x " f"boundary ({block.x_boundary.name}) of the block for the logical phase " "via y-wall operation." ) return block
[docs] def move_logical( interpretation_step: InterpretationStep, current_block: RotatedSurfaceCode, new_up_left_qubit: tuple[int, int, int], pauli: str, ) -> tuple[InterpretationStep, RotatedSurfaceCode]: """ Move the logical operator to a new position. If the logical operator is already in the new position, no action is taken. If the logical operator is not in the new position, it is moved to the new position and the stabilizers are redefined. We assume that the block is square and its logical operator is a straight line. Can be used for both X and Z logical operators. Parameters ---------- interpretation_step : InterpretationStep The current state of the interpretation step. current_block : RotatedSurfaceCode The block to which the operation is applied. new_up_left_qubit : tuple[int, int, int] The new position of the top-left qubit of the logical x operator. pauli : str The logical operator to be moved. Can be either "X" or "Z". Returns ------- interpretation_step : InterpretationStep The updated interpretation step after applying the operation. current_block : RotatedSurfaceCode The updated block after applying the operation. Raises ------ SyndromeMissingError If syndromes required to redefine the logical x operator are missing. """ if pauli == "X": current_logical_op = current_block.logical_x_operators[0] elif pauli == "Z": current_logical_op = current_block.logical_z_operators[0] else: raise ValueError( f"Logical operator {pauli} is not supported. Only 'X' and 'Z' are " "supported." ) # Use method to obtain the new logical operator and the stabilizers # that need to be redefined new_logical, stabilizers_to_redefine = ( current_block.get_shifted_equivalent_logical_operator( current_logical_op, new_up_left_qubit ) ) # If the logical x operator is not in the new position, we need to redefine # the logical x operator and declare its evolution if stabilizers_to_redefine: # Check that the stabilizers to redefine have syndromes associated # with the input block. This is to ensure that the logical phase # NOTE: At this point a `SyndromeMissingError` is raised if the syndromes are # not associated with the input block. redefinition_stab_cbits = interpretation_step.retrieve_cbits_from_stabilizers( stabilizers_to_redefine, current_block ) # Find the evolution of the logical operators if pauli == "X": interpretation_step.logical_x_evolution[new_logical.uuid] = ( current_block.logical_x_operators[0].uuid, ) + tuple(stab.uuid for stab in stabilizers_to_redefine) # Define the new logical x/z operator # (only the logical x operator is moved) new_logical_x = new_logical new_logical_z = current_block.logical_z_operators[0] else: interpretation_step.logical_z_evolution[new_logical.uuid] = ( current_block.logical_z_operators[0].uuid, ) + tuple(stab.uuid for stab in stabilizers_to_redefine) # Define the new logical z/x operator # (only the logical z operator is moved) new_logical_x = current_block.logical_x_operators[0] new_logical_z = new_logical # Pass the logical updates onto the new logical operator # along with the Cbits from the stabilizers that are need to redefine it interpretation_step.update_logical_operator_updates_MUT( pauli, new_logical.uuid, redefinition_stab_cbits, inherit_updates=True ) new_block = RotatedSurfaceCode( stabilizers=current_block.stabilizers, logical_x_operators=(new_logical_x,), logical_z_operators=(new_logical_z,), syndrome_circuits=current_block.syndrome_circuits, stabilizer_to_circuit=current_block.stabilizer_to_circuit, unique_label=current_block.unique_label, ) interpretation_step.update_block_history_and_evolution_MUT( (new_block,), (current_block,) ) current_block = new_block return interpretation_step, current_block