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

Integrate qiskit-toqm as an optional package for layout and routing. #7825

Merged
merged 15 commits into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ def transpile(
[qr[0], None, None, qr[1], None, qr[2]]

layout_method: Name of layout selection pass ('trivial', 'dense', 'noise_adaptive', 'sabre')
routing_method: Name of routing pass ('basic', 'lookahead', 'stochastic', 'sabre', 'none')
routing_method: Name of routing pass
('basic', 'lookahead', 'stochastic', 'sabre', 'toqm', 'none')
translation_method: Name of translation pass ('unroller', 'translator', 'synthesis')
scheduling_method: Name of scheduling pass.
* ``'as_soon_as_possible'``: Schedule instructions greedily, as early as possible
Expand Down
24 changes: 24 additions & 0 deletions qiskit/transpiler/preset_passmanagers/level0.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
from qiskit.transpiler.passes import ContainsInstruction

from qiskit.transpiler import TranspilerError
from qiskit.utils.optionals import HAS_TOQM


def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
Expand Down Expand Up @@ -137,6 +138,9 @@ def _choose_layout_condition(property_set):
def _swap_condition(property_set):
return not property_set["is_swap_mapped"]

def _swap_needs_basis(property_set):
return _swap_condition(property_set) and routing_method == "toqm"

_swap = [BarrierBeforeFinalMeasurements()]
if routing_method == "basic":
_swap += [BasicSwap(coupling_map)]
Expand All @@ -146,6 +150,25 @@ def _swap_condition(property_set):
_swap += [LookaheadSwap(coupling_map, search_depth=2, search_width=2)]
elif routing_method == "sabre":
_swap += [SabreSwap(coupling_map, heuristic="basic", seed=seed_transpiler)]
elif routing_method == "toqm":
HAS_TOQM.require_now("TOQM-based routing")
from qiskit_toqm import ToqmSwap, ToqmStrategyO0, latencies_from_target

if initial_layout:
raise TranspilerError("Initial layouts are not supported with TOQM-based routing.")

# Note: BarrierBeforeFinalMeasurements is skipped intentionally since ToqmSwap
# does not yet support barriers.
_swap = [
ToqmSwap(
coupling_map,
strategy=ToqmStrategyO0(
latencies_from_target(
coupling_map, instruction_durations, basis_gates, backend_properties
)
),
)
]
elif routing_method == "none":
_swap += [
Error(
Expand Down Expand Up @@ -223,6 +246,7 @@ def _direction_condition(property_set):
pm0.append(_choose_layout, condition=_choose_layout_condition)
pm0.append(_embed)
pm0.append(_swap_check)
pm0.append(_unroll, condition=_swap_needs_basis)
pm0.append(_swap, condition=_swap_condition)
pm0.append(_unroll)
if (coupling_map and not coupling_map.is_symmetric) or (
Expand Down
24 changes: 24 additions & 0 deletions qiskit/transpiler/preset_passmanagers/level1.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason

from qiskit.transpiler import TranspilerError
from qiskit.utils.optionals import HAS_TOQM


def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
Expand Down Expand Up @@ -194,6 +195,9 @@ def _vf2_match_not_found(property_set):
def _swap_condition(property_set):
return not property_set["is_swap_mapped"]

def _swap_needs_basis(property_set):
return _swap_condition(property_set) and routing_method == "toqm"

_swap = [BarrierBeforeFinalMeasurements()]
if routing_method == "basic":
_swap += [BasicSwap(coupling_map)]
Expand All @@ -203,6 +207,25 @@ def _swap_condition(property_set):
_swap += [LookaheadSwap(coupling_map, search_depth=4, search_width=4)]
elif routing_method == "sabre":
_swap += [SabreSwap(coupling_map, heuristic="lookahead", seed=seed_transpiler)]
elif routing_method == "toqm":
HAS_TOQM.require_now("TOQM-based routing")
from qiskit_toqm import ToqmSwap, ToqmStrategyO1, latencies_from_target

if initial_layout:
raise TranspilerError("Initial layouts are not supported with TOQM-based routing.")

# Note: BarrierBeforeFinalMeasurements is skipped intentionally since ToqmSwap
# does not yet support barriers.
_swap = [
ToqmSwap(
coupling_map,
strategy=ToqmStrategyO1(
latencies_from_target(
coupling_map, instruction_durations, basis_gates, backend_properties
)
),
)
]
elif routing_method == "none":
_swap += [
Error(
Expand Down Expand Up @@ -297,6 +320,7 @@ def _opt_control(property_set):
pm1.append(_improve_layout, condition=_vf2_match_not_found)
pm1.append(_embed)
pm1.append(_swap_check)
pm1.append(_unroll, condition=_swap_needs_basis)
pm1.append(_swap, condition=_swap_condition)
pm1.append(_unroll)
if (coupling_map and not coupling_map.is_symmetric) or (
Expand Down
24 changes: 24 additions & 0 deletions qiskit/transpiler/preset_passmanagers/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason

from qiskit.transpiler import TranspilerError
from qiskit.utils.optionals import HAS_TOQM


def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
Expand Down Expand Up @@ -178,6 +179,9 @@ def _vf2_match_not_found(property_set):
def _swap_condition(property_set):
return not property_set["is_swap_mapped"]

def _swap_needs_basis(property_set):
return _swap_condition(property_set) and routing_method == "toqm"

_swap = [BarrierBeforeFinalMeasurements()]
if routing_method == "basic":
_swap += [BasicSwap(coupling_map)]
Expand All @@ -187,6 +191,25 @@ def _swap_condition(property_set):
_swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=5)]
elif routing_method == "sabre":
_swap += [SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler)]
elif routing_method == "toqm":
HAS_TOQM.require_now("TOQM-based routing")
from qiskit_toqm import ToqmSwap, ToqmStrategyO2, latencies_from_target

if initial_layout:
raise TranspilerError("Initial layouts are not supported with TOQM-based routing.")

# Note: BarrierBeforeFinalMeasurements is skipped intentionally since ToqmSwap
# does not yet support barriers.
_swap = [
ToqmSwap(
coupling_map,
strategy=ToqmStrategyO2(
latencies_from_target(
coupling_map, instruction_durations, basis_gates, backend_properties
)
),
)
]
elif routing_method == "none":
_swap += [
Error(
Expand Down Expand Up @@ -284,6 +307,7 @@ def _opt_control(property_set):
pm2.append(_choose_layout_1, condition=_vf2_match_not_found)
pm2.append(_embed)
pm2.append(_swap_check)
pm2.append(_unroll, condition=_swap_needs_basis)
pm2.append(_swap, condition=_swap_condition)
pm2.append(_unroll)
if (coupling_map and not coupling_map.is_symmetric) or (
Expand Down
24 changes: 24 additions & 0 deletions qiskit/transpiler/preset_passmanagers/level3.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason

from qiskit.transpiler import TranspilerError
from qiskit.utils.optionals import HAS_TOQM


def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
Expand Down Expand Up @@ -180,6 +181,9 @@ def _vf2_match_not_found(property_set):
def _swap_condition(property_set):
return not property_set["is_swap_mapped"]

def _swap_needs_basis(property_set):
return _swap_condition(property_set) and routing_method == "toqm"

_swap = [BarrierBeforeFinalMeasurements()]
if routing_method == "basic":
_swap += [BasicSwap(coupling_map)]
Expand All @@ -189,6 +193,25 @@ def _swap_condition(property_set):
_swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=6)]
elif routing_method == "sabre":
_swap += [SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler)]
elif routing_method == "toqm":
HAS_TOQM.require_now("TOQM-based routing")
from qiskit_toqm import ToqmSwap, ToqmStrategyO3, latencies_from_target

if initial_layout:
raise TranspilerError("Initial layouts are not supported with TOQM-based routing.")

# Note: BarrierBeforeFinalMeasurements is skipped intentionally since ToqmSwap
# does not yet support barriers.
_swap = [
ToqmSwap(
coupling_map,
strategy=ToqmStrategyO3(
latencies_from_target(
coupling_map, instruction_durations, basis_gates, backend_properties
)
),
)
]
elif routing_method == "none":
_swap += [
Error(
Expand Down Expand Up @@ -295,6 +318,7 @@ def _opt_control(property_set):
pm3.append(_choose_layout_1, condition=_vf2_match_not_found)
pm3.append(_embed)
pm3.append(_swap_check)
pm3.append(_unroll, condition=_swap_needs_basis)
pm3.append(_swap, condition=_swap_condition)
pm3.append(_unroll)
if (coupling_map and not coupling_map.is_symmetric) or (
Expand Down
5 changes: 5 additions & 0 deletions qiskit/utils/optionals.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
- :mod:`Qiskit Ignis <qiskit.ignis>` provides tools for quantum hardware verification, noise
characterization, and error correction.

* - .. py:data:: HAS_TOQM
- `Qiskit TOQM <https://github.com/qiskit-toqm/qiskit-toqm>`__ provides transpiler passes
for the `Time-optimal Qubit mapping algorithm <https://doi.org/10.1145/3445814.3446706>`__.


External Python Libraries
-------------------------
Expand Down Expand Up @@ -206,6 +210,7 @@
name="Qiskit Ignis",
install="pip install qiskit-ignis",
)
HAS_TOQM = _LazyImportTester("qiskit_toqm", name="Qiskit TOQM", install="pip install qiskit-toqm")

HAS_CPLEX = _LazyImportTester(
"cplex",
Expand Down
10 changes: 10 additions & 0 deletions releasenotes/notes/qiskit-toqm-41bd0f3b6760df6f.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
features:
- |
Adds new layout and routing methods based on paper
`Time-optimal Qubit mapping` <https://dl.acm.org/doi/10.1145/3445814.3446706>`__
to :func:`~qiskit.compiler.transpile`. To use them, optional package
`Qiskit TOQM <https://github.com/qiskit-toqm/qiskit-toqm>`__ must be
installed. The ``layout_method`` and ``routing_method`` kwargs of
:func:`~qiskit.compiler.transpile` support an additional value, ``'toqm'``
which is used to enable layout and or routing via TOQM.
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ seaborn>=0.9.0
reno>=3.4.0
Sphinx>=3.0.0
qiskit-sphinx-theme>=1.6
qiskit-toqm>=0.0.1;platform_machine != 'aarch64' or platform_system != 'Linux'
sphinx-autodoc-typehints<1.14.0
jupyter-sphinx
sphinx-panels
Expand Down
51 changes: 50 additions & 1 deletion test/python/transpiler/test_preset_passmanagers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit.circuit import Qubit
from qiskit.compiler import transpile, assemble
from qiskit.transpiler import CouplingMap, Layout
from qiskit.transpiler import CouplingMap, Layout, TranspilerError
from qiskit.circuit.library import U2Gate, U3Gate
from qiskit.test import QiskitTestCase
from qiskit.test.mock import (
Expand All @@ -32,11 +32,15 @@
FakeRueschlikon,
FakeTokyo,
FakePoughkeepsie,
FakeWashington,
FakeLima,
)
from qiskit.converters import circuit_to_dag
from qiskit.circuit.library import GraphState
from qiskit.quantum_info import random_unitary

from qiskit.utils.optionals import HAS_TOQM


def emptycircuit():
"""Empty circuit"""
Expand Down Expand Up @@ -203,6 +207,51 @@ def test_alignment_constraints_called_with_delay_in_circuit(self, level):
mock.assert_called_once()


@ddt
@unittest.skipUnless(HAS_TOQM, "qiskit-toqm needs to be installed")
class TestToqmIntegration(QiskitTestCase):
"""Test transpiler with TOQM-based routing"""

@combine(
level=[0, 1, 2, 3],
layout_method=[None, "trivial", "dense", "noise_adaptive", "sabre"],
backend_vbits_pair=[(FakeWashington(), 10), (FakeLima(), 5)],
dsc="TOQM-based routing with '{layout_method}' layout"
+ "method on '{backend_vbits_pair[0]}' backend at level '{level}'",
name="TOQM_{layout_method}_{backend_vbits_pair[0]}_level{level}",
)
def test_basic_circuit(self, level, layout_method, backend_vbits_pair):
"""
Basic circuits transpile across all opt levels and layout
methods when using TOQM-based routing.
"""
backend, circuit_size = backend_vbits_pair
qr = QuantumRegister(circuit_size, "q")
qc = QuantumCircuit(qr)

# Generate a circuit that should need swaps.
for i in range(1, qr.size):
qc.cx(0, i)

result = transpile(
qc,
layout_method=layout_method,
routing_method="toqm",
backend=backend,
optimization_level=level,
seed_transpiler=4222022,
)

self.assertIsInstance(result, QuantumCircuit)

def test_initial_layout_is_rejected(self):
"""Initial layout is rejected when using TOQM-based routing"""
with self.assertRaisesRegex(
TranspilerError, "Initial layouts are not supported with TOQM-based routing."
):
transpile(QuantumCircuit(2), initial_layout=[1, 0], routing_method="toqm")


@ddt
class TestTranspileLevels(QiskitTestCase):
"""Test transpiler on fake backend"""
Expand Down