From 2933f677553cebc4626c84366cadfdc5e30c90bf Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:52:37 +0100 Subject: [PATCH 1/7] Added net-zero virtual parking visualization --- .../structure/circuit_operations.py | 2 + .../factory_draw_components.py | 9 ++ .../draw_components/operation_components.py | 84 +++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/src/qce_circuit/structure/circuit_operations.py b/src/qce_circuit/structure/circuit_operations.py index 914c048..a5c78d8 100644 --- a/src/qce_circuit/structure/circuit_operations.py +++ b/src/qce_circuit/structure/circuit_operations.py @@ -477,6 +477,8 @@ class VirtualPark(SingleQubitOperation, ICircuitOperation): Usually only interesting when working with frequency-tunable qubits. """ duration_strategy: IDurationStrategy = field(init=False, default=GlobalDurationStrategy(GlobalRegistryKey.FLUX)) + net_zero: bool = field(init=True, default=False) + """Boolean describing the net-zero behaviour of the virtual parking. - Mainly useful for visualization.""" # region Interface Properties @property 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 2f8f534..5c2c11e 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 @@ -49,6 +49,7 @@ RotationAxis, RotationAngle, SquareParkBlock, + SquareNetZeroParkBlock, ) from qce_circuit.visualization.visualize_circuit.draw_components.multi_pivot_components import ( BlockTwoQubitGate, @@ -318,6 +319,14 @@ def construct(self, operation: VirtualPark, transform_constructor: ITransformCon identifier=operation.channel_identifiers[0], time_component=operation, ) + if operation.net_zero: + return SquareNetZeroParkBlock( + pivot=transform.pivot, + height=transform.height, + width=transform.width, + alignment=transform.parent_alignment, + ) + return SquareParkBlock( pivot=transform.pivot, height=transform.height, 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 6dc9e3c..b80472a 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 @@ -387,3 +387,87 @@ def draw(self, axes: plt.Axes) -> plt.Axes: return axes # endregion + + +@dataclass(frozen=True) +class SquareNetZeroParkBlock(IRectTransformComponent, IDrawComponent): + """ + Data class, containing dimension data for drawing (net-zero) square (flux) parking block. + """ + pivot: Vec2D + height: float + width: float + alignment: TransformAlignment = field(default=TransformAlignment.MID_LEFT) + style_settings: ChannelStyleSettings = field(default=StyleManager.read_config().channel_style) + + # region Interface Properties + @property + def rectilinear_transform(self) -> IRectTransform: + """:return: 'Hard' rectilinear transform boundary. Should be treated as 'personal zone'.""" + return RectTransform( + _pivot_strategy=FixedPivot(self.pivot), + _width_strategy=FixedLength(self.width), + _height_strategy=FixedLength(self.height), + _parent_alignment=self.alignment, + ) + # endregion + + # region Class Methods + def draw(self, axes: plt.Axes) -> plt.Axes: + """Method used for drawing component on Axes.""" + transform: IRectTransform = self.rectilinear_transform + horizontal_extension: float = 0.1 * transform.width + width_ratio: float = 0.8 + width: float = width_ratio * (transform.right_pivot.x - transform.left_pivot.x) + half_width: float = 0.5 * width + + # Temporary to cover the background header bar + cover_arc_xcoords: np.ndarray = np.asarray([ + transform.left_pivot.x, + transform.right_pivot.x, + ]) + cover_arc_ycoords: np.ndarray = np.asarray([ + transform.left_pivot.y, + transform.right_pivot.y, + ]) + axes.plot( + cover_arc_xcoords, + cover_arc_ycoords, + linestyle='-', + linewidth=self.style_settings.line_width * 2, + color='white', # + zorder=-16, + ) + + # Parking line + arc_xcoords: np.ndarray = np.asarray([ + transform.left_pivot.x - horizontal_extension, + transform.center_pivot.x - half_width, + transform.center_pivot.x - half_width, + transform.center_pivot.x, + transform.center_pivot.x, + transform.center_pivot.x + half_width, + transform.center_pivot.x + half_width, + transform.right_pivot.x + horizontal_extension, + ]) + arc_ycoords: np.ndarray = np.asarray([ + transform.left_pivot.y, + transform.left_pivot.y, + transform.bot_pivot.y, + transform.bot_pivot.y, + transform.top_pivot.y, + transform.top_pivot.y, + transform.right_pivot.y, + transform.right_pivot.y, + ]) + axes.plot( + arc_xcoords, + arc_ycoords, + linestyle='-', + linewidth=self.style_settings.line_width, + color=self.style_settings.line_color, + zorder=-15, + ) + + return axes + # endregion From c31cff49723336ddaca5cf012f529952a708e1f8 Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Fri, 31 Jan 2025 17:39:49 +0100 Subject: [PATCH 2/7] Added temporary style setting override context manager for StyleManager. Allows for custom style overwrites within context. --- .../structure/circuit_operations.py | 76 +++++++++++++++++++ .../visualize_circuit/display_circuit.py | 58 ++++++++------ .../factory_draw_components.py | 24 ++++++ .../draw_components/operation_components.py | 5 +- .../visualize_circuit/style_manager.py | 35 ++++++++- 5 files changed, 170 insertions(+), 28 deletions(-) diff --git a/src/qce_circuit/structure/circuit_operations.py b/src/qce_circuit/structure/circuit_operations.py index a5c78d8..9fbad37 100644 --- a/src/qce_circuit/structure/circuit_operations.py +++ b/src/qce_circuit/structure/circuit_operations.py @@ -982,6 +982,82 @@ def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircu # endregion +@dataclass(frozen=False, unsafe_hash=True) +class VirtualOptional(ICircuitOperation): + """ + Data class, containing single-qubit operation. + Acts as a visualization wrapper. + """ + operation: SingleQubitOperation + + # region Interface 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 + + @relation_link.setter + def relation_link(self, link: IRelationLink[ICircuitOperation]): + """:sets: Description of relation to other circuit node.""" + self.operation.relation = 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 Interface Methods + def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircuitOperation]] = None) -> 'SingleQubitOperation': + """ + 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 self.operation.copy( + relation_transfer_lookup=relation_transfer_lookup, + ) + + 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 + + if __name__ == '__main__': from qce_circuit.structure.registry_duration import ( DurationRegistry, diff --git a/src/qce_circuit/visualization/visualize_circuit/display_circuit.py b/src/qce_circuit/visualization/visualize_circuit/display_circuit.py index c40f574..e82d660 100644 --- a/src/qce_circuit/visualization/visualize_circuit/display_circuit.py +++ b/src/qce_circuit/visualization/visualize_circuit/display_circuit.py @@ -5,6 +5,7 @@ import matplotlib.pyplot as plt import matplotlib.patches as patches from typing import List, Optional, TypeVar, Dict, Any, Tuple +from copy import deepcopy from qce_circuit.structure.intrf_circuit_operation import RelationLink from qce_circuit.structure.circuit_operations import ( Reset, @@ -28,6 +29,7 @@ VirtualVacant, VirtualTwoQubitVacant, VirtualEmpty, + VirtualOptional, ) from qce_circuit.visualization.visualize_circuit.draw_components.annotation_components import ( HorizontalVariableIndicator, @@ -103,6 +105,7 @@ VirtualVacantFactory, VirtualTwoQubitVacantFactory, VirtualEmptyFactory, + VirtualOptionalFactory, ) from qce_circuit.visualization.visualize_circuit.draw_components.factory_multi_draw_components import \ MultiTwoQubitBlockFactory @@ -186,32 +189,37 @@ def get_transform_constructor(self) -> TransformConstructor: ) def get_operation_draw_components(self) -> List[IDrawComponent]: + individual_component_factory = DrawComponentFactoryManager( + default_factory=DefaultFactory(), + factory_lookup={ + CPhase: TwoQubitBlockFactory(), + DispersiveMeasure: MeasureFactory(), + Reset: ResetFactory(), + Wait: WaitFactory(), + Rx180: Rx180Factory(), + Rx90: Rx90Factory(), + Rxm90: Rxm90Factory(), + Ry180: Ry180Factory(), + Ry90: Ry90Factory(), + Rym90: Rym90Factory(), + Rx180ef: Rx180efFactory(), + Rphi90: Rphi90Factory(), + VirtualPhase: ZPhaseFactory(), + Identity: IdentityFactory(), + Hadamard: HadamardFactory(), + Barrier: BarrierFactory(), + VirtualPark: VirtualParkFactory(), + VirtualVacant: VirtualVacantFactory(), + VirtualTwoQubitVacant: VirtualTwoQubitVacantFactory(), + VirtualEmpty: VirtualEmptyFactory(), + } + ) + individual_component_factory.factory_lookup.update({ + VirtualOptional: VirtualOptionalFactory(callback_draw_manager=deepcopy(individual_component_factory)) + }) + factory_manager: BulkDrawComponentFactoryManager = BulkDrawComponentFactoryManager( - individual_component_factory=DrawComponentFactoryManager( - default_factory=DefaultFactory(), - factory_lookup={ - CPhase: TwoQubitBlockFactory(), - DispersiveMeasure: MeasureFactory(), - Reset: ResetFactory(), - Wait: WaitFactory(), - Rx180: Rx180Factory(), - Rx90: Rx90Factory(), - Rxm90: Rxm90Factory(), - Ry180: Ry180Factory(), - Ry90: Ry90Factory(), - Rym90: Rym90Factory(), - Rx180ef: Rx180efFactory(), - Rphi90: Rphi90Factory(), - VirtualPhase: ZPhaseFactory(), - Identity: IdentityFactory(), - Hadamard: HadamardFactory(), - Barrier: BarrierFactory(), - VirtualPark: VirtualParkFactory(), - VirtualVacant: VirtualVacantFactory(), - VirtualTwoQubitVacant: VirtualTwoQubitVacantFactory(), - VirtualEmpty: VirtualEmptyFactory(), - } - ), + individual_component_factory=individual_component_factory, factory_lookup={ TwoQubitOperation: MultiTwoQubitBlockFactory(factory_lookup={ CPhase: TwoQubitBlockFactory(), 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 5c2c11e..27c3bbb 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 @@ -29,9 +29,11 @@ VirtualVacant, VirtualTwoQubitVacant, VirtualEmpty, + VirtualOptional, ) from qce_circuit.visualization.visualize_circuit.intrf_draw_component import IDrawComponent from qce_circuit.visualization.visualize_circuit.intrf_factory_draw_components import ( + IOperationDrawComponentFactoryManager, IOperationDrawComponentFactory, ITransformConstructor, ) @@ -495,3 +497,25 @@ def construct(self, operation: ICircuitCompositeOperation, transform_constructor text_string=f'x{operation.nr_of_repetitions}' ) # endregion + + +class VirtualOptionalFactory(IOperationDrawComponentFactory[VirtualOptional, IDrawComponent]): + """ + Behaviour class, implementing construction of draw component with additional requirements. + """ + + # region Class Constructor + def __init__(self, callback_draw_manager: IOperationDrawComponentFactoryManager): + self._factory_manager: IOperationDrawComponentFactoryManager = callback_draw_manager + # endregion + + # region Interface Methods + def construct(self, operation: VirtualOptional, transform_constructor: ITransformConstructor) -> IDrawComponent: + """:return: Draw component based on operation type.""" + with StyleManager.temporary_override(**dict(line_style_border='--')): + draw_component: IDrawComponent = self._factory_manager.construct( + operation=operation.operation, + transform_constructor=transform_constructor, + ) + return draw_component + # endregion 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 b80472a..25cae02 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 @@ -64,7 +64,7 @@ class RectangleBlock(IRectTransformComponent, IDrawComponent): width: float height: float alignment: TransformAlignment = field(default=TransformAlignment.MID_LEFT) - style_settings: OperationStyleSettings = field(default=StyleManager.read_config().operation_style) + style_settings: OperationStyleSettings = field(default_factory=lambda: StyleManager.read_config().operation_style) # region Interface Properties @property @@ -86,6 +86,7 @@ def draw(self, axes: plt.Axes) -> plt.Axes: 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, @@ -173,7 +174,7 @@ class SquareBlock(IRectTransformComponent, IDrawComponent): pivot: Vec2D height: float alignment: TransformAlignment = field(default=TransformAlignment.MID_LEFT) - style_settings: OperationStyleSettings = field(default=StyleManager.read_config().operation_style) + style_settings: OperationStyleSettings = field(default_factory=lambda: StyleManager.read_config().operation_style) _base_block: RectangleBlock = field(init=False) # region Interface Properties diff --git a/src/qce_circuit/visualization/visualize_circuit/style_manager.py b/src/qce_circuit/visualization/visualize_circuit/style_manager.py index dba2eb1..21e5d57 100644 --- a/src/qce_circuit/visualization/visualize_circuit/style_manager.py +++ b/src/qce_circuit/visualization/visualize_circuit/style_manager.py @@ -3,6 +3,8 @@ # ------------------------------------------- import os from dataclasses import dataclass, field +import threading +from contextlib import contextmanager from qce_circuit.utilities.singleton_base import Singleton from qce_circuit.utilities.readwrite_yaml import ( get_yaml_file_path, @@ -32,6 +34,7 @@ class OperationStyleSettings: text_color: str border_width: float line_width: float + border_line_style: str dot_radius: float font_size: float @@ -96,6 +99,9 @@ class StyleSettings: font_size: float = field(default=16.0) font_size_small: float = field(default=10.0) + # Line styles + line_style_border: str = field(default='-') + # region Class Properties @property def channel_style(self) -> ChannelStyleSettings: @@ -114,6 +120,7 @@ def operation_style(self) -> OperationStyleSettings: text_color=self.color_text, border_width=self.width_border, line_width=self.width_line, + border_line_style=self.line_style_border, dot_radius=self.radius_dot, font_size=self.font_size, ) @@ -126,6 +133,7 @@ def vacant_operation_style(self) -> OperationStyleSettings: text_color=self.color_text, border_width=self.width_border, line_width=self.width_line, + border_line_style=self.line_style_border, dot_radius=self.radius_dot, font_size=self.font_size, ) @@ -138,6 +146,7 @@ def empty_operation_style(self) -> OperationStyleSettings: text_color=self.color_text, border_width=self.width_border, line_width=self.width_line, + border_line_style=self.line_style_border, dot_radius=self.radius_dot, font_size=self.font_size, ) @@ -176,6 +185,7 @@ class StyleManager(metaclass=Singleton): Behaviour Class, manages import of circuit-visualization style file. """ CONFIG_NAME: str = 'config_circuit_style.yaml' + _override_stack = threading.local() # Thread-safe storage for overrides # region Class Methods @classmethod @@ -185,7 +195,11 @@ def _default_config_object(cls) -> dict: @classmethod def read_config(cls) -> StyleSettings: - """:return: File-manager config file.""" + """:return: File-manager config file or overridden settings.""" + # Check for temporary override + if hasattr(cls._override_stack, "current_override") and cls._override_stack.current_override: + return cls._override_stack.current_override + path = get_yaml_file_path(filename=cls.CONFIG_NAME) if not os.path.exists(path): # Construct config dict @@ -196,4 +210,23 @@ def read_config(cls) -> StyleSettings: make_file=True, ) return StyleSettings(**read_yaml(filename=cls.CONFIG_NAME)) + + @classmethod + @contextmanager + def temporary_override(cls, **overrides): + """ + Temporarily override specific style settings in memory. + Ensures that changes do not persist beyond the 'with' block. + """ + try: + # Store the original settings + original_config = cls.read_config() + new_config_dict = original_config.__dict__.copy() + new_config_dict.update(overrides) # Apply overrides + + # Set thread-local override + cls._override_stack.current_override = StyleSettings(**new_config_dict) + yield # Execute block within overridden context + finally: + cls._override_stack.current_override = None # Restore original settings # endregion From 7bb99dce99eac8ef097150aab678aa6e818dddb2 Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:23:06 +0100 Subject: [PATCH 3/7] Fixed bug with VirtualOptional operation wrapper. When copy return new instance of self with copy of sub-operation instead of just copy of sub-operation. --- src/qce_circuit/structure/circuit_operations.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/qce_circuit/structure/circuit_operations.py b/src/qce_circuit/structure/circuit_operations.py index 9fbad37..4176446 100644 --- a/src/qce_circuit/structure/circuit_operations.py +++ b/src/qce_circuit/structure/circuit_operations.py @@ -1023,14 +1023,16 @@ def duration(self) -> float: # endregion # region Interface Methods - def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircuitOperation]] = None) -> 'SingleQubitOperation': + def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircuitOperation]] = None) -> 'VirtualOptional': """ 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 self.operation.copy( - relation_transfer_lookup=relation_transfer_lookup, + return VirtualOptional( + operation=self.operation.copy( + relation_transfer_lookup=relation_transfer_lookup, + ) ) def apply_modifiers_to_self(self) -> ICircuitOperation: From 426821ca5bc627f96a4415da755fd5ed8e557fc5 Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Fri, 7 Feb 2025 16:04:36 +0100 Subject: [PATCH 4/7] Added custom layout display functionality for more control over stabilizer visualization. --- .../visualize_layout/display_connectivity.py | 178 +++++++++++++++++- 1 file changed, 177 insertions(+), 1 deletion(-) diff --git a/src/qce_circuit/visualization/visualize_layout/display_connectivity.py b/src/qce_circuit/visualization/visualize_layout/display_connectivity.py index 02beca0..75eeaff 100644 --- a/src/qce_circuit/visualization/visualize_layout/display_connectivity.py +++ b/src/qce_circuit/visualization/visualize_layout/display_connectivity.py @@ -17,7 +17,12 @@ Vec2D, ) from qce_circuit.visualization.visualize_circuit.intrf_draw_component import IDrawComponent -from qce_circuit.visualization.visualize_layout.style_manager import StyleManager +from qce_circuit.visualization.visualize_layout.style_manager import ( + StyleManager, + PlaquetteStyleSettings, + ElementStyleSettings, + StyleSettings, +) from qce_circuit.visualization.visualize_layout.plaquette_components import ( RectanglePlaquette, TrianglePlaquette, @@ -219,6 +224,142 @@ def identifier_to_rotation(self, identifier: IQubitID) -> float: # endregion +@dataclass(frozen=True) +class AllGreyVisualConnectivityDescription(VisualConnectivityDescription): + """ + Data class, overwriting VisualConnectivityDescription by forcing single plaquette color. + """ + plaquette_color_overwrite: str = field(default="#b0b0b0") + + # region Class Methods + def get_plaquette_components(self) -> List[IDrawComponent]: + result: List[IDrawComponent] = [] + diagonal_spacing: float = self.layout_spacing * np.sqrt(2) + + style_settings = StyleManager.read_config().plaquette_style_x + style_settings = PlaquetteStyleSettings( + background_color=self.plaquette_color_overwrite, + line_color=style_settings.line_color, + line_width=style_settings.line_width, + zorder=style_settings.zorder, + ) + + for parity_group in self.connectivity.parity_group_x: + if len(parity_group.data_ids) == 4: + result.append( + RectanglePlaquette( + pivot=self.identifier_to_pivot(parity_group.ancilla_id) + self.pivot, + width=diagonal_spacing, + height=diagonal_spacing, + rotation=self.identifier_to_rotation(parity_group.ancilla_id), + alignment=TransformAlignment.MID_CENTER, + style_settings=style_settings, + ) + ) + if len(parity_group.data_ids) == 2: + result.append( + TrianglePlaquette( + pivot=self.identifier_to_pivot(parity_group.ancilla_id) + self.pivot, + width=diagonal_spacing, + height=diagonal_spacing, + rotation=self.identifier_to_rotation(parity_group.ancilla_id), + alignment=TransformAlignment.MID_CENTER, + style_settings=style_settings, + ) + ) + for parity_group in self.connectivity.parity_group_z: + if len(parity_group.data_ids) == 4: + result.append( + RectanglePlaquette( + pivot=self.identifier_to_pivot(parity_group.ancilla_id) + self.pivot, + width=diagonal_spacing, + height=diagonal_spacing, + rotation=self.identifier_to_rotation(parity_group.ancilla_id), + alignment=TransformAlignment.MID_CENTER, + style_settings=style_settings, + ) + ) + if len(parity_group.data_ids) == 2: + result.append( + TrianglePlaquette( + pivot=self.identifier_to_pivot(parity_group.ancilla_id) + self.pivot, + width=diagonal_spacing, + height=diagonal_spacing, + rotation=self.identifier_to_rotation(parity_group.ancilla_id), + alignment=TransformAlignment.MID_CENTER, + style_settings=style_settings, + ) + ) + return result + # endregion + + +@dataclass(frozen=True) +class StabilizerGroupVisualConnectivityDescription(VisualConnectivityDescription): + """ + Data class, overwriting VisualConnectivityDescription by implementing stabilizer group element visualization. + """ + element_color_overwrite: str = field(default="#c4c4c4") + + # region Class Methods + def get_element_components(self) -> List[IDrawComponent]: + result: List[IDrawComponent] = [] + style_setting: StyleSettings = StyleManager.read_config() + + for qubit_id in self.connectivity.qubit_ids: + is_ancilla_qubit: bool = qubit_id in self.connectivity.ancilla_qubit_ids + print(qubit_id, is_ancilla_qubit) + + result.append(DotComponent( + pivot=self.identifier_to_pivot(qubit_id) + self.pivot, + alignment=TransformAlignment.MID_CENTER, + style_settings=ElementStyleSettings( + background_color=self.element_color_overwrite if not is_ancilla_qubit else style_setting.color_background_z, + line_color=style_setting.color_element, + element_radius=style_setting.radius_dot, + 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, + )) + return result + + def identifier_to_rotation(self, identifier: IQubitID) -> float: + """:return: Rotation based on (parity group) ancilla identifier.""" + rotation_offset: float = -45 + hexagon_rotation: float = 30 + + # Surface-17 layout + map_qubits: Dict[IQubitID, float] = { + QubitIDObj('Z3'): self.rotation + rotation_offset + 0, + QubitIDObj('X4'): self.rotation + rotation_offset + 270, + QubitIDObj('Z4'): self.rotation + rotation_offset - 90, + QubitIDObj('X3'): self.rotation + rotation_offset + 180, + QubitIDObj('X2'): self.rotation + rotation_offset, + QubitIDObj('Z1'): self.rotation + rotation_offset + 90, + QubitIDObj('X1'): self.rotation + rotation_offset + 90, + QubitIDObj('Z2'): self.rotation + rotation_offset + 180, + QubitIDObj('D1'): self.rotation + rotation_offset + hexagon_rotation, + QubitIDObj('D2'): self.rotation + rotation_offset + hexagon_rotation, + QubitIDObj('D3'): self.rotation + rotation_offset + hexagon_rotation, + QubitIDObj('D4'): self.rotation + rotation_offset + hexagon_rotation, + QubitIDObj('D5'): self.rotation + rotation_offset + hexagon_rotation, + QubitIDObj('D6'): self.rotation + rotation_offset + hexagon_rotation, + QubitIDObj('D7'): self.rotation + rotation_offset + hexagon_rotation, + QubitIDObj('D8'): self.rotation + rotation_offset + hexagon_rotation, + QubitIDObj('D9'): self.rotation + rotation_offset + hexagon_rotation, + } + if identifier in map_qubits: + print(identifier, map_qubits[identifier]) + return map_qubits[identifier] + return self.rotation # default + + # endregion + + def plot_layout_description(description: VisualConnectivityDescription, **kwargs) -> IFigureAxesPair: # Data allocation kwargs[SubplotKeywordEnum.FIGURE_SIZE.value] = kwargs.get(SubplotKeywordEnum.FIGURE_SIZE.value, (5, 5)) @@ -257,3 +398,38 @@ def plot_gate_sequences(description: IGenericSurfaceCodeLayer, **kwargs) -> IFig kwargs[SubplotKeywordEnum.HOST_AXES.value] = (fig, ax) plot_layout_description(descriptor, **kwargs) return fig, axes[0] + + +def plot_stabilizer_specific_gate_sequences(description: IGenericSurfaceCodeLayer, **kwargs) -> IFigureAxesPair: + """ + Constructs a similar gate sequence plot as 'plot_gate_sequences'. + However, the gate-sequence info is taken from description parameter + and background-layout is taken from the parity-group part of the description parameter. + 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. + :return: Figure and Axes pair. + """ + sequence_count: int = description.gate_sequence_count + kwargs[SubplotKeywordEnum.FIGURE_SIZE.value] = (5 * sequence_count, 5) + fig, axes = construct_subplot(ncols=sequence_count, **kwargs) + if not isinstance(axes, Iterable): + axes = [axes] + + for i, ax in enumerate(axes): + descriptor: AllGreyVisualConnectivityDescription = AllGreyVisualConnectivityDescription( + connectivity=Surface17Layer(), + gate_sequence=description.get_gate_sequence_at_index(i), + layout_spacing=1.0 + ) + kwargs[SubplotKeywordEnum.HOST_AXES.value] = (fig, ax) + fig, ax = plot_layout_description(descriptor, **kwargs) + + descriptor: StabilizerGroupVisualConnectivityDescription = StabilizerGroupVisualConnectivityDescription( + connectivity=description, + gate_sequence=description.get_gate_sequence_at_index(i), + layout_spacing=1.0 + ) + kwargs[SubplotKeywordEnum.HOST_AXES.value] = (fig, ax) + plot_layout_description(descriptor, **kwargs) + return fig, axes[0] \ No newline at end of file From b9cc6053336f0c5c84a43175d8a8b70a4eff4e95 Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Thu, 13 Feb 2025 11:09:20 +0100 Subject: [PATCH 5/7] Added RxTheta, RyTheta and virtual injected error operation as well as the corresponding draw factories. --- .../structure/circuit_operations.py | 138 ++++++++++++++++++ .../visualize_circuit/display_circuit.py | 12 +- .../factory_draw_components.py | 63 ++++++++ 3 files changed, 212 insertions(+), 1 deletion(-) diff --git a/src/qce_circuit/structure/circuit_operations.py b/src/qce_circuit/structure/circuit_operations.py index 4176446..5195a3b 100644 --- a/src/qce_circuit/structure/circuit_operations.py +++ b/src/qce_circuit/structure/circuit_operations.py @@ -320,6 +320,36 @@ def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircu # endregion +@dataclass(frozen=False, unsafe_hash=True) +class RxTheta(SingleQubitOperation, ICircuitOperation): + """ + Rotation-X (theta degrees) operation. + """ + duration_strategy: IDurationStrategy = field(init=False, default=GlobalDurationStrategy(GlobalRegistryKey.MICROWAVE)) + + # 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=self.qubit_index, _channel=QubitChannel.MICROWAVE), + ] + # endregion + + # region Interface Methods + def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircuitOperation]] = None) -> 'RxTheta': + """ + 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 RxTheta( + qubit_index=self.qubit_index, + relation=self.relation.copy(relation_transfer_lookup=relation_transfer_lookup), + ) + # endregion + + @dataclass(frozen=False, unsafe_hash=True) class Ry180(SingleQubitOperation, ICircuitOperation): """ @@ -410,6 +440,36 @@ def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircu # endregion +@dataclass(frozen=False, unsafe_hash=True) +class RyTheta(SingleQubitOperation, ICircuitOperation): + """ + Rotation-Y (theta degrees) operation. + """ + duration_strategy: IDurationStrategy = field(init=False, default=GlobalDurationStrategy(GlobalRegistryKey.MICROWAVE)) + + # 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=self.qubit_index, _channel=QubitChannel.MICROWAVE), + ] + # endregion + + # region Interface Methods + def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircuitOperation]] = None) -> 'RyTheta': + """ + 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 RyTheta( + qubit_index=self.qubit_index, + relation=self.relation.copy(relation_transfer_lookup=relation_transfer_lookup), + ) + # endregion + + @dataclass(frozen=False, unsafe_hash=True) class Rx180ef(SingleQubitOperation, ICircuitOperation): """ @@ -1060,6 +1120,84 @@ def apply_flatten_to_self(self) -> ICircuitOperation: # endregion +@dataclass(frozen=False, unsafe_hash=True) +class VirtualInjectedError(ICircuitOperation): + """ + Data class, containing single-qubit operation. + Acts as a visualization wrapper for injected errors. + """ + operation: SingleQubitOperation + + # region Interface 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 + + @relation_link.setter + def relation_link(self, link: IRelationLink[ICircuitOperation]): + """:sets: Description of relation to other circuit node.""" + self.operation.relation = 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 Interface Methods + def copy(self, relation_transfer_lookup: Optional[Dict[ICircuitOperation, ICircuitOperation]] = None) -> 'VirtualInjectedError': + """ + 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 VirtualInjectedError( + operation=self.operation.copy( + relation_transfer_lookup=relation_transfer_lookup, + ) + ) + + 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 + + if __name__ == '__main__': from qce_circuit.structure.registry_duration import ( DurationRegistry, diff --git a/src/qce_circuit/visualization/visualize_circuit/display_circuit.py b/src/qce_circuit/visualization/visualize_circuit/display_circuit.py index e82d660..8d58cf8 100644 --- a/src/qce_circuit/visualization/visualize_circuit/display_circuit.py +++ b/src/qce_circuit/visualization/visualize_circuit/display_circuit.py @@ -13,9 +13,11 @@ Rx180, Rx90, Rxm90, + RxTheta, Ry180, Ry90, Rym90, + RyTheta, Rx180ef, VirtualPhase, Rphi90, @@ -30,6 +32,7 @@ VirtualTwoQubitVacant, VirtualEmpty, VirtualOptional, + VirtualInjectedError, ) from qce_circuit.visualization.visualize_circuit.draw_components.annotation_components import ( HorizontalVariableIndicator, @@ -89,9 +92,11 @@ Rx180Factory, Rx90Factory, Rxm90Factory, + RxThetaFactory, Ry180Factory, Ry90Factory, Rym90Factory, + RyThetaFactory, Rx180efFactory, ZPhaseFactory, Rphi90Factory, @@ -106,6 +111,7 @@ VirtualTwoQubitVacantFactory, VirtualEmptyFactory, VirtualOptionalFactory, + VirtualInjectedErrorFactory, ) from qce_circuit.visualization.visualize_circuit.draw_components.factory_multi_draw_components import \ MultiTwoQubitBlockFactory @@ -199,9 +205,11 @@ def get_operation_draw_components(self) -> List[IDrawComponent]: Rx180: Rx180Factory(), Rx90: Rx90Factory(), Rxm90: Rxm90Factory(), + RxTheta: RxThetaFactory(), Ry180: Ry180Factory(), Ry90: Ry90Factory(), Rym90: Rym90Factory(), + RyTheta: RyThetaFactory(), Rx180ef: Rx180efFactory(), Rphi90: Rphi90Factory(), VirtualPhase: ZPhaseFactory(), @@ -214,8 +222,10 @@ def get_operation_draw_components(self) -> List[IDrawComponent]: VirtualEmpty: VirtualEmptyFactory(), } ) + callback_draw_manager = deepcopy(individual_component_factory) individual_component_factory.factory_lookup.update({ - VirtualOptional: VirtualOptionalFactory(callback_draw_manager=deepcopy(individual_component_factory)) + VirtualOptional: VirtualOptionalFactory(callback_draw_manager=callback_draw_manager), + VirtualInjectedError: VirtualInjectedErrorFactory(callback_draw_manager=callback_draw_manager), }) factory_manager: BulkDrawComponentFactoryManager = BulkDrawComponentFactoryManager( 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 27c3bbb..e1bad30 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 @@ -16,9 +16,11 @@ Rx180, Rx90, Rxm90, + RxTheta, Ry180, Ry90, Rym90, + RyTheta, VirtualPhase, Reset, Wait, @@ -30,6 +32,7 @@ VirtualTwoQubitVacant, VirtualEmpty, VirtualOptional, + VirtualInjectedError, ) from qce_circuit.visualization.visualize_circuit.intrf_draw_component import IDrawComponent from qce_circuit.visualization.visualize_circuit.intrf_factory_draw_components import ( @@ -160,6 +163,25 @@ def construct(self, operation: Rxm90, transform_constructor: ITransformConstruct # endregion +class RxThetaFactory(IOperationDrawComponentFactory[RxTheta, IDrawComponent]): + + # region Interface Methods + def construct(self, operation: RxTheta, transform_constructor: ITransformConstructor) -> IDrawComponent: + """:return: Draw component based on operation type.""" + transform: IRectTransform = transform_constructor.construct_transform( + identifier=operation.channel_identifiers[0], + time_component=operation, + ) + return BlockRotation( + pivot=transform.pivot, + height=transform.height, + alignment=transform.parent_alignment, + rotation_axes=RotationAxis.X, + rotation_angle=RotationAngle.THETA, + ) + # endregion + + class Ry180Factory(IOperationDrawComponentFactory[Ry180, IDrawComponent]): # region Interface Methods @@ -217,6 +239,25 @@ def construct(self, operation: Rym90, transform_constructor: ITransformConstruct # endregion +class RyThetaFactory(IOperationDrawComponentFactory[RyTheta, IDrawComponent]): + + # region Interface Methods + def construct(self, operation: RyTheta, transform_constructor: ITransformConstructor) -> IDrawComponent: + """:return: Draw component based on operation type.""" + transform: IRectTransform = transform_constructor.construct_transform( + identifier=operation.channel_identifiers[0], + time_component=operation, + ) + return BlockRotation( + pivot=transform.pivot, + height=transform.height, + alignment=transform.parent_alignment, + rotation_axes=RotationAxis.Y, + rotation_angle=RotationAngle.THETA, + ) + # endregion + + class Rx180efFactory(IOperationDrawComponentFactory[Rx180, IDrawComponent]): # region Interface Methods @@ -519,3 +560,25 @@ def construct(self, operation: VirtualOptional, transform_constructor: ITransfor ) return draw_component # endregion + + +class VirtualInjectedErrorFactory(IOperationDrawComponentFactory[VirtualInjectedError, IDrawComponent]): + """ + Behaviour class, implementing construction of draw component with additional requirements. + """ + + # region Class Constructor + def __init__(self, callback_draw_manager: IOperationDrawComponentFactoryManager): + self._factory_manager: IOperationDrawComponentFactoryManager = callback_draw_manager + # endregion + + # 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")): + draw_component: IDrawComponent = self._factory_manager.construct( + operation=operation.operation, + transform_constructor=transform_constructor, + ) + return draw_component + # endregion \ No newline at end of file From 564f8d684bbc219cbbd810401273105fea5b87ce Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Wed, 19 Feb 2025 14:58:21 +0100 Subject: [PATCH 6/7] Updated fixes definition with stabilizer type in repetition code. --- .../repetition_code/repetition_code_connectivity.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qce_circuit/library/repetition_code/repetition_code_connectivity.py b/src/qce_circuit/library/repetition_code/repetition_code_connectivity.py index 4e8599b..3d70b69 100644 --- a/src/qce_circuit/library/repetition_code/repetition_code_connectivity.py +++ b/src/qce_circuit/library/repetition_code/repetition_code_connectivity.py @@ -217,22 +217,22 @@ def __init__(self): ], parity_group_z=[ ParityGroup( - _parity_type=StabilizerType.STABILIZER_X, + _parity_type=StabilizerType.STABILIZER_Z, _ancilla_qubit=QubitIDObj('X1'), _data_qubits=[QubitIDObj('D1'), QubitIDObj('D2')] ), ParityGroup( - _parity_type=StabilizerType.STABILIZER_X, + _parity_type=StabilizerType.STABILIZER_Z, _ancilla_qubit=QubitIDObj('X2'), _data_qubits=[QubitIDObj('D2'), QubitIDObj('D3')] ), ParityGroup( - _parity_type=StabilizerType.STABILIZER_X, + _parity_type=StabilizerType.STABILIZER_Z, _ancilla_qubit=QubitIDObj('X3'), _data_qubits=[QubitIDObj('D7'), QubitIDObj('D8')] ), ParityGroup( - _parity_type=StabilizerType.STABILIZER_X, + _parity_type=StabilizerType.STABILIZER_Z, _ancilla_qubit=QubitIDObj('X4'), _data_qubits=[QubitIDObj('D8'), QubitIDObj('D9')] ), From 05f5ae7e48ca165ae7dc31d8462c17c427981341 Mon Sep 17 00:00:00 2001 From: SeanvdMeer <18538762+minisean@users.noreply.github.com> Date: Wed, 26 Feb 2025 11:18:22 +0100 Subject: [PATCH 7/7] Updated background style color for stabilizer group visualization --- .../visualize_layout/display_connectivity.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/qce_circuit/visualization/visualize_layout/display_connectivity.py b/src/qce_circuit/visualization/visualize_layout/display_connectivity.py index 75eeaff..3aa6dfc 100644 --- a/src/qce_circuit/visualization/visualize_layout/display_connectivity.py +++ b/src/qce_circuit/visualization/visualize_layout/display_connectivity.py @@ -307,14 +307,17 @@ def get_element_components(self) -> List[IDrawComponent]: style_setting: StyleSettings = StyleManager.read_config() for qubit_id in self.connectivity.qubit_ids: - is_ancilla_qubit: bool = qubit_id in self.connectivity.ancilla_qubit_ids - print(qubit_id, is_ancilla_qubit) + background_color = self.element_color_overwrite + if qubit_id in [parity_group.ancilla_id for parity_group in self.connectivity.parity_group_z]: + background_color = style_setting.color_background_z + if qubit_id in [parity_group.ancilla_id for parity_group in self.connectivity.parity_group_x]: + background_color = style_setting.color_background_x result.append(DotComponent( pivot=self.identifier_to_pivot(qubit_id) + self.pivot, alignment=TransformAlignment.MID_CENTER, style_settings=ElementStyleSettings( - background_color=self.element_color_overwrite if not is_ancilla_qubit else style_setting.color_background_z, + background_color=background_color, line_color=style_setting.color_element, element_radius=style_setting.radius_dot, zorder=style_setting.zorder_element, @@ -338,7 +341,8 @@ def identifier_to_rotation(self, identifier: IQubitID) -> float: QubitIDObj('X4'): self.rotation + rotation_offset + 270, QubitIDObj('Z4'): self.rotation + rotation_offset - 90, QubitIDObj('X3'): self.rotation + rotation_offset + 180, - QubitIDObj('X2'): self.rotation + rotation_offset, + QubitIDObj('X3'): self.rotation + rotation_offset + 90, + QubitIDObj('X2'): self.rotation + rotation_offset - 90, QubitIDObj('Z1'): self.rotation + rotation_offset + 90, QubitIDObj('X1'): self.rotation + rotation_offset + 90, QubitIDObj('Z2'): self.rotation + rotation_offset + 180,