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

Qiskit / qiskit / 15411751150

03 Jun 2025 07:57AM UTC coverage: 87.834% (+0.02%) from 87.81%
15411751150

Pull #13929

github

web-flow
Merge 7074f5b75 into 6455e8bdf
Pull Request #13929: Porting some of the MCX synthesis methods to Rust

247 of 259 new or added lines in 3 files covered. (95.37%)

1224 existing lines in 28 files now uncovered.

80581 of 91742 relevant lines covered (87.83%)

363859.73 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

UNCOV
160
    graph_attrs = {}
×
161
    if isinstance(style, dict):
×
162
        for attr in ["fontsize", "bgcolor", "dpi", "pad"]:
×
163
            if attr in style:
×
UNCOV
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)}
×
UNCOV
181
        graph_attrs.update({"dpi": str(100 * scale)})
×
182

UNCOV
183
        dag_dep_circ = dagdependency_to_circuit(dag)
×
184

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

191
            n = {}
×
192
            args = []
×
193
            for count, arg in enumerate(node.qargs + node.cargs):
×
UNCOV
194
                if count > 4:
×
UNCOV
195
                    args.append("...")
×
UNCOV
196
                    break
×
UNCOV
197
                if isinstance(arg, Qubit):
×
UNCOV
198
                    f_str = f"q_{qubit_indices[arg]}"
×
UNCOV
199
                elif isinstance(arg, Clbit):
×
UNCOV
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
            )
UNCOV
209
            if getattr(node.op, "condition", None):
×
UNCOV
210
                condition = node.op.condition
×
211
                if isinstance(condition, expr.Expr):
×
UNCOV
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

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

233
                if "nodecolor" in style:
×
UNCOV
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):
×
UNCOV
242
                    n["fillcolor"] = style["directivecolor"]
×
UNCOV
243
                elif getattr(node.op, "condition", None):
×
244
                    n["fillcolor"] = style["conditioncolor"]
×
245
                elif node.name == "measure":
×
UNCOV
246
                    n["fillcolor"] = style["measurecolor"]
×
247

UNCOV
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:
UNCOV
255
        graph_attrs.update({"dpi": str(100 * scale)})
×
256

UNCOV
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}")
×
UNCOV
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):
×
UNCOV
276
                    label = register_bit_labels.get(
×
277
                        node.wire, f"c[{dag.find_bit(node.wire).index}]"
278
                    )
279
                else:
UNCOV
280
                    label = str(node.wire.name)
×
281
                n["label"] = label
×
282

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

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

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

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

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

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

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

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

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

UNCOV
337
            return e
×
338

UNCOV
339
    image_type = "png"
×
UNCOV
340
    if filename:
×
UNCOV
341
        if "." not in filename:
×
UNCOV
342
            raise InvalidFileError("Parameter 'filename' must be in format 'name.extension'")
×
UNCOV
343
        image_type = filename.split(".")[-1]
×
UNCOV
344
        if image_type not in IMAGE_TYPES:
×
UNCOV
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

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

UNCOV
358
        prog = "dot"
×
UNCOV
359
        if not filename:
×
UNCOV
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
            )
UNCOV
368
            dot_bytes_image = io.BytesIO(dot_result.stdout)
×
UNCOV
369
            image = Image.open(dot_bytes_image)
×
UNCOV
370
            return image
×
371
        else:
UNCOV
372
            subprocess.run(
×
373
                [prog, "-T", image_type, "-o", filename],
374
                input=dot_str,
375
                check=True,
376
                encoding="utf8",
377
                text=True,
378
            )
UNCOV
379
            return None
×
380

381
    else:
UNCOV
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