diff --git a/.gitignore b/.gitignore index c9a349c3276c..b3d4b6bfd7a2 100644 --- a/.gitignore +++ b/.gitignore @@ -145,4 +145,5 @@ docs/stubs/* # Notebook testing images test/ipynb/mpl/*.png +test/ipynb/mpl/*.zip test/ipynb/mpl/result_test.json diff --git a/test/ipynb/mpl/references/conditional.png b/test/ipynb/mpl/references/conditional.png new file mode 100644 index 000000000000..4e247016969b Binary files /dev/null and b/test/ipynb/mpl/references/conditional.png differ diff --git a/test/ipynb/mpl/references/empty_circut.png b/test/ipynb/mpl/references/empty_circut.png new file mode 100644 index 000000000000..3bad502f0757 Binary files /dev/null and b/test/ipynb/mpl/references/empty_circut.png differ diff --git a/test/ipynb/mpl/references/no_barriers.png b/test/ipynb/mpl/references/no_barriers.png new file mode 100644 index 000000000000..d171482bf77d Binary files /dev/null and b/test/ipynb/mpl/references/no_barriers.png differ diff --git a/test/ipynb/mpl/references/plot_barriers_false.png b/test/ipynb/mpl/references/plot_barriers_false.png new file mode 100644 index 000000000000..fc57f227285e Binary files /dev/null and b/test/ipynb/mpl/references/plot_barriers_false.png differ diff --git a/test/ipynb/mpl/references/plot_barriers_true.png b/test/ipynb/mpl/references/plot_barriers_true.png new file mode 100644 index 000000000000..2b59ee72f26b Binary files /dev/null and b/test/ipynb/mpl/references/plot_barriers_true.png differ diff --git a/test/ipynb/mpl/test_circuit_matplotlib_drawer.py b/test/ipynb/mpl/test_circuit_matplotlib_drawer.py index e079ad7c2b3a..4584588b9ccf 100644 --- a/test/ipynb/mpl/test_circuit_matplotlib_drawer.py +++ b/test/ipynb/mpl/test_circuit_matplotlib_drawer.py @@ -19,8 +19,8 @@ from contextlib import contextmanager from qiskit.test import QiskitTestCase -from qiskit import QuantumCircuit, QuantumRegister -from qiskit.visualization import circuit_drawer +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister +from qiskit.visualization.circuit_visualization import _matplotlib_circuit_drawer RESULTDIR = os.path.dirname(os.path.abspath(__file__)) @@ -33,7 +33,7 @@ def save_data(image_filename, testname): data = json.load(datafile) else: data = {} - data[image_filename] = testname + data[image_filename] = {'testname': testname} with open(datafilename, 'w') as datafile: json.dump(data, datafile) @@ -63,7 +63,13 @@ class TestMatplotlibDrawer(QiskitTestCase): """Circuit MPL visualization""" def setUp(self): - self.circuit_drawer = save_data_wrap(circuit_drawer, str(self)) + self.circuit_drawer = save_data_wrap(_matplotlib_circuit_drawer, str(self)) + + def test_empty_circuit(self): + """Test empty circuit""" + circuit = QuantumCircuit() + + self.circuit_drawer(circuit, filename='empty_circut.png') def test_long_name(self): """Test to see that long register names can be seen completely @@ -81,7 +87,56 @@ def test_long_name(self): circuit.h(qr) circuit.h(qr) - self.circuit_drawer(circuit, output='mpl', filename='long_name.png') + self.circuit_drawer(circuit, filename='long_name.png') + + def test_conditional(self): + """Test that circuits with conditionals draw correctly + """ + qr = QuantumRegister(2, 'q') + cr = ClassicalRegister(2, 'c') + circuit = QuantumCircuit(qr, cr) + + # check gates are shifted over accordingly + circuit.h(qr) + circuit.measure(qr, cr) + circuit.h(qr[0]).c_if(cr, 2) + + self.circuit_drawer(circuit, filename='conditional.png') + + def test_plot_barriers(self): + """Test to see that plotting barriers works. + If it is set to False, no blank columns are introduced""" + + # generate a circuit with barriers and other barrier like instructions in + q = QuantumRegister(2, 'q') + c = ClassicalRegister(2, 'c') + circuit = QuantumCircuit(q, c) + + # check for barriers + circuit.h(q[0]) + circuit.barrier() + + # check for other barrier like commands + circuit.h(q[1]) + + # this import appears to be unused, but is actually needed to get snapshot instruction + import qiskit.extensions.simulator # pylint: disable=unused-import + circuit.snapshot('1') + + # check the barriers plot properly when plot_barriers= True + self.circuit_drawer(circuit, filename='plot_barriers_true.png', plot_barriers=True) + self.circuit_drawer(circuit, filename='plot_barriers_false.png', plot_barriers=False) + + def test_no_barriers_false(self): + """Generate the same circuit as test_plot_barriers but without the barrier commands + as this is what the circuit should look like when displayed with plot barriers false""" + q1 = QuantumRegister(2, 'q') + c1 = ClassicalRegister(2, 'c') + circuit = QuantumCircuit(q1, c1) + circuit.h(q1[0]) + circuit.h(q1[1]) + + self.circuit_drawer(circuit, filename='no_barriers.png', plot_barriers=False) if __name__ == '__main__': diff --git a/test/ipynb/results.py b/test/ipynb/results.py index 59565f8e2364..8d920ccda3bd 100644 --- a/test/ipynb/results.py +++ b/test/ipynb/results.py @@ -15,6 +15,7 @@ import os import json from PIL import Image, ImageChops, ImageDraw +import zipfile SWD = os.path.dirname(os.path.abspath(__file__)) @@ -56,15 +57,26 @@ def black_or_b(diff_image, image, reference, opacity=0.85): shade = new_gray(size, 0) new = reference.copy() new.paste(shade, mask=mask) + if image.size != new.size: + image = image.resize(new.size) new.paste(image, mask=thresholded_diff) return new +def zipfiles(files, zipname): + with zipfile.ZipFile(zipname, "w", zipfile.ZIP_DEFLATED) as zipf: + for file in files: + zipf.write(file, arcname=os.path.basename(file)) + + class Results: def __init__(self, names, directory): self.names = names self.directory = directory self.data = {} + self.exact_match = [] + self.mismatch = [] + self.missing = [] datafilename = os.path.join(SWD, directory, 'result_test.json') if os.path.exists(datafilename): with open(datafilename, 'r') as datafile: @@ -97,20 +109,58 @@ def no_reference_html(result, title): ret += '' return ret - def _repr_html_(self): - ret = "
" + def diff_images(self): for name in self.names: + ratio = diff_name = title = None fullpath_name = os.path.join(self.directory, name) fullpath_reference = os.path.join(self.directory, 'references', name) + if os.path.exists(os.path.join(SWD, fullpath_reference)): ratio, diff_name = similarity_ratio(fullpath_name, fullpath_reference) - title = '%s | %s | ratio: %s' % (name, self.data[name], ratio) + title = '%s | %s | ratio: %s' % (name, + self.data[name]['testname'], + ratio) if ratio == 1: + self.exact_match.append(fullpath_name) + else: + self.mismatch.append(fullpath_name) + else: + self.missing.append(fullpath_name) + + self.data[name]['ratio'] = ratio + self.data[name]['diff_name'] = diff_name + self.data[name]['title'] = title + + def summary(self): + ret = '' + + if len(self.mismatch) >= 2: + zipfiles(self.mismatch, 'mpl/mismatch.zip') + ret += '
' \ + 'Download %s mismatch results as a zip
' % len(self.mismatch) + + if len(self.mismatch) >= 2: + zipfiles(self.missing, 'mpl/missing.zip') + ret += '
' \ + 'Download %s missing results as a zip
' % len(self.missing) + + return ret + + def _repr_html_(self): + ret = self.summary() + ret += "
" + for name in self.names: + fullpath_name = os.path.join(self.directory, name) + fullpath_reference = os.path.join(self.directory, 'references', name) + if os.path.exists(os.path.join(SWD, fullpath_reference)): + if self.data[name]['ratio'] == 1: ret += Results.passed_result_html(fullpath_name, fullpath_reference, - diff_name, title) + self.data[name]['diff_name'], + self.data[name]['title']) else: ret += Results.failed_result_html(fullpath_name, fullpath_reference, - diff_name, title) + self.data[name]['diff_name'], + self.data[name]['title']) else: title = 'Download this image to %s' \ ' and add/push to the repo' % (name, fullpath_name, fullpath_reference) @@ -125,3 +175,4 @@ def _repr_html_(self): if file.endswith(".png") and not file.endswith(".diff.png"): result_files.append(file) results = Results(result_files, 'mpl') + results.diff_images() diff --git a/test/python/visualization/references/matplotlib_barriers_ref.png b/test/python/visualization/references/matplotlib_barriers_ref.png deleted file mode 100644 index 407bedaf68e0..000000000000 Binary files a/test/python/visualization/references/matplotlib_barriers_ref.png and /dev/null differ diff --git a/test/python/visualization/references/matplotlib_conditional_ref.png b/test/python/visualization/references/matplotlib_conditional_ref.png deleted file mode 100644 index 853d1b20f350..000000000000 Binary files a/test/python/visualization/references/matplotlib_conditional_ref.png and /dev/null differ diff --git a/test/python/visualization/test_circuit_matplotlib_drawer.py b/test/python/visualization/test_circuit_matplotlib_drawer.py deleted file mode 100644 index b58d00a8f72e..000000000000 --- a/test/python/visualization/test_circuit_matplotlib_drawer.py +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 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. - -# pylint: disable=invalid-name,missing-docstring - -import unittest -import os - -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister -from qiskit import visualization - -from .visualization import QiskitVisualizationTestCase - -if visualization.HAS_MATPLOTLIB: - from matplotlib import pyplot as plt - - -def _path_to_reference(filename): - return os.path.join(_this_directory(), 'references', filename) - - -def _this_directory(): - return os.path.dirname(os.path.abspath(__file__)) - - -class TestMatplotlibDrawer(QiskitVisualizationTestCase): - - def _expected_empty(self): - # Generate blank - expected = plt.figure() - expected.patch.set_facecolor(color='#ffffff') - ax = expected.add_subplot(111) - ax.axis('off') - ax.set_aspect('equal') - ax.tick_params(labelbottom=False, labeltop=False, - labelleft=False, labelright=False) - expected.set_size_inches(2.508333333333333, 0.2508333333333333) - return expected - - @unittest.skipIf(not visualization.HAS_MATPLOTLIB, 'matplotlib not available.') - def test_empty_circuit(self): - qc = QuantumCircuit() - filename = self._get_resource_path('current_pulse_matplotlib_ref.png') - visualization.circuit_drawer(qc, output='mpl', filename=filename) - self.addCleanup(os.remove, filename) - - expected_filename = self._get_resource_path('expected_current_pulse_matplotlib_ref.png') - expected = self._expected_empty() - expected.savefig(expected_filename) - self.addCleanup(os.remove, expected_filename) - - self.assertImagesAreEqual(filename, expected_filename) - - @unittest.skipIf(not visualization.HAS_MATPLOTLIB, 'matplotlib not available.') - def test_plot_barriers(self): - """Test to see that plotting barriers works. - If it is set to False, no blank columns are introduced""" - - # generate a circuit with barriers and other barrier like instructions in - q = QuantumRegister(2, 'q') - c = ClassicalRegister(2, 'c') - qc = QuantumCircuit(q, c) - - # check for barriers - qc.h(q[0]) - qc.barrier() - - # check for other barrier like commands - qc.h(q[1]) - - # this import appears to be unused, but is actually needed to get snapshot instruction - import qiskit.extensions.simulator # pylint: disable=unused-import - qc.snapshot('1') - - # check the barriers plot properly when plot_barriers= True - filename = self._get_resource_path('visualization/references/current_matplotlib_ref.png') - visualization.circuit_drawer(qc, output='mpl', plot_barriers=True, filename=filename) - self.addCleanup(os.remove, filename) - - ref_filename = self._get_resource_path( - 'visualization/references/matplotlib_barriers_ref.png') - self.assertImagesAreEqual(filename, ref_filename) - - # check that the barrier aren't plotted when plot_barriers = False - filename = self._get_resource_path('current_matplotlib_ref.png') - visualization.circuit_drawer(qc, output='mpl', plot_barriers=False, filename=filename) - self.addCleanup(os.remove, filename) - - # generate the same circuit but without the barrier commands as this is what the - # circuit should look like when displayed with plot barriers false - q1 = QuantumRegister(2, 'q') - c1 = ClassicalRegister(2, 'c') - qc1 = QuantumCircuit(q1, c1) - qc1.h(q1[0]) - qc1.h(q1[1]) - - no_barriers_filename = self._get_resource_path('current_no_barriers_matplotlib_ref.png') - visualization.circuit_drawer(qc1, output='mpl', justify='None', - filename=no_barriers_filename) - self.addCleanup(os.remove, no_barriers_filename) - - self.assertImagesAreEqual(filename, no_barriers_filename) - - @unittest.skipIf(not visualization.HAS_MATPLOTLIB, - 'matplotlib not available.') - @unittest.skip('Unreliable across python version') - def test_long_name(self): - """Test to see that long register names can be seen completely - As reported in #2605 - """ - - # add a register with a very long name - qr = QuantumRegister(4, 'veryLongQuantumRegisterName') - # add another to make sure adjustments are made based on longest - qrr = QuantumRegister(1, 'q0') - circuit = QuantumCircuit(qr, qrr) - - # check gates are shifted over accordingly - circuit.h(qr) - circuit.h(qr) - circuit.h(qr) - - filename = self._get_resource_path('current_%s_long_name_matplotlib.png' % os.name) - visualization.circuit_drawer(circuit, output='mpl', filename=filename) - self.addCleanup(os.remove, filename) - - ref_filename = self._get_resource_path( - 'visualization/references/%s_long_name_matplotlib.png' % os.name) - - self.assertImagesAreEqual(ref_filename, filename) - - @unittest.skipIf(not visualization.HAS_MATPLOTLIB, - 'matplotlib not available.') - def test_conditional(self): - """Test that circuits with conditionals draw correctly - """ - qr = QuantumRegister(2, 'q') - cr = ClassicalRegister(2, 'c') - circuit = QuantumCircuit(qr, cr) - - # check gates are shifted over accordingly - circuit.h(qr) - circuit.measure(qr, cr) - circuit.h(qr[0]).c_if(cr, 2) - - conditional_filename = self._get_resource_path('current_conditional_matplotlib_ref.png') - visualization.circuit_drawer(circuit, output='mpl', - filename=conditional_filename) - self.addCleanup(os.remove, conditional_filename) - - ref_filename = self._get_resource_path( - 'visualization/references/matplotlib_conditional_ref.png') - - self.assertImagesAreEqual(ref_filename, conditional_filename, diff_tolerance=0.002)