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

Qiskit / qiskit / 13617651171

02 Mar 2025 04:40PM UTC coverage: 87.232% (+0.2%) from 86.989%
13617651171

push

github

web-flow
Remove calibrations APIs and related functionality  (#13861)

* Handle ScheduleBlock and pulse gates loading

* Add documentation and remove redundant code

* Limit QPY version when generating circuits for compatibility test

Fix some doc issues

* Handle QPY compatibility testing. Misc other fixes

* Update qiskit/qpy/binary_io/circuits.py

Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>

* Remove pulse from GenericBackendV2

This commit removes pulse-related functionality from GenericBackendV2, as
part of Pulse removal in Qiskit 2.0. This includes the ability to initialize
the backend with custom calibrations and query it for channel information.
Also, various clean ups where made to accommodate for the updated API of
GenericBackendV2.

* First pass

* Avoid generating pulse circuits in load_qpy & version >= 2.0

* Remove more stuff

* Add reno and some misc fixes

* Add recent changes from remove-pulse-qpy branch

* Raise QpyError when loading ScheduleBlock payloads

* Clean up TODOs

* Unify transpiler renos w.r.t pulse removal

* Remove inst_map from transpile() docstring

* Small cleanup and reno update

* Fix lint

* Applying comments from Elena's review

---------

Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>

59 of 62 new or added lines in 21 files covered. (95.16%)

95 existing lines in 15 files now uncovered.

75562 of 86622 relevant lines covered (87.23%)

334492.44 hits per line

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

49.26
/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
    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 .qcstyle import load_style
1✔
58
from ._utils import (
1✔
59
    get_gate_ctrl_text,
60
    get_param_str,
61
    get_wire_map,
62
    get_bit_register,
63
    get_bit_reg_index,
64
    get_wire_label,
65
    get_condition_label_val,
66
    _get_layered_instructions,
67
)
68
from ..utils import matplotlib_close_if_inline
1✔
69

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

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

85
INFINITE_FOLD = 10000000
1✔
86

87

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

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

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

118
        self._style = style
1✔
119

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

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

134
        self._ax = ax
1✔
135

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

642
        return layer_widths
1✔
643

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

821
        return prev_x_index + 1
1✔
822

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

826
        from pylatexenc.latex2text import LatexNodes2Text
1✔
827

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1578
            if isinstance(node.op, IfElseOp):
×
1579
                flow_text = "  If"
×
1580
            elif isinstance(node.op, WhileLoopOp):
×
1581
                flow_text = " While"
×
1582
            elif isinstance(node.op, ForLoopOp):
×
1583
                flow_text = " For"
×
1584
            elif isinstance(node.op, SwitchCaseOp):
×
1585
                flow_text = "Switch"
×
1586
            else:
1587
                flow_text = node.op.name
×
1588

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

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

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

1713
            fold_level += 1
×
1714

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

1737
        if isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, ZGate, RZZGate)):
×
1738
            self._symmetric_gate(node, node_data, base_type, glob_data)
×
1739

1740
        elif num_qargs == 1 and isinstance(base_type, XGate):
×
1741
            tgt_color = self._style["dispcol"]["target"]
×
1742
            tgt = tgt_color if isinstance(tgt_color, str) else tgt_color[0]
×
1743
            self._x_tgt_qubit(xy[num_ctrl_qubits], glob_data, ec=node_data[node].ec, ac=tgt)
×
1744

1745
        elif num_qargs == 1:
×
1746
            self._gate(node, node_data, glob_data, xy[num_ctrl_qubits:][0])
×
1747

1748
        elif isinstance(base_type, SwapGate):
×
NEW
1749
            self._swap(xy[num_ctrl_qubits:], node_data[node].lc)
×
1750

1751
        else:
1752
            self._multiqubit_gate(node, node_data, glob_data, xy[num_ctrl_qubits:])
×
1753

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

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

1781
    def _ctrl_qubit(self, xy, glob_data, fc=None, ec=None, tc=None, text="", text_top=None):
1✔
1782
        """Draw a control circle and if top or bottom control, draw control label"""
1783
        xpos, ypos = xy
×
1784
        box = glob_data["patches_mod"].Circle(
×
1785
            xy=(xpos, ypos),
1786
            radius=WID * 0.15,
1787
            fc=fc,
1788
            ec=ec,
1789
            linewidth=self._lwidth15,
1790
            zorder=PORDER_GATE,
1791
        )
1792
        self._ax.add_patch(box)
×
1793

1794
        # adjust label height according to number of lines of text
1795
        label_padding = 0.7
×
1796
        if text is not None:
×
1797
            text_lines = text.count("\n")
×
1798
            if not text.endswith("(cal)\n"):
×
1799
                for _ in range(text_lines):
×
1800
                    label_padding += 0.3
×
1801

1802
        if text_top is None:
×
1803
            return
×
1804

1805
        # display the control label at the top or bottom if there is one
1806
        ctrl_ypos = ypos + label_padding * HIG if text_top else ypos - 0.3 * HIG
×
1807
        self._ax.text(
×
1808
            xpos,
1809
            ctrl_ypos,
1810
            text,
1811
            ha="center",
1812
            va="top",
1813
            fontsize=self._style["sfs"],
1814
            color=tc,
1815
            clip_on=True,
1816
            zorder=PORDER_TEXT,
1817
        )
1818

1819
    def _x_tgt_qubit(self, xy, glob_data, ec=None, ac=None):
1✔
1820
        """Draw the cnot target symbol"""
1821
        linewidth = self._lwidth2
×
1822
        xpos, ypos = xy
×
1823
        box = glob_data["patches_mod"].Circle(
×
1824
            xy=(xpos, ypos),
1825
            radius=HIG * 0.35,
1826
            fc=ec,
1827
            ec=ec,
1828
            linewidth=linewidth,
1829
            zorder=PORDER_GATE,
1830
        )
1831
        self._ax.add_patch(box)
×
1832

1833
        # add '+' symbol
1834
        self._ax.plot(
×
1835
            [xpos, xpos],
1836
            [ypos - 0.2 * HIG, ypos + 0.2 * HIG],
1837
            color=ac,
1838
            linewidth=linewidth,
1839
            zorder=PORDER_GATE_PLUS,
1840
        )
1841
        self._ax.plot(
×
1842
            [xpos - 0.2 * HIG, xpos + 0.2 * HIG],
1843
            [ypos, ypos],
1844
            color=ac,
1845
            linewidth=linewidth,
1846
            zorder=PORDER_GATE_PLUS,
1847
        )
1848

1849
    def _symmetric_gate(self, node, node_data, base_type, glob_data):
1✔
1850
        """Draw symmetric gates for cz, cu1, cp, and rzz"""
1851
        op = node.op
×
1852
        xy = node_data[node].q_xy
×
1853
        qubit_b = min(xy, key=lambda xy: xy[1])
×
1854
        qubit_t = max(xy, key=lambda xy: xy[1])
×
1855
        base_type = getattr(op, "base_gate", None)
×
1856
        ec = node_data[node].ec
×
1857
        tc = node_data[node].tc
×
1858
        lc = node_data[node].lc
×
1859

1860
        # cz and mcz gates
1861
        if not isinstance(op, ZGate) and isinstance(base_type, ZGate):
×
1862
            num_ctrl_qubits = op.num_ctrl_qubits
×
1863
            self._ctrl_qubit(xy[-1], glob_data, fc=ec, ec=ec, tc=tc)
×
1864
            self._line(qubit_b, qubit_t, lc=lc, zorder=PORDER_LINE_PLUS)
×
1865

1866
        # cu1, cp, rzz, and controlled rzz gates (sidetext gates)
1867
        elif isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, RZZGate)):
×
1868
            num_ctrl_qubits = 0 if isinstance(op, RZZGate) else op.num_ctrl_qubits
×
1869
            gate_text = "P" if isinstance(base_type, PhaseGate) else node_data[node].gate_text
×
1870

1871
            self._ctrl_qubit(xy[num_ctrl_qubits], glob_data, fc=ec, ec=ec, tc=tc)
×
1872
            if not isinstance(base_type, (U1Gate, PhaseGate)):
×
1873
                self._ctrl_qubit(xy[num_ctrl_qubits + 1], glob_data, fc=ec, ec=ec, tc=tc)
×
1874

1875
            self._sidetext(
×
1876
                node,
1877
                node_data,
1878
                qubit_b,
1879
                tc=tc,
1880
                text=f"{gate_text} ({node_data[node].param_text})",
1881
            )
1882
            self._line(qubit_b, qubit_t, lc=lc)
×
1883

1884
    def _swap(self, xy, color=None):
1✔
1885
        """Draw a Swap gate"""
1886
        self._swap_cross(xy[0], color=color)
×
1887
        self._swap_cross(xy[1], color=color)
×
1888
        self._line(xy[0], xy[1], lc=color)
×
1889

1890
    def _swap_cross(self, xy, color=None):
1✔
1891
        """Draw the Swap cross symbol"""
1892
        xpos, ypos = xy
×
1893

1894
        self._ax.plot(
×
1895
            [xpos - 0.20 * WID, xpos + 0.20 * WID],
1896
            [ypos - 0.20 * WID, ypos + 0.20 * WID],
1897
            color=color,
1898
            linewidth=self._lwidth2,
1899
            zorder=PORDER_LINE_PLUS,
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

1909
    def _sidetext(self, node, node_data, xy, tc=None, text=""):
1✔
1910
        """Draw the sidetext for symmetric gates"""
1911
        xpos, ypos = xy
×
1912

1913
        # 0.11 = the initial gap, add 1/2 text width to place on the right
1914
        xp = xpos + 0.11 + node_data[node].width / 2
×
1915
        self._ax.text(
×
1916
            xp,
1917
            ypos + HIG,
1918
            text,
1919
            ha="center",
1920
            va="top",
1921
            fontsize=self._style["sfs"],
1922
            color=tc,
1923
            clip_on=True,
1924
            zorder=PORDER_TEXT,
1925
        )
1926

1927
    def _line(self, xy0, xy1, lc=None, ls=None, zorder=PORDER_LINE):
1✔
1928
        """Draw a line from xy0 to xy1"""
1929
        x0, y0 = xy0
1✔
1930
        x1, y1 = xy1
1✔
1931
        linecolor = self._style["lc"] if lc is None else lc
1✔
1932
        linestyle = "solid" if ls is None else ls
1✔
1933

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

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

1967
        # Check folding
1968
        fold = self._fold if self._fold > 0 else INFINITE_FOLD
1✔
1969
        h_pos = x_index % fold + 1
1✔
1970

1971
        # Don't fold flow_ops here, only gates inside the flow_op
1972
        if not flow_op and h_pos + (gate_width - 1) > fold:
1✔
1973
            x_index += fold - (h_pos - 1)
×
1974
        x_pos = x_index % fold + glob_data["x_offset"] + 0.04
1✔
1975
        if not flow_op:
1✔
1976
            x_pos += 0.5 * gate_width
1✔
1977
        else:
1978
            x_pos += 0.25
×
1979
        y_pos = y_index - (x_index // fold) * (glob_data["n_lines"] + 1)
1✔
1980

1981
        # x_index could have been updated, so need to store
1982
        glob_data["next_x_index"] = x_index
1✔
1983
        return x_pos, y_pos
1✔
1984

1985

1986
class NodeData:
1✔
1987
    """Class containing drawing data on a per node basis"""
1988

1989
    def __init__(self):
1✔
1990
        # Node data for positioning
1991
        self.width = 0.0
1✔
1992
        self.x_index = 0
1✔
1993
        self.q_xy = []
1✔
1994
        self.c_xy = []
1✔
1995

1996
        # Node data for text
1997
        self.gate_text = ""
1✔
1998
        self.raw_gate_text = ""
1✔
1999
        self.ctrl_text = ""
1✔
2000
        self.param_text = ""
1✔
2001

2002
        # Node data for color
2003
        self.fc = self.ec = self.lc = self.sc = self.gt = self.tc = 0
1✔
2004

2005
        # Special values stored for ControlFlowOps
2006
        self.nest_depth = 0
1✔
2007
        self.expr_width = 0.0
1✔
2008
        self.expr_text = ""
1✔
2009
        self.inside_flow = False
1✔
2010
        self.indexset = ()  # List of indices used for ForLoopOp
1✔
2011
        self.jump_values = []  # List of jump values used for SwitchCaseOp
1✔
2012
        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