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

Qiskit / qiskit / 22993401850

12 Mar 2026 08:38AM UTC coverage: 87.804% (+0.1%) from 87.693%
22993401850

Pull #15635

github

web-flow
Merge 23bc4390b into cd8701690
Pull Request #15635: handle panic errors in Quantum Shannon Decomposition rust code

26 of 34 new or added lines in 2 files covered. (76.47%)

1394 existing lines in 34 files now uncovered.

101461 of 115554 relevant lines covered (87.8%)

1167166.39 hits per line

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

16.81
/qiskit/visualization/pass_manager_visualization.py
1
# This code is part of Qiskit.
2
#
3
# (C) Copyright IBM 2019.
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
Visualization function for a pass manager. Passes are grouped based on their
15
flow controller, and coloured based on the type of pass.
16
"""
17
from __future__ import annotations
1✔
18

19
import os
1✔
20
import inspect
1✔
21
import tempfile
1✔
22

23
from qiskit.utils import optionals as _optionals
1✔
24
from qiskit.passmanager.base_tasks import BaseController, GenericPass
1✔
25
from qiskit.passmanager.flow_controllers import FlowControllerLinear
1✔
26
from qiskit.transpiler.basepasses import AnalysisPass, TransformationPass
1✔
27
from .exceptions import VisualizationError
1✔
28

29
DEFAULT_STYLE = {AnalysisPass: "red", TransformationPass: "blue"}
1✔
30

31

32
@_optionals.HAS_GRAPHVIZ.require_in_call
1✔
33
@_optionals.HAS_PYDOT.require_in_call
1✔
34
def pass_manager_drawer(pass_manager, filename=None, style=None, raw=False):
1✔
35
    """
36
    Draws the pass manager.
37

38
    This function needs `pydot <https://github.com/pydot/pydot>`__, which in turn needs
39
    `Graphviz <https://www.graphviz.org/>`__ to be installed.
40

41
    .. warning::
42
        This function will call the system Graphviz tool on a file involving user-controllable
43
        strings (such as pass names).  It is recommended to only call this function on trusted
44
        input.
45

46
    Args:
47
        pass_manager (PassManager): the pass manager to be drawn
48
        filename (str): file path to save image to
49
        style (dict or OrderedDict): keys are the pass classes and the values are
50
            the colors to make them. An example can be seen in the DEFAULT_STYLE. An ordered
51
            dict can be used to ensure a priority coloring when pass falls into multiple
52
            categories. Any values not included in the provided dict will be filled in from
53
            the default dict
54
        raw (Bool) : True if you want to save the raw Dot output not an image. The
55
            default is False.
56
    Returns:
57
        PIL.Image or None: an in-memory representation of the pass manager. Or None if
58
        no image was generated or PIL is not installed.
59
    Raises:
60
        MissingOptionalLibraryError: when nxpd or pydot not installed.
61
        VisualizationError: If raw=True and filename=None.
62

63
    Example:
64
        .. plot::
65
            :include-source:
66
            :nofigs:
67

68
            from qiskit import QuantumCircuit
69
            from qiskit.transpiler import generate_preset_pass_manager
70
            from qiskit.visualization import pass_manager_drawer
71

72
            pm = generate_preset_pass_manager(optimization_level=0)
73
            pass_manager_drawer(pm)
74
    """
UNCOV
75
    import pydot
×
76

UNCOV
77
    if not style:
×
UNCOV
78
        style = DEFAULT_STYLE
×
79

80
    # create the overall graph
81
    graph = pydot.Dot()
×
82

83
    # identifiers for nodes need to be unique, so assign an id
84
    # can't just use python's id in case the exact same pass was
85
    # appended more than once
86
    component_id = 0
×
87

UNCOV
88
    prev_node = None
×
89

UNCOV
90
    for index, controller_group in enumerate(pass_manager.to_flow_controller().tasks):
×
91
        subgraph, component_id, prev_node = draw_subgraph(
×
92
            controller_group, component_id, style, prev_node, index
93
        )
UNCOV
94
        graph.add_subgraph(subgraph)
×
95

UNCOV
96
    output = make_output(graph, raw, filename)
×
97
    return output
×
98

99

100
def _get_node_color(pss, style):
1✔
101
    # look in the user provided dict first
102
    for typ, color in style.items():
×
103
        if isinstance(pss, typ):
×
104
            return color
×
105

106
    # failing that, look in the default
UNCOV
107
    for typ, color in DEFAULT_STYLE.items():
×
UNCOV
108
        if isinstance(pss, typ):
×
UNCOV
109
            return color
×
110

UNCOV
111
    return "black"
×
112

113

114
@_optionals.HAS_GRAPHVIZ.require_in_call
1✔
115
@_optionals.HAS_PYDOT.require_in_call
1✔
116
def staged_pass_manager_drawer(pass_manager, filename=None, style=None, raw=False):
1✔
117
    """
118
    Draws the staged pass manager.
119

120
        This function needs `pydot <https://github.com/erocarrera/pydot>`__, which in turn needs
121
    `Graphviz <https://www.graphviz.org/>`__ to be installed.
122

123
    .. warning::
124
        This function will call the system Graphviz tool on a file involving user-controllable
125
        strings (such as pass names).  It is recommended to only call this function on trusted
126
        input.
127

128
    Args:
129
        pass_manager (StagedPassManager): the staged pass manager to be drawn
130
        filename (str): file path to save image to
131
        style (dict or OrderedDict): keys are the pass classes and the values are
132
            the colors to make them. An example can be seen in the DEFAULT_STYLE. An ordered
133
            dict can be used to ensure a priority coloring when pass falls into multiple
134
            categories. Any values not included in the provided dict will be filled in from
135
            the default dict
136
        raw (Bool) : True if you want to save the raw Dot output not an image. The
137
            default is False.
138
    Returns:
139
        PIL.Image or None: an in-memory representation of the pass manager. Or None if
140
        no image was generated or PIL is not installed.
141
    Raises:
142
        MissingOptionalLibraryError: when nxpd or pydot not installed.
143
        VisualizationError: If raw=True and filename=None.
144

145
    Example:
146
        .. plot::
147
           :include-source:
148
           :nofigs:
149

150
            %matplotlib inline
151
            from qiskit.providers.fake_provider import GenericBackendV2
152
            from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
153

154
            pass_manager = generate_preset_pass_manager(3, GenericBackendV2(num_qubits=5))
155
            pass_manager.draw()
156
    """
UNCOV
157
    import pydot
×
158

159
    # only include stages that have passes
UNCOV
160
    stages = list(filter(lambda s: s is not None, pass_manager.expanded_stages))
×
161

UNCOV
162
    if not style:
×
UNCOV
163
        style = DEFAULT_STYLE
×
164

165
    # create the overall graph
166
    graph = pydot.Dot()
×
167

168
    # identifiers for nodes need to be unique, so assign an id
169
    # can't just use python's id in case the exact same pass was
170
    # appended more than once
171
    component_id = 0
×
172

173
    # keep a running count of indexes across stages
174
    idx = 0
×
175

UNCOV
176
    prev_node = None
×
177

178
    for st in stages:
×
179
        stage = getattr(pass_manager, st)
×
180

181
        if stage is not None:
×
182
            stagegraph = pydot.Cluster(str(st), fontname="helvetica", label=str(st), labeljust="l")
×
UNCOV
183
            for controller_group in stage.to_flow_controller().tasks:
×
UNCOV
184
                subgraph, component_id, prev_node = draw_subgraph(
×
185
                    controller_group, component_id, style, prev_node, idx
186
                )
187
                stagegraph.add_subgraph(subgraph)
×
UNCOV
188
                idx += 1
×
UNCOV
189
            graph.add_subgraph(stagegraph)
×
190

191
    output = make_output(graph, raw, filename)
×
UNCOV
192
    return output
×
193

194

195
def draw_subgraph(controller_group, component_id, style, prev_node, idx):
1✔
196
    """Draw subgraph."""
197
    import pydot
×
198

199
    # label is the name of the flow controller parameter
200
    label = f"[{idx}] "
×
UNCOV
201
    if isinstance(controller_group, BaseController) and not isinstance(
×
202
        controller_group, FlowControllerLinear
203
    ):
204
        label += f"{controller_group.__class__.__name__}"
×
205

206
    # create the subgraph for this controller
207
    subgraph = pydot.Cluster(str(component_id), fontname="helvetica", label=label, labeljust="l")
×
UNCOV
208
    component_id += 1
×
209

210
    if isinstance(controller_group, BaseController):
×
211
        # Assume linear pipeline
212
        # TODO: support pipeline branching when such controller is introduced
213
        tasks = getattr(controller_group, "tasks", [])
×
UNCOV
214
    elif isinstance(controller_group, GenericPass):
×
UNCOV
215
        tasks = [controller_group]
×
UNCOV
216
    elif isinstance(controller_group, (list, tuple)):
×
UNCOV
217
        tasks = controller_group
×
218
    else:
219
        # Invalid data
UNCOV
220
        return subgraph, component_id, prev_node
×
221

UNCOV
222
    flatten_tasks = []
×
223
    for task in tasks:
×
224
        # Flatten nested linear flow controller.
225
        # This situation often occurs in the builtin pass managers because it constructs
226
        # some stages by appending other pass manager instance converted into a linear controller.
227
        # Flattening inner linear controller tasks doesn't change the execution.
UNCOV
228
        if isinstance(task, FlowControllerLinear):
×
UNCOV
229
            flatten_tasks.extend(task.tasks)
×
230
        else:
UNCOV
231
            flatten_tasks.append(task)
×
232

UNCOV
233
    for task in flatten_tasks:
×
UNCOV
234
        if isinstance(task, BaseController):
×
235
            # Partly nested flow controller
236
            # TODO recursively inject subgraph into subgraph
UNCOV
237
            node = pydot.Node(
×
238
                str(component_id),
239
                color="k",
240
                fontname="helvetica",
241
                label="Nested flow controller",
242
                shape="rectangle",
243
            )
244
        else:
245
            # label is the name of the pass
UNCOV
246
            node = pydot.Node(
×
247
                str(component_id),
248
                color=_get_node_color(task, style),
249
                fontname="helvetica",
250
                label=str(type(task).__name__),
251
                shape="rectangle",
252
            )
253

UNCOV
254
        subgraph.add_node(node)
×
255
        component_id += 1
×
256

257
        # the arguments that were provided to the pass when it was created
UNCOV
258
        arg_spec = inspect.getfullargspec(task.__init__)
×
259
        # 0 is the args, 1: to remove the self arg
260
        args = arg_spec[0][1:]
×
261

262
        num_optional = len(arg_spec[3]) if arg_spec[3] else 0
×
263

264
        # add in the inputs to the pass
UNCOV
265
        for arg_index, arg in enumerate(args):
×
UNCOV
266
            nd_style = "solid"
×
267
            # any optional args are dashed
268
            # the num of optional counts from the end towards the start of the list
UNCOV
269
            if arg_index >= (len(args) - num_optional):
×
UNCOV
270
                nd_style = "dashed"
×
271

272
            input_node = pydot.Node(
×
273
                component_id,
274
                color="black",
275
                fontname="helvetica",
276
                fontsize=10,
277
                label=arg,
278
                shape="ellipse",
279
                style=nd_style,
280
            )
281
            subgraph.add_node(input_node)
×
UNCOV
282
            component_id += 1
×
UNCOV
283
            subgraph.add_edge(pydot.Edge(input_node, node))
×
284

285
        # if there is a previous node, add an edge between them
286
        if prev_node:
×
287
            subgraph.add_edge(pydot.Edge(prev_node, node))
×
288

289
        prev_node = node
×
290

291
    return subgraph, component_id, prev_node
×
292

293

294
def make_output(graph, raw, filename):
1✔
295
    """Produce output for pass_manager."""
296
    if raw:
×
UNCOV
297
        if filename:
×
298
            graph.write(filename, format="raw")
×
UNCOV
299
            return None
×
300
        else:
301
            raise VisualizationError("if format=raw, then a filename is required.")
×
302

303
    if not _optionals.HAS_PIL and filename:
×
304
        # pylint says this isn't a method - it is
UNCOV
305
        graph.write_png(filename)
×
306
        return None
×
307

308
    _optionals.HAS_PIL.require_now("pass manager drawer")
×
309

310
    with tempfile.TemporaryDirectory() as tmpdirname:
×
311
        from PIL import Image
×
312

UNCOV
313
        tmppath = os.path.join(tmpdirname, "pass_manager.png")
×
314

315
        # pylint says this isn't a method - it is
UNCOV
316
        graph.write_png(tmppath)
×
317

UNCOV
318
        image = Image.open(tmppath)
×
UNCOV
319
        os.remove(tmppath)
×
UNCOV
320
        if filename:
×
UNCOV
321
            image.save(filename, "PNG")
×
UNCOV
322
        return image
×
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