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

Metatest for swap mappers #1571

Merged
merged 86 commits into from
Feb 19, 2019
Merged
Show file tree
Hide file tree
Changes from 84 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
c6cd32e
LookaheadSwap
Dec 20, 2018
86efad4
introduce pickles
Dec 20, 2018
ed98c8f
move pickles to dir
Dec 20, 2018
c75d27e
AlmostEqual
Dec 20, 2018
05d7419
assertDictAlmostEqual
Dec 20, 2018
96caf94
handle_measurement
Dec 20, 2018
82d951b
fix circuit name
Dec 20, 2018
4d437a8
circuit name
Dec 20, 2018
37d6120
remove specific test dummy
Dec 20, 2018
4e0576c
lint
Dec 20, 2018
91cf8ab
add StochasticSwap
Dec 20, 2018
e3893aa
StochasticSwap pickles
Dec 20, 2018
b5a39f0
handling additional args
Dec 20, 2018
38d1081
style
Dec 20, 2018
1c4512e
pylint: disable=redefined-builtin,no-member,attribute-defined-outside…
Dec 20, 2018
2741d1b
Merge branch 'master' into metatest_mapper
Dec 21, 2018
e7188fc
None is not callable
Dec 21, 2018
ef34713
Merge branch 'master' into metatest_mapper
1ucian0 Dec 23, 2018
b82bac4
shots
Dec 23, 2018
2239d0d
shots
Dec 23, 2018
8e57c99
Merge branch 'master' into metatest_mapper
1ucian0 Dec 23, 2018
59ad155
Merge branch 'master' into metatest_mapper
1ucian0 Dec 24, 2018
97755b7
quotes
Dec 24, 2018
4c9c798
count -> counts
Dec 24, 2018
4386fd0
Merge branch 'metatest_mapper' of github.com:1ucian0/qiskit-terra int…
Dec 24, 2018
b28c806
basicmapper -> basicswap
Dec 24, 2018
6ea64e6
new pickles
Dec 24, 2018
0f1f154
result -> transpiled_result
Dec 24, 2018
311773a
test_initial_layout
Dec 24, 2018
f49b824
test_initial_layout pickles
Dec 24, 2018
0f14aa5
Merge branch 'master' into metatest_mapper
1ucian0 Dec 24, 2018
89e1938
style
Dec 24, 2018
7aee530
Merge branch 'metatest_mapper' of github.com:1ucian0/qiskit-terra int…
Dec 24, 2018
afe73e9
Merge branch 'master' into metatest_mapper
1ucian0 Dec 25, 2018
62cac69
new pickles
Dec 25, 2018
24135e2
Merge branch 'master' into metatest_mapper
1ucian0 Dec 26, 2018
f4af4d5
Merge branch 'master' into metatest_mapper
diego-plan9 Dec 27, 2018
5052302
Merge branch 'master' of github.com:Qiskit/qiskit-terra into metatest…
Dec 27, 2018
ef6b599
Merge branch 'metatest_mapper' of github.com:1ucian0/qiskit-terra int…
Dec 27, 2018
b97d850
triming spaces
Dec 27, 2018
e322df6
more docs!
Dec 27, 2018
7a06db7
doc self.shots
Dec 27, 2018
e37c033
Mixin added to the name
Dec 27, 2018
e3f408b
doc
Dec 27, 2018
8543327
doc
Dec 27, 2018
535155f
Merge branch 'metatest_mapper' of github.com:1ucian0/qiskit-terra int…
Dec 27, 2018
1d3f6cd
default pass_class
Dec 27, 2018
a56d141
Merge branch 'master' of github.com:Qiskit/qiskit-terra into metatest…
Dec 28, 2018
a4b697e
Merge branch 'master' into metatest_mapper
ajavadia Dec 28, 2018
8ae54eb
Merge branch 'master' into metatest_mapper
ajavadia Dec 28, 2018
75da141
Update docstrings
diego-plan9 Dec 29, 2018
a87550d
Merge branch 'master' into metatest_mapper
1ucian0 Dec 30, 2018
337af76
None is not callable
Dec 30, 2018
a39d082
QiskitTestCase moved
Dec 30, 2018
2daf8ff
Merge branch 'master' into metatest_mapper
1ucian0 Jan 29, 2019
569dda4
merge
Jan 29, 2019
ed734bc
Merge branch 'master' into metatest_mapper
1ucian0 Jan 31, 2019
cab76e1
Merge branch 'master' into metatest_mapper
1ucian0 Feb 1, 2019
ce59e5f
Merge branch 'master' into metatest_mapper
1ucian0 Feb 4, 2019
52e1b6d
Merge branch 'master' into metatest_mapper
1ucian0 Feb 4, 2019
76bea9a
Merge branch 'master' into metatest_mapper
1ucian0 Feb 5, 2019
4d44872
Merge branch 'master' into metatest_mapper
1ucian0 Feb 7, 2019
26535a6
Merge branch 'master' into metatest_mapper
1ucian0 Feb 11, 2019
a69acdd
regenerate parameter
Feb 11, 2019
962363a
docstring
Feb 11, 2019
9087f12
CommonTestCase -> CommonTestCases
Feb 11, 2019
bc5cdbb
CommonTestCases -> SwapperCommonTestCases
Feb 11, 2019
5dca49d
generate_ground_truth
Feb 11, 2019
d2eb393
lint
Feb 11, 2019
ce1b3a2
Merge branch 'master' into metatest_mapper
1ucian0 Feb 12, 2019
69c6fc2
Merge branch 'master' of github.com:Qiskit/qiskit-terra into metatest…
Feb 13, 2019
0b82ad0
Merge branch 'metatest_mapper' of https://github.com/1ucian0/qiskit-t…
Feb 13, 2019
6c7223c
assertResult takes circuit
Feb 13, 2019
d89b599
Merge branch 'master' into metatest_mapper
1ucian0 Feb 14, 2019
41386b2
Merge branch 'master' into metatest_mapper
1ucian0 Feb 14, 2019
7068919
Merge branch 'master' into metatest_mapper
1ucian0 Feb 14, 2019
d94ce61
Merge branch 'master' of github.com:Qiskit/qiskit-terra into metatest…
Feb 15, 2019
2263a5a
docstring
Feb 15, 2019
eae9672
comma
Feb 15, 2019
bffbe53
docstring
Feb 15, 2019
c6ca567
Merge branch 'master' of github.com:Qiskit/qiskit-terra into metatest…
Feb 18, 2019
0d65f25
remove all pickles before regenerating
Feb 18, 2019
248267b
lint
Feb 18, 2019
fa408a5
Merge branch 'master' into metatest_mapper
1ucian0 Feb 19, 2019
47860da
Merge branch 'master' into metatest_mapper
1ucian0 Feb 19, 2019
9bb634a
Merge branch 'master' into metatest_mapper
1ucian0 Feb 19, 2019
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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
291 changes: 291 additions & 0 deletions test/python/transpiler/test_mappers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
# -*- coding: utf-8 -*-

# Copyright 2018, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

"""Meta tests for mappers.
The test checks the output of the swapper to a ground truth DAG (one for each
test/swapper) saved in as a pickle (in `test/python/pickles/`). If they need
to be regenerated, the DAG candidate is compiled and run in a simulator and
the count is checked before being saved. This happens with (in the root
directory):
> python -m test.python.transpiler.test_mappers regenerate
To make a new swapper pass throw all the common tests, create a new class inside the file
`path/to/test_mappers.py` that:
* the class name should start with `Tests...`.
* inheriting from ``SwapperCommonTestCases, QiskitTestCase``
* overwrite the required attribute ``pass_class``
For example::
class TestsSomeSwap(SwapperCommonTestCases, QiskitTestCase):
pass_class = SomeSwap # The pass class
additional_args = {'seed': 42} # In case SomeSwap.__init__ requires
# additional arguments
To **add a test for all the swappers**, add a new method ``test_foo``to the
``SwapperCommonTestCases`` class:
* defining the following required ``self`` attributes: ``self.count``,
``self.shots``, ``self.delta``. They are required for the regeneration of the
ground truth.
* use the ``self.assertResult`` assertion for comparing for regeneration of the
ground truth.
* explicitly set a unique ``name`` of the ``QuantumCircuit``, as it it used
for the name of the pickle file of the ground truth.
For example::
def test_a_common_test(self):
self.count = {'000': 512, '110': 512} # The expected count for this circuit
self.shots = 1024 # Shots to run in the backend.
self.delta = 5 # This is delta for the AlmostEqual during
# the count check
coupling_map = [[0, 1], [0, 2]] # The coupling map for this specific test
qr = QuantumRegister(3, 'q') #
cr = ClassicalRegister(3, 'c') # Set the circuit to test
circuit = QuantumCircuit(qr, cr, # and don't forget to put a name
name='some_name') # (it will be used to save the pickle
circuit.h(qr[1]) #
circuit.cx(qr[1], qr[2]) #
circuit.measure(qr, cr) #
result = transpile(circuit, self.create_backend(), coupling_map=coupling_map,
pass_manager=self.create_passmanager(coupling_map))
self.assertResult(result, circuit)
```
"""

# pylint: disable=redefined-builtin,attribute-defined-outside-init

import unittest
import pickle
import sys
import os

from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit, BasicAer, compile
from qiskit.transpiler import PassManager, transpile
from qiskit.transpiler.passes import BasicSwap, LookaheadSwap, StochasticSwap
from qiskit.mapper import CouplingMap, Layout

from qiskit.test import QiskitTestCase

DIRNAME = QiskitTestCase._get_resource_path('pickles')


class CommonUtilitiesMixin:
"""Utilities for meta testing.
Subclasses should redefine the ``pass_class`` argument, with a Swap Mapper
class.
Note: This class assumes that the subclass is also inheriting from
``QiskitTestCase``, and it uses ``QiskitTestCase`` methods directly.
"""

regenerate_expected = False
seed = 42
additional_args = {}
1ucian0 marked this conversation as resolved.
Show resolved Hide resolved
pass_class = None

def create_passmanager(self, coupling_map, initial_layout=None):
"""Returns a PassManager using self.pass_class(coupling_map, initial_layout)"""
passmanager = PassManager(
self.pass_class(CouplingMap(coupling_map), # pylint: disable=not-callable
**self.additional_args))
if initial_layout:
passmanager.property_set['layout'] = Layout(initial_layout)
return passmanager

def create_backend(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the create_backend and create_passmanager methods are just one line each, why not just inline them?

Copy link
Member Author

@1ucian0 1ucian0 Dec 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because they are lines that need to be repeated in every test. I prefer to keep the test as clean as possible and encapsulate the complications that are not relevant to the result. Additionally, if we change the backend, this allows to change it in a single place.

"""Returns a Backend."""
return BasicAer.get_backend('qasm_simulator')

def generate_ground_truth(self, transpiled_result, filename):
"""Generates the expected result into a file.
Checks if transpiled_result matches self.counts by running in a backend
(self.create_backend()). That's saved in a pickle in filename.
Args:
transpiled_result (DAGCircuit): The DAGCircuit to compile and run.
filename (string): Where the pickle is saved.
"""
sim_backend = self.create_backend()
qobj = compile(transpiled_result, sim_backend, seed=self.seed, shots=self.shots)
job = sim_backend.run(qobj)
self.assertDictAlmostEqual(self.counts, job.result().get_counts(), delta=self.delta)

with open(filename, "wb") as output_file:
pickle.dump(transpiled_result, output_file)

def assertResult(self, result, circuit):
"""Fetches the pickle in circuit.name file and compares it with result."""
picklename = '%s_%s.pickle' % (type(self).__name__, circuit.name)
filename = os.path.join(DIRNAME, picklename)

if self.regenerate_expected:
# Run result in backend to test that is valid.
self.generate_ground_truth(result, filename)

with open(filename, "rb") as input_file:
expected = pickle.load(input_file)

self.assertEqual(result, expected)


class SwapperCommonTestCases(CommonUtilitiesMixin):
"""Tests that are run in several mappers.
The tests here will be run in several mappers. When adding a test, please
ensure that the test:
* defines ``self.count``, ``self.shots``, ``self.delta``.
* uses the ``self.assertResult`` assertion for comparing for regeneration of
the ground truth.
* explicitly sets a unique ``name`` of the ``QuantumCircuit``.
See also ``CommonUtilitiesMixin`` and the module docstring.
"""

def test_a_cx_to_map(self):
"""A single CX needs to be remapped.
q0:----------m-----
|
q1:-[H]-(+)--|-m---
| | |
q2:------.---|-|-m-
| | |
c0:----------.-|-|-
c1:------------.-|-
c2:--------------.-
CouplingMap map: [1]<-[0]->[2]
expected count: '000': 50%
'110': 50%
"""
self.counts = {'000': 512, '110': 512}
self.shots = 1024
self.delta = 5
coupling_map = [[0, 1], [0, 2]]

qr = QuantumRegister(3, 'q')
cr = ClassicalRegister(3, 'c')
circuit = QuantumCircuit(qr, cr, name='a_cx_to_map')
circuit.h(qr[1])
circuit.cx(qr[1], qr[2])
circuit.measure(qr, cr)

result = transpile(circuit, self.create_backend(), coupling_map=coupling_map,
pass_manager=self.create_passmanager(coupling_map))
self.assertResult(result, circuit)

def test_initial_layout(self):
"""Using a non-trivial initial_layout.
q3:----------------m--
q0:----------m-----|--
| |
q1:-[H]-(+)--|-m---|--
| | | |
q2:------.---|-|-m-|--
| | | |
c0:----------.-|-|-|--
c1:------------.-|-|--
c2:--------------.-|--
c3:----------------.--
CouplingMap map: [1]<-[0]->[2]->[3]
expected count: '000': 50%
'110': 50%
"""
self.counts = {'0000': 512, '0110': 512}
self.shots = 1024
self.delta = 5
coupling_map = [[0, 1], [0, 2], [2, 3]]

qr = QuantumRegister(4, 'q')
cr = ClassicalRegister(4, 'c')
circuit = QuantumCircuit(qr, cr, name='initial_layout')
circuit.h(qr[1])
circuit.cx(qr[1], qr[2])
circuit.measure(qr, cr)

layout = [qr[3], qr[0], qr[1], qr[2]]

result = transpile(circuit, self.create_backend(), coupling_map=coupling_map,
pass_manager=self.create_passmanager(coupling_map, layout))
self.assertResult(result, circuit)

def test_handle_measurement(self):
"""Handle measurement correctly.
q0:--.-----(+)-m-------
| | |
q1:-(+)-(+)-|--|-m-----
| | | |
q2:------|--|--|-|-m---
| | | | |
q3:-[H]--.--.--|-|-|-m-
| | | |
c0:------------.-|-|-|-
c1:--------------.-|-|-
c2:----------------.-|-
c3:------------------.-
CouplingMap map: [0]->[1]->[2]->[3]
expected count: '0000': 50%
'1011': 50%
"""
self.counts = {'1011': 512, '0000': 512}
self.shots = 1024
self.delta = 5
coupling_map = [[0, 1], [1, 2], [2, 3]]

qr = QuantumRegister(4, 'q')
cr = ClassicalRegister(4, 'c')
circuit = QuantumCircuit(qr, cr, name='handle_measurement')
circuit.h(qr[3])
circuit.cx(qr[0], qr[1])
circuit.cx(qr[3], qr[1])
circuit.cx(qr[3], qr[0])
circuit.measure(qr, cr)

result = transpile(circuit, self.create_backend(), coupling_map=coupling_map,
pass_manager=self.create_passmanager(coupling_map))
self.assertResult(result, circuit)


class TestsBasicSwap(SwapperCommonTestCases, QiskitTestCase):
"""Test SwapperCommonTestCases using BasicSwap."""
pass_class = BasicSwap


class TestsLookaheadSwap(SwapperCommonTestCases, QiskitTestCase):
"""Test SwapperCommonTestCases using LookaheadSwap."""
pass_class = LookaheadSwap


class TestsStochasticSwap(SwapperCommonTestCases, QiskitTestCase):
"""Test SwapperCommonTestCases using StochasticSwap."""
pass_class = StochasticSwap
additional_args = {'seed': 0}


if __name__ == '__main__':
if len(sys.argv) >= 2 and sys.argv[1] == 'regenerate':
CommonUtilitiesMixin.regenerate_expected = True

for picklefilename in os.listdir(DIRNAME):
os.remove(os.path.join(DIRNAME, picklefilename))

del sys.argv[1]
unittest.main()