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

Qiskit / qiskit / 15711349744

17 Jun 2025 03:20PM UTC coverage: 87.995% (-0.3%) from 88.333%
15711349744

Pull #14379

github

web-flow
Merge 6a26a5b2e into ad239bf01
Pull Request #14379: Explicitly specify that `Target` cannot be modified by indexing.

83531 of 94927 relevant lines covered (87.99%)

515202.86 hits per line

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

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

13
# pylint: disable=invalid-name
14

15
"""
16
Visualization function for DAG circuit representation.
17
"""
18

19
import io
1✔
20
import subprocess
1✔
21

22
from rustworkx.visualization import graphviz_draw
1✔
23

24
from qiskit.dagcircuit.dagnode import DAGOpNode, DAGInNode, DAGOutNode
1✔
25
from qiskit.dagcircuit.dagcircuit import DAGCircuit
1✔
26
from qiskit.circuit import Qubit, Clbit, ClassicalRegister
1✔
27
from qiskit.circuit.classical import expr
1✔
28
from qiskit.converters import dagdependency_to_circuit
1✔
29
from qiskit.utils import optionals as _optionals
1✔
30
from qiskit.exceptions import InvalidFileError
1✔
31
from .exceptions import VisualizationError
1✔
32

33
from .style import load_style
1✔
34
from .dag.dagstyle import DAGStyleDict, DAGDefaultStyle
1✔
35

36

37
IMAGE_TYPES = {
1✔
38
    "canon",
39
    "cmap",
40
    "cmapx",
41
    "cmapx_np",
42
    "dia",
43
    "dot",
44
    "fig",
45
    "gd",
46
    "gd2",
47
    "gif",
48
    "hpgl",
49
    "imap",
50
    "imap_np",
51
    "ismap",
52
    "jpe",
53
    "jpeg",
54
    "jpg",
55
    "mif",
56
    "mp",
57
    "pcl",
58
    "pdf",
59
    "pic",
60
    "plain",
61
    "plain-ext",
62
    "png",
63
    "ps",
64
    "ps2",
65
    "svg",
66
    "svgz",
67
    "vml",
68
    "vmlz",
69
    "vrml",
70
    "vtx",
71
    "wbmp",
72
    "xdor",
73
    "xlib",
74
}
75

76

77
@_optionals.HAS_GRAPHVIZ.require_in_call
1✔
78
@_optionals.HAS_PIL.require_in_call
1✔
79
def dag_drawer(
1✔
80
    dag,
81
    scale=0.7,
82
    filename=None,
83
    style="color",
84
):
85
    """Plot the directed acyclic graph (dag) to represent operation dependencies
86
    in a quantum circuit.
87

88
    This function calls the :func:`~rustworkx.visualization.graphviz_draw` function from the
89
    ``rustworkx`` package to draw the DAG.
90

91
    Args:
92
        dag (DAGCircuit or DAGDependency): The dag to draw.
93
        scale (float): scaling factor
94
        filename (str): file path to save image to (format inferred from name)
95
        style (dict or str): Style name, file name of style JSON file, or a
96
            dictionary specifying the style.
97

98
            * The supported style names are 'plain': B&W graph, 'color' (default):
99
                (color input/output/op nodes)
100
            * If given a JSON file, e.g. ``my_style.json`` or ``my_style`` (the ``.json``
101
                extension may be omitted), this function attempts to load the style dictionary
102
                from that location. Note, that the JSON file must completely specify the
103
                visualization specifications. The file is searched for in
104
                ``qiskit/visualization/circuit/styles``, the current working directory, and
105
                the location specified in ``~/.qiskit/settings.conf``.
106
            * If ``None`` the default style ``"color"`` is used or, if given, the default style
107
                specified in ``~/.qiskit/settings.conf``.
108

109
    Returns:
110
        PIL.Image: if in Jupyter notebook and not saving to file,
111
            otherwise None.
112

113
    Raises:
114
        VisualizationError: when style is not recognized.
115
        InvalidFileError: when filename provided is not valid
116
        ValueError: If the file extension for ``filename`` is not an image
117
            type supported by Graphviz.
118

119
    Example:
120
        .. plot::
121
            :include-source:
122
            :nofigs:
123

124
            from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
125
            from qiskit.converters import circuit_to_dag
126
            from qiskit.visualization import dag_drawer
127

128
            q = QuantumRegister(3, 'q')
129
            c = ClassicalRegister(3, 'c')
130
            circ = QuantumCircuit(q, c)
131
            circ.h(q[0])
132
            circ.cx(q[0], q[1])
133
            circ.measure(q[0], c[0])
134
            with circ.if_test((c, 2)):
135
                circ.rz(0.5, q[1])
136

137
            dag = circuit_to_dag(circ)
138

139
            style = {
140
                "inputnodecolor": "pink",
141
                "outputnodecolor": "lightblue",
142
                "opnodecolor": "red",
143
            }
144

145
            dag_drawer(dag, style=style)
146
    """
147

148
    from PIL import Image
×
149

150
    # NOTE: use type str checking to avoid potential cyclical import
151
    # the two tradeoffs ere that it will not handle subclasses and it is
152
    # slower (which doesn't matter for a visualization function)
153
    type_str = str(type(dag))
×
154
    register_bit_labels = {
×
155
        bit: f"{reg.name}[{idx}]"
156
        for reg in list(dag.qregs.values()) + list(dag.cregs.values())
157
        for (idx, bit) in enumerate(reg)
158
    }
159

160
    graph_attrs = {}
×
161
    if isinstance(style, dict):
×
162
        for attr in ["fontsize", "bgcolor", "dpi", "pad"]:
×
163
            if attr in style:
×
164
                graph_attrs[attr] = str(style[attr])
×
165

166
    style, _ = load_style(
×
167
        style,
168
        style_dict=DAGStyleDict,
169
        default_style=DAGDefaultStyle(),
170
        user_config_opt="circuit_graphviz_style",
171
        user_config_path_opt="circuit_graphviz_style_path",
172
        raise_error_if_not_found=True,
173
    )
174

175
    if "DAGDependency" in type_str:
×
176
        # pylint: disable=cyclic-import
177
        from qiskit.visualization.circuit._utils import get_bit_reg_index
×
178

179
        qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)}
×
180
        clbit_indices = {bit: index for index, bit in enumerate(dag.clbits)}
×
181
        graph_attrs.update({"dpi": str(100 * scale)})
×
182

183
        dag_dep_circ = dagdependency_to_circuit(dag)
×
184

185
        def node_attr_func(node):
×
186
            if "DAGDependencyV2" in type_str:
×
187
                nid_str = str(node._node_id)
×
188
            else:
189
                nid_str = str(node.node_id)
×
190

191
            n = {}
×
192
            args = []
×
193
            for count, arg in enumerate(node.qargs + node.cargs):
×
194
                if count > 4:
×
195
                    args.append("...")
×
196
                    break
×
197
                if isinstance(arg, Qubit):
×
198
                    f_str = f"q_{qubit_indices[arg]}"
×
199
                elif isinstance(arg, Clbit):
×
200
                    f_str = f"c_{clbit_indices[arg]}"
×
201
                else:
202
                    f_str = f"{arg.index}"
×
203
                arg_str = register_bit_labels.get(arg, f_str)
×
204
                args.append(arg_str)
×
205

206
            n["label"] = (
×
207
                nid_str + ": " + str(node.name) + " (" + str(args)[1:-1].replace("'", "") + ")"
208
            )
209
            if getattr(node.op, "condition", None):
×
210
                condition = node.op.condition
×
211
                if isinstance(condition, expr.Expr):
×
212
                    cond_txt = " (cond: [Expr]) ("
×
213
                elif isinstance(condition[0], ClassicalRegister):
×
214
                    cond_txt = f" (cond: {condition[0].name}, {int(condition[1])}) ("
×
215
                else:
216
                    register, bit_index, reg_index = get_bit_reg_index(dag_dep_circ, condition[0])
×
217
                    if register is not None:
×
218
                        cond_txt = f" (cond: {register.name}[{reg_index}], {int(condition[1])}) ("
×
219
                    else:
220
                        cond_txt = f" (cond: {bit_index}, {int(condition[1])}) ("
×
221
                n["label"] = (
×
222
                    nid_str
223
                    + ": "
224
                    + str(node.name)
225
                    + cond_txt
226
                    + str(args)[1:-1].replace("'", "")
227
                    + ")"
228
                )
229

230
            if isinstance(style, dict):
×
231
                n["style"] = "filled"
×
232

233
                if "nodecolor" in style:
×
234
                    n["fillcolor"] = style["nodecolor"]
×
235

236
                if "fontsize" in style:
×
237
                    n["fontsize"] = str(style["fontsize"])
×
238

239
                if node.name == "barrier":
×
240
                    n["fillcolor"] = style["barriercolor"]
×
241
                elif getattr(node.op, "_directive", False):
×
242
                    n["fillcolor"] = style["directivecolor"]
×
243
                elif getattr(node.op, "condition", None):
×
244
                    n["fillcolor"] = style["conditioncolor"]
×
245
                elif node.name == "measure":
×
246
                    n["fillcolor"] = style["measurecolor"]
×
247

248
                return n
×
249
            else:
250
                raise VisualizationError(f"Unrecognized style {style} for the dag_drawer.")
×
251

252
        edge_attr_func = None
×
253

254
    else:
255
        graph_attrs.update({"dpi": str(100 * scale)})
×
256

257
        def node_attr_func(node):
×
258
            n = {}
×
259
            if isinstance(node, DAGOpNode):
×
260
                n["label"] = node.name
×
261
            if isinstance(node, DAGInNode):
×
262
                if isinstance(node.wire, Qubit):
×
263
                    label = register_bit_labels.get(node.wire, f"q_{dag.find_bit(node.wire).index}")
×
264
                elif isinstance(node.wire, Clbit):
×
265
                    label = register_bit_labels.get(node.wire, f"c_{dag.find_bit(node.wire).index}")
×
266
                else:
267
                    label = str(node.wire.name)
×
268

269
                n["label"] = label
×
270
            if isinstance(node, DAGOutNode):
×
271
                if isinstance(node.wire, Qubit):
×
272
                    label = register_bit_labels.get(
×
273
                        node.wire, f"q[{dag.find_bit(node.wire).index}]"
274
                    )
275
                elif isinstance(node.wire, Clbit):
×
276
                    label = register_bit_labels.get(
×
277
                        node.wire, f"c[{dag.find_bit(node.wire).index}]"
278
                    )
279
                else:
280
                    label = str(node.wire.name)
×
281
                n["label"] = label
×
282

283
            if isinstance(style, dict):
×
284
                n["style"] = "filled"
×
285

286
                if "nodecolor" in style:
×
287
                    n["fillcolor"] = style["nodecolor"]
×
288

289
                if "fontsize" in style:
×
290
                    n["fontsize"] = str(style["fontsize"])
×
291

292
                if isinstance(node, DAGInNode):
×
293
                    if "inputnodecolor" in style:
×
294
                        n["fillcolor"] = style["inputnodecolor"]
×
295
                    if "inputnodefontcolor" in style:
×
296
                        n["fontcolor"] = style["inputnodefontcolor"]
×
297
                if isinstance(node, DAGOutNode):
×
298
                    if "outputnodecolor" in style:
×
299
                        n["fillcolor"] = style["outputnodecolor"]
×
300
                    if "outputnodefontcolor" in style:
×
301
                        n["fontcolor"] = style["outputnodefontcolor"]
×
302
                if isinstance(node, DAGOpNode):
×
303
                    if "opnodecolor" in style:
×
304
                        n["fillcolor"] = style["opnodecolor"]
×
305
                    if "opnodefontcolor" in style:
×
306
                        n["fontcolor"] = style["opnodefontcolor"]
×
307

308
                return n
×
309
            else:
310
                raise VisualizationError(f"Invalid style {style}")
×
311

312
        def edge_attr_func(edge):
×
313
            e = {}
×
314

315
            if isinstance(edge, Qubit):
×
316
                label = register_bit_labels.get(edge, f"q_{dag.find_bit(edge).index}")
×
317
            elif isinstance(edge, Clbit):
×
318
                label = register_bit_labels.get(edge, f"c_{dag.find_bit(edge).index}")
×
319
            else:
320
                label = str(edge.name)
×
321
            e["label"] = label
×
322

323
            if isinstance(style, dict):
×
324
                if "edgecolor" in style:
×
325
                    e["color"] = style["edgecolor"]
×
326
                if "fontsize" in style:
×
327
                    e["fontsize"] = str(style["fontsize"])
×
328

329
                if isinstance(edge, Qubit):
×
330
                    if "qubitedgecolor" in style:
×
331
                        e["color"] = style["qubitedgecolor"]
×
332
                if isinstance(edge, Clbit):
×
333
                    if "clbitedgecolor" in style:
×
334
                        e["color"] = style["clbitedgecolor"]
×
335
                return e
×
336

337
            return e
×
338

339
    image_type = "png"
×
340
    if filename:
×
341
        if "." not in filename:
×
342
            raise InvalidFileError("Parameter 'filename' must be in format 'name.extension'")
×
343
        image_type = filename.split(".")[-1]
×
344
        if image_type not in IMAGE_TYPES:
×
345
            raise ValueError(
×
346
                "The specified value for the image_type argument, "
347
                f"'{image_type}' is not a valid choice. It must be one of: "
348
                f"{IMAGE_TYPES}"
349
            )
350

351
    if isinstance(dag, DAGCircuit):
×
352
        dot_str = dag._to_dot(
×
353
            graph_attrs,
354
            node_attr_func,
355
            edge_attr_func,
356
        )
357

358
        prog = "dot"
×
359
        if not filename:
×
360
            dot_result = subprocess.run(
×
361
                [prog, "-T", image_type],
362
                input=dot_str.encode("utf-8"),
363
                capture_output=True,
364
                encoding=None,
365
                check=True,
366
                text=False,
367
            )
368
            dot_bytes_image = io.BytesIO(dot_result.stdout)
×
369
            image = Image.open(dot_bytes_image)
×
370
            return image
×
371
        else:
372
            subprocess.run(
×
373
                [prog, "-T", image_type, "-o", filename],
374
                input=dot_str,
375
                check=True,
376
                encoding="utf8",
377
                text=True,
378
            )
379
            return None
×
380

381
    else:
382
        return graphviz_draw(
×
383
            dag._multi_graph,
384
            node_attr_func,
385
            edge_attr_func,
386
            graph_attrs,
387
            filename,
388
            image_type,
389
        )
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