Source code for loom_rotated_surface_code.applicator.split

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

from loom_rotated_surface_code.code_factory import RotatedSurfaceCode


# pylint: disable=duplicate-code
[docs] def split_consistency_check( interpretation_step: InterpretationStep, operation: Split ) -> RotatedSurfaceCode: """Check that the split operation can be performed on the given block. Parameters ---------- interpretation_step : InterpretationStep Interpretation step containing the block to split. operation : Split Split operation to perform. Returns ------- RotatedSurfaceCode The block to split. Raises ------ ValueError If the split position is larger than the width or height of the block. """ block = interpretation_step.get_block(operation.input_block_name) if not isinstance(block, RotatedSurfaceCode): raise TypeError( f"The split operation is not supported for {type(block)} blocks." ) position = operation.split_position if len(block.logical_x_operators) >= 2 or len(block.logical_z_operators) >= 2: raise NotImplementedError( "Splitting blocks with more than one logical operator is not supported." ) if operation.orientation == Orientation.HORIZONTAL: if position >= block.size[1]: raise ValueError( f"Split position {position} is larger than the width of the block " f"({block.size[1]})." ) if position in (0, block.size[1] - 1): raise ValueError("Split position cannot be at the edge of the block.") else: if position >= block.size[0]: raise ValueError( f"Split position {position} is larger than the height of the block " f"({block.size[0]})." ) if position in (0, block.size[0] - 1): raise ValueError("Split position cannot be at the edge of the block.") return block
[docs] def create_split_circuit( interpretation_step: InterpretationStep, block: RotatedSurfaceCode, operation: Split, qubits_to_measure: tuple[tuple[int, ...], ...], boundary_type: str, ) -> tuple[Circuit, list[Cbit]]: """ Create the circuit for the split operation. Parameters ---------- interpretation_step : InterpretationStep Interpretation step containing the block to split. block : RotatedSurfaceCode Block to split. operation : Split Split operation to perform. qubits_to_measure : tuple[tuple[int, ...], ...] Qubits to be measured in the split operation. boundary_type : str Type of the boundary of the block. Returns ------- Circuit Circuit for the split operation. """ # B) CIRCUIT # B.1) Create classical channels for all data qubit measurements 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 ] # B.2) Create a measurement circuit for every measured data qubit measure_circuit_seq = [ [ Circuit( "Measurement", channels=[interpretation_step.get_channel_MUT(q), cbit_channels[i]], ) for i, q in enumerate(qubits_to_measure) ] ] # If needed, apply a basis change if boundary_type == "X": # If the boundary type is X, the data qubit can directly be read out in the # Z basis split_circuit_list = measure_circuit_seq else: # If the boundary type is Z, the 2-bodies stabilizers are X, the data qubits # have to be read out in the X basis. Apply Hadamard gates before the # Z measurement to effectively measure in the X basis basis_change_circuit_seq = [ [ Circuit("H", channels=[interpretation_step.get_channel_MUT(q)]) for q in qubits_to_measure ] ] # We add two sequences of gates to the circuit, i.e. 2 timesteps split_circuit_list = basis_change_circuit_seq + measure_circuit_seq row_column_str = ( "row" if operation.orientation == Orientation.HORIZONTAL else "column" ) split_circuit = Circuit( name=f"Split {block.unique_label} at {row_column_str} " f"{operation.split_position}", circuit=split_circuit_list, ) return split_circuit, cbits
[docs] def split_stabilizers( block: RotatedSurfaceCode, operation: Split, qubits_to_measure: tuple[tuple[int, ...], ...], ) -> tuple[list[Stabilizer], list[Stabilizer], list[Stabilizer], list[Stabilizer]]: """ Split the initial block's stabilizers into two lists of stabilizers, once for each block, and the list of old stabilizers that are gonna be replaced and the new stabilizers that will replace them. Parameters ---------- block : RotatedSurfaceCode Initial block to be split operation : Split Split operation description qubits_to_measure : tuple[tuple[int, ...], ...] Qubits to be measured in the split operation. Returns ------- tuple[list[Stabilizer], list[Stabilizer], list[Stabilizer], list[Stabilizer]] A tuple of four lists of stabilizers, one for each block, the old stabilizers that are gonna be replaced and the new stabilizers that will replace them (in the same order). """ # C) - STABILIZERS # C.1) Find stabilizers which are completely removed # C.2) Find stabilizers which have to be reduced in weight # C.3) Create new stabilizers with reduced weight # C.4) Create two sets of stabilizers, one for each new block split_boundary = ( Direction.LEFT if operation.orientation == Orientation.VERTICAL else Direction.TOP ) boundary_type = block.boundary_type(split_boundary) split_is_vertical = operation.orientation == Orientation.VERTICAL split_position = operation.split_position # C.1) Find stabilizers which are completely removed stabs_to_remove = [ stab for stab in block.stabilizers if any(q in qubits_to_measure for q in stab.data_qubits) ] # C.2) Find stabilizers which have to be reduced in weight old_stabs_to_reduce_weight = [ stab for stab in stabs_to_remove if stab.pauli[0] != boundary_type and len([q for q in stab.data_qubits if q in qubits_to_measure]) == 2 and len(stab.data_qubits) == 4 ] # C.3) Create new stabilizers with reduced weight new_stabs_reduced_weight = [ Stabilizer( pauli="".join( stab.pauli[i] for i, q in enumerate(stab.data_qubits) if q not in qubits_to_measure ), data_qubits=[q for q in stab.data_qubits if q not in qubits_to_measure], ancilla_qubits=stab.ancilla_qubits, ) for stab in old_stabs_to_reduce_weight ] new_stabilizers = [ stab for stab in block.stabilizers if stab not in stabs_to_remove ] + new_stabs_reduced_weight data_qubits_block_1 = set( q for q in block.data_qubits if q[not split_is_vertical] < split_position + block.upper_left_qubit[not split_is_vertical] ) data_qubits_block_2 = set( q for q in block.data_qubits if q[not split_is_vertical] > split_position + block.upper_left_qubit[not split_is_vertical] ) # C.4) Create two sets of stabilizers, one for each new block new_stabs_block_1 = [ stab for stab in new_stabilizers if set(stab.data_qubits).issubset(data_qubits_block_1) ] new_stabs_block_2 = [ stab for stab in new_stabilizers if set(stab.data_qubits).issubset(data_qubits_block_2) ] remaining_stabs = ( set(new_stabilizers) - set(new_stabs_block_1) - set(new_stabs_block_2) ) # Consistency check that all new stabilizers are assigned to a block if len(remaining_stabs) != 0: raise ValueError( f"Stabilizers {remaining_stabs} are not assigned to any block." ) return ( new_stabs_block_1, new_stabs_block_2, old_stabs_to_reduce_weight, new_stabs_reduced_weight, )
[docs] def find_split_stabilizer_to_circuit_mappings( block: RotatedSurfaceCode, new_block_stabilizers: list[Stabilizer], new_boundary_direction: Direction, ) -> dict[str, tuple[str, ...]]: """ Finds the mapping between the stabilizers in the new block and the associated syndrome circuits. Parameters ---------- block : RotatedSurfaceCode Initial block to be split new_block_stabilizers : list[Stabilizer] List of stabilizers in the one of the new blocks new_boundary_direction : Direction Direction of the the new boundary in the new block Returns ------- dict[str, tuple[str, ...]] New mapping of stabilizers to syndrome circuits for the block. """ stabilizer_to_circuit = block.stabilizer_to_circuit new_stabs_id = [stab.uuid for stab in new_block_stabilizers] # Copy the old mapping for stabilizers that are conserved new_stab_to_circ = { stab_id: circ_id for (stab_id, circ_id) in stabilizer_to_circuit.items() if stab_id in new_stabs_id } # Find the syndrome circuits that should be associated with the new boundary # stabilizers new_boundary_stabs = [ stab for stab in new_block_stabilizers if stab.uuid not in new_stab_to_circ.keys() ] pauli_str = new_boundary_stabs[0].pauli new_boundary_circuit = next( synd_circ for synd_circ in block.syndrome_circuits if synd_circ.pauli == pauli_str if synd_circ.name == f"{new_boundary_direction.value.lower()}-{pauli_str.lower()}" ) new_stab_to_circ.update( {stab.uuid: new_boundary_circuit.uuid for stab in new_boundary_stabs} ) return new_stab_to_circ
[docs] def split_logical_operators( # pylint: disable=too-many-locals interpretation_step: InterpretationStep, block: RotatedSurfaceCode, qubits_to_measure: tuple[tuple[int, ...], ...], operation: Split, upleft_qubit_block_1: tuple[int, ...], upleft_qubit_block_2: tuple[int, ...], ) -> tuple[ InterpretationStep, tuple[PauliOperator, PauliOperator], tuple[PauliOperator, PauliOperator], dict[str, tuple[Cbit, ...]], dict[str, tuple[Cbit, ...]], ]: """ Finds the logical operators for the new blocks after the split operation. If an operator is partially measured, it will be split into two operators. If it is fully measured, it is shifted to the new block, adding stabilizers to the operator evolution. Parameters ---------- interpretation_step : InterpretationStep Interpretation step containing the block to split. block : RotatedSurfaceCode Block to split. qubits_to_measure : tuple[tuple[int, ...], ...] Qubits involved in the split operation. operation : Split Split operation description. upleft_qubit_block_1 : tuple[int, ...] Upper left qubit of the first new block. upleft_qubit_block_2 : tuple[int, ...] Upper left qubit of the second new block. Returns ------- tuple[ InterpretationStep, tuple[PauliOperator, PauliOperator], tuple[PauliOperator, PauliOperator] dict[str, tuple[Cbit, ...]], dict[str, tuple[Cbit, ...]], ] The interpretation step updated with the logical operator evolutions, logical X and logical Z operators for the first block, logical X and logical Z operators for the second block, updates for the logical X and Z operators. Raises ------ NotImplementedError If the initial block encodes more than one logical qubit. """ # D) LOGICAL OPERATORS # Find the stabilizer required to update the logical operator split_is_vertical = operation.orientation == Orientation.VERTICAL split_position = operation.split_position # D.1) Find logical operators which are not partially measured # The logical operator is "preserved" if it is not partially measured # If it was located at the same position as the split, we move it to the new blocks preserved_log_x_ops = [ op for op in block.logical_x_operators if not any(q in op.data_qubits for q in qubits_to_measure) or all(q in op.data_qubits for q in qubits_to_measure) ] # D.2) Find logical operators which have to be split # The logical operator is split if it is partially measured split_log_x_ops = [ op for op in block.logical_x_operators if any(q in op.data_qubits for q in qubits_to_measure) and not all(q in op.data_qubits for q in qubits_to_measure) ] if len(preserved_log_x_ops) != 0 and len(split_log_x_ops) != 0: raise NotImplementedError( "Splitting blocks with more than one logical X operator is not supported." ) # D.3) Create new logical operators # If the operator is split, construct two operators from it if len(split_log_x_ops) != 0: qubits_in_log_x_1 = [ q for q in split_log_x_ops[0].data_qubits if q[not split_is_vertical] < split_position + block.upper_left_qubit[not split_is_vertical] ] new_log_x_op_1 = PauliOperator( "".join(split_log_x_ops[0].pauli[i] for i in range(len(qubits_in_log_x_1))), qubits_in_log_x_1, ) qubits_in_log_x_2 = [ q for q in split_log_x_ops[0].data_qubits if q[not split_is_vertical] > split_position + block.upper_left_qubit[not split_is_vertical] ] new_log_x_op_2 = PauliOperator( "".join(split_log_x_ops[0].pauli[i] for i in range(len(qubits_in_log_x_2))), qubits_in_log_x_2, ) stabs_required_x_1 = [] stabs_required_x_2 = [] new_x_op_updates = {} # The operator is not partially measured, it may need to be displaced else: new_log_x_op_1, stabs_required_x_1 = ( block.get_shifted_equivalent_logical_operator( initial_operator=preserved_log_x_ops[0], new_upleft_qubit=upleft_qubit_block_1, ) ) new_log_x_op_2, stabs_required_x_2 = ( block.get_shifted_equivalent_logical_operator( initial_operator=preserved_log_x_ops[0], new_upleft_qubit=upleft_qubit_block_2, ) ) new_x_op_updates = { new_log_x_op_1.uuid: interpretation_step.retrieve_cbits_from_stabilizers( stabs_required_x_1, block ), new_log_x_op_2.uuid: interpretation_step.retrieve_cbits_from_stabilizers( stabs_required_x_2, block ), } # The logical operator is "preserved" if it is not partially measured # If it was located at the same position as the split, we move it to the new blocks preserved_log_z_ops = [ op for op in block.logical_z_operators if not any(q in op.data_qubits for q in qubits_to_measure) or all(q in op.data_qubits for q in qubits_to_measure) ] # The logical operator is split if it is partially measured split_log_z_ops = [ op for op in block.logical_z_operators if any(q in op.data_qubits for q in qubits_to_measure) and not all(q in op.data_qubits for q in qubits_to_measure) ] if len(preserved_log_z_ops) != 0 and len(split_log_z_ops) != 0: raise NotImplementedError( "Splitting blocks with more than one logical Z operator is not supported." ) if len(split_log_z_ops) != 0: qubits_in_log_z_1 = [ q for q in split_log_z_ops[0].data_qubits if q[not split_is_vertical] < split_position + block.upper_left_qubit[not split_is_vertical] ] new_log_z_op_1 = PauliOperator( "".join(split_log_z_ops[0].pauli[i] for i in range(len(qubits_in_log_z_1))), qubits_in_log_z_1, ) qubits_in_log_z_2 = [ q for q in split_log_z_ops[0].data_qubits if q[not split_is_vertical] > split_position + block.upper_left_qubit[not split_is_vertical] ] new_log_z_op_2 = PauliOperator( "".join(split_log_z_ops[0].pauli[i] for i in range(len(qubits_in_log_z_2))), qubits_in_log_z_2, ) stabs_required_z_1 = [] stabs_required_z_2 = [] new_z_op_updates = {} else: new_log_z_op_1, stabs_required_z_1 = ( block.get_shifted_equivalent_logical_operator( initial_operator=preserved_log_z_ops[0], new_upleft_qubit=upleft_qubit_block_1, ) ) new_log_z_op_2, stabs_required_z_2 = ( block.get_shifted_equivalent_logical_operator( initial_operator=preserved_log_z_ops[0], new_upleft_qubit=upleft_qubit_block_2, ) ) new_z_op_updates = { new_log_z_op_1.uuid: interpretation_step.retrieve_cbits_from_stabilizers( stabs_required_z_1, block ), new_log_z_op_2.uuid: interpretation_step.retrieve_cbits_from_stabilizers( stabs_required_z_2, block ), } # D.4) Update `logical_x/z_evolution` # Hardcoded index for the logical operators !!! interpretation_step.logical_x_evolution[new_log_x_op_1.uuid] = tuple( [block.logical_x_operators[0].uuid] + [stab.uuid for stab in stabs_required_x_1] ) interpretation_step.logical_x_evolution[new_log_x_op_2.uuid] = tuple( [block.logical_x_operators[0].uuid] + [stab.uuid for stab in stabs_required_x_2] ) interpretation_step.logical_z_evolution[new_log_z_op_1.uuid] = tuple( [block.logical_z_operators[0].uuid] + [stab.uuid for stab in stabs_required_z_1] ) interpretation_step.logical_z_evolution[new_log_z_op_2.uuid] = tuple( [block.logical_z_operators[0].uuid] + [stab.uuid for stab in stabs_required_z_2] ) return ( interpretation_step, (new_log_x_op_1, new_log_z_op_1), (new_log_x_op_2, new_log_z_op_2), new_x_op_updates, new_z_op_updates, )
[docs] def split( # pylint: disable=line-too-long, too-many-locals interpretation_step: InterpretationStep, operation: Split, same_timeslice: bool, debug_mode: bool, ) -> InterpretationStep: """ Applicator to split a block using the Split description. The algorithm is the following: - A.) DATA QUBITS - Find data qubits to be measured, the initial block will be split into two - B.) CIRCUIT - B.1) Create classical channels for all data qubit measurements - B.2) Create a measurement circuit for every measured data qubit - B.3) Append the measurement circuits to the InterpretationStep circuit. \ If needed, apply a basis change - C.) STABILIZERS - C.1) Find stabilizers which are completely removed - C.2) Find stabilizers which have to be reduced in weight - C.3) Create new stabilizers with reduced weight - C.4) Create two sets of stabilizers, one for each new block - C.5) Update ``stabilizer_evolution`` and ``stabilizer_updates`` for the \ stabilizers which have been reduced in weight - C.6) Create the new ``stabilizer_to_circuit`` mapping - D.) LOGICAL OPERATORS - D.1) Find logical operators which are not partially measured - D.2) Find logical operators which have to be split - D.3) Create new logical operators and final to initial mapping - D.4) Update ``logical_x/z_evolution`` - D.5) Update ``logical_x/z_updates`` - E.) NEW BLOCK AND NEW INTERPRETATION STEP - E.1) Create the new blocks - E.2) Update the block history Parameters ---------- interpretation_step : InterpretationStep Interpretation step containing the block to split. operation : Split Split 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 split operation. """ # Check that the operation can be performed block = split_consistency_check(interpretation_step, operation) split_is_vertical = operation.orientation == Orientation.VERTICAL split_position = operation.split_position # A) - DATA QUBITS # Find data qubits to be measured, the initial block will be split into two split_boundary = ( Direction.LEFT if operation.orientation == Orientation.VERTICAL else Direction.TOP ) boundary_qubits = block.boundary_qubits(split_boundary) boundary_type = block.boundary_type(split_boundary) shift_vector = ( split_is_vertical * split_position, (not split_is_vertical) * split_position, 0, ) qubits_to_measure = tuple( tuple( coord1 + coord2 for coord1, coord2 in zip(qubit, shift_vector, strict=True) ) for qubit in boundary_qubits ) # block 1 is either the left or top block qubits_block_1 = [ q for q in block.data_qubits if q[not split_is_vertical] < split_position + block.upper_left_qubit[not split_is_vertical] ] upleft_qubit_block_1 = min(qubits_block_1, key=lambda x: x[0] + x[1]) # block 2 is either the right or bottom block qubits_block_2 = [ q for q in block.data_qubits if q[not split_is_vertical] > split_position + block.upper_left_qubit[not split_is_vertical] ] upleft_qubit_block_2 = min(qubits_block_2, key=lambda x: x[0] + x[1]) # B) - CIRCUIT # B.1) Create classical channels for all data qubit measurements # B.2) Create a measurement circuit for every measured data qubit split_circuit, cbits = create_split_circuit( interpretation_step, block, operation, qubits_to_measure, boundary_type ) # B.3) Append the measurement circuits to the InterpretationStep circuit interpretation_step.append_circuit_MUT(split_circuit, same_timeslice) # C) - STABILIZERS # C.1) Find stabilizers which are completely removed # C.2) Find stabilizers which have to be reduced in weight # C.3) Create new stabilizers with reduced weight # C.4) Create two sets of stabilizers, one for each new block ( new_stabs_block_1, new_stabs_block_2, old_stabs_to_reduce_weight, new_stabs_reduced_weight, ) = split_stabilizers(block, operation, qubits_to_measure) # C.5) Update `stabilizer_evolution` and `stabilizer_updates` for the # stabilizers which have been reduced in weight for i, stab in enumerate(new_stabs_reduced_weight): previous_updates = ( interpretation_step.stabilizer_updates[stab.uuid] if stab.uuid in interpretation_step.stabilizer_updates.keys() else () ) old_stab = old_stabs_to_reduce_weight[i] # Find the data qubits of this stabilizer which are measured and whose # corresponding cbit has to be included in the stabilizer update qubits_measured = [q for q in old_stab.data_qubits if q in qubits_to_measure] # Find the cbits and include them in `stabilizer_updates` cbit_indices = [qubits_to_measure.index(q) for q in qubits_measured] new_stab_updates = tuple(cbits[cbit_idx] for cbit_idx in cbit_indices) if updates := previous_updates + new_stab_updates: interpretation_step.stabilizer_updates[stab.uuid] = updates stab_map_weight4_to_weight2 = { new_stabs_reduced_weight[i].uuid: (stab.uuid,) for i, stab in enumerate(old_stabs_to_reduce_weight) } interpretation_step.stabilizer_evolution.update(stab_map_weight4_to_weight2) # C.6) Create the new `stabilizer_to_circuit` mapping # block 1 is the left block -> new boundary is right if split is vertical # block 1 is the top block -> new boundary is bottom if split is horizontal stab_to_circ_block_1 = find_split_stabilizer_to_circuit_mappings( block, new_stabs_block_1, Direction.RIGHT if split_is_vertical else Direction.BOTTOM, ) # block 2 is the right block -> new boundary is left if split is vertical # block 2 is the bottom block -> new boundary is top if split is horizontal stab_to_circ_block_2 = find_split_stabilizer_to_circuit_mappings( block, new_stabs_block_2, Direction.LEFT if split_is_vertical else Direction.TOP, ) # D) LOGICAL OPERATORS # D.1) Find logical operators which are not partially measured # D.2) Find logical operators which have to be split # D.3) Create new logical operators and final to initial mapping # D.4) Update `logical_x/z_evolution` ( interpretation_step, (new_log_x_op_1, new_log_z_op_1), (new_log_x_op_2, new_log_z_op_2), new_x_updates, new_z_updates, ) = split_logical_operators( interpretation_step, block, qubits_to_measure, operation, upleft_qubit_block_1, upleft_qubit_block_2, ) # D.5) Create the new `logical_x/z_operator_updates` # Find measurements that split the relevant operator, if the operator is aligned # with the split, it's either fully measured or not measured at all. # It's not contributing to the operator updates in both cases. x_and_split_aligned = block.x_boundary == operation.orientation x_op_measurements = ( tuple( c for c in cbits if c[0].split("_")[1] in map(str, block.logical_x_operators[0].data_qubits) ) if not x_and_split_aligned else () ) z_op_measurements = ( tuple( c for c in cbits if c[0].split("_")[1] in map(str, block.logical_z_operators[0].data_qubits) ) if x_and_split_aligned else () ) # The new operator is mapped to the measurement that split the initial logical operator # The first block always inherits the updates from the initial block if the operator is modified interpretation_step.update_logical_operator_updates_MUT( operator_type="X", logical_operator_id=new_log_x_op_1.uuid, new_updates=new_x_updates.get(new_log_x_op_1.uuid, ()) + x_op_measurements, inherit_updates=(new_log_x_op_1.uuid != block.logical_x_operators[0].uuid), ) interpretation_step.update_logical_operator_updates_MUT( operator_type="Z", logical_operator_id=new_log_z_op_1.uuid, new_updates=new_z_updates.get(new_log_z_op_1.uuid, ()) + z_op_measurements, inherit_updates=(new_log_z_op_1.uuid != block.logical_z_operators[0].uuid), ) interpretation_step.update_logical_operator_updates_MUT( operator_type="X", logical_operator_id=new_log_x_op_2.uuid, new_updates=new_x_updates.get(new_log_x_op_2.uuid, ()), inherit_updates=x_and_split_aligned and (new_log_x_op_2.uuid != block.logical_x_operators[0].uuid), ) interpretation_step.update_logical_operator_updates_MUT( operator_type="Z", logical_operator_id=new_log_z_op_2.uuid, new_updates=new_z_updates.get(new_log_z_op_2.uuid, ()), inherit_updates=not x_and_split_aligned and (new_log_z_op_2.uuid != block.logical_z_operators[0].uuid), ) # E) NEW BLOCK AND NEW INTERPRETATION STEP # E.1) Create the new blocks new_block_1 = RotatedSurfaceCode( stabilizers=new_stabs_block_1, logical_x_operators=[new_log_x_op_1], logical_z_operators=[new_log_z_op_1], syndrome_circuits=block.syndrome_circuits, stabilizer_to_circuit=stab_to_circ_block_1, unique_label=operation.output_blocks_name[0], skip_validation=not debug_mode, ) new_block_2 = RotatedSurfaceCode( stabilizers=new_stabs_block_2, logical_x_operators=[new_log_x_op_2], logical_z_operators=[new_log_z_op_2], syndrome_circuits=block.syndrome_circuits, stabilizer_to_circuit=stab_to_circ_block_2, unique_label=operation.output_blocks_name[1], skip_validation=not debug_mode, ) # E.2) Update the block history interpretation_step.update_block_history_and_evolution_MUT( new_blocks=(new_block_1, new_block_2), old_blocks=(block,) ) return interpretation_step