qiskit/test/python/transpiler/test_mappers.py

311 lines
11 KiB
Python

# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2023.
#
# 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.
"""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 an OpenQASM 2 file (in `test/python/qasm/`). 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_transpiler': 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 QASM 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 QASM
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=attribute-defined-outside-init
import unittest
import os
import sys
import warnings
from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit, transpile
from qiskit.providers.basic_provider import BasicSimulator
from qiskit.qasm2 import dump
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import BasicSwap, LookaheadSwap, SabreSwap, StochasticSwap
from qiskit.transpiler.passes import SetLayout
from qiskit.transpiler import CouplingMap, Layout
from test import QiskitTestCase # pylint: disable=wrong-import-order
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_simulator = 42
seed_transpiler = 42
additional_args = {}
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()
if initial_layout:
passmanager.append(SetLayout(Layout(initial_layout)))
with warnings.catch_warnings():
# TODO: remove this filter when StochasticSwap is removed
warnings.filterwarnings(
"ignore",
category=DeprecationWarning,
message=r".*StochasticSwap.*",
)
# pylint: disable=not-callable
passmanager.append(self.pass_class(CouplingMap(coupling_map), **self.additional_args))
return passmanager
def create_backend(self):
"""Returns a Backend."""
return BasicSimulator()
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 QASM in filename.
Args:
transpiled_result (DAGCircuit): The DAGCircuit to execute.
filename (string): Where the QASM is saved.
"""
sim_backend = self.create_backend()
job = sim_backend.run(
transpile(transpiled_result, sim_backend, seed_transpiler=self.seed_transpiler),
seed_simulator=self.seed_simulator,
shots=self.shots,
)
self.assertDictAlmostEqual(self.counts, job.result().get_counts(), delta=self.delta)
dump(transpiled_result.qasm(formatted=False), filename)
def assertResult(self, result, circuit):
"""Fetches the QASM in circuit.name file and compares it with result."""
qasm_name = f"{type(self).__name__}_{circuit.name}.qasm"
qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm")
filename = os.path.join(qasm_dir, qasm_name)
if self.regenerate_expected:
# Run result in backend to test that is valid.
self.generate_ground_truth(result, filename)
expected = QuantumCircuit.from_qasm_file(filename)
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 = self.create_passmanager(coupling_map).run(circuit)
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]: 0, qr[0]: 1, qr[1]: 2, qr[2]: 3}
result = self.create_passmanager(coupling_map, layout).run(circuit)
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 = self.create_passmanager(coupling_map).run(circuit)
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}
class TestsSabreSwap(SwapperCommonTestCases, QiskitTestCase):
"""Test SwapperCommonTestCases using SabreSwap."""
pass_class = SabreSwap
additional_args = {"seed": 1242, "trials": 2}
if __name__ == "__main__":
if len(sys.argv) >= 2 and sys.argv[1] == "regenerate":
CommonUtilitiesMixin.regenerate_expected = True
del sys.argv[1]
unittest.main()