From 4136d2bd7e9b77869dd66edeafdc8ea475c5622d Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Fri, 1 Aug 2025 15:16:45 +0200 Subject: [PATCH 01/16] Added abstract method for obtaining gate-sequence indices at which parity group has (edge-ID) gates. --- .../connectivity/generic_gate_sequence.py | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/qce_circuit/connectivity/generic_gate_sequence.py b/src/qce_circuit/connectivity/generic_gate_sequence.py index d3db674..1c58043 100644 --- a/src/qce_circuit/connectivity/generic_gate_sequence.py +++ b/src/qce_circuit/connectivity/generic_gate_sequence.py @@ -1,9 +1,9 @@ # ------------------------------------------- # Module containing interface and implementation of generic (Surface17) gate sequences. # ------------------------------------------- -from abc import ABCMeta +from abc import ABCMeta, abstractmethod from typing import List, Union -from qce_circuit.utilities.custom_exceptions import ElementNotIncludedException +from qce_circuit.utilities.custom_exceptions import ElementNotIncludedException, InterfaceMethodException from qce_circuit.utilities.array_manipulation import unique_in_order from qce_circuit.connectivity.intrf_channel_identifier import ( IQubitID, @@ -26,7 +26,16 @@ class IGenericSurfaceCodeLayer(ISurfaceCodeLayer, IGateSequenceLayer, metaclass= """ Interface class, combining ISurfaceCodeLayer and IGateSequenceLayer. """ - pass + + # region Interface Methods + @abstractmethod + def get_gate_sequence_indices(self, parity_group: IParityGroup) -> List[int]: + """ + :return: Array-like of gate-sequence indices corresponding to parity-group edge-ID's. + Returns an empty list if parity-group is not present. + """ + raise InterfaceMethodException + # endregion class GenericSurfaceCode(IGenericSurfaceCodeLayer): @@ -98,6 +107,26 @@ def __init__(self, gate_sequences: List[GateSequenceLayer], parity_group_z: List self._surface_code_layer: ISurfaceCodeLayer = surface_code_layer # endregion + # region IGenericSurfaceCodeLayer Methods + def get_gate_sequence_indices(self, parity_group: IParityGroup) -> List[int]: + """ + :return: Array-like of gate-sequence indices corresponding to parity-group edge-ID's. + Returns an empty list if parity-group is not present. + """ + # Guard clause, if parity-group is not present, return empty list + if parity_group not in self.parity_group_x + self.parity_group_z: + return [] + edge_ids: List[IEdgeID] = parity_group.edge_ids + result: List[int] = [] + for gate_sequence_index, gate_sequence in enumerate(self.gate_sequences): + contains_any_edge: bool = any([edge_id in gate_sequence.edge_ids for edge_id in edge_ids]) + if contains_any_edge: + result.append(gate_sequence_index) + continue + + return result + # endregion + # region IGateSequenceLayer Interface Methods def get_gate_sequence_at_index(self, index: int) -> GateSequenceLayer: """:return: Gate-sequence object based on round index.""" From d5d6e6638d5d389771590c0b8ad5835a3b1685ed Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Fri, 1 Aug 2025 17:02:15 +0200 Subject: [PATCH 02/16] Fixed stabilizer type definition in Surface17Round8Code --- .../library/surface_code/surface_code_connectivity.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qce_circuit/library/surface_code/surface_code_connectivity.py b/src/qce_circuit/library/surface_code/surface_code_connectivity.py index f83bda5..1606784 100644 --- a/src/qce_circuit/library/surface_code/surface_code_connectivity.py +++ b/src/qce_circuit/library/surface_code/surface_code_connectivity.py @@ -321,22 +321,22 @@ def __init__(self): ], parity_group_x=[ ParityGroup( - _parity_type=StabilizerType.STABILIZER_Z, + _parity_type=StabilizerType.STABILIZER_X, _ancilla_qubit=QubitIDObj('X1'), _data_qubits=[QubitIDObj('D1'), QubitIDObj('D2')] ), ParityGroup( - _parity_type=StabilizerType.STABILIZER_Z, + _parity_type=StabilizerType.STABILIZER_X, _ancilla_qubit=QubitIDObj('X2'), _data_qubits=[QubitIDObj('D2'), QubitIDObj('D3'), QubitIDObj('D5'), QubitIDObj('D6')] ), ParityGroup( - _parity_type=StabilizerType.STABILIZER_Z, + _parity_type=StabilizerType.STABILIZER_X, _ancilla_qubit=QubitIDObj('X3'), _data_qubits=[QubitIDObj('D4'), QubitIDObj('D5'), QubitIDObj('D7'), QubitIDObj('D8')] ), ParityGroup( - _parity_type=StabilizerType.STABILIZER_Z, + _parity_type=StabilizerType.STABILIZER_X, _ancilla_qubit=QubitIDObj('X4'), _data_qubits=[QubitIDObj('D8'), QubitIDObj('D9')] ), From 69450a5d566167c36e1c4c1e3ca992c05bb1aedc Mon Sep 17 00:00:00 2001 From: yxin Date: Tue, 5 Aug 2025 16:38:57 +0200 Subject: [PATCH 03/16] Fix color overwrite in CZ and measure operations --- .../draw_components/multi_pivot_components.py | 2 ++ .../visualize_circuit/draw_components/operation_components.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/qce_circuit/visualization/visualize_circuit/draw_components/multi_pivot_components.py b/src/qce_circuit/visualization/visualize_circuit/draw_components/multi_pivot_components.py index 36f76f3..5208476 100644 --- a/src/qce_circuit/visualization/visualize_circuit/draw_components/multi_pivot_components.py +++ b/src/qce_circuit/visualization/visualize_circuit/draw_components/multi_pivot_components.py @@ -214,9 +214,11 @@ def draw(self, axes: plt.Axes) -> plt.Axes: # TODO: Add logic based on attributes DotComponent( base_transform=self.main_transform_block, + style_settings=self.style_settings, ).draw(axes=axes) DotComponent( base_transform=self.second_transform_block, + style_settings=self.style_settings, ).draw(axes=axes) # CrossComponent( # base_transform=self.second_transform_block, diff --git a/src/qce_circuit/visualization/visualize_circuit/draw_components/operation_components.py b/src/qce_circuit/visualization/visualize_circuit/draw_components/operation_components.py index 0cfae89..98019fb 100644 --- a/src/qce_circuit/visualization/visualize_circuit/draw_components/operation_components.py +++ b/src/qce_circuit/visualization/visualize_circuit/draw_components/operation_components.py @@ -21,6 +21,7 @@ from qce_circuit.visualization.visualize_circuit.style_manager import ( StyleManager, OperationStyleSettings, + IconStyleSettings, ChannelStyleSettings, ) @@ -226,6 +227,7 @@ class BlockMeasure(IRectTransformComponent, IDrawComponent): height: float alignment: TransformAlignment = field(default=TransformAlignment.MID_LEFT) style_settings: OperationStyleSettings = field(default_factory=lambda: StyleManager.read_config().operation_style) + icon_style_settings: IconStyleSettings = field(default_factory=lambda: StyleManager.read_config().icon_style) _base_block: RectangleBlock = field(init=False) # region Interface Properties @@ -246,6 +248,7 @@ def icon(self) -> IconMeasure: return IconMeasure( center=icon_center, radius=icon_radius, + style_settings=self.icon_style_settings ) # endregion From 92bdee4af988faf92707d8a6bae6396d759907cd Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:54:50 +0200 Subject: [PATCH 04/16] Fixed bug related to pivot offset --- .../visualization/visualize_layout/display_connectivity.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qce_circuit/visualization/visualize_layout/display_connectivity.py b/src/qce_circuit/visualization/visualize_layout/display_connectivity.py index 29e4032..df1e166 100644 --- a/src/qce_circuit/visualization/visualize_layout/display_connectivity.py +++ b/src/qce_circuit/visualization/visualize_layout/display_connectivity.py @@ -200,15 +200,15 @@ def get_line_components(self) -> List[IDrawComponent]: def get_operation_components(self) -> List[IDrawComponent]: park_components: List[IDrawComponent] = [ ParkingComponent( - pivot=self.identifier_to_pivot(identifier=operation.identifier), + pivot=self.identifier_to_pivot(identifier=operation.identifier) + self.pivot, alignment=TransformAlignment.MID_CENTER, ) for operation in self.gate_sequence.park_operations ] gate_components: List[IDrawComponent] = [ GateOperationComponent( - pivot0=self.identifier_to_pivot(identifier=operation.identifier.qubit_ids[0]), - pivot1=self.identifier_to_pivot(identifier=operation.identifier.qubit_ids[1]), + pivot0=self.identifier_to_pivot(identifier=operation.identifier.qubit_ids[0]) + self.pivot, + pivot1=self.identifier_to_pivot(identifier=operation.identifier.qubit_ids[1]) + self.pivot, alignment=TransformAlignment.MID_CENTER, ) for operation in self.gate_sequence.gate_operations From b5e748cce9e86ba2595686fb806f2d2d9bd9f978 Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:56:07 +0200 Subject: [PATCH 05/16] Added functionality to enable or disable element label text --- .../visualize_layout/display_connectivity.py | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/qce_circuit/visualization/visualize_layout/display_connectivity.py b/src/qce_circuit/visualization/visualize_layout/display_connectivity.py index df1e166..28af781 100644 --- a/src/qce_circuit/visualization/visualize_layout/display_connectivity.py +++ b/src/qce_circuit/visualization/visualize_layout/display_connectivity.py @@ -66,6 +66,7 @@ class VisualConnectivityDescription: layout_spacing: float = field(default=1.0) pivot: Vec2D = field(default=Vec2D(0, 0)) rotation: float = field(default=-45) + include_element_labels: bool = field(default=True) # region Class Methods def get_plaquette_components(self) -> List[IDrawComponent]: @@ -173,11 +174,12 @@ def get_element_components(self) -> List[IDrawComponent]: pivot=self.identifier_to_pivot(qubit_id) + self.pivot, alignment=TransformAlignment.MID_CENTER, )) - result.append(TextComponent( - pivot=self.identifier_to_pivot(qubit_id) + self.pivot, - text=qubit_id.id, - alignment=TransformAlignment.MID_CENTER, - )) + if self.include_element_labels: + result.append(TextComponent( + pivot=self.identifier_to_pivot(qubit_id) + self.pivot, + text=qubit_id.id, + alignment=TransformAlignment.MID_CENTER, + )) return result def get_line_components(self) -> List[IDrawComponent]: @@ -390,11 +392,12 @@ def get_element_components(self) -> List[IDrawComponent]: zorder=style_setting.zorder_element, ), )) - result.append(TextComponent( - pivot=self.identifier_to_pivot(qubit_id) + self.pivot, - text=qubit_id.id, - alignment=TransformAlignment.MID_CENTER, - )) + if self.include_element_labels: + result.append(TextComponent( + pivot=self.identifier_to_pivot(qubit_id) + self.pivot, + text=qubit_id.id, + alignment=TransformAlignment.MID_CENTER, + )) return result # endregion @@ -439,7 +442,7 @@ def plot_gate_sequences(description: IGenericSurfaceCodeLayer, **kwargs) -> IFig return fig, axes[0] -def plot_stabilizer_specific_gate_sequences(description: IGenericSurfaceCodeLayer, **kwargs) -> IFigureAxesPair: +def plot_stabilizer_specific_gate_sequences(description: IGenericSurfaceCodeLayer, include_element_labels: bool = True, **kwargs) -> IFigureAxesPair: """ Constructs a similar gate sequence plot as 'plot_gate_sequences'. However, the gate-sequence info is taken from description parameter @@ -447,6 +450,7 @@ def plot_stabilizer_specific_gate_sequences(description: IGenericSurfaceCodeLaye Allowing for extra flexibility. :param description: Generic surface code layer definition including parity-groups and gate sequence. :param kwargs: Keyword arguments passed to figure constructor. + :param include_element_labels: Boolean to enable or disable element label text. :return: Figure and Axes pair. """ sequence_count: int = description.gate_sequence_count @@ -459,7 +463,8 @@ def plot_stabilizer_specific_gate_sequences(description: IGenericSurfaceCodeLaye descriptor: AllGreyVisualConnectivityDescription = AllGreyVisualConnectivityDescription( connectivity=Surface17Layer(), gate_sequence=description.get_gate_sequence_at_index(i), - layout_spacing=1.0 + layout_spacing=1.0, + include_element_labels=include_element_labels, ) kwargs[SubplotKeywordEnum.HOST_AXES.value] = (fig, ax) fig, ax = plot_layout_description(descriptor, **kwargs) @@ -467,7 +472,8 @@ def plot_stabilizer_specific_gate_sequences(description: IGenericSurfaceCodeLaye descriptor: StabilizerGroupVisualConnectivityDescription = StabilizerGroupVisualConnectivityDescription( connectivity=description, gate_sequence=description.get_gate_sequence_at_index(i), - layout_spacing=1.0 + layout_spacing=1.0, + include_element_labels=include_element_labels, ) kwargs[SubplotKeywordEnum.HOST_AXES.value] = (fig, ax) plot_layout_description(descriptor, **kwargs) From b081c20a294cb97b650af1080846ef8faa48482d Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:56:46 +0200 Subject: [PATCH 06/16] Fixed bug with inconsistent determination of rotation because of float check --- .../visualize_layout/display_connectivity.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/qce_circuit/visualization/visualize_layout/display_connectivity.py b/src/qce_circuit/visualization/visualize_layout/display_connectivity.py index 28af781..8c2ebec 100644 --- a/src/qce_circuit/visualization/visualize_layout/display_connectivity.py +++ b/src/qce_circuit/visualization/visualize_layout/display_connectivity.py @@ -5,6 +5,7 @@ from collections.abc import Iterable from typing import Dict, List, Union import numpy as np +import math from qce_circuit.connectivity.intrf_channel_identifier import IQubitID, QubitIDObj from qce_circuit.connectivity.intrf_connectivity_surface_code import ISurfaceCodeLayer, IParityGroup from qce_circuit.connectivity.connectivity_surface_code import Surface17Layer @@ -270,21 +271,22 @@ def identifier_to_rotation(self, identifier: Union[IQubitID, IParityGroup]) -> f ) mean_center: bool = all(np.isclose(mean_relative_coordinates.to_vector(), Vec2D(0.0, 0.0).to_vector())) + absolute_tolerance: float = 1e-9 if mean_center: # Weight-2 diagonal line = Line2D(start=relative_coordinates[0], end=relative_coordinates[1]) slope = (line.end.y - line.start.y) / (line.end.x - line.start.x) - if slope == +1.0: + if math.isclose(slope, +1.0, abs_tol=absolute_tolerance): pass - elif slope == -1.0: + elif math.isclose(slope, -1.0, abs_tol=absolute_tolerance): rotation_offset += 90 else: # Weight-2 triangle - if mean_relative_coordinates.x == 0.0 and mean_relative_coordinates.y > 0.0: + if math.isclose(mean_relative_coordinates.x, 0.0, abs_tol=absolute_tolerance) and mean_relative_coordinates.y > 0.0: rotation_offset += 90 - elif mean_relative_coordinates.x == 0.0 and mean_relative_coordinates.y < 0.0: + elif math.isclose(mean_relative_coordinates.x, 0.0, abs_tol=absolute_tolerance) and mean_relative_coordinates.y < 0.0: rotation_offset += 270 - elif mean_relative_coordinates.x > 0.0 and mean_relative_coordinates.y == 0.0: + elif mean_relative_coordinates.x > 0.0 and math.isclose(mean_relative_coordinates.y, 0.0, abs_tol=absolute_tolerance): rotation_offset += 0 - elif mean_relative_coordinates.x < 0.0 and mean_relative_coordinates.y == 0.0: + elif mean_relative_coordinates.x < 0.0 and math.isclose(mean_relative_coordinates.y, 0.0, abs_tol=absolute_tolerance): rotation_offset += 180 if identifier.ancilla_id in self.connectivity.ancilla_qubit_ids: From 61071e43552a896e6addc52c5623947a45b491de Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:31:17 +0200 Subject: [PATCH 07/16] Updated use of minimalist visualization argument. Can now be set at plot_circuit method --- .../visualize_circuit/display_circuit.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/qce_circuit/visualization/visualize_circuit/display_circuit.py b/src/qce_circuit/visualization/visualize_circuit/display_circuit.py index e34a899..cd8b983 100644 --- a/src/qce_circuit/visualization/visualize_circuit/display_circuit.py +++ b/src/qce_circuit/visualization/visualize_circuit/display_circuit.py @@ -154,6 +154,8 @@ class VisualCircuitDescription: composite_operations: List[ICircuitCompositeOperation] """Array of composite operations.""" channel_label_map: Dict[int, str] = field(default_factory=dict) + minimalist: bool = field(default=False) + """Minimalist drawing style, passed to factories""" # region Class Properties @property @@ -199,7 +201,7 @@ def get_transform_constructor(self) -> TransformConstructor: ) def get_operation_draw_components(self) -> List[IDrawComponent]: - minimalist: bool = False + minimalist: bool = self.minimalist individual_component_factory = DrawComponentFactoryManager( default_factory=DefaultFactory(), factory_lookup={ @@ -310,7 +312,7 @@ def reorder_map(original_order: Dict[T, Any], specific_order: List[T]) -> Dict[T return result -def construct_visual_description(circuit: IDeclarativeCircuit, custom_channel_order: Optional[List[int]] = None, custom_channel_map: Optional[Dict[int, str]] = None) -> VisualCircuitDescription: +def construct_visual_description(circuit: IDeclarativeCircuit, custom_channel_order: Optional[List[int]] = None, custom_channel_map: Optional[Dict[int, str]] = None, minimalist: bool = False) -> VisualCircuitDescription: """:return: Draw description based on declarative circuit interface instance.""" channel_indices: List[int] = unique_in_order([identifier.id for identifier in circuit.occupied_qubit_channels]) # Apply custom channel order @@ -346,6 +348,7 @@ def construct_visual_description(circuit: IDeclarativeCircuit, custom_channel_or channel_states=channel_states, operations=operations, composite_operations=circuit.composite_operations, + minimalist=minimalist, ) @@ -561,7 +564,7 @@ def plot_debug_schedule(**kwargs) -> IFigureAxesPair: return fig, ax -def plot_circuit(circuit: IDeclarativeCircuit, channel_order: List[int] = None, channel_map: Optional[Dict[int, str]] = None, compact_visualization: bool = True, **kwargs) -> IFigureAxesPair: +def plot_circuit(circuit: IDeclarativeCircuit, channel_order: List[int] = None, channel_map: Optional[Dict[int, str]] = None, compact_visualization: bool = True, minimalist_visualization: bool = False, **kwargs) -> IFigureAxesPair: if compact_visualization: with temporary_override_get_registry_at(VISUALIZATION_DURATION_REGISTRY): with clear_lru_cache(RelationLink.get_start_time): @@ -570,6 +573,7 @@ def plot_circuit(circuit: IDeclarativeCircuit, channel_order: List[int] = None, circuit=circuit, custom_channel_order=channel_order, custom_channel_map=channel_map, + minimalist=minimalist_visualization, ), **kwargs ) @@ -580,6 +584,7 @@ def plot_circuit(circuit: IDeclarativeCircuit, channel_order: List[int] = None, circuit=circuit, custom_channel_order=channel_order, custom_channel_map=channel_map, + minimalist=minimalist_visualization, ), **kwargs ) From 13992c3a1c471df13fa55aaeba11f70898762935 Mon Sep 17 00:00:00 2001 From: Sean van der Meer <18538762+minisean@users.noreply.github.com> Date: Sun, 17 Aug 2025 12:21:12 +0200 Subject: [PATCH 08/16] Added basic virtual QEC (multi-pivot text) block operator --- .../structure/circuit_operations.py | 82 +++++++++++++++++++ .../structure/registry_duration.py | 2 + .../visualize_circuit/display_circuit.py | 3 + .../factory_draw_components.py | 20 +++++ .../draw_components/multi_pivot_components.py | 54 ++++++++++++ 5 files changed, 161 insertions(+) diff --git a/src/qce_circuit/structure/circuit_operations.py b/src/qce_circuit/structure/circuit_operations.py index f534f19..bc40938 100644 --- a/src/qce_circuit/structure/circuit_operations.py +++ b/src/qce_circuit/structure/circuit_operations.py @@ -943,6 +943,88 @@ def __hash__(self): # endregion +@dataclass(frozen=False, unsafe_hash=True) +class VirtualQECOperation(ICircuitOperation): + """ + Virtual QEC-block operation. + """ + qubit_indices: List[int] = field(init=True) + relation: IRelationLink[ICircuitOperation] = field(default_factory=RelationLink.no_relation) + duration_strategy: IDurationStrategy = field(default=GlobalDurationStrategy(GlobalRegistryKey.QEC_BLOCK)) + + # region Interface Properties + @property + def channel_identifiers(self) -> List[ChannelIdentifier]: + """:return: Array-like of channel identifiers to which this operation applies to.""" + return [ + ChannelIdentifier(_id=qubit_index, _channel=QubitChannel.ALL) + for qubit_index in self.qubit_indices + ] + + @property + def nr_of_repetitions(self) -> int: + """:return: Number of repetitions for this object.""" + return 1 + + @property + def relation_link(self) -> IRelationLink[ICircuitOperation]: + """:return: Description of relation to other circuit node.""" + return self.relation + + @relation_link.setter + def relation_link(self, link: IRelationLink[ICircuitOperation]): + """:sets: Description of relation to other circuit node.""" + self.relation = link + + @property + def start_time(self) -> float: + """:return: Start time [a.u.].""" + return self.relation_link.get_start_time(duration=self.duration) + + @property + def duration(self) -> float: + """:return: Duration [ns].""" + return self.duration_strategy.get_variable_duration(task=self) + # endregion + + # region Interface Methods + def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircuitOperation]] = None) -> 'VirtualQECOperation': + """ + Creates a copy from self. Excluding any relation details. + :param relation_transfer_lookup: Lookup table used to transfer relation link. + :return: Copy of self with updated relation link. + """ + return VirtualQECOperation( + qubit_indices=self.qubit_indices, + relation=self.relation.copy(relation_transfer_lookup=relation_transfer_lookup), + duration_strategy=self.duration_strategy, + ) + + def apply_modifiers_to_self(self) -> ICircuitOperation: + """ + WARNING: Applies modifiers inplace. + Applies modifiers such as repetition and state-control. + :return: Modified self. + """ + return self + + def decomposed_operations(self) -> List[ICircuitOperation]: + """ + Functions similar to a 'flatten' operation. + Mostly intended for composite-operations such that they can apply repetition and state-dependent registries. + :return: Array-like of decomposed operations. + """ + return [self] + + def apply_flatten_to_self(self) -> ICircuitOperation: + """ + WARNING: Applies a flatten modification inplace. + :return: Modified self. + """ + return self + # endregion + + @dataclass(frozen=False, unsafe_hash=True) class VirtualVacant(SingleQubitOperation, ICircuitOperation): """ diff --git a/src/qce_circuit/structure/registry_duration.py b/src/qce_circuit/structure/registry_duration.py index c04faf4..318ee8d 100644 --- a/src/qce_circuit/structure/registry_duration.py +++ b/src/qce_circuit/structure/registry_duration.py @@ -31,6 +31,7 @@ class GlobalRegistryKey(Enum): MICROWAVE = 'default_allocated_microwave_duration' FLUX = 'default_allocated_flux_duration' RESET = 'default_allocated_reset_duration' + QEC_BLOCK = 'default_allocated_qec_duration' @dataclass(frozen=True) @@ -43,6 +44,7 @@ class GlobalDurationRegistry(IRegistryGetter[GlobalRegistryKey, float]): GlobalRegistryKey.MICROWAVE.value: 1.0, GlobalRegistryKey.FLUX.value: 1.0, GlobalRegistryKey.RESET.value: 2.0, + GlobalRegistryKey.QEC_BLOCK.value: 2.0, }) # region Class Methods diff --git a/src/qce_circuit/visualization/visualize_circuit/display_circuit.py b/src/qce_circuit/visualization/visualize_circuit/display_circuit.py index cd8b983..47205d6 100644 --- a/src/qce_circuit/visualization/visualize_circuit/display_circuit.py +++ b/src/qce_circuit/visualization/visualize_circuit/display_circuit.py @@ -35,6 +35,7 @@ VirtualOptional, VirtualInjectedError, VirtualColorOverwrite, + VirtualQECOperation, ) from qce_circuit.visualization.visualize_circuit.draw_components.annotation_components import ( HorizontalVariableIndicator, @@ -116,6 +117,7 @@ VirtualOptionalFactory, VirtualInjectedErrorFactory, VirtualColorOverwriteFactory, + VirtualQECBlockFactory, ) from qce_circuit.visualization.visualize_circuit.draw_components.factory_multi_draw_components import \ MultiTwoQubitBlockFactory @@ -228,6 +230,7 @@ def get_operation_draw_components(self) -> List[IDrawComponent]: VirtualTwoQubitVacant: VirtualTwoQubitVacantFactory(), VirtualEmpty: VirtualEmptyFactory(), VirtualWait: VirtualWaitFactory(), + VirtualQECOperation: VirtualQECBlockFactory(), } ) callback_draw_manager = deepcopy(individual_component_factory) diff --git a/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py b/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py index 1e93512..d1b8f0f 100644 --- a/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py +++ b/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py @@ -36,6 +36,7 @@ VirtualInjectedError, VirtualWait, VirtualColorOverwrite, + VirtualQECOperation, ) from qce_circuit.visualization.visualize_circuit.intrf_draw_component import IDrawComponent from qce_circuit.visualization.visualize_circuit.intrf_factory_draw_components import ( @@ -64,6 +65,7 @@ BlockTwoQubitGate, BlockVerticalBarrier, BlockTwoQubitVacant, + BlockQEC, ) from qce_circuit.visualization.visualize_circuit.draw_components.annotation_components import ( HorizontalVariableIndicator, @@ -633,6 +635,24 @@ def construct(self, operation: ICircuitCompositeOperation, transform_constructor # endregion +class VirtualQECBlockFactory(IOperationDrawComponentFactory[VirtualQECOperation, IDrawComponent]): + + # region Interface Methods + def construct(self, operation: VirtualQECOperation, transform_constructor: ITransformConstructor) -> IDrawComponent: + """:return: Draw component based on operation type.""" + transforms: List[IRectTransform] = [ + transform_constructor.construct_transform( + identifier=ChannelIdentifier(_id=qubit_index, _channel=QubitChannel.ALL), + time_component=operation, + ) + for qubit_index in operation.qubit_indices + ] + return BlockQEC( + multiple_transforms=transforms, + ) + # endregion + + class VirtualOptionalFactory(IOperationDrawComponentFactory[VirtualOptional, IDrawComponent]): """ Behaviour class, implementing construction of draw component with additional requirements. diff --git a/src/qce_circuit/visualization/visualize_circuit/draw_components/multi_pivot_components.py b/src/qce_circuit/visualization/visualize_circuit/draw_components/multi_pivot_components.py index 5208476..1be5dfa 100644 --- a/src/qce_circuit/visualization/visualize_circuit/draw_components/multi_pivot_components.py +++ b/src/qce_circuit/visualization/visualize_circuit/draw_components/multi_pivot_components.py @@ -327,3 +327,57 @@ def draw(self, axes: plt.Axes) -> plt.Axes: ) return axes # endregion + + +@dataclass(frozen=True) +class BlockQEC(IRectTransformComponent, IDrawComponent): + """ + Data class, containing information to draw a vertical barrier block + that uses multiple pivots to comply with vertical alignment. + """ + multiple_transforms: List[IRectTransform] + style_settings: OperationStyleSettings = field(default_factory=lambda: StyleManager.read_config().operation_style) + + # region Interface Properties + @property + def rectilinear_transform(self) -> IRectTransform: + """:return: 'Hard' rectilinear transform boundary. Should be treated as 'personal zone'.""" + return ITransformConstructor.combine_transforms(self.rectilinear_transforms) + # endregion + + # region Class Properties + @property + def rectilinear_transforms(self) -> List[IRectTransform]: + return self.multiple_transforms + + @property + def text_center(self) -> Vec2D: + return self.rectilinear_transform.center_pivot + # endregion + + # region Interface Methods + def draw(self, axes: plt.Axes) -> plt.Axes: + """Method used for drawing component on Axes.""" + + rectangle = patches.Rectangle( + xy=self.rectilinear_transform.origin_pivot.to_tuple(), + width=self.rectilinear_transform.width, + height=self.rectilinear_transform.height, + linewidth=self.style_settings.border_width, + linestyle=self.style_settings.border_line_style, + edgecolor=self.style_settings.border_color, + facecolor=self.style_settings.background_color, + zorder=-1, + ) + axes.add_patch(rectangle) + axes.text( + x=self.text_center.x, + y=self.text_center.y, + s="QEC", + fontsize=self.style_settings.font_size, + color=self.style_settings.text_color, + ha='center', + va='center', + ) + return axes + # endregion From e486a75a8b382ea84a92285cccbcaf0c5a99020a Mon Sep 17 00:00:00 2001 From: Sean van der Meer <18538762+minisean@users.noreply.github.com> Date: Sun, 17 Aug 2025 12:46:13 +0200 Subject: [PATCH 09/16] Fixed bug related to VirtualQECOperation hashing ID based on list properties --- src/qce_circuit/structure/circuit_operations.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/qce_circuit/structure/circuit_operations.py b/src/qce_circuit/structure/circuit_operations.py index bc40938..47ce9fb 100644 --- a/src/qce_circuit/structure/circuit_operations.py +++ b/src/qce_circuit/structure/circuit_operations.py @@ -943,7 +943,7 @@ def __hash__(self): # endregion -@dataclass(frozen=False, unsafe_hash=True) +@dataclass(frozen=False) class VirtualQECOperation(ICircuitOperation): """ Virtual QEC-block operation. @@ -1024,6 +1024,12 @@ def apply_flatten_to_self(self) -> ICircuitOperation: return self # endregion + # region Class Methods + def __hash__(self): + """Overwrites @dataclass behaviour. Circuit operation requires hash based on instance identity.""" + return id(self) + # endregion + @dataclass(frozen=False, unsafe_hash=True) class VirtualVacant(SingleQubitOperation, ICircuitOperation): From 66bbee2f116f6db70ecf37e3c6f0532715bfffc7 Mon Sep 17 00:00:00 2001 From: Sean van der Meer <18538762+minisean@users.noreply.github.com> Date: Sun, 17 Aug 2025 12:46:38 +0200 Subject: [PATCH 10/16] Fixed bug where overwrite color was not copied over during copy operation --- src/qce_circuit/structure/circuit_operations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qce_circuit/structure/circuit_operations.py b/src/qce_circuit/structure/circuit_operations.py index 47ce9fb..86629cd 100644 --- a/src/qce_circuit/structure/circuit_operations.py +++ b/src/qce_circuit/structure/circuit_operations.py @@ -1375,7 +1375,8 @@ def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircu return VirtualColorOverwrite( operation=self.operation.copy( relation_transfer_lookup=relation_transfer_lookup, - ) + ), + color_overwrite=self.color_overwrite, ) def apply_modifiers_to_self(self) -> ICircuitOperation: From f5db241decf9d94261c2582365f92ca63b28861a Mon Sep 17 00:00:00 2001 From: Sean van der Meer <18538762+minisean@users.noreply.github.com> Date: Sun, 17 Aug 2025 12:47:02 +0200 Subject: [PATCH 11/16] Added color_outline_dim to be included in color overwrite --- .../visualize_circuit/draw_components/factory_draw_components.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py b/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py index d1b8f0f..2c1d1a2 100644 --- a/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py +++ b/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py @@ -714,6 +714,7 @@ def construct(self, operation: VirtualColorOverwrite, transform_constructor: ITr color_text=operation.color_overwrite, color_icon=operation.color_overwrite, color_outline=operation.color_overwrite, + color_outline_dim=operation.color_overwrite, )): draw_component: IDrawComponent = self._factory_manager.construct( operation=operation.operation, From 74396212b5b8b074f62409e9bff34c50dc9b5791 Mon Sep 17 00:00:00 2001 From: Sean van der Meer <18538762+minisean@users.noreply.github.com> Date: Sun, 17 Aug 2025 12:51:15 +0200 Subject: [PATCH 12/16] Added global barrier duration registry --- src/qce_circuit/structure/circuit_operations.py | 2 +- src/qce_circuit/structure/registry_duration.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/qce_circuit/structure/circuit_operations.py b/src/qce_circuit/structure/circuit_operations.py index 86629cd..0337468 100644 --- a/src/qce_circuit/structure/circuit_operations.py +++ b/src/qce_circuit/structure/circuit_operations.py @@ -864,7 +864,7 @@ class Barrier(ICircuitOperation): """ qubit_indices: List[int] = field(init=True) relation: IRelationLink[ICircuitOperation] = field(init=False, default_factory=RelationLink.no_relation) - duration_strategy: IDurationStrategy = field(init=False, default=FixedDurationStrategy(duration=0.5)) + duration_strategy: IDurationStrategy = field(init=False, default=GlobalDurationStrategy(GlobalRegistryKey.BARRIER)) # region Interface Properties @property diff --git a/src/qce_circuit/structure/registry_duration.py b/src/qce_circuit/structure/registry_duration.py index 318ee8d..1b26ac6 100644 --- a/src/qce_circuit/structure/registry_duration.py +++ b/src/qce_circuit/structure/registry_duration.py @@ -32,6 +32,7 @@ class GlobalRegistryKey(Enum): FLUX = 'default_allocated_flux_duration' RESET = 'default_allocated_reset_duration' QEC_BLOCK = 'default_allocated_qec_duration' + BARRIER = 'default_allocated_barrier_duration' @dataclass(frozen=True) @@ -45,6 +46,7 @@ class GlobalDurationRegistry(IRegistryGetter[GlobalRegistryKey, float]): GlobalRegistryKey.FLUX.value: 1.0, GlobalRegistryKey.RESET.value: 2.0, GlobalRegistryKey.QEC_BLOCK.value: 2.0, + GlobalRegistryKey.BARRIER.value: 0.5, }) # region Class Methods From 7b9593de12ba957334b0f5718d7113e45bc37f61 Mon Sep 17 00:00:00 2001 From: Sean van der Meer <18538762+minisean@users.noreply.github.com> Date: Sun, 17 Aug 2025 15:48:33 +0200 Subject: [PATCH 13/16] Updated VirtualInjectedError by allowing custom line style and background color overwrites --- src/qce_circuit/structure/circuit_operations.py | 6 +++++- .../draw_components/factory_draw_components.py | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/qce_circuit/structure/circuit_operations.py b/src/qce_circuit/structure/circuit_operations.py index 0337468..757c12b 100644 --- a/src/qce_circuit/structure/circuit_operations.py +++ b/src/qce_circuit/structure/circuit_operations.py @@ -1215,6 +1215,8 @@ class VirtualInjectedError(ICircuitOperation): Acts as a visualization wrapper for injected errors. """ operation: SingleQubitOperation + line_style_border_overwrite: str = field(default="--") + color_background_overwrite: str = field(default="#ff9999") # region Interface Properties @property @@ -1258,7 +1260,9 @@ def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircu return VirtualInjectedError( operation=self.operation.copy( relation_transfer_lookup=relation_transfer_lookup, - ) + ), + line_style_border_overwrite=self.line_style_border_overwrite, + color_background_overwrite=self.color_background_overwrite, ) def apply_modifiers_to_self(self) -> ICircuitOperation: diff --git a/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py b/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py index 2c1d1a2..6363464 100644 --- a/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py +++ b/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py @@ -688,7 +688,10 @@ def __init__(self, callback_draw_manager: IOperationDrawComponentFactoryManager) # region Interface Methods def construct(self, operation: VirtualInjectedError, transform_constructor: ITransformConstructor) -> IDrawComponent: """:return: Draw component based on operation type.""" - with StyleManager.temporary_override(**dict(line_style_border='--', color_background="#ff9999")): + with StyleManager.temporary_override(**dict( + line_style_border=operation.line_style_border_overwrite, + color_background=operation.color_background_overwrite, + )): draw_component: IDrawComponent = self._factory_manager.construct( operation=operation.operation, transform_constructor=transform_constructor, From 77d360fbf4f5dd8664b8db807dc2c31281816225 Mon Sep 17 00:00:00 2001 From: Sean van der Meer <18538762+minisean@users.noreply.github.com> Date: Sun, 17 Aug 2025 18:39:42 +0200 Subject: [PATCH 14/16] MAJOR Refactoring adding ITwoQubitOperation interface to describe any two-qubit operation with control and target qubit indices. Introduces breaking change when instantiating any TwoQubitOperation and any VirtualColorOverwrite --- .../structure/circuit_modifiers.py | 4 +- .../structure/circuit_operations.py | 186 ++++++++++++++++-- .../structure/intrf_circuit_operation.py | 17 ++ .../visualize_circuit/display_circuit.py | 11 +- .../factory_draw_components.py | 8 +- .../intrf_factory_draw_components.py | 9 +- tests/language/test_declarative_circuit.py | 84 ++++---- 7 files changed, 245 insertions(+), 74 deletions(-) diff --git a/src/qce_circuit/structure/circuit_modifiers.py b/src/qce_circuit/structure/circuit_modifiers.py index 2630dc5..6d946c2 100644 --- a/src/qce_circuit/structure/circuit_modifiers.py +++ b/src/qce_circuit/structure/circuit_modifiers.py @@ -183,8 +183,8 @@ def match(self, matched_operation: TMaskedTwoQubitOperation) -> bool: def construct_operation_mask(self, masked_operation: TMaskedTwoQubitOperation) -> VirtualTwoQubitVacant: """:return: Newly constructed 'mask'-operation based on masked-operation.""" return VirtualTwoQubitVacant( - control_qubit_index=masked_operation.control_qubit_index, - target_qubit_index=masked_operation.target_qubit_index, + _control_qubit_index=masked_operation.control_qubit_index, + _target_qubit_index=masked_operation.target_qubit_index, relation=masked_operation.relation_link, duration_strategy=masked_operation.duration_strategy, ) diff --git a/src/qce_circuit/structure/circuit_operations.py b/src/qce_circuit/structure/circuit_operations.py index 757c12b..3fe430a 100644 --- a/src/qce_circuit/structure/circuit_operations.py +++ b/src/qce_circuit/structure/circuit_operations.py @@ -1,14 +1,17 @@ # ------------------------------------------- # Module describing the declarative operations. # ------------------------------------------- +from abc import ABC, abstractmethod from dataclasses import dataclass, field from typing import List, Optional, Dict +from qce_circuit.utilities.custom_exceptions import InterfaceMethodException from qce_circuit.structure.intrf_circuit_operation import ( QubitChannel, IRelationLink, RelationLink, ChannelIdentifier, ICircuitOperation, + ITwoQubitOperation, ) from qce_circuit.structure.intrf_acquisition_operation import ( IAcquisitionOperation, @@ -594,16 +597,16 @@ def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircu @dataclass(frozen=False, unsafe_hash=True) -class TwoQubitOperation(ICircuitOperation): +class TwoQubitOperation(ITwoQubitOperation): """ Minimal operation describes two-qubit implementation of ICircuitOperation. """ - control_qubit_index: int = field(init=True) - target_qubit_index: int = field(init=True) + _control_qubit_index: int = field(init=True) + _target_qubit_index: int = field(init=True) relation: IRelationLink[ICircuitOperation] = field(default_factory=RelationLink.no_relation) duration_strategy: IDurationStrategy = field(default=FixedDurationStrategy(duration=0.0)) - # region Interface Properties + # region ICircuitOperation Properties @property def channel_identifiers(self) -> List[ChannelIdentifier]: """:return: Array-like of channel identifiers to which this operation applies to.""" @@ -638,6 +641,18 @@ def duration(self) -> float: return self.duration_strategy.get_variable_duration(task=self) # endregion + # region ITwoQubitOperation Properties + @property + def control_qubit_index(self) -> int: + """:return: Index of control qubit.""" + return self._control_qubit_index + + @property + def target_qubit_index(self) -> int: + """:return: Index of target qubit.""" + return self._target_qubit_index + # endregion + # region Interface Methods def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircuitOperation]] = None) -> 'TwoQubitOperation': """ @@ -646,8 +661,8 @@ def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircu :return: Copy of self with updated relation link. """ return TwoQubitOperation( - control_qubit_index=self.control_qubit_index, - target_qubit_index=self.target_qubit_index, + _control_qubit_index=self.control_qubit_index, + _target_qubit_index=self.target_qubit_index, relation=self.relation.copy(relation_transfer_lookup=relation_transfer_lookup), duration_strategy=self.duration_strategy, ) @@ -704,8 +719,8 @@ def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircu :return: Copy of self with updated relation link. """ return CPhase( - control_qubit_index=self.control_qubit_index, - target_qubit_index=self.target_qubit_index, + _control_qubit_index=self.control_qubit_index, + _target_qubit_index=self.target_qubit_index, relation=self.relation.copy(relation_transfer_lookup=relation_transfer_lookup), ) # endregion @@ -736,8 +751,8 @@ def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircu :return: Copy of self with updated relation link. """ return TwoQubitVirtualPhase( - control_qubit_index=self.control_qubit_index, - target_qubit_index=self.target_qubit_index, + _control_qubit_index=self.control_qubit_index, + _target_qubit_index=self.target_qubit_index, relation=self.relation.copy(relation_transfer_lookup=relation_transfer_lookup), ) # endregion @@ -1090,8 +1105,8 @@ def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircu :return: Copy of self with updated relation link. """ return VirtualTwoQubitVacant( - control_qubit_index=self.control_qubit_index, - target_qubit_index=self.target_qubit_index, + _control_qubit_index=self.control_qubit_index, + _target_qubit_index=self.target_qubit_index, relation=self.relation.copy(relation_transfer_lookup=relation_transfer_lookup), ) # endregion @@ -1328,16 +1343,33 @@ def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircu # endregion +class IColorOverwrite(ABC): + + # region Interface Properties + @property + @abstractmethod + def wrapped_operation(self) -> ICircuitOperation: + """:return: Wrapped operation used to pass to sub-draw factories.""" + raise InterfaceMethodException + + @property + @abstractmethod + def color_overwrite(self) -> str: + """:return: Color identifier for overwriting draw factory colors.""" + raise InterfaceMethodException + # endregion + + @dataclass(frozen=False, unsafe_hash=True) -class VirtualColorOverwrite(ICircuitOperation): +class VirtualColorOverwrite(ICircuitOperation, IColorOverwrite): """ Data class, containing single-qubit operation. Acts as a visualization wrapper for coloring visualization. """ - operation: SingleQubitOperation - color_overwrite: str = field(init=True, default="k") + operation: ICircuitOperation + _color_overwrite: str = field(init=True, default="k") - # region Interface Properties + # region ICircuitOperation Properties @property def channel_identifiers(self) -> List[ChannelIdentifier]: """:return: Array-like of channel identifiers to which this operation applies to.""" @@ -1351,12 +1383,12 @@ def nr_of_repetitions(self) -> int: @property def relation_link(self) -> IRelationLink[ICircuitOperation]: """:return: Description of relation to other circuit node.""" - return self.operation.relation + return self.operation.relation_link @relation_link.setter def relation_link(self, link: IRelationLink[ICircuitOperation]): """:sets: Description of relation to other circuit node.""" - self.operation.relation = link + self.operation.relation_link = link @property def start_time(self) -> float: @@ -1369,6 +1401,18 @@ def duration(self) -> float: return self.operation.duration # endregion + # region IColorOverwrite Properties + @property + def wrapped_operation(self) -> ICircuitOperation: + """:return: Wrapped operation used to pass to sub-draw factories.""" + return self.operation + + @property + def color_overwrite(self) -> str: + """:return: Color identifier for overwriting draw factory colors.""" + return self._color_overwrite + # endregion + # region Interface Methods def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircuitOperation]] = None) -> 'VirtualColorOverwrite': """ @@ -1380,7 +1424,111 @@ def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircu operation=self.operation.copy( relation_transfer_lookup=relation_transfer_lookup, ), - color_overwrite=self.color_overwrite, + _color_overwrite=self._color_overwrite, + ) + + def apply_modifiers_to_self(self) -> ICircuitOperation: + """ + WARNING: Applies modifiers inplace. + Applies modifiers such as repetition and state-control. + :return: Modified self. + """ + return self + + def decomposed_operations(self) -> List[ICircuitOperation]: + """ + Functions similar to a 'flatten' operation. + Mostly intended for composite-operations such that they can apply repetition and state-dependent registries. + :return: Array-like of decomposed operations. + """ + return [self] + + def apply_flatten_to_self(self) -> ICircuitOperation: + """ + WARNING: Applies a flatten modification inplace. + :return: Modified self. + """ + return self + # endregion + + +@dataclass(frozen=False, unsafe_hash=True) +class VirtualTwoQubitColorOverwrite(ITwoQubitOperation, IColorOverwrite): + """ + Virtual color overwrite operation (behaves as TwoQubitOperation). + Acts as a visualization wrapper for coloring visualization. + """ + operation: ITwoQubitOperation = field(init=True) + _color_overwrite: str = field(init=True) + + # region ICircuitOperation Properties + @property + def channel_identifiers(self) -> List[ChannelIdentifier]: + """:return: Array-like of channel identifiers to which this operation applies to.""" + return self.operation.channel_identifiers + + @property + def nr_of_repetitions(self) -> int: + """:return: Number of repetitions for this object.""" + return self.operation.nr_of_repetitions + + @property + def relation_link(self) -> IRelationLink[ICircuitOperation]: + """:return: Description of relation to other circuit node.""" + return self.operation.relation_link + + @relation_link.setter + def relation_link(self, link: IRelationLink[ICircuitOperation]): + """:sets: Description of relation to other circuit node.""" + self.operation.relation_link = link + + @property + def start_time(self) -> float: + """:return: Start time [a.u.].""" + return self.operation.start_time + + @property + def duration(self) -> float: + """:return: Duration [ns].""" + return self.operation.duration + # endregion + + # region ITwoQubitOperation Properties + @property + def control_qubit_index(self) -> int: + """:return: Index of control qubit.""" + return self.operation.control_qubit_index + + @property + def target_qubit_index(self) -> int: + """:return: Index of target qubit.""" + return self.operation.target_qubit_index + # endregion + + # region IColorOverwrite Properties + @property + def wrapped_operation(self) -> ICircuitOperation: + """:return: Wrapped operation used to pass to sub-draw factories.""" + return self.operation + + @property + def color_overwrite(self) -> str: + """:return: Color identifier for overwriting draw factory colors.""" + return self._color_overwrite + # endregion + + # region Interface Methods + def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircuitOperation]] = None) -> 'VirtualTwoQubitColorOverwrite': + """ + Creates a copy from self. Excluding any relation details. + :param relation_transfer_lookup: Lookup table used to transfer relation link. + :return: Copy of self with updated relation link. + """ + return VirtualTwoQubitColorOverwrite( + operation=self.operation.copy( + relation_transfer_lookup=relation_transfer_lookup, + ), + _color_overwrite=self._color_overwrite, ) def apply_modifiers_to_self(self) -> ICircuitOperation: diff --git a/src/qce_circuit/structure/intrf_circuit_operation.py b/src/qce_circuit/structure/intrf_circuit_operation.py index 05e40dc..c448be8 100644 --- a/src/qce_circuit/structure/intrf_circuit_operation.py +++ b/src/qce_circuit/structure/intrf_circuit_operation.py @@ -338,6 +338,23 @@ def __hash__(self): TCircuitOperation = TypeVar('TCircuitOperation', bound=ICircuitOperation) +class ITwoQubitOperation(ICircuitOperation, metaclass=ABCMeta): + + # region Interface Properties + @property + @abstractmethod + def control_qubit_index(self) -> int: + """:return: Index of control qubit.""" + raise InterfaceMethodException + + @property + @abstractmethod + def target_qubit_index(self) -> int: + """:return: Index of target qubit.""" + raise InterfaceMethodException + # endregion + + @dataclass(frozen=True) class MultiRelationLink(IRelationLink[TCircuitOperation], Generic[TCircuitOperation]): """ diff --git a/src/qce_circuit/visualization/visualize_circuit/display_circuit.py b/src/qce_circuit/visualization/visualize_circuit/display_circuit.py index 47205d6..d5898b9 100644 --- a/src/qce_circuit/visualization/visualize_circuit/display_circuit.py +++ b/src/qce_circuit/visualization/visualize_circuit/display_circuit.py @@ -22,6 +22,7 @@ VirtualPhase, Rphi90, CPhase, + ITwoQubitOperation, TwoQubitOperation, DispersiveMeasure, Identity, @@ -35,6 +36,7 @@ VirtualOptional, VirtualInjectedError, VirtualColorOverwrite, + VirtualTwoQubitColorOverwrite, VirtualQECOperation, ) from qce_circuit.visualization.visualize_circuit.draw_components.annotation_components import ( @@ -243,9 +245,10 @@ def get_operation_draw_components(self) -> List[IDrawComponent]: factory_manager: BulkDrawComponentFactoryManager = BulkDrawComponentFactoryManager( individual_component_factory=individual_component_factory, factory_lookup={ - TwoQubitOperation: MultiTwoQubitBlockFactory(factory_lookup={ + ITwoQubitOperation: MultiTwoQubitBlockFactory(factory_lookup={ CPhase: TwoQubitBlockFactory(), - VirtualTwoQubitVacant: VirtualTwoQubitVacantFactory() + VirtualTwoQubitVacant: VirtualTwoQubitVacantFactory(), + VirtualTwoQubitColorOverwrite: VirtualColorOverwriteFactory(callback_draw_manager=callback_draw_manager), }), } ) @@ -665,8 +668,8 @@ def plot_circuit_description(description: VisualCircuitDescription, **kwargs) -> qubit_index=0, )) sub_circuit.add(CPhase( - control_qubit_index=0, - target_qubit_index=1, + _control_qubit_index=0, + _target_qubit_index=1, )) sub_circuit.add(Rx180( qubit_index=0, diff --git a/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py b/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py index 6363464..9f2a107 100644 --- a/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py +++ b/src/qce_circuit/visualization/visualize_circuit/draw_components/factory_draw_components.py @@ -35,7 +35,7 @@ VirtualOptional, VirtualInjectedError, VirtualWait, - VirtualColorOverwrite, + IColorOverwrite, VirtualQECOperation, ) from qce_circuit.visualization.visualize_circuit.intrf_draw_component import IDrawComponent @@ -700,7 +700,7 @@ def construct(self, operation: VirtualInjectedError, transform_constructor: ITra # endregion -class VirtualColorOverwriteFactory(IOperationDrawComponentFactory[VirtualColorOverwrite, IDrawComponent]): +class VirtualColorOverwriteFactory(IOperationDrawComponentFactory[IColorOverwrite, IDrawComponent]): """ Behaviour class, implementing construction of draw component with additional requirements. """ @@ -711,7 +711,7 @@ def __init__(self, callback_draw_manager: IOperationDrawComponentFactoryManager) # endregion # region Interface Methods - def construct(self, operation: VirtualColorOverwrite, transform_constructor: ITransformConstructor) -> IDrawComponent: + def construct(self, operation: IColorOverwrite, transform_constructor: ITransformConstructor) -> IDrawComponent: """:return: Draw component based on operation type.""" with StyleManager.temporary_override(**dict( color_text=operation.color_overwrite, @@ -720,7 +720,7 @@ def construct(self, operation: VirtualColorOverwrite, transform_constructor: ITr color_outline_dim=operation.color_overwrite, )): draw_component: IDrawComponent = self._factory_manager.construct( - operation=operation.operation, + operation=operation.wrapped_operation, transform_constructor=transform_constructor, ) return draw_component diff --git a/src/qce_circuit/visualization/visualize_circuit/intrf_factory_draw_components.py b/src/qce_circuit/visualization/visualize_circuit/intrf_factory_draw_components.py index 359bf5b..4ff4327 100644 --- a/src/qce_circuit/visualization/visualize_circuit/intrf_factory_draw_components.py +++ b/src/qce_circuit/visualization/visualize_circuit/intrf_factory_draw_components.py @@ -13,7 +13,10 @@ IDurationComponent, ChannelIdentifier, ) -from qce_circuit.structure.circuit_operations import TwoQubitOperation +from qce_circuit.structure.circuit_operations import ( + TwoQubitOperation, + ITwoQubitOperation, +) from qce_circuit.utilities.geometric_definitions import ( IRectTransform, TransformAlignment, @@ -179,8 +182,8 @@ def construct(self, operations: List[ICircuitOperation], transform_constructor: for operation in operations: operation_type = type(operation) # Work around, grouping two-qubit operations - if isinstance(operation, TwoQubitOperation): - operation_type = TwoQubitOperation + if isinstance(operation, ITwoQubitOperation): + operation_type = ITwoQubitOperation if operation_type not in operation_lookup: operation_lookup[operation_type] = [operation] diff --git a/tests/language/test_declarative_circuit.py b/tests/language/test_declarative_circuit.py index a45491b..60d61ec 100644 --- a/tests/language/test_declarative_circuit.py +++ b/tests/language/test_declarative_circuit.py @@ -41,8 +41,8 @@ def test_circuit_example_0(self): qubit_index=2, )) circuit.add(CPhase( - control_qubit_index=8, - target_qubit_index=2, + _control_qubit_index=8, + _target_qubit_index=2, )) circuit.add(VirtualPhase( qubit_index=2, @@ -85,8 +85,8 @@ def test_circuit_example_1(self): qubit_index=8, )) circuit.add(CPhase( - control_qubit_index=8, - target_qubit_index=2, + _control_qubit_index=8, + _target_qubit_index=2, )) # Park circuit.add(VirtualPhase( @@ -97,8 +97,8 @@ def test_circuit_example_1(self): )) circuit.add(CPhase( - control_qubit_index=8, - target_qubit_index=0, + _control_qubit_index=8, + _target_qubit_index=0, )) # Park circuit.add(VirtualPhase( @@ -149,8 +149,8 @@ def test_circuit_example_2(self): qubit_index=0, )) circuit.add(CPhase( - control_qubit_index=8, - target_qubit_index=2, + _control_qubit_index=8, + _target_qubit_index=2, )) # Park circuit.add(VirtualPhase( @@ -161,8 +161,8 @@ def test_circuit_example_2(self): )) circuit.add(CPhase( - control_qubit_index=8, - target_qubit_index=0, + _control_qubit_index=8, + _target_qubit_index=0, )) # Park circuit.add(VirtualPhase( @@ -215,8 +215,8 @@ def test_circuit_example_3(self): qubit_index=0, )) sub_circuit.add(CPhase( - control_qubit_index=8, - target_qubit_index=2, + _control_qubit_index=8, + _target_qubit_index=2, )) # Park sub_circuit.add(VirtualPhase( @@ -227,8 +227,8 @@ def test_circuit_example_3(self): )) sub_circuit.add(CPhase( - control_qubit_index=8, - target_qubit_index=0, + _control_qubit_index=8, + _target_qubit_index=0, )) # Park sub_circuit.add(VirtualPhase( @@ -324,12 +324,12 @@ def test_circuit_example_4(self): qubit_index=qubit_ancilla_index, )) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla_index, - target_qubit_index=qubit_data1_index, + _control_qubit_index=qubit_ancilla_index, + _target_qubit_index=qubit_data1_index, )) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla_index, - target_qubit_index=qubit_data2_index, + _control_qubit_index=qubit_ancilla_index, + _target_qubit_index=qubit_data2_index, )) relation: RelationLink = RelationLink(individual_parity_circuit.get_last_entry(), RelationType.FOLLOWED_BY) individual_parity_circuit.add(Rym90( @@ -405,25 +405,25 @@ def test_circuit_example_5(self): qubit_index=qubit_ancilla1_index, )) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla1_index, - target_qubit_index=qubit_data2_index, + _control_qubit_index=qubit_ancilla1_index, + _target_qubit_index=qubit_data2_index, )) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla1_index, - target_qubit_index=qubit_data1_index, + _control_qubit_index=qubit_ancilla1_index, + _target_qubit_index=qubit_data1_index, )) individual_parity_circuit.add(Rym90( qubit_index=qubit_ancilla1_index, )) relation: RelationLink = RelationLink(individual_parity_circuit.get_last_entry(), RelationType.FOLLOWED_BY) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla2_index, - target_qubit_index=qubit_data3_index, + _control_qubit_index=qubit_ancilla2_index, + _target_qubit_index=qubit_data3_index, relation=relation, )) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla2_index, - target_qubit_index=qubit_data2_index, + _control_qubit_index=qubit_ancilla2_index, + _target_qubit_index=qubit_data2_index, )) repeated_parity_circuit: DeclarativeCircuit = DeclarativeCircuit( @@ -746,22 +746,22 @@ def test_circuit_example_6(self): relation=RelationLink(individual_parity_circuit.get_last_entry(), RelationType.JOINED_START) )) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla1_index, - target_qubit_index=qubit_data2_index, + _control_qubit_index=qubit_ancilla1_index, + _target_qubit_index=qubit_data2_index, )) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla3_index, - target_qubit_index=qubit_data4_index, + _control_qubit_index=qubit_ancilla3_index, + _target_qubit_index=qubit_data4_index, relation=RelationLink(individual_parity_circuit.get_last_entry(), RelationType.FOLLOWED_BY) )) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla1_index, - target_qubit_index=qubit_data1_index, + _control_qubit_index=qubit_ancilla1_index, + _target_qubit_index=qubit_data1_index, relation=RelationLink(individual_parity_circuit.get_last_entry(), RelationType.FOLLOWED_BY) )) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla3_index, - target_qubit_index=qubit_data5_index, + _control_qubit_index=qubit_ancilla3_index, + _target_qubit_index=qubit_data5_index, relation=RelationLink(individual_parity_circuit.get_last_entry(), RelationType.FOLLOWED_BY) )) individual_parity_circuit.add(Rym90( @@ -782,22 +782,22 @@ def test_circuit_example_6(self): relation=relation )) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla2_index, - target_qubit_index=qubit_data3_index, + _control_qubit_index=qubit_ancilla2_index, + _target_qubit_index=qubit_data3_index, )) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla4_index, - target_qubit_index=qubit_data1_index, + _control_qubit_index=qubit_ancilla4_index, + _target_qubit_index=qubit_data1_index, relation=RelationLink(individual_parity_circuit.get_last_entry(), RelationType.FOLLOWED_BY) )) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla2_index, - target_qubit_index=qubit_data2_index, + _control_qubit_index=qubit_ancilla2_index, + _target_qubit_index=qubit_data2_index, relation=RelationLink(individual_parity_circuit.get_last_entry(), RelationType.FOLLOWED_BY) )) individual_parity_circuit.add(CPhase( - control_qubit_index=qubit_ancilla4_index, - target_qubit_index=qubit_data4_index, + _control_qubit_index=qubit_ancilla4_index, + _target_qubit_index=qubit_data4_index, relation=RelationLink(individual_parity_circuit.get_last_entry(), RelationType.FOLLOWED_BY) )) individual_parity_circuit.add(Rym90( From 1b1b7e8d53037dc7665c1e62e37d0fea650191fa Mon Sep 17 00:00:00 2001 From: Sean van der Meer <18538762+minisean@users.noreply.github.com> Date: Sun, 17 Aug 2025 18:49:06 +0200 Subject: [PATCH 15/16] Added factory pattern for recognizing Virtual (two-qubit) color overwrites --- src/qce_circuit/structure/circuit_operations.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/qce_circuit/structure/circuit_operations.py b/src/qce_circuit/structure/circuit_operations.py index 3fe430a..e70524f 100644 --- a/src/qce_circuit/structure/circuit_operations.py +++ b/src/qce_circuit/structure/circuit_operations.py @@ -1413,6 +1413,23 @@ def color_overwrite(self) -> str: return self._color_overwrite # endregion + # region Class Constructor + def __new__(cls, operation: ICircuitOperation, _color_overwrite: str = "k"): + """ + Factory method to select the correct wrapper class at instantiation. + """ + # Check if the operation is a two-qubit operation. + if isinstance(operation, ITwoQubitOperation): + # If so, instantiate and return the specialized two-qubit wrapper. + return VirtualTwoQubitColorOverwrite( + operation=operation, + _color_overwrite=_color_overwrite + ) + else: + # Otherwise, proceed with the standard instantiation of this class. + return super().__new__(cls) + # endregion + # region Interface Methods def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircuitOperation]] = None) -> 'VirtualColorOverwrite': """ From 471a856257090991aca28cb0d4cfb8d8937d62d6 Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Fri, 29 Aug 2025 16:03:17 +0200 Subject: [PATCH 16/16] Updated fixed bug related to temporary overwrite of global duration strategy --- src/qce_circuit/structure/registry_duration.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/qce_circuit/structure/registry_duration.py b/src/qce_circuit/structure/registry_duration.py index 1b26ac6..e53b73f 100644 --- a/src/qce_circuit/structure/registry_duration.py +++ b/src/qce_circuit/structure/registry_duration.py @@ -100,7 +100,12 @@ def temporary_override_get_registry_at(temp_registry: Dict[GlobalRegistryKey, fl original_method = GlobalDurationRegistry.get_registry_at def temp_get_registry_at(self, key: GlobalRegistryKey) -> Optional[float]: - return temp_registry.get(key, None) + extended_lookup: Dict[str, float] = GlobalDurationRegistry()._global_registry + # Update + for _key, _value in temp_registry.items(): + extended_lookup[_key.value] = _value + + return extended_lookup.get(key.value, None) try: GlobalDurationRegistry.get_registry_at = temp_get_registry_at