"""
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 uuid import uuid4
from loom.eka import Circuit
from loom.eka.operations import (
Merge,
Split,
Grow,
Shrink,
MeasureBlockSyndromes,
LogicalMeasurement,
ConditionalLogicalZ,
)
from loom.eka.utilities import Direction, Orientation
from loom.interpreter.applicator import measureblocksyndromes, conditional_logical_pauli
from loom.interpreter.interpretation_step import InterpretationStep
from .merge import merge
from .split import split
from .grow import grow
from .shrink import shrink
from ..code_factory import RotatedSurfaceCode
from ..operations import AuxCNOT
# pylint: disable=duplicate-code
[docs]
def auxcnot(
interpretation_step: InterpretationStep,
operation: AuxCNOT,
same_timeslice: bool,
debug_mode: bool,
) -> InterpretationStep:
"""
Apply an auxiliary CNOT operation using a Grow - Shrink - Merge - Split approach.
The algorithm is the following:
- A) Grow control block
- B) Measure syndromes of grown_control and target blocks
- C) Split grown_control into control and auxiliary blocks
- D) Measure syndromes of control, auxiliary and target blocks
- E) Merge auxiliary and target blocks
- F) Measure syndromes of control and merged_target blocks
- G) Apply conditionallogicalz conditioned on joint measurement
- H) Shrink the new target block
- I) Wrap the auxcnot circuit in a single circuit
Parameters
----------
interpretation_step : InterpretationStep
Input interpretation step.
operation : AuxCNOT
The CNOT operation to apply.
same_timeslice : bool
Flag indicating whether the operation is part of the same timestep as the input.
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
The updated interpretation step after applying the auxiliary CNOT operation.
"""
init_circ_len = len(interpretation_step.intermediate_circuit_sequence)
c_block = interpretation_step.get_block(operation.input_blocks_name[0])
t_block = interpretation_step.get_block(operation.input_blocks_name[1])
auxcnot_consistency_check(c_block, t_block)
grow_direction, shrink_direction = get_grow_shrink_directions(
control=c_block,
target=t_block,
)
# A) GROW CONTROL BLOCK
interpretation_step = auxcnot_grow_control(
interpretation_step=interpretation_step,
control=c_block,
target=t_block,
grow_direction=grow_direction,
same_timeslice=False, # Prevent the same timeslice flag from being set
debug_mode=debug_mode,
)
# Get the grown control block
grown_control = interpretation_step.get_block(c_block.unique_label)
aux_unique_label = f"{grown_control.unique_label}_aux"
# Ensure the auxiliary block has a unique label
if aux_unique_label in [
block.unique_label for block in interpretation_step.block_history[-1]
]:
aux_unique_label = str(uuid4())
# B) MEASURE SYNDROMES OF GROWN_CONTROL AND TARGET BLOCKS
for block, internal_same_timeslice in ((grown_control, False), (t_block, True)):
interpretation_step = measureblocksyndromes(
interpretation_step=interpretation_step,
operation=MeasureBlockSyndromes(block.unique_label, min(block.size)),
same_timeslice=internal_same_timeslice,
debug_mode=debug_mode,
)
# C) SPLIT GROWN_CONTROL INTO CONTROL AND AUXILIARY BLOCKS
interpretation_step = auxcnot_split_control(
interpretation_step=interpretation_step,
aux_unique_label=aux_unique_label,
initial_control=c_block,
initial_target=t_block,
grown_control=grown_control,
same_timeslice=False,
debug_mode=debug_mode,
)
# Get the new control and auxiliary blocks
new_control = interpretation_step.get_block(c_block.unique_label)
aux_block = interpretation_step.get_block(aux_unique_label)
# D) MEASURE SYNDROMES OF CONTROL, AUXILIARY AND TARGET BLOCKS
for block, internal_same_timeslice in (
(new_control, False),
(aux_block, True),
(t_block, True),
):
interpretation_step = measureblocksyndromes(
interpretation_step=interpretation_step,
operation=MeasureBlockSyndromes(block.unique_label, 1),
same_timeslice=internal_same_timeslice,
debug_mode=debug_mode,
)
# E) MERGE AUXILIARY AND TARGET BLOCKS
interpretation_step = auxcnot_merge_aux_target(
interpretation_step=interpretation_step,
aux=aux_block,
target=t_block,
same_timeslice=False, # Prevent the same timeslice flag from being set
debug_mode=debug_mode,
)
# Get the merged target block
merged_target = interpretation_step.get_block(t_block.unique_label)
# F) MEASURE SYNDROMES OF CONTROL AND MERGED_TARGET BLOCKS
for block, internal_same_timeslice, n_cycles in (
(new_control, True, 1),
(new_control, False, min(new_control.size) - 1),
(merged_target, True, min(merged_target.size) - 1),
):
interpretation_step = measureblocksyndromes(
interpretation_step=interpretation_step,
operation=MeasureBlockSyndromes(block.unique_label, n_cycles),
same_timeslice=internal_same_timeslice,
debug_mode=debug_mode,
)
# G) APPLY CONDITIONALLOGICALZ CONDITIONED ON JOINT MEASUREMENT
interpretation_step = auxcnot_conditional_logical_z(
interpretation_step=interpretation_step,
new_control=new_control,
initial_target=t_block,
aux_block=aux_block,
new_target=merged_target,
shrink_direction=shrink_direction,
same_timeslice=True,
debug_mode=debug_mode,
)
# H) SHRINK THE NEW TARGET BLOCK
interpretation_step = auxcnot_shrink_target(
interpretation_step=interpretation_step,
initial_target=t_block,
merged_target=merged_target,
shrink_direction=shrink_direction,
same_timeslice=False, # Prevent the same timeslice flag from being set
debug_mode=debug_mode,
)
# I) WRAP THE AUXCNOT CIRCUIT IN A SINGLE CIRCUIT
new_len = len(interpretation_step.intermediate_circuit_sequence)
len_auxcnot = new_len - init_circ_len
circuit_seq = interpretation_step.pop_intermediate_circuit_MUT(len_auxcnot)
wrapped_circuit_seq = ()
for timeslice in circuit_seq:
timespan = max(composite_circuit.duration for composite_circuit in timeslice)
# Create a circuit with empty timeslices and align circuits
template_circ = (
tuple(composite_circuit for composite_circuit in timeslice),
) + ((),) * (timespan - 1)
wrapped_circuit_seq += template_circ
wrapped_circuit = Circuit(
name=(f"auxcnot between {c_block.unique_label} and {t_block.unique_label}"),
circuit=wrapped_circuit_seq,
)
interpretation_step.append_circuit_MUT(wrapped_circuit, same_timeslice)
# Return the final step
return interpretation_step
[docs]
def auxcnot_consistency_check(
c_block: RotatedSurfaceCode,
t_block: RotatedSurfaceCode,
):
"""
Perform multiple checks to ensure that the auxiliary CNOT operation can be applied.
The blocks must be of the correct type, have the same size, the same boundary
orientations and be in a specific configuration.
The configuration is the following, the upper left corners of the two blocks must
satisfy the following relations:
- ``|t_block.upper_left_qubit[0] - c_block.upper_left_qubit[0]| = c_block.size[0]``
- ``|t_block.upper_left_qubit[1] - c_block.upper_left_qubit[1]| = c_block.size[1]``
Parameters
----------
c_block : RotatedSurfaceCode
Control block for the auxiliary CNOT operation.
t_block : RotatedSurfaceCode
Target block for the auxiliary CNOT operation.
"""
if not isinstance(c_block, RotatedSurfaceCode) or not isinstance(
t_block, RotatedSurfaceCode
):
raise TypeError(
"The auxcnot operation is only supported for RotatedSurfaceCode blocks. "
f"{set(type(block) for block in [c_block, t_block])} types "
"were given."
)
# NOTE: this is a sub-case, can be extended later
if c_block.size != t_block.size:
raise ValueError(
f"The blocks must have the same size to perform the auxiliary CNOT "
f"operation. The sizes of the blocks are {c_block.size}, {t_block.size}."
)
# Check that the blocks have the correct boundary orientations
if c_block.x_boundary != t_block.x_boundary:
raise ValueError(
"The blocks must have the same boundary orientations to perform the "
"auxiliary CNOT operation. The X boundary orientations are "
f"{c_block.x_boundary} and {t_block.x_boundary}."
)
# Check that the blocks are in the correct configuration
if (
abs(t_block.upper_left_qubit[0] - c_block.upper_left_qubit[0])
!= c_block.size[0] + 1 # +1 because the auxiliary qubit is one row/column away
or abs(t_block.upper_left_qubit[1] - c_block.upper_left_qubit[1])
!= c_block.size[1] + 1 # +1 because the auxiliary qubit is one row/column away
):
raise ValueError(
"The blocks are not in the correct configuration for the auxiliary CNOT "
"operation. The upper left corners of the blocks must satisfy the "
"following relations: \n"
"|t_block.upper_left_qubit[0] - c_block.upper_left_qubit[0]| = "
"c_block.size[0] + 1, |t_block.upper_left_qubit[1] - "
"c_block.upper_left_qubit[1]| = c_block.size[1] + 1"
f"\nGot |{t_block.upper_left_qubit[0]} - {c_block.upper_left_qubit[0]}| = "
f"{c_block.size[0] + 1}, |{t_block.upper_left_qubit[1]} - "
f"{c_block.upper_left_qubit[1]}| = {c_block.size[1] + 1} instead."
)
[docs]
def get_grow_shrink_directions(
control: RotatedSurfaceCode,
target: RotatedSurfaceCode,
) -> tuple[Direction, Direction]:
"""
Get the grow and shrink directions for the auxiliary CNOT operation.
The grow direction is the direction of the X operator of the control block, and
the shrink direction is the direction normal to the X operator of the target block.
Parameters
----------
control : RotatedSurfaceCode
Control block for the auxiliary CNOT operation.
target : RotatedSurfaceCode
Target block for the auxiliary CNOT operation.
Returns
-------
tuple[Direction, Direction]
The grow and shrink directions.
"""
upper_left_qubits_vector = (
control.upper_left_qubit[0] - target.upper_left_qubit[0],
control.upper_left_qubit[1] - target.upper_left_qubit[1],
)
match upper_left_qubits_vector:
case (x, y) if x < 0 and y < 0:
# The control block is to the left and above the target block
grow_direction = (
Direction.RIGHT
if control.x_boundary == Orientation.HORIZONTAL
else Direction.BOTTOM
)
shrink_direction = (
Direction.TOP
if control.x_boundary == Orientation.HORIZONTAL
else Direction.LEFT
)
case (x, y) if x < 0 < y:
# The control block is to the left and below the target block
grow_direction = (
Direction.RIGHT
if control.x_boundary == Orientation.HORIZONTAL
else Direction.TOP
)
shrink_direction = (
Direction.BOTTOM
if control.x_boundary == Orientation.HORIZONTAL
else Direction.LEFT
)
case (x, y) if x > 0 > y:
# The control block is to the right and above the target block
grow_direction = (
Direction.LEFT
if control.x_boundary == Orientation.HORIZONTAL
else Direction.BOTTOM
)
shrink_direction = (
Direction.TOP
if control.x_boundary == Orientation.HORIZONTAL
else Direction.RIGHT
)
case (x, y) if x > 0 and y > 0:
# The control block is to the right and below the target block
grow_direction = (
Direction.LEFT
if control.x_boundary == Orientation.HORIZONTAL
else Direction.TOP
)
shrink_direction = (
Direction.BOTTOM
if control.x_boundary == Orientation.HORIZONTAL
else Direction.RIGHT
)
case _:
raise ValueError(
"The control block is not in the correct position relative to the "
"target block."
)
return grow_direction, shrink_direction
[docs]
def auxcnot_grow_control(
interpretation_step: InterpretationStep,
control: RotatedSurfaceCode,
target: RotatedSurfaceCode,
grow_direction: Direction,
same_timeslice: bool,
debug_mode: bool,
) -> InterpretationStep:
"""
Grow the control block for the auxiliary CNOT operation.
Parameters
----------
interpretation_step : InterpretationStep
Input interpretation step.
control : RotatedSurfaceCode
Control block for the auxiliary CNOT operation.
target : RotatedSurfaceCode
Target block for the auxiliary CNOT operation.
grow_direction : Direction
Direction in which to grow the control block.
same_timeslice : bool
Flag indicating whether the operation is part of the same timestep as the input.
debug_mode : bool
Flag indicating whether the interpretation should be done in debug mode.
Returns
-------
InterpretationStep
The updated interpretation step after growing the control block.
"""
# Grow the control block in the orientation of its X operator
grow_length = (
target.size[0] + 1
if control.x_boundary == Orientation.HORIZONTAL
else target.size[1] + 1
)
grow_op = Grow(
input_block_name=control.unique_label,
direction=grow_direction,
length=grow_length,
)
interpretation_step = grow(
interpretation_step=interpretation_step,
operation=grow_op,
same_timeslice=same_timeslice,
debug_mode=debug_mode,
)
return interpretation_step
[docs]
def auxcnot_split_control(
interpretation_step: InterpretationStep,
aux_unique_label: str,
initial_control: RotatedSurfaceCode,
initial_target: RotatedSurfaceCode,
grown_control: RotatedSurfaceCode,
same_timeslice: bool,
debug_mode: bool,
) -> InterpretationStep:
"""
Split the grown control block into the control and auxiliary blocks.
Parameters
----------
interpretation_step : InterpretationStep
Input interpretation step.
aux_unique_label : str
Unique label for the auxiliary block that will be created after the split.
initial_control : RotatedSurfaceCode
Initial control block before it was grown.
initial_target : RotatedSurfaceCode
Initial target block before the auxiliary CNOT operation.
grown_control : RotatedSurfaceCode
Control block for the auxiliary CNOT operation that was just grown.
same_timeslice : bool
Flag indicating whether the operation is part of the same timestep as the input.
debug_mode : bool
Flag indicating whether the interpretation should be done in debug mode.
Returns
-------
InterpretationStep
The updated interpretation step after splitting the grown control block.
"""
# Split the grown control block into the control and auxiliary blocks
split_orientation = grown_control.x_boundary.perpendicular()
# Ensure the order of naming is right, if the initial upper-left qubit of the
# control block is the same as the grown control block, then the split control will
# also inherit that qubit and the first name in the tuple.
# The position of the split also depends on the size of the block that is located on
# the top-left
if initial_control.upper_left_qubit == grown_control.upper_left_qubit:
split_blocks_name = (grown_control.unique_label, aux_unique_label)
split_position = (
initial_control.size[0]
if split_orientation == Orientation.VERTICAL
else initial_control.size[1]
)
else:
split_blocks_name = (aux_unique_label, grown_control.unique_label)
split_position = (
initial_target.size[0]
if split_orientation == Orientation.VERTICAL
else initial_target.size[1]
)
split_op = Split(
input_block_name=grown_control.unique_label,
output_blocks_name=split_blocks_name,
orientation=split_orientation,
split_position=split_position,
)
interpretation_step = split(
interpretation_step=interpretation_step,
operation=split_op,
same_timeslice=same_timeslice,
debug_mode=debug_mode,
)
return interpretation_step
[docs]
def auxcnot_merge_aux_target(
interpretation_step: InterpretationStep,
aux: RotatedSurfaceCode,
target: RotatedSurfaceCode,
same_timeslice: bool,
debug_mode: bool,
) -> InterpretationStep:
"""
Merge the auxiliary block with the target block.
Parameters
----------
interpretation_step : InterpretationStep
Input interpretation step.
aux : RotatedSurfaceCode
Auxiliary block for the auxiliary CNOT operation.
target : RotatedSurfaceCode
Target block for the auxiliary CNOT operation.
same_timeslice : bool
Flag indicating whether the operation is part of the same timestep as the input.
debug_mode : bool
Flag indicating whether the interpretation should be done in debug mode.
Returns
-------
InterpretationStep
The updated interpretation step after merging the auxiliary and target blocks.
"""
# Merge the auxiliary block with the target block
merge_op = Merge(
input_blocks_name=(aux.unique_label, target.unique_label),
output_block_name=target.unique_label,
)
interpretation_step = merge(
interpretation_step=interpretation_step,
operation=merge_op,
same_timeslice=same_timeslice,
debug_mode=debug_mode,
)
return interpretation_step
[docs]
def auxcnot_shrink_target(
interpretation_step: InterpretationStep,
initial_target: RotatedSurfaceCode,
merged_target: RotatedSurfaceCode,
shrink_direction: Direction,
same_timeslice: bool,
debug_mode: bool,
) -> InterpretationStep:
"""
Shrink the target block after merging with the auxiliary block.
Parameters
----------
interpretation_step : InterpretationStep
Input interpretation step.
initial_target : RotatedSurfaceCode
Initial target block for the auxiliary CNOT operation.
merged_target : RotatedSurfaceCode
Merged target block after merging with the auxiliary block.
same_timeslice : bool
Flag indicating whether the operation is part of the same timestep as the input.
debug_mode : bool
Flag indicating whether the interpretation should be done in debug mode.
Returns
-------
InterpretationStep
The updated interpretation step after shrinking the target block.
"""
# Shrink the target block in the direction normal to its X operator
shrink_length = (
merged_target.size[0] - initial_target.size[0]
if merged_target.x_boundary == Orientation.VERTICAL
else merged_target.size[1] - initial_target.size[1]
)
shrink_op = Shrink(
input_block_name=merged_target.unique_label,
direction=shrink_direction,
length=shrink_length,
)
interpretation_step = shrink(
interpretation_step=interpretation_step,
operation=shrink_op,
same_timeslice=same_timeslice,
debug_mode=debug_mode,
)
return interpretation_step
[docs]
def auxcnot_conditional_logical_z(
# pylint: disable=too-many-arguments,too-many-positional-arguments
interpretation_step: InterpretationStep,
new_control: RotatedSurfaceCode,
initial_target: RotatedSurfaceCode,
aux_block: RotatedSurfaceCode,
new_target: RotatedSurfaceCode,
shrink_direction: Direction,
same_timeslice: bool,
debug_mode: bool,
) -> InterpretationStep:
"""
Applies ConditionalLogicalZ to the blocks conditioned on the value of the joint
measurement obtained from Merge.
Parameters
----------
interpretation_step : InterpretationStep
Input interpretation step.
new_control : RotatedSurfaceCode
New control block after the AuxCNOT operations.
initial_target : RotatedSurfaceCode
Target block before the the AuxCNOT operations.
aux_block : RotatedSurfaceCode
Auxiliary block for the AuxCNOT operation.
new_target : RotatedSurfaceCode
New target block after the AuxCNOT operations.
shrink_direction : Direction
Direction in which the target block was shrunk.
same_timeslice : bool
Flag indicating whether the operation is part of the same timestep as the input.
debug_mode : bool
Flag indicating whether the interpretation should be done in debug mode.
Returns
-------
InterpretationStep
The updated interpretation step after applying the LogicalZ
"""
log_meas = LogicalMeasurement(
(initial_target.unique_label, aux_block.unique_label), "XX"
)
log_op = ConditionalLogicalZ(new_control.unique_label, condition=log_meas)
interpretation_step = conditional_logical_pauli(
interpretation_step=interpretation_step,
operation=log_op,
same_timeslice=same_timeslice,
debug_mode=debug_mode,
)
# Additional correction needed if merged_target inherits the auxiliary block's
# logical X operator, which happens when shrink_direction is "TOP" or "LEFT"
if shrink_direction in {Direction.TOP, Direction.LEFT}:
log_op = ConditionalLogicalZ(new_target.unique_label, condition=log_meas)
interpretation_step = conditional_logical_pauli(
interpretation_step=interpretation_step,
operation=log_op,
same_timeslice=same_timeslice,
debug_mode=debug_mode,
)
return interpretation_step