mirror of https://github.com/Qiskit/qiskit.git
311 lines
11 KiB
Python
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()
|