Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduces PassManager drawer #2445

Merged
merged 16 commits into from
May 23, 2019
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ Removed
- The ``qiskit.qiskiterror`` module has been removed. Please use
``qiskit.exceptions`` instead. (#2399)

Added
-----

- Introduced a visualization for the Pass Manager. (#2445)


`0.8.0`_ - 2019-05-02
=====================

Expand Down
5 changes: 5 additions & 0 deletions qiskit/transpiler/passmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ def run(self, circuit):
circuit.name = name
return circuit

def draw(self, filename, style=None):
""" Draw the pass manager"""
from qiskit.visualization import pass_manager_drawer
maddy-tod marked this conversation as resolved.
Show resolved Hide resolved
pass_manager_drawer(self, filename=filename, style=style)

def _do_pass(self, pass_, dag, options):
"""Do a pass and its "requires".

Expand Down
1 change: 1 addition & 0 deletions qiskit/visualization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .pulse_visualization import pulse_drawer
from .circuit_visualization import circuit_drawer, qx_color_scheme
from .dag_visualization import dag_drawer
from .pass_manager_visualization import pass_manager_drawer
from .gate_map import plot_gate_map

from .exceptions import VisualizationError
Expand Down
147 changes: 147 additions & 0 deletions qiskit/visualization/pass_manager_visualization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2019.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
Visualization function for a pass manager. Passes are grouped based on their
flow controller, and coloured based on the type of pass.
"""
import inspect
from qiskit.transpiler.basepasses import AnalysisPass, TransformationPass
DEFAULT_STYLE = {AnalysisPass: 'red',
TransformationPass: 'blue'}


try:
maddy-tod marked this conversation as resolved.
Show resolved Hide resolved
import subprocess
print(subprocess.check_output(['dot', '-V']))
HAS_GRAPHVIZ = True
except FileNotFoundError:
# this is raised when the dot command cannot be found, which means GraphViz
# isn't installed
HAS_GRAPHVIZ = False


def pass_manager_drawer(pass_manager, filename, style=None):
maddy-tod marked this conversation as resolved.
Show resolved Hide resolved
"""
Draws the pass manager.

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

Args:
pass_manager (PassManager): the pass manager to be drawn
filename (str): file path to save image to
style (dict or OrderedDict): keys are the pass classes and the values are
the colors to make them. An example can be seen in the DEFAULT_STYLE. An ordered
dict can be used to ensure a priority coloring when pass falls into multiple
categories. Any values not included in the provided dict will be filled in from
the default dict

Raises:
ImportError: when nxpd or pydot not installed.
"""

try:
import pydot
maddy-tod marked this conversation as resolved.
Show resolved Hide resolved
if not HAS_GRAPHVIZ:
raise ImportError
except ImportError:
raise ImportError("pass_manager_drawer requires pydot and graphviz. "
"Run 'pip install pydot'. "
"Graphviz can be installed using 'brew install graphviz' on Mac"
" or by downloading it from the website.")

passes = pass_manager.passes()

if not style:
style = DEFAULT_STYLE

# create the overall graph
graph = pydot.Dot()

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

prev_node = None

for controller_group in passes:

# label is the name of the flow controller (without the word controller)
label = controller_group['type'].__name__.replace('Controller', '')

# create the subgraph for this controller
subgraph = pydot.Cluster(str(id(controller_group)), label=label)

for pss in controller_group['passes']:
maddy-tod marked this conversation as resolved.
Show resolved Hide resolved

# label is the name of the pass
node = pydot.Node(str(node_id),
label=str(type(pss).__name__),
color=_get_node_color(pss, style),
shape="rectangle")

subgraph.add_node(node)
node_id += 1

# the arguments that were provided to the pass when it was created
arg_spec = inspect.getfullargspec(pss.__init__)
# 0 is the args, 1: to remove the self arg
args = arg_spec[0][1:]

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

# add in the inputs to the pass
for arg_index, arg in enumerate(args):
nd_style = 'solid'
# any optional args are dashed
# the num of optional counts from the end towards the start of the list
if arg_index >= (len(args) - num_optional):
nd_style = 'dashed'

input_node = pydot.Node(node_id, label=arg,
color="black",
shape="ellipse",
fontsize=10,
style=nd_style)
subgraph.add_node(input_node)
node_id += 1
subgraph.add_edge(pydot.Edge(input_node, node))

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

prev_node = node

graph.add_subgraph(subgraph)

if filename:
# linter says this isn't a method - it is
graph.write_png(filename) # pylint: disable=no-member
maddy-tod marked this conversation as resolved.
Show resolved Hide resolved


def _get_node_color(pss, style):

# look in the user provided dict first
for typ, color in style.items():
if isinstance(pss, typ):
return color

# failing that, look in the default
for typ, color in DEFAULT_STYLE.items():
if isinstance(pss, typ):
return color

return "black"
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
74 changes: 74 additions & 0 deletions test/python/visualization/test_pass_manager_drawer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2019.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Tests for pass manager visualization tool."""

import unittest
import os

from qiskit.transpiler.transpile_config import TranspileConfig
from qiskit.transpiler import CouplingMap, Layout
from qiskit import QuantumRegister
from qiskit.transpiler.preset_passmanagers import level_0_pass_manager, level_1_pass_manager
from qiskit.transpiler.passes import SetLayout, CheckMap, EnlargeWithAncilla, RemoveResetInZeroState

from qiskit.visualization.pass_manager_visualization import HAS_GRAPHVIZ
maddy-tod marked this conversation as resolved.
Show resolved Hide resolved
from .visualization import QiskitVisualizationTestCase, path_to_diagram_reference


class TestPassManagerDrawer(QiskitVisualizationTestCase):
"""Qiskit pass manager drawer tests."""

def setUp(self):
coupling = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6]]
coupling_map = CouplingMap(couplinglist=coupling)
qr = QuantumRegister(7, 'q')
layout = Layout({qr[i]: i for i in range(coupling_map.size())})
self.config = TranspileConfig(optimization_level=1,
basis_gates=['u1', 'u3', 'u2', 'cx'],
initial_layout=layout,
coupling_map=coupling_map,
seed_transpiler=987,
backend_properties=None)

@unittest.skipIf(not HAS_GRAPHVIZ,
'Graphviz not installed.')
def test_pass_manager_drawer_basic(self):
"""Test to see if the drawer draws a normal pass manager correctly"""
filename = self._get_resource_path('current_l0.png')
level_0_pass_manager(self.config).draw(filename=filename)

self.assertImagesAreEqual(filename, path_to_diagram_reference('pass_manager_l0_ref.png'))
os.remove(filename)

@unittest.skipIf(not HAS_GRAPHVIZ,
'Graphviz not installed.')
def test_pass_manager_drawer_style(self):
"""Test to see if the colours are updated when provided by the user"""
# set colours for some passes, but leave others to take the default values
style = {SetLayout: 'cyan',
CheckMap: 'green',
EnlargeWithAncilla: 'pink',
RemoveResetInZeroState: 'grey'}

filename = self._get_resource_path('current_l1.png')
level_1_pass_manager(self.config).draw(filename=filename, style=style)

self.assertImagesAreEqual(filename,
path_to_diagram_reference('pass_manager_l1_style_ref.png'))
os.remove(filename)


if __name__ == '__main__':
unittest.main(verbosity=2)