Repository: Qiskit/qiskit

Issue:
Pass Manager Visualization Tests fail locally
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: Master
- **Python version**: 3.7
- **Operating system**: Linux
- dot - graphviz version 2.40.1 (0)

### What is the current behavior?

When running the terra unit tests locally from a fresh master with no local modification the image comparison in the pass manager visualization tests fail for me locally. Either we need to relax the comparison threshold or make the image drawing deterministic

```
test.python.visualization.test_pass_manager_drawer.TestPassManagerDrawer.test_pass_manager_drawer_basic
-------------------------------------------------------------------------------------------------------

Captured traceback:
~~~~~~~~~~~~~~~~~~~
    b'Traceback (most recent call last):'
    b'  File "/home/computertreker/git/qiskit/qiskit-core/test/python/visualization/test_pass_manager_drawer.py", line 52, in test_pass_manager_drawer_basic'
    b"    self.assertImagesAreEqual(filename, path_to_diagram_reference('pass_manager_l0_ref.png'))"
    b'  File "/home/computertreker/git/qiskit/qiskit-core/test/python/visualization/visualization.py", line 62, in assertImagesAreEqual'
    b'    .format(diff_tolerance * 100))'
    b'  File "/usr/lib64/python3.7/unittest/case.py", line 692, in assertTrue'
    b'    raise self.failureException(msg)'
    b'AssertionError: False is not true : The images are different by more than a 0.1%'
    b''

test.python.visualization.test_pass_manager_drawer.TestPassManagerDrawer.test_pass_manager_drawer_style
-------------------------------------------------------------------------------------------------------

Captured traceback:
~~~~~~~~~~~~~~~~~~~
    b'Traceback (most recent call last):'
    b'  File "/home/computertreker/git/qiskit/qiskit-core/test/python/visualization/test_pass_manager_drawer.py", line 69, in test_pass_manager_drawer_style'
    b"    path_to_diagram_reference('pass_manager_l1_style_ref.png'))"
    b'  File "/home/computertreker/git/qiskit/qiskit-core/test/python/visualization/visualization.py", line 62, in assertImagesAreEqual'
    b'    .format(diff_tolerance * 100))'
    b'  File "/usr/lib64/python3.7/unittest/case.py", line 692, in assertTrue'
    b'    raise self.failureException(msg)'
    b'AssertionError: False is not true : The images are different by more than a 0.1%'
    b''
```

### Steps to reproduce the problem

Run unit tests with graphviz installed

### What is the expected behavior?

The tests pass

### Suggested solutions

Either we need to relax the comparison threshold or make the image drawing deterministic



Discussion:
I also doubt these tests will work in travis, it looks like we're skipping them because graphviz isn't installed in the CI environment: https://travis-ci.com/Qiskit/qiskit-terra/jobs/202883754#L2216

In a patch that fixes this we should add graphviz to the installed package list to verify this works everywhere.
These are the images being generated locally for me by the 2 failing  tests:

current_l1.png:
![current_l1](https://user-images.githubusercontent.com/2447371/58477394-6763ac00-8121-11e9-8612-6aee73cf9413.png)
current_l0.png:
![current_l0](https://user-images.githubusercontent.com/2447371/58477395-6763ac00-8121-11e9-8f96-e73dd0ec7eb4.png)

is this an exclusive image comparison issue (like a slightly different alignment)? Maybe a font availability issue?
@1ucian0 is correct, it looks like the font is different see this snippet of the reference file :
![image](https://user-images.githubusercontent.com/40489777/58641134-82394a80-82f2-11e9-9b1e-a50bb10f3bfe.png)
I will have a look at pinning this so that it is the same everywhere.


Fonts might vary a lot in each platform. Ideally, we should try to find the same font for every platform, but it might not be possible. We should consider alternatives if that's a dead-end. 

Resolving patch:
diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py
--- a/qiskit/transpiler/passmanager.py
+++ b/qiskit/transpiler/passmanager.py
@@ -141,9 +141,9 @@ def run(self, circuit):
         circuit.name = name
         return circuit
 
-    def draw(self, filename, style=None):
+    def draw(self, filename, style=None, raw=False):
         """ Draw the pass manager"""
-        pass_manager_drawer(self, filename=filename, style=style)
+        pass_manager_drawer(self, filename=filename, style=style, raw=raw)
 
     def _do_pass(self, pass_, dag, options):
         """Do a pass and its "requires".
diff --git a/qiskit/visualization/pass_manager_visualization.py b/qiskit/visualization/pass_manager_visualization.py
--- a/qiskit/visualization/pass_manager_visualization.py
+++ b/qiskit/visualization/pass_manager_visualization.py
@@ -36,7 +36,7 @@
     HAS_GRAPHVIZ = False
 
 
-def pass_manager_drawer(pass_manager, filename, style=None):
+def pass_manager_drawer(pass_manager, filename, style=None, raw=False):
     """
     Draws the pass manager.
 
@@ -51,6 +51,8 @@ def pass_manager_drawer(pass_manager, filename, style=None):
             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
+        raw (Bool) : True if you want to save the raw Dot output not an image. The
+            default is False.
 
     Raises:
         ImportError: when nxpd or pydot not installed.
@@ -77,7 +79,7 @@ def pass_manager_drawer(pass_manager, filename, style=None):
     # 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
+    component_id = 0
 
     prev_node = None
 
@@ -87,18 +89,20 @@ def pass_manager_drawer(pass_manager, filename, style=None):
         label = controller_group['type'].__name__.replace('Controller', '')
 
         # create the subgraph for this controller
-        subgraph = pydot.Cluster(str(id(controller_group)), label=label)
+        subgraph = pydot.Cluster(str(component_id), label=label, fontname='helvetica')
+        component_id += 1
 
         for pass_ in controller_group['passes']:
 
             # label is the name of the pass
-            node = pydot.Node(str(node_id),
+            node = pydot.Node(str(component_id),
                               label=str(type(pass_).__name__),
                               color=_get_node_color(pass_, style),
-                              shape="rectangle")
+                              shape="rectangle",
+                              fontname='helvetica')
 
             subgraph.add_node(node)
-            node_id += 1
+            component_id += 1
 
             # the arguments that were provided to the pass when it was created
             arg_spec = inspect.getfullargspec(pass_.__init__)
@@ -115,13 +119,14 @@ def pass_manager_drawer(pass_manager, filename, style=None):
                 if arg_index >= (len(args) - num_optional):
                     nd_style = 'dashed'
 
-                input_node = pydot.Node(node_id, label=arg,
+                input_node = pydot.Node(component_id, label=arg,
                                         color="black",
                                         shape="ellipse",
                                         fontsize=10,
-                                        style=nd_style)
+                                        style=nd_style,
+                                        fontname='helvetica')
                 subgraph.add_node(input_node)
-                node_id += 1
+                component_id += 1
                 subgraph.add_edge(pydot.Edge(input_node, node))
 
             # if there is a previous node, add an edge between them
@@ -133,8 +138,11 @@ def pass_manager_drawer(pass_manager, filename, style=None):
         graph.add_subgraph(subgraph)
 
     if filename:
-        # linter says this isn't a method - it is
-        graph.write_png(filename)  # pylint: disable=no-member
+        if not raw:
+            # linter says this isn't a method - it is
+            graph.write_png(filename)  # pylint: disable=no-member
+        else:
+            graph.write(filename, format='raw')
 
 
 def _get_node_color(pss, style):


Test patch:
diff --git a/test/python/visualization/references/pass_manager_l0.dot b/test/python/visualization/references/pass_manager_l0.dot
new file mode 100644
--- /dev/null
+++ b/test/python/visualization/references/pass_manager_l0.dot
@@ -0,0 +1,93 @@
+digraph G {
+subgraph cluster_0 {
+fontname=helvetica;
+label=FlowLinear;
+1 [color=red, fontname=helvetica, label=SetLayout, shape=rectangle];
+2 [color=black, fontname=helvetica, fontsize=10, label=layout, shape=ellipse, style=solid];
+2 -> 1;
+}
+
+subgraph cluster_3 {
+fontname=helvetica;
+label=Conditional;
+4 [color=red, fontname=helvetica, label=TrivialLayout, shape=rectangle];
+5 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid];
+5 -> 4;
+1 -> 4;
+}
+
+subgraph cluster_6 {
+fontname=helvetica;
+label=FlowLinear;
+7 [color=red, fontname=helvetica, label=FullAncillaAllocation, shape=rectangle];
+8 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid];
+8 -> 7;
+9 [color=black, fontname=helvetica, fontsize=10, label=layout, shape=ellipse, style=dashed];
+9 -> 7;
+4 -> 7;
+10 [color=blue, fontname=helvetica, label=EnlargeWithAncilla, shape=rectangle];
+11 [color=black, fontname=helvetica, fontsize=10, label=layout, shape=ellipse, style=dashed];
+11 -> 10;
+7 -> 10;
+}
+
+subgraph cluster_12 {
+fontname=helvetica;
+label=FlowLinear;
+13 [color=blue, fontname=helvetica, label=Unroller, shape=rectangle];
+14 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=solid];
+14 -> 13;
+10 -> 13;
+}
+
+subgraph cluster_15 {
+fontname=helvetica;
+label=FlowLinear;
+16 [color=red, fontname=helvetica, label=CheckMap, shape=rectangle];
+17 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid];
+17 -> 16;
+18 [color=black, fontname=helvetica, fontsize=10, label=initial_layout, shape=ellipse, style=dashed];
+18 -> 16;
+13 -> 16;
+}
+
+subgraph cluster_19 {
+fontname=helvetica;
+label=Conditional;
+20 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle];
+16 -> 20;
+21 [color=blue, fontname=helvetica, label=LegacySwap, shape=rectangle];
+22 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid];
+22 -> 21;
+23 [color=black, fontname=helvetica, fontsize=10, label=initial_layout, shape=ellipse, style=dashed];
+23 -> 21;
+24 [color=black, fontname=helvetica, fontsize=10, label=trials, shape=ellipse, style=dashed];
+24 -> 21;
+25 [color=black, fontname=helvetica, fontsize=10, label=seed, shape=ellipse, style=dashed];
+25 -> 21;
+20 -> 21;
+26 [color=blue, fontname=helvetica, label=Decompose, shape=rectangle];
+27 [color=black, fontname=helvetica, fontsize=10, label=gate, shape=ellipse, style=dashed];
+27 -> 26;
+21 -> 26;
+}
+
+subgraph cluster_28 {
+fontname=helvetica;
+label=Conditional;
+29 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle];
+30 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid];
+30 -> 29;
+31 [color=black, fontname=helvetica, fontsize=10, label=initial_layout, shape=ellipse, style=dashed];
+31 -> 29;
+26 -> 29;
+}
+
+subgraph cluster_32 {
+fontname=helvetica;
+label=FlowLinear;
+33 [color=blue, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle];
+29 -> 33;
+}
+
+}
diff --git a/test/python/visualization/references/pass_manager_l0_ref.png b/test/python/visualization/references/pass_manager_l0_ref.png
deleted file mode 100644
Binary files a/test/python/visualization/references/pass_manager_l0_ref.png and /dev/null differ
diff --git a/test/python/visualization/references/pass_manager_l1_style_ref.png b/test/python/visualization/references/pass_manager_l1_style_ref.png
deleted file mode 100644
Binary files a/test/python/visualization/references/pass_manager_l1_style_ref.png and /dev/null differ
diff --git a/test/python/visualization/references/pass_manager_style_l1.dot b/test/python/visualization/references/pass_manager_style_l1.dot
new file mode 100644
--- /dev/null
+++ b/test/python/visualization/references/pass_manager_style_l1.dot
@@ -0,0 +1,128 @@
+digraph G {
+subgraph cluster_0 {
+fontname=helvetica;
+label=FlowLinear;
+1 [color=cyan, fontname=helvetica, label=SetLayout, shape=rectangle];
+2 [color=black, fontname=helvetica, fontsize=10, label=layout, shape=ellipse, style=solid];
+2 -> 1;
+}
+
+subgraph cluster_3 {
+fontname=helvetica;
+label=Conditional;
+4 [color=red, fontname=helvetica, label=TrivialLayout, shape=rectangle];
+5 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid];
+5 -> 4;
+1 -> 4;
+}
+
+subgraph cluster_6 {
+fontname=helvetica;
+label=FlowLinear;
+7 [color=green, fontname=helvetica, label=CheckMap, shape=rectangle];
+8 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid];
+8 -> 7;
+9 [color=black, fontname=helvetica, fontsize=10, label=initial_layout, shape=ellipse, style=dashed];
+9 -> 7;
+4 -> 7;
+}
+
+subgraph cluster_10 {
+fontname=helvetica;
+label=Conditional;
+11 [color=red, fontname=helvetica, label=DenseLayout, shape=rectangle];
+12 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid];
+12 -> 11;
+7 -> 11;
+}
+
+subgraph cluster_13 {
+fontname=helvetica;
+label=FlowLinear;
+14 [color=red, fontname=helvetica, label=FullAncillaAllocation, shape=rectangle];
+15 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid];
+15 -> 14;
+16 [color=black, fontname=helvetica, fontsize=10, label=layout, shape=ellipse, style=dashed];
+16 -> 14;
+11 -> 14;
+17 [color=pink, fontname=helvetica, label=EnlargeWithAncilla, shape=rectangle];
+18 [color=black, fontname=helvetica, fontsize=10, label=layout, shape=ellipse, style=dashed];
+18 -> 17;
+14 -> 17;
+}
+
+subgraph cluster_19 {
+fontname=helvetica;
+label=FlowLinear;
+20 [color=blue, fontname=helvetica, label=Unroller, shape=rectangle];
+21 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=solid];
+21 -> 20;
+17 -> 20;
+}
+
+subgraph cluster_22 {
+fontname=helvetica;
+label=FlowLinear;
+23 [color=green, fontname=helvetica, label=CheckMap, shape=rectangle];
+24 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid];
+24 -> 23;
+25 [color=black, fontname=helvetica, fontsize=10, label=initial_layout, shape=ellipse, style=dashed];
+25 -> 23;
+20 -> 23;
+}
+
+subgraph cluster_26 {
+fontname=helvetica;
+label=Conditional;
+27 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle];
+23 -> 27;
+28 [color=blue, fontname=helvetica, label=LegacySwap, shape=rectangle];
+29 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid];
+29 -> 28;
+30 [color=black, fontname=helvetica, fontsize=10, label=initial_layout, shape=ellipse, style=dashed];
+30 -> 28;
+31 [color=black, fontname=helvetica, fontsize=10, label=trials, shape=ellipse, style=dashed];
+31 -> 28;
+32 [color=black, fontname=helvetica, fontsize=10, label=seed, shape=ellipse, style=dashed];
+32 -> 28;
+27 -> 28;
+33 [color=blue, fontname=helvetica, label=Decompose, shape=rectangle];
+34 [color=black, fontname=helvetica, fontsize=10, label=gate, shape=ellipse, style=dashed];
+34 -> 33;
+28 -> 33;
+}
+
+subgraph cluster_35 {
+fontname=helvetica;
+label=Conditional;
+36 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle];
+37 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid];
+37 -> 36;
+38 [color=black, fontname=helvetica, fontsize=10, label=initial_layout, shape=ellipse, style=dashed];
+38 -> 36;
+33 -> 36;
+}
+
+subgraph cluster_39 {
+fontname=helvetica;
+label=FlowLinear;
+40 [color=grey, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle];
+36 -> 40;
+}
+
+subgraph cluster_41 {
+fontname=helvetica;
+label=DoWhile;
+42 [color=red, fontname=helvetica, label=Depth, shape=rectangle];
+40 -> 42;
+43 [color=red, fontname=helvetica, label=FixedPoint, shape=rectangle];
+44 [color=black, fontname=helvetica, fontsize=10, label=property_to_check, shape=ellipse, style=solid];
+44 -> 43;
+42 -> 43;
+45 [color=blue, fontname=helvetica, label=Optimize1qGates, shape=rectangle];
+43 -> 45;
+46 [color=blue, fontname=helvetica, label=CXCancellation, shape=rectangle];
+45 -> 46;
+}
+
+}
diff --git a/test/python/visualization/test_pass_manager_drawer.py b/test/python/visualization/test_pass_manager_drawer.py
--- a/test/python/visualization/test_pass_manager_drawer.py
+++ b/test/python/visualization/test_pass_manager_drawer.py
@@ -46,10 +46,10 @@ def setUp(self):
                      '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)
+        filename = self._get_resource_path('current_l0.dot')
+        level_0_pass_manager(self.config).draw(filename=filename, raw=True)
 
-        self.assertImagesAreEqual(filename, path_to_diagram_reference('pass_manager_l0_ref.png'))
+        self.assertFilesAreEqual(filename, path_to_diagram_reference('pass_manager_l0.dot'))
         os.remove(filename)
 
     @unittest.skipIf(not HAS_GRAPHVIZ,
@@ -62,11 +62,11 @@ def test_pass_manager_drawer_style(self):
                  EnlargeWithAncilla: 'pink',
                  RemoveResetInZeroState: 'grey'}
 
-        filename = self._get_resource_path('current_l1.png')
-        level_1_pass_manager(self.config).draw(filename=filename, style=style)
+        filename = self._get_resource_path('current_l1.dot')
+        level_1_pass_manager(self.config).draw(filename=filename, style=style, raw=True)
 
-        self.assertImagesAreEqual(filename,
-                                  path_to_diagram_reference('pass_manager_l1_style_ref.png'))
+        self.assertFilesAreEqual(filename,
+                                 path_to_diagram_reference('pass_manager_style_l1.dot'))
         os.remove(filename)