Skip to content

Commit

Permalink
Deprecates StochasticSwap and suggests the use of SabreSwap (#12983)
Browse files Browse the repository at this point in the history
* first attempt to fix issue 12552

* first attempt to fix issue 12552

* fixed issue 12552 and unittest

* formatted and completed 12552

* formatted and completed 12552 documentation

* fixed unit tests 12552

* Update qiskit/transpiler/passes/routing/stochastic_swap.py

Co-authored-by: Elena Peña Tapia <[email protected]>

* linted

* passed all tests, including compiler test

* final linting and unittest passing - hopefully

* Update qiskit/transpiler/passes/routing/stochastic_swap.py

Co-authored-by: Elena Peña Tapia <[email protected]>

* Update releasenotes/notes/deprecate-StochasticSwap-451f46b273602b7b.yaml

Co-authored-by: Elena Peña Tapia <[email protected]>

* added test

* Apply suggestions from code review

---------

Co-authored-by: Elena Peña Tapia <[email protected]>
  • Loading branch information
danielbultrini and ElePT authored Aug 20, 2024
1 parent a11e76c commit d430e58
Show file tree
Hide file tree
Showing 8 changed files with 469 additions and 147 deletions.
8 changes: 8 additions & 0 deletions qiskit/transpiler/passes/routing/stochastic_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from qiskit._accelerate import stochastic_swap as stochastic_swap_rs
from qiskit._accelerate import nlayout
from qiskit.transpiler.passes.layout import disjoint_utils
from qiskit.utils import deprecate_func

from .utils import get_swap_map_dag

Expand All @@ -59,6 +60,12 @@ class StochasticSwap(TransformationPass):
the circuit.
"""

@deprecate_func(
since="1.3",
removal_timeline="in the 2.0 release",
additional_msg="The `StochasticSwap` transpilation pass is a suboptimal "
"routing algorithm and has been superseded by the :class:`.SabreSwap` pass.",
)
def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_layout=None):
"""StochasticSwap initializer.
Expand All @@ -76,6 +83,7 @@ def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_l
initial_layout (Layout): starting layout at beginning of pass.
"""
super().__init__()

if isinstance(coupling_map, Target):
self.target = coupling_map
self.coupling_map = self.target.build_coupling_map()
Expand Down
46 changes: 46 additions & 0 deletions releasenotes/notes/deprecate-StochasticSwap-451f46b273602b7b.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
deprecations_transpiler:
- |
Deprecated ``StochasticSwap`` which has been superseded by :class:`.SabreSwap`.
If the class is called from the transpile function, the change would be, for example::
tqc = transpile(
circuit,
routing_method="stochastic",
layout_method="dense",
seed_transpiler=12342,
target=GenericBackendV2(
num_qubits=27,
coupling_map=MUMBAI_CMAP,
).target,
)
to::
tqc = transpile(
circuit,
routing_method="sabre",
layout_method="sabre",
seed_transpiler=12342,
target=GenericBackendV2(
num_qubits=27,
coupling_map=MUMBAI_CMAP,
).target,
)
While for a pass mananager change::
qr = QuantumRegister(4, "q")
qc = QuantumCircuit(qr)
passmanager = PassManager(StochasticSwap(coupling, 20, 13))
new_qc = passmanager.run(qc)
to::
qr = QuantumRegister(5, "q")
qc = QuantumCircuit(qr)
passmanager = PassManager(SabreSwap(target, "basic"))
new_qc = passmanager.run(qc)
203 changes: 197 additions & 6 deletions test/python/compiler/test_transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,10 @@ def test_move_measurements(self):
circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm"))

lay = [0, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6]
out = transpile(circ, initial_layout=lay, coupling_map=cmap, routing_method="stochastic")
with self.assertWarns(DeprecationWarning):
out = transpile(
circ, initial_layout=lay, coupling_map=cmap, routing_method="stochastic"
)
out_dag = circuit_to_dag(out)
meas_nodes = out_dag.named_nodes("measure")
for meas_node in meas_nodes:
Expand Down Expand Up @@ -3498,7 +3501,7 @@ def _visit_block(circuit, qubit_mapping=None):
)[0]
self.assertIn(qubit_map[op_node.qargs[0]], components[2])

@data("sabre", "stochastic", "basic", "lookahead")
@data("sabre", "basic", "lookahead")
def test_basic_connected_circuit_dense_layout(self, routing_method):
"""Test basic connected circuit on disjoint backend"""
qc = QuantumCircuit(5)
Expand All @@ -3522,8 +3525,34 @@ def test_basic_connected_circuit_dense_layout(self, routing_method):
continue
self.assertIn(qubits, self.backend.target[op_name])

@data("stochastic")
def test_basic_connected_circuit_dense_layout_stochastic(self, routing_method):
"""Test basic connected circuit on disjoint backend for deprecated stochastic swap"""
# TODO: Remove when StochasticSwap is removed
qc = QuantumCircuit(5)
qc.h(0)
qc.cx(0, 1)
qc.cx(0, 2)
qc.cx(0, 3)
qc.cx(0, 4)
qc.measure_all()
with self.assertWarns(DeprecationWarning):
tqc = transpile(
qc,
self.backend,
layout_method="dense",
routing_method=routing_method,
seed_transpiler=42,
)
for inst in tqc.data:
qubits = tuple(tqc.find_bit(x).index for x in inst.qubits)
op_name = inst.operation.name
if op_name == "barrier":
continue
self.assertIn(qubits, self.backend.target[op_name])

# Lookahead swap skipped for performance
@data("sabre", "stochastic", "basic")
@data("sabre", "basic")
def test_triple_circuit_dense_layout(self, routing_method):
"""Test a split circuit with one circuit component per chip."""
qc = QuantumCircuit(30)
Expand Down Expand Up @@ -3572,7 +3601,58 @@ def test_triple_circuit_dense_layout(self, routing_method):
continue
self.assertIn(qubits, self.backend.target[op_name])

@data("sabre", "stochastic", "basic", "lookahead")
@data("stochastic")
def test_triple_circuit_dense_layout_stochastic(self, routing_method):
"""Test a split circuit with one circuit component per chip for deprecated StochasticSwap."""
# TODO: Remove when StochasticSwap is removed
qc = QuantumCircuit(30)
qc.h(0)
qc.h(10)
qc.h(20)
qc.cx(0, 1)
qc.cx(0, 2)
qc.cx(0, 3)
qc.cx(0, 4)
qc.cx(0, 5)
qc.cx(0, 6)
qc.cx(0, 7)
qc.cx(0, 8)
qc.cx(0, 9)
qc.ecr(10, 11)
qc.ecr(10, 12)
qc.ecr(10, 13)
qc.ecr(10, 14)
qc.ecr(10, 15)
qc.ecr(10, 16)
qc.ecr(10, 17)
qc.ecr(10, 18)
qc.ecr(10, 19)
qc.cy(20, 21)
qc.cy(20, 22)
qc.cy(20, 23)
qc.cy(20, 24)
qc.cy(20, 25)
qc.cy(20, 26)
qc.cy(20, 27)
qc.cy(20, 28)
qc.cy(20, 29)
qc.measure_all()
with self.assertWarns(DeprecationWarning):
tqc = transpile(
qc,
self.backend,
layout_method="dense",
routing_method=routing_method,
seed_transpiler=42,
)
for inst in tqc.data:
qubits = tuple(tqc.find_bit(x).index for x in inst.qubits)
op_name = inst.operation.name
if op_name == "barrier":
continue
self.assertIn(qubits, self.backend.target[op_name])

@data("sabre", "basic", "lookahead")
def test_triple_circuit_invalid_layout(self, routing_method):
"""Test a split circuit with one circuit component per chip."""
qc = QuantumCircuit(30)
Expand Down Expand Up @@ -3616,8 +3696,54 @@ def test_triple_circuit_invalid_layout(self, routing_method):
seed_transpiler=42,
)

# Lookahead swap skipped for performance reasons
@data("sabre", "stochastic", "basic")
@data("stochastic")
def test_triple_circuit_invalid_layout_stochastic(self, routing_method):
"""Test a split circuit with one circuit component per chip for deprecated ``StochasticSwap``"""
# TODO: Remove when StochasticSwap is removed
qc = QuantumCircuit(30)
qc.h(0)
qc.h(10)
qc.h(20)
qc.cx(0, 1)
qc.cx(0, 2)
qc.cx(0, 3)
qc.cx(0, 4)
qc.cx(0, 5)
qc.cx(0, 6)
qc.cx(0, 7)
qc.cx(0, 8)
qc.cx(0, 9)
qc.ecr(10, 11)
qc.ecr(10, 12)
qc.ecr(10, 13)
qc.ecr(10, 14)
qc.ecr(10, 15)
qc.ecr(10, 16)
qc.ecr(10, 17)
qc.ecr(10, 18)
qc.ecr(10, 19)
qc.cy(20, 21)
qc.cy(20, 22)
qc.cy(20, 23)
qc.cy(20, 24)
qc.cy(20, 25)
qc.cy(20, 26)
qc.cy(20, 27)
qc.cy(20, 28)
qc.cy(20, 29)
qc.measure_all()
with self.assertWarns(DeprecationWarning):
with self.assertRaises(TranspilerError):
transpile(
qc,
self.backend,
layout_method="trivial",
routing_method=routing_method,
seed_transpiler=42,
)

# Lookahead swap skipped for performance reasons, stochastic moved to new test due to deprecation
@data("sabre", "basic")
def test_six_component_circuit_dense_layout(self, routing_method):
"""Test input circuit with more than 1 component per backend component."""
qc = QuantumCircuit(42)
Expand Down Expand Up @@ -3678,6 +3804,71 @@ def test_six_component_circuit_dense_layout(self, routing_method):
continue
self.assertIn(qubits, self.backend.target[op_name])

# Lookahead swap skipped for performance reasons
@data("stochastic")
def test_six_component_circuit_dense_layout_stochastic(self, routing_method):
"""Test input circuit with more than 1 component per backend component
for deprecated ``StochasticSwap``."""
# TODO: Remove when StochasticSwap is removed
qc = QuantumCircuit(42)
qc.h(0)
qc.h(10)
qc.h(20)
qc.cx(0, 1)
qc.cx(0, 2)
qc.cx(0, 3)
qc.cx(0, 4)
qc.cx(0, 5)
qc.cx(0, 6)
qc.cx(0, 7)
qc.cx(0, 8)
qc.cx(0, 9)
qc.ecr(10, 11)
qc.ecr(10, 12)
qc.ecr(10, 13)
qc.ecr(10, 14)
qc.ecr(10, 15)
qc.ecr(10, 16)
qc.ecr(10, 17)
qc.ecr(10, 18)
qc.ecr(10, 19)
qc.cy(20, 21)
qc.cy(20, 22)
qc.cy(20, 23)
qc.cy(20, 24)
qc.cy(20, 25)
qc.cy(20, 26)
qc.cy(20, 27)
qc.cy(20, 28)
qc.cy(20, 29)
qc.h(30)
qc.cx(30, 31)
qc.cx(30, 32)
qc.cx(30, 33)
qc.h(34)
qc.cx(34, 35)
qc.cx(34, 36)
qc.cx(34, 37)
qc.h(38)
qc.cx(38, 39)
qc.cx(39, 40)
qc.cx(39, 41)
qc.measure_all()
with self.assertWarns(DeprecationWarning):
tqc = transpile(
qc,
self.backend,
layout_method="dense",
routing_method=routing_method,
seed_transpiler=42,
)
for inst in tqc.data:
qubits = tuple(tqc.find_bit(x).index for x in inst.qubits)
op_name = inst.operation.name
if op_name == "barrier":
continue
self.assertIn(qubits, self.backend.target[op_name])

@data(0, 1, 2, 3)
def test_transpile_target_with_qubits_without_ops(self, opt_level):
"""Test qubits without operations aren't ever used."""
Expand Down
14 changes: 11 additions & 3 deletions test/python/transpiler/test_mappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,13 @@ def test_a_common_test(self):
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, StochasticSwap, SabreSwap
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
Expand Down Expand Up @@ -104,8 +105,15 @@ def create_passmanager(self, coupling_map, initial_layout=None):
if initial_layout:
passmanager.append(SetLayout(Layout(initial_layout)))

# pylint: disable=not-callable
passmanager.append(self.pass_class(CouplingMap(coupling_map), **self.additional_args))
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):
Expand Down
5 changes: 2 additions & 3 deletions test/python/transpiler/test_preset_passmanagers.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ def test_level1_runs_vf2post_layout_when_routing_required(self):
self.assertNotIn("SabreSwap", self.passes)

def test_level1_runs_vf2post_layout_when_routing_method_set_and_required(self):
"""Test that if we run routing as part of sabre layout VF2PostLayout runs."""
"""Test that if we run routing as part of sabre layout then VF2PostLayout runs."""
target = GenericBackendV2(num_qubits=7, coupling_map=LAGOS_CMAP, seed=42)
qc = QuantumCircuit(5)
qc.h(0)
Expand All @@ -507,7 +507,7 @@ def test_level1_runs_vf2post_layout_when_routing_method_set_and_required(self):
qc.cy(0, 4)
qc.measure_all()
_ = transpile(
qc, target, optimization_level=1, routing_method="stochastic", callback=self.callback
qc, target, optimization_level=1, routing_method="sabre", callback=self.callback
)
# Expected call path for layout and routing is:
# 1. TrivialLayout (no perfect match)
Expand All @@ -518,7 +518,6 @@ def test_level1_runs_vf2post_layout_when_routing_method_set_and_required(self):
self.assertIn("VF2Layout", self.passes)
self.assertIn("SabreLayout", self.passes)
self.assertIn("VF2PostLayout", self.passes)
self.assertIn("StochasticSwap", self.passes)

def test_level1_not_runs_vf2post_layout_when_layout_method_set(self):
"""Test that if we don't run VF2PostLayout with custom layout_method."""
Expand Down
Loading

0 comments on commit d430e58

Please sign in to comment.