From 981194dffa9d3043012050d5df233f4bfafd2457 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Mon, 17 Jun 2024 13:40:37 +0530 Subject: [PATCH 01/20] adding matplotlib rendering module --- .gitignore | 5 +- src/qutip_qip/circuit/__init__.py | 1 + src/qutip_qip/circuit/circuit.py | 17 +- src/qutip_qip/circuit/color_theme.py | 30 + src/qutip_qip/circuit/mat_renderer.py | 863 ++++++++++++++++++++++++++ src/qutip_qip/operations/gateclass.py | 35 +- 6 files changed, 938 insertions(+), 13 deletions(-) create mode 100644 src/qutip_qip/circuit/color_theme.py create mode 100644 src/qutip_qip/circuit/mat_renderer.py diff --git a/.gitignore b/.gitignore index d775c834..cd29a125 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,9 @@ instance/ # Scrapy stuff: .scrapy +# vscode +.vscode + # Sphinx documentation doc/_build/ @@ -128,4 +131,4 @@ venv.bak/ dmypy.json # Pyre type checker -.pyre/ +.pyre/ \ No newline at end of file diff --git a/src/qutip_qip/circuit/__init__.py b/src/qutip_qip/circuit/__init__.py index d723ffa4..e44b1869 100644 --- a/src/qutip_qip/circuit/__init__.py +++ b/src/qutip_qip/circuit/__init__.py @@ -4,6 +4,7 @@ from .circuit import * from .circuitsimulator import * +from .mat_renderer import * from ..operations import Gate, Measurement from .texrenderer import * diff --git a/src/qutip_qip/circuit/circuit.py b/src/qutip_qip/circuit/circuit.py index 606ab861..94cc25df 100644 --- a/src/qutip_qip/circuit/circuit.py +++ b/src/qutip_qip/circuit/circuit.py @@ -200,6 +200,7 @@ def add_gate( classical_controls=None, control_value=None, classical_control_value=None, + style=None, ): """ Adds a gate with specified parameters to the circuit. @@ -245,6 +246,7 @@ def add_gate( classical_controls=classical_controls, control_value=control_value, classical_control_value=classical_control_value, + style=style, ) if index is None: @@ -863,7 +865,7 @@ def propagators(self, expand=True, ignore_measurement=False): ignore_measurement: bool, optional Whether :class:`.Measurement` operators should be ignored. If set False, it will raise an error - when the circuit has measurement. + when the circuit hasf measurement. Returns ------- @@ -885,16 +887,9 @@ def propagators(self, expand=True, ignore_measurement=False): ) for gate in gates: if gate.name == "GLOBALPHASE": - qobj = gate.get_qobj(self.N) - else: - qobj = self._get_gate_unitary(gate) - if expand: - all_targets = gate.get_all_qubits() - qobj = expand_operator( - qobj, dims=self.dims, targets=all_targets - ) - U_list.append(qobj) - return U_list + qobj = gate.get_ (self.N) + U_list.append(qobj) + continue def _get_gate_unitary(self, gate): if gate.name in self.user_gates: diff --git a/src/qutip_qip/circuit/color_theme.py b/src/qutip_qip/circuit/color_theme.py new file mode 100644 index 00000000..50fb04c6 --- /dev/null +++ b/src/qutip_qip/circuit/color_theme.py @@ -0,0 +1,30 @@ +''' +Color theme for different gates in the circuit diagram. +''' + +default_theme = { + "H": "#6270CE", + "SNOT": "#6270CE", + "X": "#CB4BF9", + "Y": "#CB4BF9", + "Z": "#CB4BF9", + "S": "#254065", + "T": "#254065", + "RX": "#5EBDF8", + "RY": "#5EBDF8", + "RZ": "#5EBDF8", + "CNOT": "#3B3470", + "CPHASE": '#456DB2', # change + "TOFFOLI": "#3B3470", # change + "SWAP": "#3B3470", + "CX": "#9598f5", + "CY": "#9598f5", + "CZ": "#9598f5", + "CS": "#9598f5", + "CT": "#9598f5", + "CRX": "#A66DDF", + "CRY": "#A66DDF", + "CRZ": "#A66DDF", + "BERKELEY": "#7648CB", + "FREDKIN": "#7648CB", +} diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py new file mode 100644 index 00000000..863897e4 --- /dev/null +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -0,0 +1,863 @@ +""" +Module for rendering a quantum circuit using matplotlib library. +""" + +from typing import List +import matplotlib.pyplot as plt +from matplotlib.patches import ( + FancyBboxPatch, + Circle, + Arc, + FancyArrow, +) + +from ..operations import Gate, Measurement +from ..circuit import QubitCircuit + +__all__ = [ + "MatRenderer", +] + + +class MatRenderer: + """ + Class to render a quantum circuit using matplotlib. + + Parameters + ---------- + qc : QuantumCircuit Object + The quantum circuit to be rendered. + + padding : float, optional + The padding around the circuit. The default is 0.3. + + dpi : int, optional + The resolution of the image. The default is 150. + + bgcolor : str, optional + The background color of the plotted circuit. The default is "#FFFFFF" (white). + + wire_labels : list, optional + The labels for the wires. + """ + + def __init__( + self, + qc: QubitCircuit, + padding=0.3, + dpi: int = 150, + bgcolor: str = "#FFFFFF", + wire_label: List[str] = None, + condense=0.15, + ) -> None: + + self.wire_sep = 0.5 + self.layer_sep = 0.5 + self.cwire_sep = 0.02 + self.gate_height = 0.2 + self.gate_width = 0.2 + self.gate_pad = 0.05 + self.label_pad = 0.2 + self.font_size = 10 + self.default_layers = 2 + self.arrow_lenght = 0.06 + self.connector_r = 0.01 + self.target_node_r = 0.12 + self.display_layer_len = 0 + + self.qc = qc + self.qwires = qc.N + self.cwires = qc.num_cbits + self.dpi = dpi + self.gate_margin = condense + self.padding = padding + self.bgcolor = bgcolor + self.wire_label = wire_label + self.layer_list = {i: [] for i in range(self.qwires)} + + # fig config + self.fig_height = ( + (self.qwires + self.cwires) * self.wire_sep * 0.393701 * 3 + ) + self.fig_width = 10 + self.fig, self.ax = plt.subplots( + figsize=(self.fig_width, self.fig_height), + dpi=self.dpi, + facecolor=self.bgcolor, + ) + + self.canvas_plot() + + def _get_xskip(self, wire_list, layer): + """ + Get the xskip (horizontal value for getting to requested layer) for the gate to be plotted. + + Parameters + ---------- + wire_list : list + The list of wires the gate is acting on (control and target). + + layer : int + The layer the gate is acting on. + """ + + xskip = [] + for wire in wire_list: + xskip.append(sum(self.layer_list[wire][:layer])) + + return max(xskip) + + def _get_text_width( + self, text, fontsize, fontweight, fontfamily, fontstyle + ): + """ + Get the width of the text to be plotted. + + Parameters + ---------- + text : str + The text to be plotted. + + fontsize : int + The fontsize of the text. + + fontweight : str + The fontweight of the text. + + fontfamily : str + The fontfamily of the text. + + fontstyle : str + The fontstyle of the text. + Returns + ------- + float + The width of the text in cm. + """ + + text_obj = plt.Text( + 0, + 0, + text, + fontsize=fontsize, + fontweight=fontweight, + fontfamily=fontfamily, + fontstyle=fontstyle, + ) + self.ax.add_artist(text_obj) + + bbox = text_obj.get_window_extent( + renderer=self.ax.figure.canvas.get_renderer() + ) + inv = self.ax.transData.inverted() + bbox_data = bbox.transformed(inv) + text_obj.remove() + + return bbox_data.width * 2.54 * 3 + + def _manage_layers(self, gate_width, wire_list, layer, xskip=0): + """ + Manages and updates the layer widths according to the gate's width just plotted. + + Parameters + ---------- + gate_width : float + The width of the gate to be plotted. + + wire_list : list + The list of wires the gate is acting on (control and target). + + layer : int + The layer the gate is acting on. + + xskip : float, optional + The horizontal value for getting to requested layer. The default is 0. + """ + + for wire in wire_list: + if len(self.layer_list[wire]) > layer: + if ( + self.layer_list[wire][layer] + < gate_width + self.gate_margin * 2 + ): + self.layer_list[wire][layer] = ( + gate_width + self.gate_margin * 2 + ) + else: + temp = xskip - sum(self.layer_list[wire]) if xskip != 0 else 0 + self.layer_list[wire].append( + temp + gate_width + self.gate_margin * 2 + ) + + def _extend_wires(self, ext, end=False): + """ + Extends the wires in the circuit. + + Parameters + ---------- + ext : int + The number of layers to extend the wires. + + end : bool, optional + If True, the wires are extended further distance for end of the circuit. + The default is False. + """ + + max_layer = max([sum(self.layer_list[i]) for i in range(self.qwires)]) + + if ( + self.display_layer_len < max_layer + or self.display_layer_len == 0 + or end is True + ): + if self.cwires != 0: + + ext_cwires_pos = [ + [ + [ + ( + self.display_layer_len, + max_layer + ext * self.layer_sep, + ), + ( + (i * self.wire_sep) + self.cwire_sep, + (i * self.wire_sep) + self.cwire_sep, + ), + ], + [ + ( + self.display_layer_len, + max_layer + ext * self.layer_sep, + ), + ( + i * self.wire_sep - self.cwire_sep, + i * self.wire_sep - self.cwire_sep, + ), + ], + ] + for i in range(self.cwires) + ] + + for pos in ext_cwires_pos: + wire_up = plt.Line2D( + pos[0][0], pos[0][1], lw=1, color="k", zorder=1 + ) + wire_down = plt.Line2D( + pos[1][0], pos[1][1], lw=1, color="k", zorder=1 + ) + self.ax.add_line(wire_up) + self.ax.add_line(wire_down) + + ext_qwires_pos = [ + [ + ( + self.display_layer_len, + max_layer + ext * self.layer_sep, + ), + (i * self.wire_sep, i * self.wire_sep), + ] + for i in range(self.cwires, self.cwires + self.qwires) + ] + + for pos in ext_qwires_pos: + wire = plt.Line2D(pos[0], pos[1], lw=1, color="k", zorder=1) + self.ax.add_line(wire) + + self.display_layer_len = max_layer + + def _add_wire_labels(self): + """ + Adds the wire labels to the circuit. + """ + + if self.wire_label is None: + default_labels = [f"$c_{{{i}}}$" for i in range(self.cwires)] + [ + f"$q_{{{i}}}$" for i in range(self.qwires) + ] + self.wire_label = default_labels + + for i, label in enumerate(self.wire_label): + wire_label = plt.Text( + -self.label_pad, + i * self.wire_sep, + label, + fontsize=self.font_size, + verticalalignment="center", + horizontalalignment="left", + zorder=3, + ) + self.ax.add_artist(wire_label) + + def _draw_control_node(self, pos, xskip, color): + """ + Draw the control node for the multi-qubit gate. + + Parameters + ---------- + pos : int + The position of the control node. + + xskip : float + The horizontal value for getting to requested layer. + + color : str + The color of the control node. + """ + + pos = pos + self.cwires + + control_node_radius = 0.05 + control_node = Circle( + (xskip + self.gate_margin + self.gate_pad, pos * self.wire_sep), + control_node_radius, + color=color, + zorder=2, + ) + self.ax.add_artist(control_node) + + def _draw_target_node(self, pos, xskip, node_color): + """ + Draw the target node for the multi-qubit gate. + + Parameters + ---------- + pos : int + The position of the target node. + + xskip : float + The horizontal value for getting to requested layer. + + node_color : str + The color of the target node. + """ + + pos = pos + self.cwires + + target_node = Circle( + (xskip + self.gate_margin + self.gate_pad, pos * self.wire_sep), + self.target_node_r, + color=node_color, + zorder=2, + ) + vertical_line = plt.Line2D( + ( + xskip + self.gate_margin + self.gate_pad, + xskip + self.gate_margin + self.gate_pad, + ), + ( + pos * self.wire_sep - self.target_node_r / 2, + pos * self.wire_sep + self.target_node_r / 2, + ), + lw=1.5, + color="white", + zorder=3, + ) + horizontal_line = plt.Line2D( + ( + xskip + + self.gate_margin + + self.gate_pad + - self.target_node_r / 2, + xskip + + self.gate_margin + + self.gate_pad + + self.target_node_r / 2, + ), + (pos * self.wire_sep, pos * self.wire_sep), + lw=1.5, + color="white", + zorder=3, + ) + + self.ax.add_artist(target_node) + self.ax.add_line(vertical_line) + self.ax.add_line(horizontal_line) + + def _draw_qbridge(self, pos1, pos2, xskip, color): + """ + Draw the bridge between the control and target nodes for the multi-qubit gate. + + Parameters + ---------- + pos1 : int + The position of the first node for the bridge. + + pos2 : int + The position of the second node for the bridge. + + xskip : float + The horizontal value for getting to requested layer. + + color : str + The color of the bridge. + """ + pos2 = pos2 + self.cwires + pos1 = pos1 + self.cwires + + bridge = plt.Line2D( + [ + xskip + self.gate_margin + self.gate_pad, + xskip + self.gate_margin + self.gate_pad, + ], + [pos1 * self.wire_sep, pos2 * self.wire_sep], + color=color, + zorder=2, + ) + self.ax.add_line(bridge) + + def _draw_cbridge(self, c_pos, q_pos, xskip, color): + """ + Draw the bridge between the classical and quantum wires for the measurement gate. + + Parameters + ---------- + c_pos : int + The position of the classical wire. + + q_pos : int + The position of the quantum wire. + + xskip : float + The horizontal value for getting to requested layer. + + color : str + The color of the bridge. + """ + q_pos = q_pos + self.cwires + + cbridge_l = plt.Line2D( + ( + xskip + + self.gate_margin + + self.gate_width / 2 + - self.cwire_sep, + xskip + + self.gate_margin + + self.gate_width / 2 + - self.cwire_sep, + ), + (c_pos * self.wire_sep + self.arrow_lenght, q_pos * self.wire_sep), + color=color, + zorder=2, + ) + cbridge_r = plt.Line2D( + ( + xskip + + self.gate_margin + + self.gate_width / 2 + + self.cwire_sep, + xskip + + self.gate_margin + + self.gate_width / 2 + + self.cwire_sep, + ), + (c_pos * self.wire_sep + self.arrow_lenght, q_pos * self.wire_sep), + color=color, + zorder=2, + ) + end_arrow = FancyArrow( + xskip + self.gate_margin + self.gate_width / 2, + c_pos * self.wire_sep + self.arrow_lenght, + 0, + -self.cwire_sep * 3, + width=0, + head_width=self.cwire_sep * 5, + head_length=self.cwire_sep * 3, + length_includes_head=True, + color="k", + zorder=2, + ) + self.ax.add_line(cbridge_l) + self.ax.add_line(cbridge_r) + self.ax.add_artist(end_arrow) + + def _draw_swap_mark(self, pos, xskip, color): + """ + Draw the swap mark for the SWAP gate. + + Parameters + ---------- + pos : int + The position of the swap mark. + + xskip : float + The horizontal value for getting to requested layer. + + color : str + The color of the swap mark. + """ + + pos = pos + self.cwires + + dia_left = plt.Line2D( + [ + xskip + self.gate_margin + self.gate_pad + self.gate_width / 3, + xskip + self.gate_margin + self.gate_pad - self.gate_width / 3, + ], + [ + pos * self.wire_sep + self.gate_height / 2, + pos * self.wire_sep - self.gate_height / 2, + ], + color=color, + linewidth=1.5, + zorder=3, + ) + dia_right = plt.Line2D( + [ + xskip + self.gate_margin + self.gate_pad - self.gate_width / 3, + xskip + self.gate_margin + self.gate_pad + self.gate_width / 3, + ], + [ + pos * self.wire_sep + self.gate_height / 2, + pos * self.wire_sep - self.gate_height / 2, + ], + color=color, + linewidth=1.5, + zorder=3, + ) + self.ax.add_line(dia_left) + self.ax.add_line(dia_right) + + def _draw_singleq_gate(self, gate, layer): + """ + Draw the single qubit gate. + + Parameters + ---------- + gate : Gate Object + The gate to be plotted. + + layer : int + The layer the gate is acting on. + """ + + gate_wire = gate.targets[0] + text_width = self._get_text_width( + gate.text, + gate.fontsize, + gate.fontweight, + gate.fontfamily, + gate.fontstyle, + ) + gate_width = max(text_width + self.gate_pad * 2, self.gate_width) + + gate_text = plt.Text( + self._get_xskip([gate_wire], layer) + + self.gate_margin + + gate_width / 2, + (gate_wire + self.cwires) * self.wire_sep, + gate.text, + color=gate.fontcolor, + fontsize=gate.fontsize, + fontweight=gate.fontweight, + fontfamily=gate.fontfamily, + fontstyle=gate.fontstyle, + verticalalignment="center", + horizontalalignment="center", + zorder=3, + ) + gate_patch = FancyBboxPatch( + ( + self._get_xskip([gate_wire], layer) + self.gate_margin, + (gate_wire + self.cwires) * self.wire_sep + - self.gate_height / 2, + ), + gate_width, + self.gate_height, + boxstyle="round4", + mutation_scale=0.3, + facecolor=gate.color, + edgecolor=gate.color, + zorder=2, + ) + + self.ax.add_artist(gate_text) + self.ax.add_patch(gate_patch) + self._manage_layers(gate_width, [gate_wire], layer) + + def _draw_multiq_gate(self, gate, layer): + """ + Draw the multi-qubit gate. + + Parameters + ---------- + gate : Gate Object + The gate to be plotted. + + layer : int + The layer the gate is acting on. + """ + + wire_list = list( + range(self.merged_qubits[0], self.merged_qubits[-1] + 1) + ) + com_xskip = self._get_xskip(self.merged_qubits, layer) + + if gate.name == "CNOT": + self._draw_control_node(gate.controls[0], com_xskip, gate.color) + self._draw_target_node(gate.targets[0], com_xskip, gate.color) + self._draw_qbridge( + gate.targets[0], gate.controls[0], com_xskip, gate.color + ) + self._manage_layers( + 2 * self.gate_pad + self.target_node_r / 3, + wire_list, + layer, + com_xskip, + ) + + elif gate.name == "SWAP": + self._draw_swap_mark(gate.targets[0], com_xskip, gate.color) + self._draw_swap_mark(gate.targets[1], com_xskip, gate.color) + self._draw_qbridge( + gate.targets[0], gate.targets[1], com_xskip, gate.color + ) + self._manage_layers( + 2 * (self.gate_pad + self.gate_width / 3), + wire_list, + layer, + com_xskip, + ) + + elif gate.name == "TOFFOLI": + self._draw_control_node(gate.controls[0], com_xskip, gate.color) + self._draw_control_node(gate.controls[1], com_xskip, gate.color) + self._draw_target_node(gate.targets[0], com_xskip, gate.color) + self._draw_qbridge( + gate.targets[0], gate.controls[0], com_xskip, gate.color + ) + self._draw_qbridge( + gate.targets[0], gate.controls[1], com_xskip, gate.color + ) + self._manage_layers( + 2 * self.gate_pad + self.target_node_r / 3, + wire_list, + layer, + com_xskip, + ) + + else: + + adj_targets = [i + self.cwires for i in sorted(gate.targets)] + text_width = self._get_text_width( + gate.text, + gate.fontsize, + gate.fontweight, + gate.fontfamily, + gate.fontstyle, + ) + gate_width = max(text_width + self.gate_pad * 2, self.gate_width) + xskip = self._get_xskip(wire_list, layer) + + gate_text = plt.Text( + xskip + self.gate_margin + gate_width / 2, + (adj_targets[0] + adj_targets[-1]) / 2 * self.wire_sep, + gate.text, + color=gate.fontcolor, + fontsize=gate.fontsize, + fontweight=gate.fontweight, + fontfamily=gate.fontfamily, + fontstyle=gate.fontstyle, + verticalalignment="center", + horizontalalignment="center", + zorder=3, + ) + + gate_patch = FancyBboxPatch( + ( + xskip + self.gate_margin, + adj_targets[0] * self.wire_sep - self.gate_height / 2, + ), + gate_width, + self.gate_height + + self.wire_sep * (adj_targets[-1] - adj_targets[0]), + boxstyle="round4", + mutation_scale=0.3, + facecolor=gate.color, + edgecolor=gate.color, + zorder=2, + ) + + if len(gate.targets) > 1: + for i in range(len(gate.targets)): + connector_l = Circle( + ( + xskip + self.gate_margin - self.connector_r, + (adj_targets[i]) * self.wire_sep, + ), + self.connector_r, + color=gate.fontcolor, + zorder=3, + ) + connector_r = Circle( + ( + xskip + + self.gate_margin + + gate_width + + self.connector_r, + (adj_targets[i]) * self.wire_sep, + ), + self.connector_r, + color=gate.fontcolor, + zorder=3, + ) + self.ax.add_artist(connector_l) + self.ax.add_artist(connector_r) + + # add cbridge if control qubits are present + if gate.controls is not None: + for control in gate.controls: + self._draw_control_node( + control, xskip + text_width / 2, gate.color + ) + self._draw_qbridge( + control, + gate.targets[0], + xskip + text_width / 2, + gate.color, + ) + + self.ax.add_artist(gate_text) + self.ax.add_patch(gate_patch) + self._manage_layers(gate_width, wire_list, layer, xskip) + + return None + + def _draw_measure(self, c_pos, q_pos, layer): + """ + Draw the measurement gate. + + Parameters + ---------- + c_pos : int + The position of the classical wire. + + q_pos : int + The position of the quantum wire. + + layer : int + The layer the gate is acting on. + """ + + xskip = self._get_xskip( + list(range(0, self.merged_qubits[-1] + 1)), layer + ) + measure_box = FancyBboxPatch( + ( + xskip + self.gate_margin, + (q_pos + self.cwires) * self.wire_sep - self.gate_height / 2, + ), + self.gate_width, + self.gate_height, + boxstyle="round4", + mutation_scale=0.3, + facecolor="white", + edgecolor="k", + zorder=2, + ) + arc = Arc( + ( + xskip + self.gate_margin + self.gate_width / 2, + (q_pos + self.cwires) * self.wire_sep - self.gate_height / 2, + ), + self.gate_width * 1.5, + self.gate_height * 1, + angle=0, + theta1=0, + theta2=180, + color="k", + zorder=2, + ) + arrow = FancyArrow( + xskip + self.gate_margin + self.gate_width / 2, + (q_pos + self.cwires) * self.wire_sep - self.gate_height / 2, + self.gate_width * 0.7, + self.gate_height * 0.7, + length_includes_head=True, + head_width=0, + color="k", + zorder=2, + ) + + self._draw_cbridge(c_pos, q_pos, xskip, "k") + self._manage_layers( + self.gate_width, + list(range(0, self.merged_qubits[-1] + 1)), + layer, + xskip, + ) + self.ax.add_patch(measure_box) + self.ax.add_artist(arc) + self.ax.add_artist(arrow) + + def canvas_plot(self): + """ + Plot the quantum circuit. + """ + + self._extend_wires(self.default_layers) + self._add_wire_labels() + + for gate in self.qc.gates: + + # multi-qubit gate + if ( + len(gate.targets) > 1 + or getattr(gate, "controls", False) is not None + ): + + self.merged_qubits = gate.targets.copy() + if isinstance(gate, Gate) and gate.controls is not None: + self.merged_qubits += gate.controls.copy() + self.merged_qubits.sort() + + # check if the gate is a measurement gate + if isinstance(gate, Measurement): + + find_layer = [ + len(self.layer_list[i]) + for i in range(0, self.merged_qubits[-1] + 1) + ] + + self._draw_measure( + gate.classical_store, + gate.targets[0], + max(find_layer), + ) + + else: + + find_layer = [ + len(self.layer_list[i]) + for i in range( + self.merged_qubits[0], self.merged_qubits[-1] + 1 + ) + ] + + self._draw_multiq_gate(gate, max(find_layer)) + + else: + self._draw_singleq_gate( + gate, len(self.layer_list[gate.targets[0]]) + ) + + self._extend_wires(0) + self._extend_wires(2, end=True) + self._fig_config() + + def _fig_config(self): + """ + Configure the figure settings. + """ + + self.ax.set_ylim( + -self.padding, + self.padding + (self.qwires + self.cwires - 1) * self.wire_sep, + ) + self.ax.set_xlim( + -self.padding, + self.padding + + max([sum(self.layer_list[i]) for i in range(self.qwires)]), + ) + self.ax.set_aspect("equal") + self.ax.axis("off") diff --git a/src/qutip_qip/operations/gateclass.py b/src/qutip_qip/operations/gateclass.py index a125bad8..225f51bc 100644 --- a/src/qutip_qip/operations/gateclass.py +++ b/src/qutip_qip/operations/gateclass.py @@ -14,6 +14,7 @@ import qutip from qutip import Qobj, identity, qeye, sigmax, sigmay, sigmaz, tensor, fock_dm +from ..circuit.color_theme import default_theme from .gates import ( rx, ry, @@ -141,6 +142,10 @@ class Gate: It is recommended to use ``isinstance`` or ``issubclass`` to identify a gate rather than comparing the name string. + style : dict, optional + A dictionary of style options for the gate. + The options are passed to the `matplotlib` plotter. + The default is None. """ def __init__( @@ -153,17 +158,45 @@ def __init__( classical_controls=None, classical_control_value: Optional[int] = None, arg_label=None, + style=None, **kwargs, ): """ Create a gate with specified parameters. """ - self.name = name if name is not None else self.__class__.__name__ self.targets = None self.controls = None self.classical_controls = None + if style is not None: + self.text = style.get("text", self.name) + self.color = style.get( + "color", + ( + default_theme[self.name] + if self.name in default_theme + else "#000000" + ), + ) + self.fontsize = style.get("fontsize", 10) + self.fontcolor = style.get("fontcolor", "#FFFFFF") + self.fontweight = style.get("fontweight", "normal") + self.fontstyle = style.get("fontstyle", "normal") + self.fontfamily = style.get("fontfamily", "monospace") + else: + self.text = self.name + self.color = ( + default_theme[self.name] + if self.name in default_theme + else "#000000" + ) + self.fontsize = 10 + self.fontcolor = "#FFFFFF" + self.fontweight = "normal" + self.fontstyle = "normal" + self.fontfamily = "monospace" + if not isinstance(targets, Iterable) and targets is not None: self.targets = [targets] else: From efb087d039699c8c5cf38eef6702564190d9db7f Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Thu, 20 Jun 2024 00:56:22 +0530 Subject: [PATCH 02/20] fixed back circuit.py --- src/qutip_qip/circuit/circuit.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/qutip_qip/circuit/circuit.py b/src/qutip_qip/circuit/circuit.py index 94cc25df..dd9730d4 100644 --- a/src/qutip_qip/circuit/circuit.py +++ b/src/qutip_qip/circuit/circuit.py @@ -865,7 +865,7 @@ def propagators(self, expand=True, ignore_measurement=False): ignore_measurement: bool, optional Whether :class:`.Measurement` operators should be ignored. If set False, it will raise an error - when the circuit hasf measurement. + when the circuit has measurement. Returns ------- @@ -887,9 +887,16 @@ def propagators(self, expand=True, ignore_measurement=False): ) for gate in gates: if gate.name == "GLOBALPHASE": - qobj = gate.get_ (self.N) - U_list.append(qobj) - continue + qobj = gate.get_qobj(self.N) + else: + qobj = self._get_gate_unitary(gate) + if expand: + all_targets = gate.get_all_qubits() + qobj = expand_operator( + qobj, dims=self.dims, targets=all_targets + ) + U_list.append(qobj) + return U_list def _get_gate_unitary(self, gate): if gate.name in self.user_gates: From 534da3c65aa0287ca2588f6f7f54e36b2be85b16 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Sun, 23 Jun 2024 21:11:29 +0530 Subject: [PATCH 03/20] moved styling to plotting --- src/qutip_qip/operations/gateclass.py | 30 +-------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/qutip_qip/operations/gateclass.py b/src/qutip_qip/operations/gateclass.py index 225f51bc..0eb217ce 100644 --- a/src/qutip_qip/operations/gateclass.py +++ b/src/qutip_qip/operations/gateclass.py @@ -14,7 +14,6 @@ import qutip from qutip import Qobj, identity, qeye, sigmax, sigmay, sigmaz, tensor, fock_dm -from ..circuit.color_theme import default_theme from .gates import ( rx, ry, @@ -168,34 +167,7 @@ def __init__( self.targets = None self.controls = None self.classical_controls = None - - if style is not None: - self.text = style.get("text", self.name) - self.color = style.get( - "color", - ( - default_theme[self.name] - if self.name in default_theme - else "#000000" - ), - ) - self.fontsize = style.get("fontsize", 10) - self.fontcolor = style.get("fontcolor", "#FFFFFF") - self.fontweight = style.get("fontweight", "normal") - self.fontstyle = style.get("fontstyle", "normal") - self.fontfamily = style.get("fontfamily", "monospace") - else: - self.text = self.name - self.color = ( - default_theme[self.name] - if self.name in default_theme - else "#000000" - ) - self.fontsize = 10 - self.fontcolor = "#FFFFFF" - self.fontweight = "normal" - self.fontstyle = "normal" - self.fontfamily = "monospace" + self.style = style if not isinstance(targets, Iterable) and targets is not None: self.targets = [targets] From 829bcadae95588fa0a32b78d285a14736144a35a Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Sun, 23 Jun 2024 21:12:46 +0530 Subject: [PATCH 04/20] fixed bugs, add. bulge arg, temp inclusion of style arg --- src/qutip_qip/circuit/mat_renderer.py | 223 +++++++++++++++++--------- 1 file changed, 148 insertions(+), 75 deletions(-) diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index 863897e4..3c7fde54 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -3,6 +3,8 @@ """ from typing import List + +import numpy as np import matplotlib.pyplot as plt from matplotlib.patches import ( FancyBboxPatch, @@ -13,6 +15,7 @@ from ..operations import Gate, Measurement from ..circuit import QubitCircuit +from .color_theme import default_theme __all__ = [ "MatRenderer", @@ -49,6 +52,7 @@ def __init__( bgcolor: str = "#FFFFFF", wire_label: List[str] = None, condense=0.15, + bulge=True, ) -> None: self.wire_sep = 0.5 @@ -64,6 +68,13 @@ def __init__( self.connector_r = 0.01 self.target_node_r = 0.12 self.display_layer_len = 0 + self.start_pad = 0.1 + + self.end_wire_ext = 2 + if bulge: + self.bulge = "round4" + else: + self.bulge = "square" self.qc = qc self.qwires = qc.N @@ -73,7 +84,7 @@ def __init__( self.padding = padding self.bgcolor = bgcolor self.wire_label = wire_label - self.layer_list = {i: [] for i in range(self.qwires)} + self.layer_list = {i: [self.start_pad] for i in range(self.qwires)} # fig config self.fig_height = ( @@ -276,6 +287,19 @@ def _add_wire_labels(self): ] self.wire_label = default_labels + self.max_label_width = max( + [ + self._get_text_width( + label, + 8, + "normal", + "monospace", + "normal", + ) + for label in self.wire_label + ] + ) + for i, label in enumerate(self.wire_label): wire_label = plt.Text( -self.label_pad, @@ -283,7 +307,7 @@ def _add_wire_labels(self): label, fontsize=self.font_size, verticalalignment="center", - horizontalalignment="left", + horizontalalignment="right", zorder=3, ) self.ax.add_artist(wire_label) @@ -518,6 +542,39 @@ def _draw_swap_mark(self, pos, xskip, color): self.ax.add_line(dia_left) self.ax.add_line(dia_right) + def to_pi_fraction(self, value, tolerance=0.01): + """ + Convert a value to a string fraction of pi. + + Parameters + ---------- + value : float + The value to be converted. + + tolerance : float, optional + The tolerance for the fraction. The default is 0.01. + + Returns + ------- + str + The value in terms of pi. + """ + + pi_value = value / np.pi + if abs(pi_value - round(pi_value)) < tolerance: + num = round(pi_value) + return f"[{num}\\pi]" if num != 1 else "[\\pi]" + + for denom in [2, 3, 4, 6, 8, 12]: + fraction_value = pi_value * denom + if abs(fraction_value - round(fraction_value)) < tolerance: + num = round(fraction_value) + return ( + f"[{num}\\pi/{denom}]" if num != 1 else f"[\\pi/{denom}]" + ) + + return f"[{round(value, 2)}]" + def _draw_singleq_gate(self, gate, layer): """ Draw the single qubit gate. @@ -532,12 +589,18 @@ def _draw_singleq_gate(self, gate, layer): """ gate_wire = gate.targets[0] + if gate.arg_value is not None: + pi_frac = self.to_pi_fraction(gate.arg_value) + text = f"${{{self.text}}}_{{{pi_frac}}}$" + else: + text = self.text + text_width = self._get_text_width( - gate.text, - gate.fontsize, - gate.fontweight, - gate.fontfamily, - gate.fontstyle, + text, + self.fontsize, + self.fontweight, + self.fontfamily, + self.fontstyle, ) gate_width = max(text_width + self.gate_pad * 2, self.gate_width) @@ -546,12 +609,12 @@ def _draw_singleq_gate(self, gate, layer): + self.gate_margin + gate_width / 2, (gate_wire + self.cwires) * self.wire_sep, - gate.text, - color=gate.fontcolor, - fontsize=gate.fontsize, - fontweight=gate.fontweight, - fontfamily=gate.fontfamily, - fontstyle=gate.fontstyle, + text, + color=self.fontcolor, + fontsize=self.fontsize, + fontweight=self.fontweight, + fontfamily=self.fontfamily, + fontstyle=self.fontstyle, verticalalignment="center", horizontalalignment="center", zorder=3, @@ -564,10 +627,10 @@ def _draw_singleq_gate(self, gate, layer): ), gate_width, self.gate_height, - boxstyle="round4", + boxstyle=self.bulge, mutation_scale=0.3, - facecolor=gate.color, - edgecolor=gate.color, + facecolor=self.color, + edgecolor=self.color, zorder=2, ) @@ -594,10 +657,10 @@ def _draw_multiq_gate(self, gate, layer): com_xskip = self._get_xskip(self.merged_qubits, layer) if gate.name == "CNOT": - self._draw_control_node(gate.controls[0], com_xskip, gate.color) - self._draw_target_node(gate.targets[0], com_xskip, gate.color) + self._draw_control_node(gate.controls[0], com_xskip, self.color) + self._draw_target_node(gate.targets[0], com_xskip, self.color) self._draw_qbridge( - gate.targets[0], gate.controls[0], com_xskip, gate.color + gate.targets[0], gate.controls[0], com_xskip, self.color ) self._manage_layers( 2 * self.gate_pad + self.target_node_r / 3, @@ -607,10 +670,10 @@ def _draw_multiq_gate(self, gate, layer): ) elif gate.name == "SWAP": - self._draw_swap_mark(gate.targets[0], com_xskip, gate.color) - self._draw_swap_mark(gate.targets[1], com_xskip, gate.color) + self._draw_swap_mark(gate.targets[0], com_xskip, self.color) + self._draw_swap_mark(gate.targets[1], com_xskip, self.color) self._draw_qbridge( - gate.targets[0], gate.targets[1], com_xskip, gate.color + gate.targets[0], gate.targets[1], com_xskip, self.color ) self._manage_layers( 2 * (self.gate_pad + self.gate_width / 3), @@ -620,14 +683,14 @@ def _draw_multiq_gate(self, gate, layer): ) elif gate.name == "TOFFOLI": - self._draw_control_node(gate.controls[0], com_xskip, gate.color) - self._draw_control_node(gate.controls[1], com_xskip, gate.color) - self._draw_target_node(gate.targets[0], com_xskip, gate.color) + self._draw_control_node(gate.controls[0], com_xskip, self.color) + self._draw_control_node(gate.controls[1], com_xskip, self.color) + self._draw_target_node(gate.targets[0], com_xskip, self.color) self._draw_qbridge( - gate.targets[0], gate.controls[0], com_xskip, gate.color + gate.targets[0], gate.controls[0], com_xskip, self.color ) self._draw_qbridge( - gate.targets[0], gate.controls[1], com_xskip, gate.color + gate.targets[0], gate.controls[1], com_xskip, self.color ) self._manage_layers( 2 * self.gate_pad + self.target_node_r / 3, @@ -640,11 +703,11 @@ def _draw_multiq_gate(self, gate, layer): adj_targets = [i + self.cwires for i in sorted(gate.targets)] text_width = self._get_text_width( - gate.text, - gate.fontsize, - gate.fontweight, - gate.fontfamily, - gate.fontstyle, + self.text, + self.fontsize, + self.fontweight, + self.fontfamily, + self.fontstyle, ) gate_width = max(text_width + self.gate_pad * 2, self.gate_width) xskip = self._get_xskip(wire_list, layer) @@ -652,12 +715,12 @@ def _draw_multiq_gate(self, gate, layer): gate_text = plt.Text( xskip + self.gate_margin + gate_width / 2, (adj_targets[0] + adj_targets[-1]) / 2 * self.wire_sep, - gate.text, - color=gate.fontcolor, - fontsize=gate.fontsize, - fontweight=gate.fontweight, - fontfamily=gate.fontfamily, - fontstyle=gate.fontstyle, + self.text, + color=self.fontcolor, + fontsize=self.fontsize, + fontweight=self.fontweight, + fontfamily=self.fontfamily, + fontstyle=self.fontstyle, verticalalignment="center", horizontalalignment="center", zorder=3, @@ -671,10 +734,10 @@ def _draw_multiq_gate(self, gate, layer): gate_width, self.gate_height + self.wire_sep * (adj_targets[-1] - adj_targets[0]), - boxstyle="round4", + boxstyle=self.bulge, mutation_scale=0.3, - facecolor=gate.color, - edgecolor=gate.color, + facecolor=self.color, + edgecolor=self.color, zorder=2, ) @@ -686,7 +749,7 @@ def _draw_multiq_gate(self, gate, layer): (adj_targets[i]) * self.wire_sep, ), self.connector_r, - color=gate.fontcolor, + color=self.fontcolor, zorder=3, ) connector_r = Circle( @@ -698,7 +761,7 @@ def _draw_multiq_gate(self, gate, layer): (adj_targets[i]) * self.wire_sep, ), self.connector_r, - color=gate.fontcolor, + color=self.fontcolor, zorder=3, ) self.ax.add_artist(connector_l) @@ -708,13 +771,13 @@ def _draw_multiq_gate(self, gate, layer): if gate.controls is not None: for control in gate.controls: self._draw_control_node( - control, xskip + text_width / 2, gate.color + control, xskip + text_width / 2, self.color ) self._draw_qbridge( control, gate.targets[0], xskip + text_width / 2, - gate.color, + self.color, ) self.ax.add_artist(gate_text) @@ -749,7 +812,7 @@ def _draw_measure(self, c_pos, q_pos, layer): ), self.gate_width, self.gate_height, - boxstyle="round4", + boxstyle=self.bulge, mutation_scale=0.3, facecolor="white", edgecolor="k", @@ -800,32 +863,41 @@ def canvas_plot(self): for gate in self.qc.gates: - # multi-qubit gate - if ( - len(gate.targets) > 1 - or getattr(gate, "controls", False) is not None - ): - + if isinstance(gate, Measurement): self.merged_qubits = gate.targets.copy() - if isinstance(gate, Gate) and gate.controls is not None: - self.merged_qubits += gate.controls.copy() self.merged_qubits.sort() - # check if the gate is a measurement gate - if isinstance(gate, Measurement): - - find_layer = [ - len(self.layer_list[i]) - for i in range(0, self.merged_qubits[-1] + 1) - ] + find_layer = [ + len(self.layer_list[i]) + for i in range(0, self.merged_qubits[-1] + 1) + ] + self._draw_measure( + gate.classical_store, + gate.targets[0], + max(find_layer), + ) - self._draw_measure( - gate.classical_store, - gate.targets[0], - max(find_layer), - ) + if isinstance(gate, Gate): + style = gate.style if gate.style is not None else {} + self.text = style.get("text", gate.name) + self.color = style.get( + "color", default_theme.get(gate.name, "k") + ) + self.fontsize = style.get("fontsize", self.font_size) + self.fontcolor = style.get("fontcolor", "#FFFFFF") + self.fontweight = style.get("fontweight", "normal") + self.fontstyle = style.get("fontstyle", "normal") + self.fontfamily = style.get("fontfamily", "monospace") - else: + # multi-qubit gate + if ( + len(gate.targets) > 1 + or getattr(gate, "controls", False) is not None + ): + self.merged_qubits = gate.targets.copy() + if gate.controls is not None: + self.merged_qubits += gate.controls.copy() + self.merged_qubits.sort() find_layer = [ len(self.layer_list[i]) @@ -833,16 +905,16 @@ def canvas_plot(self): self.merged_qubits[0], self.merged_qubits[-1] + 1 ) ] - self._draw_multiq_gate(gate, max(find_layer)) - else: - self._draw_singleq_gate( - gate, len(self.layer_list[gate.targets[0]]) - ) + else: + self._draw_singleq_gate( + gate, len(self.layer_list[gate.targets[0]]) + ) - self._extend_wires(0) - self._extend_wires(2, end=True) + self._extend_wires(0) + + self._extend_wires(self.end_wire_ext, end=True) self._fig_config() def _fig_config(self): @@ -855,8 +927,9 @@ def _fig_config(self): self.padding + (self.qwires + self.cwires - 1) * self.wire_sep, ) self.ax.set_xlim( - -self.padding, + -self.padding - self.max_label_width - self.label_pad, self.padding + + 2 * self.layer_sep + max([sum(self.layer_list[i]) for i in range(self.qwires)]), ) self.ax.set_aspect("equal") From 316b27af420e1961e14ad2adb22a203505c82658 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Thu, 11 Jul 2024 03:37:49 +0530 Subject: [PATCH 05/20] optimized with one-shot wire rendering --- src/qutip_qip/circuit/mat_renderer.py | 119 +++++++++----------------- 1 file changed, 42 insertions(+), 77 deletions(-) diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index 3c7fde54..32c72e3d 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -53,6 +53,7 @@ def __init__( wire_label: List[str] = None, condense=0.15, bulge=True, + end_wire_ext=2, ) -> None: self.wire_sep = 0.5 @@ -61,7 +62,7 @@ def __init__( self.gate_height = 0.2 self.gate_width = 0.2 self.gate_pad = 0.05 - self.label_pad = 0.2 + self.label_pad = 0.1 self.font_size = 10 self.default_layers = 2 self.arrow_lenght = 0.06 @@ -70,7 +71,7 @@ def __init__( self.display_layer_len = 0 self.start_pad = 0.1 - self.end_wire_ext = 2 + self.end_wire_ext = end_wire_ext if bulge: self.bulge = "round4" else: @@ -200,81 +201,48 @@ def _manage_layers(self, gate_width, wire_list, layer, xskip=0): temp + gate_width + self.gate_margin * 2 ) - def _extend_wires(self, ext, end=False): + def _add_wire(self): """ - Extends the wires in the circuit. - - Parameters - ---------- - ext : int - The number of layers to extend the wires. - - end : bool, optional - If True, the wires are extended further distance for end of the circuit. - The default is False. - """ - - max_layer = max([sum(self.layer_list[i]) for i in range(self.qwires)]) - - if ( - self.display_layer_len < max_layer - or self.display_layer_len == 0 - or end is True - ): - if self.cwires != 0: - - ext_cwires_pos = [ - [ - [ - ( - self.display_layer_len, - max_layer + ext * self.layer_sep, - ), - ( - (i * self.wire_sep) + self.cwire_sep, - (i * self.wire_sep) + self.cwire_sep, - ), - ], - [ - ( - self.display_layer_len, - max_layer + ext * self.layer_sep, - ), - ( - i * self.wire_sep - self.cwire_sep, - i * self.wire_sep - self.cwire_sep, - ), - ], - ] - for i in range(self.cwires) - ] + Adds the wires to the circuit. + """ + max_len = ( + max([sum(self.layer_list[i]) for i in range(self.qwires)]) + + self.end_wire_ext * self.layer_sep + ) - for pos in ext_cwires_pos: - wire_up = plt.Line2D( - pos[0][0], pos[0][1], lw=1, color="k", zorder=1 - ) - wire_down = plt.Line2D( - pos[1][0], pos[1][1], lw=1, color="k", zorder=1 - ) - self.ax.add_line(wire_up) - self.ax.add_line(wire_down) + for i in range(self.qwires): + wire = plt.Line2D( + [0, max_len], + [i * self.wire_sep, i * self.wire_sep], + lw=1, + color="k", + zorder=1, + ) + self.ax.add_line(wire) - ext_qwires_pos = [ + for i in range(self.cwires): + wire_up = plt.Line2D( + [0, max_len], [ - ( - self.display_layer_len, - max_layer + ext * self.layer_sep, - ), - (i * self.wire_sep, i * self.wire_sep), - ] - for i in range(self.cwires, self.cwires + self.qwires) - ] - - for pos in ext_qwires_pos: - wire = plt.Line2D(pos[0], pos[1], lw=1, color="k", zorder=1) - self.ax.add_line(wire) - - self.display_layer_len = max_layer + (i + self.qwires) * self.wire_sep, + (i + self.qwires) * self.wire_sep, + ], + lw=1, + color="k", + zorder=1, + ) + wire_down = plt.Line2D( + [0, max_len], + [ + (i + self.qwires) * self.wire_sep, + (i + self.qwires) * self.wire_sep, + ], + lw=1, + color="k", + zorder=1, + ) + self.ax.add_line(wire_up) + self.ax.add_line(wire_down) def _add_wire_labels(self): """ @@ -858,7 +826,6 @@ def canvas_plot(self): Plot the quantum circuit. """ - self._extend_wires(self.default_layers) self._add_wire_labels() for gate in self.qc.gates: @@ -912,9 +879,7 @@ def canvas_plot(self): gate, len(self.layer_list[gate.targets[0]]) ) - self._extend_wires(0) - - self._extend_wires(self.end_wire_ext, end=True) + self._add_wire() self._fig_config() def _fig_config(self): From afe4b54c570e626347db294fb1040c5efbff2c29 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Fri, 12 Jul 2024 01:06:47 +0530 Subject: [PATCH 06/20] added view arg label --- src/qutip_qip/circuit/mat_renderer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index 32c72e3d..8fe73275 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -557,7 +557,7 @@ def _draw_singleq_gate(self, gate, layer): """ gate_wire = gate.targets[0] - if gate.arg_value is not None: + if gate.arg_value is not None and self.showarg is True: pi_frac = self.to_pi_fraction(gate.arg_value) text = f"${{{self.text}}}_{{{pi_frac}}}$" else: @@ -855,6 +855,7 @@ def canvas_plot(self): self.fontweight = style.get("fontweight", "normal") self.fontstyle = style.get("fontstyle", "normal") self.fontfamily = style.get("fontfamily", "monospace") + self.showarg = style.get("showarg", False) # multi-qubit gate if ( From 296668acb504364ba34eedc7365e41edf1479b4d Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Tue, 16 Jul 2024 20:26:20 +0530 Subject: [PATCH 07/20] organized, added hints and changed doc string --- src/qutip_qip/circuit/mat_renderer.py | 138 +++++++++++++------------- 1 file changed, 68 insertions(+), 70 deletions(-) diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index 8fe73275..b7b3b998 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -2,7 +2,7 @@ Module for rendering a quantum circuit using matplotlib library. """ -from typing import List +from typing import List, Union import numpy as np import matplotlib.pyplot as plt @@ -31,29 +31,14 @@ class MatRenderer: qc : QuantumCircuit Object The quantum circuit to be rendered. - padding : float, optional - The padding around the circuit. The default is 0.3. - - dpi : int, optional - The resolution of the image. The default is 150. - - bgcolor : str, optional - The background color of the plotted circuit. The default is "#FFFFFF" (white). - - wire_labels : list, optional - The labels for the wires. + style : dict, optional + The style dictionary for the circuit. The default is None. """ def __init__( self, qc: QubitCircuit, - padding=0.3, - dpi: int = 150, - bgcolor: str = "#FFFFFF", - wire_label: List[str] = None, - condense=0.15, - bulge=True, - end_wire_ext=2, + style: dict = None, ) -> None: self.wire_sep = 0.5 @@ -63,28 +48,29 @@ def __init__( self.gate_width = 0.2 self.gate_pad = 0.05 self.label_pad = 0.1 - self.font_size = 10 self.default_layers = 2 self.arrow_lenght = 0.06 self.connector_r = 0.01 self.target_node_r = 0.12 + self.control_node_radius = 0.05 self.display_layer_len = 0 self.start_pad = 0.1 - self.end_wire_ext = end_wire_ext - if bulge: - self.bulge = "round4" - else: - self.bulge = "square" - self.qc = qc self.qwires = qc.N self.cwires = qc.num_cbits - self.dpi = dpi - self.gate_margin = condense - self.padding = padding - self.bgcolor = bgcolor - self.wire_label = wire_label + + if style.get("bulge", True): + self.bulge = "round4" + else: + self.bulge = "square" + self.dpi = style.get("dpi", 150) + self.padding = style.get("padding", 0.3) + self.fontsize = style.get("fontsize", 10) + self.gate_margin = style.get("condense", 0.3) + self.bgcolor = style.get("bgcolor", "#FFFFFF") + self.wire_label = style.get("wire_label", None) + self.end_wire_ext = style.get("end_wire_ext", 2) self.layer_list = {i: [self.start_pad] for i in range(self.qwires)} # fig config @@ -100,7 +86,7 @@ def __init__( self.canvas_plot() - def _get_xskip(self, wire_list, layer): + def _get_xskip(self, wire_list: List[int], layer: int) -> float: """ Get the xskip (horizontal value for getting to requested layer) for the gate to be plotted. @@ -120,8 +106,13 @@ def _get_xskip(self, wire_list, layer): return max(xskip) def _get_text_width( - self, text, fontsize, fontweight, fontfamily, fontstyle - ): + self, + text: str, + fontsize: float, + fontweight: Union[float, str], + fontfamily: str, + fontstyle: str, + ) -> float: """ Get the width of the text to be plotted. @@ -130,10 +121,10 @@ def _get_text_width( text : str The text to be plotted. - fontsize : int + fontsize : float The fontsize of the text. - fontweight : str + fontweight : str or float The fontweight of the text. fontfamily : str @@ -167,7 +158,13 @@ def _get_text_width( return bbox_data.width * 2.54 * 3 - def _manage_layers(self, gate_width, wire_list, layer, xskip=0): + def _manage_layers( + self, + gate_width: float, + wire_list: List[int], + layer: int, + xskip: float = 0, + ) -> None: """ Manages and updates the layer widths according to the gate's width just plotted. @@ -201,7 +198,7 @@ def _manage_layers(self, gate_width, wire_list, layer, xskip=0): temp + gate_width + self.gate_margin * 2 ) - def _add_wire(self): + def _add_wire(self) -> None: """ Adds the wires to the circuit. """ @@ -244,7 +241,7 @@ def _add_wire(self): self.ax.add_line(wire_up) self.ax.add_line(wire_down) - def _add_wire_labels(self): + def _add_wire_labels(self) -> None: """ Adds the wire labels to the circuit. """ @@ -259,7 +256,7 @@ def _add_wire_labels(self): [ self._get_text_width( label, - 8, + self.fontsize, "normal", "monospace", "normal", @@ -273,54 +270,53 @@ def _add_wire_labels(self): -self.label_pad, i * self.wire_sep, label, - fontsize=self.font_size, + fontsize=self.fontsize, verticalalignment="center", horizontalalignment="right", zorder=3, ) self.ax.add_artist(wire_label) - def _draw_control_node(self, pos, xskip, color): + def _draw_control_node(self, pos: int, xskip: float, color: str) -> None: """ Draw the control node for the multi-qubit gate. Parameters ---------- pos : int - The position of the control node. + The position of the control node, in terms of the wire number. xskip : float The horizontal value for getting to requested layer. color : str - The color of the control node. + The color of the control node. HEX code or color name supported by matplotlib are valid. """ pos = pos + self.cwires - control_node_radius = 0.05 control_node = Circle( (xskip + self.gate_margin + self.gate_pad, pos * self.wire_sep), - control_node_radius, + self.control_node_radius, color=color, zorder=2, ) self.ax.add_artist(control_node) - def _draw_target_node(self, pos, xskip, node_color): + def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: """ Draw the target node for the multi-qubit gate. Parameters ---------- pos : int - The position of the target node. + The position of the target node, in terms of the wire number. xskip : float The horizontal value for getting to requested layer. - node_color : str - The color of the target node. + color : str + The color of the control node. HEX code or color name supported by matplotlib are valid. """ pos = pos + self.cwires @@ -328,7 +324,7 @@ def _draw_target_node(self, pos, xskip, node_color): target_node = Circle( (xskip + self.gate_margin + self.gate_pad, pos * self.wire_sep), self.target_node_r, - color=node_color, + color=color, zorder=2, ) vertical_line = plt.Line2D( @@ -365,23 +361,25 @@ def _draw_target_node(self, pos, xskip, node_color): self.ax.add_line(vertical_line) self.ax.add_line(horizontal_line) - def _draw_qbridge(self, pos1, pos2, xskip, color): + def _draw_qbridge( + self, pos1: int, pos2: int, xskip: float, color: str + ) -> None: """ Draw the bridge between the control and target nodes for the multi-qubit gate. Parameters ---------- pos1 : int - The position of the first node for the bridge. + The position of the first node for the bridge, in terms of the wire number. pos2 : int - The position of the second node for the bridge. + The position of the second node for the bridge, in terms of the wire number. xskip : float The horizontal value for getting to requested layer. color : str - The color of the bridge. + The color of the control node. HEX code or color name supported by matplotlib are valid. """ pos2 = pos2 + self.cwires pos1 = pos1 + self.cwires @@ -397,17 +395,19 @@ def _draw_qbridge(self, pos1, pos2, xskip, color): ) self.ax.add_line(bridge) - def _draw_cbridge(self, c_pos, q_pos, xskip, color): + def _draw_cbridge( + self, c_pos: int, q_pos: int, xskip: float, color: str + ) -> None: """ Draw the bridge between the classical and quantum wires for the measurement gate. Parameters ---------- c_pos : int - The position of the classical wire. + The position of the classical wire, in terms of the wire number. q_pos : int - The position of the quantum wire. + The position of the quantum wire, in terms of the wire number. xskip : float The horizontal value for getting to requested layer. @@ -463,14 +463,14 @@ def _draw_cbridge(self, c_pos, q_pos, xskip, color): self.ax.add_line(cbridge_r) self.ax.add_artist(end_arrow) - def _draw_swap_mark(self, pos, xskip, color): + def _draw_swap_mark(self, pos: int, xskip: int, color: str) -> None: """ Draw the swap mark for the SWAP gate. Parameters ---------- pos : int - The position of the swap mark. + The position of the swap mark, in terms of the wire number. xskip : float The horizontal value for getting to requested layer. @@ -510,7 +510,7 @@ def _draw_swap_mark(self, pos, xskip, color): self.ax.add_line(dia_left) self.ax.add_line(dia_right) - def to_pi_fraction(self, value, tolerance=0.01): + def to_pi_fraction(self, value: float, tolerance: float = 0.01) -> str: """ Convert a value to a string fraction of pi. @@ -543,7 +543,7 @@ def to_pi_fraction(self, value, tolerance=0.01): return f"[{round(value, 2)}]" - def _draw_singleq_gate(self, gate, layer): + def _draw_singleq_gate(self, gate: Gate, layer: int) -> None: """ Draw the single qubit gate. @@ -606,7 +606,7 @@ def _draw_singleq_gate(self, gate, layer): self.ax.add_patch(gate_patch) self._manage_layers(gate_width, [gate_wire], layer) - def _draw_multiq_gate(self, gate, layer): + def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: """ Draw the multi-qubit gate. @@ -752,9 +752,7 @@ def _draw_multiq_gate(self, gate, layer): self.ax.add_patch(gate_patch) self._manage_layers(gate_width, wire_list, layer, xskip) - return None - - def _draw_measure(self, c_pos, q_pos, layer): + def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: """ Draw the measurement gate. @@ -821,7 +819,7 @@ def _draw_measure(self, c_pos, q_pos, layer): self.ax.add_artist(arc) self.ax.add_artist(arrow) - def canvas_plot(self): + def canvas_plot(self) -> None: """ Plot the quantum circuit. """ @@ -850,7 +848,7 @@ def canvas_plot(self): self.color = style.get( "color", default_theme.get(gate.name, "k") ) - self.fontsize = style.get("fontsize", self.font_size) + self.fontsize = style.get("fontsize", self.fontsize) self.fontcolor = style.get("fontcolor", "#FFFFFF") self.fontweight = style.get("fontweight", "normal") self.fontstyle = style.get("fontstyle", "normal") @@ -883,7 +881,7 @@ def canvas_plot(self): self._add_wire() self._fig_config() - def _fig_config(self): + def _fig_config(self) -> None: """ Configure the figure settings. """ @@ -895,7 +893,7 @@ def _fig_config(self): self.ax.set_xlim( -self.padding - self.max_label_width - self.label_pad, self.padding - + 2 * self.layer_sep + + self.end_wire_ext * self.layer_sep + max([sum(self.layer_list[i]) for i in range(self.qwires)]), ) self.ax.set_aspect("equal") From f01dff33d4f01fd18800ecfcabd382a763d66490 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Wed, 17 Jul 2024 23:11:40 +0530 Subject: [PATCH 08/20] added circuit style, utils fn, custom axes option --- src/qutip_qip/circuit/mat_renderer.py | 56 ++++++++++++++++++++------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index b7b3b998..ae016bbe 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -6,6 +6,7 @@ import numpy as np import matplotlib.pyplot as plt +from matplotlib.axes import Axes from matplotlib.patches import ( FancyBboxPatch, Circle, @@ -38,16 +39,18 @@ class MatRenderer: def __init__( self, qc: QubitCircuit, + ax: Axes = None, style: dict = None, ) -> None: - self.wire_sep = 0.5 - self.layer_sep = 0.5 + self.qc = qc + self.ax = ax + self.qwires = qc.N + self.cwires = qc.num_cbits + self.cwire_sep = 0.02 self.gate_height = 0.2 self.gate_width = 0.2 - self.gate_pad = 0.05 - self.label_pad = 0.1 self.default_layers = 2 self.arrow_lenght = 0.06 self.connector_r = 0.01 @@ -56,10 +59,8 @@ def __init__( self.display_layer_len = 0 self.start_pad = 0.1 - self.qc = qc - self.qwires = qc.N - self.cwires = qc.num_cbits - + # user defined style + style = {} if style is None else style if style.get("bulge", True): self.bulge = "round4" else: @@ -68,9 +69,16 @@ def __init__( self.padding = style.get("padding", 0.3) self.fontsize = style.get("fontsize", 10) self.gate_margin = style.get("condense", 0.3) + self.wire_sep = style.get("wire_sep", 0.5) + self.layer_sep = style.get("layer_sep", 0.5) + self.gate_pad = style.get("gate_pad", 0.05) + self.label_pad = style.get("label_pad", 0.1) self.bgcolor = style.get("bgcolor", "#FFFFFF") self.wire_label = style.get("wire_label", None) self.end_wire_ext = style.get("end_wire_ext", 2) + self.center_ciruit = style.get("center_circuit", False) + self.title = style.get("title", None) + self.layer_list = {i: [self.start_pad] for i in range(self.qwires)} # fig config @@ -78,11 +86,12 @@ def __init__( (self.qwires + self.cwires) * self.wire_sep * 0.393701 * 3 ) self.fig_width = 10 - self.fig, self.ax = plt.subplots( - figsize=(self.fig_width, self.fig_height), - dpi=self.dpi, - facecolor=self.bgcolor, - ) + if self.ax is None: + self.fig, self.ax = plt.subplots( + figsize=(self.fig_width, self.fig_height), + dpi=self.dpi, + facecolor=self.bgcolor, + ) self.canvas_plot() @@ -881,7 +890,7 @@ def canvas_plot(self) -> None: self._add_wire() self._fig_config() - def _fig_config(self) -> None: + def _fig_config(self) -> Axes: """ Configure the figure settings. """ @@ -896,5 +905,24 @@ def _fig_config(self) -> None: + self.end_wire_ext * self.layer_sep + max([sum(self.layer_list[i]) for i in range(self.qwires)]), ) + if self.title is not None: + self.ax.set_title(self.title, pad=10) self.ax.set_aspect("equal") self.ax.axis("off") + + return self.ax + + def save(self, filename: str, **kwargs) -> None: + """ + Save the circuit to a file. + + Parameters + ---------- + filename : str + The name of the file to save the circuit to. + + **kwargs + Additional arguments to be passed to `plt.savefig`. + """ + + self.fig.savefig(filename, **kwargs) From 2e1154b8e5a061e29ff79ef5fe56858005d4685b Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Thu, 18 Jul 2024 11:18:09 +0530 Subject: [PATCH 09/20] added zorder dict and align-layer option --- src/qutip_qip/circuit/mat_renderer.py | 70 ++++++++++++++++----------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index ae016bbe..9f9f3b1a 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -68,7 +68,7 @@ def __init__( self.dpi = style.get("dpi", 150) self.padding = style.get("padding", 0.3) self.fontsize = style.get("fontsize", 10) - self.gate_margin = style.get("condense", 0.3) + self.gate_margin = style.get("condense", 0.15) self.wire_sep = style.get("wire_sep", 0.5) self.layer_sep = style.get("layer_sep", 0.5) self.gate_pad = style.get("gate_pad", 0.05) @@ -76,12 +76,24 @@ def __init__( self.bgcolor = style.get("bgcolor", "#FFFFFF") self.wire_label = style.get("wire_label", None) self.end_wire_ext = style.get("end_wire_ext", 2) - self.center_ciruit = style.get("center_circuit", False) + self.align_layer = style.get("align_layer", False) self.title = style.get("title", None) + self.fig_height = style.get("fig_height", None) + self.fig_width = style.get("fig_width", None) self.layer_list = {i: [self.start_pad] for i in range(self.qwires)} # fig config + self.zorder = { + "wire": 1, + "wire_label": 1, + "gate": 2, + "node": 2, + "bridge": 2, + "connector": 3, + "gate_label": 3, + "node_label": 3, + } self.fig_height = ( (self.qwires + self.cwires) * self.wire_sep * 0.393701 * 3 ) @@ -109,6 +121,9 @@ def _get_xskip(self, wire_list: List[int], layer: int) -> float: """ xskip = [] + if self.align_layer: + wire_list = list(range(self.qwires)) + for wire in wire_list: xskip.append(sum(self.layer_list[wire][:layer])) @@ -222,7 +237,7 @@ def _add_wire(self) -> None: [i * self.wire_sep, i * self.wire_sep], lw=1, color="k", - zorder=1, + zorder=self.zorder["wire"], ) self.ax.add_line(wire) @@ -235,7 +250,7 @@ def _add_wire(self) -> None: ], lw=1, color="k", - zorder=1, + zorder=self.zorder["wire"], ) wire_down = plt.Line2D( [0, max_len], @@ -245,7 +260,7 @@ def _add_wire(self) -> None: ], lw=1, color="k", - zorder=1, + zorder=self.zorder["wire"], ) self.ax.add_line(wire_up) self.ax.add_line(wire_down) @@ -282,7 +297,7 @@ def _add_wire_labels(self) -> None: fontsize=self.fontsize, verticalalignment="center", horizontalalignment="right", - zorder=3, + zorder=self.zorder["wire_label"], ) self.ax.add_artist(wire_label) @@ -308,7 +323,7 @@ def _draw_control_node(self, pos: int, xskip: float, color: str) -> None: (xskip + self.gate_margin + self.gate_pad, pos * self.wire_sep), self.control_node_radius, color=color, - zorder=2, + zorder=self.zorder["node"], ) self.ax.add_artist(control_node) @@ -334,7 +349,7 @@ def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: (xskip + self.gate_margin + self.gate_pad, pos * self.wire_sep), self.target_node_r, color=color, - zorder=2, + zorder=self.zorder["node"], ) vertical_line = plt.Line2D( ( @@ -347,7 +362,7 @@ def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: ), lw=1.5, color="white", - zorder=3, + zorder=self.zorder["node_label"], ) horizontal_line = plt.Line2D( ( @@ -363,7 +378,7 @@ def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: (pos * self.wire_sep, pos * self.wire_sep), lw=1.5, color="white", - zorder=3, + zorder=self.zorder["node_label"], ) self.ax.add_artist(target_node) @@ -400,7 +415,7 @@ def _draw_qbridge( ], [pos1 * self.wire_sep, pos2 * self.wire_sep], color=color, - zorder=2, + zorder=self.zorder["bridge"], ) self.ax.add_line(bridge) @@ -439,7 +454,7 @@ def _draw_cbridge( ), (c_pos * self.wire_sep + self.arrow_lenght, q_pos * self.wire_sep), color=color, - zorder=2, + zorder=self.zorder["bridge"], ) cbridge_r = plt.Line2D( ( @@ -454,7 +469,7 @@ def _draw_cbridge( ), (c_pos * self.wire_sep + self.arrow_lenght, q_pos * self.wire_sep), color=color, - zorder=2, + zorder=self.zorder["bridge"], ) end_arrow = FancyArrow( xskip + self.gate_margin + self.gate_width / 2, @@ -466,7 +481,7 @@ def _draw_cbridge( head_length=self.cwire_sep * 3, length_includes_head=True, color="k", - zorder=2, + zorder=self.zorder["bridge"], ) self.ax.add_line(cbridge_l) self.ax.add_line(cbridge_r) @@ -501,7 +516,7 @@ def _draw_swap_mark(self, pos: int, xskip: int, color: str) -> None: ], color=color, linewidth=1.5, - zorder=3, + zorder=self.zorder["gate"], ) dia_right = plt.Line2D( [ @@ -514,7 +529,7 @@ def _draw_swap_mark(self, pos: int, xskip: int, color: str) -> None: ], color=color, linewidth=1.5, - zorder=3, + zorder=self.zorder["gate"], ) self.ax.add_line(dia_left) self.ax.add_line(dia_right) @@ -594,7 +609,7 @@ def _draw_singleq_gate(self, gate: Gate, layer: int) -> None: fontstyle=self.fontstyle, verticalalignment="center", horizontalalignment="center", - zorder=3, + zorder=self.zorder["gate_label"], ) gate_patch = FancyBboxPatch( ( @@ -608,7 +623,7 @@ def _draw_singleq_gate(self, gate: Gate, layer: int) -> None: mutation_scale=0.3, facecolor=self.color, edgecolor=self.color, - zorder=2, + zorder=self.zorder["gate"], ) self.ax.add_artist(gate_text) @@ -700,7 +715,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: fontstyle=self.fontstyle, verticalalignment="center", horizontalalignment="center", - zorder=3, + zorder=self.zorder["gate_label"], ) gate_patch = FancyBboxPatch( @@ -715,7 +730,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: mutation_scale=0.3, facecolor=self.color, edgecolor=self.color, - zorder=2, + zorder=self.zorder["gate"], ) if len(gate.targets) > 1: @@ -727,7 +742,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: ), self.connector_r, color=self.fontcolor, - zorder=3, + zorder=self.zorder["connector"], ) connector_r = Circle( ( @@ -739,7 +754,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: ), self.connector_r, color=self.fontcolor, - zorder=3, + zorder=self.zorder["connector"], ) self.ax.add_artist(connector_l) self.ax.add_artist(connector_r) @@ -791,7 +806,7 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: mutation_scale=0.3, facecolor="white", edgecolor="k", - zorder=2, + zorder=self.zorder["gate"], ) arc = Arc( ( @@ -804,7 +819,7 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: theta1=0, theta2=180, color="k", - zorder=2, + zorder=self.zorder["gate_label"], ) arrow = FancyArrow( xskip + self.gate_margin + self.gate_width / 2, @@ -814,7 +829,7 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: length_includes_head=True, head_width=0, color="k", - zorder=2, + zorder=self.zorder["gate_label"], ) self._draw_cbridge(c_pos, q_pos, xskip, "k") @@ -889,8 +904,9 @@ def canvas_plot(self) -> None: self._add_wire() self._fig_config() + plt.show() - def _fig_config(self) -> Axes: + def _fig_config(self) -> None: """ Configure the figure settings. """ @@ -910,8 +926,6 @@ def _fig_config(self) -> Axes: self.ax.set_aspect("equal") self.ax.axis("off") - return self.ax - def save(self, filename: str, **kwargs) -> None: """ Save the circuit to a file. From 2246aba996dbed345f53623403ad0fc4fd535287 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Sat, 20 Jul 2024 20:41:30 +0530 Subject: [PATCH 10/20] bugfix --- src/qutip_qip/circuit/mat_renderer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index 9f9f3b1a..6eec6d41 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -646,7 +646,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: wire_list = list( range(self.merged_qubits[0], self.merged_qubits[-1] + 1) ) - com_xskip = self._get_xskip(self.merged_qubits, layer) + com_xskip = self._get_xskip(wire_list, layer) if gate.name == "CNOT": self._draw_control_node(gate.controls[0], com_xskip, self.color) From b2ac353e8fc55776b2049c7228d5a1587e66f8cb Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Sun, 21 Jul 2024 20:47:55 +0530 Subject: [PATCH 11/20] added new color theme --- src/qutip_qip/circuit/color_theme.py | 108 +++++++++++++++++++------- src/qutip_qip/circuit/mat_renderer.py | 26 ++++--- 2 files changed, 98 insertions(+), 36 deletions(-) diff --git a/src/qutip_qip/circuit/color_theme.py b/src/qutip_qip/circuit/color_theme.py index 50fb04c6..98b0099d 100644 --- a/src/qutip_qip/circuit/color_theme.py +++ b/src/qutip_qip/circuit/color_theme.py @@ -1,30 +1,84 @@ -''' +""" Color theme for different gates in the circuit diagram. -''' +""" -default_theme = { - "H": "#6270CE", - "SNOT": "#6270CE", - "X": "#CB4BF9", - "Y": "#CB4BF9", - "Z": "#CB4BF9", - "S": "#254065", - "T": "#254065", - "RX": "#5EBDF8", - "RY": "#5EBDF8", - "RZ": "#5EBDF8", - "CNOT": "#3B3470", - "CPHASE": '#456DB2', # change - "TOFFOLI": "#3B3470", # change - "SWAP": "#3B3470", - "CX": "#9598f5", - "CY": "#9598f5", - "CZ": "#9598f5", - "CS": "#9598f5", - "CT": "#9598f5", - "CRX": "#A66DDF", - "CRY": "#A66DDF", - "CRZ": "#A66DDF", - "BERKELEY": "#7648CB", - "FREDKIN": "#7648CB", +qutip = { + "H": "#6270CE", # Medium Slate Blue + "SNOT": "#6270CE", # Medium Slate Blue + "X": "#CB4BF9", # Medium Orchid + "Y": "#CB4BF9", # Medium Orchid + "Z": "#CB4BF9", # Medium Orchid + "S": "#254065", # Dark Slate Blue + "T": "#254065", # Dark Slate Blue + "RX": "#5EBDF8", # Light Sky Blue + "RY": "#5EBDF8", # Light Sky Blue + "RZ": "#5EBDF8", # Light Sky Blue + "CNOT": "#3B3470", # Indigo + "CPHASE": "#456DB2", # Steel Blue + "TOFFOLI": "#3B3470", # Indigo + "SWAP": "#3B3470", # Indigo + "CX": "#9598F5", # Light Slate Blue + "CY": "#9598F5", # Light Slate Blue + "CZ": "#9598F5", # Light Slate Blue + "CS": "#9598F5", # Light Slate Blue + "CT": "#9598F5", # Light Slate Blue + "CRX": "#A66DDF", # Medium Purple + "CRY": "#A66DDF", # Medium Purple + "CRZ": "#A66DDF", # Medium Purple + "BERKELEY": "#7648CB", # Dark Orchid + "FREDKIN": "#7648CB", # Dark Orchid +} + +light = { + "H": "#A3C1DA", # Light Blue + "SNOT": "#A3C1DA", # Light Blue + "X": "#F4A7B9", # Light Pink + "Y": "#F4A7B9", # Light Pink + "Z": "#F4A7B9", # Light Pink + "S": "#D3E2EE", # Very Light Blue + "T": "#D3E2EE", # Very Light Blue + "RX": "#B3E6E4", # Light Teal + "RY": "#B3E6E4", # Light Teal + "RZ": "#B3E6E4", # Light Teal + "CNOT": "#C3C5E6", # Light Indigo + "CPHASE": "#D5E0F2", # Light Slate Blue + "TOFFOLI": "#E6CCE6", # Soft Lavender + "SWAP": "#FFB6B6", # Lighter Coral Pink + "CX": "#E0E2F7", # Very Light Indigo + "CY": "#E0E2F7", # Very Light Indigo + "CZ": "#E0E2F7", # Very Light Indigo + "CS": "#E0E2F7", # Very Light Indigo + "CT": "#E0E2F7", # Very Light Indigo + "CRX": "#D6C9E8", # Light Muted Purple + "CRY": "#D6C9E8", # Light Muted Purple + "CRZ": "#D6C9E8", # Light Muted Purple + "BERKELEY": "#CDC1E8", # Light Purple + "FREDKIN": "#CDC1E8", # Light Purple +} + +modern = { + "H": "#C25454", # Soft Red + "SNOT": "#C25454", # Soft Red + "X": "#4A5D6D", # Dark Slate Blue + "Y": "#4A5D6D", # Dark Slate Blue + "Z": "#4A5D6D", # Dark Slate Blue + "S": "#2C3E50", # Very Dark Slate Blue + "T": "#2C3E50", # Very Dark Slate Blue + "RX": "#2F4F4F", # Dark Slate Teal + "RY": "#2F4F4F", # Dark Slate Teal + "RZ": "#2F4F4F", # Dark Slate Teal + "CNOT": "#4A6D7C", # Dark Slate Blue Gray + "CPHASE": "#5E7D8B", # Dark Slate Blue + "TOFFOLI": "#4A4A4A", # Dark Gray + "SWAP": "#6A9ACD", # Slate Blue + "CX": "#5D8AA8", # Medium Slate Blue + "CY": "#5D8AA8", # Medium Slate Blue + "CZ": "#5D8AA8", # Medium Slate Blue + "CS": "#5D8AA8", # Medium Slate Blue + "CT": "#5D8AA8", # Medium Slate Blue + "CRX": "#6C5B7B", # Dark Lavender + "CRY": "#6C5B7B", # Dark Lavender + "CRZ": "#6C5B7B", # Dark Lavender + "BERKELEY": "#4A5D6D", # Dark Slate Blue + "FREDKIN": "#4A5D6D", # Dark Slate Blue } diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index 6eec6d41..e4002bf6 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -16,7 +16,7 @@ from ..operations import Gate, Measurement from ..circuit import QubitCircuit -from .color_theme import default_theme +from .color_theme import qutip, light, modern __all__ = [ "MatRenderer", @@ -65,6 +65,7 @@ def __init__( self.bulge = "round4" else: self.bulge = "square" + self.dpi = style.get("dpi", 150) self.padding = style.get("padding", 0.3) self.fontsize = style.get("fontsize", 10) @@ -80,6 +81,15 @@ def __init__( self.title = style.get("title", None) self.fig_height = style.get("fig_height", None) self.fig_width = style.get("fig_width", None) + self.globalcolor = style.get("globalcolor", "#FFFFFF") + + if style.get("theme", "qutip") == "qutip": + self.theme = qutip + elif style.get("theme") == "light": + self.theme = light + self.globalcolor = "#000000" + elif style.get("theme") == "modern": + self.theme = modern self.layer_list = {i: [self.start_pad] for i in range(self.qwires)} @@ -361,7 +371,7 @@ def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: pos * self.wire_sep + self.target_node_r / 2, ), lw=1.5, - color="white", + color=self.globalcolor, zorder=self.zorder["node_label"], ) horizontal_line = plt.Line2D( @@ -377,7 +387,7 @@ def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: ), (pos * self.wire_sep, pos * self.wire_sep), lw=1.5, - color="white", + color=self.globalcolor, zorder=self.zorder["node_label"], ) @@ -515,7 +525,7 @@ def _draw_swap_mark(self, pos: int, xskip: int, color: str) -> None: pos * self.wire_sep - self.gate_height / 2, ], color=color, - linewidth=1.5, + linewidth=2, zorder=self.zorder["gate"], ) dia_right = plt.Line2D( @@ -528,7 +538,7 @@ def _draw_swap_mark(self, pos: int, xskip: int, color: str) -> None: pos * self.wire_sep - self.gate_height / 2, ], color=color, - linewidth=1.5, + linewidth=2, zorder=self.zorder["gate"], ) self.ax.add_line(dia_left) @@ -869,11 +879,9 @@ def canvas_plot(self) -> None: if isinstance(gate, Gate): style = gate.style if gate.style is not None else {} self.text = style.get("text", gate.name) - self.color = style.get( - "color", default_theme.get(gate.name, "k") - ) + self.color = style.get("color", self.theme.get(gate.name, "k")) self.fontsize = style.get("fontsize", self.fontsize) - self.fontcolor = style.get("fontcolor", "#FFFFFF") + self.fontcolor = style.get("fontcolor", self.globalcolor) self.fontweight = style.get("fontweight", "normal") self.fontstyle = style.get("fontstyle", "normal") self.fontfamily = style.get("fontfamily", "monospace") From bf66fa25c0a2b62e6ae32bc8267841c3cb240a2f Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Tue, 23 Jul 2024 03:45:17 +0530 Subject: [PATCH 12/20] cleaned and added dataclass --- src/qutip_qip/circuit/mat_renderer.py | 337 +++++++++++++++----------- 1 file changed, 197 insertions(+), 140 deletions(-) diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index e4002bf6..3038b718 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -2,7 +2,8 @@ Module for rendering a quantum circuit using matplotlib library. """ -from typing import List, Union +from typing import Union, Optional, List, Dict +from dataclasses import dataclass import numpy as np import matplotlib.pyplot as plt @@ -23,6 +24,53 @@ ] +@dataclass +class StyleConfig: + """ + Dataclass to store the style configuration for circuit customization. + """ + + dpi: int = 150 + fontsize: int = 10 + end_wire_ext: int = 2 + padding: float = 0.3 + gate_margin: float = 0.15 + wire_sep: float = 0.5 + layer_sep: float = 0.5 + gate_pad: float = 0.05 + label_pad: float = 0.1 + fig_height: Optional[float] = None + fig_width: Optional[float] = None + bulge: Union[str, bool] = True + align_layer: bool = False + theme: Optional[Union[str, Dict]] = "qutip" + title: Optional[str] = None + bgcolor: Optional[str] = None + color: Optional[str] = None + wire_label: Optional[List] = None + + def __post_init__(self): + if isinstance(self.bulge, bool): + self.bulge = "round4" if self.bulge else "square" + + self.bgcolor = self.bgcolor or ( + "#EEEEEE" if self.theme == "light" else "#FFFFFF" + ) + self.color = self.color or ( + "#000000" if self.theme == "light" else "#FFFFFF" + ) + if self.theme == "qutip": + self.theme = qutip + elif self.theme == "light": + self.theme = light + elif self.theme == "modern": + self.theme = modern + else: + raise ValueError( + f"Invalid theme: {self.theme}. Must be selectec from 'qutip', 'light', or 'modern'." + ) + + class MatRenderer: """ Class to render a quantum circuit using matplotlib. @@ -32,6 +80,9 @@ class MatRenderer: qc : QuantumCircuit Object The quantum circuit to be rendered. + ax : Axes Object, optional + The axes object to plot the circuit. The default is None. + style : dict, optional The style dictionary for the circuit. The default is None. """ @@ -49,49 +100,20 @@ def __init__( self.cwires = qc.num_cbits self.cwire_sep = 0.02 - self.gate_height = 0.2 - self.gate_width = 0.2 + self.min_gate_height = 0.2 + self.min_gate_width = 0.2 self.default_layers = 2 self.arrow_lenght = 0.06 self.connector_r = 0.01 self.target_node_r = 0.12 - self.control_node_radius = 0.05 + self.control_node_r = 0.05 self.display_layer_len = 0 self.start_pad = 0.1 + self.layer_list = {i: [self.start_pad] for i in range(self.qwires)} # user defined style style = {} if style is None else style - if style.get("bulge", True): - self.bulge = "round4" - else: - self.bulge = "square" - - self.dpi = style.get("dpi", 150) - self.padding = style.get("padding", 0.3) - self.fontsize = style.get("fontsize", 10) - self.gate_margin = style.get("condense", 0.15) - self.wire_sep = style.get("wire_sep", 0.5) - self.layer_sep = style.get("layer_sep", 0.5) - self.gate_pad = style.get("gate_pad", 0.05) - self.label_pad = style.get("label_pad", 0.1) - self.bgcolor = style.get("bgcolor", "#FFFFFF") - self.wire_label = style.get("wire_label", None) - self.end_wire_ext = style.get("end_wire_ext", 2) - self.align_layer = style.get("align_layer", False) - self.title = style.get("title", None) - self.fig_height = style.get("fig_height", None) - self.fig_width = style.get("fig_width", None) - self.globalcolor = style.get("globalcolor", "#FFFFFF") - - if style.get("theme", "qutip") == "qutip": - self.theme = qutip - elif style.get("theme") == "light": - self.theme = light - self.globalcolor = "#000000" - elif style.get("theme") == "modern": - self.theme = modern - - self.layer_list = {i: [self.start_pad] for i in range(self.qwires)} + self.style = StyleConfig(**style) # fig config self.zorder = { @@ -105,14 +127,14 @@ def __init__( "node_label": 3, } self.fig_height = ( - (self.qwires + self.cwires) * self.wire_sep * 0.393701 * 3 + (self.qwires + self.cwires) * self.style.wire_sep * 0.393701 * 3 ) self.fig_width = 10 if self.ax is None: self.fig, self.ax = plt.subplots( figsize=(self.fig_width, self.fig_height), - dpi=self.dpi, - facecolor=self.bgcolor, + dpi=self.style.dpi, + facecolor=self.style.bgcolor, ) self.canvas_plot() @@ -131,7 +153,7 @@ def _get_xskip(self, wire_list: List[int], layer: int) -> float: """ xskip = [] - if self.align_layer: + if self.style.align_layer: wire_list = list(range(self.qwires)) for wire in wire_list: @@ -221,15 +243,15 @@ def _manage_layers( if len(self.layer_list[wire]) > layer: if ( self.layer_list[wire][layer] - < gate_width + self.gate_margin * 2 + < gate_width + self.style.gate_margin * 2 ): self.layer_list[wire][layer] = ( - gate_width + self.gate_margin * 2 + gate_width + self.style.gate_margin * 2 ) else: temp = xskip - sum(self.layer_list[wire]) if xskip != 0 else 0 self.layer_list[wire].append( - temp + gate_width + self.gate_margin * 2 + temp + gate_width + self.style.gate_margin * 2 ) def _add_wire(self) -> None: @@ -238,13 +260,13 @@ def _add_wire(self) -> None: """ max_len = ( max([sum(self.layer_list[i]) for i in range(self.qwires)]) - + self.end_wire_ext * self.layer_sep + + self.style.end_wire_ext * self.style.layer_sep ) for i in range(self.qwires): wire = plt.Line2D( [0, max_len], - [i * self.wire_sep, i * self.wire_sep], + [i * self.style.wire_sep, i * self.style.wire_sep], lw=1, color="k", zorder=self.zorder["wire"], @@ -255,8 +277,8 @@ def _add_wire(self) -> None: wire_up = plt.Line2D( [0, max_len], [ - (i + self.qwires) * self.wire_sep, - (i + self.qwires) * self.wire_sep, + (i + self.qwires) * self.style.wire_sep, + (i + self.qwires) * self.style.wire_sep, ], lw=1, color="k", @@ -265,8 +287,8 @@ def _add_wire(self) -> None: wire_down = plt.Line2D( [0, max_len], [ - (i + self.qwires) * self.wire_sep, - (i + self.qwires) * self.wire_sep, + (i + self.qwires) * self.style.wire_sep, + (i + self.qwires) * self.style.wire_sep, ], lw=1, color="k", @@ -280,31 +302,31 @@ def _add_wire_labels(self) -> None: Adds the wire labels to the circuit. """ - if self.wire_label is None: + if self.style.wire_label is None: default_labels = [f"$c_{{{i}}}$" for i in range(self.cwires)] + [ f"$q_{{{i}}}$" for i in range(self.qwires) ] - self.wire_label = default_labels + self.style.wire_label = default_labels self.max_label_width = max( [ self._get_text_width( label, - self.fontsize, + self.style.fontsize, "normal", "monospace", "normal", ) - for label in self.wire_label + for label in self.style.wire_label ] ) - for i, label in enumerate(self.wire_label): + for i, label in enumerate(self.style.wire_label): wire_label = plt.Text( - -self.label_pad, - i * self.wire_sep, + -self.style.label_pad, + i * self.style.wire_sep, label, - fontsize=self.fontsize, + fontsize=self.style.fontsize, verticalalignment="center", horizontalalignment="right", zorder=self.zorder["wire_label"], @@ -330,8 +352,11 @@ def _draw_control_node(self, pos: int, xskip: float, color: str) -> None: pos = pos + self.cwires control_node = Circle( - (xskip + self.gate_margin + self.gate_pad, pos * self.wire_sep), - self.control_node_radius, + ( + xskip + self.style.gate_margin + self.style.gate_pad, + pos * self.style.wire_sep, + ), + self.control_node_r, color=color, zorder=self.zorder["node"], ) @@ -356,38 +381,41 @@ def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: pos = pos + self.cwires target_node = Circle( - (xskip + self.gate_margin + self.gate_pad, pos * self.wire_sep), + ( + xskip + self.style.gate_margin + self.style.gate_pad, + pos * self.style.wire_sep, + ), self.target_node_r, color=color, zorder=self.zorder["node"], ) vertical_line = plt.Line2D( ( - xskip + self.gate_margin + self.gate_pad, - xskip + self.gate_margin + self.gate_pad, + xskip + self.style.gate_margin + self.style.gate_pad, + xskip + self.style.gate_margin + self.style.gate_pad, ), ( - pos * self.wire_sep - self.target_node_r / 2, - pos * self.wire_sep + self.target_node_r / 2, + pos * self.style.wire_sep - self.target_node_r / 2, + pos * self.style.wire_sep + self.target_node_r / 2, ), lw=1.5, - color=self.globalcolor, + color=self.style.color, zorder=self.zorder["node_label"], ) horizontal_line = plt.Line2D( ( xskip - + self.gate_margin - + self.gate_pad + + self.style.gate_margin + + self.style.gate_pad - self.target_node_r / 2, xskip - + self.gate_margin - + self.gate_pad + + self.style.gate_margin + + self.style.gate_pad + self.target_node_r / 2, ), - (pos * self.wire_sep, pos * self.wire_sep), + (pos * self.style.wire_sep, pos * self.style.wire_sep), lw=1.5, - color=self.globalcolor, + color=self.style.color, zorder=self.zorder["node_label"], ) @@ -420,10 +448,10 @@ def _draw_qbridge( bridge = plt.Line2D( [ - xskip + self.gate_margin + self.gate_pad, - xskip + self.gate_margin + self.gate_pad, + xskip + self.style.gate_margin + self.style.gate_pad, + xskip + self.style.gate_margin + self.style.gate_pad, ], - [pos1 * self.wire_sep, pos2 * self.wire_sep], + [pos1 * self.style.wire_sep, pos2 * self.style.wire_sep], color=color, zorder=self.zorder["bridge"], ) @@ -454,36 +482,42 @@ def _draw_cbridge( cbridge_l = plt.Line2D( ( xskip - + self.gate_margin - + self.gate_width / 2 + + self.style.gate_margin + + self.min_gate_width / 2 - self.cwire_sep, xskip - + self.gate_margin - + self.gate_width / 2 + + self.style.gate_margin + + self.min_gate_width / 2 - self.cwire_sep, ), - (c_pos * self.wire_sep + self.arrow_lenght, q_pos * self.wire_sep), + ( + c_pos * self.style.wire_sep + self.arrow_lenght, + q_pos * self.style.wire_sep, + ), color=color, zorder=self.zorder["bridge"], ) cbridge_r = plt.Line2D( ( xskip - + self.gate_margin - + self.gate_width / 2 + + self.style.gate_margin + + self.min_gate_width / 2 + self.cwire_sep, xskip - + self.gate_margin - + self.gate_width / 2 + + self.style.gate_margin + + self.min_gate_width / 2 + self.cwire_sep, ), - (c_pos * self.wire_sep + self.arrow_lenght, q_pos * self.wire_sep), + ( + c_pos * self.style.wire_sep + self.arrow_lenght, + q_pos * self.style.wire_sep, + ), color=color, zorder=self.zorder["bridge"], ) end_arrow = FancyArrow( - xskip + self.gate_margin + self.gate_width / 2, - c_pos * self.wire_sep + self.arrow_lenght, + xskip + self.style.gate_margin + self.min_gate_width / 2, + c_pos * self.style.wire_sep + self.arrow_lenght, 0, -self.cwire_sep * 3, width=0, @@ -517,12 +551,18 @@ def _draw_swap_mark(self, pos: int, xskip: int, color: str) -> None: dia_left = plt.Line2D( [ - xskip + self.gate_margin + self.gate_pad + self.gate_width / 3, - xskip + self.gate_margin + self.gate_pad - self.gate_width / 3, + xskip + + self.style.gate_margin + + self.style.gate_pad + + self.min_gate_width / 3, + xskip + + self.style.gate_margin + + self.style.gate_pad + - self.min_gate_width / 3, ], [ - pos * self.wire_sep + self.gate_height / 2, - pos * self.wire_sep - self.gate_height / 2, + pos * self.style.wire_sep + self.min_gate_height / 2, + pos * self.style.wire_sep - self.min_gate_height / 2, ], color=color, linewidth=2, @@ -530,12 +570,18 @@ def _draw_swap_mark(self, pos: int, xskip: int, color: str) -> None: ) dia_right = plt.Line2D( [ - xskip + self.gate_margin + self.gate_pad - self.gate_width / 3, - xskip + self.gate_margin + self.gate_pad + self.gate_width / 3, + xskip + + self.style.gate_margin + + self.style.gate_pad + - self.min_gate_width / 3, + xskip + + self.style.gate_margin + + self.style.gate_pad + + self.min_gate_width / 3, ], [ - pos * self.wire_sep + self.gate_height / 2, - pos * self.wire_sep - self.gate_height / 2, + pos * self.style.wire_sep + self.min_gate_height / 2, + pos * self.style.wire_sep - self.min_gate_height / 2, ], color=color, linewidth=2, @@ -604,13 +650,15 @@ def _draw_singleq_gate(self, gate: Gate, layer: int) -> None: self.fontfamily, self.fontstyle, ) - gate_width = max(text_width + self.gate_pad * 2, self.gate_width) + gate_width = max( + text_width + self.style.gate_pad * 2, self.min_gate_width + ) gate_text = plt.Text( self._get_xskip([gate_wire], layer) - + self.gate_margin + + self.style.gate_margin + gate_width / 2, - (gate_wire + self.cwires) * self.wire_sep, + (gate_wire + self.cwires) * self.style.wire_sep, text, color=self.fontcolor, fontsize=self.fontsize, @@ -623,13 +671,13 @@ def _draw_singleq_gate(self, gate: Gate, layer: int) -> None: ) gate_patch = FancyBboxPatch( ( - self._get_xskip([gate_wire], layer) + self.gate_margin, - (gate_wire + self.cwires) * self.wire_sep - - self.gate_height / 2, + self._get_xskip([gate_wire], layer) + self.style.gate_margin, + (gate_wire + self.cwires) * self.style.wire_sep + - self.min_gate_height / 2, ), gate_width, - self.gate_height, - boxstyle=self.bulge, + self.min_gate_height, + boxstyle=self.style.bulge, mutation_scale=0.3, facecolor=self.color, edgecolor=self.color, @@ -665,7 +713,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: gate.targets[0], gate.controls[0], com_xskip, self.color ) self._manage_layers( - 2 * self.gate_pad + self.target_node_r / 3, + 2 * self.style.gate_pad + self.target_node_r / 3, wire_list, layer, com_xskip, @@ -678,7 +726,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: gate.targets[0], gate.targets[1], com_xskip, self.color ) self._manage_layers( - 2 * (self.gate_pad + self.gate_width / 3), + 2 * (self.style.gate_pad + self.min_gate_width / 3), wire_list, layer, com_xskip, @@ -695,7 +743,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: gate.targets[0], gate.controls[1], com_xskip, self.color ) self._manage_layers( - 2 * self.gate_pad + self.target_node_r / 3, + 2 * self.style.gate_pad + self.target_node_r / 3, wire_list, layer, com_xskip, @@ -711,12 +759,14 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: self.fontfamily, self.fontstyle, ) - gate_width = max(text_width + self.gate_pad * 2, self.gate_width) + gate_width = max( + text_width + self.style.gate_pad * 2, self.min_gate_width + ) xskip = self._get_xskip(wire_list, layer) gate_text = plt.Text( - xskip + self.gate_margin + gate_width / 2, - (adj_targets[0] + adj_targets[-1]) / 2 * self.wire_sep, + xskip + self.style.gate_margin + gate_width / 2, + (adj_targets[0] + adj_targets[-1]) / 2 * self.style.wire_sep, self.text, color=self.fontcolor, fontsize=self.fontsize, @@ -730,13 +780,14 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: gate_patch = FancyBboxPatch( ( - xskip + self.gate_margin, - adj_targets[0] * self.wire_sep - self.gate_height / 2, + xskip + self.style.gate_margin, + adj_targets[0] * self.style.wire_sep + - self.min_gate_height / 2, ), gate_width, - self.gate_height - + self.wire_sep * (adj_targets[-1] - adj_targets[0]), - boxstyle=self.bulge, + self.min_gate_height + + self.style.wire_sep * (adj_targets[-1] - adj_targets[0]), + boxstyle=self.style.bulge, mutation_scale=0.3, facecolor=self.color, edgecolor=self.color, @@ -747,8 +798,8 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: for i in range(len(gate.targets)): connector_l = Circle( ( - xskip + self.gate_margin - self.connector_r, - (adj_targets[i]) * self.wire_sep, + xskip + self.style.gate_margin - self.connector_r, + (adj_targets[i]) * self.style.wire_sep, ), self.connector_r, color=self.fontcolor, @@ -757,10 +808,10 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: connector_r = Circle( ( xskip - + self.gate_margin + + self.style.gate_margin + gate_width + self.connector_r, - (adj_targets[i]) * self.wire_sep, + (adj_targets[i]) * self.style.wire_sep, ), self.connector_r, color=self.fontcolor, @@ -807,12 +858,13 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: ) measure_box = FancyBboxPatch( ( - xskip + self.gate_margin, - (q_pos + self.cwires) * self.wire_sep - self.gate_height / 2, + xskip + self.style.gate_margin, + (q_pos + self.cwires) * self.style.wire_sep + - self.min_gate_height / 2, ), - self.gate_width, - self.gate_height, - boxstyle=self.bulge, + self.min_gate_width, + self.min_gate_height, + boxstyle=self.style.bulge, mutation_scale=0.3, facecolor="white", edgecolor="k", @@ -820,11 +872,12 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: ) arc = Arc( ( - xskip + self.gate_margin + self.gate_width / 2, - (q_pos + self.cwires) * self.wire_sep - self.gate_height / 2, + xskip + self.style.gate_margin + self.min_gate_width / 2, + (q_pos + self.cwires) * self.style.wire_sep + - self.min_gate_height / 2, ), - self.gate_width * 1.5, - self.gate_height * 1, + self.min_gate_width * 1.5, + self.min_gate_height * 1, angle=0, theta1=0, theta2=180, @@ -832,10 +885,11 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: zorder=self.zorder["gate_label"], ) arrow = FancyArrow( - xskip + self.gate_margin + self.gate_width / 2, - (q_pos + self.cwires) * self.wire_sep - self.gate_height / 2, - self.gate_width * 0.7, - self.gate_height * 0.7, + xskip + self.style.gate_margin + self.min_gate_width / 2, + (q_pos + self.cwires) * self.style.wire_sep + - self.min_gate_height / 2, + self.min_gate_width * 0.7, + self.min_gate_height * 0.7, length_includes_head=True, head_width=0, color="k", @@ -844,7 +898,7 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: self._draw_cbridge(c_pos, q_pos, xskip, "k") self._manage_layers( - self.gate_width, + self.min_gate_width, list(range(0, self.merged_qubits[-1] + 1)), layer, xskip, @@ -879,9 +933,11 @@ def canvas_plot(self) -> None: if isinstance(gate, Gate): style = gate.style if gate.style is not None else {} self.text = style.get("text", gate.name) - self.color = style.get("color", self.theme.get(gate.name, "k")) - self.fontsize = style.get("fontsize", self.fontsize) - self.fontcolor = style.get("fontcolor", self.globalcolor) + self.color = style.get( + "color", self.style.theme.get(gate.name, "k") + ) + self.fontsize = style.get("fontsize", self.style.fontsize) + self.fontcolor = style.get("fontcolor", self.style.color) self.fontweight = style.get("fontweight", "normal") self.fontstyle = style.get("fontstyle", "normal") self.fontfamily = style.get("fontfamily", "monospace") @@ -920,17 +976,18 @@ def _fig_config(self) -> None: """ self.ax.set_ylim( - -self.padding, - self.padding + (self.qwires + self.cwires - 1) * self.wire_sep, + -self.style.padding, + self.style.padding + + (self.qwires + self.cwires - 1) * self.style.wire_sep, ) self.ax.set_xlim( - -self.padding - self.max_label_width - self.label_pad, - self.padding - + self.end_wire_ext * self.layer_sep + -self.style.padding - self.max_label_width - self.style.label_pad, + self.style.padding + + self.style.end_wire_ext * self.style.layer_sep + max([sum(self.layer_list[i]) for i in range(self.qwires)]), ) - if self.title is not None: - self.ax.set_title(self.title, pad=10) + if self.style.title is not None: + self.ax.set_title(self.style.title, pad=10) self.ax.set_aspect("equal") self.ax.axis("off") From 529749371baa944ead41a64877364f289975693b Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Tue, 23 Jul 2024 17:26:42 +0530 Subject: [PATCH 13/20] changed prop. to private --- src/qutip_qip/circuit/mat_renderer.py | 278 +++++++++++++------------- 1 file changed, 139 insertions(+), 139 deletions(-) diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index 3038b718..e9c06f8b 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -91,25 +91,25 @@ def __init__( self, qc: QubitCircuit, ax: Axes = None, - style: dict = None, + **style, ) -> None: - self.qc = qc - self.ax = ax - self.qwires = qc.N - self.cwires = qc.num_cbits - - self.cwire_sep = 0.02 - self.min_gate_height = 0.2 - self.min_gate_width = 0.2 - self.default_layers = 2 - self.arrow_lenght = 0.06 - self.connector_r = 0.01 - self.target_node_r = 0.12 - self.control_node_r = 0.05 - self.display_layer_len = 0 - self.start_pad = 0.1 - self.layer_list = {i: [self.start_pad] for i in range(self.qwires)} + self._qc = qc + self._ax = ax + self._qwires = qc.N + self._cwires = qc.num_cbits + + self._cwire_sep = 0.02 + self._min_gate_height = 0.2 + self._min_gate_width = 0.2 + self._default_layers = 2 + self._arrow_lenght = 0.06 + self._connector_r = 0.01 + self._target_node_r = 0.12 + self._control_node_r = 0.05 + self._display_layer_len = 0 + self._start_pad = 0.1 + self._layer_list = {i: [self._start_pad] for i in range(self._qwires)} # user defined style style = {} if style is None else style @@ -127,11 +127,11 @@ def __init__( "node_label": 3, } self.fig_height = ( - (self.qwires + self.cwires) * self.style.wire_sep * 0.393701 * 3 + (self._qwires + self._cwires) * self.style.wire_sep * 0.393701 * 3 ) self.fig_width = 10 - if self.ax is None: - self.fig, self.ax = plt.subplots( + if self._ax is None: + self.fig, self._ax = plt.subplots( figsize=(self.fig_width, self.fig_height), dpi=self.style.dpi, facecolor=self.style.bgcolor, @@ -154,10 +154,10 @@ def _get_xskip(self, wire_list: List[int], layer: int) -> float: xskip = [] if self.style.align_layer: - wire_list = list(range(self.qwires)) + wire_list = list(range(self._qwires)) for wire in wire_list: - xskip.append(sum(self.layer_list[wire][:layer])) + xskip.append(sum(self._layer_list[wire][:layer])) return max(xskip) @@ -203,12 +203,12 @@ def _get_text_width( fontfamily=fontfamily, fontstyle=fontstyle, ) - self.ax.add_artist(text_obj) + self._ax.add_artist(text_obj) bbox = text_obj.get_window_extent( - renderer=self.ax.figure.canvas.get_renderer() + renderer=self._ax.figure.canvas.get_renderer() ) - inv = self.ax.transData.inverted() + inv = self._ax.transData.inverted() bbox_data = bbox.transformed(inv) text_obj.remove() @@ -240,17 +240,17 @@ def _manage_layers( """ for wire in wire_list: - if len(self.layer_list[wire]) > layer: + if len(self._layer_list[wire]) > layer: if ( - self.layer_list[wire][layer] + self._layer_list[wire][layer] < gate_width + self.style.gate_margin * 2 ): - self.layer_list[wire][layer] = ( + self._layer_list[wire][layer] = ( gate_width + self.style.gate_margin * 2 ) else: - temp = xskip - sum(self.layer_list[wire]) if xskip != 0 else 0 - self.layer_list[wire].append( + temp = xskip - sum(self._layer_list[wire]) if xskip != 0 else 0 + self._layer_list[wire].append( temp + gate_width + self.style.gate_margin * 2 ) @@ -259,11 +259,11 @@ def _add_wire(self) -> None: Adds the wires to the circuit. """ max_len = ( - max([sum(self.layer_list[i]) for i in range(self.qwires)]) + max([sum(self._layer_list[i]) for i in range(self._qwires)]) + self.style.end_wire_ext * self.style.layer_sep ) - for i in range(self.qwires): + for i in range(self._qwires): wire = plt.Line2D( [0, max_len], [i * self.style.wire_sep, i * self.style.wire_sep], @@ -271,14 +271,14 @@ def _add_wire(self) -> None: color="k", zorder=self.zorder["wire"], ) - self.ax.add_line(wire) + self._ax.add_line(wire) - for i in range(self.cwires): + for i in range(self._cwires): wire_up = plt.Line2D( [0, max_len], [ - (i + self.qwires) * self.style.wire_sep, - (i + self.qwires) * self.style.wire_sep, + (i + self._qwires) * self.style.wire_sep, + (i + self._qwires) * self.style.wire_sep, ], lw=1, color="k", @@ -287,15 +287,15 @@ def _add_wire(self) -> None: wire_down = plt.Line2D( [0, max_len], [ - (i + self.qwires) * self.style.wire_sep, - (i + self.qwires) * self.style.wire_sep, + (i + self._qwires) * self.style.wire_sep, + (i + self._qwires) * self.style.wire_sep, ], lw=1, color="k", zorder=self.zorder["wire"], ) - self.ax.add_line(wire_up) - self.ax.add_line(wire_down) + self._ax.add_line(wire_up) + self._ax.add_line(wire_down) def _add_wire_labels(self) -> None: """ @@ -303,8 +303,8 @@ def _add_wire_labels(self) -> None: """ if self.style.wire_label is None: - default_labels = [f"$c_{{{i}}}$" for i in range(self.cwires)] + [ - f"$q_{{{i}}}$" for i in range(self.qwires) + default_labels = [f"$c_{{{i}}}$" for i in range(self._cwires)] + [ + f"$q_{{{i}}}$" for i in range(self._qwires) ] self.style.wire_label = default_labels @@ -331,7 +331,7 @@ def _add_wire_labels(self) -> None: horizontalalignment="right", zorder=self.zorder["wire_label"], ) - self.ax.add_artist(wire_label) + self._ax.add_artist(wire_label) def _draw_control_node(self, pos: int, xskip: float, color: str) -> None: """ @@ -349,18 +349,18 @@ def _draw_control_node(self, pos: int, xskip: float, color: str) -> None: The color of the control node. HEX code or color name supported by matplotlib are valid. """ - pos = pos + self.cwires + pos = pos + self._cwires control_node = Circle( ( xskip + self.style.gate_margin + self.style.gate_pad, pos * self.style.wire_sep, ), - self.control_node_r, + self._control_node_r, color=color, zorder=self.zorder["node"], ) - self.ax.add_artist(control_node) + self._ax.add_artist(control_node) def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: """ @@ -378,14 +378,14 @@ def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: The color of the control node. HEX code or color name supported by matplotlib are valid. """ - pos = pos + self.cwires + pos = pos + self._cwires target_node = Circle( ( xskip + self.style.gate_margin + self.style.gate_pad, pos * self.style.wire_sep, ), - self.target_node_r, + self._target_node_r, color=color, zorder=self.zorder["node"], ) @@ -395,8 +395,8 @@ def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: xskip + self.style.gate_margin + self.style.gate_pad, ), ( - pos * self.style.wire_sep - self.target_node_r / 2, - pos * self.style.wire_sep + self.target_node_r / 2, + pos * self.style.wire_sep - self._target_node_r / 2, + pos * self.style.wire_sep + self._target_node_r / 2, ), lw=1.5, color=self.style.color, @@ -407,11 +407,11 @@ def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: xskip + self.style.gate_margin + self.style.gate_pad - - self.target_node_r / 2, + - self._target_node_r / 2, xskip + self.style.gate_margin + self.style.gate_pad - + self.target_node_r / 2, + + self._target_node_r / 2, ), (pos * self.style.wire_sep, pos * self.style.wire_sep), lw=1.5, @@ -419,9 +419,9 @@ def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: zorder=self.zorder["node_label"], ) - self.ax.add_artist(target_node) - self.ax.add_line(vertical_line) - self.ax.add_line(horizontal_line) + self._ax.add_artist(target_node) + self._ax.add_line(vertical_line) + self._ax.add_line(horizontal_line) def _draw_qbridge( self, pos1: int, pos2: int, xskip: float, color: str @@ -443,8 +443,8 @@ def _draw_qbridge( color : str The color of the control node. HEX code or color name supported by matplotlib are valid. """ - pos2 = pos2 + self.cwires - pos1 = pos1 + self.cwires + pos2 = pos2 + self._cwires + pos1 = pos1 + self._cwires bridge = plt.Line2D( [ @@ -455,7 +455,7 @@ def _draw_qbridge( color=color, zorder=self.zorder["bridge"], ) - self.ax.add_line(bridge) + self._ax.add_line(bridge) def _draw_cbridge( self, c_pos: int, q_pos: int, xskip: float, color: str @@ -477,21 +477,21 @@ def _draw_cbridge( color : str The color of the bridge. """ - q_pos = q_pos + self.cwires + q_pos = q_pos + self._cwires cbridge_l = plt.Line2D( ( xskip + self.style.gate_margin - + self.min_gate_width / 2 - - self.cwire_sep, + + self._min_gate_width / 2 + - self._cwire_sep, xskip + self.style.gate_margin - + self.min_gate_width / 2 - - self.cwire_sep, + + self._min_gate_width / 2 + - self._cwire_sep, ), ( - c_pos * self.style.wire_sep + self.arrow_lenght, + c_pos * self.style.wire_sep + self._arrow_lenght, q_pos * self.style.wire_sep, ), color=color, @@ -501,35 +501,35 @@ def _draw_cbridge( ( xskip + self.style.gate_margin - + self.min_gate_width / 2 - + self.cwire_sep, + + self._min_gate_width / 2 + + self._cwire_sep, xskip + self.style.gate_margin - + self.min_gate_width / 2 - + self.cwire_sep, + + self._min_gate_width / 2 + + self._cwire_sep, ), ( - c_pos * self.style.wire_sep + self.arrow_lenght, + c_pos * self.style.wire_sep + self._arrow_lenght, q_pos * self.style.wire_sep, ), color=color, zorder=self.zorder["bridge"], ) end_arrow = FancyArrow( - xskip + self.style.gate_margin + self.min_gate_width / 2, - c_pos * self.style.wire_sep + self.arrow_lenght, + xskip + self.style.gate_margin + self._min_gate_width / 2, + c_pos * self.style.wire_sep + self._arrow_lenght, 0, - -self.cwire_sep * 3, + -self._cwire_sep * 3, width=0, - head_width=self.cwire_sep * 5, - head_length=self.cwire_sep * 3, + head_width=self._cwire_sep * 5, + head_length=self._cwire_sep * 3, length_includes_head=True, color="k", zorder=self.zorder["bridge"], ) - self.ax.add_line(cbridge_l) - self.ax.add_line(cbridge_r) - self.ax.add_artist(end_arrow) + self._ax.add_line(cbridge_l) + self._ax.add_line(cbridge_r) + self._ax.add_artist(end_arrow) def _draw_swap_mark(self, pos: int, xskip: int, color: str) -> None: """ @@ -547,22 +547,22 @@ def _draw_swap_mark(self, pos: int, xskip: int, color: str) -> None: The color of the swap mark. """ - pos = pos + self.cwires + pos = pos + self._cwires dia_left = plt.Line2D( [ xskip + self.style.gate_margin + self.style.gate_pad - + self.min_gate_width / 3, + + self._min_gate_width / 3, xskip + self.style.gate_margin + self.style.gate_pad - - self.min_gate_width / 3, + - self._min_gate_width / 3, ], [ - pos * self.style.wire_sep + self.min_gate_height / 2, - pos * self.style.wire_sep - self.min_gate_height / 2, + pos * self.style.wire_sep + self._min_gate_height / 2, + pos * self.style.wire_sep - self._min_gate_height / 2, ], color=color, linewidth=2, @@ -573,22 +573,22 @@ def _draw_swap_mark(self, pos: int, xskip: int, color: str) -> None: xskip + self.style.gate_margin + self.style.gate_pad - - self.min_gate_width / 3, + - self._min_gate_width / 3, xskip + self.style.gate_margin + self.style.gate_pad - + self.min_gate_width / 3, + + self._min_gate_width / 3, ], [ - pos * self.style.wire_sep + self.min_gate_height / 2, - pos * self.style.wire_sep - self.min_gate_height / 2, + pos * self.style.wire_sep + self._min_gate_height / 2, + pos * self.style.wire_sep - self._min_gate_height / 2, ], color=color, linewidth=2, zorder=self.zorder["gate"], ) - self.ax.add_line(dia_left) - self.ax.add_line(dia_right) + self._ax.add_line(dia_left) + self._ax.add_line(dia_right) def to_pi_fraction(self, value: float, tolerance: float = 0.01) -> str: """ @@ -651,14 +651,14 @@ def _draw_singleq_gate(self, gate: Gate, layer: int) -> None: self.fontstyle, ) gate_width = max( - text_width + self.style.gate_pad * 2, self.min_gate_width + text_width + self.style.gate_pad * 2, self._min_gate_width ) gate_text = plt.Text( self._get_xskip([gate_wire], layer) + self.style.gate_margin + gate_width / 2, - (gate_wire + self.cwires) * self.style.wire_sep, + (gate_wire + self._cwires) * self.style.wire_sep, text, color=self.fontcolor, fontsize=self.fontsize, @@ -672,11 +672,11 @@ def _draw_singleq_gate(self, gate: Gate, layer: int) -> None: gate_patch = FancyBboxPatch( ( self._get_xskip([gate_wire], layer) + self.style.gate_margin, - (gate_wire + self.cwires) * self.style.wire_sep - - self.min_gate_height / 2, + (gate_wire + self._cwires) * self.style.wire_sep + - self._min_gate_height / 2, ), gate_width, - self.min_gate_height, + self._min_gate_height, boxstyle=self.style.bulge, mutation_scale=0.3, facecolor=self.color, @@ -684,8 +684,8 @@ def _draw_singleq_gate(self, gate: Gate, layer: int) -> None: zorder=self.zorder["gate"], ) - self.ax.add_artist(gate_text) - self.ax.add_patch(gate_patch) + self._ax.add_artist(gate_text) + self._ax.add_patch(gate_patch) self._manage_layers(gate_width, [gate_wire], layer) def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: @@ -713,7 +713,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: gate.targets[0], gate.controls[0], com_xskip, self.color ) self._manage_layers( - 2 * self.style.gate_pad + self.target_node_r / 3, + 2 * self.style.gate_pad + self._target_node_r / 3, wire_list, layer, com_xskip, @@ -726,7 +726,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: gate.targets[0], gate.targets[1], com_xskip, self.color ) self._manage_layers( - 2 * (self.style.gate_pad + self.min_gate_width / 3), + 2 * (self.style.gate_pad + self._min_gate_width / 3), wire_list, layer, com_xskip, @@ -743,7 +743,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: gate.targets[0], gate.controls[1], com_xskip, self.color ) self._manage_layers( - 2 * self.style.gate_pad + self.target_node_r / 3, + 2 * self.style.gate_pad + self._target_node_r / 3, wire_list, layer, com_xskip, @@ -751,7 +751,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: else: - adj_targets = [i + self.cwires for i in sorted(gate.targets)] + adj_targets = [i + self._cwires for i in sorted(gate.targets)] text_width = self._get_text_width( self.text, self.fontsize, @@ -760,7 +760,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: self.fontstyle, ) gate_width = max( - text_width + self.style.gate_pad * 2, self.min_gate_width + text_width + self.style.gate_pad * 2, self._min_gate_width ) xskip = self._get_xskip(wire_list, layer) @@ -782,10 +782,10 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: ( xskip + self.style.gate_margin, adj_targets[0] * self.style.wire_sep - - self.min_gate_height / 2, + - self._min_gate_height / 2, ), gate_width, - self.min_gate_height + self._min_gate_height + self.style.wire_sep * (adj_targets[-1] - adj_targets[0]), boxstyle=self.style.bulge, mutation_scale=0.3, @@ -798,10 +798,10 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: for i in range(len(gate.targets)): connector_l = Circle( ( - xskip + self.style.gate_margin - self.connector_r, + xskip + self.style.gate_margin - self._connector_r, (adj_targets[i]) * self.style.wire_sep, ), - self.connector_r, + self._connector_r, color=self.fontcolor, zorder=self.zorder["connector"], ) @@ -810,15 +810,15 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: xskip + self.style.gate_margin + gate_width - + self.connector_r, + + self._connector_r, (adj_targets[i]) * self.style.wire_sep, ), - self.connector_r, + self._connector_r, color=self.fontcolor, zorder=self.zorder["connector"], ) - self.ax.add_artist(connector_l) - self.ax.add_artist(connector_r) + self._ax.add_artist(connector_l) + self._ax.add_artist(connector_r) # add cbridge if control qubits are present if gate.controls is not None: @@ -833,8 +833,8 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: self.color, ) - self.ax.add_artist(gate_text) - self.ax.add_patch(gate_patch) + self._ax.add_artist(gate_text) + self._ax.add_patch(gate_patch) self._manage_layers(gate_width, wire_list, layer, xskip) def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: @@ -859,11 +859,11 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: measure_box = FancyBboxPatch( ( xskip + self.style.gate_margin, - (q_pos + self.cwires) * self.style.wire_sep - - self.min_gate_height / 2, + (q_pos + self._cwires) * self.style.wire_sep + - self._min_gate_height / 2, ), - self.min_gate_width, - self.min_gate_height, + self._min_gate_width, + self._min_gate_height, boxstyle=self.style.bulge, mutation_scale=0.3, facecolor="white", @@ -872,12 +872,12 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: ) arc = Arc( ( - xskip + self.style.gate_margin + self.min_gate_width / 2, - (q_pos + self.cwires) * self.style.wire_sep - - self.min_gate_height / 2, + xskip + self.style.gate_margin + self._min_gate_width / 2, + (q_pos + self._cwires) * self.style.wire_sep + - self._min_gate_height / 2, ), - self.min_gate_width * 1.5, - self.min_gate_height * 1, + self._min_gate_width * 1.5, + self._min_gate_height * 1, angle=0, theta1=0, theta2=180, @@ -885,11 +885,11 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: zorder=self.zorder["gate_label"], ) arrow = FancyArrow( - xskip + self.style.gate_margin + self.min_gate_width / 2, - (q_pos + self.cwires) * self.style.wire_sep - - self.min_gate_height / 2, - self.min_gate_width * 0.7, - self.min_gate_height * 0.7, + xskip + self.style.gate_margin + self._min_gate_width / 2, + (q_pos + self._cwires) * self.style.wire_sep + - self._min_gate_height / 2, + self._min_gate_width * 0.7, + self._min_gate_height * 0.7, length_includes_head=True, head_width=0, color="k", @@ -898,14 +898,14 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: self._draw_cbridge(c_pos, q_pos, xskip, "k") self._manage_layers( - self.min_gate_width, + self._min_gate_width, list(range(0, self.merged_qubits[-1] + 1)), layer, xskip, ) - self.ax.add_patch(measure_box) - self.ax.add_artist(arc) - self.ax.add_artist(arrow) + self._ax.add_patch(measure_box) + self._ax.add_artist(arc) + self._ax.add_artist(arrow) def canvas_plot(self) -> None: """ @@ -914,14 +914,14 @@ def canvas_plot(self) -> None: self._add_wire_labels() - for gate in self.qc.gates: + for gate in self._qc.gates: if isinstance(gate, Measurement): self.merged_qubits = gate.targets.copy() self.merged_qubits.sort() find_layer = [ - len(self.layer_list[i]) + len(self._layer_list[i]) for i in range(0, self.merged_qubits[-1] + 1) ] self._draw_measure( @@ -954,7 +954,7 @@ def canvas_plot(self) -> None: self.merged_qubits.sort() find_layer = [ - len(self.layer_list[i]) + len(self._layer_list[i]) for i in range( self.merged_qubits[0], self.merged_qubits[-1] + 1 ) @@ -963,7 +963,7 @@ def canvas_plot(self) -> None: else: self._draw_singleq_gate( - gate, len(self.layer_list[gate.targets[0]]) + gate, len(self._layer_list[gate.targets[0]]) ) self._add_wire() @@ -975,21 +975,21 @@ def _fig_config(self) -> None: Configure the figure settings. """ - self.ax.set_ylim( + self._ax.set_ylim( -self.style.padding, self.style.padding - + (self.qwires + self.cwires - 1) * self.style.wire_sep, + + (self._qwires + self._cwires - 1) * self.style.wire_sep, ) - self.ax.set_xlim( + self._ax.set_xlim( -self.style.padding - self.max_label_width - self.style.label_pad, self.style.padding + self.style.end_wire_ext * self.style.layer_sep - + max([sum(self.layer_list[i]) for i in range(self.qwires)]), + + max([sum(self._layer_list[i]) for i in range(self._qwires)]), ) if self.style.title is not None: - self.ax.set_title(self.style.title, pad=10) - self.ax.set_aspect("equal") - self.ax.axis("off") + self._ax.set_title(self.style.title, pad=10) + self._ax.set_aspect("equal") + self._ax.axis("off") def save(self, filename: str, **kwargs) -> None: """ From f343041c7b40cf6de0b026db34b942a18173a9f2 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Tue, 23 Jul 2024 22:32:27 +0530 Subject: [PATCH 14/20] added new color theme, code refactoring --- src/qutip_qip/circuit/color_theme.py | 46 +++++++++- src/qutip_qip/circuit/mat_renderer.py | 118 +++++++++++++++----------- 2 files changed, 113 insertions(+), 51 deletions(-) diff --git a/src/qutip_qip/circuit/color_theme.py b/src/qutip_qip/circuit/color_theme.py index 98b0099d..f2eea62c 100644 --- a/src/qutip_qip/circuit/color_theme.py +++ b/src/qutip_qip/circuit/color_theme.py @@ -3,6 +3,10 @@ """ qutip = { + "bgcolor": "#FFFFFF", # White + "color": "#FFFFFF", # White + "wire_color": "#000000", # Black + "default_gate": "#000000", # Black "H": "#6270CE", # Medium Slate Blue "SNOT": "#6270CE", # Medium Slate Blue "X": "#CB4BF9", # Medium Orchid @@ -30,6 +34,10 @@ } light = { + "bgcolor": "#EEEEEE", # Light Gray + "color": "#000000", # Black + "wire_color": "#000000", # Black + "default_gate": "#D8CDAF", # Bit Dark Beige "H": "#A3C1DA", # Light Blue "SNOT": "#A3C1DA", # Light Blue "X": "#F4A7B9", # Light Pink @@ -40,7 +48,7 @@ "RX": "#B3E6E4", # Light Teal "RY": "#B3E6E4", # Light Teal "RZ": "#B3E6E4", # Light Teal - "CNOT": "#C3C5E6", # Light Indigo + "CNOT": "#B7C9F2", # Light Indigo "CPHASE": "#D5E0F2", # Light Slate Blue "TOFFOLI": "#E6CCE6", # Soft Lavender "SWAP": "#FFB6B6", # Lighter Coral Pink @@ -56,7 +64,43 @@ "FREDKIN": "#CDC1E8", # Light Purple } +dark = { + "bgcolor": "#000000", # Black + "color": "#000000", # Black + "wire_color": "#989898", # Dark Gray + "default_gate": "#D8BFD8", # (Thistle) + "H": "#AFEEEE", # Pale Turquoise + "SNOT": "#AFEEEE", # Pale Turquoise + "X": "#9370DB", # Medium Purple + "Y": "#9370DB", # Medium Purple + "Z": "#9370DB", # Medium Purple + "S": "#B0E0E6", # Powder Blue + "T": "#B0E0E6", # Powder Blue + "RX": "#87CEEB", # Sky Blue + "RY": "#87CEEB", # Sky Blue + "RZ": "#87CEEB", # Sky Blue + "CNOT": "#6495ED", # Cornflower Blue + "CPHASE": "#8A2BE2", # Blue Violet + "TOFFOLI": "#DA70D6", # Orchid + "SWAP": "#BA55D3", # Medium Orchid + "CX": "#4682B4", # Steel Blue + "CY": "#4682B4", # Steel Blue + "CZ": "#4682B4", # Steel Blue + "CS": "#4682B4", # Steel Blue + "CT": "#4682B4", # Steel Blue + "CRX": "#7B68EE", # Medium Slate Blue + "CRY": "#7B68EE", # Medium Slate Blue + "CRZ": "#7B68EE", # Medium Slate Blue + "BERKELEY": "#6A5ACD", # Slate Blue + "FREDKIN": "#6A5ACD", # Slate Blue +} + + modern = { + "bgcolor": "#FFFFFF", # White + "color": "#FFFFFF", # White + "wire_color": "#000000", # Black + "default_gate": "#ED9455", # Slate Orange "H": "#C25454", # Soft Red "SNOT": "#C25454", # Soft Red "X": "#4A5D6D", # Dark Slate Blue diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index e9c06f8b..1d5e7897 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -17,7 +17,7 @@ from ..operations import Gate, Measurement from ..circuit import QubitCircuit -from .color_theme import qutip, light, modern +from .color_theme import qutip, light, dark, modern __all__ = [ "MatRenderer", @@ -40,7 +40,7 @@ class StyleConfig: gate_pad: float = 0.05 label_pad: float = 0.1 fig_height: Optional[float] = None - fig_width: Optional[float] = None + fig_width: Optional[float] = 10 bulge: Union[str, bool] = True align_layer: bool = False theme: Optional[Union[str, Dict]] = "qutip" @@ -48,28 +48,31 @@ class StyleConfig: bgcolor: Optional[str] = None color: Optional[str] = None wire_label: Optional[List] = None + wire_color: Optional[str] = None def __post_init__(self): if isinstance(self.bulge, bool): self.bulge = "round4" if self.bulge else "square" - self.bgcolor = self.bgcolor or ( - "#EEEEEE" if self.theme == "light" else "#FFFFFF" - ) - self.color = self.color or ( - "#000000" if self.theme == "light" else "#FFFFFF" - ) + self.measure_color = "#000000" if self.theme == "qutip": self.theme = qutip elif self.theme == "light": self.theme = light + elif self.theme == "dark": + self.theme = dark + self.measure_color = "#FFFFFF" elif self.theme == "modern": self.theme = modern else: raise ValueError( - f"Invalid theme: {self.theme}. Must be selectec from 'qutip', 'light', or 'modern'." + f"Invalid theme: {self.theme}. Must be selectec from 'qutip', 'light', 'dark', or 'modern'." ) + self.bgcolor = self.bgcolor or self.theme["bgcolor"] + self.color = self.color or self.theme["color"] + self.wire_color = self.wire_color or self.theme["wire_color"] + class MatRenderer: """ @@ -83,8 +86,8 @@ class MatRenderer: ax : Axes Object, optional The axes object to plot the circuit. The default is None. - style : dict, optional - The style dictionary for the circuit. The default is None. + **style + Additional style arguments to be passed to the `StyleConfig` dataclass. """ def __init__( @@ -116,7 +119,7 @@ def __init__( self.style = StyleConfig(**style) # fig config - self.zorder = { + self._zorder = { "wire": 1, "wire_label": 1, "gate": 2, @@ -126,13 +129,19 @@ def __init__( "gate_label": 3, "node_label": 3, } - self.fig_height = ( - (self._qwires + self._cwires) * self.style.wire_sep * 0.393701 * 3 + self.style.fig_height = ( + ( + (self._qwires + self._cwires) + * self.style.wire_sep + * 0.393701 + * 3 + ) + if self.style.fig_height is None + else self.style.fig_height ) - self.fig_width = 10 if self._ax is None: self.fig, self._ax = plt.subplots( - figsize=(self.fig_width, self.fig_height), + figsize=(self.style.fig_width, self.style.fig_height), dpi=self.style.dpi, facecolor=self.style.bgcolor, ) @@ -268,8 +277,8 @@ def _add_wire(self) -> None: [0, max_len], [i * self.style.wire_sep, i * self.style.wire_sep], lw=1, - color="k", - zorder=self.zorder["wire"], + color=self.style.wire_color, + zorder=self._zorder["wire"], ) self._ax.add_line(wire) @@ -281,8 +290,8 @@ def _add_wire(self) -> None: (i + self._qwires) * self.style.wire_sep, ], lw=1, - color="k", - zorder=self.zorder["wire"], + color=self.style.wire_color, + zorder=self._zorder["wire"], ) wire_down = plt.Line2D( [0, max_len], @@ -291,8 +300,8 @@ def _add_wire(self) -> None: (i + self._qwires) * self.style.wire_sep, ], lw=1, - color="k", - zorder=self.zorder["wire"], + color=self.style.wire_color, + zorder=self._zorder["wire"], ) self._ax.add_line(wire_up) self._ax.add_line(wire_down) @@ -329,7 +338,8 @@ def _add_wire_labels(self) -> None: fontsize=self.style.fontsize, verticalalignment="center", horizontalalignment="right", - zorder=self.zorder["wire_label"], + zorder=self._zorder["wire_label"], + color=self.style.wire_color, ) self._ax.add_artist(wire_label) @@ -358,7 +368,7 @@ def _draw_control_node(self, pos: int, xskip: float, color: str) -> None: ), self._control_node_r, color=color, - zorder=self.zorder["node"], + zorder=self._zorder["node"], ) self._ax.add_artist(control_node) @@ -387,7 +397,7 @@ def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: ), self._target_node_r, color=color, - zorder=self.zorder["node"], + zorder=self._zorder["node"], ) vertical_line = plt.Line2D( ( @@ -400,7 +410,7 @@ def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: ), lw=1.5, color=self.style.color, - zorder=self.zorder["node_label"], + zorder=self._zorder["node_label"], ) horizontal_line = plt.Line2D( ( @@ -416,7 +426,7 @@ def _draw_target_node(self, pos: int, xskip: float, color: str) -> None: (pos * self.style.wire_sep, pos * self.style.wire_sep), lw=1.5, color=self.style.color, - zorder=self.zorder["node_label"], + zorder=self._zorder["node_label"], ) self._ax.add_artist(target_node) @@ -453,7 +463,7 @@ def _draw_qbridge( ], [pos1 * self.style.wire_sep, pos2 * self.style.wire_sep], color=color, - zorder=self.zorder["bridge"], + zorder=self._zorder["bridge"], ) self._ax.add_line(bridge) @@ -495,7 +505,7 @@ def _draw_cbridge( q_pos * self.style.wire_sep, ), color=color, - zorder=self.zorder["bridge"], + zorder=self._zorder["bridge"], ) cbridge_r = plt.Line2D( ( @@ -513,7 +523,7 @@ def _draw_cbridge( q_pos * self.style.wire_sep, ), color=color, - zorder=self.zorder["bridge"], + zorder=self._zorder["bridge"], ) end_arrow = FancyArrow( xskip + self.style.gate_margin + self._min_gate_width / 2, @@ -524,8 +534,8 @@ def _draw_cbridge( head_width=self._cwire_sep * 5, head_length=self._cwire_sep * 3, length_includes_head=True, - color="k", - zorder=self.zorder["bridge"], + color=color, + zorder=self._zorder["bridge"], ) self._ax.add_line(cbridge_l) self._ax.add_line(cbridge_r) @@ -566,7 +576,7 @@ def _draw_swap_mark(self, pos: int, xskip: int, color: str) -> None: ], color=color, linewidth=2, - zorder=self.zorder["gate"], + zorder=self._zorder["gate"], ) dia_right = plt.Line2D( [ @@ -585,7 +595,7 @@ def _draw_swap_mark(self, pos: int, xskip: int, color: str) -> None: ], color=color, linewidth=2, - zorder=self.zorder["gate"], + zorder=self._zorder["gate"], ) self._ax.add_line(dia_left) self._ax.add_line(dia_right) @@ -667,7 +677,7 @@ def _draw_singleq_gate(self, gate: Gate, layer: int) -> None: fontstyle=self.fontstyle, verticalalignment="center", horizontalalignment="center", - zorder=self.zorder["gate_label"], + zorder=self._zorder["gate_label"], ) gate_patch = FancyBboxPatch( ( @@ -681,7 +691,7 @@ def _draw_singleq_gate(self, gate: Gate, layer: int) -> None: mutation_scale=0.3, facecolor=self.color, edgecolor=self.color, - zorder=self.zorder["gate"], + zorder=self._zorder["gate"], ) self._ax.add_artist(gate_text) @@ -775,7 +785,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: fontstyle=self.fontstyle, verticalalignment="center", horizontalalignment="center", - zorder=self.zorder["gate_label"], + zorder=self._zorder["gate_label"], ) gate_patch = FancyBboxPatch( @@ -791,7 +801,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: mutation_scale=0.3, facecolor=self.color, edgecolor=self.color, - zorder=self.zorder["gate"], + zorder=self._zorder["gate"], ) if len(gate.targets) > 1: @@ -803,7 +813,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: ), self._connector_r, color=self.fontcolor, - zorder=self.zorder["connector"], + zorder=self._zorder["connector"], ) connector_r = Circle( ( @@ -815,7 +825,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: ), self._connector_r, color=self.fontcolor, - zorder=self.zorder["connector"], + zorder=self._zorder["connector"], ) self._ax.add_artist(connector_l) self._ax.add_artist(connector_r) @@ -866,9 +876,10 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: self._min_gate_height, boxstyle=self.style.bulge, mutation_scale=0.3, - facecolor="white", - edgecolor="k", - zorder=self.zorder["gate"], + facecolor=self.style.bgcolor, + edgecolor=self.style.measure_color, + linewidth=1.25, + zorder=self._zorder["gate"], ) arc = Arc( ( @@ -881,8 +892,9 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: angle=0, theta1=0, theta2=180, - color="k", - zorder=self.zorder["gate_label"], + color=self.style.measure_color, + linewidth=1.25, + zorder=self._zorder["gate_label"], ) arrow = FancyArrow( xskip + self.style.gate_margin + self._min_gate_width / 2, @@ -892,11 +904,12 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: self._min_gate_height * 0.7, length_includes_head=True, head_width=0, - color="k", - zorder=self.zorder["gate_label"], + linewidth=1.25, + color=self.style.measure_color, + zorder=self._zorder["gate_label"], ) - self._draw_cbridge(c_pos, q_pos, xskip, "k") + self._draw_cbridge(c_pos, q_pos, xskip, color=self.style.measure_color) self._manage_layers( self._min_gate_width, list(range(0, self.merged_qubits[-1] + 1)), @@ -934,7 +947,10 @@ def canvas_plot(self) -> None: style = gate.style if gate.style is not None else {} self.text = style.get("text", gate.name) self.color = style.get( - "color", self.style.theme.get(gate.name, "k") + "color", + self.style.theme.get( + gate.name, self.style.theme["default_gate"] + ), ) self.fontsize = style.get("fontsize", self.style.fontsize) self.fontcolor = style.get("fontcolor", self.style.color) @@ -987,7 +1003,9 @@ def _fig_config(self) -> None: + max([sum(self._layer_list[i]) for i in range(self._qwires)]), ) if self.style.title is not None: - self._ax.set_title(self.style.title, pad=10) + self._ax.set_title( + self.style.title, pad=10, color=self.style.wire_color + ) self._ax.set_aspect("equal") self._ax.axis("off") From 6f9d52b681a7f6b48cbb3774d0e94a14ab1b5cf6 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Thu, 25 Jul 2024 02:14:46 +0530 Subject: [PATCH 15/20] changed var naming --- src/qutip_qip/circuit/mat_renderer.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index 1d5e7897..ca31a5bf 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -712,7 +712,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: """ wire_list = list( - range(self.merged_qubits[0], self.merged_qubits[-1] + 1) + range(self.merged_wires[0], self.merged_wires[-1] + 1) ) com_xskip = self._get_xskip(wire_list, layer) @@ -864,7 +864,7 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: """ xskip = self._get_xskip( - list(range(0, self.merged_qubits[-1] + 1)), layer + list(range(0, self.merged_wires[-1] + 1)), layer ) measure_box = FancyBboxPatch( ( @@ -912,7 +912,7 @@ def _draw_measure(self, c_pos: int, q_pos: int, layer: int) -> None: self._draw_cbridge(c_pos, q_pos, xskip, color=self.style.measure_color) self._manage_layers( self._min_gate_width, - list(range(0, self.merged_qubits[-1] + 1)), + list(range(0, self.merged_wires[-1] + 1)), layer, xskip, ) @@ -930,17 +930,16 @@ def canvas_plot(self) -> None: for gate in self._qc.gates: if isinstance(gate, Measurement): - self.merged_qubits = gate.targets.copy() - self.merged_qubits.sort() + self.merged_wires = gate.targets.copy() + self.merged_wires.sort() - find_layer = [ - len(self._layer_list[i]) - for i in range(0, self.merged_qubits[-1] + 1) - ] self._draw_measure( gate.classical_store, gate.targets[0], - max(find_layer), + max( + len(self._layer_list[i]) + for i in range(0, self.merged_wires[-1] + 1) + ), ) if isinstance(gate, Gate): @@ -964,15 +963,15 @@ def canvas_plot(self) -> None: len(gate.targets) > 1 or getattr(gate, "controls", False) is not None ): - self.merged_qubits = gate.targets.copy() + self.merged_wires = gate.targets.copy() if gate.controls is not None: - self.merged_qubits += gate.controls.copy() - self.merged_qubits.sort() + self.merged_wires += gate.controls.copy() + self.merged_wires.sort() find_layer = [ len(self._layer_list[i]) for i in range( - self.merged_qubits[0], self.merged_qubits[-1] + 1 + self.merged_wires[0], self.merged_wires[-1] + 1 ) ] self._draw_multiq_gate(gate, max(find_layer)) From 095f24e27922c49dbabf724460c702ebeedbc37b Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Mon, 29 Jul 2024 00:39:02 +0530 Subject: [PATCH 16/20] added suggested edits --- src/qutip_qip/circuit/circuit.py | 8 ++++++-- src/qutip_qip/circuit/mat_renderer.py | 8 +++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/qutip_qip/circuit/circuit.py b/src/qutip_qip/circuit/circuit.py index dd9730d4..b33feef5 100644 --- a/src/qutip_qip/circuit/circuit.py +++ b/src/qutip_qip/circuit/circuit.py @@ -970,7 +970,7 @@ def svg(self): def draw( self, - renderer="latex", + renderer="matplotlib", file_type="png", dpi=None, file_name="exported_pic", @@ -982,7 +982,7 @@ def draw( Parameters ---------- renderer : choose the renderer for the circuit. - Default: 'latex' + Default: 'matplotlib' file_type : Provide a supported image file_type eg: "svg"/"png". Default : "png". @@ -1012,6 +1012,10 @@ def draw( os.path.join(file_path, file_name + "." + file_type), mode ) as f: f.write(image_data) + elif renderer == "matplotlib": + from .mat_renderer import MatRenderer + mat = MatRenderer(self) + mat.canvas_plot() else: raise ValueError( f"Unknown renderer '{renderer}' not supported. Please choose from 'latex', 'matplotlib', 'text'." diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index ca31a5bf..724b16ed 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -146,8 +146,6 @@ def __init__( facecolor=self.style.bgcolor, ) - self.canvas_plot() - def _get_xskip(self, wire_list: List[int], layer: int) -> float: """ Get the xskip (horizontal value for getting to requested layer) for the gate to be plotted. @@ -249,15 +247,19 @@ def _manage_layers( """ for wire in wire_list: + # check if requested layer exists for the wire if len(self._layer_list[wire]) > layer: + # check if the layer width is greater than new layer width if ( self._layer_list[wire][layer] < gate_width + self.style.gate_margin * 2 ): + # update with new layer width self._layer_list[wire][layer] = ( gate_width + self.style.gate_margin * 2 ) else: + # add layer width: new layer width + missing layer widths if exits temp = xskip - sum(self._layer_list[wire]) if xskip != 0 else 0 self._layer_list[wire].append( temp + gate_width + self.style.gate_margin * 2 @@ -716,7 +718,7 @@ def _draw_multiq_gate(self, gate: Gate, layer: int) -> None: ) com_xskip = self._get_xskip(wire_list, layer) - if gate.name == "CNOT": + if gate.name == "CNOT" or gate.name == "CX": self._draw_control_node(gate.controls[0], com_xskip, self.color) self._draw_target_node(gate.targets[0], com_xskip, self.color) self._draw_qbridge( From de3a5c2e2fcc20861695c407f13495c88dd8acd0 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Thu, 1 Aug 2024 15:03:19 +0530 Subject: [PATCH 17/20] fixed issue with classical wire render --- src/qutip_qip/circuit/mat_renderer.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index 724b16ed..6c2de08f 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -277,7 +277,10 @@ def _add_wire(self) -> None: for i in range(self._qwires): wire = plt.Line2D( [0, max_len], - [i * self.style.wire_sep, i * self.style.wire_sep], + [ + (i + self._cwires) * self.style.wire_sep, + (i + self._cwires) * self.style.wire_sep, + ], lw=1, color=self.style.wire_color, zorder=self._zorder["wire"], @@ -288,8 +291,8 @@ def _add_wire(self) -> None: wire_up = plt.Line2D( [0, max_len], [ - (i + self._qwires) * self.style.wire_sep, - (i + self._qwires) * self.style.wire_sep, + (i * self.style.wire_sep) + self._cwire_sep, + (i * self.style.wire_sep) + self._cwire_sep, ], lw=1, color=self.style.wire_color, @@ -298,8 +301,8 @@ def _add_wire(self) -> None: wire_down = plt.Line2D( [0, max_len], [ - (i + self._qwires) * self.style.wire_sep, - (i + self._qwires) * self.style.wire_sep, + (i * self.style.wire_sep) - self._cwire_sep, + (i * self.style.wire_sep) - self._cwire_sep, ], lw=1, color=self.style.wire_color, From 7ef4a030f2f3251898cfc75707843f22cb9214db Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Mon, 5 Aug 2024 17:46:52 +0530 Subject: [PATCH 18/20] added styling docstring --- src/qutip_qip/circuit/mat_renderer.py | 61 +++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index 6c2de08f..b547cbb0 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -28,8 +28,69 @@ class StyleConfig: """ Dataclass to store the style configuration for circuit customization. + + Parameters + ---------- + dpi : int, optional + The dpi of the figure. The default is 150. + + fontsize : int, optional + The fontsize control at circuit level, including tile and wire labels. The default is 10. + + end_wire_ext : int, optional + The extension of the wire at the end of the circuit. The default is 2. + + padding : float, optional + The padding between the circuit and the figure border. The default is 0.3. + + gate_margin : float, optional + The margin space left on each side of the gate. The default is 0.15. + + wire_sep : float, optional + The separation between the wires. The default is 0.5. + + layer_sep : float, optional + The separation between the layers. The default is 0.5. + + gate_pad : float, optional + The padding between the gate and the gate label. The default is 0.05. + + label_pad : float, optional + The padding between the wire label and the wire. The default is 0.1. + + fig_height : float, optional + The height of the figure. The default is None. + + fig_width : float, optional + The width of the figure. The default is None. + + bulge : Union[str, bool], optional + The bulge style of the gate. Renders non-bulge gates if False. The default is True. + + align_layer : bool, optional + Align the layers of the gates across different wires. The default is False. + + theme : Optional[Union[str, Dict]], optional + The color theme of the circuit. The default is "qutip". + The available themes are 'qutip', 'light', and 'modern'. + + title : Optional[str], optional + The title of the circuit. The default is None. + + bgcolor : Optional[str], optional + The background color of the circuit. The default is None. + + color : Optional[str], optional + Controls color of acsent elements (eg. cross sign in the target node) + and set as deafult color of gate-label. Can be overwritten by gate specific color. + The default is None. + + wire_label : Optional[List], optional + The labels of the wires. The default is None. """ + + dpi: int = 150 fontsize: int = 10 end_wire_ext: int = 2 From c8002464ec1439119d4a88ff63880085f54bb381 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Mon, 5 Aug 2024 18:12:05 +0530 Subject: [PATCH 19/20] remove text arg from gate styling and fowarded styling args from circuit.py --- src/qutip_qip/circuit/circuit.py | 4 +++- src/qutip_qip/circuit/mat_renderer.py | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/qutip_qip/circuit/circuit.py b/src/qutip_qip/circuit/circuit.py index b33feef5..f95a15ac 100644 --- a/src/qutip_qip/circuit/circuit.py +++ b/src/qutip_qip/circuit/circuit.py @@ -975,6 +975,7 @@ def draw( dpi=None, file_name="exported_pic", file_path="", + **kwargs, ): """ Export circuit object as an image file in a supported format. @@ -1014,7 +1015,8 @@ def draw( f.write(image_data) elif renderer == "matplotlib": from .mat_renderer import MatRenderer - mat = MatRenderer(self) + + mat = MatRenderer(self, **kwargs) mat.canvas_plot() else: raise ValueError( diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index b547cbb0..e78fa488 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -81,16 +81,14 @@ class StyleConfig: The background color of the circuit. The default is None. color : Optional[str], optional - Controls color of acsent elements (eg. cross sign in the target node) - and set as deafult color of gate-label. Can be overwritten by gate specific color. + Controls color of acsent elements (eg. cross sign in the target node) + and set as deafult color of gate-label. Can be overwritten by gate specific color. The default is None. wire_label : Optional[List], optional The labels of the wires. The default is None. """ - - dpi: int = 150 fontsize: int = 10 end_wire_ext: int = 2 @@ -1010,7 +1008,9 @@ def canvas_plot(self) -> None: if isinstance(gate, Gate): style = gate.style if gate.style is not None else {} - self.text = style.get("text", gate.name) + self.text = ( + gate.arg_label if gate.arg_label is not None else gate.name + ) self.color = style.get( "color", self.style.theme.get( From e6be0edd3feffd7e6aded123c908cc697d0f05d5 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Mon, 5 Aug 2024 19:04:57 +0530 Subject: [PATCH 20/20] minor changes --- src/qutip_qip/circuit/mat_renderer.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/qutip_qip/circuit/mat_renderer.py b/src/qutip_qip/circuit/mat_renderer.py index e78fa488..d7653a6d 100644 --- a/src/qutip_qip/circuit/mat_renderer.py +++ b/src/qutip_qip/circuit/mat_renderer.py @@ -87,6 +87,9 @@ class StyleConfig: wire_label : Optional[List], optional The labels of the wires. The default is None. + + wire_color : Optional[str], optional + The color of the wires. The default is None. """ dpi: int = 150 @@ -125,7 +128,9 @@ def __post_init__(self): self.theme = modern else: raise ValueError( - f"Invalid theme: {self.theme}. Must be selectec from 'qutip', 'light', 'dark', or 'modern'." + f"""Invalid theme: {self.theme}, + Must be selectec from 'qutip', 'light', 'dark', or 'modern'. + """ ) self.bgcolor = self.bgcolor or self.theme["bgcolor"] @@ -218,10 +223,10 @@ def _get_xskip(self, wire_list: List[int], layer: int) -> float: The layer the gate is acting on. """ - xskip = [] if self.style.align_layer: wire_list = list(range(self._qwires)) + xskip = [] for wire in wire_list: xskip.append(sum(self._layer_list[wire][:layer])) @@ -400,6 +405,9 @@ def _add_wire_labels(self) -> None: i * self.style.wire_sep, label, fontsize=self.style.fontsize, + fontweight="normal", + fontfamily="monospace", + fontstyle="normal", verticalalignment="center", horizontalalignment="right", zorder=self._zorder["wire_label"],