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

Qiskit / qiskit / 22661162557

04 Mar 2026 08:26AM UTC coverage: 87.726% (-0.2%) from 87.962%
22661162557

Pull #15502

github

web-flow
Merge dd7301f9a into c81aece0f
Pull Request #15502: Transpiler pass that converts a generic circuit to PBC

259 of 271 new or added lines in 5 files covered. (95.57%)

4547 existing lines in 202 files now uncovered.

100736 of 114830 relevant lines covered (87.73%)

1142028.45 hits per line

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

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

13

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

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

21
import numpy as np
1✔
22

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

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

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

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

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

86
INFINITE_FOLD = 10000000
1✔
87

88

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

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

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

120
        self._style = style
1✔
121

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

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

136
        self._ax = ax
1✔
137

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

670
        return layer_widths
1✔
671

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

854
        return prev_x_index + 1
1✔
855

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

859
        from pylatexenc.latex2text import LatexNodes2Text
1✔
860

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1105
                self._get_colors(node, node_data)
1✔
1106

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

1121
                # AnnotatedOperation with ControlModifier
1122
                mod_control = None
1✔
1123
                if getattr(op, "modifiers", None):
1✔
UNCOV
1124
                    canonical_modifiers = _canonicalize_modifiers(op.modifiers)
×
UNCOV
1125
                    for modifier in canonical_modifiers:
×
UNCOV
1126
                        if isinstance(modifier, ControlModifier):
×
UNCOV
1127
                            mod_control = modifier
×
UNCOV
1128
                            break
×
1129

1130
                # draw measure
1131
                if isinstance(op, Measure):
1✔
UNCOV
1132
                    self._measure(node, node_data, outer_circuit, glob_data)
×
1133

1134
                # draw barriers, snapshots, etc.
1135
                elif getattr(op, "_directive", False):
1✔
1136
                    if self._plot_barriers:
×
1137
                        self._barrier(node, node_data, glob_data)
×
1138

1139
                # draw the box for control flow circuits
1140
                elif isinstance(op, ControlFlowOp):
1✔
UNCOV
1141
                    self._flow_op_gate(node, node_data, glob_data)
×
1142

1143
                # draw single qubit gates
1144
                elif len(node_data[node].q_xy) == 1 and not node.cargs:
1✔
1145
                    self._gate(node, node_data, glob_data)
1✔
1146

1147
                # draw controlled gates
1148
                elif isinstance(op, ControlledGate) or mod_control:
1✔
UNCOV
1149
                    self._control_gate(node, node_data, glob_data, mod_control)
×
1150

1151
                # draw multi-qubit gate as final default
1152
                else:
1153
                    self._multiqubit_gate(node, node_data, glob_data)
1✔
1154

1155
                # Determine the max width of the circuit only at the top level
1156
                if not node_data[node].inside_flow:
1✔
1157
                    l_width.append(layer_widths[node][0])
1✔
1158

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

1168
    def _get_colors(self, node, node_data):
1✔
1169
        """Get all the colors needed for drawing the circuit"""
1170

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

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

1220
    def _condition(self, node, node_data, wire_map, outer_circuit, cond_xy, glob_data):
1✔
1221
        """Add a conditional to a gate"""
1222

1223
        # For SwitchCaseOp convert the target to a fully closed Clbit or register
1224
        # in condition format
UNCOV
1225
        if isinstance(node.op, SwitchCaseOp):
×
UNCOV
1226
            if isinstance(node.op.target, expr.Expr):
×
UNCOV
1227
                condition = node.op.target
×
UNCOV
1228
            elif isinstance(node.op.target, Clbit):
×
UNCOV
1229
                condition = (node.op.target, 1)
×
1230
            else:
UNCOV
1231
                condition = (node.op.target, 2 ** (node.op.target.size) - 1)
×
1232
        else:
UNCOV
1233
            condition = node.op.condition
×
1234

1235
        override_fc = False
×
1236
        first_clbit = len(self._qubits)
×
1237
        cond_pos = []
×
1238

1239
        if isinstance(condition, expr.Expr):
×
1240
            # If fixing this, please update the docstrings of `QuantumCircuit.draw` and
1241
            # `visualization.circuit_drawer` to remove warnings.
1242

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

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

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

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

UNCOV
1299
        if not xy_plot:
×
1300
            # Expression that's only on new-style `expr.Var` nodes, and doesn't need any vertical
1301
            # line drawing.
UNCOV
1302
            return
×
1303

UNCOV
1304
        qubit_b = min(node_data[node].q_xy, key=lambda xy: xy[1])
×
UNCOV
1305
        clbit_b = min(xy_plot, key=lambda xy: xy[1])
×
1306

1307
        # For IfElseOp, WhileLoopOp or SwitchCaseOp, place the condition line
1308
        # near the left edge of the box
1309
        if isinstance(node.op, (IfElseOp, WhileLoopOp, SwitchCaseOp)):
×
UNCOV
1310
            qubit_b = (qubit_b[0], qubit_b[1] - (0.5 * HIG + 0.14))
×
1311

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

1329
    def _measure(self, node, node_data, outer_circuit, glob_data):
1✔
1330
        """Draw the measure symbol and the line to the clbit"""
UNCOV
1331
        qx, qy = node_data[node].q_xy[0]
×
UNCOV
1332
        cx, cy = node_data[node].c_xy[0]
×
UNCOV
1333
        register, _, reg_index = get_bit_reg_index(outer_circuit, node.cargs[0])
×
1334

1335
        # draw gate box
UNCOV
1336
        self._gate(node, node_data, glob_data)
×
1337

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

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

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

1453
    def _gate(self, node, node_data, glob_data, xy=None):
1✔
1454
        """Draw a 1-qubit gate"""
1455
        if xy is None:
1✔
1456
            xy = node_data[node].q_xy[0]
1✔
1457
        xpos, ypos = xy
1✔
1458
        wid = max(node_data[node].width, WID)
1✔
1459

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

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

1498
    def _multiqubit_gate(self, node, node_data, glob_data, xy=None):
1✔
1499
        """Draw a gate covering more than one qubit"""
1500
        op = node.op
1✔
1501
        if xy is None:
1✔
1502
            xy = node_data[node].q_xy
1✔
1503

1504
        # Swap gate
1505
        if isinstance(op, SwapGate):
1✔
UNCOV
1506
            self._swap(xy, node_data[node].lc)
×
UNCOV
1507
            return
×
1508

1509
        # RZZ Gate
1510
        elif isinstance(op, RZZGate):
1✔
UNCOV
1511
            self._symmetric_gate(node, node_data, RZZGate, glob_data)
×
UNCOV
1512
            return
×
1513

1514
        c_xy = node_data[node].c_xy
1✔
1515
        xpos = min(x[0] for x in xy)
1✔
1516
        ypos = min(y[1] for y in xy)
1✔
1517
        ypos_max = max(y[1] for y in xy)
1✔
1518
        if c_xy:
1✔
1519
            cxpos = min(x[0] for x in c_xy)
1✔
1520
            cypos = min(y[1] for y in c_xy)
1✔
1521
            ypos = min(ypos, cypos)
1✔
1522

1523
        wid = max(node_data[node].width + 0.21, WID)
1✔
1524
        qubit_span = abs(ypos) - abs(ypos_max)
1✔
1525
        height = HIG + qubit_span
1✔
1526

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

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

1592
    def _flow_op_gate(self, node, node_data, glob_data):
1✔
1593
        """Draw the box for a flow op circuit"""
UNCOV
1594
        xy = node_data[node].q_xy
×
UNCOV
1595
        xpos = min(x[0] for x in xy)
×
UNCOV
1596
        ypos = min(y[1] for y in xy)
×
UNCOV
1597
        ypos_max = max(y[1] for y in xy)
×
1598

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

UNCOV
1608
        qubit_span = abs(ypos) - abs(ypos_max)
×
UNCOV
1609
        height = HIG + qubit_span
×
1610

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

UNCOV
1624
        while end_x > 0.0:
×
UNCOV
1625
            x_shift = fold_level * self._fold
×
UNCOV
1626
            y_shift = fold_level * (glob_data["n_lines"] + 1)
×
UNCOV
1627
            end_x = xpos + box_width - x_shift if self._fold > 0 else 0.0
×
1628

UNCOV
1629
            if isinstance(node.op, IfElseOp):
×
UNCOV
1630
                flow_text = "  If"
×
1631
            elif isinstance(node.op, WhileLoopOp):
×
1632
                flow_text = " While"
×
UNCOV
1633
            elif isinstance(node.op, ForLoopOp):
×
1634
                flow_text = " For"
×
1635
            elif isinstance(node.op, SwitchCaseOp):
×
1636
                flow_text = "Switch"
×
1637
            elif isinstance(node.op, BoxOp):
×
UNCOV
1638
                flow_text = ""
×
1639
            else:
1640
                raise RuntimeError(f"unhandled control-flow op: {node.name}")
1641

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

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

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

UNCOV
1773
            fold_level += 1
×
1774

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

UNCOV
1797
        if isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, ZGate, RZZGate)):
×
UNCOV
1798
            self._symmetric_gate(node, node_data, base_type, glob_data)
×
1799

UNCOV
1800
        elif num_qargs == 1 and isinstance(base_type, XGate):
×
UNCOV
1801
            tgt_color = self._style["dispcol"]["target"]
×
UNCOV
1802
            tgt = tgt_color if isinstance(tgt_color, str) else tgt_color[0]
×
UNCOV
1803
            self._x_tgt_qubit(xy[num_ctrl_qubits], glob_data, ec=node_data[node].ec, ac=tgt)
×
1804

1805
        elif num_qargs == 1:
×
UNCOV
1806
            self._gate(node, node_data, glob_data, xy[num_ctrl_qubits:][0])
×
1807

1808
        elif isinstance(base_type, SwapGate):
×
UNCOV
1809
            self._swap(xy[num_ctrl_qubits:], node_data[node].lc)
×
1810

1811
        else:
1812
            self._multiqubit_gate(node, node_data, glob_data, xy[num_ctrl_qubits:])
×
1813

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

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

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

1854
        # adjust label height according to number of lines of text
UNCOV
1855
        label_padding = 0.7
×
UNCOV
1856
        if text is not None:
×
UNCOV
1857
            text_lines = text.count("\n")
×
UNCOV
1858
            if not text.endswith("(cal)\n"):
×
UNCOV
1859
                for _ in range(text_lines):
×
UNCOV
1860
                    label_padding += 0.3
×
1861

1862
        if text_top is None:
×
UNCOV
1863
            return
×
1864

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

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

1893
        # add '+' symbol
UNCOV
1894
        self._ax.plot(
×
1895
            [xpos, xpos],
1896
            [ypos - 0.2 * HIG, ypos + 0.2 * HIG],
1897
            color=ac,
1898
            linewidth=linewidth,
1899
            zorder=PORDER_GATE_PLUS,
1900
        )
1901
        self._ax.plot(
×
1902
            [xpos - 0.2 * HIG, xpos + 0.2 * HIG],
1903
            [ypos, ypos],
1904
            color=ac,
1905
            linewidth=linewidth,
1906
            zorder=PORDER_GATE_PLUS,
1907
        )
1908

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

1920
        # cz and mcz gates
1921
        if not isinstance(op, ZGate) and isinstance(base_type, ZGate):
×
1922
            num_ctrl_qubits = op.num_ctrl_qubits
×
1923
            self._ctrl_qubit(xy[-1], glob_data, fc=ec, ec=ec, tc=tc)
×
1924
            self._line(qubit_b, qubit_t, lc=lc, zorder=PORDER_LINE_PLUS)
×
1925

1926
        # cu1, cp, rzz, and controlled rzz gates (sidetext gates)
1927
        elif isinstance(op, RZZGate) or isinstance(base_type, (U1Gate, PhaseGate, RZZGate)):
×
1928
            num_ctrl_qubits = 0 if isinstance(op, RZZGate) else op.num_ctrl_qubits
×
UNCOV
1929
            gate_text = "P" if isinstance(base_type, PhaseGate) else node_data[node].gate_text
×
1930

1931
            self._ctrl_qubit(xy[num_ctrl_qubits], glob_data, fc=ec, ec=ec, tc=tc)
×
1932
            if not isinstance(base_type, (U1Gate, PhaseGate)):
×
1933
                self._ctrl_qubit(xy[num_ctrl_qubits + 1], glob_data, fc=ec, ec=ec, tc=tc)
×
1934

UNCOV
1935
            self._sidetext(
×
1936
                node,
1937
                node_data,
1938
                qubit_b,
1939
                tc=tc,
1940
                text=f"{gate_text} ({node_data[node].param_text})",
1941
            )
1942
            self._line(qubit_b, qubit_t, lc=lc)
×
1943

1944
    def _swap(self, xy, color=None):
1✔
1945
        """Draw a Swap gate"""
UNCOV
1946
        self._swap_cross(xy[0], color=color)
×
UNCOV
1947
        self._swap_cross(xy[1], color=color)
×
UNCOV
1948
        self._line(xy[0], xy[1], lc=color)
×
1949

1950
    def _swap_cross(self, xy, color=None):
1✔
1951
        """Draw the Swap cross symbol"""
1952
        xpos, ypos = xy
×
1953

UNCOV
1954
        self._ax.plot(
×
1955
            [xpos - 0.20 * WID, xpos + 0.20 * WID],
1956
            [ypos - 0.20 * WID, ypos + 0.20 * WID],
1957
            color=color,
1958
            linewidth=self._lwidth2,
1959
            zorder=PORDER_LINE_PLUS,
1960
        )
UNCOV
1961
        self._ax.plot(
×
1962
            [xpos - 0.20 * WID, xpos + 0.20 * WID],
1963
            [ypos + 0.20 * WID, ypos - 0.20 * WID],
1964
            color=color,
1965
            linewidth=self._lwidth2,
1966
            zorder=PORDER_LINE_PLUS,
1967
        )
1968

1969
    def _sidetext(self, node, node_data, xy, tc=None, text=""):
1✔
1970
        """Draw the sidetext for symmetric gates"""
1971
        xpos, ypos = xy
×
1972

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

1987
    def _line(self, xy0, xy1, lc=None, ls=None, zorder=PORDER_LINE):
1✔
1988
        """Draw a line from xy0 to xy1"""
1989
        x0, y0 = xy0
1✔
1990
        x1, y1 = xy1
1✔
1991
        linecolor = self._style["lc"] if lc is None else lc
1✔
1992
        linestyle = "solid" if ls is None else ls
1✔
1993

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

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

2027
        # Check folding
2028
        fold = self._fold if self._fold > 0 else INFINITE_FOLD
1✔
2029
        h_pos = x_index % fold + 1
1✔
2030

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

2041
        # x_index could have been updated, so need to store
2042
        glob_data["next_x_index"] = x_index
1✔
2043
        return x_pos, y_pos
1✔
2044

2045

2046
class NodeData:
1✔
2047
    """Class containing drawing data on a per node basis"""
2048

2049
    def __init__(self):
1✔
2050
        # Node data for positioning
2051
        self.width = 0.0
1✔
2052
        self.x_index = 0
1✔
2053
        self.q_xy = []
1✔
2054
        self.c_xy = []
1✔
2055

2056
        # Node data for text
2057
        self.gate_text = ""
1✔
2058
        self.raw_gate_text = ""
1✔
2059
        self.ctrl_text = ""
1✔
2060
        self.param_text = ""
1✔
2061

2062
        # Node data for color
2063
        self.fc = self.ec = self.lc = self.sc = self.gt = self.tc = 0
1✔
2064

2065
        # Special values stored for ControlFlowOps
2066
        self.nest_depth = 0
1✔
2067
        self.expr_width = 0.0
1✔
2068
        self.expr_text = ""
1✔
2069
        self.inside_flow = False
1✔
2070
        self.indexset = ()  # List of indices used for ForLoopOp
1✔
2071
        self.jump_values = []  # List of jump values used for SwitchCaseOp
1✔
2072
        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