From a1d141776a58f68548c470a783302a3203b7ab2f Mon Sep 17 00:00:00 2001 From: CodeMaverick2 Date: Tue, 17 Feb 2026 23:42:10 +0530 Subject: [PATCH 1/6] Improved Pattern.draw_graph visualization --- graphix/pattern.py | 15 +- graphix/visualization.py | 304 ++++++++++++++---- .../test_draw_graph_reference_False.png | Bin 2911 -> 2796 bytes .../test_draw_graph_reference_True.png | Bin 16325 -> 16781 bytes tests/test_visualization.py | 91 +++++- 5 files changed, 352 insertions(+), 58 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index ec900639..02c81427 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -1438,6 +1438,8 @@ def draw_graph( show_pauli_measurement: bool = True, show_local_clifford: bool = False, show_measurement_planes: bool = False, + show_measurement_angles: bool = False, + show_legend: bool = False, show_loop: bool = True, node_distance: tuple[float, float] = (1, 1), figsize: tuple[int, int] | None = None, @@ -1445,16 +1447,23 @@ def draw_graph( ) -> None: """Visualize the underlying graph of the pattern with flow or gflow structure. + Nodes are drawn following MBQC literature conventions: inputs as squares, + measured nodes as filled circles, and outputs as empty circles. + Parameters ---------- flow_from_pattern : bool If True, the command sequence of the pattern is used to derive flow or gflow structure. If False, only the underlying graph is used. show_pauli_measurement : bool - If True, the nodes with Pauli measurement angles are colored light blue. + If True, Pauli-measured nodes are filled with blue instead of black. show_local_clifford : bool If True, indexes of the local Clifford operator are displayed adjacent to the nodes. show_measurement_planes : bool If True, measurement planes are displayed adjacent to the nodes. + show_measurement_angles : bool + If True, measurement angles (in units of pi) are displayed adjacent to the nodes. + show_legend : bool + If True, a legend is displayed indicating node types and edge meanings. show_loop : bool whether or not to show loops for graphs with gflow. defaulted to True. node_distance : tuple @@ -1476,6 +1485,8 @@ def draw_graph( show_pauli_measurement=show_pauli_measurement, show_local_clifford=show_local_clifford, show_measurement_planes=show_measurement_planes, + show_measurement_angles=show_measurement_angles, + show_legend=show_legend, show_loop=show_loop, node_distance=node_distance, figsize=figsize, @@ -1486,6 +1497,8 @@ def draw_graph( show_pauli_measurement=show_pauli_measurement, show_local_clifford=show_local_clifford, show_measurement_planes=show_measurement_planes, + show_measurement_angles=show_measurement_angles, + show_legend=show_legend, show_loop=show_loop, node_distance=node_distance, figsize=figsize, diff --git a/graphix/visualization.py b/graphix/visualization.py index 4322b82b..9ffec4c9 100644 --- a/graphix/visualization.py +++ b/graphix/visualization.py @@ -9,9 +9,12 @@ import networkx as nx import numpy as np from matplotlib import pyplot as plt +from matplotlib.lines import Line2D from graphix.flow.exceptions import FlowError -from graphix.measurements import Measurement, PauliMeasurement +from graphix.fundamentals import Sign +from graphix.measurements import BlochMeasurement, Measurement, PauliMeasurement +from graphix.pretty_print import OutputFormat, angle_to_str # OpenGraph is needed for dataclass from graphix.opengraph import OpenGraph # noqa: TC001 @@ -57,13 +60,14 @@ def visualize( show_pauli_measurement: bool = True, show_local_clifford: bool = False, show_measurement_planes: bool = False, + show_measurement_angles: bool = False, + show_legend: bool = False, show_loop: bool = True, node_distance: tuple[float, float] = (1, 1), figsize: tuple[int, int] | None = None, filename: Path | None = None, ) -> None: - """ - Visualize the graph with flow or gflow structure. + """Visualize the graph with flow or gflow structure. If there exists a flow structure, then the graph is visualized with the flow structure. If flow structure is not found and there exists a gflow structure, then the graph is visualized @@ -73,11 +77,15 @@ def visualize( Parameters ---------- show_pauli_measurement : bool - If True, the nodes with Pauli measurement angles are colored light blue. + If True, Pauli-measured nodes are filled with blue instead of black. show_local_clifford : bool If True, indexes of the local Clifford operator are displayed adjacent to the nodes. show_measurement_planes : bool If True, the measurement planes are displayed adjacent to the nodes. + show_measurement_angles : bool + If True, the measurement angles (in units of pi) are displayed adjacent to the nodes. + show_legend : bool + If True, a legend is displayed indicating node types and edge meanings. show_loop : bool whether or not to show loops for graphs with gflow. defaulted to True. node_distance : tuple @@ -139,6 +147,8 @@ def place_paths( show_pauli_measurement, show_local_clifford, show_measurement_planes, + show_measurement_angles, + show_legend, show_loop, node_distance, figsize, @@ -151,13 +161,14 @@ def visualize_from_pattern( show_pauli_measurement: bool = True, show_local_clifford: bool = False, show_measurement_planes: bool = False, + show_measurement_angles: bool = False, + show_legend: bool = False, show_loop: bool = True, node_distance: tuple[float, float] = (1, 1), figsize: tuple[int, int] | None = None, filename: Path | None = None, ) -> None: - """ - Visualize the graph with flow or gflow structure found from the given pattern. + """Visualize the graph with flow or gflow structure found from the given pattern. If pattern sequence is consistent with flow structure, then the graph is visualized with the flow structure. If it is not consistent with flow structure and consistent with gflow structure, then the graph is visualized @@ -168,11 +179,15 @@ def visualize_from_pattern( pattern : Pattern pattern to be visualized show_pauli_measurement : bool - If True, the nodes with Pauli measurement angles are colored light blue. + If True, Pauli-measured nodes are filled with blue instead of black. show_local_clifford : bool If True, indexes of the local Clifford operator are displayed adjacent to the nodes. show_measurement_planes : bool If True, the measurement planes are displayed adjacent to the nodes. + show_measurement_angles : bool + If True, the measurement angles (in units of pi) are displayed adjacent to the nodes. + show_legend : bool + If True, a legend is displayed indicating node types and edge meanings. show_loop : bool whether or not to show loops for graphs with gflow. defaulted to True. node_distance : tuple @@ -236,6 +251,8 @@ def place_paths( show_pauli_measurement, show_local_clifford, show_measurement_planes, + show_measurement_angles, + show_legend, show_loop, node_distance, figsize, @@ -252,35 +269,78 @@ def _shorten_path(path: Sequence[_Point]) -> list[_Point]: new_path[-1] = last_edge return new_path - def _draw_labels(self, pos: Mapping[int, _Point]) -> None: + def _draw_labels(self, pos: Mapping[int, _Point], dark_nodes: set[int] | None = None) -> None: + """Draw node number labels with appropriate text color. + + Parameters + ---------- + pos : Mapping[int, tuple[float, float]] + Dictionary of node positions. + dark_nodes : set[int] | None + Nodes with dark backgrounds that need white text. If None, all labels are black. + """ fontsize = 12 if max(self.og.graph.nodes(), default=0) >= 100: fontsize = int(fontsize * 2 / len(str(max(self.og.graph.nodes())))) - nx.draw_networkx_labels(self.og.graph, pos, font_size=fontsize) + if dark_nodes: + light_labels = {n: str(n) for n in self.og.graph.nodes() if n not in dark_nodes} + dark_labels = {n: str(n) for n in self.og.graph.nodes() if n in dark_nodes} + if light_labels: + nx.draw_networkx_labels(self.og.graph, pos, labels=light_labels, font_size=fontsize, font_color="black") + if dark_labels: + nx.draw_networkx_labels(self.og.graph, pos, labels=dark_labels, font_size=fontsize, font_color="white") + else: + nx.draw_networkx_labels(self.og.graph, pos, font_size=fontsize) - def __draw_nodes_role(self, pos: Mapping[int, _Point], show_pauli_measurement: bool = False) -> None: - """ - Draw the nodes with different colors based on their role (input, output, or other). + def __draw_nodes_role(self, pos: Mapping[int, _Point], show_pauli_measurement: bool = False) -> set[int]: + """Draw the nodes with shapes and fills following MBQC literature conventions. + + Input nodes are drawn as squares, measured (non-output) nodes as filled circles, + and output nodes as empty circles. Pauli-measured nodes are optionally distinguished + with a blue fill. Parameters ---------- pos : Mapping[int, tuple[float, float]] - dictionary of node positions. + Dictionary of node positions. show_pauli_measurement : bool - If True, the nodes with Pauli measurement angles are colored light blue. + If True, Pauli-measured nodes are filled with blue instead of black. + + Returns + ------- + set[int] + Set of node indices with dark (black) fill, used for white label text. """ + dark_nodes: set[int] = set() + input_set = set(self.og.input_nodes) + output_set = set(self.og.output_nodes) + for node in self.og.graph.nodes(): - color = "black" # default color for 'other' nodes - inner_color = "white" - if node in self.og.input_nodes: - color = "red" - if node in self.og.output_nodes: - inner_color = "lightgray" - elif show_pauli_measurement and isinstance(self.og.measurements[node], PauliMeasurement): - inner_color = "lightblue" + is_input = node in input_set + is_output = node in output_set + is_pauli = ( + show_pauli_measurement + and node in self.og.measurements + and isinstance(self.og.measurements[node], PauliMeasurement) + ) + + # Inputs are squares, all others are circles + marker = "s" if is_input else "o" + + if is_output: + facecolor = "white" + elif is_pauli: + facecolor = "#4292c6" + else: + facecolor = "black" + dark_nodes.add(node) + plt.scatter( - *pos[node], edgecolor=color, facecolor=inner_color, s=350, zorder=2 - ) # Draw the nodes manually with scatter() + *pos[node], marker=marker, edgecolor="black", facecolor=facecolor, + s=350, zorder=2, linewidths=1.5, + ) + + return dark_nodes def visualize_graph( self, @@ -293,18 +353,19 @@ def visualize_graph( show_pauli_measurement: bool = True, show_local_clifford: bool = False, show_measurement_planes: bool = False, + show_measurement_angles: bool = False, + show_legend: bool = False, show_loop: bool = True, node_distance: tuple[float, float] = (1, 1), figsize: _Point | None = None, filename: Path | None = None, ) -> None: - """ - Visualizes the graph. + """Visualize the graph. - Nodes are colored based on their role (input, output, or other) and edges are depicted as arrows - or dashed lines depending on whether they are in the flow mapping. Vertical dashed lines separate - different layers of the graph. This function does not return anything but plots the graph - using matplotlib's pyplot. + Nodes are drawn following MBQC literature conventions: inputs as squares, + measured nodes as filled circles, and outputs as empty circles. Graph edges + are solid lines and flow arrows indicate corrections. A horizontal arrow + below the graph indicates the measurement order. Parameters ---------- @@ -319,11 +380,15 @@ def visualize_graph( corrections: tuple[Mapping[int, AbstractSet[int]], Mapping[int, AbstractSet[int]]] | None X and Z corrections if any. show_pauli_measurement : bool - If True, the nodes with Pauli measurement angles are colored light blue. + If True, Pauli-measured nodes are filled with blue instead of black. show_local_clifford : bool If True, indexes of the local Clifford operator are displayed adjacent to the nodes. show_measurement_planes : bool If True, the measurement planes are displayed adjacent to the nodes. + show_measurement_angles : bool + If True, the measurement angles (in units of pi) are displayed adjacent to the nodes. + show_legend : bool + If True, a legend is displayed indicating node types and edge meanings. show_loop : bool whether or not to show loops for graphs with gflow. defaulted to True. node_distance : tuple @@ -341,7 +406,7 @@ def visualize_graph( edge_path, arrow_path = place_paths(pos) - if corrections is not None: + if show_legend or corrections is not None: # add some padding to the right for the legend figsize = (figsize[0] + 3.0, figsize[1]) @@ -349,10 +414,10 @@ def visualize_graph( for edge, path in edge_path.items(): if len(path) == 2: - nx.draw_networkx_edges(self.og.graph, pos, edgelist=[edge], style="dashed", alpha=0.7) + nx.draw_networkx_edges(self.og.graph, pos, edgelist=[edge], style="solid", edge_color="gray", alpha=0.5) else: curve = self._bezier_curve_linspace(path) - plt.plot(curve[:, 0], curve[:, 1], "k--", linewidth=1, alpha=0.7) + plt.plot(curve[:, 0], curve[:, 1], color="gray", linewidth=1, alpha=0.5) if arrow_path is not None: for arrow, path in arrow_path.items(): @@ -391,19 +456,21 @@ def visualize_graph( arrowprops={"arrowstyle": "->", "color": color, "lw": 1}, ) - self.__draw_nodes_role(pos, show_pauli_measurement) + dark_nodes = self.__draw_nodes_role(pos, show_pauli_measurement) if show_local_clifford: self.__draw_local_clifford(pos) - if show_measurement_planes: - self.__draw_measurement_planes(pos) + if show_measurement_planes or show_measurement_angles: + self.__draw_measurement_labels(pos, show_measurement_planes, show_measurement_angles) - self._draw_labels(pos) + self._draw_labels(pos, dark_nodes) - if corrections is not None: - # legend for arrow colors - plt.plot([], [], "k--", alpha=0.7, label="graph edge") + if show_legend: + self.__draw_legend(show_pauli_measurement, corrections, arrow_path is not None) + elif corrections is not None: + # backward-compatible minimal legend for correction arrows + plt.plot([], [], color="gray", alpha=0.5, label="graph edge") plt.plot([], [], color="tab:red", label="xflow") plt.plot([], [], color="tab:green", label="zflow") plt.plot([], [], color="tab:brown", label="xflow and zflow") @@ -414,26 +481,39 @@ def visualize_graph( y_min = min((pos[node][1] for node in self.og.graph.nodes()), default=0) # Get the minimum y coordinate y_max = max((pos[node][1] for node in self.og.graph.nodes()), default=0) # Get the maximum y coordinate - if l_k is not None and l_k: - # Draw the vertical lines to separate different layers - for layer in range(min(l_k.values()), max(l_k.values())): - plt.axvline( - x=(layer + 0.5) * node_distance[0], color="gray", linestyle="--", alpha=0.5 - ) # Draw line between layers - for layer in range(min(l_k.values()), max(l_k.values()) + 1): + has_layers = l_k is not None and l_k + if has_layers: + l_min_val = min(l_k.values()) + l_max_val = max(l_k.values()) + # Draw layer labels below nodes + for layer in range(l_min_val, l_max_val + 1): plt.text( - layer * node_distance[0], y_min - 0.5, f"L: {max(l_k.values()) - layer}", ha="center", va="top" - ) # Add layer label at bottom + layer * node_distance[0], y_min - 0.4, + f"L{l_max_val - layer}", + ha="center", va="top", fontsize=8, color="gray", + ) + # Draw horizontal arrow indicating measurement order + if l_max_val > l_min_val: + arrow_y = y_min - 0.7 + plt.annotate( + "", + xy=(l_max_val * node_distance[0] + 0.3, arrow_y), + xytext=(l_min_val * node_distance[0] - 0.3, arrow_y), + arrowprops={"arrowstyle": "->", "color": "gray", "lw": 1.2}, + ) + mid_x = (l_min_val + l_max_val) / 2 * node_distance[0] + plt.text(mid_x, arrow_y - 0.2, "Measurement order", ha="center", va="top", fontsize=8, color="gray") plt.xlim( x_min - 0.5 * node_distance[0], x_max + 0.5 * node_distance[0] ) # Add some padding to the left and right - plt.ylim(y_min - 1, y_max + 0.5) # Add some padding to the top and bottom + bottom_margin = 1.3 if has_layers else 1 + plt.ylim(y_min - bottom_margin, y_max + 0.5) if filename is None: plt.show() else: - plt.savefig(filename) + plt.savefig(filename, bbox_inches="tight") def __draw_local_clifford(self, pos: Mapping[int, _Point]) -> None: if self.local_clifford is not None: @@ -441,12 +521,124 @@ def __draw_local_clifford(self, pos: Mapping[int, _Point]) -> None: x, y = pos[node] + np.array([0.2, 0.2]) plt.text(x, y, f"{self.local_clifford[node]}", fontsize=10, zorder=3) - def __draw_measurement_planes(self, pos: Mapping[int, _Point]) -> None: + def __draw_legend( + self, + show_pauli_measurement: bool, + corrections: tuple[Mapping[int, AbstractSet[int]], Mapping[int, AbstractSet[int]]] | None, + has_arrows: bool, + ) -> None: + """Draw a legend indicating node types and edge meanings. + + Parameters + ---------- + show_pauli_measurement : bool + Whether Pauli-measured nodes are visually distinct. + corrections : tuple or None + X and Z corrections if any, to determine arrow legend entries. + has_arrows : bool + Whether flow arrows are present in the graph. + """ + elements: list[Line2D] = [] + + # Node types + elements.append(Line2D( + [0], [0], marker="s", color="w", markerfacecolor="black", + markeredgecolor="black", markersize=10, label="Input", + )) + elements.append(Line2D( + [0], [0], marker="o", color="w", markerfacecolor="black", + markeredgecolor="black", markersize=10, label="Measured", + )) + if show_pauli_measurement: + elements.append(Line2D( + [0], [0], marker="o", color="w", markerfacecolor="#4292c6", + markeredgecolor="black", markersize=10, label="Pauli-measured", + )) + elements.append(Line2D( + [0], [0], marker="o", color="w", markerfacecolor="white", + markeredgecolor="black", markersize=10, label="Output", + )) + + # Edge types + elements.append(Line2D([0], [0], color="gray", linewidth=1, alpha=0.5, label="Graph edge")) + + if corrections is not None: + elements.append(Line2D([0], [0], color="tab:red", linewidth=1, label="X-correction")) + elements.append(Line2D([0], [0], color="tab:green", linewidth=1, label="Z-correction")) + elements.append(Line2D([0], [0], color="tab:brown", linewidth=1, label="X & Z-correction")) + elif has_arrows: + elements.append(Line2D([0], [0], color="black", linewidth=1, label="Flow")) + + plt.legend(handles=elements, loc="center left", fontsize=9, bbox_to_anchor=(1, 0.5)) + + def __draw_measurement_labels( + self, pos: Mapping[int, _Point], show_planes: bool = False, show_angles: bool = False + ) -> None: + """Draw measurement labels (planes and/or angles) next to measured nodes. + + Labels are rendered with a white background to ensure legibility over graph edges. + + Parameters + ---------- + pos : Mapping[int, tuple[float, float]] + Dictionary of node positions. + show_planes : bool + If True, display the measurement plane (XY, XZ, YZ) or axis (X, Y, Z). + show_angles : bool + If True, display the measurement angle in units of pi. + """ for node, meas in self.og.measurements.items(): - x, y = pos[node] + np.array([0.22, -0.2]) - label = meas.to_plane_or_axis().name + label = self._format_measurement_label(meas, show_planes, show_angles) + if label: + x, y = pos[node] + plt.text( + x + 0.22, y - 0.25, label, fontsize=8, zorder=3, + bbox={"boxstyle": "round,pad=0.15", "facecolor": "white", "edgecolor": "none", "alpha": 0.85}, + ) - plt.text(x, y, label, fontsize=9, zorder=3) + @staticmethod + def _format_measurement_label(meas: Measurement, show_planes: bool, show_angles: bool) -> str: + """Format a measurement label for display. + + Parameters + ---------- + meas : Measurement + The measurement to format. + show_planes : bool + Include measurement plane/axis in the label. + show_angles : bool + Include measurement angle in the label. + + Returns + ------- + str + Formatted label string, or empty string if nothing to show. + """ + if isinstance(meas, PauliMeasurement): + if show_planes: + sign = "" if meas.sign == Sign.PLUS else "-" + return f"{sign}{meas.axis.name}" + if show_angles: + bloch = meas.to_bloch() + if isinstance(bloch.angle, (int, float)): + return angle_to_str(bloch.angle, OutputFormat.Unicode) + return str(bloch.angle) + return "" + if isinstance(meas, BlochMeasurement): + plane_str = meas.plane.name if show_planes else "" + if show_angles: + if isinstance(meas.angle, (int, float)): + angle_str = angle_to_str(meas.angle, OutputFormat.Unicode) + else: + angle_str = str(meas.angle) + if plane_str: + return f"{plane_str}({angle_str})" + return angle_str + return plane_str + # Generic Measurement fallback + if show_planes: + return meas.to_plane_or_axis().name + return "" def determine_figsize( self, diff --git a/tests/baseline/test_draw_graph_reference_False.png b/tests/baseline/test_draw_graph_reference_False.png index aca8ad90275bc72bf2f717e05d54e724d38e712c..22c9742d35487610cfbabdf0df74f4e3411a0688 100644 GIT binary patch literal 2796 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 ILpewN2MGpWJ^%m! literal 2911 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®VnZ1(UMOPAMG6SO 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 literal 16325 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 B7|Q?v diff --git a/tests/test_visualization.py b/tests/test_visualization.py index 030ada64..15b0cc04 100644 --- a/tests/test_visualization.py +++ b/tests/test_visualization.py @@ -9,7 +9,7 @@ import pytest from graphix import Circuit, Pattern, command, visualization -from graphix.fundamentals import ANGLE_PI +from graphix.fundamentals import ANGLE_PI, Sign from graphix.measurements import Measurement from graphix.opengraph import OpenGraph, OpenGraphError from graphix.visualization import GraphVisualizer @@ -250,3 +250,92 @@ def test_draw_graph_reference(flow_and_not_pauli_presimulate: bool) -> Figure: flow_from_pattern=flow_and_not_pauli_presimulate, node_distance=(0.7, 0.6), show_measurement_planes=True ) return plt.gcf() + + +@pytest.mark.usefixtures("mock_plot") +def test_draw_graph_show_measurement_angles(fx_rng: Generator) -> None: + pattern = example_flow(fx_rng) + pattern.draw_graph( + show_measurement_angles=True, + node_distance=(0.7, 0.6), + ) + + +@pytest.mark.usefixtures("mock_plot") +def test_draw_graph_show_measurement_planes_and_angles(fx_rng: Generator) -> None: + pattern = example_pflow(fx_rng) + pattern.draw_graph( + show_measurement_planes=True, + show_measurement_angles=True, + node_distance=(0.7, 0.6), + ) + + +@pytest.mark.usefixtures("mock_plot") +def test_draw_graph_show_legend(fx_rng: Generator) -> None: + pattern = example_flow(fx_rng) + pattern.draw_graph( + show_legend=True, + node_distance=(0.7, 0.6), + ) + + +@pytest.mark.usefixtures("mock_plot") +def test_draw_graph_show_legend_with_corrections(fx_rng: Generator) -> None: + pattern = example_flow(fx_rng) + pattern.draw_graph( + flow_from_pattern=True, + show_legend=True, + show_pauli_measurement=True, + node_distance=(0.7, 0.6), + ) + + +def test_format_measurement_label_bloch() -> None: + bloch_xy = Measurement.XY(0.25) + # planes + angles + label = GraphVisualizer._format_measurement_label(bloch_xy, show_planes=True, show_angles=True) + assert "XY" in label + assert "/" in label # pi/4 contains "/" + # planes only + assert GraphVisualizer._format_measurement_label(bloch_xy, show_planes=True, show_angles=False) == "XY" + # angles only + angle_label = GraphVisualizer._format_measurement_label(bloch_xy, show_planes=False, show_angles=True) + assert angle_label != "" + assert "XY" not in angle_label + # neither + assert GraphVisualizer._format_measurement_label(bloch_xy, show_planes=False, show_angles=False) == "" + + +def test_format_measurement_label_bloch_zero() -> None: + bloch_zero = Measurement.XY(0) + label = GraphVisualizer._format_measurement_label(bloch_zero, show_planes=True, show_angles=True) + assert "XY" in label + assert "0" in label + + +def test_format_measurement_label_bloch_xz() -> None: + bloch_xz = Measurement.XZ(0.5) + label = GraphVisualizer._format_measurement_label(bloch_xz, show_planes=True, show_angles=True) + assert "XZ" in label + + +def test_format_measurement_label_pauli() -> None: + pauli_x = Measurement.X + # planes + assert GraphVisualizer._format_measurement_label(pauli_x, show_planes=True, show_angles=False) == "X" + assert GraphVisualizer._format_measurement_label(pauli_x, show_planes=True, show_angles=True) == "X" + # angles only (returns Bloch equivalent angle) + angle_label = GraphVisualizer._format_measurement_label(pauli_x, show_planes=False, show_angles=True) + assert angle_label == "0" + # neither + assert GraphVisualizer._format_measurement_label(pauli_x, show_planes=False, show_angles=False) == "" + + +def test_format_measurement_label_pauli_minus() -> None: + from graphix.fundamentals import Axis + from graphix.measurements import PauliMeasurement + + pauli_minus_z = PauliMeasurement(Axis.Z, Sign.MINUS) + label = GraphVisualizer._format_measurement_label(pauli_minus_z, show_planes=True, show_angles=False) + assert label == "-Z" From f6d0cf16543f5551aacc5326d3db8f691ca9975a Mon Sep 17 00:00:00 2001 From: CodeMaverick2 Date: Wed, 18 Feb 2026 01:27:49 +0530 Subject: [PATCH 2/6] ci fail fixes --- CHANGELOG.md | 1 + graphix/visualization.py | 112 +++++++++++++++++++++++++----------- tests/test_visualization.py | 13 ++--- 3 files changed, 84 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74b81175..974a0d3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - #386, #433: Added `Statevec.fidelity` and `Statevec.isclose` methods for pure-state fidelity computation and equality check up to global phase. +- #387, #444: Improved `Pattern.draw_graph` visualization: MBQC literature node shapes (squares for inputs, filled/empty circles for measured/output), solid gray edges, measurement order arrow, `show_measurement_angles` and `show_legend` parameters. ### Fixed diff --git a/graphix/visualization.py b/graphix/visualization.py index 9ffec4c9..70f6b8df 100644 --- a/graphix/visualization.py +++ b/graphix/visualization.py @@ -14,11 +14,11 @@ from graphix.flow.exceptions import FlowError from graphix.fundamentals import Sign from graphix.measurements import BlochMeasurement, Measurement, PauliMeasurement -from graphix.pretty_print import OutputFormat, angle_to_str # OpenGraph is needed for dataclass from graphix.opengraph import OpenGraph # noqa: TC001 from graphix.optimization import StandardizedPattern +from graphix.pretty_print import OutputFormat, angle_to_str if TYPE_CHECKING: from collections.abc import Callable, Hashable, Iterable, Mapping, Sequence @@ -336,8 +336,13 @@ def __draw_nodes_role(self, pos: Mapping[int, _Point], show_pauli_measurement: b dark_nodes.add(node) plt.scatter( - *pos[node], marker=marker, edgecolor="black", facecolor=facecolor, - s=350, zorder=2, linewidths=1.5, + *pos[node], + marker=marker, + edgecolor="black", + facecolor=facecolor, + s=350, + zorder=2, + linewidths=1.5, ) return dark_nodes @@ -481,16 +486,20 @@ def visualize_graph( y_min = min((pos[node][1] for node in self.og.graph.nodes()), default=0) # Get the minimum y coordinate y_max = max((pos[node][1] for node in self.og.graph.nodes()), default=0) # Get the maximum y coordinate - has_layers = l_k is not None and l_k - if has_layers: + has_layers = l_k is not None and len(l_k) > 0 + if has_layers and l_k is not None: l_min_val = min(l_k.values()) l_max_val = max(l_k.values()) # Draw layer labels below nodes for layer in range(l_min_val, l_max_val + 1): plt.text( - layer * node_distance[0], y_min - 0.4, + layer * node_distance[0], + y_min - 0.4, f"L{l_max_val - layer}", - ha="center", va="top", fontsize=8, color="gray", + ha="center", + va="top", + fontsize=8, + color="gray", ) # Draw horizontal arrow indicating measurement order if l_max_val > l_min_val: @@ -521,8 +530,8 @@ def __draw_local_clifford(self, pos: Mapping[int, _Point]) -> None: x, y = pos[node] + np.array([0.2, 0.2]) plt.text(x, y, f"{self.local_clifford[node]}", fontsize=10, zorder=3) + @staticmethod def __draw_legend( - self, show_pauli_measurement: bool, corrections: tuple[Mapping[int, AbstractSet[int]], Mapping[int, AbstractSet[int]]] | None, has_arrows: bool, @@ -538,34 +547,65 @@ def __draw_legend( has_arrows : bool Whether flow arrows are present in the graph. """ - elements: list[Line2D] = [] - - # Node types - elements.append(Line2D( - [0], [0], marker="s", color="w", markerfacecolor="black", - markeredgecolor="black", markersize=10, label="Input", - )) - elements.append(Line2D( - [0], [0], marker="o", color="w", markerfacecolor="black", - markeredgecolor="black", markersize=10, label="Measured", - )) + elements: list[Line2D] = [ + Line2D( + [0], + [0], + marker="s", + color="w", + markerfacecolor="black", + markeredgecolor="black", + markersize=10, + label="Input", + ), + Line2D( + [0], + [0], + marker="o", + color="w", + markerfacecolor="black", + markeredgecolor="black", + markersize=10, + label="Measured", + ), + ] if show_pauli_measurement: - elements.append(Line2D( - [0], [0], marker="o", color="w", markerfacecolor="#4292c6", - markeredgecolor="black", markersize=10, label="Pauli-measured", - )) - elements.append(Line2D( - [0], [0], marker="o", color="w", markerfacecolor="white", - markeredgecolor="black", markersize=10, label="Output", - )) - - # Edge types - elements.append(Line2D([0], [0], color="gray", linewidth=1, alpha=0.5, label="Graph edge")) + elements.append( + Line2D( + [0], + [0], + marker="o", + color="w", + markerfacecolor="#4292c6", + markeredgecolor="black", + markersize=10, + label="Pauli-measured", + ) + ) + elements.extend( + [ + Line2D( + [0], + [0], + marker="o", + color="w", + markerfacecolor="white", + markeredgecolor="black", + markersize=10, + label="Output", + ), + Line2D([0], [0], color="gray", linewidth=1, alpha=0.5, label="Graph edge"), + ] + ) if corrections is not None: - elements.append(Line2D([0], [0], color="tab:red", linewidth=1, label="X-correction")) - elements.append(Line2D([0], [0], color="tab:green", linewidth=1, label="Z-correction")) - elements.append(Line2D([0], [0], color="tab:brown", linewidth=1, label="X & Z-correction")) + elements.extend( + [ + Line2D([0], [0], color="tab:red", linewidth=1, label="X-correction"), + Line2D([0], [0], color="tab:green", linewidth=1, label="Z-correction"), + Line2D([0], [0], color="tab:brown", linewidth=1, label="X & Z-correction"), + ] + ) elif has_arrows: elements.append(Line2D([0], [0], color="black", linewidth=1, label="Flow")) @@ -592,7 +632,11 @@ def __draw_measurement_labels( if label: x, y = pos[node] plt.text( - x + 0.22, y - 0.25, label, fontsize=8, zorder=3, + x + 0.22, + y - 0.25, + label, + fontsize=8, + zorder=3, bbox={"boxstyle": "round,pad=0.15", "facecolor": "white", "edgecolor": "none", "alpha": 0.85}, ) diff --git a/tests/test_visualization.py b/tests/test_visualization.py index 15b0cc04..fb855219 100644 --- a/tests/test_visualization.py +++ b/tests/test_visualization.py @@ -9,8 +9,8 @@ import pytest from graphix import Circuit, Pattern, command, visualization -from graphix.fundamentals import ANGLE_PI, Sign -from graphix.measurements import Measurement +from graphix.fundamentals import ANGLE_PI, Axis, Sign +from graphix.measurements import Measurement, PauliMeasurement from graphix.opengraph import OpenGraph, OpenGraphError from graphix.visualization import GraphVisualizer @@ -301,10 +301,10 @@ def test_format_measurement_label_bloch() -> None: assert GraphVisualizer._format_measurement_label(bloch_xy, show_planes=True, show_angles=False) == "XY" # angles only angle_label = GraphVisualizer._format_measurement_label(bloch_xy, show_planes=False, show_angles=True) - assert angle_label != "" + assert angle_label assert "XY" not in angle_label # neither - assert GraphVisualizer._format_measurement_label(bloch_xy, show_planes=False, show_angles=False) == "" + assert not GraphVisualizer._format_measurement_label(bloch_xy, show_planes=False, show_angles=False) def test_format_measurement_label_bloch_zero() -> None: @@ -329,13 +329,10 @@ def test_format_measurement_label_pauli() -> None: angle_label = GraphVisualizer._format_measurement_label(pauli_x, show_planes=False, show_angles=True) assert angle_label == "0" # neither - assert GraphVisualizer._format_measurement_label(pauli_x, show_planes=False, show_angles=False) == "" + assert not GraphVisualizer._format_measurement_label(pauli_x, show_planes=False, show_angles=False) def test_format_measurement_label_pauli_minus() -> None: - from graphix.fundamentals import Axis - from graphix.measurements import PauliMeasurement - pauli_minus_z = PauliMeasurement(Axis.Z, Sign.MINUS) label = GraphVisualizer._format_measurement_label(pauli_minus_z, show_planes=True, show_angles=False) assert label == "-Z" From 1128b568f98225c91b56bba731e7893541fdeba5 Mon Sep 17 00:00:00 2001 From: CodeMaverick2 Date: Wed, 18 Feb 2026 15:22:25 +0530 Subject: [PATCH 3/6] review fixes --- CHANGELOG.md | 2 +- examples/visualization.py | 10 +- graphix/pattern.py | 15 +- graphix/visualization.py | 143 ++++++------------ .../test_draw_graph_reference_True.png | Bin 16781 -> 18845 bytes tests/test_visualization.py | 49 +++--- 6 files changed, 74 insertions(+), 145 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76848a1f..428e00bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - #386, #433: Added `Statevec.fidelity` and `Statevec.isclose` methods for pure-state fidelity computation and equality check up to global phase. -- #387, #444: Improved `Pattern.draw_graph` visualization: MBQC literature node shapes (squares for inputs, filled/empty circles for measured/output), solid gray edges, measurement order arrow, `show_measurement_angles` and `show_legend` parameters. +- #387, #444: Improved `Pattern.draw_graph` visualization: MBQC literature node shapes (squares for inputs, filled/empty circles for measured/output), solid gray edges, measurement order arrow, `show_measurements` and `show_legend` parameters. ### Fixed diff --git a/examples/visualization.py b/examples/visualization.py index 2c08fbeb..28668caa 100644 --- a/examples/visualization.py +++ b/examples/visualization.py @@ -33,13 +33,13 @@ pattern = circuit.transpile().pattern # note that this visualization is not always consistent with the correction set of pattern, # since we find the correction sets with flow-finding algorithms. -pattern.draw_graph(flow_from_pattern=False, show_measurement_planes=True) +pattern.draw_graph(flow_from_pattern=False, show_measurements=True) # %% # next, show the gflow: pattern.remove_input_nodes() pattern.perform_pauli_measurements() -pattern.draw_graph(flow_from_pattern=False, show_measurement_planes=True, node_distance=(1, 0.6)) +pattern.draw_graph(flow_from_pattern=False, show_measurements=True, node_distance=(1, 0.6)) # %% @@ -49,7 +49,7 @@ # # node_distance argument specifies the scale of the node arrangement in x and y directions. -pattern.draw_graph(flow_from_pattern=True, show_measurement_planes=True, node_distance=(0.7, 0.6)) +pattern.draw_graph(flow_from_pattern=True, show_measurements=True, node_distance=(0.7, 0.6)) # %% # Instead of the measurement planes, we can show the local Clifford of the resource graph. @@ -75,7 +75,7 @@ measurements = {node: Measurement.XY(0) for node in graph.nodes() if node not in outputs} og = OpenGraph(graph, inputs, outputs, measurements) vis = GraphVisualizer(og) -vis.visualize(show_measurement_planes=True) +vis.visualize(show_measurements=True) # %% @@ -91,6 +91,6 @@ } og = OpenGraph(graph, inputs, outputs, measurements) vis = GraphVisualizer(og) -vis.visualize(show_measurement_planes=True) +vis.visualize(show_measurements=True) # %% diff --git a/graphix/pattern.py b/graphix/pattern.py index 02c81427..ad19a16b 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -1437,8 +1437,7 @@ def draw_graph( flow_from_pattern: bool = True, show_pauli_measurement: bool = True, show_local_clifford: bool = False, - show_measurement_planes: bool = False, - show_measurement_angles: bool = False, + show_measurements: bool = False, show_legend: bool = False, show_loop: bool = True, node_distance: tuple[float, float] = (1, 1), @@ -1458,10 +1457,8 @@ def draw_graph( If True, Pauli-measured nodes are filled with blue instead of black. show_local_clifford : bool If True, indexes of the local Clifford operator are displayed adjacent to the nodes. - show_measurement_planes : bool - If True, measurement planes are displayed adjacent to the nodes. - show_measurement_angles : bool - If True, measurement angles (in units of pi) are displayed adjacent to the nodes. + show_measurements : bool + If True, measurement labels are displayed adjacent to the nodes. show_legend : bool If True, a legend is displayed indicating node types and edge meanings. show_loop : bool @@ -1484,8 +1481,7 @@ def draw_graph( pattern=self.copy(), show_pauli_measurement=show_pauli_measurement, show_local_clifford=show_local_clifford, - show_measurement_planes=show_measurement_planes, - show_measurement_angles=show_measurement_angles, + show_measurements=show_measurements, show_legend=show_legend, show_loop=show_loop, node_distance=node_distance, @@ -1496,8 +1492,7 @@ def draw_graph( vis.visualize( show_pauli_measurement=show_pauli_measurement, show_local_clifford=show_local_clifford, - show_measurement_planes=show_measurement_planes, - show_measurement_angles=show_measurement_angles, + show_measurements=show_measurements, show_legend=show_legend, show_loop=show_loop, node_distance=node_distance, diff --git a/graphix/visualization.py b/graphix/visualization.py index 70f6b8df..1fbf83b2 100644 --- a/graphix/visualization.py +++ b/graphix/visualization.py @@ -12,7 +12,6 @@ from matplotlib.lines import Line2D from graphix.flow.exceptions import FlowError -from graphix.fundamentals import Sign from graphix.measurements import BlochMeasurement, Measurement, PauliMeasurement # OpenGraph is needed for dataclass @@ -59,8 +58,7 @@ def visualize( self, show_pauli_measurement: bool = True, show_local_clifford: bool = False, - show_measurement_planes: bool = False, - show_measurement_angles: bool = False, + show_measurements: bool = False, show_legend: bool = False, show_loop: bool = True, node_distance: tuple[float, float] = (1, 1), @@ -80,10 +78,8 @@ def visualize( If True, Pauli-measured nodes are filled with blue instead of black. show_local_clifford : bool If True, indexes of the local Clifford operator are displayed adjacent to the nodes. - show_measurement_planes : bool - If True, the measurement planes are displayed adjacent to the nodes. - show_measurement_angles : bool - If True, the measurement angles (in units of pi) are displayed adjacent to the nodes. + show_measurements : bool + If True, measurement labels are displayed adjacent to the nodes. show_legend : bool If True, a legend is displayed indicating node types and edge meanings. show_loop : bool @@ -146,8 +142,7 @@ def place_paths( None, show_pauli_measurement, show_local_clifford, - show_measurement_planes, - show_measurement_angles, + show_measurements, show_legend, show_loop, node_distance, @@ -160,8 +155,7 @@ def visualize_from_pattern( pattern: Pattern, show_pauli_measurement: bool = True, show_local_clifford: bool = False, - show_measurement_planes: bool = False, - show_measurement_angles: bool = False, + show_measurements: bool = False, show_legend: bool = False, show_loop: bool = True, node_distance: tuple[float, float] = (1, 1), @@ -182,10 +176,8 @@ def visualize_from_pattern( If True, Pauli-measured nodes are filled with blue instead of black. show_local_clifford : bool If True, indexes of the local Clifford operator are displayed adjacent to the nodes. - show_measurement_planes : bool - If True, the measurement planes are displayed adjacent to the nodes. - show_measurement_angles : bool - If True, the measurement angles (in units of pi) are displayed adjacent to the nodes. + show_measurements : bool + If True, measurement labels are displayed adjacent to the nodes. show_legend : bool If True, a legend is displayed indicating node types and edge meanings. show_loop : bool @@ -250,8 +242,7 @@ def place_paths( corrections, show_pauli_measurement, show_local_clifford, - show_measurement_planes, - show_measurement_angles, + show_measurements, show_legend, show_loop, node_distance, @@ -269,30 +260,22 @@ def _shorten_path(path: Sequence[_Point]) -> list[_Point]: new_path[-1] = last_edge return new_path - def _draw_labels(self, pos: Mapping[int, _Point], dark_nodes: set[int] | None = None) -> None: + def _draw_labels(self, pos: Mapping[int, _Point], font_color: Mapping[int, str] | str = "black") -> None: """Draw node number labels with appropriate text color. Parameters ---------- pos : Mapping[int, tuple[float, float]] Dictionary of node positions. - dark_nodes : set[int] | None - Nodes with dark backgrounds that need white text. If None, all labels are black. + font_color : Mapping[int, str] | str + Font color for node labels. Can be a single color string or a mapping from node to color. """ fontsize = 12 if max(self.og.graph.nodes(), default=0) >= 100: fontsize = int(fontsize * 2 / len(str(max(self.og.graph.nodes())))) - if dark_nodes: - light_labels = {n: str(n) for n in self.og.graph.nodes() if n not in dark_nodes} - dark_labels = {n: str(n) for n in self.og.graph.nodes() if n in dark_nodes} - if light_labels: - nx.draw_networkx_labels(self.og.graph, pos, labels=light_labels, font_size=fontsize, font_color="black") - if dark_labels: - nx.draw_networkx_labels(self.og.graph, pos, labels=dark_labels, font_size=fontsize, font_color="white") - else: - nx.draw_networkx_labels(self.og.graph, pos, font_size=fontsize) + nx.draw_networkx_labels(self.og.graph, pos, font_size=fontsize, font_color=font_color) - def __draw_nodes_role(self, pos: Mapping[int, _Point], show_pauli_measurement: bool = False) -> set[int]: + def __draw_nodes_role(self, pos: Mapping[int, _Point], show_pauli_measurement: bool = False) -> dict[int, str]: """Draw the nodes with shapes and fills following MBQC literature conventions. Input nodes are drawn as squares, measured (non-output) nodes as filled circles, @@ -308,32 +291,23 @@ def __draw_nodes_role(self, pos: Mapping[int, _Point], show_pauli_measurement: b Returns ------- - set[int] - Set of node indices with dark (black) fill, used for white label text. + dict[int, str] + Mapping from node index to font color for label rendering. """ - dark_nodes: set[int] = set() - input_set = set(self.og.input_nodes) - output_set = set(self.og.output_nodes) + font_colors: dict[int, str] = {} for node in self.og.graph.nodes(): - is_input = node in input_set - is_output = node in output_set - is_pauli = ( - show_pauli_measurement - and node in self.og.measurements - and isinstance(self.og.measurements[node], PauliMeasurement) - ) + marker = "s" if node in self.og.input_nodes else "o" + is_pauli = node in self.og.measurements and isinstance(self.og.measurements[node], PauliMeasurement) - # Inputs are squares, all others are circles - marker = "s" if is_input else "o" - - if is_output: + if node in self.og.output_nodes: facecolor = "white" - elif is_pauli: + elif show_pauli_measurement and is_pauli: facecolor = "#4292c6" else: facecolor = "black" - dark_nodes.add(node) + + font_colors[node] = "white" if facecolor == "black" else "black" plt.scatter( *pos[node], @@ -345,7 +319,7 @@ def __draw_nodes_role(self, pos: Mapping[int, _Point], show_pauli_measurement: b linewidths=1.5, ) - return dark_nodes + return font_colors def visualize_graph( self, @@ -357,8 +331,7 @@ def visualize_graph( corrections: tuple[Mapping[int, AbstractSet[int]], Mapping[int, AbstractSet[int]]] | None, show_pauli_measurement: bool = True, show_local_clifford: bool = False, - show_measurement_planes: bool = False, - show_measurement_angles: bool = False, + show_measurements: bool = False, show_legend: bool = False, show_loop: bool = True, node_distance: tuple[float, float] = (1, 1), @@ -388,10 +361,8 @@ def visualize_graph( If True, Pauli-measured nodes are filled with blue instead of black. show_local_clifford : bool If True, indexes of the local Clifford operator are displayed adjacent to the nodes. - show_measurement_planes : bool - If True, the measurement planes are displayed adjacent to the nodes. - show_measurement_angles : bool - If True, the measurement angles (in units of pi) are displayed adjacent to the nodes. + show_measurements : bool + If True, measurement labels are displayed adjacent to the nodes. show_legend : bool If True, a legend is displayed indicating node types and edge meanings. show_loop : bool @@ -461,15 +432,15 @@ def visualize_graph( arrowprops={"arrowstyle": "->", "color": color, "lw": 1}, ) - dark_nodes = self.__draw_nodes_role(pos, show_pauli_measurement) + font_colors = self.__draw_nodes_role(pos, show_pauli_measurement) if show_local_clifford: self.__draw_local_clifford(pos) - if show_measurement_planes or show_measurement_angles: - self.__draw_measurement_labels(pos, show_measurement_planes, show_measurement_angles) + if show_measurements: + self.__draw_measurement_labels(pos) - self._draw_labels(pos, dark_nodes) + self._draw_labels(pos, font_colors) if show_legend: self.__draw_legend(show_pauli_measurement, corrections, arrow_path is not None) @@ -611,10 +582,8 @@ def __draw_legend( plt.legend(handles=elements, loc="center left", fontsize=9, bbox_to_anchor=(1, 0.5)) - def __draw_measurement_labels( - self, pos: Mapping[int, _Point], show_planes: bool = False, show_angles: bool = False - ) -> None: - """Draw measurement labels (planes and/or angles) next to measured nodes. + def __draw_measurement_labels(self, pos: Mapping[int, _Point]) -> None: + """Draw measurement labels next to measured nodes. Labels are rendered with a white background to ensure legibility over graph edges. @@ -622,14 +591,10 @@ def __draw_measurement_labels( ---------- pos : Mapping[int, tuple[float, float]] Dictionary of node positions. - show_planes : bool - If True, display the measurement plane (XY, XZ, YZ) or axis (X, Y, Z). - show_angles : bool - If True, display the measurement angle in units of pi. """ for node, meas in self.og.measurements.items(): - label = self._format_measurement_label(meas, show_planes, show_angles) - if label: + label = self._format_measurement_label(meas) + if label is not None: x, y = pos[node] plt.text( x + 0.22, @@ -641,48 +606,28 @@ def __draw_measurement_labels( ) @staticmethod - def _format_measurement_label(meas: Measurement, show_planes: bool, show_angles: bool) -> str: + def _format_measurement_label(meas: Measurement) -> str | None: """Format a measurement label for display. Parameters ---------- meas : Measurement The measurement to format. - show_planes : bool - Include measurement plane/axis in the label. - show_angles : bool - Include measurement angle in the label. Returns ------- - str - Formatted label string, or empty string if nothing to show. + str | None + Formatted label string, or None if nothing to show. """ if isinstance(meas, PauliMeasurement): - if show_planes: - sign = "" if meas.sign == Sign.PLUS else "-" - return f"{sign}{meas.axis.name}" - if show_angles: - bloch = meas.to_bloch() - if isinstance(bloch.angle, (int, float)): - return angle_to_str(bloch.angle, OutputFormat.Unicode) - return str(bloch.angle) - return "" + return str(meas) if isinstance(meas, BlochMeasurement): - plane_str = meas.plane.name if show_planes else "" - if show_angles: - if isinstance(meas.angle, (int, float)): - angle_str = angle_to_str(meas.angle, OutputFormat.Unicode) - else: - angle_str = str(meas.angle) - if plane_str: - return f"{plane_str}({angle_str})" - return angle_str - return plane_str - # Generic Measurement fallback - if show_planes: - return meas.to_plane_or_axis().name - return "" + if isinstance(meas.angle, (int, float)): + angle_str = angle_to_str(meas.angle, OutputFormat.Unicode) + else: + angle_str = str(meas.angle) + return f"{meas.plane.name}({angle_str})" + return None def determine_figsize( self, diff --git a/tests/baseline/test_draw_graph_reference_True.png b/tests/baseline/test_draw_graph_reference_True.png index b02ae067b48bed0157c69bea6eb7bd493f718856..c163fa46ba33b73c3a3aa17a5c9fa7da566650ca 100644 GIT binary patch literal 18845 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®VnZ1(UMOPAMG6SO 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 diff --git a/tests/test_visualization.py b/tests/test_visualization.py index fb855219..2fa59cdd 100644 --- a/tests/test_visualization.py +++ b/tests/test_visualization.py @@ -144,10 +144,10 @@ def test_draw_graph_show_local_clifford() -> None: @pytest.mark.usefixtures("mock_plot") -def test_draw_graph_show_measurement_planes(fx_rng: Generator) -> None: +def test_draw_graph_show_measurements_basic(fx_rng: Generator) -> None: pattern = example_pflow(fx_rng) pattern.draw_graph( - show_measurement_planes=True, + show_measurements=True, node_distance=(0.7, 0.6), ) @@ -247,26 +247,25 @@ def test_draw_graph_reference(flow_and_not_pauli_presimulate: bool) -> Figure: pattern.perform_pauli_measurements() pattern.standardize() pattern.draw_graph( - flow_from_pattern=flow_and_not_pauli_presimulate, node_distance=(0.7, 0.6), show_measurement_planes=True + flow_from_pattern=flow_and_not_pauli_presimulate, node_distance=(0.7, 0.6), show_measurements=True ) return plt.gcf() @pytest.mark.usefixtures("mock_plot") -def test_draw_graph_show_measurement_angles(fx_rng: Generator) -> None: +def test_draw_graph_show_measurements(fx_rng: Generator) -> None: pattern = example_flow(fx_rng) pattern.draw_graph( - show_measurement_angles=True, + show_measurements=True, node_distance=(0.7, 0.6), ) @pytest.mark.usefixtures("mock_plot") -def test_draw_graph_show_measurement_planes_and_angles(fx_rng: Generator) -> None: +def test_draw_graph_show_measurements_pflow(fx_rng: Generator) -> None: pattern = example_pflow(fx_rng) pattern.draw_graph( - show_measurement_planes=True, - show_measurement_angles=True, + show_measurements=True, node_distance=(0.7, 0.6), ) @@ -293,46 +292,36 @@ def test_draw_graph_show_legend_with_corrections(fx_rng: Generator) -> None: def test_format_measurement_label_bloch() -> None: bloch_xy = Measurement.XY(0.25) - # planes + angles - label = GraphVisualizer._format_measurement_label(bloch_xy, show_planes=True, show_angles=True) + label = GraphVisualizer._format_measurement_label(bloch_xy) + assert label is not None assert "XY" in label assert "/" in label # pi/4 contains "/" - # planes only - assert GraphVisualizer._format_measurement_label(bloch_xy, show_planes=True, show_angles=False) == "XY" - # angles only - angle_label = GraphVisualizer._format_measurement_label(bloch_xy, show_planes=False, show_angles=True) - assert angle_label - assert "XY" not in angle_label - # neither - assert not GraphVisualizer._format_measurement_label(bloch_xy, show_planes=False, show_angles=False) def test_format_measurement_label_bloch_zero() -> None: bloch_zero = Measurement.XY(0) - label = GraphVisualizer._format_measurement_label(bloch_zero, show_planes=True, show_angles=True) + label = GraphVisualizer._format_measurement_label(bloch_zero) + assert label is not None assert "XY" in label assert "0" in label def test_format_measurement_label_bloch_xz() -> None: bloch_xz = Measurement.XZ(0.5) - label = GraphVisualizer._format_measurement_label(bloch_xz, show_planes=True, show_angles=True) + label = GraphVisualizer._format_measurement_label(bloch_xz) + assert label is not None assert "XZ" in label def test_format_measurement_label_pauli() -> None: pauli_x = Measurement.X - # planes - assert GraphVisualizer._format_measurement_label(pauli_x, show_planes=True, show_angles=False) == "X" - assert GraphVisualizer._format_measurement_label(pauli_x, show_planes=True, show_angles=True) == "X" - # angles only (returns Bloch equivalent angle) - angle_label = GraphVisualizer._format_measurement_label(pauli_x, show_planes=False, show_angles=True) - assert angle_label == "0" - # neither - assert not GraphVisualizer._format_measurement_label(pauli_x, show_planes=False, show_angles=False) + label = GraphVisualizer._format_measurement_label(pauli_x) + assert label == str(pauli_x) + assert "X" in label def test_format_measurement_label_pauli_minus() -> None: pauli_minus_z = PauliMeasurement(Axis.Z, Sign.MINUS) - label = GraphVisualizer._format_measurement_label(pauli_minus_z, show_planes=True, show_angles=False) - assert label == "-Z" + label = GraphVisualizer._format_measurement_label(pauli_minus_z) + assert label == str(pauli_minus_z) + assert "-Z" in label From d91f34145976ff9c69745ee96ee13d2129aea703 Mon Sep 17 00:00:00 2001 From: CodeMaverick2 Date: Wed, 18 Feb 2026 15:48:53 +0530 Subject: [PATCH 4/6] added type: ignore --- graphix/visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphix/visualization.py b/graphix/visualization.py index 1fbf83b2..0c0cb986 100644 --- a/graphix/visualization.py +++ b/graphix/visualization.py @@ -273,7 +273,7 @@ def _draw_labels(self, pos: Mapping[int, _Point], font_color: Mapping[int, str] fontsize = 12 if max(self.og.graph.nodes(), default=0) >= 100: fontsize = int(fontsize * 2 / len(str(max(self.og.graph.nodes())))) - nx.draw_networkx_labels(self.og.graph, pos, font_size=fontsize, font_color=font_color) + nx.draw_networkx_labels(self.og.graph, pos, font_size=fontsize, font_color=font_color) # type: ignore[arg-type] def __draw_nodes_role(self, pos: Mapping[int, _Point], show_pauli_measurement: bool = False) -> dict[int, str]: """Draw the nodes with shapes and fills following MBQC literature conventions. From 1f89adf0ef3e32f8bd163bf2c5acc5af0465eb74 Mon Sep 17 00:00:00 2001 From: CodeMaverick2 Date: Wed, 18 Feb 2026 16:06:01 +0530 Subject: [PATCH 5/6] ci fix --- tests/test_visualization.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_visualization.py b/tests/test_visualization.py index 2fa59cdd..5a1d23f5 100644 --- a/tests/test_visualization.py +++ b/tests/test_visualization.py @@ -316,6 +316,7 @@ def test_format_measurement_label_bloch_xz() -> None: def test_format_measurement_label_pauli() -> None: pauli_x = Measurement.X label = GraphVisualizer._format_measurement_label(pauli_x) + assert label is not None assert label == str(pauli_x) assert "X" in label @@ -323,5 +324,6 @@ def test_format_measurement_label_pauli() -> None: def test_format_measurement_label_pauli_minus() -> None: pauli_minus_z = PauliMeasurement(Axis.Z, Sign.MINUS) label = GraphVisualizer._format_measurement_label(pauli_minus_z) + assert label is not None assert label == str(pauli_minus_z) assert "-Z" in label From d3adee95295c334601b482789d48a13cd0c4eb0d Mon Sep 17 00:00:00 2001 From: CodeMaverick2 Date: Wed, 18 Feb 2026 16:41:37 +0530 Subject: [PATCH 6/6] suggestion fixes --- graphix/visualization.py | 14 ++++++++------ .../test_draw_graph_reference_True.png | Bin 18845 -> 19121 bytes 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/graphix/visualization.py b/graphix/visualization.py index 0c0cb986..c8499d88 100644 --- a/graphix/visualization.py +++ b/graphix/visualization.py @@ -390,10 +390,12 @@ def visualize_graph( for edge, path in edge_path.items(): if len(path) == 2: - nx.draw_networkx_edges(self.og.graph, pos, edgelist=[edge], style="solid", edge_color="gray", alpha=0.5) + nx.draw_networkx_edges( + self.og.graph, pos, edgelist=[edge], style="dashed", edge_color="gray", alpha=0.6 + ) else: curve = self._bezier_curve_linspace(path) - plt.plot(curve[:, 0], curve[:, 1], color="gray", linewidth=1, alpha=0.5) + plt.plot(curve[:, 0], curve[:, 1], color="gray", linewidth=1, alpha=0.6, linestyle="dashed") if arrow_path is not None: for arrow, path in arrow_path.items(): @@ -446,7 +448,7 @@ def visualize_graph( self.__draw_legend(show_pauli_measurement, corrections, arrow_path is not None) elif corrections is not None: # backward-compatible minimal legend for correction arrows - plt.plot([], [], color="gray", alpha=0.5, label="graph edge") + plt.plot([], [], color="gray", alpha=0.6, linestyle="dashed", label="graph edge") plt.plot([], [], color="tab:red", label="xflow") plt.plot([], [], color="tab:green", label="zflow") plt.plot([], [], color="tab:brown", label="xflow and zflow") @@ -565,7 +567,7 @@ def __draw_legend( markersize=10, label="Output", ), - Line2D([0], [0], color="gray", linewidth=1, alpha=0.5, label="Graph edge"), + Line2D([0], [0], color="gray", linewidth=1, alpha=0.6, linestyle="dashed", label="Graph edge"), ] ) @@ -597,8 +599,8 @@ def __draw_measurement_labels(self, pos: Mapping[int, _Point]) -> None: if label is not None: x, y = pos[node] plt.text( - x + 0.22, - y - 0.25, + x + 0.18, + y - 0.2, label, fontsize=8, zorder=3, diff --git a/tests/baseline/test_draw_graph_reference_True.png b/tests/baseline/test_draw_graph_reference_True.png index c163fa46ba33b73c3a3aa17a5c9fa7da566650ca..07ae5dc86825b8528a8f56a185d411f58bb3ee3d 100644 GIT binary patch literal 19121 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