Skip to content

Commit

Permalink
Make all Layout and Routing passes target aware (#9263)
Browse files Browse the repository at this point in the history
* Make all Layout and Routing passes target aware

This commit updates all the layout and routing passes which leverage
hardware constraints to have a new optional keyword argument, target,
to specify a Target object for modelling the constraints of the target
backend. This option will superscede any other specified arguments for
backend constraints. For most of these passes the target argument is
just internally leveraged to build a CouplingMap (and in 2 cases a
BackendProperties) as most of the algorithms don't need more than the
global connectivity graph and there is minimal overhead to convert
from a target to a CouplingMap. The 2 passes which take backend properties
should be refactored to work natively with the target because the
conversion adds extra overhead which isn't needed, but in those cases
the passes aren't enabled by default (or are slated to be moved out of
tree as a plugin, see #8662) so the extra overhead isn't of particular
concern.

This is part of the first step towards making all backend constraints
for the transpiler used the Target internally (see #9256). Once all
passes that take hardware constraints parameters as an input are updated
to have a target we can start internally using the Target only for all
preset pass manager construction and start the long process of
deprecating the legacy interface in these passes.

Related to #9256

* Update pass manager drawer reference dot files

The pass manager drawer tests were comparing the dot output for statically
built pass managers to test that the output visualization is generated
correctly. However, the new arguments for the taget are changing the
visualization (to show the new option) and caused these tests to fail.
This commit updates the reference images to include the new argument.

* Fix lint

* Optimize CheckMap

This commit optimizes the logic of the CheckMap pass. Previously we were
unecessarily creating a distance matrix from the coupling graph, and
with a target unecessarily creating a CouplingMap object. Instead of
doing this instead the pass now creates a list of bidirectional edges
for adjacent nodes in the coupling graph and then a set lookup is done
for each check instead of checking for a distance of 1 in the distance
matrix.

* Fix test failures

* Update qiskit/transpiler/passes/layout/full_ancilla_allocation.py

* Add test coverage for passes not run in preset pass managers

* Fix lint

* Fix reference dot for new preset pass managers

* Rework arguments to take CouplingMap or Target

This commit updates the pass constructors to use a single positional
argument for both the coupling map or a target. This replaces the
previous behavior where a new keyword argument was added to pass in the
target. The exception to this is passes where the target was used
differently than the coupling map for extra functionality.

* Expand test coverage

* Small typo fixes from review

* Fix lint and test failure

* Update qiskit/transpiler/preset_passmanagers/level0.py

Co-authored-by: Ali Javadi-Abhari <ajavadia@users.noreply.github.com>

* Update logic to favor target in all optimization levels

In an earlier commit we updated the logic in optimization level 0 to
favor target instead of a coupling map. But, that ignored the other
optimization levels. This commit corrects this oversight and adds the
same logically change to all the preset pass managers.

* Update release note for api changes in earlier iterations

* Update docstrings

* Use a single positional argument for embed pm function

---------

Co-authored-by: Ali Javadi-Abhari <ajavadia@users.noreply.github.com>
  • Loading branch information
mtreinish and ajavadia committed Apr 6, 2023
1 parent fe2f0f2 commit 5672c70
Show file tree
Hide file tree
Showing 34 changed files with 660 additions and 125 deletions.
18 changes: 15 additions & 3 deletions qiskit/transpiler/passes/layout/csp_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,20 @@
from qiskit.transpiler.basepasses import AnalysisPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.utils import optionals as _optionals
from qiskit.transpiler.target import Target


@_optionals.HAS_CONSTRAINT.require_in_instance
class CSPLayout(AnalysisPass):
"""If possible, chooses a Layout as a CSP, using backtracking."""

def __init__(
self, coupling_map, strict_direction=False, seed=None, call_limit=1000, time_limit=10
self,
coupling_map,
strict_direction=False,
seed=None,
call_limit=1000,
time_limit=10,
):
"""If possible, chooses a Layout as a CSP, using backtracking.
Expand All @@ -42,7 +48,7 @@ def __init__(
* time limit reached: If no perfect layout was found and the time limit was reached.
Args:
coupling_map (Coupling): Directed graph representing a coupling map.
coupling_map (Union[CouplingMap, Target]): Directed graph representing a coupling map.
strict_direction (bool): If True, considers the direction of the coupling map.
Default is False.
seed (int): Sets the seed of the PRNG.
Expand All @@ -53,7 +59,13 @@ def __init__(
None means no time limit. Default: 10 seconds.
"""
super().__init__()
self.coupling_map = coupling_map
if isinstance(coupling_map, Target):
self.target = coupling_map
self.coupling_map = self.target.build_coupling_map()
else:
self.target = None
self.coupling_map = coupling_map

self.strict_direction = strict_direction
self.call_limit = call_limit
self.time_limit = time_limit
Expand Down
10 changes: 8 additions & 2 deletions qiskit/transpiler/passes/layout/full_ancilla_allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from qiskit.circuit import QuantumRegister
from qiskit.transpiler.basepasses import AnalysisPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.target import Target


class FullAncillaAllocation(AnalysisPass):
Expand All @@ -35,10 +36,15 @@ def __init__(self, coupling_map):
"""FullAncillaAllocation initializer.
Args:
coupling_map (Coupling): directed graph representing a coupling map.
coupling_map (Union[CouplingMap, Target]): directed graph representing a coupling map.
"""
super().__init__()
self.coupling_map = coupling_map
if isinstance(coupling_map, Target):
self.target = coupling_map
self.coupling_map = self.target.build_coupling_map()
else:
self.target = None
self.coupling_map = coupling_map
self.ancilla_name = "ancilla"

def run(self, dag):
Expand Down
10 changes: 8 additions & 2 deletions qiskit/transpiler/passes/layout/layout_2q_distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"""

from qiskit.transpiler.basepasses import AnalysisPass
from qiskit.transpiler.target import Target


class Layout2qDistance(AnalysisPass):
Expand All @@ -34,11 +35,16 @@ def __init__(self, coupling_map, property_name="layout_score"):
"""Layout2qDistance initializer.
Args:
coupling_map (CouplingMap): Directed graph represented a coupling map.
coupling_map (Union[CouplingMap, Target]): Directed graph represented a coupling map.
property_name (str): The property name to save the score. Default: layout_score
"""
super().__init__()
self.coupling_map = coupling_map
if isinstance(coupling_map, Target):
self.target = coupling_map
self.coupling_map = self.target.build_coupling_map()
else:
self.target = None
self.coupling_map = coupling_map
self.property_name = property_name

def run(self, dag):
Expand Down
10 changes: 8 additions & 2 deletions qiskit/transpiler/passes/layout/noise_adaptive_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from qiskit.transpiler.layout import Layout
from qiskit.transpiler.basepasses import AnalysisPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.target import target_to_backend_properties, Target


class NoiseAdaptiveLayout(AnalysisPass):
Expand Down Expand Up @@ -58,13 +59,18 @@ def __init__(self, backend_prop):
"""NoiseAdaptiveLayout initializer.
Args:
backend_prop (BackendProperties): backend properties object
backend_prop (Union[BackendProperties, Target]): backend properties object
Raises:
TranspilerError: if invalid options
"""
super().__init__()
self.backend_prop = backend_prop
if isinstance(backend_prop, Target):
self.target = backend_prop
self.backend_prop = target_to_backend_properties(self.target)
else:
self.target = None
self.backend_prop = backend_prop
self.swap_graph = rx.PyDiGraph()
self.cx_reliability = {}
self.readout_reliability = {}
Expand Down
27 changes: 16 additions & 11 deletions qiskit/transpiler/passes/layout/sabre_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
NeighborTable,
)
from qiskit.transpiler.passes.routing.sabre_swap import process_swaps, apply_gate
from qiskit.transpiler.target import Target
from qiskit.tools.parallel import CPU_COUNT

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -86,7 +87,7 @@ def __init__(
"""SabreLayout initializer.
Args:
coupling_map (Coupling): directed graph representing a coupling map.
coupling_map (Union[CouplingMap, Target]): directed graph representing a coupling map.
routing_pass (BasePass): the routing pass to use while iterating.
If specified this pass operates as an :class:`~.AnalysisPass` and
will only populate the ``layout`` field in the property set and
Expand Down Expand Up @@ -124,17 +125,13 @@ def __init__(
both ``routing_pass`` and ``layout_trials`` are specified
"""
super().__init__()
self.coupling_map = coupling_map
if isinstance(coupling_map, Target):
self.target = coupling_map
self.coupling_map = self.target.build_coupling_map()
else:
self.target = None
self.coupling_map = coupling_map
self._neighbor_table = None
if self.coupling_map is not None:
if not self.coupling_map.is_symmetric:
# deepcopy is needed here to avoid modifications updating
# shared references in passes which require directional
# constraints
self.coupling_map = copy.deepcopy(self.coupling_map)
self.coupling_map.make_symmetric()
self._neighbor_table = NeighborTable(rx.adjacency_matrix(self.coupling_map.graph))

if routing_pass is not None and (swap_trials is not None or layout_trials is not None):
raise TranspilerError("Both routing_pass and swap_trials can't be set at the same time")
self.routing_pass = routing_pass
Expand All @@ -150,6 +147,14 @@ def __init__(
else:
self.layout_trials = layout_trials
self.skip_routing = skip_routing
if self.coupling_map is not None:
if not self.coupling_map.is_symmetric:
# deepcopy is needed here to avoid modifications updating
# shared references in passes which require directional
# constraints
self.coupling_map = copy.deepcopy(self.coupling_map)
self.coupling_map.make_symmetric()
self._neighbor_table = NeighborTable(rx.adjacency_matrix(self.coupling_map.graph))

def run(self, dag):
"""Run the SabreLayout pass on `dag`.
Expand Down
19 changes: 14 additions & 5 deletions qiskit/transpiler/passes/layout/trivial_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from qiskit.transpiler.layout import Layout
from qiskit.transpiler.basepasses import AnalysisPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.target import Target


class TrivialLayout(AnalysisPass):
Expand All @@ -33,13 +34,18 @@ def __init__(self, coupling_map):
"""TrivialLayout initializer.
Args:
coupling_map (Coupling): directed graph representing a coupling map.
coupling_map (Union[CouplingMap, Target]): directed graph representing a coupling map.
Raises:
TranspilerError: if invalid options
"""
super().__init__()
self.coupling_map = coupling_map
if isinstance(coupling_map, Target):
self.target = coupling_map
self.coupling_map = self.target.build_coupling_map()
else:
self.target = None
self.coupling_map = coupling_map

def run(self, dag):
"""Run the TrivialLayout pass on `dag`.
Expand All @@ -48,15 +54,18 @@ def run(self, dag):
dag (DAGCircuit): DAG to find layout for.
Raises:
TranspilerError: if dag wider than self.coupling_map
TranspilerError: if dag wider than the target backend
"""
if self.target is not None:
if dag.num_qubits() > self.target.num_qubits:
raise TranspilerError("Number of qubits greater than device.")
elif dag.num_qubits() > self.coupling_map.size():
raise TranspilerError("Number of qubits greater than device.")
if not self.coupling_map.is_connected():
raise TranspilerError(
"Coupling Map is disjoint, this pass can't be used with a disconnected coupling "
"map."
)
if dag.num_qubits() > self.coupling_map.size():
raise TranspilerError("Number of qubits greater than device.")
self.property_set["layout"] = Layout.generate_trivial_layout(
*(dag.qubits + list(dag.qregs.values()))
)
10 changes: 8 additions & 2 deletions qiskit/transpiler/passes/routing/basic_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from qiskit.dagcircuit import DAGCircuit
from qiskit.transpiler.layout import Layout
from qiskit.circuit.library.standard_gates import SwapGate
from qiskit.transpiler.target import Target


class BasicSwap(TransformationPass):
Expand All @@ -31,12 +32,17 @@ def __init__(self, coupling_map, fake_run=False):
"""BasicSwap initializer.
Args:
coupling_map (CouplingMap): Directed graph represented a coupling map.
coupling_map (Union[CouplingMap, Target]): Directed graph represented a coupling map.
fake_run (bool): if true, it only pretend to do routing, i.e., no
swap is effectively added.
"""
super().__init__()
self.coupling_map = coupling_map
if isinstance(coupling_map, Target):
self.target = coupling_map
self.coupling_map = self.target.build_coupling_map()
else:
self.target = None
self.coupling_map = coupling_map
self.fake_run = fake_run

def run(self, dag):
Expand Down
19 changes: 14 additions & 5 deletions qiskit/transpiler/passes/routing/bip_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from qiskit.transpiler import TransformationPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.passes.routing.algorithms.bip_model import BIPMappingModel
from qiskit.transpiler.target import target_to_backend_properties, Target

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -77,7 +78,7 @@ def __init__(
"""BIPMapping initializer.
Args:
coupling_map (CouplingMap): Directed graph represented a coupling map.
coupling_map (Union[CouplingMap, Target]): Directed graph represented a coupling map.
qubit_subset (list[int]): Sublist of physical qubits to be used in the mapping.
If None, all qubits in the coupling_map will be considered.
objective (str): Type of objective function to be minimized:
Expand Down Expand Up @@ -112,17 +113,25 @@ def __init__(
TranspilerError: if invalid options are specified.
"""
super().__init__()
self.coupling_map = coupling_map
if isinstance(coupling_map, Target):
self.target = coupling_map
self.coupling_map = self.target.build_coupling_map()
self.backend_prop = target_to_backend_properties(self.target)
else:
self.target = None
self.coupling_map = coupling_map
self.backend_prop = None
self.qubit_subset = qubit_subset
if self.coupling_map is not None and self.qubit_subset is None:
self.qubit_subset = list(range(self.coupling_map.size()))
self.objective = objective
self.backend_prop = backend_prop
if backend_prop is not None:
self.backend_prop = backend_prop
self.time_limit = time_limit
self.threads = threads
self.max_swaps_inbetween_layers = max_swaps_inbetween_layers
self.depth_obj_weight = depth_obj_weight
self.default_cx_error_rate = default_cx_error_rate
if self.coupling_map is not None and self.qubit_subset is None:
self.qubit_subset = list(range(self.coupling_map.size()))

def run(self, dag):
"""Run the BIPMapping pass on `dag`, assuming the number of virtual qubits (defined in
Expand Down
16 changes: 10 additions & 6 deletions qiskit/transpiler/passes/routing/layout_transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.passes.routing.algorithms import ApproximateTokenSwapper
from qiskit.transpiler.target import Target


class LayoutTransformation(TransformationPass):
Expand All @@ -30,7 +31,7 @@ class LayoutTransformation(TransformationPass):

def __init__(
self,
coupling_map: CouplingMap,
coupling_map: Union[CouplingMap, Target, None],
from_layout: Union[Layout, str],
to_layout: Union[Layout, str],
seed: Union[int, np.random.default_rng] = None,
Expand All @@ -39,7 +40,7 @@ def __init__(
"""LayoutTransformation initializer.
Args:
coupling_map (CouplingMap):
coupling_map:
Directed graph representing a coupling map.
from_layout (Union[Layout, str]):
Expand All @@ -59,12 +60,15 @@ def __init__(
super().__init__()
self.from_layout = from_layout
self.to_layout = to_layout
if coupling_map:
self.coupling_map = coupling_map
graph = coupling_map.graph.to_undirected()
if isinstance(coupling_map, Target):
self.target = coupling_map
self.coupling_map = self.target.build_coupling_map()
else:
self.target = None
self.coupling_map = coupling_map
if self.coupling_map is None:
self.coupling_map = CouplingMap.from_full(len(to_layout))
graph = self.coupling_map.graph.to_undirected()
graph = self.coupling_map.graph.to_undirected()
self.token_swapper = ApproximateTokenSwapper(graph, seed)
self.trials = trials

Expand Down
10 changes: 8 additions & 2 deletions qiskit/transpiler/passes/routing/lookahead_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.layout import Layout
from qiskit.dagcircuit import DAGOpNode
from qiskit.transpiler.target import Target

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -85,15 +86,20 @@ def __init__(self, coupling_map, search_depth=4, search_width=4, fake_run=False)
"""LookaheadSwap initializer.
Args:
coupling_map (CouplingMap): CouplingMap of the target backend.
coupling_map (Union[CouplingMap, Target]): CouplingMap of the target backend.
search_depth (int): lookahead tree depth when ranking best SWAP options.
search_width (int): lookahead tree width when ranking best SWAP options.
fake_run (bool): if true, it only pretend to do routing, i.e., no
swap is effectively added.
"""

super().__init__()
self.coupling_map = coupling_map
if isinstance(coupling_map, Target):
self.target = coupling_map
self.coupling_map = self.target.build_coupling_map()
else:
self.target = None
self.coupling_map = coupling_map
self.search_depth = search_depth
self.search_width = search_width
self.fake_run = fake_run
Expand Down
Loading

0 comments on commit 5672c70

Please sign in to comment.