From 7ff655105376a446720e2afb013c2fe929519373 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sat, 22 Apr 2023 10:09:41 -0700 Subject: [PATCH 01/30] Remove deprecated args from mpl drawer --- .../circuit/circuit_visualization.py | 7 +- qiskit/visualization/circuit/matplotlib.py | 70 +------------------ ...eprecated-mpl-drawer-9d6eaa40d5a86777.yaml | 10 +++ 3 files changed, 14 insertions(+), 73 deletions(-) create mode 100644 releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml diff --git a/qiskit/visualization/circuit/circuit_visualization.py b/qiskit/visualization/circuit/circuit_visualization.py index f8cc4bc839d0..a2b5e25674b0 100644 --- a/qiskit/visualization/circuit/circuit_visualization.py +++ b/qiskit/visualization/circuit/circuit_visualization.py @@ -649,20 +649,15 @@ def _matplotlib_circuit_drawer( qubits, clbits, nodes, + circuit, scale=scale, style=style, reverse_bits=reverse_bits, plot_barriers=plot_barriers, - layout=None, fold=fold, ax=ax, initial_state=initial_state, cregbundle=cregbundle, - global_phase=None, - calibrations=None, - qregs=None, - cregs=None, with_layout=with_layout, - circuit=circuit, ) return qcd.draw(filename) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 155afff43776..b1b04ca6602c 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -20,8 +20,7 @@ import numpy as np -from qiskit.circuit import ControlledGate, Qubit, Clbit, ClassicalRegister -from qiskit.circuit import Measure, QuantumCircuit, QuantumRegister +from qiskit.circuit import ControlledGate, Qubit, Clbit, ClassicalRegister, Measure from qiskit.circuit.library.standard_gates import ( SwapGate, RZZGate, @@ -69,21 +68,16 @@ def __init__( qubits, clbits, nodes, + circuit, scale=None, style=None, reverse_bits=False, plot_barriers=True, - layout=None, fold=25, ax=None, initial_state=False, cregbundle=None, - global_phase=None, - qregs=None, - cregs=None, - calibrations=None, with_layout=False, - circuit=None, ): from matplotlib import patches from matplotlib import pyplot as plt @@ -91,65 +85,7 @@ def __init__( self._patches_mod = patches self._plt_mod = plt - if qregs is not None: - warn( - "The 'qregs' kwarg to the MatplotlibDrawer class is deprecated " - "as of 0.20.0 and will be removed no earlier than 3 months " - "after the release date.", - DeprecationWarning, - 2, - ) - if cregs is not None: - warn( - "The 'cregs' kwarg to the MatplotlibDrawer class is deprecated " - "as of 0.20.0 and will be removed no earlier than 3 months " - "after the release date.", - DeprecationWarning, - 2, - ) - if global_phase is not None: - warn( - "The 'global_phase' kwarg to the MatplotlibDrawer class is deprecated " - "as of 0.20.0 and will be removed no earlier than 3 months " - "after the release date.", - DeprecationWarning, - 2, - ) - if layout is not None: - warn( - "The 'layout' kwarg to the MatplotlibDrawer class is deprecated " - "as of 0.20.0 and will be removed no earlier than 3 months " - "after the release date.", - DeprecationWarning, - 2, - ) - if calibrations is not None: - warn( - "The 'calibrations' kwarg to the MatplotlibDrawer class is deprecated " - "as of 0.20.0 and will be removed no earlier than 3 months " - "after the release date.", - DeprecationWarning, - 2, - ) - # This check should be removed when the 5 deprecations above are removed - if circuit is None: - warn( - "The 'circuit' kwarg to the MaptlotlibDrawer class must be a valid " - "QuantumCircuit and not None. A new circuit is being created using " - "the qubits and clbits for rendering the drawing.", - DeprecationWarning, - 2, - ) - circ = QuantumCircuit(qubits, clbits) - for reg in qregs: - bits = [qubits[circ._qubit_indices[q].index] for q in reg] - circ.add_register(QuantumRegister(None, reg.name, list(bits))) - for reg in cregs: - bits = [clbits[circ._clbit_indices[q].index] for q in reg] - circ.add_register(ClassicalRegister(None, reg.name, list(bits))) - self._circuit = circ - else: - self._circuit = circuit + self._circuit = circuit self._qubits = qubits self._clbits = clbits self._qubits_dict = {} diff --git a/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml b/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml new file mode 100644 index 000000000000..2a325115941c --- /dev/null +++ b/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml @@ -0,0 +1,10 @@ +--- +upgrade: + - | + In the internal ``qiskit.visualization.circuit.matplotlib.MatplotlibDrawer`` object, the arguments + ``layout``, ``global_phase``, ``qregs`` and ``cregs`` have been removed. They were originally + deprecated in Qiskit Terra 0.20. These objects are simply inferred from the given ``circuit`` + now. + + This is an internal worker class of the visualization routines. It is unlikely you will + need to change any of your code. From 2ab4405c841c69bb67e5f91a1163e23e40afa95c Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sat, 22 Apr 2023 10:12:38 -0700 Subject: [PATCH 02/30] Reno mod --- .../remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml b/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml index 2a325115941c..59d8cef6d5e9 100644 --- a/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml +++ b/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml @@ -2,9 +2,9 @@ upgrade: - | In the internal ``qiskit.visualization.circuit.matplotlib.MatplotlibDrawer`` object, the arguments - ``layout``, ``global_phase``, ``qregs`` and ``cregs`` have been removed. They were originally - deprecated in Qiskit Terra 0.20. These objects are simply inferred from the given ``circuit`` + ``layout``, ``global_phase``, ``qregs`` and ``cregs`` have been removed. They were originally + deprecated in Qiskit Terra 0.20. These objects are simply inferred from the given ``circuit`` now. - This is an internal worker class of the visualization routines. It is unlikely you will + This is an internal worker class of the visualization routines. It is unlikely you will need to change any of your code. From b8428099cd5f83d58f551d003264b04d8a364e8b Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Tue, 25 Apr 2023 12:16:49 -0700 Subject: [PATCH 03/30] Convert to wire_map, node_data, etc --- qiskit/visualization/circuit/matplotlib.py | 449 +++++++++++---------- 1 file changed, 234 insertions(+), 215 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 155afff43776..8c67c2d1abae 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -152,12 +152,6 @@ def __init__( self._circuit = circuit self._qubits = qubits self._clbits = clbits - self._qubits_dict = {} - self._clbits_dict = {} - self._q_anchors = {} - self._c_anchors = {} - self._wire_map = {} - self._nodes = nodes self._scale = 1.0 if scale is None else scale @@ -219,11 +213,8 @@ def __init__( self._lwidth2 = 2.0 self._x_offset = 0.0 - # _data per node with 'width', 'gate_text', 'raw_gate_text', - # 'ctrl_text', 'param', q_xy', 'c_xy', and 'c_indxs' - # and colors 'fc', 'ec', 'lc', 'sc', 'gt', and 'tc' - self._data = {} - self._layer_widths = [] + self._n_lines = 0 + self._x_index = 0 # _char_list for finding text_width of names, labels, and params self._char_list = { @@ -327,31 +318,43 @@ def draw(self, filename=None, verbose=False): """Main entry point to 'matplotlib' ('mpl') drawer. Called from ``visualization.circuit_drawer`` and from ``QuantumCircuit.draw`` through circuit_drawer. """ - # All information for the drawing is first loaded into self._data for the gates and into - # self._qubits_dict and self._clbits_dict for the qubits, clbits, and wires, + # All information for the drawing is first loaded into node_data for the gates and into + # qubits_dict, clbits_dict, and wire_map for the qubits, clbits, and wires, # followed by the coordinates for each gate. + # load the wire map + wire_map = get_wire_map(self._circuit, self._qubits + self._clbits, self._cregbundle) + + # node_data per node with 'width', 'gate_text', 'raw_gate_text', + # 'ctrl_text', 'param_text', q_xy', and 'c_xy', + # and colors 'fc', 'ec', 'lc', 'sc', 'gt', and 'tc' + node_data = {} + + # dicts for the names and locations of register/bit labels + qubits_dict = {} + clbits_dict = {} + # get layer widths - self._get_layer_widths() + layer_widths = self._get_layer_widths(node_data, wire_map) # load the _qubit_dict and _clbit_dict with register info - n_lines = self._set_bit_reg_info() + self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict) # load the coordinates for each gate and compute number of folds - max_anc = self._get_coords(n_lines) - num_folds = max(0, max_anc - 1) // self._fold if self._fold > 0 else 0 + max_x = self._get_coords(node_data, wire_map, layer_widths, qubits_dict, clbits_dict) + num_folds = max(0, max_x - 1) // self._fold if self._fold > 0 else 0 # The window size limits are computed, followed by one of the four possible ways # of scaling the drawing. # compute the window size - if max_anc > self._fold > 0: + if max_x > self._fold > 0: xmax = self._fold + self._x_offset + 0.1 - ymax = (num_folds + 1) * (n_lines + 1) - 1 + ymax = (num_folds + 1) * (self._n_lines + 1) - 1 else: x_incr = 0.4 if not self._nodes else 0.9 - xmax = max_anc + 1 + self._x_offset - x_incr - ymax = n_lines + xmax = max_x + 1 + self._x_offset - x_incr + ymax = self._n_lines xl = -self._style["margin"][0] xr = xmax + self._style["margin"][1] @@ -401,8 +404,10 @@ def draw(self, filename=None, verbose=False): self._plt_mod.text( xl, yt, "Global Phase: %s" % pi_check(self._global_phase, output="mpl") ) - self._draw_regs_wires(num_folds, xmax, n_lines, max_anc) - self._draw_ops(verbose) + self._draw_regs_wires(num_folds, xmax, max_x, qubits_dict, clbits_dict) + self._draw_ops( + self._nodes, node_data, wire_map, layer_widths, qubits_dict, clbits_dict, verbose + ) if filename: self._figure.savefig( @@ -415,29 +420,40 @@ def draw(self, filename=None, verbose=False): matplotlib_close_if_inline(self._figure) return self._figure - def _get_layer_widths(self): + def _get_layer_widths(self, node_data, wire_map): """Compute the layer_widths for the layers""" - for layer in self._nodes: + + layer_widths = {} + for i, layer in enumerate(self._nodes): widest_box = WID + first_node = True + save_layer = {} for node in layer: + if first_node: + first_node = False + save_layer[node] = i + else: + save_layer[node] = -1 + layer_widths[node] = (1, save_layer[node]) + op = node.op - self._data[node] = {} - self._data[node]["width"] = WID + node_data[node] = {} + node_data[node]["width"] = WID num_ctrl_qubits = 0 if not hasattr(op, "num_ctrl_qubits") else op.num_ctrl_qubits if ( getattr(op, "_directive", False) and (not op.label or not self._plot_barriers) ) or isinstance(op, Measure): - self._data[node]["raw_gate_text"] = op.name + node_data[node]["raw_gate_text"] = op.name continue base_type = None if not hasattr(op, "base_gate") else op.base_gate gate_text, ctrl_text, raw_gate_text = get_gate_ctrl_text( op, "mpl", style=self._style, calibrations=self._calibrations ) - self._data[node]["gate_text"] = gate_text - self._data[node]["ctrl_text"] = ctrl_text - self._data[node]["raw_gate_text"] = raw_gate_text - self._data[node]["param"] = "" + node_data[node]["gate_text"] = gate_text + node_data[node]["ctrl_text"] = ctrl_text + node_data[node]["raw_gate_text"] = raw_gate_text + node_data[node]["param_text"] = "" # if single qubit, no params, and no labels, layer_width is 1 if ( @@ -461,11 +477,13 @@ def _get_layer_widths(self): and len(op.params) > 0 and not any(isinstance(param, np.ndarray) for param in op.params) ): - param = get_param_str(op, "mpl", ndigits=3) + param_text = get_param_str(op, "mpl", ndigits=3) if isinstance(op, Initialize): - param = f"$[{param.replace('$', '')}]$" - self._data[node]["param"] = param - raw_param_width = self._get_text_width(param, fontsize=self._sfs, param=True) + param_text = f"$[{param_text.replace('$', '')}]$" + node_data[node]["param_text"] = param_text + raw_param_width = self._get_text_width( + param_text, fontsize=self._sfs, param=True + ) param_width = raw_param_width + 0.08 else: param_width = raw_param_width = 0.0 @@ -480,7 +498,7 @@ def _get_layer_widths(self): ) gate_width = (raw_gate_width + 0.08) * 1.58 - # otherwise, standard gate or multiqubit gate + # Otherwise, standard gate or multiqubit gate else: raw_gate_width = self._get_text_width(gate_text, fontsize=self._fs) gate_width = raw_gate_width + 0.10 @@ -491,26 +509,28 @@ def _get_layer_widths(self): box_width = max(gate_width, ctrl_width, param_width, WID) if box_width > widest_box: widest_box = box_width - self._data[node]["width"] = max(raw_gate_width, raw_param_width) + node_data[node]["width"] = max(raw_gate_width, raw_param_width) + for node in layer: + layer_widths[node] = (int(widest_box) + 1, save_layer[node]) - self._layer_widths.append(int(widest_box) + 1) + return layer_widths - def _set_bit_reg_info(self): + def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict): """Get all the info for drawing bit/reg names and numbers""" - self._wire_map = get_wire_map(self._circuit, self._qubits + self._clbits, self._cregbundle) longest_wire_label_width = 0 - n_lines = 0 + self._n_lines = 0 initial_qbit = " |0>" if self._initial_state else "" initial_cbit = " 0" if self._initial_state else "" idx = 0 pos = y_off = -len(self._qubits) + 1 - for ii, wire in enumerate(self._wire_map): + for ii, wire in enumerate(wire_map): + # if it's a creg, register is the key and just load the index if isinstance(wire, ClassicalRegister): register = wire - index = self._wire_map[wire] + index = wire_map[wire] # otherwise, get the register from find_bit and use bit_index if # it's a bit, or the index of the bit in the register if it's a reg @@ -541,76 +561,79 @@ def _set_bit_reg_info(self): if isinstance(wire, Qubit): pos = -ii - self._qubits_dict[ii] = { + qubits_dict[ii] = { "y": pos, "wire_label": wire_label, - "index": bit_index, - "register": register, } - n_lines += 1 + self._n_lines += 1 else: if ( not self._cregbundle or register is None or (self._cregbundle and isinstance(wire, ClassicalRegister)) ): - n_lines += 1 + self._n_lines += 1 idx += 1 pos = y_off - idx - self._clbits_dict[ii] = { + clbits_dict[ii] = { "y": pos, "wire_label": wire_label, - "index": bit_index, "register": register, } - self._x_offset = -1.2 + longest_wire_label_width - return n_lines - def _get_coords(self, n_lines): - """Load all the coordinate info needed to place the gates on the drawing""" - - # create the anchor arrays - for key, qubit in self._qubits_dict.items(): - self._q_anchors[key] = Anchor(num_wires=n_lines, y_index=qubit["y"], fold=self._fold) - for key, clbit in self._clbits_dict.items(): - self._c_anchors[key] = Anchor(num_wires=n_lines, y_index=clbit["y"], fold=self._fold) - - # get all the necessary coordinates for placing gates on the wires + def _get_coords( + self, + node_data, + wire_map, + layer_widths, + qubits_dict, + clbits_dict, + ): + """Load all the coordinate info needed to place the gates on the drawing. + flow_node is node id for the parent of a gate inside a ControlFlowOp circuit. + """ prev_x_index = -1 for i, layer in enumerate(self._nodes): - layer_width = self._layer_widths[i] - anc_x_index = prev_x_index + 1 + curr_x_index = prev_x_index + 1 + l_width = [] for node in layer: - # get qubit index + + # get qubit indexes q_indxs = [] for qarg in node.qargs: if qarg in self._qubits: - q_indxs.append(self._wire_map[qarg]) + q_indxs.append(wire_map[qarg]) + # get clbit indexes c_indxs = [] for carg in node.cargs: if carg in self._clbits: register = get_bit_register(self._circuit, carg) if register is not None and self._cregbundle: - c_indxs.append(self._wire_map[register]) + c_indxs.append(wire_map[register]) else: - c_indxs.append(self._wire_map[carg]) + c_indxs.append(wire_map[carg]) - # qubit coordinate - self._data[node]["q_xy"] = [ - self._q_anchors[ii].plot_coord(anc_x_index, layer_width, self._x_offset) + # qubit coordinates + node_data[node]["q_xy"] = [ + self._plot_coord( + curr_x_index, qubits_dict[ii]["y"], layer_widths[node][0], self._x_offset + ) for ii in q_indxs ] - # clbit coordinate - self._data[node]["c_xy"] = [ - self._c_anchors[ii].plot_coord(anc_x_index, layer_width, self._x_offset) + # clbit coordinates + node_data[node]["c_xy"] = [ + self._plot_coord( + curr_x_index, clbits_dict[ii]["y"], layer_widths[node][0], self._x_offset + ) for ii in c_indxs ] + # update index based on the value from plotting - anc_x_index = self._q_anchors[q_indxs[0]].get_x_index() - self._data[node]["c_indxs"] = c_indxs + curr_x_index = self._x_index + l_width.append(layer_widths[node][0]) # adjust the column if there have been barriers encountered, but not plotted barrier_offset = 0 @@ -619,7 +642,7 @@ def _get_coords(self, n_lines): barrier_offset = ( -1 if all(getattr(nd.op, "_directive", False) for nd in layer) else 0 ) - prev_x_index = anc_x_index + layer_width + barrier_offset - 1 + prev_x_index = curr_x_index + max(l_width) + barrier_offset - 1 return prev_x_index + 1 @@ -668,14 +691,14 @@ def _get_text_width(self, text, fontsize, param=False, reg_remove_under=None): sum_text *= self._subfont_factor return sum_text - def _draw_regs_wires(self, num_folds, xmax, n_lines, max_anc): + def _draw_regs_wires(self, num_folds, xmax, max_x, qubits_dict, clbits_dict): """Draw the register names and numbers, wires, and vertical lines at the ends""" for fold_num in range(num_folds + 1): # quantum registers - for qubit in self._qubits_dict.values(): + for qubit in qubits_dict.values(): qubit_label = qubit["wire_label"] - y = qubit["y"] - fold_num * (n_lines + 1) + y = qubit["y"] - fold_num * (self._n_lines + 1) self._ax.text( self._x_offset - 0.2, y, @@ -692,15 +715,13 @@ def _draw_regs_wires(self, num_folds, xmax, n_lines, max_anc): # classical registers this_clbit_dict = {} - for clbit in self._clbits_dict.values(): - clbit_label = clbit["wire_label"] - clbit_reg = clbit["register"] - y = clbit["y"] - fold_num * (n_lines + 1) + for clbit in clbits_dict.values(): + y = clbit["y"] - fold_num * (self._n_lines + 1) if y not in this_clbit_dict.keys(): this_clbit_dict[y] = { "val": 1, - "wire_label": clbit_label, - "register": clbit_reg, + "wire_label": clbit["wire_label"], + "register": clbit["register"], } else: this_clbit_dict[y]["val"] += 1 @@ -751,8 +772,8 @@ def _draw_regs_wires(self, num_folds, xmax, n_lines, max_anc): if feedline_l or feedline_r: xpos_l = self._x_offset - 0.01 xpos_r = self._fold + self._x_offset + 0.1 - ypos1 = -fold_num * (n_lines + 1) - ypos2 = -(fold_num + 1) * (n_lines) - fold_num + 1 + ypos1 = -fold_num * (self._n_lines + 1) + ypos2 = -(fold_num + 1) * (self._n_lines) - fold_num + 1 if feedline_l: self._ax.plot( [xpos_l, xpos_l], @@ -770,12 +791,12 @@ def _draw_regs_wires(self, num_folds, xmax, n_lines, max_anc): zorder=PORDER_LINE, ) - # draw anchor index number + # draw index number if self._style["index"]: - for layer_num in range(max_anc): + for layer_num in range(max_x): if self._fold > 0: x_coord = layer_num % self._fold + self._x_offset + 0.53 - y_coord = -(layer_num // self._fold) * (n_lines + 1) + 0.65 + y_coord = -(layer_num // self._fold) * (self._n_lines + 1) + 0.65 else: x_coord = layer_num + self._x_offset + 0.53 y_coord = 0.65 @@ -791,17 +812,20 @@ def _draw_regs_wires(self, num_folds, xmax, n_lines, max_anc): zorder=PORDER_TEXT, ) - def _draw_ops(self, verbose=False): + def _draw_ops( + self, nodes, node_data, wire_map, layer_widths, qubits_dict, clbits_dict, verbose=False + ): """Draw the gates in the circuit""" prev_x_index = -1 - for i, layer in enumerate(self._nodes): - layer_width = self._layer_widths[i] - anc_x_index = prev_x_index + 1 + for i, layer in enumerate(nodes): + l_width = [] + curr_x_index = prev_x_index + 1 # draw the gates in this layer for node in layer: op = node.op - self._get_colors(node) + + self._get_colors(node, node_data) if verbose: print(op) @@ -809,35 +833,40 @@ def _draw_ops(self, verbose=False): # add conditional if getattr(op, "condition", None): cond_xy = [ - self._c_anchors[ii].plot_coord(anc_x_index, layer_width, self._x_offset) - for ii in self._clbits_dict - ] - if self._clbits_dict: - anc_x_index = max( - anc_x_index, next(iter(self._c_anchors.items()))[1].get_x_index() + self._plot_coord( + curr_x_index, + clbits_dict[ii]["y"], + layer_widths[node][0], + self._x_offset, ) - self._condition(node, cond_xy) + for ii in clbits_dict + ] + if clbits_dict: + curr_x_index = max(curr_x_index, self._x_index) + self._condition(node, node_data, wire_map, cond_xy) # draw measure if isinstance(op, Measure): - self._measure(node) + self._measure(node, node_data) # draw barriers, snapshots, etc. elif getattr(op, "_directive", False): if self._plot_barriers: - self._barrier(node) + self._barrier(node, node_data) # draw single qubit gates - elif len(self._data[node]["q_xy"]) == 1 and not node.cargs: - self._gate(node) + elif len(node_data[node]["q_xy"]) == 1 and not node.cargs: + self._gate(node, node_data) # draw controlled gates elif isinstance(op, ControlledGate): - self._control_gate(node) + self._control_gate(node, node_data) # draw multi-qubit gate as final default else: - self._multiqubit_gate(node) + self._multiqubit_gate(node, node_data) + + l_width.append(layer_widths[node][0]) # adjust the column if there have been barriers encountered, but not plotted barrier_offset = 0 @@ -847,15 +876,15 @@ def _draw_ops(self, verbose=False): -1 if all(getattr(nd.op, "_directive", False) for nd in layer) else 0 ) - prev_x_index = anc_x_index + layer_width + barrier_offset - 1 + prev_x_index = curr_x_index + max(l_width) + barrier_offset - 1 - def _get_colors(self, node): + def _get_colors(self, node, node_data): """Get all the colors needed for drawing the circuit""" op = node.op base_name = None if not hasattr(op, "base_gate") else op.base_gate.name color = None - if self._data[node]["raw_gate_text"] in self._style["dispcol"]: - color = self._style["dispcol"][self._data[node]["raw_gate_text"]] + if node_data[node]["raw_gate_text"] in self._style["dispcol"]: + color = self._style["dispcol"][node_data[node]["raw_gate_text"]] elif op.name in self._style["dispcol"]: color = self._style["dispcol"][op.name] if color is not None: @@ -889,15 +918,16 @@ def _get_colors(self, node): lc = fc # Subtext needs to be same color as gate text sc = gt - self._data[node]["fc"] = fc - self._data[node]["ec"] = ec - self._data[node]["gt"] = gt - self._data[node]["tc"] = self._style["tc"] - self._data[node]["sc"] = sc - self._data[node]["lc"] = lc - - def _condition(self, node, cond_xy): + node_data[node]["fc"] = fc + node_data[node]["ec"] = ec + node_data[node]["gt"] = gt + node_data[node]["tc"] = self._style["tc"] + node_data[node]["sc"] = sc + node_data[node]["lc"] = lc + + def _condition(self, node, node_data, wire_map, cond_xy): """Add a conditional to a gate""" + label, val_bits = get_condition_label_val( node.op.condition, self._circuit, self._cregbundle ) @@ -911,17 +941,17 @@ def _condition(self, node, cond_xy): # other cases, only one bit is shown. if not self._cregbundle and isinstance(cond_bit_reg, ClassicalRegister): for idx in range(cond_bit_reg.size): - cond_pos.append(cond_xy[self._wire_map[cond_bit_reg[idx]] - first_clbit]) + cond_pos.append(cond_xy[wire_map[cond_bit_reg[idx]] - first_clbit]) # If it's a register bit and cregbundle, need to use the register to find the location elif self._cregbundle and isinstance(cond_bit_reg, Clbit): register = get_bit_register(self._circuit, cond_bit_reg) if register is not None: - cond_pos.append(cond_xy[self._wire_map[register] - first_clbit]) + cond_pos.append(cond_xy[wire_map[register] - first_clbit]) else: - cond_pos.append(cond_xy[self._wire_map[cond_bit_reg] - first_clbit]) + cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit]) else: - cond_pos.append(cond_xy[self._wire_map[cond_bit_reg] - first_clbit]) + cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit]) xy_plot = [] for idx, xy in enumerate(cond_pos): @@ -943,7 +973,8 @@ def _condition(self, node, cond_xy): ) self._ax.add_patch(box) xy_plot.append(xy) - qubit_b = min(self._data[node]["q_xy"], key=lambda xy: xy[1]) + + qubit_b = min(node_data[node]["q_xy"], key=lambda xy: xy[1]) clbit_b = min(xy_plot, key=lambda xy: xy[1]) # display the label at the bottom of the lowest conditional and draw the double line @@ -963,14 +994,14 @@ def _condition(self, node, cond_xy): ) self._line(qubit_b, clbit_b, lc=self._style["cc"], ls=self._style["cline"]) - def _measure(self, node): + def _measure(self, node, node_data): """Draw the measure symbol and the line to the clbit""" - qx, qy = self._data[node]["q_xy"][0] - cx, cy = self._data[node]["c_xy"][0] + qx, qy = node_data[node]["q_xy"][0] + cx, cy = node_data[node]["c_xy"][0] register, _, reg_index = get_bit_reg_index(self._circuit, node.cargs[0]) # draw gate box - self._gate(node) + self._gate(node, node_data) # add measure symbol arc = self._patches_mod.Arc( @@ -980,7 +1011,7 @@ def _measure(self, node): theta1=0, theta2=180, fill=False, - ec=self._data[node]["gt"], + ec=node_data[node]["gt"], linewidth=self._lwidth2, zorder=PORDER_GATE, ) @@ -988,13 +1019,13 @@ def _measure(self, node): self._ax.plot( [qx, qx + 0.35 * WID], [qy - 0.15 * HIG, qy + 0.20 * HIG], - color=self._data[node]["gt"], + color=node_data[node]["gt"], linewidth=self._lwidth2, zorder=PORDER_GATE, ) # arrow self._line( - self._data[node]["q_xy"][0], + node_data[node]["q_xy"][0], [cx, cy + 0.35 * WID], lc=self._style["cc"], ls=self._style["cline"], @@ -1023,9 +1054,9 @@ def _measure(self, node): zorder=PORDER_TEXT, ) - def _barrier(self, node): + def _barrier(self, node, node_data): """Draw a barrier""" - for i, xy in enumerate(self._data[node]["q_xy"]): + for i, xy in enumerate(node_data[node]["q_xy"]): xpos, ypos = xy # For the topmost barrier, reduce the rectangle if there's a label to allow for the text. if i == 0 and node.op.label is not None: @@ -1062,73 +1093,73 @@ def _barrier(self, node): ha="center", va="top", fontsize=self._fs, - color=self._data[node]["tc"], + color=node_data[node]["tc"], clip_on=True, zorder=PORDER_TEXT, ) - def _gate(self, node, xy=None): + def _gate(self, node, node_data, xy=None): """Draw a 1-qubit gate""" if xy is None: - xy = self._data[node]["q_xy"][0] + xy = node_data[node]["q_xy"][0] xpos, ypos = xy - wid = max(self._data[node]["width"], WID) + wid = max(node_data[node]["width"], WID) box = self._patches_mod.Rectangle( xy=(xpos - 0.5 * wid, ypos - 0.5 * HIG), width=wid, height=HIG, - fc=self._data[node]["fc"], - ec=self._data[node]["ec"], + fc=node_data[node]["fc"], + ec=node_data[node]["ec"], linewidth=self._lwidth15, zorder=PORDER_GATE, ) self._ax.add_patch(box) - if "gate_text" in self._data[node]: + if "gate_text" in node_data[node]: gate_ypos = ypos - if "param" in self._data[node] and self._data[node]["param"] != "": + if "param_text" in node_data[node] and node_data[node]["param_text"] != "": gate_ypos = ypos + 0.15 * HIG self._ax.text( xpos, ypos - 0.3 * HIG, - self._data[node]["param"], + node_data[node]["param_text"], ha="center", va="center", fontsize=self._sfs, - color=self._data[node]["sc"], + color=node_data[node]["sc"], clip_on=True, zorder=PORDER_TEXT, ) self._ax.text( xpos, gate_ypos, - self._data[node]["gate_text"], + node_data[node]["gate_text"], ha="center", va="center", fontsize=self._fs, - color=self._data[node]["gt"], + color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, ) - def _multiqubit_gate(self, node, xy=None): + def _multiqubit_gate(self, node, node_data, xy=None): """Draw a gate covering more than one qubit""" op = node.op if xy is None: - xy = self._data[node]["q_xy"] + xy = node_data[node]["q_xy"] # Swap gate if isinstance(op, SwapGate): - self._swap(xy, node, self._data[node]["lc"]) + self._swap(xy, node, node_data, node_data[node]["lc"]) return # RZZ Gate elif isinstance(op, RZZGate): - self._symmetric_gate(node, RZZGate) + self._symmetric_gate(node, node_data, RZZGate) return - c_xy = self._data[node]["c_xy"] + c_xy = node_data[node]["c_xy"] xpos = min(x[0] for x in xy) ypos = min(y[1] for y in xy) ypos_max = max(y[1] for y in xy) @@ -1137,16 +1168,17 @@ def _multiqubit_gate(self, node, xy=None): cypos = min(y[1] for y in c_xy) ypos = min(ypos, cypos) - wid = max(self._data[node]["width"] + 0.21, WID) + wid = max(node_data[node]["width"] + 0.21, WID) + + qubit_span = abs(ypos) - abs(ypos_max) + height = HIG + qubit_span - qubit_span = abs(ypos) - abs(ypos_max) + 1 - height = HIG + (qubit_span - 1) box = self._patches_mod.Rectangle( xy=(xpos - 0.5 * wid, ypos - 0.5 * HIG), width=wid, height=height, - fc=self._data[node]["fc"], - ec=self._data[node]["ec"], + fc=node_data[node]["fc"], + ec=node_data[node]["ec"], linewidth=self._lwidth15, zorder=PORDER_GATE, ) @@ -1161,7 +1193,7 @@ def _multiqubit_gate(self, node, xy=None): ha="left", va="center", fontsize=self._fs, - color=self._data[node]["gt"], + color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, ) @@ -1175,42 +1207,42 @@ def _multiqubit_gate(self, node, xy=None): ha="left", va="center", fontsize=self._fs, - color=self._data[node]["gt"], + color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, ) - if "gate_text" in self._data[node] and self._data[node]["gate_text"] != "": - gate_ypos = ypos + 0.5 * (qubit_span - 1) - if "param" in self._data[node] and self._data[node]["param"] != "": + if "gate_text" in node_data[node] and node_data[node]["gate_text"] != "": + gate_ypos = ypos + 0.5 * qubit_span + if "param_text" in node_data[node] and node_data[node]["param_text"] != "": gate_ypos = ypos + 0.4 * height self._ax.text( xpos + 0.11, ypos + 0.2 * height, - self._data[node]["param"], + node_data[node]["param_text"], ha="center", va="center", fontsize=self._sfs, - color=self._data[node]["sc"], + color=node_data[node]["sc"], clip_on=True, zorder=PORDER_TEXT, ) self._ax.text( xpos + 0.11, gate_ypos, - self._data[node]["gate_text"], + node_data[node]["gate_text"], ha="center", va="center", fontsize=self._fs, - color=self._data[node]["gt"], + color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, ) - def _control_gate(self, node): + def _control_gate(self, node, node_data): """Draw a controlled gate""" op = node.op + xy = node_data[node]["q_xy"] base_type = None if not hasattr(op, "base_gate") else op.base_gate - xy = self._data[node]["q_xy"] qubit_b = min(xy, key=lambda xy: xy[1]) qubit_t = max(xy, key=lambda xy: xy[1]) num_ctrl_qubits = op.num_ctrl_qubits @@ -1219,29 +1251,29 @@ def _control_gate(self, node): op.ctrl_state, num_ctrl_qubits, xy, - ec=self._data[node]["ec"], - tc=self._data[node]["tc"], - text=self._data[node]["ctrl_text"], + ec=node_data[node]["ec"], + tc=node_data[node]["tc"], + text=node_data[node]["ctrl_text"], qargs=node.qargs, ) - self._line(qubit_b, qubit_t, lc=self._data[node]["lc"]) + self._line(qubit_b, qubit_t, lc=node_data[node]["lc"]) if isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, ZGate, RZZGate)): - self._symmetric_gate(node, base_type) + self._symmetric_gate(node, node_data, base_type) elif num_qargs == 1 and isinstance(base_type, XGate): tgt_color = self._style["dispcol"]["target"] tgt = tgt_color if isinstance(tgt_color, str) else tgt_color[0] - self._x_tgt_qubit(xy[num_ctrl_qubits], ec=self._data[node]["ec"], ac=tgt) + self._x_tgt_qubit(xy[num_ctrl_qubits], ec=node_data[node]["ec"], ac=tgt) elif num_qargs == 1: - self._gate(node, xy[num_ctrl_qubits:][0]) + self._gate(node, node_data, xy[num_ctrl_qubits:][0]) elif isinstance(base_type, SwapGate): - self._swap(xy[num_ctrl_qubits:], node, self._data[node]["lc"]) + self._swap(xy[num_ctrl_qubits:], node, node_data, node_data[node]["lc"]) else: - self._multiqubit_gate(node, xy[num_ctrl_qubits:]) + self._multiqubit_gate(node, node_data, xy[num_ctrl_qubits:]) def _set_ctrl_bits( self, ctrl_state, num_ctrl_qubits, qbit, ec=None, tc=None, text="", qargs=None @@ -1336,16 +1368,16 @@ def _x_tgt_qubit(self, xy, ec=None, ac=None): zorder=PORDER_GATE + 1, ) - def _symmetric_gate(self, node, base_type): + def _symmetric_gate(self, node, node_data, base_type): """Draw symmetric gates for cz, cu1, cp, and rzz""" op = node.op - xy = self._data[node]["q_xy"] + xy = node_data[node]["q_xy"] qubit_b = min(xy, key=lambda xy: xy[1]) qubit_t = max(xy, key=lambda xy: xy[1]) base_type = None if not hasattr(op, "base_gate") else op.base_gate - ec = self._data[node]["ec"] - tc = self._data[node]["tc"] - lc = self._data[node]["lc"] + ec = node_data[node]["ec"] + tc = node_data[node]["tc"] + lc = node_data[node]["lc"] # cz and mcz gates if not isinstance(op, ZGate) and isinstance(base_type, ZGate): @@ -1356,24 +1388,26 @@ def _symmetric_gate(self, node, base_type): # cu1, cp, rzz, and controlled rzz gates (sidetext gates) elif isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, RZZGate)): num_ctrl_qubits = 0 if isinstance(op, RZZGate) else op.num_ctrl_qubits - gate_text = "P" if isinstance(base_type, PhaseGate) else self._data[node]["gate_text"] + gate_text = "P" if isinstance(base_type, PhaseGate) else node_data[node]["gate_text"] self._ctrl_qubit(xy[num_ctrl_qubits], fc=ec, ec=ec, tc=tc) if not isinstance(base_type, (U1Gate, PhaseGate)): self._ctrl_qubit(xy[num_ctrl_qubits + 1], fc=ec, ec=ec, tc=tc) - self._sidetext(node, qubit_b, tc=tc, text=f"{gate_text} ({self._data[node]['param']})") + self._sidetext( + node, node_data, qubit_b, tc=tc, text=f"{gate_text} ({node_data[node]['param_text']})" + ) self._line(qubit_b, qubit_t, lc=lc) - def _swap(self, xy, node, color=None): + def _swap(self, xy, node, node_data, color=None): """Draw a Swap gate""" self._swap_cross(xy[0], color=color) self._swap_cross(xy[1], color=color) self._line(xy[0], xy[1], lc=color) # add calibration text - gate_text = self._data[node]["gate_text"].split("\n")[-1] - if self._data[node]["raw_gate_text"] in self._calibrations: + gate_text = node_data[node]["gate_text"].split("\n")[-1] + if node_data[node]["raw_gate_text"] in self._calibrations: xpos, ypos = xy[0] self._ax.text( xpos, @@ -1406,12 +1440,12 @@ def _swap_cross(self, xy, color=None): zorder=PORDER_LINE + 1, ) - def _sidetext(self, node, xy, tc=None, text=""): + def _sidetext(self, node, node_data, xy, tc=None, text=""): """Draw the sidetext for symmetric gates""" xpos, ypos = xy # 0.11 = the initial gap, add 1/2 text width to place on the right - xp = xpos + 0.11 + self._data[node]["width"] / 2 + xp = xpos + 0.11 + node_data[node]["width"] / 2 self._ax.text( xp, ypos + HIG, @@ -1461,33 +1495,18 @@ def _line(self, xy0, xy1, lc=None, ls=None, zorder=PORDER_LINE): zorder=zorder, ) - -class Anchor: - """Locate the anchors for the gates""" - - def __init__(self, num_wires, y_index, fold): - self._num_wires = num_wires - self._fold = fold - self._y_index = y_index - self._x_index = 0 - - def plot_coord(self, x_index, gate_width, x_offset): + def _plot_coord(self, x_index, y_index, gate_width, x_offset): """Get the coord positions for an index""" - h_pos = x_index % self._fold + 1 # check folding - if self._fold > 0: - if h_pos + (gate_width - 1) > self._fold: - x_index += self._fold - (h_pos - 1) - x_pos = x_index % self._fold + 0.5 * gate_width + 0.04 - y_pos = self._y_index - (x_index // self._fold) * (self._num_wires + 1) - else: - x_pos = x_index + 0.5 * gate_width + 0.04 - y_pos = self._y_index + fold = self._fold if self._fold > 0 else 100000 + h_pos = x_index % fold + 1 + + if h_pos + (gate_width - 1) > fold: + x_index += fold - (h_pos - 1) + x_pos = x_index % fold + x_offset + 0.04 + x_pos += 0.5 * gate_width + y_pos = y_index - (x_index // fold) * (self._n_lines + 1) # could have been updated, so need to store self._x_index = x_index - return x_pos + x_offset, y_pos - - def get_x_index(self): - """Getter for the x index""" - return self._x_index + return x_pos, y_pos From 6fca5d84afbc1c348cc1f31cd216357c4fd9c574 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 26 Apr 2023 15:27:17 -0700 Subject: [PATCH 04/30] Lint --- .../notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml b/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml index 59d8cef6d5e9..6a8af404d037 100644 --- a/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml +++ b/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml @@ -5,6 +5,6 @@ upgrade: ``layout``, ``global_phase``, ``qregs`` and ``cregs`` have been removed. They were originally deprecated in Qiskit Terra 0.20. These objects are simply inferred from the given ``circuit`` now. - + This is an internal worker class of the visualization routines. It is unlikely you will need to change any of your code. From 1c543a0dda329b65b61da187a66df289dff71a77 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Mon, 8 May 2023 14:42:56 -0700 Subject: [PATCH 05/30] Minor changes to match flow changes --- qiskit/visualization/circuit/matplotlib.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index f3b73ff965a7..14792a392ba1 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -55,6 +55,8 @@ PORDER_GRAY = 3 PORDER_TEXT = 6 +INFINITE_FOLD = 10000000 + @_optionals.HAS_MATPLOTLIB.require_in_instance @_optionals.HAS_PYLATEX.require_in_instance @@ -360,17 +362,18 @@ def _get_layer_widths(self, node_data, wire_map): """Compute the layer_widths for the layers""" layer_widths = {} + layer_num = -1 for i, layer in enumerate(self._nodes): widest_box = WID + layer_num += 1 first_node = True - save_layer = {} for node in layer: if first_node: + save_layer = i first_node = False - save_layer[node] = i else: - save_layer[node] = -1 - layer_widths[node] = (1, save_layer[node]) + save_layer = -1 + layer_widths[node] = [1, layer_num, save_layer] op = node.op node_data[node] = {} @@ -447,7 +450,7 @@ def _get_layer_widths(self, node_data, wire_map): widest_box = box_width node_data[node]["width"] = max(raw_gate_width, raw_param_width) for node in layer: - layer_widths[node] = (int(widest_box) + 1, save_layer[node]) + layer_widths[node][0] = int(widest_box) + 1 return layer_widths @@ -1434,7 +1437,7 @@ def _line(self, xy0, xy1, lc=None, ls=None, zorder=PORDER_LINE): def _plot_coord(self, x_index, y_index, gate_width, x_offset): """Get the coord positions for an index""" # check folding - fold = self._fold if self._fold > 0 else 100000 + fold = self._fold if self._fold > 0 else INFINITE_FOLD h_pos = x_index % fold + 1 if h_pos + (gate_width - 1) > fold: From b01d536dece0973c292359c3511cb68039bc0bbc Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Mon, 8 May 2023 14:46:58 -0700 Subject: [PATCH 06/30] Cleanup --- qiskit/visualization/circuit/matplotlib.py | 6 +++++- .../remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 14792a392ba1..1664c383af8a 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -1334,7 +1334,11 @@ def _symmetric_gate(self, node, node_data, base_type): self._ctrl_qubit(xy[num_ctrl_qubits + 1], fc=ec, ec=ec, tc=tc) self._sidetext( - node, node_data, qubit_b, tc=tc, text=f"{gate_text} ({node_data[node]['param_text']})" + node, + node_data, + qubit_b, + tc=tc, + text=f"{gate_text} ({node_data[node]['param_text']})", ) self._line(qubit_b, qubit_t, lc=lc) diff --git a/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml b/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml index 6a8af404d037..59d8cef6d5e9 100644 --- a/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml +++ b/releasenotes/notes/remove-deprecated-mpl-drawer-9d6eaa40d5a86777.yaml @@ -5,6 +5,6 @@ upgrade: ``layout``, ``global_phase``, ``qregs`` and ``cregs`` have been removed. They were originally deprecated in Qiskit Terra 0.20. These objects are simply inferred from the given ``circuit`` now. - + This is an internal worker class of the visualization routines. It is unlikely you will need to change any of your code. From c30564c417fe596616ad12bdbab1b0798f525e6f Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 10 May 2023 05:30:31 -0700 Subject: [PATCH 07/30] Fix layer width per node and comments --- qiskit/visualization/circuit/matplotlib.py | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 1664c383af8a..9fcc961e0828 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -279,19 +279,19 @@ def draw(self, filename=None, verbose=False): self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict) # load the coordinates for each gate and compute number of folds - max_x = self._get_coords(node_data, wire_map, layer_widths, qubits_dict, clbits_dict) - num_folds = max(0, max_x - 1) // self._fold if self._fold > 0 else 0 + max_x_index = self._get_coords(node_data, wire_map, layer_widths, qubits_dict, clbits_dict) + num_folds = max(0, max_x_index - 1) // self._fold if self._fold > 0 else 0 # The window size limits are computed, followed by one of the four possible ways # of scaling the drawing. # compute the window size - if max_x > self._fold > 0: + if max_x_index > self._fold > 0: xmax = self._fold + self._x_offset + 0.1 ymax = (num_folds + 1) * (self._n_lines + 1) - 1 else: x_incr = 0.4 if not self._nodes else 0.9 - xmax = max_x + 1 + self._x_offset - x_incr + xmax = max_x_index + 1 + self._x_offset - x_incr ymax = self._n_lines xl = -self._style["margin"][0] @@ -342,7 +342,7 @@ def draw(self, filename=None, verbose=False): self._plt_mod.text( xl, yt, "Global Phase: %s" % pi_check(self._global_phase, output="mpl") ) - self._draw_regs_wires(num_folds, xmax, max_x, qubits_dict, clbits_dict) + self._draw_regs_wires(num_folds, xmax, max_x_index, qubits_dict, clbits_dict) self._draw_ops( self._nodes, node_data, wire_map, layer_widths, qubits_dict, clbits_dict, verbose ) @@ -362,18 +362,16 @@ def _get_layer_widths(self, node_data, wire_map): """Compute the layer_widths for the layers""" layer_widths = {} - layer_num = -1 - for i, layer in enumerate(self._nodes): + for layer_num, layer in enumerate(self._nodes): widest_box = WID - layer_num += 1 first_node = True for node in layer: + # Put the layer_num in the first node in the layer and put -1 in the rest if first_node: - save_layer = i first_node = False else: - save_layer = -1 - layer_widths[node] = [1, layer_num, save_layer] + layer_num = -1 + layer_widths[node] = [1, layer_num] op = node.op node_data[node] = {} @@ -530,9 +528,8 @@ def _get_coords( qubits_dict, clbits_dict, ): - """Load all the coordinate info needed to place the gates on the drawing. - flow_node is node id for the parent of a gate inside a ControlFlowOp circuit. - """ + """Load all the coordinate info needed to place the gates on the drawing.""" + prev_x_index = -1 for i, layer in enumerate(self._nodes): curr_x_index = prev_x_index + 1 @@ -587,6 +584,7 @@ def _get_coords( def _get_text_width(self, text, fontsize, param=False, reg_remove_under=None): """Compute the width of a string in the default font""" + from pylatexenc.latex2text import LatexNodes2Text if not text: @@ -630,7 +628,7 @@ def _get_text_width(self, text, fontsize, param=False, reg_remove_under=None): sum_text *= self._subfont_factor return sum_text - def _draw_regs_wires(self, num_folds, xmax, max_x, qubits_dict, clbits_dict): + def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dict): """Draw the register names and numbers, wires, and vertical lines at the ends""" for fold_num in range(num_folds + 1): @@ -732,7 +730,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x, qubits_dict, clbits_dict): # draw index number if self._style["index"]: - for layer_num in range(max_x): + for layer_num in range(max_x_index): if self._fold > 0: x_coord = layer_num % self._fold + self._x_offset + 0.53 y_coord = -(layer_num // self._fold) * (self._n_lines + 1) + 0.65 @@ -755,6 +753,7 @@ def _draw_ops( self, nodes, node_data, wire_map, layer_widths, qubits_dict, clbits_dict, verbose=False ): """Draw the gates in the circuit""" + prev_x_index = -1 for i, layer in enumerate(nodes): l_width = [] @@ -819,6 +818,7 @@ def _draw_ops( def _get_colors(self, node, node_data): """Get all the colors needed for drawing the circuit""" + op = node.op base_name = None if not hasattr(op, "base_gate") else op.base_gate.name color = None From 8df2b587a71de3f30b601f5891faaca6abb3ece4 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 10 May 2023 09:13:17 -0700 Subject: [PATCH 08/30] Lint --- qiskit/visualization/circuit/matplotlib.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 9fcc961e0828..71725939d160 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -273,7 +273,7 @@ def draw(self, filename=None, verbose=False): clbits_dict = {} # get layer widths - layer_widths = self._get_layer_widths(node_data, wire_map) + layer_widths = self._get_layer_widths(node_data) # load the _qubit_dict and _clbit_dict with register info self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict) @@ -343,9 +343,7 @@ def draw(self, filename=None, verbose=False): xl, yt, "Global Phase: %s" % pi_check(self._global_phase, output="mpl") ) self._draw_regs_wires(num_folds, xmax, max_x_index, qubits_dict, clbits_dict) - self._draw_ops( - self._nodes, node_data, wire_map, layer_widths, qubits_dict, clbits_dict, verbose - ) + self._draw_ops(self._nodes, node_data, wire_map, layer_widths, clbits_dict, verbose) if filename: self._figure.savefig( @@ -358,7 +356,7 @@ def draw(self, filename=None, verbose=False): matplotlib_close_if_inline(self._figure) return self._figure - def _get_layer_widths(self, node_data, wire_map): + def _get_layer_widths(self, node_data): """Compute the layer_widths for the layers""" layer_widths = {} @@ -531,7 +529,7 @@ def _get_coords( """Load all the coordinate info needed to place the gates on the drawing.""" prev_x_index = -1 - for i, layer in enumerate(self._nodes): + for layer in self._nodes: curr_x_index = prev_x_index + 1 l_width = [] for node in layer: @@ -749,13 +747,11 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic zorder=PORDER_TEXT, ) - def _draw_ops( - self, nodes, node_data, wire_map, layer_widths, qubits_dict, clbits_dict, verbose=False - ): + def _draw_ops(self, nodes, node_data, wire_map, layer_widths, clbits_dict, verbose=False): """Draw the gates in the circuit""" prev_x_index = -1 - for i, layer in enumerate(nodes): + for layer in nodes: l_width = [] curr_x_index = prev_x_index + 1 From 3caf36a24f056b0b9973c3df33ee67616c13ec80 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Thu, 11 May 2023 05:19:13 -0700 Subject: [PATCH 09/30] Adjust layer num loading --- qiskit/visualization/circuit/matplotlib.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 71725939d160..604ad8ddc9b7 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -362,12 +362,10 @@ def _get_layer_widths(self, node_data): layer_widths = {} for layer_num, layer in enumerate(self._nodes): widest_box = WID - first_node = True - for node in layer: + for i, node in enumerate(layer): # Put the layer_num in the first node in the layer and put -1 in the rest - if first_node: - first_node = False - else: + # so that layer widths are not counted more than once + if i != 0: layer_num = -1 layer_widths[node] = [1, layer_num] From 179f17a3d704fb74512afc0a83332145cd92ffd8 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Tue, 23 May 2023 14:44:58 -0700 Subject: [PATCH 10/30] Revert n_lines --- qiskit/visualization/circuit/matplotlib.py | 45 +++++++++++----------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 604ad8ddc9b7..a94d53c6a61c 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -150,8 +150,6 @@ def __init__( self._lwidth15 = 1.5 self._lwidth2 = 2.0 self._x_offset = 0.0 - - self._n_lines = 0 self._x_index = 0 # _char_list for finding text_width of names, labels, and params @@ -276,10 +274,10 @@ def draw(self, filename=None, verbose=False): layer_widths = self._get_layer_widths(node_data) # load the _qubit_dict and _clbit_dict with register info - self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict) + n_lines = self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict) # load the coordinates for each gate and compute number of folds - max_x_index = self._get_coords(node_data, wire_map, layer_widths, qubits_dict, clbits_dict) + max_x_index = self._get_coords(node_data, wire_map, layer_widths, qubits_dict, clbits_dict, n_lines) num_folds = max(0, max_x_index - 1) // self._fold if self._fold > 0 else 0 # The window size limits are computed, followed by one of the four possible ways @@ -288,11 +286,11 @@ def draw(self, filename=None, verbose=False): # compute the window size if max_x_index > self._fold > 0: xmax = self._fold + self._x_offset + 0.1 - ymax = (num_folds + 1) * (self._n_lines + 1) - 1 + ymax = (num_folds + 1) * (n_lines + 1) - 1 else: x_incr = 0.4 if not self._nodes else 0.9 xmax = max_x_index + 1 + self._x_offset - x_incr - ymax = self._n_lines + ymax = n_lines xl = -self._style["margin"][0] xr = xmax + self._style["margin"][1] @@ -342,8 +340,8 @@ def draw(self, filename=None, verbose=False): self._plt_mod.text( xl, yt, "Global Phase: %s" % pi_check(self._global_phase, output="mpl") ) - self._draw_regs_wires(num_folds, xmax, max_x_index, qubits_dict, clbits_dict) - self._draw_ops(self._nodes, node_data, wire_map, layer_widths, clbits_dict, verbose) + self._draw_regs_wires(num_folds, xmax, max_x_index, qubits_dict, clbits_dict, n_lines) + self._draw_ops(self._nodes, node_data, wire_map, layer_widths, clbits_dict, n_lines, verbose) if filename: self._figure.savefig( @@ -452,7 +450,7 @@ def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict): """Get all the info for drawing bit/reg names and numbers""" longest_wire_label_width = 0 - self._n_lines = 0 + n_lines = 0 initial_qbit = " |0>" if self._initial_state else "" initial_cbit = " 0" if self._initial_state else "" @@ -498,14 +496,14 @@ def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict): "y": pos, "wire_label": wire_label, } - self._n_lines += 1 + n_lines += 1 else: if ( not self._cregbundle or register is None or (self._cregbundle and isinstance(wire, ClassicalRegister)) ): - self._n_lines += 1 + n_lines += 1 idx += 1 pos = y_off - idx @@ -515,6 +513,7 @@ def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict): "register": register, } self._x_offset = -1.2 + longest_wire_label_width + return n_lines def _get_coords( self, @@ -523,6 +522,7 @@ def _get_coords( layer_widths, qubits_dict, clbits_dict, + n_lines ): """Load all the coordinate info needed to place the gates on the drawing.""" @@ -551,14 +551,14 @@ def _get_coords( # qubit coordinates node_data[node]["q_xy"] = [ self._plot_coord( - curr_x_index, qubits_dict[ii]["y"], layer_widths[node][0], self._x_offset + curr_x_index, qubits_dict[ii]["y"], layer_widths[node][0], self._x_offset, n_lines ) for ii in q_indxs ] # clbit coordinates node_data[node]["c_xy"] = [ self._plot_coord( - curr_x_index, clbits_dict[ii]["y"], layer_widths[node][0], self._x_offset + curr_x_index, clbits_dict[ii]["y"], layer_widths[node][0], self._x_offset, n_lines ) for ii in c_indxs ] @@ -624,14 +624,14 @@ def _get_text_width(self, text, fontsize, param=False, reg_remove_under=None): sum_text *= self._subfont_factor return sum_text - def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dict): + def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dict, n_lines): """Draw the register names and numbers, wires, and vertical lines at the ends""" for fold_num in range(num_folds + 1): # quantum registers for qubit in qubits_dict.values(): qubit_label = qubit["wire_label"] - y = qubit["y"] - fold_num * (self._n_lines + 1) + y = qubit["y"] - fold_num * (n_lines + 1) self._ax.text( self._x_offset - 0.2, y, @@ -649,7 +649,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic # classical registers this_clbit_dict = {} for clbit in clbits_dict.values(): - y = clbit["y"] - fold_num * (self._n_lines + 1) + y = clbit["y"] - fold_num * (n_lines + 1) if y not in this_clbit_dict.keys(): this_clbit_dict[y] = { "val": 1, @@ -705,8 +705,8 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic if feedline_l or feedline_r: xpos_l = self._x_offset - 0.01 xpos_r = self._fold + self._x_offset + 0.1 - ypos1 = -fold_num * (self._n_lines + 1) - ypos2 = -(fold_num + 1) * (self._n_lines) - fold_num + 1 + ypos1 = -fold_num * (n_lines + 1) + ypos2 = -(fold_num + 1) * (n_lines) - fold_num + 1 if feedline_l: self._ax.plot( [xpos_l, xpos_l], @@ -729,7 +729,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic for layer_num in range(max_x_index): if self._fold > 0: x_coord = layer_num % self._fold + self._x_offset + 0.53 - y_coord = -(layer_num // self._fold) * (self._n_lines + 1) + 0.65 + y_coord = -(layer_num // self._fold) * (n_lines + 1) + 0.65 else: x_coord = layer_num + self._x_offset + 0.53 y_coord = 0.65 @@ -745,7 +745,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic zorder=PORDER_TEXT, ) - def _draw_ops(self, nodes, node_data, wire_map, layer_widths, clbits_dict, verbose=False): + def _draw_ops(self, nodes, node_data, wire_map, layer_widths, clbits_dict, n_lines, verbose=False): """Draw the gates in the circuit""" prev_x_index = -1 @@ -770,6 +770,7 @@ def _draw_ops(self, nodes, node_data, wire_map, layer_widths, clbits_dict, verbo clbits_dict[ii]["y"], layer_widths[node][0], self._x_offset, + n_lines ) for ii in clbits_dict ] @@ -1432,7 +1433,7 @@ def _line(self, xy0, xy1, lc=None, ls=None, zorder=PORDER_LINE): zorder=zorder, ) - def _plot_coord(self, x_index, y_index, gate_width, x_offset): + def _plot_coord(self, x_index, y_index, gate_width, x_offset, n_lines): """Get the coord positions for an index""" # check folding fold = self._fold if self._fold > 0 else INFINITE_FOLD @@ -1442,7 +1443,7 @@ def _plot_coord(self, x_index, y_index, gate_width, x_offset): x_index += fold - (h_pos - 1) x_pos = x_index % fold + x_offset + 0.04 x_pos += 0.5 * gate_width - y_pos = y_index - (x_index // fold) * (self._n_lines + 1) + y_pos = y_index - (x_index // fold) * (n_lines + 1) # could have been updated, so need to store self._x_index = x_index From addbfbc85da7bdf8a98e17b43654005f74e011dd Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Tue, 23 May 2023 15:02:37 -0700 Subject: [PATCH 11/30] Lint --- qiskit/visualization/circuit/matplotlib.py | 36 +++++++++++++--------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index a94d53c6a61c..608f19136979 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -277,7 +277,9 @@ def draw(self, filename=None, verbose=False): n_lines = self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict) # load the coordinates for each gate and compute number of folds - max_x_index = self._get_coords(node_data, wire_map, layer_widths, qubits_dict, clbits_dict, n_lines) + max_x_index = self._get_coords( + node_data, wire_map, layer_widths, qubits_dict, clbits_dict, n_lines + ) num_folds = max(0, max_x_index - 1) // self._fold if self._fold > 0 else 0 # The window size limits are computed, followed by one of the four possible ways @@ -341,7 +343,9 @@ def draw(self, filename=None, verbose=False): xl, yt, "Global Phase: %s" % pi_check(self._global_phase, output="mpl") ) self._draw_regs_wires(num_folds, xmax, max_x_index, qubits_dict, clbits_dict, n_lines) - self._draw_ops(self._nodes, node_data, wire_map, layer_widths, clbits_dict, n_lines, verbose) + self._draw_ops( + self._nodes, node_data, wire_map, layer_widths, clbits_dict, n_lines, verbose + ) if filename: self._figure.savefig( @@ -515,15 +519,7 @@ def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict): self._x_offset = -1.2 + longest_wire_label_width return n_lines - def _get_coords( - self, - node_data, - wire_map, - layer_widths, - qubits_dict, - clbits_dict, - n_lines - ): + def _get_coords(self, node_data, wire_map, layer_widths, qubits_dict, clbits_dict, n_lines): """Load all the coordinate info needed to place the gates on the drawing.""" prev_x_index = -1 @@ -551,14 +547,22 @@ def _get_coords( # qubit coordinates node_data[node]["q_xy"] = [ self._plot_coord( - curr_x_index, qubits_dict[ii]["y"], layer_widths[node][0], self._x_offset, n_lines + curr_x_index, + qubits_dict[ii]["y"], + layer_widths[node][0], + self._x_offset, + n_lines, ) for ii in q_indxs ] # clbit coordinates node_data[node]["c_xy"] = [ self._plot_coord( - curr_x_index, clbits_dict[ii]["y"], layer_widths[node][0], self._x_offset, n_lines + curr_x_index, + clbits_dict[ii]["y"], + layer_widths[node][0], + self._x_offset, + n_lines, ) for ii in c_indxs ] @@ -745,7 +749,9 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic zorder=PORDER_TEXT, ) - def _draw_ops(self, nodes, node_data, wire_map, layer_widths, clbits_dict, n_lines, verbose=False): + def _draw_ops( + self, nodes, node_data, wire_map, layer_widths, clbits_dict, n_lines, verbose=False + ): """Draw the gates in the circuit""" prev_x_index = -1 @@ -770,7 +776,7 @@ def _draw_ops(self, nodes, node_data, wire_map, layer_widths, clbits_dict, n_lin clbits_dict[ii]["y"], layer_widths[node][0], self._x_offset, - n_lines + n_lines, ) for ii in clbits_dict ] From 3ea5acead7a7ba8438928f2ee2ae4bb2b1fa206f Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 26 May 2023 09:26:06 -0700 Subject: [PATCH 12/30] Add glob_data --- qiskit/visualization/circuit/matplotlib.py | 92 +++++++++++----------- 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 608f19136979..dd9624496f10 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -149,8 +149,6 @@ def __init__( self._lwidth1 = 1.0 self._lwidth15 = 1.5 self._lwidth2 = 2.0 - self._x_offset = 0.0 - self._x_index = 0 # _char_list for finding text_width of names, labels, and params self._char_list = { @@ -266,19 +264,21 @@ def draw(self, filename=None, verbose=False): # and colors 'fc', 'ec', 'lc', 'sc', 'gt', and 'tc' node_data = {} + glob_data = {} + # dicts for the names and locations of register/bit labels qubits_dict = {} clbits_dict = {} # get layer widths - layer_widths = self._get_layer_widths(node_data) + layer_widths = self._get_layer_widths(node_data, glob_data) # load the _qubit_dict and _clbit_dict with register info - n_lines = self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict) + self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict, glob_data) # load the coordinates for each gate and compute number of folds max_x_index = self._get_coords( - node_data, wire_map, layer_widths, qubits_dict, clbits_dict, n_lines + node_data, wire_map, layer_widths, qubits_dict, clbits_dict, glob_data ) num_folds = max(0, max_x_index - 1) // self._fold if self._fold > 0 else 0 @@ -287,12 +287,12 @@ def draw(self, filename=None, verbose=False): # compute the window size if max_x_index > self._fold > 0: - xmax = self._fold + self._x_offset + 0.1 - ymax = (num_folds + 1) * (n_lines + 1) - 1 + xmax = self._fold + glob_data["x_offset"] + 0.1 + ymax = (num_folds + 1) * (glob_data["n_lines"] + 1) - 1 else: x_incr = 0.4 if not self._nodes else 0.9 - xmax = max_x_index + 1 + self._x_offset - x_incr - ymax = n_lines + xmax = max_x_index + 1 + glob_data["x_offset"] - x_incr + ymax = glob_data["n_lines"] xl = -self._style["margin"][0] xr = xmax + self._style["margin"][1] @@ -342,9 +342,9 @@ def draw(self, filename=None, verbose=False): self._plt_mod.text( xl, yt, "Global Phase: %s" % pi_check(self._global_phase, output="mpl") ) - self._draw_regs_wires(num_folds, xmax, max_x_index, qubits_dict, clbits_dict, n_lines) + self._draw_regs_wires(num_folds, xmax, max_x_index, qubits_dict, clbits_dict, glob_data) self._draw_ops( - self._nodes, node_data, wire_map, layer_widths, clbits_dict, n_lines, verbose + self._nodes, node_data, wire_map, layer_widths, clbits_dict, glob_data, verbose ) if filename: @@ -358,7 +358,7 @@ def draw(self, filename=None, verbose=False): matplotlib_close_if_inline(self._figure) return self._figure - def _get_layer_widths(self, node_data): + def _get_layer_widths(self, node_data, glob_data): """Compute the layer_widths for the layers""" layer_widths = {} @@ -450,11 +450,11 @@ def _get_layer_widths(self, node_data): return layer_widths - def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict): + def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict, glob_data): """Get all the info for drawing bit/reg names and numbers""" longest_wire_label_width = 0 - n_lines = 0 + glob_data["n_lines"] = 0 initial_qbit = " |0>" if self._initial_state else "" initial_cbit = " 0" if self._initial_state else "" @@ -500,14 +500,14 @@ def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict): "y": pos, "wire_label": wire_label, } - n_lines += 1 + glob_data["n_lines"] += 1 else: if ( not self._cregbundle or register is None or (self._cregbundle and isinstance(wire, ClassicalRegister)) ): - n_lines += 1 + glob_data["n_lines"] += 1 idx += 1 pos = y_off - idx @@ -516,10 +516,9 @@ def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict): "wire_label": wire_label, "register": register, } - self._x_offset = -1.2 + longest_wire_label_width - return n_lines + glob_data["x_offset"] = -1.2 + longest_wire_label_width - def _get_coords(self, node_data, wire_map, layer_widths, qubits_dict, clbits_dict, n_lines): + def _get_coords(self, node_data, wire_map, layer_widths, qubits_dict, clbits_dict, glob_data): """Load all the coordinate info needed to place the gates on the drawing.""" prev_x_index = -1 @@ -550,8 +549,7 @@ def _get_coords(self, node_data, wire_map, layer_widths, qubits_dict, clbits_dic curr_x_index, qubits_dict[ii]["y"], layer_widths[node][0], - self._x_offset, - n_lines, + glob_data, ) for ii in q_indxs ] @@ -561,14 +559,13 @@ def _get_coords(self, node_data, wire_map, layer_widths, qubits_dict, clbits_dic curr_x_index, clbits_dict[ii]["y"], layer_widths[node][0], - self._x_offset, - n_lines, + glob_data, ) for ii in c_indxs ] # update index based on the value from plotting - curr_x_index = self._x_index + curr_x_index = glob_data["next_x_index"] l_width.append(layer_widths[node][0]) # adjust the column if there have been barriers encountered, but not plotted @@ -628,16 +625,16 @@ def _get_text_width(self, text, fontsize, param=False, reg_remove_under=None): sum_text *= self._subfont_factor return sum_text - def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dict, n_lines): + def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dict, glob_data): """Draw the register names and numbers, wires, and vertical lines at the ends""" for fold_num in range(num_folds + 1): # quantum registers for qubit in qubits_dict.values(): qubit_label = qubit["wire_label"] - y = qubit["y"] - fold_num * (n_lines + 1) + y = qubit["y"] - fold_num * (glob_data["n_lines"] + 1) self._ax.text( - self._x_offset - 0.2, + glob_data["x_offset"] - 0.2, y, qubit_label, ha="right", @@ -648,12 +645,12 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic zorder=PORDER_TEXT, ) # draw the qubit wire - self._line([self._x_offset, y], [xmax, y], zorder=PORDER_REGLINE) + self._line([glob_data["x_offset"], y], [xmax, y], zorder=PORDER_REGLINE) # classical registers this_clbit_dict = {} for clbit in clbits_dict.values(): - y = clbit["y"] - fold_num * (n_lines + 1) + y = clbit["y"] - fold_num * (glob_data["n_lines"] + 1) if y not in this_clbit_dict.keys(): this_clbit_dict[y] = { "val": 1, @@ -667,13 +664,13 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic # cregbundle if self._cregbundle and this_clbit["register"] is not None: self._ax.plot( - [self._x_offset + 0.2, self._x_offset + 0.3], + [glob_data["x_offset"] + 0.2, glob_data["x_offset"] + 0.3], [y - 0.1, y + 0.1], color=self._style["cc"], zorder=PORDER_LINE, ) self._ax.text( - self._x_offset + 0.1, + glob_data["x_offset"] + 0.1, y + 0.1, str(this_clbit["register"].size), ha="left", @@ -684,7 +681,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic zorder=PORDER_TEXT, ) self._ax.text( - self._x_offset - 0.2, + glob_data["x_offset"] - 0.2, y, this_clbit["wire_label"], ha="right", @@ -696,7 +693,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic ) # draw the clbit wire self._line( - [self._x_offset, y], + [glob_data["x_offset"], y], [xmax, y], lc=self._style["cc"], ls=self._style["cline"], @@ -707,10 +704,10 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic feedline_r = num_folds > 0 and num_folds > fold_num feedline_l = fold_num > 0 if feedline_l or feedline_r: - xpos_l = self._x_offset - 0.01 - xpos_r = self._fold + self._x_offset + 0.1 - ypos1 = -fold_num * (n_lines + 1) - ypos2 = -(fold_num + 1) * (n_lines) - fold_num + 1 + xpos_l = glob_data["x_offset"] - 0.01 + xpos_r = self._fold + glob_data["x_offset"] + 0.1 + ypos1 = -fold_num * (glob_data["n_lines"] + 1) + ypos2 = -(fold_num + 1) * (glob_data["n_lines"]) - fold_num + 1 if feedline_l: self._ax.plot( [xpos_l, xpos_l], @@ -732,10 +729,10 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic if self._style["index"]: for layer_num in range(max_x_index): if self._fold > 0: - x_coord = layer_num % self._fold + self._x_offset + 0.53 - y_coord = -(layer_num // self._fold) * (n_lines + 1) + 0.65 + x_coord = layer_num % self._fold + glob_data["x_offset"] + 0.53 + y_coord = -(layer_num // self._fold) * (glob_data["n_lines"] + 1) + 0.65 else: - x_coord = layer_num + self._x_offset + 0.53 + x_coord = layer_num + glob_data["x_offset"] + 0.53 y_coord = 0.65 self._ax.text( x_coord, @@ -750,7 +747,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic ) def _draw_ops( - self, nodes, node_data, wire_map, layer_widths, clbits_dict, n_lines, verbose=False + self, nodes, node_data, wire_map, layer_widths, clbits_dict, glob_data, verbose=False ): """Draw the gates in the circuit""" @@ -775,13 +772,12 @@ def _draw_ops( curr_x_index, clbits_dict[ii]["y"], layer_widths[node][0], - self._x_offset, - n_lines, + glob_data, ) for ii in clbits_dict ] if clbits_dict: - curr_x_index = max(curr_x_index, self._x_index) + curr_x_index = max(curr_x_index, glob_data["next_x_index"]) self._condition(node, node_data, wire_map, cond_xy) # draw measure @@ -1439,7 +1435,7 @@ def _line(self, xy0, xy1, lc=None, ls=None, zorder=PORDER_LINE): zorder=zorder, ) - def _plot_coord(self, x_index, y_index, gate_width, x_offset, n_lines): + def _plot_coord(self, x_index, y_index, gate_width, glob_data): """Get the coord positions for an index""" # check folding fold = self._fold if self._fold > 0 else INFINITE_FOLD @@ -1447,10 +1443,10 @@ def _plot_coord(self, x_index, y_index, gate_width, x_offset, n_lines): if h_pos + (gate_width - 1) > fold: x_index += fold - (h_pos - 1) - x_pos = x_index % fold + x_offset + 0.04 + x_pos = x_index % fold + glob_data["x_offset"] + 0.04 x_pos += 0.5 * gate_width - y_pos = y_index - (x_index // fold) * (n_lines + 1) + y_pos = y_index - (x_index // fold) * (glob_data["n_lines"] + 1) # could have been updated, so need to store - self._x_index = x_index + glob_data["next_x_index"] = x_index return x_pos, y_pos From 7e4d8d26badda8c05bc4b15568184336ad289865 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 26 May 2023 09:31:39 -0700 Subject: [PATCH 13/30] Lint and cleanup --- qiskit/visualization/circuit/matplotlib.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index dd9624496f10..9717355db5ea 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -264,18 +264,19 @@ def draw(self, filename=None, verbose=False): # and colors 'fc', 'ec', 'lc', 'sc', 'gt', and 'tc' node_data = {} + # glob_data contains global values used throughout, "n_lines", "x_offset", "next_x_index" glob_data = {} # dicts for the names and locations of register/bit labels qubits_dict = {} clbits_dict = {} - # get layer widths - layer_widths = self._get_layer_widths(node_data, glob_data) - # load the _qubit_dict and _clbit_dict with register info self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict, glob_data) + # get layer widths + layer_widths = self._get_layer_widths(node_data, glob_data) + # load the coordinates for each gate and compute number of folds max_x_index = self._get_coords( node_data, wire_map, layer_widths, qubits_dict, clbits_dict, glob_data From 6d753ccb2fced15c5f293b2c3c7d0e107bd342f8 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 26 May 2023 10:16:06 -0700 Subject: [PATCH 14/30] Initial PR for if-else --- qiskit/visualization/circuit/_utils.py | 4 +- qiskit/visualization/circuit/matplotlib.py | 741 ++++++++++++++------- 2 files changed, 508 insertions(+), 237 deletions(-) diff --git a/qiskit/visualization/circuit/_utils.py b/qiskit/visualization/circuit/_utils.py index 0cb97755efac..2e6b6e05082f 100644 --- a/qiskit/visualization/circuit/_utils.py +++ b/qiskit/visualization/circuit/_utils.py @@ -26,7 +26,7 @@ Measure, ) from qiskit.circuit.library import PauliEvolutionGate -from qiskit.circuit import ClassicalRegister, QuantumCircuit +from qiskit.circuit import ClassicalRegister, QuantumCircuit, IfElseOp from qiskit.circuit.tools import pi_check from qiskit.converters import circuit_to_dag from qiskit.utils import optionals as _optionals @@ -464,7 +464,7 @@ def _get_gate_span(qubits, node): if index > max_index: max_index = index - if node.cargs or getattr(node.op, "condition", None): + if not isinstance(node.op, IfElseOp) and (node.cargs or getattr(node.op, "condition", None)): return qubits[min_index : len(qubits)] return qubits[min_index : max_index + 1] diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index b1b04ca6602c..5406a2e78ac7 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -20,7 +20,8 @@ import numpy as np -from qiskit.circuit import ControlledGate, Qubit, Clbit, ClassicalRegister, Measure +from qiskit.circuit import QuantumCircuit, Qubit, Clbit, ClassicalRegister +from qiskit.circuit import ControlledGate, Measure, IfElseOp from qiskit.circuit.library.standard_gates import ( SwapGate, RZZGate, @@ -42,6 +43,7 @@ get_bit_reg_index, get_wire_label, get_condition_label_val, + _get_layered_instructions, ) from ..utils import matplotlib_close_if_inline @@ -54,6 +56,9 @@ PORDER_REGLINE = 2 PORDER_GRAY = 3 PORDER_TEXT = 6 +PORDER_FLOW = 1 + +INFINITE_FOLD = 10000000 @_optionals.HAS_MATPLOTLIB.require_in_instance @@ -88,13 +93,8 @@ def __init__( self._circuit = circuit self._qubits = qubits self._clbits = clbits - self._qubits_dict = {} - self._clbits_dict = {} - self._q_anchors = {} - self._c_anchors = {} - self._wire_map = {} - self._nodes = nodes + self._flow_parent = None self._scale = 1.0 if scale is None else scale self._style, def_font_ratio = load_style(style) @@ -153,13 +153,10 @@ def __init__( self._lwidth1 = 1.0 self._lwidth15 = 1.5 self._lwidth2 = 2.0 - self._x_offset = 0.0 + self._lwidth3 = 3.0 - # _data per node with 'width', 'gate_text', 'raw_gate_text', - # 'ctrl_text', 'param', q_xy', 'c_xy', and 'c_indxs' - # and colors 'fc', 'ec', 'lc', 'sc', 'gt', and 'tc' - self._data = {} - self._layer_widths = [] + # Class instances of MatplotlibDrawer for each flow gate - If/Else, For, While, Switch + self._flow_drawers = {} # _char_list for finding text_width of names, labels, and params self._char_list = { @@ -263,31 +260,51 @@ def draw(self, filename=None, verbose=False): """Main entry point to 'matplotlib' ('mpl') drawer. Called from ``visualization.circuit_drawer`` and from ``QuantumCircuit.draw`` through circuit_drawer. """ - # All information for the drawing is first loaded into self._data for the gates and into - # self._qubits_dict and self._clbits_dict for the qubits, clbits, and wires, + # All information for the drawing is first loaded into node_data for the gates and into + # qubits_dict, clbits_dict, and wire_map for the qubits, clbits, and wires, # followed by the coordinates for each gate. - # get layer widths - self._get_layer_widths() + # load the wire map + wire_map = get_wire_map(self._circuit, self._qubits + self._clbits, self._cregbundle) + + # node_data per node with 'width', 'gate_text', 'raw_gate_text', + # 'ctrl_text', 'param_text', 'inside_flow', q_xy', and 'c_xy', + # and colors 'fc', 'ec', 'lc', 'sc', 'gt', and 'tc' + node_data = {} + + # glob_data contains global values used throughout, "n_lines", "x_offset", "next_x_index" + glob_data = {} + + # dicts for the names and locations of register/bit labels + qubits_dict = {} + clbits_dict = {} # load the _qubit_dict and _clbit_dict with register info - n_lines = self._set_bit_reg_info() + self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict, glob_data) + + # get layer widths + layer_widths = self._get_layer_widths( + node_data, wire_map, qubits_dict, clbits_dict, glob_data + ) - # load the coordinates for each gate and compute number of folds - max_anc = self._get_coords(n_lines) - num_folds = max(0, max_anc - 1) // self._fold if self._fold > 0 else 0 + # load the coordinates for each top level gate and compute number of folds. + # coordinates for flow gates are loaded before draw_ops + max_x_index = self._get_coords( + node_data, wire_map, layer_widths, qubits_dict, clbits_dict, glob_data + ) + num_folds = max(0, max_x_index - 1) // self._fold if self._fold > 0 else 0 # The window size limits are computed, followed by one of the four possible ways # of scaling the drawing. # compute the window size - if max_anc > self._fold > 0: - xmax = self._fold + self._x_offset + 0.1 - ymax = (num_folds + 1) * (n_lines + 1) - 1 + if max_x_index > self._fold > 0: + xmax = self._fold + glob_data["x_offset"] + 0.1 + ymax = (num_folds + 1) * (glob_data["n_lines"] + 1) - 1 else: x_incr = 0.4 if not self._nodes else 0.9 - xmax = max_anc + 1 + self._x_offset - x_incr - ymax = n_lines + xmax = max_x_index + 1 + glob_data["x_offset"] - x_incr + ymax = glob_data["n_lines"] xl = -self._style["margin"][0] xr = xmax + self._style["margin"][1] @@ -330,6 +347,7 @@ def draw(self, filename=None, verbose=False): self._lwidth1 = 1.0 * scale self._lwidth15 = 1.5 * scale self._lwidth2 = 2.0 * scale + self._lwidth3 = 3.0 * scale # Once the scaling factor has been determined, the global phase, register names # and numbers, wires, and gates are drawn @@ -337,8 +355,17 @@ def draw(self, filename=None, verbose=False): self._plt_mod.text( xl, yt, "Global Phase: %s" % pi_check(self._global_phase, output="mpl") ) - self._draw_regs_wires(num_folds, xmax, n_lines, max_anc) - self._draw_ops(verbose) + self._draw_regs_wires(num_folds, xmax, max_x_index, qubits_dict, clbits_dict, glob_data) + self._draw_ops( + self._nodes, + node_data, + wire_map, + layer_widths, + qubits_dict, + clbits_dict, + glob_data, + verbose, + ) if filename: self._figure.savefig( @@ -351,29 +378,53 @@ def draw(self, filename=None, verbose=False): matplotlib_close_if_inline(self._figure) return self._figure - def _get_layer_widths(self): + def _load_flow_wire_maps(self, wire_map): + """Load the qubits and clbits from ControlFlowOps into + the wire_map if not already there. + """ + for flow_drawer in self._flow_drawers.values(): + for i in range(0, 2): + if flow_drawer[i] is None: + continue + inner_wire_map = { + inner: wire_map[outer] + for outer, inner in zip(self._qubits, flow_drawer[i]._qubits) + if inner not in wire_map + } + wire_map.update(inner_wire_map) + flow_drawer[i]._load_flow_wire_maps(wire_map) + + def _get_layer_widths(self, node_data, wire_map, qubits_dict, clbits_dict, glob_data): """Compute the layer_widths for the layers""" - for layer in self._nodes: + + layer_widths = {} + for layer_num, layer in enumerate(self._nodes): widest_box = WID - for node in layer: + for i, node in enumerate(layer): + # Put the layer_num in the first node in the layer and put -1 in the rest + # so that layer widths are not counted more than once + if i != 0: + layer_num = -1 + layer_widths[node] = [1, layer_num, self._flow_parent] + op = node.op - self._data[node] = {} - self._data[node]["width"] = WID + node_data[node] = {} + node_data[node]["width"] = WID num_ctrl_qubits = 0 if not hasattr(op, "num_ctrl_qubits") else op.num_ctrl_qubits if ( getattr(op, "_directive", False) and (not op.label or not self._plot_barriers) ) or isinstance(op, Measure): - self._data[node]["raw_gate_text"] = op.name + node_data[node]["raw_gate_text"] = op.name continue base_type = None if not hasattr(op, "base_gate") else op.base_gate gate_text, ctrl_text, raw_gate_text = get_gate_ctrl_text( op, "mpl", style=self._style, calibrations=self._calibrations ) - self._data[node]["gate_text"] = gate_text - self._data[node]["ctrl_text"] = ctrl_text - self._data[node]["raw_gate_text"] = raw_gate_text - self._data[node]["param"] = "" + node_data[node]["gate_text"] = gate_text + node_data[node]["ctrl_text"] = ctrl_text + node_data[node]["raw_gate_text"] = raw_gate_text + node_data[node]["param_text"] = "" # if single qubit, no params, and no labels, layer_width is 1 if ( @@ -391,17 +442,20 @@ def _get_layer_widths(self): # spacing adjustments between gates ctrl_width = self._get_text_width(ctrl_text, fontsize=self._sfs) - 0.05 - # get param_width, but 0 for gates with array params + # get param_width, but 0 for gates with array params or circuits in params if ( hasattr(op, "params") and len(op.params) > 0 and not any(isinstance(param, np.ndarray) for param in op.params) + and not any(isinstance(param, QuantumCircuit) for param in op.params) ): - param = get_param_str(op, "mpl", ndigits=3) + param_text = get_param_str(op, "mpl", ndigits=3) if isinstance(op, Initialize): - param = f"$[{param.replace('$', '')}]$" - self._data[node]["param"] = param - raw_param_width = self._get_text_width(param, fontsize=self._sfs, param=True) + param_text = f"$[{param_text.replace('$', '')}]$" + node_data[node]["param_text"] = param_text + raw_param_width = self._get_text_width( + param_text, fontsize=self._sfs, param=True + ) param_width = raw_param_width + 0.08 else: param_width = raw_param_width = 0.0 @@ -416,7 +470,65 @@ def _get_layer_widths(self): ) gate_width = (raw_gate_width + 0.08) * 1.58 - # otherwise, standard gate or multiqubit gate + # Check if an IfElseOp - node_data load for these gates is done here + elif isinstance(node.op, IfElseOp): + self._flow_drawers[node] = [] + node_data[node]["width"] = [] + node_data[node]["if_depth"] = 0 + gate_width = 0.0 + + # params[0] holds circuit for if, params[1] holds circuit for else + for k, circuit in enumerate(node.op.params): + raw_gate_width = 0.0 + if circuit is None: # No else + self._flow_drawers[node].append(None) + node_data[node]["width"].append(0.0) + break + + # Depth of nested if/else used for color of box + if self._flow_parent is not None: + node_data[node]["if_depth"] = ( + node_data[self._flow_parent]["if_depth"] + 1 + ) + # Get the layered node lists and instantiate a new drawer class for + # the circuit inside the if or else. + qubits, clbits, nodes = _get_layered_instructions(circuit) + flow_drawer = MatplotlibDrawer( + qubits, + clbits, + nodes, + circuit, + style=self._style, + plot_barriers=self._plot_barriers, + fold=self._fold, + cregbundle=self._cregbundle, + ) + # flow_parent is the parent of the new class instance + flow_drawer._flow_parent = node + + # Recursively call _get_layer_widths for the circuit inside the if/else + flow_widths = flow_drawer._get_layer_widths( + node_data, wire_map, qubits_dict, clbits_dict, glob_data + ) + layer_widths.update(flow_widths) + self._flow_drawers[node].append(flow_drawer) + + curr_layer = 0 + for width, layer_num, flow_parent in flow_widths.values(): + if layer_num != -1 and flow_parent == flow_drawer._flow_parent: + curr_layer = layer_num + raw_gate_width += width + + # Need extra incr of 1.0 for else box + gate_width += raw_gate_width + (1.0 if k == 1 else 0.0) + + # Minor adjustment so else section gates align with indexes + if k == 1: + raw_gate_width += 0.045 + node_data[node]["width"].append(raw_gate_width) + self._load_flow_wire_maps(wire_map) + + # Otherwise, standard gate or multiqubit gate else: raw_gate_width = self._get_text_width(gate_text, fontsize=self._fs) gate_width = raw_gate_width + 0.10 @@ -427,30 +539,37 @@ def _get_layer_widths(self): box_width = max(gate_width, ctrl_width, param_width, WID) if box_width > widest_box: widest_box = box_width - self._data[node]["width"] = max(raw_gate_width, raw_param_width) + if not isinstance(node.op, IfElseOp): + node_data[node]["width"] = max(raw_gate_width, raw_param_width) + for node in layer: + layer_widths[node][0] = int(widest_box) + 1 - self._layer_widths.append(int(widest_box) + 1) + return layer_widths - def _set_bit_reg_info(self): + def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict, glob_data): """Get all the info for drawing bit/reg names and numbers""" - self._wire_map = get_wire_map(self._circuit, self._qubits + self._clbits, self._cregbundle) longest_wire_label_width = 0 - n_lines = 0 + glob_data["n_lines"] = 0 initial_qbit = " |0>" if self._initial_state else "" initial_cbit = " 0" if self._initial_state else "" idx = 0 pos = y_off = -len(self._qubits) + 1 - for ii, wire in enumerate(self._wire_map): + for ii, wire in enumerate(wire_map): + # if it's a creg, register is the key and just load the index if isinstance(wire, ClassicalRegister): + if wire[0] not in self._clbits: + continue register = wire - index = self._wire_map[wire] + index = wire_map[wire] # otherwise, get the register from find_bit and use bit_index if # it's a bit, or the index of the bit in the register if it's a reg else: + if wire not in self._qubits + self._clbits: + continue register, bit_index, reg_index = get_bit_reg_index(self._circuit, wire) index = bit_index if register is None else reg_index @@ -477,76 +596,106 @@ def _set_bit_reg_info(self): if isinstance(wire, Qubit): pos = -ii - self._qubits_dict[ii] = { + qubits_dict[ii] = { "y": pos, "wire_label": wire_label, - "index": bit_index, - "register": register, } - n_lines += 1 + glob_data["n_lines"] += 1 else: if ( not self._cregbundle or register is None or (self._cregbundle and isinstance(wire, ClassicalRegister)) ): - n_lines += 1 + glob_data["n_lines"] += 1 idx += 1 pos = y_off - idx - self._clbits_dict[ii] = { + clbits_dict[ii] = { "y": pos, "wire_label": wire_label, - "index": bit_index, "register": register, } + glob_data["x_offset"] = -1.2 + longest_wire_label_width - self._x_offset = -1.2 + longest_wire_label_width - return n_lines - - def _get_coords(self, n_lines): - """Load all the coordinate info needed to place the gates on the drawing""" - - # create the anchor arrays - for key, qubit in self._qubits_dict.items(): - self._q_anchors[key] = Anchor(num_wires=n_lines, y_index=qubit["y"], fold=self._fold) - for key, clbit in self._clbits_dict.items(): - self._c_anchors[key] = Anchor(num_wires=n_lines, y_index=clbit["y"], fold=self._fold) + def _get_coords( + self, + node_data, + wire_map, + layer_widths, + qubits_dict, + clbits_dict, + glob_data, + flow_parent=None, + is_if=None, + ): + """Load all the coordinate info needed to place the gates on the drawing.""" - # get all the necessary coordinates for placing gates on the wires prev_x_index = -1 - for i, layer in enumerate(self._nodes): - layer_width = self._layer_widths[i] - anc_x_index = prev_x_index + 1 + for layer in self._nodes: + curr_x_index = prev_x_index + 1 + l_width = [] for node in layer: - # get qubit index + # For gates inside if/else, set the x_index and increment by if width + # if it's an else + if flow_parent is not None: + node_data[node]["x_index"] = ( + node_data[flow_parent]["x_index"] + curr_x_index + 1 + ) + if not is_if: + node_data[node]["x_index"] += int(node_data[flow_parent]["width"][0]) + 1 + + # get qubit indexes q_indxs = [] for qarg in node.qargs: if qarg in self._qubits: - q_indxs.append(self._wire_map[qarg]) + q_indxs.append(wire_map[qarg]) + # get clbit indexes c_indxs = [] for carg in node.cargs: if carg in self._clbits: register = get_bit_register(self._circuit, carg) if register is not None and self._cregbundle: - c_indxs.append(self._wire_map[register]) + c_indxs.append(wire_map[register]) else: - c_indxs.append(self._wire_map[carg]) + c_indxs.append(wire_map[carg]) - # qubit coordinate - self._data[node]["q_xy"] = [ - self._q_anchors[ii].plot_coord(anc_x_index, layer_width, self._x_offset) + flow_op = isinstance(node.op, IfElseOp) + if flow_parent is not None: + node_data[node]["inside_flow"] = True + x_index = node_data[node]["x_index"] + else: + node_data[node]["inside_flow"] = False + x_index = curr_x_index + + # qubit coordinates + node_data[node]["q_xy"] = [ + self._plot_coord( + x_index, + qubits_dict[ii]["y"], + layer_widths[node][0], + glob_data, + flow_op, + ) for ii in q_indxs ] - # clbit coordinate - self._data[node]["c_xy"] = [ - self._c_anchors[ii].plot_coord(anc_x_index, layer_width, self._x_offset) + # clbit coordinates + node_data[node]["c_xy"] = [ + self._plot_coord( + x_index, + clbits_dict[ii]["y"], + layer_widths[node][0], + glob_data, + flow_op, + ) for ii in c_indxs ] # update index based on the value from plotting - anc_x_index = self._q_anchors[q_indxs[0]].get_x_index() - self._data[node]["c_indxs"] = c_indxs + if flow_parent is None: + curr_x_index = glob_data["next_x_index"] + l_width.append(layer_widths[node][0]) + node_data[node]["x_index"] = x_index # adjust the column if there have been barriers encountered, but not plotted barrier_offset = 0 @@ -555,12 +704,13 @@ def _get_coords(self, n_lines): barrier_offset = ( -1 if all(getattr(nd.op, "_directive", False) for nd in layer) else 0 ) - prev_x_index = anc_x_index + layer_width + barrier_offset - 1 + prev_x_index = curr_x_index + max(l_width) + barrier_offset - 1 return prev_x_index + 1 def _get_text_width(self, text, fontsize, param=False, reg_remove_under=None): """Compute the width of a string in the default font""" + from pylatexenc.latex2text import LatexNodes2Text if not text: @@ -604,16 +754,16 @@ def _get_text_width(self, text, fontsize, param=False, reg_remove_under=None): sum_text *= self._subfont_factor return sum_text - def _draw_regs_wires(self, num_folds, xmax, n_lines, max_anc): + def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dict, glob_data): """Draw the register names and numbers, wires, and vertical lines at the ends""" for fold_num in range(num_folds + 1): # quantum registers - for qubit in self._qubits_dict.values(): + for qubit in qubits_dict.values(): qubit_label = qubit["wire_label"] - y = qubit["y"] - fold_num * (n_lines + 1) + y = qubit["y"] - fold_num * (glob_data["n_lines"] + 1) self._ax.text( - self._x_offset - 0.2, + glob_data["x_offset"] - 0.2, y, qubit_label, ha="right", @@ -624,19 +774,17 @@ def _draw_regs_wires(self, num_folds, xmax, n_lines, max_anc): zorder=PORDER_TEXT, ) # draw the qubit wire - self._line([self._x_offset, y], [xmax, y], zorder=PORDER_REGLINE) + self._line([glob_data["x_offset"], y], [xmax, y], zorder=PORDER_REGLINE) # classical registers this_clbit_dict = {} - for clbit in self._clbits_dict.values(): - clbit_label = clbit["wire_label"] - clbit_reg = clbit["register"] - y = clbit["y"] - fold_num * (n_lines + 1) + for clbit in clbits_dict.values(): + y = clbit["y"] - fold_num * (glob_data["n_lines"] + 1) if y not in this_clbit_dict.keys(): this_clbit_dict[y] = { "val": 1, - "wire_label": clbit_label, - "register": clbit_reg, + "wire_label": clbit["wire_label"], + "register": clbit["register"], } else: this_clbit_dict[y]["val"] += 1 @@ -645,13 +793,13 @@ def _draw_regs_wires(self, num_folds, xmax, n_lines, max_anc): # cregbundle if self._cregbundle and this_clbit["register"] is not None: self._ax.plot( - [self._x_offset + 0.2, self._x_offset + 0.3], + [glob_data["x_offset"] + 0.2, glob_data["x_offset"] + 0.3], [y - 0.1, y + 0.1], color=self._style["cc"], zorder=PORDER_LINE, ) self._ax.text( - self._x_offset + 0.1, + glob_data["x_offset"] + 0.1, y + 0.1, str(this_clbit["register"].size), ha="left", @@ -662,7 +810,7 @@ def _draw_regs_wires(self, num_folds, xmax, n_lines, max_anc): zorder=PORDER_TEXT, ) self._ax.text( - self._x_offset - 0.2, + glob_data["x_offset"] - 0.2, y, this_clbit["wire_label"], ha="right", @@ -674,7 +822,7 @@ def _draw_regs_wires(self, num_folds, xmax, n_lines, max_anc): ) # draw the clbit wire self._line( - [self._x_offset, y], + [glob_data["x_offset"], y], [xmax, y], lc=self._style["cc"], ls=self._style["cline"], @@ -685,10 +833,10 @@ def _draw_regs_wires(self, num_folds, xmax, n_lines, max_anc): feedline_r = num_folds > 0 and num_folds > fold_num feedline_l = fold_num > 0 if feedline_l or feedline_r: - xpos_l = self._x_offset - 0.01 - xpos_r = self._fold + self._x_offset + 0.1 - ypos1 = -fold_num * (n_lines + 1) - ypos2 = -(fold_num + 1) * (n_lines) - fold_num + 1 + xpos_l = glob_data["x_offset"] - 0.01 + xpos_r = self._fold + glob_data["x_offset"] + 0.1 + ypos1 = -fold_num * (glob_data["n_lines"] + 1) + ypos2 = -(fold_num + 1) * (glob_data["n_lines"]) - fold_num + 1 if feedline_l: self._ax.plot( [xpos_l, xpos_l], @@ -706,14 +854,14 @@ def _draw_regs_wires(self, num_folds, xmax, n_lines, max_anc): zorder=PORDER_LINE, ) - # draw anchor index number + # draw index number if self._style["index"]: - for layer_num in range(max_anc): + for layer_num in range(max_x_index): if self._fold > 0: - x_coord = layer_num % self._fold + self._x_offset + 0.53 - y_coord = -(layer_num // self._fold) * (n_lines + 1) + 0.65 + x_coord = layer_num % self._fold + glob_data["x_offset"] + 0.53 + y_coord = -(layer_num // self._fold) * (glob_data["n_lines"] + 1) + 0.65 else: - x_coord = layer_num + self._x_offset + 0.53 + x_coord = layer_num + glob_data["x_offset"] + 0.53 y_coord = 0.65 self._ax.text( x_coord, @@ -727,17 +875,58 @@ def _draw_regs_wires(self, num_folds, xmax, n_lines, max_anc): zorder=PORDER_TEXT, ) - def _draw_ops(self, verbose=False): + def _add_nodes_and_coords( + self, nodes, node_data, wire_map, layer_widths, qubits_dict, clbits_dict, glob_data + ): + """Add the nodes from ControlFlowOps and their coordinates to the main circuit""" + for flow_drawer in self._flow_drawers.values(): + for i in range(0, 2): + if flow_drawer[i] is None: # No else + continue + + nodes += flow_drawer[i]._nodes + flow_drawer[i]._get_coords( + node_data, + wire_map, + layer_widths, + qubits_dict, + clbits_dict, + glob_data, + flow_parent=flow_drawer[i]._flow_parent, + is_if=True if i == 0 else False, + ) + # Recurse for if/else ops inside the flow_drawer + flow_drawer[i]._add_nodes_and_coords( + nodes, node_data, wire_map, layer_widths, qubits_dict, clbits_dict, glob_data + ) + + def _draw_ops( + self, + nodes, + node_data, + wire_map, + layer_widths, + qubits_dict, + clbits_dict, + glob_data, + verbose=False, + ): """Draw the gates in the circuit""" + + # Add the nodes from all the ControlFlowOps and their coordinates to the main nodes + self._add_nodes_and_coords( + nodes, node_data, wire_map, layer_widths, qubits_dict, clbits_dict, glob_data + ) prev_x_index = -1 - for i, layer in enumerate(self._nodes): - layer_width = self._layer_widths[i] - anc_x_index = prev_x_index + 1 + for layer in nodes: + l_width = [] + curr_x_index = prev_x_index + 1 # draw the gates in this layer for node in layer: op = node.op - self._get_colors(node) + + self._get_colors(node, node_data) if verbose: print(op) @@ -745,35 +934,45 @@ def _draw_ops(self, verbose=False): # add conditional if getattr(op, "condition", None): cond_xy = [ - self._c_anchors[ii].plot_coord(anc_x_index, layer_width, self._x_offset) - for ii in self._clbits_dict - ] - if self._clbits_dict: - anc_x_index = max( - anc_x_index, next(iter(self._c_anchors.items()))[1].get_x_index() + self._plot_coord( + node_data[node]["x_index"], + clbits_dict[ii]["y"], + layer_widths[node][0], + glob_data, + isinstance(op, IfElseOp), ) - self._condition(node, cond_xy) + for ii in clbits_dict + ] + self._condition(node, node_data, wire_map, cond_xy) # draw measure if isinstance(op, Measure): - self._measure(node) + self._measure(node, node_data) # draw barriers, snapshots, etc. elif getattr(op, "_directive", False): if self._plot_barriers: - self._barrier(node) + self._barrier(node, node_data) + + # draw the box for control flow circuits + elif isinstance(op, IfElseOp): + self._flow_op_gate(node, node_data) # draw single qubit gates - elif len(self._data[node]["q_xy"]) == 1 and not node.cargs: - self._gate(node) + elif len(node_data[node]["q_xy"]) == 1 and not node.cargs: + self._gate(node, node_data) # draw controlled gates elif isinstance(op, ControlledGate): - self._control_gate(node) + self._control_gate(node, node_data) # draw multi-qubit gate as final default else: - self._multiqubit_gate(node) + self._multiqubit_gate(node, node_data) + + # Determine the max width of the circuit only at the top level + if not node_data[node]["inside_flow"]: + l_width.append(layer_widths[node][0]) # adjust the column if there have been barriers encountered, but not plotted barrier_offset = 0 @@ -782,16 +981,16 @@ def _draw_ops(self, verbose=False): barrier_offset = ( -1 if all(getattr(nd.op, "_directive", False) for nd in layer) else 0 ) + prev_x_index = curr_x_index + (max(l_width) if l_width else 0) + barrier_offset - 1 - prev_x_index = anc_x_index + layer_width + barrier_offset - 1 - - def _get_colors(self, node): + def _get_colors(self, node, node_data): """Get all the colors needed for drawing the circuit""" + op = node.op base_name = None if not hasattr(op, "base_gate") else op.base_gate.name color = None - if self._data[node]["raw_gate_text"] in self._style["dispcol"]: - color = self._style["dispcol"][self._data[node]["raw_gate_text"]] + if node_data[node]["raw_gate_text"] in self._style["dispcol"]: + color = self._style["dispcol"][node_data[node]["raw_gate_text"]] elif op.name in self._style["dispcol"]: color = self._style["dispcol"][op.name] if color is not None: @@ -825,15 +1024,16 @@ def _get_colors(self, node): lc = fc # Subtext needs to be same color as gate text sc = gt - self._data[node]["fc"] = fc - self._data[node]["ec"] = ec - self._data[node]["gt"] = gt - self._data[node]["tc"] = self._style["tc"] - self._data[node]["sc"] = sc - self._data[node]["lc"] = lc - - def _condition(self, node, cond_xy): + node_data[node]["fc"] = fc + node_data[node]["ec"] = ec + node_data[node]["gt"] = gt + node_data[node]["tc"] = self._style["tc"] + node_data[node]["sc"] = sc + node_data[node]["lc"] = lc + + def _condition(self, node, node_data, wire_map, cond_xy): """Add a conditional to a gate""" + label, val_bits = get_condition_label_val( node.op.condition, self._circuit, self._cregbundle ) @@ -847,17 +1047,17 @@ def _condition(self, node, cond_xy): # other cases, only one bit is shown. if not self._cregbundle and isinstance(cond_bit_reg, ClassicalRegister): for idx in range(cond_bit_reg.size): - cond_pos.append(cond_xy[self._wire_map[cond_bit_reg[idx]] - first_clbit]) + cond_pos.append(cond_xy[wire_map[cond_bit_reg[idx]] - first_clbit]) # If it's a register bit and cregbundle, need to use the register to find the location elif self._cregbundle and isinstance(cond_bit_reg, Clbit): register = get_bit_register(self._circuit, cond_bit_reg) if register is not None: - cond_pos.append(cond_xy[self._wire_map[register] - first_clbit]) + cond_pos.append(cond_xy[wire_map[register] - first_clbit]) else: - cond_pos.append(cond_xy[self._wire_map[cond_bit_reg] - first_clbit]) + cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit]) else: - cond_pos.append(cond_xy[self._wire_map[cond_bit_reg] - first_clbit]) + cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit]) xy_plot = [] for idx, xy in enumerate(cond_pos): @@ -879,9 +1079,14 @@ def _condition(self, node, cond_xy): ) self._ax.add_patch(box) xy_plot.append(xy) - qubit_b = min(self._data[node]["q_xy"], key=lambda xy: xy[1]) + + qubit_b = min(node_data[node]["q_xy"], key=lambda xy: xy[1]) clbit_b = min(xy_plot, key=lambda xy: xy[1]) + # For IfElseOps, place the condition at almost the left edge of the box + if isinstance(node.op, IfElseOp): + qubit_b = (qubit_b[0], qubit_b[1] - (0.5 * HIG + 0.14)) + # display the label at the bottom of the lowest conditional and draw the double line xpos, ypos = clbit_b if isinstance(node.op, Measure): @@ -899,14 +1104,14 @@ def _condition(self, node, cond_xy): ) self._line(qubit_b, clbit_b, lc=self._style["cc"], ls=self._style["cline"]) - def _measure(self, node): + def _measure(self, node, node_data): """Draw the measure symbol and the line to the clbit""" - qx, qy = self._data[node]["q_xy"][0] - cx, cy = self._data[node]["c_xy"][0] + qx, qy = node_data[node]["q_xy"][0] + cx, cy = node_data[node]["c_xy"][0] register, _, reg_index = get_bit_reg_index(self._circuit, node.cargs[0]) # draw gate box - self._gate(node) + self._gate(node, node_data) # add measure symbol arc = self._patches_mod.Arc( @@ -916,7 +1121,7 @@ def _measure(self, node): theta1=0, theta2=180, fill=False, - ec=self._data[node]["gt"], + ec=node_data[node]["gt"], linewidth=self._lwidth2, zorder=PORDER_GATE, ) @@ -924,13 +1129,13 @@ def _measure(self, node): self._ax.plot( [qx, qx + 0.35 * WID], [qy - 0.15 * HIG, qy + 0.20 * HIG], - color=self._data[node]["gt"], + color=node_data[node]["gt"], linewidth=self._lwidth2, zorder=PORDER_GATE, ) # arrow self._line( - self._data[node]["q_xy"][0], + node_data[node]["q_xy"][0], [cx, cy + 0.35 * WID], lc=self._style["cc"], ls=self._style["cline"], @@ -959,9 +1164,9 @@ def _measure(self, node): zorder=PORDER_TEXT, ) - def _barrier(self, node): + def _barrier(self, node, node_data): """Draw a barrier""" - for i, xy in enumerate(self._data[node]["q_xy"]): + for i, xy in enumerate(node_data[node]["q_xy"]): xpos, ypos = xy # For the topmost barrier, reduce the rectangle if there's a label to allow for the text. if i == 0 and node.op.label is not None: @@ -998,73 +1203,73 @@ def _barrier(self, node): ha="center", va="top", fontsize=self._fs, - color=self._data[node]["tc"], + color=node_data[node]["tc"], clip_on=True, zorder=PORDER_TEXT, ) - def _gate(self, node, xy=None): + def _gate(self, node, node_data, xy=None): """Draw a 1-qubit gate""" if xy is None: - xy = self._data[node]["q_xy"][0] + xy = node_data[node]["q_xy"][0] xpos, ypos = xy - wid = max(self._data[node]["width"], WID) + wid = max(node_data[node]["width"], WID) box = self._patches_mod.Rectangle( xy=(xpos - 0.5 * wid, ypos - 0.5 * HIG), width=wid, height=HIG, - fc=self._data[node]["fc"], - ec=self._data[node]["ec"], + fc=node_data[node]["fc"], + ec=node_data[node]["ec"], linewidth=self._lwidth15, zorder=PORDER_GATE, ) self._ax.add_patch(box) - if "gate_text" in self._data[node]: + if "gate_text" in node_data[node]: gate_ypos = ypos - if "param" in self._data[node] and self._data[node]["param"] != "": + if "param_text" in node_data[node] and node_data[node]["param_text"] != "": gate_ypos = ypos + 0.15 * HIG self._ax.text( xpos, ypos - 0.3 * HIG, - self._data[node]["param"], + node_data[node]["param_text"], ha="center", va="center", fontsize=self._sfs, - color=self._data[node]["sc"], + color=node_data[node]["sc"], clip_on=True, zorder=PORDER_TEXT, ) self._ax.text( xpos, gate_ypos, - self._data[node]["gate_text"], + node_data[node]["gate_text"], ha="center", va="center", fontsize=self._fs, - color=self._data[node]["gt"], + color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, ) - def _multiqubit_gate(self, node, xy=None): + def _multiqubit_gate(self, node, node_data, xy=None): """Draw a gate covering more than one qubit""" op = node.op if xy is None: - xy = self._data[node]["q_xy"] + xy = node_data[node]["q_xy"] # Swap gate if isinstance(op, SwapGate): - self._swap(xy, node, self._data[node]["lc"]) + self._swap(xy, node, node_data, node_data[node]["lc"]) return # RZZ Gate elif isinstance(op, RZZGate): - self._symmetric_gate(node, RZZGate) + self._symmetric_gate(node, node_data, RZZGate) return - c_xy = self._data[node]["c_xy"] + c_xy = node_data[node]["c_xy"] xpos = min(x[0] for x in xy) ypos = min(y[1] for y in xy) ypos_max = max(y[1] for y in xy) @@ -1073,16 +1278,17 @@ def _multiqubit_gate(self, node, xy=None): cypos = min(y[1] for y in c_xy) ypos = min(ypos, cypos) - wid = max(self._data[node]["width"] + 0.21, WID) + wid = max(node_data[node]["width"] + 0.21, WID) + + qubit_span = abs(ypos) - abs(ypos_max) + height = HIG + qubit_span - qubit_span = abs(ypos) - abs(ypos_max) + 1 - height = HIG + (qubit_span - 1) box = self._patches_mod.Rectangle( xy=(xpos - 0.5 * wid, ypos - 0.5 * HIG), width=wid, height=height, - fc=self._data[node]["fc"], - ec=self._data[node]["ec"], + fc=node_data[node]["fc"], + ec=node_data[node]["ec"], linewidth=self._lwidth15, zorder=PORDER_GATE, ) @@ -1097,7 +1303,7 @@ def _multiqubit_gate(self, node, xy=None): ha="left", va="center", fontsize=self._fs, - color=self._data[node]["gt"], + color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, ) @@ -1111,42 +1317,111 @@ def _multiqubit_gate(self, node, xy=None): ha="left", va="center", fontsize=self._fs, - color=self._data[node]["gt"], + color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, ) - if "gate_text" in self._data[node] and self._data[node]["gate_text"] != "": - gate_ypos = ypos + 0.5 * (qubit_span - 1) - if "param" in self._data[node] and self._data[node]["param"] != "": + if "gate_text" in node_data[node] and node_data[node]["gate_text"] != "": + gate_ypos = ypos + 0.5 * qubit_span + if "param_text" in node_data[node] and node_data[node]["param_text"] != "": gate_ypos = ypos + 0.4 * height self._ax.text( xpos + 0.11, ypos + 0.2 * height, - self._data[node]["param"], + node_data[node]["param_text"], ha="center", va="center", fontsize=self._sfs, - color=self._data[node]["sc"], + color=node_data[node]["sc"], clip_on=True, zorder=PORDER_TEXT, ) self._ax.text( xpos + 0.11, gate_ypos, - self._data[node]["gate_text"], + node_data[node]["gate_text"], ha="center", va="center", fontsize=self._fs, - color=self._data[node]["gt"], + color=node_data[node]["gt"], + clip_on=True, + zorder=PORDER_TEXT, + ) + + def _flow_op_gate(self, node, node_data): + """Draw the box for a flow op circuit""" + xy = node_data[node]["q_xy"] + xpos = min(x[0] for x in xy) + ypos = min(y[1] for y in xy) + ypos_max = max(y[1] for y in xy) + + if_width = node_data[node]["width"][0] + WID + else_width = node_data[node]["width"][1] + wid = max(if_width, WID) + if else_width > 0.0: + wid += else_width + WID + 0.3 + + qubit_span = abs(ypos) - abs(ypos_max) + height = HIG + qubit_span + + # Cycle through box colors based on depth. + # Default - blue, purple, green, black + colors = [ + self._style["dispcol"]["h"][0], + self._style["dispcol"]["u"][0], + self._style["dispcol"]["x"][0], + self._style["cc"], + ] + # FancyBbox allows rounded corners + box = self._patches_mod.FancyBboxPatch( + xy=(xpos, ypos - 0.5 * HIG), + width=wid, + height=height, + boxstyle="round, pad=0.1", + fc="none", + ec=colors[node_data[node]["if_depth"] % 4], + linewidth=3.0, + zorder=PORDER_FLOW, + ) + self._ax.add_patch(box) + self._ax.spines["top"].set_visible(False) + self._ax.text( + xpos + 0.22, + ypos_max + 0.2, + "If", + ha="left", + va="center", + fontsize=self._fs, + color=node_data[node]["gt"], + clip_on=True, + zorder=PORDER_TEXT, + ) + if else_width > 0.0: + self._ax.plot( + [xpos + if_width + 0.3, xpos + if_width + 0.3], + [ypos - 0.5 * HIG - 0.1, ypos + height - 0.22], + color=colors[node_data[node]["if_depth"]], + linewidth=3.0, + linestyle="solid", + zorder=PORDER_FLOW, + ) + self._ax.text( + xpos + if_width + 0.5, + ypos_max + 0.2, + "Else", + ha="left", + va="center", + fontsize=self._fs, + color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, ) - def _control_gate(self, node): + def _control_gate(self, node, node_data): """Draw a controlled gate""" op = node.op + xy = node_data[node]["q_xy"] base_type = None if not hasattr(op, "base_gate") else op.base_gate - xy = self._data[node]["q_xy"] qubit_b = min(xy, key=lambda xy: xy[1]) qubit_t = max(xy, key=lambda xy: xy[1]) num_ctrl_qubits = op.num_ctrl_qubits @@ -1155,29 +1430,29 @@ def _control_gate(self, node): op.ctrl_state, num_ctrl_qubits, xy, - ec=self._data[node]["ec"], - tc=self._data[node]["tc"], - text=self._data[node]["ctrl_text"], + ec=node_data[node]["ec"], + tc=node_data[node]["tc"], + text=node_data[node]["ctrl_text"], qargs=node.qargs, ) - self._line(qubit_b, qubit_t, lc=self._data[node]["lc"]) + self._line(qubit_b, qubit_t, lc=node_data[node]["lc"]) if isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, ZGate, RZZGate)): - self._symmetric_gate(node, base_type) + self._symmetric_gate(node, node_data, base_type) elif num_qargs == 1 and isinstance(base_type, XGate): tgt_color = self._style["dispcol"]["target"] tgt = tgt_color if isinstance(tgt_color, str) else tgt_color[0] - self._x_tgt_qubit(xy[num_ctrl_qubits], ec=self._data[node]["ec"], ac=tgt) + self._x_tgt_qubit(xy[num_ctrl_qubits], ec=node_data[node]["ec"], ac=tgt) elif num_qargs == 1: - self._gate(node, xy[num_ctrl_qubits:][0]) + self._gate(node, node_data, xy[num_ctrl_qubits:][0]) elif isinstance(base_type, SwapGate): - self._swap(xy[num_ctrl_qubits:], node, self._data[node]["lc"]) + self._swap(xy[num_ctrl_qubits:], node, node_data, node_data[node]["lc"]) else: - self._multiqubit_gate(node, xy[num_ctrl_qubits:]) + self._multiqubit_gate(node, node_data, xy[num_ctrl_qubits:]) def _set_ctrl_bits( self, ctrl_state, num_ctrl_qubits, qbit, ec=None, tc=None, text="", qargs=None @@ -1272,16 +1547,16 @@ def _x_tgt_qubit(self, xy, ec=None, ac=None): zorder=PORDER_GATE + 1, ) - def _symmetric_gate(self, node, base_type): + def _symmetric_gate(self, node, node_data, base_type): """Draw symmetric gates for cz, cu1, cp, and rzz""" op = node.op - xy = self._data[node]["q_xy"] + xy = node_data[node]["q_xy"] qubit_b = min(xy, key=lambda xy: xy[1]) qubit_t = max(xy, key=lambda xy: xy[1]) base_type = None if not hasattr(op, "base_gate") else op.base_gate - ec = self._data[node]["ec"] - tc = self._data[node]["tc"] - lc = self._data[node]["lc"] + ec = node_data[node]["ec"] + tc = node_data[node]["tc"] + lc = node_data[node]["lc"] # cz and mcz gates if not isinstance(op, ZGate) and isinstance(base_type, ZGate): @@ -1292,24 +1567,30 @@ def _symmetric_gate(self, node, base_type): # cu1, cp, rzz, and controlled rzz gates (sidetext gates) elif isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, RZZGate)): num_ctrl_qubits = 0 if isinstance(op, RZZGate) else op.num_ctrl_qubits - gate_text = "P" if isinstance(base_type, PhaseGate) else self._data[node]["gate_text"] + gate_text = "P" if isinstance(base_type, PhaseGate) else node_data[node]["gate_text"] self._ctrl_qubit(xy[num_ctrl_qubits], fc=ec, ec=ec, tc=tc) if not isinstance(base_type, (U1Gate, PhaseGate)): self._ctrl_qubit(xy[num_ctrl_qubits + 1], fc=ec, ec=ec, tc=tc) - self._sidetext(node, qubit_b, tc=tc, text=f"{gate_text} ({self._data[node]['param']})") + self._sidetext( + node, + node_data, + qubit_b, + tc=tc, + text=f"{gate_text} ({node_data[node]['param_text']})", + ) self._line(qubit_b, qubit_t, lc=lc) - def _swap(self, xy, node, color=None): + def _swap(self, xy, node, node_data, color=None): """Draw a Swap gate""" self._swap_cross(xy[0], color=color) self._swap_cross(xy[1], color=color) self._line(xy[0], xy[1], lc=color) # add calibration text - gate_text = self._data[node]["gate_text"].split("\n")[-1] - if self._data[node]["raw_gate_text"] in self._calibrations: + gate_text = node_data[node]["gate_text"].split("\n")[-1] + if node_data[node]["raw_gate_text"] in self._calibrations: xpos, ypos = xy[0] self._ax.text( xpos, @@ -1342,12 +1623,12 @@ def _swap_cross(self, xy, color=None): zorder=PORDER_LINE + 1, ) - def _sidetext(self, node, xy, tc=None, text=""): + def _sidetext(self, node, node_data, xy, tc=None, text=""): """Draw the sidetext for symmetric gates""" xpos, ypos = xy # 0.11 = the initial gap, add 1/2 text width to place on the right - xp = xpos + 0.11 + self._data[node]["width"] / 2 + xp = xpos + 0.11 + node_data[node]["width"] / 2 self._ax.text( xp, ypos + HIG, @@ -1397,33 +1678,23 @@ def _line(self, xy0, xy1, lc=None, ls=None, zorder=PORDER_LINE): zorder=zorder, ) + def _plot_coord(self, x_index, y_index, gate_width, glob_data, flow_op=False): + """Get the coord positions for an index""" -class Anchor: - """Locate the anchors for the gates""" - - def __init__(self, num_wires, y_index, fold): - self._num_wires = num_wires - self._fold = fold - self._y_index = y_index - self._x_index = 0 + # Check folding + fold = self._fold if self._fold > 0 else INFINITE_FOLD + h_pos = x_index % fold + 1 - def plot_coord(self, x_index, gate_width, x_offset): - """Get the coord positions for an index""" - h_pos = x_index % self._fold + 1 - # check folding - if self._fold > 0: - if h_pos + (gate_width - 1) > self._fold: - x_index += self._fold - (h_pos - 1) - x_pos = x_index % self._fold + 0.5 * gate_width + 0.04 - y_pos = self._y_index - (x_index // self._fold) * (self._num_wires + 1) + # Don't fold flow_ops here, only gates inside the flow_op + if not flow_op and h_pos + (gate_width - 1) > fold: + x_index += fold - (h_pos - 1) + x_pos = x_index % fold + glob_data["x_offset"] + 0.04 + if not flow_op: + x_pos += 0.5 * gate_width else: - x_pos = x_index + 0.5 * gate_width + 0.04 - y_pos = self._y_index - - # could have been updated, so need to store - self._x_index = x_index - return x_pos + x_offset, y_pos + x_pos += 0.25 + y_pos = y_index - (x_index // fold) * (glob_data["n_lines"] + 1) - def get_x_index(self): - """Getter for the x index""" - return self._x_index + # x_index could have been updated, so need to store + glob_data["next_x_index"] = x_index + return x_pos, y_pos From b33a0a79de7692cc98df9ad8089cd33e42b4f7ac Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 26 May 2023 11:06:25 -0700 Subject: [PATCH 15/30] Unused arg --- qiskit/visualization/circuit/matplotlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 9717355db5ea..3ee21a87e6dc 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -275,7 +275,7 @@ def draw(self, filename=None, verbose=False): self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict, glob_data) # get layer widths - layer_widths = self._get_layer_widths(node_data, glob_data) + layer_widths = self._get_layer_widths(node_data) # load the coordinates for each gate and compute number of folds max_x_index = self._get_coords( @@ -359,7 +359,7 @@ def draw(self, filename=None, verbose=False): matplotlib_close_if_inline(self._figure) return self._figure - def _get_layer_widths(self, node_data, glob_data): + def _get_layer_widths(self, node_data): """Compute the layer_widths for the layers""" layer_widths = {} From 5ec419c4c6d0b9bafe277b1b3ba3cec1760f9a85 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 26 May 2023 11:20:01 -0700 Subject: [PATCH 16/30] Lint --- qiskit/visualization/circuit/matplotlib.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 5406a2e78ac7..59c8b4f385f2 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -513,10 +513,8 @@ def _get_layer_widths(self, node_data, wire_map, qubits_dict, clbits_dict, glob_ layer_widths.update(flow_widths) self._flow_drawers[node].append(flow_drawer) - curr_layer = 0 for width, layer_num, flow_parent in flow_widths.values(): if layer_num != -1 and flow_parent == flow_drawer._flow_parent: - curr_layer = layer_num raw_gate_width += width # Need extra incr of 1.0 for else box @@ -893,7 +891,7 @@ def _add_nodes_and_coords( clbits_dict, glob_data, flow_parent=flow_drawer[i]._flow_parent, - is_if=True if i == 0 else False, + is_if=(i == 0), ) # Recurse for if/else ops inside the flow_drawer flow_drawer[i]._add_nodes_and_coords( From b09addfd038ea6c1a6b1ab6211dcb9753e9831f0 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sun, 28 May 2023 12:21:32 -0700 Subject: [PATCH 17/30] Iniital box fold --- qiskit/visualization/circuit/matplotlib.py | 112 +++++++++++++-------- 1 file changed, 68 insertions(+), 44 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 59c8b4f385f2..642b181f2b42 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -267,9 +267,9 @@ def draw(self, filename=None, verbose=False): # load the wire map wire_map = get_wire_map(self._circuit, self._qubits + self._clbits, self._cregbundle) - # node_data per node with 'width', 'gate_text', 'raw_gate_text', - # 'ctrl_text', 'param_text', 'inside_flow', q_xy', and 'c_xy', - # and colors 'fc', 'ec', 'lc', 'sc', 'gt', and 'tc' + # node_data per node with "width", "gate_text", "raw_gate_text", "ctrl_text", + # "param_text", "if_depth", "inside_flow", "x_index", q_xy", and "c_xy", + # and colors "fc", "ec", "lc", "sc", "gt", and "tc" node_data = {} # glob_data contains global values used throughout, "n_lines", "x_offset", "next_x_index" @@ -283,9 +283,7 @@ def draw(self, filename=None, verbose=False): self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict, glob_data) # get layer widths - layer_widths = self._get_layer_widths( - node_data, wire_map, qubits_dict, clbits_dict, glob_data - ) + layer_widths = self._get_layer_widths(node_data, wire_map, glob_data) # load the coordinates for each top level gate and compute number of folds. # coordinates for flow gates are loaded before draw_ops @@ -394,7 +392,7 @@ def _load_flow_wire_maps(self, wire_map): wire_map.update(inner_wire_map) flow_drawer[i]._load_flow_wire_maps(wire_map) - def _get_layer_widths(self, node_data, wire_map, qubits_dict, clbits_dict, glob_data): + def _get_layer_widths(self, node_data, wire_map, glob_data): """Compute the layer_widths for the layers""" layer_widths = {} @@ -503,15 +501,14 @@ def _get_layer_widths(self, node_data, wire_map, qubits_dict, clbits_dict, glob_ fold=self._fold, cregbundle=self._cregbundle, ) + self._flow_drawers[node].append(flow_drawer) + # flow_parent is the parent of the new class instance flow_drawer._flow_parent = node # Recursively call _get_layer_widths for the circuit inside the if/else - flow_widths = flow_drawer._get_layer_widths( - node_data, wire_map, qubits_dict, clbits_dict, glob_data - ) + flow_widths = flow_drawer._get_layer_widths(node_data, wire_map, glob_data) layer_widths.update(flow_widths) - self._flow_drawers[node].append(flow_drawer) for width, layer_num, flow_parent in flow_widths.values(): if layer_num != -1 and flow_parent == flow_drawer._flow_parent: @@ -954,7 +951,7 @@ def _draw_ops( # draw the box for control flow circuits elif isinstance(op, IfElseOp): - self._flow_op_gate(node, node_data) + self._flow_op_gate(node, node_data, glob_data) # draw single qubit gates elif len(node_data[node]["q_xy"]) == 1 and not node.cargs: @@ -1346,7 +1343,7 @@ def _multiqubit_gate(self, node, node_data, xy=None): zorder=PORDER_TEXT, ) - def _flow_op_gate(self, node, node_data): + def _flow_op_gate(self, node, node_data, glob_data): """Draw the box for a flow op circuit""" xy = node_data[node]["q_xy"] xpos = min(x[0] for x in xy) @@ -1370,43 +1367,43 @@ def _flow_op_gate(self, node, node_data): self._style["dispcol"]["x"][0], self._style["cc"], ] - # FancyBbox allows rounded corners - box = self._patches_mod.FancyBboxPatch( - xy=(xpos, ypos - 0.5 * HIG), - width=wid, - height=height, - boxstyle="round, pad=0.1", - fc="none", - ec=colors[node_data[node]["if_depth"] % 4], - linewidth=3.0, - zorder=PORDER_FLOW, - ) - self._ax.add_patch(box) - self._ax.spines["top"].set_visible(False) - self._ax.text( - xpos + 0.22, - ypos_max + 0.2, - "If", - ha="left", - va="center", - fontsize=self._fs, - color=node_data[node]["gt"], - clip_on=True, - zorder=PORDER_TEXT, - ) - if else_width > 0.0: - self._ax.plot( - [xpos + if_width + 0.3, xpos + if_width + 0.3], - [ypos - 0.5 * HIG - 0.1, ypos + height - 0.22], - color=colors[node_data[node]["if_depth"]], + end_x = xpos + wid + i = 0 + while end_x > 0.0: + if end_x < 0.0: + break + end_x = xpos + wid - i * self._fold + # FancyBbox allows rounded corners + box = self._patches_mod.FancyBboxPatch( + xy=(xpos - i * self._fold, ypos - 0.5 * HIG - i * (glob_data["n_lines"] + 1)), + width=wid, + height=height, + boxstyle="round, pad=0.1", + fc="none", + ec=colors[node_data[node]["if_depth"] % 4], linewidth=3.0, linestyle="solid", zorder=PORDER_FLOW, ) + self._ax.add_patch(box) + if i > 0: + box = self._patches_mod.FancyBboxPatch( + xy=(xpos - i * self._fold, ypos - 0.5 * HIG - i * (glob_data["n_lines"] + 1)), + width=wid - end_x - 0.7, + height=height, + boxstyle="round, pad=0.1", + fc="none", + ec=self._style["bg"], # [colors[node_data[node]["if_depth"] % 4], + linewidth=4.0, + linestyle="solid", + zorder=PORDER_FLOW, + ) + self._ax.add_patch(box) + self._ax.spines["top"].set_visible(False) self._ax.text( - xpos + if_width + 0.5, + xpos + 0.22, ypos_max + 0.2, - "Else", + "If", ha="left", va="center", fontsize=self._fs, @@ -1414,6 +1411,33 @@ def _flow_op_gate(self, node, node_data): clip_on=True, zorder=PORDER_TEXT, ) + if else_width > 0.0: + self._ax.plot( + [ + xpos + if_width + 0.3 - i * self._fold, + xpos + if_width + 0.3 - i * self._fold, + ], + [ + ypos - 0.5 * HIG - 0.1 - i * (glob_data["n_lines"] + 1), + ypos + height - 0.22 - i * (glob_data["n_lines"] + 1), + ], + color=colors[node_data[node]["if_depth"]], + linewidth=3.0, + linestyle="solid", + zorder=PORDER_FLOW, + ) + self._ax.text( + xpos + if_width + 0.5 - i * self._fold, + ypos_max + 0.2 - i * (glob_data["n_lines"] + 1), + "Else", + ha="left", + va="center", + fontsize=self._fs, + color=node_data[node]["gt"], + clip_on=True, + zorder=PORDER_TEXT, + ) + i += 1 def _control_gate(self, node, node_data): """Draw a controlled gate""" From bce82957728772b2f77197d72900cd2d32ac7c55 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Mon, 29 May 2023 13:40:53 -0700 Subject: [PATCH 18/30] Fold flow boxes --- qiskit/visualization/circuit/matplotlib.py | 84 +++++++++++----------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 642b181f2b42..3e6a915d7899 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -1367,68 +1367,58 @@ def _flow_op_gate(self, node, node_data, glob_data): self._style["dispcol"]["x"][0], self._style["cc"], ] + # To fold box onto next lines, draw it repeatedly, shifting it left by + # ``fold_level * self._fold`` and cutting it off at ``end_x - 0.1 + glob_data["x_offset"]`` + # so it doesn't draw in the area of the bit names. + fold_level= 0 end_x = xpos + wid - i = 0 + while end_x > 0.0: if end_x < 0.0: break - end_x = xpos + wid - i * self._fold + x_shift = fold_level * self._fold + y_shift = fold_level * (glob_data["n_lines"] + 1) + end_x = xpos + wid - x_shift + left_edge = end_x + 0.1 - glob_data["x_offset"] + # FancyBbox allows rounded corners box = self._patches_mod.FancyBboxPatch( - xy=(xpos - i * self._fold, ypos - 0.5 * HIG - i * (glob_data["n_lines"] + 1)), + xy=(xpos - x_shift, ypos - 0.5 * HIG - y_shift), width=wid, height=height, boxstyle="round, pad=0.1", fc="none", ec=colors[node_data[node]["if_depth"] % 4], linewidth=3.0, - linestyle="solid", zorder=PORDER_FLOW, ) self._ax.add_patch(box) - if i > 0: - box = self._patches_mod.FancyBboxPatch( - xy=(xpos - i * self._fold, ypos - 0.5 * HIG - i * (glob_data["n_lines"] + 1)), - width=wid - end_x - 0.7, - height=height, - boxstyle="round, pad=0.1", - fc="none", - ec=self._style["bg"], # [colors[node_data[node]["if_depth"] % 4], - linewidth=4.0, - linestyle="solid", - zorder=PORDER_FLOW, + # Don't draw text in the area of the bit names + if xpos - x_shift > glob_data["x_offset"] + 0.1: + self._ax.text( + xpos - x_shift + 0.22, + ypos_max + 0.2 - y_shift, + "If", + ha="left", + va="center", + fontsize=self._fs, + color=node_data[node]["gt"], + clip_on=True, + zorder=PORDER_TEXT, ) - self._ax.add_patch(box) - self._ax.spines["top"].set_visible(False) - self._ax.text( - xpos + 0.22, - ypos_max + 0.2, - "If", - ha="left", - va="center", - fontsize=self._fs, - color=node_data[node]["gt"], - clip_on=True, - zorder=PORDER_TEXT, - ) - if else_width > 0.0: + # If there's an else, draw the box and the name unless it's off the left edge + if else_width > 0.0 and xpos + if_width + 0.3 - x_shift > glob_data["x_offset"]: self._ax.plot( - [ - xpos + if_width + 0.3 - i * self._fold, - xpos + if_width + 0.3 - i * self._fold, - ], - [ - ypos - 0.5 * HIG - 0.1 - i * (glob_data["n_lines"] + 1), - ypos + height - 0.22 - i * (glob_data["n_lines"] + 1), - ], + [xpos + if_width + 0.3 - x_shift, xpos + if_width + 0.3 - x_shift], + [ypos - 0.5 * HIG - 0.1 - y_shift, ypos + height - 0.22 - y_shift], color=colors[node_data[node]["if_depth"]], linewidth=3.0, linestyle="solid", zorder=PORDER_FLOW, ) self._ax.text( - xpos + if_width + 0.5 - i * self._fold, - ypos_max + 0.2 - i * (glob_data["n_lines"] + 1), + xpos + if_width + 0.5 - x_shift, + ypos_max + 0.2 - y_shift, "Else", ha="left", va="center", @@ -1437,7 +1427,21 @@ def _flow_op_gate(self, node, node_data, glob_data): clip_on=True, zorder=PORDER_TEXT, ) - i += 1 + # To clean up box stuff in the bit name area, draw the box again using background + # color in that area. + if fold_level > 0: + box = self._patches_mod.FancyBboxPatch( + xy=(xpos - x_shift, ypos - 0.5 * HIG - y_shift), + width=min(wid, wid - left_edge), + height=height, + boxstyle="round, pad=0.1", + fc="none", + ec=self._style["bg"], + linewidth=4.0, + zorder=PORDER_FLOW, + ) + self._ax.add_patch(box) + fold_level += 1 def _control_gate(self, node, node_data): """Draw a controlled gate""" From 5c80f45e049d0ce15f9f3198eef2645726483b9f Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Mon, 29 May 2023 14:25:42 -0700 Subject: [PATCH 19/30] Add reno --- qiskit/visualization/circuit/matplotlib.py | 4 ++-- .../display-circuits-inside-if-else-9b1a3ca2fcce5020.yaml | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/display-circuits-inside-if-else-9b1a3ca2fcce5020.yaml diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 3e6a915d7899..8682d04b16f2 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -1370,7 +1370,7 @@ def _flow_op_gate(self, node, node_data, glob_data): # To fold box onto next lines, draw it repeatedly, shifting it left by # ``fold_level * self._fold`` and cutting it off at ``end_x - 0.1 + glob_data["x_offset"]`` # so it doesn't draw in the area of the bit names. - fold_level= 0 + fold_level = 0 end_x = xpos + wid while end_x > 0.0: @@ -1407,7 +1407,7 @@ def _flow_op_gate(self, node, node_data, glob_data): zorder=PORDER_TEXT, ) # If there's an else, draw the box and the name unless it's off the left edge - if else_width > 0.0 and xpos + if_width + 0.3 - x_shift > glob_data["x_offset"]: + if else_width > 0.0 and (xpos + if_width + 0.3 - x_shift) > glob_data["x_offset"] + 0.1: self._ax.plot( [xpos + if_width + 0.3 - x_shift, xpos + if_width + 0.3 - x_shift], [ypos - 0.5 * HIG - 0.1 - y_shift, ypos + height - 0.22 - y_shift], diff --git a/releasenotes/notes/display-circuits-inside-if-else-9b1a3ca2fcce5020.yaml b/releasenotes/notes/display-circuits-inside-if-else-9b1a3ca2fcce5020.yaml new file mode 100644 index 000000000000..b3d861af3be6 --- /dev/null +++ b/releasenotes/notes/display-circuits-inside-if-else-9b1a3ca2fcce5020.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + In :class:`~qiskit.visualization.circuit.MatplotlibDrawer`, operations + built from :class:`~qiskit.circuit.control_flow.IfElseOp`, whether + directly instantiated or built using :meth:`~qiskit.circuit.QuantumCircuit.if_test`, + will now fully display the circuits inside the ``if`` and ``else`` wrapped + with boxes to delineate the circuits. From b7efcc06062c4f95b432ece51f058b4eead6c18a Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 31 May 2023 10:33:43 -0700 Subject: [PATCH 20/30] Fix initial mpl and window load --- qiskit/visualization/circuit/matplotlib.py | 59 ++++++++++++---------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 8682d04b16f2..16b52f91ab2a 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -84,12 +84,6 @@ def __init__( cregbundle=None, with_layout=False, ): - from matplotlib import patches - from matplotlib import pyplot as plt - - self._patches_mod = patches - self._plt_mod = plt - self._circuit = circuit self._qubits = qubits self._clbits = clbits @@ -97,11 +91,7 @@ def __init__( self._flow_parent = None self._scale = 1.0 if scale is None else scale - self._style, def_font_ratio = load_style(style) - - # If font/subfont ratio changes from default, have to scale width calculations for - # subfont. Font change is auto scaled in the self._figure.set_size_inches call in draw() - self._subfont_factor = self._style["sfs"] * def_font_ratio / self._style["fs"] + self._style = style self._plot_barriers = plot_barriers self._reverse_bits = reverse_bits @@ -117,18 +107,7 @@ def __init__( if self._fold < 2: self._fold = -1 - if ax is None: - self._user_ax = False - self._figure = plt.figure() - self._figure.patch.set_facecolor(color=self._style["bg"]) - self._ax = self._figure.add_subplot(111) - else: - self._user_ax = True - self._ax = ax - self._figure = ax.get_figure() - self._ax.axis("off") - self._ax.set_aspect("equal") - self._ax.tick_params(labelbottom=False, labeltop=False, labelleft=False, labelright=False) + self._ax = ax self._initial_state = initial_state self._global_phase = self._circuit.global_phase @@ -148,12 +127,11 @@ def __init__( else: self._cregbundle = True if cregbundle is None else cregbundle - self._fs = self._style["fs"] - self._sfs = self._style["sfs"] self._lwidth1 = 1.0 self._lwidth15 = 1.5 self._lwidth2 = 2.0 self._lwidth3 = 3.0 + self._lwidth4 = 4.0 # Class instances of MatplotlibDrawer for each flow gate - If/Else, For, While, Switch self._flow_drawers = {} @@ -260,6 +238,33 @@ def draw(self, filename=None, verbose=False): """Main entry point to 'matplotlib' ('mpl') drawer. Called from ``visualization.circuit_drawer`` and from ``QuantumCircuit.draw`` through circuit_drawer. """ + from matplotlib import patches + from matplotlib import pyplot as plt + + self._patches_mod = patches + self._plt_mod = plt + + self._style, def_font_ratio = load_style(self._style) + + # If font/subfont ratio changes from default, have to scale width calculations for + # subfont. Font change is auto scaled in the self._figure.set_size_inches call in draw() + self._subfont_factor = self._style["sfs"] * def_font_ratio / self._style["fs"] + self._fs = self._style["fs"] + self._sfs = self._style["sfs"] + + if self._ax is None: + self._user_ax = False + self._figure = plt.figure() + self._figure.patch.set_facecolor(color=self._style["bg"]) + self._ax = self._figure.add_subplot(111) + else: + self._user_ax = True + self._ax = ax + self._figure = ax.get_figure() + self._ax.axis("off") + self._ax.set_aspect("equal") + self._ax.tick_params(labelbottom=False, labeltop=False, labelleft=False, labelright=False) + # All information for the drawing is first loaded into node_data for the gates and into # qubits_dict, clbits_dict, and wire_map for the qubits, clbits, and wires, # followed by the coordinates for each gate. @@ -1389,7 +1394,7 @@ def _flow_op_gate(self, node, node_data, glob_data): boxstyle="round, pad=0.1", fc="none", ec=colors[node_data[node]["if_depth"] % 4], - linewidth=3.0, + linewidth=self._lwidth3, zorder=PORDER_FLOW, ) self._ax.add_patch(box) @@ -1437,7 +1442,7 @@ def _flow_op_gate(self, node, node_data, glob_data): boxstyle="round, pad=0.1", fc="none", ec=self._style["bg"], - linewidth=4.0, + linewidth=self._lwidth4, zorder=PORDER_FLOW, ) self._ax.add_patch(box) From 883e5911513a8f9205820c70cd62eb1c73ea113c Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 31 May 2023 10:43:23 -0700 Subject: [PATCH 21/30] Move figure, mpl, and style info to draw() --- qiskit/visualization/circuit/matplotlib.py | 57 ++++++++++++---------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 3ee21a87e6dc..28d4261aa87f 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -81,23 +81,13 @@ def __init__( cregbundle=None, with_layout=False, ): - from matplotlib import patches - from matplotlib import pyplot as plt - - self._patches_mod = patches - self._plt_mod = plt - self._circuit = circuit self._qubits = qubits self._clbits = clbits self._nodes = nodes self._scale = 1.0 if scale is None else scale - self._style, def_font_ratio = load_style(style) - - # If font/subfont ratio changes from default, have to scale width calculations for - # subfont. Font change is auto scaled in the self._figure.set_size_inches call in draw() - self._subfont_factor = self._style["sfs"] * def_font_ratio / self._style["fs"] + self._style = style self._plot_barriers = plot_barriers self._reverse_bits = reverse_bits @@ -113,18 +103,7 @@ def __init__( if self._fold < 2: self._fold = -1 - if ax is None: - self._user_ax = False - self._figure = plt.figure() - self._figure.patch.set_facecolor(color=self._style["bg"]) - self._ax = self._figure.add_subplot(111) - else: - self._user_ax = True - self._ax = ax - self._figure = ax.get_figure() - self._ax.axis("off") - self._ax.set_aspect("equal") - self._ax.tick_params(labelbottom=False, labeltop=False, labelleft=False, labelright=False) + self._ax = ax self._initial_state = initial_state self._global_phase = self._circuit.global_phase @@ -144,8 +123,6 @@ def __init__( else: self._cregbundle = True if cregbundle is None else cregbundle - self._fs = self._style["fs"] - self._sfs = self._style["sfs"] self._lwidth1 = 1.0 self._lwidth15 = 1.5 self._lwidth2 = 2.0 @@ -252,6 +229,36 @@ def draw(self, filename=None, verbose=False): """Main entry point to 'matplotlib' ('mpl') drawer. Called from ``visualization.circuit_drawer`` and from ``QuantumCircuit.draw`` through circuit_drawer. """ + + # Import matplotlib and load all the figure, window, and style info + from matplotlib import patches + from matplotlib import pyplot as plt + + self._patches_mod = patches + self._plt_mod = plt + + self._style, def_font_ratio = load_style(self._style) + + # If font/subfont ratio changes from default, have to scale width calculations for + # subfont. Font change is auto scaled in the self._figure.set_size_inches call in draw() + self._subfont_factor = self._style["sfs"] * def_font_ratio / self._style["fs"] + self._fs = self._style["fs"] + self._sfs = self._style["sfs"] + + # if no user ax, setup default figure. Else use the user figure. + if self._ax is None: + self._user_ax = False + self._figure = plt.figure() + self._figure.patch.set_facecolor(color=self._style["bg"]) + self._ax = self._figure.add_subplot(111) + else: + self._user_ax = True + self._ax = ax + self._figure = ax.get_figure() + self._ax.axis("off") + self._ax.set_aspect("equal") + self._ax.tick_params(labelbottom=False, labeltop=False, labelleft=False, labelright=False) + # All information for the drawing is first loaded into node_data for the gates and into # qubits_dict, clbits_dict, and wire_map for the qubits, clbits, and wires, # followed by the coordinates for each gate. From 575728d9b15415e66950b855b555b9562b760462 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 31 May 2023 11:22:57 -0700 Subject: [PATCH 22/30] Comments --- qiskit/visualization/circuit/matplotlib.py | 63 +++++++++++----------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 16b52f91ab2a..6d0d81c61f1b 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -238,6 +238,8 @@ def draw(self, filename=None, verbose=False): """Main entry point to 'matplotlib' ('mpl') drawer. Called from ``visualization.circuit_drawer`` and from ``QuantumCircuit.draw`` through circuit_drawer. """ + + # Import matplotlib and load all the figure, window, and style info from matplotlib import patches from matplotlib import pyplot as plt @@ -249,9 +251,8 @@ def draw(self, filename=None, verbose=False): # If font/subfont ratio changes from default, have to scale width calculations for # subfont. Font change is auto scaled in the self._figure.set_size_inches call in draw() self._subfont_factor = self._style["sfs"] * def_font_ratio / self._style["fs"] - self._fs = self._style["fs"] - self._sfs = self._style["sfs"] + # if no user ax, setup default figure. Else use the user figure. if self._ax is None: self._user_ax = False self._figure = plt.figure() @@ -259,8 +260,7 @@ def draw(self, filename=None, verbose=False): self._ax = self._figure.add_subplot(111) else: self._user_ax = True - self._ax = ax - self._figure = ax.get_figure() + self._figure = self._ax.get_figure() self._ax.axis("off") self._ax.set_aspect("equal") self._ax.tick_params(labelbottom=False, labeltop=False, labelleft=False, labelright=False) @@ -317,7 +317,7 @@ def draw(self, filename=None, verbose=False): self._ax.set_ylim(yb, yt) # update figure size and, for backward compatibility, - # need to scale by a default value equal to (self._fs * 3.01 / 72 / 0.65) + # need to scale by a default value equal to (self._style["fs"] * 3.01 / 72 / 0.65) base_fig_w = (xr - xl) * 0.8361111 base_fig_h = (yt - yb) * 0.8361111 scale = self._scale @@ -345,12 +345,13 @@ def draw(self, filename=None, verbose=False): # drawing will scale with 'set_size_inches', but fonts and linewidths do not if scale != 1.0: - self._fs *= scale - self._sfs *= scale + self._style["fs"] *= scale + self._style["sfs"] *= scale self._lwidth1 = 1.0 * scale self._lwidth15 = 1.5 * scale self._lwidth2 = 2.0 * scale self._lwidth3 = 3.0 * scale + self._lwidth4 = 4.0 * scale # Once the scaling factor has been determined, the global phase, register names # and numbers, wires, and gates are drawn @@ -443,7 +444,7 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): # small increments at end of the 3 _get_text_width calls are for small # spacing adjustments between gates - ctrl_width = self._get_text_width(ctrl_text, fontsize=self._sfs) - 0.05 + ctrl_width = self._get_text_width(ctrl_text, fontsize=self._style["sfs"]) - 0.05 # get param_width, but 0 for gates with array params or circuits in params if ( @@ -457,7 +458,7 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): param_text = f"$[{param_text.replace('$', '')}]$" node_data[node]["param_text"] = param_text raw_param_width = self._get_text_width( - param_text, fontsize=self._sfs, param=True + param_text, fontsize=self._style["sfs"], param=True ) param_width = raw_param_width + 0.08 else: @@ -468,7 +469,7 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): if isinstance(base_type, PhaseGate): gate_text = "P" raw_gate_width = ( - self._get_text_width(gate_text + " ()", fontsize=self._sfs) + self._get_text_width(gate_text + " ()", fontsize=self._style["sfs"]) + raw_param_width ) gate_width = (raw_gate_width + 0.08) * 1.58 @@ -530,7 +531,7 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): # Otherwise, standard gate or multiqubit gate else: - raw_gate_width = self._get_text_width(gate_text, fontsize=self._fs) + raw_gate_width = self._get_text_width(gate_text, fontsize=self._style["fs"]) gate_width = raw_gate_width + 0.10 # add .21 for the qubit numbers on the left of the multibit gates if len(node.qargs) - num_ctrl_qubits > 1: @@ -589,7 +590,7 @@ def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict, glob_data): ) reg_remove_under = 0 if reg_size < 2 else 1 text_width = ( - self._get_text_width(wire_label, self._fs, reg_remove_under=reg_remove_under) * 1.15 + self._get_text_width(wire_label, self._style["fs"], reg_remove_under=reg_remove_under) * 1.15 ) if text_width > longest_wire_label_width: longest_wire_label_width = text_width @@ -742,7 +743,7 @@ def _get_text_width(self, text, fontsize, param=False, reg_remove_under=None): if param: text = text.replace("-", "+") - f = 0 if fontsize == self._fs else 1 + f = 0 if fontsize == self._style["fs"] else 1 sum_text = 0.0 for c in text: try: @@ -768,7 +769,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic qubit_label, ha="right", va="center", - fontsize=1.25 * self._fs, + fontsize=1.25 * self._style["fs"], color=self._style["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -804,7 +805,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic str(this_clbit["register"].size), ha="left", va="bottom", - fontsize=0.8 * self._fs, + fontsize=0.8 * self._style["fs"], color=self._style["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -815,7 +816,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic this_clbit["wire_label"], ha="right", va="center", - fontsize=1.25 * self._fs, + fontsize=1.25 * self._style["fs"], color=self._style["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -869,7 +870,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic str(layer_num + 1), ha="center", va="center", - fontsize=self._sfs, + fontsize=self._style["sfs"], color=self._style["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -1097,7 +1098,7 @@ def _condition(self, node, node_data, wire_map, cond_xy): label, ha="center", va="top", - fontsize=self._sfs, + fontsize=self._style["sfs"], color=self._style["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -1158,7 +1159,7 @@ def _measure(self, node, node_data): str(reg_index), ha="left", va="bottom", - fontsize=0.8 * self._fs, + fontsize=0.8 * self._style["fs"], color=self._style["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -1202,7 +1203,7 @@ def _barrier(self, node, node_data): node.op.label, ha="center", va="top", - fontsize=self._fs, + fontsize=self._style["fs"], color=node_data[node]["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -1236,7 +1237,7 @@ def _gate(self, node, node_data, xy=None): node_data[node]["param_text"], ha="center", va="center", - fontsize=self._sfs, + fontsize=self._style["sfs"], color=node_data[node]["sc"], clip_on=True, zorder=PORDER_TEXT, @@ -1247,7 +1248,7 @@ def _gate(self, node, node_data, xy=None): node_data[node]["gate_text"], ha="center", va="center", - fontsize=self._fs, + fontsize=self._style["fs"], color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, @@ -1302,7 +1303,7 @@ def _multiqubit_gate(self, node, node_data, xy=None): str(bit), ha="left", va="center", - fontsize=self._fs, + fontsize=self._style["fs"], color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, @@ -1316,7 +1317,7 @@ def _multiqubit_gate(self, node, node_data, xy=None): str(bit), ha="left", va="center", - fontsize=self._fs, + fontsize=self._style["fs"], color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, @@ -1331,7 +1332,7 @@ def _multiqubit_gate(self, node, node_data, xy=None): node_data[node]["param_text"], ha="center", va="center", - fontsize=self._sfs, + fontsize=self._style["sfs"], color=node_data[node]["sc"], clip_on=True, zorder=PORDER_TEXT, @@ -1342,7 +1343,7 @@ def _multiqubit_gate(self, node, node_data, xy=None): node_data[node]["gate_text"], ha="center", va="center", - fontsize=self._fs, + fontsize=self._style["fs"], color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, @@ -1406,7 +1407,7 @@ def _flow_op_gate(self, node, node_data, glob_data): "If", ha="left", va="center", - fontsize=self._fs, + fontsize=self._style["fs"], color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, @@ -1415,7 +1416,7 @@ def _flow_op_gate(self, node, node_data, glob_data): if else_width > 0.0 and (xpos + if_width + 0.3 - x_shift) > glob_data["x_offset"] + 0.1: self._ax.plot( [xpos + if_width + 0.3 - x_shift, xpos + if_width + 0.3 - x_shift], - [ypos - 0.5 * HIG - 0.1 - y_shift, ypos + height - 0.22 - y_shift], + [ypos - 0.5 * HIG - 0.08 - y_shift, ypos + height - 0.22 - y_shift], color=colors[node_data[node]["if_depth"]], linewidth=3.0, linestyle="solid", @@ -1427,7 +1428,7 @@ def _flow_op_gate(self, node, node_data, glob_data): "Else", ha="left", va="center", - fontsize=self._fs, + fontsize=self._style["fs"], color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, @@ -1542,7 +1543,7 @@ def _ctrl_qubit(self, xy, fc=None, ec=None, tc=None, text="", text_top=None): text, ha="center", va="top", - fontsize=self._sfs, + fontsize=self._style["sfs"], color=tc, clip_on=True, zorder=PORDER_TEXT, @@ -1666,7 +1667,7 @@ def _sidetext(self, node, node_data, xy, tc=None, text=""): text, ha="center", va="top", - fontsize=self._sfs, + fontsize=self._style["sfs"], color=tc, clip_on=True, zorder=PORDER_TEXT, From ff2ad661e882b2db1a0eef2b51e049768357faf7 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 31 May 2023 11:34:06 -0700 Subject: [PATCH 23/30] Change font vars and fix ax --- qiskit/visualization/circuit/matplotlib.py | 53 ++++++++++------------ 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 28d4261aa87f..a3fe35c4f0c4 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -242,8 +242,6 @@ def draw(self, filename=None, verbose=False): # If font/subfont ratio changes from default, have to scale width calculations for # subfont. Font change is auto scaled in the self._figure.set_size_inches call in draw() self._subfont_factor = self._style["sfs"] * def_font_ratio / self._style["fs"] - self._fs = self._style["fs"] - self._sfs = self._style["sfs"] # if no user ax, setup default figure. Else use the user figure. if self._ax is None: @@ -253,8 +251,7 @@ def draw(self, filename=None, verbose=False): self._ax = self._figure.add_subplot(111) else: self._user_ax = True - self._ax = ax - self._figure = ax.get_figure() + self._figure = self._ax.get_figure() self._ax.axis("off") self._ax.set_aspect("equal") self._ax.tick_params(labelbottom=False, labeltop=False, labelleft=False, labelright=False) @@ -310,7 +307,7 @@ def draw(self, filename=None, verbose=False): self._ax.set_ylim(yb, yt) # update figure size and, for backward compatibility, - # need to scale by a default value equal to (self._fs * 3.01 / 72 / 0.65) + # need to scale by a default value equal to (self._style["fs"] * 3.01 / 72 / 0.65) base_fig_w = (xr - xl) * 0.8361111 base_fig_h = (yt - yb) * 0.8361111 scale = self._scale @@ -338,8 +335,8 @@ def draw(self, filename=None, verbose=False): # drawing will scale with 'set_size_inches', but fonts and linewidths do not if scale != 1.0: - self._fs *= scale - self._sfs *= scale + self._style["fs"] *= scale + self._style["sfs"] *= scale self._lwidth1 = 1.0 * scale self._lwidth15 = 1.5 * scale self._lwidth2 = 2.0 * scale @@ -412,7 +409,7 @@ def _get_layer_widths(self, node_data): # small increments at end of the 3 _get_text_width calls are for small # spacing adjustments between gates - ctrl_width = self._get_text_width(ctrl_text, fontsize=self._sfs) - 0.05 + ctrl_width = self._get_text_width(ctrl_text, fontsize=self._style["sfs"]) - 0.05 # get param_width, but 0 for gates with array params if ( @@ -425,7 +422,7 @@ def _get_layer_widths(self, node_data): param_text = f"$[{param_text.replace('$', '')}]$" node_data[node]["param_text"] = param_text raw_param_width = self._get_text_width( - param_text, fontsize=self._sfs, param=True + param_text, fontsize=self._style["sfs"], param=True ) param_width = raw_param_width + 0.08 else: @@ -436,14 +433,14 @@ def _get_layer_widths(self, node_data): if isinstance(base_type, PhaseGate): gate_text = "P" raw_gate_width = ( - self._get_text_width(gate_text + " ()", fontsize=self._sfs) + self._get_text_width(gate_text + " ()", fontsize=self._style["sfs"]) + raw_param_width ) gate_width = (raw_gate_width + 0.08) * 1.58 # Otherwise, standard gate or multiqubit gate else: - raw_gate_width = self._get_text_width(gate_text, fontsize=self._fs) + raw_gate_width = self._get_text_width(gate_text, fontsize=self._style["fs"]) gate_width = raw_gate_width + 0.10 # add .21 for the qubit numbers on the left of the multibit gates if len(node.qargs) - num_ctrl_qubits > 1: @@ -497,7 +494,7 @@ def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict, glob_data): ) reg_remove_under = 0 if reg_size < 2 else 1 text_width = ( - self._get_text_width(wire_label, self._fs, reg_remove_under=reg_remove_under) * 1.15 + self._get_text_width(wire_label, self._style["fs"], reg_remove_under=reg_remove_under) * 1.15 ) if text_width > longest_wire_label_width: longest_wire_label_width = text_width @@ -621,7 +618,7 @@ def _get_text_width(self, text, fontsize, param=False, reg_remove_under=None): if param: text = text.replace("-", "+") - f = 0 if fontsize == self._fs else 1 + f = 0 if fontsize == self._style["fs"] else 1 sum_text = 0.0 for c in text: try: @@ -647,7 +644,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic qubit_label, ha="right", va="center", - fontsize=1.25 * self._fs, + fontsize=1.25 * self._style["fs"], color=self._style["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -683,7 +680,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic str(this_clbit["register"].size), ha="left", va="bottom", - fontsize=0.8 * self._fs, + fontsize=0.8 * self._style["fs"], color=self._style["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -694,7 +691,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic this_clbit["wire_label"], ha="right", va="center", - fontsize=1.25 * self._fs, + fontsize=1.25 * self._style["fs"], color=self._style["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -748,7 +745,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic str(layer_num + 1), ha="center", va="center", - fontsize=self._sfs, + fontsize=self._style["sfs"], color=self._style["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -931,7 +928,7 @@ def _condition(self, node, node_data, wire_map, cond_xy): label, ha="center", va="top", - fontsize=self._sfs, + fontsize=self._style["sfs"], color=self._style["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -992,7 +989,7 @@ def _measure(self, node, node_data): str(reg_index), ha="left", va="bottom", - fontsize=0.8 * self._fs, + fontsize=0.8 * self._style["fs"], color=self._style["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -1036,7 +1033,7 @@ def _barrier(self, node, node_data): node.op.label, ha="center", va="top", - fontsize=self._fs, + fontsize=self._style["fs"], color=node_data[node]["tc"], clip_on=True, zorder=PORDER_TEXT, @@ -1070,7 +1067,7 @@ def _gate(self, node, node_data, xy=None): node_data[node]["param_text"], ha="center", va="center", - fontsize=self._sfs, + fontsize=self._style["sfs"], color=node_data[node]["sc"], clip_on=True, zorder=PORDER_TEXT, @@ -1081,7 +1078,7 @@ def _gate(self, node, node_data, xy=None): node_data[node]["gate_text"], ha="center", va="center", - fontsize=self._fs, + fontsize=self._style["fs"], color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, @@ -1136,7 +1133,7 @@ def _multiqubit_gate(self, node, node_data, xy=None): str(bit), ha="left", va="center", - fontsize=self._fs, + fontsize=self._style["fs"], color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, @@ -1150,7 +1147,7 @@ def _multiqubit_gate(self, node, node_data, xy=None): str(bit), ha="left", va="center", - fontsize=self._fs, + fontsize=self._style["fs"], color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, @@ -1165,7 +1162,7 @@ def _multiqubit_gate(self, node, node_data, xy=None): node_data[node]["param_text"], ha="center", va="center", - fontsize=self._sfs, + fontsize=self._style["sfs"], color=node_data[node]["sc"], clip_on=True, zorder=PORDER_TEXT, @@ -1176,7 +1173,7 @@ def _multiqubit_gate(self, node, node_data, xy=None): node_data[node]["gate_text"], ha="center", va="center", - fontsize=self._fs, + fontsize=self._style["fs"], color=node_data[node]["gt"], clip_on=True, zorder=PORDER_TEXT, @@ -1276,7 +1273,7 @@ def _ctrl_qubit(self, xy, fc=None, ec=None, tc=None, text="", text_top=None): text, ha="center", va="top", - fontsize=self._sfs, + fontsize=self._style["sfs"], color=tc, clip_on=True, zorder=PORDER_TEXT, @@ -1400,7 +1397,7 @@ def _sidetext(self, node, node_data, xy, tc=None, text=""): text, ha="center", va="top", - fontsize=self._sfs, + fontsize=self._style["sfs"], color=tc, clip_on=True, zorder=PORDER_TEXT, From eedf11a7e1c1bab6cb6ac4da5b1fa3581ca504ed Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 31 May 2023 13:49:44 -0700 Subject: [PATCH 24/30] Make patches_mod global --- qiskit/visualization/circuit/matplotlib.py | 149 +++++++++++---------- 1 file changed, 80 insertions(+), 69 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index a3fe35c4f0c4..965dec412337 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -234,24 +234,28 @@ def draw(self, filename=None, verbose=False): from matplotlib import patches from matplotlib import pyplot as plt - self._patches_mod = patches - self._plt_mod = plt + # glob_data contains global values used throughout, "n_lines", "x_offset", "next_x_index", + # "patches_mod", subfont_factor" + glob_data = {} + + glob_data["patches_mod"] = patches + plt_mod = plt self._style, def_font_ratio = load_style(self._style) # If font/subfont ratio changes from default, have to scale width calculations for - # subfont. Font change is auto scaled in the self._figure.set_size_inches call in draw() - self._subfont_factor = self._style["sfs"] * def_font_ratio / self._style["fs"] + # subfont. Font change is auto scaled in the mpl_figure.set_size_inches call in draw() + glob_data["subfont_factor"] = self._style["sfs"] * def_font_ratio / self._style["fs"] # if no user ax, setup default figure. Else use the user figure. if self._ax is None: - self._user_ax = False - self._figure = plt.figure() - self._figure.patch.set_facecolor(color=self._style["bg"]) - self._ax = self._figure.add_subplot(111) + is_user_ax = False + mpl_figure = plt.figure() + mpl_figure.patch.set_facecolor(color=self._style["bg"]) + self._ax = mpl_figure.add_subplot(111) else: - self._user_ax = True - self._figure = self._ax.get_figure() + is_user_ax = True + mpl_figure = self._ax.get_figure() self._ax.axis("off") self._ax.set_aspect("equal") self._ax.tick_params(labelbottom=False, labeltop=False, labelleft=False, labelright=False) @@ -268,9 +272,6 @@ def draw(self, filename=None, verbose=False): # and colors 'fc', 'ec', 'lc', 'sc', 'gt', and 'tc' node_data = {} - # glob_data contains global values used throughout, "n_lines", "x_offset", "next_x_index" - glob_data = {} - # dicts for the names and locations of register/bit labels qubits_dict = {} clbits_dict = {} @@ -279,7 +280,7 @@ def draw(self, filename=None, verbose=False): self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict, glob_data) # get layer widths - layer_widths = self._get_layer_widths(node_data) + layer_widths = self._get_layer_widths(node_data, glob_data) # load the coordinates for each gate and compute number of folds max_x_index = self._get_coords( @@ -313,25 +314,25 @@ def draw(self, filename=None, verbose=False): scale = self._scale # if user passes in an ax, this size takes priority over any other settings - if self._user_ax: + if is_user_ax: # from stackoverflow #19306510, get the bbox size for the ax and then reset scale - bbox = self._ax.get_window_extent().transformed(self._figure.dpi_scale_trans.inverted()) + bbox = self._ax.get_window_extent().transformed(mpl_figure.dpi_scale_trans.inverted()) scale = bbox.width / base_fig_w / 0.8361111 # if scale not 1.0, use this scale factor elif self._scale != 1.0: - self._figure.set_size_inches(base_fig_w * self._scale, base_fig_h * self._scale) + mpl_figure.set_size_inches(base_fig_w * self._scale, base_fig_h * self._scale) # if "figwidth" style param set, use this to scale elif self._style["figwidth"] > 0.0: # in order to get actual inches, need to scale by factor adj_fig_w = self._style["figwidth"] * 1.282736 - self._figure.set_size_inches(adj_fig_w, adj_fig_w * base_fig_h / base_fig_w) + mpl_figure.set_size_inches(adj_fig_w, adj_fig_w * base_fig_h / base_fig_w) scale = adj_fig_w / base_fig_w # otherwise, display default size else: - self._figure.set_size_inches(base_fig_w, base_fig_h) + mpl_figure.set_size_inches(base_fig_w, base_fig_h) # drawing will scale with 'set_size_inches', but fonts and linewidths do not if scale != 1.0: @@ -344,26 +345,24 @@ def draw(self, filename=None, verbose=False): # Once the scaling factor has been determined, the global phase, register names # and numbers, wires, and gates are drawn if self._global_phase: - self._plt_mod.text( - xl, yt, "Global Phase: %s" % pi_check(self._global_phase, output="mpl") - ) + plt_mod.text(xl, yt, "Global Phase: %s" % pi_check(self._global_phase, output="mpl")) self._draw_regs_wires(num_folds, xmax, max_x_index, qubits_dict, clbits_dict, glob_data) self._draw_ops( self._nodes, node_data, wire_map, layer_widths, clbits_dict, glob_data, verbose ) if filename: - self._figure.savefig( + mpl_figure.savefig( filename, dpi=self._style["dpi"], bbox_inches="tight", - facecolor=self._figure.get_facecolor(), + facecolor=mpl_figure.get_facecolor(), ) - if not self._user_ax: - matplotlib_close_if_inline(self._figure) - return self._figure + if not is_user_ax: + matplotlib_close_if_inline(mpl_figure) + return mpl_figure - def _get_layer_widths(self, node_data): + def _get_layer_widths(self, node_data, glob_data): """Compute the layer_widths for the layers""" layer_widths = {} @@ -409,7 +408,9 @@ def _get_layer_widths(self, node_data): # small increments at end of the 3 _get_text_width calls are for small # spacing adjustments between gates - ctrl_width = self._get_text_width(ctrl_text, fontsize=self._style["sfs"]) - 0.05 + ctrl_width = ( + self._get_text_width(ctrl_text, glob_data, fontsize=self._style["sfs"]) - 0.05 + ) # get param_width, but 0 for gates with array params if ( @@ -422,7 +423,7 @@ def _get_layer_widths(self, node_data): param_text = f"$[{param_text.replace('$', '')}]$" node_data[node]["param_text"] = param_text raw_param_width = self._get_text_width( - param_text, fontsize=self._style["sfs"], param=True + param_text, glob_data, fontsize=self._style["sfs"], param=True ) param_width = raw_param_width + 0.08 else: @@ -433,14 +434,18 @@ def _get_layer_widths(self, node_data): if isinstance(base_type, PhaseGate): gate_text = "P" raw_gate_width = ( - self._get_text_width(gate_text + " ()", fontsize=self._style["sfs"]) + self._get_text_width( + gate_text + " ()", glob_data, fontsize=self._style["sfs"] + ) + raw_param_width ) gate_width = (raw_gate_width + 0.08) * 1.58 # Otherwise, standard gate or multiqubit gate else: - raw_gate_width = self._get_text_width(gate_text, fontsize=self._style["fs"]) + raw_gate_width = self._get_text_width( + gate_text, glob_data, fontsize=self._style["fs"] + ) gate_width = raw_gate_width + 0.10 # add .21 for the qubit numbers on the left of the multibit gates if len(node.qargs) - num_ctrl_qubits > 1: @@ -494,7 +499,10 @@ def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict, glob_data): ) reg_remove_under = 0 if reg_size < 2 else 1 text_width = ( - self._get_text_width(wire_label, self._style["fs"], reg_remove_under=reg_remove_under) * 1.15 + self._get_text_width( + wire_label, glob_data, self._style["fs"], reg_remove_under=reg_remove_under + ) + * 1.15 ) if text_width > longest_wire_label_width: longest_wire_label_width = text_width @@ -584,7 +592,7 @@ def _get_coords(self, node_data, wire_map, layer_widths, qubits_dict, clbits_dic return prev_x_index + 1 - def _get_text_width(self, text, fontsize, param=False, reg_remove_under=None): + def _get_text_width(self, text, glob_data, fontsize, param=False, reg_remove_under=None): """Compute the width of a string in the default font""" from pylatexenc.latex2text import LatexNodes2Text @@ -627,7 +635,7 @@ def _get_text_width(self, text, fontsize, param=False, reg_remove_under=None): # if non-ASCII char, use width of 'c', an average size sum_text += self._char_list["c"][f] if f == 1: - sum_text *= self._subfont_factor + sum_text *= glob_data["subfont_factor"] return sum_text def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dict, glob_data): @@ -783,28 +791,28 @@ def _draw_ops( ] if clbits_dict: curr_x_index = max(curr_x_index, glob_data["next_x_index"]) - self._condition(node, node_data, wire_map, cond_xy) + self._condition(node, node_data, wire_map, cond_xy, glob_data) # draw measure if isinstance(op, Measure): - self._measure(node, node_data) + self._measure(node, node_data, glob_data) # draw barriers, snapshots, etc. elif getattr(op, "_directive", False): if self._plot_barriers: - self._barrier(node, node_data) + self._barrier(node, node_data, glob_data) # draw single qubit gates elif len(node_data[node]["q_xy"]) == 1 and not node.cargs: - self._gate(node, node_data) + self._gate(node, node_data, glob_data) # draw controlled gates elif isinstance(op, ControlledGate): - self._control_gate(node, node_data) + self._control_gate(node, node_data, glob_data) # draw multi-qubit gate as final default else: - self._multiqubit_gate(node, node_data) + self._multiqubit_gate(node, node_data, glob_data) l_width.append(layer_widths[node][0]) @@ -866,7 +874,7 @@ def _get_colors(self, node, node_data): node_data[node]["sc"] = sc node_data[node]["lc"] = lc - def _condition(self, node, node_data, wire_map, cond_xy): + def _condition(self, node, node_data, wire_map, cond_xy, glob_data): """Add a conditional to a gate""" label, val_bits = get_condition_label_val( @@ -904,7 +912,7 @@ def _condition(self, node, node_data, wire_map, cond_xy): fc = self._style["lc"] else: fc = self._style["bg"] - box = self._patches_mod.Circle( + box = glob_data["patches_mod"].Circle( xy=xy, radius=WID * 0.15, fc=fc, @@ -935,17 +943,17 @@ def _condition(self, node, node_data, wire_map, cond_xy): ) self._line(qubit_b, clbit_b, lc=self._style["cc"], ls=self._style["cline"]) - def _measure(self, node, node_data): + def _measure(self, node, node_data, glob_data): """Draw the measure symbol and the line to the clbit""" qx, qy = node_data[node]["q_xy"][0] cx, cy = node_data[node]["c_xy"][0] register, _, reg_index = get_bit_reg_index(self._circuit, node.cargs[0]) # draw gate box - self._gate(node, node_data) + self._gate(node, node_data, glob_data) # add measure symbol - arc = self._patches_mod.Arc( + arc = glob_data["patches_mod"].Arc( xy=(qx, qy - 0.15 * HIG), width=WID * 0.7, height=HIG * 0.7, @@ -971,7 +979,7 @@ def _measure(self, node, node_data): lc=self._style["cc"], ls=self._style["cline"], ) - arrowhead = self._patches_mod.Polygon( + arrowhead = glob_data["patches_mod"].Polygon( ( (cx - 0.20 * WID, cy + 0.35 * WID), (cx + 0.20 * WID, cy + 0.35 * WID), @@ -995,7 +1003,7 @@ def _measure(self, node, node_data): zorder=PORDER_TEXT, ) - def _barrier(self, node, node_data): + def _barrier(self, node, node_data, glob_data): """Draw a barrier""" for i, xy in enumerate(node_data[node]["q_xy"]): xpos, ypos = xy @@ -1012,7 +1020,7 @@ def _barrier(self, node, node_data): color=self._style["lc"], zorder=PORDER_TEXT, ) - box = self._patches_mod.Rectangle( + box = glob_data["patches_mod"].Rectangle( xy=(xpos - (0.3 * WID), ypos - 0.5), width=0.6 * WID, height=1.0 + ypos_adj, @@ -1039,14 +1047,14 @@ def _barrier(self, node, node_data): zorder=PORDER_TEXT, ) - def _gate(self, node, node_data, xy=None): + def _gate(self, node, node_data, glob_data, xy=None): """Draw a 1-qubit gate""" if xy is None: xy = node_data[node]["q_xy"][0] xpos, ypos = xy wid = max(node_data[node]["width"], WID) - box = self._patches_mod.Rectangle( + box = glob_data["patches_mod"].Rectangle( xy=(xpos - 0.5 * wid, ypos - 0.5 * HIG), width=wid, height=HIG, @@ -1084,7 +1092,7 @@ def _gate(self, node, node_data, xy=None): zorder=PORDER_TEXT, ) - def _multiqubit_gate(self, node, node_data, xy=None): + def _multiqubit_gate(self, node, node_data, glob_data, xy=None): """Draw a gate covering more than one qubit""" op = node.op if xy is None: @@ -1097,7 +1105,7 @@ def _multiqubit_gate(self, node, node_data, xy=None): # RZZ Gate elif isinstance(op, RZZGate): - self._symmetric_gate(node, node_data, RZZGate) + self._symmetric_gate(node, node_data, RZZGate, glob_data) return c_xy = node_data[node]["c_xy"] @@ -1114,7 +1122,7 @@ def _multiqubit_gate(self, node, node_data, xy=None): qubit_span = abs(ypos) - abs(ypos_max) height = HIG + qubit_span - box = self._patches_mod.Rectangle( + box = glob_data["patches_mod"].Rectangle( xy=(xpos - 0.5 * wid, ypos - 0.5 * HIG), width=wid, height=height, @@ -1179,7 +1187,7 @@ def _multiqubit_gate(self, node, node_data, xy=None): zorder=PORDER_TEXT, ) - def _control_gate(self, node, node_data): + def _control_gate(self, node, node_data, glob_data): """Draw a controlled gate""" op = node.op xy = node_data[node]["q_xy"] @@ -1192,6 +1200,7 @@ def _control_gate(self, node, node_data): op.ctrl_state, num_ctrl_qubits, xy, + glob_data, ec=node_data[node]["ec"], tc=node_data[node]["tc"], text=node_data[node]["ctrl_text"], @@ -1200,24 +1209,24 @@ def _control_gate(self, node, node_data): self._line(qubit_b, qubit_t, lc=node_data[node]["lc"]) if isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, ZGate, RZZGate)): - self._symmetric_gate(node, node_data, base_type) + self._symmetric_gate(node, node_data, base_type, glob_data) elif num_qargs == 1 and isinstance(base_type, XGate): tgt_color = self._style["dispcol"]["target"] tgt = tgt_color if isinstance(tgt_color, str) else tgt_color[0] - self._x_tgt_qubit(xy[num_ctrl_qubits], ec=node_data[node]["ec"], ac=tgt) + self._x_tgt_qubit(xy[num_ctrl_qubits], glob_data, ec=node_data[node]["ec"], ac=tgt) elif num_qargs == 1: - self._gate(node, node_data, xy[num_ctrl_qubits:][0]) + self._gate(node, node_data, glob_data, xy[num_ctrl_qubits:][0]) elif isinstance(base_type, SwapGate): self._swap(xy[num_ctrl_qubits:], node, node_data, node_data[node]["lc"]) else: - self._multiqubit_gate(node, node_data, xy[num_ctrl_qubits:]) + self._multiqubit_gate(node, node_data, glob_data, xy[num_ctrl_qubits:]) def _set_ctrl_bits( - self, ctrl_state, num_ctrl_qubits, qbit, ec=None, tc=None, text="", qargs=None + self, ctrl_state, num_ctrl_qubits, qbit, glob_data, ec=None, tc=None, text="", qargs=None ): """Determine which qubits are controls and whether they are open or closed""" # place the control label at the top or bottom of controls @@ -1239,12 +1248,14 @@ def _set_ctrl_bits( text_top = True elif not top and qlist[i] == max_ctbit: text_top = False - self._ctrl_qubit(qbit[i], fc=fc_open_close, ec=ec, tc=tc, text=text, text_top=text_top) + self._ctrl_qubit( + qbit[i], glob_data, fc=fc_open_close, ec=ec, tc=tc, text=text, text_top=text_top + ) - def _ctrl_qubit(self, xy, fc=None, ec=None, tc=None, text="", text_top=None): + def _ctrl_qubit(self, xy, glob_data, fc=None, ec=None, tc=None, text="", text_top=None): """Draw a control circle and if top or bottom control, draw control label""" xpos, ypos = xy - box = self._patches_mod.Circle( + box = glob_data["patches_mod"].Circle( xy=(xpos, ypos), radius=WID * 0.15, fc=fc, @@ -1279,11 +1290,11 @@ def _ctrl_qubit(self, xy, fc=None, ec=None, tc=None, text="", text_top=None): zorder=PORDER_TEXT, ) - def _x_tgt_qubit(self, xy, ec=None, ac=None): + def _x_tgt_qubit(self, xy, glob_data, ec=None, ac=None): """Draw the cnot target symbol""" linewidth = self._lwidth2 xpos, ypos = xy - box = self._patches_mod.Circle( + box = glob_data["patches_mod"].Circle( xy=(xpos, ypos), radius=HIG * 0.35, fc=ec, @@ -1309,7 +1320,7 @@ def _x_tgt_qubit(self, xy, ec=None, ac=None): zorder=PORDER_GATE + 1, ) - def _symmetric_gate(self, node, node_data, base_type): + def _symmetric_gate(self, node, node_data, base_type, glob_data): """Draw symmetric gates for cz, cu1, cp, and rzz""" op = node.op xy = node_data[node]["q_xy"] @@ -1323,7 +1334,7 @@ def _symmetric_gate(self, node, node_data, base_type): # cz and mcz gates if not isinstance(op, ZGate) and isinstance(base_type, ZGate): num_ctrl_qubits = op.num_ctrl_qubits - self._ctrl_qubit(xy[-1], fc=ec, ec=ec, tc=tc) + self._ctrl_qubit(xy[-1], glob_data, fc=ec, ec=ec, tc=tc) self._line(qubit_b, qubit_t, lc=lc, zorder=PORDER_LINE + 1) # cu1, cp, rzz, and controlled rzz gates (sidetext gates) @@ -1331,9 +1342,9 @@ def _symmetric_gate(self, node, node_data, base_type): num_ctrl_qubits = 0 if isinstance(op, RZZGate) else op.num_ctrl_qubits gate_text = "P" if isinstance(base_type, PhaseGate) else node_data[node]["gate_text"] - self._ctrl_qubit(xy[num_ctrl_qubits], fc=ec, ec=ec, tc=tc) + self._ctrl_qubit(xy[num_ctrl_qubits], glob_data, fc=ec, ec=ec, tc=tc) if not isinstance(base_type, (U1Gate, PhaseGate)): - self._ctrl_qubit(xy[num_ctrl_qubits + 1], fc=ec, ec=ec, tc=tc) + self._ctrl_qubit(xy[num_ctrl_qubits + 1], glob_data, fc=ec, ec=ec, tc=tc) self._sidetext( node, From aa3c57f549ffe17e5be02e2f862c4f8e969f8897 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 31 May 2023 14:34:43 -0700 Subject: [PATCH 25/30] Minor fixes after pre merege --- qiskit/visualization/circuit/matplotlib.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 1113f43574a4..19991f5afdd6 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -283,9 +283,6 @@ def draw(self, filename=None, verbose=False): # and colors "fc", "ec", "lc", "sc", "gt", and "tc" node_data = {} - # glob_data contains global values used throughout, "n_lines", "x_offset", "next_x_index" - glob_data = {} - # dicts for the names and locations of register/bit labels qubits_dict = {} clbits_dict = {} @@ -691,7 +688,7 @@ def _get_coords( glob_data, flow_op, ) - for ii in q_indxs + for ii in q_indxs ] # clbit coordinates node_data[node]["c_xy"] = [ @@ -966,12 +963,11 @@ def _draw_ops( # draw barriers, snapshots, etc. elif getattr(op, "_directive", False): if self._plot_barriers: - self._barrier(node, node_data) + self._barrier(node, node_data, glob_data) # draw the box for control flow circuits elif isinstance(op, IfElseOp): self._flow_op_gate(node, node_data, glob_data) - self._barrier(node, node_data, glob_data) # draw single qubit gates elif len(node_data[node]["q_xy"]) == 1 and not node.cargs: @@ -1223,7 +1219,7 @@ def _barrier(self, node, node_data, glob_data): zorder=PORDER_TEXT, ) - def _gate(self, node, node_data, glob_data, xy=None): + def _gate(self, node, node_data, glob_data, xy=None): """Draw a 1-qubit gate""" if xy is None: xy = node_data[node]["q_xy"][0] @@ -1401,7 +1397,7 @@ def _flow_op_gate(self, node, node_data, glob_data): left_edge = end_x + 0.1 - glob_data["x_offset"] # FancyBbox allows rounded corners - box = self._patches_mod.FancyBboxPatch( + box = glob_data["patches_mod"].FancyBboxPatch( xy=(xpos - x_shift, ypos - 0.5 * HIG - y_shift), width=wid, height=height, @@ -1449,7 +1445,7 @@ def _flow_op_gate(self, node, node_data, glob_data): # To clean up box stuff in the bit name area, draw the box again using background # color in that area. if fold_level > 0: - box = self._patches_mod.FancyBboxPatch( + box = glob_data["patches_mod"].FancyBboxPatch( xy=(xpos - x_shift, ypos - 0.5 * HIG - y_shift), width=min(wid, wid - left_edge), height=height, From b872565aff3b48813da1bb4343b635292223369d Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 31 May 2023 15:18:03 -0700 Subject: [PATCH 26/30] Lint --- qiskit/visualization/circuit/matplotlib.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 19991f5afdd6..e47d35cd97a7 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -358,9 +358,7 @@ def draw(self, filename=None, verbose=False): # Once the scaling factor has been determined, the global phase, register names # and numbers, wires, and gates are drawn if self._global_phase: - plt_mod.text( - xl, yt, "Global Phase: %s" % pi_check(self._global_phase, output="mpl") - ) + plt_mod.text(xl, yt, "Global Phase: %s" % pi_check(self._global_phase, output="mpl")) self._draw_regs_wires(num_folds, xmax, max_x_index, qubits_dict, clbits_dict, glob_data) self._draw_ops( self._nodes, From 4d0e1193086f6d46d0e469d71de88d784a4a038a Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Thu, 1 Jun 2023 13:37:49 -0700 Subject: [PATCH 27/30] Add while --- qiskit/visualization/circuit/matplotlib.py | 50 +++++++++++----------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index e47d35cd97a7..30f5b28c3606 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -21,7 +21,7 @@ import numpy as np from qiskit.circuit import QuantumCircuit, Qubit, Clbit, ClassicalRegister -from qiskit.circuit import ControlledGate, Measure, IfElseOp +from qiskit.circuit import ControlledGate, Measure, ControlFlowOp, WhileLoopOp, IfElseOp from qiskit.circuit.library.standard_gates import ( SwapGate, RZZGate, @@ -386,7 +386,7 @@ def _load_flow_wire_maps(self, wire_map): the wire_map if not already there. """ for flow_drawer in self._flow_drawers.values(): - for i in range(0, 2): + for i in range(0, len(flow_drawer)): if flow_drawer[i] is None: continue inner_wire_map = { @@ -476,14 +476,14 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): ) gate_width = (raw_gate_width + 0.08) * 1.58 - # Check if an IfElseOp - node_data load for these gates is done here - elif isinstance(node.op, IfElseOp): + # Check if a ControlFlowOp - node_data load for these gates is done here + elif isinstance(node.op, ControlFlowOp): self._flow_drawers[node] = [] node_data[node]["width"] = [] node_data[node]["if_depth"] = 0 gate_width = 0.0 - # params[0] holds circuit for if, params[1] holds circuit for else + # params[0] holds circuit for if or while, params[1] holds circuit for else for k, circuit in enumerate(node.op.params): raw_gate_width = 0.0 if circuit is None: # No else @@ -491,13 +491,13 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): node_data[node]["width"].append(0.0) break - # Depth of nested if/else used for color of box + # Depth of nested ControlFlowOp used for color of box if self._flow_parent is not None: node_data[node]["if_depth"] = ( node_data[self._flow_parent]["if_depth"] + 1 ) # Get the layered node lists and instantiate a new drawer class for - # the circuit inside the if or else. + # the circuit inside the if, else, or while. qubits, clbits, nodes = _get_layered_instructions(circuit) flow_drawer = MatplotlibDrawer( qubits, @@ -514,7 +514,7 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): # flow_parent is the parent of the new class instance flow_drawer._flow_parent = node - # Recursively call _get_layer_widths for the circuit inside the if/else + # Recursively call _get_layer_widths for the circuit inside the ControlFlowOp flow_widths = flow_drawer._get_layer_widths(node_data, wire_map, glob_data) layer_widths.update(flow_widths) @@ -544,7 +544,7 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): box_width = max(gate_width, ctrl_width, param_width, WID) if box_width > widest_box: widest_box = box_width - if not isinstance(node.op, IfElseOp): + if not isinstance(node.op, ControlFlowOp): node_data[node]["width"] = max(raw_gate_width, raw_param_width) for node in layer: layer_widths[node][0] = int(widest_box) + 1 @@ -635,7 +635,7 @@ def _get_coords( clbits_dict, glob_data, flow_parent=None, - is_if=None, + is_if_while=None, ): """Load all the coordinate info needed to place the gates on the drawing.""" @@ -644,13 +644,13 @@ def _get_coords( curr_x_index = prev_x_index + 1 l_width = [] for node in layer: - # For gates inside if/else, set the x_index and increment by if width - # if it's an else + # For gates inside if, else, or while, set the x_index and if it's an else, + # increment by if width if flow_parent is not None: node_data[node]["x_index"] = ( node_data[flow_parent]["x_index"] + curr_x_index + 1 ) - if not is_if: + if not is_if_while: node_data[node]["x_index"] += int(node_data[flow_parent]["width"][0]) + 1 # get qubit indexes @@ -669,7 +669,7 @@ def _get_coords( else: c_indxs.append(wire_map[carg]) - flow_op = isinstance(node.op, IfElseOp) + flow_op = isinstance(node.op, ControlFlowOp) if flow_parent is not None: node_data[node]["inside_flow"] = True x_index = node_data[node]["x_index"] @@ -889,7 +889,7 @@ def _add_nodes_and_coords( ): """Add the nodes from ControlFlowOps and their coordinates to the main circuit""" for flow_drawer in self._flow_drawers.values(): - for i in range(0, 2): + for i in range(0, len(flow_drawer)): if flow_drawer[i] is None: # No else continue @@ -902,9 +902,9 @@ def _add_nodes_and_coords( clbits_dict, glob_data, flow_parent=flow_drawer[i]._flow_parent, - is_if=(i == 0), + is_if_while=(i == 0), ) - # Recurse for if/else ops inside the flow_drawer + # Recurse for ControlFlowOps inside the flow_drawer flow_drawer[i]._add_nodes_and_coords( nodes, node_data, wire_map, layer_widths, qubits_dict, clbits_dict, glob_data ) @@ -948,7 +948,7 @@ def _draw_ops( clbits_dict[ii]["y"], layer_widths[node][0], glob_data, - isinstance(op, IfElseOp), + isinstance(op, ControlFlowOp), ) for ii in clbits_dict ] @@ -964,7 +964,7 @@ def _draw_ops( self._barrier(node, node_data, glob_data) # draw the box for control flow circuits - elif isinstance(op, IfElseOp): + elif isinstance(op, ControlFlowOp): self._flow_op_gate(node, node_data, glob_data) # draw single qubit gates @@ -1092,8 +1092,9 @@ def _condition(self, node, node_data, wire_map, cond_xy, glob_data): qubit_b = min(node_data[node]["q_xy"], key=lambda xy: xy[1]) clbit_b = min(xy_plot, key=lambda xy: xy[1]) - # For IfElseOps, place the condition at almost the left edge of the box - if isinstance(node.op, IfElseOp): + # For IfElseOp or WhileLoopOp, place the condition + # at almost the left edge of the box + if isinstance(node.op, (IfElseOp, WhileLoopOp)): qubit_b = (qubit_b[0], qubit_b[1] - (0.5 * HIG + 0.14)) # display the label at the bottom of the lowest conditional and draw the double line @@ -1364,7 +1365,7 @@ def _flow_op_gate(self, node, node_data, glob_data): ypos_max = max(y[1] for y in xy) if_width = node_data[node]["width"][0] + WID - else_width = node_data[node]["width"][1] + else_width = 0.0 if len(node_data[node]["width"]) == 1 else node_data[node]["width"][1] wid = max(if_width, WID) if else_width > 0.0: wid += else_width + WID + 0.3 @@ -1407,11 +1408,12 @@ def _flow_op_gate(self, node, node_data, glob_data): ) self._ax.add_patch(box) # Don't draw text in the area of the bit names + flow_text = " If" if isinstance(node.op, IfElseOp) else "While" if xpos - x_shift > glob_data["x_offset"] + 0.1: self._ax.text( - xpos - x_shift + 0.22, + xpos - x_shift + 0.03, ypos_max + 0.2 - y_shift, - "If", + flow_text, ha="left", va="center", fontsize=self._style["fs"], From ef2202f25a275d0127640ab76b19221094a7809b Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Mon, 5 Jun 2023 15:04:02 -0700 Subject: [PATCH 28/30] Make IfElse While specific --- qiskit/visualization/circuit/matplotlib.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 30f5b28c3606..11de3250a9c5 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -477,7 +477,7 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): gate_width = (raw_gate_width + 0.08) * 1.58 # Check if a ControlFlowOp - node_data load for these gates is done here - elif isinstance(node.op, ControlFlowOp): + elif isinstance(node.op, (IfElseOp, WhileLoopOp)): self._flow_drawers[node] = [] node_data[node]["width"] = [] node_data[node]["if_depth"] = 0 @@ -544,7 +544,7 @@ def _get_layer_widths(self, node_data, wire_map, glob_data): box_width = max(gate_width, ctrl_width, param_width, WID) if box_width > widest_box: widest_box = box_width - if not isinstance(node.op, ControlFlowOp): + if not isinstance(node.op, (IfElseOp, WhileLoopOp)): node_data[node]["width"] = max(raw_gate_width, raw_param_width) for node in layer: layer_widths[node][0] = int(widest_box) + 1 @@ -669,7 +669,7 @@ def _get_coords( else: c_indxs.append(wire_map[carg]) - flow_op = isinstance(node.op, ControlFlowOp) + flow_op = isinstance(node.op, (IfElseOp, WhileLoopOp)) if flow_parent is not None: node_data[node]["inside_flow"] = True x_index = node_data[node]["x_index"] @@ -948,7 +948,7 @@ def _draw_ops( clbits_dict[ii]["y"], layer_widths[node][0], glob_data, - isinstance(op, ControlFlowOp), + isinstance(op, (IfElseOp, WhileLoopOp)), ) for ii in clbits_dict ] @@ -964,7 +964,7 @@ def _draw_ops( self._barrier(node, node_data, glob_data) # draw the box for control flow circuits - elif isinstance(op, ControlFlowOp): + elif isinstance(op, (IfElseOp, WhileLoopOp)): self._flow_op_gate(node, node_data, glob_data) # draw single qubit gates From 1d982f62d8915da749ded799ff8cea0c91f42ed8 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Mon, 5 Jun 2023 15:59:54 -0700 Subject: [PATCH 29/30] Lint --- qiskit/visualization/circuit/matplotlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 11de3250a9c5..845ffa62cc7e 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -21,7 +21,7 @@ import numpy as np from qiskit.circuit import QuantumCircuit, Qubit, Clbit, ClassicalRegister -from qiskit.circuit import ControlledGate, Measure, ControlFlowOp, WhileLoopOp, IfElseOp +from qiskit.circuit import ControlledGate, Measure, WhileLoopOp, IfElseOp from qiskit.circuit.library.standard_gates import ( SwapGate, RZZGate, From 97487be864fb3275adf7f05a79c02e2ab443d383 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Tue, 13 Jun 2023 08:30:15 -0700 Subject: [PATCH 30/30] Deleted release note --- .../display-circuits-inside-if-else-9b1a3ca2fcce5020.yaml | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 releasenotes/notes/display-circuits-inside-if-else-9b1a3ca2fcce5020.yaml diff --git a/releasenotes/notes/display-circuits-inside-if-else-9b1a3ca2fcce5020.yaml b/releasenotes/notes/display-circuits-inside-if-else-9b1a3ca2fcce5020.yaml deleted file mode 100644 index b3d861af3be6..000000000000 --- a/releasenotes/notes/display-circuits-inside-if-else-9b1a3ca2fcce5020.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -features: - - | - In :class:`~qiskit.visualization.circuit.MatplotlibDrawer`, operations - built from :class:`~qiskit.circuit.control_flow.IfElseOp`, whether - directly instantiated or built using :meth:`~qiskit.circuit.QuantumCircuit.if_test`, - will now fully display the circuits inside the ``if`` and ``else`` wrapped - with boxes to delineate the circuits.