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