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

Qiskit / qiskit / 13688561003

06 Mar 2025 12:41AM UTC coverage: 88.093% (-0.004%) from 88.097%
13688561003

push

github

web-flow
Add representation of `box` (#13869)

* Add representation of `box`

This adds in the base `box` control-flow construction, with support for
containing instructions and having a literal delay, like the `Delay`
instruction.

This supports basic output to OpenQASM 3, QPY and some rudimentary
support in the text and mpl drawers.  The transpiler largely handles
things already, since control flow is handled generically in most
places.

Known issues:

- We expect this to be able to accept stretches in its duration, just as
  `Delay` can, which will need a follow-up.
- We expect `Box` to support "annotations" in a future release of
  Qiskit.
- There is currently no way in OpenQASM 3 to represent a qubit that is
  idle during a `box` without inserting a magic instruction on it.
- IBM backends don't claim support for `box` yet, so `transpile` against
  a backend will fail, though you can modify the `Target` to add the
  instruction manually.

Add tests of box

* Add QPY backwards-compatibility test

* Add `BoxOp.body` getter and tests

111 of 118 new or added lines in 11 files covered. (94.07%)

18 existing lines in 3 files now uncovered.

75228 of 85396 relevant lines covered (88.09%)

334350.47 hits per line

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

48.96
/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 http://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
# pylint: disable=invalid-name,inconsistent-return-statements
14

15
"""mpl circuit visualization backend."""
16

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

22
import numpy as np
1✔
23

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

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

58
from .qcstyle import load_style
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
    ):
113
        self._circuit = circuit
1✔
114
        self._qubits = qubits
1✔
115
        self._clbits = clbits
1✔
116
        self._nodes = nodes
1✔
117
        self._scale = 1.0 if scale is None else scale
1✔
118

119
        self._style = style
1✔
120

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

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

135
        self._ax = ax
1✔
136

137
        self._initial_state = initial_state
1✔
138
        self._global_phase = self._circuit.global_phase
1✔
139
        self._expr_len = expr_len
1✔
140
        self._cregbundle = cregbundle
1✔
141

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

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

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

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

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

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

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

266
        glob_data["patches_mod"] = patches
1✔
267
        plt_mod = plt
1✔
268

269
        self._style, def_font_ratio = load_style(self._style)
1✔
270

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

275
        # if no user ax, setup default figure. Else use the user figure.
276
        if self._ax is None:
1✔
277
            is_user_ax = False
1✔
278
            mpl_figure = plt.figure()
1✔
279
            mpl_figure.patch.set_facecolor(color=self._style["bg"])
1✔
280
            self._ax = mpl_figure.add_subplot(111)
1✔
281
        else:
282
            is_user_ax = True
×
283
            mpl_figure = self._ax.get_figure()
×
284
        self._ax.axis("off")
1✔
285
        self._ax.set_aspect("equal")
1✔
286
        self._ax.tick_params(labelbottom=False, labeltop=False, labelleft=False, labelright=False)
1✔
287

288
        # All information for the drawing is first loaded into node_data for the gates and into
289
        # qubits_dict, clbits_dict, and wire_map for the qubits, clbits, and wires,
290
        # followed by the coordinates for each gate.
291

292
        # load the wire map
293
        wire_map = get_wire_map(self._circuit, self._qubits + self._clbits, self._cregbundle)
1✔
294

295
        # node_data per node filled with class NodeData attributes
296
        node_data = {}
1✔
297

298
        # dicts for the names and locations of register/bit labels
299
        qubits_dict = {}
1✔
300
        clbits_dict = {}
1✔
301

302
        # load the _qubit_dict and _clbit_dict with register info
303
        self._set_bit_reg_info(wire_map, qubits_dict, clbits_dict, glob_data)
1✔
304

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

308
        # load the coordinates for each top level gate and compute number of folds.
309
        # coordinates for flow gates are loaded before draw_ops
310
        max_x_index = self._get_coords(
1✔
311
            node_data, wire_map, self._circuit, layer_widths, qubits_dict, clbits_dict, glob_data
312
        )
313
        num_folds = max(0, max_x_index - 1) // self._fold if self._fold > 0 else 0
1✔
314

315
        # The window size limits are computed, followed by one of the four possible ways
316
        # of scaling the drawing.
317

318
        # compute the window size
319
        if max_x_index > self._fold > 0:
1✔
320
            xmax = self._fold + glob_data["x_offset"] + 0.1
×
321
            ymax = (num_folds + 1) * (glob_data["n_lines"] + 1) - 1
×
322
        else:
323
            x_incr = 0.4 if not self._nodes else 0.9
1✔
324
            xmax = max_x_index + 1 + glob_data["x_offset"] - x_incr
1✔
325
            ymax = glob_data["n_lines"]
1✔
326

327
        xl = -self._style["margin"][0]
1✔
328
        xr = xmax + self._style["margin"][1]
1✔
329
        yb = -ymax - self._style["margin"][2] + 0.5
1✔
330
        yt = self._style["margin"][3] + 0.5
1✔
331
        self._ax.set_xlim(xl, xr)
1✔
332
        self._ax.set_ylim(yb, yt)
1✔
333

334
        # update figure size and, for backward compatibility,
335
        # need to scale by a default value equal to (self._style["fs"] * 3.01 / 72 / 0.65)
336
        base_fig_w = (xr - xl) * 0.8361111
1✔
337
        base_fig_h = (yt - yb) * 0.8361111
1✔
338
        scale = self._scale
1✔
339

340
        # if user passes in an ax, this size takes priority over any other settings
341
        if is_user_ax:
1✔
342
            # from stackoverflow #19306510, get the bbox size for the ax and then reset scale
343
            bbox = self._ax.get_window_extent().transformed(mpl_figure.dpi_scale_trans.inverted())
×
344
            scale = bbox.width / base_fig_w / 0.8361111
×
345

346
        # if scale not 1.0, use this scale factor
347
        elif self._scale != 1.0:
1✔
348
            mpl_figure.set_size_inches(base_fig_w * self._scale, base_fig_h * self._scale)
×
349

350
        # if "figwidth" style param set, use this to scale
351
        elif self._style["figwidth"] > 0.0:
1✔
352
            # in order to get actual inches, need to scale by factor
353
            adj_fig_w = self._style["figwidth"] * 1.282736
×
354
            mpl_figure.set_size_inches(adj_fig_w, adj_fig_w * base_fig_h / base_fig_w)
×
355
            scale = adj_fig_w / base_fig_w
×
356

357
        # otherwise, display default size
358
        else:
359
            mpl_figure.set_size_inches(base_fig_w, base_fig_h)
1✔
360

361
        # drawing will scale with 'set_size_inches', but fonts and linewidths do not
362
        if scale != 1.0:
1✔
363
            self._style["fs"] *= scale
×
364
            self._style["sfs"] *= scale
×
365
            self._lwidth1 = 1.0 * scale
×
366
            self._lwidth15 = 1.5 * scale
×
367
            self._lwidth2 = 2.0 * scale
×
368
            self._lwidth3 = 3.0 * scale
×
369
            self._lwidth4 = 4.0 * scale
×
370

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

398
    def _get_layer_widths(self, node_data, wire_map, outer_circuit, glob_data):
1✔
399
        """Compute the layer_widths for the layers"""
400

401
        layer_widths = {}
1✔
402
        for layer_num, layer in enumerate(self._nodes):
1✔
403
            widest_box = WID
1✔
404
            for i, node in enumerate(layer):
1✔
405
                # Put the layer_num in the first node in the layer and put -1 in the rest
406
                # so that layer widths are not counted more than once
407
                if i != 0:
1✔
408
                    layer_num = -1
×
409
                layer_widths[node] = [1, layer_num, self._flow_parent]
1✔
410

411
                op = node.op
1✔
412
                node_data[node] = NodeData()
1✔
413
                node_data[node].width = WID
1✔
414
                num_ctrl_qubits = getattr(op, "num_ctrl_qubits", 0)
1✔
415
                if (
1✔
416
                    getattr(op, "_directive", False) and (not op.label or not self._plot_barriers)
417
                ) or isinstance(op, Measure):
418
                    node_data[node].raw_gate_text = op.name
×
419
                    continue
×
420

421
                base_type = getattr(op, "base_gate", None)
1✔
422
                gate_text, ctrl_text, raw_gate_text = get_gate_ctrl_text(
1✔
423
                    op, "mpl", style=self._style
424
                )
425
                node_data[node].gate_text = gate_text
1✔
426
                node_data[node].ctrl_text = ctrl_text
1✔
427
                node_data[node].raw_gate_text = raw_gate_text
1✔
428
                node_data[node].param_text = ""
1✔
429

430
                # if single qubit, no params, and no labels, layer_width is 1
431
                if (
1✔
432
                    (len(node.qargs) - num_ctrl_qubits) == 1
433
                    and len(gate_text) < 3
434
                    and len(getattr(op, "params", [])) == 0
435
                    and ctrl_text is None
436
                ):
437
                    continue
1✔
438

439
                if isinstance(op, SwapGate) or isinstance(base_type, SwapGate):
1✔
440
                    continue
×
441

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

464
                # get gate_width for sidetext symmetric gates
465
                if isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, RZZGate)):
1✔
466
                    if isinstance(base_type, PhaseGate):
×
467
                        gate_text = "P"
×
468
                    raw_gate_width = (
×
469
                        self._get_text_width(
470
                            gate_text + " ()", glob_data, fontsize=self._style["sfs"]
471
                        )
472
                        + raw_param_width
473
                    )
474
                    gate_width = (raw_gate_width + 0.08) * 1.58
×
475

476
                # Check if a ControlFlowOp - node_data load for these gates is done here
477
                elif isinstance(node.op, ControlFlowOp):
1✔
478
                    self._flow_drawers[node] = []
×
479
                    node_data[node].width = []
×
480
                    node_data[node].nest_depth = 0
×
481
                    gate_width = 0.0
×
482
                    expr_width = 0.0
×
483

484
                    if (isinstance(op, SwitchCaseOp) and isinstance(op.target, expr.Expr)) or (
×
485
                        getattr(op, "condition", None) and isinstance(op.condition, expr.Expr)
486
                    ):
487

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

517
                        condition = op.target if isinstance(op, SwitchCaseOp) else op.condition
×
518
                        stream = StringIO()
×
519
                        BasicPrinter(stream, indent="  ").visit(
×
520
                            condition.accept(_ExprBuilder(lookup_var))
521
                        )
522
                        expr_text = stream.getvalue()
×
523
                        # Truncate expr_text so that first gate is no more than about 3 x_index's over
524
                        if len(expr_text) > self._expr_len:
×
525
                            expr_text = expr_text[: self._expr_len] + "..."
×
526
                        node_data[node].expr_text = expr_text
×
527

528
                        expr_width = self._get_text_width(
×
529
                            node_data[node].expr_text, glob_data, fontsize=self._style["sfs"]
530
                        )
531
                        node_data[node].expr_width = int(expr_width)
×
532

533
                    # Get the list of circuits to iterate over from the blocks
534
                    circuit_list = list(node.op.blocks)
×
535

536
                    # params is [indexset, loop_param, circuit] for for_loop,
537
                    # op.cases_specifier() returns jump tuple and circuit for switch/case
538
                    if isinstance(op, ForLoopOp):
×
539
                        node_data[node].indexset = op.params[0]
×
540
                    elif isinstance(op, SwitchCaseOp):
×
541
                        node_data[node].jump_values = []
×
542
                        cases = list(op.cases_specifier())
×
543

544
                        # Create an empty circuit at the head of the circuit_list if a Switch box
545
                        circuit_list.insert(0, cases[0][1].copy_empty_like())
×
546
                        for jump_values, _ in cases:
×
547
                            node_data[node].jump_values.append(jump_values)
×
548

549
                    # Now process the circuits inside the ControlFlowOps
550
                    for circ_num, circuit in enumerate(circuit_list):
×
551
                        # Only add expr_width for if, while, and switch
552
                        raw_gate_width = expr_width if circ_num == 0 else 0.0
×
553

554
                        # Depth of nested ControlFlowOp used for color of box
555
                        if self._flow_parent is not None:
×
556
                            node_data[node].nest_depth = node_data[self._flow_parent].nest_depth + 1
×
557

558
                        # Build the wire_map to be used by this flow op
559
                        flow_wire_map = wire_map.copy()
×
560
                        flow_wire_map.update(
×
561
                            {
562
                                inner: wire_map[outer]
563
                                for outer, inner in zip(node.qargs, circuit.qubits)
564
                            }
565
                        )
566
                        for outer, inner in zip(node.cargs, circuit.clbits):
×
567
                            if self._cregbundle and (
×
568
                                (in_reg := get_bit_register(outer_circuit, inner)) is not None
569
                            ):
570
                                out_reg = get_bit_register(outer_circuit, outer)
×
571
                                flow_wire_map.update({in_reg: wire_map[out_reg]})
×
572
                            else:
573
                                flow_wire_map.update({inner: wire_map[outer]})
×
574

575
                        # Get the layered node lists and instantiate a new drawer class for
576
                        # the circuit inside the ControlFlowOp.
577
                        qubits, clbits, flow_nodes = _get_layered_instructions(
×
578
                            circuit, wire_map=flow_wire_map
579
                        )
580
                        flow_drawer = MatplotlibDrawer(
×
581
                            qubits,
582
                            clbits,
583
                            flow_nodes,
584
                            circuit,
585
                            style=self._style,
586
                            plot_barriers=self._plot_barriers,
587
                            fold=self._fold,
588
                            cregbundle=self._cregbundle,
589
                        )
590

591
                        # flow_parent is the parent of the new class instance
592
                        flow_drawer._flow_parent = node
×
593
                        flow_drawer._flow_wire_map = flow_wire_map
×
594
                        self._flow_drawers[node].append(flow_drawer)
×
595

596
                        # Recursively call _get_layer_widths for the circuit inside the ControlFlowOp
597
                        flow_widths = flow_drawer._get_layer_widths(
×
598
                            node_data, flow_wire_map, outer_circuit, glob_data
599
                        )
600
                        layer_widths.update(flow_widths)
×
601

602
                        for flow_layer in flow_nodes:
×
603
                            for flow_node in flow_layer:
×
604
                                node_data[flow_node].circ_num = circ_num
×
605

606
                        # Add up the width values of the same flow_parent that are not -1
607
                        # to get the raw_gate_width
608
                        for width, layer_num, flow_parent in flow_widths.values():
×
609
                            if layer_num != -1 and flow_parent == flow_drawer._flow_parent:
×
610
                                raw_gate_width += width
×
611

612
                        # Need extra incr of 1.0 for else and case boxes
613
                        gate_width += raw_gate_width + (1.0 if circ_num > 0 else 0.0)
×
614

615
                        # Minor adjustment so else and case section gates align with indexes
616
                        if circ_num > 0:
×
617
                            raw_gate_width += 0.045
×
618

619
                        # If expr_width has a value, remove the decimal portion from raw_gate_widthl
620
                        if not isinstance(op, ForLoopOp) and circ_num == 0:
×
621
                            node_data[node].width.append(raw_gate_width - (expr_width % 1))
×
622
                        else:
623
                            node_data[node].width.append(raw_gate_width)
×
624

625
                # Otherwise, standard gate or multiqubit gate
626
                else:
627
                    raw_gate_width = self._get_text_width(
1✔
628
                        gate_text, glob_data, fontsize=self._style["fs"]
629
                    )
630
                    gate_width = raw_gate_width + 0.10
1✔
631
                    # add .21 for the qubit numbers on the left of the multibit gates
632
                    if len(node.qargs) - num_ctrl_qubits > 1:
1✔
633
                        gate_width += 0.21
×
634

635
                box_width = max(gate_width, ctrl_width, param_width, WID)
1✔
636
                if box_width > widest_box:
1✔
637
                    widest_box = box_width
1✔
638
                if not isinstance(node.op, ControlFlowOp):
1✔
639
                    node_data[node].width = max(raw_gate_width, raw_param_width)
1✔
640
            for node in layer:
1✔
641
                layer_widths[node][0] = int(widest_box) + 1
1✔
642

643
        return layer_widths
1✔
644

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

648
        longest_wire_label_width = 0
1✔
649
        glob_data["n_lines"] = 0
1✔
650
        initial_qbit = r" $|0\rangle$" if self._initial_state else ""
1✔
651
        initial_cbit = " 0" if self._initial_state else ""
1✔
652

653
        idx = 0
1✔
654
        pos = y_off = -len(self._qubits) + 1
1✔
655
        for ii, wire in enumerate(wire_map):
1✔
656
            # if it's a creg, register is the key and just load the index
657
            if isinstance(wire, ClassicalRegister):
1✔
658
                # If wire came from ControlFlowOp and not in clbits, don't draw it
659
                if wire[0] not in self._clbits:
×
660
                    continue
×
661
                register = wire
×
662
                index = wire_map[wire]
×
663

664
            # otherwise, get the register from find_bit and use bit_index if
665
            # it's a bit, or the index of the bit in the register if it's a reg
666
            else:
667
                # If wire came from ControlFlowOp and not in qubits or clbits, don't draw it
668
                if wire not in self._qubits + self._clbits:
1✔
669
                    continue
×
670
                register, bit_index, reg_index = get_bit_reg_index(self._circuit, wire)
1✔
671
                index = bit_index if register is None else reg_index
1✔
672

673
            wire_label = get_wire_label(
1✔
674
                "mpl", register, index, layout=self._layout, cregbundle=self._cregbundle
675
            )
676
            initial_bit = initial_qbit if isinstance(wire, Qubit) else initial_cbit
1✔
677

678
            # for cregs with cregbundle on, don't use math formatting, which means
679
            # no italics
680
            if isinstance(wire, Qubit) or register is None or not self._cregbundle:
1✔
681
                wire_label = "$" + wire_label + "$"
1✔
682
            wire_label += initial_bit
1✔
683

684
            reg_size = (
1✔
685
                0 if register is None or isinstance(wire, ClassicalRegister) else register.size
686
            )
687
            reg_remove_under = 0 if reg_size < 2 else 1
1✔
688
            text_width = (
1✔
689
                self._get_text_width(
690
                    wire_label, glob_data, self._style["fs"], reg_remove_under=reg_remove_under
691
                )
692
                * 1.15
693
            )
694
            if text_width > longest_wire_label_width:
1✔
695
                longest_wire_label_width = text_width
1✔
696

697
            if isinstance(wire, Qubit):
1✔
698
                pos = -ii
1✔
699
                qubits_dict[ii] = {
1✔
700
                    "y": pos,
701
                    "wire_label": wire_label,
702
                }
703
                glob_data["n_lines"] += 1
1✔
704
            else:
705
                if (
1✔
706
                    not self._cregbundle
707
                    or register is None
708
                    or (self._cregbundle and isinstance(wire, ClassicalRegister))
709
                ):
710
                    glob_data["n_lines"] += 1
1✔
711
                    idx += 1
1✔
712

713
                pos = y_off - idx
1✔
714
                clbits_dict[ii] = {
1✔
715
                    "y": pos,
716
                    "wire_label": wire_label,
717
                    "register": register,
718
                }
719
        glob_data["x_offset"] = -1.2 + longest_wire_label_width
1✔
720

721
    def _get_coords(
1✔
722
        self,
723
        node_data,
724
        wire_map,
725
        outer_circuit,
726
        layer_widths,
727
        qubits_dict,
728
        clbits_dict,
729
        glob_data,
730
        flow_parent=None,
731
    ):
732
        """Load all the coordinate info needed to place the gates on the drawing."""
733

734
        prev_x_index = -1
1✔
735
        for layer in self._nodes:
1✔
736
            curr_x_index = prev_x_index + 1
1✔
737
            l_width = []
1✔
738
            for node in layer:
1✔
739
                # For gates inside a flow op set the x_index and if it's an else or case,
740
                # increment by if/switch width. If more cases increment by width of previous cases.
741
                if flow_parent is not None:
1✔
742
                    node_data[node].inside_flow = True
×
743
                    node_data[node].x_index = node_data[flow_parent].x_index + curr_x_index + 1
×
744
                    # If an else or case
745
                    if node_data[node].circ_num > 0:
×
746
                        for width in node_data[flow_parent].width[: node_data[node].circ_num]:
×
747
                            node_data[node].x_index += int(width) + 1
×
748
                        x_index = node_data[node].x_index
×
749
                    # Add expr_width to if, while, or switch if expr used
750
                    else:
751
                        x_index = node_data[node].x_index + node_data[flow_parent].expr_width
×
752
                else:
753
                    node_data[node].inside_flow = False
1✔
754
                    x_index = curr_x_index
1✔
755

756
                # get qubit indexes
757
                q_indxs = []
1✔
758
                for qarg in node.qargs:
1✔
759
                    if qarg in self._qubits:
1✔
760
                        q_indxs.append(wire_map[qarg])
1✔
761

762
                # get clbit indexes
763
                c_indxs = []
1✔
764
                for carg in node.cargs:
1✔
765
                    if carg in self._clbits:
1✔
766
                        if self._cregbundle:
1✔
767
                            register = get_bit_register(outer_circuit, carg)
×
768
                            if register is not None:
×
769
                                c_indxs.append(wire_map[register])
×
770
                            else:
771
                                c_indxs.append(wire_map[carg])
×
772
                        else:
773
                            c_indxs.append(wire_map[carg])
1✔
774

775
                flow_op = isinstance(node.op, ControlFlowOp)
1✔
776

777
                # qubit coordinates
778
                node_data[node].q_xy = [
1✔
779
                    self._plot_coord(
780
                        x_index,
781
                        qubits_dict[ii]["y"],
782
                        layer_widths[node][0],
783
                        glob_data,
784
                        flow_op,
785
                    )
786
                    for ii in q_indxs
787
                ]
788
                # clbit coordinates
789
                node_data[node].c_xy = [
1✔
790
                    self._plot_coord(
791
                        x_index,
792
                        clbits_dict[ii]["y"],
793
                        layer_widths[node][0],
794
                        glob_data,
795
                        flow_op,
796
                    )
797
                    for ii in c_indxs
798
                ]
799

800
                # update index based on the value from plotting
801
                if flow_parent is None:
1✔
802
                    curr_x_index = glob_data["next_x_index"]
1✔
803
                l_width.append(layer_widths[node][0])
1✔
804
                node_data[node].x_index = x_index
1✔
805

806
                # Special case of default case with no ops in it, need to push end
807
                # of switch op one extra x_index
808
                if isinstance(node.op, SwitchCaseOp):
1✔
809
                    if len(node.op.blocks[-1]) == 0:
×
810
                        curr_x_index += 1
×
811

812
            # adjust the column if there have been barriers encountered, but not plotted
813
            barrier_offset = 0
1✔
814
            if not self._plot_barriers:
1✔
815
                # only adjust if everything in the layer wasn't plotted
816
                barrier_offset = (
×
817
                    -1 if all(getattr(nd.op, "_directive", False) for nd in layer) else 0
818
                )
819
            max_lwidth = max(l_width) if l_width else 0
1✔
820
            prev_x_index = curr_x_index + max_lwidth + barrier_offset - 1
1✔
821

822
        return prev_x_index + 1
1✔
823

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

827
        from pylatexenc.latex2text import LatexNodes2Text
1✔
828

829
        if not text:
1✔
830
            return 0.0
1✔
831

832
        math_mode_match = self._mathmode_regex.search(text)
1✔
833
        num_underscores = 0
1✔
834
        num_carets = 0
1✔
835
        if math_mode_match:
1✔
836
            math_mode_text = math_mode_match.group(1)
1✔
837
            num_underscores = math_mode_text.count("_")
1✔
838
            num_carets = math_mode_text.count("^")
1✔
839
        text = LatexNodes2Text().latex_to_text(text.replace("$$", ""))
1✔
840

841
        # If there are subscripts or superscripts in mathtext string
842
        # we need to account for that spacing by manually removing
843
        # from text string for text length
844

845
        # if it's a register and there's a subscript at the end,
846
        # remove 1 underscore, otherwise don't remove any
847
        if reg_remove_under is not None:
1✔
848
            num_underscores = reg_remove_under
1✔
849
        if num_underscores:
1✔
850
            text = text.replace("_", "", num_underscores)
1✔
851
        if num_carets:
1✔
852
            text = text.replace("^", "", num_carets)
×
853

854
        # This changes hyphen to + to match width of math mode minus sign.
855
        if param:
1✔
856
            text = text.replace("-", "+")
×
857

858
        f = 0 if fontsize == self._style["fs"] else 1
1✔
859
        sum_text = 0.0
1✔
860
        for c in text:
1✔
861
            try:
1✔
862
                sum_text += self._char_list[c][f]
1✔
863
            except KeyError:
×
864
                # if non-ASCII char, use width of 'c', an average size
865
                sum_text += self._char_list["c"][f]
×
866
        if f == 1:
1✔
867
            sum_text *= glob_data["subfont_factor"]
×
868
        return sum_text
1✔
869

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

873
        for fold_num in range(num_folds + 1):
1✔
874
            # quantum registers
875
            for qubit in qubits_dict.values():
1✔
876
                qubit_label = qubit["wire_label"]
1✔
877
                y = qubit["y"] - fold_num * (glob_data["n_lines"] + 1)
1✔
878
                self._ax.text(
1✔
879
                    glob_data["x_offset"] - 0.2,
880
                    y,
881
                    qubit_label,
882
                    ha="right",
883
                    va="center",
884
                    fontsize=1.25 * self._style["fs"],
885
                    color=self._style["tc"],
886
                    clip_on=True,
887
                    zorder=PORDER_TEXT,
888
                )
889
                # draw the qubit wire
890
                self._line([glob_data["x_offset"], y], [xmax, y], zorder=PORDER_REGLINE)
1✔
891

892
            # classical registers
893
            this_clbit_dict = {}
1✔
894
            for clbit in clbits_dict.values():
1✔
895
                y = clbit["y"] - fold_num * (glob_data["n_lines"] + 1)
1✔
896
                if y not in this_clbit_dict:
1✔
897
                    this_clbit_dict[y] = {
1✔
898
                        "val": 1,
899
                        "wire_label": clbit["wire_label"],
900
                        "register": clbit["register"],
901
                    }
902
                else:
903
                    this_clbit_dict[y]["val"] += 1
×
904

905
            for y, this_clbit in this_clbit_dict.items():
1✔
906
                # cregbundle
907
                if self._cregbundle and this_clbit["register"] is not None:
1✔
908
                    self._ax.plot(
×
909
                        [glob_data["x_offset"] + 0.2, glob_data["x_offset"] + 0.3],
910
                        [y - 0.1, y + 0.1],
911
                        color=self._style["cc"],
912
                        zorder=PORDER_REGLINE,
913
                    )
914
                    self._ax.text(
×
915
                        glob_data["x_offset"] + 0.1,
916
                        y + 0.1,
917
                        str(this_clbit["register"].size),
918
                        ha="left",
919
                        va="bottom",
920
                        fontsize=0.8 * self._style["fs"],
921
                        color=self._style["tc"],
922
                        clip_on=True,
923
                        zorder=PORDER_TEXT,
924
                    )
925
                self._ax.text(
1✔
926
                    glob_data["x_offset"] - 0.2,
927
                    y,
928
                    this_clbit["wire_label"],
929
                    ha="right",
930
                    va="center",
931
                    fontsize=1.25 * self._style["fs"],
932
                    color=self._style["tc"],
933
                    clip_on=True,
934
                    zorder=PORDER_TEXT,
935
                )
936
                # draw the clbit wire
937
                self._line(
1✔
938
                    [glob_data["x_offset"], y],
939
                    [xmax, y],
940
                    lc=self._style["cc"],
941
                    ls=self._style["cline"],
942
                    zorder=PORDER_REGLINE,
943
                )
944

945
            # lf vertical line at either end
946
            feedline_r = num_folds > 0 and num_folds > fold_num
1✔
947
            feedline_l = fold_num > 0
1✔
948
            if feedline_l or feedline_r:
1✔
949
                xpos_l = glob_data["x_offset"] - 0.01
×
950
                xpos_r = self._fold + glob_data["x_offset"] + 0.1
×
951
                ypos1 = -fold_num * (glob_data["n_lines"] + 1)
×
952
                ypos2 = -(fold_num + 1) * (glob_data["n_lines"]) - fold_num + 1
×
953
                if feedline_l:
×
954
                    self._ax.plot(
×
955
                        [xpos_l, xpos_l],
956
                        [ypos1, ypos2],
957
                        color=self._style["lc"],
958
                        linewidth=self._lwidth15,
959
                        zorder=PORDER_REGLINE,
960
                    )
961
                if feedline_r:
×
962
                    self._ax.plot(
×
963
                        [xpos_r, xpos_r],
964
                        [ypos1, ypos2],
965
                        color=self._style["lc"],
966
                        linewidth=self._lwidth15,
967
                        zorder=PORDER_REGLINE,
968
                    )
969
            # Mask off any lines or boxes in the bit label area to clean up
970
            # from folding for ControlFlow and other wrapping gates
971
            box = glob_data["patches_mod"].Rectangle(
1✔
972
                xy=(glob_data["x_offset"] - 0.1, -fold_num * (glob_data["n_lines"] + 1) + 0.5),
973
                width=-25.0,
974
                height=-(fold_num + 1) * (glob_data["n_lines"] + 1),
975
                fc=self._style["bg"],
976
                ec=self._style["bg"],
977
                linewidth=self._lwidth15,
978
                zorder=PORDER_MASK,
979
            )
980
            self._ax.add_patch(box)
1✔
981

982
        # draw index number
983
        if self._style["index"]:
1✔
984
            for layer_num in range(max_x_index):
×
985
                if self._fold > 0:
×
986
                    x_coord = layer_num % self._fold + glob_data["x_offset"] + 0.53
×
987
                    y_coord = -(layer_num // self._fold) * (glob_data["n_lines"] + 1) + 0.65
×
988
                else:
989
                    x_coord = layer_num + glob_data["x_offset"] + 0.53
×
990
                    y_coord = 0.65
×
991
                self._ax.text(
×
992
                    x_coord,
993
                    y_coord,
994
                    str(layer_num + 1),
995
                    ha="center",
996
                    va="center",
997
                    fontsize=self._style["sfs"],
998
                    color=self._style["tc"],
999
                    clip_on=True,
1000
                    zorder=PORDER_TEXT,
1001
                )
1002

1003
    def _add_nodes_and_coords(
1✔
1004
        self,
1005
        nodes,
1006
        node_data,
1007
        wire_map,
1008
        outer_circuit,
1009
        layer_widths,
1010
        qubits_dict,
1011
        clbits_dict,
1012
        glob_data,
1013
    ):
1014
        """Add the nodes from ControlFlowOps and their coordinates to the main circuit"""
1015
        for flow_drawers in self._flow_drawers.values():
1✔
1016
            for flow_drawer in flow_drawers:
×
1017
                nodes += flow_drawer._nodes
×
1018
                flow_drawer._get_coords(
×
1019
                    node_data,
1020
                    flow_drawer._flow_wire_map,
1021
                    outer_circuit,
1022
                    layer_widths,
1023
                    qubits_dict,
1024
                    clbits_dict,
1025
                    glob_data,
1026
                    flow_parent=flow_drawer._flow_parent,
1027
                )
1028
                # Recurse for ControlFlowOps inside the flow_drawer
1029
                flow_drawer._add_nodes_and_coords(
×
1030
                    nodes,
1031
                    node_data,
1032
                    wire_map,
1033
                    outer_circuit,
1034
                    layer_widths,
1035
                    qubits_dict,
1036
                    clbits_dict,
1037
                    glob_data,
1038
                )
1039

1040
    def _draw_ops(
1✔
1041
        self,
1042
        nodes,
1043
        node_data,
1044
        wire_map,
1045
        outer_circuit,
1046
        layer_widths,
1047
        qubits_dict,
1048
        clbits_dict,
1049
        glob_data,
1050
        verbose=False,
1051
    ):
1052
        """Draw the gates in the circuit"""
1053

1054
        # Add the nodes from all the ControlFlowOps and their coordinates to the main nodes
1055
        self._add_nodes_and_coords(
1✔
1056
            nodes,
1057
            node_data,
1058
            wire_map,
1059
            outer_circuit,
1060
            layer_widths,
1061
            qubits_dict,
1062
            clbits_dict,
1063
            glob_data,
1064
        )
1065
        prev_x_index = -1
1✔
1066
        for layer in nodes:
1✔
1067
            l_width = []
1✔
1068
            curr_x_index = prev_x_index + 1
1✔
1069

1070
            # draw the gates in this layer
1071
            for node in layer:
1✔
1072
                op = node.op
1✔
1073

1074
                self._get_colors(node, node_data)
1✔
1075

1076
                if verbose:
1✔
1077
                    print(op)  # pylint: disable=bad-builtin
×
1078

1079
                # add conditional
1080
                if getattr(op, "condition", None) or isinstance(op, SwitchCaseOp):
1✔
1081
                    cond_xy = [
×
1082
                        self._plot_coord(
1083
                            node_data[node].x_index,
1084
                            clbits_dict[ii]["y"],
1085
                            layer_widths[node][0],
1086
                            glob_data,
1087
                            isinstance(op, ControlFlowOp),
1088
                        )
1089
                        for ii in clbits_dict
1090
                    ]
1091
                    self._condition(node, node_data, wire_map, outer_circuit, cond_xy, glob_data)
×
1092

1093
                # AnnotatedOperation with ControlModifier
1094
                mod_control = None
1✔
1095
                if getattr(op, "modifiers", None):
1✔
1096
                    canonical_modifiers = _canonicalize_modifiers(op.modifiers)
×
1097
                    for modifier in canonical_modifiers:
×
1098
                        if isinstance(modifier, ControlModifier):
×
1099
                            mod_control = modifier
×
1100
                            break
×
1101

1102
                # draw measure
1103
                if isinstance(op, Measure):
1✔
1104
                    self._measure(node, node_data, outer_circuit, glob_data)
×
1105

1106
                # draw barriers, snapshots, etc.
1107
                elif getattr(op, "_directive", False):
1✔
1108
                    if self._plot_barriers:
×
1109
                        self._barrier(node, node_data, glob_data)
×
1110

1111
                # draw the box for control flow circuits
1112
                elif isinstance(op, ControlFlowOp):
1✔
1113
                    self._flow_op_gate(node, node_data, glob_data)
×
1114

1115
                # draw single qubit gates
1116
                elif len(node_data[node].q_xy) == 1 and not node.cargs:
1✔
1117
                    self._gate(node, node_data, glob_data)
1✔
1118

1119
                # draw controlled gates
1120
                elif isinstance(op, ControlledGate) or mod_control:
1✔
1121
                    self._control_gate(node, node_data, glob_data, mod_control)
×
1122

1123
                # draw multi-qubit gate as final default
1124
                else:
1125
                    self._multiqubit_gate(node, node_data, glob_data)
1✔
1126

1127
                # Determine the max width of the circuit only at the top level
1128
                if not node_data[node].inside_flow:
1✔
1129
                    l_width.append(layer_widths[node][0])
1✔
1130

1131
            # adjust the column if there have been barriers encountered, but not plotted
1132
            barrier_offset = 0
1✔
1133
            if not self._plot_barriers:
1✔
1134
                # only adjust if everything in the layer wasn't plotted
1135
                barrier_offset = (
×
1136
                    -1 if all(getattr(nd.op, "_directive", False) for nd in layer) else 0
1137
                )
1138
            prev_x_index = curr_x_index + (max(l_width) if l_width else 0) + barrier_offset - 1
1✔
1139

1140
    def _get_colors(self, node, node_data):
1✔
1141
        """Get all the colors needed for drawing the circuit"""
1142

1143
        op = node.op
1✔
1144
        base_name = getattr(getattr(op, "base_gate", None), "name", None)
1✔
1145
        color = None
1✔
1146
        if node_data[node].raw_gate_text in self._style["dispcol"]:
1✔
1147
            color = self._style["dispcol"][node_data[node].raw_gate_text]
1✔
1148
        elif op.name in self._style["dispcol"]:
1✔
1149
            color = self._style["dispcol"][op.name]
×
1150
        if color is not None:
1✔
1151
            # Backward compatibility for style dict using 'displaycolor' with
1152
            # gate color and no text color, so test for str first
1153
            if isinstance(color, str):
1✔
1154
                fc = color
×
1155
                gt = self._style["gt"]
×
1156
            else:
1157
                fc = color[0]
1✔
1158
                gt = color[1]
1✔
1159
        # Treat special case of classical gates in iqx style by making all
1160
        # controlled gates of x, dcx, and swap the classical gate color
1161
        elif self._style["name"] in ["iqp", "iqx", "iqp-dark", "iqx-dark"] and base_name in [
1✔
1162
            "x",
1163
            "dcx",
1164
            "swap",
1165
        ]:
1166
            color = self._style["dispcol"][base_name]
×
1167
            if isinstance(color, str):
×
1168
                fc = color
×
1169
                gt = self._style["gt"]
×
1170
            else:
1171
                fc = color[0]
×
1172
                gt = color[1]
×
1173
        else:
1174
            fc = self._style["gc"]
1✔
1175
            gt = self._style["gt"]
1✔
1176

1177
        if self._style["name"] == "bw":
1✔
1178
            ec = self._style["ec"]
×
1179
            lc = self._style["lc"]
×
1180
        else:
1181
            ec = fc
1✔
1182
            lc = fc
1✔
1183
        # Subtext needs to be same color as gate text
1184
        sc = gt
1✔
1185
        node_data[node].fc = fc
1✔
1186
        node_data[node].ec = ec
1✔
1187
        node_data[node].gt = gt
1✔
1188
        node_data[node].tc = self._style["tc"]
1✔
1189
        node_data[node].sc = sc
1✔
1190
        node_data[node].lc = lc
1✔
1191

1192
    def _condition(self, node, node_data, wire_map, outer_circuit, cond_xy, glob_data):
1✔
1193
        """Add a conditional to a gate"""
1194

1195
        # For SwitchCaseOp convert the target to a fully closed Clbit or register
1196
        # in condition format
1197
        if isinstance(node.op, SwitchCaseOp):
×
1198
            if isinstance(node.op.target, expr.Expr):
×
1199
                condition = node.op.target
×
1200
            elif isinstance(node.op.target, Clbit):
×
1201
                condition = (node.op.target, 1)
×
1202
            else:
1203
                condition = (node.op.target, 2 ** (node.op.target.size) - 1)
×
1204
        else:
1205
            condition = node.op.condition
×
1206

1207
        override_fc = False
×
1208
        first_clbit = len(self._qubits)
×
1209
        cond_pos = []
×
1210

1211
        if isinstance(condition, expr.Expr):
×
1212
            # If fixing this, please update the docstrings of `QuantumCircuit.draw` and
1213
            # `visualization.circuit_drawer` to remove warnings.
1214

1215
            condition_bits = condition_resources(condition).clbits
×
1216
            label = "[expr]"
×
1217
            override_fc = True
×
1218
            registers = collections.defaultdict(list)
×
1219
            for bit in condition_bits:
×
1220
                registers[get_bit_register(outer_circuit, bit)].append(bit)
×
1221
            # Registerless bits don't care whether cregbundle is set.
1222
            cond_pos.extend(cond_xy[wire_map[bit] - first_clbit] for bit in registers.pop(None, ()))
×
1223
            if self._cregbundle:
×
1224
                cond_pos.extend(cond_xy[wire_map[register] - first_clbit] for register in registers)
×
1225
            else:
1226
                cond_pos.extend(
×
1227
                    cond_xy[wire_map[bit] - first_clbit]
1228
                    for bit in itertools.chain.from_iterable(registers.values())
1229
                )
1230
            val_bits = ["1"] * len(cond_pos)
×
1231
        else:
1232
            label, val_bits = get_condition_label_val(condition, self._circuit, self._cregbundle)
×
1233
            cond_bit_reg = condition[0]
×
1234
            cond_bit_val = int(condition[1])
×
1235
            override_fc = (
×
1236
                cond_bit_val != 0
1237
                and self._cregbundle
1238
                and isinstance(cond_bit_reg, ClassicalRegister)
1239
            )
1240

1241
            # In the first case, multiple bits are indicated on the drawing. In all
1242
            # other cases, only one bit is shown.
1243
            if not self._cregbundle and isinstance(cond_bit_reg, ClassicalRegister):
×
1244
                for idx in range(cond_bit_reg.size):
×
1245
                    cond_pos.append(cond_xy[wire_map[cond_bit_reg[idx]] - first_clbit])
×
1246

1247
            # If it's a register bit and cregbundle, need to use the register to find the location
1248
            elif self._cregbundle and isinstance(cond_bit_reg, Clbit):
×
1249
                register = get_bit_register(outer_circuit, cond_bit_reg)
×
1250
                if register is not None:
×
1251
                    cond_pos.append(cond_xy[wire_map[register] - first_clbit])
×
1252
                else:
1253
                    cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit])
×
1254
            else:
1255
                cond_pos.append(cond_xy[wire_map[cond_bit_reg] - first_clbit])
×
1256

1257
        xy_plot = []
×
1258
        for val_bit, xy in zip(val_bits, cond_pos):
×
1259
            fc = self._style["lc"] if override_fc or val_bit == "1" else self._style["bg"]
×
1260
            box = glob_data["patches_mod"].Circle(
×
1261
                xy=xy,
1262
                radius=WID * 0.15,
1263
                fc=fc,
1264
                ec=self._style["lc"],
1265
                linewidth=self._lwidth15,
1266
                zorder=PORDER_GATE,
1267
            )
1268
            self._ax.add_patch(box)
×
1269
            xy_plot.append(xy)
×
1270

1271
        if not xy_plot:
×
1272
            # Expression that's only on new-style `expr.Var` nodes, and doesn't need any vertical
1273
            # line drawing.
1274
            return
×
1275

1276
        qubit_b = min(node_data[node].q_xy, key=lambda xy: xy[1])
×
1277
        clbit_b = min(xy_plot, key=lambda xy: xy[1])
×
1278

1279
        # For IfElseOp, WhileLoopOp or SwitchCaseOp, place the condition line
1280
        # near the left edge of the box
1281
        if isinstance(node.op, (IfElseOp, WhileLoopOp, SwitchCaseOp)):
×
1282
            qubit_b = (qubit_b[0], qubit_b[1] - (0.5 * HIG + 0.14))
×
1283

1284
        # display the label at the bottom of the lowest conditional and draw the double line
1285
        xpos, ypos = clbit_b
×
1286
        if isinstance(node.op, Measure):
×
1287
            xpos += 0.3
×
1288
        self._ax.text(
×
1289
            xpos,
1290
            ypos - 0.3 * HIG,
1291
            label,
1292
            ha="center",
1293
            va="top",
1294
            fontsize=self._style["sfs"],
1295
            color=self._style["tc"],
1296
            clip_on=True,
1297
            zorder=PORDER_TEXT,
1298
        )
1299
        self._line(qubit_b, clbit_b, lc=self._style["cc"], ls=self._style["cline"])
×
1300

1301
    def _measure(self, node, node_data, outer_circuit, glob_data):
1✔
1302
        """Draw the measure symbol and the line to the clbit"""
1303
        qx, qy = node_data[node].q_xy[0]
×
1304
        cx, cy = node_data[node].c_xy[0]
×
1305
        register, _, reg_index = get_bit_reg_index(outer_circuit, node.cargs[0])
×
1306

1307
        # draw gate box
1308
        self._gate(node, node_data, glob_data)
×
1309

1310
        # add measure symbol
1311
        arc = glob_data["patches_mod"].Arc(
×
1312
            xy=(qx, qy - 0.15 * HIG),
1313
            width=WID * 0.7,
1314
            height=HIG * 0.7,
1315
            theta1=0,
1316
            theta2=180,
1317
            fill=False,
1318
            ec=node_data[node].gt,
1319
            linewidth=self._lwidth2,
1320
            zorder=PORDER_GATE,
1321
        )
1322
        self._ax.add_patch(arc)
×
1323
        self._ax.plot(
×
1324
            [qx, qx + 0.35 * WID],
1325
            [qy - 0.15 * HIG, qy + 0.20 * HIG],
1326
            color=node_data[node].gt,
1327
            linewidth=self._lwidth2,
1328
            zorder=PORDER_GATE,
1329
        )
1330
        # arrow
1331
        self._line(
×
1332
            node_data[node].q_xy[0],
1333
            [cx, cy + 0.35 * WID],
1334
            lc=self._style["cc"],
1335
            ls=self._style["cline"],
1336
        )
1337
        arrowhead = glob_data["patches_mod"].Polygon(
×
1338
            (
1339
                (cx - 0.20 * WID, cy + 0.35 * WID),
1340
                (cx + 0.20 * WID, cy + 0.35 * WID),
1341
                (cx, cy + 0.04),
1342
            ),
1343
            fc=self._style["cc"],
1344
            ec=None,
1345
        )
1346
        self._ax.add_artist(arrowhead)
×
1347
        # target
1348
        if self._cregbundle and register is not None:
×
1349
            self._ax.text(
×
1350
                cx + 0.25,
1351
                cy + 0.1,
1352
                str(reg_index),
1353
                ha="left",
1354
                va="bottom",
1355
                fontsize=0.8 * self._style["fs"],
1356
                color=self._style["tc"],
1357
                clip_on=True,
1358
                zorder=PORDER_TEXT,
1359
            )
1360

1361
    def _barrier(self, node, node_data, glob_data):
1✔
1362
        """Draw a barrier"""
1363
        for i, xy in enumerate(node_data[node].q_xy):
×
1364
            xpos, ypos = xy
×
1365
            # For the topmost barrier, reduce the rectangle if there's a label to allow for the text.
1366
            if i == 0 and node.op.label is not None:
×
1367
                ypos_adj = -0.35
×
1368
            else:
1369
                ypos_adj = 0.0
×
1370
            self._ax.plot(
×
1371
                [xpos, xpos],
1372
                [ypos + 0.5 + ypos_adj, ypos - 0.5],
1373
                linewidth=self._lwidth1,
1374
                linestyle="dashed",
1375
                color=self._style["lc"],
1376
                zorder=PORDER_TEXT,
1377
            )
1378
            box = glob_data["patches_mod"].Rectangle(
×
1379
                xy=(xpos - (0.3 * WID), ypos - 0.5),
1380
                width=0.6 * WID,
1381
                height=1.0 + ypos_adj,
1382
                fc=self._style["bc"],
1383
                ec=None,
1384
                alpha=0.6,
1385
                linewidth=self._lwidth15,
1386
                zorder=PORDER_BARRIER,
1387
            )
1388
            self._ax.add_patch(box)
×
1389

1390
            # display the barrier label at the top if there is one
1391
            if i == 0 and node.op.label is not None:
×
1392
                dir_ypos = ypos + 0.65 * HIG
×
1393
                self._ax.text(
×
1394
                    xpos,
1395
                    dir_ypos,
1396
                    node.op.label,
1397
                    ha="center",
1398
                    va="top",
1399
                    fontsize=self._style["fs"],
1400
                    color=node_data[node].tc,
1401
                    clip_on=True,
1402
                    zorder=PORDER_TEXT,
1403
                )
1404

1405
    def _gate(self, node, node_data, glob_data, xy=None):
1✔
1406
        """Draw a 1-qubit gate"""
1407
        if xy is None:
1✔
1408
            xy = node_data[node].q_xy[0]
1✔
1409
        xpos, ypos = xy
1✔
1410
        wid = max(node_data[node].width, WID)
1✔
1411

1412
        box = glob_data["patches_mod"].Rectangle(
1✔
1413
            xy=(xpos - 0.5 * wid, ypos - 0.5 * HIG),
1414
            width=wid,
1415
            height=HIG,
1416
            fc=node_data[node].fc,
1417
            ec=node_data[node].ec,
1418
            linewidth=self._lwidth15,
1419
            zorder=PORDER_GATE,
1420
        )
1421
        self._ax.add_patch(box)
1✔
1422

1423
        if node_data[node].gate_text:
1✔
1424
            gate_ypos = ypos
1✔
1425
            if node_data[node].param_text:
1✔
1426
                gate_ypos = ypos + 0.15 * HIG
×
1427
                self._ax.text(
×
1428
                    xpos,
1429
                    ypos - 0.3 * HIG,
1430
                    node_data[node].param_text,
1431
                    ha="center",
1432
                    va="center",
1433
                    fontsize=self._style["sfs"],
1434
                    color=node_data[node].sc,
1435
                    clip_on=True,
1436
                    zorder=PORDER_TEXT,
1437
                )
1438
            self._ax.text(
1✔
1439
                xpos,
1440
                gate_ypos,
1441
                node_data[node].gate_text,
1442
                ha="center",
1443
                va="center",
1444
                fontsize=self._style["fs"],
1445
                color=node_data[node].gt,
1446
                clip_on=True,
1447
                zorder=PORDER_TEXT,
1448
            )
1449

1450
    def _multiqubit_gate(self, node, node_data, glob_data, xy=None):
1✔
1451
        """Draw a gate covering more than one qubit"""
1452
        op = node.op
1✔
1453
        if xy is None:
1✔
1454
            xy = node_data[node].q_xy
1✔
1455

1456
        # Swap gate
1457
        if isinstance(op, SwapGate):
1✔
1458
            self._swap(xy, node_data[node].lc)
×
1459
            return
×
1460

1461
        # RZZ Gate
1462
        elif isinstance(op, RZZGate):
1✔
1463
            self._symmetric_gate(node, node_data, RZZGate, glob_data)
×
1464
            return
×
1465

1466
        c_xy = node_data[node].c_xy
1✔
1467
        xpos = min(x[0] for x in xy)
1✔
1468
        ypos = min(y[1] for y in xy)
1✔
1469
        ypos_max = max(y[1] for y in xy)
1✔
1470
        if c_xy:
1✔
1471
            cxpos = min(x[0] for x in c_xy)
1✔
1472
            cypos = min(y[1] for y in c_xy)
1✔
1473
            ypos = min(ypos, cypos)
1✔
1474

1475
        wid = max(node_data[node].width + 0.21, WID)
1✔
1476
        qubit_span = abs(ypos) - abs(ypos_max)
1✔
1477
        height = HIG + qubit_span
1✔
1478

1479
        box = glob_data["patches_mod"].Rectangle(
1✔
1480
            xy=(xpos - 0.5 * wid, ypos - 0.5 * HIG),
1481
            width=wid,
1482
            height=height,
1483
            fc=node_data[node].fc,
1484
            ec=node_data[node].ec,
1485
            linewidth=self._lwidth15,
1486
            zorder=PORDER_GATE,
1487
        )
1488
        self._ax.add_patch(box)
1✔
1489

1490
        # annotate inputs
1491
        for bit, y in enumerate([x[1] for x in xy]):
1✔
1492
            self._ax.text(
1✔
1493
                xpos + 0.07 - 0.5 * wid,
1494
                y,
1495
                str(bit),
1496
                ha="left",
1497
                va="center",
1498
                fontsize=self._style["fs"],
1499
                color=node_data[node].gt,
1500
                clip_on=True,
1501
                zorder=PORDER_TEXT,
1502
            )
1503
        if c_xy:
1✔
1504
            # annotate classical inputs
1505
            for bit, y in enumerate([x[1] for x in c_xy]):
1✔
1506
                self._ax.text(
1✔
1507
                    cxpos + 0.07 - 0.5 * wid,
1508
                    y,
1509
                    str(bit),
1510
                    ha="left",
1511
                    va="center",
1512
                    fontsize=self._style["fs"],
1513
                    color=node_data[node].gt,
1514
                    clip_on=True,
1515
                    zorder=PORDER_TEXT,
1516
                )
1517
        if node_data[node].gate_text:
1✔
1518
            gate_ypos = ypos + 0.5 * qubit_span
1✔
1519
            if node_data[node].param_text:
1✔
1520
                gate_ypos = ypos + 0.4 * height
×
1521
                self._ax.text(
×
1522
                    xpos + 0.11,
1523
                    ypos + 0.2 * height,
1524
                    node_data[node].param_text,
1525
                    ha="center",
1526
                    va="center",
1527
                    fontsize=self._style["sfs"],
1528
                    color=node_data[node].sc,
1529
                    clip_on=True,
1530
                    zorder=PORDER_TEXT,
1531
                )
1532
            self._ax.text(
1✔
1533
                xpos + 0.11,
1534
                gate_ypos,
1535
                node_data[node].gate_text,
1536
                ha="center",
1537
                va="center",
1538
                fontsize=self._style["fs"],
1539
                color=node_data[node].gt,
1540
                clip_on=True,
1541
                zorder=PORDER_TEXT,
1542
            )
1543

1544
    def _flow_op_gate(self, node, node_data, glob_data):
1✔
1545
        """Draw the box for a flow op circuit"""
1546
        xy = node_data[node].q_xy
×
1547
        xpos = min(x[0] for x in xy)
×
1548
        ypos = min(y[1] for y in xy)
×
1549
        ypos_max = max(y[1] for y in xy)
×
1550

1551
        if_width = node_data[node].width[0] + WID
×
1552
        box_width = if_width
×
1553
        # Add the else and case widths to the if_width
1554
        for ewidth in node_data[node].width[1:]:
×
1555
            if ewidth > 0.0:
×
1556
                box_width += ewidth + WID + 0.3
×
1557

1558
        qubit_span = abs(ypos) - abs(ypos_max)
×
1559
        height = HIG + qubit_span
×
1560

1561
        # Cycle through box colors based on depth.
1562
        # Default - blue, purple, green, black
1563
        colors = [
×
1564
            self._style["dispcol"]["h"][0],
1565
            self._style["dispcol"]["u"][0],
1566
            self._style["dispcol"]["x"][0],
1567
            self._style["cc"],
1568
        ]
1569
        # To fold box onto next lines, draw it repeatedly, shifting
1570
        # it left by x_shift and down by y_shift
1571
        fold_level = 0
×
1572
        end_x = xpos + box_width
×
1573

1574
        while end_x > 0.0:
×
1575
            x_shift = fold_level * self._fold
×
1576
            y_shift = fold_level * (glob_data["n_lines"] + 1)
×
1577
            end_x = xpos + box_width - x_shift if self._fold > 0 else 0.0
×
1578

1579
            if isinstance(node.op, IfElseOp):
×
1580
                flow_text = "  If"
×
1581
            elif isinstance(node.op, WhileLoopOp):
×
1582
                flow_text = " While"
×
1583
            elif isinstance(node.op, ForLoopOp):
×
1584
                flow_text = " For"
×
1585
            elif isinstance(node.op, SwitchCaseOp):
×
1586
                flow_text = "Switch"
×
NEW
1587
            elif isinstance(node.op, BoxOp):
×
NEW
1588
                flow_text = ""
×
1589
            else:
1590
                raise RuntimeError(f"unhandled control-flow op: {node.name}")
1591

1592
            # Some spacers. op_spacer moves 'Switch' back a bit for alignment,
1593
            # expr_spacer moves the expr over to line up with 'Switch' and
1594
            # empty_default_spacer makes the switch box longer if the default
1595
            # case is empty so text doesn't run past end of box.
1596
            if isinstance(node.op, SwitchCaseOp):
×
1597
                op_spacer = 0.04
×
1598
                expr_spacer = 0.0
×
1599
                empty_default_spacer = 0.3 if len(node.op.blocks[-1]) == 0 else 0.0
×
NEW
1600
            elif isinstance(node.op, BoxOp):
×
NEW
1601
                op_spacer = 0.0
×
NEW
1602
                expr_spacer = 0.0
×
NEW
1603
                empty_default_spacer = 0.0
×
1604
            else:
1605
                op_spacer = 0.08
×
1606
                expr_spacer = 0.02
×
1607
                empty_default_spacer = 0.0
×
1608

1609
            # FancyBbox allows rounded corners
1610
            box = glob_data["patches_mod"].FancyBboxPatch(
×
1611
                xy=(xpos - x_shift, ypos - 0.5 * HIG - y_shift),
1612
                width=box_width + empty_default_spacer,
1613
                height=height,
1614
                boxstyle="round, pad=0.1",
1615
                fc="none",
1616
                ec=colors[node_data[node].nest_depth % 4],
1617
                linewidth=self._lwidth3,
1618
                zorder=PORDER_FLOW,
1619
            )
1620
            self._ax.add_patch(box)
×
1621

1622
            # Indicate type of ControlFlowOp and if expression used, print below
1623
            self._ax.text(
×
1624
                xpos - x_shift - op_spacer,
1625
                ypos_max + 0.2 - y_shift,
1626
                flow_text,
1627
                ha="left",
1628
                va="center",
1629
                fontsize=self._style["fs"],
1630
                color=node_data[node].tc,
1631
                clip_on=True,
1632
                zorder=PORDER_FLOW,
1633
            )
1634
            self._ax.text(
×
1635
                xpos - x_shift + expr_spacer,
1636
                ypos_max + 0.2 - y_shift - 0.4,
1637
                node_data[node].expr_text,
1638
                ha="left",
1639
                va="center",
1640
                fontsize=self._style["sfs"],
1641
                color=node_data[node].tc,
1642
                clip_on=True,
1643
                zorder=PORDER_FLOW,
1644
            )
1645
            if isinstance(node.op, ForLoopOp):
×
1646
                idx_set = str(node_data[node].indexset)
×
1647
                # If a range was used display 'range' and grab the range value
1648
                # to be displayed below
1649
                if "range" in idx_set:
×
1650
                    idx_set = "r(" + idx_set[6:-1] + ")"
×
1651
                else:
1652
                    # If a tuple, show first 4 elements followed by '...'
1653
                    idx_set = str(node_data[node].indexset)[1:-1].split(",")[:5]
×
1654
                    if len(idx_set) > 4:
×
1655
                        idx_set[4] = "..."
×
1656
                    idx_set = f"{','.join(idx_set)}"
×
1657
                y_spacer = 0.2 if len(node.qargs) == 1 else 0.5
×
1658
                self._ax.text(
×
1659
                    xpos - x_shift - 0.04,
1660
                    ypos_max - y_spacer - y_shift,
1661
                    idx_set,
1662
                    ha="left",
1663
                    va="center",
1664
                    fontsize=self._style["sfs"],
1665
                    color=node_data[node].tc,
1666
                    clip_on=True,
1667
                    zorder=PORDER_FLOW,
1668
                )
1669
            # If there's an else or a case draw the vertical line and the name
1670
            else_case_text = "Else" if isinstance(node.op, IfElseOp) else "Case"
×
1671
            ewidth_incr = if_width
×
1672
            for circ_num, ewidth in enumerate(node_data[node].width[1:]):
×
1673
                if ewidth > 0.0:
×
1674
                    self._ax.plot(
×
1675
                        [xpos + ewidth_incr + 0.3 - x_shift, xpos + ewidth_incr + 0.3 - x_shift],
1676
                        [ypos - 0.5 * HIG - 0.08 - y_shift, ypos + height - 0.22 - y_shift],
1677
                        color=colors[node_data[node].nest_depth % 4],
1678
                        linewidth=3.0,
1679
                        linestyle="solid",
1680
                        zorder=PORDER_FLOW,
1681
                    )
1682
                    self._ax.text(
×
1683
                        xpos + ewidth_incr + 0.4 - x_shift,
1684
                        ypos_max + 0.2 - y_shift,
1685
                        else_case_text,
1686
                        ha="left",
1687
                        va="center",
1688
                        fontsize=self._style["fs"],
1689
                        color=node_data[node].tc,
1690
                        clip_on=True,
1691
                        zorder=PORDER_FLOW,
1692
                    )
1693
                    if isinstance(node.op, SwitchCaseOp):
×
1694
                        jump_val = node_data[node].jump_values[circ_num]
×
1695
                        # If only one value, e.g. (0,)
1696
                        if len(str(jump_val)) == 4:
×
1697
                            jump_text = str(jump_val)[1]
×
1698
                        elif "default" in str(jump_val):
×
1699
                            jump_text = "default"
×
1700
                        else:
1701
                            # If a tuple, show first 4 elements followed by '...'
1702
                            jump_text = str(jump_val)[1:-1].replace(" ", "").split(",")[:5]
×
1703
                            if len(jump_text) > 4:
×
1704
                                jump_text[4] = "..."
×
1705
                            jump_text = f"{', '.join(jump_text)}"
×
1706
                        y_spacer = 0.2 if len(node.qargs) == 1 else 0.5
×
1707
                        self._ax.text(
×
1708
                            xpos + ewidth_incr + 0.4 - x_shift,
1709
                            ypos_max - y_spacer - y_shift,
1710
                            jump_text,
1711
                            ha="left",
1712
                            va="center",
1713
                            fontsize=self._style["sfs"],
1714
                            color=node_data[node].tc,
1715
                            clip_on=True,
1716
                            zorder=PORDER_FLOW,
1717
                        )
1718
                ewidth_incr += ewidth + 1
×
1719

1720
            fold_level += 1
×
1721

1722
    def _control_gate(self, node, node_data, glob_data, mod_control):
1✔
1723
        """Draw a controlled gate"""
1724
        op = node.op
×
1725
        xy = node_data[node].q_xy
×
1726
        base_type = getattr(op, "base_gate", None)
×
1727
        qubit_b = min(xy, key=lambda xy: xy[1])
×
1728
        qubit_t = max(xy, key=lambda xy: xy[1])
×
1729
        num_ctrl_qubits = mod_control.num_ctrl_qubits if mod_control else op.num_ctrl_qubits
×
1730
        num_qargs = len(xy) - num_ctrl_qubits
×
1731
        ctrl_state = mod_control.ctrl_state if mod_control else op.ctrl_state
×
1732
        self._set_ctrl_bits(
×
1733
            ctrl_state,
1734
            num_ctrl_qubits,
1735
            xy,
1736
            glob_data,
1737
            ec=node_data[node].ec,
1738
            tc=node_data[node].tc,
1739
            text=node_data[node].ctrl_text,
1740
            qargs=node.qargs,
1741
        )
1742
        self._line(qubit_b, qubit_t, lc=node_data[node].lc)
×
1743

1744
        if isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, ZGate, RZZGate)):
×
1745
            self._symmetric_gate(node, node_data, base_type, glob_data)
×
1746

1747
        elif num_qargs == 1 and isinstance(base_type, XGate):
×
1748
            tgt_color = self._style["dispcol"]["target"]
×
1749
            tgt = tgt_color if isinstance(tgt_color, str) else tgt_color[0]
×
1750
            self._x_tgt_qubit(xy[num_ctrl_qubits], glob_data, ec=node_data[node].ec, ac=tgt)
×
1751

1752
        elif num_qargs == 1:
×
1753
            self._gate(node, node_data, glob_data, xy[num_ctrl_qubits:][0])
×
1754

1755
        elif isinstance(base_type, SwapGate):
×
1756
            self._swap(xy[num_ctrl_qubits:], node_data[node].lc)
×
1757

1758
        else:
1759
            self._multiqubit_gate(node, node_data, glob_data, xy[num_ctrl_qubits:])
×
1760

1761
    def _set_ctrl_bits(
1✔
1762
        self, ctrl_state, num_ctrl_qubits, qbit, glob_data, ec=None, tc=None, text="", qargs=None
1763
    ):
1764
        """Determine which qubits are controls and whether they are open or closed"""
1765
        # place the control label at the top or bottom of controls
1766
        if text:
×
1767
            qlist = [self._circuit.find_bit(qubit).index for qubit in qargs]
×
1768
            ctbits = qlist[:num_ctrl_qubits]
×
1769
            qubits = qlist[num_ctrl_qubits:]
×
1770
            max_ctbit = max(ctbits)
×
1771
            min_ctbit = min(ctbits)
×
1772
            top = min(qubits) > min_ctbit
×
1773

1774
        # display the control qubits as open or closed based on ctrl_state
1775
        cstate = f"{ctrl_state:b}".rjust(num_ctrl_qubits, "0")[::-1]
×
1776
        for i in range(num_ctrl_qubits):
×
1777
            fc_open_close = ec if cstate[i] == "1" else self._style["bg"]
×
1778
            text_top = None
×
1779
            if text:
×
1780
                if top and qlist[i] == min_ctbit:
×
1781
                    text_top = True
×
1782
                elif not top and qlist[i] == max_ctbit:
×
1783
                    text_top = False
×
1784
            self._ctrl_qubit(
×
1785
                qbit[i], glob_data, fc=fc_open_close, ec=ec, tc=tc, text=text, text_top=text_top
1786
            )
1787

1788
    def _ctrl_qubit(self, xy, glob_data, fc=None, ec=None, tc=None, text="", text_top=None):
1✔
1789
        """Draw a control circle and if top or bottom control, draw control label"""
1790
        xpos, ypos = xy
×
1791
        box = glob_data["patches_mod"].Circle(
×
1792
            xy=(xpos, ypos),
1793
            radius=WID * 0.15,
1794
            fc=fc,
1795
            ec=ec,
1796
            linewidth=self._lwidth15,
1797
            zorder=PORDER_GATE,
1798
        )
1799
        self._ax.add_patch(box)
×
1800

1801
        # adjust label height according to number of lines of text
1802
        label_padding = 0.7
×
1803
        if text is not None:
×
1804
            text_lines = text.count("\n")
×
1805
            if not text.endswith("(cal)\n"):
×
1806
                for _ in range(text_lines):
×
1807
                    label_padding += 0.3
×
1808

1809
        if text_top is None:
×
1810
            return
×
1811

1812
        # display the control label at the top or bottom if there is one
1813
        ctrl_ypos = ypos + label_padding * HIG if text_top else ypos - 0.3 * HIG
×
1814
        self._ax.text(
×
1815
            xpos,
1816
            ctrl_ypos,
1817
            text,
1818
            ha="center",
1819
            va="top",
1820
            fontsize=self._style["sfs"],
1821
            color=tc,
1822
            clip_on=True,
1823
            zorder=PORDER_TEXT,
1824
        )
1825

1826
    def _x_tgt_qubit(self, xy, glob_data, ec=None, ac=None):
1✔
1827
        """Draw the cnot target symbol"""
1828
        linewidth = self._lwidth2
×
1829
        xpos, ypos = xy
×
1830
        box = glob_data["patches_mod"].Circle(
×
1831
            xy=(xpos, ypos),
1832
            radius=HIG * 0.35,
1833
            fc=ec,
1834
            ec=ec,
1835
            linewidth=linewidth,
1836
            zorder=PORDER_GATE,
1837
        )
1838
        self._ax.add_patch(box)
×
1839

1840
        # add '+' symbol
1841
        self._ax.plot(
×
1842
            [xpos, xpos],
1843
            [ypos - 0.2 * HIG, ypos + 0.2 * HIG],
1844
            color=ac,
1845
            linewidth=linewidth,
1846
            zorder=PORDER_GATE_PLUS,
1847
        )
1848
        self._ax.plot(
×
1849
            [xpos - 0.2 * HIG, xpos + 0.2 * HIG],
1850
            [ypos, ypos],
1851
            color=ac,
1852
            linewidth=linewidth,
1853
            zorder=PORDER_GATE_PLUS,
1854
        )
1855

1856
    def _symmetric_gate(self, node, node_data, base_type, glob_data):
1✔
1857
        """Draw symmetric gates for cz, cu1, cp, and rzz"""
1858
        op = node.op
×
1859
        xy = node_data[node].q_xy
×
1860
        qubit_b = min(xy, key=lambda xy: xy[1])
×
1861
        qubit_t = max(xy, key=lambda xy: xy[1])
×
1862
        base_type = getattr(op, "base_gate", None)
×
1863
        ec = node_data[node].ec
×
1864
        tc = node_data[node].tc
×
1865
        lc = node_data[node].lc
×
1866

1867
        # cz and mcz gates
1868
        if not isinstance(op, ZGate) and isinstance(base_type, ZGate):
×
1869
            num_ctrl_qubits = op.num_ctrl_qubits
×
1870
            self._ctrl_qubit(xy[-1], glob_data, fc=ec, ec=ec, tc=tc)
×
1871
            self._line(qubit_b, qubit_t, lc=lc, zorder=PORDER_LINE_PLUS)
×
1872

1873
        # cu1, cp, rzz, and controlled rzz gates (sidetext gates)
1874
        elif isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, RZZGate)):
×
1875
            num_ctrl_qubits = 0 if isinstance(op, RZZGate) else op.num_ctrl_qubits
×
1876
            gate_text = "P" if isinstance(base_type, PhaseGate) else node_data[node].gate_text
×
1877

1878
            self._ctrl_qubit(xy[num_ctrl_qubits], glob_data, fc=ec, ec=ec, tc=tc)
×
1879
            if not isinstance(base_type, (U1Gate, PhaseGate)):
×
1880
                self._ctrl_qubit(xy[num_ctrl_qubits + 1], glob_data, fc=ec, ec=ec, tc=tc)
×
1881

1882
            self._sidetext(
×
1883
                node,
1884
                node_data,
1885
                qubit_b,
1886
                tc=tc,
1887
                text=f"{gate_text} ({node_data[node].param_text})",
1888
            )
1889
            self._line(qubit_b, qubit_t, lc=lc)
×
1890

1891
    def _swap(self, xy, color=None):
1✔
1892
        """Draw a Swap gate"""
1893
        self._swap_cross(xy[0], color=color)
×
1894
        self._swap_cross(xy[1], color=color)
×
1895
        self._line(xy[0], xy[1], lc=color)
×
1896

1897
    def _swap_cross(self, xy, color=None):
1✔
1898
        """Draw the Swap cross symbol"""
1899
        xpos, ypos = xy
×
1900

1901
        self._ax.plot(
×
1902
            [xpos - 0.20 * WID, xpos + 0.20 * WID],
1903
            [ypos - 0.20 * WID, ypos + 0.20 * WID],
1904
            color=color,
1905
            linewidth=self._lwidth2,
1906
            zorder=PORDER_LINE_PLUS,
1907
        )
1908
        self._ax.plot(
×
1909
            [xpos - 0.20 * WID, xpos + 0.20 * WID],
1910
            [ypos + 0.20 * WID, ypos - 0.20 * WID],
1911
            color=color,
1912
            linewidth=self._lwidth2,
1913
            zorder=PORDER_LINE_PLUS,
1914
        )
1915

1916
    def _sidetext(self, node, node_data, xy, tc=None, text=""):
1✔
1917
        """Draw the sidetext for symmetric gates"""
1918
        xpos, ypos = xy
×
1919

1920
        # 0.11 = the initial gap, add 1/2 text width to place on the right
1921
        xp = xpos + 0.11 + node_data[node].width / 2
×
1922
        self._ax.text(
×
1923
            xp,
1924
            ypos + HIG,
1925
            text,
1926
            ha="center",
1927
            va="top",
1928
            fontsize=self._style["sfs"],
1929
            color=tc,
1930
            clip_on=True,
1931
            zorder=PORDER_TEXT,
1932
        )
1933

1934
    def _line(self, xy0, xy1, lc=None, ls=None, zorder=PORDER_LINE):
1✔
1935
        """Draw a line from xy0 to xy1"""
1936
        x0, y0 = xy0
1✔
1937
        x1, y1 = xy1
1✔
1938
        linecolor = self._style["lc"] if lc is None else lc
1✔
1939
        linestyle = "solid" if ls is None else ls
1✔
1940

1941
        if linestyle == "doublet":
1✔
1942
            theta = np.arctan2(np.abs(x1 - x0), np.abs(y1 - y0))
1✔
1943
            dx = 0.05 * WID * np.cos(theta)
1✔
1944
            dy = 0.05 * WID * np.sin(theta)
1✔
1945
            self._ax.plot(
1✔
1946
                [x0 + dx, x1 + dx],
1947
                [y0 + dy, y1 + dy],
1948
                color=linecolor,
1949
                linewidth=self._lwidth2,
1950
                linestyle="solid",
1951
                zorder=zorder,
1952
            )
1953
            self._ax.plot(
1✔
1954
                [x0 - dx, x1 - dx],
1955
                [y0 - dy, y1 - dy],
1956
                color=linecolor,
1957
                linewidth=self._lwidth2,
1958
                linestyle="solid",
1959
                zorder=zorder,
1960
            )
1961
        else:
1962
            self._ax.plot(
1✔
1963
                [x0, x1],
1964
                [y0, y1],
1965
                color=linecolor,
1966
                linewidth=self._lwidth2,
1967
                linestyle=linestyle,
1968
                zorder=zorder,
1969
            )
1970

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

1974
        # Check folding
1975
        fold = self._fold if self._fold > 0 else INFINITE_FOLD
1✔
1976
        h_pos = x_index % fold + 1
1✔
1977

1978
        # Don't fold flow_ops here, only gates inside the flow_op
1979
        if not flow_op and h_pos + (gate_width - 1) > fold:
1✔
1980
            x_index += fold - (h_pos - 1)
×
1981
        x_pos = x_index % fold + glob_data["x_offset"] + 0.04
1✔
1982
        if not flow_op:
1✔
1983
            x_pos += 0.5 * gate_width
1✔
1984
        else:
1985
            x_pos += 0.25
×
1986
        y_pos = y_index - (x_index // fold) * (glob_data["n_lines"] + 1)
1✔
1987

1988
        # x_index could have been updated, so need to store
1989
        glob_data["next_x_index"] = x_index
1✔
1990
        return x_pos, y_pos
1✔
1991

1992

1993
class NodeData:
1✔
1994
    """Class containing drawing data on a per node basis"""
1995

1996
    def __init__(self):
1✔
1997
        # Node data for positioning
1998
        self.width = 0.0
1✔
1999
        self.x_index = 0
1✔
2000
        self.q_xy = []
1✔
2001
        self.c_xy = []
1✔
2002

2003
        # Node data for text
2004
        self.gate_text = ""
1✔
2005
        self.raw_gate_text = ""
1✔
2006
        self.ctrl_text = ""
1✔
2007
        self.param_text = ""
1✔
2008

2009
        # Node data for color
2010
        self.fc = self.ec = self.lc = self.sc = self.gt = self.tc = 0
1✔
2011

2012
        # Special values stored for ControlFlowOps
2013
        self.nest_depth = 0
1✔
2014
        self.expr_width = 0.0
1✔
2015
        self.expr_text = ""
1✔
2016
        self.inside_flow = False
1✔
2017
        self.indexset = ()  # List of indices used for ForLoopOp
1✔
2018
        self.jump_values = []  # List of jump values used for SwitchCaseOp
1✔
2019
        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