Skip to content

Commit

Permalink
Make all Layout and Routing passes target aware
Browse files Browse the repository at this point in the history
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 Qiskit#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 Qiskit#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 Qiskit#9256
  • Loading branch information
mtreinish committed Dec 7, 2022
1 parent 088f600 commit ed529f1
Show file tree
Hide file tree
Showing 20 changed files with 211 additions and 59 deletions.
14 changes: 13 additions & 1 deletion qiskit/transpiler/passes/layout/csp_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ 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=None,
strict_direction=False,
seed=None,
call_limit=1000,
time_limit=10,
target=None,
):
"""If possible, chooses a Layout as a CSP, using backtracking.
Expand All @@ -50,13 +56,19 @@ def __init__(
None means no call limit. Default: 1000.
time_limit (int): Amount of seconds that the pass will try to find a solution.
None means no time limit. Default: 10 seconds.
target (Target): A target representing the target backend, if both
``coupling_map`` and this are specified then this argument will take
precedence and ``coupling_map`` will be ignored.
"""
super().__init__()
self.coupling_map = coupling_map
self.strict_direction = strict_direction
self.call_limit = call_limit
self.time_limit = time_limit
self.seed = seed
self.target = target
if self.target is not None:
self.coupling_map = self.target.build_coupling_map()

def run(self, dag):
"""run the layout method"""
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 @@ -31,15 +31,21 @@ class FullAncillaAllocation(AnalysisPass):
circuit.
"""

def __init__(self, coupling_map):
"""FullAncillaAllocation initializer.
def __init__(self, coupling_map=None, target=None):
"""FullAncillaAllocation initializer.<F12>
Args:
coupling_map (Coupling): directed graph representing a coupling map.
target (Target): A target representing the target backend, if both
``coupling_map`` and this are specified then this argument will take
precedence and ``coupling_map`` will be ignored.
"""
super().__init__()
self.coupling_map = coupling_map
self.ancilla_name = "ancilla"
self.target = target
if self.target is not None:
self.coupling_map = self.target.build_coupling_map()

def run(self, dag):
"""Run the FullAncillaAllocation pass on `dag`.
Expand Down
9 changes: 8 additions & 1 deletion qiskit/transpiler/passes/layout/layout_2q_distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,23 @@ class Layout2qDistance(AnalysisPass):
No CX direction is considered.
"""

def __init__(self, coupling_map, property_name="layout_score"):
def __init__(self, coupling_map=None, property_name="layout_score", target=None):
"""Layout2qDistance initializer.
Args:
coupling_map (CouplingMap): Directed graph represented a coupling map.
property_name (str): The property name to save the score. Default: layout_score
target (Target): A target representing the target backend, if both
``coupling_map`` and this are specified then this argument will take
precedence and ``coupling_map`` will be ignored.
"""
super().__init__()
self.coupling_map = coupling_map
self.property_name = property_name
self.target = target
if self.target is not None:
self.coupling_map = self.target.build_coupling_map()

def run(self, dag):
"""
Expand Down
9 changes: 8 additions & 1 deletion 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


class NoiseAdaptiveLayout(AnalysisPass):
Expand Down Expand Up @@ -54,11 +55,14 @@ class NoiseAdaptiveLayout(AnalysisPass):
by being set in `property_set`.
"""

def __init__(self, backend_prop):
def __init__(self, backend_prop=None, target=None):
"""NoiseAdaptiveLayout initializer.
Args:
backend_prop (BackendProperties): backend properties object
target (Target): A target representing the target backend, if both
``backend_prop`` and this are specified then this argument will take
precedence and ``coupling_map`` will be ignored.
Raises:
TranspilerError: if invalid options
Expand All @@ -77,6 +81,9 @@ def __init__(self, backend_prop):
self.qarg_to_id = {}
self.pending_program_edges = []
self.prog2hw = {}
self.target = target
if self.target is not None:
self.backend_prop = target_to_backend_properties(self.target)

def _initialize_backend_prop(self):
"""Extract readout and CNOT errors and compute swap costs."""
Expand Down
14 changes: 13 additions & 1 deletion qiskit/transpiler/passes/layout/sabre_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ class SabreLayout(AnalysisPass):
"""

def __init__(
self, coupling_map, routing_pass=None, seed=None, max_iterations=3, swap_trials=None
self,
coupling_map,
routing_pass=None,
seed=None,
max_iterations=3,
swap_trials=None,
target=None,
):
"""SabreLayout initializer.
Expand All @@ -71,6 +77,9 @@ def __init__(
on the number of trials run. This option is mutually exclusive
with the ``routing_pass`` argument and an error will be raised
if both are used.
target (Target): A target representing the target backend, if both
``coupling_map`` and this are specified then this argument will take
precedence and ``coupling_map`` will be ignored.
Raises:
TranspilerError: If both ``routing_pass`` and ``swap_trials`` are
Expand All @@ -85,6 +94,9 @@ def __init__(
self.max_iterations = max_iterations
self.trials = swap_trials
self.swap_trials = swap_trials
self.target = target
if self.target is not None:
self.coupling_map = self.target.build_coupling_map()

def run(self, dag):
"""Run the SabreLayout pass on `dag`.
Expand Down
16 changes: 12 additions & 4 deletions qiskit/transpiler/passes/layout/trivial_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,21 @@ class TrivialLayout(AnalysisPass):
Does not assume any ancilla.
"""

def __init__(self, coupling_map):
def __init__(self, coupling_map=None, target=None):
"""TrivialLayout initializer.
Args:
coupling_map (Coupling): directed graph representing a coupling map.
target (Target): A target representing the target backend, if both
``coupling_map`` and this are specified then this argument will take
precedence and ``coupling_map`` will be ignored.
Raises:
TranspilerError: if invalid options
"""
super().__init__()
self.coupling_map = coupling_map
self.target = target

def run(self, dag):
"""Run the TrivialLayout pass on `dag`.
Expand All @@ -48,10 +52,14 @@ 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 dag.num_qubits() > self.coupling_map.size():
raise TranspilerError("Number of qubits greater than device.")
if self.target is not None:
if dag.num_qubits() > self.target.num_qubits:
raise TranspilerError("Number of qubits greater than device.")
else:
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()))
)
8 changes: 7 additions & 1 deletion qiskit/transpiler/passes/routing/basic_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,23 @@ class BasicSwap(TransformationPass):
one or more swaps in front to make it compatible.
"""

def __init__(self, coupling_map, fake_run=False):
def __init__(self, coupling_map=None, fake_run=False, target=None):
"""BasicSwap initializer.
Args:
coupling_map (CouplingMap): Directed graph represented a coupling map.
fake_run (bool): if true, it only pretend to do routing, i.e., no
swap is effectively added.
target (Target): A target representing the target backend, if both
``coupling_map`` and this are specified then this argument will take
precedence and ``coupling_map`` will be ignored.
"""
super().__init__()
self.coupling_map = coupling_map
self.fake_run = fake_run
self.target = target
if self.target is not None:
self.coupling_map = self.target.build_coupling_map()

def run(self, dag):
"""Run the BasicSwap pass on `dag`.
Expand Down
16 changes: 13 additions & 3 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

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -64,7 +65,7 @@ class BIPMapping(TransformationPass):

def __init__(
self,
coupling_map,
coupling_map=None,
qubit_subset=None,
objective="balanced",
backend_prop=None,
Expand All @@ -73,6 +74,7 @@ def __init__(
max_swaps_inbetween_layers=None,
depth_obj_weight=0.1,
default_cx_error_rate=5e-3,
target=None,
):
"""BIPMapping initializer.
Expand Down Expand Up @@ -106,6 +108,9 @@ def __init__(
default_cx_error_rate (float):
Default CX error rate to be used if backend_prop is not available.
target (Target): A target representing the target backend, if both
``coupling_map`` or ``backend_prop`` and this are specified then this argument will take
precedence and the other argument will be ignored.
Raises:
MissingOptionalLibraryError: if cplex or docplex are not installed.
Expand All @@ -114,15 +119,20 @@ def __init__(
super().__init__()
self.coupling_map = coupling_map
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
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
self.target = target
if self.target is not None:
self.coupling_map = self.target.build_coupling_map()
self.backend_prop = target_to_backend_properties(self.target)

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
15 changes: 10 additions & 5 deletions qiskit/transpiler/passes/routing/layout_transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def __init__(
to_layout: Union[Layout, str],
seed: Union[int, np.random.default_rng] = None,
trials=4,
target=None,
):
"""LayoutTransformation initializer.
Expand All @@ -55,16 +56,20 @@ def __init__(
trials (int):
How many randomized trials to perform, taking the best circuit as output.
target (Target): A target representing the target backend, if both
``coupling_map`` and this are specified then this argument will take
precedence and the other argument will be ignored.
"""
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()
else:
self.coupling_map = coupling_map
self.target = target
if self.target is not None:
self.coupling_map = target.build_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
8 changes: 7 additions & 1 deletion qiskit/transpiler/passes/routing/lookahead_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class LookaheadSwap(TransformationPass):
https://medium.com/qiskit/improving-a-quantum-compiler-48410d7a7084
"""

def __init__(self, coupling_map, search_depth=4, search_width=4, fake_run=False):
def __init__(self, coupling_map, search_depth=4, search_width=4, fake_run=False, target=None):
"""LookaheadSwap initializer.
Args:
Expand All @@ -90,13 +90,19 @@ def __init__(self, coupling_map, search_depth=4, search_width=4, fake_run=False)
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.
target (Target): A target representing the target backend, if both
``coupling_map`` and this are specified then this argument will take
precedence and the other argument will be ignored.
"""

super().__init__()
self.coupling_map = coupling_map
self.search_depth = search_depth
self.search_width = search_width
self.fake_run = fake_run
self.target = target
if self.target is not None:
self.coupling_map = self.target.build_coupling_map()

def run(self, dag):
"""Run the LookaheadSwap pass on `dag`.
Expand Down
17 changes: 12 additions & 5 deletions qiskit/transpiler/passes/routing/sabre_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ class SabreSwap(TransformationPass):
`arXiv:1809.02573 <https://arxiv.org/pdf/1809.02573.pdf>`_
"""

def __init__(self, coupling_map, heuristic="basic", seed=None, fake_run=False, trials=None):
def __init__(
self, coupling_map, heuristic="basic", seed=None, fake_run=False, trials=None, target=None
):
r"""SabreSwap initializer.
Args:
Expand All @@ -88,6 +90,9 @@ def __init__(self, coupling_map, heuristic="basic", seed=None, fake_run=False, t
CPUs on the local system. For reproducible results it is recommended
that you set this explicitly, as the output will be deterministic for
a fixed number of trials.
target (Target): A target representing the target backend, if both
``coupling_map`` and this are specified then this argument will take
precedence and the other argument will be ignored.
Raises:
TranspilerError: If the specified heuristic is not valid.
Expand Down Expand Up @@ -139,10 +144,12 @@ def __init__(self, coupling_map, heuristic="basic", seed=None, fake_run=False, t
super().__init__()

# Assume bidirectional couplings, fixing gate direction is easy later.
if coupling_map is None or coupling_map.is_symmetric:
self.coupling_map = coupling_map
else:
self.coupling_map = deepcopy(coupling_map)
self.coupling_map = coupling_map
self.target = target
if self.target is not None:
self.coupling_map = self.target.build_coupling_map()
if self.coupling_map is not None and not self.coupling_map.is_symmetric:
self.coupling_map = deepcopy(self.coupling_map)
self.coupling_map.make_symmetric()
self._neighbor_table = None
if coupling_map is not None:
Expand Down
Loading

0 comments on commit ed529f1

Please sign in to comment.