diff --git a/cqlib/circuits/circuit.py b/cqlib/circuits/circuit.py index 4d430637465b43faddded575fb318417088206f1..d594fc7647053126767093f023ddd6d1475dcde1 100644 --- a/cqlib/circuits/circuit.py +++ b/cqlib/circuits/circuit.py @@ -1015,6 +1015,7 @@ class Circuit: category (str): The type of drawing to produce. Supported categories are: - 'text': Draws the circuit as ASCII art. - 'mpl': Draws the circuit using Matplotlib for a graphical representation. + - 'plotly': Draws the circuit using Plotly for an interactive representation. qubit_order (list[int | Qubit], optional): A list specifying the order of qubits in the output. If None, the default order is used. **kwargs: Additional keyword arguments that are passed to the specific drawing @@ -1024,6 +1025,7 @@ class Circuit: The return value depends on the selected category: - For 'text', it returns a string representing the circuit in text format. - For 'mpl', it typically displays or returns a Matplotlib figure object. + - For 'plotly', it returns a Plotly figure object for interactive visualization. Raises: ValueError: If an unsupported category is provided. @@ -1036,6 +1038,10 @@ class Circuit: # pylint: disable=import-outside-toplevel from cqlib.visualization.circuit.mpl import draw_mpl return draw_mpl(self, qubit_order=qubit_order, **kwargs) + if category == 'plotly': + # pylint: disable=import-outside-toplevel + from cqlib.visualization.circuit.plotly import draw_plotly + return draw_plotly(self, qubit_order=qubit_order, **kwargs) raise VisualizationError(f"Unsupported category: {category}") def clone(self): diff --git a/cqlib/visualization/__init__.py b/cqlib/visualization/__init__.py index 166d24c83dd5688cc89118f4b89284bb286ea419..df44866b798bd1c9f8fe81615ee8c7726dd9c17f 100644 --- a/cqlib/visualization/__init__.py +++ b/cqlib/visualization/__init__.py @@ -15,12 +15,12 @@ visualization module defines methods to visualize qcis circuits and plotting experiment results. """ -from .circuit import draw_text, draw_mpl, TextDrawer, MatplotlibDrawer +from .circuit import draw_text, draw_mpl, draw_plotly, TextDrawer, MatplotlibDrawer, PlotlyDrawer from .gplot import draw_gplot from .result import draw_histogram __all__ = [ - 'draw_text', 'draw_mpl', 'TextDrawer', 'MatplotlibDrawer', + 'draw_text', 'draw_mpl', 'draw_plotly', 'TextDrawer', 'MatplotlibDrawer', 'PlotlyDrawer', 'draw_gplot', 'draw_histogram' ] diff --git a/cqlib/visualization/circuit/__init__.py b/cqlib/visualization/circuit/__init__.py index 9617dff35ba12067344a2092d762a6f35e427e98..c975e668e5d3377703fba31893842933ddffe129 100644 --- a/cqlib/visualization/circuit/__init__.py +++ b/cqlib/visualization/circuit/__init__.py @@ -17,8 +17,10 @@ Quantum circuit visualization from .text import draw_text, TextDrawer from .mpl import MatplotlibDrawer, draw_mpl +from .plotly import PlotlyDrawer, draw_plotly __all__ = [ 'draw_text', 'TextDrawer', 'draw_mpl', 'MatplotlibDrawer', + 'draw_plotly', 'PlotlyDrawer' ] diff --git a/cqlib/visualization/circuit/mpl.py b/cqlib/visualization/circuit/mpl.py index 1f35aab18e7752fa3363023d9cc16d7b42723ef4..f46e9f8ed519d0117ad2b346c1589e2eb5115d08 100644 --- a/cqlib/visualization/circuit/mpl.py +++ b/cqlib/visualization/circuit/mpl.py @@ -16,6 +16,7 @@ Matplotlib quantum circuit drawer module """ import logging +import pickle from collections.abc import Sequence from enum import IntEnum @@ -87,6 +88,7 @@ class MatplotlibDrawer(BaseDrawer): title: str | None = None, fonts: str | list[str] | None = None, filename: str | None = None, + file_type: str | None = None, style: str = 'default', ): """ @@ -101,6 +103,8 @@ class MatplotlibDrawer(BaseDrawer): title (str | None): Title displayed above the circuit. fonts (str | list[str] | None): Font family/families for text rendering. filename (str, optional): Optional filename to save the diagram to. + file_type(str | None): Optional save file type, + support inputting one of the values: png, svg or fig """ super().__init__(circuit, qubit_order) @@ -113,6 +117,7 @@ class MatplotlibDrawer(BaseDrawer): self._fonts = fonts self._title = title self._filename = filename + self._file_type = file_type self._fig = None self._ax = None self._real_line_width = 1 @@ -159,7 +164,17 @@ class MatplotlibDrawer(BaseDrawer): ) self._current_x += self.figure_styles['moment_width'] if self._filename: - self._fig.savefig(self._filename, transparent=None, dpi='figure', ) + if self._file_type == 'png' or not self._file_type: + self._fig.savefig(self._filename, transparent=None, dpi='figure', ) + elif self._file_type == 'svg': + self._fig.savefig(f'{self._filename}.svg', + format='svg',transparent=None, dpi='figure', ) + elif self._file_type == 'fig': + with open(f'{self._filename}.fig', 'wb') as f: + pickle.dump(self._fig, f) + else: + raise CqlibError('The "file_type" parameter' + ' can only accept one of the values: png, svg, or fig.') return self._fig def _make_lines(self): @@ -443,6 +458,7 @@ def draw_mpl( circuit: Circuit, title: str | None = None, filename: str | None = None, + file_type: str | None = None, qubit_order: list[int | Qubit] | None = None, figure_styles: dict[str, str | bool | float | tuple[float | int]] | None = None, gate_styles: dict[str, str | bool | float | tuple[float | int]] | None = None, @@ -456,6 +472,8 @@ def draw_mpl( circuit(Circuit): Quantum circuit to draw title (str | None): Optional diagram title filename (str | None): Optional filename to save the diagram + file_type(str | None): Optional save file type, + support inputting one of the values: png, svg or fig qubit_order (list[int | Qubit] | None): Custom display order for qubits. Defaults to circuit's natural ordering. figure_styles (dict | None): Overrides for default figure styles (colors, margins, etc.). @@ -470,6 +488,7 @@ def draw_mpl( circuit, title=title, filename=filename, + file_type=file_type, figure_styles=figure_styles, qubit_order=qubit_order, gate_styles=gate_styles, diff --git a/cqlib/visualization/circuit/plotly.py b/cqlib/visualization/circuit/plotly.py new file mode 100644 index 0000000000000000000000000000000000000000..a84f1720b87b2ba743524c88aa05438e1c4bed34 --- /dev/null +++ b/cqlib/visualization/circuit/plotly.py @@ -0,0 +1,643 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Plotly quantum circuit drawer module +""" + +import logging +from typing import Sequence +import plotly.graph_objects as go +import numpy as np +from plotly.graph_objs import Figure +from cqlib.circuits import InstructionData +from cqlib.circuits.barrier import Barrier +from cqlib.circuits.bit import Bit +from cqlib.circuits.gates import SWAP, CZ +from cqlib.circuits.gates.gate import ControlledGate +from cqlib.circuits.measure import Measure +from cqlib.circuits.qubit import Qubit + +from .base import BaseDrawer +from .style import Style +from ... import Circuit +from ...exceptions import CqlibError + +logger = logging.getLogger('cqlib.vis') + + +# pylint: disable=too-many-instance-attributes +class PlotlyDrawer(BaseDrawer): + """ + Quantum circuit visualizer using Plotly. + + Renders interactive circuit diagrams with customizable styling and layout. + + Attributes: + figure_styles (dict): Default styles for the figure layout and appearance + _title (str): Title to display above the circuit diagram + _gate_style (Style): Styling configuration for quantum gates + _fig (go.Figure): Plotly figure object for rendering + _real_line_width (float): Actual width of the circuit visualization + _current_x (float): Current x-coordinate position during drawing + _filename (str | None): Filename for saving the visualization + """ + + # Default styles for figure layout and appearance + figure_styles = { + 'figure_color': 'white', + 'show_axis': '0', + 'margin_top': 30, + 'margin_bottom': 0, + 'margin_left': 0, + 'margin_right': 0, + 'moment_width': 30, + 'gate_width': 60, + 'gate_height': 40, + } + + # pylint: disable=too-many-arguments,too-many-positional-arguments + def __init__( + self, + circuit: Circuit, + qubit_order: list[int | Qubit] | None = None, + figure_styles: dict[str, str | bool | float | tuple[float | int]] | None = None, + gate_styles: dict[str, str | bool | float | tuple[float | int]] | None = None, + title: str | None = None, + filename: str | None = None, + ): + """ + Initializes the Plotly circuit visualizer. + + Args: + circuit: Quantum circuit to visualize + qubit_order: Custom display order for qubits (defaults to circuit's ordering) + figure_styles: Overrides for default figure styles + gate_styles: Overrides for default gate rendering styles + title: Title displayed above the circuit diagram + filename: Filename for saving the visualization (saves as HTML if provided) + """ + super().__init__(circuit, qubit_order) + + # Update figure styles with any customizations + if figure_styles: + self.figure_styles.update(figure_styles) + + # Set visualization properties + self._title = title + self._gate_style = Style('default', gate_styles) # Gate styling configuration + self._fig = None # Will hold the Plotly figure + self._real_line_width = self.figure_styles['gate_width'] # Initialize circuit width + self._current_x = 0 # Current drawing position + self._filename = filename # Optional output filename + self._gate_height = self.figure_styles['gate_height'] + self._gate_width = self.figure_styles['gate_width'] + + def drawer(self) -> go.Figure: + """ + Generates complete circuit visualization. + + Returns: + Plotly Figure object containing the interactive circuit diagram + """ + # Generate circuit layout structure + lines = self._make_lines() + + # Initialize Plotly figure with configured styles + self._fig = self._setup_figure_plotly() + + # Draw qubit lines and labels + self._draw_lines() + + moment_width = self.figure_styles['moment_width'] + self._current_x += 40 + moment_width # Initial offset + + for moment in lines: + start = self._current_x # Track start position of the moment + + # Process each column within the moment + for column in moment['moment']: + column_width = column['width'] + + # Position gate in the center of the column + self._current_x += column_width / 2 + self.draw_column(column['column']) + self._current_x += column_width / 2 + + # Add background highlight for moments with multiple columns + if len(moment['moment']) > 1: + # Calculate highlight area coordinates + x_min = start - moment_width * 0.1 + x_max = self._current_x + moment_width * 0.1 + y_min = (self.qubit_mapping.get(self.sorted_qubits[0]) - 0.6) * self._gate_height + y_max = ((self.qubit_mapping + .get(self.sorted_qubits[len(self.sorted_qubits) - 1]) + 0.6) + * self._gate_height) + + # Add semi-transparent background rectangle + self._fig.add_shape( + type="rect", + x0=x_min, + y0=y_min, + x1=x_max, + y1=y_max, + fillcolor="lightgray", + opacity=0.2, + line={"width": 0}, + layer="below", + ) + + # Add spacing between moments + self._current_x += self.figure_styles['moment_width'] + + # Save to HTML if filename was provided + if self._filename: + self._fig.write_html(f'{self._filename}.html') + + return self._fig + + def _setup_figure_plotly(self) -> Figure: + """ + Configures the base Plotly figure with layout settings. + + Returns: + Initialized Plotly Figure object with configured layout + """ + # Create new figure + fig = go.Figure() + + # Set core layout configuration + fig.update_layout( + width=self._real_line_width, + height=len(self.sorted_qubits) * 2 * self._gate_height, + margin={"l": self.figure_styles['margin_left'], + "r": self.figure_styles['margin_right'], + "t": self.figure_styles['margin_top'], + "b": self.figure_styles['margin_bottom']}, + plot_bgcolor='white', + paper_bgcolor=self.figure_styles['figure_color'], + title_text=self._title, + title_x=0.5, + modebar={"orientation": 'h', + "bgcolor": 'rgba(255,255,255,0.7)', + "color": 'black', + "activecolor": 'red'}, + # Toolbar buttons + modebar_add=[ + 'zoom2d', 'pan2d', 'select2d', 'lasso2d', + 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d', + 'toImage' + ] + ) + + # Configure X-axis + fig.update_xaxes( + range=[0, self._real_line_width], + showgrid=True, + zeroline=False, + visible=self.figure_styles['show_axis'] in [1, True, 'true', '1'], + ) + + # Configure Y-axis (with inverted coordinates) + fig.update_yaxes( + range=[len(self.sorted_qubits) * 2 * self._gate_height, 0], + showgrid=True, + zeroline=False, + visible=self.figure_styles['show_axis'] in [1, True, 'true', '1'], + scaleanchor="x", + scaleratio=1, + ) + + return fig + + def _make_lines(self): + """ + Organizes circuit moments into renderable columns. + + Returns: + List of moments with layout information + """ + lines = [] + for moment in self.generate_moment(): + columns = [] + max_width = 0 + for column in self.moment_to_columns(moment): + col_width = 0 + instructions = [] + + for ins in column: + ins_width = self._gate_width + if ins.instruction.params: + t = self._str_params(ins.instruction) + ins_width = max(ins_width, len(t) / 4.5 * self._gate_width) + instructions.append({ + 'width': ins_width, + 'instruction': ins + }) + col_width = max(ins_width, col_width) + self._real_line_width += col_width + columns.append({ + 'width': col_width, + 'column': instructions + }) + max_width += col_width + self._real_line_width += self.figure_styles['moment_width'] + lines.append({ + 'width': max_width, + 'moment': columns + }) + return lines + + def _draw_lines(self): + """Draws qubit lines and labels.""" + for i, qubit in enumerate(self.sorted_qubits): + # Calculate y-position for the qubit line + y_position = (i + 0.5) * 2 * self._gate_height + + # Draw qubit line + self._fig.add_shape( + type="line", + x0=40, + y0=y_position, + x1=self._real_line_width, + y1=y_position, + line={"color": 'black', "width": 1}, + layer="below" + ) + + # Add qubit label + self._fig.add_annotation( + x=30, + y=y_position, + text=f"Q{qubit.index}", + showarrow=False, + font={"size": 14, "color": 'black', "family": 'Arial'}, + xanchor="right", + yanchor="middle" + ) + + def draw_column(self, column: list[dict]): + """ + Renders a column of quantum operations. + + Args: + column: List of quantum operations to render vertically aligned + """ + for item in column: + ins_width = item['width'] + ins = item['instruction'] + + # Handle different gate types + if len(ins.qubits) == 1: + self._draw_single_gate_plotly(ins, ins_width) + else: + self._draw_multi_gate(ins, ins_width) + + def _draw_single_gate_plotly( + self, + instruction_data: InstructionData, + width: float, + gate_style=None + ): + """ + Renders a single-qubit gate. + + Args: + instruction_data: Gate operation and associated qubit + width: Width of the gate in coordinate units + gate_style: Custom style overrides for the gate + """ + # Get qubit position + qubit_idx = self.qubit_line_no(instruction_data.qubits[0]) + y_center = qubit_idx * self._gate_height # Y-center position + + # Get gate style configuration + if gate_style: + gs = gate_style + else: + gs = self._gate_style[instruction_data.instruction.name] + + ins = instruction_data.instruction + + # Special handling for measurement gate + if isinstance(ins, Measure): + self._draw_measure(qubit_idx) + return + + # Calculate gate boundaries + x0 = self._current_x - width / 2 + y0 = y_center - self._gate_height / 2 + x1 = self._current_x + width / 2 + y1 = y_center + self._gate_height / 2 + + # Add gate rectangle + self._fig.add_shape( + type="rect", + x0=x0, y0=y0, + x1=x1, y1=y1, + line={"width": 0}, + fillcolor=gs['background_color'], + ) + + # Generate gate text (include parameters if present) + if ins.params: + text = f"{ins.name}\n{self._str_params(ins)}" + else: + text = ins.name + + # Add text annotation + self._fig.add_annotation( + x=self._current_x, + y=y_center, + text=text, + showarrow=False, + font={"size": gs['font_size'], "color": gs['text_color']}, + align="center", + valign="middle" + ) + + # pylint: disable=too-many-locals + def _draw_measure(self, qubit_idx: int): + """ + Draws a measurement gate with custom symbol. + + Args: + qubit_idx: Index of the qubit being measured + """ + # Get measurement gate style + gs = self._gate_style['M'] + + # Center position + cx = self._current_x + cy = qubit_idx * self._gate_height + + # Calculate boundaries + x0 = cx - self._gate_width / 2 + y0 = cy - self._gate_height / 2 + x1 = cx + self._gate_width / 2 + y1 = cy + self._gate_height / 2 + + # 1. Draw square border + self._fig.add_shape( + type="rect", + x0=x0, + y0=y0, + x1=x1, + y1=y1, + line={"width": 2, "color": gs['text_color']}, + fillcolor=gs['background_color'], + layer='below' + ) + + # 2. Draw downward semicircle + r = self._gate_height * 0.4 # Radius + theta = np.linspace(0, np.pi, 50) # Angle points for semicircle + + # Calculate semicircle path points + x_vals = cx + r * np.cos(theta) + y_vals = cy + self._gate_height / 4 - r * np.sin(theta) # Adjusted for downward opening + + # Add semicircle trace + self._fig.add_trace( + go.Scatter( + x=x_vals, + y=y_vals, + mode='lines', + line={"color": gs['text_color'], "width": 2}, + hoverinfo='none', + showlegend=False + ) + ) + + # 3. Draw northeast-pointing arrow + arrow_start_x = cx - self._gate_width / 2 * 0.3 + arrow_start_y = cy + self._gate_height / 2 * 0.8 + arrow_end_x = cx + self._gate_width / 2 * 0.3 + arrow_end_y = cy - self._gate_height / 2 * 0.8 + + self._fig.add_annotation( + x=arrow_end_x, + y=arrow_end_y, + ax=arrow_start_x, + ay=arrow_start_y, + xref="x", + yref="y", + axref="x", + ayref="y", + showarrow=True, + arrowhead=1, + arrowsize=1, + arrowwidth=2, + arrowcolor=gs['text_color'] + ) + + def _draw_multi_gate(self, instruction_data: InstructionData, width: float): + """ + Draws multi-qubit gates with connection lines. + + Args: + instruction_data: Gate operation and associated qubits + width: Width of the gate column + """ + qs = instruction_data.qubits + instruction = instruction_data.instruction + + # Handle different multi-qubit gate types + if isinstance(instruction, SWAP): + self._draw_swap(qs) + elif isinstance(instruction, CZ): + self._draw_cz(qs) + elif isinstance(instruction, Barrier): + self._draw_barrier(qs) + elif isinstance(instruction, ControlledGate): + self._draw_controlled_gate(instruction_data, width) + + def _draw_swap(self, qs: Sequence[Bit]): + """Draws a SWAP gate between qubits.""" + gs = self._gate_style['SWAP'] + + # Draw X symbols for each qubit + for q in qs: + qubit_idx = self.qubit_line_no(q) + y = qubit_idx * self._gate_height + half_height = self._gate_height / 3 + half_width = self._gate_width / 2 + + # First diagonal line (top-left to bottom-right) + self._fig.add_trace(go.Scatter( + x=[self._current_x - half_width, self._current_x + half_width], + y=[y + half_height, y - half_height], + mode='lines', + line={"color": gs['text_color'], "width": gs['line_width']}, + showlegend=False + )) + + # Second diagonal line (bottom-left to top-right) + self._fig.add_trace(go.Scatter( + x=[self._current_x - half_width, self._current_x + half_width], + y=[y - half_height, y + half_height], + mode='lines', + line={"color": gs['text_color'], "width": gs['line_width']}, + showlegend=False + )) + + # Draw connecting vertical line between qubits + y_start = self.qubit_line_no(qs[0]) * self._gate_height + y_end = self.qubit_line_no(qs[1]) * self._gate_height + + self._fig.add_trace(go.Scatter( + x=[self._current_x, self._current_x], + y=[y_start, y_end], + mode='lines', + line={"color": gs['line_color'], "width": gs['line_width']}, + showlegend=False + )) + + def _draw_cz(self, qs: Sequence[Bit]): + """Draws a CZ (controlled-Z) gate.""" + gs = self._gate_style['CZ'] + current_x = self._current_x + + # Draw connecting line between qubits + y_start = self.qubit_line_no(qs[0]) * self._gate_height + y_end = self.qubit_line_no(qs[1]) * self._gate_height + + self._fig.add_trace(go.Scatter( + x=[current_x, current_x], + y=[y_start, y_end], + mode='lines', + line={"color": gs['line_color'], "width": 1}, + showlegend=False + )) + + # Add square markers for each qubit + for q in qs: + y_pos = self.qubit_line_no(q) * self._gate_height + + self._fig.add_trace(go.Scatter( + x=[current_x], + y=[y_pos], + mode='markers', + marker={"symbol": 'square', + "size": 12, + "color": gs['line_color'], + "line": {"color": 'white', "width": 1}, + }, + + showlegend=False + )) + + def _draw_barrier(self, qs: Sequence[Bit]): + """Draws a barrier across specified qubits.""" + gs = self._gate_style['B'] + current_x = self._current_x + + # Add vertical dashed line for each qubit + for q in qs: + y_center = self.qubit_line_no(q) * self._gate_height + y_min = y_center - self._gate_height / 1.5 + y_max = y_center + self._gate_height / 1.5 + + self._fig.add_trace(go.Scatter( + x=[current_x, current_x], + y=[y_min, y_max], + mode='lines', + line={"color": gs['line_color'], "width": gs['line_width'], "dash": 'dash'}, + showlegend=False + )) + + def _draw_controlled_gate(self, instruction_data: InstructionData, width: float): + """ + Draws controlled gates with control points and target gate. + + Args: + instruction_data: Must contain a ControlledGate instance + """ + gs = self._gate_style[instruction_data.instruction.name] + + # Validate input + if not isinstance(instruction_data.instruction, ControlledGate): + raise CqlibError(f"Invalid instruction type for controlled gate. {instruction_data}") + + qubit_nos = [] + current_x = self._current_x + + # Get y-positions of all involved qubits + for i, qubit in enumerate(instruction_data.qubits): + qubit_nos.append(self.qubit_line_no(qubit) * self._gate_height) + + # Draw vertical connection line spanning all qubits + self._fig.add_trace(go.Scatter( + x=[current_x, current_x], + y=[min(qubit_nos), max(qubit_nos)], + mode='lines', + line={"color": gs['line_color'], "width": 1}, + showlegend=False + )) + + # Draw control points and target gate + for i, qubit in enumerate(instruction_data.qubits): + y_pos = self.qubit_line_no(qubit) * self._gate_height + + if i in instruction_data.instruction.control_index: + # Control point - draw filled square + self._fig.add_trace(go.Scatter( + x=[current_x], + y=[y_pos], + mode='markers', + marker={"symbol": 'square', + "size": 12, + "color": gs['line_color'], + "line": {"color": 'white', "width": 1}}, + showlegend=False + )) + else: + # Target gate - draw the actual gate + self._draw_single_gate_plotly( + InstructionData(instruction_data.instruction.base_gate, [qubit]), + gate_style=gs, + width=width + ) + + +# pylint: disable=too-many-arguments,too-many-positional-arguments +def draw_plotly( + circuit: Circuit, + qubit_order: list[int | Qubit] | None = None, + figure_styles: dict[str, str | bool | float | tuple[float | int]] | None = None, + gate_styles: dict[str, str | bool | float | tuple[float | int]] | None = None, + title: str | None = None, + filename: str | None = None, +) -> go.Figure: + """ + Convenience function for generating Plotly circuit visualizations. + + Args: + circuit: Quantum circuit to visualize + qubit_order: Custom display order for qubits + figure_styles: Overrides for default figure styles + gate_styles: Overrides for default gate rendering styles + title: Title displayed above the circuit diagram + filename: Filename for saving HTML output + + Returns: + Plotly Figure object containing the interactive circuit diagram + """ + return PlotlyDrawer( + circuit, + qubit_order=qubit_order, + figure_styles=figure_styles, + gate_styles=gate_styles, + title=title, + filename=filename + ).drawer() diff --git a/cqlib/visualization/circuit/styles/default.json b/cqlib/visualization/circuit/styles/default.json index 056341f3bcea7065e98eaab016f8038931aea557..37fdb4ae18da630da7be9896f08fb14f6f3b28b1 100644 --- a/cqlib/visualization/circuit/styles/default.json +++ b/cqlib/visualization/circuit/styles/default.json @@ -148,7 +148,7 @@ "border_color": "none", "background_color": "red", "font_size": 12, - "text_color": "red" + "text_color": "black" }, "Y": { "border_color": "none", diff --git a/docs/guides/06-others.ipynb b/docs/guides/06-others.ipynb index 4f69825c91b17c4e0d1ae2bcf2671a587d46b785..c416a46d1dd5f132838748c215507434a4ad8f02 100644 --- a/docs/guides/06-others.ipynb +++ b/docs/guides/06-others.ipynb @@ -34,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 5, "id": "b08e52be", "metadata": {}, "outputs": [], @@ -92,6 +92,14 @@ "print(draw_text(c))" ] }, + { + "cell_type": "markdown", + "id": "5482818e", + "metadata": {}, + "source": [ + "使用Matplotlib将线路可视化" + ] + }, { "cell_type": "code", "execution_count": 3, @@ -116,6 +124,1411 @@ "img.show()" ] }, + { + "cell_type": "markdown", + "id": "ae0fb6bf", + "metadata": {}, + "source": [ + "使用plotly将线路可视化" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "76737ac4", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "line": { + "color": "#002D9C", + "width": 1 + }, + "mode": "lines", + "showlegend": false, + "type": "scatter", + "x": [ + 190, + 190 + ], + "y": [ + 40, + 120 + ] + }, + { + "marker": { + "color": "#002D9C", + "line": { + "color": "white", + "width": 1 + }, + "size": 12, + "symbol": "square" + }, + "mode": "markers", + "showlegend": false, + "type": "scatter", + "x": [ + 190 + ], + "y": [ + 40 + ] + }, + { + "line": { + "color": "#002D9C", + "width": 1 + }, + "mode": "lines", + "showlegend": false, + "type": "scatter", + "x": [ + 280, + 280 + ], + "y": [ + 40, + 200 + ] + }, + { + "marker": { + "color": "#002D9C", + "line": { + "color": "white", + "width": 1 + }, + "size": 12, + "symbol": "square" + }, + "mode": "markers", + "showlegend": false, + "type": "scatter", + "x": [ + 280 + ], + "y": [ + 40 + ] + }, + { + "line": { + "color": "#33B1FF", + "width": 1 + }, + "mode": "lines", + "showlegend": false, + "type": "scatter", + "x": [ + 413.33333333333337, + 413.33333333333337 + ], + "y": [ + 120, + 200 + ] + }, + { + "marker": { + "color": "#33B1FF", + "line": { + "color": "white", + "width": 1 + }, + "size": 12, + "symbol": "square" + }, + "mode": "markers", + "showlegend": false, + "type": "scatter", + "x": [ + 413.33333333333337 + ], + "y": [ + 120 + ] + }, + { + "line": { + "color": "blue", + "dash": "dash", + "width": 5 + }, + "mode": "lines", + "showlegend": false, + "type": "scatter", + "x": [ + 546.6666666666667, + 546.6666666666667 + ], + "y": [ + 13.333333333333332, + 66.66666666666667 + ] + }, + { + "line": { + "color": "blue", + "dash": "dash", + "width": 5 + }, + "mode": "lines", + "showlegend": false, + "type": "scatter", + "x": [ + 546.6666666666667, + 546.6666666666667 + ], + "y": [ + 93.33333333333333, + 146.66666666666666 + ] + }, + { + "line": { + "color": "blue", + "dash": "dash", + "width": 5 + }, + "mode": "lines", + "showlegend": false, + "type": "scatter", + "x": [ + 546.6666666666667, + 546.6666666666667 + ], + "y": [ + 173.33333333333334, + 226.66666666666666 + ] + }, + { + "hoverinfo": "none", + "line": { + "color": "gray", + "width": 2 + }, + "mode": "lines", + "showlegend": false, + "type": "scatter", + "x": { + "bdata": "VlVVVVVlhECU3AkCEmWEQLpE+k5IZIRALoRUEPlihEAKgcKmJWGEQEUU9/3PXoRAhsqoivpbhEDIgvxHqFiEQIeiYrTcVIRAACzqzZtQhEBJlA0O6kuEQErE/WTMRoRAOjhwNEhBhECTtPVJYzuEQA2F39gjNYRAr7G5c5AuhEBhBmEFsCeEQA0zvMmJIIRA5asfRiUZhEAyS2RBihGEQEUHubvACYRAnFc45tABhEDaI0oaw/mDQDRN29Cf8YNAow90mW/pg0AJmzYRO+GDQHhdz9kK2YNA0oZgkOfQg0AQU3LE2ciDQGij8e7pwINAel9GaSC5g0DH/opkhbGDQJ937uAgqoNAS6RJpfqig0D9+PA2GpyDQJ8ly9GGlYNAGfa0YEePg0Bycjp2YomDQGLmrEXeg4NAYxadnMB+g0CsfsDcDnqDQCUISPbNdYNA5CeuYgJyg0Am4AEgsG6DQGeWs6zaa4NAoinoA4Vpg0B+JlaasWeDQPJlsFtiZoNAGM6gqJhlg0BWVVVVVWWDQA==", + "dtype": "f8" + }, + "y": { + "bdata": "AAAAAABAakAUEikwMh9qQMpReeKG/mlAQcLKdCDeaUChQYP8IL5pQILGuSKqnmlA6YzNAN1/aUCicpT92WFpQMsjRavARGlAvvJApq8oaUBGS+B0xA1pQAWhY2gb9GhANIUpf8/baEDtOUlI+sRoQBuir8izr2hAx9baYRKcaEBA9k66KopoQIDx3KcPemhA7THSG9JraEBj7yURgV9oQEf2tnwpVWhAaXGqP9ZMaEBUDvobkEZoQP6FPatdQmhAAzu5V0NAaEADO7lXQ0BoQP6FPatdQmhAVA76G5BGaEBpcao/1kxoQEf2tnwpVWhAY+8lEYFfaEDtMdIb0mtoQIDx3KcPemhAQPZOuiqKaEDH1tphEpxoQBuir8izr2hA7TlJSPrEaEA0hSl/z9toQAShY2gb9GhARkvgdMQNaUC98kCmryhpQMsjRavARGlAonKU/dlhaUDpjM0A3X9pQILGuSKqnmlAoEGD/CC+aUBAwsp0IN5pQMlReeKG/mlAFBIpMDIfakAAAAAAAEBqQA==", + "dtype": "f8" + } + }, + { + "hoverinfo": "none", + "line": { + "color": "gray", + "width": 2 + }, + "mode": "lines", + "showlegend": false, + "type": "scatter", + "x": { + "bdata": "VlVVVVVlhECU3AkCEmWEQLpE+k5IZIRALoRUEPlihEAKgcKmJWGEQEUU9/3PXoRAhsqoivpbhEDIgvxHqFiEQIeiYrTcVIRAACzqzZtQhEBJlA0O6kuEQErE/WTMRoRAOjhwNEhBhECTtPVJYzuEQA2F39gjNYRAr7G5c5AuhEBhBmEFsCeEQA0zvMmJIIRA5asfRiUZhEAyS2RBihGEQEUHubvACYRAnFc45tABhEDaI0oaw/mDQDRN29Cf8YNAow90mW/pg0AJmzYRO+GDQHhdz9kK2YNA0oZgkOfQg0AQU3LE2ciDQGij8e7pwINAel9GaSC5g0DH/opkhbGDQJ937uAgqoNAS6RJpfqig0D9+PA2GpyDQJ8ly9GGlYNAGfa0YEePg0Bycjp2YomDQGLmrEXeg4NAYxadnMB+g0CsfsDcDnqDQCUISPbNdYNA5CeuYgJyg0Am4AEgsG6DQGeWs6zaa4NAoinoA4Vpg0B+JlaasWeDQPJlsFtiZoNAGM6gqJhlg0BWVVVVVWWDQA==", + "dtype": "f8" + }, + "y": { + "bdata": "AAAAAABAYEAUEikwMh9gQJSj8sQN/V9AgYSV6UC8X0BBgwb5QXxfQASNc0VUPV9A0hmbAbr/XkBE5Sj7s8NeQJZHilaBiV5Ae+WBTF9RXkCMlsDpiBteQApCx9A26F1AaApT/p63XUDac5KQ9IldQDdEX5FnX11Ajq21wyQ4XUCB7J10VRRdQADjuU8f9FxA2WOkN6TXXEDG3ksiAr9cQI7sbflSqlxA0+JUf6yZXECnHPQ3II1cQPwLe1a7hFxAB3Zyr4aAXEAHdnKvhoBcQPwLe1a7hFxApxz0NyCNXEDT4lR/rJlcQI3sbflSqlxAxt5LIgK/XEDZY6Q3pNdcQADjuU8f9FxAgeyddFUUXUCOrbXDJDhdQDZEX5FnX11A2nOSkPSJXUBoClP+nrddQAlCx9A26F1Ai5bA6YgbXkB75YFMX1FeQJZHilaBiV5AROUo+7PDXkDSGZsBuv9eQASNc0VUPV9AQYMG+UF8X0CBhJXpQLxfQJOj8sQN/V9AFBIpMDIfYEAAAAAAAEBgQA==", + "dtype": "f8" + } + }, + { + "hoverinfo": "none", + "line": { + "color": "gray", + "width": 2 + }, + "mode": "lines", + "showlegend": false, + "type": "scatter", + "x": { + "bdata": "VlVVVVVlhECU3AkCEmWEQLpE+k5IZIRALoRUEPlihEAKgcKmJWGEQEUU9/3PXoRAhsqoivpbhEDIgvxHqFiEQIeiYrTcVIRAACzqzZtQhEBJlA0O6kuEQErE/WTMRoRAOjhwNEhBhECTtPVJYzuEQA2F39gjNYRAr7G5c5AuhEBhBmEFsCeEQA0zvMmJIIRA5asfRiUZhEAyS2RBihGEQEUHubvACYRAnFc45tABhEDaI0oaw/mDQDRN29Cf8YNAow90mW/pg0AJmzYRO+GDQHhdz9kK2YNA0oZgkOfQg0AQU3LE2ciDQGij8e7pwINAel9GaSC5g0DH/opkhbGDQJ937uAgqoNAS6RJpfqig0D9+PA2GpyDQJ8ly9GGlYNAGfa0YEePg0Bycjp2YomDQGLmrEXeg4NAYxadnMB+g0CsfsDcDnqDQCUISPbNdYNA5CeuYgJyg0Am4AEgsG6DQGeWs6zaa4NAoinoA4Vpg0B+JlaasWeDQPJlsFtiZoNAGM6gqJhlg0BWVVVVVWWDQA==", + "dtype": "f8" + }, + "y": { + "bdata": "AAAAAAAASUBSSKTAyHxIQCdH5Ykb+kdAAgkr04F4R0CCBg3yg/hGQAga54qoekZApDM2A3T/RUCIylH2Z4dFQCyPFK0CE0VA9soDmb6iREAXLYHTETdEQBOEjqFt0ENAzxSm/D1vQ0C05yQh6RNDQG2IviLPvkJAHVtrh0lwQkAC2TvpqihCQADGc58+6EFAs8dIb0ivQUCNvZdEBH5BQBvZ2/KlVEFApsWp/lgzQUBOOehvQBpBQPgX9qx2CUFADuzkXg0BQUAO7OReDQFBQPgX9qx2CUFATjnob0AaQUCmxan+WDNBQBvZ2/KlVEFAjb2XRAR+QUCyx0hvSK9BQADGc58+6EFAAdk76aooQkAdW2uHSXBCQG2IviLPvkJAtOckIekTQ0DPFKb8PW9DQBKEjqFt0ENAFy2B0xE3RED2ygOZvqJEQCuPFK0CE0VAiMpR9meHRUCkMzYDdP9FQAga54qoekZAgQYN8oP4RkACCSvTgXhHQCZH5Ykb+kdAUUikwMh8SEAAAAAAAABJQA==", + "dtype": "f8" + } + } + ], + "layout": { + "annotations": [ + { + "font": { + "color": "black", + "family": "Arial", + "size": 14 + }, + "showarrow": false, + "text": "Q0", + "x": 30, + "xanchor": "right", + "y": 40, + "yanchor": "middle" + }, + { + "font": { + "color": "black", + "family": "Arial", + "size": 14 + }, + "showarrow": false, + "text": "Q1", + "x": 30, + "xanchor": "right", + "y": 120, + "yanchor": "middle" + }, + { + "font": { + "color": "black", + "family": "Arial", + "size": 14 + }, + "showarrow": false, + "text": "Q2", + "x": 30, + "xanchor": "right", + "y": 200, + "yanchor": "middle" + }, + { + "align": "center", + "font": { + "color": "black", + "size": 12 + }, + "showarrow": false, + "text": "H", + "valign": "middle", + "x": 100, + "y": 40 + }, + { + "align": "center", + "font": { + "color": "white", + "size": 12 + }, + "showarrow": false, + "text": "RX\n0.2", + "valign": "middle", + "x": 100, + "y": 120 + }, + { + "align": "center", + "font": { + "color": "white", + "size": 12 + }, + "showarrow": false, + "text": "RX\nπ/2", + "valign": "middle", + "x": 100, + "y": 200 + }, + { + "align": "center", + "font": { + "color": "white", + "size": 12 + }, + "showarrow": false, + "text": "X", + "valign": "middle", + "x": 190, + "y": 120 + }, + { + "align": "center", + "font": { + "color": "white", + "size": 12 + }, + "showarrow": false, + "text": "X", + "valign": "middle", + "x": 280, + "y": 200 + }, + { + "align": "center", + "font": { + "color": "white", + "size": 12 + }, + "showarrow": false, + "text": "RZ\nphi + theta", + "valign": "middle", + "x": 413.33333333333337, + "y": 200 + }, + { + "align": "center", + "font": { + "color": "white", + "size": 12 + }, + "showarrow": false, + "text": "RZ\ntheta", + "valign": "middle", + "x": 413.33333333333337, + "y": 40 + }, + { + "arrowcolor": "gray", + "arrowhead": 1, + "arrowsize": 1, + "arrowwidth": 2, + "ax": 627.6666666666667, + "axref": "x", + "ay": 216, + "ayref": "y", + "showarrow": true, + "x": 645.6666666666667, + "xref": "x", + "y": 184, + "yref": "y" + }, + { + "arrowcolor": "gray", + "arrowhead": 1, + "arrowsize": 1, + "arrowwidth": 2, + "ax": 627.6666666666667, + "axref": "x", + "ay": 136, + "ayref": "y", + "showarrow": true, + "x": 645.6666666666667, + "xref": "x", + "y": 104, + "yref": "y" + }, + { + "arrowcolor": "gray", + "arrowhead": 1, + "arrowsize": 1, + "arrowwidth": 2, + "ax": 627.6666666666667, + "axref": "x", + "ay": 56, + "ayref": "y", + "showarrow": true, + "x": 645.6666666666667, + "xref": "x", + "y": 24, + "yref": "y" + } + ], + "height": 240, + "margin": { + "b": 0, + "l": 0, + "r": 0, + "t": 30 + }, + "modebar": { + "activecolor": "red", + "add": [ + "zoom2d", + "pan2d", + "select2d", + "lasso2d", + "zoomIn2d", + "zoomOut2d", + "autoScale2d", + "resetScale2d", + "toImage" + ], + "bgcolor": "rgba(255,255,255,0.7)", + "color": "black", + "orientation": "h" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "white", + "shapes": [ + { + "layer": "below", + "line": { + "color": "black", + "width": 1 + }, + "type": "line", + "x0": 40, + "x1": 686.6666666666667, + "y0": 40, + "y1": 40 + }, + { + "layer": "below", + "line": { + "color": "black", + "width": 1 + }, + "type": "line", + "x0": 40, + "x1": 686.6666666666667, + "y0": 120, + "y1": 120 + }, + { + "layer": "below", + "line": { + "color": "black", + "width": 1 + }, + "type": "line", + "x0": 40, + "x1": 686.6666666666667, + "y0": 200, + "y1": 200 + }, + { + "fillcolor": "red", + "line": { + "width": 0 + }, + "type": "rect", + "x0": 70, + "x1": 130, + "y0": 20, + "y1": 60 + }, + { + "fillcolor": "#9F1853", + "line": { + "width": 0 + }, + "type": "rect", + "x0": 70, + "x1": 130, + "y0": 100, + "y1": 140 + }, + { + "fillcolor": "#9F1853", + "line": { + "width": 0 + }, + "type": "rect", + "x0": 70, + "x1": 130, + "y0": 180, + "y1": 220 + }, + { + "fillcolor": "#002D9C", + "line": { + "width": 0 + }, + "type": "rect", + "x0": 160, + "x1": 220, + "y0": 100, + "y1": 140 + }, + { + "fillcolor": "#002D9C", + "line": { + "width": 0 + }, + "type": "rect", + "x0": 250, + "x1": 310, + "y0": 180, + "y1": 220 + }, + { + "fillcolor": "#33B1FF", + "line": { + "width": 0 + }, + "type": "rect", + "x0": 340, + "x1": 486.66666666666674, + "y0": 180, + "y1": 220 + }, + { + "fillcolor": "#33B1FF", + "line": { + "width": 0 + }, + "type": "rect", + "x0": 380.00000000000006, + "x1": 446.6666666666667, + "y0": 20, + "y1": 60 + }, + { + "fillcolor": "white", + "layer": "below", + "line": { + "color": "gray", + "width": 2 + }, + "type": "rect", + "x0": 606.6666666666667, + "x1": 666.6666666666667, + "y0": 180, + "y1": 220 + }, + { + "fillcolor": "white", + "layer": "below", + "line": { + "color": "gray", + "width": 2 + }, + "type": "rect", + "x0": 606.6666666666667, + "x1": 666.6666666666667, + "y0": 100, + "y1": 140 + }, + { + "fillcolor": "white", + "layer": "below", + "line": { + "color": "gray", + "width": 2 + }, + "type": "rect", + "x0": 606.6666666666667, + "x1": 666.6666666666667, + "y0": 20, + "y1": 60 + } + ], + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermap": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermap" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "x": 0.5 + }, + "width": 686.6666666666667, + "xaxis": { + "range": [ + 0, + 686.6666666666667 + ], + "showgrid": true, + "visible": false, + "zeroline": false + }, + "yaxis": { + "range": [ + 240, + 0 + ], + "scaleanchor": "x", + "scaleratio": 1, + "showgrid": true, + "visible": false, + "zeroline": false + } + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from cqlib.visualization import draw_plotly\n", + "img = draw_plotly(c)\n", + "img.show()" + ] + }, { "cell_type": "markdown", "id": "b582ee08", @@ -421,7 +1834,7 @@ ], "metadata": { "kernelspec": { - "display_name": "cqlib3.10", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -435,7 +1848,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.16" + "version": "3.10.10" } }, "nbformat": 4, diff --git a/requirements.txt b/requirements.txt index 07f6331e3f9b83acd366d8a5dca11fec1647ab58..d91074200bf54d001050d2b183b019d9fe018247 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ sympy>=1.12.0 tabulate>=0.9.0 openqasm3[parser]>=1.0.0 antlr4-python3-runtime>=4.13.2 -rustworkx>=0.16.0 \ No newline at end of file +rustworkx>=0.16.0 +plotly>=5.0.0 \ No newline at end of file diff --git a/tests/visualization/circuit/test_plotly.py b/tests/visualization/circuit/test_plotly.py new file mode 100644 index 0000000000000000000000000000000000000000..42b19be9f8f0039e28a94185381b256e3687f9e6 --- /dev/null +++ b/tests/visualization/circuit/test_plotly.py @@ -0,0 +1,123 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + +""" Test plotly visualization.""" +# pylint: disable=missing-function-docstring +from math import pi + +from cqlib import Circuit, Parameter +from cqlib.visualization.circuit import draw_plotly + + +class TestCircuitPltVis: + """ Test plotly visualization.""" + + def test_parameter(self): + theta = Parameter('theta') + circuit = Circuit(2, parameters=[theta]) + circuit.h(0) + circuit.rx(1, theta) + circuit.cry(0, 1, theta) + circuit.measure_all() + fig = draw_plotly(circuit) + fig.show() + assert len(fig.data) > 0, "No data traces in figure" + assert len(fig.layout.shapes) > 0, "No shapes in layout" + + def test_rotation_gates(self): + circuit = Circuit(3) + circuit.h(0) + circuit.rx(1, 0.1) + circuit.ry(2, 0.2) + circuit.rz(0, 0.3) + circuit.measure_all() + fig = draw_plotly(circuit) + fig.show() + assert len(fig.data) > 0, "No data traces in figure" + assert len(fig.layout.shapes) > 0, "No shapes in layout" + + def test_moment(self): + circuit = Circuit(6) + circuit.h(1) + circuit.cx(0, 2) + circuit.ccx(3, 4, 5) + circuit.measure_all() + fig = draw_plotly(circuit) + fig.show() + assert len(fig.data) > 0, "No data traces in figure" + assert len(fig.layout.shapes) > 0, "No shapes in layout" + + def test_long_theta(self): + circuit = Circuit(3) + circuit.h(0) + circuit.rx(1, pi) + circuit.rx(1, pi * 1 / 3) + circuit.rx(0, 1 / 3) + circuit.rx(1, pi * 13 / 3) + circuit.rx(2, 11 / 3 * pi * pi) + circuit.measure_all() + fig = draw_plotly(circuit) + fig.show() + assert len(fig.data) > 0, "No data traces in figure" + assert len(fig.layout.shapes) > 0, "No shapes in layout" + + def test_phy_qubits(self): + circuit = Circuit([1, 7, 13]) + circuit.h(1) + circuit.cx(7, 13) + circuit.measure_all() + fig = draw_plotly(circuit) + fig.show() + assert len(fig.data) > 0, "No data traces in figure" + assert len(fig.layout.shapes) > 0, "No shapes in layout" + + def test_gates_0(self): + theta = Parameter('theta') + circuit = Circuit(5, parameters=[theta]) + circuit.h(0) + circuit.i(1, 100) + circuit.rx(2, 0.123) + circuit.crx(3, 4, theta) + circuit.rxy(0, 1, 0.123) + circuit.ry(1, 0.123) + circuit.cry(2, 3, 0.123) + circuit.rz(4, theta) + circuit.crz(1, 2, 1.2365478563155) + circuit.s(1) + circuit.sd(1) + circuit.swap(2, 3) + circuit.t(1) + circuit.td(2) + circuit.u(3, 0.1, 1.0, 1 / 3) + circuit.x(1) + circuit.swap(0, 4) + circuit.cx(1, 3) + circuit.ccx(2, 3, 4) + circuit.x2p(0) + circuit.x2m(4) + circuit.xy(3, 1.0) + circuit.xy2p(3, 2.3) + circuit.xy2m(3, 1.36521) + circuit.y(0) + circuit.y2m(3) + circuit.y2p(4) + circuit.cy(1, 4) + circuit.z(0) + circuit.cz(0, 3) + circuit.barrier_all() + circuit.measure_all() + fig = draw_plotly(circuit) + fig.show() + assert len(fig.data) > 0, "No data traces in figure" + assert len(fig.layout.shapes) > 0, "No shapes in layout"