• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / qiskit / 22309958047

23 Feb 2026 02:17PM UTC coverage: 87.87% (+0.03%) from 87.843%
22309958047

push

github

web-flow
Pivot to using ruff for all linting (#15603)

* Pivot to using ruff for all linting

This commit switches us to fully using ruff for all the linting in CI
and locally. The primary motivation for this change is to improve
productivity because ruff is signficantly faster. Pylint is incredibly
slow in general, but compared to ruff especially so. For example, on my
laptop ruff takes 0.04 seconds to run on the qiskit/ subdirectory (after
clearing the cache, with the cache populated it takes 0.025 sec) of the
source tree while running pylint on the same path took 70 sec. This leads
to people skipping lint locally and causes churn in CI becaus.

We had started to experimenting with ruff in the past and
used it for a some small set of rules but were still using pylint for
the bulk of the linting in the repo. The concern at the time was a loss
of lint coverage or a lot of code churn caused by migrating to a new tool.
Specifically pylint does more type inference and checking that ruff
doesn't. However since we started the experiment one major change in
qiskit is how much work is happening in rust now vs Python. At this
point any loss in lint coverage is unlikely to cause a significant
problem in practice and we'll make real productivity gains by making
this change.

* Remove out of date comment

* Update makefile

* Enable more rules

* Revert lambda autofixes

* Fix new rules

* Add bandit rules

* Enable Ruff native rules

* Remove pylint disable comments

* Enable docstring rules

This commit adds the docstring rules on the repo. This involves a few
more changes than previous commits because there are a lot of formatting
consistency rules that needed to be auto-applied. The checking also
found several instances where there was missing documentation that
should have been included.

* Add categories from old ruff config

* Fix from rebase

* Add flake8-raise rule

* Add flake8-pie rules

* Add implicit namespace rules

* Fix deprecation decorator error on impo... (continued)

624 of 677 new or added lines in 198 files covered. (92.17%)

16 existing lines in 9 files now uncovered.

100211 of 114044 relevant lines covered (87.87%)

1151735.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

48.03
/qiskit/visualization/circuit/matplotlib.py
1
# This code is part of Qiskit.
2
#
3
# (C) Copyright IBM 2017, 2018.
4
#
5
# This code is licensed under the Apache License, Version 2.0. You may
6
# obtain a copy of this license in the LICENSE.txt file in the root directory
7
# of this source tree or at https://www.apache.org/licenses/LICENSE-2.0.
8
#
9
# Any modifications or derivative works of this code must retain this
10
# copyright notice, and modified files need to carry a notice indicating
11
# that they have been altered from the originals.
12

13

14
"""mpl circuit visualization backend."""
15

16
import collections
1✔
17
import itertools
1✔
18
import re
1✔
19
from io import StringIO
1✔
20

21
import numpy as np
1✔
22

23
from qiskit.circuit import (
1✔
24
    QuantumCircuit,
25
    Qubit,
26
    Clbit,
27
    ClassicalRegister,
28
    ControlledGate,
29
    Measure,
30
    ControlFlowOp,
31
    BoxOp,
32
    WhileLoopOp,
33
    IfElseOp,
34
    ForLoopOp,
35
    SwitchCaseOp,
36
    CircuitError,
37
)
38
from qiskit.circuit.controlflow import condition_resources
1✔
39
from qiskit.circuit.classical import expr
1✔
40
from qiskit.circuit.annotated_operation import _canonicalize_modifiers, ControlModifier
1✔
41
from qiskit.circuit.library import Initialize
1✔
42
from qiskit.circuit.library.standard_gates import (
1✔
43
    SwapGate,
44
    RZZGate,
45
    U1Gate,
46
    PhaseGate,
47
    XGate,
48
    ZGate,
49
)
50
from qiskit.qasm3 import ast
1✔
51
from qiskit.qasm3.exporter import _ExprBuilder
1✔
52
from qiskit.qasm3.printer import BasicPrinter
1✔
53

54
from qiskit.circuit.tools.pi_check import pi_check
1✔
55
from qiskit.utils import optionals as _optionals
1✔
56

57
from qiskit.visualization.style import load_style
1✔
58
from qiskit.visualization.circuit.qcstyle import MPLDefaultStyle, MPLStyleDict
1✔
59
from ._utils import (
1✔
60
    get_gate_ctrl_text,
61
    get_param_str,
62
    get_wire_map,
63
    get_bit_register,
64
    get_bit_reg_index,
65
    get_wire_label,
66
    get_condition_label_val,
67
    _get_layered_instructions,
68
)
69
from ..utils import matplotlib_close_if_inline
1✔
70

71
# Default gate width and height
72
WID = 0.65
1✔
73
HIG = 0.65
1✔
74

75
# Z dimension order for different drawing types
76
PORDER_REGLINE = 1
1✔
77
PORDER_FLOW = 3
1✔
78
PORDER_MASK = 4
1✔
79
PORDER_LINE = 6
1✔
80
PORDER_LINE_PLUS = 7
1✔
81
PORDER_BARRIER = 8
1✔
82
PORDER_GATE = 10
1✔
83
PORDER_GATE_PLUS = 11
1✔
84
PORDER_TEXT = 13
1✔
85

86
INFINITE_FOLD = 10000000
1✔
87

88

89
@_optionals.HAS_MATPLOTLIB.require_in_instance
1✔
90
@_optionals.HAS_PYLATEX.require_in_instance
1✔
91
class MatplotlibDrawer:
1✔
92
    """Matplotlib drawer class called from circuit_drawer"""
93

94
    _mathmode_regex = re.compile(r"(?<!\\)\$(.*)(?<!\\)\$")
1✔
95

96
    def __init__(
1✔
97
        self,
98
        qubits,
99
        clbits,
100
        nodes,
101
        circuit,
102
        scale=None,
103
        style=None,
104
        reverse_bits=False,
105
        plot_barriers=True,
106
        fold=25,
107
        ax=None,
108
        initial_state=False,
109
        cregbundle=None,
110
        with_layout=False,
111
        expr_len=30,
112
        measure_arrows=None,
113
    ):
114
        self._circuit = circuit
1✔
115
        self._qubits = qubits
1✔
116
        self._clbits = clbits
1✔
117
        self._nodes = nodes
1✔
118
        self._scale = 1.0 if scale is None else scale
1✔
119

120
        self._style = style
1✔
121

122
        self._plot_barriers = plot_barriers
1✔
123
        self._reverse_bits = reverse_bits
1✔
124
        if with_layout:
1✔
125
            if self._circuit._layout:
1✔
126
                self._layout = self._circuit._layout.initial_layout
×
127
            else:
128
                self._layout = None
1✔
129
        else:
130
            self._layout = None
×
131

132
        self._fold = fold
1✔
133
        if self._fold < 2:
1✔
134
            self._fold = -1
×
135

136
        self._ax = ax
1✔
137

138
        self._initial_state = initial_state
1✔
139
        self._global_phase = self._circuit.global_phase
1✔
140
        self._expr_len = expr_len
1✔
141
        self._cregbundle = cregbundle
1✔
142
        self._measure_arrows = measure_arrows
1✔
143

144
        self._lwidth1 = 1.0
1✔
145
        self._lwidth15 = 1.5
1✔
146
        self._lwidth2 = 2.0
1✔
147
        self._lwidth3 = 3.0
1✔
148
        self._lwidth4 = 4.0
1✔
149

150
        # Class instances of MatplotlibDrawer for each flow gate - If/Else, For, While, Switch
151
        self._flow_drawers = {}
1✔
152

153
        # Set if gate is inside a flow gate
154
        self._flow_parent = None
1✔
155
        self._flow_wire_map = {}
1✔
156

157
        # _char_list for finding text_width of names, labels, and params
158
        self._char_list = {
1✔
159
            " ": (0.0958, 0.0583),
160
            "!": (0.1208, 0.0729),
161
            '"': (0.1396, 0.0875),
162
            "#": (0.2521, 0.1562),
163
            "$": (0.1917, 0.1167),
164
            "%": (0.2854, 0.1771),
165
            "&": (0.2333, 0.1458),
166
            "'": (0.0833, 0.0521),
167
            "(": (0.1167, 0.0729),
168
            ")": (0.1167, 0.0729),
169
            "*": (0.15, 0.0938),
170
            "+": (0.25, 0.1562),
171
            ",": (0.0958, 0.0583),
172
            "-": (0.1083, 0.0667),
173
            ".": (0.0958, 0.0604),
174
            "/": (0.1021, 0.0625),
175
            "0": (0.1875, 0.1167),
176
            "1": (0.1896, 0.1167),
177
            "2": (0.1917, 0.1188),
178
            "3": (0.1917, 0.1167),
179
            "4": (0.1917, 0.1188),
180
            "5": (0.1917, 0.1167),
181
            "6": (0.1896, 0.1167),
182
            "7": (0.1917, 0.1188),
183
            "8": (0.1896, 0.1188),
184
            "9": (0.1917, 0.1188),
185
            ":": (0.1021, 0.0604),
186
            ";": (0.1021, 0.0604),
187
            "<": (0.25, 0.1542),
188
            "=": (0.25, 0.1562),
189
            ">": (0.25, 0.1542),
190
            "?": (0.1583, 0.0979),
191
            "@": (0.2979, 0.1854),
192
            "A": (0.2062, 0.1271),
193
            "B": (0.2042, 0.1271),
194
            "C": (0.2083, 0.1292),
195
            "D": (0.2312, 0.1417),
196
            "E": (0.1875, 0.1167),
197
            "F": (0.1708, 0.1062),
198
            "G": (0.2312, 0.1438),
199
            "H": (0.225, 0.1396),
200
            "I": (0.0875, 0.0542),
201
            "J": (0.0875, 0.0542),
202
            "K": (0.1958, 0.1208),
203
            "L": (0.1667, 0.1042),
204
            "M": (0.2583, 0.1604),
205
            "N": (0.225, 0.1396),
206
            "O": (0.2354, 0.1458),
207
            "P": (0.1812, 0.1125),
208
            "Q": (0.2354, 0.1458),
209
            "R": (0.2083, 0.1292),
210
            "S": (0.1896, 0.1188),
211
            "T": (0.1854, 0.1125),
212
            "U": (0.2208, 0.1354),
213
            "V": (0.2062, 0.1271),
214
            "W": (0.2958, 0.1833),
215
            "X": (0.2062, 0.1271),
216
            "Y": (0.1833, 0.1125),
217
            "Z": (0.2042, 0.1271),
218
            "[": (0.1167, 0.075),
219
            "\\": (0.1021, 0.0625),
220
            "]": (0.1167, 0.0729),
221
            "^": (0.2521, 0.1562),
222
            "_": (0.1521, 0.0938),
223
            "`": (0.15, 0.0938),
224
            "a": (0.1854, 0.1146),
225
            "b": (0.1917, 0.1167),
226
            "c": (0.1646, 0.1021),
227
            "d": (0.1896, 0.1188),
228
            "e": (0.1854, 0.1146),
229
            "f": (0.1042, 0.0667),
230
            "g": (0.1896, 0.1188),
231
            "h": (0.1896, 0.1188),
232
            "i": (0.0854, 0.0521),
233
            "j": (0.0854, 0.0521),
234
            "k": (0.1729, 0.1083),
235
            "l": (0.0854, 0.0521),
236
            "m": (0.2917, 0.1812),
237
            "n": (0.1896, 0.1188),
238
            "o": (0.1833, 0.1125),
239
            "p": (0.1917, 0.1167),
240
            "q": (0.1896, 0.1188),
241
            "r": (0.125, 0.0771),
242
            "s": (0.1562, 0.0958),
243
            "t": (0.1167, 0.0729),
244
            "u": (0.1896, 0.1188),
245
            "v": (0.1771, 0.1104),
246
            "w": (0.2458, 0.1521),
247
            "x": (0.1771, 0.1104),
248
            "y": (0.1771, 0.1104),
249
            "z": (0.1562, 0.0979),
250
            "{": (0.1917, 0.1188),
251
            "|": (0.1, 0.0604),
252
            "}": (0.1896, 0.1188),
253
        }
254

255
    def draw(self, filename=None, verbose=False):
1✔
256
        """Main entry point to 'matplotlib' ('mpl') drawer. Called from
257
        ``visualization.circuit_drawer`` and from ``QuantumCircuit.draw`` through circuit_drawer.
258
        """
259

260
        # Import matplotlib and load all the figure, window, and style info
261
        from matplotlib import patches
1✔
262
        from matplotlib import pyplot as plt
1✔
263

264
        # glob_data contains global values used throughout, "n_lines", "x_offset", "next_x_index",
265
        # "patches_mod", "subfont_factor"
266
        glob_data = {}
1✔
267

268
        glob_data["patches_mod"] = patches
1✔
269
        plt_mod = plt
1✔
270

271
        self._style, def_font_ratio = load_style(
1✔
272
            self._style,
273
            style_dict=MPLStyleDict,
274
            default_style=MPLDefaultStyle(),
275
            user_config_opt="circuit_mpl_style",
276
            user_config_path_opt="circuit_mpl_style_path",
277
        )
278

279
        # If font/subfont ratio changes from default, have to scale width calculations for
280
        # subfont. Font change is auto scaled in the mpl_figure.set_size_inches call in draw()
281
        glob_data["subfont_factor"] = self._style["sfs"] * def_font_ratio / self._style["fs"]
1✔
282

283
        # if no user ax, setup default figure. Else use the user figure.
284
        if self._ax is None:
1✔
285
            is_user_ax = False
1✔
286
            mpl_figure = plt.figure()
1✔
287
            mpl_figure.patch.set_facecolor(color=self._style["bg"])
1✔
288
            self._ax = mpl_figure.add_subplot(111)
1✔
289
        else:
290
            is_user_ax = True
×
291
            mpl_figure = self._ax.get_figure()
×
292
        self._ax.axis("off")
1✔
293
        self._ax.set_aspect("equal")
1✔
294
        self._ax.tick_params(labelbottom=False, labeltop=False, labelleft=False, labelright=False)
1✔
295

296
        # All information for the drawing is first loaded into node_data for the gates and into
297
        # qubits_dict, clbits_dict, and wire_map for the qubits, clbits, and wires,
298
        # followed by the coordinates for each gate.
299

300
        # load the wire map
301
        wire_map = get_wire_map(self._circuit, self._qubits + self._clbits, self._cregbundle)
1✔
302

303
        # node_data per node filled with class NodeData attributes
304
        node_data = {}
1✔
305

306
        # dicts for the names and locations of register/bit labels
307
        qubits_dict = {}
1✔
308
        clbits_dict = {}
1✔
309

310
        # load the _qubit_dict and _clbit_dict with register info
311
        self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict, glob_data)
1✔
312

313
        # get layer widths - flow gates are initialized here
314
        layer_widths = self._get_layer_widths(node_data, wire_map, self._circuit, glob_data)
1✔
315

316
        # load the coordinates for each top level gate and compute number of folds.
317
        # coordinates for flow gates are loaded before draw_ops
318
        max_x_index = self._get_coords(
1✔
319
            node_data, wire_map, self._circuit, layer_widths, qubits_dict, clbits_dict, glob_data
320
        )
321
        num_folds = max(0, max_x_index - 1) // self._fold if self._fold > 0 else 0
1✔
322

323
        # The window size limits are computed, followed by one of the four possible ways
324
        # of scaling the drawing.
325

326
        # compute the window size
327
        if max_x_index > self._fold > 0:
1✔
328
            xmax = self._fold + glob_data["x_offset"] + 0.1
×
329
            ymax = (num_folds + 1) * (glob_data["n_lines"] + 1) - 1
×
330
        else:
331
            x_incr = 0.4 if not self._nodes else 0.9
1✔
332
            xmax = max_x_index + 1 + glob_data["x_offset"] - x_incr
1✔
333
            ymax = glob_data["n_lines"]
1✔
334

335
        xl = -self._style["margin"][0]
1✔
336
        xr = xmax + self._style["margin"][1]
1✔
337
        yb = -ymax - self._style["margin"][2] + 0.5
1✔
338
        yt = self._style["margin"][3] + 0.5
1✔
339
        self._ax.set_xlim(xl, xr)
1✔
340
        self._ax.set_ylim(yb, yt)
1✔
341

342
        # update figure size and, for backward compatibility,
343
        # need to scale by a default value equal to (self._style["fs"] * 3.01 / 72 / 0.65)
344
        base_fig_w = (xr - xl) * 0.8361111
1✔
345
        base_fig_h = (yt - yb) * 0.8361111
1✔
346
        scale = self._scale
1✔
347

348
        # if user passes in an ax, this size takes priority over any other settings
349
        if is_user_ax:
1✔
350
            # from stackoverflow #19306510, get the bbox size for the ax and then reset scale
351
            bbox = self._ax.get_window_extent().transformed(mpl_figure.dpi_scale_trans.inverted())
×
352
            scale = bbox.width / base_fig_w / 0.8361111
×
353

354
        # if scale not 1.0, use this scale factor
355
        elif self._scale != 1.0:
1✔
356
            mpl_figure.set_size_inches(base_fig_w * self._scale, base_fig_h * self._scale)
×
357

358
        # if "figwidth" style param set, use this to scale
359
        elif self._style["figwidth"] > 0.0:
1✔
360
            # in order to get actual inches, need to scale by factor
361
            adj_fig_w = self._style["figwidth"] * 1.282736
×
362
            mpl_figure.set_size_inches(adj_fig_w, adj_fig_w * base_fig_h / base_fig_w)
×
363
            scale = adj_fig_w / base_fig_w
×
364

365
        # otherwise, display default size
366
        else:
367
            mpl_figure.set_size_inches(base_fig_w, base_fig_h)
1✔
368

369
        # drawing will scale with 'set_size_inches', but fonts and linewidths do not
370
        if scale != 1.0:
1✔
371
            self._style["fs"] *= scale
×
372
            self._style["sfs"] *= scale
×
373
            self._lwidth1 = 1.0 * scale
×
374
            self._lwidth15 = 1.5 * scale
×
375
            self._lwidth2 = 2.0 * scale
×
376
            self._lwidth3 = 3.0 * scale
×
377
            self._lwidth4 = 4.0 * scale
×
378

379
        # Once the scaling factor has been determined, the global phase, register names
380
        # and numbers, wires, and gates are drawn
381
        if self._global_phase:
1✔
382
            plt_mod.text(xl, yt, f"Global Phase: {pi_check(self._global_phase, output='mpl')}")
×
383
        self._draw_regs_wires(num_folds, xmax, max_x_index, qubits_dict, clbits_dict, glob_data)
1✔
384
        self._draw_ops(
1✔
385
            self._nodes,
386
            node_data,
387
            wire_map,
388
            self._circuit,
389
            layer_widths,
390
            qubits_dict,
391
            clbits_dict,
392
            glob_data,
393
            verbose,
394
        )
395
        if filename:
1✔
396
            mpl_figure.savefig(
×
397
                filename,
398
                dpi=self._style["dpi"],
399
                bbox_inches="tight",
400
                facecolor=mpl_figure.get_facecolor(),
401
            )
402
        if not is_user_ax:
1✔
403
            matplotlib_close_if_inline(mpl_figure)
1✔
404
            return mpl_figure
1✔
405

406
    def _get_layer_widths(self, node_data, wire_map, outer_circuit, glob_data):
1✔
407
        """Compute the layer_widths for the layers"""
408

409
        layer_widths = {}
1✔
410
        for layer_num, layer in enumerate(self._nodes):
1✔
411
            widest_box = WID
1✔
412
            for i, node in enumerate(layer):
1✔
413
                # Put the layer_num in the first node in the layer and put -1 in the rest
414
                # so that layer widths are not counted more than once
415
                if i != 0:
1✔
416
                    layer_num = -1
×
417
                layer_widths[node] = [1, layer_num, self._flow_parent]
1✔
418

419
                op = node.op
1✔
420
                node_data[node] = NodeData()
1✔
421
                node_data[node].width = WID
1✔
422
                num_ctrl_qubits = getattr(op, "num_ctrl_qubits", 0)
1✔
423
                if (
1✔
424
                    getattr(op, "_directive", False) and (not op.label or not self._plot_barriers)
425
                ) or (self._measure_arrows and isinstance(op, Measure)):
426
                    node_data[node].raw_gate_text = op.name
×
427
                    continue
×
428

429
                base_type = getattr(op, "base_gate", None)
1✔
430
                gate_text, ctrl_text, raw_gate_text = get_gate_ctrl_text(
1✔
431
                    op, "mpl", style=self._style
432
                )
433
                node_data[node].gate_text = gate_text
1✔
434
                node_data[node].ctrl_text = ctrl_text
1✔
435
                # Measure doesn't use raw_gate_text since it displays a dial
436
                if not isinstance(op, Measure):
1✔
437
                    node_data[node].raw_gate_text = raw_gate_text
1✔
438
                node_data[node].param_text = ""
1✔
439

440
                # if single qubit, no params, and no labels, layer_width is 1
441
                if (
1✔
442
                    (len(node.qargs) - num_ctrl_qubits) == 1
443
                    and len(gate_text) < 3
444
                    and len(getattr(op, "params", [])) == 0
445
                    and ctrl_text is None
446
                ):
447
                    continue
1✔
448

449
                if isinstance(op, SwapGate) or isinstance(base_type, SwapGate):
1✔
450
                    continue
×
451

452
                # small increments at end of the 3 _get_text_width calls are for small
453
                # spacing adjustments between gates
454
                ctrl_width = (
1✔
455
                    self._get_text_width(ctrl_text, glob_data, fontsize=self._style["sfs"]) - 0.05
456
                )
457
                # get param_width, but 0 for gates with array params or circuits in params
458
                if (
1✔
459
                    len(getattr(op, "params", [])) > 0
460
                    and not any(isinstance(param, np.ndarray) for param in op.params)
461
                    and not any(isinstance(param, QuantumCircuit) for param in op.params)
462
                ):
463
                    param_text = get_param_str(op, "mpl", ndigits=3)
×
464
                    if isinstance(op, Initialize):
×
465
                        param_text = f"$[{param_text.replace('$', '')}]$"
×
466
                    node_data[node].param_text = param_text
×
467
                    raw_param_width = self._get_text_width(
×
468
                        param_text, glob_data, fontsize=self._style["sfs"], param=True
469
                    )
470
                    param_width = raw_param_width + 0.08
×
471
                else:
472
                    param_width = raw_param_width = 0.0
1✔
473

474
                # get gate_width for sidetext symmetric gates
475
                if isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, RZZGate)):
1✔
476
                    if isinstance(base_type, PhaseGate):
×
477
                        gate_text = "P"
×
478
                    raw_gate_width = (
×
479
                        self._get_text_width(
480
                            gate_text + " ()", glob_data, fontsize=self._style["sfs"]
481
                        )
482
                        + raw_param_width
483
                    )
484
                    gate_width = (raw_gate_width + 0.08) * 1.58
×
485

486
                # Check if a ControlFlowOp - node_data load for these gates is done here
487
                elif isinstance(node.op, ControlFlowOp):
1✔
488
                    self._flow_drawers[node] = []
×
489
                    node_data[node].width = []
×
490
                    node_data[node].nest_depth = 0
×
491
                    gate_width = 0.0
×
492
                    expr_width = 0.0
×
493

494
                    if (isinstance(op, SwitchCaseOp) and isinstance(op.target, expr.Expr)) or (
×
495
                        getattr(op, "condition", None) and isinstance(op.condition, expr.Expr)
496
                    ):
497

498
                        def lookup_var(var):
×
499
                            """Look up a classical-expression variable or register/bit in our
500
                            internal symbol table, and return an OQ3-like identifier."""
501
                            # We don't attempt to disambiguate anything like register/var naming
502
                            # collisions; we already don't really show classical variables.
503
                            if isinstance(var, expr.Var):
×
504
                                return ast.Identifier(var.name)
×
505
                            if isinstance(var, ClassicalRegister):
×
506
                                return ast.Identifier(var.name)
×
507
                            # Single clbit.  This is not actually the correct way to lookup a bit on
508
                            # the circuit (it doesn't handle bit bindings fully), but the mpl
509
                            # drawer doesn't completely track inner-outer _bit_ bindings, only
510
                            # inner-indices, so we can't fully recover the information losslessly.
511
                            # Since most control-flow uses the control-flow builders, we should
512
                            # decay to something usable most of the time.
513
                            try:
×
514
                                register, bit_index, reg_index = get_bit_reg_index(
×
515
                                    outer_circuit, var
516
                                )
517
                            except CircuitError:
×
518
                                # We failed to find the bit due to binding problems - fall back to
519
                                # something that's probably wrong, but at least disambiguating.
520
                                return ast.Identifier(f"bit{wire_map[var]}")
×
521
                            if register is None:
×
522
                                return ast.Identifier(f"bit{bit_index}")
×
523
                            return ast.SubscriptedIdentifier(
×
524
                                register.name, ast.IntegerLiteral(reg_index)
525
                            )
526

527
                        condition = op.target if isinstance(op, SwitchCaseOp) else op.condition
×
528
                        stream = StringIO()
×
529
                        BasicPrinter(stream, indent="  ").visit(
×
530
                            condition.accept(_ExprBuilder(lookup_var))
531
                        )
532
                        expr_text = stream.getvalue()
×
533
                        # Truncate expr_text so that first gate is no more than about 3 x_index's over
534
                        if len(expr_text) > self._expr_len:
×
535
                            expr_text = expr_text[: self._expr_len] + "..."
×
536
                        node_data[node].expr_text = expr_text
×
537

538
                        expr_width = self._get_text_width(
×
539
                            node_data[node].expr_text, glob_data, fontsize=self._style["sfs"]
540
                        )
541
                        node_data[node].expr_width = int(expr_width)
×
542

543
                    # Get the list of circuits to iterate over from the blocks
544
                    circuit_list = list(node.op.blocks)
×
545

546
                    # params is [indexset, loop_param, circuit] for for_loop,
547
                    # op.cases_specifier() returns jump tuple and circuit for switch/case
548
                    if isinstance(op, ForLoopOp):
×
549
                        node_data[node].indexset = op.params[0]
×
550
                    elif isinstance(op, SwitchCaseOp):
×
551
                        node_data[node].jump_values = []
×
552
                        cases = list(op.cases_specifier())
×
553

554
                        # Create an empty circuit at the head of the circuit_list if a Switch box
555
                        circuit_list.insert(0, cases[0][1].copy_empty_like())
×
556
                        for jump_values, _ in cases:
×
557
                            node_data[node].jump_values.append(jump_values)
×
558

559
                    # Now process the circuits inside the ControlFlowOps
560
                    for circ_num, circuit in enumerate(circuit_list):
×
561
                        # Only add expr_width for if, while, and switch
562
                        raw_gate_width = expr_width if circ_num == 0 else 0.0
×
563

564
                        # Depth of nested ControlFlowOp used for color of box
565
                        if self._flow_parent is not None:
×
566
                            node_data[node].nest_depth = node_data[self._flow_parent].nest_depth + 1
×
567

568
                        # Build the wire_map to be used by this flow op
569
                        flow_wire_map = wire_map.copy()
×
570
                        flow_wire_map.update(
×
571
                            {
572
                                inner: wire_map[outer]
573
                                for outer, inner in zip(node.qargs, circuit.qubits)
574
                            }
575
                        )
576
                        for outer, inner in zip(node.cargs, circuit.clbits):
×
577
                            if self._cregbundle and (
×
578
                                (in_reg := get_bit_register(outer_circuit, inner)) is not None
579
                            ):
580
                                out_reg = get_bit_register(outer_circuit, outer)
×
581
                                flow_wire_map.update({in_reg: wire_map[out_reg]})
×
582
                            else:
583
                                flow_wire_map.update({inner: wire_map[outer]})
×
584

585
                        # Get the layered node lists and instantiate a new drawer class for
586
                        # the circuit inside the ControlFlowOp.
587
                        qubits, clbits, flow_nodes = _get_layered_instructions(
×
588
                            circuit, wire_map=flow_wire_map, measure_arrows=self._measure_arrows
589
                        )
590
                        flow_drawer = MatplotlibDrawer(
×
591
                            qubits,
592
                            clbits,
593
                            flow_nodes,
594
                            circuit,
595
                            style=self._style,
596
                            plot_barriers=self._plot_barriers,
597
                            fold=self._fold,
598
                            cregbundle=self._cregbundle,
599
                        )
600

601
                        # flow_parent is the parent of the new class instance
602
                        flow_drawer._flow_parent = node
×
603
                        flow_drawer._flow_wire_map = flow_wire_map
×
604
                        self._flow_drawers[node].append(flow_drawer)
×
605

606
                        # Recursively call _get_layer_widths for the circuit inside the ControlFlowOp
607
                        flow_widths = flow_drawer._get_layer_widths(
×
608
                            node_data, flow_wire_map, outer_circuit, glob_data
609
                        )
610
                        layer_widths.update(flow_widths)
×
611

612
                        for flow_layer in flow_nodes:
×
613
                            for flow_node in flow_layer:
×
614
                                node_data[flow_node].circ_num = circ_num
×
615

616
                        # Add up the width values of the same flow_parent that are not -1
617
                        # to get the raw_gate_width
618
                        for width, layer_num, flow_parent in flow_widths.values():
×
619
                            if layer_num != -1 and flow_parent == flow_drawer._flow_parent:
×
620
                                raw_gate_width += width
×
621
                                # This is necessary to prevent 1 being added to the width of a
622
                                # BoxOp in layer_widths at the end of this method
623
                                if isinstance(node.op, BoxOp):
×
624
                                    raw_gate_width -= 0.001
×
625

626
                        # Need extra incr of 1.0 for else and case boxes
627
                        gate_width += raw_gate_width + (1.0 if circ_num > 0 else 0.0)
×
628

629
                        # Minor adjustment so else and case section gates align with indexes
630
                        if circ_num > 0:
×
631
                            raw_gate_width += 0.045
×
632

633
                        # If expr_width has a value, remove the decimal portion from raw_gate_widthl
634
                        if not isinstance(op, ForLoopOp) and circ_num == 0:
×
635
                            node_data[node].width.append(raw_gate_width - (expr_width % 1))
×
636
                        else:
637
                            node_data[node].width.append(raw_gate_width)
×
638

639
                # If measure_arrows is False, this section gets the layer width for a measure
640
                # based on the width of register_bit and puts it into the param_width. If the
641
                # register_bit is small enough, the gate will just use the WID width.
642
                elif not self._measure_arrows and isinstance(op, Measure):
1✔
643
                    register, _, reg_index = get_bit_reg_index(outer_circuit, node.cargs[0])
×
644
                    if register is not None:
×
645
                        param_text = f"{register.name}_{reg_index}"
×
646
                    else:
647
                        param_text = f"{reg_index}"
×
648
                    raw_param_width = self._get_text_width(
×
649
                        param_text, glob_data, fontsize=self._style["sfs"], param=True
650
                    )
651
                    param_width = raw_param_width
×
652
                    raw_gate_width = gate_width = ctrl_width = 0.0
×
653

654
                # Otherwise, standard gate or multiqubit gate
655
                else:
656
                    raw_gate_width = self._get_text_width(
1✔
657
                        gate_text, glob_data, fontsize=self._style["fs"]
658
                    )
659
                    gate_width = raw_gate_width + 0.10
1✔
660
                    # add .21 for the qubit numbers on the left of the multibit gates
661
                    if len(node.qargs) - num_ctrl_qubits > 1:
1✔
662
                        gate_width += 0.21
×
663

664
                box_width = max(gate_width, ctrl_width, param_width, WID)
1✔
665
                widest_box = max(widest_box, box_width)
1✔
666
                if not isinstance(node.op, ControlFlowOp):
1✔
667
                    node_data[node].width = max(raw_gate_width, raw_param_width)
1✔
668
            for node in layer:
1✔
669
                layer_widths[node][0] = int(widest_box) + 1
1✔
670

671
        return layer_widths
1✔
672

673
    def _set_bit_reg_info(self, wire_map, qubits_dict, clbits_dict, glob_data):
1✔
674
        """Get all the info for drawing bit/reg names and numbers"""
675

676
        longest_wire_label_width = 0
1✔
677
        glob_data["n_lines"] = 0
1✔
678
        initial_qbit = r" $|0\rangle$" if self._initial_state else ""
1✔
679
        initial_cbit = " 0" if self._initial_state else ""
1✔
680

681
        idx = 0
1✔
682
        pos = y_off = -len(self._qubits) + 1
1✔
683
        for ii, wire in enumerate(wire_map):
1✔
684
            # if it's a creg, register is the key and just load the index
685
            if isinstance(wire, ClassicalRegister):
1✔
686
                # If wire came from ControlFlowOp and not in clbits, don't draw it
687
                if wire[0] not in self._clbits:
×
688
                    continue
×
689
                register = wire
×
690
                index = wire_map[wire]
×
691

692
            # otherwise, get the register from find_bit and use bit_index if
693
            # it's a bit, or the index of the bit in the register if it's a reg
694
            else:
695
                # If wire came from ControlFlowOp and not in qubits or clbits, don't draw it
696
                if wire not in self._qubits + self._clbits:
1✔
697
                    continue
×
698
                register, bit_index, reg_index = get_bit_reg_index(self._circuit, wire)
1✔
699
                index = bit_index if register is None else reg_index
1✔
700

701
            wire_label = get_wire_label(
1✔
702
                "mpl", register, index, layout=self._layout, cregbundle=self._cregbundle
703
            )
704
            initial_bit = initial_qbit if isinstance(wire, Qubit) else initial_cbit
1✔
705

706
            # for cregs with cregbundle on, don't use math formatting, which means
707
            # no italics
708
            if isinstance(wire, Qubit) or register is None or not self._cregbundle:
1✔
709
                wire_label = "$" + wire_label + "$"
1✔
710
            wire_label += initial_bit
1✔
711

712
            reg_size = (
1✔
713
                0 if register is None or isinstance(wire, ClassicalRegister) else register.size
714
            )
715
            reg_remove_under = 0 if reg_size < 2 else 1
1✔
716
            text_width = (
1✔
717
                self._get_text_width(
718
                    wire_label, glob_data, self._style["fs"], reg_remove_under=reg_remove_under
719
                )
720
                * 1.15
721
            )
722
            longest_wire_label_width = max(longest_wire_label_width, text_width)
1✔
723

724
            if isinstance(wire, Qubit):
1✔
725
                pos = -ii
1✔
726
                qubits_dict[ii] = {
1✔
727
                    "y": pos,
728
                    "wire_label": wire_label,
729
                }
730
                glob_data["n_lines"] += 1
1✔
731
            else:
732
                if (
1✔
733
                    not self._cregbundle
734
                    or register is None
735
                    or (self._cregbundle and isinstance(wire, ClassicalRegister))
736
                ):
737
                    glob_data["n_lines"] += 1
1✔
738
                    idx += 1
1✔
739

740
                pos = y_off - idx
1✔
741
                clbits_dict[ii] = {
1✔
742
                    "y": pos,
743
                    "wire_label": wire_label,
744
                    "register": register,
745
                }
746
        glob_data["x_offset"] = -1.2 + longest_wire_label_width
1✔
747

748
    def _get_coords(
1✔
749
        self,
750
        node_data,
751
        wire_map,
752
        outer_circuit,
753
        layer_widths,
754
        qubits_dict,
755
        clbits_dict,
756
        glob_data,
757
        flow_parent=None,
758
    ):
759
        """Load all the coordinate info needed to place the gates on the drawing."""
760

761
        prev_x_index = -1
1✔
762
        for layer in self._nodes:
1✔
763
            curr_x_index = prev_x_index + 1
1✔
764
            l_width = []
1✔
765
            for node in layer:
1✔
766
                # For gates inside a flow op set the x_index and if it's an else or case,
767
                # increment by if/switch width. If more cases increment by width of previous cases.
768
                if flow_parent is not None:
1✔
769
                    node_data[node].inside_flow = True
×
770
                    # front_space provides a space for 'If', 'While', etc. which is not
771
                    # necessary for a BoxOp
772
                    front_space = 0 if isinstance(flow_parent.op, BoxOp) else 1
×
773
                    node_data[node].x_index = (
×
774
                        node_data[flow_parent].x_index + curr_x_index + front_space
775
                    )
776

777
                    # If an else or case
778
                    if node_data[node].circ_num > 0:
×
779
                        for width in node_data[flow_parent].width[: node_data[node].circ_num]:
×
780
                            node_data[node].x_index += int(width) + 1
×
781
                        x_index = node_data[node].x_index
×
782
                    # Add expr_width to if, while, or switch if expr used
783
                    else:
784
                        x_index = node_data[node].x_index + node_data[flow_parent].expr_width
×
785
                else:
786
                    node_data[node].inside_flow = False
1✔
787
                    x_index = curr_x_index
1✔
788

789
                # get qubit indexes
790
                q_indxs = []
1✔
791
                for qarg in node.qargs:
1✔
792
                    if qarg in self._qubits:
1✔
793
                        q_indxs.append(wire_map[qarg])
1✔
794

795
                # get clbit indexes
796
                c_indxs = []
1✔
797
                for carg in node.cargs:
1✔
798
                    if carg in self._clbits:
1✔
799
                        if self._cregbundle:
1✔
800
                            register = get_bit_register(outer_circuit, carg)
×
801
                            if register is not None:
×
802
                                c_indxs.append(wire_map[register])
×
803
                            else:
804
                                c_indxs.append(wire_map[carg])
×
805
                        else:
806
                            c_indxs.append(wire_map[carg])
1✔
807

808
                flow_op = isinstance(node.op, ControlFlowOp)
1✔
809

810
                # qubit coordinates
811
                node_data[node].q_xy = [
1✔
812
                    self._plot_coord(
813
                        x_index,
814
                        qubits_dict[ii]["y"],
815
                        layer_widths[node][0],
816
                        glob_data,
817
                        flow_op,
818
                    )
819
                    for ii in q_indxs
820
                ]
821
                # clbit coordinates
822
                node_data[node].c_xy = [
1✔
823
                    self._plot_coord(
824
                        x_index,
825
                        clbits_dict[ii]["y"],
826
                        layer_widths[node][0],
827
                        glob_data,
828
                        flow_op,
829
                    )
830
                    for ii in c_indxs
831
                ]
832

833
                # update index based on the value from plotting
834
                if flow_parent is None:
1✔
835
                    curr_x_index = glob_data["next_x_index"]
1✔
836
                l_width.append(layer_widths[node][0])
1✔
837
                node_data[node].x_index = x_index
1✔
838

839
                # Special case of default case with no ops in it, need to push end
840
                # of switch op one extra x_index
841
                if isinstance(node.op, SwitchCaseOp):
1✔
842
                    if len(node.op.blocks[-1]) == 0:
×
843
                        curr_x_index += 1
×
844

845
            # adjust the column if there have been barriers encountered, but not plotted
846
            barrier_offset = 0
1✔
847
            if not self._plot_barriers:
1✔
848
                # only adjust if everything in the layer wasn't plotted
849
                barrier_offset = (
×
850
                    -1 if all(getattr(nd.op, "_directive", False) for nd in layer) else 0
851
                )
852
            max_lwidth = max(l_width) if l_width else 0
1✔
853
            prev_x_index = curr_x_index + max_lwidth + barrier_offset - 1
1✔
854

855
        return prev_x_index + 1
1✔
856

857
    def _get_text_width(self, text, glob_data, fontsize, param=False, reg_remove_under=None):
1✔
858
        """Compute the width of a string in the default font"""
859

860
        from pylatexenc.latex2text import LatexNodes2Text
1✔
861

862
        if not text:
1✔
863
            return 0.0
1✔
864

865
        math_mode_match = self._mathmode_regex.search(text)
1✔
866
        num_underscores = 0
1✔
867
        num_carets = 0
1✔
868
        if math_mode_match:
1✔
869
            math_mode_text = math_mode_match.group(1)
1✔
870
            num_underscores = math_mode_text.count("_")
1✔
871
            num_carets = math_mode_text.count("^")
1✔
872
        text = LatexNodes2Text().latex_to_text(text.replace("$$", ""))
1✔
873

874
        # If there are subscripts or superscripts in mathtext string
875
        # we need to account for that spacing by manually removing
876
        # from text string for text length
877

878
        # if it's a register and there's a subscript at the end,
879
        # remove 1 underscore, otherwise don't remove any
880
        if reg_remove_under is not None:
1✔
881
            num_underscores = reg_remove_under
1✔
882
        if num_underscores:
1✔
883
            text = text.replace("_", "", num_underscores)
1✔
884
        if num_carets:
1✔
885
            text = text.replace("^", "", num_carets)
×
886

887
        # This changes hyphen to + to match width of math mode minus sign.
888
        if param:
1✔
889
            text = text.replace("-", "+")
×
890

891
        f = 0 if fontsize == self._style["fs"] else 1
1✔
892
        sum_text = 0.0
1✔
893
        for c in text:
1✔
894
            try:
1✔
895
                sum_text += self._char_list[c][f]
1✔
896
            except KeyError:
×
897
                # if non-ASCII char, use width of 'c', an average size
898
                sum_text += self._char_list["c"][f]
×
899
        if f == 1:
1✔
900
            sum_text *= glob_data["subfont_factor"]
×
901
        return sum_text
1✔
902

903
    def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dict, glob_data):
1✔
904
        """Draw the register names and numbers, wires, and vertical lines at the ends"""
905

906
        for fold_num in range(num_folds + 1):
1✔
907
            # quantum registers
908
            for qubit in qubits_dict.values():
1✔
909
                qubit_label = qubit["wire_label"]
1✔
910
                y = qubit["y"] - fold_num * (glob_data["n_lines"] + 1)
1✔
911
                self._ax.text(
1✔
912
                    glob_data["x_offset"] - 0.2,
913
                    y,
914
                    qubit_label,
915
                    ha="right",
916
                    va="center",
917
                    fontsize=1.25 * self._style["fs"],
918
                    color=self._style["tc"],
919
                    clip_on=True,
920
                    zorder=PORDER_TEXT,
921
                )
922
                # draw the qubit wire
923
                self._line([glob_data["x_offset"], y], [xmax, y], zorder=PORDER_REGLINE)
1✔
924

925
            # classical registers
926
            this_clbit_dict = {}
1✔
927
            for clbit in clbits_dict.values():
1✔
928
                y = clbit["y"] - fold_num * (glob_data["n_lines"] + 1)
1✔
929
                if y not in this_clbit_dict:
1✔
930
                    this_clbit_dict[y] = {
1✔
931
                        "val": 1,
932
                        "wire_label": clbit["wire_label"],
933
                        "register": clbit["register"],
934
                    }
935
                else:
936
                    this_clbit_dict[y]["val"] += 1
×
937

938
            for y, this_clbit in this_clbit_dict.items():
1✔
939
                # cregbundle
940
                if self._cregbundle and this_clbit["register"] is not None:
1✔
941
                    self._ax.plot(
×
942
                        [glob_data["x_offset"] + 0.2, glob_data["x_offset"] + 0.3],
943
                        [y - 0.1, y + 0.1],
944
                        color=self._style["cc"],
945
                        zorder=PORDER_REGLINE,
946
                    )
947
                    self._ax.text(
×
948
                        glob_data["x_offset"] + 0.1,
949
                        y + 0.1,
950
                        str(this_clbit["register"].size),
951
                        ha="left",
952
                        va="bottom",
953
                        fontsize=0.8 * self._style["fs"],
954
                        color=self._style["tc"],
955
                        clip_on=True,
956
                        zorder=PORDER_TEXT,
957
                    )
958
                self._ax.text(
1✔
959
                    glob_data["x_offset"] - 0.2,
960
                    y,
961
                    this_clbit["wire_label"],
962
                    ha="right",
963
                    va="center",
964
                    fontsize=1.25 * self._style["fs"],
965
                    color=self._style["tc"],
966
                    clip_on=True,
967
                    zorder=PORDER_TEXT,
968
                )
969
                # draw the clbit wire
970
                self._line(
1✔
971
                    [glob_data["x_offset"], y],
972
                    [xmax, y],
973
                    lc=self._style["cc"],
974
                    ls=self._style["cline"],
975
                    zorder=PORDER_REGLINE,
976
                )
977

978
            # lf vertical line at either end
979
            feedline_r = num_folds > 0 and num_folds > fold_num
1✔
980
            feedline_l = fold_num > 0
1✔
981
            if feedline_l or feedline_r:
1✔
982
                xpos_l = glob_data["x_offset"] - 0.01
×
983
                xpos_r = self._fold + glob_data["x_offset"] + 0.1
×
984
                ypos1 = -fold_num * (glob_data["n_lines"] + 1)
×
985
                ypos2 = -(fold_num + 1) * (glob_data["n_lines"]) - fold_num + 1
×
986
                if feedline_l:
×
987
                    self._ax.plot(
×
988
                        [xpos_l, xpos_l],
989
                        [ypos1, ypos2],
990
                        color=self._style["lc"],
991
                        linewidth=self._lwidth15,
992
                        zorder=PORDER_REGLINE,
993
                    )
994
                if feedline_r:
×
995
                    self._ax.plot(
×
996
                        [xpos_r, xpos_r],
997
                        [ypos1, ypos2],
998
                        color=self._style["lc"],
999
                        linewidth=self._lwidth15,
1000
                        zorder=PORDER_REGLINE,
1001
                    )
1002
            # Mask off any lines or boxes in the bit label area to clean up
1003
            # from folding for ControlFlow and other wrapping gates
1004
            box = glob_data["patches_mod"].Rectangle(
1✔
1005
                xy=(glob_data["x_offset"] - 0.1, -fold_num * (glob_data["n_lines"] + 1) + 0.5),
1006
                width=-25.0,
1007
                height=-(fold_num + 1) * (glob_data["n_lines"] + 1),
1008
                fc=self._style["bg"],
1009
                ec=self._style["bg"],
1010
                linewidth=self._lwidth15,
1011
                zorder=PORDER_MASK,
1012
            )
1013
            self._ax.add_patch(box)
1✔
1014

1015
        # draw index number
1016
        if self._style["index"]:
1✔
1017
            for layer_num in range(max_x_index):
×
1018
                if self._fold > 0:
×
1019
                    x_coord = layer_num % self._fold + glob_data["x_offset"] + 0.53
×
1020
                    y_coord = -(layer_num // self._fold) * (glob_data["n_lines"] + 1) + 0.65
×
1021
                else:
1022
                    x_coord = layer_num + glob_data["x_offset"] + 0.53
×
1023
                    y_coord = 0.65
×
1024
                self._ax.text(
×
1025
                    x_coord,
1026
                    y_coord,
1027
                    str(layer_num + 1),
1028
                    ha="center",
1029
                    va="center",
1030
                    fontsize=self._style["sfs"],
1031
                    color=self._style["tc"],
1032
                    clip_on=True,
1033
                    zorder=PORDER_TEXT,
1034
                )
1035

1036
    def _add_nodes_and_coords(
1✔
1037
        self,
1038
        nodes,
1039
        node_data,
1040
        wire_map,
1041
        outer_circuit,
1042
        layer_widths,
1043
        qubits_dict,
1044
        clbits_dict,
1045
        glob_data,
1046
    ):
1047
        """Add the nodes from ControlFlowOps and their coordinates to the main circuit"""
1048
        for flow_drawers in self._flow_drawers.values():
1✔
1049
            for flow_drawer in flow_drawers:
×
1050
                nodes += flow_drawer._nodes
×
1051
                flow_drawer._get_coords(
×
1052
                    node_data,
1053
                    flow_drawer._flow_wire_map,
1054
                    outer_circuit,
1055
                    layer_widths,
1056
                    qubits_dict,
1057
                    clbits_dict,
1058
                    glob_data,
1059
                    flow_parent=flow_drawer._flow_parent,
1060
                )
1061
                # Recurse for ControlFlowOps inside the flow_drawer
1062
                flow_drawer._add_nodes_and_coords(
×
1063
                    nodes,
1064
                    node_data,
1065
                    wire_map,
1066
                    outer_circuit,
1067
                    layer_widths,
1068
                    qubits_dict,
1069
                    clbits_dict,
1070
                    glob_data,
1071
                )
1072

1073
    def _draw_ops(
1✔
1074
        self,
1075
        nodes,
1076
        node_data,
1077
        wire_map,
1078
        outer_circuit,
1079
        layer_widths,
1080
        qubits_dict,
1081
        clbits_dict,
1082
        glob_data,
1083
        verbose=False,
1084
    ):
1085
        """Draw the gates in the circuit"""
1086

1087
        # Add the nodes from all the ControlFlowOps and their coordinates to the main nodes
1088
        self._add_nodes_and_coords(
1✔
1089
            nodes,
1090
            node_data,
1091
            wire_map,
1092
            outer_circuit,
1093
            layer_widths,
1094
            qubits_dict,
1095
            clbits_dict,
1096
            glob_data,
1097
        )
1098
        prev_x_index = -1
1✔
1099
        for layer in nodes:
1✔
1100
            l_width = []
1✔
1101
            curr_x_index = prev_x_index + 1
1✔
1102

1103
            # draw the gates in this layer
1104
            for node in layer:
1✔
1105
                op = node.op
1✔
1106

1107
                self._get_colors(node, node_data)
1✔
1108

1109
                if verbose:
1✔
NEW
1110
                    print(op)
×
1111

1112
                # add conditional
1113
                if getattr(op, "condition", None) or isinstance(op, SwitchCaseOp):
1✔
1114
                    cond_xy = [
×
1115
                        self._plot_coord(
1116
                            node_data[node].x_index,
1117
                            clbits_dict[ii]["y"],
1118
                            layer_widths[node][0],
1119
                            glob_data,
1120
                            isinstance(op, ControlFlowOp),
1121
                        )
1122
                        for ii in clbits_dict
1123
                    ]
1124
                    self._condition(node, node_data, wire_map, outer_circuit, cond_xy, glob_data)
×
1125

1126
                # AnnotatedOperation with ControlModifier
1127
                mod_control = None
1✔
1128
                if getattr(op, "modifiers", None):
1✔
1129
                    canonical_modifiers = _canonicalize_modifiers(op.modifiers)
×
1130
                    for modifier in canonical_modifiers:
×
1131
                        if isinstance(modifier, ControlModifier):
×
1132
                            mod_control = modifier
×
1133
                            break
×
1134

1135
                # draw measure
1136
                if isinstance(op, Measure):
1✔
1137
                    self._measure(node, node_data, outer_circuit, glob_data)
×
1138

1139
                # draw barriers, snapshots, etc.
1140
                elif getattr(op, "_directive", False):
1✔
1141
                    if self._plot_barriers:
×
1142
                        self._barrier(node, node_data, glob_data)
×
1143

1144
                # draw the box for control flow circuits
1145
                elif isinstance(op, ControlFlowOp):
1✔
1146
                    self._flow_op_gate(node, node_data, glob_data)
×
1147

1148
                # draw single qubit gates
1149
                elif len(node_data[node].q_xy) == 1 and not node.cargs:
1✔
1150
                    self._gate(node, node_data, glob_data)
1✔
1151

1152
                # draw controlled gates
1153
                elif isinstance(op, ControlledGate) or mod_control:
1✔
1154
                    self._control_gate(node, node_data, glob_data, mod_control)
×
1155

1156
                # draw multi-qubit gate as final default
1157
                else:
1158
                    self._multiqubit_gate(node, node_data, glob_data)
1✔
1159

1160
                # Determine the max width of the circuit only at the top level
1161
                if not node_data[node].inside_flow:
1✔
1162
                    l_width.append(layer_widths[node][0])
1✔
1163

1164
            # adjust the column if there have been barriers encountered, but not plotted
1165
            barrier_offset = 0
1✔
1166
            if not self._plot_barriers:
1✔
1167
                # only adjust if everything in the layer wasn't plotted
1168
                barrier_offset = (
×
1169
                    -1 if all(getattr(nd.op, "_directive", False) for nd in layer) else 0
1170
                )
1171
            prev_x_index = curr_x_index + (max(l_width) if l_width else 0) + barrier_offset - 1
1✔
1172

1173
    def _get_colors(self, node, node_data):
1✔
1174
        """Get all the colors needed for drawing the circuit"""
1175

1176
        op = node.op
1✔
1177
        base_name = getattr(getattr(op, "base_gate", None), "name", None)
1✔
1178
        color = None
1✔
1179
        if node_data[node].raw_gate_text in self._style["dispcol"]:
1✔
1180
            color = self._style["dispcol"][node_data[node].raw_gate_text]
1✔
1181
        elif op.name in self._style["dispcol"]:
1✔
1182
            color = self._style["dispcol"][op.name]
×
1183
        if color is not None:
1✔
1184
            # Backward compatibility for style dict using 'displaycolor' with
1185
            # gate color and no text color, so test for str first
1186
            if isinstance(color, str):
1✔
1187
                fc = color
×
1188
                gt = self._style["gt"]
×
1189
            else:
1190
                fc = color[0]
1✔
1191
                gt = color[1]
1✔
1192
        # Treat special case of classical gates in iqx style by making all
1193
        # controlled gates of x, dcx, and swap the classical gate color
1194
        elif self._style["name"] in ["iqp", "iqx", "iqp-dark", "iqx-dark"] and base_name in [
1✔
1195
            "x",
1196
            "dcx",
1197
            "swap",
1198
        ]:
1199
            color = self._style["dispcol"][base_name]
×
1200
            if isinstance(color, str):
×
1201
                fc = color
×
1202
                gt = self._style["gt"]
×
1203
            else:
1204
                fc = color[0]
×
1205
                gt = color[1]
×
1206
        else:
1207
            fc = self._style["gc"]
1✔
1208
            gt = self._style["gt"]
1✔
1209

1210
        if self._style["name"] == "bw":
1✔
1211
            ec = self._style["ec"]
×
1212
            lc = self._style["lc"]
×
1213
        else:
1214
            ec = fc
1✔
1215
            lc = fc
1✔
1216
        # Subtext needs to be same color as gate text
1217
        sc = gt
1✔
1218
        node_data[node].fc = fc
1✔
1219
        node_data[node].ec = ec
1✔
1220
        node_data[node].gt = gt
1✔
1221
        node_data[node].tc = self._style["tc"]
1✔
1222
        node_data[node].sc = sc
1✔
1223
        node_data[node].lc = lc
1✔
1224

1225
    def _condition(self, node, node_data, wire_map, outer_circuit, cond_xy, glob_data):
1✔
1226
        """Add a conditional to a gate"""
1227

1228
        # For SwitchCaseOp convert the target to a fully closed Clbit or register
1229
        # in condition format
1230
        if isinstance(node.op, SwitchCaseOp):
×
1231
            if isinstance(node.op.target, expr.Expr):
×
1232
                condition = node.op.target
×
1233
            elif isinstance(node.op.target, Clbit):
×
1234
                condition = (node.op.target, 1)
×
1235
            else:
1236
                condition = (node.op.target, 2 ** (node.op.target.size) - 1)
×
1237
        else:
1238
            condition = node.op.condition
×
1239

1240
        override_fc = False
×
1241
        first_clbit = len(self._qubits)
×
1242
        cond_pos = []
×
1243

1244
        if isinstance(condition, expr.Expr):
×
1245
            # If fixing this, please update the docstrings of `QuantumCircuit.draw` and
1246
            # `visualization.circuit_drawer` to remove warnings.
1247

1248
            condition_bits = condition_resources(condition).clbits
×
1249
            label = "[expr]"
×
1250
            override_fc = True
×
1251
            registers = collections.defaultdict(list)
×
1252
            for bit in condition_bits:
×
1253
                registers[get_bit_register(outer_circuit, bit)].append(bit)
×
1254
            # Registerless bits don't care whether cregbundle is set.
1255
            cond_pos.extend(cond_xy[wire_map[bit] - first_clbit] for bit in registers.pop(None, ()))
×
1256
            if self._cregbundle:
×
1257
                cond_pos.extend(cond_xy[wire_map[register] - first_clbit] for register in registers)
×
1258
            else:
1259
                cond_pos.extend(
×
1260
                    cond_xy[wire_map[bit] - first_clbit]
1261
                    for bit in itertools.chain.from_iterable(registers.values())
1262
                )
1263
            val_bits = ["1"] * len(cond_pos)
×
1264
        else:
1265
            label, val_bits = get_condition_label_val(condition, self._circuit, self._cregbundle)
×
1266
            cond_bit_reg = condition[0]
×
1267
            cond_bit_val = int(condition[1])
×
1268
            override_fc = (
×
1269
                cond_bit_val != 0
1270
                and self._cregbundle
1271
                and isinstance(cond_bit_reg, ClassicalRegister)
1272
            )
1273

1274
            # In the first case, multiple bits are indicated on the drawing. In all
1275
            # other cases, only one bit is shown.
1276
            if not self._cregbundle and isinstance(cond_bit_reg, ClassicalRegister):
×
1277
                for idx in range(cond_bit_reg.size):
×
1278
                    cond_pos.append(cond_xy[wire_map[cond_bit_reg[idx]] - first_clbit])
×
1279

1280
            # If it's a register bit and cregbundle, need to use the register to find the location
1281
            elif self._cregbundle and isinstance(cond_bit_reg, Clbit):
×
1282
                register = get_bit_register(outer_circuit, cond_bit_reg)
×
1283
                if register is not None:
×
1284
                    cond_pos.append(cond_xy[wire_map[register] - first_clbit])
×
1285
                else:
1286
                    cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit])
×
1287
            else:
1288
                cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit])
×
1289

1290
        xy_plot = []
×
1291
        for val_bit, xy in zip(val_bits, cond_pos):
×
1292
            fc = self._style["lc"] if override_fc or val_bit == "1" else self._style["bg"]
×
1293
            box = glob_data["patches_mod"].Circle(
×
1294
                xy=xy,
1295
                radius=WID * 0.15,
1296
                fc=fc,
1297
                ec=self._style["lc"],
1298
                linewidth=self._lwidth15,
1299
                zorder=PORDER_GATE,
1300
            )
1301
            self._ax.add_patch(box)
×
1302
            xy_plot.append(xy)
×
1303

1304
        if not xy_plot:
×
1305
            # Expression that's only on new-style `expr.Var` nodes, and doesn't need any vertical
1306
            # line drawing.
1307
            return
×
1308

1309
        qubit_b = min(node_data[node].q_xy, key=lambda xy: xy[1])
×
1310
        clbit_b = min(xy_plot, key=lambda xy: xy[1])
×
1311

1312
        # For IfElseOp, WhileLoopOp or SwitchCaseOp, place the condition line
1313
        # near the left edge of the box
1314
        if isinstance(node.op, (IfElseOp, WhileLoopOp, SwitchCaseOp)):
×
1315
            qubit_b = (qubit_b[0], qubit_b[1] - (0.5 * HIG + 0.14))
×
1316

1317
        # display the label at the bottom of the lowest conditional and draw the double line
1318
        xpos, ypos = clbit_b
×
1319
        if isinstance(node.op, Measure):
×
1320
            xpos += 0.3
×
1321
        self._ax.text(
×
1322
            xpos,
1323
            ypos - 0.3 * HIG,
1324
            label,
1325
            ha="center",
1326
            va="top",
1327
            fontsize=self._style["sfs"],
1328
            color=self._style["tc"],
1329
            clip_on=True,
1330
            zorder=PORDER_TEXT,
1331
        )
1332
        self._line(qubit_b, clbit_b, lc=self._style["cc"], ls=self._style["cline"])
×
1333

1334
    def _measure(self, node, node_data, outer_circuit, glob_data):
1✔
1335
        """Draw the measure symbol and the line to the clbit"""
1336
        qx, qy = node_data[node].q_xy[0]
×
1337
        cx, cy = node_data[node].c_xy[0]
×
1338
        register, _, reg_index = get_bit_reg_index(outer_circuit, node.cargs[0])
×
1339

1340
        # draw gate box
1341
        self._gate(node, node_data, glob_data)
×
1342

1343
        # add measure symbol
1344
        qy_adj1 = 0.15 if self._measure_arrows else 0.05
×
1345
        arc = glob_data["patches_mod"].Arc(
×
1346
            xy=(qx, qy - qy_adj1 * HIG),
1347
            width=WID * 0.7,
1348
            height=HIG * 0.7,
1349
            theta1=0,
1350
            theta2=180,
1351
            fill=False,
1352
            ec=node_data[node].gt,
1353
            linewidth=self._lwidth2,
1354
            zorder=PORDER_GATE,
1355
        )
1356
        self._ax.add_patch(arc)
×
1357
        qy_adj2 = 0.2 if self._measure_arrows else 0.3
×
1358
        self._ax.plot(
×
1359
            [qx, qx + 0.35 * WID],
1360
            [qy - qy_adj1 * HIG, qy + qy_adj2 * HIG],
1361
            color=node_data[node].gt,
1362
            linewidth=self._lwidth2,
1363
            zorder=PORDER_GATE,
1364
        )
1365
        # If measure_arrows, draw the down arrow to the clbit
1366
        if self._measure_arrows:
×
1367
            self._line(
×
1368
                node_data[node].q_xy[0],
1369
                [cx, cy + 0.35 * WID],
1370
                lc=self._style["cc"],
1371
                ls=self._style["cline"],
1372
            )
1373
            arrowhead = glob_data["patches_mod"].Polygon(
×
1374
                (
1375
                    (cx - 0.20 * WID, cy + 0.35 * WID),
1376
                    (cx + 0.20 * WID, cy + 0.35 * WID),
1377
                    (cx, cy + 0.04),
1378
                ),
1379
                fc=self._style["cc"],
1380
                ec=None,
1381
            )
1382
            self._ax.add_artist(arrowhead)
×
1383
            # target
1384
            if self._cregbundle and register is not None:
×
1385
                self._ax.text(
×
1386
                    cx + 0.25,
1387
                    cy + 0.1,
1388
                    str(reg_index),
1389
                    ha="left",
1390
                    va="bottom",
1391
                    fontsize=0.8 * self._style["fs"],
1392
                    color=self._style["tc"],
1393
                    clip_on=True,
1394
                    zorder=PORDER_TEXT,
1395
                )
1396
        else:
1397
            # If not measure_arrows, write the reg_bit into the measure box
1398
            if register is not None:
×
1399
                label = f"{register.name}_{reg_index}"
×
1400
            else:
1401
                label = f"{reg_index}"
×
1402
            self._ax.text(
×
1403
                qx,
1404
                qy - 0.42 * HIG,
1405
                label,
1406
                ha="center",
1407
                va="bottom",
1408
                fontsize=self._style["sfs"],
1409
                color=self._style["tc"],
1410
                clip_on=True,
1411
                zorder=PORDER_TEXT,
1412
            )
1413

1414
    def _barrier(self, node, node_data, glob_data):
1✔
1415
        """Draw a barrier"""
1416
        for i, xy in enumerate(node_data[node].q_xy):
×
1417
            xpos, ypos = xy
×
1418
            # For the topmost barrier, reduce the rectangle if there's a label to allow for the text.
1419
            if i == 0 and node.op.label is not None:
×
1420
                ypos_adj = -0.35
×
1421
            else:
1422
                ypos_adj = 0.0
×
1423
            self._ax.plot(
×
1424
                [xpos, xpos],
1425
                [ypos + 0.5 + ypos_adj, ypos - 0.5],
1426
                linewidth=self._lwidth1,
1427
                linestyle="dashed",
1428
                color=self._style["lc"],
1429
                zorder=PORDER_TEXT,
1430
            )
1431
            box = glob_data["patches_mod"].Rectangle(
×
1432
                xy=(xpos - (0.3 * WID), ypos - 0.5),
1433
                width=0.6 * WID,
1434
                height=1.0 + ypos_adj,
1435
                fc=self._style["bc"],
1436
                ec=None,
1437
                alpha=0.6,
1438
                linewidth=self._lwidth15,
1439
                zorder=PORDER_BARRIER,
1440
            )
1441
            self._ax.add_patch(box)
×
1442

1443
            # display the barrier label at the top if there is one
1444
            if i == 0 and node.op.label is not None:
×
1445
                dir_ypos = ypos + 0.65 * HIG
×
1446
                self._ax.text(
×
1447
                    xpos,
1448
                    dir_ypos,
1449
                    node.op.label,
1450
                    ha="center",
1451
                    va="top",
1452
                    fontsize=self._style["fs"],
1453
                    color=node_data[node].tc,
1454
                    clip_on=True,
1455
                    zorder=PORDER_TEXT,
1456
                )
1457

1458
    def _gate(self, node, node_data, glob_data, xy=None):
1✔
1459
        """Draw a 1-qubit gate"""
1460
        if xy is None:
1✔
1461
            xy = node_data[node].q_xy[0]
1✔
1462
        xpos, ypos = xy
1✔
1463
        wid = max(node_data[node].width, WID)
1✔
1464

1465
        box = glob_data["patches_mod"].Rectangle(
1✔
1466
            xy=(xpos - 0.5 * wid, ypos - 0.5 * HIG),
1467
            width=wid,
1468
            height=HIG,
1469
            fc=node_data[node].fc,
1470
            ec=node_data[node].ec,
1471
            linewidth=self._lwidth15,
1472
            zorder=PORDER_GATE,
1473
        )
1474
        self._ax.add_patch(box)
1✔
1475

1476
        if node_data[node].gate_text:
1✔
1477
            gate_ypos = ypos
1✔
1478
            if node_data[node].param_text:
1✔
1479
                gate_ypos = ypos + 0.15 * HIG
×
1480
                self._ax.text(
×
1481
                    xpos,
1482
                    ypos - 0.3 * HIG,
1483
                    node_data[node].param_text,
1484
                    ha="center",
1485
                    va="center",
1486
                    fontsize=self._style["sfs"],
1487
                    color=node_data[node].sc,
1488
                    clip_on=True,
1489
                    zorder=PORDER_TEXT,
1490
                )
1491
            self._ax.text(
1✔
1492
                xpos,
1493
                gate_ypos,
1494
                node_data[node].gate_text,
1495
                ha="center",
1496
                va="center",
1497
                fontsize=self._style["fs"],
1498
                color=node_data[node].gt,
1499
                clip_on=True,
1500
                zorder=PORDER_TEXT,
1501
            )
1502

1503
    def _multiqubit_gate(self, node, node_data, glob_data, xy=None):
1✔
1504
        """Draw a gate covering more than one qubit"""
1505
        op = node.op
1✔
1506
        if xy is None:
1✔
1507
            xy = node_data[node].q_xy
1✔
1508

1509
        # Swap gate
1510
        if isinstance(op, SwapGate):
1✔
1511
            self._swap(xy, node_data[node].lc)
×
1512
            return
×
1513

1514
        # RZZ Gate
1515
        elif isinstance(op, RZZGate):
1✔
1516
            self._symmetric_gate(node, node_data, RZZGate, glob_data)
×
1517
            return
×
1518

1519
        c_xy = node_data[node].c_xy
1✔
1520
        xpos = min(x[0] for x in xy)
1✔
1521
        ypos = min(y[1] for y in xy)
1✔
1522
        ypos_max = max(y[1] for y in xy)
1✔
1523
        if c_xy:
1✔
1524
            cxpos = min(x[0] for x in c_xy)
1✔
1525
            cypos = min(y[1] for y in c_xy)
1✔
1526
            ypos = min(ypos, cypos)
1✔
1527

1528
        wid = max(node_data[node].width + 0.21, WID)
1✔
1529
        qubit_span = abs(ypos) - abs(ypos_max)
1✔
1530
        height = HIG + qubit_span
1✔
1531

1532
        box = glob_data["patches_mod"].Rectangle(
1✔
1533
            xy=(xpos - 0.5 * wid, ypos - 0.5 * HIG),
1534
            width=wid,
1535
            height=height,
1536
            fc=node_data[node].fc,
1537
            ec=node_data[node].ec,
1538
            linewidth=self._lwidth15,
1539
            zorder=PORDER_GATE,
1540
        )
1541
        self._ax.add_patch(box)
1✔
1542

1543
        # annotate inputs
1544
        for bit, y in enumerate([x[1] for x in xy]):
1✔
1545
            self._ax.text(
1✔
1546
                xpos + 0.07 - 0.5 * wid,
1547
                y,
1548
                str(bit),
1549
                ha="left",
1550
                va="center",
1551
                fontsize=self._style["fs"],
1552
                color=node_data[node].gt,
1553
                clip_on=True,
1554
                zorder=PORDER_TEXT,
1555
            )
1556
        if c_xy:
1✔
1557
            # annotate classical inputs
1558
            for bit, y in enumerate([x[1] for x in c_xy]):
1✔
1559
                self._ax.text(
1✔
1560
                    cxpos + 0.07 - 0.5 * wid,
1561
                    y,
1562
                    str(bit),
1563
                    ha="left",
1564
                    va="center",
1565
                    fontsize=self._style["fs"],
1566
                    color=node_data[node].gt,
1567
                    clip_on=True,
1568
                    zorder=PORDER_TEXT,
1569
                )
1570
        if node_data[node].gate_text:
1✔
1571
            gate_ypos = ypos + 0.5 * qubit_span
1✔
1572
            if node_data[node].param_text:
1✔
1573
                gate_ypos = ypos + 0.4 * height
×
1574
                self._ax.text(
×
1575
                    xpos + 0.11,
1576
                    ypos + 0.2 * height,
1577
                    node_data[node].param_text,
1578
                    ha="center",
1579
                    va="center",
1580
                    fontsize=self._style["sfs"],
1581
                    color=node_data[node].sc,
1582
                    clip_on=True,
1583
                    zorder=PORDER_TEXT,
1584
                )
1585
            self._ax.text(
1✔
1586
                xpos + 0.11,
1587
                gate_ypos,
1588
                node_data[node].gate_text,
1589
                ha="center",
1590
                va="center",
1591
                fontsize=self._style["fs"],
1592
                color=node_data[node].gt,
1593
                clip_on=True,
1594
                zorder=PORDER_TEXT,
1595
            )
1596

1597
    def _flow_op_gate(self, node, node_data, glob_data):
1✔
1598
        """Draw the box for a flow op circuit"""
1599
        xy = node_data[node].q_xy
×
1600
        xpos = min(x[0] for x in xy)
×
1601
        ypos = min(y[1] for y in xy)
×
1602
        ypos_max = max(y[1] for y in xy)
×
1603

1604
        # If a BoxOp, bring the right side back tight against the gates to allow for
1605
        # better spacing
1606
        if_width = node_data[node].width[0] + (WID if not isinstance(node.op, BoxOp) else -0.19)
×
1607
        box_width = if_width
×
1608
        # Add the else and case widths to the if_width
1609
        for ewidth in node_data[node].width[1:]:
×
1610
            if ewidth > 0.0:
×
1611
                box_width += ewidth + WID + 0.3
×
1612

1613
        qubit_span = abs(ypos) - abs(ypos_max)
×
1614
        height = HIG + qubit_span
×
1615

1616
        # Cycle through box colors based on depth.
1617
        # Default - blue, purple, green, black
1618
        colors = [
×
1619
            self._style["dispcol"]["h"][0],
1620
            self._style["dispcol"]["u"][0],
1621
            self._style["dispcol"]["x"][0],
1622
            self._style["cc"],
1623
        ]
1624
        # To fold box onto next lines, draw it repeatedly, shifting
1625
        # it left by x_shift and down by y_shift
1626
        fold_level = 0
×
1627
        end_x = xpos + box_width
×
1628

1629
        while end_x > 0.0:
×
1630
            x_shift = fold_level * self._fold
×
1631
            y_shift = fold_level * (glob_data["n_lines"] + 1)
×
1632
            end_x = xpos + box_width - x_shift if self._fold > 0 else 0.0
×
1633

1634
            if isinstance(node.op, IfElseOp):
×
1635
                flow_text = "  If"
×
1636
            elif isinstance(node.op, WhileLoopOp):
×
1637
                flow_text = " While"
×
1638
            elif isinstance(node.op, ForLoopOp):
×
1639
                flow_text = " For"
×
1640
            elif isinstance(node.op, SwitchCaseOp):
×
1641
                flow_text = "Switch"
×
1642
            elif isinstance(node.op, BoxOp):
×
1643
                flow_text = ""
×
1644
            else:
1645
                raise RuntimeError(f"unhandled control-flow op: {node.name}")
1646

1647
            # Some spacers. op_spacer moves 'Switch' back a bit for alignment,
1648
            # expr_spacer moves the expr over to line up with 'Switch' and
1649
            # empty_default_spacer makes the switch box longer if the default
1650
            # case is empty so text doesn't run past end of box.
1651
            if isinstance(node.op, SwitchCaseOp):
×
1652
                op_spacer = 0.04
×
1653
                expr_spacer = 0.0
×
1654
                empty_default_spacer = 0.3 if len(node.op.blocks[-1]) == 0 else 0.0
×
1655
            elif isinstance(node.op, BoxOp):
×
1656
                # Move the X start position back for a BoxOp, since there is no
1657
                # leading text. This tightens the BoxOp with other ops.
1658
                xpos -= 0.15
×
1659
                op_spacer = 0.0
×
1660
                expr_spacer = 0.0
×
1661
                empty_default_spacer = 0.0
×
1662
            else:
1663
                op_spacer = 0.08
×
1664
                expr_spacer = 0.02
×
1665
                empty_default_spacer = 0.0
×
1666

1667
            # FancyBbox allows rounded corners
1668
            box = glob_data["patches_mod"].FancyBboxPatch(
×
1669
                xy=(xpos - x_shift, ypos - 0.5 * HIG - y_shift),
1670
                width=box_width + empty_default_spacer,
1671
                height=height,
1672
                boxstyle="round, pad=0.1",
1673
                fc="none",
1674
                ec=colors[node_data[node].nest_depth % 4],
1675
                linewidth=self._lwidth3,
1676
                zorder=PORDER_FLOW,
1677
            )
1678
            self._ax.add_patch(box)
×
1679

1680
            # Indicate type of ControlFlowOp and if expression used, print below
1681
            self._ax.text(
×
1682
                xpos - x_shift - op_spacer,
1683
                ypos_max + 0.2 - y_shift,
1684
                flow_text,
1685
                ha="left",
1686
                va="center",
1687
                fontsize=self._style["fs"],
1688
                color=node_data[node].tc,
1689
                clip_on=True,
1690
                zorder=PORDER_FLOW,
1691
            )
1692
            self._ax.text(
×
1693
                xpos - x_shift + expr_spacer,
1694
                ypos_max + 0.2 - y_shift - 0.4,
1695
                node_data[node].expr_text,
1696
                ha="left",
1697
                va="center",
1698
                fontsize=self._style["sfs"],
1699
                color=node_data[node].tc,
1700
                clip_on=True,
1701
                zorder=PORDER_FLOW,
1702
            )
1703
            if isinstance(node.op, ForLoopOp):
×
1704
                idx_set = str(node_data[node].indexset)
×
1705
                # If a range was used display 'range' and grab the range value
1706
                # to be displayed below
1707
                if "range" in idx_set:
×
1708
                    idx_set = "r(" + idx_set[6:-1] + ")"
×
1709
                else:
1710
                    # If a tuple, show first 4 elements followed by '...'
1711
                    idx_set = str(node_data[node].indexset)[1:-1].split(",")[:5]
×
1712
                    if len(idx_set) > 4:
×
1713
                        idx_set[4] = "..."
×
1714
                    idx_set = f"{','.join(idx_set)}"
×
1715
                y_spacer = 0.2 if len(node.qargs) == 1 else 0.5
×
1716
                self._ax.text(
×
1717
                    xpos - x_shift - 0.04,
1718
                    ypos_max - y_spacer - y_shift,
1719
                    idx_set,
1720
                    ha="left",
1721
                    va="center",
1722
                    fontsize=self._style["sfs"],
1723
                    color=node_data[node].tc,
1724
                    clip_on=True,
1725
                    zorder=PORDER_FLOW,
1726
                )
1727
            # If there's an else or a case draw the vertical line and the name
1728
            else_case_text = "Else" if isinstance(node.op, IfElseOp) else "Case"
×
1729
            ewidth_incr = if_width
×
1730
            for circ_num, ewidth in enumerate(node_data[node].width[1:]):
×
1731
                if ewidth > 0.0:
×
1732
                    self._ax.plot(
×
1733
                        [xpos + ewidth_incr + 0.3 - x_shift, xpos + ewidth_incr + 0.3 - x_shift],
1734
                        [ypos - 0.5 * HIG - 0.08 - y_shift, ypos + height - 0.22 - y_shift],
1735
                        color=colors[node_data[node].nest_depth % 4],
1736
                        linewidth=3.0,
1737
                        linestyle="solid",
1738
                        zorder=PORDER_FLOW,
1739
                    )
1740
                    self._ax.text(
×
1741
                        xpos + ewidth_incr + 0.4 - x_shift,
1742
                        ypos_max + 0.2 - y_shift,
1743
                        else_case_text,
1744
                        ha="left",
1745
                        va="center",
1746
                        fontsize=self._style["fs"],
1747
                        color=node_data[node].tc,
1748
                        clip_on=True,
1749
                        zorder=PORDER_FLOW,
1750
                    )
1751
                    if isinstance(node.op, SwitchCaseOp):
×
1752
                        jump_val = node_data[node].jump_values[circ_num]
×
1753
                        # If only one value, e.g. (0,)
1754
                        if len(str(jump_val)) == 4:
×
1755
                            jump_text = str(jump_val)[1]
×
1756
                        elif "default" in str(jump_val):
×
1757
                            jump_text = "default"
×
1758
                        else:
1759
                            # If a tuple, show first 4 elements followed by '...'
1760
                            jump_text = str(jump_val)[1:-1].replace(" ", "").split(",")[:5]
×
1761
                            if len(jump_text) > 4:
×
1762
                                jump_text[4] = "..."
×
1763
                            jump_text = f"{', '.join(jump_text)}"
×
1764
                        y_spacer = 0.2 if len(node.qargs) == 1 else 0.5
×
1765
                        self._ax.text(
×
1766
                            xpos + ewidth_incr + 0.4 - x_shift,
1767
                            ypos_max - y_spacer - y_shift,
1768
                            jump_text,
1769
                            ha="left",
1770
                            va="center",
1771
                            fontsize=self._style["sfs"],
1772
                            color=node_data[node].tc,
1773
                            clip_on=True,
1774
                            zorder=PORDER_FLOW,
1775
                        )
1776
                ewidth_incr += ewidth + 1
×
1777

1778
            fold_level += 1
×
1779

1780
    def _control_gate(self, node, node_data, glob_data, mod_control):
1✔
1781
        """Draw a controlled gate"""
1782
        op = node.op
×
1783
        xy = node_data[node].q_xy
×
1784
        base_type = getattr(op, "base_gate", None)
×
1785
        qubit_b = min(xy, key=lambda xy: xy[1])
×
1786
        qubit_t = max(xy, key=lambda xy: xy[1])
×
1787
        num_ctrl_qubits = mod_control.num_ctrl_qubits if mod_control else op.num_ctrl_qubits
×
1788
        num_qargs = len(xy) - num_ctrl_qubits
×
1789
        ctrl_state = mod_control.ctrl_state if mod_control else op.ctrl_state
×
1790
        self._set_ctrl_bits(
×
1791
            ctrl_state,
1792
            num_ctrl_qubits,
1793
            xy,
1794
            glob_data,
1795
            ec=node_data[node].ec,
1796
            tc=node_data[node].tc,
1797
            text=node_data[node].ctrl_text,
1798
            qargs=node.qargs,
1799
        )
1800
        self._line(qubit_b, qubit_t, lc=node_data[node].lc)
×
1801

1802
        if isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, ZGate, RZZGate)):
×
1803
            self._symmetric_gate(node, node_data, base_type, glob_data)
×
1804

1805
        elif num_qargs == 1 and isinstance(base_type, XGate):
×
1806
            tgt_color = self._style["dispcol"]["target"]
×
1807
            tgt = tgt_color if isinstance(tgt_color, str) else tgt_color[0]
×
1808
            self._x_tgt_qubit(xy[num_ctrl_qubits], glob_data, ec=node_data[node].ec, ac=tgt)
×
1809

1810
        elif num_qargs == 1:
×
1811
            self._gate(node, node_data, glob_data, xy[num_ctrl_qubits:][0])
×
1812

1813
        elif isinstance(base_type, SwapGate):
×
1814
            self._swap(xy[num_ctrl_qubits:], node_data[node].lc)
×
1815

1816
        else:
1817
            self._multiqubit_gate(node, node_data, glob_data, xy[num_ctrl_qubits:])
×
1818

1819
    def _set_ctrl_bits(
1✔
1820
        self, ctrl_state, num_ctrl_qubits, qbit, glob_data, ec=None, tc=None, text="", qargs=None
1821
    ):
1822
        """Determine which qubits are controls and whether they are open or closed"""
1823
        # place the control label at the top or bottom of controls
1824
        if text:
×
1825
            qlist = [self._circuit.find_bit(qubit).index for qubit in qargs]
×
1826
            ctbits = qlist[:num_ctrl_qubits]
×
1827
            qubits = qlist[num_ctrl_qubits:]
×
1828
            max_ctbit = max(ctbits)
×
1829
            min_ctbit = min(ctbits)
×
1830
            top = min(qubits) > min_ctbit
×
1831

1832
        # display the control qubits as open or closed based on ctrl_state
1833
        cstate = f"{ctrl_state:b}".rjust(num_ctrl_qubits, "0")[::-1]
×
1834
        for i in range(num_ctrl_qubits):
×
1835
            fc_open_close = ec if cstate[i] == "1" else self._style["bg"]
×
1836
            text_top = None
×
1837
            if text:
×
1838
                if top and qlist[i] == min_ctbit:
×
1839
                    text_top = True
×
1840
                elif not top and qlist[i] == max_ctbit:
×
1841
                    text_top = False
×
1842
            self._ctrl_qubit(
×
1843
                qbit[i], glob_data, fc=fc_open_close, ec=ec, tc=tc, text=text, text_top=text_top
1844
            )
1845

1846
    def _ctrl_qubit(self, xy, glob_data, fc=None, ec=None, tc=None, text="", text_top=None):
1✔
1847
        """Draw a control circle and if top or bottom control, draw control label"""
1848
        xpos, ypos = xy
×
1849
        box = glob_data["patches_mod"].Circle(
×
1850
            xy=(xpos, ypos),
1851
            radius=WID * 0.15,
1852
            fc=fc,
1853
            ec=ec,
1854
            linewidth=self._lwidth15,
1855
            zorder=PORDER_GATE,
1856
        )
1857
        self._ax.add_patch(box)
×
1858

1859
        # adjust label height according to number of lines of text
1860
        label_padding = 0.7
×
1861
        if text is not None:
×
1862
            text_lines = text.count("\n")
×
1863
            if not text.endswith("(cal)\n"):
×
1864
                for _ in range(text_lines):
×
1865
                    label_padding += 0.3
×
1866

1867
        if text_top is None:
×
1868
            return
×
1869

1870
        # display the control label at the top or bottom if there is one
1871
        ctrl_ypos = ypos + label_padding * HIG if text_top else ypos - 0.3 * HIG
×
1872
        self._ax.text(
×
1873
            xpos,
1874
            ctrl_ypos,
1875
            text,
1876
            ha="center",
1877
            va="top",
1878
            fontsize=self._style["sfs"],
1879
            color=tc,
1880
            clip_on=True,
1881
            zorder=PORDER_TEXT,
1882
        )
1883

1884
    def _x_tgt_qubit(self, xy, glob_data, ec=None, ac=None):
1✔
1885
        """Draw the cnot target symbol"""
1886
        linewidth = self._lwidth2
×
1887
        xpos, ypos = xy
×
1888
        box = glob_data["patches_mod"].Circle(
×
1889
            xy=(xpos, ypos),
1890
            radius=HIG * 0.35,
1891
            fc=ec,
1892
            ec=ec,
1893
            linewidth=linewidth,
1894
            zorder=PORDER_GATE,
1895
        )
1896
        self._ax.add_patch(box)
×
1897

1898
        # add '+' symbol
1899
        self._ax.plot(
×
1900
            [xpos, xpos],
1901
            [ypos - 0.2 * HIG, ypos + 0.2 * HIG],
1902
            color=ac,
1903
            linewidth=linewidth,
1904
            zorder=PORDER_GATE_PLUS,
1905
        )
1906
        self._ax.plot(
×
1907
            [xpos - 0.2 * HIG, xpos + 0.2 * HIG],
1908
            [ypos, ypos],
1909
            color=ac,
1910
            linewidth=linewidth,
1911
            zorder=PORDER_GATE_PLUS,
1912
        )
1913

1914
    def _symmetric_gate(self, node, node_data, base_type, glob_data):
1✔
1915
        """Draw symmetric gates for cz, cu1, cp, and rzz"""
1916
        op = node.op
×
1917
        xy = node_data[node].q_xy
×
1918
        qubit_b = min(xy, key=lambda xy: xy[1])
×
1919
        qubit_t = max(xy, key=lambda xy: xy[1])
×
1920
        base_type = getattr(op, "base_gate", None)
×
1921
        ec = node_data[node].ec
×
1922
        tc = node_data[node].tc
×
1923
        lc = node_data[node].lc
×
1924

1925
        # cz and mcz gates
1926
        if not isinstance(op, ZGate) and isinstance(base_type, ZGate):
×
1927
            num_ctrl_qubits = op.num_ctrl_qubits
×
1928
            self._ctrl_qubit(xy[-1], glob_data, fc=ec, ec=ec, tc=tc)
×
1929
            self._line(qubit_b, qubit_t, lc=lc, zorder=PORDER_LINE_PLUS)
×
1930

1931
        # cu1, cp, rzz, and controlled rzz gates (sidetext gates)
1932
        elif isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, RZZGate)):
×
1933
            num_ctrl_qubits = 0 if isinstance(op, RZZGate) else op.num_ctrl_qubits
×
1934
            gate_text = "P" if isinstance(base_type, PhaseGate) else node_data[node].gate_text
×
1935

1936
            self._ctrl_qubit(xy[num_ctrl_qubits], glob_data, fc=ec, ec=ec, tc=tc)
×
1937
            if not isinstance(base_type, (U1Gate, PhaseGate)):
×
1938
                self._ctrl_qubit(xy[num_ctrl_qubits + 1], glob_data, fc=ec, ec=ec, tc=tc)
×
1939

1940
            self._sidetext(
×
1941
                node,
1942
                node_data,
1943
                qubit_b,
1944
                tc=tc,
1945
                text=f"{gate_text} ({node_data[node].param_text})",
1946
            )
1947
            self._line(qubit_b, qubit_t, lc=lc)
×
1948

1949
    def _swap(self, xy, color=None):
1✔
1950
        """Draw a Swap gate"""
1951
        self._swap_cross(xy[0], color=color)
×
1952
        self._swap_cross(xy[1], color=color)
×
1953
        self._line(xy[0], xy[1], lc=color)
×
1954

1955
    def _swap_cross(self, xy, color=None):
1✔
1956
        """Draw the Swap cross symbol"""
1957
        xpos, ypos = xy
×
1958

1959
        self._ax.plot(
×
1960
            [xpos - 0.20 * WID, xpos + 0.20 * WID],
1961
            [ypos - 0.20 * WID, ypos + 0.20 * WID],
1962
            color=color,
1963
            linewidth=self._lwidth2,
1964
            zorder=PORDER_LINE_PLUS,
1965
        )
1966
        self._ax.plot(
×
1967
            [xpos - 0.20 * WID, xpos + 0.20 * WID],
1968
            [ypos + 0.20 * WID, ypos - 0.20 * WID],
1969
            color=color,
1970
            linewidth=self._lwidth2,
1971
            zorder=PORDER_LINE_PLUS,
1972
        )
1973

1974
    def _sidetext(self, node, node_data, xy, tc=None, text=""):
1✔
1975
        """Draw the sidetext for symmetric gates"""
1976
        xpos, ypos = xy
×
1977

1978
        # 0.11 = the initial gap, add 1/2 text width to place on the right
1979
        xp = xpos + 0.11 + node_data[node].width / 2
×
1980
        self._ax.text(
×
1981
            xp,
1982
            ypos + HIG,
1983
            text,
1984
            ha="center",
1985
            va="top",
1986
            fontsize=self._style["sfs"],
1987
            color=tc,
1988
            clip_on=True,
1989
            zorder=PORDER_TEXT,
1990
        )
1991

1992
    def _line(self, xy0, xy1, lc=None, ls=None, zorder=PORDER_LINE):
1✔
1993
        """Draw a line from xy0 to xy1"""
1994
        x0, y0 = xy0
1✔
1995
        x1, y1 = xy1
1✔
1996
        linecolor = self._style["lc"] if lc is None else lc
1✔
1997
        linestyle = "solid" if ls is None else ls
1✔
1998

1999
        if linestyle == "doublet":
1✔
2000
            theta = np.arctan2(np.abs(x1 - x0), np.abs(y1 - y0))
1✔
2001
            dx = 0.05 * WID * np.cos(theta)
1✔
2002
            dy = 0.05 * WID * np.sin(theta)
1✔
2003
            self._ax.plot(
1✔
2004
                [x0 + dx, x1 + dx],
2005
                [y0 + dy, y1 + dy],
2006
                color=linecolor,
2007
                linewidth=self._lwidth2,
2008
                linestyle="solid",
2009
                zorder=zorder,
2010
            )
2011
            self._ax.plot(
1✔
2012
                [x0 - dx, x1 - dx],
2013
                [y0 - dy, y1 - dy],
2014
                color=linecolor,
2015
                linewidth=self._lwidth2,
2016
                linestyle="solid",
2017
                zorder=zorder,
2018
            )
2019
        else:
2020
            self._ax.plot(
1✔
2021
                [x0, x1],
2022
                [y0, y1],
2023
                color=linecolor,
2024
                linewidth=self._lwidth2,
2025
                linestyle=linestyle,
2026
                zorder=zorder,
2027
            )
2028

2029
    def _plot_coord(self, x_index, y_index, gate_width, glob_data, flow_op=False):
1✔
2030
        """Get the coord positions for an index"""
2031

2032
        # Check folding
2033
        fold = self._fold if self._fold > 0 else INFINITE_FOLD
1✔
2034
        h_pos = x_index % fold + 1
1✔
2035

2036
        # Don't fold flow_ops here, only gates inside the flow_op
2037
        if not flow_op and h_pos + (gate_width - 1) > fold:
1✔
2038
            x_index += fold - (h_pos - 1)
×
2039
        x_pos = x_index % fold + glob_data["x_offset"] + 0.04
1✔
2040
        if not flow_op:
1✔
2041
            x_pos += 0.5 * gate_width
1✔
2042
        else:
2043
            x_pos += 0.25
×
2044
        y_pos = y_index - (x_index // fold) * (glob_data["n_lines"] + 1)
1✔
2045

2046
        # x_index could have been updated, so need to store
2047
        glob_data["next_x_index"] = x_index
1✔
2048
        return x_pos, y_pos
1✔
2049

2050

2051
class NodeData:
1✔
2052
    """Class containing drawing data on a per node basis"""
2053

2054
    def __init__(self):
1✔
2055
        # Node data for positioning
2056
        self.width = 0.0
1✔
2057
        self.x_index = 0
1✔
2058
        self.q_xy = []
1✔
2059
        self.c_xy = []
1✔
2060

2061
        # Node data for text
2062
        self.gate_text = ""
1✔
2063
        self.raw_gate_text = ""
1✔
2064
        self.ctrl_text = ""
1✔
2065
        self.param_text = ""
1✔
2066

2067
        # Node data for color
2068
        self.fc = self.ec = self.lc = self.sc = self.gt = self.tc = 0
1✔
2069

2070
        # Special values stored for ControlFlowOps
2071
        self.nest_depth = 0
1✔
2072
        self.expr_width = 0.0
1✔
2073
        self.expr_text = ""
1✔
2074
        self.inside_flow = False
1✔
2075
        self.indexset = ()  # List of indices used for ForLoopOp
1✔
2076
        self.jump_values = []  # List of jump values used for SwitchCaseOp
1✔
2077
        self.circ_num = 0  # Which block is it in op.blocks
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc